From ac430861e98d4d095f813b26fb11bbb2fcd78461 Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Tue, 8 Feb 2022 19:01:27 +0100 Subject: [PATCH] Fix #11008: Support generic tuples as a valid unapply result --- .../tools/dotc/transform/PatternMatcher.scala | 18 +++++++++++------- .../dotty/tools/dotc/typer/Applications.scala | 12 ++++++++++++ tests/run/i11008.check | 2 ++ tests/run/i11008.scala | 13 +++++++++++++ 4 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 tests/run/i11008.check create mode 100644 tests/run/i11008.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index cafa552ff5ec..fde54570662e 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -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._ @@ -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) @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index d8e9c413decb..3fcefdef6c52 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -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 diff --git a/tests/run/i11008.check b/tests/run/i11008.check new file mode 100644 index 000000000000..3be25c0dc8dd --- /dev/null +++ b/tests/run/i11008.check @@ -0,0 +1,2 @@ +hello +hello and 10 diff --git a/tests/run/i11008.scala b/tests/run/i11008.scala new file mode 100644 index 000000000000..dfe6be288d7a --- /dev/null +++ b/tests/run/i11008.scala @@ -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")