package dotty.tools.dotc package transform import TreeTransforms._ import core.DenotTransformers._ import core.Symbols._ import core.Contexts._ import core.Types._ import core.Flags._ import core.Decorators._ import core.SymDenotations._ import core.StdNames.nme import core.Names._ import core.NameOps._ import core.NameKinds.TempResultName import ast.Trees._ import SymUtils._ import collection.{ mutable, immutable } import collection.mutable.{ LinkedHashMap, LinkedHashSet, TreeSet } class CapturedVars extends MiniPhase with IdentityDenotTransformer { thisTransform => import ast.tpd._ /** the following two members override abstract members in Transform */ val phaseName: String = "capturedVars" val treeTransform = new Transform(Set()) private class RefInfo(implicit ctx: Context) { /** The classes for which a Ref type exists. */ val refClassKeys: collection.Set[Symbol] = defn.ScalaNumericValueClasses() + defn.BooleanClass + defn.ObjectClass val refClass: Map[Symbol, Symbol] = refClassKeys.map(rc => rc -> ctx.requiredClass(s"scala.runtime.${rc.name}Ref")).toMap val volatileRefClass: Map[Symbol, Symbol] = refClassKeys.map(rc => rc -> ctx.requiredClass(s"scala.runtime.Volatile${rc.name}Ref")).toMap val boxedRefClasses: collection.Set[Symbol] = refClassKeys.flatMap(k => Set(refClass(k), volatileRefClass(k))) } class Transform(captured: collection.Set[Symbol]) extends TreeTransform { def phase = thisTransform private var myRefInfo: RefInfo = null private def refInfo(implicit ctx: Context) = { if (myRefInfo == null) myRefInfo = new RefInfo() myRefInfo } private class CollectCaptured(implicit ctx: Context) extends EnclosingMethodTraverser { private val captured = mutable.HashSet[Symbol]() def traverse(enclMeth: Symbol, tree: Tree)(implicit ctx: Context) = tree match { case id: Ident => val sym = id.symbol if (sym.is(Mutable, butNot = Method) && sym.owner.isTerm && sym.enclosingMethod != enclMeth) { ctx.log(i"capturing $sym in ${sym.enclosingMethod}, referenced from $enclMeth") captured += sym } case _ => foldOver(enclMeth, tree) } def runOver(tree: Tree): collection.Set[Symbol] = { apply(NoSymbol, tree) captured } } override def prepareForUnit(tree: Tree)(implicit ctx: Context) = { val captured = (new CollectCaptured)(ctx.withPhase(thisTransform)) .runOver(ctx.compilationUnit.tpdTree) new Transform(captured) } /** The {Volatile|}{Int|Double|...|Object}Ref class corresponding to the class `cls`, * depending on whether the reference should be @volatile */ def refClass(cls: Symbol, isVolatile: Boolean)(implicit ctx: Context): Symbol = { val refMap = if (isVolatile) refInfo.volatileRefClass else refInfo.refClass if (cls.isClass) { refMap.getOrElse(cls, refMap(defn.ObjectClass)) } else refMap(defn.ObjectClass) } override def prepareForValDef(vdef: ValDef)(implicit ctx: Context) = { val sym = vdef.symbol if (captured contains sym) { val newd = sym.denot(ctx.withPhase(thisTransform)).copySymDenotation( info = refClass(sym.info.classSymbol, sym.hasAnnotation(defn.VolatileAnnot)).typeRef, initFlags = sym.flags &~ Mutable) newd.removeAnnotation(defn.VolatileAnnot) newd.installAfter(thisTransform) } this } override def transformValDef(vdef: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = { val vble = vdef.symbol if (captured contains vble) { def boxMethod(name: TermName): Tree = ref(vble.info.classSymbol.companionModule.info.member(name).symbol) cpy.ValDef(vdef)( rhs = vdef.rhs match { case EmptyTree => boxMethod(nme.zero).appliedToNone.withPos(vdef.pos) case arg => boxMethod(nme.create).appliedTo(arg) }, tpt = TypeTree(vble.info).withPos(vdef.tpt.pos)) } else vdef } override def transformIdent(id: Ident)(implicit ctx: Context, info: TransformerInfo): Tree = { val vble = id.symbol if (captured(vble)) (id select nme.elem).ensureConforms(vble.denot(ctx.withPhase(thisTransform)).info) else id } /** If assignment is to a boxed ref type, e.g. * * intRef.elem = expr * * rewrite using a temporary var to * * val ev$n = expr * intRef.elem = ev$n * * That way, we avoid the problem that `expr` might contain a `try` that would * run on a non-empty stack (which is illegal under JVM rules). Note that LiftTry * has already run before, so such `try`s would not be eliminated. * * Also: If the ref type lhs is followed by a cast (can be an artifact of nested translation), * drop the cast. */ override def transformAssign(tree: Assign)(implicit ctx: Context, info: TransformerInfo): Tree = { def recur(lhs: Tree): Tree = lhs match { case TypeApply(Select(qual, nme.asInstanceOf_), _) => val Select(_, nme.elem) = qual recur(qual) case Select(_, nme.elem) if refInfo.boxedRefClasses.contains(lhs.symbol.maybeOwner) => val tempDef = transformFollowing(SyntheticValDef(TempResultName.fresh(), tree.rhs)) transformFollowing(Block(tempDef :: Nil, cpy.Assign(tree)(lhs, ref(tempDef.symbol)))) case _ => tree } recur(tree.lhs) } } }