diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/UnCurry.scala | 2 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala | 30 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala | 2 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala | 164 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala | 46 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Infer.scala | 90 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala | 418 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Typers.scala | 6 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Unapplies.scala | 22 | ||||
-rw-r--r-- | test/files/neg/t4425.check | 10 | ||||
-rw-r--r-- | test/files/neg/t4425.scala | 10 | ||||
-rw-r--r-- | test/files/neg/t4425b.check | 36 | ||||
-rw-r--r-- | test/files/neg/t6675.check | 2 | ||||
-rw-r--r-- | test/files/neg/t997.check | 7 | ||||
-rw-r--r-- | test/files/pos/annotated-treecopy/Impls_Macros_1.scala | 2 | ||||
-rw-r--r-- | test/files/run/matchonseq.scala | 10 | ||||
-rw-r--r-- | test/files/run/t7214.scala | 2 |
17 files changed, 400 insertions, 459 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index ca123f8782..16c803e2e8 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -460,7 +460,7 @@ abstract class UnCurry extends InfoTransform val fn1 = transform(fn) val args1 = transformTrees(fn.symbol.name match { case nme.unapply => args - case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, analyzer.unapplyTypeList(fn.pos, fn.symbol, fn.tpe, args)) + case nme.unapplySeq => transformArgs(tree.pos, fn.symbol, args, localTyper.expectedPatternTypes(fn, args)) case _ => sys.error("internal error: UnApply node has wrong symbol") }) treeCopy.UnApply(tree, fn1, args1) diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala index 77a6b3940c..52055dea85 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala @@ -83,15 +83,14 @@ trait MatchCodeGen extends Interface { trait PureMatchMonadInterface extends MatchMonadInterface { val matchStrategy: Tree - - def inMatchMonad(tp: Type): Type = appliedType(oneSig, List(tp)).finalResultType - def pureType(tp: Type): Type = appliedType(oneSig, List(tp)).paramTypes.headOption getOrElse NoType // fail gracefully (otherwise we get crashes) - protected def matchMonadSym = oneSig.finalResultType.typeSymbol - import CODE._ def _match(n: Name): SelectStart = matchStrategy DOT n - private lazy val oneSig: Type = typer.typedOperator(_match(vpmName.one)).tpe // TODO: error message + // TODO: error message + private lazy val oneType = typer.typedOperator(_match(vpmName.one)).tpe + private def oneApplied(tp: Type): Type = appliedType(oneType, tp :: Nil) + override def pureType(tp: Type): Type = firstParamType(oneApplied(tp)) + override def mapResultType(prev: Type, elem: Type): Type = oneApplied(elem).finalResultType } trait PureCodegen extends CodegenCore with PureMatchMonadInterface { @@ -123,13 +122,7 @@ trait MatchCodeGen extends Interface { } } - trait OptimizedMatchMonadInterface extends MatchMonadInterface { - override def inMatchMonad(tp: Type): Type = optionType(tp) - override def pureType(tp: Type): Type = tp - override protected def matchMonadSym = OptionClass - } - - trait OptimizedCodegen extends CodegenCore with TypedSubstitution with OptimizedMatchMonadInterface { + trait OptimizedCodegen extends CodegenCore with TypedSubstitution with MatchMonadInterface { override def codegen: AbsCodegen = optimizedCodegen // when we know we're targetting Option, do some inlining the optimizer won't do @@ -195,15 +188,14 @@ trait MatchCodeGen extends Interface { // next: MatchMonad[U] // returns MatchMonad[U] def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = { - val tp = inMatchMonad(b.tpe) - val prevSym = freshSym(prev.pos, tp, "o") - val isEmpty = tp member vpmName.isEmpty - val get = tp member vpmName.get - + val prevSym = freshSym(prev.pos, prev.tpe, "o") BLOCK( VAL(prevSym) === prev, // must be isEmpty and get as we don't control the target of the call (prev is an extractor call) - ifThenElseZero(NOT(prevSym DOT isEmpty), Substitution(b, prevSym DOT get)(next)) + ifThenElseZero( + NOT(prevSym DOT vpmName.isEmpty), + Substitution(b, prevSym DOT vpmName.get)(next) + ) ) } diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala index 9854e4ef62..ec45789687 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala @@ -210,7 +210,7 @@ trait MatchOptimization extends MatchTreeMaking with MatchAnalysis { // } //// SWITCHES -- TODO: operate on Tests rather than TreeMakers - trait SwitchEmission extends TreeMakers with OptimizedMatchMonadInterface { + trait SwitchEmission extends TreeMakers with MatchMonadInterface { import treeInfo.isGuardedCase abstract class SwitchMaker { diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala index c14b8919dd..5ddcd3528b 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala @@ -347,9 +347,8 @@ trait MatchTranslation extends CpsPatternHacks { // don't fail here though (or should we?) val translationStep = patTree match { case WildcardPattern() => none() - case UnApply(unfun, args) => translateExtractorPattern(ExtractorCall(unfun, args)) - case Apply(fun, args) => ExtractorCall.fromCaseClass(fun, args) map translateExtractorPattern getOrElse noFurtherSubPats() - case MaybeBoundTyped(subPatBinder, pt) => one(TypeTestTreeMaker(subPatBinder, patBinder, pt, glb(List(dealiasWiden(patBinder.info), pt)).normalize)(pos)) + case _: UnApply | _: Apply => translateExtractorPattern(ExtractorCall(patTree)) + case MaybeBoundTyped(subPatBinder, pt) => one(TypeTestTreeMaker(subPatBinder, patBinder, pt, glbWithBinder(pt))(pos)) case Bound(subpatBinder, p) => withSubPats(List(SubstOnlyTreeMaker(subpatBinder, patBinder)), (patBinder, p)) case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => one(EqualityTestTreeMaker(patBinder, patTree, pos)) case Alternative(alts) => one(AlternativesTreeMaker(patBinder, alts map (translatePattern(patBinder, _)), alts.head.pos)) @@ -421,22 +420,37 @@ trait MatchTranslation extends CpsPatternHacks { /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// object ExtractorCall { - def apply(unfun: Tree, args: List[Tree]): ExtractorCall = new ExtractorCallRegular(unfun, args) - def fromCaseClass(fun: Tree, args: List[Tree]): Option[ExtractorCall] = Some(new ExtractorCallProd(fun, args)) + // 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 + } } - abstract class ExtractorCall(val args: List[Tree]) { - val nbSubPats = args.length + abstract class ExtractorCall { + import CODE._ - // everything okay, captain? - def isTyped : Boolean + 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 - lazy val lastIsStar = (nbSubPats > 0) && treeInfo.isStar(args.last) + + private def hasStar = nbSubPats > 0 && isStar(args.last) + private def isNonEmptySeq = nbSubPats > 0 && isSeq + + def isSingle = nbSubPats == 0 && !isSeq // to which type should the previous binder be casted? def paramType : Type + protected def rawSubPatTypes: List[Type] + /** Create the TreeMaker that embodies this extractor call * * `binder` has been casted to `paramType` if necessary @@ -467,68 +481,83 @@ trait MatchTranslation extends CpsPatternHacks { } // never store these in local variables (for PreserveSubPatBinders) - lazy val ignoredSubPatBinders = (subPatBinders zip args).collect{ - case (b, PatternBoundToUnderscore()) => b - }.toSet - - def subPatTypes: List[Type] = - if(isSeq) { - val TypeRef(pre, SeqClass, args) = seqTp - // do repeated-parameter expansion to match up with the expected number of arguments (in casu, subpatterns) - val formalsWithRepeated = rawSubPatTypes.init :+ typeRef(pre, RepeatedParamClass, args) - - if (lastIsStar) formalTypes(formalsWithRepeated, nbSubPats - 1) :+ seqTp - else formalTypes(formalsWithRepeated, nbSubPats) - } else rawSubPatTypes - - protected def rawSubPatTypes: List[Type] - - protected def seqTp = rawSubPatTypes.last baseType SeqClass - protected def seqLenCmp = rawSubPatTypes.last member nme.lengthCompare - protected lazy val firstIndexingBinder = rawSubPatTypes.length - 1 // rawSubPatTypes.last is the Seq, thus there are `rawSubPatTypes.length - 1` non-seq elements in the tuple - protected lazy val lastIndexingBinder = if(lastIsStar) nbSubPats-2 else nbSubPats-1 - protected lazy val expectedLength = lastIndexingBinder - firstIndexingBinder + 1 - protected lazy val minLenToCheck = if(lastIsStar) 1 else 0 - protected def seqTree(binder: Symbol) = tupleSel(binder)(firstIndexingBinder+1) + 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) + + def subPatTypes: List[Type] = ( + if (rawSubPatTypes.isEmpty || !isSeq) rawSubPatTypes + else if (hasStar) nonStarSubPatTypes :+ rawLast + else nonStarSubPatTypes + ) + + private def emptySub = rawSubPatTypes.isEmpty + private def rawLast = if (emptySub) NothingTpe else rawSubPatTypes.last + private def rawInit = rawSubPatTypes dropRight 1 + protected def sequenceType = if (emptySub) NothingTpe else rawLast + protected def elementType = if (emptySub) NothingTpe else unapplySeqElementType(rawLast) + protected def repeatedType = if (emptySub) NothingTpe else scalaRepeatedType(elementType) + + // 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 + + 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)) + private def genDrop(binder: Symbol, n: Int): List[Tree] = codegen.drop(seqTree(binder))(expectedLength) :: Nil + + // codegen.drop(seqTree(binder))(nbIndexingIndices)))).toList + protected def seqTree(binder: Symbol) = tupleSel(binder)(firstIndexingBinder + 1) protected def tupleSel(binder: Symbol)(i: Int): Tree = codegen.tupleSel(binder)(i) // the trees that select the subpatterns on the extractor's result, // referenced by `binder` protected def subPatRefsSeq(binder: Symbol): List[Tree] = { - val indexingIndices = (0 to (lastIndexingBinder-firstIndexingBinder)) - val nbIndexingIndices = indexingIndices.length - + def lastTrees: List[Tree] = ( + if (!hasStar) 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)) - // there are `firstIndexingBinder` non-seq tuple elements preceding the Seq - (((1 to firstIndexingBinder) map tupleSel(binder)) ++ - // then we have to index the binder that represents the sequence for the remaining subpatterns, except for... - (indexingIndices map codegen.index(seqTree(binder))) ++ - // the last one -- if the last subpattern is a sequence wildcard: drop the prefix (indexed by the refs on the line above), return the remainder - (if(!lastIsStar) Nil else List( - if(nbIndexingIndices == 0) seqTree(binder) - else codegen.drop(seqTree(binder))(nbIndexingIndices)))).toList + + // [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... + // [3] the last one -- if the last subpattern is a sequence wildcard: + // drop the prefix (indexed by the refs on the preceding line), return the remainder + ( productElemsToN(binder, firstIndexingBinder) + ++ genTake(binder, expectedLength) + ++ lastTrees + ).toList } // 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 (nbSubPats == 0) Nil - else if (isSeq) subPatRefsSeq(binder) - else ((1 to nbSubPats) map tupleSel(binder)).toList + if (isNonEmptySeq) subPatRefsSeq(binder) else productElemsToN(binder, nbSubPats) + + private def compareInts(t1: Tree, t2: Tree) = + gen.mkMethodCall(termMember(ScalaPackage, "math"), TermName("signum"), Nil, (t1 INT_- t2) :: Nil) protected def lengthGuard(binder: Symbol): Option[Tree] = // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied - checkedLength map { expectedLength => import CODE._ + checkedLength map { expectedLength => // `binder.lengthCompare(expectedLength)` - def checkExpectedLength = (seqTree(binder) DOT seqLenCmp)(LIT(expectedLength)) + // ...if binder has a lengthCompare method, otherwise + // `scala.math.signum(binder.length - expectedLength)` + def checkExpectedLength = sequenceType member nme.lengthCompare match { + case NoSymbol => compareInts(Select(seqTree(binder), nme.length), LIT(expectedLength)) + case lencmp => (seqTree(binder) DOT lencmp)(LIT(expectedLength)) + } // the comparison to perform // 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 (lastIsStar) _ INT_>= _ - else _ INT_== _ + if (hasStar) _ INT_>= _ + else _ INT_== _ // `if (binder != null && $checkExpectedLength [== | >=] 0) then else zero` (seqTree(binder) ANY_!= NULL) AND compareOp(checkExpectedLength, ZERO) @@ -536,14 +565,14 @@ trait MatchTranslation extends CpsPatternHacks { def checkedLength: Option[Int] = // no need to check unless it's an unapplySeq and the minimal length is non-trivially satisfied - if (!isSeq || (expectedLength < minLenToCheck)) None + if (!isSeq || expectedLength < starLength) 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(fun: Tree, args: List[Tree]) extends ExtractorCall(args) { + class ExtractorCallProd(val fun: Tree, val args: List[Tree]) extends ExtractorCall { // TODO: fix the illegal type bound in pos/t602 -- type inference messes up before we get here: /*override def equals(x$1: Any): Boolean = ... val o5: Option[com.mosol.sl.Span[Any]] = // Span[Any] --> Any is not a legal type argument for Span! @@ -588,17 +617,17 @@ trait MatchTranslation extends CpsPatternHacks { else codegen.tupleSel(binder)(i) // this won't type check for case classes, as they do not inherit ProductN } - override def toString(): String = "case class "+ (if (constructorTp eq null) fun else paramType.typeSymbol) +" with arguments "+ args + override def toString() = s"ExtractorCallProd($fun:${fun.tpe} / ${fun.symbol} / args=$args)" } - class ExtractorCallRegular(extractorCallIncludingDummy: Tree, args: List[Tree]) extends ExtractorCall(args) { - private lazy val Some(Apply(extractorCall, _)) = extractorCallIncludingDummy.find{ case Apply(_, List(Ident(nme.SELECTOR_DUMMY))) => true case _ => false } + class ExtractorCallRegular(extractorCallIncludingDummy: Tree, val args: List[Tree]) extends ExtractorCall { + val Unapplied(fun) = extractorCallIncludingDummy - def tpe = extractorCall.tpe - def isTyped = (tpe ne NoType) && extractorCall.isTyped && (resultInMonad ne ErrorType) - def paramType = tpe.paramTypes.head - def resultType = tpe.finalResultType - def isSeq = extractorCall.symbol.name == nme.unapplySeq + def tpe = fun.tpe + def paramType = firstParamType(tpe) + def resultType = fun.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 @@ -656,15 +685,16 @@ trait MatchTranslation extends CpsPatternHacks { // turn an extractor's result type into something `monadTypeToSubPatTypesAndRefs` understands protected lazy val resultInMonad: Type = if (isBool) UnitTpe else matchMonadResult(resultType) // the type of "get" - protected lazy val rawSubPatTypes = - if (resultInMonad.typeSymbol eq UnitClass) Nil - else if(!isSeq && nbSubPats == 1) List(resultInMonad) - else getProductArgs(resultInMonad) match { - case Nil => List(resultInMonad) + protected lazy val rawSubPatTypes = ( + if (isBool) Nil + else if (!isSeq && nbSubPats == 1) resultInMonad :: Nil + else getNameBasedProductSelectorTypes(resultInMonad) match { + case Nil => resultInMonad :: Nil case x => x } + ) - override def toString() = extractorCall +": "+ extractorCall.tpe +" (symbol= "+ extractorCall.symbol +")." + override def toString() = s"ExtractorCallRegular($fun:${fun.tpe} / ${fun.symbol})" } /** A conservative approximation of which patterns do not discern anything. diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala index aa923b1059..616abaaf3b 100644 --- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala @@ -171,6 +171,42 @@ trait Interface extends ast.TreeDSL { trait MatchMonadInterface { val typer: Typer val matchOwner = typer.context.owner + def pureType(tp: Type): Type = tp + + // Extracting from the monad: tp == Option[T], result == T + def matchMonadResult(tp: Type) = definitions typeOfMemberNamedGet tp + + // prev == CC[T] + // elem == U + // result == CC[U] + // where "CC" here is Option or any other single-type-parameter container + // + // TODO - what if it has multiple type parameters? + // If we have access to the zero, maybe we can infer the + // type parameter by contrasting with the zero's application. + def mapResultType(prev: Type, elem: Type): Type = { + // default to Option[U] if we can't reliably infer the types + def fallback(elem: Type): Type = elem match { + case TypeRef(_, sym, _) if sym.isTypeParameterOrSkolem => fallback(sym.info.bounds.hi) + case _ => optionType(elem) + } + + // optionType(elem) //pack(elem)) + // The type of "get" in CC[T] is what settles what was wrapped. + val prevElem = matchMonadResult(prev) + if (prevElem =:= elem) prev + else prev.typeArgs match { + case targ :: Nil if targ =:= prevElem => + // the type of "get" in the result should be elem. + // If not, the type arguments are doing something nonobvious + // so fall back on Option. + val result = appliedType(prev.typeConstructor, elem :: Nil) + val newElem = matchMonadResult(result) + if (elem =:= newElem) result else fallback(newElem) + case _ => + fallback(AnyTpe) + } + } def reportUnreachable(pos: Position) = typer.context.unit.warning(pos, "unreachable code") def reportMissingCases(pos: Position, counterExamples: List[String]) = { @@ -180,16 +216,6 @@ trait Interface extends ast.TreeDSL { typer.context.unit.warning(pos, "match may not be exhaustive.\nIt would fail on the following "+ ceString) } - - def inMatchMonad(tp: Type): Type - def pureType(tp: Type): Type - final def matchMonadResult(tp: Type): Type = - tp.baseType(matchMonadSym).typeArgs match { - case arg :: Nil => arg - case _ => ErrorType - } - - protected def matchMonadSym: Symbol } diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index a7c43361fa..b199176d90 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -76,96 +76,6 @@ trait Infer extends Checkable { override def complete(sym: Symbol) = () } - /** Returns `(formals, formalsExpanded)` where `formalsExpanded` are the expected types - * for the `nbSubPats` sub-patterns of an extractor pattern, of which the corresponding - * unapply[Seq] call is assumed to have result type `resTp`. - * - * `formals` are the formal types before expanding a potential repeated parameter (must come last in `formals`, if at all) - * - * @param nbSubPats The number of arguments to the extractor pattern - * @param effectiveNbSubPats `nbSubPats`, unless there is one sub-pattern which, after unwrapping - * bind patterns, is a Tuple pattern, in which case it is the number of - * elements. Used to issue warnings about binding a `TupleN` to a single value. - * @throws TypeError when the unapply[Seq] definition is ill-typed - * @returns (null, null) when the expected number of sub-patterns cannot be satisfied by the given extractor - * - * This is the spec currently implemented -- TODO: update it. - * - * 8.1.8 ExtractorPatterns - * - * An extractor pattern x(p1, ..., pn) where n ≥ 0 is of the same syntactic form as a constructor pattern. - * However, instead of a case class, the stable identifier x denotes an object which has a member method named unapply or unapplySeq that matches the pattern. - * - * An `unapply` method with result type `R` in an object `x` matches the - * pattern `x(p_1, ..., p_n)` if it takes exactly one argument and, either: - * - `n = 0` and `R =:= Boolean`, or - * - `n = 1` and `R <:< Option[T]`, for some type `T`. - * The argument pattern `p1` is typed in turn with expected type `T`. - * - Or, `n > 1` and `R <:< Option[Product_n[T_1, ..., T_n]]`, for some - * types `T_1, ..., T_n`. The argument patterns `p_1, ..., p_n` are - * typed with expected types `T_1, ..., T_n`. - * - * An `unapplySeq` method in an object `x` matches the pattern `x(p_1, ..., p_n)` - * if it takes exactly one argument and its result type is of the form `Option[S]`, - * where either: - * - `S` is a subtype of `Seq[U]` for some element type `U`, (set `m = 0`) - * - or `S` is a `ProductX[T_1, ..., T_m]` and `T_m <: Seq[U]` (`m <= n`). - * - * The argument patterns `p_1, ..., p_n` are typed with expected types - * `T_1, ..., T_m, U, ..., U`. Here, `U` is repeated `n-m` times. - * - */ - def extractorFormalTypes(pos: Position, resTp: Type, nbSubPats: Int, - unappSym: Symbol, effectiveNbSubPats: Int): (List[Type], List[Type]) = { - val isUnapplySeq = unappSym.name == nme.unapplySeq - val booleanExtractor = resTp.typeSymbolDirect == BooleanClass - - def seqToRepeatedChecked(tp: Type) = { - val toRepeated = seqToRepeated(tp) - if (tp eq toRepeated) throw new TypeError("(the last tuple-component of) the result type of an unapplySeq must be a Seq[_]") - else toRepeated - } - - // empty list --> error, otherwise length == 1 - lazy val optionArgs = resTp.baseType(OptionClass).typeArgs - // empty list --> not a ProductN, otherwise product element types - def productArgs = getProductArgs(optionArgs.head) - - val formals = - // convert Seq[T] to the special repeated argument type - // so below we can use formalTypes to expand formals to correspond to the number of actuals - if (isUnapplySeq) { - if (optionArgs.nonEmpty) - productArgs match { - case Nil => List(seqToRepeatedChecked(optionArgs.head)) - case normalTps :+ seqTp => normalTps :+ seqToRepeatedChecked(seqTp) - } - else throw new TypeError(s"result type $resTp of unapplySeq defined in ${unappSym.fullLocationString} does not conform to Option[_]") - } else { - if (booleanExtractor && nbSubPats == 0) Nil - else if (optionArgs.nonEmpty) - if (nbSubPats == 1) { - val productArity = productArgs.size - if (productArity > 1 && productArity != effectiveNbSubPats && settings.lint) - global.currentUnit.warning(pos, - s"extractor pattern binds a single value to a Product${productArity} of type ${optionArgs.head}") - optionArgs - } - // TODO: update spec to reflect we allow any ProductN, not just TupleN - else productArgs - else - throw new TypeError(s"result type $resTp of unapply defined in ${unappSym.fullLocationString} does not conform to Option[_] or Boolean") - } - - // for unapplySeq, replace last vararg by as many instances as required by nbSubPats - val formalsExpanded = - if (isUnapplySeq && formals.nonEmpty) formalTypes(formals, nbSubPats) - else formals - - if (formalsExpanded.lengthCompare(nbSubPats) != 0) (null, null) - else (formals, formalsExpanded) - } - /** A fresh type variable with given type parameter as origin. */ def freshVar(tparam: Symbol): TypeVar = TypeVar(tparam) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala index 990de0ca1f..13926ca18b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala @@ -38,6 +38,13 @@ trait PatternTypers { import global._ import definitions._ + private object FixedAndRepeatedTypes { + def unapply(types: List[Type]) = types match { + case init :+ last if isRepeatedParamType(last) => Some((init, dropRepeated(last))) + case _ => Some((types, NoType)) + } + } + // when true: // - we may virtualize matches (if -Xexperimental and there's a suitable __match in scope) // - we synthesize PartialFunction implementations for `x => x match {...}` and `match {...}` when the expected type is PartialFunction @@ -52,75 +59,118 @@ trait PatternTypers { private def unit = context.unit - /** Type trees in `args0` against corresponding expected type in `adapted0`. - * - * The mode in which each argument is typed is derived from `mode` and - * whether the arg was originally by-name or var-arg (need `formals0` for that) - * the default is by-val, of course. - * - * (docs reverse-engineered -- AM) - */ - def typedArgs(args0: List[Tree], mode: Mode, formals0: List[Type], adapted0: List[Type]): List[Tree] = { - def loop(args: List[Tree], formals: List[Type], adapted: List[Type]): List[Tree] = { - if (args.isEmpty || adapted.isEmpty) Nil - else { - // No formals left or * indicates varargs. - val isVarArgs = formals.isEmpty || formals.tail.isEmpty && isRepeatedParamType(formals.head) - val isByName = formals.nonEmpty && isByNameParamType(formals.head) - def typedMode = if (isByName) mode.onlySticky else mode.onlySticky | BYVALmode - def body = typedArg(args.head, mode, typedMode, adapted.head) - def arg1 = if (isVarArgs) context.withinStarPatterns(body) else body - - // formals may be empty, so don't call tail - arg1 :: loop(args.tail, formals drop 1, adapted.tail) - } + // If the tree's symbol's type does not define an extractor, maybe the tree's type does. + // this is the case when we encounter an arbitrary tree as the target of an unapply call + // (rather than something that looks like a constructor call.) (for now, this only happens + // due to wrapClassTagUnapply, but when we support parameterized extractors, it will become + // more common place) + private def hasUnapplyMember(tpe: Type): Boolean = reallyExists(unapplyMember(tpe)) + private def hasUnapplyMember(sym: Symbol): Boolean = hasUnapplyMember(sym.tpe_*) + private def hasUnapplyMember(fun: Tree): Boolean = hasUnapplyMember(fun.symbol) || hasUnapplyMember(fun.tpe) + + // ad-hoc overloading resolution to deal with unapplies and case class constructors + // If some but not all alternatives survive filtering the tree's symbol with `p`, + // then update the tree's symbol and type to exclude the filtered out alternatives. + private def inPlaceAdHocOverloadingResolution(fun: Tree)(p: Symbol => Boolean): Tree = fun.symbol filter p match { + case sym if sym.exists && (sym ne fun.symbol) => fun setSymbol sym modifyType (tp => filterOverloadedAlts(tp)(p)) + case _ => fun + } + private def filterOverloadedAlts(tpe: Type)(p: Symbol => Boolean): Type = tpe match { + case OverloadedType(pre, alts) => overloadedType(pre, alts filter p) + case tp => tp + } + + def typedConstructorPattern(fun0: Tree, pt: Type) = { + // Do some ad-hoc overloading resolution and update the tree's symbol and type + // do not update the symbol if the tree's symbol's type does not define an unapply member + // (e.g. since it's some method that returns an object with an unapply member) + val fun = inPlaceAdHocOverloadingResolution(fun0)(hasUnapplyMember) + def caseClass = fun.tpe.typeSymbol.linkedClassOfClass + + // 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. + if (caseClass.isCase && !unapplyMember(fun.tpe).isOverloaded) + convertToCaseConstructor(fun, caseClass, pt) + else if (hasUnapplyMember(fun)) + fun + else + 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 + typedArg(arg, mode, newMode, dropByName(pt)) + } + val FixedAndRepeatedTypes(fixed, elem) = formals + val front = (args, fixed).zipped map typedArgWithFormal + def rest = context withinStarPatterns (args drop front.length map (typedArgWithFormal(_, elem))) + + elem match { + case NoType => front + case _ => front ::: rest } - loop(args0, formals0, adapted0) + } + + private def boundedArrayType(bound: Type): Type = { + val tparam = context.owner freshExistential "" setInfo (TypeBounds upper bound) + newExistentialType(tparam :: Nil, arrayType(tparam.tpe_*)) } protected def typedStarInPattern(tree: Tree, mode: Mode, pt: Type) = { val Typed(expr, tpt) = tree - val exprTyped = typed(expr, mode.onlySticky) - def subArrayType(pt: Type) = - if (isPrimitiveValueClass(pt.typeSymbol) || !isFullyDefined(pt)) arrayType(pt) - else { - val tparam = context.owner freshExistential "" setInfo TypeBounds.upper(pt) - newExistentialType(List(tparam), arrayType(tparam.tpe)) - } - - val (exprAdapted, baseClass) = exprTyped.tpe.typeSymbol match { - case ArrayClass => (adapt(exprTyped, mode.onlySticky, subArrayType(pt)), ArrayClass) - case _ => (adapt(exprTyped, mode.onlySticky, seqType(pt)), SeqClass) + val exprTyped = typed(expr, mode) + val baseClass = exprTyped.tpe.typeSymbol match { + case ArrayClass => ArrayClass + case _ => SeqClass + } + val starType = baseClass match { + case ArrayClass if isPrimitiveValueType(pt) || !isFullyDefined(pt) => arrayType(pt) + case ArrayClass => boundedArrayType(pt) + case _ => seqType(pt) } - exprAdapted.tpe.baseType(baseClass) match { - case TypeRef(_, _, List(elemtp)) => - treeCopy.Typed(tree, exprAdapted, tpt setType elemtp) setType elemtp - case _ => - setError(tree) + val exprAdapted = adapt(exprTyped, mode, starType) + exprAdapted.tpe baseType baseClass match { + case TypeRef(_, _, elemtp :: Nil) => treeCopy.Typed(tree, exprAdapted, tpt setType elemtp) setType elemtp + case _ => setError(tree) } } protected def typedInPattern(tree: Typed, mode: Mode, pt: Type) = { val Typed(expr, tpt) = tree - val tptTyped = typedType(tpt, mode) - val exprTyped = typed(expr, mode.onlySticky, tptTyped.tpe.deconst) - val treeTyped = treeCopy.Typed(tree, exprTyped, tptTyped) - - if (mode.inPatternMode) { - val uncheckedTypeExtractor = extractorForUncheckedType(tpt.pos, tptTyped.tpe) - // make fully defined to avoid bounded wildcard types that may be in pt from calling dropExistential (SI-2038) - val ptDefined = ensureFullyDefined(pt) // FIXME this is probably redundant now that we don't dropExistenial in pattern mode. - val ownType = inferTypedPattern(tptTyped, tptTyped.tpe, ptDefined, canRemedy = uncheckedTypeExtractor.nonEmpty) - treeTyped setType ownType - - uncheckedTypeExtractor match { - case None => treeTyped - case Some(extractor) => wrapClassTagUnapply(treeTyped, extractor, tptTyped.tpe) - } - } else - treeTyped setType tptTyped.tpe + val tptTyped = typedType(tpt, mode) + val tpe = tptTyped.tpe + val exprTyped = typed(expr, mode, tpe.deconst) + val extractor = extractorForUncheckedType(tpt.pos, tpe) + + val canRemedy = tpe match { + case RefinedType(_, decls) if !decls.isEmpty => false + case RefinedType(parents, _) if parents exists isUncheckable => false + case _ => extractor.nonEmpty + } + + val ownType = inferTypedPattern(tptTyped, tpe, pt, canRemedy) + val treeTyped = treeCopy.Typed(tree, exprTyped, tptTyped) setType ownType + + extractor match { + case EmptyTree => treeTyped + case _ => wrapClassTagUnapply(treeTyped, extractor, tpe) + } } + def newExtractorShape(tree: Tree): ExtractorShape = tree match { + case Apply(fun, args) => ExtractorShape(fun, args) + case UnApply(fun, args) => ExtractorShape(fun, args) + } + 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 @@ -243,7 +293,6 @@ trait PatternTypers { case tp1 => tp1 } } - /* * To deal with the type slack between actual (run-time) types and statically known types, for each abstract type T, * reflect its variance as a skolem that is upper-bounded by T (covariant position), or lower-bounded by T (contravariant). @@ -271,91 +320,39 @@ trait PatternTypers { * * see test/files/../t5189*.scala */ - def adaptConstrPattern(tree: Tree, pt: Type): Tree = { // (5) - def hasUnapplyMember(tp: Type) = reallyExists(unapplyMember(tp)) - val overloadedExtractorOfObject = tree.symbol filter (sym => hasUnapplyMember(sym.tpe)) - // if the tree's symbol's type does not define an extractor, maybe the tree's type does. - // this is the case when we encounter an arbitrary tree as the target of an unapply call - // (rather than something that looks like a constructor call.) (for now, this only happens - // due to wrapClassTagUnapply, but when we support parameterized extractors, it will become - // more common place) - val extractor = overloadedExtractorOfObject orElse unapplyMember(tree.tpe) - def convertToCaseConstructor(clazz: Symbol): TypeTree = { - // convert synthetic unapply of case class to case class constructor - val prefix = tree.tpe.prefix - val tree1 = TypeTree(clazz.primaryConstructor.tpe.asSeenFrom(prefix, clazz.owner)) - .setOriginal(tree) - - val skolems = new mutable.ListBuffer[TypeSymbol] - object variantToSkolem extends TypeMap(trackVariance = true) { - def apply(tp: Type) = mapOver(tp) match { - // !!! FIXME - skipping this when variance.isInvariant allows unsoundness, see SI-5189 - case TypeRef(NoPrefix, tpSym, Nil) if !variance.isInvariant && tpSym.isTypeParameterOrSkolem && tpSym.owner.isTerm => - // must initialize or tpSym.tpe might see random type params!! - // without this, we'll get very weird types inferred in test/scaladoc/run/SI-5933.scala - // TODO: why is that?? - tpSym.initialize - val bounds = if (variance.isPositive) TypeBounds.upper(tpSym.tpe) else TypeBounds.lower(tpSym.tpe) - // origin must be the type param so we can deskolemize - val skolem = context.owner.newGADTSkolem(unit.freshTypeName("?"+tpSym.name), tpSym, bounds) - // println("mapping "+ tpSym +" to "+ skolem + " : "+ bounds +" -- pt= "+ pt +" in "+ context.owner +" at "+ context.tree ) - skolems += skolem - skolem.tpe - case tp1 => tp1 - } - } - - // have to open up the existential and put the skolems in scope - // can't simply package up pt in an ExistentialType, because that takes us back to square one (List[_ <: T] == List[T] due to covariance) - val ptSafe = variantToSkolem(pt) // TODO: pt.skolemizeExistential(context.owner, tree) ? - val freeVars = skolems.toList - - // use "tree" for the context, not context.tree: don't make another CaseDef context, - // as instantiateTypeVar's bounds would end up there - val ctorContext = context.makeNewScope(tree, context.owner) - freeVars foreach ctorContext.scope.enter - newTyper(ctorContext).infer.inferConstructorInstance(tree1, clazz.typeParams, ptSafe) - - // simplify types without losing safety, - // so that we get rid of unnecessary type slack, and so that error messages don't unnecessarily refer to skolems - val extrapolate = new ExistentialExtrapolation(freeVars) extrapolate (_: Type) - val extrapolated = tree1.tpe match { - case MethodType(ctorArgs, res) => // ctorArgs are actually in a covariant position, since this is the type of the subpatterns of the pattern represented by this Apply node - ctorArgs foreach (p => p.info = extrapolate(p.info)) // no need to clone, this is OUR method type - copyMethodType(tree1.tpe, ctorArgs, extrapolate(res)) - case tp => tp - } - - // once the containing CaseDef has been type checked (see typedCase), - // tree1's remaining type-slack skolems will be deskolemized (to the method type parameter skolems) - tree1 setType extrapolated - } - - if (extractor != NoSymbol) { - // if we did some ad-hoc overloading resolution, update the tree's symbol - // do not update the symbol if the tree's symbol's type does not define an unapply member - // (e.g. since it's some method that returns an object with an unapply member) - if (overloadedExtractorOfObject != NoSymbol) - tree setSymbol overloadedExtractorOfObject - - tree.tpe match { - case OverloadedType(pre, alts) => tree setType overloadedType(pre, alts filter (alt => hasUnapplyMember(alt.tpe))) - case _ => - } - val unapply = unapplyMember(extractor.tpe) - val clazz = unapplyParameterType(unapply) - - if (unapply.isCase && clazz.isCase) { - convertToCaseConstructor(clazz) - } else { - tree - } - } else { - val clazz = tree.tpe.typeSymbol.linkedClassOfClass - if (clazz.isCase) - convertToCaseConstructor(clazz) - else - CaseClassConstructorError(tree) + private def convertToCaseConstructor(tree: Tree, caseClass: Symbol, pt: Type): Tree = { + val variantToSkolem = new VariantToSkolemMap + val caseConstructorType = tree.tpe.prefix memberType caseClass memberType caseClass.primaryConstructor + val tree1 = TypeTree(caseConstructorType) setOriginal tree + + // have to open up the existential and put the skolems in scope + // can't simply package up pt in an ExistentialType, because that takes us back to square one (List[_ <: T] == List[T] due to covariance) + val ptSafe = variantToSkolem(pt) // TODO: pt.skolemizeExistential(context.owner, tree) ? + val freeVars = variantToSkolem.skolems + + // use "tree" for the context, not context.tree: don't make another CaseDef context, + // as instantiateTypeVar's bounds would end up there + log(sm"""|convert to case constructor { + | tree: $tree: ${tree.tpe} + | ptSafe: $ptSafe + | context.tree: ${context.tree}: ${context.tree.tpe} + |}""".trim) + + val ctorContext = context.makeNewScope(tree, context.owner) + freeVars foreach ctorContext.scope.enter + newTyper(ctorContext).infer.inferConstructorInstance(tree1, caseClass.typeParams, ptSafe) + + // simplify types without losing safety, + // so that we get rid of unnecessary type slack, and so that error messages don't unnecessarily refer to skolems + val extrapolator = new ExistentialExtrapolation(freeVars) + def extrapolate(tp: Type) = extrapolator extrapolate tp + + // once the containing CaseDef has been type checked (see typedCase), + // tree1's remaining type-slack skolems will be deskolemized (to the method type parameter skolems) + tree1 modifyType { + case MethodType(ctorArgs, restpe) => // ctorArgs are actually in a covariant position, since this is the type of the subpatterns of the pattern represented by this Apply node + copyMethodType(tree1.tpe, ctorArgs map (_ modifyInfo extrapolate), extrapolate(restpe)) // no need to clone ctorArgs, this is OUR method type + case tp => tp } } @@ -363,75 +360,61 @@ trait PatternTypers { def duplErrTree = setError(treeCopy.Apply(tree, fun0, args)) def duplErrorTree(err: AbsTypeError) = { issue(err); duplErrTree } - val otpe = fun.tpe - if (args.length > MaxTupleArity) return duplErrorTree(TooManyArgsPatternError(fun)) - // - def freshArgType(tp: Type): (List[Symbol], Type) = tp match { - case MethodType(param :: _, _) => - (Nil, param.tpe) - case PolyType(tparams, restpe) => - createFromClonedSymbols(tparams, freshArgType(restpe)._2)((ps, t) => ((ps, t))) - // No longer used, see test case neg/t960.scala (#960 has nothing to do with it) - case OverloadedType(_, _) => - OverloadedUnapplyError(fun) - (Nil, ErrorType) - case _ => - UnapplyWithSingleArgError(fun) - (Nil, ErrorType) + def freshArgType(tp: Type): Type = tp match { + case MethodType(param :: _, _) => param.tpe + case PolyType(tparams, restpe) => createFromClonedSymbols(tparams, freshArgType(restpe))(polyType) + case OverloadedType(_, _) => OverloadedUnapplyError(fun) ; ErrorType + case _ => UnapplyWithSingleArgError(fun) ; ErrorType + } + val shape = newExtractorShape(fun, args) + import shape.{ unapplyParamType, unapplyType, unapplyMethod } + + def extractor = extractorForUncheckedType(shape.pos, unapplyParamType) + def canRemedy = unapplyParamType match { + case RefinedType(_, decls) if !decls.isEmpty => false + case RefinedType(parents, _) if parents exists isUncheckable => false + case _ => extractor.nonEmpty } - val unapp = unapplyMember(otpe) - val unappType = otpe.memberType(unapp) - val argDummy = context.owner.newValue(nme.SELECTOR_DUMMY, fun.pos, Flags.SYNTHETIC) setInfo pt - val arg = Ident(argDummy) setType pt - - val uncheckedTypeExtractor = - if (unappType.paramTypes.nonEmpty) - extractorForUncheckedType(tree.pos, unappType.paramTypes.head) - else None - - if (!isApplicableSafe(Nil, unappType, List(pt), WildcardType)) { - //Console.println(s"UNAPP: need to typetest, arg: ${arg.tpe} unappType: $unappType") - val (freeVars, unappFormal) = freshArgType(unappType.skolemizeExistential(context.owner, tree)) + def freshUnapplyArgType(): Type = { + val GenPolyType(freeVars, unappFormal) = freshArgType(unapplyType.skolemizeExistential(context.owner, tree)) val unapplyContext = context.makeNewScope(context.tree, context.owner) freeVars foreach unapplyContext.scope.enter - - val typer1 = newTyper(unapplyContext) - val pattp = typer1.infer.inferTypedPattern(tree, unappFormal, arg.tpe, canRemedy = uncheckedTypeExtractor.nonEmpty) - + val pattp = newTyper(unapplyContext).infer.inferTypedPattern(tree, unappFormal, pt, canRemedy) // turn any unresolved type variables in freevars into existential skolems val skolems = freeVars map (fv => unapplyContext.owner.newExistentialSkolem(fv, fv)) - arg setType pattp.substSym(freeVars, skolems) - argDummy setInfo arg.tpe + pattp.substSym(freeVars, skolems) } + val unapplyArg = ( + context.owner.newValue(nme.SELECTOR_DUMMY, fun.pos, Flags.SYNTHETIC) setInfo ( + if (isApplicableSafe(Nil, unapplyType, pt :: Nil, WildcardType)) pt + else freshUnapplyArgType() + ) + ) // clearing the type is necessary so that ref will be stabilized; see bug 881 - val fun1 = typedPos(fun.pos)(Apply(Select(fun.clearType(), unapp), List(arg))) - - if (fun1.tpe.isErroneous) duplErrTree - else { - val resTp = fun1.tpe.finalResultType.dealiasWiden - val nbSubPats = args.length - val (formals, formalsExpanded) = - extractorFormalTypes(fun0.pos, resTp, nbSubPats, fun1.symbol, treeInfo.effectivePatternArity(args)) - if (formals == null) duplErrorTree(WrongNumberOfArgsError(tree, fun)) - else { - val args1 = typedArgs(args, mode, formals, formalsExpanded) - val pt1 = ensureFullyDefined(pt) // SI-1048 - val itype = glb(List(pt1, arg.tpe)) - arg setType pt1 // restore type (arg is a dummy tree, just needs to pass typechecking) - val unapply = UnApply(fun1, args1) setPos tree.pos setType itype - - // if the type that the unapply method expects for its argument is uncheckable, wrap in classtag extractor - // skip if the unapply's type is not a method type with (at least, but really it should be exactly) one argument - // also skip if we already wrapped a classtag extractor (so we don't keep doing that forever) - if (uncheckedTypeExtractor.isEmpty || fun1.symbol.owner.isNonBottomSubClass(ClassTagClass)) unapply - else wrapClassTagUnapply(unapply, uncheckedTypeExtractor.get, unappType.paramTypes.head) - } + val fun1 = typedPos(fun.pos)(Apply(Select(fun.clearType(), unapplyMethod), Ident(unapplyArg) :: Nil)) + + def makeTypedUnApply() = { + // 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 result = UnApply(fun1, args1) setPos tree.pos setType glbType + + if (wrapInTypeTest) + wrapClassTagUnapply(result, extractor, glbType) + else + result } + + if (fun1.tpe.isErroneous) + duplErrTree + else + makeTypedUnApply() } def wrapClassTagUnapply(uncheckedPattern: Tree, classTagExtractor: Tree, pt: Type): Tree = { @@ -459,28 +442,23 @@ trait PatternTypers { // if there's a ClassTag that allows us to turn the unchecked type test for `pt` into a checked type test // return the corresponding extractor (an instance of ClassTag[`pt`]) - def extractorForUncheckedType(pos: Position, pt: Type): Option[Tree] = if (isPastTyper) None else { - // only look at top-level type, can't (reliably) do anything about unchecked type args (in general) - // but at least make a proper type before passing it elsewhere - val pt1 = pt.dealiasWiden match { - case tr @ TypeRef(pre, sym, args) if args.nonEmpty => copyTypeRef(tr, pre, sym, sym.typeParams map (_.tpeHK)) // replace actual type args with dummies - case pt1 => pt1 - } - pt1 match { - // if at least one of the types in an intersection is checkable, use the checkable ones - // this avoids problems as in run/matchonseq.scala, where the expected type is `Coll with scala.collection.SeqLike` - // Coll is an abstract type, but SeqLike of course is not - case RefinedType(ps, _) if ps.length > 1 && (ps exists infer.isCheckable) => - None - - case ptCheckable if infer isUncheckable ptCheckable => - val classTagExtractor = resolveClassTag(pos, ptCheckable) - - if (classTagExtractor != EmptyTree && unapplyMember(classTagExtractor.tpe) != NoSymbol) - Some(classTagExtractor) - else None - - case _ => None + def extractorForUncheckedType(pos: Position, pt: Type): Tree = { + if (isPastTyper || (pt eq NoType)) EmptyTree else { + pt match { + case RefinedType(parents, decls) if !decls.isEmpty || (parents exists isUncheckable) => return EmptyTree + case _ => + } + // only look at top-level type, can't (reliably) do anything about unchecked type args (in general) + // but at least make a proper type before passing it elsewhere + val pt1 = pt.dealiasWiden match { + case tr @ TypeRef(pre, sym, args) if args.nonEmpty => copyTypeRef(tr, pre, sym, sym.typeParams map (_.tpeHK)) // replace actual type args with dummies + case pt1 => pt1 + } + if (isCheckable(pt1)) EmptyTree + else resolveClassTag(pos, pt1) match { + case tree if unapplyMember(tree.tpe).exists => tree + case _ => devWarning(s"Cannot create runtime type test for $pt1") ; EmptyTree + } } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index a1fb5816b9..fc452db737 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1091,7 +1091,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper else if (mode.typingExprNotFun && treeInfo.isMacroApplication(tree)) macroExpandApply(this, tree, mode, pt) else if (mode.typingConstructorPattern) - adaptConstrPattern(tree, pt) + typedConstructorPattern(tree, pt) else if (shouldInsertApply(tree)) insertApply() else if (hasUndetsInMonoMode) { // (9) @@ -3154,7 +3154,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (noExpectedType) typedArgs(args, forArgMode(fun, mode)) else - typedArgs(args, forArgMode(fun, mode), paramTypes, formals) + typedArgsForFormals(args, paramTypes, forArgMode(fun, mode)) ) // instantiate dependent method types, must preserve singleton types where possible (stableTypeFor) -- example use case: @@ -4937,7 +4937,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // we should get here only when something before failed // and we try again (@see tryTypedApply). In that case we can assign // whatever type to tree; we just have to survive until a real error message is issued. - devWarning(tree.pos, s"Assigning Any type to TypeTree because tree.original == null") + devWarning(tree.pos, s"Assigning Any type to TypeTree because tree.original is null: tree is $tree/${System.identityHashCode(tree)}, sym=${tree.symbol}, tpe=${tree.tpe}") tree setType AnyTpe } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index 18b8f8a9ce..5049fec65b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -30,22 +30,6 @@ trait Unapplies extends ast.TreeDSL { // moduleClass symbol of the companion module. class ClassForCaseCompanionAttachment(val caseClass: ClassDef) - /** returns type list for return type of the extraction - * @see extractorFormalTypes - */ - def unapplyTypeList(pos: Position, ufn: Symbol, ufntpe: Type, args: List[Tree]) = { - assert(ufn.isMethod, ufn) - val nbSubPats = args.length - //Console.println("utl "+ufntpe+" "+ufntpe.typeSymbol) - ufn.name match { - case nme.unapply | nme.unapplySeq => - val (formals, _) = extractorFormalTypes(pos, unapplyUnwrap(ufntpe), nbSubPats, ufn, treeInfo.effectivePatternArity(args)) - if (formals == null) throw new TypeError(s"$ufn of type $ufntpe cannot extract $nbSubPats sub-patterns") - else formals - case _ => throw new TypeError(ufn+" is not an unapply or unapplySeq") - } - } - /** Returns unapply or unapplySeq if available, without further checks. */ def directUnapplyMember(tp: Type): Symbol = (tp member nme.unapply) orElse (tp member nme.unapplySeq) @@ -59,12 +43,6 @@ trait Unapplies extends ast.TreeDSL { def unapply(tp: Type): Option[Symbol] = unapplyMember(tp).toOption } - /** returns unapply member's parameter type. */ - def unapplyParameterType(extractor: Symbol) = extractor.tpe.params match { - case p :: Nil => p.tpe.typeSymbol - case _ => NoSymbol - } - def copyUntyped[T <: Tree](tree: T): T = returning[T](tree.duplicate)(UnTyper traverse _) diff --git a/test/files/neg/t4425.check b/test/files/neg/t4425.check index cb5da6e7dc..95b88a6b3d 100644 --- a/test/files/neg/t4425.check +++ b/test/files/neg/t4425.check @@ -2,4 +2,12 @@ t4425.scala:3: error: object X is not a case class constructor, nor does it have Note: def unapply(x: Int)(y: Option[Int]): None.type exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list 42 match { case _ X _ => () } ^ -one error found +t4425.scala:8: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method +Note: def unapply(x: Int)(y: Int): Some[(Int, Int)] exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list + 42 match { case _ X _ => () } + ^ +t4425.scala:13: error: object X is not a case class constructor, nor does it have an unapply/unapplySeq method +Note: def unapply(x: String)(y: String): Some[(Int, Int)] exists in object X, but it cannot be used as an extractor due to its second non-implicit parameter list + "" match { case _ X _ => () } + ^ +three errors found diff --git a/test/files/neg/t4425.scala b/test/files/neg/t4425.scala index d8cc6922f7..1714955c27 100644 --- a/test/files/neg/t4425.scala +++ b/test/files/neg/t4425.scala @@ -2,3 +2,13 @@ object Foo { object X { def unapply(x : Int)(y : Option[Int] = None) = None } 42 match { case _ X _ => () } } + +object Foo2 { + object X { def unapply(x : Int)(y: Int) = Some((2,2)) } + 42 match { case _ X _ => () } +} + +object Foo3 { + object X { def unapply(x : String)(y: String) = Some((2,2)) } + "" match { case _ X _ => () } +}
\ No newline at end of file diff --git a/test/files/neg/t4425b.check b/test/files/neg/t4425b.check index e43c489586..3af3027da1 100644 --- a/test/files/neg/t4425b.check +++ b/test/files/neg/t4425b.check @@ -22,34 +22,40 @@ 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: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean +t4425b.scala:18: error: wrong number of patterns for object X offering <notype>: expected 1, found 2 println( "" match { case _ X _ => "ok" ; case _ => "fail" }) ^ -t4425b.scala:19: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean +t4425b.scala:19: error: wrong number of patterns for object X offering <notype>: expected 1, found 2 println((X: Any) match { case _ X _ => "ok" ; case _ => "fail" }) ^ -t4425b.scala:20: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean - println( "" match { case X(_) => "ok" ; case _ => "fail" }) - ^ -t4425b.scala:21: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean - println((X: Any) match { case X(_) => "ok" ; case _ => "fail" }) - ^ -t4425b.scala:22: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean +t4425b.scala:22: error: wrong number of patterns for object X offering <notype>: expected 1, found 2 + println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) + ^ +t4425b.scala:22: error: wrong number of patterns for object X offering <notype>: expected 1, found 2 println( "" match { case X(_, _) => "ok" ; case _ => "fail" }) ^ -t4425b.scala:23: error: result type Nothing of unapply defined in method unapply in object X does not conform to Option[_] or Boolean +t4425b.scala:23: error: wrong number of patterns for object X offering <notype>: 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 <notype>: expected 1, found 2 println((X: Any) match { case X(_, _) => "ok" ; case _ => "fail" }) ^ -t4425b.scala:31: error: wrong number of arguments for object X +t4425b.scala:31: error: wrong number of patterns for object X offering Nothing: expected 1, found 2 println( "" match { case _ X _ => "ok" ; case _ => "fail" }) ^ -t4425b.scala:32: error: wrong number of arguments for object X +t4425b.scala:32: 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:35: error: wrong number of arguments for object X +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: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 arguments for object X +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" }) + ^ +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" }) ^ -16 errors found +18 errors found diff --git a/test/files/neg/t6675.check b/test/files/neg/t6675.check index 3a277af866..aecf04cb68 100644 --- a/test/files/neg/t6675.check +++ b/test/files/neg/t6675.check @@ -1,4 +1,4 @@ -t6675.scala:10: warning: extractor pattern binds a single value to a Product3 of type (Int, Int, Int) +t6675.scala:10: warning: object X expects 3 patterns to hold (Int, Int, Int) but crushing into 3-tuple to fit single pattern (SI-6675) "" match { case X(b) => b } // should warn under -Xlint. Not an error because of SI-6111 ^ error: No warnings can be incurred under -Xfatal-warnings. diff --git a/test/files/neg/t997.check b/test/files/neg/t997.check index 186095f44a..be1e92c369 100644 --- a/test/files/neg/t997.check +++ b/test/files/neg/t997.check @@ -1,7 +1,10 @@ -t997.scala:13: error: wrong number of arguments for object Foo +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)) } + ^ +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)) } ^ t997.scala:13: error: not found: value a "x" match { case Foo(a, b, c) => Console.println((a,b,c)) } ^ -two errors found +three errors found diff --git a/test/files/pos/annotated-treecopy/Impls_Macros_1.scala b/test/files/pos/annotated-treecopy/Impls_Macros_1.scala index cf58bc3dfd..ecf8916c46 100644 --- a/test/files/pos/annotated-treecopy/Impls_Macros_1.scala +++ b/test/files/pos/annotated-treecopy/Impls_Macros_1.scala @@ -22,7 +22,7 @@ object Macros { var b1 = new Transformer { override def transform(tree: Tree): Tree = tree match { case Ident(x) if (x==n) => Ident(TermName("_arg")) - case tt @ TypeTree() if tt.original != null => TypeTree(tt.tpe) setOriginal transform(tt.original) + case tt: TypeTree if tt.original != null => TypeTree(tt.tpe) setOriginal transform(tt.original) // without the fix to LazyTreeCopier.Annotated, we would need to uncomment the line below to make the macro work // that's because the pattern match in the input expression gets expanded into Typed(<x>, TypeTree(<Int @unchecked>)) // with the original of the TypeTree being Annotated(<@unchecked>, Ident(<x>)) diff --git a/test/files/run/matchonseq.scala b/test/files/run/matchonseq.scala index 49b406a6ec..f6f320245a 100644 --- a/test/files/run/matchonseq.scala +++ b/test/files/run/matchonseq.scala @@ -1,8 +1,8 @@ -object Test extends App{ - Vector(1,2,3) match { - case head +: tail => println("It worked! head=" + head) +object Test extends App { + Vector(1,2,3) match { + case head +: tail => println("It worked! head=" + head) } - Vector(1,2,3) match { - case init :+ last => println("It worked! last=" + last) + Vector(1,2,3) match { + case init :+ last => println("It worked! last=" + last) } } diff --git a/test/files/run/t7214.scala b/test/files/run/t7214.scala index ff1ea8082d..15c2c24fa0 100644 --- a/test/files/run/t7214.scala +++ b/test/files/run/t7214.scala @@ -25,7 +25,7 @@ class Crash { def unapply(a: Alias): Option[Any] = None } (t: Any) match { - case Extractor() => + case Extractor(_) => case _ => } |