Skip to content

Commit 084ab1a

Browse files
authored
Add note about type mismatch in automatically inserted apply argument (#20023)
Started during the last spree with @jan-pieter and @iusildra. Fixes #19680.
2 parents 2e41763 + 748596a commit 084ab1a

File tree

9 files changed

+102
-4
lines changed

9 files changed

+102
-4
lines changed

compiler/src/dotty/tools/dotc/reporting/ExploringReporter.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ class ExploringReporter extends StoreReporter(null, fromTyperState = false):
1818
override def removeBufferedMessages(using Context): List[Diagnostic] =
1919
try infos.toList finally reset()
2020

21+
override def mapBufferedMessages(f: Diagnostic => Diagnostic)(using Context): Unit =
22+
infos.mapInPlace(f)
23+
2124
def reset(): Unit = infos.clear()
2225

23-
end ExploringReporter
26+
end ExploringReporter

compiler/src/dotty/tools/dotc/reporting/Reporter.scala

+3
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ abstract class Reporter extends interfaces.ReporterResult {
269269
/** If this reporter buffers messages, remove and return all buffered messages. */
270270
def removeBufferedMessages(using Context): List[Diagnostic] = Nil
271271

272+
/** If this reporter buffers messages, apply `f` to all buffered messages. */
273+
def mapBufferedMessages(f: Diagnostic => Diagnostic)(using Context): Unit = ()
274+
272275
/** Issue all messages in this reporter to next outer one, or make sure they are written. */
273276
def flush()(using Context): Unit =
274277
val msgs = removeBufferedMessages

compiler/src/dotty/tools/dotc/reporting/StoreReporter.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class StoreReporter(outer: Reporter | Null = Reporter.NoReporter, fromTyperState
2121

2222
protected var infos: mutable.ListBuffer[Diagnostic] | Null = null
2323

24-
def doReport(dia: Diagnostic)(using Context): Unit = {
24+
override def doReport(dia: Diagnostic)(using Context): Unit = {
2525
typr.println(s">>>> StoredError: ${dia.message}") // !!! DEBUG
2626
if (infos == null) infos = new mutable.ListBuffer
2727
infos.uncheckedNN += dia
@@ -37,6 +37,9 @@ class StoreReporter(outer: Reporter | Null = Reporter.NoReporter, fromTyperState
3737
if (infos != null) try infos.uncheckedNN.toList finally infos = null
3838
else Nil
3939

40+
override def mapBufferedMessages(f: Diagnostic => Diagnostic)(using Context): Unit =
41+
if infos != null then infos.uncheckedNN.mapInPlace(f)
42+
4043
override def pendingMessages(using Context): List[Diagnostic] =
4144
if (infos != null) infos.uncheckedNN.toList else Nil
4245

compiler/src/dotty/tools/dotc/reporting/messages.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ extends NotFoundMsg(MissingIdentID) {
290290
}
291291
}
292292

293-
class TypeMismatch(val found: Type, expected: Type, inTree: Option[untpd.Tree], addenda: => String*)(using Context)
293+
class TypeMismatch(val found: Type, expected: Type, val inTree: Option[untpd.Tree], addenda: => String*)(using Context)
294294
extends TypeMismatchMsg(found, expected)(TypeMismatchID):
295295

296296
def msg(using Context) =

compiler/src/dotty/tools/dotc/typer/Applications.scala

+32-1
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,33 @@ trait Applications extends Compatibility {
10371037
}
10381038
}
10391039

1040+
/** If the applied function is an automatically inserted `apply`
1041+
* method and one of its arguments has a type mismatch , append
1042+
* a note to the error message that explains where the required
1043+
* type comes from. See #19680 and associated test case.
1044+
*/
1045+
def maybeAddInsertedApplyNote(failedState: TyperState, fun1: Tree)(using Context): Unit =
1046+
if fun1.symbol.name == nme.apply && fun1.span.isSynthetic then
1047+
fun1 match
1048+
case Select(qualifier, _) =>
1049+
def mapMessage(dia: Diagnostic): Diagnostic =
1050+
dia match
1051+
case dia: Diagnostic.Error =>
1052+
dia.msg match
1053+
case msg: TypeMismatch =>
1054+
msg.inTree match
1055+
case Some(arg) if tree.args.exists(_.span == arg.span) =>
1056+
val noteText =
1057+
i"""The required type comes from a parameter of the automatically
1058+
|inserted `apply` method of `${qualifier.tpe}`.""".stripMargin
1059+
Diagnostic.Error(msg.appendExplanation("\n\n" + noteText), dia.pos)
1060+
case _ => dia
1061+
case msg => dia
1062+
case dia => dia
1063+
failedState.reporter.mapBufferedMessages(mapMessage)
1064+
case _ => ()
1065+
else ()
1066+
10401067
fun1.tpe match {
10411068
case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs()).withType(err)
10421069
case TryDynamicCallType =>
@@ -1097,7 +1124,11 @@ trait Applications extends Compatibility {
10971124
simpleApply(fun1, proto)
10981125
} {
10991126
(failedVal, failedState) =>
1100-
def fail = { failedState.commit(); failedVal }
1127+
def fail =
1128+
maybeAddInsertedApplyNote(failedState, fun1)
1129+
failedState.commit()
1130+
failedVal
1131+
11011132
// Try once with original prototype and once (if different) with tupled one.
11021133
// The reason we need to try both is that the decision whether to use tupled
11031134
// or not was already taken but might have to be revised when an implicit

tests/neg/19680.check

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-- [E007] Type Mismatch Error: tests/neg/19680.scala:9:67 --------------------------------------------------------------
2+
9 |def renderWidget(using Config): Unit = renderWebsite("/tmp")(Config()) // error: found Config, required Int
3+
| ^^^^^^^^
4+
| Found: Config
5+
| Required: Int
6+
|---------------------------------------------------------------------------------------------------------------------
7+
| Explanation (enabled by `-explain`)
8+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
9+
|
10+
| Tree: new Config()
11+
| I tried to show that
12+
| Config
13+
| conforms to
14+
| Int
15+
| but none of the attempts shown below succeeded:
16+
|
17+
| ==> Config <: Int = false
18+
|
19+
| The tests were made under the empty constraint
20+
|
21+
| The required type comes from a parameter of the automatically
22+
| inserted `apply` method of `scala.collection.StringOps`.
23+
---------------------------------------------------------------------------------------------------------------------

tests/neg/19680.scala

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//> using options -explain
2+
3+
// Tests that the error message indicates that the required type `Int` comes
4+
// from the automatically inserted `apply` method of `String`. This note is
5+
// inserted by `insertedApplyNote` in `Applications`.
6+
7+
class Config()
8+
def renderWebsite(path: String)(using config: Config): String = ???
9+
def renderWidget(using Config): Unit = renderWebsite("/tmp")(Config()) // error: found Config, required Int

tests/neg/19680b.check

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-- [E007] Type Mismatch Error: tests/neg/19680b.scala:2:21 -------------------------------------------------------------
2+
2 |def Test = List(1,2)("hello") // error: found String, required Int
3+
| ^^^^^^^
4+
| Found: ("hello" : String)
5+
| Required: Int
6+
|---------------------------------------------------------------------------------------------------------------------
7+
| Explanation (enabled by `-explain`)
8+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
9+
|
10+
| Tree: "hello"
11+
| I tried to show that
12+
| ("hello" : String)
13+
| conforms to
14+
| Int
15+
| but none of the attempts shown below succeeded:
16+
|
17+
| ==> ("hello" : String) <: Int
18+
| ==> String <: Int = false
19+
|
20+
| The tests were made under the empty constraint
21+
|
22+
| The required type comes from a parameter of the automatically
23+
| inserted `apply` method of `List[Int]`.
24+
---------------------------------------------------------------------------------------------------------------------

tests/neg/19680b.scala

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
//> using options -explain
2+
def Test = List(1,2)("hello") // error: found String, required Int

0 commit comments

Comments
 (0)