Skip to content

Commit

Permalink
move more of match translation out of typers
Browse files Browse the repository at this point in the history
reduce duplication in [typed/translated]Match & co

in preparation of moving match translation out of the type checker,
setting everything up so that we can simply type Match nodes first,
then translate them separately

using DefaultOverrideMatchAttachment to remember the default override for a match
that defines a PartialFunction

only strip annotations when translating match
or cps in matches fails

widen selector type when translating

match-derived partialfunction slightly less cps-specific
  • Loading branch information
adriaanm committed May 2, 2012
1 parent f004e99 commit bc860f3
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 79 deletions.
2 changes: 0 additions & 2 deletions src/compiler/scala/reflect/internal/Definitions.scala
Expand Up @@ -412,8 +412,6 @@ trait Definitions extends reflect.api.StandardDefinitions {
lazy val JavaRepeatedParamClass = specialPolyClass(tpnme.JAVA_REPEATED_PARAM_CLASS_NAME, COVARIANT)(tparam => arrayType(tparam.tpe))
lazy val RepeatedParamClass = specialPolyClass(tpnme.REPEATED_PARAM_CLASS_NAME, COVARIANT)(tparam => seqType(tparam.tpe))

lazy val MarkerCPSTypes = getClassIfDefined("scala.util.continuations.cpsParam")

def isByNameParamType(tp: Type) = tp.typeSymbol == ByNameParamClass
def isScalaRepeatedParamType(tp: Type) = tp.typeSymbol == RepeatedParamClass
def isJavaRepeatedParamType(tp: Type) = tp.typeSymbol == JavaRepeatedParamClass
Expand Down
34 changes: 29 additions & 5 deletions src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala
Expand Up @@ -35,6 +35,9 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer =>

val SYNTH_CASE = Flags.CASE | SYNTHETIC

object TranslatedMatchAttachment
case class DefaultOverrideMatchAttachment(default: Tree)

object vpmName {
val one = newTermName("one")
val drop = newTermName("drop")
Expand Down Expand Up @@ -136,15 +139,36 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer =>
* thus, you must typecheck the result (and that will in turn translate nested matches)
* this could probably optimized... (but note that the matchStrategy must be solved for each nested patternmatch)
*/
def translateMatch(scrut: Tree, cases: List[CaseDef], pt: Type, scrutType: Type, matchFailGenOverride: Option[Tree => Tree] = None): Tree = {
def translateMatch(match_ : Match): Tree = {
val Match(selector, cases) = match_

// we don't transform after uncurry
// (that would require more sophistication when generating trees,
// and the only place that emits Matches after typers is for exception handling anyway)
if(phase.id >= currentRun.uncurryPhase.id) debugwarn("running translateMatch at "+ phase +" on "+ scrut +" match "+ cases)
if(phase.id >= currentRun.uncurryPhase.id) debugwarn("running translateMatch at "+ phase +" on "+ selector +" match "+ cases)
// println("translating "+ cases.mkString("{", "\n", "}"))
val scrutSym = freshSym(scrut.pos, pureType(scrutType)) setFlag SYNTH_CASE

def repeatedToSeq(tp: Type): Type = (tp baseType RepeatedParamClass) match {
case TypeRef(_, RepeatedParamClass, arg :: Nil) => seqType(arg)
case _ => tp
}

val selectorTp = repeatedToSeq(elimAnonymousClass(selector.tpe.widen.withoutAnnotations))
val pt0 = match_.tpe

// we've packed the type for each case in typedMatch so that if all cases have the same existential case, we get a clean lub
// here, we should open up the existential again
// relevant test cases: pos/existentials-harmful.scala, pos/gadt-gilles.scala, pos/t2683.scala, pos/virtpatmat_exist4.scala
// TODO: fix skolemizeExistential (it should preserve annotations, right?)
val pt = repeatedToSeq(pt0.skolemizeExistential(context.owner, context.tree) withAnnotations pt0.annotations)

// the alternative to attaching the default case override would be to simply
// append the default to the list of cases and suppress the unreachable case error that may arise (once we detect that...)
val matchFailGenOverride = match_ firstAttachment {case DefaultOverrideMatchAttachment(default) => ((scrut: Tree) => default)}

val selectorSym = freshSym(selector.pos, pureType(selectorTp)) setFlag SYNTH_CASE
// pt = Any* occurs when compiling test/files/pos/annotDepMethType.scala with -Xexperimental
combineCases(scrut, scrutSym, cases map translateCase(scrutSym, pt), pt, matchOwner, matchFailGenOverride)
combineCases(selector, selectorSym, cases map translateCase(selectorSym, pt), pt, matchOwner, matchFailGenOverride)
}

// return list of typed CaseDefs that are supported by the backend (typed/bind/wildcard)
Expand Down Expand Up @@ -1586,7 +1610,7 @@ class Foo(x: Other) { x._1 } // no error in this order
else (REF(scrutSym) DOT (nme.toInt))
Some(BLOCK(
VAL(scrutSym) === scrut,
Match(gen.mkSynthSwitchSelector(scrutToInt), caseDefsWithDefault) // add switch annotation
Match(scrutToInt, caseDefsWithDefault) withAttachment TranslatedMatchAttachment // add switch annotation
))
}
} else None
Expand Down
121 changes: 49 additions & 72 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -2208,44 +2208,26 @@ trait Typers extends Modes with Adaptations with Taggings with PatMatVirtualiser

def adaptCase(cdef: CaseDef, mode: Int, tpe: Type): CaseDef = deriveCaseDef(cdef)(adapt(_, mode, tpe))

def ptOrLub(tps: List[Type], pt: Type ) = if (isFullyDefined(pt)) (pt, false) else weakLub(tps map (_.deconst))
def ptOrLubPacked(trees: List[Tree], pt: Type) = if (isFullyDefined(pt)) (pt, false) else weakLub(trees map (c => packedType(c, context.owner).deconst))

// takes untyped sub-trees of a match and type checks them
def typedMatch(selector0: Tree, cases: List[CaseDef], mode: Int, resTp: Type): Match = {
// strip off the annotation as it won't type check
val (selector, doTranslation) = selector0 match {
case Annotated(Ident(nme.synthSwitch), selector) => (selector, false)
case s => (s, true)
}
val selector1 = checkDead(typed(selector, EXPRmode | BYVALmode, WildcardType))
val selectorTp = packCaptured(selector1.tpe.widen.withoutAnnotations)
def typedMatch(selector: Tree, cases: List[CaseDef], mode: Int, pt: Type, tree: Tree = EmptyTree): Match = {
val selector1 = checkDead(typed(selector, EXPRmode | BYVALmode, WildcardType))
val selectorTp = packCaptured(selector1.tpe.widen)
val casesTyped = typedCases(cases, selectorTp, pt)

val casesTyped = typedCases(cases, selectorTp, resTp)
val caseTypes = casesTyped map (c => packedType(c, context.owner).deconst)
val (ownType, needAdapt) = if (isFullyDefined(resTp)) (resTp, false) else weakLub(caseTypes)
val (resTp, needAdapt) =
if (opt.virtPatmat) ptOrLubPacked(casesTyped, pt)
else ptOrLub(casesTyped map (_.tpe), pt)

val casesAdapted = if (!needAdapt) casesTyped else casesTyped map (adaptCase(_, mode, ownType))
val casesAdapted = if (!needAdapt) casesTyped else casesTyped map (adaptCase(_, mode, resTp))

(selector1, selectorTp, casesAdapted, ownType, doTranslation)
treeCopy.Match(tree, selector1, casesAdapted) setType resTp
}

// match has been typed, now translate it
def translatedMatch(selector1: Tree, selectorTp: Type, casesAdapted: List[CaseDef], ownType: Type, doTranslation: Boolean, matchFailGen: Option[Tree => Tree] = None) = {
def repeatedToSeq(tp: Type): Type = (tp baseType RepeatedParamClass) match {
case TypeRef(_, RepeatedParamClass, arg :: Nil) => seqType(arg)
case _ => tp
}

if (!doTranslation) { // a switch
Match(selector1, casesAdapted) setType ownType // setType of the Match to avoid recursing endlessly
} else {
val scrutType = repeatedToSeq(elimAnonymousClass(selectorTp))
// we've packed the type for each case in typedMatch so that if all cases have the same existential case, we get a clean lub
// here, we should open up the existential again
// relevant test cases: pos/existentials-harmful.scala, pos/gadt-gilles.scala, pos/t2683.scala, pos/virtpatmat_exist4.scala
// TODO: fix skolemizeExistential (it should preserve annotations, right?)
val ownTypeSkolemized = ownType.skolemizeExistential(context.owner, context.tree) withAnnotations ownType.annotations
MatchTranslator(this).translateMatch(selector1, casesAdapted, repeatedToSeq(ownTypeSkolemized), scrutType, matchFailGen)
}
}
def translatedMatch(match_ : Match) = MatchTranslator(this).translateMatch(match_)

// synthesize and type check a (Partial)Function implementation based on a match specified by `cases`
// Match(EmptyTree, cases) ==> new <Partial>Function { def apply<OrElse>(params) = `translateMatch('`(param1,...,paramN)` match { cases }')` }
Expand Down Expand Up @@ -2298,7 +2280,8 @@ trait Typers extends Modes with Adaptations with Taggings with PatMatVirtualiser
val methodBodyTyper = newTyper(context.makeNewScope(context.tree, methodSym)) // should use the DefDef for the context's tree, but it doesn't exist yet (we need the typer we're creating to create it)
paramSyms foreach (methodBodyTyper.context.scope enter _)

val (selector1, selectorTp, casesAdapted, resTp, doTranslation) = methodBodyTyper.typedMatch(selector, cases, mode, ptRes)
val match_ = methodBodyTyper.typedMatch(selector, cases, mode, ptRes)
val resTp = match_.tpe

val methFormals = paramSyms map (_.tpe)
val parents =
Expand All @@ -2308,7 +2291,7 @@ trait Typers extends Modes with Adaptations with Taggings with PatMatVirtualiser
anonClass setInfo ClassInfoType(parents, newScope, anonClass)
methodSym setInfoAndEnter MethodType(paramSyms, resTp)

DefDef(methodSym, methodBodyTyper.translatedMatch(selector1, selectorTp, casesAdapted, resTp, doTranslation))
DefDef(methodSym, methodBodyTyper.translatedMatch(match_))
}
}

