Skip to content

Commit

Permalink
Merge pull request #14590 from dwijnand/patmat-fix-generic-tuple-casting
Browse files Browse the repository at this point in the history
  • Loading branch information
dwijnand authored Mar 15, 2022
2 parents ba57cdd + 08889fe commit 9cbee41
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 4 deletions.
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1489,10 +1489,11 @@ class Definitions {
* @return true if the dealiased type of `tp` is `TupleN[T1, T2, ..., Tn]`
*/
def isTupleNType(tp: Type)(using Context): Boolean = {
val arity = tp.dealias.argInfos.length
val tp1 = tp.dealias
val arity = tp1.argInfos.length
arity <= MaxTupleArity && {
val tupletp = TupleType(arity)
tupletp != null && tp.isRef(tupletp.symbol)
tupletp != null && tp1.isRef(tupletp.symbol)
}
}

Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Symbols._, Contexts._, Types._, StdNames._, NameOps._
import util.Spans._
import typer.Applications.*
import SymUtils._
import TypeUtils.*
import Flags._, Constants._
import Decorators._
import NameKinds.{PatMatStdBinderName, PatMatAltsName, PatMatResultName}
Expand Down Expand Up @@ -332,11 +333,11 @@ object PatternMatcher {
ref(defn.RuntimeTuplesModule)
.select(defn.RuntimeTuples_apply)
.appliedTo(receiver, Literal(Constant(i)))
.cast(args(i).tpe.widen)

if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length)
def tupleSel(sym: Symbol) = ref(scrutinee).select(sym)
val isGenericTuple = defn.isTupleClass(caseClass) && !defn.isTupleNType(tree.tpe)
val isGenericTuple = defn.isTupleClass(caseClass) &&
!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
val components = if isGenericTuple then caseAccessors.indices.toList.map(tupleApp(_, ref(scrutinee))) else caseAccessors.map(tupleSel)
matchArgsPlan(components, args, onSuccess)
else if (unapp.tpe <:< (defn.BooleanType))
Expand Down
10 changes: 10 additions & 0 deletions tests/pos/i14587.hard-union-tuples.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// scalac: -Werror
class Test:
type Foo = Option[String] | Option[Int]

def test(foo: Foo) =
val (_, foo2: Foo) = // was: the type test for Test.this.Foo cannot be checked at runtime
foo match
case Some(s: String) => ((), s)
case _ => ((), 0)
foo2
10 changes: 10 additions & 0 deletions tests/run/i14587.min.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
object Test:
def test(cond: Boolean) =
val tup: (String, Unit) | (Int, Unit) = if cond then ("", ()) else (1, ())
tup match
case (s: String, _) => s
case _ => "n/a"

def main(args: Array[String]): Unit =
test(true) // works
test(false) // was: ClassCastException: class scala.None$ cannot be cast to class scala.Some
8 changes: 8 additions & 0 deletions tests/run/i14587.opaques.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
object Test:
opaque type Tup = (Int, String)

def test(tup: Tup) =
val (n, s) = tup
assert(n == 1 && s == "")

def main(args: Array[String]): Unit = test((1, ""))
16 changes: 16 additions & 0 deletions tests/run/i14587.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
def test(foo: String): Unit = {
// Does not crash if the type is written explicitly as: Option[(Option[Int], String)]
val bar = {
if (foo.isEmpty) Some((Some(1), ""))
else Some((None, ""))
}

bar.foreach {
case (Some(_), "") =>
case _ =>
}
}

@main def Test() =
test("") // works
test("a")

0 comments on commit 9cbee41

Please sign in to comment.