From 1de399d3c655807465c6369f77d08e57743e7eaa Mon Sep 17 00:00:00 2001 From: James Iry Date: Mon, 28 Jan 2013 08:55:34 -0800 Subject: SI-6963 Add version to -Xmigration Adds an optional version parameter to the -Xmigration compiler setting. Doing -Xmigration without version number behaves as it used to by dumping every possible migration warning. This commit adds a ScalaVersion class (plus ancillary stuff), and a ScalaVersionSetting. --- .../tools/nsc/matching/ParallelMatching.scala | 3 +- .../scala/tools/nsc/settings/MutableSettings.scala | 30 ++++ .../scala/tools/nsc/settings/ScalaSettings.scala | 3 +- .../scala/tools/nsc/settings/ScalaVersion.scala | 194 +++++++++++++++++++++ .../scala/tools/nsc/transform/ExplicitOuter.scala | 3 +- .../scala/tools/nsc/typechecker/RefChecks.scala | 21 ++- src/library/scala/annotation/migration.scala | 3 +- 7 files changed, 247 insertions(+), 10 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/settings/ScalaVersion.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala b/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala index 9d01e73063..dbb9b7a003 100644 --- a/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala +++ b/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala @@ -15,6 +15,7 @@ import symtab.Flags import mutable.ListBuffer import scala.annotation.elidable import scala.language.postfixOps +import scala.tools.nsc.settings.ScalaVersion trait ParallelMatching extends ast.TreeDSL with MatchSupport @@ -821,7 +822,7 @@ trait ParallelMatching extends ast.TreeDSL // match that's unimportant; so we add an instance check only if there // is a binding. def bindingWarning() = { - if (isBound && settings.Xmigration28.value) { + if (isBound && settings.Xmigration.value < ScalaVersion.twoDotEight) { cunit.warning(scrutTree.pos, "A bound pattern such as 'x @ Pattern' now matches fewer cases than the same pattern with no binding.") } diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index f1f289ed4d..e4f99474e1 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -221,6 +221,7 @@ class MutableSettings(val errorFn: String => Unit) def OutputSetting(outputDirs: OutputDirs, default: String) = add(new OutputSetting(outputDirs, 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) = add(new StringSetting(name, arg, descr, default)) + def ScalaVersionSetting(name: String, arg: String, descr: String, default: ScalaVersion) = add(new ScalaVersionSetting(name, arg, descr, default)) def PathSetting(name: String, descr: String, default: String): PathSetting = { val prepend = StringSetting(name + "/p", "", "", "").internalOnly() val append = StringSetting(name + "/a", "", "", "").internalOnly() @@ -486,6 +487,35 @@ class MutableSettings(val errorFn: String => Unit) withHelpSyntax(name + " <" + arg + ">") } + /** A setting represented by a Scala version, (`default` unless set) */ + class ScalaVersionSetting private[nsc]( + name: String, + val arg: String, + descr: String, + default: ScalaVersion) + extends Setting(name, descr) { + import ScalaVersion._ + + type T = ScalaVersion + protected var v: T = NoScalaVersion + + override def tryToSet(args: List[String]) = { + value = default + Some(args) + } + + override def tryToSetColon(args: List[String]) = args match { + case Nil => value = default; Some(Nil) + case x :: xs => value = ScalaVersion(x, errorFn) ; Some(xs) + } + + override def tryToSetFromPropertyValue(s: String) = tryToSet(List(s)) + + def unparse: List[String] = if (value == NoScalaVersion) Nil else List(s"${name}:${value.unparse}") + + withHelpSyntax(s"${name}:<${arg}>") + } + class PathSetting private[nsc]( name: String, descr: String, diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index b820d10ddc..ee8db61020 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -85,8 +85,7 @@ trait ScalaSettings extends AbsScalaSettings val logFreeTerms = BooleanSetting ("-Xlog-free-terms", "Print a message when reification creates a free term.") val logFreeTypes = BooleanSetting ("-Xlog-free-types", "Print a message when reification resorts to generating a free type.") val maxClassfileName = IntSetting ("-Xmax-classfile-name", "Maximum filename length for generated classes", 255, Some((72, 255)), _ => None) - val Xmigration28 = BooleanSetting ("-Xmigration", "Warn about constructs whose behavior may have changed between 2.7 and 2.8."). - withDeprecationMessage("This setting is no longer useful and will be removed. Please remove it from your build.") + val Xmigration = ScalaVersionSetting("-Xmigration", "version", "Warn about constructs whose behavior may have changed since version.", AnyScalaVersion) val nouescape = BooleanSetting ("-Xno-uescape", "Disable handling of \\u unicode escapes.") val Xnojline = BooleanSetting ("-Xnojline", "Do not use JLine for editing.") val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode (asm backend only.)") diff --git a/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala new file mode 100644 index 0000000000..d6a0149411 --- /dev/null +++ b/src/compiler/scala/tools/nsc/settings/ScalaVersion.scala @@ -0,0 +1,194 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL + * @author James Iry + */ +// $Id$ + +package scala.tools.nsc.settings + +/** + * Represents a single Scala version in a manner that + * supports easy comparison and sorting. + */ +abstract class ScalaVersion extends Ordered[ScalaVersion] { + def unparse: String +} + +/** + * A scala version that sorts higher than all actual versions + */ +case object NoScalaVersion extends ScalaVersion { + def unparse = "none" + + def compare(that: ScalaVersion): Int = that match { + case NoScalaVersion => 0 + case _ => 1 + } +} + +/** + * 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 + * final, release candidate, milestone, and development builds. The build argument is used + * to segregate builds + */ +case class SpecificScalaVersion(major: Int, minor: Int, rev: Int, build: ScalaBuild) extends ScalaVersion { + def unparse = s"${major}.${minor}.${rev}.${build.unparse}" + + def compare(that: ScalaVersion): Int = that match { + case SpecificScalaVersion(thatMajor, thatMinor, thatRev, thatBuild) => + // this could be done more cleanly by importing scala.math.Ordering.Implicits, but we have to do these + // comparisons a lot so I'm using brute force direct style code + if (major < thatMajor) -1 + else if (major > thatMajor) 1 + else if (minor < thatMinor) -1 + else if (minor > thatMinor) 1 + else if (rev < thatRev) -1 + else if (rev > thatRev) 1 + else build compare thatBuild + case AnyScalaVersion => 1 + case NoScalaVersion => -1 + } +} + +/** + * A Scala version that sorts lower than all actual versions + */ +case object AnyScalaVersion extends ScalaVersion { + def unparse = "any" + + def compare(that: ScalaVersion): Int = that match { + case AnyScalaVersion => 0 + case _ => -1 + } +} + +/** + * Factory methods for producing ScalaVersions + */ +object ScalaVersion { + private val dot = "\\." + private val dash = "\\-" + private def not(s:String) = s"[^${s}]" + private val R = s"((${not(dot)}*)(${dot}(${not(dot)}*)(${dot}(${not(dash)}*)(${dash}(.*))?)?)?)".r + + def apply(versionString : String, errorHandler: String => Unit): ScalaVersion = { + def errorAndValue() = { + errorHandler( + s"There was a problem parsing ${versionString}. " + + "Versions should be in the form major[.minor[.revision]] " + + "where each part is a positive number, as in 2.10.1. " + + "The minor and revision parts are optional." + ) + AnyScalaVersion + } + + def toInt(s: String) = s match { + case null | "" => 0 + case _ => s.toInt + } + + def isInt(s: String) = util.Try(toInt(s)).isSuccess + + def toBuild(s: String) = s match { + case null | "FINAL" => Final + case s if (s.toUpperCase.startsWith("RC") && isInt(s.substring(2))) => RC(toInt(s.substring(2))) + case s if (s.toUpperCase.startsWith("M") && isInt(s.substring(1))) => Milestone(toInt(s.substring(1))) + case _ => Development(s) + } + + try versionString match { + case "none" => NoScalaVersion + case "any" => AnyScalaVersion + case R(_, majorS, _, minorS, _, revS, _, buildS) => + SpecificScalaVersion(toInt(majorS), toInt(minorS), toInt(revS), toBuild(buildS)) + case _ => + errorAndValue() + } catch { + case e: NumberFormatException => errorAndValue() + } + } + + def apply(versionString: String): ScalaVersion = + apply(versionString, msg => throw new NumberFormatException(msg)) + + /** + * The version of the compiler running now + */ + val current = apply(util.Properties.versionNumberString) + + /** + * The 2.8.0 version. + */ + val twoDotEight = SpecificScalaVersion(2, 8, 0, Final) +} + +/** + * Represents the data after the dash in major.minor.rev-build + */ +abstract class ScalaBuild extends Ordered[ScalaBuild] { + /** + * Return a version of this build information that can be parsed back into the + * same ScalaBuild + */ + def unparse: String +} +/** + * A development, test, nightly, snapshot or other "unofficial" build + */ +case class Development(id: String) extends ScalaBuild { + def unparse = s"-${id}" + + def compare(that: ScalaBuild) = that match { + // sorting two development builds based on id is reasonably valid for two versions created with the same schema + // otherwise it's not correct, but since it's impossible to put a total ordering on development build versions + // this is a pragmatic compromise + case Development(thatId) => id compare thatId + // assume a development build is newer than anything else, that's not really true, but good luck + // mapping development build versions to other build types + case _ => 1 + } +} +/** + * A final final + */ +case object Final extends ScalaBuild { + def unparse = "" + + def compare(that: ScalaBuild) = that match { + case Final => 0 + // a final is newer than anything other than a development build or another final + case Development(_) => -1 + case _ => 1 + } +} + +/** + * A candidate for final release + */ +case class RC(n: Int) extends ScalaBuild { + def unparse = s"-RC${n}" + + def compare(that: ScalaBuild) = that match { + // compare two rcs based on their RC numbers + case RC(thatN) => n - thatN + // an rc is older than anything other than a milestone or another rc + case Milestone(_) => 1 + case _ => -1 + } +} + +/** + * An intermediate release + */ +case class Milestone(n: Int) extends ScalaBuild { + def unparse = s"-M${n}" + + def compare(that: ScalaBuild) = that match { + // compare two milestones based on their milestone numbers + case Milestone(thatN) => n - thatN + // a milestone is older than anything other than another milestone + case _ => -1 + + } +} diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index 1003d417f6..78c120c1ad 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -10,6 +10,7 @@ import symtab._ import Flags.{ CASE => _, _ } import scala.collection.mutable.ListBuffer import matching.{ Patterns, ParallelMatching } +import scala.tools.nsc.settings.ScalaVersion /** This class ... * @@ -553,7 +554,7 @@ abstract class ExplicitOuter extends InfoTransform } case _ => - if (settings.Xmigration28.value) tree match { + if (settings.Xmigration.value < ScalaVersion.twoDotEight) tree match { case TypeApply(fn @ Select(qual, _), args) if fn.symbol == Object_isInstanceOf || fn.symbol == Any_isInstanceOf => if (isArraySeqTest(qual.tpe, args.head.tpe)) unit.warning(tree.pos, "An Array will no longer match as Seq[_].") diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 969bb8aceb..d1d70370d2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -11,6 +11,9 @@ import scala.collection.{ mutable, immutable } import transform.InfoTransform import scala.collection.mutable.ListBuffer import scala.language.postfixOps +import scala.tools.nsc.settings.ScalaVersion +import scala.tools.nsc.settings.AnyScalaVersion +import scala.tools.nsc.settings.NoScalaVersion /**

