Skip to content

Commit

Permalink
Merge pull request #10573 from som-snytt/issue/12883-source-3-X
Browse files Browse the repository at this point in the history
Distinguish between `-Xsource:3` (for preparing to switch to 3) and `-Xsource:3-cross` (for crossbuilding on 2 and 3)
  • Loading branch information
lrytz committed Jan 30, 2024
2 parents d0bcece + 3950943 commit 8e03f3b
Show file tree
Hide file tree
Showing 60 changed files with 684 additions and 240 deletions.
4 changes: 3 additions & 1 deletion src/compiler/scala/tools/nsc/Global.scala
Expand Up @@ -1182,7 +1182,9 @@ class Global(var currentSettings: Settings, reporter0: Reporter)

// We hit these checks regularly. They shouldn't change inside the same run, so cache the comparisons here.
@nowarn("cat=deprecation")
val isScala3: Boolean = settings.isScala3.value
val isScala3: Boolean = settings.isScala3.value // reporting.isScala3
@nowarn("cat=deprecation")
val isScala3Cross: Boolean = settings.isScala3Cross.value // reporting.isScala3Cross
val isScala3ImplicitResolution: Boolean = settings.Yscala3ImplicitResolution.value

// used in sbt
Expand Down
6 changes: 4 additions & 2 deletions src/compiler/scala/tools/nsc/Reporting.scala
Expand Up @@ -44,8 +44,10 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
if (settings.rootdir.value.isEmpty) ""
else Regex.quote(new java.io.File(settings.rootdir.value).getCanonicalPath.replace("\\", "/"))
@nowarn("cat=deprecation")
def isScala3 = settings.isScala3.value
def isScala3Migration = settings.Xmigration.value != NoScalaVersion
val isScala3 = settings.isScala3.value
@nowarn("cat=deprecation")
val isScala3Cross: Boolean = settings.isScala3Cross.value
val isScala3Migration = settings.Xmigration.value != NoScalaVersion
lazy val wconf = WConf.parse(settings.Wconf.value, rootDirPrefix) match {
case Left(msgs) =>
val multiHelp =
Expand Down
9 changes: 2 additions & 7 deletions src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Expand Up @@ -1556,8 +1556,8 @@ self =>

// Scala 2 allowed uprooted Ident for purposes of virtualization
val t1 =
if (currentRun.isScala3) atPos(o2p(start)) { Select(Select(Ident(nme.ROOTPKG), nme.scala_), nme.StringContextName) }
else atPos(o2p(start)) { Ident(nme.StringContextName) }
if (currentRun.isScala3Cross) atPos(o2p(start)) { Select(Select(Ident(nme.ROOTPKG), nme.scala_), nme.StringContextName) }
else atPos(o2p(start)) { Ident(nme.StringContextName).updateAttachment(VirtualStringContext) }
val t2 = atPos(start) { Apply(t1, partsBuf.toList) } updateAttachment InterpolatedString
t2 setPos t2.pos.makeTransparent
val t3 = Select(t2, interpolator) setPos t2.pos
Expand Down Expand Up @@ -3392,11 +3392,6 @@ self =>
val templateOffset = if (body.isEmpty && in.lastOffset < tstart) in.lastOffset else tstart
val templatePos = o2p(templateOffset)

// warn now if user wrote parents for package object; `gen.mkParents` adds AnyRef to parents
if (currentRun.isScala3 && name == nme.PACKAGEkw && !parents.isEmpty)
migrationWarning(tstart, sm"""|package object inheritance is deprecated (https://github.com/scala/scala-dev/issues/441);
|drop the `extends` clause or use a regular object instead""", "3.0.0")

atPos(templateOffset) {
// Exclude only the 9 primitives plus AnyVal.
if (inScalaRootPackage && ScalaValueClassNames.contains(name))
Expand Down
21 changes: 11 additions & 10 deletions src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
Expand Up @@ -17,7 +17,7 @@ import scala.annotation.{switch, tailrec}
import scala.collection.mutable, mutable.{ArrayBuffer, ListBuffer}
import scala.reflect.internal.Chars._
import scala.reflect.internal.util._
import scala.tools.nsc.Reporting.WarningCategory
import scala.tools.nsc.Reporting.WarningCategory, WarningCategory.Scala3Migration
import scala.tools.nsc.ast.parser.xml.Utility.isNameStart
import scala.tools.nsc.settings.ScalaVersion
import scala.tools.nsc.util.{CharArrayReader, CharArrayReaderData}
Expand Down Expand Up @@ -528,12 +528,13 @@ trait Scanners extends ScannersCommon {
(sepRegions.isEmpty || sepRegions.head == RBRACE)) {
if (pastBlankLine()) insertNL(NEWLINES)
else if (!isLeadingInfixOperator) insertNL(NEWLINE)
else if (!currentRun.isScala3) {
else if (!currentRun.isScala3Cross) {
val msg = """|Line starts with an operator that in future
|will be taken as an infix expression continued from the previous line.
|To force the previous interpretation as a separate statement,
|add an explicit `;`, add an empty line, or remove spaces after the operator."""
if (infixMigration) deprecationWarning(msg.stripMargin, "2.13.2")
if (currentRun.isScala3) warning(offset, msg.stripMargin, Scala3Migration)
else if (infixMigration) deprecationWarning(msg.stripMargin, "2.13.2")
insertNL(NEWLINE)
}
}
Expand Down Expand Up @@ -965,19 +966,19 @@ trait Scanners extends ScannersCommon {
if (strVal != null)
try {
val processed = StringContext.processUnicode(strVal)
if (processed != strVal) {
val diffPosition = processed.zip(strVal).zipWithIndex.collectFirst{ case ((r, o), i) if r != o => i}.getOrElse(processed.length - 1)
if (processed != strVal && !currentRun.isScala3Cross) {
val diffPosition = processed.zip(strVal).zipWithIndex.collectFirst { case ((r, o), i) if r != o => i }.getOrElse(processed.length - 1)
val pos = offset + 3 + diffPosition
def msg(what: String) = s"Unicode escapes in triple quoted strings are $what; use the literal character instead"
if (!currentRun.isScala3) {
if (currentRun.isScala3)
warning(pos, msg("ignored in Scala 3"), WarningCategory.Scala3Migration)
else
deprecationWarning(pos, msg("deprecated"), since="2.13.2")
strVal = processed
}
else warning(pos, msg("ignored under -Xsource:3"), WarningCategory.Scala3Migration)
strVal = processed
}
} catch {
case ue: StringContext.InvalidUnicodeEscapeException =>
if (!currentRun.isScala3)
if (!currentRun.isScala3Cross)
syntaxError(offset + 3 + ue.index, ue.getMessage())
}

Expand Down
13 changes: 10 additions & 3 deletions src/compiler/scala/tools/nsc/settings/MutableSettings.scala
Expand Up @@ -217,8 +217,8 @@ class MutableSettings(val errorFn: String => Unit, val pathFactory: PathFactory)
def OutputSetting(default: String) = add(new OutputSetting(default))
def PhasesSetting(name: String, descr: String, default: String = "") = add(new PhasesSetting(name, descr, default))
def StringSetting(name: String, arg: String, descr: String, default: String, helpText: Option[String] = None) = add(new StringSetting(name, arg, descr, default, helpText))
def ScalaVersionSetting(name: String, arg: String, descr: String, initial: ScalaVersion, default: Option[ScalaVersion] = None) =
add(new ScalaVersionSetting(name, arg, descr, initial, default))
def ScalaVersionSetting(name: String, arg: String, descr: String, initial: ScalaVersion, default: Option[ScalaVersion] = None, helpText: Option[String] = None) =
add(new ScalaVersionSetting(name, arg, descr, initial, default, helpText))
def PathSetting(name: String, descr: String, default: String): PathSetting = {
val prepend = StringSetting(name + "/p", "", "", "").internalOnly()
val append = StringSetting(name + "/a", "", "", "").internalOnly()
Expand Down Expand Up @@ -506,10 +506,12 @@ class MutableSettings(val errorFn: String => Unit, val pathFactory: PathFactory)
val arg: String,
descr: String,
val initial: ScalaVersion,
default: Option[ScalaVersion])
default: Option[ScalaVersion],
helpText: Option[String])
extends Setting(name, descr) {
type T = ScalaVersion
protected var v: T = initial
protected var sawHelp: Boolean = false

// This method is invoked if there are no colonated args. In this case the default value is
// used. No arguments are consumed.
Expand All @@ -522,12 +524,17 @@ class MutableSettings(val errorFn: String => Unit, val pathFactory: PathFactory)
}

def tryToSetColon(args: List[String]) = args match {
case "help" :: rest if helpText.nonEmpty => sawHelp = true; Some(rest)
case x :: xs => value = ScalaVersion(x, errorFn); Some(xs)
case nil => Some(nil)
}

def unparse: List[String] = if (value == NoScalaVersion) Nil else List(s"${name}:${value.unparse}")

override def isHelping: Boolean = sawHelp

override def help = helpText.get

withHelpSyntax(s"${name}:<${arg}>")
}

Expand Down
39 changes: 36 additions & 3 deletions src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
Expand Up @@ -124,17 +124,50 @@ trait ScalaSettings extends StandardScalaSettings with Warnings { _: MutableSett
val mainClass = StringSetting ("-Xmain-class", "path", "Class for manifest's Main-Class entry (only useful with -d <jar>)", "")
val sourceReader = StringSetting ("-Xsource-reader", "classname", "Specify a custom method for reading source files.", "")
val reporter = StringSetting ("-Xreporter", "classname", "Specify a custom subclass of FilteringReporter for compiler messages.", "scala.tools.nsc.reporters.ConsoleReporter")
private val XsourceHelp =
sm"""|-Xsource:3 is for migrating a codebase, -Xsource:3-cross is for cross-building.
|
|-Xsource:3 isues migration warnings in category `cat=scala3-migration`,
| which by default are promoted to errors under the `-Wconf` configuration.
| Examples of promoted warnings:
| * Implicit definitions must have an explicit type
| * (x: Any) + "" is deprecated
| * Args not adapted to unit value
| * Member classes cannot shadow a same-named class defined in a parent
| * Presence or absence of parentheses in overrides must match exactly
|
|Certain benign syntax features are enabled:
| * case C(xs*) =>
| * A & B type intersection
| * import p.*
| * import p.m as n
| * import p.{given, *}
| * Eta-expansion `x.m` of methods without trailing `_`
|
|The following constructs emit a migration warning under -Xsource:3. With
|-Xsource:3-cross the semantics change to match Scala 3 and no warning is issued.
| * Unicode escapes in raw interpolations and triple-quoted strings
| * Leading infix operators continue the previous line
| * Interpolator must be selectable from `scala.StringContext`
| * Case class copy and apply have the same access modifier as the constructor
| * The inferred type of an override is taken from the member it overrides
|"""
@nowarn("cat=deprecation")
val source = ScalaVersionSetting ("-Xsource", "version", "Enable features that will be available in a future version of Scala, for purposes of early migration and alpha testing.", initial = ScalaVersion("2.13")).withPostSetHook { s =>
if (s.value >= ScalaVersion("3"))
val source = ScalaVersionSetting ("-Xsource", "version", "Enable warnings and features for a future version.", initial = ScalaVersion("2.13"), helpText = Some(XsourceHelp)).withPostSetHook { s =>
if (s.value >= ScalaVersion("3")) {
isScala3.value = true
if (s.value > ScalaVersion("3"))
isScala3Cross.value = true
}
else if (s.value >= ScalaVersion("2.14"))
s.withDeprecationMessage("instead of -Xsource:2.14, use -Xsource:3").value = ScalaVersion("3")
s.withDeprecationMessage("instead of -Xsource:2.14, use -Xsource:3 or -Xsource:3-cross").value = ScalaVersion("3")
else if (s.value < ScalaVersion("2.13"))
errorFn.apply(s"-Xsource must be at least the current major version (${ScalaVersion("2.13").versionString})")
}
@deprecated("Use currentRun.isScala3 instead", since="2.13.9")
val isScala3 = BooleanSetting ("isScala3", "Is -Xsource Scala 3?").internalOnly()
@deprecated("Use currentRun.isScala3Cross instead", since="2.13.13")
val isScala3Cross = BooleanSetting ("isScala3Cross", "Is -Xsource > Scala 3?").internalOnly()
// The previous "-Xsource" option is intended to be used mainly though ^ helper

val XnoPatmatAnalysis = BooleanSetting ("-Xno-patmat-analysis", "Don't perform exhaustivity/unreachability analysis. Also, ignore @switch annotation.")
Expand Down
34 changes: 20 additions & 14 deletions src/compiler/scala/tools/nsc/settings/ScalaVersion.scala
Expand Up @@ -24,18 +24,24 @@ sealed abstract class ScalaVersion extends Ordered[ScalaVersion] {
def versionString: String = unparse
}

/**
* A scala version that sorts higher than all actual versions
*/
case object NoScalaVersion extends ScalaVersion {
def unparse = "none"

/** A scala version that sorts higher than all actual versions. */
sealed abstract class MaximalScalaVersion extends ScalaVersion {
def compare(that: ScalaVersion): Int = that match {
case NoScalaVersion => 0
case _: MaximalScalaVersion => 0
case _ => 1
}
}

/** If "no version" is specified, assume a maximal version, "the latest". */
case object NoScalaVersion extends MaximalScalaVersion {
def unparse = "none"
}

/** Same as `NoScalaVersion` but with a different toString */
case object Scala3Cross extends MaximalScalaVersion {
def unparse = "3-cross"
}

/**
* A specific Scala version, not one of the magic min/max versions. An SpecificScalaVersion
* may or may not be a released version - i.e. this same class is used to represent
Expand All @@ -58,7 +64,7 @@ case class SpecificScalaVersion(major: Int, minor: Int, rev: Int, build: ScalaBu
else if (rev > thatRev) 1
else build compare thatBuild
case AnyScalaVersion => 1
case NoScalaVersion => -1
case _: MaximalScalaVersion => -1
}
}

Expand All @@ -81,13 +87,13 @@ object ScalaVersion {
private val dot = """\."""
private val dash = "-"
private val vchar = """\d""" //"[^-+.]"
private val vpat = s"(?s)($vchar+)(?:$dot($vchar+)(?:$dot($vchar+)(?:$dash(.*))?)?)?".r
private val vpat = s"(?s)($vchar+)(?:$dot($vchar+)(?:$dot($vchar+))?)?(?:$dash(.+))?".r
private val rcpat = """(?i)rc(\d*)""".r
private val mspat = """(?i)m(\d*)""".r

def apply(versionString: String, errorHandler: String => Unit): ScalaVersion = {
def error() = errorHandler(
s"Bad version (${versionString}) not major[.minor[.revision[-suffix]]]"
s"Bad version (${versionString}) not major[.minor[.revision]][-suffix]"
)

def toInt(s: String) = s match {
Expand All @@ -103,12 +109,12 @@ object ScalaVersion {
}

versionString match {
case "none" => NoScalaVersion
case "" => NoScalaVersion
case "any" => AnyScalaVersion
case "none" | "" => NoScalaVersion
case "3-cross" => Scala3Cross
case "any" => AnyScalaVersion
case vpat(majorS, minorS, revS, buildS) =>
SpecificScalaVersion(toInt(majorS), toInt(minorS), toInt(revS), toBuild(buildS))
case _ => error() ; AnyScalaVersion
case _ => error(); AnyScalaVersion
}
}

Expand Down
11 changes: 4 additions & 7 deletions src/compiler/scala/tools/nsc/typechecker/Adaptations.scala
Expand Up @@ -77,16 +77,15 @@ trait Adaptations {
)
}
@inline def msg(what: String): String = s"adaptation of an empty argument list by inserting () $what"
@inline def noAdaptation: false = {
context.error(t.pos, adaptWarningMessage(msg("has been removed"), showAdaptation = false))
false // drop adaptation
}
@inline def deprecatedAdaptation: true = {
val twist =
if (isLeakyTarget) "leaky (Object-receiving) target makes this especially dangerous"
else "this is unlikely to be what you want"
val text = s"${msg("is deprecated")}: ${twist}"
context.deprecationWarning(t.pos, t.symbol, adaptWarningMessage(text), "2.11.0")
if (currentRun.isScala3)
currentRun.reporting.warning(t.pos, adaptWarningMessage(text), WarningCategory.Scala3Migration, t.symbol)
else
context.deprecationWarning(t.pos, t.symbol, adaptWarningMessage(text), "2.11.0")
true // keep adaptation
}
@inline def warnAdaptation: true = {
Expand All @@ -109,8 +108,6 @@ trait Adaptations {
}
if (args.nonEmpty)
warnAdaptation
else if (currentRun.isScala3)
noAdaptation
else
deprecatedAdaptation
}
Expand Down
Expand Up @@ -55,7 +55,7 @@ trait AnalyzerPlugins { self: Analyzer with splain.SplainData =>
* Let analyzer plugins change the types assigned to definitions. For definitions that have
* an annotated type, the assigned type is obtained by typing that type tree. Otherwise, the
* type is inferred by typing the definition's righthand side, or from the overridden
* member under `-Xsource:3`.
* member under `-Xsource:3-cross`.
*
* In order to know if the type was inferred, you can query the `wasEmpty` field in the `tpt`
* TypeTree of the definition (for DefDef and ValDef).
Expand Down
3 changes: 1 addition & 2 deletions src/compiler/scala/tools/nsc/typechecker/Contexts.scala
Expand Up @@ -1075,7 +1075,7 @@ trait Contexts { self: Analyzer =>
!(
// [eed3si9n] ideally I'd like to do this: val fd = currentRun.isScala3 && sym.isDeprecated
// but implicit caching currently does not report sym.isDeprecated correctly.
currentRun.isScala3 && (sym == currentRun.runDefinitions.Predef_any2stringaddMethod)
currentRun.isScala3Cross && (sym == currentRun.runDefinitions.Predef_any2stringaddMethod)
) &&
!(imported && {
val e = scope.lookupEntry(name)
Expand Down Expand Up @@ -1554,7 +1554,6 @@ trait Contexts { self: Analyzer =>
* 1b) Definitions and declarations that are either inherited, or made
* available by a package clause and also defined in the same compilation unit
* as the reference to them, have the next highest precedence.
* (Only in -Xsource:3, same precedence as 1 with a warning in Scala 2.)
* 2) Explicit imports have next highest precedence.
* 3) Wildcard imports have next highest precedence.
* 4) Bindings made available by a package clause,
Expand Down

0 comments on commit 8e03f3b

Please sign in to comment.