Expand Down Expand Up @@ -2337,16 +2320,17 @@ trait Typers extends Modes with Adaptations with Taggings with PatMatVirtualiser
val methodBodyTyper = newTyper(context.makeNewScope(context.tree, methodSym)) // should use the DefDef for the context's tree, but it doesn't exist yet (we need the typer we're creating to create it)
paramSyms foreach (methodBodyTyper.context.scope enter _)

val (selector1, selectorTp, casesAdapted, resTp, doTranslation) = methodBodyTyper.typedMatch(selector, cases, mode, ptRes)
val match_ = methodBodyTyper.typedMatch(selector, cases, mode, ptRes)
val resTp = match_.tpe

anonClass setInfo ClassInfoType(parentsPartial(List(argTp, resTp)), newScope, anonClass)
B1 setInfo TypeBounds.lower(resTp)
anonClass.info.decls enter methodSym // methodSym's info need not change (B1's bound has been updated instead)

// use applyOrElse's first parameter since the scrut's type has been widened
def doDefault(scrut_ignored: Tree) = REF(default) APPLY (REF(x))
match_ setType B1.tpe

val body = methodBodyTyper.translatedMatch(selector1, selectorTp, casesAdapted, B1.tpe, doTranslation, Some(doDefault))
// the default uses applyOrElse's first parameter since the scrut's type has been widened
val body = methodBodyTyper.translatedMatch(match_ withAttachment DefaultOverrideMatchAttachment(REF(default) APPLY (REF(x))))

