summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2016-04-15 14:05:02 +1000
committerJason Zaugg <jzaugg@gmail.com>2016-06-03 11:28:08 +1000
commitf07019ffa56ec2dfab8ab0d9a83133005761a877 (patch)
tree44b0a9ee54c662885ee96ebff80dc8b181976ebf /src
parente077c24525bf8f9bd8b73684e630eb7fc6bcb5f6 (diff)
downloadscala-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')
-rw-r--r--src/compiler/scala/tools/nsc/transform/Constructors.scala3
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala8
-rw-r--r--src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala4
-rw-r--r--src/reflect/scala/reflect/internal/StdAttachments.scala5
-rw-r--r--src/reflect/scala/reflect/internal/StdNames.scala1
-rw-r--r--src/reflect/scala/reflect/internal/Symbols.scala3
-rw-r--r--src/reflect/scala/reflect/runtime/JavaUniverseForce.scala1
7 files changed, 22 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)
diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala
index ef95b38843..76e34153c9 100644
--- a/src/reflect/scala/reflect/internal/StdAttachments.scala
+++ b/src/reflect/scala/reflect/internal/StdAttachments.scala
@@ -71,4 +71,9 @@ trait StdAttachments {
abstract class InlineAnnotatedAttachment
case object NoInlineCallsiteAttachment extends InlineAnnotatedAttachment
case object InlineCallsiteAttachment extends InlineAnnotatedAttachment
+
+ /** Attached to a local class that has its outer field elided. A `null` constant may be passed
+ * in place of the outer parameter, can help callers to avoid capturing the outer instance.
+ */
+ case object OuterArgCanBeElided extends PlainAttachment
}
diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala
index c93ecac3fa..d96d06ca94 100644
--- a/src/reflect/scala/reflect/internal/StdNames.scala
+++ b/src/reflect/scala/reflect/internal/StdNames.scala
@@ -364,6 +364,7 @@ trait StdNames {
val MODULE_INSTANCE_FIELD: NameType = NameTransformer.MODULE_INSTANCE_NAME // "MODULE$"
val OUTER: NameType = "$outer"
val OUTER_LOCAL: NameType = OUTER.localName
+ val OUTER_ARG: NameType = "arg" + OUTER
val OUTER_SYNTH: NameType = "<outer>" // emitted by virtual pattern matcher, replaced by outer accessor in explicitouter
val ROOTPKG: NameType = "_root_"
val SELECTOR_DUMMY: NameType = "<unapply-selector>"
diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala
index 3b886d357f..e2fb827186 100644
--- a/src/reflect/scala/reflect/internal/Symbols.scala
+++ b/src/reflect/scala/reflect/internal/Symbols.scala
@@ -914,6 +914,9 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
/** Is this symbol an accessor method for outer? */
final def isOuterField = isArtifact && (unexpandedName == nme.OUTER_LOCAL)
+ /** Is this symbol an outer parameter in a constructor */
+ final def isOuterParam = isParameter && owner.isConstructor && (name == nme.OUTER_ARG || name == nme.OUTER)
+
/** Does this symbol denote a stable value, ignoring volatility?
*
* Stability and volatility are checked separately to allow volatile paths in patterns that amount to equality checks. SI-6815
diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
index 28222cf9a7..0a90a141d3 100644
--- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
+++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
@@ -45,6 +45,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
this.SubpatternsAttachment
this.NoInlineCallsiteAttachment
this.InlineCallsiteAttachment
+ this.OuterArgCanBeElided
this.noPrint
this.typeDebug
this.Range