Skip to content

Fix #21841: Check more that an unapplySeq on a NonEmptyTuple is valid. #22366

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

Merged
merged 1 commit into from
Jan 14, 2025
Merged
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
36 changes: 28 additions & 8 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ object Applications {
unapplySeqTypeElemTp(productSelectorTypes(tp, errorPos).last).exists
}

/** Does `tp` fit the "product-seq match" conditions for a `NonEmptyTuple` as
* an unapply result type for a pattern with `numArgs` subpatterns?
* This is the case if (1) `tp` derives from `NonEmptyTuple`.
* (2) `tp.tupleElementTypes` exists.
* (3) `tp.tupleElementTypes.last` conforms to Seq match
*/
def isNonEmptyTupleSeqMatch(tp: Type, numArgs: Int, errorPos: SrcPos = NoSourcePosition)(using Context): Boolean = {
tp.derivesFrom(defn.NonEmptyTupleClass)
&& tp.tupleElementTypes.exists { elemTypes =>
val arity = elemTypes.size
arity > 0 && arity <= numArgs + 1 &&
unapplySeqTypeElemTp(elemTypes.last).exists
}
}

/** Does `tp` fit the "get match" conditions as an unapply result type?
* This is the case of `tp` has a `get` member as well as a
* parameterless `isEmpty` member of result type `Boolean`.
Expand Down Expand Up @@ -140,12 +155,17 @@ object Applications {
sels.takeWhile(_.exists).toList
}

def productSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] = {
val selTps = productSelectorTypes(tp, pos)
val arity = selTps.length
val elemTp = unapplySeqTypeElemTp(selTps.last)
(0 until argsNum).map(i => if (i < arity - 1) selTps(i) else elemTp).toList
}
def productSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] =
seqSelectors(productSelectorTypes(tp, pos), argsNum)

def nonEmptyTupleSeqSelectors(tp: Type, argsNum: Int, pos: SrcPos)(using Context): List[Type] =
seqSelectors(tp.tupleElementTypes.get, argsNum)

private def seqSelectors(selectorTypes: List[Type], argsNum: Int)(using Context): List[Type] =
val arity = selectorTypes.length
val elemTp = unapplySeqTypeElemTp(selectorTypes.last)
(0 until argsNum).map(i => if (i < arity - 1) selectorTypes(i) else elemTp).toList
end seqSelectors

/** A utility class that matches results of unapplys with patterns. Two queriable members:
* val argTypes: List[Type]
Expand Down Expand Up @@ -176,8 +196,8 @@ object Applications {
args.map(Function.const(elemTp))
else if isProductSeqMatch(tp, args.length, pos) then
productSeqSelectors(tp, args.length, pos)
else if tp.derivesFrom(defn.NonEmptyTupleClass) then
tp.tupleElementTypes.getOrElse(Nil)
else if isNonEmptyTupleSeqMatch(tp, args.length, pos) then
nonEmptyTupleSeqSelectors(tp, args.length, pos)
else fallback

private def tryAdaptPatternArgs(elems: List[untpd.Tree], pt: Type)(using Context): Option[List[untpd.Tree]] =
Expand Down
6 changes: 6 additions & 0 deletions tests/neg/i21841.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- [E108] Declaration Error: tests/neg/i21841.scala:20:13 --------------------------------------------------------------
20 | case v[T](l, r) => () // error
| ^^^^^^^^^^
| Option[(Test.Expr[Test.T], Test.Expr[Test.T])] is not a valid result type of an unapplySeq method of an extractor.
|
| longer explanation available when compiling with `-explain`
22 changes: 22 additions & 0 deletions tests/neg/i21841.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
object Test {

sealed trait T
sealed trait Arrow[A, B]

type ArgsTo[S1, Target] <: NonEmptyTuple = S1 match {
case Arrow[a, Target] => Tuple1[Expr[a]]
case Arrow[a, b] => Expr[a] *: ArgsTo[b, Target]
}

sealed trait Expr[S] :
def unapplySeq[Target](e: Expr[Target]): Option[ArgsTo[S, Target]] = ???

case class Variable[S](id: String) extends Expr[S]

val v = Variable[Arrow[T, Arrow[T, T]]]("v")
val e : Expr[T] = ???

e match
case v[T](l, r) => () // error
case _ => ()
}
Loading