DefDef(methodSym, body)
}
Expand All @@ -2363,17 +2347,18 @@ trait Typers extends Modes with Adaptations with Taggings with PatMatVirtualiser
paramSyms foreach (methodBodyTyper.context.scope enter _)
methodSym setInfoAndEnter MethodType(paramSyms, BooleanClass.tpe)

val (selector1, selectorTp, casesAdapted, resTp, doTranslation) = methodBodyTyper.typedMatch(selector, casesTrue, mode, BooleanClass.tpe)
val body = methodBodyTyper.translatedMatch(selector1, selectorTp, casesAdapted, resTp, doTranslation, Some(scrutinee => FALSE_typed))
val match_ = methodBodyTyper.typedMatch(selector, casesTrue, mode, BooleanClass.tpe)
val body = methodBodyTyper.translatedMatch(match_ withAttachment DefaultOverrideMatchAttachment(FALSE_typed))

DefDef(methodSym, body)
}
}

val members = if (isPartial) {
// TODO: don't check for MarkerCPSTypes -- check whether all targs are subtype of any (which they are not under CPS)
if ((MarkerCPSTypes ne NoSymbol) && (targs exists (_ hasAnnotation MarkerCPSTypes))) List(applyMethod, isDefinedAtMethod)
else List(applyOrElseMethodDef, isDefinedAtMethod)
// somehow @cps annotations upset the typer when looking at applyOrElse's signature, but not apply's
// TODO: figure out the details (T @cps[U] is not a subtype of Any, but then why does it work for the apply method?)
if (targs forall (_ <:< AnyClass.tpe)) List(applyOrElseMethodDef, isDefinedAtMethod)
else List(applyMethod, isDefinedAtMethod)
} else List(applyMethod)

def translated =
Expand Down Expand Up @@ -3639,8 +3624,6 @@ trait Typers extends Modes with Adaptations with Taggings with PatMatVirtualiser
def isPatternMode = inPatternMode(mode)

//Console.println("typed1("+tree.getClass()+","+Integer.toHexString(mode)+","+pt+")")
def ptOrLub(tps: List[Type]) = if (isFullyDefined(pt)) (pt, false) else weakLub(tps map (_.deconst))

