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/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index bbe45fa9638a..d81321fc0058 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._ @@ -286,6 +286,21 @@ object PatternMatcher { matchElemsPlan(getResult, args, exact = true, onSuccess) } + /** Plan for matching the sequence in `getResult` + * + * `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,12 +321,20 @@ 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 = diff --git a/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala b/compiler/src/dotty/tools/dotc/transform/patmat/Space.scala index 3b184b6bb9fd..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 * @@ -338,8 +339,13 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { if (fun.symbol.name == nme.unapplySeq) if (fun.symbol.owner == scalaSeqFactoryClass) projectSeq(pats) - else - Prod(erase(pat.tpe.stripAnnots), fun.tpe, fun.symbol, projectSeq(pats) :: Nil, irrefutable(fun)) + else { + 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 + 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) @@ -354,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) @@ -424,17 +442,26 @@ class SpaceEngine(implicit ctx: Context) extends SpaceLogic { List() else { val isUnapplySeq = unappSym.name == nme.unapplySeq - if (isProductMatch(mt.finalResultType, argLen) && !isUnapplySeq) { - productSelectors(mt.finalResultType).take(argLen) - .map(_.info.asSeenFrom(mt.finalResultType, mt.resultType.classSymbol).widenExpr) + + if (isUnapplySeq) { + val (arity, elemTp, resultTp) = unapplySeqInfo(mt.finalResultType, unappSym.sourcePos) + if (elemTp.exists) scalaListType.appliedTo(elemTp) :: Nil + else { + val sels = productSeqSelectors(resultTp, arity, unappSym.sourcePos) + sels.init :+ scalaListType.appliedTo(sels.last) + } } 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)) - productSelectors(resTp).map(_.info.asSeenFrom(resTp, resTp.classSymbol).widenExpr) - else resTp :: Nil + 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) + } } } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 7a590b65d205..ea6814f6befc 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) @@ -92,10 +136,16 @@ object Applications { else tp :: Nil } else tp :: Nil + 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 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] = { val unapplyName = unapplyFn.symbol.name - def seqSelector = defn.RepeatedParamType.appliedTo(unapplyResult.elemType :: Nil) def getTp = extractorMemberType(unapplyResult, nme.get, pos) def fail = { @@ -103,46 +153,18 @@ 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 + 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) { - if (isGetMatch(unapplyResult, pos)) { - val elemTp = unapplySeqTypeElemTp(getTp) - if (elemTp.exists) args.map(Function.const(elemTp)) + unapplySeq(unapplyResult) { + if (isGetMatch(unapplyResult, pos)) unapplySeq(getTp)(fail) else fail } - else fail } else { assert(unapplyName == nme.unapply) @@ -1076,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/docs/docs/reference/changed-features/pattern-matching.md b/docs/docs/reference/changed-features/pattern-matching.md index 3285f0b04ea7..940a99321445 100644 --- a/docs/docs/reference/changed-features/pattern-matching.md +++ b/docs/docs/reference/changed-features/pattern-matching.md @@ -7,9 +7,79 @@ 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 fixed-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 + +The former form of `unapply` has higher precedence, and _single match_ has higher +precedence over _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. + +The former form of `unapplySeq` has higher priority, and _sequence match_ has higher +precedence over _product-sequence match_. + +## Boolean Match + +- `U =:= Boolean` - Pattern-matching on exactly `0` patterns For example: @@ -28,10 +98,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 +130,53 @@ object FirstChars { // First: H; Second: i ``` +## Single Match -## Name-based Seq Pattern +- If there is exactly `1` pattern, pattern-matching on `1` pattern with type `U` + + -- 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` +```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 _ => () +} +``` + + +## Sequence Match + +- `U <: X`, `T2` and `T3` conform to `T1` ```Scala type X = { @@ -97,32 +206,25 @@ object CharList { // e,x,a,m ``` +## Product-Sequence 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. +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 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/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/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) + } } diff --git a/tests/run/i3248b.scala b/tests/run/i3248b.scala new file mode 100644 index 000000000000..31984780e9f6 --- /dev/null +++ b/tests/run/i3248b.scala @@ -0,0 +1,27 @@ +object 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: _*) => + 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..1d7aef0742f7 --- /dev/null +++ b/tests/run/i3248d.scala @@ -0,0 +1,39 @@ +object Test { + 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: _*) => + 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 = new Foo("hello", List(3, 5)) + foo(f) + bar(f) + 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) + } +} 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 }