From ac430861e98d4d095f813b26fb11bbb2fcd78461 Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Tue, 8 Feb 2022 19:01:27 +0100 Subject: [PATCH 1/3] 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") From 51362d82da426370df4e52872b97c1775f088605 Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Thu, 10 Feb 2022 12:26:02 +0100 Subject: [PATCH 2/3] Support generic tuples as a valid unapplySeq return type --- .../src/dotty/tools/dotc/transform/PatternMatcher.scala | 6 +++--- compiler/src/dotty/tools/dotc/typer/Applications.scala | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index fde54570662e..3e9cb7eb8dcd 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -346,9 +346,6 @@ 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) @@ -356,6 +353,9 @@ object PatternMatcher { else if (isUnapplySeq && unapplySeqTypeElemTp(unapp.tpe.widen.finalResultType).exists) { unapplySeqPlan(unappResult, args) } + 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 { assert(isGetMatch(unapp.tpe)) val argsPlan = { diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 3fcefdef6c52..fbc568a17ffd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -173,6 +173,7 @@ object Applications { val elemTp = unapplySeqTypeElemTp(tp) if (elemTp.exists) args.map(Function.const(elemTp)) else if (isProductSeqMatch(tp, args.length, pos)) productSeqSelectors(tp, args.length, pos) + else if tp.derivesFrom(defn.NonEmptyTupleClass) then foldApplyTupleType(tp) else fallback } From 69b884daeec8a54daf18bf1adb304e0e08c70f84 Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Mon, 14 Feb 2022 14:34:13 +0100 Subject: [PATCH 3/3] Add test for the generic tuples as result types of unapplySeq --- tests/run/i11008b.check | 1 + tests/run/i11008b.scala | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 tests/run/i11008b.check create mode 100644 tests/run/i11008b.scala diff --git a/tests/run/i11008b.check b/tests/run/i11008b.check new file mode 100644 index 000000000000..c0c7fc09b324 --- /dev/null +++ b/tests/run/i11008b.check @@ -0,0 +1 @@ +Many 3 characters List(f, o, o) \ No newline at end of file diff --git a/tests/run/i11008b.scala b/tests/run/i11008b.scala new file mode 100644 index 000000000000..f5c8a69c7b48 --- /dev/null +++ b/tests/run/i11008b.scala @@ -0,0 +1,7 @@ +object Foo: + def unapplySeq(x: String): Int *: Seq[String] *: EmptyTuple = (x.length, x.toList.map(_.toString)) + +@main def Test = + "foo" match + case Foo(1, c) => println("One character " + c) + case Foo(x, xs*) => println(s"Many $x characters $xs")