From e5aafda8ee452ec9d3b42b8b5fa022a3124134d9 Mon Sep 17 00:00:00 2001 From: Dmitry Petrashko Date: Tue, 9 Sep 2014 15:44:56 +0200 Subject: Pattern expanders for patmat --- .../tools/dotc/transform/PatternMatcher.scala | 143 ++++++++++++++++++++- 1 file changed, 141 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/transform/PatternMatcher.scala b/src/dotty/tools/dotc/transform/PatternMatcher.scala index 9e4ec2af2..5ca66b021 100644 --- a/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -939,7 +939,7 @@ class PatternMatcher extends MiniPhaseTransform { trait MatchTranslator extends TreeMakers { - def isBackquoted(x: Ident) = false //x.hasAttachment[BackquotedIdentifierAttachment.type] + def isBackquoted(x: Ident) = x.isInstanceOf[BackquotedIdent] def isVarPattern(pat: Tree): Boolean = pat match { case x: Ident => !isBackquoted(x) && nme.isVariableName(x.name) @@ -954,7 +954,7 @@ class PatternMatcher extends MiniPhaseTransform { case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol! //case Star(WildcardPattern()) => true // dd todo:? case x: Ident => isVarPattern(x) - case Alternative(ps) => ps.forall(x => unapply(x)) + case Alternative(ps) => ps forall unapply case EmptyTree => true case _ => false } @@ -1654,4 +1654,143 @@ class PatternMatcher extends MiniPhaseTransform { |}""".stripMargin.trim } } + + /** This is scalac-specific logic layered on top of the scalac-agnostic + * "matching products to patterns" logic defined in PatternExpander. + */ + trait ScalacPatternExpanders { + + 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 = 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): Extractor = + logResult(s"newExtractor($whole, $fixed, $repeated")(Extractor(whole, fixed, repeated)) + + // 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(method: Type, isSeq: Boolean): Extractor = { + val whole = firstParamType(method) + val result = method.finalResultType + val expanded = ( + if (result =:= BooleanTpe) Nil + else typeOfMemberNamedGet(result) match { + case rawGet if !hasSelectors(rawGet) => rawGet :: Nil + case rawGet => typesOfSelectors(rawGet) + } + ) + expanded match { + case init :+ last if isSeq => newExtractor(whole, init, repeatedFromSeq(last)) + case tps => newExtractor(whole, tps, NoRepeated) + } + } + } + object alignPatterns extends ScalacPatternExpander { + /** Converts a T => (A, B, C) extractor to a T => ((A, B, CC)) extractor. + */ + def tupleExtractor(extractor: Extractor): Extractor = + extractor.copy(fixed = tupleType(extractor.fixed) :: Nil) + + private def validateAligned(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) = currentUnit.error(tree.pos, msg) + def warn(msg: String) = currentUnit.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 && !extractor.hasSeq) + arityError("too many") + + aligned + } + + def apply(sel: Tree, args: List[Tree]): Aligned = { + val fn = sel match { + case Unapplied(fn) => fn + case _ => sel + } + val patterns = newPatterns(args) + val isSeq = sel.symbol.name == nme.unapplySeq + val isUnapply = sel.symbol.name == nme.unapply + val extractor = sel.symbol.name match { + case nme.unapply => unapplyMethodTypes(fn.tpe, isSeq = false) + case nme.unapplySeq => unapplyMethodTypes(fn.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 + + if (requiresTupling && effectivePatternArity(args) == 1) + currentUnit.deprecationWarning(sel.pos, s"${sel.symbol.owner} expects $productArity patterns$acceptMessage but crushing into $productArity-tuple to fit single pattern (SI-6675)") + + val normalizedExtractor = if (requiresTupling) tupleExtractor(extractor) else extractor + validateAligned(fn, Aligned(patterns, normalizedExtractor)) + } + + def apply(tree: Tree): Aligned = tree match { + case Apply(fn, args) => apply(fn, args) + case UnApply(fn, args) => apply(fn, args) + } + } + } } \ No newline at end of file -- cgit v1.2.3