diff --git a/build-ant-macros.xml b/build-ant-macros.xml index 609f106d0925..398809ad991a 100644 --- a/build-ant-macros.xml +++ b/build-ant-macros.xml @@ -733,6 +733,7 @@ + diff --git a/build.xml b/build.xml index 6c750e530df5..387d223e122b 100755 --- a/build.xml +++ b/build.xml @@ -349,6 +349,7 @@ TODO: + @@ -650,7 +651,7 @@ TODO: - + @@ -732,6 +733,11 @@ TODO: + + + + + @@ -786,6 +792,7 @@ TODO: + @@ -795,6 +802,7 @@ TODO: + @@ -804,6 +812,7 @@ TODO: + @@ -820,6 +829,10 @@ TODO: + + + + @@ -866,6 +879,7 @@ TODO: + @@ -877,6 +891,7 @@ TODO: + @@ -892,6 +907,7 @@ TODO: + @@ -917,6 +933,7 @@ TODO: + @@ -940,6 +957,7 @@ TODO: + @@ -950,6 +968,7 @@ TODO: + @@ -976,6 +995,7 @@ TODO: + @@ -1090,6 +1110,9 @@ TODO: + + + @@ -1128,6 +1151,8 @@ TODO: ============================================================================ --> + + @@ -1147,7 +1172,7 @@ TODO: - + @@ -1166,7 +1191,7 @@ TODO: - + @@ -1232,6 +1257,7 @@ TODO: + @@ -1244,6 +1270,10 @@ TODO: + + + + @@ -1568,6 +1598,12 @@ TODO: + + + + + + @@ -1611,7 +1647,7 @@ TODO: - + @@ -1667,6 +1703,7 @@ MAIN DISTRIBUTION PACKAGING + @@ -1731,30 +1768,35 @@ MAIN DISTRIBUTION PACKAGING + + + + + diff --git a/src/build/bnd/scala-typelevel.bnd b/src/build/bnd/scala-typelevel.bnd new file mode 100644 index 000000000000..7581dc382470 --- /dev/null +++ b/src/build/bnd/scala-typelevel.bnd @@ -0,0 +1,7 @@ +Bundle-Name: Scala Typelevel Library +Bundle-SymbolicName: org.scala-lang.scala-typelevel +ver: @VERSION@ +Bundle-Version: ${ver} +Export-Package: *;version=${ver} +Import-Package: scala.*;version="${range;[==,=+);@VERSION@}",* +Bundle-RequiredExecutionEnvironment: JavaSE-1.6, JavaSE-1.7 diff --git a/src/build/maven/scala-dist-pom.xml b/src/build/maven/scala-dist-pom.xml index 9477e1428538..1027ef45ed0c 100644 --- a/src/build/maven/scala-dist-pom.xml +++ b/src/build/maven/scala-dist-pom.xml @@ -34,6 +34,11 @@ scala-library-all @VERSION@ + + org.scala-lang + scala-typelevel + @VERSION@ + org.scala-lang scala-compiler diff --git a/src/build/maven/scala-typelevel-pom.xml b/src/build/maven/scala-typelevel-pom.xml new file mode 100644 index 000000000000..e46cb09577f8 --- /dev/null +++ b/src/build/maven/scala-typelevel-pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + org.scala-lang + scala-typelevel + jar + @VERSION@ + Scala Typelevel Library + Typelevel Library for the Typelevel Scala Compiler + http://typelevel.org/ + 2014 + + Typelevel + http://typelevel.org/ + + + + BSD 3-Clause + http://www.scala-lang.org/license.html + repo + + + + scm:git:git://github.com/typelevel/scala.git + https://github.com/typelevel/scala.git + + + GitHub + https://github.com/typelevel/scala/issues/ + + + http://www.scala-lang.org/api/@VERSION@/ + + + + org.scala-lang + scala-library + @VERSION@ + + + + + typelevel + Typelevel + + + diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala index 20e462bbce8f..f95c0a8af7a3 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala @@ -1195,7 +1195,8 @@ trait ContextErrors { import definitions._ - def AmbiguousImplicitError(info1: ImplicitInfo, info2: ImplicitInfo, + def AmbiguousImplicitError(info1: ImplicitInfo, tree1: Tree, + info2: ImplicitInfo, tree2: Tree, pre1: String, pre2: String, trailer: String) (isView: Boolean, pt: Type, tree: Tree)(implicit context0: Context) = { if (!info1.tpe.isErroneous && !info2.tpe.isErroneous) { @@ -1231,10 +1232,20 @@ trait ContextErrors { if (explanation == "") "" else "\n" + explanation ) } + + def treeTypeArgs(annotatedTree: Tree) = annotatedTree match { + case TypeApply(_, args) => args.map(_.toString) + case _ => Nil + } + context.issueAmbiguousError(AmbiguousImplicitTypeError(tree, - if (isView) viewMsg - else s"ambiguous implicit values:\n${coreMsg}match expected type $pt") - ) + (tree1.symbol, tree2.symbol) match { + case (ImplicitAmbiguousMsg(msg), _) => msg.format(treeTypeArgs(tree1)) + case (_, ImplicitAmbiguousMsg(msg)) => msg.format(treeTypeArgs(tree2)) + case (_, _) if isView => viewMsg + case (_, _) => s"ambiguous implicit values:\n${coreMsg}match expected type $pt" + } + )) } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index b85c8e6d42c6..168a6c0abb90 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -885,7 +885,7 @@ trait Implicits { * - find the most likely one * - if it matches, forget about all others it improves upon */ - @tailrec private def rankImplicits(pending: Infos, acc: Infos): Infos = pending match { + @tailrec private def rankImplicits(pending: Infos, acc: List[(SearchResult, ImplicitInfo)]): List[(SearchResult, ImplicitInfo)] = pending match { case Nil => acc case firstPending :: otherPending => def firstPendingImproves(alt: ImplicitInfo) = @@ -912,7 +912,7 @@ trait Implicits { val pendingImprovingBest = undoLog undo { otherPending filterNot firstPendingImproves } - rankImplicits(pendingImprovingBest, firstPending :: acc) + rankImplicits(pendingImprovingBest, (newBest, firstPending) :: acc) } } @@ -928,14 +928,14 @@ trait Implicits { // So if there is any element not improved upon by the first it is an error. rankImplicits(eligible, Nil) match { case Nil => () - case chosen :: rest => - rest find (alt => !improves(chosen, alt)) match { - case Some(competing) => - AmbiguousImplicitError(chosen, competing, "both", "and", "")(isView, pt, tree)(context) + case (chosenResult, chosenInfo) :: rest => + rest find { case (_, alt) => !improves(chosenInfo, alt) } match { + case Some((competingResult, competingInfo)) => + AmbiguousImplicitError(chosenInfo, chosenResult.tree, competingInfo, competingResult.tree, "both", "and", "")(isView, pt, tree)(context) return AmbiguousSearchFailure // Stop the search once ambiguity is encountered, see t4457_2.scala case _ => - if (isView) chosen.useCountView += 1 - else chosen.useCountArg += 1 + if (isView) chosenInfo.useCountView += 1 + else chosenInfo.useCountArg += 1 } } @@ -1448,9 +1448,9 @@ trait Implicits { } } - object ImplicitNotFoundMsg { - def unapply(sym: Symbol): Option[(Message)] = sym.implicitNotFoundMsg match { - case Some(m) => Some(new Message(sym, m)) + class ImplicitAnnotationMsg(f: Symbol => Option[String], clazz: Symbol, annotationName: String) { + def unapply(sym: Symbol): Option[(Message)] = f(sym) match { + case Some(m) => Some(new Message(sym, m, annotationName)) case None if sym.isAliasType => // perform exactly one step of dealiasing // this is necessary because ClassManifests are now aliased to ClassTags @@ -1462,39 +1462,43 @@ trait Implicits { // check the message's syntax: should be a string literal that may contain occurrences of the string "${X}", // where `X` refers to a type parameter of `sym` def check(sym: Symbol): Option[String] = - sym.getAnnotation(ImplicitNotFoundClass).flatMap(_.stringArg(0) match { - case Some(m) => new Message(sym, m).validate - case None => Some("Missing argument `msg` on implicitNotFound annotation.") + sym.getAnnotation(clazz).flatMap(_.stringArg(0) match { + case Some(m) => new Message(sym, m, annotationName).validate + case None => Some(s"Missing argument `msg` on $annotationName annotation.") }) + } + + object ImplicitNotFoundMsg extends ImplicitAnnotationMsg(_.implicitNotFoundMsg, ImplicitNotFoundClass, "implicitNotFound") + + object ImplicitAmbiguousMsg extends ImplicitAnnotationMsg(_.implicitAmbiguousMsg, ImplicitAmbiguousClass, "implicitAmbiguous") + class Message(sym: Symbol, msg: String, annotationName: String) { // http://dcsobral.blogspot.com/2010/01/string-interpolation-in-scala-with.html private val Intersobralator = """\$\{\s*([^}\s]+)\s*\}""".r - class Message(sym: Symbol, msg: String) { - private def interpolate(text: String, vars: Map[String, String]) = - Intersobralator.replaceAllIn(text, (_: Regex.Match) match { - case Regex.Groups(v) => Regex quoteReplacement vars.getOrElse(v, "") + private def interpolate(text: String, vars: Map[String, String]) = + Intersobralator.replaceAllIn(text, (_: Regex.Match) match { + case Regex.Groups(v) => Regex quoteReplacement vars.getOrElse(v, "") // #3915: need to quote replacement string since it may include $'s (such as the interpreter's $iw) - }) + }) - private lazy val typeParamNames: List[String] = sym.typeParams.map(_.decodedName) + private lazy val typeParamNames: List[String] = sym.typeParams.map(_.decodedName) - def format(paramName: Name, paramTp: Type): String = format(paramTp.typeArgs map (_.toString)) - def format(typeArgs: List[String]): String = - interpolate(msg, Map((typeParamNames zip typeArgs): _*)) // TODO: give access to the name and type of the implicit argument, etc? + def format(paramName: Name, paramTp: Type): String = format(paramTp.typeArgs map (_.toString)) + def format(typeArgs: List[String]): String = + interpolate(msg, Map((typeParamNames zip typeArgs): _*)) // TODO: give access to the name and type of the implicit argument, etc? - def validate: Option[String] = { - val refs = Intersobralator.findAllMatchIn(msg).map(_ group 1).toSet - val decls = typeParamNames.toSet + def validate: Option[String] = { + val refs = Intersobralator.findAllMatchIn(msg).map(_ group 1).toSet + val decls = typeParamNames.toSet - (refs &~ decls) match { - case s if s.isEmpty => None - case unboundNames => - val singular = unboundNames.size == 1 - val ess = if (singular) "" else "s" - val bee = if (singular) "is" else "are" - Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @implicitNotFound annotation $bee not defined by $sym.") - } + (refs &~ decls) match { + case s if s.isEmpty => None + case unboundNames => + val singular = unboundNames.size == 1 + val ess = if (singular) "" else "s" + val bee = if (singular) "is" else "are" + Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @$annotationName annotation $bee not defined by $sym.") } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index af4e9e892712..8cdd59dfdef6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1468,10 +1468,13 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans case m: MemberDef => val sym = m.symbol applyChecks(sym.annotations) - // validate implicitNotFoundMessage - analyzer.ImplicitNotFoundMsg.check(sym) foreach { warn => - reporter.warning(tree.pos, f"Invalid implicitNotFound message for ${sym}%s${sym.locationString}%s:%n$warn") - } + + def messageWarning(name: String)(warn: String) = + reporter.warning(tree.pos, f"Invalid $name message for ${sym}%s${sym.locationString}%s:%n$warn") + + // validate implicitNotFoundMessage and implicitAmbiguousMessage + analyzer.ImplicitNotFoundMsg.check(sym) foreach messageWarning("implicitNotFound") + analyzer.ImplicitAmbiguousMsg.check(sym) foreach messageWarning("implicitAmbiguous") case tpt@TypeTree() => if(tpt.original != null) { diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 02578e20386f..d6f9e3de06d8 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -1091,6 +1091,7 @@ trait Definitions extends api.StandardDefinitions { lazy val BridgeClass = requiredClass[scala.annotation.bridge] lazy val ElidableMethodClass = requiredClass[scala.annotation.elidable] lazy val ImplicitNotFoundClass = requiredClass[scala.annotation.implicitNotFound] + lazy val ImplicitAmbiguousClass = getClassIfDefined("scala.typelevel.annotation.implicitAmbiguous") lazy val MigrationAnnotationClass = requiredClass[scala.annotation.migration] lazy val ScalaStrictFPAttr = requiredClass[scala.annotation.strictfp] lazy val SwitchClass = requiredClass[scala.annotation.switch] diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 2670faa22d17..9ad68d4db14b 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -865,10 +865,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => // string. So this needs attention. For now the fact that migration is // private[scala] ought to provide enough protection. def hasMigrationAnnotation = hasAnnotation(MigrationAnnotationClass) - def migrationMessage = getAnnotation(MigrationAnnotationClass) flatMap { _.stringArg(0) } - def migrationVersion = getAnnotation(MigrationAnnotationClass) flatMap { _.stringArg(1) } - def elisionLevel = getAnnotation(ElidableMethodClass) flatMap { _.intArg(0) } - def implicitNotFoundMsg = getAnnotation(ImplicitNotFoundClass) flatMap { _.stringArg(0) } + def migrationMessage = getAnnotation(MigrationAnnotationClass) flatMap { _.stringArg(0) } + def migrationVersion = getAnnotation(MigrationAnnotationClass) flatMap { _.stringArg(1) } + def elisionLevel = getAnnotation(ElidableMethodClass) flatMap { _.intArg(0) } + def implicitNotFoundMsg = getAnnotation(ImplicitNotFoundClass) flatMap { _.stringArg(0) } + def implicitAmbiguousMsg = getAnnotation(ImplicitAmbiguousClass) flatMap { _.stringArg(0) } def isCompileTimeOnly = hasAnnotation(CompileTimeOnlyAttr) def compileTimeOnlyMessage = getAnnotation(CompileTimeOnlyAttr) flatMap (_ stringArg 0) diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index dcd262c288cc..28f11de91cfa 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -363,6 +363,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.BridgeClass definitions.ElidableMethodClass definitions.ImplicitNotFoundClass + definitions.ImplicitAmbiguousClass definitions.MigrationAnnotationClass definitions.ScalaStrictFPAttr definitions.SwitchClass diff --git a/src/typelevel/scala/typelevel/annotation/implicitAmbiguous.scala b/src/typelevel/scala/typelevel/annotation/implicitAmbiguous.scala new file mode 100644 index 000000000000..4f560ca7ef21 --- /dev/null +++ b/src/typelevel/scala/typelevel/annotation/implicitAmbiguous.scala @@ -0,0 +1,6 @@ +package scala.typelevel.annotation + +import scala.annotation.meta._ + +@getter +final class implicitAmbiguous(msg: String) extends scala.annotation.StaticAnnotation {} diff --git a/test/files/neg/implicit-ambiguous-invalid.check b/test/files/neg/implicit-ambiguous-invalid.check new file mode 100644 index 000000000000..68b607c4c28d --- /dev/null +++ b/test/files/neg/implicit-ambiguous-invalid.check @@ -0,0 +1,7 @@ +implicit-ambiguous-invalid.scala:5: warning: Invalid implicitAmbiguous message for method neqAmbig1 in object Test: +The type parameter B referenced in the message of the @implicitAmbiguous annotation is not defined by method neqAmbig1. + implicit def neqAmbig1[A] : A =!= A = null + ^ +error: No warnings can be incurred under -Xfatal-warnings. +one warning found +one error found diff --git a/test/files/neg/implicit-ambiguous-invalid.flags b/test/files/neg/implicit-ambiguous-invalid.flags new file mode 100644 index 000000000000..85d8eb2ba295 --- /dev/null +++ b/test/files/neg/implicit-ambiguous-invalid.flags @@ -0,0 +1 @@ +-Xfatal-warnings diff --git a/test/files/neg/implicit-ambiguous-invalid.scala b/test/files/neg/implicit-ambiguous-invalid.scala new file mode 100644 index 000000000000..4910f68a1417 --- /dev/null +++ b/test/files/neg/implicit-ambiguous-invalid.scala @@ -0,0 +1,6 @@ +object Test { + trait =!=[C, D] + + @typelevel.annotation.implicitAmbiguous("Could not prove ${A} =!= ${B}") + implicit def neqAmbig1[A] : A =!= A = null +} diff --git a/test/files/neg/implicit-ambiguous.check b/test/files/neg/implicit-ambiguous.check new file mode 100644 index 000000000000..0b3cebcb6fd1 --- /dev/null +++ b/test/files/neg/implicit-ambiguous.check @@ -0,0 +1,4 @@ +implicit-ambiguous.scala:10: error: Could not prove Int =!= Int + implicitly[Int =!= Int] + ^ +one error found diff --git a/test/files/neg/implicit-ambiguous.scala b/test/files/neg/implicit-ambiguous.scala new file mode 100644 index 000000000000..6c5285dbdc70 --- /dev/null +++ b/test/files/neg/implicit-ambiguous.scala @@ -0,0 +1,11 @@ +object Test { + trait =!=[C, D] + + implicit def neq[E, F] : E =!= F = null + + @typelevel.annotation.implicitAmbiguous("Could not prove ${J} =!= ${J}") + implicit def neqAmbig1[G, H, J] : J =!= J = null + implicit def neqAmbig2[I] : I =!= I = null + + implicitly[Int =!= Int] +}