diff --git a/compiler/src/dotty/tools/dotc/reporting/ExploringReporter.scala b/compiler/src/dotty/tools/dotc/reporting/ExploringReporter.scala index f469c03764c0..99720b8e4d29 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ExploringReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ExploringReporter.scala @@ -18,6 +18,9 @@ class ExploringReporter extends StoreReporter(null, fromTyperState = false): override def removeBufferedMessages(using Context): List[Diagnostic] = try infos.toList finally reset() + override def mapBufferedMessages(f: Diagnostic => Diagnostic)(using Context): Unit = + infos.mapInPlace(f) + def reset(): Unit = infos.clear() -end ExploringReporter \ No newline at end of file +end ExploringReporter diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 237a3f166fe8..ddea384f4832 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -269,6 +269,9 @@ abstract class Reporter extends interfaces.ReporterResult { /** If this reporter buffers messages, remove and return all buffered messages. */ def removeBufferedMessages(using Context): List[Diagnostic] = Nil + /** If this reporter buffers messages, apply `f` to all buffered messages. */ + def mapBufferedMessages(f: Diagnostic => Diagnostic)(using Context): Unit = () + /** Issue all messages in this reporter to next outer one, or make sure they are written. */ def flush()(using Context): Unit = val msgs = removeBufferedMessages diff --git a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala index aef5f2c5863b..9395788d4cc7 100644 --- a/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala @@ -21,7 +21,7 @@ class StoreReporter(outer: Reporter | Null = Reporter.NoReporter, fromTyperState protected var infos: mutable.ListBuffer[Diagnostic] | Null = null - def doReport(dia: Diagnostic)(using Context): Unit = { + override def doReport(dia: Diagnostic)(using Context): Unit = { typr.println(s">>>> StoredError: ${dia.message}") // !!! DEBUG if (infos == null) infos = new mutable.ListBuffer infos.uncheckedNN += dia @@ -37,6 +37,9 @@ class StoreReporter(outer: Reporter | Null = Reporter.NoReporter, fromTyperState if (infos != null) try infos.uncheckedNN.toList finally infos = null else Nil + override def mapBufferedMessages(f: Diagnostic => Diagnostic)(using Context): Unit = + if infos != null then infos.uncheckedNN.mapInPlace(f) + override def pendingMessages(using Context): List[Diagnostic] = if (infos != null) infos.uncheckedNN.toList else Nil diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 07ab1f21d6a0..c6431a4af5fd 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -289,7 +289,7 @@ extends NotFoundMsg(MissingIdentID) { } } -class TypeMismatch(val found: Type, expected: Type, inTree: Option[untpd.Tree], addenda: => String*)(using Context) +class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tree], addenda: => String*)(using Context) extends TypeMismatchMsg(found, expected)(TypeMismatchID): def msg(using Context) = diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 184b250e94fb..f406ec5f26c0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1084,7 +1084,37 @@ trait Applications extends Compatibility { simpleApply(fun1, proto) } { (failedVal, failedState) => - def fail = { failedState.commit(); failedVal } + def fail = + insertedApplyNote() + failedState.commit() + failedVal + + /** If the applied function is an automatically inserted `apply` + * method and one of its arguments has a type mismatch , append + * a note to the error message that explains where the required + * type comes from. See #19680 and associated test case. + */ + def insertedApplyNote() = + if fun1.symbol.name == nme.apply && fun1.span.isSynthetic then + fun1 match + case Select(qualifier, _) => + failedState.reporter.mapBufferedMessages: + case dia: Diagnostic.Error => + dia.msg match + case msg: TypeMismatch => + msg.inTree match + case Some(arg) if tree.args.exists(_.span == arg.span) => + val Select(qualifier, _) = fun1: @unchecked + val noteText = + i"""The required type comes from a parameter of the automatically + |inserted `apply` method of `${qualifier.tpe}`, + |which is the type of `${qualifier.show}`.""".stripMargin + Diagnostic.Error(msg.appendExplanation("\n\n" + noteText), dia.pos) + case _ => dia + case msg => dia + case dia => dia + case _ => () + // Try once with original prototype and once (if different) with tupled one. // The reason we need to try both is that the decision whether to use tupled // or not was already taken but might have to be revised when an implicit diff --git a/tests/neg/19680.check b/tests/neg/19680.check new file mode 100644 index 000000000000..8372d5129960 --- /dev/null +++ b/tests/neg/19680.check @@ -0,0 +1,24 @@ +-- [E007] Type Mismatch Error: tests/neg/19680.scala:9:67 -------------------------------------------------------------- +9 |def renderWidget(using Config): Unit = renderWebsite("/tmp")(Config()) // error: found Config, required Int + | ^^^^^^^^ + | Found: Config + | Required: Int + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | + | Tree: new Config() + | I tried to show that + | Config + | conforms to + | Int + | but none of the attempts shown below succeeded: + | + | ==> Config <: Int = false + | + | The tests were made under the empty constraint + | + | The required type comes from a parameter of the automatically + | inserted `apply` method of `scala.collection.StringOps`, + | which is the type of `augmentString(renderWebsite("/tmp")(x$1))`. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/19680.scala b/tests/neg/19680.scala new file mode 100644 index 000000000000..57fdd851dc54 --- /dev/null +++ b/tests/neg/19680.scala @@ -0,0 +1,9 @@ +//> using options -explain + +// Tests that the error message indicates that the required type `Int` comes +// from the automatically inserted `apply` method of `String`. This note is +// inserted by `insertedApplyNote` in `Applications`. + +class Config() +def renderWebsite(path: String)(using config: Config): String = ??? +def renderWidget(using Config): Unit = renderWebsite("/tmp")(Config()) // error: found Config, required Int diff --git a/tests/neg/19680b.check b/tests/neg/19680b.check new file mode 100644 index 000000000000..14f2a30c5caa --- /dev/null +++ b/tests/neg/19680b.check @@ -0,0 +1,25 @@ +-- [E007] Type Mismatch Error: tests/neg/19680b.scala:2:21 ------------------------------------------------------------- +2 |def Test = List(1,2)("hello") // error: found String, required Int + | ^^^^^^^ + | Found: ("hello" : String) + | Required: Int + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | + | Tree: "hello" + | I tried to show that + | ("hello" : String) + | conforms to + | Int + | but none of the attempts shown below succeeded: + | + | ==> ("hello" : String) <: Int + | ==> String <: Int = false + | + | The tests were made under the empty constraint + | + | The required type comes from a parameter of the automatically + | inserted `apply` method of `List[Int]`, + | which is the type of `List.apply[Int]([1,2 : Int]*)`. + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/19680b.scala b/tests/neg/19680b.scala new file mode 100644 index 000000000000..a089d23e6a32 --- /dev/null +++ b/tests/neg/19680b.scala @@ -0,0 +1,2 @@ +//> using options -explain +def Test = List(1,2)("hello") // error: found String, required Int