Skip to content

Commit 91f88b6

Browse files
committed
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 not emitted static. For those that require a this reference, an extra parameter is added. 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 will 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.
1 parent fef591d commit 91f88b6

8 files changed

+55
-21
lines changed

src/compiler/scala/tools/nsc/ast/TreeGen.scala

+13-2
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,19 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
274274
}
275275

276276
// used to create the lifted method that holds a function's body
277-
def mkLiftedFunctionBodyMethod(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) =
278-
mkMethodForFunctionBody(localTyper)(owner, fun, nme.ANON_FUN_NAME)(additionalFlags = ARTIFACT)
277+
def mkLiftedFunctionBodyMethod(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) = {
278+
def nonLocalEnclosingMember(sym: Symbol) = {
279+
if (sym.isLocalDummy) sym.enclClass.primaryConstructor else {
280+
if (sym.isLocalToBlock) sym.originalOwner else sym
281+
}
282+
}
283+
val ownerName = nonLocalEnclosingMember(fun.symbol.originalOwner).name match {
284+
case nme.CONSTRUCTOR => nme.NEWkw // do as javac does for the suffix, prefer "new" to "$lessinit$greater$1"
285+
case x => x
286+
}
287+
val newName = nme.ANON_FUN_NAME.append(nme.NAME_JOIN_STRING).append(ownerName)
288+
mkMethodForFunctionBody(localTyper)(owner, fun, newName)(additionalFlags = ARTIFACT)
289+
}
279290

280291

281292
/**

src/compiler/scala/tools/nsc/transform/Delambdafy.scala

+29-8
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,18 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
6161

6262
private def mkLambdaMetaFactoryCall(fun: Function, target: Symbol, functionalInterface: Symbol, samUserDefined: Symbol, isSpecialized: Boolean): Tree = {
6363
val pos = fun.pos
64+
def isSelfParam(p: Symbol) = p.isSynthetic && p.name == nme.SELF
65+
val hasSelfParam = isSelfParam(target.firstParam)
66+
6467
val allCapturedArgRefs = {
6568
// find which variables are free in the lambda because those are captures that need to be
6669
// passed into the constructor of the anonymous function class
6770
val captureArgs = FreeVarTraverser.freeVarsOf(fun).iterator.map(capture =>
6871
gen.mkAttributedRef(capture) setPos pos
6972
).toList
7073

71-
if (target hasFlag STATIC) captureArgs // no `this` reference needed
74+
if (!hasSelfParam) captureArgs.filterNot(arg => isSelfParam(arg.symbol))
75+
else if (currentMethod.hasFlag(Flags.STATIC)) captureArgs
7276
else (gen.mkAttributedThis(fun.symbol.enclClass) setPos pos) :: captureArgs
7377
}
7478

@@ -179,7 +183,7 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
179183
val numCaptures = targetParams.length - functionParamTypes.length
180184
val (targetCapturedParams, targetFunctionParams) = targetParams.splitAt(numCaptures)
181185

182-
val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT)
186+
val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT | STATIC)
183187
val bridgeCapturedParams = targetCapturedParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName))
184188
val bridgeFunctionParams =
185189
map2(targetFunctionParams, bridgeParamTypes)((param, tp) => methSym.newSyntheticValueParam(tp, param.name.toTermName))
@@ -223,10 +227,8 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
223227

224228
private def transformFunction(originalFunction: Function): Tree = {
225229
val target = targetMethod(originalFunction)
226-
target.makeNotPrivate(target.owner)
227-
228-
// must be done before calling createBoxingBridgeMethod and mkLambdaMetaFactoryCall
229-
if (!(target hasFlag STATIC) && !methodReferencesThis(target)) target setFlag STATIC
230+
assert(target.hasFlag(Flags.STATIC))
231+
target.setFlag(notPRIVATE)
230232

231233
val funSym = originalFunction.tpe.typeSymbolDirect
232234
// The functional interface that can be used to adapt the lambda target method `target` to the given function type.
@@ -252,11 +254,30 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
252254
// here's the main entry point of the transform
253255
override def transform(tree: Tree): Tree = tree match {
254256
// the main thing we care about is lambdas
255-
case fun: Function => super.transform(transformFunction(fun))
257+
case fun: Function =>
258+
super.transform(transformFunction(fun))
256259
case Template(_, _, _) =>
260+
def pretransform(tree: Tree): Tree = tree match {
261+
case dd: DefDef if dd.symbol.isDelambdafyTarget =>
262+
if (!dd.symbol.hasFlag(Flags.STATIC) && methodReferencesThis(dd.symbol)) {
263+
dd.symbol.setFlag(Flags.STATIC)
264+
val selfParamSym = dd.symbol.newSyntheticValueParam(dd.symbol.owner.typeConstructor, nme.SELF)
265+
dd.symbol.updateInfo(dd.symbol.info match {
266+
case mt @ MethodType(params, res) => copyMethodType(mt, selfParamSym :: params, res)
267+
})
268+
val selfParam = ValDef(selfParamSym)
269+
val rhs = dd.rhs.substituteThis(dd.symbol.owner, atPos(dd.symbol.pos)(gen.mkAttributedIdent(selfParamSym)))
270+
val result = treeCopy.DefDef(dd, dd.mods, dd.name, dd.tparams, (selfParam :: dd.vparamss.head) :: Nil, dd.tpt, rhs)
271+
result
272+
} else {
273+
dd.symbol setFlag STATIC
274+
dd
275+
}
276+
case t => t
277+
}
257278
try {
258279
// during this call boxingBridgeMethods will be populated from the Function case
259-
val Template(parents, self, body) = super.transform(tree)
280+
val Template(parents, self, body) = super.transform(deriveTemplate(tree)(_.mapConserve(pretransform)))
260281
Template(parents, self, body ++ boxingBridgeMethods)
261282
} finally boxingBridgeMethods.clear()
262283
case _ => super.transform(tree)

test/files/run/delambdafy_t6028.check

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ package <empty> {
1111
def foo(methodParam: String): Function0 = {
1212
val methodLocal: String = "";
1313
{
14-
(() => T.this.$anonfun$1(methodParam, methodLocal))
14+
(() => T.this.$anonfun$foo$1(methodParam, methodLocal))
1515
}
1616
};
1717
def bar(barParam: String): Object = {
@@ -21,10 +21,10 @@ package <empty> {
2121
def tryy(tryyParam: String): Function0 = {
2222
var tryyLocal: runtime.ObjectRef = scala.runtime.ObjectRef.create("");
2323
{
24-
(() => T.this.$anonfun$2(tryyParam, tryyLocal))
24+
(() => T.this.$anonfun$tryy$1(tryyParam, tryyLocal))
2525
}
2626
};
27-
final <artifact> private[this] def $anonfun$1(methodParam$1: String, methodLocal$1: String): String = T.this.classParam.+(T.this.field()).+(methodParam$1).+(methodLocal$1);
27+
final <artifact> private[this] def $anonfun$foo$1(methodParam$1: String, methodLocal$1: String): String = T.this.classParam.+(T.this.field()).+(methodParam$1).+(methodLocal$1);
2828
abstract trait MethodLocalTrait$1 extends Object {
2929
def /*MethodLocalTrait$1*/$init$(barParam$1: String): Unit = {
3030
()
@@ -54,7 +54,7 @@ package <empty> {
5454
T.this.MethodLocalObject$lzycompute$1(barParam$1, MethodLocalObject$module$1)
5555
else
5656
MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]();
57-
final <artifact> private[this] def $anonfun$2(tryyParam$1: String, tryyLocal$1: runtime.ObjectRef): Unit = try {
57+
final <artifact> private[this] def $anonfun$tryy$1(tryyParam$1: String, tryyLocal$1: runtime.ObjectRef): Unit = try {
5858
tryyLocal$1.elem = tryyParam$1
5959
} finally ()
6060
}

test/files/run/delambdafy_t6555.check

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ package <empty> {
66
()
77
};
88
private[this] val f: String => String = {
9-
final <artifact> def $anonfun(param: String): String = param;
10-
((param: String) => $anonfun(param))
9+
final <artifact> def $anonfun$f(param: String): String = param;
10+
((param: String) => $anonfun$f(param))
1111
};
1212
<stable> <accessor> def f(): String => String = Foo.this.f
1313
}

