From d656ff25b4dab5ccf5e912de6299c0fc11b6e24e Mon Sep 17 00:00:00 2001 From: Jasper Moeys Date: Fri, 16 Feb 2018 12:58:12 +0100 Subject: [PATCH 1/4] fix #3797 Add support for @implicitAmbiguous --- .../dotty/tools/dotc/core/Definitions.scala | 2 + .../tools/dotc/typer/ErrorReporting.scala | 6 +- .../dotty/tools/dotc/typer/Implicits.scala | 50 ++++++--- .../dotc/reporting/ErrorMessagesTests.scala | 102 ++++++++++++++++++ 4 files changed, 145 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index f876f7a70c77..75a7b560945c 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -679,6 +679,8 @@ class Definitions { def ContravariantBetweenAnnot(implicit ctx: Context) = ContravariantBetweenAnnotType.symbol.asClass lazy val DeprecatedAnnotType = ctx.requiredClassRef("scala.deprecated") def DeprecatedAnnot(implicit ctx: Context) = DeprecatedAnnotType.symbol.asClass + lazy val ImplicitAmbiguousAnnotType = ctx.requiredClassRef("scala.annotation.implicitAmbiguous") + def ImplicitAmbiguousAnnot(implicit ctx: Context) = ImplicitAmbiguousAnnotType.symbol.asClass lazy val ImplicitNotFoundAnnotType = ctx.requiredClassRef("scala.annotation.implicitNotFound") def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass lazy val InlineAnnotType = ctx.requiredClassRef("scala.inline") diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 3b8f18001c15..6e03384e31e1 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -160,11 +160,11 @@ object ErrorReporting { TypeMismatch(found2, expected2, whyNoMatchStr(found, expected), postScript) } - /** Format `raw` implicitNotFound argument, replacing all - * occurrences of `${X}` where `X` is in `paramNames` with the + /** Format `raw` implicitNotFound or implicitAmbiguous argument, replacing + * all occurrences of `${X}` where `X` is in `paramNames` with the * corresponding shown type in `args`. */ - def implicitNotFoundString(raw: String, paramNames: List[String], args: List[Type]): String = { + def userDefinedErrorString(raw: String, paramNames: List[String], args: List[Type]): String = { def translate(name: String): Option[String] = { val idx = paramNames.indexOf(name) if (idx >= 0) Some(quoteReplacement(ex"${args(idx)}")) else None diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 31f23a3327e5..273d1e039190 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -26,6 +26,7 @@ import Constants._ import Applications._ import ProtoTypes._ import ErrorReporting._ +import Annotations.Annotation import reporting.diagnostic.{Message, MessageContainer} import Inferencing.fullyDefinedType import Trees._ @@ -685,22 +686,47 @@ trait Implicits { self: Typer => } } def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where" + def userDefinedMessage(annot: Annotation, params: List[String], args: List[Type]): Option[String] = + for (Trees.Literal(Constant(raw: String)) <- annot.argument(0)) yield { + err.userDefinedErrorString( + raw, + params, + args) + } arg.tpe match { case ambi: AmbiguousImplicits => - msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")( - s"ambiguous implicit arguments of type ${pt.show} found${location("for")}") - case _ => - val userDefined = - for { - notFound <- pt.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot) - Trees.Literal(Constant(raw: String)) <- notFound.argument(0) + val maybeAnnot = ambi.alt1.ref.symbol.getAnnotation(defn.ImplicitAmbiguousAnnot).map( + (_, ambi.alt1) + ).orElse( + ambi.alt2.ref.symbol.getAnnotation(defn.ImplicitAmbiguousAnnot).map( + (_, ambi.alt2) + ) + ) + val userDefined = maybeAnnot.flatMap { case (annot, alt) => + val params = alt.ref.underlying match { + case p: PolyType => p.paramNames.map(_.toString) + case _ => Nil } - yield { - err.implicitNotFoundString( - raw, - pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString), - pt.argInfos) + def resolveTypes(targs: List[Tree])(implicit ctx: Context) = + targs.map(a => fullyDefinedType(a.tpe, "type argument", a.pos)) + val args = alt.tree match { + case TypeApply(_, targs) => + resolveTypes(targs)(ctx.fresh.setTyperState(alt.tstate)) + case Block(List(DefDef(_, _, _, _, Apply(TypeApply(_, targs), _))), _) => + resolveTypes(targs.asInstanceOf[List[Tree]])(ctx.fresh.setTyperState(alt.tstate)) + case _ => + Nil } + userDefinedMessage(annot, params, args) + } + userDefined.map(msg(_)()).getOrElse( + msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")( + s"ambiguous implicit arguments of type ${pt.show} found${location("for")}") + ) + case _ => + val userDefined = pt.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot).flatMap( + userDefinedMessage(_, pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString), pt.argInfos) + ) msg(userDefined.getOrElse(em"no implicit argument of type $pt was found${location("for")}"))() } } diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 96f6c55ce501..2413f20664f1 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -1306,4 +1306,106 @@ class ErrorMessagesTests extends ErrorMessagesTest { val DoubleDeclaration(symbol, previousSymbol) :: Nil = messages assertEquals(symbol.name.mangledString, "a") } + + @Test def userDefinedImplicitAmbiguous1 = + checkMessagesAfter("frontend") { + """ + |object Test { + | 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] + |} + + """.stripMargin + }.expect { (itcx, messages) => + import diagnostic.NoExplanation + implicit val ctx: Context = itcx + + assertMessageCount(1, messages) + val (m: NoExplanation) :: Nil = messages + + assertEquals(m.msg, "Could not prove Int =!= Int") + } + + @Test def userDefinedImplicitAmbiguous2 = + checkMessagesAfter("frontend") { + """ + |object Test { + | trait =!=[C, D] + | + | implicit def neq[E, F] : E =!= F = null + | + | implicit def neqAmbig1[G, H, J] : J =!= J = null + | @annotation.implicitAmbiguous("Could not prove ${I} =!= ${I}") + | implicit def neqAmbig2[I] : I =!= I = null + | + | implicitly[Int =!= Int] + |} + + """.stripMargin + }.expect { (itcx, messages) => + import diagnostic.NoExplanation + implicit val ctx: Context = itcx + + assertMessageCount(1, messages) + val (m: NoExplanation) :: Nil = messages + + assertEquals(m.msg, "Could not prove Int =!= Int") + } + + @Test def userDefinedImplicitAmbiguous3 = + checkMessagesAfter("frontend") { + """ + |object Test { + | 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 + | @annotation.implicitAmbiguous("Could not prove ${I} =!= ${I}") + | implicit def neqAmbig2[I] : I =!= I = null + | + | implicitly[Int =!= Int] + |} + + """.stripMargin + }.expect { (itcx, messages) => + import diagnostic.NoExplanation + implicit val ctx: Context = itcx + + assertMessageCount(1, messages) + val (m: NoExplanation) :: Nil = messages + + assertEquals(m.msg, "Could not prove Int =!= Int") + } + + @Test def userDefinedImplicitAmbiguous4 = + checkMessagesAfter("frontend") { + """ + |class C { + | @annotation.implicitAmbiguous("msg A=${A}") + | implicit def f[A](x: Int): String = "f was here" + | implicit def g(x: Int): String = "f was here" + | def test: Unit = { + | implicitly[Int => String] + | } + |} + + """.stripMargin + }.expect { (itcx, messages) => + import diagnostic.NoExplanation + implicit val ctx: Context = itcx + + assertMessageCount(1, messages) + val (m: NoExplanation) :: Nil = messages + + assertEquals(m.msg, "msg A=Any") + } } From a1732a78b5a3132b2c2e13a7cc8de25c64d72e44 Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Tue, 20 Mar 2018 17:55:28 +0100 Subject: [PATCH 2/4] Move code around --- .../dotty/tools/dotc/typer/Implicits.scala | 57 +++++---- .../dotc/reporting/ErrorMessagesTests.scala | 102 ---------------- .../reporting/UserDefinedErrorMessages.scala | 112 ++++++++++++++++++ 3 files changed, 144 insertions(+), 127 deletions(-) create mode 100644 compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 273d1e039190..e263845512ce 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -546,7 +546,7 @@ trait Implicits { self: Typer => /** If `formal` is of the form ClassTag[T], where `T` is a class type, * synthesize a class tag for `T`. - */ + */ def synthesizedClassTag(formal: Type): Tree = formal.argInfos match { case arg :: Nil => fullyDefinedType(arg, "ClassTag argument", pos) match { @@ -591,7 +591,7 @@ trait Implicits { self: Typer => /** If `formal` is of the form Eq[T, U], where no `Eq` instance exists for * either `T` or `U`, synthesize `Eq.eqAny[T, U]` as solution. - */ + */ def synthesizedEq(formal: Type)(implicit ctx: Context): Tree = { //println(i"synth eq $formal / ${formal.argTypes}%, %") formal.argTypes match { @@ -686,23 +686,21 @@ trait Implicits { self: Typer => } } def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where" - def userDefinedMessage(annot: Annotation, params: List[String], args: List[Type]): Option[String] = - for (Trees.Literal(Constant(raw: String)) <- annot.argument(0)) yield { - err.userDefinedErrorString( - raw, - params, - args) - } + + def userDefinedMsg(sym: Symbol, cls: Symbol) = for { + ann <- sym.getAnnotation(cls) + Trees.Literal(Constant(msg: String)) <- ann.argument(0) + } yield msg + + arg.tpe match { case ambi: AmbiguousImplicits => - val maybeAnnot = ambi.alt1.ref.symbol.getAnnotation(defn.ImplicitAmbiguousAnnot).map( - (_, ambi.alt1) - ).orElse( - ambi.alt2.ref.symbol.getAnnotation(defn.ImplicitAmbiguousAnnot).map( - (_, ambi.alt2) - ) - ) - val userDefined = maybeAnnot.flatMap { case (annot, alt) => + object AmbiguousImplicitMsg { + def unapply(search: SearchSuccess) = + userDefinedMsg(search.ref.symbol, defn.ImplicitAmbiguousAnnot) + } + + def userDefinedAmbiguousImplicitMsg(alt: SearchSuccess, raw: String) = { val params = alt.ref.underlying match { case p: PolyType => p.paramNames.map(_.toString) case _ => Nil @@ -717,16 +715,25 @@ trait Implicits { self: Typer => case _ => Nil } - userDefinedMessage(annot, params, args) + err.userDefinedErrorString(raw, params, args) + } + + (ambi.alt1, ambi.alt2) match { + case (AmbiguousImplicitMsg(msg), _) => + userDefinedAmbiguousImplicitMsg(ambi.alt1, msg) + case (_, AmbiguousImplicitMsg(msg)) => + userDefinedAmbiguousImplicitMsg(ambi.alt2, msg) + case _ => + msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")( + s"ambiguous implicit arguments of type ${pt.show} found${location("for")}") } - userDefined.map(msg(_)()).getOrElse( - msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")( - s"ambiguous implicit arguments of type ${pt.show} found${location("for")}") - ) + case _ => - val userDefined = pt.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot).flatMap( - userDefinedMessage(_, pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString), pt.argInfos) - ) + val userDefined = userDefinedMsg(pt.typeSymbol, defn.ImplicitNotFoundAnnot).map(raw => + err.userDefinedErrorString( + raw, + pt.typeSymbol.typeParams.map(_.name.unexpandedName.toString), + pt.argInfos)) msg(userDefined.getOrElse(em"no implicit argument of type $pt was found${location("for")}"))() } } diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 2413f20664f1..96f6c55ce501 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -1306,106 +1306,4 @@ class ErrorMessagesTests extends ErrorMessagesTest { val DoubleDeclaration(symbol, previousSymbol) :: Nil = messages assertEquals(symbol.name.mangledString, "a") } - - @Test def userDefinedImplicitAmbiguous1 = - checkMessagesAfter("frontend") { - """ - |object Test { - | 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] - |} - - """.stripMargin - }.expect { (itcx, messages) => - import diagnostic.NoExplanation - implicit val ctx: Context = itcx - - assertMessageCount(1, messages) - val (m: NoExplanation) :: Nil = messages - - assertEquals(m.msg, "Could not prove Int =!= Int") - } - - @Test def userDefinedImplicitAmbiguous2 = - checkMessagesAfter("frontend") { - """ - |object Test { - | trait =!=[C, D] - | - | implicit def neq[E, F] : E =!= F = null - | - | implicit def neqAmbig1[G, H, J] : J =!= J = null - | @annotation.implicitAmbiguous("Could not prove ${I} =!= ${I}") - | implicit def neqAmbig2[I] : I =!= I = null - | - | implicitly[Int =!= Int] - |} - - """.stripMargin - }.expect { (itcx, messages) => - import diagnostic.NoExplanation - implicit val ctx: Context = itcx - - assertMessageCount(1, messages) - val (m: NoExplanation) :: Nil = messages - - assertEquals(m.msg, "Could not prove Int =!= Int") - } - - @Test def userDefinedImplicitAmbiguous3 = - checkMessagesAfter("frontend") { - """ - |object Test { - | 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 - | @annotation.implicitAmbiguous("Could not prove ${I} =!= ${I}") - | implicit def neqAmbig2[I] : I =!= I = null - | - | implicitly[Int =!= Int] - |} - - """.stripMargin - }.expect { (itcx, messages) => - import diagnostic.NoExplanation - implicit val ctx: Context = itcx - - assertMessageCount(1, messages) - val (m: NoExplanation) :: Nil = messages - - assertEquals(m.msg, "Could not prove Int =!= Int") - } - - @Test def userDefinedImplicitAmbiguous4 = - checkMessagesAfter("frontend") { - """ - |class C { - | @annotation.implicitAmbiguous("msg A=${A}") - | implicit def f[A](x: Int): String = "f was here" - | implicit def g(x: Int): String = "f was here" - | def test: Unit = { - | implicitly[Int => String] - | } - |} - - """.stripMargin - }.expect { (itcx, messages) => - import diagnostic.NoExplanation - implicit val ctx: Context = itcx - - assertMessageCount(1, messages) - val (m: NoExplanation) :: Nil = messages - - assertEquals(m.msg, "msg A=Any") - } } diff --git a/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala b/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala new file mode 100644 index 000000000000..e84de7f0b42d --- /dev/null +++ b/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala @@ -0,0 +1,112 @@ +package dotty.tools +package dotc +package reporting + +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.reporting.diagnostic.messages._ +import org.junit.Assert._ +import org.junit.Test + +class UserDefinedErrorMessages extends ErrorMessagesTest { + @Test def userDefinedImplicitAmbiguous1 = + checkMessagesAfter("frontend") { + """ + |object Test { + | 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] + |} + + """.stripMargin + }.expect { (itcx, messages) => + import diagnostic.NoExplanation + implicit val ctx: Context = itcx + + assertMessageCount(1, messages) + val (m: NoExplanation) :: Nil = messages + + assertEquals(m.msg, "Could not prove Int =!= Int") + } + + @Test def userDefinedImplicitAmbiguous2 = + checkMessagesAfter("frontend") { + """ + |object Test { + | trait =!=[C, D] + | + | implicit def neq[E, F] : E =!= F = null + | + | implicit def neqAmbig1[G, H, J] : J =!= J = null + | @annotation.implicitAmbiguous("Could not prove ${I} =!= ${I}") + | implicit def neqAmbig2[I] : I =!= I = null + | + | implicitly[Int =!= Int] + |} + + """.stripMargin + }.expect { (itcx, messages) => + import diagnostic.NoExplanation + implicit val ctx: Context = itcx + + assertMessageCount(1, messages) + val (m: NoExplanation) :: Nil = messages + + assertEquals(m.msg, "Could not prove Int =!= Int") + } + + @Test def userDefinedImplicitAmbiguous3 = + checkMessagesAfter("frontend") { + """ + |object Test { + | 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 + | @annotation.implicitAmbiguous("Could not prove ${I} =!= ${I}") + | implicit def neqAmbig2[I] : I =!= I = null + | + | implicitly[Int =!= Int] + |} + + """.stripMargin + }.expect { (itcx, messages) => + import diagnostic.NoExplanation + implicit val ctx: Context = itcx + + assertMessageCount(1, messages) + val (m: NoExplanation) :: Nil = messages + + assertEquals(m.msg, "Could not prove Int =!= Int") + } + + @Test def userDefinedImplicitAmbiguous4 = + checkMessagesAfter("frontend") { + """ + |class C { + | @annotation.implicitAmbiguous("msg A=${A}") + | implicit def f[A](x: Int): String = "f was here" + | implicit def g(x: Int): String = "f was here" + | def test: Unit = { + | implicitly[Int => String] + | } + |} + + """.stripMargin + }.expect { (itcx, messages) => + import diagnostic.NoExplanation + implicit val ctx: Context = itcx + + assertMessageCount(1, messages) + val (m: NoExplanation) :: Nil = messages + + assertEquals(m.msg, "msg A=Any") + } +} From 86ecedeee50df92059a58a780aecf083ff99d001 Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Wed, 21 Mar 2018 14:13:00 +0100 Subject: [PATCH 3/4] Use existing helper functions to deconstruct closures and function calls --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 27 ++++++++++++------- .../dotty/tools/dotc/typer/Implicits.scala | 23 ++++++++++------ .../reporting/UserDefinedErrorMessages.scala | 25 ++++++++++++++--- 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 6aecb1791903..d1e91799f31a 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -9,6 +9,8 @@ import util.HashSet import typer.ConstFold import reporting.trace +import scala.annotation.tailrec + trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] => import TreeInfo._ @@ -512,17 +514,22 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => } /** Decompose a call fn[targs](vargs_1)...(vargs_n) - * into its constituents (where targs, vargss may be empty) + * into its constituents (fn, targs, vargss). + * + * Note: targ and vargss may be empty */ - def decomposeCall(tree: Tree): (Tree, List[Tree], List[List[Tree]]) = tree match { - case Apply(fn, args) => - val (meth, targs, argss) = decomposeCall(fn) - (meth, targs, argss :+ args) - case TypeApply(fn, targs) => - val (meth, targss, args) = decomposeCall(fn) - (meth, targs ++ targss, args) - case _ => - (tree, Nil, Nil) + def decomposeCall(tree: Tree): (Tree, List[Tree], List[List[Tree]]) = { + @tailrec + def loop(tree: Tree, targss: List[Tree], argss: List[List[Tree]]): (Tree, List[Tree], List[List[Tree]]) = + tree match { + case Apply(fn, args) => + loop(fn, targss, args :: argss) + case TypeApply(fn, targs) => + loop(fn, targs ::: targss, argss) + case _ => + (tree, targss, argss) + } + loop(tree, Nil, Nil) } /** An extractor for closures, either contained in a block or standalone. diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index e263845512ce..6e67c345816f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -707,14 +707,21 @@ trait Implicits { self: Typer => } def resolveTypes(targs: List[Tree])(implicit ctx: Context) = targs.map(a => fullyDefinedType(a.tpe, "type argument", a.pos)) - val args = alt.tree match { - case TypeApply(_, targs) => - resolveTypes(targs)(ctx.fresh.setTyperState(alt.tstate)) - case Block(List(DefDef(_, _, _, _, Apply(TypeApply(_, targs), _))), _) => - resolveTypes(targs.asInstanceOf[List[Tree]])(ctx.fresh.setTyperState(alt.tstate)) - case _ => - Nil - } + + // We can extract type arguments from: + // - a function call: + // @implicitAmbiguous("msg A=${A}") + // implicit def f[A](): String = ... + // implicitly[String] // found: f[Any]() + // + // - an eta-expanded function: + // @implicitAmbiguous("msg A=${A}") + // implicit def f[A](x: Int): String = ... + // implicitly[Int => String] // found: x => f[Any](x) + + val call = closureBody(alt.tree) // the tree itself if not a closure + val (_, targs, _) = decomposeCall(call) + val args = resolveTypes(targs)(ctx.fresh.setTyperState(alt.tstate)) err.userDefinedErrorString(raw, params, args) } diff --git a/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala b/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala index e84de7f0b42d..bb8db2aff35c 100644 --- a/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala +++ b/compiler/test/dotty/tools/dotc/reporting/UserDefinedErrorMessages.scala @@ -22,7 +22,6 @@ class UserDefinedErrorMessages extends ErrorMessagesTest { | | implicitly[Int =!= Int] |} - """.stripMargin }.expect { (itcx, messages) => import diagnostic.NoExplanation @@ -48,7 +47,6 @@ class UserDefinedErrorMessages extends ErrorMessagesTest { | | implicitly[Int =!= Int] |} - """.stripMargin }.expect { (itcx, messages) => import diagnostic.NoExplanation @@ -75,7 +73,6 @@ class UserDefinedErrorMessages extends ErrorMessagesTest { | | implicitly[Int =!= Int] |} - """.stripMargin }.expect { (itcx, messages) => import diagnostic.NoExplanation @@ -98,7 +95,29 @@ class UserDefinedErrorMessages extends ErrorMessagesTest { | implicitly[Int => String] | } |} + """.stripMargin + }.expect { (itcx, messages) => + import diagnostic.NoExplanation + implicit val ctx: Context = itcx + + assertMessageCount(1, messages) + val (m: NoExplanation) :: Nil = messages + assertEquals(m.msg, "msg A=Any") + } + + @Test def userDefinedImplicitAmbiguous5 = + checkMessagesAfter("frontend") { + """ + |class C { + | @annotation.implicitAmbiguous("msg A=${A}") + | implicit def f[A](implicit x: String): Int = 1 + | implicit def g: Int = 2 + | def test: Unit = { + | implicit val s: String = "Hello" + | implicitly[Int] + | } + |} """.stripMargin }.expect { (itcx, messages) => import diagnostic.NoExplanation From 5a55c33d6a074ac613417a8117eaeaecfc05b08d Mon Sep 17 00:00:00 2001 From: Allan Renucci Date: Wed, 21 Mar 2018 15:16:23 +0100 Subject: [PATCH 4/4] Address review comments --- .../src/dotty/tools/dotc/typer/Implicits.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 6e67c345816f..f11f75b704c4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -687,6 +687,9 @@ trait Implicits { self: Typer => } def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where" + /** Extract a user defined error message from a symbol `sym` + * with an annotation matching the given class symbol `cls`. + */ def userDefinedMsg(sym: Symbol, cls: Symbol) = for { ann <- sym.getAnnotation(cls) Trees.Literal(Constant(msg: String)) <- ann.argument(0) @@ -696,10 +699,13 @@ trait Implicits { self: Typer => arg.tpe match { case ambi: AmbiguousImplicits => object AmbiguousImplicitMsg { - def unapply(search: SearchSuccess) = + def unapply(search: SearchSuccess): Option[String] = userDefinedMsg(search.ref.symbol, defn.ImplicitAmbiguousAnnot) } + /** Construct a custom error message given an ambiguous implicit + * candidate `alt` and a user defined message `raw`. + */ def userDefinedAmbiguousImplicitMsg(alt: SearchSuccess, raw: String) = { val params = alt.ref.underlying match { case p: PolyType => p.paramNames.map(_.toString) @@ -726,10 +732,10 @@ trait Implicits { self: Typer => } (ambi.alt1, ambi.alt2) match { - case (AmbiguousImplicitMsg(msg), _) => - userDefinedAmbiguousImplicitMsg(ambi.alt1, msg) - case (_, AmbiguousImplicitMsg(msg)) => - userDefinedAmbiguousImplicitMsg(ambi.alt2, msg) + case (alt @ AmbiguousImplicitMsg(msg), _) => + userDefinedAmbiguousImplicitMsg(alt, msg) + case (_, alt @ AmbiguousImplicitMsg(msg)) => + userDefinedAmbiguousImplicitMsg(alt, msg) case _ => msg(s"ambiguous implicit arguments: ${ambi.explanation}${location("of")}")( s"ambiguous implicit arguments of type ${pt.show} found${location("for")}")