* Post-attribution checking and transformation. @@ -1378,10 +1381,18 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans * indicating it has changed semantics between versions. */ private def checkMigration(sym: Symbol, pos: Position) = { - if (sym.hasMigrationAnnotation) - unit.warning(pos, "%s has changed semantics in version %s:\n%s".format( - sym.fullLocationString, sym.migrationVersion.get, sym.migrationMessage.get) - ) + if (sym.hasMigrationAnnotation) { + val changed = try + settings.Xmigration.value < ScalaVersion(sym.migrationVersion.get) + catch { + case e : NumberFormatException => + unit.warning(pos, s"${sym.fullLocationString} has an unparsable version number: ${e.getMessage()}") + // if we can't parse the format on the migration annotation just conservatively assume it changed + true + } + if (changed) + unit.warning(pos, s"${sym.fullLocationString} has changed semantics in version ${sym.migrationVersion.get}:\n${sym.migrationMessage.get}") + } } private def checkCompileTimeOnly(sym: Symbol, pos: Position) = { @@ -1587,7 +1598,7 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans * arbitrarily choose one as more important than the other. */ checkDeprecated(sym, tree.pos) - if (settings.Xmigration28.value) + if(settings.Xmigration.value != NoScalaVersion) checkMigration(sym, tree.pos) checkCompileTimeOnly(sym, tree.pos) diff --git a/src/library/scala/annotation/migration.scala b/src/library/scala/annotation/migration.scala index 49fea9434c..adb6de6afd 100644 --- a/src/library/scala/annotation/migration.scala +++ b/src/library/scala/annotation/migration.scala @@ -17,7 +17,8 @@ package scala.annotation * order between Scala 2.7 and 2.8. * * @param message A message describing the change, which is emitted - * by the compiler if the flag `-Xmigration` is set. + * by the compiler if the flag `-Xmigration` indicates a version + * prior to the changedIn version. * * @param changedIn The version, in which the behaviour change was * introduced. -- cgit v1.2.3