Skip to content

Commit 453df6b

Browse files
Backport "Make more anonymous functions static" to LTS (#20799)
Backports #19251 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents d4a1742 + 26442fd commit 453df6b

File tree

2 files changed

+55
-8
lines changed

2 files changed

+55
-8
lines changed

compiler/src/dotty/tools/dotc/transform/Dependencies.scala

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package dotty.tools.dotc
1+
package dotty.tools
2+
package dotc
23
package transform
34

45
import core.*
@@ -180,26 +181,47 @@ abstract class Dependencies(root: ast.tpd.Tree, @constructorOnly rootContext: Co
180181
if enclClass.isContainedIn(thisClass) then thisClass
181182
else enclClass) // unknown this reference, play it safe and assume the narrowest possible owner
182183

184+
/** Set the first owner of a local method or class that's nested inside a term.
185+
* This is either the enclosing package or the enclosing class. If the former,
186+
* the method will be be translated to a static method of its toplevel class.
187+
* In that case, we might later re-adjust the owner to a nested class via
188+
* `narrowTo` when we see that the method refers to the this-type of that class.
189+
* We choose the enclosing package when there's something potentially to gain from this
190+
* and when it is safe to do so
191+
*/
183192
def setLogicOwner(local: Symbol) =
184193
val encClass = local.owner.enclosingClass
194+
// When to prefer the enclosing class over the enclosing package:
185195
val preferEncClass =
186-
(
187196
encClass.isStatic
188-
// non-static classes can capture owners, so should be avoided
197+
// If class is not static, we try to hoist the method out of
198+
// the class to avoid the outer pointer.
189199
&& (encClass.isProperlyContainedIn(local.topLevelClass)
190-
// can be false for symbols which are defined in some weird combination of supercalls.
200+
// If class is nested in an outer object, we prefer to leave the method in the class,
201+
// since putting it in the outer object makes access more complicated
191202
|| encClass.is(ModuleClass, butNot = Package)
192-
// needed to not cause deadlocks in classloader. see t5375.scala
203+
// If class is an outermost object we also want to avoid making the
204+
// method static since that could cause deadlocks in interacting
205+
// with class initialization. See deadlock.scala
193206
)
194-
)
195-
|| (
207+
&& (!sym.isAnonymousFunction || sym.owner.ownersIterator.exists(_.isConstructor))
208+
// The previous conditions mean methods in static objects and nested static classes
209+
// don't get lifted out to be static. In general it is prudent to do that. However,
210+
// for anonymous functions, we prefer them to be static because that means lambdas
211+
// are memoized and can be serialized even if the enclosing object or class
212+
// is not serializable. See run/lambda-serialization-gc.scala and run/i19224.scala.
213+
// On the other hand, we don't want to lift anonymous functions from inside the
214+
// object or class constructor to be static since that can cause again deadlocks
215+
// by its interaction with class initialization. See run/deadlock.scala, which works
216+
// in Scala 3 but deadlocks in Scala 2.
217+
||
196218
/* Scala.js: Never move any member beyond the boundary of a DynamicImportThunk.
197219
* DynamicImportThunk subclasses are boundaries between the eventual ES modules
198220
* that can be dynamically loaded. Moving members across that boundary changes
199221
* the dynamic and static dependencies between ES modules, which is forbidden.
200222
*/
201223
ctx.settings.scalajs.value && encClass.isSubClass(jsdefn.DynamicImportThunkClass)
202-
)
224+
203225
logicOwner(sym) = if preferEncClass then encClass else local.enclosingPackageClass
204226

205227
tree match

tests/run/i19224.scala

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// scalajs: --skip
2+
3+
object Test extends App {
4+
val field = 1
5+
def x(): Int => String = (i: Int) => i.toString
6+
def y(): () => String = () => field.toString
7+
8+
locally {
9+
assert(x() == x()) // true on Scala 2, was false on Scala 3...
10+
assert(y() == y()) // also true if `y` accesses object-local fields
11+
12+
def z(): Int => String = (i: Int) => i.toString
13+
assert(z() != z()) // lambdas in constructor are not lifted to static, so no memoization (Scala 2 lifts them, though).
14+
}
15+
16+
val t1 = new C
17+
val t2 = new C
18+
19+
locally {
20+
assert(t1.x() == t2.x()) // true on Scala 2, was false on Scala 3...
21+
}
22+
}
23+
class C {
24+
def x(): Int => String = (i: Int) => i.toString
25+
}

0 commit comments

Comments
 (0)