From 44bdec1a44db7ac880183e0c70b5f5668048961e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 6 Sep 2014 05:06:16 +0200 Subject: Rename OuterAccessors to ExplicitOuter Better to keep the old name for easy cross-referencing with Scala 2. --- src/dotty/tools/dotc/Compiler.scala | 2 +- src/dotty/tools/dotc/TypeErasure.scala | 2 +- src/dotty/tools/dotc/transform/Erasure.scala | 2 +- src/dotty/tools/dotc/transform/ExplicitOuter.scala | 256 +++++++++++++++++++++ .../tools/dotc/transform/OuterAccessors.scala | 247 -------------------- 5 files changed, 259 insertions(+), 250 deletions(-) create mode 100644 src/dotty/tools/dotc/transform/ExplicitOuter.scala delete mode 100644 src/dotty/tools/dotc/transform/OuterAccessors.scala diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 060f1fcbd..377f9fbdc 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -54,7 +54,7 @@ class Compiler { new ElimRepeated, new ElimLocals), List(new ExtensionMethods), - List(new TailRec, new OuterAccessors), + List(new TailRec, new ExplicitOuter), List(new PatternMatcher, // new LazyValTranformContext().transformer, // disabled, awaiting fixes new Splitter), diff --git a/src/dotty/tools/dotc/TypeErasure.scala b/src/dotty/tools/dotc/TypeErasure.scala index 3b7dd7460..d46c64006 100644 --- a/src/dotty/tools/dotc/TypeErasure.scala +++ b/src/dotty/tools/dotc/TypeErasure.scala @@ -3,7 +3,7 @@ package dotc package core import Symbols._, Types._, Contexts._, Flags._, Names._, StdNames._, Decorators._, Flags.JavaDefined -import dotc.transform.OuterAccessors._ +import dotc.transform.ExplicitOuter._ import util.DotClass /** Erased types are: diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index a5679d598..82acb482d 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -24,7 +24,7 @@ import scala.collection.mutable.ListBuffer import dotty.tools.dotc.core.Flags import ValueClasses._ import TypeUtils._ -import OuterAccessors._ +import ExplicitOuter._ import typer.Mode class Erasure extends Phase with DenotTransformer { thisTransformer => diff --git a/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/src/dotty/tools/dotc/transform/ExplicitOuter.scala new file mode 100644 index 000000000..0a1c94dd1 --- /dev/null +++ b/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -0,0 +1,256 @@ +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.StdNames.nme +import core.Names._ +import core.NameOps._ +import ast.Trees._ +import SymUtils._ +import util.Attachment +import collection.mutable + +/** This phase adds outer accessors to classes and traits that need them. + * Compared to Scala 2.x, it tries to minimize the set of classes + * that take outer accessors and also tries to minimize the number + * of objects referred to by outer accessors. This helps prevent space + * leaks. + * + * The following things are delayed until erasure and are performed + * by class OuterOps: + * + * - add outer parameters to constructors + * - pass outer arguments in constructor calls + * - replace outer this by outer paths. + */ +class ExplicitOuter extends MiniPhaseTransform with InfoTransformer { thisTransformer => + import ExplicitOuter._ + import ast.tpd._ + + val Outer = new Attachment.Key[Tree] + + override def phaseName: String = "ExplicitOuter" + + override def treeTransformPhase = thisTransformer.next + + /** Add outer accessors if a class always needs an outer pointer */ + override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { + case tp @ ClassInfo(_, cls, _, decls, _) if needsOuterAlways(cls) => + val newDecls = decls.cloneScope + newExplicitOuter(cls).foreach(newDecls.enter) + tp.derivedClassInfo(decls = newDecls) + case _ => + tp + } + + /** A new outer accessor or param accessor */ + private def newOuterSym(owner: ClassSymbol, cls: ClassSymbol, name: TermName, flags: FlagSet)(implicit ctx: Context) = { + ctx.newSymbol(owner, name, Synthetic | flags, cls.owner.enclosingClass.typeRef, coord = cls.coord) + } + + /** A new outer accessor for class `cls` which is a member of `owner` */ + private def newOuterAccessor(owner: ClassSymbol, cls: ClassSymbol)(implicit ctx: Context) = { + val deferredIfTrait = if (cls.is(Trait)) Deferred else EmptyFlags + val outerAccIfOwn = if (owner == cls) OuterAccessor else EmptyFlags + newOuterSym(owner, cls, outerAccName(cls), + Final | Method | Stable | outerAccIfOwn | deferredIfTrait) + } + + /** A new param accessor for the outer field in class `cls` */ + private def newOuterParamAccessor(cls: ClassSymbol)(implicit ctx: Context) = + newOuterSym(cls, cls, nme.OUTER, Private | ParamAccessor) + + /** The outer accessor and potentially outer param accessor needed for class `cls` */ + private def newExplicitOuter(cls: ClassSymbol)(implicit ctx: Context) = + newOuterAccessor(cls, cls) :: (if (cls is Trait) Nil else newOuterParamAccessor(cls) :: Nil) + + /** First, add outer accessors if a class does not have them yet and it references an outer this. + * If the class has outer accessors, implement them. + * Furthermore, if a parent trait might have outer accessors (decided by needsOuterIfReferenced), + * provide an implementation for the outer accessor by computing the parent's + * outer from the parent type prefix. If the trait ends up not having an outer accessor + * after all, the implementation is redundant, but does not harm. + * The same logic is not done for non-trait parent classes because for them the outer + * pointer is passed in the super constructor, which will be implemented later in + * a separate phase which needs to run after erasure. However, we make sure here + * that the super class constructor is indeed a New, and not just a type. + */ + override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { + val cls = ctx.owner.asClass + val isTrait = cls.is(Trait) + if (needsOuterIfReferenced(cls) && !needsOuterAlways(cls) && referencesOuter(cls, impl)) + newExplicitOuter(cls).foreach(_.enteredAfter(thisTransformer)) + if (hasOuter(cls)) { + val newDefs = new mutable.ListBuffer[Tree] + if (isTrait) + newDefs += DefDef(outerAccessor(cls).asTerm, EmptyTree) + else { + val outerParamAcc = outerParamAccessor(cls) + newDefs += ValDef(outerParamAcc, EmptyTree) + newDefs += DefDef(outerAccessor(cls).asTerm, ref(outerParamAcc)) + } + val parents1 = + for (parent <- impl.parents) yield { + val parentCls = parent.tpe.classSymbol.asClass + if (parentCls.is(Trait)) { + if (needsOuterIfReferenced(parentCls)) { + val outerAccImpl = newOuterAccessor(cls, parentCls).enteredAfter(thisTransformer) + newDefs += DefDef(outerAccImpl, singleton(outerPrefix(parent.tpe))) + } + parent + } + else parent match { // ensure class parent is a constructor + case parent: TypeTree => New(parent.tpe, Nil).withPos(impl.pos) + case _ => parent + } + } + cpy.Template(impl)(parents = parents1, body = impl.body ++ newDefs) + } + else impl + } +} + +object ExplicitOuter { + import ast.tpd._ + + private val LocalInstantiationSite = Module | Private + + private def outerAccName(cls: ClassSymbol)(implicit ctx: Context): TermName = + nme.OUTER.expandedName(cls) + + /** Class needs an outer pointer, provided there is a reference to an outer this in it. */ + private def needsOuterIfReferenced(cls: ClassSymbol)(implicit ctx: Context): Boolean = !( + cls.isStatic || + cls.owner.enclosingClass.isStaticOwner || + cls.is(Interface) + ) + + /** Class unconditionally needs an outer pointer. This is the case if + * the class needs an outer pointer if referenced and one of the following holds: + * - we might not know at all instantiation sites whether outer is referenced or not + * - we need to potentially pass along outer to a parent class or trait + */ + private def needsOuterAlways(cls: ClassSymbol)(implicit ctx: Context): Boolean = + needsOuterIfReferenced(cls) && + (!hasLocalInstantiation(cls) || // needs outer because we might not know whether outer is referenced or not + cls.classInfo.parents.exists(parent => // needs outer to potentially pass along to parent + needsOuterIfReferenced(parent.classSymbol.asClass))) + + /** Class is always instantiated in the compilation unit where it is defined */ + private def hasLocalInstantiation(cls: ClassSymbol)(implicit ctx: Context): Boolean = + cls.owner.isTerm || cls.is(LocalInstantiationSite) + + /** The outer parameter accessor of cass `cls` */ + private def outerParamAccessor(cls: ClassSymbol)(implicit ctx: Context): TermSymbol = + cls.info.decl(nme.OUTER).symbol.asTerm + + /** The outer access of class `cls` */ + private def outerAccessor(cls: ClassSymbol)(implicit ctx: Context): Symbol = + cls.info.member(outerAccName(cls)).suchThat(_ is OuterAccessor).symbol orElse + cls.info.decls.find(_ is OuterAccessor).getOrElse(NoSymbol) + + /** Class has an outer accessor. Can be called only after phase ExplicitOuter. */ + private def hasOuter(cls: ClassSymbol)(implicit ctx: Context): Boolean = + needsOuterIfReferenced(cls) && outerAccessor(cls).exists + + /** Template `impl` of class `cls` references an outer this which goes to + * a class that is not a static owner. + */ + private def referencesOuter(cls: ClassSymbol, impl: Template)(implicit ctx: Context): Boolean = + existsSubTreeOf(impl) { + case thisTree @ This(_) => + val thisCls = thisTree.symbol + thisCls != cls && !thisCls.isStaticOwner && cls.isContainedIn(thisCls) + case _ => + false + } + + /** The outer prefix implied by type `tpe` */ + private def outerPrefix(tpe: Type)(implicit ctx: Context): Type = tpe match { + case tpe: TypeRef => + tpe.symbol match { + case cls: ClassSymbol => + if (tpe.prefix eq NoPrefix) cls.owner.enclosingClass.thisType + else tpe.prefix + case _ => + outerPrefix(tpe.underlying) + } + case tpe: TypeProxy => + outerPrefix(tpe.underlying) + } + + def outer(implicit ctx: Context): OuterOps = new OuterOps(ctx) + + /** The operations in this class + * - add outer parameters + * - pass outer arguments to these parameters + * - replace outer this references by outer paths. + * They are called from erasure. There are two constraints which + * suggest these operations should be done in erasure. + * - Replacing this references with outer paths loses aliasing information, + * so programs will not typecheck with unerased types unless a lot of type + * refinements are added. Therefore, outer paths should be computed no + * earlier than erasure. + * - outer parameters should not show up in signatures, so again + * they cannot be added before erasure. + * - outer arguments need access to outer parameters as well as to the + * original type prefixes of types in New expressions. These prefixes + * get erased during erasure. Therefore, outer argumenrts have to be passed + * no later than erasure. + */ + class OuterOps(val ictx: Context) extends AnyVal { + private implicit def ctx: Context = ictx + + /** If `cls` has an outer parameter add one to the method type `tp`. */ + def addParam(cls: ClassSymbol, tp: Type): Type = + if (hasOuter(cls)) { + val mt @ MethodType(pnames, ptypes) = tp + mt.derivedMethodType( + nme.OUTER :: pnames, cls.owner.enclosingClass.typeRef :: ptypes, mt.resultType) + } else tp + + /** If function in an apply node is a constructor that needs to be passed an + * outer argument, the singleton list with the argument, otherwise Nil. + */ + def args(fun: Tree): List[Tree] = { + if (fun.symbol.isConstructor) { + val cls = fun.symbol.owner.asClass + def outerArg(receiver: Tree): Tree = receiver match { + case New(tpt) => TypeTree(outerPrefix(tpt.tpe)).withPos(tpt.pos) + case This(_) => ref(outerParamAccessor(cls)) + case TypeApply(Select(r, nme.asInstanceOf_), args) => outerArg(r) // cast was inserted, skip + } + if (hasOuter(cls)) + methPart(fun) match { + case Select(receiver, _) => outerArg(receiver) :: Nil + } + else Nil + } else Nil + } + + /** The path of outer accessors that references `toCls.this` starting from + * the context owner's this node. + */ + def path(toCls: Symbol): Tree = try { + def loop(tree: Tree): Tree = { + val treeCls = tree.tpe.widen.classSymbol + ctx.log(i"outer to $toCls of $tree: ${tree.tpe}, looking for ${outerAccName(treeCls.asClass)}") + if (treeCls == toCls) tree + else loop(tree select outerAccessor(treeCls.asClass)) + } + ctx.log(i"computing outerpath to $toCls from ${ctx.outersIterator.map(_.owner).toList}") + loop(This(ctx.owner.enclosingClass.asClass)) + } catch { + case ex: ClassCastException => + throw new ClassCastException(i"no path exists from ${ctx.owner.enclosingClass} to $toCls") + case ex: AssertionError => + throw new ClassCastException(i"no path exists from ${ctx.owner.enclosingClass} to $toCls\n because ${ex.getMessage}") + } + } +} \ No newline at end of file diff --git a/src/dotty/tools/dotc/transform/OuterAccessors.scala b/src/dotty/tools/dotc/transform/OuterAccessors.scala deleted file mode 100644 index 74371486c..000000000 --- a/src/dotty/tools/dotc/transform/OuterAccessors.scala +++ /dev/null @@ -1,247 +0,0 @@ -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.StdNames.nme -import core.Names._ -import core.NameOps._ -import ast.Trees._ -import SymUtils._ -import util.Attachment -import collection.mutable - -/** This phase decorates News and parent constructors of non-static inner classes - * with an attachment indicating the outer reference as a tree. This is necessary because - * outer prefixes are erased, and explicit outer runs only after erasure. - */ -class OuterAccessors extends MiniPhaseTransform with InfoTransformer { thisTransformer => - import OuterAccessors._ - import ast.tpd._ - - val Outer = new Attachment.Key[Tree] - - override def phaseName: String = "outerAccessors" - - override def treeTransformPhase = thisTransformer.next - - /** Add outer accessors if a class always needs an outer pointer */ - override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context) = tp match { - case tp @ ClassInfo(_, cls, _, decls, _) if needsOuterAlways(cls) => - val newDecls = decls.cloneScope - newOuterAccessors(cls).foreach(newDecls.enter) - tp.derivedClassInfo(decls = newDecls) - case _ => - tp - } - - /** A new outer accessor or param accessor */ - private def newOuterSym(owner: ClassSymbol, cls: ClassSymbol, name: TermName, flags: FlagSet)(implicit ctx: Context) = { - ctx.newSymbol(owner, name, Synthetic | flags, cls.owner.enclosingClass.typeRef, coord = cls.coord) - } - - /** A new outer accessor for class `cls` which is a member of `owner` */ - private def newOuterAccessor(owner: ClassSymbol, cls: ClassSymbol)(implicit ctx: Context) = { - val deferredIfTrait = if (cls.is(Trait)) Deferred else EmptyFlags - val outerAccIfOwn = if (owner == cls) OuterAccessor else EmptyFlags - newOuterSym(owner, cls, outerAccName(cls), - Final | Method | Stable | outerAccIfOwn | deferredIfTrait) - } - - /** A new param accessor for the outer field in class `cls` */ - private def newOuterParamAccessor(cls: ClassSymbol)(implicit ctx: Context) = - newOuterSym(cls, cls, nme.OUTER, Private | ParamAccessor) - - /** The outer accessor and potentially outer param accessor needed for class `cls` */ - private def newOuterAccessors(cls: ClassSymbol)(implicit ctx: Context) = - newOuterAccessor(cls, cls) :: (if (cls is Trait) Nil else newOuterParamAccessor(cls) :: Nil) - - /** First, add outer accessors if a class does not have them yet and it references an outer this. - * If the class has outer accessors, implement them. - * Furthermore, if a parent trait might have outer accessors (decided by needsOuterIfReferenced), - * provide an implementation for the outer accessor by computing the parent's - * outer from the parent type prefix. If the trait ends up not having an outer accessor - * after all, the implementation is redundant, but does not harm. - * The same logic is not done for non-trait parent classes because for them the outer - * pointer is passed in the super constructor, which will be implemented later in - * a separate phase which needs to run after erasure. However, we make sure here - * that the super class constructor is indeed a New, and not just a type. - */ - override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { - val cls = ctx.owner.asClass - val isTrait = cls.is(Trait) - if (needsOuterIfReferenced(cls) && !needsOuterAlways(cls) && referencesOuter(cls, impl)) - newOuterAccessors(cls).foreach(_.enteredAfter(thisTransformer)) - if (hasOuter(cls)) { - val newDefs = new mutable.ListBuffer[Tree] - if (isTrait) - newDefs += DefDef(outerAccessor(cls).asTerm, EmptyTree) - else { - val outerParamAcc = outerParamAccessor(cls) - newDefs += ValDef(outerParamAcc, EmptyTree) - newDefs += DefDef(outerAccessor(cls).asTerm, ref(outerParamAcc)) - } - val parents1 = - for (parent <- impl.parents) yield { - val parentCls = parent.tpe.classSymbol.asClass - if (parentCls.is(Trait)) { - if (needsOuterIfReferenced(parentCls)) { - val outerAccImpl = newOuterAccessor(cls, parentCls).enteredAfter(thisTransformer) - newDefs += DefDef(outerAccImpl, singleton(outerPrefix(parent.tpe))) - } - parent - } - else parent match { // ensure class parent is a constructor - case parent: TypeTree => New(parent.tpe, Nil).withPos(impl.pos) - case _ => parent - } - } - cpy.Template(impl)(parents = parents1, body = impl.body ++ newDefs) - } - else impl - } -} - -object OuterAccessors { - import ast.tpd._ - - private val LocalInstantiationSite = Module | Private - - private def outerAccName(cls: ClassSymbol)(implicit ctx: Context): TermName = - nme.OUTER.expandedName(cls) - - /** Class needs an outer pointer, provided there is a reference to an outer this in it. */ - private def needsOuterIfReferenced(cls: ClassSymbol)(implicit ctx: Context): Boolean = !( - cls.isStatic || - cls.owner.enclosingClass.isStaticOwner || - cls.is(Interface) - ) - - /** Class unconditionally needs an outer pointer. This is the case if - * the class needs an outer pointer if referenced and one of the following holds: - * - we might not know at all instantiation sites whether outer is referenced or not - * - we need to potentially pass along outer to a parent class or trait - */ - private def needsOuterAlways(cls: ClassSymbol)(implicit ctx: Context): Boolean = - needsOuterIfReferenced(cls) && - (!hasLocalInstantiation(cls) || // needs outer because we might not know whether outer is referenced or not - cls.classInfo.parents.exists(parent => // needs outer to potentially pass along to parent - needsOuterIfReferenced(parent.classSymbol.asClass))) - - /** Class is always instantiated in the compilation unit where it is defined */ - private def hasLocalInstantiation(cls: ClassSymbol)(implicit ctx: Context): Boolean = - cls.owner.isTerm || cls.is(LocalInstantiationSite) - - /** The outer parameter accessor of cass `cls` */ - private def outerParamAccessor(cls: ClassSymbol)(implicit ctx: Context): TermSymbol = - cls.info.decl(nme.OUTER).symbol.asTerm - - /** The outer access of class `cls` */ - private def outerAccessor(cls: ClassSymbol)(implicit ctx: Context): Symbol = - cls.info.member(outerAccName(cls)).suchThat(_ is OuterAccessor).symbol orElse - cls.info.decls.find(_ is OuterAccessor).getOrElse(NoSymbol) - - /** Class has an outer accessor. Can be called only after phase OuterAccessors. */ - private def hasOuter(cls: ClassSymbol)(implicit ctx: Context): Boolean = - needsOuterIfReferenced(cls) && outerAccessor(cls).exists - - /** Template `impl` of class `cls` references an outer this which goes to - * a class that is not a static owner. - */ - private def referencesOuter(cls: ClassSymbol, impl: Template)(implicit ctx: Context): Boolean = - existsSubTreeOf(impl) { - case thisTree @ This(_) => - val thisCls = thisTree.symbol - thisCls != cls && !thisCls.isStaticOwner && cls.isContainedIn(thisCls) - case _ => - false - } - - /** The outer prefix implied by type `tpe` */ - private def outerPrefix(tpe: Type)(implicit ctx: Context): Type = tpe match { - case tpe: TypeRef => - tpe.symbol match { - case cls: ClassSymbol => - if (tpe.prefix eq NoPrefix) cls.owner.enclosingClass.thisType - else tpe.prefix - case _ => - outerPrefix(tpe.underlying) - } - case tpe: TypeProxy => - outerPrefix(tpe.underlying) - } - - def outer(implicit ctx: Context): OuterOps = new OuterOps(ctx) - - /** The operations in this class - * - add outer parameters - * - pass outer arguments to these parameters - * - replace outer this references by outer paths. - * They are called from erasure. There are two constraints which - * suggest these operations should be done in erasure. - * - Replacing this references with outer paths loses aliasing information, - * so programs will not typecheck with unerased types unless a lot of type - * refinements are added. Therefore, outer paths should be computed no - * earlier than erasure. - * - outer parameters should not show up in signatures, so again - * they cannot be added before erasure. - * - outer arguments need access to outer parameters as well as to the - * original type prefixes of types in New expressions. These prefixes - * get erased during erasure. Therefore, outer argumenrts have to be passed - * no later than erasure. - */ - class OuterOps(val ictx: Context) extends AnyVal { - private implicit def ctx: Context = ictx - - /** If `cls` has an outer parameter add one to the method type `tp`. */ - def addParam(cls: ClassSymbol, tp: Type): Type = - if (hasOuter(cls)) { - val mt @ MethodType(pnames, ptypes) = tp - mt.derivedMethodType( - nme.OUTER :: pnames, cls.owner.enclosingClass.typeRef :: ptypes, mt.resultType) - } else tp - - /** If function in an apply node is a constructor that needs to be passed an - * outer argument, the singleton list with the argument, otherwise Nil. - */ - def args(fun: Tree): List[Tree] = { - if (fun.symbol.isConstructor) { - val cls = fun.symbol.owner.asClass - def outerArg(receiver: Tree): Tree = receiver match { - case New(tpt) => TypeTree(outerPrefix(tpt.tpe)).withPos(tpt.pos) - case This(_) => ref(outerParamAccessor(cls)) - case TypeApply(Select(r, nme.asInstanceOf_), args) => outerArg(r) // cast was inserted, skip - } - if (hasOuter(cls)) - methPart(fun) match { - case Select(receiver, _) => outerArg(receiver) :: Nil - } - else Nil - } else Nil - } - - /** The path of outer accessors that references `toCls.this` starting from - * the context owner's this node. - */ - def path(toCls: Symbol): Tree = try { - def loop(tree: Tree): Tree = { - val treeCls = tree.tpe.widen.classSymbol - ctx.log(i"outer to $toCls of $tree: ${tree.tpe}, looking for ${outerAccName(treeCls.asClass)}") - if (treeCls == toCls) tree - else loop(tree select outerAccessor(treeCls.asClass)) - } - ctx.log(i"computing outerpath to $toCls from ${ctx.outersIterator.map(_.owner).toList}") - loop(This(ctx.owner.enclosingClass.asClass)) - } catch { - case ex: ClassCastException => - throw new ClassCastException(i"no path exists from ${ctx.owner.enclosingClass} to $toCls") - case ex: AssertionError => - throw new ClassCastException(i"no path exists from ${ctx.owner.enclosingClass} to $toCls\n because ${ex.getMessage}") - } - } -} \ No newline at end of file -- cgit v1.2.3