summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/typechecker/PatternTypers.scala
blob: f09c142aef8bb6d0cc457ce3596ef9fa6f7090d7 (plain) (tree)






























































































































































































































































































































                                                                                                                                                                                     
/* 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
      }
    }
  }
}