Skip to content

Commit a85ee64

Browse files
mboveltgodzik
authored andcommitted
Refuse trailing type parameters in extractors
Co-authored-by: Dimi Racordon <[email protected]> [Cherry-picked 94b0b13]
1 parent 84ea7eb commit a85ee64

File tree

8 files changed

+130
-7
lines changed

8 files changed

+130
-7
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2381,13 +2381,15 @@ class ClassCannotExtendEnum(cls: Symbol, parent: Symbol)(using Context) extends
23812381
}
23822382

23832383
class NotAnExtractor(tree: untpd.Tree)(using Context) extends PatternMatchMsg(NotAnExtractorID) {
2384-
def msg(using Context) = i"$tree cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method"
2384+
def msg(using Context) = i"$tree cannot be used as an extractor in a pattern because it lacks an ${hl("unapply")} or ${hl("unapplySeq")} method with the appropriate signature"
23852385
def explain(using Context) =
2386-
i"""|An ${hl("unapply")} method should be defined in an ${hl("object")} as follow:
2386+
i"""|An ${hl("unapply")} method should be in an ${hl("object")}, take a single explicit term parameter, and:
23872387
| - If it is just a test, return a ${hl("Boolean")}. For example ${hl("case even()")}
23882388
| - If it returns a single sub-value of type T, return an ${hl("Option[T]")}
23892389
| - If it returns several sub-values T1,...,Tn, group them in an optional tuple ${hl("Option[(T1,...,Tn)]")}
23902390
|
2391+
|Additionaly, ${hl("unapply")} or ${hl("unapplySeq")} methods cannot take type parameters after their explicit term parameter.
2392+
|
23912393
|Sometimes, the number of sub-values isn't fixed and we would like to return a sequence.
23922394
|For this reason, you can also define patterns through ${hl("unapplySeq")} which returns ${hl("Option[Seq[T]]")}.
23932395
|This mechanism is used for instance in pattern ${hl("case List(x1, ..., xn)")}"""

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import annotation.threadUnsafe
3535

3636
import scala.util.control.NonFatal
3737
import dotty.tools.dotc.inlines.Inlines
38+
import scala.annotation.tailrec
3839

3940
object Applications {
4041
import tpd.*
@@ -1428,14 +1429,34 @@ trait Applications extends Compatibility {
14281429
def trySelectUnapply(qual: untpd.Tree)(fallBack: (Tree, TyperState) => Tree): Tree = {
14291430
// try first for non-overloaded, then for overloaded occurrences
14301431
def tryWithName(name: TermName)(fallBack: (Tree, TyperState) => Tree)(using Context): Tree =
1432+
/** Returns `true` if there are type parameters after the last explicit
1433+
* (non-implicit) term parameters list.
1434+
*/
1435+
@tailrec
1436+
def hasTrailingTypeParams(paramss: List[List[Symbol]], acc: Boolean = false): Boolean =
1437+
paramss match
1438+
case Nil => acc
1439+
case params :: rest =>
1440+
val newAcc =
1441+
params match
1442+
case param :: _ if param.isType => true
1443+
case param :: _ if param.isTerm && !param.isOneOf(GivenOrImplicit) => false
1444+
case _ => acc
1445+
hasTrailingTypeParams(paramss.tail, newAcc)
14311446

14321447
def tryWithProto(qual: untpd.Tree, targs: List[Tree], pt: Type)(using Context) =
14331448
val proto = UnapplyFunProto(pt, this)
14341449
val unapp = untpd.Select(qual, name)
14351450
val result =
14361451
if targs.isEmpty then typedExpr(unapp, proto)
14371452
else typedExpr(unapp, PolyProto(targs, proto)).appliedToTypeTrees(targs)
1438-
if !result.symbol.exists
1453+
if result.symbol.exists && hasTrailingTypeParams(result.symbol.paramSymss) then
1454+
// We don't accept `unapply` or `unapplySeq` methods with type
1455+
// parameters after the last explicit term parameter because we
1456+
// can't encode them: `UnApply` nodes cannot take type paremeters.
1457+
// See #22550 and associated test cases.
1458+
notAnExtractor(result)
1459+
else if !result.symbol.exists
14391460
|| result.symbol.name == name
14401461
|| ctx.reporter.hasErrors
14411462
then result

tests/neg/22550.check

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
-- [E127] Pattern Match Error: tests/neg/22550.scala:8:9 ---------------------------------------------------------------
2+
8 | case Matches(x) => println(x) // error // error
3+
| ^^^^^^^
4+
|Matches cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method with the appropriate signature
5+
|---------------------------------------------------------------------------------------------------------------------
6+
| Explanation (enabled by `-explain`)
7+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8+
| An unapply method should be in an object, take a single explicit term parameter, and:
9+
| - If it is just a test, return a Boolean. For example case even()
10+
| - If it returns a single sub-value of type T, return an Option[T]
11+
| - If it returns several sub-values T1,...,Tn, group them in an optional tuple Option[(T1,...,Tn)]
12+
|
13+
| Additionaly, unapply or unapplySeq methods cannot take type parameters after their explicit term parameter.
14+
|
15+
| Sometimes, the number of sub-values isn't fixed and we would like to return a sequence.
16+
| For this reason, you can also define patterns through unapplySeq which returns Option[Seq[T]].
17+
| This mechanism is used for instance in pattern case List(x1, ..., xn)
18+
---------------------------------------------------------------------------------------------------------------------
19+
-- [E006] Not Found Error: tests/neg/22550.scala:8:31 ------------------------------------------------------------------
20+
8 | case Matches(x) => println(x) // error // error
21+
| ^
22+
| Not found: x
23+
|---------------------------------------------------------------------------------------------------------------------
24+
| Explanation (enabled by `-explain`)
25+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
26+
| Each identifier in Scala needs a matching declaration. There are two kinds of
27+
| identifiers: type identifiers and value identifiers. Value identifiers are introduced
28+
| by `val`, `def`, or `object` declarations. Type identifiers are introduced by `type`,
29+
| `class`, `enum`, or `trait` declarations.
30+
|
31+
| Identifiers refer to matching declarations in their environment, or they can be
32+
| imported from elsewhere.
33+
|
34+
| Possible reasons why no matching declaration was found:
35+
| - The declaration or the use is mis-spelt.
36+
| - An import is missing.
37+
---------------------------------------------------------------------------------------------------------------------

tests/neg/22550.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//> using options -explain
2+
3+
object Matches:
4+
def unapply(y: Any)[T]: Option[Any] = None
5+
6+
def main =
7+
42 match
8+
case Matches(x) => println(x) // error // error

tests/neg/22550b.check

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
-- [E127] Pattern Match Error: tests/neg/22550b.scala:16:9 -------------------------------------------------------------
2+
16 | case Matches[Unit](x) => println(x) // error // error
3+
| ^^^^^^^^^^^^
4+
|Matches[Unit] cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method with the appropriate signature
5+
|--------------------------------------------------------------------------------------------------------------------
6+
| Explanation (enabled by `-explain`)
7+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8+
| An unapply method should be in an object, take a single explicit term parameter, and:
9+
| - If it is just a test, return a Boolean. For example case even()
10+
| - If it returns a single sub-value of type T, return an Option[T]
11+
| - If it returns several sub-values T1,...,Tn, group them in an optional tuple Option[(T1,...,Tn)]
12+
|
13+
| Additionaly, unapply or unapplySeq methods cannot take type parameters after their explicit term parameter.
14+
|
15+
| Sometimes, the number of sub-values isn't fixed and we would like to return a sequence.
16+
| For this reason, you can also define patterns through unapplySeq which returns Option[Seq[T]].
17+
| This mechanism is used for instance in pattern case List(x1, ..., xn)
18+
--------------------------------------------------------------------------------------------------------------------
19+
-- [E006] Not Found Error: tests/neg/22550b.scala:16:37 ----------------------------------------------------------------
20+
16 | case Matches[Unit](x) => println(x) // error // error
21+
| ^
22+
| Not found: x
23+
|--------------------------------------------------------------------------------------------------------------------
24+
| Explanation (enabled by `-explain`)
25+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
26+
| Each identifier in Scala needs a matching declaration. There are two kinds of
27+
| identifiers: type identifiers and value identifiers. Value identifiers are introduced
28+
| by `val`, `def`, or `object` declarations. Type identifiers are introduced by `type`,
29+
| `class`, `enum`, or `trait` declarations.
30+
|
31+
| Identifiers refer to matching declarations in their environment, or they can be
32+
| imported from elsewhere.
33+
|
34+
| Possible reasons why no matching declaration was found:
35+
| - The declaration or the use is mis-spelt.
36+
| - An import is missing.
37+
--------------------------------------------------------------------------------------------------------------------

tests/neg/22550b.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//> using options -explain
2+
3+
case class Data[A]()
4+
5+
trait TCl[A, B]
6+
7+
object Matches:
8+
def unapply[A](adt: Data[?])[B](using
9+
ft: TCl[A, B]
10+
): Option[Data[A]] = None
11+
12+
given TCl[Unit, String] = new TCl {}
13+
14+
def main =
15+
Data() match
16+
case Matches[Unit](x) => println(x) // error // error

tests/neg/bad-unapplies.check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
-- [E127] Pattern Match Error: tests/neg/bad-unapplies.scala:23:9 ------------------------------------------------------
1111
23 | case B("2") => // error (cannot be used as an extractor)
1212
| ^
13-
| B cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
13+
|B cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method with the appropriate signature
1414
|
1515
| longer explanation available when compiling with `-explain`
1616
-- [E127] Pattern Match Error: tests/neg/bad-unapplies.scala:24:9 ------------------------------------------------------
1717
24 | case D("2") => // error (cannot be used as an extractor)
1818
| ^
19-
| D cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
19+
|D cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method with the appropriate signature
2020
|
2121
| longer explanation available when compiling with `-explain`
2222
-- [E050] Type Error: tests/neg/bad-unapplies.scala:25:9 ---------------------------------------------------------------

tests/neg/i18684.check

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,17 @@
6767
-- [E127] Pattern Match Error: tests/neg/i18684.scala:12:6 -------------------------------------------------------------
6868
12 | val inner(x) = 3 // error
6969
| ^^^^^
70-
| Test.inner cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
70+
|Test.inner cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method with the appropriate signature
7171
|--------------------------------------------------------------------------------------------------------------------
7272
| Explanation (enabled by `-explain`)
7373
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
74-
| An unapply method should be defined in an object as follow:
74+
| An unapply method should be in an object, take a single explicit term parameter, and:
7575
| - If it is just a test, return a Boolean. For example case even()
7676
| - If it returns a single sub-value of type T, return an Option[T]
7777
| - If it returns several sub-values T1,...,Tn, group them in an optional tuple Option[(T1,...,Tn)]
7878
|
79+
| Additionaly, unapply or unapplySeq methods cannot take type parameters after their explicit term parameter.
80+
|
7981
| Sometimes, the number of sub-values isn't fixed and we would like to return a sequence.
8082
| For this reason, you can also define patterns through unapplySeq which returns Option[Seq[T]].
8183
| This mechanism is used for instance in pattern case List(x1, ..., xn)

0 commit comments

Comments
 (0)