Skip to content

Commit d53cbac

Browse files
committed
Better error message when a pattern match extractor is not found.
Fixes #18684 [Cherry-picked 7c308d6][modified]
1 parent b68b34b commit d53cbac

File tree

9 files changed

+101
-26
lines changed

9 files changed

+101
-26
lines changed

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

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

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

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

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2340,7 +2340,7 @@ class ClassCannotExtendEnum(cls: Symbol, parent: Symbol)(using Context) extends
23402340
def explain(using Context) = ""
23412341
}
23422342

2343-
class NotAnExtractor(tree: untpd.Tree)(using Context) extends SyntaxMsg(NotAnExtractorID) {
2343+
class NotAnExtractor(tree: untpd.Tree)(using Context) extends PatternMatchMsg(NotAnExtractorID) {
23442344
def msg(using Context) = i"$tree cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method"
23452345
def explain(using Context) =
23462346
i"""|An ${hl("unapply")} method should be defined in an ${hl("object")} as follow:
@@ -2353,6 +2353,14 @@ class NotAnExtractor(tree: untpd.Tree)(using Context) extends SyntaxMsg(NotAnExt
23532353
|This mechanism is used for instance in pattern ${hl("case List(x1, ..., xn)")}"""
23542354
}
23552355

2356+
class ExtractorNotFound(val name: Name)(using Context) extends NotFoundMsg(ExtractorNotFoundID):
2357+
def msg(using Context) = i"no pattern match extractor named $name was found"
2358+
def explain(using Context) =
2359+
i"""An application $name(...) in a pattern can refer to an extractor
2360+
|which defines an unapply or unapplySeq method. Case classes and enum cases
2361+
|implicitly define extractors with the name of the class or enum case.
2362+
|Here, no extractor named $name was found, so the pattern could not be typed."""
2363+
23562364
class MemberWithSameNameAsStatic()(using Context)
23572365
extends SyntaxMsg(MemberWithSameNameAsStaticID) {
23582366
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
@@ -1278,15 +1278,22 @@ trait Applications extends Compatibility {
12781278

12791279
/** Report errors buffered in state.
12801280
* @pre state has errors to report
1281-
* If there is a single error stating that "unapply" is not a member, print
1282-
* the more informative "notAnExtractor" message instead.
1281+
* If the last reported error states that "unapply" is not a member, report
1282+
* the more informative `NotAnExtractor` message instead.
1283+
* If the last reported error states that the qualifier was not found, report
1284+
* the more informative `ExtractorNotFound` message instead.
12831285
*/
12841286
def reportErrors(tree: Tree, state: TyperState): Tree =
12851287
assert(state.reporter.hasErrors)
1286-
if saysNotFound(state, nme.unapply) then notAnExtractor(tree)
1287-
else
1288-
state.reporter.flush()
1289-
tree
1288+
if saysNotFound(state, nme.unapply) then
1289+
notAnExtractor(tree)
1290+
else qual match
1291+
case qual: Ident if saysNotFound(state, qual.name) =>
1292+
report.error(ExtractorNotFound(qual.name), tree.srcPos)
1293+
tree
1294+
case _ =>
1295+
state.reporter.flush()
1296+
tree
12901297

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

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3098,6 +3098,22 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
30983098
case _ => typedUnadapted(desugar(tree, pt), pt, locked)
30993099
}
31003100

3101+
def handleTypeError(ex: TypeError): Tree = ex match
3102+
case ex: CyclicReference
3103+
if ctx.reporter.errorsReported
3104+
&& xtree.span.isZeroExtent
3105+
&& ex.isVal && false =>
3106+
// Don't report a "recursive val ... needs type" if errors were reported
3107+
// previously and the span of the offending tree is empty. In this case,
3108+
// it's most likely that this is desugared code, and the error message would
3109+
// be redundant and confusing.
3110+
xtree.withType(ErrorType(ex.toMessage))
3111+
case _ =>
3112+
// Use focussed sourcePos since tree might be a large definition
3113+
// and a large error span would hide all errors in interior.
3114+
// TODO: Not clear that hiding is what we want, actually
3115+
errorTree(xtree, ex, xtree.srcPos.focus)
3116+
31013117
try
31023118
val ifpt = defn.asContextFunctionType(pt)
31033119
val result =
@@ -3115,10 +3131,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
31153131
case xtree => typedUnnamed(xtree)
31163132

31173133
simplify(result, pt, locked)
3118-
catch case ex: TypeError => errorTree(xtree, ex, xtree.srcPos.focus)
3119-
// use focussed sourcePos since tree might be a large definition
3120-
// and a large error span would hide all errors in interior.
3121-
// TODO: Not clear that hiding is what we want, actually
3134+
catch case ex: TypeError =>
3135+
handleTypeError(ex)
31223136
}
31233137
}
31243138

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
@@ -38,16 +38,16 @@
3838
| x is already defined as value x
3939
|
4040
| Note that overloaded methods must all be defined in the same group of toplevel definitions
41-
-- [E006] Not Found Error: tests/neg/t5702-neg-bad-and-wild.scala:12:20 ------------------------------------------------
41+
-- [E189] Not Found Error: tests/neg/t5702-neg-bad-and-wild.scala:12:20 ------------------------------------------------
4242
12 | case List(1, _*3,) => // error: pattern expected // error
4343
| ^
44-
| Not found: *
44+
| no pattern match extractor named * was found
4545
|
4646
| longer explanation available when compiling with `-explain`
47-
-- [E006] Not Found Error: tests/neg/t5702-neg-bad-and-wild.scala:13:20 ------------------------------------------------
47+
-- [E189] Not Found Error: tests/neg/t5702-neg-bad-and-wild.scala:13:20 ------------------------------------------------
4848
13 | case List(1, _*3:) => // error // error
4949
| ^
50-
| Not found: *
50+
| no pattern match extractor named * was found
5151
|
5252
| longer explanation available when compiling with `-explain`
5353
-- [E045] Cyclic Error: tests/neg/t5702-neg-bad-and-wild.scala:23:19 ---------------------------------------------------

0 commit comments

Comments
 (0)