diff options
author | Paul Phillips <paulp@improving.org> | 2010-03-05 06:01:19 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2010-03-05 06:01:19 +0000 |
commit | 4e7fd5ce080a42fb4c6eeba5f8a005bd973d6c8e (patch) | |
tree | a44c5ed902c1abc93303c9cdfb162d2c9b97e364 /src | |
parent | 98c87462f7ffcc14dc4fbab9df586a200b77428b (diff) | |
download | scala-4e7fd5ce080a42fb4c6eeba5f8a005bd973d6c8e.tar.gz scala-4e7fd5ce080a42fb4c6eeba5f8a005bd973d6c8e.tar.bz2 scala-4e7fd5ce080a42fb4c6eeba5f8a005bd973d6c8e.zip |
Added -Xmigration option and @migration annotat...
Added -Xmigration option and @migration annotation. At present it will
warn about the following changes from 2.7 to 2.8:
Stack iterator order reversed
mutable.Set.map returns a Set and thus discards duplicates
A case 'x @ Pattern' matches differently than 'Pattern'
Review by odersky.
Diffstat (limited to 'src')
9 files changed, 83 insertions, 25 deletions
diff --git a/src/compiler/scala/tools/nsc/Settings.scala b/src/compiler/scala/tools/nsc/Settings.scala index 2d88b38777..1740844110 100644 --- a/src/compiler/scala/tools/nsc/Settings.scala +++ b/src/compiler/scala/tools/nsc/Settings.scala @@ -877,6 +877,7 @@ trait ScalacSettings { val future = BooleanSetting ("-Xfuture", "Turn on future language features") val genPhaseGraph = StringSetting ("-Xgenerate-phase-graph", "file", "Generate the phase graphs (outputs .dot files) to fileX.dot", "") val XlogImplicits = BooleanSetting ("-Xlog-implicits", "Show more info on why some implicits are not applicable") + val Xmigration28 = BooleanSetting ("-Xmigration", "Warn about constructs whose behavior may have changed between 2.7 and 2.8") val nouescape = BooleanSetting ("-Xno-uescape", "Disables handling of \\u unicode escapes") val Xnojline = BooleanSetting ("-Xnojline", "Do not use JLine for editing") val plugin = MultiStringSetting("-Xplugin", "file", "Load a plugin from a file") diff --git a/src/compiler/scala/tools/nsc/matching/Matrix.scala b/src/compiler/scala/tools/nsc/matching/Matrix.scala index 8464ecc46e..88507efa2f 100644 --- a/src/compiler/scala/tools/nsc/matching/Matrix.scala +++ b/src/compiler/scala/tools/nsc/matching/Matrix.scala @@ -156,7 +156,7 @@ trait Matrix extends MatrixAdditions { def tpe = valsym.tpe lazy val ident = ID(lhs) - lazy val valDef = tracing("typedVal", typer typedValDef (VAL(lhs) === rhs)) + lazy val valDef = tracing("typedVal", typer typedValDef (VAL(lhs) === rhs) setPos lhs.pos) override def toString() = "%s: %s = %s".format(lhs, lhs.info, rhs) } diff --git a/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala b/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala index 79812474ee..4a000b1901 100644 --- a/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala +++ b/src/compiler/scala/tools/nsc/matching/ParallelMatching.scala @@ -95,7 +95,7 @@ trait ParallelMatching extends ast.TreeDSL def sym = pv.sym def tpe = sym.tpe def pos = sym.pos - def id = ID(sym) // attributed ident + def id = ID(sym) setPos pos // attributed ident def accessors = if (isCaseClass) sym.caseFieldAccessors else Nil def accessorTypes = accessors map (x => (tpe memberType x).resultType) @@ -830,7 +830,12 @@ trait ParallelMatching extends ast.TreeDSL // type, but if the value doesn't appear on the right hand side of the // match that's unimportant; so we add an instance check only if there // is a binding. - if (isBound) eqTest AND (scrutTree IS tpe.widen) + if (isBound) { + if (settings.Xmigration28.value) { + cunit.warning(scrutTree.pos, "A bound pattern such as 'x @ Pattern' now matches fewer cases than the same pattern with no binding.") + } + eqTest AND (scrutTree IS tpe.widen) + } else eqTest case _ if scrutTree.tpe <:< tpe && tpe.isAnyRef => scrutTree OBJ_!= NULL diff --git a/src/compiler/scala/tools/nsc/symtab/Definitions.scala b/src/compiler/scala/tools/nsc/symtab/Definitions.scala index edf2e49f5a..d540b67c82 100644 --- a/src/compiler/scala/tools/nsc/symtab/Definitions.scala +++ b/src/compiler/scala/tools/nsc/symtab/Definitions.scala @@ -437,6 +437,7 @@ trait Definitions extends reflect.generic.StandardDefinitions { // special attributes lazy val SerializableAttr: Symbol = getClass("scala.serializable") lazy val DeprecatedAttr: Symbol = getClass("scala.deprecated") + lazy val MigrationAnnotationClass: Symbol = getClass("scala.annotation.migration") lazy val BeanPropertyAttr: Symbol = getClass(sn.BeanProperty) lazy val BooleanBeanPropertyAttr: Symbol = getClass(sn.BooleanBeanProperty) diff --git a/src/compiler/scala/tools/nsc/symtab/Symbols.scala b/src/compiler/scala/tools/nsc/symtab/Symbols.scala index 021478a23d..dfdab4189f 100644 --- a/src/compiler/scala/tools/nsc/symtab/Symbols.scala +++ b/src/compiler/scala/tools/nsc/symtab/Symbols.scala @@ -133,6 +133,14 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => def getAnnotation(cls: Symbol): Option[AnnotationInfo] = annotations find (_.atp.typeSymbol == cls) + /** Finds the requested annotation and returns Some(Tree) containing + * the argument at position 'index', or None if either the annotation + * or the index does not exist. + */ + private def getAnnotationArg(cls: Symbol, index: Int) = + for (AnnotationInfo(_, args, _) <- getAnnotation(cls) ; if args.size > index) yield + args(index) + /** Remove all annotations matching the given class. */ def removeAnnotation(cls: Symbol): Unit = setAnnotations(annotations filterNot (_.atp.typeSymbol == cls)) @@ -446,26 +454,10 @@ trait Symbols extends reflect.generic.Symbols { self: SymbolTable => } } - def isDeprecated = hasAnnotation(DeprecatedAttr) - def deprecationMessage: Option[String] = - annotations find (_.atp.typeSymbol == DeprecatedAttr) flatMap { annot => - annot.args match { - case Literal(const) :: Nil => - Some(const.stringValue) - case _ => - None - } - } - def elisionLevel: Option[Int] = { - if (!hasAnnotation(ElidableMethodClass)) None - else annotations find (_.atp.typeSymbol == ElidableMethodClass) flatMap { annot => - // since we default to enabled by default, only look hard for falsity - annot.args match { - case Literal(Constant(x: Int)) :: Nil => Some(x) - case _ => None - } - } - } + def isDeprecated = hasAnnotation(DeprecatedAttr) + def deprecationMessage = getAnnotationArg(DeprecatedAttr, 0) partialMap { case Literal(const) => const.stringValue } + def migrationMessage = getAnnotationArg(MigrationAnnotationClass, 2) partialMap { case Literal(const) => const.stringValue } + def elisionLevel = getAnnotationArg(ElidableMethodClass, 0) partialMap { case Literal(Constant(x: Int)) => x } /** Does this symbol denote a wrapper object of the interpreter or its class? */ final def isInterpreterWrapper = diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 55500c7f17..28eebdc033 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -927,6 +927,13 @@ abstract class RefChecks extends InfoTransform { unit.deprecationWarning(pos, msg) } } + /** Similar to deprecation: check if the symbol is marked with @migration + * indicating it has changed semantics between versions. + */ + private def checkMigration(sym: Symbol, pos: Position) = + for (msg <- sym.migrationMessage) + unit.warning(pos, "%s%s has changed semantics:\n %s".format(sym, sym.locationString, msg)) + /** Check that a deprecated val or def does not override a * concrete, non-deprecated method. If it does, then * deprecation is meaningless. @@ -1027,7 +1034,16 @@ abstract class RefChecks extends InfoTransform { private def transformSelect(tree: Select): Tree = { val Select(qual, name) = tree val sym = tree.symbol + + /** Note: if a symbol has both @deprecated and @migration annotations and both + * warnings are enabled, only the first one checked here will be emitted. + * I assume that's a consequence of some code trying to avoid noise by suppressing + * warnings after the first, but I think it'd be better if we didn't have to + * arbitrarily choose one as more important than the other. + */ checkDeprecated(sym, tree.pos) + if (settings.Xmigration28.value) + checkMigration(sym, tree.pos) if (currentClass != sym.owner && (sym hasFlag LOCAL)) { var o = currentClass diff --git a/src/library/scala/annotation/migration.scala b/src/library/scala/annotation/migration.scala new file mode 100644 index 0000000000..b0915cde34 --- /dev/null +++ b/src/library/scala/annotation/migration.scala @@ -0,0 +1,28 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2010, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.annotation + +/** + * An annotation that marks a member as having changed semantics + * between versions. This is intended for methods which for one + * reason or another retain the same name and type signature, + * but some aspect of their behavior is different. An illustrative + * examples is Stack.iterator, which reversed from LIFO to FIFO + * order between scala 2.7 and 2.8. + * + * The version numbers are to mark the scala major/minor release + * version where the change took place. + * + * @since 2.8 + */ +private[scala] final class migration( + majorVersion: Int, + minorVersion: Int, + message: String) +extends StaticAnnotation {} diff --git a/src/library/scala/collection/mutable/SetLike.scala b/src/library/scala/collection/mutable/SetLike.scala index cb6eb293c1..c7ea037f49 100644 --- a/src/library/scala/collection/mutable/SetLike.scala +++ b/src/library/scala/collection/mutable/SetLike.scala @@ -14,6 +14,7 @@ package mutable import generic._ import script._ +import scala.annotation.migration /** A template trait for mutable sets of type `mutable.Set[A]`. * @tparam A the type of the elements of the set @@ -63,6 +64,9 @@ trait SetLike[A, +This <: SetLike[A, This] with Set[A]] */ override protected[this] def newBuilder: Builder[A, This] = empty + @migration(2, 8, "Set.map now returns a Set, so it will discard duplicate values.") + override def map[B, That](f: A => B)(implicit bf: CanBuildFrom[This, B, That]): That = super.map(f)(bf) + /** Adds an element to this $coll. * * @param elem the element to be added diff --git a/src/library/scala/collection/mutable/Stack.scala b/src/library/scala/collection/mutable/Stack.scala index bbb4189dc3..8f626e7f33 100644 --- a/src/library/scala/collection/mutable/Stack.scala +++ b/src/library/scala/collection/mutable/Stack.scala @@ -15,6 +15,7 @@ package mutable import generic._ import collection.immutable.{List, Nil} import collection.Iterator +import annotation.migration /** A stack implements a data structure which allows to store and retrieve * objects in a last-in-first-out (LIFO) fashion. @@ -74,8 +75,13 @@ class Stack[A] private (var elems: List[A]) extends scala.collection.Seq[A] with */ def pushAll(elems: scala.collection.Traversable[A]): this.type = { for (elem <- elems) { push(elem); () }; this } - @deprecated("use pushAll") def ++=(it: Iterator[A]): this.type = pushAll(it) - @deprecated("use pushAll") def ++=(it: scala.collection.Iterable[A]): this.type = pushAll(it) + @deprecated("use pushAll") + @migration(2, 8, "Stack ++= now pushes arguments on the stack from left to right.") + def ++=(it: Iterator[A]): this.type = pushAll(it) + + @deprecated("use pushAll") + @migration(2, 8, "Stack ++= now pushes arguments on the stack from left to right.") + def ++=(it: scala.collection.Iterable[A]): this.type = pushAll(it) /** Returns the top element of the stack. This method will not remove * the element from the stack. An error is signaled if there is no @@ -112,14 +118,19 @@ class Stack[A] private (var elems: List[A]) extends scala.collection.Seq[A] with * * @return an iterator over all stack elements. */ + @migration(2, 8, "Stack iterator and foreach now traverse in FIFO order.") override def iterator: Iterator[A] = elems.iterator /** Creates a list of all stack elements in LIFO order. * * @return the created list. */ + @migration(2, 8, "Stack iterator and foreach now traverse in FIFO order.") override def toList: List[A] = elems + @migration(2, 8, "Stack iterator and foreach now traverse in FIFO order.") + override def foreach[U](f: A => U): Unit = super.foreach(f) + /** This method clones the stack. * * @return a stack with the same elements. |