aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2016-03-01 16:43:53 +0100
committerMartin Odersky <odersky@gmail.com>2016-03-01 18:46:28 +0100
commite94e8ef5200471a99facd818a6c1a25f994b163d (patch)
treeb9b4c64fe744bcd9808a9861d8d605e0f8a6a43b /src
parent67de3d35019816a1297215770f1a8012c390db0e (diff)
downloaddotty-e94e8ef5200471a99facd818a6c1a25f994b163d.tar.gz
dotty-e94e8ef5200471a99facd818a6c1a25f994b163d.tar.bz2
dotty-e94e8ef5200471a99facd818a6c1a25f994b163d.zip
Handle local traits in lambda lift
Diffstat (limited to 'src')
-rw-r--r--src/dotty/tools/dotc/transform/LambdaLift.scala123
1 files changed, 99 insertions, 24 deletions
diff --git a/src/dotty/tools/dotc/transform/LambdaLift.scala b/src/dotty/tools/dotc/transform/LambdaLift.scala
index 3b86ba13d..480e07339 100644
--- a/src/dotty/tools/dotc/transform/LambdaLift.scala
+++ b/src/dotty/tools/dotc/transform/LambdaLift.scala
@@ -26,6 +26,37 @@ object LambdaLift {
private class NoPath extends Exception
}
+/** This phase performs the necessary rewritings to eliminate classes and methods
+ * nested in other methods. In detail:
+ * 1. It adds all free variables of local functions as additional parameters (proxies).
+ * 2. It rebinds references to free variables to the corresponding proxies,
+ * 3. It lifts all local functions and classes out as far as possible, but at least
+ * to the enclosing class.
+ * 4. It stores free variables of non-trait classes as additional fields of the class.
+ * The fields serve as proxies for methods in the class, which avoids the need
+ * of passing additional parameters to these methods.
+ *
+ * A particularly tricky case are local traits. These cannot store free variables
+ * as field proxies, because LambdaLift runs after Mixin, so the fields cannot be
+ * expanded anymore. Instead, methods of local traits get free variables of
+ * the trait as additional proxy parameters. The difference between local classes
+ * and local traits is illustrated by the two rewritings below.
+ *
+ * def f(x: Int) = { def f(x: Int) = new C(x).f2
+ * class C { ==> class C(x$1: Int) {
+ * def f2 = x def f2 = x$1
+ * } }
+ * new C().f2
+ * }
+ *
+ * def f(x: Int) = { def f(x: Int) = new C().f2(x)
+ * trait T { ==> trait T
+ * def f2 = x def f2(x$1: Int) = x$1
+ * } }
+ * class C extends T class C extends T
+ * new C().f2
+ * }
+ */
class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform =>
import LambdaLift._
import ast.tpd._
@@ -67,9 +98,20 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform
* For methods and classes that do not have any dependencies this will be the enclosing package.
* symbols with packages as lifted owners will subsequently represented as static
* members of their toplevel class, unless their enclosing class was already static.
+ * Note: During tree transform (which runs at phase LambdaLift + 1), liftedOwner
+ * is also used to decide whether a method had a term owner before.
*/
private val liftedOwner = new HashMap[Symbol, Symbol]
+ /** All methods of local traits; these also get augmented with the free variables of
+ * methods enclosing the owning trait, because traits cannot cache free references
+ * like normal classes do.
+ */
+ private val localTraitMethods = new HashSet[Symbol]
+
+ /** The outer parameter of a constructor */
+ private val outerParam = new HashMap[Symbol, Symbol]
+
/** Buffers for lifted out classes and methods, indexed by owner */
private val liftedDefs = new HashMap[Symbol, mutable.ListBuffer[Tree]]
@@ -90,6 +132,12 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform
free.getOrElse(sym, Nil).toList.map(pm)
}
+ /** A symbol is local if it is owned by a term or a local trait */
+ def isLocal(sym: Symbol)(implicit ctx: Context): Boolean = {
+ val owner = sym.maybeOwner
+ owner.isTerm || owner.is(Trait) && isLocal(owner)
+ }
+
/** Set `liftedOwner(sym)` to `owner` if `owner` is more deeply nested
* than the previous value of `liftedowner(sym)`.
*/
@@ -149,26 +197,27 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform
if (enclosure == sym.enclosure) NoSymbol
else {
ctx.log(i"mark free: ${sym.showLocated} with owner ${sym.maybeOwner} marked free in $enclosure")
- ctx.debuglog(i"$enclosure != ${sym.enclosure}")
val intermediate =
if (enclosure.is(PackageClass)) enclosure
else markFree(sym, enclosure.skipConstructor.enclosure)
// `enclosure` might be a constructor, in which case we want the enclosure
// of the enclosing class, so skipConstructor is needed here.
- if (intermediate.exists) {
- narrowLiftedOwner(enclosure, intermediate)
- intermediate
- }
- else {
- narrowLiftedOwner(enclosure, sym.enclosingClass)
- val ss = symSet(free, enclosure)
- if (!ss(sym)) {
- ss += sym
- changedFreeVars = true
- ctx.debuglog(i"$sym is free in $enclosure")
+ narrowLiftedOwner(enclosure, intermediate orElse sym.enclosingClass)
+ if (!intermediate.isClass || intermediate.is(Trait)) {
+ // Methods nested inside traits get the free variables of the enclosing trait.
+ // Conversely, local traits do not get free variables.
+ if (!enclosure.is(Trait)) {
+ val ss = symSet(free, enclosure)
+ if (!ss(sym)) {
+ ss += sym
+ changedFreeVars = true
+ ctx.debuglog(i"$sym is free in $enclosure")
+ }
}
- if (enclosure.isClass) enclosure else NoSymbol
}
+ if (intermediate.exists) intermediate
+ else if (enclosure.isClass) enclosure
+ else NoSymbol
}
} catch {
case ex: NoPath =>
@@ -178,7 +227,7 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform
private def markCalled(callee: Symbol, caller: Symbol)(implicit ctx: Context): Unit = {
ctx.debuglog(i"mark called: $callee of ${callee.owner} is called by $caller")
- assert(callee.skipConstructor.owner.isTerm)
+ assert(isLocal(callee.skipConstructor))
symSet(called, caller) += callee
if (callee.enclosingClass != caller.enclosingClass) calledFromInner += callee
}
@@ -194,18 +243,18 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform
else enclClass) // unknown this reference, play it safe and assume the narrowest possible owner
}
tree match {
- case tree: Ident =>
- if (sym.maybeOwner.isTerm) {
+ case tree: RefTree =>
+ if (isLocal(sym)) {
if (sym is Label)
assert(enclosure == sym.enclosure,
i"attempt to refer to label $sym from nested $enclosure")
else if (sym is Method) markCalled(sym, enclosure)
else if (sym.isTerm) markFree(sym, enclosure)
- } else if (sym.maybeOwner.isClass)
+ } else if (sym.maybeOwner.isClass) {
narrowTo(sym.owner.asClass)
- case tree: Select =>
- if (sym.isConstructor && sym.owner.owner.isTerm)
- markCalled(sym, enclosure)
+ if (sym.isConstructor && sym.owner.owner.isTerm)
+ markCalled(sym, enclosure)
+ }
case tree: This =>
narrowTo(tree.symbol.asClass)
case tree: DefDef =>
@@ -216,8 +265,20 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform
// On the other hand, all other methods will be indirectly owned by their
// top-level class. This avoids possible deadlocks when a static method
// has to access its enclosing object from the outside.
- else if (sym.isPrimaryConstructor && sym.owner.owner.isTerm)
- symSet(called, sym) += sym.owner
+ else if (sym.owner.is(Trait) && isLocal(sym.owner))
+ localTraitMethods += sym
+ if (sym.isConstructor) {
+ if (sym.isPrimaryConstructor && sym.owner.owner.isTerm && !sym.owner.is(Trait))
+ // add a call edge from the constructor of a local non-trait class to
+ // the class itself. This is done so that the constructor inherits
+ // the free variables of the class.
+ symSet(called, sym) += sym.owner
+
+ tree.vparamss.head.find(_.name == nme.OUTER) match {
+ case Some(vdef) => outerParam(sym) = vdef.symbol
+ case _ =>
+ }
+ }
case tree: TypeDef =>
if (sym.owner.isTerm) liftedOwner(sym) = sym.topLevelClass.owner
case tree: Template =>
@@ -251,7 +312,19 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform
for {
caller <- called.keys
callee <- called(caller)
- } narrowLiftedOwner(caller, liftedOwner(callee.skipConstructor))
+ } {
+ val normalizedCallee = callee.skipConstructor
+ val calleeOwner = normalizedCallee.owner
+ if (calleeOwner.isTerm) narrowLiftedOwner(caller, liftedOwner(normalizedCallee))
+ else {
+ assert(calleeOwner.is(Trait))
+ // methods nested inside local trait methods cannot be lifted out
+ // beyond the trait. Note that we can also call a trait method through
+ // a qualifier; in that case no restriction to lifted owner arises.
+ if (caller.isContainedIn(calleeOwner))
+ narrowLiftedOwner(caller, calleeOwner)
+ }
+ }
} while (changedLiftedOwner)
private def newName(sym: Symbol)(implicit ctx: Context): Name =
@@ -312,6 +385,8 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform
member.copySymDenotation(info = linfo).installAfter(thisTransform)
}
}
+ for (local <- localTraitMethods)
+ local.copySymDenotation(info = liftedInfo(local)).installAfter(thisTransform)
}
private def init(implicit ctx: Context) = {
@@ -453,7 +528,7 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform
override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo) = {
val sym = tree.symbol
val proxyHolder = sym.skipConstructor
- if (needsLifting(proxyHolder)) {
+ if (needsLifting(proxyHolder) || localTraitMethods.contains(sym)) {
val paramsAdded = addFreeParams(tree, proxies(proxyHolder)).asInstanceOf[DefDef]
if (sym.isConstructor) paramsAdded else liftDef(paramsAdded)
}