Skip to content

Commit 7c308d6

Browse files
committed
Better error message when a pattern match extractor is not found.
Fixes #18684
1 parent 48bb59c commit 7c308d6

File tree

10 files changed

+104
-28
lines changed

10 files changed

+104
-28
lines changed

compiler/src/dotty/tools/dotc/core/TypeErrors.scala

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,18 @@ end handleRecursive
134134
* so it requires knowing denot already.
135135
* @param denot
136136
*/
137-
class CyclicReference private (val denot: SymDenotation)(using Context) extends TypeError:
137+
class CyclicReference(val denot: SymDenotation)(using Context) extends TypeError:
138138
var inImplicitSearch: Boolean = false
139139

140-
override def toMessage(using Context): Message =
141-
val cycleSym = denot.symbol
140+
val cycleSym = denot.symbol
141+
142+
// cycleSym.flags would try completing denot and would fail, but here we can use flagsUNSAFE to detect flags
143+
// set by the parser.
144+
def unsafeFlags = cycleSym.flagsUNSAFE
145+
def isMethod = unsafeFlags.is(Method)
146+
def isVal = !isMethod && cycleSym.isTerm
142147

143-
// cycleSym.flags would try completing denot and would fail, but here we can use flagsUNSAFE to detect flags
144-
// set by the parser.
148+
override def toMessage(using Context): Message =
145149
val unsafeFlags = cycleSym.flagsUNSAFE
146150
val isMethod = unsafeFlags.is(Method)
147151
val isVal = !isMethod && cycleSym.isTerm

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
202202
case ImplausiblePatternWarningID // erorNumber: 186
203203
case SynchronizedCallOnBoxedClassID // errorNumber: 187
204204
case VarArgsParamCannotBeGivenID // erorNumber: 188
205+
case ExtractorNotFoundID // errorNumber: 189
205206

