Skip to content
This repository was archived by the owner on Sep 1, 2020. It is now read-only.

Commit 4d4f703

Browse files
committed
Add an @implicitAmbiguous annotation
Example usage: trait =!=[C, D] implicit def neq[E, F] : E =!= F = null @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] Which gives the following error: implicit-ambiguous.scala:9: error: Could not prove Int =!= Int implicitly[Int =!= Int] ^ Better than what was previously given: implicit-ambiguous.scala:9: error: ambiguous implicit values: both method neqAmbig1 in object Test of type [G, H, J]=> Main.$anon.Test.=!=[J,J] and method neqAmbig2 in object Test of type [I]=> Main.$anon.Test.=!=[I,I] match expected type Main.$anon.Test.=!=[Int,Int] implicitly[Int =!= Int] ^
1 parent 9afc167 commit 4d4f703

12 files changed

+102
-46
lines changed

src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala

+15-4
Original file line numberDiff line numberDiff line change
@@ -1195,7 +1195,8 @@ trait ContextErrors {
11951195

11961196
import definitions._
11971197

1198-
def AmbiguousImplicitError(info1: ImplicitInfo, info2: ImplicitInfo,
1198+
def AmbiguousImplicitError(info1: ImplicitInfo, tree1: Tree,
1199+
info2: ImplicitInfo, tree2: Tree,
11991200
pre1: String, pre2: String, trailer: String)
12001201
(isView: Boolean, pt: Type, tree: Tree)(implicit context0: Context) = {
12011202
if (!info1.tpe.isErroneous && !info2.tpe.isErroneous) {
@@ -1231,10 +1232,20 @@ trait ContextErrors {
12311232
if (explanation == "") "" else "\n" + explanation
12321233
)
12331234
}
1235+
1236+
def treeTypeArgs(annotatedTree: Tree) = annotatedTree match {
1237+
case TypeApply(_, args) => args.map(_.toString)
1238+
case _ => Nil
1239+
}
1240+
12341241
context.issueAmbiguousError(AmbiguousImplicitTypeError(tree,
1235-
if (isView) viewMsg
1236-
else s"ambiguous implicit values:\n${coreMsg}match expected type $pt")
1237-
)
1242+
(tree1.symbol, tree2.symbol) match {
1243+
case (ImplicitAmbiguousMsg(msg), _) => msg.format(treeTypeArgs(tree1))
1244+
case (_, ImplicitAmbiguousMsg(msg)) => msg.format(treeTypeArgs(tree2))
1245+
case (_, _) if isView => viewMsg
1246+
case (_, _) => s"ambiguous implicit values:\n${coreMsg}match expected type $pt"
1247+
}
1248+
))
12381249
}
12391250
}
12401251

src/compiler/scala/tools/nsc/typechecker/Implicits.scala

