diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 03a3d8e57438..eb4e542f0281 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2737,19 +2737,27 @@ class MissingImplicitArgument( val idx = paramNames.indexOf(name) if (idx >= 0) Some(i"${args(idx)}") else None """\$\{\s*([^}\s]+)\s*\}""".r.replaceAllIn(raw, (_: Regex.Match) match - case Regex.Groups(v) => quoteReplacement(translate(v).getOrElse("")).nn + case Regex.Groups(v) => quoteReplacement(translate(v).getOrElse("?" + v)).nn ) /** @param rawMsg Message template with variables, e.g. "Variable A is ${A}" * @param sym Symbol of the annotated type or of the method whose parameter was annotated + * @param paramNames Names of type parameters to substitute with `args` in the message template + * @param args Resolved type arguments to substitute for `paramNames` in the message template * @param substituteType Function substituting specific types for abstract types associated with variables, e.g A -> Int */ - def formatAnnotationMessage(rawMsg: String, sym: Symbol, substituteType: Type => Type)(using Context): String = + def formatAnnotationMessage( + rawMsg: String, + sym: Symbol, + paramNames: List[Name], + args: List[Type], + substituteType: Type => Type, + )(using Context): String = val substitutableTypesSymbols = substitutableTypeSymbolsInScope(sym) userDefinedErrorString( rawMsg, - paramNames = substitutableTypesSymbols.map(_.name.unexpandedName.toString), - args = substitutableTypesSymbols.map(_.typeRef).map(substituteType) + paramNames = (paramNames ::: substitutableTypesSymbols.map(_.name)).map(_.unexpandedName.toString), + args = args ::: substitutableTypesSymbols.map(_.typeRef).map(substituteType) ) /** Extract a user defined error message from a symbol `sym` @@ -2761,14 +2769,17 @@ class MissingImplicitArgument( msg <- ann.argumentConstantString(0) yield msg - def userDefinedImplicitNotFoundTypeMessageFor(sym: Symbol)(using Context): Option[String] = - for - rawMsg <- userDefinedMsg(sym, defn.ImplicitNotFoundAnnot) - if Feature.migrateTo3 || sym != defn.Function1 - // Don't inherit "No implicit view available..." message if subtypes of Function1 are not treated as implicit conversions anymore - yield - val substituteType = (_: Type).asSeenFrom(pt, sym) - formatAnnotationMessage(rawMsg, sym, substituteType) + def userDefinedImplicitNotFoundTypeMessageFor( + sym: Symbol, + params: List[ParamInfo] = Nil, + args: List[Type] = Nil + )(using Context): Option[String] = for + rawMsg <- userDefinedMsg(sym, defn.ImplicitNotFoundAnnot) + if Feature.migrateTo3 || sym != defn.Function1 + // Don't inherit "No implicit view available..." message if subtypes of Function1 are not treated as implicit conversions anymore + yield + val paramNames = params.map(_.paramName) + formatAnnotationMessage(rawMsg, sym, paramNames, args, _.asSeenFrom(pt, sym)) /** Extracting the message from a method parameter, e.g. in * @@ -2783,19 +2794,22 @@ class MissingImplicitArgument( val targs = tpd.typeArgss(applTree).flatten val methodOwner = fn.symbol.owner val methodOwnerType = tpd.qualifier(fn).tpe - val methodTypeParams = fn.symbol.paramSymss.flatten.filter(_.isType) + val methodTypeParams = fn.symbol.paramSymss.flatten.withFilter(_.isType).map(_.name) val methodTypeArgs = targs.map(_.tpe) - val substituteType = (_: Type).asSeenFrom(methodOwnerType, methodOwner).subst(methodTypeParams, methodTypeArgs) - formatAnnotationMessage(rawMsg, sym.owner, substituteType) + formatAnnotationMessage(rawMsg, sym.owner, methodTypeParams, methodTypeArgs, _.asSeenFrom(methodOwnerType, methodOwner)) def userDefinedImplicitNotFoundTypeMessage(using Context): Option[String] = - def recur(tp: Type): Option[String] = tp match + def recur(tp: Type, params: List[ParamInfo] = Nil, args: List[Type] = Nil): Option[String] = tp match + case tp: AppliedType => + val tycon = tp.typeConstructor + val typeParams = if tycon.isLambdaSub then tycon.hkTypeParams else tycon.typeParams + recur(tycon, typeParams ::: params, tp.args ::: args) case tp: TypeRef => - val sym = tp.symbol - userDefinedImplicitNotFoundTypeMessageFor(sym).orElse(recur(tp.info)) + userDefinedImplicitNotFoundTypeMessageFor(tp.symbol, params, args) + .orElse(recur(tp.info)) case tp: ClassInfo => tp.baseClasses.iterator - .map(userDefinedImplicitNotFoundTypeMessageFor) + .map(userDefinedImplicitNotFoundTypeMessageFor(_)) .find(_.isDefined).flatten case tp: TypeProxy => recur(tp.superType) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index bccbcbee29e1..f3e3507add11 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -266,12 +266,11 @@ trait ParallelTesting extends RunnerOrchestration { self => */ final def diffTest(testSource: TestSource, checkFile: JFile, actual: List[String], reporters: Seq[TestReporter], logger: LoggedRunnable) = { for (msg <- FileDiff.check(testSource.title, actual, checkFile.getPath)) { - onFailure(testSource, reporters, logger, Some(msg)) - if (updateCheckFiles) { FileDiff.dump(checkFile.toPath.toString, actual) echo("Updated checkfile: " + checkFile.getPath) } else { + onFailure(testSource, reporters, logger, Some(msg)) val outFile = checkFile.toPath.resolveSibling(s"${checkFile.toPath.getFileName}.out").toString FileDiff.dump(outFile, actual) echo(FileDiff.diffMessage(checkFile.getPath, outFile)) diff --git a/tests/neg/i4986c.check b/tests/neg/i4986c.check index 8befc30f5a60..0517ae10e427 100644 --- a/tests/neg/i4986c.check +++ b/tests/neg/i4986c.check @@ -61,4 +61,4 @@ -- [E172] Type Error: tests/neg/i4986c.scala:62:19 --------------------------------------------------------------------- 62 | i.m[Option[Long]] // error | ^ - | String; List; [A, _] =>> List[Option[?]]; Int; Option[Long]; + | String; List; [A, _] =>> List[Option[?]]; Int; Option[Long]; ?XX diff --git a/tests/neg/i7092.check b/tests/neg/i7092.check new file mode 100644 index 000000000000..bd4bdc783a08 --- /dev/null +++ b/tests/neg/i7092.check @@ -0,0 +1,32 @@ +-- [E172] Type Error: tests/neg/i7092.scala:24:19 ---------------------------------------------------------------------- +24 | summon[F[String]] // error + | ^ + | Not found for String +-- [E172] Type Error: tests/neg/i7092.scala:25:19 ---------------------------------------------------------------------- +25 | summon[G[String]] // error + | ^ + | Not found for String +-- [E172] Type Error: tests/neg/i7092.scala:26:16 ---------------------------------------------------------------------- +26 | summon[H[Int]] // error + | ^ + | Not found for Int, ?B +-- [E172] Type Error: tests/neg/i7092.scala:27:23 ---------------------------------------------------------------------- +27 | summon[H[Int][Float]] // error + | ^ + | Not found for Int, Float +-- [E172] Type Error: tests/neg/i7092.scala:28:18 ---------------------------------------------------------------------- +28 | summon[AAA[Int]] // error + | ^ + | Not found for Int +-- [E172] Type Error: tests/neg/i7092.scala:29:25 ---------------------------------------------------------------------- +29 | summon[AAA[Int][Float]] // error + | ^ + | Not found for Int +-- [E172] Type Error: tests/neg/i7092.scala:30:19 ---------------------------------------------------------------------- +30 | summon[op.F[Int]] // error + | ^ + | Could not find Int +-- [E172] Type Error: tests/neg/i7092.scala:31:28 ---------------------------------------------------------------------- +31 | summon[String =!:= String] // error + | ^ + | Cannot proof type inequality because types are equal: String =:= String diff --git a/tests/neg/i7092.scala b/tests/neg/i7092.scala new file mode 100644 index 000000000000..9481008b9d70 --- /dev/null +++ b/tests/neg/i7092.scala @@ -0,0 +1,31 @@ +import scala.annotation.implicitNotFound +import scala.util.NotGiven + +@implicitNotFound("Not found for ${A}") +type F[A] + +@implicitNotFound("Not found for ${A}") +trait G[A] + +@implicitNotFound("Not found for ${A}, ${B}") +type H = [A] =>> [B] =>> (A, B) + +@implicitNotFound("Not found for ${A}") +type AAA = [A] =>> [A] =>> A + +object op: + @implicitNotFound("Could not find ${A}") + opaque type F[A] = A + +@implicitNotFound("Cannot proof type inequality because types are equal: ${A} =:= ${B}") +type =!:=[A, B] = NotGiven[A =:= B] + +object Test: + summon[F[String]] // error + summon[G[String]] // error + summon[H[Int]] // error + summon[H[Int][Float]] // error + summon[AAA[Int]] // error + summon[AAA[Int][Float]] // error + summon[op.F[Int]] // error + summon[String =!:= String] // error