206207
def errorNumber = ordinal - 1
207208

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -876,7 +876,7 @@ extends Message(PatternMatchExhaustivityID) {
876876

877877
val pathes = List(
878878
ActionPatch(
879-
srcPos = endPos,
879+
srcPos = endPos,
880880
replacement = uncoveredCases.map(c => indent(s"case $c => ???", startColumn))
881881
.mkString("\n", "\n", "")
882882
),
@@ -2358,7 +2358,7 @@ class ClassCannotExtendEnum(cls: Symbol, parent: Symbol)(using Context) extends
23582358
def explain(using Context) = ""
23592359
}
23602360

2361-
class NotAnExtractor(tree: untpd.Tree)(using Context) extends SyntaxMsg(NotAnExtractorID) {
2361+
class NotAnExtractor(tree: untpd.Tree)(using Context) extends PatternMatchMsg(NotAnExtractorID) {
23622362
def msg(using Context) = i"$tree cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method"
23632363
def explain(using Context) =
23642364
i"""|An ${hl("unapply")} method should be defined in an ${hl("object")} as follow:
@@ -2371,6 +2371,14 @@ class NotAnExtractor(tree: untpd.Tree)(using Context) extends SyntaxMsg(NotAnExt
23712371
|This mechanism is used for instance in pattern ${hl("case List(x1, ..., xn)")}"""
23722372
}
23732373

2374+
class ExtractorNotFound(val name: Name)(using Context) extends NotFoundMsg(ExtractorNotFoundID):
2375+
def msg(using Context) = i"no pattern match extractor named $name was found"
2376+
def explain(using Context) =
2377+
i"""An application $name(...) in a pattern can refer to an extractor
2378+
|which defines an unapply or unapplySeq method. Case classes and enum cases
2379+
|implicitly define extractors with the name of the class or enum case.
2380+
|Here, no extractor named $name was found, so the pattern could not be typed."""
2381+
23742382
class MemberWithSameNameAsStatic()(using Context)
23752383
extends SyntaxMsg(MemberWithSameNameAsStaticID) {
23762384
def msg(using Context) = i"Companion classes cannot define members with same name as a ${hl("@static")} member"

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,15 +1295,22 @@ trait Applications extends Compatibility {
12951295

12961296
/** Report errors buffered in state.
12971297
* @pre state has errors to report
1298-
* If there is a single error stating that "unapply" is not a member, print
1299-
* the more informative "notAnExtractor" message instead.
1298+
* If the last reported error states that "unapply" is not a member, report
1299+
* the more informative `NotAnExtractor` message instead.
1300+
* If the last reported error states that the qualifier was not found, report
1301+
* the more informative `ExtractorNotFound` message instead.
13001302
*/
13011303
def reportErrors(tree: Tree, state: TyperState): Tree =
13021304
assert(state.reporter.hasErrors)
1303-
if saysNotFound(state, nme.unapply) then notAnExtractor(tree)
1304-
else
1305-
state.reporter.flush()
1306-
tree
1305+
if saysNotFound(state, nme.unapply) then
1306+
notAnExtractor(tree)
1307+
else qual match
1308+
case qual: Ident if saysNotFound(state, qual.name) =>
1309+
report.error(ExtractorNotFound(qual.name), tree.srcPos)
1310+
tree
1311+
case _ =>
1312+
state.reporter.flush()
1313+
tree
13071314

13081315
/** If this is a term ref tree, try to typecheck with its type name.
13091316
* If this refers to a type alias, follow the alias, and if

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3172,6 +3172,22 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
31723172
case _ => typedUnadapted(desugar(tree, pt), pt, locked)
31733173
}
31743174

3175+
def handleTypeError(ex: TypeError): Tree = ex match
3176+
case ex: CyclicReference
3177+
if ctx.reporter.errorsReported
3178+
&& xtree.span.isZeroExtent
3179+
&& ex.isVal && false =>
3180+
// Don't report a "recursive val ... needs type" if errors were reported
3181+
// previously and the span of the offending tree is empty. In this case,
3182+
// it's most likely that this is desugared code, and the error message would
3183+
// be redundant and confusing.
3184+
xtree.withType(ErrorType(ex.toMessage))
3185+
case _ =>
3186+
// Use focussed sourcePos since tree might be a large definition
3187+
// and a large error span would hide all errors in interior.
3188+
// TODO: Not clear that hiding is what we want, actually
3189+
errorTree(xtree, ex, xtree.srcPos.focus)
3190+
31753191
try
31763192
val ifpt = defn.asContextFunctionType(pt)
31773193
val result =
@@ -3194,11 +3210,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
31943210
result.tpe.stripTypeVar match
31953211
case e: ErrorType if !unsimplifiedType.isErroneous => errorTree(xtree, e.msg, xtree.srcPos)
31963212
case _ => result
3197-
catch case ex: TypeError => errorTree(xtree, ex, xtree.srcPos.focus)
3198-
// use focussed sourcePos since tree might be a large definition
3199-
// and a large error span would hide all errors in interior.
3200-
// TODO: Not clear that hiding is what we want, actually
3201-
}
3213+
catch case ex: TypeError =>
3214+
handleTypeError(ex)
3215+
}
32023216
}
32033217

32043218
/** Interpolate and simplify the type of the given tree. */

tests/neg/bad-unapplies.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
| both match arguments (C)
88
|
99
| longer explanation available when compiling with `-explain`
10-
-- [E127] Syntax Error: tests/neg/bad-unapplies.scala:23:9 -------------------------------------------------------------
10+
-- [E127] Pattern Match Error: tests/neg/bad-unapplies.scala:23:9 ------------------------------------------------------
1111
23 | case B("2") => // error (cannot be used as an extractor)
1212
| ^
1313
| B cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
1414
|
1515
| longer explanation available when compiling with `-explain`
16-
-- [E127] Syntax Error: tests/neg/bad-unapplies.scala:24:9 -------------------------------------------------------------
16+
-- [E127] Pattern Match Error: tests/neg/bad-unapplies.scala:24:9 ------------------------------------------------------
1717
24 | case D("2") => // error (cannot be used as an extractor)
1818
| ^
1919
| D cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
@@ -31,9 +31,9 @@
3131
| Wrong number of argument patterns for F; expected: ()
3232
|
3333
| longer explanation available when compiling with `-explain`
34-
-- [E006] Not Found Error: tests/neg/bad-unapplies.scala:27:9 ----------------------------------------------------------
34+
-- [E189] Not Found Error: tests/neg/bad-unapplies.scala:27:9 ----------------------------------------------------------
3535
27 | case G("2") => // error (Not found: G)
3636
| ^
37-
| Not found: G
37+
| no pattern match extractor named G was found
3838
|
3939
| longer explanation available when compiling with `-explain`

tests/neg/i18684.check

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-- [E189] Not Found Error: tests/neg/i18684.scala:3:6 ------------------------------------------------------------------
2+
3 | val s(): String = "hello, world" // error
3+
| ^
4+
| no pattern match extractor named s was found
5+
|
6+
| longer explanation available when compiling with `-explain`
7+
-- [E189] Not Found Error: tests/neg/i18684.scala:5:6 ------------------------------------------------------------------
8+
5 | val i() = 22 // error
9+
| ^
10+
| no pattern match extractor named i was found
11+
|
12+
| longer explanation available when compiling with `-explain`
13+
-- [E189] Not Found Error: tests/neg/i18684.scala:10:8 -----------------------------------------------------------------
14+
10 | val foo() = "33" // error
15+
| ^^^
16+
| no pattern match extractor named foo was found
17+
|
18+
| longer explanation available when compiling with `-explain`
19+
-- [E127] Pattern Match Error: tests/neg/i18684.scala:12:6 -------------------------------------------------------------
20+
12 | val inner(x) = 3 // error // error
21+
| ^^^^^
22+
| Test.inner cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
23+
|
24+
| longer explanation available when compiling with `-explain`
25+
-- [E045] Cyclic Error: tests/neg/i18684.scala:12:14 -------------------------------------------------------------------
26+
12 | val inner(x) = 3 // error // error
27+
| ^
28+
| Recursive value x needs type
29+
|
30+
| longer explanation available when compiling with `-explain`

tests/neg/i18684.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//> using option -explain
2+
object Test:
3+
val s(): String = "hello, world" // error
4+
5+
val i() = 22 // error
6+
7+
def foo(): String = "22"
8+
9+
object inner:
10+
val foo() = "33" // error
11+
12+
val inner(x) = 3 // error // error

tests/neg/i5101.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
-- [E006] Not Found Error: tests/neg/i5101.scala:11:11 -----------------------------------------------------------------
1+
-- [E189] Not Found Error: tests/neg/i5101.scala:11:11 -----------------------------------------------------------------
22
11 | case A0(_) => // error
33
| ^^
4-
| Not found: A0
4+
| no pattern match extractor named A0 was found
55
|
66
| longer explanation available when compiling with `-explain`

tests/neg/t5702-neg-bad-and-wild.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,16 @@
4646
| x is already defined as value x
4747
|
4848
| Note that overloaded methods must all be defined in the same group of toplevel definitions
49-
-- [E006] Not Found Error: tests/neg/t5702-neg-bad-and-wild.scala:12:20 ------------------------------------------------
49+
-- [E189] Not Found Error: tests/neg/t5702-neg-bad-and-wild.scala:12:20 ------------------------------------------------
5050
12 | case List(1, _*3,) => // error: pattern expected // error
5151
| ^
52-
| Not found: *
52+
| no pattern match extractor named * was found
5353
|
5454
| longer explanation available when compiling with `-explain`
55-
-- [E006] Not Found Error: tests/neg/t5702-neg-bad-and-wild.scala:13:20 ------------------------------------------------
55+
-- [E189] Not Found Error: tests/neg/t5702-neg-bad-and-wild.scala:13:20 ------------------------------------------------
5656
13 | case List(1, _*3:) => // error // error
5757
| ^
58-
| Not found: *
58+
| no pattern match extractor named * was found
5959
|
6060
| longer explanation available when compiling with `-explain`
6161
-- [E045] Cyclic Error: tests/neg/t5702-neg-bad-and-wild.scala:23:19 ---------------------------------------------------

0 commit comments

Comments
 (0)