summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2013-12-15 18:28:03 -0800
committerPaul Phillips <paulp@improving.org>2013-12-15 18:28:03 -0800
commit11bfa25e37d32f4017d5c04b4899b1bdfbd95e06 (patch)
tree42fab30ce189d1bc93fbecb5496448a7176ba5e9
parentdbe7a366c994fe359edc368bfcd8a6a35a00e0da (diff)
downloadscala-11bfa25e37d32f4017d5c04b4899b1bdfbd95e06.tar.gz
scala-11bfa25e37d32f4017d5c04b4899b1bdfbd95e06.tar.bz2
scala-11bfa25e37d32f4017d5c04b4899b1bdfbd95e06.zip
SI-7897, SI-6675 improves name-based patmat
This emerges from a recent attempt to eliminate pattern matcher related duplication and to bake the scalac-independent logic out of it. I had in mind something a lot cleaner, but it was a whole lot of work to get it here and I can take it no further. Key file to admire is PatternExpander.scala, which should provide a basis for some separation of concerns. The bugs addressed are a CCE involving Tuple1 and an imprecise warning regarding multiple pattern crushing. Editorial: auto-tupling unapply results was a terrible idea which should never have escaped from the crib. It is tantamount to purposely throwing type safety down the toilet in the very place where people need type safety the most. See SI-6111 and SI-6675 for some other comments.
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala11
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala165
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala78
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/PatternExpander.scala155
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala154
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala122
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Unapplies.scala2
-rw-r--r--src/reflect/scala/reflect/internal/Definitions.scala7
-rw-r--r--src/reflect/scala/reflect/internal/TreeInfo.scala13
-rw-r--r--test/files/neg/t4425b.check30
-rw-r--r--test/files/neg/t5903a.check2
-rw-r--r--test/files/neg/t6675b.check37
-rw-r--r--test/files/neg/t6675b.flags1
-rw-r--r--test/files/neg/t6675b.scala40
-rw-r--r--test/files/neg/t7214neg.check7
-rw-r--r--test/files/neg/t7897.check4
-rw-r--r--test/files/neg/t7897.scala23
-rw-r--r--test/files/neg/t997.check7
-rw-r--r--test/files/run/name-based-patmat.check2
-rw-r--r--test/files/run/name-based-patmat.scala42
-rw-r--r--test/files/run/patmat-mix-case-extractor.check8
-rw-r--r--test/files/run/patmat-mix-case-extractor.scala110
23 files changed, 723 insertions, 303 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index 844774e75f..ef50ae276f 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -457,12 +457,11 @@ abstract class UnCurry extends InfoTransform
else
super.transform(tree)
case UnApply(fn, args) =>
- val fn1 = transform(fn)
- val args1 = transformTrees(fn.symbol.name match {
- case nme.unapply => args
- case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, localTyper.expectedPatternTypes(fn, args))
- case _ => sys.error("internal error: UnApply node has wrong symbol")
- })
+ val fn1 = transform(fn)
+ val args1 = fn.symbol.name match {
+ case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, patmat.alignPatterns(tree).expectedTypes)
+ case _ => args
+ }
treeCopy.UnApply(tree, fn1, args1)
case Apply(fn, args) =>
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala
index 63f4a4bf25..35564560b4 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala
@@ -31,6 +31,30 @@ trait MatchTranslation {
trait MatchTranslator extends TreeMakers with TreeMakerWarnings {
import typer.context
+ /** A conservative approximation of which patterns do not discern anything.
+ * They are discarded during the translation.
+ */
+ object WildcardPattern {
+ def unapply(pat: Tree): Boolean = pat match {
+ case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol!
+ case Star(WildcardPattern()) => true
+ case x: Ident => treeInfo.isVarPattern(x)
+ case Alternative(ps) => ps forall unapply
+ case EmptyTree => true
+ case _ => false
+ }
+ }
+
+ object PatternBoundToUnderscore {
+ def unapply(pat: Tree): Boolean = pat match {
+ case Bind(nme.WILDCARD, _) => true // don't skip when binding an interesting symbol!
+ case Ident(nme.WILDCARD) => true
+ case Alternative(ps) => ps forall unapply
+ case Typed(PatternBoundToUnderscore(), _) => true
+ case _ => false
+ }
+ }
+
object SymbolBound {
def unapply(tree: Tree): Option[(Symbol, Tree)] = tree match {
case Bind(_, expr) if hasSym(tree) => Some(tree.symbol -> expr)
@@ -86,9 +110,10 @@ trait MatchTranslation {
// example check: List[Int] <:< ::[Int]
private def extractorStep(): TranslationStep = {
- import extractor.{ paramType, treeMaker }
- if (!extractor.isTyped)
- ErrorUtils.issueNormalTypeError(tree, "Could not typecheck extractor call: "+ extractor)(context)
+ def paramType = extractor.aligner.wholeType
+ import extractor.treeMaker
+ // if (!extractor.isTyped)
+ // ErrorUtils.issueNormalTypeError(tree, "Could not typecheck extractor call: "+ extractor)(context)
// chain a type-testing extractor before the actual extractor call
// it tests the type, checks the outer pointer and casts to the expected type
@@ -355,36 +380,20 @@ trait MatchTranslation {
object ExtractorCall {
// TODO: check unargs == args
def apply(tree: Tree): ExtractorCall = tree match {
- case UnApply(unfun, args) => new ExtractorCallRegular(unfun, args) // extractor
- case Apply(fun, args) => new ExtractorCallProd(fun, args) // case class
+ case UnApply(unfun, args) => new ExtractorCallRegular(alignPatterns(tree), unfun, args) // extractor
+ case Apply(fun, args) => new ExtractorCallProd(alignPatterns(tree), fun, args) // case class
}
}
- abstract class ExtractorCall {
+ abstract class ExtractorCall(val aligner: PatternAligned) {
+ import aligner._
def fun: Tree
def args: List[Tree]
- val nbSubPats = args.length
- val starLength = if (hasStar) 1 else 0
- val nonStarLength = args.length - starLength
-
- // everything okay, captain?
- def isTyped: Boolean
- def isSeq: Boolean
-
- private def hasStar = nbSubPats > 0 && isStar(args.last)
- private def isNonEmptySeq = nbSubPats > 0 && isSeq
-
- /** This is special cased so that a single pattern will accept any extractor
- * result, even if it's a tuple (SI-6675)
- */
- def isSingle = nbSubPats == 1 && !isSeq
-
- // to which type should the previous binder be casted?
- def paramType : Type
-
- protected def rawSubPatTypes: List[Type]
- protected def resultType: Type
+ // don't go looking for selectors if we only expect one pattern
+ def rawSubPatTypes = aligner.extractedTypes
+ def resultInMonad = if (isBool) UnitTpe else typeOfMemberNamedGet(resultType)
+ def resultType = fun.tpe.finalResultType
/** Create the TreeMaker that embodies this extractor call
*
@@ -407,24 +416,14 @@ trait MatchTranslation {
lazy val ignoredSubPatBinders: Set[Symbol] = subPatBinders zip args collect { case (b, PatternBoundToUnderscore()) => b } toSet
// do repeated-parameter expansion to match up with the expected number of arguments (in casu, subpatterns)
- private def nonStarSubPatTypes = formalTypes(rawInit :+ repeatedType, nonStarLength)
+ private def nonStarSubPatTypes = aligner.typedNonStarPatterns map (_.tpe)
- def subPatTypes: List[Type] = (
- if (rawSubPatTypes.isEmpty || !isSeq) rawSubPatTypes
- else if (hasStar) nonStarSubPatTypes :+ sequenceType
- else nonStarSubPatTypes
- )
-
- private def rawGet = typeOfMemberNamedGetOrSelf(resultType)
- private def rawInit = rawSubPatTypes dropRight 1
- protected def sequenceType = typeOfLastSelectorOrSelf(rawGet)
- protected def elementType = elementTypeOfLastSelectorOrSelf(rawGet)
- protected def repeatedType = scalaRepeatedType(elementType)
+ def subPatTypes: List[Type] = typedPatterns map (_.tpe)
- // rawSubPatTypes.last is the Seq, thus there are `rawSubPatTypes.length - 1` non-seq elements in the tuple
- protected def firstIndexingBinder = rawSubPatTypes.length - 1
- protected def lastIndexingBinder = nbSubPats - 1 - starLength
- protected def expectedLength = lastIndexingBinder - firstIndexingBinder + 1
+ // there are `productArity` non-seq elements in the tuple.
+ protected def firstIndexingBinder = productArity
+ protected def expectedLength = elementArity
+ protected def lastIndexingBinder = totalArity - starArity - 1
private def productElemsToN(binder: Symbol, n: Int): List[Tree] = 1 to n map tupleSel(binder) toList
private def genTake(binder: Symbol, n: Int): List[Tree] = (0 until n).toList map (codegen index seqTree(binder))
@@ -438,12 +437,12 @@ trait MatchTranslation {
// referenced by `binder`
protected def subPatRefsSeq(binder: Symbol): List[Tree] = {
def lastTrees: List[Tree] = (
- if (!hasStar) Nil
+ if (!aligner.isStar) Nil
else if (expectedLength == 0) seqTree(binder) :: Nil
else genDrop(binder, expectedLength)
)
// this error-condition has already been checked by checkStarPatOK:
- // if(isSeq) assert(firstIndexingBinder + nbIndexingIndices + (if(lastIsStar) 1 else 0) == nbSubPats, "(resultInMonad, ts, subPatTypes, subPats)= "+(resultInMonad, ts, subPatTypes, subPats))
+ // if(isSeq) assert(firstIndexingBinder + nbIndexingIndices + (if(lastIsStar) 1 else 0) == totalArity, "(resultInMonad, ts, subPatTypes, subPats)= "+(resultInMonad, ts, subPatTypes, subPats))
// [1] there are `firstIndexingBinder` non-seq tuple elements preceding the Seq
// [2] then we have to index the binder that represents the sequence for the remaining subpatterns, except for...
@@ -457,8 +456,10 @@ trait MatchTranslation {
// the trees that select the subpatterns on the extractor's result, referenced by `binder`
// require (nbSubPats > 0 && (!lastIsStar || isSeq))
- protected def subPatRefs(binder: Symbol): List[Tree] =
- if (isNonEmptySeq) subPatRefsSeq(binder) else productElemsToN(binder, nbSubPats)
+ protected def subPatRefs(binder: Symbol): List[Tree] = (
+ if (totalArity > 0 && isSeq) subPatRefsSeq(binder)
+ else productElemsToN(binder, totalArity)
+ )
private def compareInts(t1: Tree, t2: Tree) =
gen.mkMethodCall(termMember(ScalaPackage, "math"), TermName("signum"), Nil, (t1 INT_- t2) :: Nil)
@@ -478,7 +479,7 @@ trait MatchTranslation {
// when the last subpattern is a wildcard-star the expectedLength is but a lower bound
// (otherwise equality is required)
def compareOp: (Tree, Tree) => Tree =
- if (hasStar) _ INT_>= _
+ if (aligner.isStar) _ INT_>= _
else _ INT_== _
// `if (binder != null && $checkExpectedLength [== | >=] 0) then else zero`
@@ -487,26 +488,14 @@ trait MatchTranslation {
def checkedLength: Option[Int] =
// no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied
- if (!isSeq || expectedLength < starLength) None
+ if (!isSeq || expectedLength < starArity) None
else Some(expectedLength)
}
// TODO: to be called when there's a def unapplyProd(x: T): U
// U must have N members _1,..., _N -- the _i are type checked, call their type Ti,
// for now only used for case classes -- pretending there's an unapplyProd that's the identity (and don't call it)
- class ExtractorCallProd(val fun: Tree, val args: List[Tree]) extends ExtractorCall {
- private def constructorTp = fun.tpe
-
- def isTyped = fun.isTyped
-
- // to which type should the previous binder be casted?
- def paramType = constructorTp.finalResultType
- def resultType = fun.tpe.finalResultType
-
- def isSeq = isVarArgTypes(rawSubPatTypes)
-
- protected def rawSubPatTypes = constructorTp.paramTypes
-
+ class ExtractorCallProd(aligner: PatternAligned, val fun: Tree, val args: List[Tree]) extends ExtractorCall(aligner) {
/** Create the TreeMaker that embodies this extractor call
*
* `binder` has been casted to `paramType` if necessary
@@ -535,20 +524,11 @@ trait MatchTranslation {
if (accessors isDefinedAt (i-1)) REF(binder) DOT accessors(i-1)
else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN
}
-
- override def toString() = s"ExtractorCallProd($fun:${fun.tpe} / ${fun.symbol} / args=$args)"
}
- class ExtractorCallRegular(extractorCallIncludingDummy: Tree, val args: List[Tree]) extends ExtractorCall {
+ class ExtractorCallRegular(aligner: PatternAligned, extractorCallIncludingDummy: Tree, val args: List[Tree]) extends ExtractorCall(aligner) {
val Unapplied(fun) = extractorCallIncludingDummy
- def tpe = fun.tpe
- def paramType = firstParamType(tpe)
- def resultType = tpe.finalResultType
- def isTyped = (tpe ne NoType) && fun.isTyped && (resultInMonad ne ErrorType)
- def isSeq = fun.symbol.name == nme.unapplySeq
- def isBool = resultType =:= BooleanTpe
-
/** Create the TreeMaker that embodies this extractor call
*
* `binder` has been casted to `paramType` if necessary
@@ -571,7 +551,7 @@ trait MatchTranslation {
ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)(
subPatBinders,
subPatRefs(binder),
- isBool,
+ aligner.isBool,
checkedLength,
patBinderOrCasted,
ignoredSubPatBinders
@@ -583,9 +563,9 @@ trait MatchTranslation {
else super.seqTree(binder)
// the trees that select the subpatterns on the extractor's result, referenced by `binder`
- // require (nbSubPats > 0 && (!lastIsStar || isSeq))
+ // require (totalArity > 0 && (!lastIsStar || isSeq))
override protected def subPatRefs(binder: Symbol): List[Tree] =
- if (isSingle) REF(binder) :: Nil // special case for extractors
+ if (aligner.isSingle) REF(binder) :: Nil // special case for extractors
else super.subPatRefs(binder)
protected def spliceApply(binder: Symbol): Tree = {
@@ -606,40 +586,7 @@ trait MatchTranslation {
splice transform extractorCallIncludingDummy
}
- // what's the extractor's result type in the monad? It is the type of its nullary member `get`.
- protected lazy val resultInMonad: Type = if (isBool) UnitTpe else typeOfMemberNamedGet(resultType)
-
- protected lazy val rawSubPatTypes = (
- if (isBool) Nil
- else if (isSingle) resultInMonad :: Nil // don't go looking for selectors if we only expect one pattern
- else typesOfSelectorsOrSelf(resultInMonad)
- )
-
- override def toString() = s"ExtractorCallRegular($fun: $tpe / ${fun.symbol})"
- }
-
- /** A conservative approximation of which patterns do not discern anything.
- * They are discarded during the translation.
- */
- object WildcardPattern {
- def unapply(pat: Tree): Boolean = pat match {
- case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol!
- case Star(WildcardPattern()) => true
- case x: Ident => treeInfo.isVarPattern(x)
- case Alternative(ps) => ps forall unapply
- case EmptyTree => true
- case _ => false
- }
- }
-
- object PatternBoundToUnderscore {
- def unapply(pat: Tree): Boolean = pat match {
- case Bind(nme.WILDCARD, _) => true // don't skip when binding an interesting symbol!
- case Ident(nme.WILDCARD) => true
- case Alternative(ps) => ps forall unapply
- case Typed(PatternBoundToUnderscore(), _) => true
- case _ => false
- }
+ override def rawSubPatTypes = aligner.extractor.varargsTypes
}
}
}
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
index 7df03044aa..a80f158949 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
@@ -395,8 +395,10 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
debug.patmat("TTTM"+((prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp)))
lazy val outerTestNeeded = (
- !((expectedTp.prefix eq NoPrefix) || expectedTp.prefix.typeSymbol.isPackageClass)
- && needsOuterTest(expectedTp, testedBinder.info, matchOwner))
+ (expectedTp.prefix ne NoPrefix)
+ && !expectedTp.prefix.typeSymbol.isPackageClass
+ && needsOuterTest(expectedTp, testedBinder.info, matchOwner)
+ )
// the logic to generate the run-time test that follows from the fact that
// a `prevBinder` is expected to have type `expectedTp`
@@ -406,44 +408,52 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
def renderCondition(cs: TypeTestCondStrategy): cs.Result = {
import cs._
- def default =
- // do type test first to ensure we won't select outer on null
- if (outerTestNeeded) and(typeTest(testedBinder, expectedTp), outerTest(testedBinder, expectedTp))
- else typeTest(testedBinder, expectedTp)
-
// propagate expected type
def expTp(t: Tree): t.type = t setType expectedTp
+ def testedWide = testedBinder.info.widen
+ def expectedWide = expectedTp.widen
+ def isAnyRef = testedWide <:< AnyRefTpe
+ def isAsExpected = testedWide <:< expectedTp
+ def isExpectedPrimitiveType = isAsExpected && isPrimitiveValueType(expectedTp)
+ def isExpectedReferenceType = isAsExpected && (expectedTp <:< AnyRefTpe)
+ def mkNullTest = nonNullTest(testedBinder)
+ def mkOuterTest = outerTest(testedBinder, expectedTp)
+ def mkTypeTest = typeTest(testedBinder, expectedWide)
+
+ def mkEqualsTest(lhs: Tree): cs.Result = equalsTest(lhs, testedBinder)
+ def mkEqTest(lhs: Tree): cs.Result = eqTest(lhs, testedBinder)
+ def addOuterTest(res: cs.Result): cs.Result = if (outerTestNeeded) and(res, mkOuterTest) else res
+
+ // If we conform to expected primitive type:
+ // it cannot be null and cannot have an outer pointer. No further checking.
+ // If we conform to expected reference type:
+ // have to test outer and non-null
+ // If we do not conform to expected type:
+ // have to test type and outer (non-null is implied by successful type test)
+ def mkDefault = (
+ if (isExpectedPrimitiveType) tru
+ else addOuterTest(
+ if (isExpectedReferenceType) mkNullTest
+ else mkTypeTest
+ )
+ )
+
// true when called to type-test the argument to an extractor
// don't do any fancy equality checking, just test the type
- if (extractorArgTypeTest) default
+ // TODO: verify that we don't need to special-case Array
+ // I think it's okay:
+ // - the isInstanceOf test includes a test for the element type
+ // - Scala's arrays are invariant (so we don't drop type tests unsoundly)
+ if (extractorArgTypeTest) mkDefault
else expectedTp match {
- // TODO: [SPEC] the spec requires `eq` instead of `==` for singleton types
- // this implies sym.isStable
- case SingleType(_, sym) => and(equalsTest(gen.mkAttributedQualifier(expectedTp), testedBinder), typeTest(testedBinder, expectedTp.widen))
- // must use == to support e.g. List() == Nil
- case ThisType(sym) if sym.isModule => and(equalsTest(CODE.REF(sym), testedBinder), typeTest(testedBinder, expectedTp.widen))
- case ConstantType(Constant(null)) if testedBinder.info.widen <:< AnyRefTpe
- => eqTest(expTp(CODE.NULL), testedBinder)
- case ConstantType(const) => equalsTest(expTp(Literal(const)), testedBinder)
- case ThisType(sym) => eqTest(expTp(This(sym)), testedBinder)
-
- // TODO: verify that we don't need to special-case Array
- // I think it's okay:
- // - the isInstanceOf test includes a test for the element type
- // - Scala's arrays are invariant (so we don't drop type tests unsoundly)
- case _ if testedBinder.info.widen <:< expectedTp =>
- // if the expected type is a primitive value type, it cannot be null and it cannot have an outer pointer
- // since the types conform, no further checking is required
- if (isPrimitiveValueType(expectedTp)) tru
- // have to test outer and non-null only when it's a reference type
- else if (expectedTp <:< AnyRefTpe) {
- // do non-null check first to ensure we won't select outer on null
- if (outerTestNeeded) and(nonNullTest(testedBinder), outerTest(testedBinder, expectedTp))
- else nonNullTest(testedBinder)
- } else default
-
- case _ => default
+ // TODO: [SPEC] the spec requires `eq` instead of `==` for singleton types - this implies sym.isStable
+ case SingleType(_, sym) => and(mkEqualsTest(gen.mkAttributedQualifier(expectedTp)), mkTypeTest)
+ case ThisType(sym) if sym.isModule => and(mkEqualsTest(CODE.REF(sym)), mkTypeTest) // must use == to support e.g. List() == Nil
+ case ConstantType(Constant(null)) if isAnyRef => mkEqTest(expTp(CODE.NULL))
+ case ConstantType(const) => mkEqualsTest(expTp(Literal(const)))
+ case ThisType(sym) => mkEqTest(expTp(This(sym)))
+ case _ => mkDefault
}
}
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternExpander.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternExpander.scala
new file mode 100644
index 0000000000..5cb4aff272
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternExpander.scala
@@ -0,0 +1,155 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2013 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala
+package tools
+package nsc
+package transform
+package patmat
+
+/** An extractor returns: F1, F2, ..., Fi, opt[Seq[E] or E*]
+ * A case matches: P1, P2, ..., Pj, opt[Seq[E]]
+ * Put together: P1/F1, P2/F2, ... Pi/Fi, Pi+1/E, Pi+2/E, ... Pj/E, opt[Seq[E]]
+ *
+ * Here Pm/Fi is the last pattern to match the fixed arity section.
+ *
+ * productArity: the value of i, i.e. the number of non-sequence types in the extractor
+ * nonStarArity: the value of j, i.e. the number of non-star patterns in the case definition
+ * elementArity: j - i, i.e. the number of non-star patterns which must match sequence elements
+ * starArity: 1 or 0 based on whether there is a star (sequence-absorbing) pattern
+ * totalArity: nonStarArity + starArity, i.e. the number of patterns in the case definition
+ *
+ * Note that productArity is a function only of the extractor, and
+ * nonStar/star/totalArity are all functions of the patterns. The key
+ * value for aligning and typing the patterns is elementArity, as it
+ * is derived from both sets of information.
+ */
+trait PatternExpander[Pattern, Type] {
+ /** You'll note we're not inside the cake. "Pattern" and "Type" are
+ * arbitrary types here, and NoPattern and NoType arbitrary values.
+ */
+ def NoPattern: Pattern
+ def NoType: Type
+
+ /** It's not optimal that we're carrying both sequence and repeated
+ * type here, but the implementation requires more unraveling before
+ * it can be avoided.
+ *
+ * sequenceType is Seq[T], elementType is T, repeatedType is T*.
+ */
+ case class Repeated(sequenceType: Type, elementType: Type, repeatedType: Type) {
+ def exists = elementType != NoType
+
+ def elementList = if (exists) elementType :: Nil else Nil
+ def sequenceList = if (exists) sequenceType :: Nil else Nil
+ def repeatedList = if (exists) repeatedType :: Nil else Nil
+
+ override def toString = s"${elementType}*"
+ }
+ object NoRepeated extends Repeated(NoType, NoType, NoType) {
+ override def toString = "<none>"
+ }
+
+ case class Patterns(fixed: List[Pattern], star: Pattern) {
+ def hasStar = star != NoPattern
+ def starArity = if (hasStar) 1 else 0
+ def nonStarArity = fixed.length
+ def totalArity = nonStarArity + starArity
+ def starPatterns = if (hasStar) star :: Nil else Nil
+ def all = fixed ::: starPatterns
+
+ override def toString = all mkString ", "
+ }
+
+ /** An 'extractor' can be a case class or an unapply or unapplySeq method.
+ * Decoding what it is that they extract takes place before we arrive here,
+ * so that this class can concentrate only on the relationship between
+ * patterns and types.
+ *
+ * In a case class, the class is the unextracted type and the fixed and
+ * repeated types are derived from its constructor parameters.
+ *
+ * In an unapply, this is reversed: the parameter to the unapply is the
+ * unextracted type, and the other types are derived based on the return
+ * type of the unapply method.
+ *
+ * In other words, this case class and unapply are encoded the same:
+ *
+ * case class Foo(x: Int, y: Int, zs: Char*)
+ * def unapplySeq(x: Foo): Option[(Int, Int, Seq[Char])]
+ *
+ * Both are Extractor(Foo, Int :: Int :: Nil, Repeated(Seq[Char], Char, Char*))
+ *
+ * @param whole The type in its unextracted form
+ * @param fixed The non-sequence types which are extracted
+ * @param repeated The sequence type which is extracted
+ */
+ case class Extractor(whole: Type, fixed: List[Type], repeated: Repeated) {
+ require(whole != NoType, s"expandTypes($whole, $fixed, $repeated)")
+
+ def productArity = fixed.length
+ def hasSeq = repeated.exists
+ def elementType = repeated.elementType
+ def sequenceType = repeated.sequenceType
+ def allTypes = fixed ::: repeated.sequenceList
+ def varargsTypes = fixed ::: repeated.repeatedList
+ def isErroneous = allTypes contains NoType
+
+ private def typeStrings = fixed.map("" + _) ::: ( if (hasSeq) List("" + repeated) else Nil )
+
+ def offeringString = if (isErroneous) "<error>" else typeStrings match {
+ case Nil => "Boolean"
+ case tp :: Nil => tp
+ case tps => tps.mkString("(", ", ", ")")
+ }
+ override def toString = "%s => %s".format(whole, offeringString)
+ }
+
+ case class TypedPat(pat: Pattern, tpe: Type) {
+ override def toString = s"$pat: $tpe"
+ }
+
+ /** If elementArity is...
+ * 0: A perfect match between extractor and the fixed patterns.
+ * If there is a star pattern it will match any sequence.
+ * > 0: There are more patterns than products. There will have to be a
+ * sequence which can populate at least <elementArity> patterns.
+ * < 0: There are more products than patterns: compile time error.
+ */
+ case class Aligned(patterns: Patterns, extractor: Extractor) {
+ def elementArity = patterns.nonStarArity - productArity
+ def productArity = extractor.productArity
+ def starArity = patterns.starArity
+ def totalArity = patterns.totalArity
+
+ def wholeType = extractor.whole
+ def sequenceType = extractor.sequenceType
+ def productTypes = extractor.fixed
+ def extractedTypes = extractor.allTypes
+ def typedNonStarPatterns = products ::: elements
+ def typedPatterns = typedNonStarPatterns ::: stars
+
+ def isBool = !isSeq && productArity == 0
+ def isSingle = !isSeq && totalArity == 1
+ def isStar = patterns.hasStar
+ def isSeq = extractor.hasSeq
+
+ private def typedAsElement(pat: Pattern) = TypedPat(pat, extractor.elementType)
+ private def typedAsSequence(pat: Pattern) = TypedPat(pat, extractor.sequenceType)
+ private def productPats = patterns.fixed take productArity
+ private def elementPats = patterns.fixed drop productArity
+ private def products = (productPats, productTypes).zipped map TypedPat
+ private def elements = elementPats map typedAsElement
+ private def stars = patterns.starPatterns map typedAsSequence
+
+ override def toString = s"""
+ |Aligned {
+ | patterns $patterns
+ | extractor $extractor
+ | arities $productArity/$elementArity/$starArity // product/element/star
+ | typed ${typedPatterns mkString ", "}
+ |}""".stripMargin.trim
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
index 394ba98f17..f6c960d089 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
@@ -34,7 +34,8 @@ import scala.reflect.internal.util.Position
* - recover GADT typing by locally inserting implicit witnesses to type equalities derived from the current case, and considering these witnesses during subtyping (?)
* - recover exhaustivity/unreachability of user-defined extractors by partitioning the types they match on using an HList or similar type-level structure
*/
-trait PatternMatching extends Transform with TypingTransformers
+trait PatternMatching extends Transform
+ with TypingTransformers
with Debugging
with Interface
with MatchTranslation
@@ -45,7 +46,8 @@ trait PatternMatching extends Transform with TypingTransformers
with Solving
with MatchAnalysis
with MatchOptimization
- with MatchWarnings {
+ with MatchWarnings
+ with ScalacPatternExpanders {
import global._
val phaseName: String = "patmat"
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala
new file mode 100644
index 0000000000..7858cb5586
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/transform/patmat/ScalacPatternExpanders.scala
@@ -0,0 +1,154 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2013 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala
+package tools
+package nsc
+package transform
+package patmat
+
+/** This is scalac-specific logic layered on top of the scalac-agnostic
+ * "matching products to patterns" logic defined in PatternExpander.
+ */
+trait ScalacPatternExpanders {
+ val global: Global
+
+ import global._
+ import definitions._
+ import treeInfo._
+
+ type PatternAligned = ScalacPatternExpander#Aligned
+
+ implicit class AlignedOps(val aligned: PatternAligned) {
+ import aligned._
+ def expectedTypes = typedPatterns map (_.tpe)
+ def unexpandedFormals = extractor.varargsTypes
+ }
+ trait ScalacPatternExpander extends PatternExpander[Tree, Type] {
+ def NoPattern = EmptyTree
+ def NoType = global.NoType
+
+ def newPatterns(patterns: List[Tree]): Patterns = patterns match {
+ case init :+ last if isStar(last) => Patterns(init, last)
+ case _ => Patterns(patterns, NoPattern)
+ }
+ def elementTypeOf(tpe: Type) = {
+ val seq = repeatedToSeq(tpe)
+
+ ( typeOfMemberNamedHead(seq)
+ orElse typeOfMemberNamedApply(seq)
+ orElse definitions.elementType(ArrayClass, seq)
+ )
+ }
+ def newExtractor(whole: Type, fixed: List[Type], repeated: Repeated): Extractor =
+ logResult(s"newExtractor($whole, $fixed, $repeated")(Extractor(whole, fixed, repeated))
+
+ // Turn Seq[A] into Repeated(Seq[A], A, A*)
+ def repeatedFromSeq(seqType: Type): Repeated = {
+ val elem = elementTypeOf(seqType)
+ val repeated = scalaRepeatedType(elem)
+
+ Repeated(seqType, elem, repeated)
+ }
+ // Turn A* into Repeated(Seq[A], A, A*)
+ def repeatedFromVarargs(repeated: Type): Repeated =
+ Repeated(repeatedToSeq(repeated), repeatedToSingle(repeated), repeated)
+
+ /** In this case we are basing the pattern expansion on a case class constructor.
+ * The argument is the MethodType carried by the primary constructor.
+ */
+ def applyMethodTypes(method: Type): Extractor = {
+ val whole = method.finalResultType
+
+ method.paramTypes match {
+ case init :+ last if isScalaRepeatedParamType(last) => newExtractor(whole, init, repeatedFromVarargs(last))
+ case tps => newExtractor(whole, tps, NoRepeated)
+ }
+ }
+
+ /** In this case, expansion is based on an unapply or unapplySeq method.
+ * Unfortunately the MethodType does not carry the information of whether
+ * it was unapplySeq, so we have to funnel that information in separately.
+ */
+ def unapplyMethodTypes(method: Type, isSeq: Boolean): Extractor = {
+ val whole = firstParamType(method)
+ val result = method.finalResultType
+ val expanded = (
+ if (result =:= BooleanTpe) Nil
+ else typeOfMemberNamedGet(result) match {
+ case rawGet if !hasSelectors(rawGet) => rawGet :: Nil
+ case rawGet => typesOfSelectors(rawGet)
+ }
+ )
+ expanded match {
+ case init :+ last if isSeq => newExtractor(whole, init, repeatedFromSeq(last))
+ case tps => newExtractor(whole, tps, NoRepeated)
+ }
+ }
+ }
+ object alignPatterns extends ScalacPatternExpander {
+ /** Converts a T => (A, B, C) extractor to a T => ((A, B, CC)) extractor.
+ */
+ def tupleExtractor(extractor: Extractor): Extractor =
+ extractor.copy(fixed = tupleType(extractor.fixed) :: Nil)
+
+ private def validateAligned(tree: Tree, aligned: Aligned): Aligned = {
+ import aligned._
+
+ def owner = tree.symbol.owner
+ def offering = extractor.offeringString
+ def symString = tree.symbol.fullLocationString
+ def offerString = if (extractor.isErroneous) "" else s" offering $offering"
+ def arityExpected = ( if (extractor.hasSeq) "at least " else "" ) + productArity
+
+ def err(msg: String) = currentUnit.error(tree.pos, msg)
+ def warn(msg: String) = currentUnit.warning(tree.pos, msg)
+ def arityError(what: String) = err(s"$what patterns for $owner$offerString: expected $arityExpected, found $totalArity")
+
+ if (isStar && !isSeq)
+ err("Star pattern must correspond with varargs or unapplySeq")
+ else if (elementArity < 0)
+ arityError("not enough")
+ else if (elementArity > 0 && !extractor.hasSeq)
+ arityError("too many")
+
+ aligned
+ }
+
+ def apply(sel: Tree, args: List[Tree]): Aligned = {
+ val fn = sel match {
+ case Unapplied(fn) => fn
+ case _ => sel
+ }
+ val patterns = newPatterns(args)
+ val isSeq = sel.symbol.name == nme.unapplySeq
+ val isUnapply = sel.symbol.name == nme.unapply
+ val extractor = sel.symbol.name match {
+ case nme.unapply => unapplyMethodTypes(fn.tpe, isSeq = false)
+ case nme.unapplySeq => unapplyMethodTypes(fn.tpe, isSeq = true)
+ case _ => applyMethodTypes(fn.tpe)
+ }
+
+ /** Rather than let the error that is SI-6675 pollute the entire matching
+ * process, we will tuple the extractor before creation Aligned so that
+ * it contains known good values.
+ */
+ def productArity = extractor.productArity
+ def acceptMessage = if (extractor.isErroneous) "" else s" to hold ${extractor.offeringString}"
+ val requiresTupling = isUnapply && patterns.totalArity == 1 && productArity > 1
+
+ if (settings.lint && requiresTupling && effectivePatternArity(args) == 1)
+ currentUnit.warning(sel.pos, s"${sel.symbol.owner} expects $productArity patterns$acceptMessage but crushing into $productArity-tuple to fit single pattern (SI-6675)")
+
+ val normalizedExtractor = if (requiresTupling) tupleExtractor(extractor) else extractor
+ validateAligned(fn, Aligned(patterns, normalizedExtractor))
+ }
+
+ def apply(tree: Tree): Aligned = tree match {
+ case Apply(fn, args) => apply(fn, args)
+ case UnApply(fn, args) => apply(fn, args)
+ }
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala
index 069d6d5fb2..1fef6bd66b 100644
--- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala
@@ -83,7 +83,7 @@ trait PatternTypers {
// Dueling test cases: pos/overloaded-unapply.scala, run/case-class-23.scala, pos/t5022.scala
// A case class with 23+ params has no unapply method.
- // A case class constructor be overloaded with unapply methods in the companion.
+ // A case class constructor may be overloaded with unapply methods in the companion.
if (caseClass.isCase && !unapplyMember(fun.tpe).isOverloaded)
logResult(s"convertToCaseConstructor($fun, $caseClass, pt=$pt)")(convertToCaseConstructor(fun, caseClass, pt))
else if (hasUnapplyMember(fun))
@@ -92,12 +92,6 @@ trait PatternTypers {
CaseClassConstructorError(fun)
}
- def expectedPatternTypes(fun: Tree, args: List[Tree]): List[Type] =
- newExtractorShape(fun, args).expectedPatternTypes
-
- def typedPatternArgs(fun: Tree, args: List[Tree], mode: Mode): List[Tree] =
- typedArgsForFormals(args, newExtractorShape(fun, args).formals, mode)
-
def typedArgsForFormals(args: List[Tree], formals: List[Type], mode: Mode): List[Tree] = {
def typedArgWithFormal(arg: Tree, pt: Type) = {
val newMode = if (isByNameParamType(pt)) mode.onlySticky else mode.onlySticky | BYVALmode
@@ -158,109 +152,6 @@ trait PatternTypers {
case _ => wrapClassTagUnapply(treeTyped, extractor, tpe)
}
}
-
- def newExtractorShape(fun: Tree, args: List[Tree]): ExtractorShape = ExtractorShape(fun, args)
-
- case class CaseClassInfo(clazz: Symbol, classType: Type) {
- def constructor = clazz.primaryConstructor
- def constructorType = classType.prefix memberType clazz memberType constructor
- def accessors = clazz.caseFieldAccessors
- }
- object NoCaseClassInfo extends CaseClassInfo(NoSymbol, NoType) {
- override def toString = "NoCaseClassInfo"
- }
-
- case class UnapplyMethodInfo(unapply: Symbol, tpe: Type) {
- def name = unapply.name
- def isUnapplySeq = name == nme.unapplySeq
- def unapplyType = tpe memberType method
- def resultType = tpe.finalResultType
- def method = unapplyMember(tpe)
- def paramType = firstParamType(unapplyType)
- def rawGet = if (isBool) UnitTpe else typeOfMemberNamedGetOrSelf(resultType)
- def rawTypes = if (isBool) Nil else typesOfSelectorsOrSelf(rawGet)
- def isBool = resultType =:= BooleanTpe // aka "Tuple0" or "Option[Unit]"
- }
-
- object NoUnapplyMethodInfo extends UnapplyMethodInfo(NoSymbol, NoType) {
- override def toString = "NoUnapplyMethodInfo"
- }
-
- case class ExtractorShape(fun: Tree, args: List[Tree]) {
- def pos = fun.pos
- private def symbol = fun.symbol
- private def tpe = fun.tpe
-
- val ccInfo = tpe.typeSymbol.linkedClassOfClass match {
- case clazz if clazz.isCase => CaseClassInfo(clazz, tpe)
- case _ => NoCaseClassInfo
- }
- val exInfo = UnapplyMethodInfo(symbol, tpe)
- import exInfo.{ rawGet, rawTypes, isUnapplySeq }
-
- override def toString = s"ExtractorShape($fun, $args)"
-
- def unapplyMethod = exInfo.method
- def unapplyType = exInfo.unapplyType
- def unapplyParamType = exInfo.paramType
- def enclClass = symbol.enclClass
-
- // TODO - merge these. The difference between these two methods is that expectedPatternTypes
- // expands the list of types so it is the same length as the number of patterns, whereas formals
- // leaves the varargs type unexpanded.
- def formals = (
- if (isUnapplySeq) productTypes :+ varargsType
- else if (elementArity == 0) productTypes
- else if (isSingle) squishIntoOne()
- else wrongArity(patternFixedArity)
- )
- def expectedPatternTypes = elementArity match {
- case 0 => productTypes
- case _ if elementArity > 0 && isUnapplySeq => productTypes ::: elementTypes
- case _ if productArity > 1 && patternFixedArity == 1 => squishIntoOne()
- case _ => wrongArity(patternFixedArity)
- }
-
- def elementType = elementTypeOfLastSelectorOrSelf(rawGet)
-
- private def hasBogusExtractor = directUnapplyMember(tpe).exists && !unapplyMethod.exists
- private def expectedArity = "" + productArity + ( if (isUnapplySeq) "+" else "")
- private def wrongArityMsg(n: Int) = (
- if (hasBogusExtractor) s"$enclClass does not define a valid extractor method"
- else s"wrong number of patterns for $enclClass offering $rawTypes_s: expected $expectedArity, found $n"
- )
- private def rawTypes_s = rawTypes match {
- case Nil => "()"
- case tp :: Nil => "" + tp
- case tps => tps.mkString("(", ", ", ")")
- }
-
- private def err(msg: String) = { unit.error(pos, msg) ; throw new TypeError(msg) }
- private def wrongArity(n: Int) = err(wrongArityMsg(n))
-
- def squishIntoOne() = {
- if (settings.lint)
- unit.warning(pos, s"$enclClass expects $expectedArity patterns to hold $rawGet but crushing into $productArity-tuple to fit single pattern (SI-6675)")
-
- rawGet :: Nil
- }
- // elementArity is the number of non-sequence patterns minus the
- // the number of non-sequence product elements returned by the extractor.
- // If it is zero, there is a perfect match between those parts, and
- // if there is a wildcard star it will match any sequence.
- // If it is positive, there are more patterns than products,
- // so a sequence will have to fill in the elements. If it is negative,
- // there are more products than patterns, which is a compile time error.
- def elementArity = patternFixedArity - productArity
- def patternFixedArity = treeInfo effectivePatternArity args
- def productArity = productTypes.size
- def isSingle = !isUnapplySeq && (patternFixedArity == 1)
-
- def productTypes = if (isUnapplySeq) rawTypes dropRight 1 else rawTypes
- def elementTypes = List.fill(elementArity)(elementType)
- def varargsType = scalaRepeatedType(elementType)
- }
-
private class VariantToSkolemMap extends TypeMap(trackVariance = true) {
private val skolemBuffer = mutable.ListBuffer[TypeSymbol]()
@@ -365,10 +256,12 @@ trait PatternTypers {
case OverloadedType(_, _) => OverloadedUnapplyError(fun) ; ErrorType
case _ => UnapplyWithSingleArgError(fun) ; ErrorType
}
- val shape = newExtractorShape(fun, args)
- import shape.{ unapplyParamType, unapplyType, unapplyMethod }
+ val unapplyMethod = unapplyMember(fun.tpe)
+ val unapplyType = fun.tpe memberType unapplyMethod
+ val unapplyParamType = firstParamType(unapplyType)
+ def isSeq = unapplyMethod.name == nme.unapplySeq
- def extractor = extractorForUncheckedType(shape.pos, unapplyParamType)
+ def extractor = extractorForUncheckedType(fun.pos, unapplyParamType)
def canRemedy = unapplyParamType match {
case RefinedType(_, decls) if !decls.isEmpty => false
case RefinedType(parents, _) if parents exists isUncheckable => false
@@ -400,7 +293,8 @@ trait PatternTypers {
// the union of the expected type and the inferred type of the argument to unapply
val glbType = glb(ensureFullyDefined(pt) :: unapplyArg.tpe_* :: Nil)
val wrapInTypeTest = canRemedy && !(fun1.symbol.owner isNonBottomSubClass ClassTagClass)
- val args1 = typedPatternArgs(fun1, args, mode)
+ val formals = patmat.alignPatterns(fun1, args).unexpandedFormals
+ val args1 = typedArgsForFormals(args, formals, mode)
val result = UnApply(fun1, args1) setPos tree.pos setType glbType
if (wrapInTypeTest)
diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
index ed96f66ab8..6557a9803a 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
@@ -39,6 +39,8 @@ trait Unapplies extends ast.TreeDSL {
*/
def unapplyMember(tp: Type): Symbol = directUnapplyMember(tp) filter (sym => !hasMultipleNonImplicitParamLists(sym))
+ def unapplyMemberType(tree: Tree): Type = tree.tpe memberType unapplyMember(tree.tpe)
+
object HasUnapply {
def unapply(tp: Type): Option[Symbol] = unapplyMember(tp).toOption
}
diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala
index 1fe6f249b8..f784d6a772 100644
--- a/src/reflect/scala/reflect/internal/Definitions.scala
+++ b/src/reflect/scala/reflect/internal/Definitions.scala
@@ -812,6 +812,7 @@ trait Definitions extends api.StandardDefinitions {
def byNameType(arg: Type) = appliedType(ByNameParamClass, arg)
def iteratorOfType(tp: Type) = appliedType(IteratorClass, tp)
def javaRepeatedType(arg: Type) = appliedType(JavaRepeatedParamClass, arg)
+ def optionType(tp: Type) = appliedType(OptionClass, tp)
def scalaRepeatedType(arg: Type) = appliedType(RepeatedParamClass, arg)
def seqType(arg: Type) = appliedType(SeqClass, arg)
@@ -826,6 +827,12 @@ trait Definitions extends api.StandardDefinitions {
def typesOfSelectors(tp: Type) = getterMemberTypes(tp, productSelectors(tp))
def typesOfCaseAccessors(tp: Type) = getterMemberTypes(tp, tp.typeSymbol.caseFieldAccessors)
+ // Can't only check for _1 thanks to pos/t796.
+ def hasSelectors(tp: Type) = (
+ (tp.members containsName nme._1)
+ && (tp.members containsName nme._2)
+ )
+
/** If this is a case class, the case field accessors (which may be an empty list.)
* Otherwise, if there are any product selectors, that list.
* Otherwise, a list containing only the type itself.
diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala
index 8fdf4dc27a..06c7e32378 100644
--- a/src/reflect/scala/reflect/internal/TreeInfo.scala
+++ b/src/reflect/scala/reflect/internal/TreeInfo.scala
@@ -611,11 +611,24 @@ abstract class TreeInfo {
*/
def effectivePatternArity(args: List[Tree]): Int = flattenedPatternArgs(args).length
+ /** Not enough to check the symbol because we need this information
+ * early to type the trees. ParserTuple checks for the syntactic form
+ * as generated by the parser.
+ */
def flattenedPatternArgs(args: List[Tree]): List[Tree] = args map unbind match {
case Apply(fun, xs) :: Nil if isTupleSymbol(fun.symbol) => xs
+ case ParserTuple(xs) :: Nil => xs
case xs => xs
}
+ object ParserTuple {
+ private val TupleName = """^Tuple(\d+)$""".r
+ def unapply(t: Tree): Option[List[Tree]] = t match {
+ case Apply(Select(Ident(nme.scala_), TermName(TupleName(n))), args) if n.toInt == args.length => Some((args))
+ case _ => None
+ }
+ }
+
// used in the symbols for labeldefs and valdefs emitted by the pattern matcher
// tailcalls, cps,... use this flag combination to detect translated matches
// TODO: move to Flags
diff --git a/test/files/neg/t4425b.check b/test/files/neg/t4425b.check
index 1186e8b609..ae7b469d52 100644
--- a/test/files/neg/t4425b.check
+++ b/test/files/neg/t4425b.check
@@ -22,40 +22,28 @@ t4425b.scala:10: error: object X is not a case class constructor, nor does it ha
Note: def unapply(x: String)(y: String): Nothing exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list
println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" })
^
-t4425b.scala:18: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
+t4425b.scala:18: error: too many patterns for object X: expected 1, found 2
println( "" match { case _ X _ => "ok" ; case _ => "fail" })
^
-t4425b.scala:19: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
+t4425b.scala:19: error: too many patterns for object X: expected 1, found 2
println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" })
^
-t4425b.scala:22: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
+t4425b.scala:22: error: too many patterns for object X: expected 1, found 2
println( "" match { case X(_, _) => "ok" ; case _ => "fail" })
^
-t4425b.scala:22: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
- println( "" match { case X(_, _) => "ok" ; case _ => "fail" })
- ^
-t4425b.scala:23: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
+t4425b.scala:23: error: too many patterns for object X: expected 1, found 2
println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" })
^
-t4425b.scala:23: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
- println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" })
- ^
-t4425b.scala:31: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
+t4425b.scala:31: error: too many patterns for object X offering Nothing: expected 1, found 2
println( "" match { case _ X _ => "ok" ; case _ => "fail" })
^
-t4425b.scala:32: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
+t4425b.scala:32: error: too many patterns for object X offering Nothing: expected 1, found 2
println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" })
^
-t4425b.scala:35: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
+t4425b.scala:35: error: too many patterns for object X offering Nothing: expected 1, found 2
println( "" match { case X(_, _) => "ok" ; case _ => "fail" })
^
-t4425b.scala:35: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
- println( "" match { case X(_, _) => "ok" ; case _ => "fail" })
- ^
-t4425b.scala:36: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
+t4425b.scala:36: error: too many patterns for object X offering Nothing: expected 1, found 2
println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" })
^
-t4425b.scala:36: error: wrong number of patterns for object X offering Nothing: expected 1, found 2
- println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" })
- ^
-18 errors found
+14 errors found
diff --git a/test/files/neg/t5903a.check b/test/files/neg/t5903a.check
index 2e5cc87167..34003b0a82 100644
--- a/test/files/neg/t5903a.check
+++ b/test/files/neg/t5903a.check
@@ -1,4 +1,4 @@
-Test_2.scala:4: error: wrong number of patterns for <$anon: AnyRef> offering (SomeTree.type, SomeTree.type): expected 2, found 3
+Test_2.scala:4: error: too many patterns for <$anon: AnyRef> offering (SomeTree.type, SomeTree.type): expected 2, found 3
case nq"$x + $y + $z" => println((x, y))
^
one error found
diff --git a/test/files/neg/t6675b.check b/test/files/neg/t6675b.check
new file mode 100644
index 0000000000..77f6b3ccbc
--- /dev/null
+++ b/test/files/neg/t6675b.check
@@ -0,0 +1,37 @@
+t6675b.scala:17: warning: object LeftOrRight expects 2 patterns to hold (Int, Int) but crushing into 2-tuple to fit single pattern (SI-6675)
+ def f1 = (Left((0, 0)): Either[(Int, Int), (Int, Int)]) match { case LeftOrRight(a) => a } // warn
+ ^
+t6675b.scala:19: error: constructor cannot be instantiated to expected type;
+ found : (T1, T2, T3)
+ required: (Int, Int)
+ def f3 = (Left((0, 0)): Either[(Int, Int), (Int, Int)]) match { case LeftOrRight((a, b, c)) => a } // fail
+ ^
+t6675b.scala:24: warning: object LeftOrRight expects 2 patterns to hold (A, A) but crushing into 2-tuple to fit single pattern (SI-6675)
+ def f2[A](x: A) = (Left(x -> x): Either[(A, A), (A, A)]) match { case LeftOrRight(a) => a } // warn
+ ^
+t6675b.scala:26: error: constructor cannot be instantiated to expected type;
+ found : (T1, T2, T3)
+ required: (?A11, ?A12) where type ?A12 <: A (this is a GADT skolem), type ?A11 <: A (this is a GADT skolem)
+ def f4[A](x: A) = (Left(x -> x): Either[(A, A), (A, A)]) match { case LeftOrRight((a, b, c)) => a } // fail
+ ^
+t6675b.scala:30: warning: object NativelyTwo expects 2 patterns to hold ((Int, Int), (Int, Int)) but crushing into 2-tuple to fit single pattern (SI-6675)
+ def f1 = (Left((0, 0)): Either[(Int, Int), (Int, Int)]) match { case NativelyTwo(a) => a } // warn
+ ^
+t6675b.scala:32: error: constructor cannot be instantiated to expected type;
+ found : (T1, T2, T3)
+ required: ((Int, Int), (Int, Int))
+ def f3 = (Left((0, 0)): Either[(Int, Int), (Int, Int)]) match { case NativelyTwo((a, b, c)) => a } // fail
+ ^
+t6675b.scala:36: warning: object NativelyTwo expects 2 patterns to hold (A, A) but crushing into 2-tuple to fit single pattern (SI-6675)
+ def f1[A](x: A) = (Left(x): Either[A, A]) match { case NativelyTwo(a) => a } // warn
+ ^
+t6675b.scala:37: warning: object NativelyTwo expects 2 patterns to hold ((A, A), (A, A)) but crushing into 2-tuple to fit single pattern (SI-6675)
+ def f2[A](x: A) = (Left(x -> x): Either[(A, A), (A, A)]) match { case NativelyTwo(a) => a } // warn
+ ^
+t6675b.scala:39: error: constructor cannot be instantiated to expected type;
+ found : (T1, T2, T3)
+ required: ((?A17, ?A18), (?A19, ?A20)) where type ?A20 <: A (this is a GADT skolem), type ?A19 <: A (this is a GADT skolem), type ?A18 <: A (this is a GADT skolem), type ?A17 <: A (this is a GADT skolem)
+ def f4[A](x: A) = (Left(x -> x): Either[(A, A), (A, A)]) match { case NativelyTwo((a, b, c)) => a } // fail
+ ^
+5 warnings found
+four errors found
diff --git a/test/files/neg/t6675b.flags b/test/files/neg/t6675b.flags
new file mode 100644
index 0000000000..1008b0a70c
--- /dev/null
+++ b/test/files/neg/t6675b.flags
@@ -0,0 +1 @@
+-Xlint
diff --git a/test/files/neg/t6675b.scala b/test/files/neg/t6675b.scala
new file mode 100644
index 0000000000..c86c9c3955
--- /dev/null
+++ b/test/files/neg/t6675b.scala
@@ -0,0 +1,40 @@
+object LeftOrRight {
+ def unapply[A](value: Either[A, A]): Option[A] = value match {
+ case scala.Left(x) => Some(x)
+ case scala.Right(x) => Some(x)
+ }
+}
+
+object NativelyTwo {
+ def unapply[A](value: Either[A, A]): Option[(A, A)] = value match {
+ case scala.Left(x) => Some(x -> x)
+ case scala.Right(x) => Some(x -> x)
+ }
+}
+
+
+class A {
+ def f1 = (Left((0, 0)): Either[(Int, Int), (Int, Int)]) match { case LeftOrRight(a) => a } // warn
+ def f2 = (Left((0, 0)): Either[(Int, Int), (Int, Int)]) match { case LeftOrRight((a, b)) => a } // no warn
+ def f3 = (Left((0, 0)): Either[(Int, Int), (Int, Int)]) match { case LeftOrRight((a, b, c)) => a } // fail
+}
+
+class B {
+ def f1[A](x: A) = (Left(x): Either[A, A]) match { case LeftOrRight(a) => a } // no warn
+ def f2[A](x: A) = (Left(x -> x): Either[(A, A), (A, A)]) match { case LeftOrRight(a) => a } // warn
+ def f3[A](x: A) = (Left(x -> x): Either[(A, A), (A, A)]) match { case LeftOrRight((a, b)) => a } // no warn
+ def f4[A](x: A) = (Left(x -> x): Either[(A, A), (A, A)]) match { case LeftOrRight((a, b, c)) => a } // fail
+}
+
+class C {
+ def f1 = (Left((0, 0)): Either[(Int, Int), (Int, Int)]) match { case NativelyTwo(a) => a } // warn
+ def f2 = (Left((0, 0)): Either[(Int, Int), (Int, Int)]) match { case NativelyTwo((a, b)) => a } // no warn
+ def f3 = (Left((0, 0)): Either[(Int, Int), (Int, Int)]) match { case NativelyTwo((a, b, c)) => a } // fail
+}
+
+class D {
+ def f1[A](x: A) = (Left(x): Either[A, A]) match { case NativelyTwo(a) => a } // warn
+ def f2[A](x: A) = (Left(x -> x): Either[(A, A), (A, A)]) match { case NativelyTwo(a) => a } // warn
+ def f3[A](x: A) = (Left(x -> x): Either[(A, A), (A, A)]) match { case NativelyTwo((a, b)) => a } // no warn
+ def f4[A](x: A) = (Left(x -> x): Either[(A, A), (A, A)]) match { case NativelyTwo((a, b, c)) => a } // fail
+}
diff --git a/test/files/neg/t7214neg.check b/test/files/neg/t7214neg.check
index 0660cccd02..291af04578 100644
--- a/test/files/neg/t7214neg.check
+++ b/test/files/neg/t7214neg.check
@@ -1,7 +1,4 @@
-t7214neg.scala:28: error: wrong number of patterns for object Extractor offering Any: expected 1, found 0
+t7214neg.scala:28: error: not enough patterns for object Extractor offering Any: expected 1, found 0
case Extractor() =>
^
-t7214neg.scala:28: error: wrong number of patterns for object Extractor offering Any: expected 1, found 0
- case Extractor() =>
- ^
-two errors found
+one error found
diff --git a/test/files/neg/t7897.check b/test/files/neg/t7897.check
new file mode 100644
index 0000000000..48eff511c7
--- /dev/null
+++ b/test/files/neg/t7897.check
@@ -0,0 +1,4 @@
+t7897.scala:19: error: value length is not a member of p0.Single
+ case p0.Single(x) => println(s"`$x` has ${x.length} chars")
+ ^
+one error found
diff --git a/test/files/neg/t7897.scala b/test/files/neg/t7897.scala
new file mode 100644
index 0000000000..87c966b1e0
--- /dev/null
+++ b/test/files/neg/t7897.scala
@@ -0,0 +1,23 @@
+package p0 {
+ class Single(val x: Any) extends AnyRef with Product1[String] {
+ private def s = "" + x
+ override def canEqual(x: Any) = this eq x.asInstanceOf[AnyRef]
+ def isEmpty = false
+ def get = this
+ def _1 = s + " only"
+
+ override def toString = s"Single(${_1})"
+ }
+
+ object Single {
+ def unapply(x: Any): Single = new Single(x)
+ }
+}
+object Test {
+ def main(args: Array[String]): Unit = {
+ "catdog" match {
+ case p0.Single(x) => println(s"`$x` has ${x.length} chars")
+ case x => println("fail: " + x)
+ }
+ }
+}
diff --git a/test/files/neg/t997.check b/test/files/neg/t997.check
index 8c41060ba2..b118792229 100644
--- a/test/files/neg/t997.check
+++ b/test/files/neg/t997.check
@@ -1,7 +1,4 @@
-t997.scala:13: error: wrong number of patterns for object Foo offering (String, String): expected 2, found 3
+t997.scala:13: error: too many patterns for object Foo offering (String, String): expected 2, found 3
"x" match { case Foo(a, b, c) => Console.println((a,b,c)) }
^
-t997.scala:13: error: wrong number of patterns for object Foo offering (String, String): expected 2, found 3
-"x" match { case Foo(a, b, c) => Console.println((a,b,c)) }
- ^
-two errors found
+one error found
diff --git a/test/files/run/name-based-patmat.check b/test/files/run/name-based-patmat.check
index 1cc605ea3d..3d5fc40ed7 100644
--- a/test/files/run/name-based-patmat.check
+++ b/test/files/run/name-based-patmat.check
@@ -1,3 +1,5 @@
+`catdog only` has 11 chars
+`catdog only, no product` has 23 chars
catdog
2 catdogs! A ha ha!
3 catdogs! A ha ha!
diff --git a/test/files/run/name-based-patmat.scala b/test/files/run/name-based-patmat.scala
index 2c429c141f..8e20940100 100644
--- a/test/files/run/name-based-patmat.scala
+++ b/test/files/run/name-based-patmat.scala
@@ -1,5 +1,33 @@
final class MiniSome[T](val get: T) extends AnyVal { def isEmpty = false }
+package p0 {
+ class Single(val x: Any) extends AnyRef with Product1[String] {
+ private def s = "" + x
+ override def canEqual(x: Any) = this eq x.asInstanceOf[AnyRef]
+ def isEmpty = false
+ def get = this
+ def _1 = s + " only"
+
+ override def toString = s"Single(${_1})"
+ }
+
+ object Single {
+ def unapply(x: Any): Single = new Single(x)
+ }
+
+ class SingleNoProduct(val x: Any) extends AnyRef {
+ private def s = "" + x
+ def isEmpty = false
+ def get = s + " only, no product"
+
+ override def toString = s"SingleNoProduct($get)"
+ }
+
+ object SingleNoProduct {
+ def unapply(x: Any): SingleNoProduct = new SingleNoProduct(x)
+ }
+}
+
package p1 {
class Triple(val x: Any) extends AnyRef with Product3[String, String, String] {
private def s = "" + x
@@ -49,14 +77,16 @@ package p3 {
}
object Test {
-
- // def f(x: Any) = x match {
- // case p1.Foo(x, y, z) => println((x, y, z))
- // case x => println(x)
- // }
-
def main(args: Array[String]): Unit = {
"catdog" match {
+ case p0.Single(x) => println(s"`${x._1}` has ${x._1.length} chars")
+ case x => println("fail: " + x)
+ }
+ "catdog" match {
+ case p0.SingleNoProduct(x) => println(s"`$x` has ${x.length} chars")
+ case x => println("fail: " + x)
+ }
+ "catdog" match {
case p1.Triple(x, y, z) => List(x, y, z) foreach println
case x => println("fail: " + x)
}
diff --git a/test/files/run/patmat-mix-case-extractor.check b/test/files/run/patmat-mix-case-extractor.check
new file mode 100644
index 0000000000..a6e1bd23df
--- /dev/null
+++ b/test/files/run/patmat-mix-case-extractor.check
@@ -0,0 +1,8 @@
+-1
+6
+4
+18
+-1
+1006
+1004
+1018
diff --git a/test/files/run/patmat-mix-case-extractor.scala b/test/files/run/patmat-mix-case-extractor.scala
new file mode 100644
index 0000000000..964e6f743c
--- /dev/null
+++ b/test/files/run/patmat-mix-case-extractor.scala
@@ -0,0 +1,110 @@
+trait CaseClass
+trait ProdCaseClass extends CaseClass { def x: Int }
+trait SeqCaseClass extends CaseClass { def xs: Seq[Int] }
+
+case class CaseClass1() extends CaseClass
+case class CaseClass2(xs: Int*) extends SeqCaseClass
+case class CaseClass3(x: Int) extends ProdCaseClass
+case class CaseClass4(x: Int, xs: Int*) extends ProdCaseClass with SeqCaseClass
+
+object Extractor1 { def unapply(x: CaseClass): Boolean = false }
+object Extractor2 { def unapplySeq(x: SeqCaseClass): Option[Seq[Int]] = Some(x.xs) }
+object Extractor3 { def unapply(x: ProdCaseClass): Option[Int] = Some(x.x) }
+object Extractor4 { def unapplySeq(x: ProdCaseClass with SeqCaseClass): Option[(Int, Seq[Int])] = Some(x.x, x.xs) }
+
+class A {
+ def f1(x: Any) = x match {
+ case CaseClass1() => -1
+ case CaseClass2(xs @ _*) => xs.sum
+ case CaseClass3(x) => x
+ case CaseClass4(x, xs @ _*) => x + xs.sum
+ case Extractor4(x, xs @ _*) => 1000 + x + xs.sum
+ case Extractor3(x) => 1000 + x
+ case Extractor2(xs @ _*) => 1000 + xs.sum
+ case Extractor1() => -3
+ case _ => -2
+ }
+ def f2(x: Any) = x match {
+ case Extractor4(x, xs @ _*) => 1000 + x + xs.sum
+ case Extractor3(x) => 1000 + x
+ case Extractor2(xs @ _*) => 1000 + xs.sum
+ case Extractor1() => -3
+ case CaseClass1() => -1
+ case CaseClass2(xs @ _*) => xs.sum
+ case CaseClass3(x) => x
+ case CaseClass4(x, xs @ _*) => x + xs.sum
+ case _ => -2
+ }
+ def run() {
+ List(
+ f1(CaseClass1()),
+ f1(CaseClass2(1, 2, 3)),
+ f1(CaseClass3(4)),
+ f1(CaseClass4(5, 6, 7)),
+ f2(CaseClass1()),
+ f2(CaseClass2(1, 2, 3)),
+ f2(CaseClass3(4)),
+ f2(CaseClass4(5, 6, 7))
+ ) foreach println
+ }
+}
+
+object Test {
+ def main(args: Array[String]): Unit = {
+ (new A).run
+ }
+}
+
+
+class B {
+ case class CaseClass0()
+ case class CaseClass0v(xs: Int*)
+
+ case class CaseClass(x: Int, y: Int)
+ object Extractor { def unapply(x: Any): Option[(Int, Int)] = Some((1, 1)) }
+
+ case class CaseSeq(x: Char, y: Double, zs: Int*)
+ object ExtractorSeq { def unapplySeq(x: Any): Option[(Int, Int, Seq[Int])] = Some((1, 1, List(1))) }
+
+ def f1(x: CaseClass) = x match { case CaseClass(y, z) => y }
+ def f2(x: Any) = x match { case Extractor(y, z) => y }
+
+ def f3(x: CaseSeq) = x match {
+ case CaseSeq(x, y) => y
+ case CaseSeq(x, y, z) => z
+ }
+ def f4(x: CaseSeq) = x match {
+ case CaseSeq(x, y, z) => z :: Nil
+ case CaseSeq(x, y, z @ _*) => z
+ }
+
+ def f5(x: Any) = x match { case ExtractorSeq(x, y, z) => z }
+ def f6(x: Any) = x match { case ExtractorSeq(x, y, z @ _*) => z }
+
+ def g1(x: CaseClass0) = x match {
+ case CaseClass0() => true
+ }
+ def g2(x: CaseClass0v) = x match {
+ case CaseClass0v() => true
+ case CaseClass0v(5) => true
+ case CaseClass0v(x) => true
+ case CaseClass0v(xs @ _*) => false
+ }
+}
+
+package p1 {
+ trait _X {
+ case class _Foo();
+ object _Bar {
+ def unapply(foo: _Foo): Boolean = true;
+ }
+ }
+
+ object Y extends _X {
+ val foo = _Foo()
+ foo match {
+ case _Bar() =>
+ case _ => assert(false)
+ }
+ }
+}