//@M! get the type of the qualifier in a Select tree, otherwise: NoType
def prefixType(fun: Tree): Type = fun match {
case Select(qualifier, _) => qualifier.tpe
Expand Down Expand Up @@ -3830,7 +3813,7 @@ trait Typers extends Modes with Adaptations with Taggings with PatMatVirtualiser
&& thenTp =:= elseTp
) (thenp1.tpe, false) // use unpacked type
// TODO: skolemize (lub of packed types) when that no longer crashes on files/pos/t4070b.scala
else ptOrLub(List(thenp1.tpe, elsep1.tpe))
else ptOrLub(List(thenp1.tpe, elsep1.tpe), pt)

if (needAdapt) { //isNumericValueType(owntype)) {
thenp1 = adapt(thenp1, mode, owntype)
Expand All @@ -3840,34 +3823,28 @@ trait Typers extends Modes with Adaptations with Taggings with PatMatVirtualiser
}
}

def typedTranslatedMatch(tree: Tree, selector: Tree, cases: List[CaseDef]): Tree = {
if (doMatchTranslation) {
if (selector ne EmptyTree) {
val (selector1, selectorTp, casesAdapted, ownType, doTranslation) = typedMatch(selector, cases, mode, pt)
typed(translatedMatch(selector1, selectorTp, casesAdapted, ownType, doTranslation), mode, pt)
} else (new MatchFunTyper(tree, cases, mode, pt)).translated
} else if (selector == EmptyTree) {
if (opt.virtPatmat) debugwarn("virtpatmat should not encounter empty-selector matches "+ tree)
val arity = if (isFunctionType(pt)) pt.normalize.typeArgs.length - 1 else 1
val params = for (i <- List.range(0, arity)) yield
atPos(tree.pos.focusStart) {
ValDef(Modifiers(PARAM | SYNTHETIC),
unit.freshTermName("x" + i + "$"), TypeTree(), EmptyTree)
}
val ids = for (p <- params) yield Ident(p.name)
val selector1 = atPos(tree.pos.focusStart) { if (arity == 1) ids.head else gen.mkTuple(ids) }
val body = treeCopy.Match(tree, selector1, cases)
typed1(atPos(tree.pos) { Function(params, body) }, mode, pt)
} else {
val selector1 = checkDead(typed(selector, EXPRmode | BYVALmode, WildcardType))
var cases1 = typedCases(cases, packCaptured(selector1.tpe.widen), pt)
val (owntype, needAdapt) = ptOrLub(cases1 map (_.tpe))
if (needAdapt) {
cases1 = cases1 map (adaptCase(_, mode, owntype))
def typedTranslatedMatch(tree: Tree, selector: Tree, cases: List[CaseDef]): Tree =
if (selector == EmptyTree) {
if (doMatchTranslation) (new MatchFunTyper(tree, cases, mode, pt)).translated
else {
if (opt.virtPatmat) debugwarn("virtpatmat should not encounter empty-selector matches "+ tree)
val arity = if (isFunctionType(pt)) pt.normalize.typeArgs.length - 1 else 1
val params = for (i <- List.range(0, arity)) yield
atPos(tree.pos.focusStart) {
ValDef(Modifiers(PARAM | SYNTHETIC),
unit.freshTermName("x" + i + "$"), TypeTree(), EmptyTree)
}
val ids = for (p <- params) yield Ident(p.name)
val selector1 = atPos(tree.pos.focusStart) { if (arity == 1) ids.head else gen.mkTuple(ids) }
val body = treeCopy.Match(tree, selector1, cases)
typed1(atPos(tree.pos) { Function(params, body) }, mode, pt)
}
treeCopy.Match(tree, selector1, cases1) setType owntype
} else {
if (!doMatchTranslation || (tree firstAttachment {case TranslatedMatchAttachment => } nonEmpty))
typedMatch(selector, cases, mode, pt, tree)
else
typed(translatedMatch(typedMatch(selector, cases, mode, pt, tree)), mode, pt)
}
}

def typedReturn(expr: Tree) = {
val enclMethod = context.enclMethod
Expand Down Expand Up @@ -4719,7 +4696,7 @@ trait Typers extends Modes with Adaptations with Taggings with PatMatVirtualiser
var catches1 = typedCases(catches, ThrowableClass.tpe, pt)
val finalizer1 = if (finalizer.isEmpty) finalizer
else typed(finalizer, UnitClass.tpe)
val (owntype, needAdapt) = ptOrLub(block1.tpe :: (catches1 map (_.tpe)))
val (owntype, needAdapt) = ptOrLub(block1.tpe :: (catches1 map (_.tpe)), pt)
if (needAdapt) {
block1 = adapt(block1, mode, owntype)
catches1 = catches1 map (adaptCase(_, mode, owntype))
Expand Down

0 comments on commit bc860f3

Please sign in to comment.