Skip to content

Commit aa132db

Browse files
dwijnandWojciechMazur
authored andcommitted
Handle TupleXXL in match analysis
There's a number of problems with the match analysis of TupleXXL. Of course, they manifest as (in)exhaustivity and (un)reachability warnings. Reachability suffered by the problem that a large generic tuple scrutinee type wasn't considered extractable by the TupleXXL extractor that Typer associates the extractor pattern with. That was solved by special handling in SpaceEngine's isSubType. Exhaustivity suffered by a variety of problems, again stemming from the disconnect between the TupleXXL pattern type and the large generic tuple scrutinee (or component) type. That was solved by special handling in exhaustivityCheckable to ignore large generic tuple scrutinees. That then highlighted an irrefutable failure (checkIrrefutable), which also needed to be taught that extra large generic tuples do conform to TupleXXL extractors type, afterwhich SpaceEngine isIrrefutable needed special handling to consider TuplXXL irrefutable. [Cherry-picked e56b75e]
1 parent e064bf2 commit aa132db

File tree

7 files changed

+97
-15
lines changed

7 files changed

+97
-15
lines changed

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

Lines changed: 33 additions & 9 deletions
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

Lines changed: 4 additions & 5 deletions
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,7 @@ 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
842841
tpw.isInstanceOf[OrType] ||
843842
(tpw.isInstanceOf[AndType] && {
844843
val and = tpw.asInstanceOf[AndType]

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

Lines changed: 4 additions & 1 deletion
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

Lines changed: 20 additions & 0 deletions
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

Lines changed: 9 additions & 0 deletions
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

Lines changed: 13 additions & 0 deletions
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

Lines changed: 14 additions & 0 deletions
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 _ => ()

0 commit comments

Comments
 (0)