Skip to content

Commit

Permalink
Fix scala#21841: Check more that an unapplySeq on a NonEmptyTuple
Browse files Browse the repository at this point in the history
… is valid. (scala#22366)

Review by @odersky because you seem to have been in the area recently
with named tuples:

scala@0fbdb49
  • Loading branch information
odersky authored Jan 14, 2025
2 parents 1bcc03c + d5b5def commit 312c89a
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 8 deletions.
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 _ => ()
}

0 comments on commit 312c89a

Please sign in to comment.