Skip to content

Commit

Permalink
Merge pull request #10439 from som-snytt/issue/12798-xsource3-migration
Browse files Browse the repository at this point in the history
Emit migration warnings under `-Xsource:3` as fatal warnigns, not errors
  • Loading branch information
lrytz committed Jul 4, 2023
2 parents 21eed35 + 1d4d907 commit 0a460e8
Show file tree
Hide file tree
Showing 50 changed files with 518 additions and 168 deletions.
17 changes: 15 additions & 2 deletions src/compiler/scala/tools/nsc/Reporting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ package tools
package nsc

import java.util.regex.PatternSyntaxException
import scala.annotation.nowarn
import scala.collection.mutable
import scala.reflect.internal
import scala.reflect.internal.util.StringOps.countElementsAsString
import scala.reflect.internal.util.{NoSourceFile, Position, SourceFile}
import scala.tools.nsc.Reporting.Version.{NonParseableVersion, ParseableVersion}
import scala.tools.nsc.Reporting._
import scala.tools.nsc.settings.NoScalaVersion
import scala.util.matching.Regex

/** Provides delegates to the reporter doing the actual work.
Expand All @@ -38,6 +40,9 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
val rootDirPrefix: String =
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
lazy val wconf = WConf.parse(settings.Wconf.value, rootDirPrefix) match {
case Left(msgs) =>
val multiHelp =
Expand All @@ -48,7 +53,13 @@ trait Reporting extends internal.Reporting { self: ast.Positions with Compilatio
else ""
globalError(s"Failed to parse `-Wconf` configuration: ${settings.Wconf.value}\n${msgs.mkString("\n")}$multiHelp")
WConf(Nil)
case Right(c) => c
case Right(conf) =>
if (isScala3 && !conf.filters.exists(_._1.exists { case MessageFilter.Category(WarningCategory.Scala3Migration) => true case _ => false })) {
val migrationAction = if (isScala3Migration) Action.Warning else Action.Error
val migrationCategory = MessageFilter.Category(WarningCategory.Scala3Migration) :: Nil
WConf(conf.filters :+ (migrationCategory, migrationAction))
}
else conf
}

private val summarizedWarnings: mutable.Map[WarningCategory, mutable.LinkedHashMap[Position, Message]] = mutable.HashMap.empty
Expand Down Expand Up @@ -353,11 +364,13 @@ object Reporting {

object JavaSource extends WarningCategory; add(JavaSource)

object Scala3Migration extends WarningCategory; add(Scala3Migration)

sealed trait Other extends WarningCategory { override def summaryCategory: WarningCategory = Other }
object Other extends Other { override def includes(o: WarningCategory): Boolean = o.isInstanceOf[Other] }; add(Other)
object OtherShadowing extends Other; add(OtherShadowing)
object OtherPureStatement extends Other; add(OtherPureStatement)
object OtherMigration extends Other; add(OtherMigration)
object OtherMigration extends Other; add(OtherMigration) // API annotation
object OtherMatchAnalysis extends Other; add(OtherMatchAnalysis)
object OtherDebug extends Other; add(OtherDebug)
object OtherNullaryOverride extends Other; add(OtherNullaryOverride)
Expand Down
62 changes: 33 additions & 29 deletions src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import scala.tools.nsc.Reporting.WarningCategory
* the beginnings of a campaign against this latest incursion by Cutty
* McPastington and his army of very similar soldiers.
*/
trait ParsersCommon extends ScannersCommon { self =>
trait ParsersCommon extends ScannersCommon {
self =>
val global : Global
// the use of currentUnit in the parser should be avoided as it might
// cause unexpected behaviour when you work with two units at the
Expand Down Expand Up @@ -606,6 +607,20 @@ self =>
and
}

// warn under -Xsource:3
def migrationWarning(offset: Offset, msg: String, since: String): Unit =
if (currentRun.isScala3) warning(offset, msg, WarningCategory.Scala3Migration)

// warn under -Xsource:3, otherwise deprecation
def hardMigrationWarning(offset: Offset, msg: String, since: String): Unit =
if (currentRun.isScala3) warning(offset, msg, WarningCategory.Scala3Migration)
else deprecationWarning(offset, msg, since)

// deprecation or migration under -Xsource:3, with different messages
def hardMigrationWarning(offset: Offset, depr: => String, migr: => String, since: String): Unit =
if (currentRun.isScala3) warning(offset, migr, WarningCategory.Scala3Migration)
else deprecationWarning(offset, depr, since)

def expectedMsgTemplate(exp: String, fnd: String) = s"$exp expected but $fnd found."
def expectedMsg(token: Token): String =
in.token match {
Expand Down Expand Up @@ -782,8 +797,7 @@ self =>
val msg = "parentheses are required around the parameter of a lambda"
val wrn = sm"""|$msg
|Use '-Wconf:msg=lambda-parens:s' to silence this warning."""
if (currentRun.isScala3)
deprecationWarning(tree.pos.point, wrn, "2.13.11")
migrationWarning(tree.pos.point, wrn, "2.13.11")
List(convertToParam(tree))
case _ => List(convertToParam(tree))
}
Expand Down Expand Up @@ -994,10 +1008,7 @@ self =>
def finishBinaryOp(isExpr: Boolean, opinfo: OpInfo, rhs: Tree): Tree = {
import opinfo._
if (targs.nonEmpty)
if (currentRun.isScala3)
syntaxError(offset, "type application is not allowed for infix operators")
else
deprecationWarning(offset, "type application will be disallowed for infix operators", "2.13.11")
migrationWarning(offset, "type application is not allowed for infix operators", "2.13.11")
val operatorPos: Position = Position.range(rhs.pos.source, offset, offset, offset + operator.length)
val pos = lhs.pos.union(rhs.pos).union(operatorPos).withEnd(in.lastOffset).withPoint(offset)

Expand Down Expand Up @@ -2064,8 +2075,7 @@ self =>
def msg(what: String, instead: String): String = s"`val` keyword in for comprehension is $what: $instead"
if (hasEq) {
val without = "instead, bind the value without `val`"
if (currentRun.isScala3) syntaxError(in.offset, msg("unsupported", without))
else deprecationWarning(in.offset, msg("deprecated", without), "2.10.0")
hardMigrationWarning(in.offset, msg("deprecated", without), msg("unsupported", without), "2.10.0")
}
else syntaxError(in.offset, msg("unsupported", "just remove `val`"))
}
Expand Down Expand Up @@ -2616,9 +2626,9 @@ self =>
checkQMarkDefinition()
checkKeywordDefinition()
val pname: TypeName =
if (in.token == USCORE && (isAbstractOwner || !currentRun.isScala3)) {
if (in.token == USCORE) {
if (!isAbstractOwner)
deprecationWarning(in.offset, "Top-level wildcard is not allowed and will error under -Xsource:3", "2.13.7")
hardMigrationWarning(in.offset, "Top-level wildcard is not allowed", "2.13.7")
in.nextToken()
freshTypeName("_$$")
}
Expand All @@ -2631,8 +2641,7 @@ self =>
def msg(what: String) = s"""view bounds are $what; use an implicit parameter instead.
| example: instead of `def f[A <% Int](a: A)` use `def f[A](a: A)(implicit ev: A => Int)`""".stripMargin
while (in.token == VIEWBOUND) {
if (currentRun.isScala3) syntaxError(in.offset, msg("unsupported"))
else deprecationWarning(in.offset, msg("deprecated"), "2.12.0")
hardMigrationWarning(in.offset, msg("deprecated"), msg("unsupported"), "2.12.0")
contextBoundBuf += atPos(in.skipToken())(makeFunctionTypeTree(List(Ident(pname)), typ()))
}
while (in.token == COLON) {
Expand Down Expand Up @@ -2932,12 +2941,12 @@ self =>
def funDefOrDcl(start: Int, mods: Modifiers): Tree = {
in.nextToken()
if (in.token == THIS) {
def missingEquals() = deprecationWarning(in.lastOffset, "procedure syntax is deprecated for constructors: add `=`, as in method definition", "2.13.2")
def missingEquals() = hardMigrationWarning(in.lastOffset, "procedure syntax is deprecated for constructors: add `=`, as in method definition", "2.13.2")
atPos(start, in.skipToken()) {
val vparamss = paramClauses(nme.CONSTRUCTOR, classContextBounds map (_.duplicate), ofCaseClass = false)
newLineOptWhenFollowedBy(LBRACE)
val rhs =
if (in.token == LBRACE && !currentRun.isScala3) {
if (in.token == LBRACE) {
missingEquals(); atPos(in.offset) { constrBlock(vparamss) }
}
else {
Expand Down Expand Up @@ -2972,15 +2981,13 @@ self =>
val rhs =
if (isStatSep || in.token == RBRACE) {
if (restype.isEmpty) {
if (currentRun.isScala3) syntaxError(in.lastOffset, msg("unsupported", ": Unit"))
else deprecationWarning(in.lastOffset, msg("deprecated", ": Unit"), "2.13.0")
hardMigrationWarning(in.lastOffset, msg("deprecated", ": Unit"), msg("unsupported", ": Unit"), "2.13.0")
restype = scalaUnitConstr
}
newmods |= Flags.DEFERRED
EmptyTree
} else if (restype.isEmpty && in.token == LBRACE) {
if (currentRun.isScala3) syntaxError(in.offset, msg("unsupported", ": Unit ="))
else deprecationWarning(in.offset, msg("deprecated", ": Unit ="), "2.13.0")
hardMigrationWarning(in.offset, msg("deprecated", ": Unit ="), msg("unsupported", ": Unit ="), "2.13.0")
restype = scalaUnitConstr
blockExpr()
} else {
Expand All @@ -2998,9 +3005,7 @@ self =>
if (nme.isEncodedUnary(name) && vparamss.nonEmpty) {
def instead = DefDef(newmods, name.toTermName.decodedName, tparams, vparamss.drop(1), restype, rhs)
def unaryMsg(what: String) = s"unary prefix operator definition with empty parameter list is $what: instead, remove () to declare as `$instead`"
def warnNilary(): Unit =
if (currentRun.isScala3) syntaxError(nameOffset, unaryMsg("unsupported"))
else deprecationWarning(nameOffset, unaryMsg("deprecated"), "2.13.4")
def warnNilary() = hardMigrationWarning(nameOffset, unaryMsg("deprecated"), unaryMsg("unsupported"), "2.13.4")
vparamss match {
case List(List()) => warnNilary()
case List(List(), x :: xs) if x.mods.isImplicit => warnNilary()
Expand Down Expand Up @@ -3129,7 +3134,7 @@ self =>
val nameOffset = in.offset
val name = identForType()
if (currentRun.isScala3 && in.token == LBRACKET && isAfterLineEnd)
deprecationWarning(in.offset, "type parameters should not follow newline", "2.13.7")
hardMigrationWarning(in.offset, "type parameters should not follow newline", "2.13.7")
def orStart(p: Offset) = if (name == tpnme.ERROR) start else p
val namePos = NamePos(r2p(orStart(nameOffset), orStart(nameOffset)))
atPos(start, orStart(nameOffset)) {
Expand All @@ -3140,7 +3145,7 @@ self =>
val tstart = (in.offset :: classContextBounds.map(_.pos.start)).min
if (!classContextBounds.isEmpty && mods.isTrait) {
val viewBoundsExist = if (currentRun.isScala3) "" else " nor view bounds `<% ...`"
syntaxError(s"traits cannot have type parameters with context bounds `: ...`$viewBoundsExist", skipIt = false)
syntaxError(s"traits cannot have type parameters with context bounds `: ...`$viewBoundsExist", skipIt = false)
classContextBounds = List()
}
val constrAnnots = if (!mods.isTrait) constructorAnnotations() else Nil
Expand Down Expand Up @@ -3238,7 +3243,7 @@ self =>
val advice =
if (currentRun.isScala3) "use trait parameters instead."
else "they will be replaced by trait parameters in 3.0, see the migration guide on avoiding var/val in traits."
deprecationWarning(braceOffset, s"early initializers are deprecated; $advice", "2.13.0")
hardMigrationWarning(braceOffset, s"early initializers are deprecated; $advice", "2.13.0")
val earlyDefs: List[Tree] = body.map(ensureEarlyDef).filter(_.nonEmpty)
in.nextToken()
val parents = templateParents()
Expand All @@ -3259,8 +3264,7 @@ self =>
copyValDef(vdef)(mods = mods | Flags.PRESUPER)
case tdef @ TypeDef(mods, name, tparams, rhs) =>
def msg(what: String): String = s"early type members are $what: move them to the regular body; the semantics are the same"
if (currentRun.isScala3) syntaxError(tdef.pos.point, msg("unsupported"))
else deprecationWarning(tdef.pos.point, msg("deprecated"), "2.11.0")
hardMigrationWarning(tdef.pos.point, msg("deprecated"), msg("unsupported"), "2.11.0")
treeCopy.TypeDef(tdef, mods | Flags.PRESUPER, name, tparams, rhs)
case docdef @ DocDef(comm, rhs) =>
treeCopy.DocDef(docdef, comm, rhs)
Expand Down Expand Up @@ -3302,8 +3306,8 @@ self =>

// warn now if user wrote parents for package object; `gen.mkParents` adds AnyRef to parents
if (currentRun.isScala3 && name == nme.PACKAGEkw && !parents.isEmpty)
deprecationWarning(tstart, """package object inheritance is deprecated (https://github.com/scala/scala-dev/issues/441);
|drop the `extends` clause or use a regular object instead""".stripMargin, "3.0.0")
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.
Expand Down
40 changes: 21 additions & 19 deletions src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,17 @@
package scala.tools.nsc
package ast.parser

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.ast.parser.xml.Utility.isNameStart
import scala.tools.nsc.settings.ScalaVersion
import scala.tools.nsc.util.{CharArrayReader, CharArrayReaderData}
import scala.reflect.internal.util._
import scala.reflect.internal.Chars._
import Tokens._
import scala.annotation.{switch, tailrec}
import scala.collection.mutable
import mutable.{ArrayBuffer, ListBuffer}
import scala.tools.nsc.ast.parser.xml.Utility.isNameStart
import java.lang.StringBuilder

import scala.tools.nsc.Reporting.WarningCategory

object Cbuf {
final val TargetCapacity = 256

Expand Down Expand Up @@ -958,27 +956,31 @@ trait Scanners extends ScannersCommon {
}

private def replaceUnicodeEscapesInTriple(): Unit =
if(strVal != null) {
if (strVal != null)
try {
val replaced = StringContext.processUnicode(strVal)
if(replaced != strVal) {
val diffPosition = replaced.zip(strVal).zipWithIndex.collectFirst{ case ((r, o), i) if r != o => i}.getOrElse(replaced.length - 1)
deprecationWarning(offset + 3 + diffPosition, "Unicode escapes in triple quoted strings are deprecated, use the literal character instead", since="2.13.2")
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)
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) {
deprecationWarning(pos, msg("deprecated"), since="2.13.2")
strVal = processed
}
else warning(pos, msg("ignored under -Xsource:3"), WarningCategory.Scala3Migration)
}
strVal = replaced
} catch {
case ue: StringContext.InvalidUnicodeEscapeException => {
syntaxError(offset + 3 + ue.index, ue.getMessage())
}
case ue: StringContext.InvalidUnicodeEscapeException =>
if (!currentRun.isScala3)
syntaxError(offset + 3 + ue.index, ue.getMessage())
}
}

@tailrec private def getRawStringLit(): Unit = {
if (ch == '\"') {
nextRawChar()
if (isTripleQuote()) {
setStrVal()
if (!currentRun.isScala3) replaceUnicodeEscapesInTriple()
replaceUnicodeEscapesInTriple()
token = STRINGLIT
} else
getRawStringLit()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ trait ContextErrors extends splain.SplainErrors {
s"Implicit definition ${if (currentRun.isScala3) "must" else "should"} have explicit type${
if (!inferred.isErroneous) s" (inferred $inferred)" else ""
}"
if (currentRun.isScala3) ErrorUtils.issueNormalTypeError(tree, msg)(cx)
if (currentRun.isScala3) cx.warning(tree.pos, msg, WarningCategory.Scala3Migration)
else cx.warning(tree.pos, msg, WarningCategory.OtherImplicitType)
}
val sym = tree.symbol
Expand Down
14 changes: 11 additions & 3 deletions src/compiler/scala/tools/nsc/typechecker/Namers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1148,9 +1148,17 @@ trait Namers extends MethodSynthesis {
case _ => true
}
}
if (inferOverridden) pt
else dropIllegalStarTypes(widenIfNecessary(tree.symbol, rhsTpe, pt))
.tap(InferredImplicitError(tree, _, context))
val legacy = dropIllegalStarTypes(widenIfNecessary(tree.symbol, rhsTpe, pt))
if (inferOverridden) {
if (!(legacy =:= pt) && currentRun.isScala3) {
val pts = pt.toString
val leg = legacy.toString
val help = if (pts != leg) s" instead of $leg" else ""
runReporting.warning(tree.pos, s"under -Xsource:3, inferred $pts$help", WarningCategory.Scala3Migration, tree.symbol)
}
pt
}
else legacy.tap(InferredImplicitError(tree, _, context))
}.setPos(tree.pos.focus)
tree.tpt.tpe
}
Expand Down
15 changes: 6 additions & 9 deletions src/compiler/scala/tools/nsc/typechecker/NamesDefaults.scala
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ trait NamesDefaults { self: Analyzer =>
* Verifies that names are not specified twice, and positional args don't appear after named ones.
*/
def removeNames(typer: Typer)(args: List[Tree], params: List[Symbol]): (List[Tree], Array[Int]) = {
implicit val context0 = typer.context
implicit val context0: Context = typer.context
def matchesName(param: Symbol, name: Name, argIndex: Int) = {
def warn(msg: String, since: String) = context0.deprecationWarning(args(argIndex).pos, param, msg, since)
def checkDeprecation(anonOK: Boolean) =
Expand Down Expand Up @@ -554,8 +554,7 @@ trait NamesDefaults { self: Analyzer =>
val NamedArg(Ident(name), rhs) = arg: @unchecked
params.indexWhere(p => matchesName(p, name, argIndex)) match {
case -1 =>
val warnVariableInScope = !currentRun.isScala3 && context0.lookupSymbol(name, _.isVariable).isSuccess
UnknownParameterNameNamesDefaultError(arg, name, warnVariableInScope)
UnknownParameterNameNamesDefaultError(arg, name, warnVariableInScope = context0.lookupSymbol(name, _.isVariable).isSuccess)
case paramPos if argPos contains paramPos =>
val existingArgIndex = argPos.indexWhere(_ == paramPos)
val otherName = Some(args(paramPos)) collect {
Expand All @@ -574,12 +573,10 @@ trait NamesDefaults { self: Analyzer =>
val t = stripNamedArg(arg, argIndex)
if (!t.isErroneous && argPos(argIndex) < 0) argPos(argIndex) = argIndex
t
case (arg, argIndex) =>
if (positionalAllowed) {
argPos(argIndex) = argIndex
arg
} else
PositionalAfterNamedNamesDefaultError(arg)
case (arg, argIndex) if positionalAllowed =>
argPos(argIndex) = argIndex
arg
case (arg, _) => PositionalAfterNamedNamesDefaultError(arg)
}
}
(namelessArgs, argPos)
Expand Down
Loading

0 comments on commit 0a460e8

Please sign in to comment.