/* NSC -- new Scala compiler * Copyright 2005-2013 LAMP/EPFL * @author Paul Phillips */ package scala package tools package nsc package transform package patmat /** This is scalac-specific logic layered on top of the scalac-agnostic * "matching products to patterns" logic defined in PatternExpander. */ trait ScalacPatternExpanders { val global: Global import global._ import definitions._ import treeInfo._ import analyzer._ type PatternAligned = ScalacPatternExpander#Aligned implicit class AlignedOps(val aligned: PatternAligned) { import aligned._ def expectedTypes = typedPatterns map (_.tpe) def unexpandedFormals = extractor.varargsTypes } trait ScalacPatternExpander extends PatternExpander[Tree, Type] { def NoPattern = EmptyTree def NoType = global.NoType def newPatterns(patterns: List[Tree]): Patterns = patterns match { case init :+ last if isStar(last) => Patterns(init, last) case _ => Patterns(patterns, NoPattern) } def elementTypeOf(tpe: Type) = { val seq = repeatedToSeq(tpe) ( typeOfMemberNamedHead(seq) orElse typeOfMemberNamedApply(seq) orElse definitions.elementType(ArrayClass, seq) ) } def newExtractor(whole: Type, fixed: List[Type], repeated: Repeated, typeOfSinglePattern: Type): Extractor = logResult(s"newExtractor($whole, $fixed, $repeated, $typeOfSinglePattern")(Extractor(whole, fixed, repeated, typeOfSinglePattern)) def newExtractor(whole: Type, fixed: List[Type], repeated: Repeated): Extractor = newExtractor(whole, fixed, repeated, tupleType(fixed)) // Turn Seq[A] into Repeated(Seq[A], A, A*) def repeatedFromSeq(seqType: Type): Repeated = { val elem = elementTypeOf(seqType) val repeated = scalaRepeatedType(elem) Repeated(seqType, elem, repeated) } // Turn A* into Repeated(Seq[A], A, A*) def repeatedFromVarargs(repeated: Type): Repeated = Repeated(repeatedToSeq(repeated), repeatedToSingle(repeated), repeated) /** In this case we are basing the pattern expansion on a case class constructor. * The argument is the MethodType carried by the primary constructor. */ def applyMethodTypes(method: Type): Extractor = { val whole = method.finalResultType method.paramTypes match { case init :+ last if isScalaRepeatedParamType(last) => newExtractor(whole, init, repeatedFromVarargs(last)) case tps => newExtractor(whole, tps, NoRepeated) } } /** In this case, expansion is based on an unapply or unapplySeq method. * Unfortunately the MethodType does not carry the information of whether * it was unapplySeq, so we have to funnel that information in separately. */ def unapplyMethodTypes(context: Context, whole: Type, result: Type, isSeq: Boolean): Extractor = { if (result =:= BooleanTpe) newExtractor(whole, Nil, NoRepeated) else { val getResult = typeOfMemberNamedGet(result) def noGetError() = { val name = "unapply" + (if (isSeq) "Seq" else "") context.error(context.tree.pos, s"The result type of an $name method must contain a member `get` to be used as an extractor pattern, no such member exists in ${result}") } val expanded = getResult match { case global.NoType => noGetError(); Nil case rawGet if !hasSelectors(rawGet) => rawGet :: Nil case rawGet => typesOfSelectors(rawGet) } expanded match { case init :+ last if isSeq => newExtractor(whole, init, repeatedFromSeq(last), getResult) case tps => newExtractor(whole, tps, NoRepeated, getResult) } } } } object alignPatterns extends ScalacPatternExpander { private def validateAligned(context: Context, tree: Tree, aligned: Aligned): Aligned = { import aligned._ def owner = tree.symbol.owner def offering = extractor.offeringString def symString = tree.symbol.fullLocationString def offerString = if (extractor.isErroneous) "" else s" offering $offering" def arityExpected = ( if (extractor.hasSeq) "at least " else "" ) + productArity def err(msg: String) = context.error(tree.pos, msg) def warn(msg: String) = context.warning(tree.pos, msg) def arityError(what: String) = err(s"$what patterns for $owner$offerString: expected $arityExpected, found $totalArity") if (isStar && !isSeq) err("Star pattern must correspond with varargs or unapplySeq") else if (elementArity < 0) arityError("not enough") else if (elementArity > 0 && !isSeq) arityError("too many") else if (settings.warnStarsAlign && isSeq && productArity > 0 && elementArity > 0) warn { if (isStar) "Sequence wildcard (_*) does not align with repeated case parameter or extracted sequence; the result may be unexpected." else "A repeated case parameter or extracted sequence is not matched by a sequence wildcard (_*), and may fail at runtime." } aligned } def apply(context: Context, sel: Tree, args: List[Tree]): Aligned = { val fn = sel match { case Unapplied(fn) => fn case _ => sel } val patterns = newPatterns(args) val isUnapply = sel.symbol.name == nme.unapply val extractor = sel.symbol.name match { case nme.unapply => unapplyMethodTypes(context, firstParamType(fn.tpe), sel.tpe, isSeq = false) case nme.unapplySeq => unapplyMethodTypes(context, firstParamType(fn.tpe), sel.tpe, isSeq = true) case _ => applyMethodTypes(fn.tpe) } /** Rather than let the error that is SI-6675 pollute the entire matching * process, we will tuple the extractor before creation Aligned so that * it contains known good values. */ def productArity = extractor.productArity def acceptMessage = if (extractor.isErroneous) "" else s" to hold ${extractor.offeringString}" val requiresTupling = isUnapply && patterns.totalArity == 1 && productArity > 1 val normalizedExtractor = if (requiresTupling) { val tupled = extractor.asSinglePattern if (effectivePatternArity(args) == 1 && isTupleType(extractor.typeOfSinglePattern)) { val sym = sel.symbol.owner currentRun.reporting.deprecationWarning(sel.pos, sym, s"${sym} expects $productArity patterns$acceptMessage but crushing into $productArity-tuple to fit single pattern (SI-6675)", "2.11.0") } tupled } else extractor validateAligned(context, fn, Aligned(patterns, normalizedExtractor)) } def apply(context: Context, tree: Tree): Aligned = tree match { case Apply(fn, args) => apply(context, fn, args) case UnApply(fn, args) => apply(context, fn, args) } } }