Skip to content

Commit e69a18d

Browse files
Backport "Handle TupleXXL in match analysis" to LTS (#20791)
Backports #19212 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents 9d202ef + 42e8c0c commit e69a18d

File tree

9 files changed

+116
-15
lines changed

9 files changed

+116
-15
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

+1
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,7 @@ class Definitions {
947947
def TupleXXLModule(using Context): Symbol = TupleXXLClass.companionModule
948948

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

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

compiler/src/dotty/tools/dotc/core/TypeUtils.scala

+33-9
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,41 @@ class TypeUtils {
7272
else None
7373
recur(self.stripTypeVar, bound)
7474

75-
/** Is this a generic tuple that would fit into the range 1..22,
76-
* but is not already an instance of one of Tuple1..22?
77-
* In this case we need to cast it to make the TupleN/ members accessible.
78-
* This works only for generic tuples of known size up to 22.
79-
*/
80-
def isSmallGenericTuple(using Context): Boolean =
75+
/** Is this a generic tuple but not already an instance of one of Tuple1..22? */
76+
def isGenericTuple(using Context): Boolean =
8177
self.derivesFrom(defn.PairClass)
8278
&& !defn.isTupleNType(self.widenDealias)
83-
&& self.widenTermRefExpr.tupleElementTypesUpTo(Definitions.MaxTupleArity).match
84-
case Some(elems) if elems.length <= Definitions.MaxTupleArity => true
85-
case _ => false
79+
80+
/** Is this a generic tuple that would fit into the range 1..22?
81+
* In this case we need to cast it to make the TupleN members accessible.
82+
* This works only for generic tuples of known size up to 22.
83+
*/
84+
def isSmallGenericTuple(using Context): Boolean = genericTupleArityCompare < 0
85+
86+
/** Is this a generic tuple with an arity above 22? */
87+
def isLargeGenericTuple(using Context): Boolean = genericTupleArityCompare > 0
88+
89+
/** If this is a generic tuple with element types, compare the arity and return:
90+
* * -1, if the generic tuple is small (<= MaxTupleArity)
91+
* * 1, if the generic tuple is large (> MaxTupleArity)
92+
* * 0 if this isn't a generic tuple with element types
93+
*/
94+
def genericTupleArityCompare(using Context): Int =
95+
if self.isGenericTuple then
96+
self.widenTermRefExpr.tupleElementTypesUpTo(Definitions.MaxTupleArity).match
97+
case Some(elems) => if elems.length <= Definitions.MaxTupleArity then -1 else 1
98+
case _ => 0
99+
else 0
100+
101+
/** Is this a large generic tuple and is `pat` TupleXXL?
102+
* TupleXXL.unapplySeq extracts values of type TupleXXL
103+
* but large scrutinee terms are typed as large generic tuples.
104+
* This allows them to hold on to their precise element types,
105+
* but it means type-wise, the terms don't conform to the
106+
* extractor's parameter type, so this method identifies case.
107+
*/
108+
def isTupleXXLExtract(pat: Type)(using Context): Boolean =
109+
pat.typeSymbol == defn.TupleXXLClass && self.isLargeGenericTuple
86110

87111
/** The `*:` equivalent of an instance of a Tuple class */
88112
def toNestedPairs(using Context): Type =

compiler/src/dotty/tools/dotc/transform/patmat/Space.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,9 @@ object SpaceEngine {
286286
|| (unapp.symbol.is(Synthetic) && unapp.symbol.owner.linkedClass.is(Case)) // scala2 compatibility
287287
|| unapplySeqTypeElemTp(unappResult).exists // only for unapplySeq
288288
|| isProductMatch(unappResult, argLen)
289-
|| {
290-
val isEmptyTp = extractorMemberType(unappResult, nme.isEmpty, NoSourcePosition)
291-
isEmptyTp <:< ConstantType(Constant(false))
292-
}
289+
|| extractorMemberType(unappResult, nme.isEmpty, NoSourcePosition) <:< ConstantType(Constant(false))
293290
|| unappResult.derivesFrom(defn.NonEmptyTupleClass)
291+
|| unapp.symbol == defn.TupleXXL_unapplySeq // Fixes TupleXXL.unapplySeq which returns Some but declares Option
294292
}
295293

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

@@ -838,7 +837,8 @@ object SpaceEngine {
838837
def isCheckable(tp: Type): Boolean =
839838
val tpw = tp.widen.dealias
840839
val classSym = tpw.classSymbol
841-
classSym.is(Sealed) ||
840+
classSym.is(Sealed) && !tpw.isLargeGenericTuple || // exclude large generic tuples from exhaustivity
841+
// requires an unknown number of changes to make work
842842
tpw.isInstanceOf[OrType] ||
843843
(tpw.isInstanceOf[AndType] && {
844844
val and = tpw.asInstanceOf[AndType]

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -911,7 +911,10 @@ trait Checking {
911911
false
912912
}
913913

914-
def check(pat: Tree, pt: Type): Boolean = (pt <:< pat.tpe) || fail(pat, pt, Reason.NonConforming)
914+
def check(pat: Tree, pt: Type): Boolean =
915+
pt.isTupleXXLExtract(pat.tpe) // See isTupleXXLExtract, fixes TupleXXL parameter type
916+
|| pt <:< pat.tpe
917+
|| fail(pat, pt, Reason.NonConforming)
915918

916919
def recur(pat: Tree, pt: Type): Boolean =
917920
!sourceVersion.isAtLeast(`3.2`)

tests/pos/i14588.scala

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//> using options -Werror
2+
3+
class Test:
4+
def t1: Unit =
5+
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23) match
6+
case (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23) =>
7+
def t2: Unit =
8+
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23) match
9+
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) =>
10+
def t3: Unit =
11+
(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
12+
case (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23) =>
13+
14+
object Main:
15+
def main(args: Array[String]): Unit = {
16+
val t = new Test
17+
t.t1
18+
try { t.t2; ??? } catch case _: MatchError => ()
19+
try { t.t3; ??? } catch case _: MatchError => ()
20+
}

tests/pos/i16186.scala

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//> using options -Werror
2+
3+
class Test:
4+
val x = 42
5+
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)
6+
7+
tup23 match {
8+
case (_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) => "Tuple Pattern"
9+
}

tests/pos/i16657.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//> using options -Werror
2+
3+
class Test:
4+
val (_, (
5+
_, _, _, _, _, _, _, _, _, _, // 10
6+
_, _, _, _, _, _, _, _, _, _, // 20
7+
_, c22, _ // 23
8+
)) = // nested pattern has 23 elems
9+
(0, (
10+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11+
1, 2, 3, 4, 5, 6, 7, 8, 9, 20,
12+
1, 2, 3
13+
)) // ok, exhaustive, reachable, conforming and irrefutable

tests/pos/i19084.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//> using options -Werror
2+
3+
class Test:
4+
def t1(y: (
5+
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
6+
"Bob", Int, 33, Int,
7+
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)
8+
): Unit = y match
9+
case b @ (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9,
10+
"Bob", y1, 33, y2,
11+
z0, z1, z2, z3, z4, z5, z6, z7, z8, z9)
12+
=> // was: !!! spurious unreachable case warning
13+
()
14+
case _ => ()

tests/warn/i19084.scala

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
3+
class Test:
4+
def t1(y: (
5+
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
6+
"Bob", Int, 33, Int,
7+
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int)
8+
): Unit = y match
9+
case b @ (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9,
10+
"Bob", y1, 33, y2,
11+
z0, z1, z2, z3, z4, z5, z6, z7, z8, z9)
12+
=> ()
13+
case b @ (x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, // warn: unreachable
14+
"Bob", y1, 33, y2,
15+
z0, z1, z2, z3, z4, z5, z6, z7, z8, z9)
16+
=> ()
17+
case _ => ()

0 commit comments

Comments
 (0)