Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow either -Xsource:3 (for preparing to switch to 3) or -Xsource:3-cross (for crossbuilding on 2 and 3) #10573

Merged
merged 2 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/compiler/scala/tools/nsc/Global.scala
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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