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
Co-Authored-By: Jan-Pieter van den Heuvel <1197006+jan-pieter@users.noreply.github.com>
Co-Authored-By: Lucas Nouguier <lucas.nouguier@protonmail.com>
  • Loading branch information
3 people committed Apr 26, 2024
1 parent 912d886 commit 2c70f3d
Show file tree
Hide file tree
Showing 9 changed files with 103 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 @@ -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) =
Expand Down
32 changes: 31 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions tests/neg/19680.check
Original file line number Diff line number Diff line change
@@ -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))`.
---------------------------------------------------------------------------------------------------------------------
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
25 changes: 25 additions & 0 deletions tests/neg/19680b.check
Original file line number Diff line number Diff line change
@@ -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]*)`.
---------------------------------------------------------------------------------------------------------------------
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 2c70f3d

Please sign in to comment.