Skip to content

Commit

Permalink
Fix scala#11008: Support generic tuples as a valid unapply result
Browse files Browse the repository at this point in the history
  • Loading branch information
anatoliykmetyuk committed Feb 8, 2022
1 parent 61bb394 commit ac43086
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 7 deletions.
18 changes: 11 additions & 7 deletions compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import collection.mutable
import Symbols._, Contexts._, Types._, StdNames._, NameOps._
import ast.Trees._
import util.Spans._
import typer.Applications.{isProductMatch, isGetMatch, isProductSeqMatch, productSelectors, productArity, unapplySeqTypeElemTp}
import typer.Applications.*
import SymUtils._
import Flags._, Constants._
import Decorators._
Expand Down Expand Up @@ -325,15 +325,16 @@ object PatternMatcher {
def isSyntheticScala2Unapply(sym: Symbol) =
sym.isAllOf(SyntheticCase) && sym.owner.is(Scala2x)

def tupleApp(i: Int, receiver: Tree) = // manually inlining the call to NonEmptyTuple#apply, because it's an inline method
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)
def tupleApp(i: Int) = // manually inlining the call to NonEmptyTuple#apply, because it's an inline method
ref(defn.RuntimeTuplesModule)
.select(defn.RuntimeTuples_apply)
.appliedTo(ref(scrutinee), Literal(Constant(i)))
.cast(args(i).tpe.widen)
val isGenericTuple = defn.isTupleClass(caseClass) && !defn.isTupleNType(tree.tpe)
val components = if isGenericTuple then caseAccessors.indices.toList.map(tupleApp) else caseAccessors.map(tupleSel)
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))
TestPlan(GuardTest, unapp, unapp.span, onSuccess)
Expand All @@ -345,6 +346,9 @@ object PatternMatcher {
.map(ref(unappResult).select(_))
matchArgsPlan(selectors, args, onSuccess)
}
else if unappResult.info <:< defn.NonEmptyTupleTypeRef then
val components = (0 until foldApplyTupleType(unappResult.denot.info).length).toList.map(tupleApp(_, ref(unappResult)))
matchArgsPlan(components, args, onSuccess)
else if (isUnapplySeq && isProductSeqMatch(unapp.tpe.widen, args.length, unapp.srcPos)) {
val arity = productArity(unapp.tpe.widen, unapp.srcPos)
unapplyProductSeqPlan(unappResult, args, arity)
Expand Down
12 changes: 12 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,22 @@ object Applications {
productSelectorTypes(unapplyResult, pos)
// this will cause a "wrong number of arguments in pattern" error later on,
// which is better than the message in `fail`.
else if unapplyResult.derivesFrom(defn.NonEmptyTupleClass) then
foldApplyTupleType(unapplyResult)
else fail
}
}

def foldApplyTupleType(tp: Type)(using Context): List[Type] =
object tupleFold extends TypeAccumulator[List[Type]]:
override def apply(accum: List[Type], t: Type): List[Type] =
t match
case AppliedType(tycon, x :: x2 :: Nil) if tycon.typeSymbol == defn.PairClass =>
apply(x :: accum, x2)
case x => foldOver(accum, x)
end tupleFold
tupleFold(Nil, tp).reverse

def wrapDefs(defs: mutable.ListBuffer[Tree], tree: Tree)(using Context): Tree =
if (defs != null && defs.nonEmpty) tpd.Block(defs.toList, tree) else tree

Expand Down
2 changes: 2 additions & 0 deletions tests/run/i11008.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
hello
hello and 10
13 changes: 13 additions & 0 deletions tests/run/i11008.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
object A:
def unapply(s: String): String *: EmptyTuple = Tuple1(s)

object B:
def unapply(s:String): String *: Int *: EmptyTuple = Tuple2(s, 10)

@main def Test =
"hello" match
case A(x) =>
println(x)
"hello" match
case B(x, y) =>
println(s"$x and $y")

0 comments on commit ac43086

Please sign in to comment.