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

Fix casting of generic tuple selection #14590

Merged
merged 6 commits into from Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
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
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
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
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/run/i14587.min.scala
@@ -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
@@ -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
@@ -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")