/* NSC -- new Scala compiler
* Copyright 2005-2013 LAMP/EPFL
* @author Paul Phillips
*/
package scala
package tools
package nsc
package typechecker
import scala.collection.mutable
import symtab.Flags
import Mode._
/**
*
* A pattern match such as
*
* x match { case Foo(a, b) => ...}
*
* Might match an instance of any of the following definitions of Foo.
* Note the analogous treatment between case classes and unapplies.
*
* case class Foo(xs: Int*)
* case class Foo(a: Int, xs: Int*)
* case class Foo(a: Int, b: Int)
* case class Foo(a: Int, b: Int, xs: Int*)
*
* object Foo { def unapplySeq(x: Any): Option[Seq[Int]] }
* object Foo { def unapplySeq(x: Any): Option[(Int, Seq[Int])] }
* object Foo { def unapply(x: Any): Option[(Int, Int)] }
* object Foo { def unapplySeq(x: Any): Option[(Int, Int, Seq[Int])] }
*/
trait PatternTypers {
self: Analyzer =>
import global._
import definitions._
// 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
// this is disabled by: interactive compilation (we run it for scaladoc due to SI-5933)
protected def newPatternMatching = true // presently overridden in the presentation compiler
trait PatternTyper {
self: Typer =>
import TyperErrorGen._
import infer._
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)
}
}
loop(args0, formals0, adapted0)
}
/*
* 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).
*
* Consider the following example:
*
* class AbsWrapperCov[+A]
* case class Wrapper[B](x: Wrapped[B]) extends AbsWrapperCov[B]
*
* def unwrap[T](x: AbsWrapperCov[T]): Wrapped[T] = x match {
* case Wrapper(wrapped) => // Wrapper's type parameter must not be assumed to be equal to T, it's *upper-bounded* by it
* wrapped // : Wrapped[_ <: T]
* }
*
* this method should type check if and only if Wrapped is covariant in its type parameter
*
* when inferring Wrapper's type parameter B from x's type AbsWrapperCov[T],
* we must take into account that x's actual type is AbsWrapperCov[Tactual] forSome {type Tactual <: T}
* as AbsWrapperCov is covariant in A -- in other words, we must not assume we know T exactly, all we know is its upper bound
*
* since method application is the only way to generate this slack between run-time and compile-time types (TODO: right!?),
* we can simply replace skolems that represent method type parameters as seen from the method's body
* by other skolems that are (upper/lower)-bounded by that type-parameter skolem
* (depending on the variance position of the skolem in the statically assumed type of the scrutinee, pt)
*
* 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)
}
}
def doTypedUnapply(tree: Tree, fun0: Tree, fun: Tree, args: List[Tree], mode: Mode, pt: Type): Tree = {
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)
}
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))
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)
// 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
}
// 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)
}
}
}
def wrapClassTagUnapply(uncheckedPattern: Tree, classTagExtractor: Tree, pt: Type): Tree = {
// TODO: disable when in unchecked match
// we don't create a new Context for a Match, so find the CaseDef, then go out one level and navigate back to the match that has this case
// val thisCase = context.nextEnclosing(_.tree.isInstanceOf[CaseDef])
// val unchecked = thisCase.outer.tree.collect{case Match(selector, cases) if cases contains thisCase => selector} match {
// case List(Typed(_, tpt)) if tpt.tpe hasAnnotation UncheckedClass => true
// case t => println("outer tree: "+ (t, thisCase, thisCase.outer.tree)); false
// }
// println("wrapClassTagUnapply"+ (!isPastTyper && infer.containsUnchecked(pt), pt, uncheckedPattern))
// println("wrapClassTagUnapply: "+ extractor)
// println(util.Position.formatMessage(uncheckedPattern.pos, "made unchecked type test into a checked one", true))
val args = List(uncheckedPattern)
val app = atPos(uncheckedPattern.pos)(Apply(classTagExtractor, args))
// must call doTypedUnapply directly, as otherwise we get undesirable rewrites
// and re-typechecks of the target of the unapply call in PATTERNmode,
// this breaks down when the classTagExtractor (which defineds the unapply member) is not a simple reference to an object,
// but an arbitrary tree as is the case here
doTypedUnapply(app, classTagExtractor, classTagExtractor, args, PATTERNmode, pt)
}
// 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
}
}
}
}