From 127372d6b6c44a64561dc1c7bba0b0f9e633552f Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Tue, 26 Mar 2024 15:10:11 +0100 Subject: [PATCH] Add note about type mismatch in automatically inserted apply argument Co-Authored-By: Jan-Pieter van den Heuvel <1197006+jan-pieter@users.noreply.github.com> Co-Authored-By: Lucas Nouguier --- .../dotty/tools/dotc/reporting/Reporter.scala | 5 ++++ .../tools/dotc/reporting/StoreReporter.scala | 2 +- .../dotty/tools/dotc/reporting/messages.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 27 ++++++++++++++++++- tests/pos/19680.scala | 3 +++ 5 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 tests/pos/19680.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index 22500cbbaa48..db63cd7a7ce4 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -263,6 +263,11 @@ 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 = + val mappedDiagnostics = removeBufferedMessages.map(f) + mappedDiagnostics.foreach(report) + /** 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..532aeab9d383 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 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 484bc88c0983..621da58e80f5 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 82f4c89ae203..33707652026a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -23,6 +23,7 @@ import Inferencing.* import reporting.* import Nullables.*, NullOpsDecorator.* import config.Feature +import printing.Texts.{stringToText, Text} import collection.mutable import config.Printers.{overload, typr, unapp} @@ -1068,7 +1069,31 @@ 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 mistmathc , append a note + * to the error message that explains that the required type comes from a parameter + * of the inserted `apply` method. See #19680 and associated test case. + */ + def insertedApplyNote() = + if fun1.symbol.name == nme.apply && fun1.span.isSynthetic then + 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(prefix, _apply) = fun1: @unchecked + val txt = ("\n\nThe required type comes from a parameter of the automatically inserted " ~ ("`" + ctx.printer.nameString(nme.apply ) + "`") ~ " method of " ~ prefix.tpe.show ~ ", which is the type of " ~ prefix.show ~ ".").show + Diagnostic.Error(msg.append(txt), dia.pos) + case _ => dia + case msg => dia + case dia => dia + // 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/pos/19680.scala b/tests/pos/19680.scala new file mode 100644 index 000000000000..360a7908a4e7 --- /dev/null +++ b/tests/pos/19680.scala @@ -0,0 +1,3 @@ +class Config() +def renderWebsite(path: String)(using config: Config): String = ??? +def renderWidget(using Config): Unit = renderWebsite("/tmp")(Config())