From 0b8dc356f8831febe4e732fe39ac3d26e0d621a3 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Mon, 22 Apr 2024 17:11:32 +0200 Subject: [PATCH 1/4] Fix implicit search failure reporting [Cherry-picked fc06435366b42f48a0a90803ac3dda4ad58b9f02] --- .../src/dotty/tools/dotc/typer/Typer.scala | 105 ++++++++++-------- tests/neg/19414-desugared.check | 4 + tests/neg/19414-desugared.scala | 22 ++++ tests/neg/19414.check | 4 + tests/neg/19414.scala | 15 +++ tests/neg/given-ambiguous-default-1.check | 4 + tests/neg/given-ambiguous-default-1.scala | 18 +++ tests/neg/given-ambiguous-default-2.check | 4 + tests/neg/given-ambiguous-default-2.scala | 18 +++ tests/neg/i8827a.check | 7 +- tests/neg/i8827b.check | 7 +- tests/neg/i9568.check | 7 +- tests/neg/implicitSearch.check | 7 +- tests/neg/missing-implicit3.check | 11 +- 14 files changed, 154 insertions(+), 79 deletions(-) create mode 100644 tests/neg/19414-desugared.check create mode 100644 tests/neg/19414-desugared.scala create mode 100644 tests/neg/19414.check create mode 100644 tests/neg/19414.scala create mode 100644 tests/neg/given-ambiguous-default-1.check create mode 100644 tests/neg/given-ambiguous-default-1.scala create mode 100644 tests/neg/given-ambiguous-default-2.check create mode 100644 tests/neg/given-ambiguous-default-2.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a6b953f8ed2f..bd412174a41e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3626,7 +3626,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer private def adapt1(tree: Tree, pt: Type, locked: TypeVars)(using Context): Tree = { assert(pt.exists && !pt.isInstanceOf[ExprType] || ctx.reporter.errorsReported, i"tree: $tree, pt: $pt") - def methodStr = err.refStr(methPart(tree).tpe) def readapt(tree: Tree)(using Context) = adapt(tree, pt, locked) def readaptSimplified(tree: Tree)(using Context) = readapt(simplify(tree, pt, locked)) @@ -3803,49 +3802,38 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer arg :: inferArgsAfter(arg) end implicitArgs - val args = implicitArgs(wtp.paramInfos, 0, pt) - - def propagatedFailure(args: List[Tree]): Type = args match { - case arg :: args1 => - arg.tpe match { - case ambi: AmbiguousImplicits => - propagatedFailure(args1) match { - case NoType | (_: AmbiguousImplicits) => ambi - case failed => failed - } - case failed: SearchFailureType => failed - case _ => propagatedFailure(args1) - } - case Nil => NoType - } - - val propFail = propagatedFailure(args) - - def issueErrors(): Tree = { - def paramSymWithMethodTree(paramName: TermName) = - if tree.symbol.exists then - tree.symbol.paramSymss.flatten - .map(sym => sym.name -> sym) - .toMap - .get(paramName) - .map((_, tree)) - else - None - - wtp.paramNames.lazyZip(wtp.paramInfos).lazyZip(args).foreach { (paramName, formal, arg) => - arg.tpe match { + /** Reports errors for arguments of `appTree` that have a + * `SearchFailureType`, recursively traversing arguments that are + * themselves applications. `mt` must be the type of `appTree.fun`. + */ + def reportErrors(appTree: Apply, mt: MethodType): Unit = + val Apply(fun, args) = appTree + for (paramName, formal, arg) <- mt.paramNames.lazyZip(mt.paramInfos).lazyZip(args) do + arg.tpe match case failure: SearchFailureType => - report.error( - missingArgMsg(arg, formal, implicitParamString(paramName, methodStr, tree), paramSymWithMethodTree(paramName)), - tree.srcPos.endPos - ) - case _ => - } - } - untpd.Apply(tree, args).withType(propFail) - } + arg match + case childAppTree: Apply => + childAppTree.fun.tpe.widen match + case childMt: MethodType => reportErrors(childAppTree, childMt) + case _ => () + case _ => () + + val methodStr = err.refStr(methPart(fun).tpe) + val paramStr = implicitParamString(paramName, methodStr, fun) + val paramSymWithMethodCallTree = + fun.symbol.paramSymss.flatten + .find(_.name == paramName) + .map((_, appTree)) + val message = missingArgMsg(arg, formal, paramStr, paramSymWithMethodCallTree) + // Note: if the same error type appears on several trees, we + // might report it several times, but this is not a problem + // because only the first one will be displayed. We traverse in + // post-order, so that the most detailed message gets displayed. + report.error(message, fun.srcPos.endPos) + case _ => () - if (propFail.exists) { + val args = implicitArgs(wtp.paramInfos, 0, pt) + if (args.tpes.exists(_.isInstanceOf[SearchFailureType])) { // If there are several arguments, some arguments might already // have influenced the context, binding variables, but later ones // might fail. In that case the constraint and instantiated variables @@ -3854,15 +3842,36 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // If method has default params, fall back to regular application // where all inferred implicits are passed as named args. - if hasDefaultParams && !propFail.isInstanceOf[AmbiguousImplicits] then - val namedArgs = wtp.paramNames.lazyZip(args).flatMap { (pname, arg) => - if (arg.tpe.isError) Nil else untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil - } + if hasDefaultParams then + // Only keep the arguments that don't have an error type, or that + // have an `AmbiguousImplicits` error type. The later ensures that a + // default argument can't override an ambiguous implicit. See tests + // `given-ambiguous-default*` and `19414*`. + val namedArgs = + wtp.paramNames.lazyZip(args) + .filter((_, arg) => !arg.tpe.isError || arg.tpe.isInstanceOf[AmbiguousImplicits]) + .map((pname, arg) => untpd.NamedArg(pname, untpd.TypedSplice(arg))) + val app = cpy.Apply(tree)(untpd.TypedSplice(tree), namedArgs) if (wtp.isContextualMethod) app.setApplyKind(ApplyKind.Using) typr.println(i"try with default implicit args $app") - typed(app, pt, locked) - else issueErrors() + val retyped = typed(app, pt, locked) + + // If the retyped tree still has an error type and is an `Apply` + // node, we can report the errors for each argument nicely. + // Otherwise, we don't report anything here. + retyped match + case retyped: Apply if retyped.tpe.isError => reportErrors(retyped, wtp) + case _ => () + + retyped + else + val firstNonAmbiguous = args.tpes.find(tp => tp.isError && !tp.isInstanceOf[AmbiguousImplicits]) + def firstError = args.tpes.find(_.isError) + val errorType = firstNonAmbiguous.orElse(firstError).getOrElse(NoType) + val res = untpd.Apply(tree, args).withType(errorType) + reportErrors(res, wtp) + res } else tree match { case tree: Block => diff --git a/tests/neg/19414-desugared.check b/tests/neg/19414-desugared.check new file mode 100644 index 000000000000..eb8389649348 --- /dev/null +++ b/tests/neg/19414-desugared.check @@ -0,0 +1,4 @@ +-- [E172] Type Error: tests/neg/19414-desugared.scala:22:34 ------------------------------------------------------------ +22 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances + | ^ + |Ambiguous given instances: both given instance given_Writer_JsValue and given instance given_Writer_JsObject match type Writer[B] of parameter writer of given instance given_BodySerializer_B diff --git a/tests/neg/19414-desugared.scala b/tests/neg/19414-desugared.scala new file mode 100644 index 000000000000..9fc16e2249a2 --- /dev/null +++ b/tests/neg/19414-desugared.scala @@ -0,0 +1,22 @@ +trait JsValue +trait JsObject extends JsValue + +trait Writer[T] +trait BodySerializer[-B] + +class Printer + +given Writer[JsValue] = ??? +given Writer[JsObject] = ??? + +// This is not an exact desugaring of the original code: currently the compiler +// actually changes the modifier of the parameter list from `using` to +// `implicit` when desugaring the context-bound `B: Writer` to `implicit writer: +// Writer[B]`, but we can't write it in user code as this is not valid syntax. +given [B](using + writer: Writer[B], + printer: Printer = new Printer +): BodySerializer[B] = ??? + +def f: Unit = + summon[BodySerializer[JsObject]] // error: Ambiguous given instances diff --git a/tests/neg/19414.check b/tests/neg/19414.check new file mode 100644 index 000000000000..b865b4ba227c --- /dev/null +++ b/tests/neg/19414.check @@ -0,0 +1,4 @@ +-- [E172] Type Error: tests/neg/19414.scala:15:34 ---------------------------------------------------------------------- +15 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances + | ^ + |Ambiguous given instances: both given instance given_Writer_JsValue and given instance given_Writer_JsObject match type Writer[B] of a context parameter of given instance given_BodySerializer_B diff --git a/tests/neg/19414.scala b/tests/neg/19414.scala new file mode 100644 index 000000000000..bb275ad943b7 --- /dev/null +++ b/tests/neg/19414.scala @@ -0,0 +1,15 @@ +trait JsValue +trait JsObject extends JsValue + +trait Writer[T] +trait BodySerializer[-B] + +class Printer + +given Writer[JsValue] = ??? +given Writer[JsObject] = ??? + +given [B: Writer](using printer: Printer = new Printer): BodySerializer[B] = ??? + +def f: Unit = + summon[BodySerializer[JsObject]] // error: Ambiguous given instances diff --git a/tests/neg/given-ambiguous-default-1.check b/tests/neg/given-ambiguous-default-1.check new file mode 100644 index 000000000000..734143b337d8 --- /dev/null +++ b/tests/neg/given-ambiguous-default-1.check @@ -0,0 +1,4 @@ +-- [E172] Type Error: tests/neg/given-ambiguous-default-1.scala:18:23 -------------------------------------------------- +18 |def f: Unit = summon[B] // error: Ambiguous given instances + | ^ + |Ambiguous given instances: both given instance a1 and given instance a2 match type A of parameter a of given instance given_B diff --git a/tests/neg/given-ambiguous-default-1.scala b/tests/neg/given-ambiguous-default-1.scala new file mode 100644 index 000000000000..140736e9eee3 --- /dev/null +++ b/tests/neg/given-ambiguous-default-1.scala @@ -0,0 +1,18 @@ +/** This test checks that provided ambiguous given instances take precedence + * over default given arguments. In the following code, the compiler must + * report an "Ambiguous implicits" error for the parameter `a`, and must not + * use its default value. + * + * See also: + * - tests/neg/19414.scala + * - tests/neg/19414-desugared.scala + * - tests/neg/given-ambiguous-default-2.scala + */ + +class A +class B +given a1: A = ??? +given a2: A = ??? +given (using a: A = A()): B = ??? + +def f: Unit = summon[B] // error: Ambiguous given instances diff --git a/tests/neg/given-ambiguous-default-2.check b/tests/neg/given-ambiguous-default-2.check new file mode 100644 index 000000000000..25e9914e8288 --- /dev/null +++ b/tests/neg/given-ambiguous-default-2.check @@ -0,0 +1,4 @@ +-- [E172] Type Error: tests/neg/given-ambiguous-default-2.scala:18:23 -------------------------------------------------- +18 |def f: Unit = summon[C] // error: Ambiguous given instances + | ^ + |Ambiguous given instances: both given instance a1 and given instance a2 match type A of parameter a of given instance given_C diff --git a/tests/neg/given-ambiguous-default-2.scala b/tests/neg/given-ambiguous-default-2.scala new file mode 100644 index 000000000000..9e639b66f3d1 --- /dev/null +++ b/tests/neg/given-ambiguous-default-2.scala @@ -0,0 +1,18 @@ +/** This test checks that provided given instances take precedence over default + * given arguments, even when there are multiple default arguments. Before the + * fix for issue #19414, this code would compile without errors. + * + * See also: + * - tests/neg/given-ambiguous-default-1.scala + * - tests/neg/19414.scala + * - tests/neg/19414-desugared.scala + */ + +class A +class B +class C +given a1: A = ??? +given a2: A = ??? +given (using a: A = A(), b: B = B()): C = ??? + +def f: Unit = summon[C] // error: Ambiguous given instances diff --git a/tests/neg/i8827a.check b/tests/neg/i8827a.check index 3d6c2bfa500b..8ebc50caf128 100644 --- a/tests/neg/i8827a.check +++ b/tests/neg/i8827a.check @@ -1,12 +1,7 @@ -- [E172] Type Error: tests/neg/i8827a.scala:16:26 --------------------------------------------------------------------- 16 | summon[Order[List[Foo]]] // error | ^ - | No given instance of type pkg.Order[List[pkg.Foo]] was found for parameter x of method summon in object Predef. - | I found: - | - | pkg.Order.orderList[pkg.Foo](/* missing */summon[pkg.Order[pkg.Foo]]) - | - | But no implicit values were found that match type pkg.Order[pkg.Foo]. + | No given instance of type pkg.Order[pkg.Foo] was found for parameter orderA of method orderList in object Order | | The following import might fix the problem: | diff --git a/tests/neg/i8827b.check b/tests/neg/i8827b.check index 6848c53aee28..de726ede38d1 100644 --- a/tests/neg/i8827b.check +++ b/tests/neg/i8827b.check @@ -1,12 +1,7 @@ -- [E172] Type Error: tests/neg/i8827b.scala:16:28 --------------------------------------------------------------------- 16 | summon[Order[Option[Foo]]] // error | ^ - |No given instance of type pkg.Order[Option[pkg.Foo]] was found for parameter x of method summon in object Predef. - |I found: - | - | pkg.Order.given_Order_Option[pkg.Foo](/* missing */summon[pkg.Order[pkg.Foo]]) - | - |But no implicit values were found that match type pkg.Order[pkg.Foo]. + |No given instance of type pkg.Order[pkg.Foo] was found for parameter orderA of given instance given_Order_Option in object Order | |The following import might fix the problem: | diff --git a/tests/neg/i9568.check b/tests/neg/i9568.check index 3f318d0b0111..744023714a69 100644 --- a/tests/neg/i9568.check +++ b/tests/neg/i9568.check @@ -4,13 +4,10 @@ | No given instance of type => Monad[F] was found for parameter ev of method blaMonad in object Test. | I found: | - | Test.blaMonad[F², S](Test.blaMonad[F³, S²]) + | Test.blaMonad[F², S] | - | But method blaMonad in object Test does not match type => Monad[F²] + | But method blaMonad in object Test does not match type => Monad[F] | | where: F is a type variable with constraint <: [_] =>> Any | F² is a type variable with constraint <: [_] =>> Any - | F³ is a type variable with constraint <: [_] =>> Any - | S is a type variable - | S² is a type variable | . diff --git a/tests/neg/implicitSearch.check b/tests/neg/implicitSearch.check index e8efc744ac0a..01325c5bf736 100644 --- a/tests/neg/implicitSearch.check +++ b/tests/neg/implicitSearch.check @@ -1,12 +1,7 @@ -- [E172] Type Error: tests/neg/implicitSearch.scala:13:12 ------------------------------------------------------------- 13 | sort(xs) // error (with a partially constructed implicit argument shown) | ^ - | No given instance of type Test.Ord[List[List[T]]] was found for parameter o of method sort in object Test. - | I found: - | - | Test.listOrd[List[T]](Test.listOrd[T](/* missing */summon[Test.Ord[T]])) - | - | But no implicit values were found that match type Test.Ord[T]. + | No given instance of type Test.Ord[T] was found for parameter o of method listOrd in object Test -- [E172] Type Error: tests/neg/implicitSearch.scala:15:38 ------------------------------------------------------------- 15 | listOrd(listOrd(implicitly[Ord[T]] /*not found*/)) // error | ^ diff --git a/tests/neg/missing-implicit3.check b/tests/neg/missing-implicit3.check index c58b4430f3fe..1b1df3d5a46d 100644 --- a/tests/neg/missing-implicit3.check +++ b/tests/neg/missing-implicit3.check @@ -1,14 +1,9 @@ -- [E172] Type Error: tests/neg/missing-implicit3.scala:13:36 ---------------------------------------------------------- 13 |val sortedFoos = sort(List(new Foo)) // error | ^ - | No given instance of type ord.Ord[ord.Foo] was found for a context parameter of method sort in package ord. - | I found: + |No given instance of type ord.Foo => Comparable[? >: ord.Foo] was found for parameter x$1 of given instance ordered in object Ord | - | ord.Ord.ordered[ord.Foo](/* missing */summon[ord.Foo => Comparable[? >: ord.Foo]]) + |The following import might make progress towards fixing the problem: | - | But no implicit values were found that match type ord.Foo => Comparable[? >: ord.Foo]. - | - | The following import might make progress towards fixing the problem: - | - | import scala.math.Ordered.orderingToOrdered + | import scala.math.Ordered.orderingToOrdered | From 520e548af625cfab5f0357a669042ff91107bcc4 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Thu, 25 Apr 2024 09:51:16 +0200 Subject: [PATCH 2/4] Type `addImplicitsArgs` result with the first failure [Cherry-picked 0890e7ae50f72592928e350c450ca949ff1a3873] --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bd412174a41e..b751d74b169c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3833,7 +3833,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => () val args = implicitArgs(wtp.paramInfos, 0, pt) - if (args.tpes.exists(_.isInstanceOf[SearchFailureType])) { + val firstFailure = args.tpes.find(_.isInstanceOf[SearchFailureType]) + if (firstFailure.isDefined) { // If there are several arguments, some arguments might already // have influenced the context, binding variables, but later ones // might fail. In that case the constraint and instantiated variables @@ -3866,10 +3867,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer retyped else - val firstNonAmbiguous = args.tpes.find(tp => tp.isError && !tp.isInstanceOf[AmbiguousImplicits]) - def firstError = args.tpes.find(_.isError) - val errorType = firstNonAmbiguous.orElse(firstError).getOrElse(NoType) - val res = untpd.Apply(tree, args).withType(errorType) + val res = untpd.Apply(tree, args).withType(firstFailure.get) reportErrors(res, wtp) res } From 4b84055e1273152bd014a77bfd963be5cc6f22fb Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Sat, 6 Jul 2024 16:21:12 +0200 Subject: [PATCH 3/4] Do not visit args recursively in issueErrors Instead, fix the display of nested `AmbiguousImplicits` errors. [Cherry-picked 0e4d51a8ed2f4068a506aa236bd5d7740c8af50b][modified] --- .../dotty/tools/dotc/reporting/messages.scala | 2 +- .../dotty/tools/dotc/typer/Implicits.scala | 3 +- .../src/dotty/tools/dotc/typer/Typer.scala | 59 ++++++++----------- tests/neg/19414-desugared.check | 12 +++- tests/neg/19414.check | 12 +++- tests/neg/given-ambiguous-1.check | 9 +++ tests/neg/given-ambiguous-1.scala | 12 ++++ tests/neg/given-ambiguous-2.check | 4 ++ tests/neg/given-ambiguous-2.scala | 10 ++++ tests/neg/given-ambiguous-default-1.check | 7 ++- tests/neg/given-ambiguous-default-2.check | 7 ++- tests/neg/i8827a.check | 7 ++- tests/neg/i8827b.check | 7 ++- tests/neg/i9568.check | 7 ++- tests/neg/implicitSearch.check | 7 ++- tests/neg/missing-implicit3.check | 11 +++- 16 files changed, 129 insertions(+), 47 deletions(-) create mode 100644 tests/neg/given-ambiguous-1.check create mode 100644 tests/neg/given-ambiguous-1.scala create mode 100644 tests/neg/given-ambiguous-2.check create mode 100644 tests/neg/given-ambiguous-2.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 56105bfe0c48..365253c864be 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2955,7 +2955,7 @@ class MissingImplicitArgument( * def foo(implicit foo: Foo): Any = ??? */ arg.tpe match - case ambi: AmbiguousImplicits => + case ambi: AmbiguousImplicits if !ambi.nested => (ambi.alt1, ambi.alt2) match case (alt @ AmbiguousImplicitMsg(msg), _) => userDefinedAmbiguousImplicitMsg(alt, msg) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 2e5acb1d217a..3a539d1f9133 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -514,7 +514,8 @@ object Implicits: override def toString = s"TooUnspecific" /** An ambiguous implicits failure */ - class AmbiguousImplicits(val alt1: SearchSuccess, val alt2: SearchSuccess, val expectedType: Type, val argument: Tree) extends SearchFailureType { + class AmbiguousImplicits(val alt1: SearchSuccess, val alt2: SearchSuccess, val expectedType: Type, val argument: Tree, val nested: Boolean = false) extends SearchFailureType { + def msg(using Context): Message = var str1 = err.refStr(alt1.ref) var str2 = err.refStr(alt2.ref) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b751d74b169c..d780f8366072 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3803,38 +3803,36 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer end implicitArgs /** Reports errors for arguments of `appTree` that have a - * `SearchFailureType`, recursively traversing arguments that are - * themselves applications. `mt` must be the type of `appTree.fun`. + * `SearchFailureType`. */ - def reportErrors(appTree: Apply, mt: MethodType): Unit = - val Apply(fun, args) = appTree - for (paramName, formal, arg) <- mt.paramNames.lazyZip(mt.paramInfos).lazyZip(args) do + def issueErrors(fun: Tree, args: List[Tree]): Tree = + def firstFailure = args.tpes.find(_.isInstanceOf[SearchFailureType]).getOrElse(NoType) + val errorType = + firstFailure match + case tp: AmbiguousImplicits => + AmbiguousImplicits(tp.alt1, tp.alt2, tp.expectedType, tp.argument, nested = true) + case tp => + tp + val res = untpd.Apply(fun, args).withType(errorType) + + wtp.paramNames.lazyZip(wtp.paramInfos).lazyZip(args).foreach { (paramName, formal, arg) => arg.tpe match case failure: SearchFailureType => - arg match - case childAppTree: Apply => - childAppTree.fun.tpe.widen match - case childMt: MethodType => reportErrors(childAppTree, childMt) - case _ => () - case _ => () - val methodStr = err.refStr(methPart(fun).tpe) val paramStr = implicitParamString(paramName, methodStr, fun) - val paramSymWithMethodCallTree = - fun.symbol.paramSymss.flatten - .find(_.name == paramName) - .map((_, appTree)) - val message = missingArgMsg(arg, formal, paramStr, paramSymWithMethodCallTree) - // Note: if the same error type appears on several trees, we - // might report it several times, but this is not a problem - // because only the first one will be displayed. We traverse in - // post-order, so that the most detailed message gets displayed. - report.error(message, fun.srcPos.endPos) - case _ => () + val paramSym = fun.symbol.paramSymss.flatten.find(_.name == paramName) + val paramSymWithMethodCallTree = paramSym.map((_, res)) + report.error( + missingArgMsg(arg, formal, paramStr, paramSymWithMethodCallTree), + tree.srcPos.endPos + ) + case _ => + } + + res val args = implicitArgs(wtp.paramInfos, 0, pt) - val firstFailure = args.tpes.find(_.isInstanceOf[SearchFailureType]) - if (firstFailure.isDefined) { + if (args.tpes.exists(_.isInstanceOf[SearchFailureType])) { // If there are several arguments, some arguments might already // have influenced the context, binding variables, but later ones // might fail. In that case the constraint and instantiated variables @@ -3862,14 +3860,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // node, we can report the errors for each argument nicely. // Otherwise, we don't report anything here. retyped match - case retyped: Apply if retyped.tpe.isError => reportErrors(retyped, wtp) - case _ => () - - retyped - else - val res = untpd.Apply(tree, args).withType(firstFailure.get) - reportErrors(res, wtp) - res + case Apply(tree, args) if retyped.tpe.isError => issueErrors(tree, args) + case _ => retyped + else issueErrors(tree, args) } else tree match { case tree: Block => diff --git a/tests/neg/19414-desugared.check b/tests/neg/19414-desugared.check index eb8389649348..e126404e3e4f 100644 --- a/tests/neg/19414-desugared.check +++ b/tests/neg/19414-desugared.check @@ -1,4 +1,14 @@ -- [E172] Type Error: tests/neg/19414-desugared.scala:22:34 ------------------------------------------------------------ 22 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances | ^ - |Ambiguous given instances: both given instance given_Writer_JsValue and given instance given_Writer_JsObject match type Writer[B] of parameter writer of given instance given_BodySerializer_B + |No given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef. + |I found: + | + | given_BodySerializer_B[B]( + | writer = + | /* ambiguous: both given instance given_Writer_JsValue and given instance given_Writer_JsObject match type Writer[B] */ + | summon[Writer[B]] + | , + | this.given_BodySerializer_B$default$2[B]) + | + |But both given instance given_Writer_JsValue and given instance given_Writer_JsObject match type Writer[B]. diff --git a/tests/neg/19414.check b/tests/neg/19414.check index b865b4ba227c..f80f1681739d 100644 --- a/tests/neg/19414.check +++ b/tests/neg/19414.check @@ -1,4 +1,14 @@ -- [E172] Type Error: tests/neg/19414.scala:15:34 ---------------------------------------------------------------------- 15 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances | ^ - |Ambiguous given instances: both given instance given_Writer_JsValue and given instance given_Writer_JsObject match type Writer[B] of a context parameter of given instance given_BodySerializer_B + |No given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef. + |I found: + | + | given_BodySerializer_B[B]( + | evidence$1 = + | /* ambiguous: both given instance given_Writer_JsValue and given instance given_Writer_JsObject match type Writer[B] */ + | summon[Writer[B]] + | , + | this.given_BodySerializer_B$default$2[B]) + | + |But both given instance given_Writer_JsValue and given instance given_Writer_JsObject match type Writer[B]. diff --git a/tests/neg/given-ambiguous-1.check b/tests/neg/given-ambiguous-1.check new file mode 100644 index 000000000000..017714de2ea7 --- /dev/null +++ b/tests/neg/given-ambiguous-1.check @@ -0,0 +1,9 @@ +-- [E172] Type Error: tests/neg/given-ambiguous-1.scala:12:23 ---------------------------------------------------------- +12 |def f: Unit = summon[B] // error: Ambiguous given instances + | ^ + | No given instance of type B was found for parameter x of method summon in object Predef. + | I found: + | + | given_B(/* ambiguous: both given instance a1 and given instance a2 match type A */summon[A]) + | + | But both given instance a1 and given instance a2 match type A. diff --git a/tests/neg/given-ambiguous-1.scala b/tests/neg/given-ambiguous-1.scala new file mode 100644 index 000000000000..0ce4f566e615 --- /dev/null +++ b/tests/neg/given-ambiguous-1.scala @@ -0,0 +1,12 @@ +class A +class B +given a1: A = ??? +given a2: A = ??? +given (using a: A): B = ??? + +// In this case, the ambiguous given instance is not directly the argument of +// `summon`; it is the argument of `given_B` which is needed for the argument of +// `summon`. This is a nested ambiguous implicit, thus we report an error in +// the style "I found ... but". See `given-ambiguous-2` for a direct ambiguous +// implicit error. +def f: Unit = summon[B] // error: Ambiguous given instances diff --git a/tests/neg/given-ambiguous-2.check b/tests/neg/given-ambiguous-2.check new file mode 100644 index 000000000000..ec84b750e691 --- /dev/null +++ b/tests/neg/given-ambiguous-2.check @@ -0,0 +1,4 @@ +-- [E172] Type Error: tests/neg/given-ambiguous-2.scala:10:15 ---------------------------------------------------------- +10 |def f: Unit = g // error: Ambiguous given instances + | ^ + | Ambiguous given instances: both given instance a1 and given instance a2 match type A of parameter a of method g diff --git a/tests/neg/given-ambiguous-2.scala b/tests/neg/given-ambiguous-2.scala new file mode 100644 index 000000000000..2c3c52f1ccb0 --- /dev/null +++ b/tests/neg/given-ambiguous-2.scala @@ -0,0 +1,10 @@ +class A +class B +given a1: A = ??? +given a2: A = ??? +def g(using a: A): B = ??? + +// In this case, the ambiguous given instance is directly the argument of +// `summon`. This is a direct ambiguous implicit, thus we report the error +// directly. See `given-ambiguous-1` for a nested ambiguous implicit error. +def f: Unit = g // error: Ambiguous given instances diff --git a/tests/neg/given-ambiguous-default-1.check b/tests/neg/given-ambiguous-default-1.check index 734143b337d8..0b24a89b82cf 100644 --- a/tests/neg/given-ambiguous-default-1.check +++ b/tests/neg/given-ambiguous-default-1.check @@ -1,4 +1,9 @@ -- [E172] Type Error: tests/neg/given-ambiguous-default-1.scala:18:23 -------------------------------------------------- 18 |def f: Unit = summon[B] // error: Ambiguous given instances | ^ - |Ambiguous given instances: both given instance a1 and given instance a2 match type A of parameter a of given instance given_B + | No given instance of type B was found for parameter x of method summon in object Predef. + | I found: + | + | given_B(a = /* ambiguous: both given instance a1 and given instance a2 match type A */summon[A]) + | + | But both given instance a1 and given instance a2 match type A. diff --git a/tests/neg/given-ambiguous-default-2.check b/tests/neg/given-ambiguous-default-2.check index 25e9914e8288..10094f652485 100644 --- a/tests/neg/given-ambiguous-default-2.check +++ b/tests/neg/given-ambiguous-default-2.check @@ -1,4 +1,9 @@ -- [E172] Type Error: tests/neg/given-ambiguous-default-2.scala:18:23 -------------------------------------------------- 18 |def f: Unit = summon[C] // error: Ambiguous given instances | ^ - |Ambiguous given instances: both given instance a1 and given instance a2 match type A of parameter a of given instance given_C + |No given instance of type C was found for parameter x of method summon in object Predef. + |I found: + | + | given_C(a = /* ambiguous: both given instance a1 and given instance a2 match type A */summon[A], this.given_C$default$2) + | + |But both given instance a1 and given instance a2 match type A. diff --git a/tests/neg/i8827a.check b/tests/neg/i8827a.check index 8ebc50caf128..3d6c2bfa500b 100644 --- a/tests/neg/i8827a.check +++ b/tests/neg/i8827a.check @@ -1,7 +1,12 @@ -- [E172] Type Error: tests/neg/i8827a.scala:16:26 --------------------------------------------------------------------- 16 | summon[Order[List[Foo]]] // error | ^ - | No given instance of type pkg.Order[pkg.Foo] was found for parameter orderA of method orderList in object Order + | No given instance of type pkg.Order[List[pkg.Foo]] was found for parameter x of method summon in object Predef. + | I found: + | + | pkg.Order.orderList[pkg.Foo](/* missing */summon[pkg.Order[pkg.Foo]]) + | + | But no implicit values were found that match type pkg.Order[pkg.Foo]. | | The following import might fix the problem: | diff --git a/tests/neg/i8827b.check b/tests/neg/i8827b.check index de726ede38d1..6848c53aee28 100644 --- a/tests/neg/i8827b.check +++ b/tests/neg/i8827b.check @@ -1,7 +1,12 @@ -- [E172] Type Error: tests/neg/i8827b.scala:16:28 --------------------------------------------------------------------- 16 | summon[Order[Option[Foo]]] // error | ^ - |No given instance of type pkg.Order[pkg.Foo] was found for parameter orderA of given instance given_Order_Option in object Order + |No given instance of type pkg.Order[Option[pkg.Foo]] was found for parameter x of method summon in object Predef. + |I found: + | + | pkg.Order.given_Order_Option[pkg.Foo](/* missing */summon[pkg.Order[pkg.Foo]]) + | + |But no implicit values were found that match type pkg.Order[pkg.Foo]. | |The following import might fix the problem: | diff --git a/tests/neg/i9568.check b/tests/neg/i9568.check index 744023714a69..3f318d0b0111 100644 --- a/tests/neg/i9568.check +++ b/tests/neg/i9568.check @@ -4,10 +4,13 @@ | No given instance of type => Monad[F] was found for parameter ev of method blaMonad in object Test. | I found: | - | Test.blaMonad[F², S] + | Test.blaMonad[F², S](Test.blaMonad[F³, S²]) | - | But method blaMonad in object Test does not match type => Monad[F] + | But method blaMonad in object Test does not match type => Monad[F²] | | where: F is a type variable with constraint <: [_] =>> Any | F² is a type variable with constraint <: [_] =>> Any + | F³ is a type variable with constraint <: [_] =>> Any + | S is a type variable + | S² is a type variable | . diff --git a/tests/neg/implicitSearch.check b/tests/neg/implicitSearch.check index 01325c5bf736..e8efc744ac0a 100644 --- a/tests/neg/implicitSearch.check +++ b/tests/neg/implicitSearch.check @@ -1,7 +1,12 @@ -- [E172] Type Error: tests/neg/implicitSearch.scala:13:12 ------------------------------------------------------------- 13 | sort(xs) // error (with a partially constructed implicit argument shown) | ^ - | No given instance of type Test.Ord[T] was found for parameter o of method listOrd in object Test + | No given instance of type Test.Ord[List[List[T]]] was found for parameter o of method sort in object Test. + | I found: + | + | Test.listOrd[List[T]](Test.listOrd[T](/* missing */summon[Test.Ord[T]])) + | + | But no implicit values were found that match type Test.Ord[T]. -- [E172] Type Error: tests/neg/implicitSearch.scala:15:38 ------------------------------------------------------------- 15 | listOrd(listOrd(implicitly[Ord[T]] /*not found*/)) // error | ^ diff --git a/tests/neg/missing-implicit3.check b/tests/neg/missing-implicit3.check index 1b1df3d5a46d..c58b4430f3fe 100644 --- a/tests/neg/missing-implicit3.check +++ b/tests/neg/missing-implicit3.check @@ -1,9 +1,14 @@ -- [E172] Type Error: tests/neg/missing-implicit3.scala:13:36 ---------------------------------------------------------- 13 |val sortedFoos = sort(List(new Foo)) // error | ^ - |No given instance of type ord.Foo => Comparable[? >: ord.Foo] was found for parameter x$1 of given instance ordered in object Ord + | No given instance of type ord.Ord[ord.Foo] was found for a context parameter of method sort in package ord. + | I found: | - |The following import might make progress towards fixing the problem: + | ord.Ord.ordered[ord.Foo](/* missing */summon[ord.Foo => Comparable[? >: ord.Foo]]) | - | import scala.math.Ordered.orderingToOrdered + | But no implicit values were found that match type ord.Foo => Comparable[? >: ord.Foo]. + | + | The following import might make progress towards fixing the problem: + | + | import scala.math.Ordered.orderingToOrdered | From a04403c9484d5bd1e51799d0ccc16d6fad7b264e Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 30 Apr 2024 19:31:56 +0200 Subject: [PATCH 4/4] Output "no best given instance" for ambiguous implicit error messages [Cherry-picked ebf58bbfcae00a86a7247cb18e031611eccec918] --- .../src/dotty/tools/dotc/reporting/messages.scala | 11 ++++++++++- tests/neg/19414-desugared.check | 2 +- tests/neg/19414.check | 2 +- tests/neg/given-ambiguous-1.check | 2 +- tests/neg/given-ambiguous-default-1.check | 2 +- tests/neg/given-ambiguous-default-2.check | 2 +- 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 365253c864be..c8e6fe21409e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2913,11 +2913,20 @@ class MissingImplicitArgument( def location(preposition: String) = if (where.isEmpty) "" else s" $preposition $where" + /** Default error message for non-nested ambiguous implicits. */ def defaultAmbiguousImplicitMsg(ambi: AmbiguousImplicits) = s"Ambiguous given instances: ${ambi.explanation}${location("of")}" + /** Default error messages for non-ambiguous implicits, or nested ambiguous + * implicits. + * + * The default message is shown for ambiguous implicits only if they have + * the `nested` flag set. In this case, we output "no best given instance" + * instead of "no given instance". + */ def defaultImplicitNotFoundMessage = - i"No given instance of type $pt was found${location("for")}" + val bestStr = if arg.tpe.isInstanceOf[AmbiguousImplicits] then " best" else "" + i"No$bestStr given instance of type $pt was found${location("for")}" /** Construct a custom error message given an ambiguous implicit * candidate `alt` and a user defined message `raw`. diff --git a/tests/neg/19414-desugared.check b/tests/neg/19414-desugared.check index e126404e3e4f..c21806e16c2c 100644 --- a/tests/neg/19414-desugared.check +++ b/tests/neg/19414-desugared.check @@ -1,7 +1,7 @@ -- [E172] Type Error: tests/neg/19414-desugared.scala:22:34 ------------------------------------------------------------ 22 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances | ^ - |No given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef. + |No best given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef. |I found: | | given_BodySerializer_B[B]( diff --git a/tests/neg/19414.check b/tests/neg/19414.check index f80f1681739d..6804546df037 100644 --- a/tests/neg/19414.check +++ b/tests/neg/19414.check @@ -1,7 +1,7 @@ -- [E172] Type Error: tests/neg/19414.scala:15:34 ---------------------------------------------------------------------- 15 | summon[BodySerializer[JsObject]] // error: Ambiguous given instances | ^ - |No given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef. + |No best given instance of type BodySerializer[JsObject] was found for parameter x of method summon in object Predef. |I found: | | given_BodySerializer_B[B]( diff --git a/tests/neg/given-ambiguous-1.check b/tests/neg/given-ambiguous-1.check index 017714de2ea7..ed64164b351f 100644 --- a/tests/neg/given-ambiguous-1.check +++ b/tests/neg/given-ambiguous-1.check @@ -1,7 +1,7 @@ -- [E172] Type Error: tests/neg/given-ambiguous-1.scala:12:23 ---------------------------------------------------------- 12 |def f: Unit = summon[B] // error: Ambiguous given instances | ^ - | No given instance of type B was found for parameter x of method summon in object Predef. + | No best given instance of type B was found for parameter x of method summon in object Predef. | I found: | | given_B(/* ambiguous: both given instance a1 and given instance a2 match type A */summon[A]) diff --git a/tests/neg/given-ambiguous-default-1.check b/tests/neg/given-ambiguous-default-1.check index 0b24a89b82cf..1a5006c23055 100644 --- a/tests/neg/given-ambiguous-default-1.check +++ b/tests/neg/given-ambiguous-default-1.check @@ -1,7 +1,7 @@ -- [E172] Type Error: tests/neg/given-ambiguous-default-1.scala:18:23 -------------------------------------------------- 18 |def f: Unit = summon[B] // error: Ambiguous given instances | ^ - | No given instance of type B was found for parameter x of method summon in object Predef. + | No best given instance of type B was found for parameter x of method summon in object Predef. | I found: | | given_B(a = /* ambiguous: both given instance a1 and given instance a2 match type A */summon[A]) diff --git a/tests/neg/given-ambiguous-default-2.check b/tests/neg/given-ambiguous-default-2.check index 10094f652485..cbe8b972a389 100644 --- a/tests/neg/given-ambiguous-default-2.check +++ b/tests/neg/given-ambiguous-default-2.check @@ -1,7 +1,7 @@ -- [E172] Type Error: tests/neg/given-ambiguous-default-2.scala:18:23 -------------------------------------------------- 18 |def f: Unit = summon[C] // error: Ambiguous given instances | ^ - |No given instance of type C was found for parameter x of method summon in object Predef. + |No best given instance of type C was found for parameter x of method summon in object Predef. |I found: | | given_C(a = /* ambiguous: both given instance a1 and given instance a2 match type A */summon[A], this.given_C$default$2)