/* 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._
private object FixedAndRepeatedTypes {
def unapply(types: List[Type]) = types match {
case init :+ last if isRepeatedParamType(last) => Some((init, dropRepeated(last)))
case _ => Some((types, NoType))
}
}
trait PatternTyper {
self: Typer =>
import TyperErrorGen._
import infer._
private def unit = context.unit
// 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): Tree = {
// 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)
val canElide = treeInfo.isQualifierSafeToElide(fun)
val caseClass = companionSymbolOf(fun.tpe.typeSymbol.sourceModule, context)
val member = unapplyMember(fun.tpe)
def resultType = (fun.tpe memberType member).finalResultType
def isEmptyType = resultOfMatchingMethod(resultType, nme.isEmpty)()
def isOkay = (
resultType.isErroneous
|| (resultType <:< BooleanTpe)
|| (isEmptyType <:< BooleanTpe)
|| member.isMacro
|| member.isOverloaded // the whole overloading situation is over the rails
)
// 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 may be overloaded with unapply methods in the companion.
if (canElide && caseClass.isCase && !member.isOverloaded)
logResult(s"convertToCaseConstructor($fun, $caseClass, pt=$pt)")(convertToCaseConstructor(fun, caseClass, pt))
else if (!reallyExists(member))
CaseClassConstructorError(fun, s"${fun.symbol} is not a case class, nor does it have an unapply/unapplySeq member")
else if (isOkay)
fun
else if (isEmptyType == NoType)
CaseClassConstructorError(fun, s"an unapply result must have a member `def isEmpty: Boolean")
else
CaseClassConstructorError(fun, s"an unapply result must have a member `def isEmpty: Boolean (found: def isEmpty: $isEmptyType)")
}
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
}
}
private def boundedArrayType(bound: Type): Type = {
val tparam = context.owner.freshExistential("", 0) 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)
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)
}
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 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)
}
}
private class VariantToSkolemMap extends TypeMap(trackVariance = true) {
private val skolemBuffer = mutable.ListBuffer[TypeSymbol]()
// !!! FIXME - skipping this when variance.isInvariant allows unsoundness, see SI-5189
// Test case which presently requires the exclusion is run/gadts.scala.
def eligible(tparam: Symbol) = (
tparam.isTypeParameterOrSkolem
&& tparam.owner.isTerm
&& (settings.strictInference || !variance.isInvariant)
)
def skolems = try skolemBuffer.toList finally skolemBuffer.clear()
def apply(tp: Type): Type = mapOver(tp) match {
case tp @ TypeRef(NoPrefix, tpSym, Nil) if eligible(tpSym) =>
val bounds = (
if (variance.isInvariant) tpSym.tpeHK.bounds
else if (variance.isPositive) TypeBounds.upper(tpSym.tpeHK)
else TypeBounds.lower(tpSym.tpeHK)
)
// origin must be the type param so we can deskolemize
val skolem = context.owner.newGADTSkolem(unit.freshTypeName("?" + tpSym.name), tpSym, bounds)
skolemBuffer += skolem
logResult(s"Created gadt skolem $skolem: ${skolem.tpe_*} to stand in for $tpSym")(skolem.tpe_*)
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).
*
* 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
*/
private def convertToCaseConstructor(tree: Tree, caseClass: Symbol, ptIn: Type): Tree = {
// TODO SI-7886 / SI-5900 This is well intentioned but doesn't quite hit the nail on the head.
// For now, I've put it completely behind -Xstrict-inference.
val untrustworthyPt = settings.strictInference && (
ptIn =:= AnyTpe
|| ptIn =:= NothingTpe
|| ptIn.typeSymbol != caseClass
)
val variantToSkolem = new VariantToSkolemMap
val caseClassType = tree.tpe.prefix memberType caseClass
val caseConstructorType = caseClassType memberType caseClass.primaryConstructor
val tree1 = TypeTree(caseConstructorType) setOriginal tree
val pt = if (untrustworthyPt) caseClassType else ptIn
// 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 = logResult(s"case constructor from (${tree.summaryString}, $caseClassType, $pt)")(variantToSkolem(pt))
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
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
}
}
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) = { context.issue(err); duplErrTree }
if (args.length > MaxTupleArity)
return duplErrorTree(TooManyArgsPatternError(fun))
def freshArgType(tp: Type): Type = tp match {
case MethodType(param :: _, _) => param.tpe
case PolyType(tparams, restpe) => createFromClonedSymbols(tparams, freshArgType(restpe))(genPolyType)
case OverloadedType(_, _) => OverloadedUnapplyError(fun) ; ErrorType
case _ => UnapplyWithSingleArgError(fun) ; ErrorType
}
val unapplyMethod = unapplyMember(fun.tpe)
val unapplyType = fun.tpe memberType unapplyMethod
val unapplyParamType = firstParamType(unapplyType)
def isSeq = unapplyMethod.name == nme.unapplySeq
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
case _ => extractor.nonEmpty
}
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 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))
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()
)
)
val unapplyArgTree = Ident(unapplyArg) updateAttachment SubpatternsAttachment(args)
// clearing the type is necessary so that ref will be stabilized; see bug 881
val fun1 = typedPos(fun.pos)(Apply(Select(fun.clearType(), unapplyMethod), unapplyArgTree :: 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 formals = patmat.alignPatterns(context.asInstanceOf[analyzer.Context], fun1, args).unexpandedFormals
val args1 = typedArgsForFormals(args, formals, 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 if (unapplyMethod.isMacro && !fun1.isInstanceOf[Apply]) {
if (isBlackbox(unapplyMethod)) duplErrorTree(BlackboxExtractorExpansion(tree))
else duplErrorTree(WrongShapeExtractorExpansion(tree))
} else
makeTypedUnapply()
}
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 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 defines the unapply member) is not a simple reference to an object,
// but an arbitrary tree as is the case here
val res = doTypedUnapply(app, classTagExtractor, classTagExtractor, args, PATTERNmode, pt)
log(sm"""
|wrapClassTagUnapply {
| pattern: $uncheckedPattern
| extract: $classTagExtractor
| pt: $pt
| res: $res
|}""".trim)
res
}
// 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): 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
}
}
}
}
}