Skip to content

Commit a550d09

Browse files
committed
fix #3797 Add support for @implicitAmbiguous
1 parent b24f80d commit a550d09

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
@@ -670,6 +670,8 @@ class Definitions {
670670
def ContravariantBetweenAnnot(implicit ctx: Context) = ContravariantBetweenAnnotType.symbol.asClass
671671
lazy val DeprecatedAnnotType = ctx.requiredClassRef("scala.deprecated")
672672
def DeprecatedAnnot(implicit ctx: Context) = DeprecatedAnnotType.symbol.asClass
673+
lazy val ImplicitAmbiguousAnnotType = ctx.requiredClassRef("scala.annotation.implicitAmbiguous")
674+
def ImplicitAmbiguousAnnot(implicit ctx: Context) = ImplicitAmbiguousAnnotType.symbol.asClass
673675
lazy val ImplicitNotFoundAnnotType = ctx.requiredClassRef("scala.annotation.implicitNotFound")
674676
def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass
675677
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
@@ -159,11 +159,11 @@ object ErrorReporting {
159159
TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript)
160160
}
161161

162-
/** Format `raw` implicitNotFound argument, replacing all
163-
* occurrences of `${X}` where `X` is in `paramNames` with the
162+
/** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing
163+
* all occurrences of `${X}` where `X` is in `paramNames` with the
164164
* corresponding shown type in `args`.
165165
*/
166-
def implicitNotFoundString(raw: String, paramNames: List[String], args: List[Type]): String = {
166+
def userDefinedErrorString(raw: String, paramNames: List[String], args: List[Type]): String = {
167167
def translate(name: String): Option[String] = {
168168
val idx = paramNames.indexOf(name)
169169
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._
@@ -697,22 +698,47 @@ trait Implicits { self: Typer =>
697698
}
698699
}
699700
def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where"
701+
def userDefinedMessage(annot: Annotation, params: List[String], args: List[Type]): Option[String] =
702+
for (Trees.Literal(Constant(raw: String)) <- annot.argument(0)) yield {
703+
err.userDefinedErrorString(
704+
raw,
705+
params,
706+
args)
707+
}
700708
arg.tpe match {
701709
case ambi: AmbiguousImplicits =>
702-
msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")(
703-
s"ambiguous implicit arguments of type ${pt.show} found${location("for")}")
704-
case _ =>
705-
val userDefined =
706-
for {
707-
notFound <- pt.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot)
708-
Trees.Literal(Constant(raw: String)) <- notFound.argument(0)
710+
val maybeAnnot = ambi.alt1.ref.symbol.getAnnotation(defn.ImplicitAmbiguousAnnot).map(
711+
(_, ambi.alt1)
712+
).orElse(
713+
ambi.alt2.ref.symbol.getAnnotation(defn.ImplicitAmbiguousAnnot).map(
714+
(_, ambi.alt2)
715+
)
716+
)
717+
val userDefined = maybeAnnot.flatMap { case (annot, alt) =>
718+
val params = alt.ref.underlying match {
719+
case p: PolyType => p.paramNames.map(_.toString)
720+
case _ => Nil
709721
}
710-
yield {
711-
err.implicitNotFoundString(
712-
raw,
713-
pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
714-
pt.argInfos)
722+
def resolveTypes(targs: List[Tree])(implicit ctx: Context) =
723+
targs.map(a => fullyDefinedType(a.tpe, "type argument", a.pos))
724+
val args = alt.tree match {
725+
case TypeApply(_, targs) =>
726+
resolveTypes(targs)(ctx.fresh.setTyperState(alt.tstate))
727+
case Block(List(DefDef(_, _, _, _, Apply(TypeApply(_, targs), _))), _) =>
728+
resolveTypes(targs.asInstanceOf[List[Tree]])(ctx.fresh.setTyperState(alt.tstate))
729+
case _ =>
730+
Nil
715731
}
732+
userDefinedMessage(annot, params, args)
733+
}
734+
userDefined.map(msg(_)()).getOrElse(
735+
msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")(
736+
s"ambiguous implicit arguments of type ${pt.show} found${location("for")}")
737+
)
738+
case _ =>
739+
val userDefined = pt.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot).flatMap(
740+
userDefinedMessage(_, pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString), pt.argInfos)
741+
)
716742
msg(userDefined.getOrElse(em"no implicit argument of type $pt was found${location("for")}"))()
717743
}
718744
}

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

13071307
assert(ctx.reporter.hasErrors)
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)