Skip to content

Commit

Permalink
Backport "Handle TupleXXL in match analysis" to LTS (#20791)
Browse files Browse the repository at this point in the history
Backports #19212 to the LTS branch.

PR submitted by the release tooling.
[skip ci]
  • Loading branch information
WojciechMazur authored Jun 26, 2024
2 parents 9d202ef + 42e8c0c commit e69a18d
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 15 deletions.
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,7 @@ class Definitions {
def TupleXXLModule(using Context): Symbol = TupleXXLClass.companionModule

def TupleXXL_fromIterator(using Context): Symbol = TupleXXLModule.requiredMethod("fromIterator")
def TupleXXL_unapplySeq(using Context): Symbol = TupleXXLModule.requiredMethod(nme.unapplySeq)

@tu lazy val RuntimeTupleMirrorTypeRef: TypeRef = requiredClassRef("scala.runtime.TupleMirror")

Expand Down
42 changes: 33 additions & 9 deletions compiler/src/dotty/tools/dotc/core/TypeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,41 @@ class TypeUtils {
else None
recur(self.stripTypeVar, bound)

/** Is this a generic tuple that would fit into the range 1..22,
* but is not already an instance of one of Tuple1..22?
* In this case we need to cast it to make the TupleN/ members accessible.
* This works only for generic tuples of known size up to 22.
*/
def isSmallGenericTuple(using Context): Boolean =
/** Is this a generic tuple but not already an instance of one of Tuple1..22? */
def isGenericTuple(using Context): Boolean =
self.derivesFrom(defn.PairClass)
&& !defn.isTupleNType(self.widenDealias)
&& self.widenTermRefExpr.tupleElementTypesUpTo(Definitions.MaxTupleArity).match
case Some(elems) if elems.length <= Definitions.MaxTupleArity => true
case _ => false

/** Is this a generic tuple that would fit into the range 1..22?
* In this case we need to cast it to make the TupleN members accessible.
* This works only for generic tuples of known size up to 22.
*/
def isSmallGenericTuple(using Context): Boolean = genericTupleArityCompare < 0

/** Is this a generic tuple with an arity above 22? */
def isLargeGenericTuple(using Context): Boolean = genericTupleArityCompare > 0

/** If this is a generic tuple with element types, compare the arity and return:
* * -1, if the generic tuple is small (<= MaxTupleArity)
* * 1, if the generic tuple is large (> MaxTupleArity)
* * 0 if this isn't a generic tuple with element types
*/
def genericTupleArityCompare(using Context): Int =
if self.isGenericTuple then
self.widenTermRefExpr.tupleElementTypesUpTo(Definitions.MaxTupleArity).match
case Some(elems) => if elems.length <= Definitions.MaxTupleArity then -1 else 1
case _ => 0
else 0

/** Is this a large generic tuple and is `pat` TupleXXL?
* TupleXXL.unapplySeq extracts values of type TupleXXL
* but large scrutinee terms are typed as large generic tuples.
* This allows them to hold on to their precise element types,
* but it means type-wise, the terms don't conform to the
* extractor's parameter type, so this method identifies case.
*/
def isTupleXXLExtract(pat: Type)(using Context): Boolean =
pat.typeSymbol == defn.TupleXXLClass && self.isLargeGenericTuple

/** The `*:` equivalent of an instance of a Tuple class */
def toNestedPairs(using Context): Type =
Expand Down
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -286,11 +286,9 @@ object SpaceEngine {
|| (unapp.symbol.is(Synthetic) && unapp.symbol.owner.linkedClass.is(Case)) // scala2 compatibility
|| unapplySeqTypeElemTp(unappResult).exists // only for unapplySeq
|| isProductMatch(unappResult, argLen)
|| {
val isEmptyTp = extractorMemberType(unappResult, nme.isEmpty, NoSourcePosition)
isEmptyTp <:< ConstantType(Constant(false))
}
|| extractorMemberType(unappResult, nme.isEmpty, NoSourcePosition) <:< ConstantType(Constant(false))
|| unappResult.derivesFrom(defn.NonEmptyTupleClass)
|| unapp.symbol == defn.TupleXXL_unapplySeq // Fixes TupleXXL.unapplySeq which returns Some but declares Option
}

/** Is the unapply or unapplySeq irrefutable?
Expand Down Expand Up @@ -514,6 +512,7 @@ object SpaceEngine {
def isSubType(tp1: Type, tp2: Type)(using Context): Boolean = trace(i"$tp1 <:< $tp2", debug, show = true) {
if tp1 == ConstantType(Constant(null)) && !ctx.mode.is(Mode.SafeNulls)
then tp2 == ConstantType(Constant(null))
else if tp1.isTupleXXLExtract(tp2) then true // See isTupleXXLExtract, fixes TupleXXL parameter type
else tp1 <:< tp2
}

Expand Down Expand Up @@ -838,7 +837,8 @@ object SpaceEngine {
def isCheckable(tp: Type): Boolean =
val tpw = tp.widen.dealias
val classSym = tpw.classSymbol
classSym.is(Sealed) ||
classSym.is(Sealed) && !tpw.isLargeGenericTuple || // exclude large generic tuples from exhaustivity
// requires an unknown number of changes to make work
tpw.isInstanceOf[OrType] ||
(tpw.isInstanceOf[AndType] && {
val and = tpw.asInstanceOf[AndType]
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,10 @@ trait Checking {
false
}

def check(pat: Tree, pt: Type): Boolean = (pt <:< pat.tpe) || fail(pat, pt, Reason.NonConforming)
def check(pat: Tree, pt: Type): Boolean =
pt.isTupleXXLExtract(pat.tpe) // See isTupleXXLExtract, fixes TupleXXL parameter type
|| pt <:< pat.tpe
|| fail(pat, pt, Reason.NonConforming)

def recur(pat: Tree, pt: Type): Boolean =
!sourceVersion.isAtLeast(`3.2`)
Expand Down
20 changes: 20 additions & 0 deletions tests/pos/i14588.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//> using options -Werror

class Test:
def t1: Unit =
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23) match
case (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23) =>
def t2: Unit =
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23) match
case (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24) =>
def t3: Unit =
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24) match
case (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23) =>

object Main:
def main(args: Array[String]): Unit = {
val t = new Test
t.t1
try { t.t2; ??? } catch case _: MatchError => ()
try { t.t3; ??? } catch case _: MatchError => ()
}
9 changes: 9 additions & 0 deletions tests/pos/i16186.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//> using options -Werror

class Test:
val x = 42
val tup23 = (x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x)

tup23 match {
case (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => "Tuple Pattern"
}
13 changes: 13 additions & 0 deletions tests/pos/i16657.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//> using options -Werror

class Test:
val (_, (
_, _, _, _, _, _, _, _, _, _, // 10
_, _, _, _, _, _, _, _, _, _, // 20
_, c22, _ // 23
)) = // nested pattern has 23 elems
(0, (
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
1, 2, 3, 4, 5, 6, 7, 8, 9, 20,
1, 2, 3
)) // ok, exhaustive, reachable, conforming and irrefutable
14 changes: 14 additions & 0 deletions tests/pos/i19084.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//> using options -Werror

class Test:
def t1(y: (
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
"Bob", Int, 33, Int,
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)
): Unit = y match
case b @ (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9,
"Bob", y1, 33, y2,
z0, z1, z2, z3, z4, z5, z6, z7, z8, z9)
=> // was: !!! spurious unreachable case warning
()
case _ => ()
17 changes: 17 additions & 0 deletions tests/warn/i19084.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@


class Test:
def t1(y: (
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
"Bob", Int, 33, Int,
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)
): Unit = y match
case b @ (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9,
"Bob", y1, 33, y2,
z0, z1, z2, z3, z4, z5, z6, z7, z8, z9)
=> ()
case b @ (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, // warn: unreachable
"Bob", y1, 33, y2,
z0, z1, z2, z3, z4, z5, z6, z7, z8, z9)
=> ()
case _ => ()

0 comments on commit e69a18d

Please sign in to comment.