[2.0.x] fix: Fix error with "-language:postfixOps" (#9158)

Co-authored-by: kenji yoshida <6b656e6a69@gmail.com>
This commit is contained in:
eugene yokota 2026-04-30 01:13:13 -04:00 committed by GitHub
parent 5fe73c82fd
commit 06e53a0ae4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 61 additions and 12 deletions

View File

@ -29,7 +29,7 @@ import sbt.io.{ Hash, IO }
* - mkReporter - an optional factory method to create a reporter
*/
class Eval(
nonCpOptions: Seq[String],
private[internal] val nonCpOptions: Seq[String],
classpath: Seq[Path],
backingDir: Option[Path],
mkReporter: Option[() => EvalReporter]

View File

@ -93,10 +93,11 @@ private[sbt] object EvaluateConfigurations {
file: VirtualFileRef,
lines: Seq[String],
builtinImports: Seq[String],
offset: Int
offset: Int,
options: Seq[String]
): ParsedFile = {
def loseTree(l: (String, Tree, LineRange)): (String, LineRange) = (l._1, l._3)
val (importStatements, settingsAndDefinitions) = splitExpressions(file, lines)
val (importStatements, settingsAndDefinitions) = splitExpressions(file, lines, options)
val allImports = builtinImports.map(s => (s, -1)) ++ addOffset(offset, importStatements)
val (definitions, settings) = splitSettingsDefinitions(
addOffsetToRange(offset, settingsAndDefinitions)
@ -147,7 +148,7 @@ private[sbt] object EvaluateConfigurations {
val name = file match
case file: PathBasedFile => file.toPath.toString
case file => file.id
val parsed = parseConfiguration(file, lines, imports, offset)
val parsed = parseConfiguration(file, lines, imports, offset, eval.nonCpOptions)
val (importDefs, definitions) =
if (parsed.definitions.isEmpty) (Nil, DefinedSbtValues.empty)
else {
@ -277,15 +278,22 @@ private[sbt] object EvaluateConfigurations {
case _ => Nil
}
private[sbt] def splitExpressions(
file: VirtualFileRef,
lines: Seq[String],
): (Seq[(String, Int)], Seq[(String, Tree, LineRange)]) =
splitExpressions(file, lines, Nil)
/**
* Splits a set of lines into (imports, expressions). That is,
* anything on the right of the tuple is a scala expression (definition or setting).
*/
private[sbt] def splitExpressions(
file: VirtualFileRef,
lines: Seq[String]
lines: Seq[String],
options: Seq[String]
): (Seq[(String, Int)], Seq[(String, Tree, LineRange)]) =
val split = SbtParser(file, lines)
val split = SbtParser(file, lines, options)
// TODO - Look at pulling the parsed expression trees from the SbtParser and stitch them back into a different
// scala compiler rather than re-parsing.
(

View File

@ -27,6 +27,7 @@ import dotty.tools.dotc.reporting.Diagnostic
import dotty.tools.dotc.reporting.Reporter
import dotty.tools.dotc.reporting.StoreReporter
import scala.util.Random
import scala.collection.concurrent.TrieMap
import xsbti.VirtualFileRef
private[sbt] object SbtParser:
@ -50,6 +51,9 @@ private[sbt] object SbtParser:
private final val defaultClasspath =
sbt.io.Path.makeString(sbt.io.IO.classLocationPath(classOf[Product]).toFile :: Nil)
def apply(path: VirtualFileRef, lines: Seq[String]): SbtParser =
new SbtParser(path, lines)
def isIdentifier(ident: String): Boolean =
val code = s"val $ident = 0; val ${ident}${ident} = $ident"
try
@ -135,10 +139,21 @@ private[sbt] object SbtParser:
// Retry since Scala 3 compiler initialization can fail due to sys.props change
private[sbt] val defaultGlobalForParser: ParseDriver =
Retry(ParseDriver())
private[sbt] final class ParseDriver extends Driver:
private val parseDriverCache: TrieMap[Seq[String], ParseDriver] =
TrieMap.empty
private[sbt] def defaultGlobalForParserWithOption(options: Seq[String]): ParseDriver =
parseDriverCache.getOrElseUpdate(
options,
Retry(ParseDriver(options.toList))
)
private[sbt] final class ParseDriver(
options: List[String] = List("-classpath", s"$defaultClasspath")
) extends Driver:
override protected val sourcesRequired: Boolean = false
val compileCtx0 = initCtx.fresh
val options = List("-classpath", s"$defaultClasspath")
val compileCtx1 = setup(options.toArray, compileCtx0) match
case Some((_, ctx)) => ctx
case _ => sys.error(s"initialization failed for $options")
@ -219,8 +234,15 @@ end ParsedSbtFileExpressions
* @param path The path we're parsing (may be a dummy file)
* @param lines The parsed "lines" of the file, where each string is a line.
*/
private[sbt] case class SbtParser(path: VirtualFileRef, lines: Seq[String])
extends ParsedSbtFileExpressions:
private[sbt] case class SbtParser(
path: VirtualFileRef,
lines: Seq[String],
private val options: Seq[String]
) extends ParsedSbtFileExpressions:
def this(path: VirtualFileRef, lines: Seq[String]) =
this(path, lines, Nil)
// settingsTrees,modifiedContent needed for "session save"
// TODO - We should look into splitting out "definitions" vs. "settings" here instead of further string lookups, since we have the
// parsed trees.
@ -241,7 +263,12 @@ private[sbt] case class SbtParser(path: VirtualFileRef, lines: Seq[String])
VirtualFile(reporterId, wrapCode.getBytes(StandardCharsets.UTF_8)),
scala.io.Codec.UTF8
)
given Context = SbtParser.defaultGlobalForParser.compileCtx.fresh.setSource(sourceFile)
val ctx =
if options.isEmpty then SbtParser.defaultGlobalForParser
else SbtParser.defaultGlobalForParserWithOption(options)
given Context = ctx.compileCtx.fresh.setSource(sourceFile)
val parsedTrees = parse(fileName, reporterId)
// Check No val (a,b) = foo *or* val a,b = foo as these are problematic to range positions and the WHOLE architecture.

View File

@ -1,7 +1,7 @@
package sbt.internal
import sbt.internal.parser.SbtParser
import sbt.internal.util.LineRange
import sbt.internal.util.{ LineRange, MessageOnlyException }
import xsbti.VirtualFileRef
object SbtParserTest extends verify.BasicTestSuite:
@ -102,4 +102,15 @@ val x = 1
test("isIdentifier") {
assert(SbtParser.isIdentifier("1a") == false)
}
test("postfix") {
val ref = VirtualFileRef.of("vfile")
val src = "Nil head"
intercept[MessageOnlyException] {
SbtParser(ref, Seq(src))
}
val p = SbtParser(ref, Seq(src), Seq("-language:postfixOps"))
assert(p.imports.isEmpty)
assert(p.lines == Seq(src))
}
end SbtParserTest

View File

@ -0,0 +1 @@
def foo: Int = Seq(2) head

View File

@ -0,0 +1 @@
scalacOptions += "-language:postfixOps"

View File

@ -0,0 +1 @@
> name