From 855adf82111f16e8862603773526719083e156c4 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 26 Feb 2019 15:42:51 +0100 Subject: [PATCH 1/9] Fix #3248: support product-seq patterns --- .../tools/dotc/transform/PatternMatcher.scala | 46 ++++++++-- .../dotty/tools/dotc/typer/Applications.scala | 92 ++++++++++++------- tests/run/i3248.scala | 32 ++++--- 3 files changed, 115 insertions(+), 55 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index bbe45fa9638a..7f26ce816b1f 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, productSelectors} +import typer.Applications.{isProductMatch, isGetMatch, isProductSeqMatch, productSelectors, productArity} import SymUtils._ import Flags._, Constants._ import Decorators._ @@ -262,6 +262,8 @@ object PatternMatcher { /** Plan for matching the sequence in `getResult` against sequence elements * and a possible last varargs argument `args`. + * + * `getResult` could also be a product, where the last element is a sequence of elements. */ def unapplySeqPlan(getResult: Symbol, args: List[Tree]): Plan = args.lastOption match { case Some(VarArgPattern(arg)) => @@ -286,6 +288,22 @@ object PatternMatcher { matchElemsPlan(getResult, args, exact = true, onSuccess) } + /** Plan for matching the sequence in `getResult` against sequence elements + * and a possible last varargs argument `args`. + * + * `getResult` is a product, where the last element is a sequence of elements. + */ + def unapplyProductSeqPlan(getResult: Symbol, args: List[Tree], arity: Int): Plan = { + assert(arity <= args.size + 1) + val selectors = productSelectors(getResult.info).map(ref(getResult).select(_)) + + val matchSeq = + letAbstract(selectors.last) { seqResult => + unapplySeqPlan(seqResult, args.drop(arity - 1)) + } + matchArgsPlan(selectors.take(arity - 1), args.take(arity - 1), matchSeq) + } + /** Plan for matching the result of an unapply against argument patterns `args` */ def unapplyPlan(unapp: Tree, args: List[Tree]): Plan = { def caseClass = unapp.symbol.owner.linkedClass @@ -306,18 +324,34 @@ object PatternMatcher { .map(ref(unappResult).select(_)) matchArgsPlan(selectors, args, onSuccess) } + else if (isProductSeqMatch(unapp.tpe.widen, args.length, unapp.sourcePos) && !isUnapplySeq) { + val arity = productArity(unapp.tpe.widen, unapp.sourcePos) + unapplyProductSeqPlan(unappResult, args, arity) + } else { assert(isGetMatch(unapp.tpe)) val argsPlan = { val get = ref(unappResult).select(nme.get, _.info.isParameterless) + val arity = productArity(get.tpe, unapp.sourcePos) if (isUnapplySeq) - letAbstract(get)(unapplySeqPlan(_, args)) + letAbstract(get) { getResult => + if (arity > 0) unapplyProductSeqPlan(getResult, args, arity) + else unapplySeqPlan(getResult, args) + } else letAbstract(get) { getResult => - val selectors = - if (args.tail.isEmpty) ref(getResult) :: Nil - else productSelectors(get.tpe).map(ref(getResult).select(_)) - matchArgsPlan(selectors, args, onSuccess) + if (args.tail.isEmpty) // Single pattern takes precedence + matchArgsPlan(ref(getResult) :: Nil, args, onSuccess) + else if (isProductMatch(get.tpe, args.length, unapp.sourcePos)) { + val sels = productSelectors(get.tpe).map(ref(getResult).select(_)) + matchArgsPlan(sels, args, onSuccess) + } + else if (isProductSeqMatch(get.tpe, args.length, unapp.sourcePos)) + unapplyProductSeqPlan(getResult, args, arity) + else { // name-based + val sels = productSelectors(get.tpe).map(ref(getResult).select(_)) + matchArgsPlan(sels, args, onSuccess) + } } } TestPlan(NonEmptyTest, unappResult, unapp.span, argsPlan) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 7a590b65d205..0d64877ecc12 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -47,11 +47,22 @@ object Applications { /** Does `tp` fit the "product match" conditions as an unapply result type * for a pattern with `numArgs` subpatterns? - * This is the case of `tp` has members `_1` to `_N` where `N == numArgs`. + * This is the case if `tp` has members `_1` to `_N` where `N == numArgs`. */ def isProductMatch(tp: Type, numArgs: Int, errorPos: SourcePosition = NoSourcePosition)(implicit ctx: Context): Boolean = numArgs > 0 && productArity(tp, errorPos) == numArgs + /** Does `tp` fit the "product-seq match" conditions as an unapply result type + * for a pattern with `numArgs` subpatterns? + * This is the case if (1) `tp` has members `_1` to `_N` where `N <= numArgs + 1`. + * (2) `tp._N` conforms to Seq match + */ + def isProductSeqMatch(tp: Type, numArgs: Int, errorPos: SourcePosition = NoSourcePosition)(implicit ctx: Context): Boolean = { + val arity = productArity(tp, errorPos) + arity > 0 && arity <= numArgs + 1 && + unapplySeqTypeElemTp(productSelectorTypes(tp, errorPos).last).exists + } + /** Does `tp` fit the "get match" conditions as an unapply result type? * This is the case of `tp` has a `get` member as well as a * parameterless `isEmpty` member of result type `Boolean`. @@ -60,6 +71,39 @@ object Applications { extractorMemberType(tp, nme.isEmpty, errorPos).isRef(defn.BooleanClass) && extractorMemberType(tp, nme.get, errorPos).exists + /** If `getType` is of the form: + * ``` + * { + * def lengthCompare(len: Int): Int // or, def length: Int + * def apply(i: Int): T = a(i) + * def drop(n: Int): scala.Seq[T] + * def toSeq: scala.Seq[T] + * } + * ``` + * returns `T`, otherwise NoType. + */ + def unapplySeqTypeElemTp(getTp: Type)(implicit ctx: Context): Type = { + def lengthTp = ExprType(defn.IntType) + def lengthCompareTp = MethodType(List(defn.IntType), defn.IntType) + def applyTp(elemTp: Type) = MethodType(List(defn.IntType), elemTp) + def dropTp(elemTp: Type) = MethodType(List(defn.IntType), defn.SeqType.appliedTo(elemTp)) + def toSeqTp(elemTp: Type) = ExprType(defn.SeqType.appliedTo(elemTp)) + + // the result type of `def apply(i: Int): T` + val elemTp = getTp.member(nme.apply).suchThat(_.info <:< applyTp(WildcardType)).info.resultType + + def hasMethod(name: Name, tp: Type) = + getTp.member(name).suchThat(getTp.memberInfo(_) <:< tp).exists + + val isValid = + elemTp.exists && + (hasMethod(nme.lengthCompare, lengthCompareTp) || hasMethod(nme.length, lengthTp)) && + hasMethod(nme.drop, dropTp(elemTp)) && + hasMethod(nme.toSeq, toSeqTp(elemTp)) + + if (isValid) elemTp else NoType + } + def productSelectorTypes(tp: Type, errorPos: SourcePosition)(implicit ctx: Context): List[Type] = { def tupleSelectors(n: Int, tp: Type): List[Type] = { val sel = extractorMemberType(tp, nme.selectorName(n), errorPos) @@ -89,9 +133,17 @@ object Applications { if (args.length > 1 && !(tp.derivesFrom(defn.SeqClass))) { val sels = productSelectorTypes(tp, pos) if (sels.length == args.length) sels + else if (isProductSeqMatch(tp, args.length, pos)) productSeqSelectors(tp, args, pos) else tp :: Nil } else tp :: Nil + def productSeqSelectors(tp: Type, args: List[untpd.Tree], pos: SourcePosition)(implicit ctx: Context): List[Type] = { + val selTps = productSelectorTypes(tp, pos) + val arity = selTps.length + val elemTp = unapplySeqTypeElemTp(selTps.last) + (0 until args.length).map(i => if (i < arity - 1) selTps(i) else elemTp).toList + } + def unapplyArgs(unapplyResult: Type, unapplyFn: Tree, args: List[untpd.Tree], pos: SourcePosition)(implicit ctx: Context): List[Type] = { val unapplyName = unapplyFn.symbol.name @@ -103,43 +155,11 @@ object Applications { Nil } - /** If `getType` is of the form: - * ``` - * { - * def lengthCompare(len: Int): Int // or, def length: Int - * def apply(i: Int): T = a(i) - * def drop(n: Int): scala.Seq[T] - * def toSeq: scala.Seq[T] - * } - * ``` - * returns `T`, otherwise NoType. - */ - def unapplySeqTypeElemTp(getTp: Type): Type = { - def lengthTp = ExprType(defn.IntType) - def lengthCompareTp = MethodType(List(defn.IntType), defn.IntType) - def applyTp(elemTp: Type) = MethodType(List(defn.IntType), elemTp) - def dropTp(elemTp: Type) = MethodType(List(defn.IntType), defn.SeqType.appliedTo(elemTp)) - def toSeqTp(elemTp: Type) = defn.SeqType.appliedTo(elemTp) - - // the result type of `def apply(i: Int): T` - val elemTp = getTp.member(nme.apply).suchThat(_.info <:< applyTp(WildcardType)).info.resultType - - def hasMethod(name: Name, tp: Type) = - getTp.member(name).suchThat(getTp.memberInfo(_) <:< tp).exists - - val isValid = - elemTp.exists && - (hasMethod(nme.lengthCompare, lengthCompareTp) || hasMethod(nme.length, lengthTp)) && - hasMethod(nme.drop, dropTp(elemTp)) && - hasMethod(nme.toSeq, toSeqTp(elemTp)) - - if (isValid) elemTp else NoType - } - - if (unapplyName == nme.unapplySeq) { + if (unapplyName == nme.unapplySeq) { // && ctx.scala2Mode if (isGetMatch(unapplyResult, pos)) { val elemTp = unapplySeqTypeElemTp(getTp) if (elemTp.exists) args.map(Function.const(elemTp)) + else if (isProductSeqMatch(getTp, args.length, pos)) productSeqSelectors(getTp, args, pos) else fail } else fail @@ -148,6 +168,8 @@ object Applications { assert(unapplyName == nme.unapply) if (isProductMatch(unapplyResult, args.length, pos)) productSelectorTypes(unapplyResult, pos) + else if (isProductSeqMatch(unapplyResult, args.length, pos)) + productSeqSelectors(unapplyResult, args, pos) else if (isGetMatch(unapplyResult, pos)) getUnapplySelectors(getTp, args, pos) else if (unapplyResult.widenSingleton isRef defn.BooleanClass) diff --git a/tests/run/i3248.scala b/tests/run/i3248.scala index bf05b88b5883..5e6e0c05d94c 100644 --- a/tests/run/i3248.scala +++ b/tests/run/i3248.scala @@ -1,23 +1,27 @@ -object Test extends App { +object Test { class Foo(val name: String, val children: Int *) object Foo { - def unapply(f: Foo) = Some((f.name, f.children)) + def unapplySeq(f: Foo) = Some((f.name, f.children)) } - def foo(f: Foo) = (f: Any) match { - case Foo(name, ns: _*) => ns.length - case List(ns: _*) => ns.length + def foo(f: Foo) = f match { + case Foo(name, ns : _*) => + assert(name == "hello") + assert(ns(0) == 3) + assert(ns(1) == 5) } - case class Bar(val children: Int*) - - def bar(f: Any) = f match { - case Bar(1, 2, 3) => 0 - case Bar(a, b) => a + b - case Bar(ns: _*) => ns.length + def bar(f: Foo) = f match { + case Foo(name, x, y, ns : _*) => + assert(name == "hello") + assert(x == 3) + assert(y == 5) + assert(ns.isEmpty) } - assert(bar(new Bar(1, 2, 3)) == 0) - assert(bar(new Bar(3, 2, 1)) == 3) - assert(foo(new Foo("name", 1, 2, 3)) == 3) + def main(args: Array[String]): Unit = { + val f = new Foo("hello", 3, 5) + foo(f) + bar(f) + } } From 9c0cc173eb66ed6e67531bde8bfbc2cb620e99ca Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 26 Feb 2019 17:42:25 +0100 Subject: [PATCH 2/9] Add more tests --- .../tools/dotc/transform/patmat/Space.scala | 2 +- tests/neg/i3248.scala | 10 ------ tests/run/i3248b.scala | 27 ++++++++++++++ tests/run/i3248c.scala | 24 +++++++++++++ tests/run/i3248d.scala | 35 +++++++++++++++++++ 5 files changed, 87 insertions(+), 11 deletions(-) delete mode 100644 tests/neg/i3248.scala create mode 100644 tests/run/i3248b.scala create mode 100644 tests/run/i3248c.scala create mode 100644 tests/run/i3248d.scala diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 3b184b6bb9fd..8990fb05f69a 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -424,7 +424,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { List() else { val isUnapplySeq = unappSym.name == nme.unapplySeq - if (isProductMatch(mt.finalResultType, argLen) && !isUnapplySeq) { + if (productArity(mt.finalResultType, unappSym.sourcePos) > 0 && !isUnapplySeq) { productSelectors(mt.finalResultType).take(argLen) .map(_.info.asSeenFrom(mt.finalResultType, mt.resultType.classSymbol).widenExpr) } diff --git a/tests/neg/i3248.scala b/tests/neg/i3248.scala deleted file mode 100644 index 3d70f50cd066..000000000000 --- a/tests/neg/i3248.scala +++ /dev/null @@ -1,10 +0,0 @@ -class Test { - class Foo(val name: String, val children: Int *) - object Foo { - def unapplySeq(f: Foo) = Some((f.name, f.children)) - } - - def foo(f: Foo) = f match { - case Foo(name, ns: _*) => 1 // error: not a valid unapply result type - } -} diff --git a/tests/run/i3248b.scala b/tests/run/i3248b.scala new file mode 100644 index 000000000000..a563ee5da541 --- /dev/null +++ b/tests/run/i3248b.scala @@ -0,0 +1,27 @@ +object Test { + class Foo(val name: String, val children: Int *) + object Foo { + def unapply(f: Foo) = Some((f.name, f.children)) + } + + def foo(f: Foo) = f match { + case Foo(name, ns: _*) => + assert(name == "hello") + assert(ns(0) == 3) + assert(ns(1) == 5) + } + + def bar(f: Foo) = f match { + case Foo(name, x, y, ns : _*) => + assert(name == "hello") + assert(x == 3) + assert(y == 5) + assert(ns.isEmpty) + } + + def main(args: Array[String]): Unit = { + val f = new Foo("hello", 3, 5) + foo(f) + bar(f) + } +} diff --git a/tests/run/i3248c.scala b/tests/run/i3248c.scala new file mode 100644 index 000000000000..138973251b45 --- /dev/null +++ b/tests/run/i3248c.scala @@ -0,0 +1,24 @@ +object Test { + case class Foo(name: String, children: Int *) + + def foo(f: Foo) = f match { + case Foo(name, ns: _*) => + assert(name == "hello") + assert(ns(0) == 3) + assert(ns(1) == 5) + } + + def bar(f: Foo) = f match { + case Foo(name, x, y, ns : _*) => + assert(name == "hello") + assert(x == 3) + assert(y == 5) + assert(ns.isEmpty) + } + + def main(args: Array[String]): Unit = { + val f = new Foo("hello", 3, 5) + foo(f) + bar(f) + } +} diff --git a/tests/run/i3248d.scala b/tests/run/i3248d.scala new file mode 100644 index 000000000000..342c436d517a --- /dev/null +++ b/tests/run/i3248d.scala @@ -0,0 +1,35 @@ +object Test { + case class Foo(name: String, children: Seq[Int]) + + def foo(f: Foo) = f match { + case Foo(name, ns) => + assert(name == "hello") + assert(ns(0) == 3) + assert(ns(1) == 5) + } + + def bar(f: Foo) = f match { + case Foo(name, x, y, ns : _*) => + assert(name == "hello") + assert(x == 3) + assert(y == 5) + assert(ns.isEmpty) + } + + def qux(f: Foo) = f match { + case Foo(name) => 1 + case Foo(name, x, y) => 2 + case Foo(name, xs) => 0 + } + + + def main(args: Array[String]): Unit = { + val f = Foo("hello", List(3, 5)) + foo(f) + bar(f) + assert(qux(Foo("hello", Nil)) == 1) + assert(qux(Foo("hello", 5 :: 6 :: Nil)) == 2) + assert(qux(Foo("hello", 5 :: Nil)) == 0) + assert(qux(Foo("hello", 5 :: 6 :: 7 :: Nil)) == 0) + } +} From 00857dde2ce79de847784371d5ad295a65861179 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 26 Feb 2019 17:49:18 +0100 Subject: [PATCH 3/9] Fix #581: add tests --- .../tools/dotc/transform/PatternMatcher.scala | 2 -- .../run/value-class-extractor-seq.check | 3 --- .../{pending => }/run/string-extractor.check | 0 .../{pending => }/run/string-extractor.scala | 20 ++++++++++--------- tests/run/value-class-extractor-seq.check | 3 +++ .../run/value-class-extractor-seq.scala | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 tests/pending/run/value-class-extractor-seq.check rename tests/{pending => }/run/string-extractor.check (100%) rename tests/{pending => }/run/string-extractor.scala (69%) create mode 100644 tests/run/value-class-extractor-seq.check rename tests/{pending => }/run/value-class-extractor-seq.scala (94%) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 7f26ce816b1f..32f096f37ae4 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -262,8 +262,6 @@ object PatternMatcher { /** Plan for matching the sequence in `getResult` against sequence elements * and a possible last varargs argument `args`. - * - * `getResult` could also be a product, where the last element is a sequence of elements. */ def unapplySeqPlan(getResult: Symbol, args: List[Tree]): Plan = args.lastOption match { case Some(VarArgPattern(arg)) => diff --git a/tests/pending/run/value-class-extractor-seq.check b/tests/pending/run/value-class-extractor-seq.check deleted file mode 100644 index 84552a7aa5b2..000000000000 --- a/tests/pending/run/value-class-extractor-seq.check +++ /dev/null @@ -1,3 +0,0 @@ -Bip(1, 2, 3) -Bip(1, 2, c @ Array(3, 4, 5): _*) -class [I diff --git a/tests/pending/run/string-extractor.check b/tests/run/string-extractor.check similarity index 100% rename from tests/pending/run/string-extractor.check rename to tests/run/string-extractor.check diff --git a/tests/pending/run/string-extractor.scala b/tests/run/string-extractor.scala similarity index 69% rename from tests/pending/run/string-extractor.scala rename to tests/run/string-extractor.scala index 7ab2c2eaab29..13d20ca1a321 100644 --- a/tests/pending/run/string-extractor.scala +++ b/tests/run/string-extractor.scala @@ -6,20 +6,22 @@ final class StringExtract(val s: String) extends AnyVal { def apply(idx: Int): Char = s charAt idx def head: Char = s charAt 0 def tail: String = s drop 1 - def drop(n: Int): StringExtract = new StringExtract(s drop n) + def drop(n: Int): Seq[Char] = toSeq.drop(n) + def toSeq: Seq[Char] = s.toSeq override def toString = s } final class ThreeStringExtract(val s: String) extends AnyVal { - def isEmpty = (s eq null) || (s == "") - def get: (List[Int], Double, ThreeStringExtract) = ((s.length :: Nil, s.length.toDouble, this)) - def length = s.length - def lengthCompare(n: Int) = s.length compare n - def apply(idx: Int): Char = s charAt idx - def head: Char = s charAt 0 - def tail: String = s drop 1 - def drop(n: Int): ThreeStringExtract = new ThreeStringExtract(s drop n) + def isEmpty = (s eq null) || (s == "") + def get: (List[Int], Double, Seq[Char]) = ((s.length :: Nil, s.length.toDouble, toSeq)) + def length = s.length + def lengthCompare(n: Int) = s.length compare n + def apply(idx: Int): Char = s charAt idx + def head: Char = s charAt 0 + def tail: String = s drop 1 + def drop(n: Int): Seq[Char] = toSeq.drop(n) + def toSeq: Seq[Char] = s.toSeq override def toString = s } diff --git a/tests/run/value-class-extractor-seq.check b/tests/run/value-class-extractor-seq.check new file mode 100644 index 000000000000..dc1cb3905607 --- /dev/null +++ b/tests/run/value-class-extractor-seq.check @@ -0,0 +1,3 @@ +Bip(1, 2, 3) +Bip(1, 2, c : WrappedArray(3, 4, 5): _*) +class [I diff --git a/tests/pending/run/value-class-extractor-seq.scala b/tests/run/value-class-extractor-seq.scala similarity index 94% rename from tests/pending/run/value-class-extractor-seq.scala rename to tests/run/value-class-extractor-seq.scala index 9264e70387d0..4aa246b249eb 100644 --- a/tests/pending/run/value-class-extractor-seq.scala +++ b/tests/run/value-class-extractor-seq.scala @@ -2,7 +2,7 @@ import scala.runtime.ScalaRunTime.stringOf final class ArrayOpt[T](val xs: Array[T]) extends AnyVal { def isEmpty = xs == null - def get = xs + def get: Seq[T] = xs.toSeq } object Bip { @@ -44,7 +44,7 @@ object Bip { object Test { def f(x: Any) = x match { case Bip(a, b, c) => s"Bip($a, $b, $c)" - case Bip(a, b, c : _*) => s"Bip($a, $b, c @ ${stringOf(c)}: _*)" + case Bip(a, b, c : _*) => s"Bip($a, $b, c : ${stringOf(c)}: _*)" case _ => "" + x.getClass } From da3957118723e86d8d82592e00fd6b10c51f0d1a Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 27 Feb 2019 01:04:53 +0100 Subject: [PATCH 4/9] Fix exhaustivity check --- .../tools/dotc/transform/patmat/Space.scala | 48 ++++++++++++++----- .../dotty/tools/dotc/typer/Applications.scala | 10 ++-- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 8990fb05f69a..0810225f7f02 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -335,13 +335,25 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { case Bind(_, pat) => project(pat) case SeqLiteral(pats, _) => projectSeq(pats) case UnApply(fun, _, pats) => - if (fun.symbol.name == nme.unapplySeq) - if (fun.symbol.owner == scalaSeqFactoryClass) - projectSeq(pats) - else + if (fun.symbol.owner == scalaSeqFactoryClass && fun.symbol.name == nme.unapplySeq) + projectSeq(pats) + else { + var tp = fun.tpe.widen.finalResultType + var arity = productArity(tp, fun.sourcePos) + if (arity <= 0) { + tp = fun.tpe.widen.finalResultType.select(nme.get).finalResultType.widen + if (pats.length == 1) + return Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.map(project), irrefutable(fun)) + arity = productSelectorTypes(tp, fun.sourcePos).size + } + + if (arity > 0 && arity != pats.length) + Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.take(arity - 1).map(project) :+ projectSeq(pats.drop(arity - 1)), irrefutable(fun)) + else if (arity <= 0 && unapplySeqTypeElemTp(tp).exists) Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, projectSeq(pats) :: Nil, irrefutable(fun)) - else - Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.map(project), irrefutable(fun)) + else + Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.map(project), irrefutable(fun)) + } case Typed(pat @ UnApply(_, _, _), _) => project(pat) case Typed(expr, tpt) => Typ(erase(expr.tpe.stripAnnots), true) @@ -424,17 +436,27 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { List() else { val isUnapplySeq = unappSym.name == nme.unapplySeq - if (productArity(mt.finalResultType, unappSym.sourcePos) > 0 && !isUnapplySeq) { - productSelectors(mt.finalResultType).take(argLen) - .map(_.info.asSeenFrom(mt.finalResultType, mt.resultType.classSymbol).widenExpr) + val arity = productArity(mt.finalResultType, unappSym.sourcePos) + if (arity > 0 && !isUnapplySeq) { + if (arity != argLen) { + val sels = productSeqSelectors(mt.finalResultType, arity, unappSym.sourcePos) + sels.init :+ scalaListType.appliedTo(sels.last) + } + else + productSelectors(mt.finalResultType) + .map(_.info.asSeenFrom(mt.finalResultType, mt.resultType.classSymbol).widenExpr) } else { val resTp = mt.finalResultType.select(nme.get).finalResultType.widen - if (isUnapplySeq) scalaListType.appliedTo(resTp.argTypes.head) :: Nil - else if (argLen == 0) Nil - else if (isProductMatch(resTp, argLen)) + val arity = productArity(resTp, unappSym.sourcePos) + if (isUnapplySeq && arity < 0) scalaListType.appliedTo(resTp.argTypes.head) :: Nil + else if (argLen == 1) resTp :: Nil + else if (arity > 0 && arity != argLen) { + val sels = productSeqSelectors(resTp, arity, unappSym.sourcePos) + sels.init :+ scalaListType.appliedTo(sels.last) + } + else productSelectors(resTp).map(_.info.asSeenFrom(resTp, resTp.classSymbol).widenExpr) - else resTp :: Nil } } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 0d64877ecc12..f73d0a419c5f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -133,15 +133,15 @@ object Applications { if (args.length > 1 && !(tp.derivesFrom(defn.SeqClass))) { val sels = productSelectorTypes(tp, pos) if (sels.length == args.length) sels - else if (isProductSeqMatch(tp, args.length, pos)) productSeqSelectors(tp, args, pos) + else if (isProductSeqMatch(tp, args.length, pos)) productSeqSelectors(tp, args.length, pos) else tp :: Nil } else tp :: Nil - def productSeqSelectors(tp: Type, args: List[untpd.Tree], pos: SourcePosition)(implicit ctx: Context): List[Type] = { + def productSeqSelectors(tp: Type, argsNum: Int, pos: SourcePosition)(implicit ctx: Context): List[Type] = { val selTps = productSelectorTypes(tp, pos) val arity = selTps.length val elemTp = unapplySeqTypeElemTp(selTps.last) - (0 until args.length).map(i => if (i < arity - 1) selTps(i) else elemTp).toList + (0 until argsNum).map(i => if (i < arity - 1) selTps(i) else elemTp).toList } def unapplyArgs(unapplyResult: Type, unapplyFn: Tree, args: List[untpd.Tree], pos: SourcePosition)(implicit ctx: Context): List[Type] = { @@ -159,7 +159,7 @@ object Applications { if (isGetMatch(unapplyResult, pos)) { val elemTp = unapplySeqTypeElemTp(getTp) if (elemTp.exists) args.map(Function.const(elemTp)) - else if (isProductSeqMatch(getTp, args.length, pos)) productSeqSelectors(getTp, args, pos) + else if (isProductSeqMatch(getTp, args.length, pos)) productSeqSelectors(getTp, args.length, pos) else fail } else fail @@ -169,7 +169,7 @@ object Applications { if (isProductMatch(unapplyResult, args.length, pos)) productSelectorTypes(unapplyResult, pos) else if (isProductSeqMatch(unapplyResult, args.length, pos)) - productSeqSelectors(unapplyResult, args, pos) + productSeqSelectors(unapplyResult, args.length, pos) else if (isGetMatch(unapplyResult, pos)) getUnapplySelectors(getTp, args, pos) else if (unapplyResult.widenSingleton isRef defn.BooleanClass) From ce81c02093037c8b45720aabfaeeecabbbf1693e Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 7 Mar 2019 14:34:04 +0100 Subject: [PATCH 5/9] Refactoring: keep unapplySeq --- .../tools/dotc/transform/PatternMatcher.scala | 21 ++--- .../tools/dotc/transform/patmat/Space.scala | 76 ++++++++++--------- .../dotty/tools/dotc/typer/Applications.scala | 18 +++-- 3 files changed, 58 insertions(+), 57 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 32f096f37ae4..d81321fc0058 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -286,8 +286,7 @@ object PatternMatcher { matchElemsPlan(getResult, args, exact = true, onSuccess) } - /** Plan for matching the sequence in `getResult` against sequence elements - * and a possible last varargs argument `args`. + /** Plan for matching the sequence in `getResult` * * `getResult` is a product, where the last element is a sequence of elements. */ @@ -322,7 +321,7 @@ object PatternMatcher { .map(ref(unappResult).select(_)) matchArgsPlan(selectors, args, onSuccess) } - else if (isProductSeqMatch(unapp.tpe.widen, args.length, unapp.sourcePos) && !isUnapplySeq) { + else if (isProductSeqMatch(unapp.tpe.widen, args.length, unapp.sourcePos) && isUnapplySeq) { val arity = productArity(unapp.tpe.widen, unapp.sourcePos) unapplyProductSeqPlan(unappResult, args, arity) } @@ -338,18 +337,10 @@ object PatternMatcher { } else letAbstract(get) { getResult => - if (args.tail.isEmpty) // Single pattern takes precedence - matchArgsPlan(ref(getResult) :: Nil, args, onSuccess) - else if (isProductMatch(get.tpe, args.length, unapp.sourcePos)) { - val sels = productSelectors(get.tpe).map(ref(getResult).select(_)) - matchArgsPlan(sels, args, onSuccess) - } - else if (isProductSeqMatch(get.tpe, args.length, unapp.sourcePos)) - unapplyProductSeqPlan(getResult, args, arity) - else { // name-based - val sels = productSelectors(get.tpe).map(ref(getResult).select(_)) - matchArgsPlan(sels, args, onSuccess) - } + val selectors = + if (args.tail.isEmpty) ref(getResult) :: Nil + else productSelectors(get.tpe).map(ref(getResult).select(_)) + matchArgsPlan(selectors, args, onSuccess) } } TestPlan(NonEmptyTest, unappResult, unapp.span, argsPlan) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 0810225f7f02..6a4567e67b49 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -335,25 +335,26 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { case Bind(_, pat) => project(pat) case SeqLiteral(pats, _) => projectSeq(pats) case UnApply(fun, _, pats) => - if (fun.symbol.owner == scalaSeqFactoryClass && fun.symbol.name == nme.unapplySeq) - projectSeq(pats) - else { - var tp = fun.tpe.widen.finalResultType - var arity = productArity(tp, fun.sourcePos) - if (arity <= 0) { - tp = fun.tpe.widen.finalResultType.select(nme.get).finalResultType.widen - if (pats.length == 1) - return Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.map(project), irrefutable(fun)) - arity = productSelectorTypes(tp, fun.sourcePos).size - } + if (fun.symbol.name == nme.unapplySeq) + if (fun.symbol.owner == scalaSeqFactoryClass) + projectSeq(pats) + else { + val resultTp = fun.tpe.widen.finalResultType + var elemTp = unapplySeqTypeElemTp(resultTp) + var arity = productArity(resultTp, fun.sourcePos) + if (!elemTp.exists && arity <= 0) { + val resultTp = fun.tpe.widen.finalResultType.select(nme.get).finalResultType + elemTp = unapplySeqTypeElemTp(resultTp.widen) + arity = productSelectorTypes(resultTp, fun.sourcePos).size + } - if (arity > 0 && arity != pats.length) - Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.take(arity - 1).map(project) :+ projectSeq(pats.drop(arity - 1)), irrefutable(fun)) - else if (arity <= 0 && unapplySeqTypeElemTp(tp).exists) - Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, projectSeq(pats) :: Nil, irrefutable(fun)) - else - Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.map(project), irrefutable(fun)) - } + if (elemTp.exists) + Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, projectSeq(pats) :: Nil, irrefutable(fun)) + else + Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.take(arity - 1).map(project) :+ projectSeq(pats.drop(arity - 1)), irrefutable(fun)) + } + else + Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, pats.map(project), irrefutable(fun)) case Typed(pat @ UnApply(_, _, _), _) => project(pat) case Typed(expr, tpt) => Typ(erase(expr.tpe.stripAnnots), true) @@ -436,27 +437,34 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { List() else { val isUnapplySeq = unappSym.name == nme.unapplySeq - val arity = productArity(mt.finalResultType, unappSym.sourcePos) - if (arity > 0 && !isUnapplySeq) { - if (arity != argLen) { - val sels = productSeqSelectors(mt.finalResultType, arity, unappSym.sourcePos) + + if (isUnapplySeq) { + var resultTp = mt.finalResultType + var elemTp = unapplySeqTypeElemTp(resultTp) + var arity = productArity(resultTp, unappSym.sourcePos) + if (!elemTp.exists && arity <= 0) { + resultTp = mt.finalResultType.select(nme.get).finalResultType + elemTp = unapplySeqTypeElemTp(resultTp.widen) + arity = productSelectorTypes(resultTp, unappSym.sourcePos).size + } + + if (elemTp.exists) scalaListType.appliedTo(elemTp) :: Nil + else { + val sels = productSeqSelectors(resultTp, arity, unappSym.sourcePos) sels.init :+ scalaListType.appliedTo(sels.last) } - else - productSelectors(mt.finalResultType) - .map(_.info.asSeenFrom(mt.finalResultType, mt.resultType.classSymbol).widenExpr) } else { - val resTp = mt.finalResultType.select(nme.get).finalResultType.widen - val arity = productArity(resTp, unappSym.sourcePos) - if (isUnapplySeq && arity < 0) scalaListType.appliedTo(resTp.argTypes.head) :: Nil - else if (argLen == 1) resTp :: Nil - else if (arity > 0 && arity != argLen) { - val sels = productSeqSelectors(resTp, arity, unappSym.sourcePos) - sels.init :+ scalaListType.appliedTo(sels.last) + val arity = productArity(mt.finalResultType, unappSym.sourcePos) + if (arity > 0) + productSelectors(mt.finalResultType) + .map(_.info.asSeenFrom(mt.finalResultType, mt.resultType.classSymbol).widenExpr) + else { + val resTp = mt.finalResultType.select(nme.get).finalResultType.widen + val arity = productArity(resTp, unappSym.sourcePos) + if (argLen == 1) resTp :: Nil + else productSelectors(resTp).map(_.info.asSeenFrom(resTp, resTp.classSymbol).widenExpr) } - else - productSelectors(resTp).map(_.info.asSeenFrom(resTp, resTp.classSymbol).widenExpr) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index f73d0a419c5f..62ef56f6e7de 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -155,21 +155,23 @@ object Applications { Nil } - if (unapplyName == nme.unapplySeq) { // && ctx.scala2Mode - if (isGetMatch(unapplyResult, pos)) { - val elemTp = unapplySeqTypeElemTp(getTp) - if (elemTp.exists) args.map(Function.const(elemTp)) - else if (isProductSeqMatch(getTp, args.length, pos)) productSeqSelectors(getTp, args.length, pos) + def unapplySeq(tp: Type)(fallback: => List[Type]): List[Type] = { + 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 fallback + } + + if (unapplyName == nme.unapplySeq) { + unapplySeq(unapplyResult) { + if (isGetMatch(unapplyResult, pos)) unapplySeq(getTp)(fail) else fail } - else fail } else { assert(unapplyName == nme.unapply) if (isProductMatch(unapplyResult, args.length, pos)) productSelectorTypes(unapplyResult, pos) - else if (isProductSeqMatch(unapplyResult, args.length, pos)) - productSeqSelectors(unapplyResult, args.length, pos) else if (isGetMatch(unapplyResult, pos)) getUnapplySelectors(getTp, args, pos) else if (unapplyResult.widenSingleton isRef defn.BooleanClass) From 21c6fd0c1770a29a95db00d2b2fe8ca66aa3760c Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 7 Mar 2019 15:02:41 +0100 Subject: [PATCH 6/9] Revert #3248: generate unapplySeq for case class with varargs --- .../src/dotty/tools/dotc/ast/Desugar.scala | 20 +++++++++++------- .../dotty/tools/dotc/typer/Applications.scala | 21 ++++++------------- tests/run/i3248b.scala | 2 +- tests/run/i3248d.scala | 20 +++++++++++------- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 1ada58556f6a..9fa01c370433 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -432,6 +432,12 @@ object desugar { appliedTypeTree(tycon, targs) } + def isRepeated(tree: Tree): Boolean = tree match { + case PostfixOp(_, Ident(tpnme.raw.STAR)) => true + case ByNameTypeTree(tree1) => isRepeated(tree1) + case _ => false + } + // a reference to the class type bound by `cdef`, with type parameters coming from the constructor val classTypeRef = appliedRef(classTycon) @@ -482,11 +488,6 @@ object desugar { } def enumTagMeths = if (isEnumCase) enumTagMeth(CaseKind.Class)._1 :: Nil else Nil def copyMeths = { - def isRepeated(tree: Tree): Boolean = tree match { - case PostfixOp(_, Ident(tpnme.raw.STAR)) => true - case ByNameTypeTree(tree1) => isRepeated(tree1) - case _ => false - } val hasRepeatedParam = constrVparamss.exists(_.exists { case ValDef(_, tpt, _) => isRepeated(tpt) }) @@ -560,7 +561,8 @@ object desugar { // companion definitions include: // 1. If class is a case class case class C[Ts](p1: T1, ..., pN: TN)(moreParams): // def apply[Ts](p1: T1, ..., pN: TN)(moreParams) = new C[Ts](p1, ..., pN)(moreParams) (unless C is abstract) - // def unapply[Ts]($1: C[Ts]) = $1 + // def unapply[Ts]($1: C[Ts]) = $1 // if not repeated + // def unapplySeq[Ts]($1: C[Ts]) = $1 // if repeated // 2. The default getters of the constructor // The parent of the companion object of a non-parameterized case class // (T11, ..., T1N) => ... => (TM1, ..., TMN) => C @@ -609,9 +611,13 @@ object desugar { app :: widenDefs } val unapplyMeth = { + val hasRepeatedParam = constrVparamss.head.exists { + case ValDef(_, tpt, _) => isRepeated(tpt) + } + val methName = if (hasRepeatedParam) nme.unapplySeq else nme.unapply val unapplyParam = makeSyntheticParameter(tpt = classTypeRef) val unapplyRHS = if (arity == 0) Literal(Constant(true)) else Ident(unapplyParam.name) - DefDef(nme.unapply, derivedTparams, (unapplyParam :: Nil) :: Nil, TypeTree(), unapplyRHS) + DefDef(methName, derivedTparams, (unapplyParam :: Nil) :: Nil, TypeTree(), unapplyRHS) .withMods(synthetic) } companionDefs(companionParent, applyMeths ::: unapplyMeth :: companionMembers) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 62ef56f6e7de..ea6814f6befc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -133,7 +133,6 @@ object Applications { if (args.length > 1 && !(tp.derivesFrom(defn.SeqClass))) { val sels = productSelectorTypes(tp, pos) if (sels.length == args.length) sels - else if (isProductSeqMatch(tp, args.length, pos)) productSeqSelectors(tp, args.length, pos) else tp :: Nil } else tp :: Nil @@ -147,7 +146,6 @@ object Applications { def unapplyArgs(unapplyResult: Type, unapplyFn: Tree, args: List[untpd.Tree], pos: SourcePosition)(implicit ctx: Context): List[Type] = { val unapplyName = unapplyFn.symbol.name - def seqSelector = defn.RepeatedParamType.appliedTo(unapplyResult.elemType :: Nil) def getTp = extractorMemberType(unapplyResult, nme.get, pos) def fail = { @@ -1100,19 +1098,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => var argTypes = unapplyArgs(unapplyApp.tpe, unapplyFn, args, tree.sourcePos) for (argType <- argTypes) assert(!isBounds(argType), unapplyApp.tpe.show) - val bunchedArgs = - if (argTypes.nonEmpty && argTypes.last.isRepeatedParam) - args.lastOption match { - case Some(arg @ Typed(argSeq, _)) if untpd.isWildcardStarArg(arg) => - args.init :+ argSeq - case _ => - val (regularArgs, varArgs) = args.splitAt(argTypes.length - 1) - regularArgs :+ untpd.SeqLiteral(varArgs, untpd.TypeTree()).withSpan(tree.span) - } - else if (argTypes.lengthCompare(1) == 0 && args.lengthCompare(1) > 0 && ctx.canAutoTuple) - untpd.Tuple(args) :: Nil - else - args + val bunchedArgs = argTypes match { + case argType :: Nil => + if (args.lengthCompare(1) > 0 && ctx.canAutoTuple) untpd.Tuple(args) :: Nil + else args + case _ => args + } if (argTypes.length != bunchedArgs.length) { ctx.error(UnapplyInvalidNumberOfArguments(qual, argTypes), tree.sourcePos) argTypes = argTypes.take(args.length) ++ diff --git a/tests/run/i3248b.scala b/tests/run/i3248b.scala index a563ee5da541..31984780e9f6 100644 --- a/tests/run/i3248b.scala +++ b/tests/run/i3248b.scala @@ -1,7 +1,7 @@ object Test { class Foo(val name: String, val children: Int *) object Foo { - def unapply(f: Foo) = Some((f.name, f.children)) + def unapplySeq(f: Foo) = Some((f.name, f.children)) } def foo(f: Foo) = f match { diff --git a/tests/run/i3248d.scala b/tests/run/i3248d.scala index 342c436d517a..1d7aef0742f7 100644 --- a/tests/run/i3248d.scala +++ b/tests/run/i3248d.scala @@ -1,8 +1,12 @@ object Test { - case class Foo(name: String, children: Seq[Int]) + class Foo(val name: String, val children: Seq[Int]) + + object Foo { + def unapplySeq(foo: Foo): (String, Seq[Int]) = (foo.name, foo.children) + } def foo(f: Foo) = f match { - case Foo(name, ns) => + case Foo(name, ns: _*) => assert(name == "hello") assert(ns(0) == 3) assert(ns(1) == 5) @@ -19,17 +23,17 @@ object Test { def qux(f: Foo) = f match { case Foo(name) => 1 case Foo(name, x, y) => 2 - case Foo(name, xs) => 0 + case Foo(name, xs: _*) => 0 } def main(args: Array[String]): Unit = { - val f = Foo("hello", List(3, 5)) + val f = new Foo("hello", List(3, 5)) foo(f) bar(f) - assert(qux(Foo("hello", Nil)) == 1) - assert(qux(Foo("hello", 5 :: 6 :: Nil)) == 2) - assert(qux(Foo("hello", 5 :: Nil)) == 0) - assert(qux(Foo("hello", 5 :: 6 :: 7 :: Nil)) == 0) + assert(qux(new Foo("hello", Nil)) == 1) + assert(qux(new Foo("hello", 5 :: 6 :: Nil)) == 2) + assert(qux(new Foo("hello", 5 :: Nil)) == 0) + assert(qux(new Foo("hello", 5 :: 6 :: 7 :: Nil)) == 0) } } From a964e04da28708056e613a6c79a8bb597bf11a59 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Thu, 7 Mar 2019 15:47:48 +0100 Subject: [PATCH 7/9] Update spec for pattern match --- .../changed-features/pattern-matching.md | 160 ++++++++++++++---- 1 file changed, 127 insertions(+), 33 deletions(-) diff --git a/docs/docs/reference/changed-features/pattern-matching.md b/docs/docs/reference/changed-features/pattern-matching.md index 3285f0b04ea7..ec8bb06e73f7 100644 --- a/docs/docs/reference/changed-features/pattern-matching.md +++ b/docs/docs/reference/changed-features/pattern-matching.md @@ -7,9 +7,74 @@ Dotty implementation of pattern matching was greatly simplified compared to scal Dotty supports a superset of scalac's [extractors](https://www.scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#extractor-patterns). -## Boolean Pattern +## Extractors -- Extractor defines `def unapply(x: T): Boolean` +Extractors are objects that expose a method `unapply` or `unapplySeq`: + +```Scala +def unapply[A](x: T)(implicit x: B): U +def unapplySeq[A](x: T)(implicit x: B): U +``` + +Extractors expose the method `unapply` are called fix-arity extractors, which +work with patterns of fixed arity. Extractors expose the method `unapplySeq` are +called variadic extractors, which enables variadic patterns. + +### Fixed-Arity Extractors + +Fixed-arity extractors expose the following signature: + +```Scala +def unapply[A](x: T)(implicit x: B): U +``` + +The type `U` conforms to one of the following matches: + +- Boolean match +- Product match + +Or `U` conforms to the type `R`: + +```Scala +type R = { + def isEmpty: Boolean + def get: S +} +``` + +and `S` conforms to one of the following matches: + +- Single match +- Name-based match + + +### Variadic Extractors + +Variadic extractors expose the following signature: + +```Scala +def unapplySeq[A](x: T)(implicit x: B): U +``` + +The type `U` conforms to one of the following matches: + +- Sequence match +- Product-sequence match + +Or `U` conforms to the type `R`: + +```Scala +type R = { + def isEmpty: Boolean + def get: S +} +``` + +and `S` conforms to one of the two matches above. + +## Boolean Match + +- `U =:= Boolean` - Pattern-matching on exactly `0` patterns For example: @@ -28,10 +93,8 @@ object Even { // even has an even number of characters ``` +## Product Match -## Product Pattern - -- Extractor defines `def unapply(x: T): U` - `U <: Product` - `N > 0` is the maximum number of consecutive (parameterless `def` or `val`) `_1: P1` ... `_N: PN` members in `U` - Pattern-matching on exactly `N` patterns with types `P1, P2, ..., PN` @@ -62,12 +125,53 @@ object FirstChars { // First: H; Second: i ``` +## Single Match + +- If there is exactly `1` pattern, pattern-matching on `1` pattern with type `U` + + + +```scala +class Nat(val x: Int) { + def get: Int = x + def isEmpty = x < 0 +} + +object Nat { + def unapply(x: Int): Nat = new Nat(x) +} + +5 match { + case Nat(n) => println(s"$n is a natural number") + case _ => () +} +// 5 is a natural number +``` + +## Name-based Match + +- `N > 1` is the maximum number of consecutive (parameterless `def` or `val`) `_1: P1 ... _N: PN` members in `U` +- Pattern-matching on exactly `N` patterns with types `P1, P2, ..., PN` + +```Scala +object ProdEmpty { + def _1: Int = ??? + def _2: String = ??? + def isEmpty = true + def unapply(s: String): this.type = this + def get = this +} + +"" match { + case ProdEmpty(_, _) => ??? + case _ => () +} +``` + -## Name-based Seq Pattern +## Seq Match -- Extractor defines `def unapplySeq(x: T): U` -- `U` has (parameterless `def` or `val`) members `isEmpty: Boolean` and `get: S` -- `S` conforms to `X`, `T2` and `T3` conform to `T1` +- `U <: X`, `T2` and `T3` conform to `T1` ```Scala type X = { @@ -97,32 +201,22 @@ object CharList { // e,x,a,m ``` +## Product-Seq Match -## Name-based Pattern - -- Extractor defines `def unapply(x: T): U` -- `U` has (parameterless `def` or `val`) members `isEmpty: Boolean` and `get: S` -- If there is exactly `1` pattern, pattern-matching on `1` pattern with type `S` -- Otherwise `N > 0` is the maximum number of consecutive (parameterless `def` or `val`) `_1: P1` ... `_N: PN` members in `U` -- Pattern-matching on exactly `N` patterns with types `P1, P2, ..., PN` - - - -```scala -class Nat(val x: Int) { - def get: Int = x - def isEmpty = x < 0 -} +- `U <: Product` +- `N > 0` is the maximum number of consecutive (parameterless `def` or `val`) `_1: P1` ... `_N: PN` members in `U` +- `PN` conforms to the signature `X` defined in Seq Pattern +- Pattern-matching on exactly `>= N` patterns, the first `N - 1` patterns have types `P1, P2, ... P(N-1)`, + the type of the remaining patterns are determined as in Seq Pattern. -object Nat { - def unapply(x: Int): Nat = new Nat(x) +```Scala +class Foo(val name: String, val children: Int *) +object Foo { + def unapplySeq(f: Foo): Option[(String, Seq[Int])] = Some((f.name, f.children)) } -5 match { - case Nat(n) => println(s"$n is a natural number") - case _ => () +def foo(f: Foo) = f match { + case Foo(name, ns : _*) => + case Foo(name, x, y, ns : _*) => } -// 5 is a natural number -``` - -In case of ambiguities, *Product Pattern* is preferred over *Name Based Pattern*. This document reflects the state of pattern matching as currently implemented in Dotty. There are plans for further simplification, in particular to factor out *Product Pattern* and *Name Based Pattern* into a single type of extractor. +``` \ No newline at end of file From 1a2620879c98ed8567726bfbbe5a590f2561546d Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 11 Mar 2019 22:33:39 +0100 Subject: [PATCH 8/9] Address review --- .../tools/dotc/transform/patmat/Space.scala | 33 +++++++++---------- .../changed-features/pattern-matching.md | 5 ++- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 6a4567e67b49..bca5cb4eca7f 100644 --- a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala +++ b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala @@ -20,6 +20,7 @@ import ProtoTypes._ import transform.SymUtils._ import reporting.diagnostic.messages._ import config.Printers.{exhaustivity => debug} +import util.SourcePosition /** Space logic for checking exhaustivity and unreachability of pattern matching * @@ -339,15 +340,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { if (fun.symbol.owner == scalaSeqFactoryClass) projectSeq(pats) else { - val resultTp = fun.tpe.widen.finalResultType - var elemTp = unapplySeqTypeElemTp(resultTp) - var arity = productArity(resultTp, fun.sourcePos) - if (!elemTp.exists && arity <= 0) { - val resultTp = fun.tpe.widen.finalResultType.select(nme.get).finalResultType - elemTp = unapplySeqTypeElemTp(resultTp.widen) - arity = productSelectorTypes(resultTp, fun.sourcePos).size - } - + val (arity, elemTp, resultTp) = unapplySeqInfo(fun.tpe.widen.finalResultType, fun.sourcePos) if (elemTp.exists) Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, projectSeq(pats) :: Nil, irrefutable(fun)) else @@ -367,6 +360,18 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { Empty } + private def unapplySeqInfo(resTp: Type, pos: SourcePosition)(implicit ctx: Context): (Int, Type, Type) = { + var resultTp = resTp + var elemTp = unapplySeqTypeElemTp(resultTp) + var arity = productArity(resultTp, pos) + if (!elemTp.exists && arity <= 0) { + resultTp = resTp.select(nme.get).finalResultType + elemTp = unapplySeqTypeElemTp(resultTp.widen) + arity = productSelectorTypes(resultTp, pos).size + } + (arity, elemTp, resultTp) + } + /* Erase pattern bound types with WildcardType */ def erase(tp: Type): Type = { def isPatternTypeSymbol(sym: Symbol) = !sym.isClass && sym.is(Case) @@ -439,15 +444,7 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { val isUnapplySeq = unappSym.name == nme.unapplySeq if (isUnapplySeq) { - var resultTp = mt.finalResultType - var elemTp = unapplySeqTypeElemTp(resultTp) - var arity = productArity(resultTp, unappSym.sourcePos) - if (!elemTp.exists && arity <= 0) { - resultTp = mt.finalResultType.select(nme.get).finalResultType - elemTp = unapplySeqTypeElemTp(resultTp.widen) - arity = productSelectorTypes(resultTp, unappSym.sourcePos).size - } - + val (arity, elemTp, resultTp) = unapplySeqInfo(mt.finalResultType, unappSym.sourcePos) if (elemTp.exists) scalaListType.appliedTo(elemTp) :: Nil else { val sels = productSeqSelectors(resultTp, arity, unappSym.sourcePos) diff --git a/docs/docs/reference/changed-features/pattern-matching.md b/docs/docs/reference/changed-features/pattern-matching.md index ec8bb06e73f7..e75cbc0a6a41 100644 --- a/docs/docs/reference/changed-features/pattern-matching.md +++ b/docs/docs/reference/changed-features/pattern-matching.md @@ -219,4 +219,7 @@ def foo(f: Foo) = f match { case Foo(name, ns : _*) => case Foo(name, x, y, ns : _*) => } -``` \ No newline at end of file +``` + +There are plans for further simplification, in particular to factor out *Product +Pattern* and *Name Based Pattern* into a single type of extractor. \ No newline at end of file From ec5a391c81c200cfff208771dd8c77f7d3d05627 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 11 Mar 2019 22:40:42 +0100 Subject: [PATCH 9/9] Be clear about precedence --- .../changed-features/pattern-matching.md | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/docs/reference/changed-features/pattern-matching.md b/docs/docs/reference/changed-features/pattern-matching.md index e75cbc0a6a41..940a99321445 100644 --- a/docs/docs/reference/changed-features/pattern-matching.md +++ b/docs/docs/reference/changed-features/pattern-matching.md @@ -16,7 +16,7 @@ def unapply[A](x: T)(implicit x: B): U def unapplySeq[A](x: T)(implicit x: B): U ``` -Extractors expose the method `unapply` are called fix-arity extractors, which +Extractors expose the method `unapply` are called fixed-arity extractors, which work with patterns of fixed arity. Extractors expose the method `unapplySeq` are called variadic extractors, which enables variadic patterns. @@ -44,9 +44,11 @@ type R = { and `S` conforms to one of the following matches: -- Single match -- Name-based match +- single match +- name-based match +The former form of `unapply` has higher precedence, and _single match_ has higher +precedence over _name-based match_. ### Variadic Extractors @@ -58,8 +60,8 @@ def unapplySeq[A](x: T)(implicit x: B): U The type `U` conforms to one of the following matches: -- Sequence match -- Product-sequence match +- sequence match +- product-sequence match Or `U` conforms to the type `R`: @@ -72,6 +74,9 @@ type R = { and `S` conforms to one of the two matches above. +The former form of `unapplySeq` has higher priority, and _sequence match_ has higher +precedence over _product-sequence match_. + ## Boolean Match - `U =:= Boolean` @@ -169,7 +174,7 @@ object ProdEmpty { ``` -## Seq Match +## Sequence Match - `U <: X`, `T2` and `T3` conform to `T1` @@ -201,7 +206,7 @@ object CharList { // e,x,a,m ``` -## Product-Seq Match +## Product-Sequence Match - `U <: Product` - `N > 0` is the maximum number of consecutive (parameterless `def` or `val`) `_1: P1` ... `_N: PN` members in `U` @@ -221,5 +226,5 @@ def foo(f: Foo) = f match { } ``` -There are plans for further simplification, in particular to factor out *Product -Pattern* and *Name Based Pattern* into a single type of extractor. \ No newline at end of file +There are plans for further simplification, in particular to factor out *product +match* and *name-based match* into a single type of extractor. \ No newline at end of file