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 all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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/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")