Skip to content

Commit

Permalink
Add note about type mismatch in automatically inserted apply argument (
Browse files Browse the repository at this point in the history
…#20023)

Started during the last spree with @jan-pieter and @iusildra.

Fixes #19680.
  • Loading branch information
odersky committed May 1, 2024
2 parents 2e41763 + 748596a commit 084ab1a
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
end ExploringReporter
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/reporting/Reporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,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) =
Expand Down
33 changes: 32 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,33 @@ trait Applications extends Compatibility {
}
}

/** 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 maybeAddInsertedApplyNote(failedState: TyperState, fun1: Tree)(using Context): Unit =
if fun1.symbol.name == nme.apply && fun1.span.isSynthetic then
fun1 match
case Select(qualifier, _) =>
def mapMessage(dia: Diagnostic): Diagnostic =
dia match
case dia: Diagnostic.Error =>
dia.msg match
case msg: TypeMismatch =>
msg.inTree match
case Some(arg) if tree.args.exists(_.span == arg.span) =>
val noteText =
i"""The required type comes from a parameter of the automatically
|inserted `apply` method of `${qualifier.tpe}`.""".stripMargin
Diagnostic.Error(msg.appendExplanation("\n\n" + noteText), dia.pos)
case _ => dia
case msg => dia
case dia => dia
failedState.reporter.mapBufferedMessages(mapMessage)
case _ => ()
else ()

fun1.tpe match {
case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs()).withType(err)
case TryDynamicCallType =>
Expand Down Expand Up @@ -1097,7 +1124,11 @@ trait Applications extends Compatibility {
simpleApply(fun1, proto)
} {
(failedVal, failedState) =>
def fail = { failedState.commit(); failedVal }
def fail =
maybeAddInsertedApplyNote(failedState, fun1)
failedState.commit()
failedVal

// 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
Expand Down
23 changes: 23 additions & 0 deletions tests/neg/19680.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- [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`.
---------------------------------------------------------------------------------------------------------------------
9 changes: 9 additions & 0 deletions tests/neg/19680.scala
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions tests/neg/19680b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- [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]`.
---------------------------------------------------------------------------------------------------------------------
2 changes: 2 additions & 0 deletions tests/neg/19680b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
//> using options -explain
def Test = List(1,2)("hello") // error: found String, required Int

0 comments on commit 084ab1a

Please sign in to comment.