summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/ast/TreeGen.scala
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2016-05-04 21:16:41 +1000
committerJason Zaugg <jzaugg@gmail.com>2016-06-01 11:15:48 +1000
commit0533a3df71e9c855ac68e10d060c2c87d16994e0 (patch)
tree4aa840fb0367b00a0a2dc0c1108b064e7166669c /src/compiler/scala/tools/nsc/ast/TreeGen.scala
parent7b132f39b82e4fc47cd95eadce9e3f22da8c8d82 (diff)
downloadscala-0533a3df71e9c855ac68e10d060c2c87d16994e0.tar.gz
scala-0533a3df71e9c855ac68e10d060c2c87d16994e0.tar.bz2
scala-0533a3df71e9c855ac68e10d060c2c87d16994e0.zip
Lambda impl methods static and more stably named
The body of lambdas is compiled into a synthetic method in the enclosing class. Previously, this method was a public virtual method named `fully$qualified$Class$$anonfun$n`. For lambdas that didn't capture a `this` reference, a static method was used. This commit changes two aspects. Firstly, all lambda impl methods are now emitted static. An extra parameter is added to those that require a this reference. This is an improvement as it: - allows, shorter, more readable names for the lambda impl method - avoids pollution of the vtable of the class. Note that javac uses private instance methods, rather than public static methods. If we followed its lead, we would be unable to support important use cases in our inliner Secondly, the name of the enclosing method has been included in the name of the lambda impl method to improve debuggability and to improve serialization compatibility. The serialization improvement comes from the way that fresh names for the impl methods are allocated: adding or removing lambdas in methods not named "foo" won't change the numbering of the `anonfun$foo$n` impl methods from methods named "foo". This is in line with user expectations about anonymous class and lambda serialization stability. Brian Goetz has described this tricky area well in: http://cr.openjdk.java.net/~briangoetz/eg-attachments/lambda-serialization.html This commit doesn't go as far a Javac, we don't use the hash of the lambda type info, param names, etc to map to a lambda impl method name. As such, we are more prone to the type-1 and -2 failures described there. However, our Scala 2.11.8 has similar characteristics, so we aren't going backwards. Special case in the naming: Use "new" rather than "<init>" for constructor enclosed lambdas, as javac does. I have also changed the way that "delambdafy target" methods are identifed. Rather than relying on the naming convention, I have switched to using a symbol attachment. The assumption is that we only need to identify them from within the same compilation unit. This means we can distinguish impl metbods for expanded functions (ones called from an `apply` method of an ahead-of-time expanded anonfun class), from those that truly end up as targets for lambda metafactory. Only the latter are translated to static methods in this patch.
Diffstat (limited to 'src/compiler/scala/tools/nsc/ast/TreeGen.scala')
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeGen.scala50
1 files changed, 47 insertions, 3 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
index 0786ceb7c2..3dff4a02c9 100644
--- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala
+++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
@@ -238,7 +238,8 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
* (outside the synchronized block).
*
* The idiom works only if the condition is using a volatile field.
- * @see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
+ *
+ * @see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
*/
def mkSynchronizedCheck(clazz: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree =
mkSynchronizedCheck(mkAttributedThis(clazz), cond, syncBody, stats)
@@ -274,8 +275,19 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
}
// used to create the lifted method that holds a function's body
- def mkLiftedFunctionBodyMethod(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) =
- mkMethodForFunctionBody(localTyper)(owner, fun, nme.ANON_FUN_NAME)(additionalFlags = ARTIFACT)
+ def mkLiftedFunctionBodyMethod(localTyper: global.analyzer.Typer)(owner: global.Symbol, fun: global.Function) = {
+ def nonLocalEnclosingMember(sym: Symbol): Symbol = {
+ if (sym.isLocalDummy) sym.enclClass.primaryConstructor
+ else if (sym.isLocalToBlock) nonLocalEnclosingMember(sym.originalOwner)
+ else sym
+ }
+ val ownerName = nonLocalEnclosingMember(fun.symbol.originalOwner).name match {
+ case nme.CONSTRUCTOR => nme.NEWkw // do as javac does for the suffix, prefer "new" to "$lessinit$greater$1"
+ case x => x
+ }
+ val newName = nme.ANON_FUN_NAME.append(nme.NAME_JOIN_STRING).append(ownerName)
+ mkMethodForFunctionBody(localTyper)(owner, fun, newName)(additionalFlags = ARTIFACT)
+ }
/**
@@ -310,6 +322,38 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
newDefDef(methSym, moveToMethod(useMethodParams(fun.body)))(tpt = TypeTree(resTp))
}
+ /**
+ * Create a new `DefDef` based on `orig` with an explicit self parameter.
+ *
+ * Details:
+ * - Must by run after erasure
+ * - If `maybeClone` is the identity function, this runs "in place"
+ * and mutates the symbol of `orig`. `orig` should be discarded
+ * - Symbol owners and returns are substituted, as are parameter symbols
+ * - Recursive calls are not rewritten. This is correct if we assume
+ * that we either:
+ * - are in "in-place" mode, but can guarantee that no recursive calls exists
+ * - are associating the RHS with a cloned symbol, but intend for the original
+ * method to remain and for recursive calls to target it.
+ */
+ final def mkStatic(orig: DefDef, maybeClone: Symbol => Symbol): DefDef = {
+ assert(phase.erasedTypes, phase)
+ assert(!orig.symbol.hasFlag(SYNCHRONIZED), orig.symbol.defString)
+ val origSym = orig.symbol
+ val origParams = orig.symbol.info.params
+ val newSym = maybeClone(orig.symbol)
+ newSym.setFlag(STATIC)
+ // Add an explicit self parameter
+ val selfParamSym = newSym.newSyntheticValueParam(newSym.owner.typeConstructor, nme.SELF)
+ newSym.updateInfo(newSym.info match {
+ case mt @ MethodType(params, res) => copyMethodType(mt, selfParamSym :: params, res)
+ })
+ val selfParam = ValDef(selfParamSym)
+ val rhs = orig.rhs.substituteThis(newSym.owner, atPos(newSym.pos)(gen.mkAttributedIdent(selfParamSym)))
+ .substituteSymbols(origParams, newSym.info.params.drop(1)).changeOwner(origSym -> newSym)
+ treeCopy.DefDef(orig, orig.mods, orig.name, orig.tparams, (selfParam :: orig.vparamss.head) :: Nil, orig.tpt, rhs).setSymbol(newSym)
+ }
+
// TODO: the rewrite to AbstractFunction is superfluous once we compile FunctionN to a SAM type (aka functional interface)
def functionClassType(fun: Function): Type =
if (isFunctionType(fun.tpe)) abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst)