Skip to content

Backport "Refuse trailing type parameters in extractors" to 3.3 LTS #363

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2381,13 +2381,15 @@ class ClassCannotExtendEnum(cls: Symbol, parent: Symbol)(using Context) extends
}

class NotAnExtractor(tree: untpd.Tree)(using Context) extends PatternMatchMsg(NotAnExtractorID) {
def msg(using Context) = i"$tree cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method"
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"
def explain(using Context) =
i"""|An ${hl("unapply")} method should be defined in an ${hl("object")} as follow:
i"""|An ${hl("unapply")} method should be in an ${hl("object")}, take a single explicit term parameter, and:
| - If it is just a test, return a ${hl("Boolean")}. For example ${hl("case even()")}
| - If it returns a single sub-value of type T, return an ${hl("Option[T]")}
| - If it returns several sub-values T1,...,Tn, group them in an optional tuple ${hl("Option[(T1,...,Tn)]")}
|
|Additionaly, ${hl("unapply")} or ${hl("unapplySeq")} methods cannot take type parameters after their explicit term parameter.
|
|Sometimes, the number of sub-values isn't fixed and we would like to return a sequence.
|For this reason, you can also define patterns through ${hl("unapplySeq")} which returns ${hl("Option[Seq[T]]")}.
|This mechanism is used for instance in pattern ${hl("case List(x1, ..., xn)")}"""
Expand Down
23 changes: 22 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import annotation.threadUnsafe

import scala.util.control.NonFatal
import dotty.tools.dotc.inlines.Inlines
import scala.annotation.tailrec

object Applications {
import tpd.*
Expand Down Expand Up @@ -1428,14 +1429,34 @@ trait Applications extends Compatibility {
def trySelectUnapply(qual: untpd.Tree)(fallBack: (Tree, TyperState) => Tree): Tree = {
// try first for non-overloaded, then for overloaded occurrences
def tryWithName(name: TermName)(fallBack: (Tree, TyperState) => Tree)(using Context): Tree =
/** Returns `true` if there are type parameters after the last explicit
* (non-implicit) term parameters list.
*/
@tailrec
def hasTrailingTypeParams(paramss: List[List[Symbol]], acc: Boolean = false): Boolean =
paramss match
case Nil => acc
case params :: rest =>
val newAcc =
params match
case param :: _ if param.isType => true
case param :: _ if param.isTerm && !param.isOneOf(GivenOrImplicit) => false
case _ => acc
hasTrailingTypeParams(paramss.tail, newAcc)

def tryWithProto(qual: untpd.Tree, targs: List[Tree], pt: Type)(using Context) =
val proto = UnapplyFunProto(pt, this)
val unapp = untpd.Select(qual, name)
val result =
if targs.isEmpty then typedExpr(unapp, proto)
else typedExpr(unapp, PolyProto(targs, proto)).appliedToTypeTrees(targs)
if !result.symbol.exists
if result.symbol.exists && hasTrailingTypeParams(result.symbol.paramSymss) then
// We don't accept `unapply` or `unapplySeq` methods with type
// parameters after the last explicit term parameter because we
// can't encode them: `UnApply` nodes cannot take type paremeters.
// See #22550 and associated test cases.
notAnExtractor(result)
else if !result.symbol.exists
|| result.symbol.name == name
|| ctx.reporter.hasErrors
then result
Expand Down
37 changes: 37 additions & 0 deletions tests/neg/22550.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- [E127] Pattern Match Error: tests/neg/22550.scala:8:9 ---------------------------------------------------------------
8 | case Matches(x) => println(x) // error // error
| ^^^^^^^
|Matches cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method with the appropriate signature
|---------------------------------------------------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| An unapply method should be in an object, take a single explicit term parameter, and:
| - If it is just a test, return a Boolean. For example case even()
| - If it returns a single sub-value of type T, return an Option[T]
| - If it returns several sub-values T1,...,Tn, group them in an optional tuple Option[(T1,...,Tn)]
|
| Additionaly, unapply or unapplySeq methods cannot take type parameters after their explicit term parameter.
|
| Sometimes, the number of sub-values isn't fixed and we would like to return a sequence.
| For this reason, you can also define patterns through unapplySeq which returns Option[Seq[T]].
| This mechanism is used for instance in pattern case List(x1, ..., xn)
---------------------------------------------------------------------------------------------------------------------
-- [E006] Not Found Error: tests/neg/22550.scala:8:31 ------------------------------------------------------------------
8 | case Matches(x) => println(x) // error // error
| ^
| Not found: x
|---------------------------------------------------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Each identifier in Scala needs a matching declaration. There are two kinds of
| identifiers: type identifiers and value identifiers. Value identifiers are introduced
| by `val`, `def`, or `object` declarations. Type identifiers are introduced by `type`,
| `class`, `enum`, or `trait` declarations.
|
| Identifiers refer to matching declarations in their environment, or they can be
| imported from elsewhere.
|
| Possible reasons why no matching declaration was found:
| - The declaration or the use is mis-spelt.
| - An import is missing.
---------------------------------------------------------------------------------------------------------------------
8 changes: 8 additions & 0 deletions tests/neg/22550.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//> using options -explain

object Matches:
def unapply(y: Any)[T]: Option[Any] = None

def main =
42 match
case Matches(x) => println(x) // error // error
37 changes: 37 additions & 0 deletions tests/neg/22550b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- [E127] Pattern Match Error: tests/neg/22550b.scala:16:9 -------------------------------------------------------------
16 | case Matches[Unit](x) => println(x) // error // error
| ^^^^^^^^^^^^
|Matches[Unit] cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method with the appropriate signature
|--------------------------------------------------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| An unapply method should be in an object, take a single explicit term parameter, and:
| - If it is just a test, return a Boolean. For example case even()
| - If it returns a single sub-value of type T, return an Option[T]
| - If it returns several sub-values T1,...,Tn, group them in an optional tuple Option[(T1,...,Tn)]
|
| Additionaly, unapply or unapplySeq methods cannot take type parameters after their explicit term parameter.
|
| Sometimes, the number of sub-values isn't fixed and we would like to return a sequence.
| For this reason, you can also define patterns through unapplySeq which returns Option[Seq[T]].
| This mechanism is used for instance in pattern case List(x1, ..., xn)
--------------------------------------------------------------------------------------------------------------------
-- [E006] Not Found Error: tests/neg/22550b.scala:16:37 ----------------------------------------------------------------
16 | case Matches[Unit](x) => println(x) // error // error
| ^
| Not found: x
|--------------------------------------------------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| Each identifier in Scala needs a matching declaration. There are two kinds of
| identifiers: type identifiers and value identifiers. Value identifiers are introduced
| by `val`, `def`, or `object` declarations. Type identifiers are introduced by `type`,
| `class`, `enum`, or `trait` declarations.
|
| Identifiers refer to matching declarations in their environment, or they can be
| imported from elsewhere.
|
| Possible reasons why no matching declaration was found:
| - The declaration or the use is mis-spelt.
| - An import is missing.
--------------------------------------------------------------------------------------------------------------------
16 changes: 16 additions & 0 deletions tests/neg/22550b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//> using options -explain

case class Data[A]()

trait TCl[A, B]

object Matches:
def unapply[A](adt: Data[?])[B](using
ft: TCl[A, B]
): Option[Data[A]] = None

given TCl[Unit, String] = new TCl {}

def main =
Data() match
case Matches[Unit](x) => println(x) // error // error
4 changes: 2 additions & 2 deletions tests/neg/bad-unapplies.check
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
-- [E127] Pattern Match Error: tests/neg/bad-unapplies.scala:23:9 ------------------------------------------------------
23 | case B("2") => // error (cannot be used as an extractor)
| ^
| B cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
|B cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method with the appropriate signature
|
| longer explanation available when compiling with `-explain`
-- [E127] Pattern Match Error: tests/neg/bad-unapplies.scala:24:9 ------------------------------------------------------
24 | case D("2") => // error (cannot be used as an extractor)
| ^
| D cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
|D cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method with the appropriate signature
|
| longer explanation available when compiling with `-explain`
-- [E050] Type Error: tests/neg/bad-unapplies.scala:25:9 ---------------------------------------------------------------
Expand Down
6 changes: 4 additions & 2 deletions tests/neg/i18684.check
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,17 @@
-- [E127] Pattern Match Error: tests/neg/i18684.scala:12:6 -------------------------------------------------------------
12 | val inner(x) = 3 // error
| ^^^^^
| Test.inner cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method
|Test.inner cannot be used as an extractor in a pattern because it lacks an unapply or unapplySeq method with the appropriate signature
|--------------------------------------------------------------------------------------------------------------------
| Explanation (enabled by `-explain`)
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| An unapply method should be defined in an object as follow:
| An unapply method should be in an object, take a single explicit term parameter, and:
| - If it is just a test, return a Boolean. For example case even()
| - If it returns a single sub-value of type T, return an Option[T]
| - If it returns several sub-values T1,...,Tn, group them in an optional tuple Option[(T1,...,Tn)]
|
| Additionaly, unapply or unapplySeq methods cannot take type parameters after their explicit term parameter.
|
| Sometimes, the number of sub-values isn't fixed and we would like to return a sequence.
| For this reason, you can also define patterns through unapplySeq which returns Option[Seq[T]].
| This mechanism is used for instance in pattern case List(x1, ..., xn)
Expand Down