diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index 5df9379cb606..c76b5117dc89 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -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 = diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index beb4119775d0..5e8df7941ab6 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -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? @@ -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 } @@ -836,7 +835,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] diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 86b3fee5ae7e..f4605adea0e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -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`) diff --git a/tests/pos/i14588.scala b/tests/pos/i14588.scala new file mode 100644 index 000000000000..80483ace34f6 --- /dev/null +++ b/tests/pos/i14588.scala @@ -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 => () + } diff --git a/tests/pos/i16186.scala b/tests/pos/i16186.scala new file mode 100644 index 000000000000..9a8948cae529 --- /dev/null +++ b/tests/pos/i16186.scala @@ -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" + } diff --git a/tests/pos/i16657.scala b/tests/pos/i16657.scala new file mode 100644 index 000000000000..5c2c0aa6567d --- /dev/null +++ b/tests/pos/i16657.scala @@ -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 diff --git a/tests/pos/i19084.scala b/tests/pos/i19084.scala new file mode 100644 index 000000000000..ab44f0e48b43 --- /dev/null +++ b/tests/pos/i19084.scala @@ -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 _ => () diff --git a/tests/warn/i19084.scala b/tests/warn/i19084.scala new file mode 100644 index 000000000000..0ba033d131be --- /dev/null +++ b/tests/warn/i19084.scala @@ -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 _ => ()