+38-34
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,7 @@ trait Implicits {
885885
* - find the most likely one
886886
* - if it matches, forget about all others it improves upon
887887
*/
888-
@tailrec private def rankImplicits(pending: Infos, acc: Infos): Infos = pending match {
888+
@tailrec private def rankImplicits(pending: Infos, acc: List[(SearchResult, ImplicitInfo)]): List[(SearchResult, ImplicitInfo)] = pending match {
889889
case Nil => acc
890890
case firstPending :: otherPending =>
891891
def firstPendingImproves(alt: ImplicitInfo) =
@@ -912,7 +912,7 @@ trait Implicits {
912912
val pendingImprovingBest = undoLog undo {
913913
otherPending filterNot firstPendingImproves
914914
}
915-
rankImplicits(pendingImprovingBest, firstPending :: acc)
915+
rankImplicits(pendingImprovingBest, (newBest, firstPending) :: acc)
916916
}
917917
}
918918

@@ -928,14 +928,14 @@ trait Implicits {
928928
// So if there is any element not improved upon by the first it is an error.
929929
rankImplicits(eligible, Nil) match {
930930
case Nil => ()
931-
case chosen :: rest =>
932-
rest find (alt => !improves(chosen, alt)) match {
933-
case Some(competing) =>
934-
AmbiguousImplicitError(chosen, competing, "both", "and", "")(isView, pt, tree)(context)
931+
case (chosenResult, chosenInfo) :: rest =>
932+
rest find { case (_, alt) => !improves(chosenInfo, alt) } match {
933+
case Some((competingResult, competingInfo)) =>
934+
AmbiguousImplicitError(chosenInfo, chosenResult.tree, competingInfo, competingResult.tree, "both", "and", "")(isView, pt, tree)(context)
935935
return AmbiguousSearchFailure // Stop the search once ambiguity is encountered, see t4457_2.scala
936936
case _ =>
937-
if (isView) chosen.useCountView += 1
938-
else chosen.useCountArg += 1
937+
if (isView) chosenInfo.useCountView += 1
938+
else chosenInfo.useCountArg += 1
939939
}
940940
}
941941

@@ -1448,9 +1448,9 @@ trait Implicits {
14481448
}
14491449
}
14501450

1451-
object ImplicitNotFoundMsg {
1452-
def unapply(sym: Symbol): Option[(Message)] = sym.implicitNotFoundMsg match {
1453-
case Some(m) => Some(new Message(sym, m))
1451+
class ImplicitAnnotationMsg(f: Symbol => Option[String], clazz: Symbol, annotationName: String) {
1452+
def unapply(sym: Symbol): Option[(Message)] = f(sym) match {
1453+
case Some(m) => Some(new Message(sym, m, annotationName))
14541454
case None if sym.isAliasType =>
14551455
// perform exactly one step of dealiasing
14561456
// this is necessary because ClassManifests are now aliased to ClassTags
@@ -1462,39 +1462,43 @@ trait Implicits {
14621462
// check the message's syntax: should be a string literal that may contain occurrences of the string "${X}",
14631463
// where `X` refers to a type parameter of `sym`
14641464
def check(sym: Symbol): Option[String] =
1465-
sym.getAnnotation(ImplicitNotFoundClass).flatMap(_.stringArg(0) match {
1466-
case Some(m) => new Message(sym, m).validate
1467-
case None => Some("Missing argument `msg` on implicitNotFound annotation.")
1465+
sym.getAnnotation(clazz).flatMap(_.stringArg(0) match {
1466+
case Some(m) => new Message(sym, m, annotationName).validate
1467+
case None => Some(s"Missing argument `msg` on $annotationName annotation.")
14681468
})
1469+
}
1470+
1471+
object ImplicitNotFoundMsg extends ImplicitAnnotationMsg(_.implicitNotFoundMsg, ImplicitNotFoundClass, "implicitNotFound")
1472+
1473+
object ImplicitAmbiguousMsg extends ImplicitAnnotationMsg(_.implicitAmbiguousMsg, ImplicitAmbiguousClass, "implicitAmbiguous")
14691474

1475+
class Message(sym: Symbol, msg: String, annotationName: String) {
14701476
// http://dcsobral.blogspot.com/2010/01/string-interpolation-in-scala-with.html
14711477
private val Intersobralator = """\$\{\s*([^}\s]+)\s*\}""".r
14721478

1473-
class Message(sym: Symbol, msg: String) {
1474-
private def interpolate(text: String, vars: Map[String, String]) =
1475-
Intersobralator.replaceAllIn(text, (_: Regex.Match) match {
1476-
case Regex.Groups(v) => Regex quoteReplacement vars.getOrElse(v, "")
1479+
private def interpolate(text: String, vars: Map[String, String]) =
1480+
Intersobralator.replaceAllIn(text, (_: Regex.Match) match {
1481+
case Regex.Groups(v) => Regex quoteReplacement vars.getOrElse(v, "")
14771482
// #3915: need to quote replacement string since it may include $'s (such as the interpreter's $iw)
1478-
})
1483+
})
14791484

1480-
private lazy val typeParamNames: List[String] = sym.typeParams.map(_.decodedName)
1485+
private lazy val typeParamNames: List[String] = sym.typeParams.map(_.decodedName)
14811486

1482-
def format(paramName: Name, paramTp: Type): String = format(paramTp.typeArgs map (_.toString))
1483-
def format(typeArgs: List[String]): String =
1484-
interpolate(msg, Map((typeParamNames zip typeArgs): _*)) // TODO: give access to the name and type of the implicit argument, etc?
1487+
def format(paramName: Name, paramTp: Type): String = format(paramTp.typeArgs map (_.toString))
1488+
def format(typeArgs: List[String]): String =
1489+
interpolate(msg, Map((typeParamNames zip typeArgs): _*)) // TODO: give access to the name and type of the implicit argument, etc?
14851490

1486-
def validate: Option[String] = {
1487-
val refs = Intersobralator.findAllMatchIn(msg).map(_ group 1).toSet
1488-
val decls = typeParamNames.toSet
1491+
def validate: Option[String] = {
1492+
val refs = Intersobralator.findAllMatchIn(msg).map(_ group 1).toSet
1493+
val decls = typeParamNames.toSet
14891494

1490-
(refs &~ decls) match {
1491-
case s if s.isEmpty => None
1492-
case unboundNames =>
1493-
val singular = unboundNames.size == 1
1494-
val ess = if (singular) "" else "s"
1495-
val bee = if (singular) "is" else "are"
1496-
Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @implicitNotFound annotation $bee not defined by $sym.")
1497-
}
1495+
(refs &~ decls) match {
1496+
case s if s.isEmpty => None
1497+
case unboundNames =>
1498+
val singular = unboundNames.size == 1
1499+
val ess = if (singular) "" else "s"
1500+
val bee = if (singular) "is" else "are"
1501+
Some(s"The type parameter$ess ${unboundNames mkString ", "} referenced in the message of the @$annotationName annotation $bee not defined by $sym.")
14981502
}
14991503
}
15001504
}

src/compiler/scala/tools/nsc/typechecker/RefChecks.scala

+7-4
Original file line numberDiff line numberDiff line change
@@ -1468,10 +1468,13 @@ abstract class RefChecks extends InfoTransform with scala.reflect.internal.trans
14681468
case m: MemberDef =>
14691469
val sym = m.symbol
14701470
applyChecks(sym.annotations)
1471-
// validate implicitNotFoundMessage
1472-
analyzer.ImplicitNotFoundMsg.check(sym) foreach { warn =>
1473-
reporter.warning(tree.pos, f"Invalid implicitNotFound message for ${sym}%s${sym.locationString}%s:%n$warn")
1474-
}
1471+
1472+
def messageWarning(name: String)(warn: String) =
1473+
reporter.warning(tree.pos, f"Invalid $name message for ${sym}%s${sym.locationString}%s:%n$warn")
1474+
1475+
// validate implicitNotFoundMessage and implicitAmbiguousMessage
1476+
analyzer.ImplicitNotFoundMsg.check(sym) foreach messageWarning("implicitNotFound")
1477+
analyzer.ImplicitAmbiguousMsg.check(sym) foreach messageWarning("implicitAmbiguous")
14751478

14761479
case tpt@TypeTree() =>
14771480
if(tpt.original != null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package scala.annotation
2+
3+
import scala.annotation.meta._
4+
5+
@getter
6+
final class implicitAmbiguous(msg: String) extends scala.annotation.StaticAnnotation {}

src/reflect/scala/reflect/internal/Definitions.scala

+1
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,7 @@ trait Definitions extends api.StandardDefinitions {
10911091
lazy val BridgeClass = requiredClass[scala.annotation.bridge]
10921092
lazy val ElidableMethodClass = requiredClass[scala.annotation.elidable]
10931093
lazy val ImplicitNotFoundClass = requiredClass[scala.annotation.implicitNotFound]
1094+
lazy val ImplicitAmbiguousClass = requiredClass[scala.annotation.implicitAmbiguous]
10941095
lazy val MigrationAnnotationClass = requiredClass[scala.annotation.migration]
10951096
lazy val ScalaStrictFPAttr = requiredClass[scala.annotation.strictfp]
10961097
lazy val SwitchClass = requiredClass[scala.annotation.switch]

src/reflect/scala/reflect/internal/Symbols.scala

+5-4
Original file line numberDiff line numberDiff line change
@@ -865,10 +865,11 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
865865
// string. So this needs attention. For now the fact that migration is
866866
// private[scala] ought to provide enough protection.
867867
def hasMigrationAnnotation = hasAnnotation(MigrationAnnotationClass)
868-
def migrationMessage = getAnnotation(MigrationAnnotationClass) flatMap { _.stringArg(0) }
869-
def migrationVersion = getAnnotation(MigrationAnnotationClass) flatMap { _.stringArg(1) }
870-
def elisionLevel = getAnnotation(ElidableMethodClass) flatMap { _.intArg(0) }
871-
def implicitNotFoundMsg = getAnnotation(ImplicitNotFoundClass) flatMap { _.stringArg(0) }
868+
def migrationMessage = getAnnotation(MigrationAnnotationClass) flatMap { _.stringArg(0) }
869+
def migrationVersion = getAnnotation(MigrationAnnotationClass) flatMap { _.stringArg(1) }
870+
def elisionLevel = getAnnotation(ElidableMethodClass) flatMap { _.intArg(0) }
871+
def implicitNotFoundMsg = getAnnotation(ImplicitNotFoundClass) flatMap { _.stringArg(0) }
872+
def implicitAmbiguousMsg = getAnnotation(ImplicitAmbiguousClass) flatMap { _.stringArg(0) }
872873

873874
def isCompileTimeOnly = hasAnnotation(CompileTimeOnlyAttr)
874875
def compileTimeOnlyMessage = getAnnotation(CompileTimeOnlyAttr) flatMap (_ stringArg 0)

src/reflect/scala/reflect/runtime/JavaUniverseForce.scala

+1
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
363363
definitions.BridgeClass
364364
definitions.ElidableMethodClass
365365
definitions.ImplicitNotFoundClass
366+
definitions.ImplicitAmbiguousClass
366367
definitions.MigrationAnnotationClass
367368
definitions.ScalaStrictFPAttr
368369
definitions.SwitchClass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
implicit-ambiguous-invalid.scala:5: warning: Invalid implicitAmbiguous message for method neqAmbig1 in object Test:
2+
The type parameter B referenced in the message of the @implicitAmbiguous annotation is not defined by method neqAmbig1.
3+
implicit def neqAmbig1[A] : A =!= A = null
4+
^
5+
error: No warnings can be incurred under -Xfatal-warnings.
6+
one warning found
7+
one error found
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xfatal-warnings
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
object Test {
2+
trait =!=[C, D]
3+
4+
@annotation.implicitAmbiguous("Could not prove ${A} =!= ${B}")
5+
implicit def neqAmbig1[A] : A =!= A = null
6+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
implicit-ambiguous.scala:10: error: Could not prove Int =!= Int
2+
implicitly[Int =!= Int]
3+
^
4+
one error found
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
object Test {
2+
trait =!=[C, D]
3+
4+
implicit def neq[E, F] : E =!= F = null
5+
6+
@annotation.implicitAmbiguous("Could not prove ${J} =!= ${J}")
7+
implicit def neqAmbig1[G, H, J] : J =!= J = null
8+
implicit def neqAmbig2[I] : I =!= I = null
9+
10+
implicitly[Int =!= Int]
11+
}

0 commit comments

Comments
 (0)