diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2016-04-15 14:05:02 +1000 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2016-06-03 11:28:08 +1000 |
commit | f07019ffa56ec2dfab8ab0d9a83133005761a877 (patch) | |
tree | 44b0a9ee54c662885ee96ebff80dc8b181976ebf /src/compiler | |
parent | e077c24525bf8f9bd8b73684e630eb7fc6bcb5f6 (diff) | |
download | scala-f07019ffa56ec2dfab8ab0d9a83133005761a877.tar.gz scala-f07019ffa56ec2dfab8ab0d9a83133005761a877.tar.bz2 scala-f07019ffa56ec2dfab8ab0d9a83133005761a877.zip |
SI-9390 Avoid needless outer capture with local classes
An existing optimization in `Constructors` elides the outer
field in member and local classes, if the class doesn't use
the outer reference. (Member classes also need to be final,
which is a secret handshake to say we're also happy to weaken
prefix matching in the pattern matcher.)
That optimization leaves the constructor signature as is: the
constructor still accepts the outer instance, but does not store
it. For member classes, this means that we can separately compile
code that calls the constructor.
Local classes need not be hampered by this constraint, we could
remove the outer instance from the constructor call too.
Why would we want to do this?
Let's look at the case before and after this commit.
Before:
```
class C extends Object {
def foo(): Function1 = $anonfun();
final <static> <artifact> def $anonfun$foo$1($this: C, x: Object): Object = new <$anon: Object>($this);
def <init>(): C = {
C.super.<init>();
()
}
};
final class anon$1 extends Object {
def <init>($outer: C): <$anon: Object> = {
anon$1.super.<init>();
()
}
}
```
After:
```
class C extends Object {
def foo(): Function1 = $anonfun();
final <static> <artifact> def $anonfun$foo$1(x: Object): Object = new <$anon: Object>(null);
def <init>(): C = {
C.super.<init>();
()
}
};
final class anon$1 extends Object {
def <init>($outer: C): <$anon: Object> = {
anon$1.super.<init>();
()
}
}
```
However, the status quo means that a lambda that
This in turn makes lambdas that refer to such classes serializable
even when the outer class is not itself serialiable.
I have not attempted to extend this to calls to secondary constructors.
Diffstat (limited to 'src/compiler')
3 files changed, 12 insertions, 3 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 636fb08b89..971a55f763 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -715,6 +715,9 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { primaryConstrBody.expr) }) + if (omittableAccessor.exists(_.isOuterField) && !constructorStats.exists(_.exists { case i: Ident if i.symbol.isOuterParam => true; case _ => false})) + primaryConstructor.symbol.updateAttachment(OuterArgCanBeElided) + val constructors = primaryConstructor :: auxConstructors // Unlink all fields that can be dropped from class scope diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index a8933a9ee6..2dd8def53e 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -279,10 +279,15 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre if (!dd.symbol.hasFlag(STATIC) && !methodReferencesThis(dd.symbol)) dd.symbol.setFlag(STATIC) super.transform(tree) + case Apply(fun, outer :: rest) if shouldElideOuterArg(fun.symbol, outer) => + val nullOuter = gen.mkZero(outer.tpe) + treeCopy.Apply(tree, transform(fun), nullOuter :: transformTrees(rest)) case _ => super.transform(tree) } } // DelambdafyTransformer + private def shouldElideOuterArg(fun: Symbol, outerArg: Tree): Boolean = + fun.isConstructor && treeInfo.isQualifierSafeToElide(outerArg) && fun.hasAttachment[OuterArgCanBeElided.type] // A traverser that finds symbols used but not defined in the given Tree // TODO freeVarTraverser in LambdaLift does a very similar task. With some @@ -368,6 +373,9 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre case Apply(sel @ Select(This(_), _), args) if sel.symbol.isLiftedMethod => if (currentMethod.exists) liftedMethodReferences(currentMethod) += sel.symbol super.traverseTrees(args) + case Apply(fun, outer :: rest) if shouldElideOuterArg(fun.symbol, outer) => + super.traverse(fun) + super.traverseTrees(rest) case This(_) => if (currentMethod.exists && tree.symbol == currentMethod.enclClass) { debuglog(s"$currentMethod directly refers to 'this'") diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index 3d6fad4238..411ff6b9be 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -67,8 +67,6 @@ abstract class ExplicitOuter extends InfoTransform result } - private val innerClassConstructorParamName: TermName = newTermName("arg" + nme.OUTER) - class RemoveBindingsTransformer(toRemove: Set[Symbol]) extends Transformer { override def transform(tree: Tree) = tree match { case Bind(_, body) if toRemove(tree.symbol) => super.transform(body) @@ -169,7 +167,7 @@ abstract class ExplicitOuter extends InfoTransform val paramsWithOuter = if (sym.isClassConstructor && isInner(sym.owner)) // 1 - sym.newValueParameter(innerClassConstructorParamName, sym.pos).setInfo(sym.owner.outerClass.thisType) :: params + sym.newValueParameter(nme.OUTER_ARG, sym.pos).setInfo(sym.owner.outerClass.thisType) :: params else params if ((resTpTransformed ne resTp) || (paramsWithOuter ne params)) MethodType(paramsWithOuter, resTpTransformed) |