Skip to content

Commit 516978a

Browse files
Merge pull request #8317 from dotty-staging/fix-unapply-reporting
Better error reporting for patterns
2 parents 7d50725 + 8015b0a commit 516978a

File tree

12 files changed

+128
-31
lines changed

12 files changed

+128
-31
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,19 @@ class ConsoleReporter(
2424
def doReport(m: MessageContainer)(implicit ctx: Context): Unit = {
2525
val didPrint = m match {
2626
case m: Error =>
27-
printMessage(messageAndPos(m.contained(), m.pos, diagnosticLevel(m)))
27+
printMessage(messageAndPos(m.contained, m.pos, diagnosticLevel(m)))
2828
if (ctx.settings.Xprompt.value) Reporter.displayPrompt(reader, writer)
2929
true
3030
case m: ConditionalWarning if !m.enablingOption.value =>
3131
false
3232
case m =>
33-
printMessage(messageAndPos(m.contained(), m.pos, diagnosticLevel(m)))
33+
printMessage(messageAndPos(m.contained, m.pos, diagnosticLevel(m)))
3434
true
3535
}
3636

3737
if (didPrint && ctx.shouldExplain(m))
38-
printMessage(explanation(m.contained()))
39-
else if (didPrint && m.contained().explanation.nonEmpty)
38+
printMessage(explanation(m.contained))
39+
else if (didPrint && m.contained.explanation.nonEmpty)
4040
printMessage("\nlonger explanation available when compiling with `-explain`")
4141
}
4242

compiler/src/dotty/tools/dotc/reporting/diagnostic/MessageContainer.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ object MessageContainer {
1515
implicit class MessageContext(val c: Context) extends AnyVal {
1616
def shouldExplain(cont: MessageContainer): Boolean = {
1717
implicit val ctx = c
18-
cont.contained().explanation match {
18+
cont.contained.explanation match {
1919
case "" => false
2020
case _ => ctx.settings.explain.value
2121
}
@@ -39,7 +39,7 @@ class MessageContainer(
3939
/** The message to report */
4040
def message: String = {
4141
if (myMsg == null) {
42-
myMsg = contained().msg.replaceAll("\u001B\\[[;\\d]*m", "")
42+
myMsg = contained.msg.replaceAll("\u001B\\[[;\\d]*m", "")
4343
if (myMsg.contains(nonSensicalStartTag)) {
4444
myIsNonSensical = true
4545
// myMsg might be composed of several d"..." invocations -> nested
@@ -54,7 +54,7 @@ class MessageContainer(
5454
}
5555

5656
/** This function forces the contained message and returns it */
57-
def contained(): Message = {
57+
def contained: Message = {
5858
if (myContained == null)
5959
myContained = msgFn
6060

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

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import collection.mutable
2828
import config.Printers.{overload, typr, unapp}
2929
import TypeApplications._
3030

31-
import reporting.diagnostic.Message
31+
import reporting.diagnostic.{Message, messages}
3232
import reporting.trace
3333
import Constants.{Constant, IntTag, LongTag}
3434
import dotty.tools.dotc.reporting.diagnostic.messages.{UnapplyInvalidReturnType, NotAnExtractor, UnapplyInvalidNumberOfArguments}
@@ -1058,12 +1058,29 @@ trait Applications extends Compatibility {
10581058
record("typedUnApply")
10591059
val Apply(qual, args) = tree
10601060

1061-
def notAnExtractor(tree: Tree) =
1061+
def notAnExtractor(tree: Tree): Tree =
10621062
// prefer inner errors
10631063
// e.g. report not found ident instead of not an extractor in tests/neg/i2950.scala
10641064
if (!tree.tpe.isError && tree.tpe.isErroneous) tree
10651065
else errorTree(tree, NotAnExtractor(qual))
10661066

1067+
/** Report errors buffered in state.
1068+
* @pre state has errors to report
1069+
* If there is a single error stating that "unapply" is not a member, print
1070+
* the more informative "notAnExtractor" message instead.
1071+
*/
1072+
def reportErrors(tree: Tree, state: TyperState): Tree =
1073+
assert(state.reporter.hasErrors)
1074+
val msgs = state.reporter.removeBufferedMessages
1075+
msgs match
1076+
case msg :: Nil =>
1077+
msg.contained match
1078+
case messages.NotAMember(_, nme.unapply, _, _) => return notAnExtractor(tree)
1079+
case _ =>
1080+
case _ =>
1081+
msgs.foreach(ctx.reporter.report)
1082+
tree
1083+
10671084
/** If this is a term ref tree, try to typecheck with its type name.
10681085
* If this refers to a type alias, follow the alias, and if
10691086
* one finds a class, reference the class companion module.
@@ -1097,42 +1114,52 @@ trait Applications extends Compatibility {
10971114
* overloaded unapply does *not* need to be applicable to its argument
10981115
* whereas overloaded variants need to have a conforming variant.
10991116
*/
1100-
def trySelectUnapply(qual: untpd.Tree)(fallBack: Tree => Tree): Tree = {
1117+
def trySelectUnapply(qual: untpd.Tree)(fallBack: (Tree, TyperState) => Tree): Tree = {
11011118
// try first for non-overloaded, then for overloaded ocurrences
1102-
def tryWithName(name: TermName)(fallBack: Tree => Tree)(implicit ctx: Context): Tree = {
1119+
def tryWithName(name: TermName)(fallBack: (Tree, TyperState) => Tree)(implicit ctx: Context): Tree = {
11031120
def tryWithProto(pt: Type)(implicit ctx: Context) = {
11041121
val result = typedExpr(untpd.Select(qual, name), new UnapplyFunProto(pt, this))
1105-
if (!result.symbol.exists || result.symbol.name == name) result
1122+
if !result.symbol.exists
1123+
|| result.symbol.name == name
1124+
|| ctx.reporter.hasErrors
1125+
then result
11061126
else notAnExtractor(result)
11071127
// It might be that the result of typedExpr is an `apply` selection or implicit conversion.
11081128
// Reject in this case.
11091129
}
11101130
tryEither {
11111131
tryWithProto(selType)
11121132
} {
1113-
(sel, _) =>
1133+
(sel, state) =>
11141134
tryEither {
11151135
tryWithProto(WildcardType)
11161136
} {
1117-
(_, _) => fallBack(sel)
1137+
(_, _) => fallBack(sel, state)
11181138
}
11191139
}
11201140
}
11211141

11221142
// try first for unapply, then for unapplySeq
11231143
tryWithName(nme.unapply) {
1124-
sel => tryWithName(nme.unapplySeq)(_ => fallBack(sel)) // for backwards compatibility; will be dropped
1144+
(sel, state) =>
1145+
tryWithName(nme.unapplySeq) {
1146+
(_, _) => fallBack(sel, state)
1147+
}
11251148
}
11261149
}
11271150

11281151
/** Produce a typed qual.unapply or qual.unapplySeq tree, or
11291152
* else if this fails follow a type alias and try again.
11301153
*/
1131-
var unapplyFn = trySelectUnapply(qual) { sel =>
1132-
val qual1 = followTypeAlias(qual)
1133-
if (qual1.isEmpty) notAnExtractor(sel)
1134-
else trySelectUnapply(qual1)(_ => notAnExtractor(sel))
1135-
}
1154+
var unapplyFn =
1155+
trySelectUnapply(qual) {
1156+
(sel, state) =>
1157+
val qual1 = followTypeAlias(qual)
1158+
if (qual1.isEmpty) reportErrors(sel, state)
1159+
else trySelectUnapply(qual1) {
1160+
(_, state) => reportErrors(sel, state)
1161+
}
1162+
}
11361163

11371164
/** Add a `Bind` node for each `bound` symbol in a type application `unapp` */
11381165
def addBinders(unapp: Tree, bound: List[Symbol]) = unapp match {

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package typer
44

55
import core._
66
import Contexts._, Types._, Symbols._, Names._, Decorators._, ProtoTypes._
7-
import Flags._
7+
import Flags._, SymDenotations._
88
import NameKinds.FlatName
99
import config.Printers.implicits
1010
import util.Spans.Span
@@ -82,8 +82,11 @@ trait ImportSuggestions:
8282
|| refSym == defn.JavaLangPackageClass // ... or java.lang.
8383
then Nil
8484
else refSym.info.decls.filter(lookInside)
85+
else if refSym.infoOrCompleter.isInstanceOf[StubInfo] then
86+
Nil // Don't chase roots that do not exist
8587
else
86-
if !refSym.is(Touched) then refSym.ensureCompleted() // JavaDefined is reliably known only after completion
88+
if !refSym.is(Touched) then
89+
refSym.ensureCompleted() // JavaDefined is reliably known only after completion
8790
if refSym.is(JavaDefined) then Nil
8891
else nestedRoots(site)
8992
nested

compiler/src/dotty/tools/repl/ReplDriver.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ class ReplDriver(settings: Array[String],
381381

382382
/** Render messages using the `MessageRendering` trait */
383383
private def renderMessage(cont: MessageContainer): Context => String =
384-
messageRenderer.messageAndPos(cont.contained(), cont.pos, messageRenderer.diagnosticLevel(cont))(_)
384+
messageRenderer.messageAndPos(cont.contained, cont.pos, messageRenderer.diagnosticLevel(cont))(_)
385385

386386
/** Output errors to `out` */
387387
private def displayErrors(errs: Seq[MessageContainer])(implicit state: State): State = {

compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ trait ErrorMessagesTest extends DottyTest {
3939
if (!runCtx.reporter.hasErrors) new EmptyReport
4040
else {
4141
val rep = runCtx.reporter.asInstanceOf[StoreReporter]
42-
val msgs = rep.removeBufferedMessages(runCtx).map(_.contained()).reverse
42+
val msgs = rep.removeBufferedMessages(runCtx).map(_.contained).reverse
4343
new Report(msgs, runCtx)
4444
}
4545
}

compiler/test/dotty/tools/dotc/reporting/TestReporter.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M
5454

5555
/** Prints the message with the given position indication. */
5656
def printMessageAndPos(m: MessageContainer, extra: String)(implicit ctx: Context): Unit = {
57-
val msg = messageAndPos(m.contained(), m.pos, diagnosticLevel(m))
57+
val msg = messageAndPos(m.contained, m.pos, diagnosticLevel(m))
5858
val extraInfo = inlineInfo(m.pos)
5959

6060
if (m.level >= logLevel) {
@@ -69,7 +69,7 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M
6969
override def doReport(m: MessageContainer)(implicit ctx: Context): Unit = {
7070

7171
// Here we add extra information that we should know about the error message
72-
val extra = m.contained() match {
72+
val extra = m.contained match {
7373
case pm: PatternMatchExhaustivity => s": ${pm.uncovered}"
7474
case _ => ""
7575
}
@@ -127,7 +127,7 @@ object TestReporter {
127127
/** Prints the message with the given position indication in a simplified manner */
128128
override def printMessageAndPos(m: MessageContainer, extra: String)(implicit ctx: Context): Unit = {
129129
def report() = {
130-
val msg = s"${m.pos.line + 1}: " + m.contained().kind + extra
130+
val msg = s"${m.pos.line + 1}: " + m.contained.kind + extra
131131
val extraInfo = inlineInfo(m.pos)
132132

133133
writer.println(msg)

doc-tool/test/dotty/tools/dottydoc/DottyDocTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ trait DottyDocTest extends MessageRendering {
5454
System.err.println("reporter had errors:")
5555
ctx.reporter.removeBufferedMessages.foreach { msg =>
5656
System.err.println {
57-
messageAndPos(msg.contained(), msg.pos, diagnosticLevel(msg))
57+
messageAndPos(msg.contained, msg.pos, diagnosticLevel(msg))
5858
}
5959
}
6060
}

language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ object DottyLanguageServer {
709709
}
710710
}
711711

712-
val message = mc.contained()
712+
val message = mc.contained
713713
if (displayMessage(message, mc.pos.source)) {
714714
val code = message.errorId.errorNumber.toString
715715
range(mc.pos).map(r =>

tests/neg/bad-unapplies.check

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
-- [E051] Reference Error: tests/neg/bad-unapplies.scala:22:9 ----------------------------------------------------------
2+
22 | case A("2") => // error (cannot resolve overloading)
3+
| ^
4+
| Ambiguous overload. The overloaded alternatives of method unapply in object A with types
5+
| (x: B): Option[String]
6+
| (x: A): Option[String]
7+
| both match arguments (C)
8+
9+
longer explanation available when compiling with `-explain`
10+
-- [E127] Syntax Error: tests/neg/bad-unapplies.scala:23:9 -------------------------------------------------------------
11+
23 | case B("2") => // error (cannot be used as an extractor)
12+
| ^
13+
| B cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
14+
15+
longer explanation available when compiling with `-explain`
16+
-- [E127] Syntax Error: tests/neg/bad-unapplies.scala:24:9 -------------------------------------------------------------
17+
24 | case D("2") => // error (cannot be used as an extractor)
18+
| ^
19+
| D cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
20+
21+
longer explanation available when compiling with `-explain`
22+
-- [E050] Reference Error: tests/neg/bad-unapplies.scala:25:9 ----------------------------------------------------------
23+
25 | case E("2") => // error (value unapply in object E does not take parameters)
24+
| ^
25+
| value unapply in object E does not take parameters
26+
27+
longer explanation available when compiling with `-explain`
28+
-- [E107] Syntax Error: tests/neg/bad-unapplies.scala:26:10 ------------------------------------------------------------
29+
26 | case F("2") => // error (Wrong number of argument patterns for F; expected: ())
30+
| ^^^^^^
31+
| Wrong number of argument patterns for F; expected: ()
32+
33+
longer explanation available when compiling with `-explain`
34+
-- [E006] Unbound Identifier Error: tests/neg/bad-unapplies.scala:27:9 -------------------------------------------------
35+
27 | case G("2") => // error (Not found: G)
36+
| ^
37+
| Not found: G
38+
39+
longer explanation available when compiling with `-explain`

tests/neg/bad-unapplies.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
trait A
2+
trait B
3+
class C extends A, B
4+
object A:
5+
def unapply(x: A): Option[String] = Some(x.toString)
6+
def unapply(x: B): Option[String] = Some(x.toString)
7+
8+
object B
9+
10+
object D:
11+
def unapply(x: A, y: B): Option[String] = Some(x.toString)
12+
13+
object E:
14+
val unapply: Option[String] = Some("")
15+
16+
object F:
17+
def unapply(x: Int): Boolean = true
18+
19+
20+
@main def Test =
21+
C() match
22+
case A("2") => // error (cannot resolve overloading)
23+
case B("2") => // error (cannot be used as an extractor)
24+
case D("2") => // error (cannot be used as an extractor)
25+
case E("2") => // error (value unapply in object E does not take parameters)
26+
case F("2") => // error (Wrong number of argument patterns for F; expected: ())
27+
case G("2") => // error (Not found: G)
28+
end Test

tests/neg/i5101.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
-- [E006] Unbound Identifier Error: tests/neg/i5101.scala:11:13 --------------------------------------------------------
1+
-- [E006] Unbound Identifier Error: tests/neg/i5101.scala:11:11 --------------------------------------------------------
22
11 | case A0(_) => // error
3-
| ^^^^^
3+
| ^^
44
| Not found: A0
55

66
longer explanation available when compiling with `-explain`

0 commit comments

Comments
 (0)