test/files/run/delambdafy_uncurry_byname_method.check

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ package <empty> {
77
};
88
def bar(x: () => String): String = x.apply();
99
def foo(): String = Foo.this.bar({
10-
final <artifact> def $anonfun(): String = "";
11-
(() => $anonfun())
10+
final <artifact> def $anonfun$foo(): String = "";
11+
(() => $anonfun$foo())
1212
})
1313
}
1414
}

test/files/run/delambdafy_uncurry_method.check

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ package <empty> {
77
};
88
def bar(): Unit = {
99
val f: Int => Int = {
10-
final <artifact> def $anonfun(x: Int): Int = x.+(1);
11-
((x: Int) => $anonfun(x))
10+
final <artifact> def $anonfun|(x: Int): Int = x.+(1);
11+
((x: Int) => $anonfun|(x))
1212
};
1313
()
1414
}

test/files/run/mixin-signatures.check

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ interface Foo1 {
6060
generic: public abstract R Base.f(T)
6161
public default java.lang.String Foo1.f(java.lang.Object)
6262
generic: public default java.lang.String Foo1.f(T)
63+
public static java.lang.String Foo1.f(Foo1,java.lang.Object) <synthetic>
6364
public abstract java.lang.Object Base.g(java.lang.Object)
6465
generic: public abstract R Base.g(T)
6566
public abstract java.lang.String Foo1.g(java.lang.Object)
@@ -73,6 +74,7 @@ interface Foo2 {
7374
generic: public abstract R Base.f(T)
7475
public default java.lang.Object Foo2.f(java.lang.String)
7576
generic: public default R Foo2.f(java.lang.String)
77+
public static java.lang.Object Foo2.f(Foo2,java.lang.String) <synthetic>
7678
public abstract java.lang.Object Base.g(java.lang.Object)
7779
generic: public abstract R Base.g(T)
7880
public abstract java.lang.Object Foo2.g(java.lang.String)

test/files/run/t9097.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ object Test extends StoreReporterDirectTest {
2828
assert(!storeReporter.hasErrors, message = filteredInfos map (_.msg) mkString "; ")
2929
val out = baos.toString("UTF-8")
3030
// was 2 before the fix, the two PackageDefs for a would both contain the ClassDef for the closure
31-
assert(out.lines.count(_ contains "def $anonfun$1(x$1: Int): String") == 1, out)
31+
assert(out.lines.count(_ contains "def $anonfun$hihi$1(x$1: Int): String") == 1, out)
3232
}
3333
}

0 commit comments

Comments
 (0)