package scala.tools.nsc package typechecker import scala.tools.nsc.symtab.Flags import scala.collection.{mutable, immutable} /** Duplicate trees and re-type check them, taking care to replace * and create fresh symbols for new local definitions. */ abstract class Duplicators extends Analyzer { import global._ def retyped(context: Context, tree: Tree): Tree = { resetClassOwners (new BodyDuplicator(context)).typed(tree) } /** Retype the given tree in the given context. Use this method when retyping * a method in a different class. The typer will replace references to the this of * the old class with the new class, and map symbols through the given 'env'. The * environment is a map from type skolems to concrete types (see SpecializedTypes). */ def retyped(context: Context, tree: Tree, oldThis: Symbol, newThis: Symbol, env: collection.Map[Symbol, Type]): Tree = { if (oldThis ne newThis) { oldClassOwner = oldThis newClassOwner = newThis } else resetClassOwners envSubstitution = new SubstSkolemsTypeMap(env.keysIterator.toList, env.valuesIterator.toList) log("retyped with env: " + env) (new BodyDuplicator(context)).typed(tree) } def retypedMethod(context: Context, tree: Tree, oldThis: Symbol, newThis: Symbol): Tree = (new BodyDuplicator(context)).retypedMethod(tree.asInstanceOf[DefDef], oldThis, newThis) /** Return the special typer for duplicate method bodies. */ override def newTyper(context: Context): Typer = new BodyDuplicator(context) private def resetClassOwners { oldClassOwner = null newClassOwner = null } private var oldClassOwner: Symbol = _ private var newClassOwner: Symbol = _ private var envSubstitution: SubstTypeMap = _ private class SubstSkolemsTypeMap(from: List[Symbol], to: List[Type]) extends SubstTypeMap(from, to) { protected override def matches(sym1: Symbol, sym2: Symbol) = if (sym2.isTypeSkolem) sym2.deSkolemize eq sym1 else sym1 eq sym2 } private val invalidSyms: mutable.Map[Symbol, Tree] = mutable.HashMap.empty[Symbol, Tree] /** A typer that creates new symbols for all definitions in the given tree * and updates references to them while re-typechecking. All types in the * tree, except for TypeTrees, are erased prior to type checking. TypeTrees * are fixed by substituting invalid symbols for the new ones. */ class BodyDuplicator(context: Context) extends Typer(context: Context) { class FixInvalidSyms extends TypeMap { def apply(tpe: Type): Type = { mapOver(tpe) } override def mapOver(tpe: Type): Type = tpe match { case TypeRef(NoPrefix, sym, args) if sym.isTypeParameterOrSkolem => val sym1 = context.scope.lookup(sym.name) // assert(sym1 ne NoSymbol, tpe) if ((sym1 ne NoSymbol) && (sym1 ne sym)) { log("fixing " + sym + " -> " + sym1) typeRef(NoPrefix, sym1, mapOverArgs(args, sym1.typeParams)) } else super.mapOver(tpe) case TypeRef(pre, sym, args) => val newsym = updateSym(sym) if (newsym ne sym) { log("fixing " + sym + " -> " + newsym) typeRef(mapOver(pre), newsym, mapOverArgs(args, newsym.typeParams)) } else super.mapOver(tpe) case _ => super.mapOver(tpe) } } /** Fix the given type by replacing invalid symbols with the new ones. */ def fixType(tpe: Type): Type = { val tpe1 = envSubstitution(tpe) log("tpe1: " + tpe1) (new FixInvalidSyms)(tpe1) } /** Return the new symbol corresponding to `sym'. */ private def updateSym(sym: Symbol): Symbol = if (invalidSyms.isDefinedAt(sym)) invalidSyms(sym).symbol else sym private def invalidate(tree: Tree) { if (tree.isDef && tree.symbol != NoSymbol) { log("invalid " + tree.symbol) invalidSyms(tree.symbol) = tree tree match { case ldef @ LabelDef(name, params, rhs) => log("LabelDef " + name + " sym.info: " + ldef.symbol.info) invalidSyms(ldef.symbol) = ldef // breakIf(true, this, ldef, context) val newsym = ldef.symbol.cloneSymbol(context.owner) newsym.setInfo(fixType(ldef.symbol.info)) ldef.symbol = newsym log("newsym: " + newsym + " info: " + newsym.info) case DefDef(_, _, _, _, _, rhs) => // invalidate parameters invalidate(tree.asInstanceOf[DefDef].tparams) invalidate(List.flatten(tree.asInstanceOf[DefDef].vparamss)) tree.symbol = NoSymbol case _ => tree.symbol = NoSymbol } } } private def invalidate(stats: List[Tree]) { stats foreach invalidate } def retypedMethod(ddef: DefDef, oldThis: Symbol, newThis: Symbol): Tree = { oldClassOwner = oldThis newClassOwner = newThis invalidate(ddef.tparams) for (vdef <- List.flatten(ddef.vparamss)) { invalidate(vdef) vdef.tpe = null } ddef.symbol = NoSymbol enterSym(context, ddef) log("remapping this of " + oldClassOwner + " to " + newClassOwner) typed(ddef) } /** Special typer method allowing for re-type checking trees. It expects a typed tree. * Returns a typed tree that has fresh symbols for all definitions in the original tree. * * Each definition tree is visited and its symbol added to the invalidSyms map (except LabelDefs), * then cleared (forcing the namer to create fresh symbols). * All invalid symbols found in trees are cleared (except for LabelDefs), forcing the * typechecker to look for fresh ones in the context. * * Type trees are typed by substituting old symbols for new ones (@see fixType). * * LabelDefs are not typable from trees alone, unless they have the type ()Unit. Therefore, * their symbols are recreated ad-hoc and their types are fixed inline, instead of letting the * namer/typer handle them, or Idents that refer to them. */ override def typed(tree: Tree, mode: Int, pt: Type): Tree = { log("typing " + tree) if (tree.hasSymbol && tree.symbol != NoSymbol && !tree.symbol.isLabel // labels cannot be retyped by the type checker as LabelDef has no ValDef/return type trees && invalidSyms.isDefinedAt(tree.symbol)) { tree.symbol = NoSymbol } tree match { case ttree @ TypeTree() => log("fixing tpe: " + tree.tpe + " with sym: " + tree.tpe.typeSymbol) ttree.tpe = fixType(ttree.tpe) ttree case Block(stats, res) => log("invalidating block") invalidate(stats) invalidate(res) tree.tpe = null super.typed(tree, mode, pt) case ClassDef(_, _, _, tmpl @ Template(parents, _, stats)) => // log("invalidating classdef " + tree.tpe) tmpl.symbol = tree.symbol.newLocalDummy(tree.pos) invalidate(stats) tree.tpe = null super.typed(tree, mode, pt) case ddef @ DefDef(_, _, _, _, tpt, rhs) => ddef.tpt.tpe = fixType(ddef.tpt.tpe) ddef.tpe = null super.typed(ddef, mode, pt) case vdef @ ValDef(_, _, tpt, rhs) => // log("vdef fixing tpe: " + tree.tpe + " with sym: " + tree.tpe.typeSymbol + " and " + invalidSyms) vdef.tpt.tpe = fixType(vdef.tpt.tpe) vdef.tpe = null super.typed(vdef, mode, pt) case ldef @ LabelDef(name, params, rhs) => ldef.tpe = null val params1 = params map { p => Ident(updateSym(p.symbol)) } super.typed(treeCopy.LabelDef(tree, name, params1, rhs), mode, pt) case Bind(name, _) => invalidate(tree) tree.tpe = null super.typed(tree, mode, pt) case Ident(_) if tree.symbol.isLabel => log("Ident to labeldef " + tree + " switched to ") tree.symbol = updateSym(tree.symbol) tree.tpe = null super.typed(tree, mode, pt) case Select(th @ This(_), sel) if (oldClassOwner ne null) && (th.symbol == oldClassOwner) => log("selection on this, no type ascription required") super.typed(atPos(tree.pos)(Select(This(newClassOwner), sel)), mode, pt) case This(_) if (oldClassOwner ne null) && (tree.symbol == oldClassOwner) => // val tree1 = Typed(This(newClassOwner), TypeTree(fixType(tree.tpe.widen))) val tree1 = This(newClassOwner) log("mapped " + tree + " to " + tree1) super.typed(atPos(tree.pos)(tree1), mode, pt) case Super(qual, mix) if (oldClassOwner ne null) && (tree.symbol == oldClassOwner) => val tree1 = Super(qual, mix) log("changed " + tree + " to " + tree1) super.typed(atPos(tree.pos)(tree1)) case _ => tree.tpe = null super.typed(tree, mode, pt) } } } }