Skip to content

Commit ac43086

Browse files
Fix #11008: Support generic tuples as a valid unapply result
1 parent 61bb394 commit ac43086

File tree

4 files changed

+38
-7
lines changed

4 files changed

+38
-7
lines changed

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import collection.mutable
88
import Symbols._, Contexts._, Types._, StdNames._, NameOps._
99
import ast.Trees._
1010
import util.Spans._
11-
import typer.Applications.{isProductMatch, isGetMatch, isProductSeqMatch, productSelectors, productArity, unapplySeqTypeElemTp}
11+
import typer.Applications.*
1212
import SymUtils._
1313
import Flags._, Constants._
1414
import Decorators._
@@ -325,15 +325,16 @@ object PatternMatcher {
325325
def isSyntheticScala2Unapply(sym: Symbol) =
326326
sym.isAllOf(SyntheticCase) && sym.owner.is(Scala2x)
327327

328+
def tupleApp(i: Int, receiver: Tree) = // manually inlining the call to NonEmptyTuple#apply, because it's an inline method
329+
ref(defn.RuntimeTuplesModule)
330+
.select(defn.RuntimeTuples_apply)
331+
.appliedTo(receiver, Literal(Constant(i)))
332+
.cast(args(i).tpe.widen)
333+
328334
if (isSyntheticScala2Unapply(unapp.symbol) && caseAccessors.length == args.length)
329335
def tupleSel(sym: Symbol) = ref(scrutinee).select(sym)
330-
def tupleApp(i: Int) = // manually inlining the call to NonEmptyTuple#apply, because it's an inline method
331-
ref(defn.RuntimeTuplesModule)
332-
.select(defn.RuntimeTuples_apply)
333-
.appliedTo(ref(scrutinee), Literal(Constant(i)))
334-
.cast(args(i).tpe.widen)
335336
val isGenericTuple = defn.isTupleClass(caseClass) && !defn.isTupleNType(tree.tpe)
336-
val components = if isGenericTuple then caseAccessors.indices.toList.map(tupleApp) else caseAccessors.map(tupleSel)
337+
val components = if isGenericTuple then caseAccessors.indices.toList.map(tupleApp(_, ref(scrutinee))) else caseAccessors.map(tupleSel)
337338
matchArgsPlan(components, args, onSuccess)
338339
else if (unapp.tpe <:< (defn.BooleanType))
339340
TestPlan(GuardTest, unapp, unapp.span, onSuccess)
@@ -345,6 +346,9 @@ object PatternMatcher {
345346
.map(ref(unappResult).select(_))
346347
matchArgsPlan(selectors, args, onSuccess)
347348
}
349+
else if unappResult.info <:< defn.NonEmptyTupleTypeRef then
350+
val components = (0 until foldApplyTupleType(unappResult.denot.info).length).toList.map(tupleApp(_, ref(unappResult)))
351+
matchArgsPlan(components, args, onSuccess)
348352
else if (isUnapplySeq && isProductSeqMatch(unapp.tpe.widen, args.length, unapp.srcPos)) {
349353
val arity = productArity(unapp.tpe.widen, unapp.srcPos)
350354
unapplyProductSeqPlan(unappResult, args, arity)

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,22 @@ object Applications {
193193
productSelectorTypes(unapplyResult, pos)
194194
// this will cause a "wrong number of arguments in pattern" error later on,
195195
// which is better than the message in `fail`.
196+
else if unapplyResult.derivesFrom(defn.NonEmptyTupleClass) then
197+
foldApplyTupleType(unapplyResult)
196198
else fail
197199
}
198200
}
199201

202+
def foldApplyTupleType(tp: Type)(using Context): List[Type] =
203+
object tupleFold extends TypeAccumulator[List[Type]]:
204+
override def apply(accum: List[Type], t: Type): List[Type] =
205+
t match
206+
case AppliedType(tycon, x :: x2 :: Nil) if tycon.typeSymbol == defn.PairClass =>
207+
apply(x :: accum, x2)
208+
case x => foldOver(accum, x)
209+
end tupleFold
210+
tupleFold(Nil, tp).reverse
211+
200212
def wrapDefs(defs: mutable.ListBuffer[Tree], tree: Tree)(using Context): Tree =
201213
if (defs != null && defs.nonEmpty) tpd.Block(defs.toList, tree) else tree
202214

tests/run/i11008.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
hello
2+
hello and 10

tests/run/i11008.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
object A:
2+
def unapply(s: String): String *: EmptyTuple = Tuple1(s)
3+
4+
object B:
5+
def unapply(s:String): String *: Int *: EmptyTuple = Tuple2(s, 10)
6+
7+
@main def Test =
8+
"hello" match
9+
case A(x) =>
10+
println(x)
11+
"hello" match
12+
case B(x, y) =>
13+
println(s"$x and $y")

0 commit comments

Comments
 (0)