Skip to content

Commit d656ff2

Browse files
Jasper-Mallanrenucci
authored andcommitted
fix #3797 Add support for @implicitAmbiguous
1 parent 911e388 commit d656ff2

File tree

4 files changed

+145
-15
lines changed

4 files changed

+145
-15
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,8 @@ class Definitions {
679679
def ContravariantBetweenAnnot(implicit ctx: Context) = ContravariantBetweenAnnotType.symbol.asClass
680680
lazy val DeprecatedAnnotType = ctx.requiredClassRef("scala.deprecated")
681681
def DeprecatedAnnot(implicit ctx: Context) = DeprecatedAnnotType.symbol.asClass
682+
lazy val ImplicitAmbiguousAnnotType = ctx.requiredClassRef("scala.annotation.implicitAmbiguous")
683+
def ImplicitAmbiguousAnnot(implicit ctx: Context) = ImplicitAmbiguousAnnotType.symbol.asClass
682684
lazy val ImplicitNotFoundAnnotType = ctx.requiredClassRef("scala.annotation.implicitNotFound")
683685
def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass
684686
lazy val InlineAnnotType = ctx.requiredClassRef("scala.inline")

compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,11 @@ object ErrorReporting {
160160
TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript)
161161
}
162162

163-
/** Format `raw` implicitNotFound argument, replacing all
164-
* occurrences of `${X}` where `X` is in `paramNames` with the
163+
/** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing
164+
* all occurrences of `${X}` where `X` is in `paramNames` with the
165165
* corresponding shown type in `args`.
166166
*/
167-
def implicitNotFoundString(raw: String, paramNames: List[String], args: List[Type]): String = {
167+
def userDefinedErrorString(raw: String, paramNames: List[String], args: List[Type]): String = {
168168
def translate(name: String): Option[String] = {
169169
val idx = paramNames.indexOf(name)
170170
if (idx >= 0) Some(quoteReplacement(ex"${args(idx)}")) else None

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import Constants._
2626
import Applications._
2727
import ProtoTypes._
2828
import ErrorReporting._
29+
import Annotations.Annotation
2930
import reporting.diagnostic.{Message, MessageContainer}
3031
import Inferencing.fullyDefinedType
3132
import Trees._
@@ -685,22 +686,47 @@ trait Implicits { self: Typer =>
685686
}
686687
}
687688
def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where"
689+
def userDefinedMessage(annot: Annotation, params: List[String], args: List[Type]): Option[String] =
690+
for (Trees.Literal(Constant(raw: String)) <- annot.argument(0)) yield {
691+
err.userDefinedErrorString(
692+
raw,
693+
params,
694+
args)
695+
}
688696
arg.tpe match {
689697
case ambi: AmbiguousImplicits =>
690-
msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")(
691-
s"ambiguous implicit arguments of type ${pt.show} found${location("for")}")
692-
case _ =>
693-
val userDefined =
694-
for {
695-
notFound <- pt.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot)
696-
Trees.Literal(Constant(raw: String)) <- notFound.argument(0)
698+
val maybeAnnot = ambi.alt1.ref.symbol.getAnnotation(defn.ImplicitAmbiguousAnnot).map(
699+
(_, ambi.alt1)
700+
).orElse(
701+
ambi.alt2.ref.symbol.getAnnotation(defn.ImplicitAmbiguousAnnot).map(
702+
(_, ambi.alt2)
703+
)
704+
)
705+
val userDefined = maybeAnnot.flatMap { case (annot, alt) =>
706+
val params = alt.ref.underlying match {
707+
case p: PolyType => p.paramNames.map(_.toString)
708+
case _ => Nil
697709
}
698-
yield {
699-
err.implicitNotFoundString(
700-
raw,
701-
pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
702-
pt.argInfos)
710+
def resolveTypes(targs: List[Tree])(implicit ctx: Context) =
711+
targs.map(a => fullyDefinedType(a.tpe, "type argument", a.pos))
712+
val args = alt.tree match {
713+
case TypeApply(_, targs) =>
714+
resolveTypes(targs)(ctx.fresh.setTyperState(alt.tstate))
715+
case Block(List(DefDef(_, _, _, _, Apply(TypeApply(_, targs), _))), _) =>
716+
resolveTypes(targs.asInstanceOf[List[Tree]])(ctx.fresh.setTyperState(alt.tstate))
717+
case _ =>
718+
Nil
703719
}
720+
userDefinedMessage(annot, params, args)
721+
}
722+
userDefined.map(msg(_)()).getOrElse(
723+
msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")(
724+
s"ambiguous implicit arguments of type ${pt.show} found${location("for")}")
725+
)
726+
case _ =>
727+
val userDefined = pt.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot).flatMap(
728+
userDefinedMessage(_, pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString), pt.argInfos)
729+
)
704730
msg(userDefined.getOrElse(em"no implicit argument of type $pt was found${location("for")}"))()
705731
}
706732
}

compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,4 +1306,106 @@ class ErrorMessagesTests extends ErrorMessagesTest {
13061306
val DoubleDeclaration(symbol, previousSymbol) :: Nil = messages
13071307
assertEquals(symbol.name.mangledString, "a")
13081308
}
1309+
1310+
@Test def userDefinedImplicitAmbiguous1 =
1311+
checkMessagesAfter("frontend") {
1312+
"""
1313+
|object Test {
1314+
| trait =!=[C, D]
1315+
|
1316+
| implicit def neq[E, F] : E =!= F = null
1317+
|
1318+
| @annotation.implicitAmbiguous("Could not prove ${J} =!= ${J}")
1319+
| implicit def neqAmbig1[G, H, J] : J =!= J = null
1320+
| implicit def neqAmbig2[I] : I =!= I = null
1321+
|
1322+
| implicitly[Int =!= Int]
1323+
|}
1324+
1325+
""".stripMargin
1326+
}.expect { (itcx, messages) =>
1327+
import diagnostic.NoExplanation
1328+
implicit val ctx: Context = itcx
1329+
1330+
assertMessageCount(1, messages)
1331+
val (m: NoExplanation) :: Nil = messages
1332+
1333+
assertEquals(m.msg, "Could not prove Int =!= Int")
1334+
}
1335+
1336+
@Test def userDefinedImplicitAmbiguous2 =
1337+
checkMessagesAfter("frontend") {
1338+
"""
1339+
|object Test {
1340+
| trait =!=[C, D]
1341+
|
1342+
| implicit def neq[E, F] : E =!= F = null
1343+
|
1344+
| implicit def neqAmbig1[G, H, J] : J =!= J = null
1345+
| @annotation.implicitAmbiguous("Could not prove ${I} =!= ${I}")
1346+
| implicit def neqAmbig2[I] : I =!= I = null
1347+
|
1348+
| implicitly[Int =!= Int]
1349+
|}
1350+
1351+
""".stripMargin
1352+
}.expect { (itcx, messages) =>
1353+
import diagnostic.NoExplanation
1354+
implicit val ctx: Context = itcx
1355+
1356+
assertMessageCount(1, messages)
1357+
val (m: NoExplanation) :: Nil = messages
1358+
1359+
assertEquals(m.msg, "Could not prove Int =!= Int")
1360+
}
1361+
1362+
@Test def userDefinedImplicitAmbiguous3 =
1363+
checkMessagesAfter("frontend") {
1364+
"""
1365+
|object Test {
1366+
| trait =!=[C, D]
1367+
|
1368+
| implicit def neq[E, F] : E =!= F = null
1369+
|
1370+
| @annotation.implicitAmbiguous("Could not prove ${J} =!= ${J}")
1371+
| implicit def neqAmbig1[G, H, J] : J =!= J = null
1372+
| @annotation.implicitAmbiguous("Could not prove ${I} =!= ${I}")
1373+
| implicit def neqAmbig2[I] : I =!= I = null
1374+
|
1375+
| implicitly[Int =!= Int]
1376+
|}
1377+
1378+
""".stripMargin
1379+
}.expect { (itcx, messages) =>
1380+
import diagnostic.NoExplanation
1381+
implicit val ctx: Context = itcx
1382+
1383+
assertMessageCount(1, messages)
1384+
val (m: NoExplanation) :: Nil = messages
1385+
1386+
assertEquals(m.msg, "Could not prove Int =!= Int")
1387+
}
1388+
1389+
@Test def userDefinedImplicitAmbiguous4 =
1390+
checkMessagesAfter("frontend") {
1391+
"""
1392+
|class C {
1393+
| @annotation.implicitAmbiguous("msg A=${A}")
1394+
| implicit def f[A](x: Int): String = "f was here"
1395+
| implicit def g(x: Int): String = "f was here"
1396+
| def test: Unit = {
1397+
| implicitly[Int => String]
1398+
| }
1399+
|}
1400+
1401+
""".stripMargin
1402+
}.expect { (itcx, messages) =>
1403+
import diagnostic.NoExplanation
1404+
implicit val ctx: Context = itcx
1405+
1406+
assertMessageCount(1, messages)
1407+
val (m: NoExplanation) :: Nil = messages
1408+
1409+
assertEquals(m.msg, "msg A=Any")
1410+
}
13091411
}

0 commit comments

Comments
 (0)