Skip to content
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

Handle TupleXXL in match analysis #19212

Merged
merged 3 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
9 changes: 4 additions & 5 deletions compiler/src/dotty/tools/dotc/transform/patmat/Space.scala
Original file line number Diff line number Diff line change
Expand Up @@ -287,11 +287,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 @@ -505,6 +503,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 @@ -836,7 +835,7 @@ 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
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -936,7 +936,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 _ => ()