Skip to content

Commit 63144a0

Browse files
committed
Fix casting of generic tuple selection
Obviously, in hindsight, we can't use the types of the unapply arguments as the type for selecting the components of a (generic) tuple...
1 parent 808c669 commit 63144a0

File tree

3 files changed

+31
-4
lines changed

3 files changed

+31
-4
lines changed

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

Lines changed: 5 additions & 4 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}
@@ -328,16 +329,16 @@ object PatternMatcher {
328329
def isSyntheticScala2Unapply(sym: Symbol) =
329330
sym.isAllOf(SyntheticCase) && sym.owner.is(Scala2x)
330331

331-
def tupleApp(i: Int, receiver: Tree) = // manually inlining the call to NonEmptyTuple#apply, because it's an inline method
332+
def tupleApp(tuple: Type, i: Int, receiver: Tree) = // manually inlining the call to NonEmptyTuple#apply, because it's an inline method
332333
ref(defn.RuntimeTuplesModule)
333334
.select(defn.RuntimeTuples_apply)
334335
.appliedTo(receiver, Literal(Constant(i)))
335-
.cast(args(i).tpe.widen)
336+
.cast(tuple.tupleElementTypes.applyOrElse(i, (_: Int) => NoType).ensuring(_ != NoType, i"Failed to find type at index $i in $tuple (${tree.sourcePos})"))
336337

337338
if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length)
338339
def tupleSel(sym: Symbol) = ref(scrutinee).select(sym)
339340
val isGenericTuple = defn.isTupleClass(caseClass) && !defn.isTupleNType(tree.tpe)
340-
val components = if isGenericTuple then caseAccessors.indices.toList.map(tupleApp(_, ref(scrutinee))) else caseAccessors.map(tupleSel)
341+
val components = if isGenericTuple then caseAccessors.indices.toList.map(tupleApp(tree.tpe.dealias, _, ref(scrutinee))) else caseAccessors.map(tupleSel)
341342
matchArgsPlan(components, args, onSuccess)
342343
else if (unapp.tpe <:< (defn.BooleanType))
343344
TestPlan(GuardTest, unapp, unapp.span, onSuccess)
@@ -357,7 +358,7 @@ object PatternMatcher {
357358
unapplySeqPlan(unappResult, args)
358359
}
359360
else if unappResult.info <:< defn.NonEmptyTupleTypeRef then
360-
val components = (0 until foldApplyTupleType(unappResult.denot.info).length).toList.map(tupleApp(_, ref(unappResult)))
361+
val components = (0 until foldApplyTupleType(unappResult.denot.info).length).toList.map(tupleApp(unappResult.info, _, ref(unappResult)))
361362
matchArgsPlan(components, args, onSuccess)
362363
else {
363364
assert(isGetMatch(unapp.tpe))

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.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)