Skip to content

Commit 9cbee41

Browse files
authored
Merge pull request #14590 from dwijnand/patmat-fix-generic-tuple-casting
2 parents ba57cdd + 08889fe commit 9cbee41

File tree

6 files changed

+50
-4
lines changed

6 files changed

+50
-4
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,10 +1489,11 @@ class Definitions {
14891489
* @return true if the dealiased type of `tp` is `TupleN[T1, T2, ..., Tn]`
14901490
*/
14911491
def isTupleNType(tp: Type)(using Context): Boolean = {
1492-
val arity = tp.dealias.argInfos.length
1492+
val tp1 = tp.dealias
1493+
val arity = tp1.argInfos.length
14931494
arity <= MaxTupleArity && {
14941495
val tupletp = TupleType(arity)
1495-
tupletp != null && tp.isRef(tupletp.symbol)
1496+
tupletp != null && tp1.isRef(tupletp.symbol)
14961497
}
14971498
}
14981499

compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Symbols._, Contexts._, Types._, StdNames._, NameOps._
99
import util.Spans._
1010
import typer.Applications.*
1111
import SymUtils._
12+
import TypeUtils.*
1213
import Flags._, Constants._
1314
import Decorators._
1415
import NameKinds.{PatMatStdBinderName, PatMatAltsName, PatMatResultName}
@@ -332,11 +333,11 @@ object PatternMatcher {
332333
ref(defn.RuntimeTuplesModule)
333334
.select(defn.RuntimeTuples_apply)
334335
.appliedTo(receiver, Literal(Constant(i)))
335-
.cast(args(i).tpe.widen)
336336

337337
if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length)
338338
def tupleSel(sym: Symbol) = ref(scrutinee).select(sym)
339-
val isGenericTuple = defn.isTupleClass(caseClass) && !defn.isTupleNType(tree.tpe)
339+
val isGenericTuple = defn.isTupleClass(caseClass) &&
340+
!defn.isTupleNType(tree.tpe match { case tp: OrType => tp.join case tp => tp }) // widen even hard unions, to see if it's a union of tuples
340341
val components = if isGenericTuple then caseAccessors.indices.toList.map(tupleApp(_, ref(scrutinee))) else caseAccessors.map(tupleSel)
341342
matchArgsPlan(components, args, onSuccess)
342343
else if (unapp.tpe <:< (defn.BooleanType))
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// scalac: -Werror
2+
class Test:
3+
type Foo = Option[String] | Option[Int]
4+
5+
def test(foo: Foo) =
6+
val (_, foo2: Foo) = // was: the type test for Test.this.Foo cannot be checked at runtime
7+
foo match
8+
case Some(s: String) => ((), s)
9+
case _ => ((), 0)
10+
foo2

tests/run/i14587.min.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
object Test:
2+
def test(cond: Boolean) =
3+
val tup: (String, Unit) | (Int, Unit) = if cond then ("", ()) else (1, ())
4+
tup match
5+
case (s: String, _) => s
6+
case _ => "n/a"
7+
8+
def main(args: Array[String]): Unit =
9+
test(true) // works
10+
test(false) // was: ClassCastException: class scala.None$ cannot be cast to class scala.Some

tests/run/i14587.opaques.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
object Test:
2+
opaque type Tup = (Int, String)
3+
4+
def test(tup: Tup) =
5+
val (n, s) = tup
6+
assert(n == 1 && s == "")
7+
8+
def main(args: Array[String]): Unit = test((1, ""))

tests/run/i14587.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
def test(foo: String): Unit = {
2+
// Does not crash if the type is written explicitly as: Option[(Option[Int], String)]
3+
val bar = {
4+
if (foo.isEmpty) Some((Some(1), ""))
5+
else Some((None, ""))
6+
}
7+
8+
bar.foreach {
9+
case (Some(_), "") =>
10+
case _ =>
11+
}
12+
}
13+
14+
@main def Test() =
15+
test("") // works
16+
test("a")

0 commit comments

Comments
 (0)