Skip to content

Commit ac7e99e

Browse files
mbovelkyouko-taiga
andcommitted
Refuse trailing types parameters in extractors
Co-authored-by: Dimi Racordon <[email protected]>
1 parent 93af7b8 commit ac7e99e

File tree

8 files changed

+76
-7
lines changed

8 files changed

+76
-7
lines changed

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -2421,13 +2421,15 @@ class ClassCannotExtendEnum(cls: Symbol, parent: Symbol)(using Context) extends
24212421
}
24222422

24232423
class NotAnExtractor(tree: untpd.Tree)(using Context) extends PatternMatchMsg(NotAnExtractorID) {
2424-
def msg(using Context) = i"$tree cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method"
2424+
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"
24252425
def explain(using Context) =
2426-
i"""|An ${hl("unapply")} method should be defined in an ${hl("object")} as follow:
2426+
i"""|An ${hl("unapply")} method should be in an ${hl("object")}, take a single explicit term parameter, and:
24272427
| - If it is just a test, return a ${hl("Boolean")}. For example ${hl("case even()")}
24282428
| - If it returns a single sub-value of type T, return an ${hl("Option[T]")}
24292429
| - If it returns several sub-values T1,...,Tn, group them in an optional tuple ${hl("Option[(T1,...,Tn)]")}
24302430
|
2431+
|Additionnaly, ${hl("unapply")} or ${hl("unapplySeq")} methods can not take type parameters after their explicit term parameter.
2432+
|
24312433
|Sometimes, the number of sub-values isn't fixed and we would like to return a sequence.
24322434
|For this reason, you can also define patterns through ${hl("unapplySeq")} which returns ${hl("Option[Seq[T]]")}.
24332435
|This mechanism is used for instance in pattern ${hl("case List(x1, ..., xn)")}"""

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

+22-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import annotation.threadUnsafe
3636

3737
import scala.util.control.NonFatal
3838
import dotty.tools.dotc.inlines.Inlines
39+
import scala.annotation.tailrec
3940

4041
object Applications {
4142
import tpd.*
@@ -1525,14 +1526,34 @@ trait Applications extends Compatibility {
15251526
def trySelectUnapply(qual: untpd.Tree)(fallBack: (Tree, TyperState) => Tree): Tree = {
15261527
// try first for non-overloaded, then for overloaded occurrences
15271528
def tryWithName(name: TermName)(fallBack: (Tree, TyperState) => Tree)(using Context): Tree =
1529+
/** Returns `true` if there are type parameters after the last explicit
1530+
* (non-implicit) term parameters list.
1531+
*/
1532+
@tailrec
1533+
def hasTrailingTypeParams(paramss: List[List[Symbol]], acc: Boolean = false): Boolean =
1534+
paramss match
1535+
case Nil => acc
1536+
case params :: rest =>
1537+
val newAcc =
1538+
params match
1539+
case param :: _ if param.isType => true
1540+
case param :: _ if param.isTerm && !param.isOneOf(GivenOrImplicit) => false
1541+
case _ => acc
1542+
hasTrailingTypeParams(paramss.tail, newAcc)
15281543

15291544
def tryWithProto(qual: untpd.Tree, targs: List[Tree], pt: Type)(using Context) =
15301545
val proto = UnapplyFunProto(pt, this)
15311546
val unapp = untpd.Select(qual, name)
15321547
val result =
15331548
if targs.isEmpty then typedExpr(unapp, proto)
15341549
else typedExpr(unapp, PolyProto(targs, proto)).appliedToTypeTrees(targs)
1535-
if !result.symbol.exists
1550+
if result.symbol.exists && hasTrailingTypeParams(result.symbol.paramSymss) then
1551+
// We don't accept `unapply` or `unapplySeq` methods with type
1552+
// parameters after the last explicit term parameter because we
1553+
// can't encode them: `UnApply` nodes cannot take type paremeters.
1554+
// See #22550 and associated test cases.
1555+
notAnExtractor(result)
1556+
else if !result.symbol.exists
15361557
|| result.symbol.name == name
15371558
|| ctx.reporter.hasErrors
15381559
then result

tests/neg/22550.check

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- [E127] Pattern Match Error: tests/neg/22550.scala:6:9 ---------------------------------------------------------------
2+
6 | 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+
| longer explanation available when compiling with `-explain`
7+
-- [E006] Not Found Error: tests/neg/22550.scala:6:31 ------------------------------------------------------------------
8+
6 | case Matches(x) => println(x) // error // error
9+
| ^
10+
| Not found: x
11+
|
12+
| longer explanation available when compiling with `-explain`

tests/neg/22550.scala

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

tests/neg/22550b.check

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- [E127] Pattern Match Error: tests/neg/22550b.scala:14:9 -------------------------------------------------------------
2+
14 | 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+
| longer explanation available when compiling with `-explain`
7+
-- [E006] Not Found Error: tests/neg/22550b.scala:14:37 ----------------------------------------------------------------
8+
14 | case Matches[Unit](x) => println(x) // error // error
9+
| ^
10+
| Not found: x
11+
|
12+
| longer explanation available when compiling with `-explain`

tests/neg/22550b.scala

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

tests/neg/bad-unapplies.check

+2-2
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

+4-2
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+
| Additionnaly, unapply or unapplySeq methods can not 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)