@@ -248,6 +248,138 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
248
248
)(withNewLocalNameScope(body))
249
249
}
250
250
251
+ // Global state for tracking methods that reference `this` -----------------
252
+
253
+ /* scalac generates private instance methods for
254
+ * a) local defs and
255
+ * b) anonymous functions.
256
+ *
257
+ * In many cases, these methods do not need access to the enclosing `this`
258
+ * value. For every other local variable, `lambdalift` only adds them as
259
+ * arguments when they are needed; but `this` is always implicitly added
260
+ * because all such methods are instance methods.
261
+ *
262
+ * We run a separate analysis to figure out which of those methods actually
263
+ * need their `this` value. We compile those that do not as static methods,
264
+ * as if they were `isStaticMember`.
265
+ *
266
+ * The `delambdafy` phase of scalac performs a similar analysis, although
267
+ * as it runs after our backend (for other reasons), we do not benefit from
268
+ * it. Our analysis is much simpler because it deals with local defs and
269
+ * anonymous functions in a unified way.
270
+ *
271
+ * Performing this analysis is particularly important for lifted methods
272
+ * that appear within arguments to a super constructor call. The lexical
273
+ * scope of Scala guarantees that they cannot use `this`, but if they are
274
+ * compiled as instance methods, they force the constructor to leak the
275
+ * `this` value before the super constructor call, which is invalid.
276
+ * While most of the time this analysis is only an optimization, in those
277
+ * (rare) situations it is required for correctness. See for example
278
+ * the partest `run/t8733.scala`.
279
+ */
280
+
281
+ private var statifyCandidateMethodsThatReferenceThis : scala.collection.Set [Symbol ] = null
282
+
283
+ /** Is that given method a statify-candidate?
284
+ *
285
+ * If a method is a statify-candidate, we will analyze whether it actually
286
+ * needs its `this` value. If it does not, we will compile it as a static
287
+ * method in the IR.
288
+ *
289
+ * A method is a statify-candidate if it is a lifted method (a local def)
290
+ * or the target of an anonymous function.
291
+ *
292
+ * TODO Currently we also require that the method owner not be a JS type.
293
+ * We should relax that restriction in the future.
294
+ */
295
+ private def isStatifyCandidate (sym : Symbol ): Boolean =
296
+ (sym.isLiftedMethod || sym.isDelambdafyTarget) && ! isJSType(sym.owner)
297
+
298
+ /** Do we compile the given method as a static method in the IR?
299
+ *
300
+ * This is true if one of the following is true:
301
+ *
302
+ * - It is `isStaticMember`, or
303
+ * - It is a statify-candidate method and it does not reference `this`.
304
+ */
305
+ private def compileAsStaticMethod (sym : Symbol ): Boolean = {
306
+ sym.isStaticMember || {
307
+ isStatifyCandidate(sym) &&
308
+ ! statifyCandidateMethodsThatReferenceThis.contains(sym)
309
+ }
310
+ }
311
+
312
+ /** Finds all statify-candidate methods that reference `this`, inspired by Delambdafy. */
313
+ private final class ThisReferringMethodsTraverser extends Traverser {
314
+ // the set of statify-candidate methods that directly refer to `this`
315
+ private val roots = mutable.Set .empty[Symbol ]
316
+
317
+ // for each statify-candidate method `m`, the set of statify-candidate methods that call `m`
318
+ private val methodReferences = mutable.Map .empty[Symbol , mutable.Set [Symbol ]]
319
+
320
+ def methodReferencesThisIn (tree : Tree ): collection.Set [Symbol ] = {
321
+ traverse(tree)
322
+ propagateReferences()
323
+ }
324
+
325
+ private def addRoot (symbol : Symbol ): Unit =
326
+ roots += symbol
327
+
328
+ private def addReference (from : Symbol , to : Symbol ): Unit =
329
+ methodReferences.getOrElseUpdate(to, mutable.Set .empty) += from
330
+
331
+ private def propagateReferences (): collection.Set [Symbol ] = {
332
+ val result = mutable.Set .empty[Symbol ]
333
+
334
+ def rec (symbol : Symbol ): Unit = {
335
+ // mark `symbol` as referring to `this`
336
+ if (result.add(symbol)) {
337
+ // `symbol` was not yet in the set; propagate further
338
+ for {
339
+ referrers <- methodReferences.get(symbol)
340
+ referrer <- referrers
341
+ } {
342
+ // referrer calls `symbol`, so it transitively references `this`
343
+ rec(referrer)
344
+ }
345
+ }
346
+ }
347
+
348
+ roots.foreach(rec(_))
349
+
350
+ result
351
+ }
352
+
353
+ private var currentMethod : Symbol = NoSymbol
354
+
355
+ override def traverse (tree : Tree ): Unit = tree match {
356
+ case _ : DefDef =>
357
+ if (isStatifyCandidate(tree.symbol)) {
358
+ currentMethod = tree.symbol
359
+ super .traverse(tree)
360
+ currentMethod = NoSymbol
361
+ } else {
362
+ // No need to traverse other methods; we always assume they refer to `this`.
363
+ }
364
+ case Function (_, Apply (target, _)) =>
365
+ /* We don't drill into functions because at this phase they will always refer to `this`.
366
+ * They will be of the form {(args...) => this.anonfun(args...)}
367
+ * but we do need to make note of the lifted body method in case it refers to `this`.
368
+ */
369
+ if (currentMethod.exists)
370
+ addReference(from = currentMethod, to = target.symbol)
371
+ case Apply (sel @ Select (This (_), _), args) if isStatifyCandidate(sel.symbol) =>
372
+ if (currentMethod.exists)
373
+ addReference(from = currentMethod, to = sel.symbol)
374
+ super .traverseTrees(args)
375
+ case This (_) =>
376
+ if (currentMethod.exists && tree.symbol == currentMethod.enclClass)
377
+ addRoot(currentMethod)
378
+ case _ =>
379
+ super .traverse(tree)
380
+ }
381
+ }
382
+
251
383
// Global class generation state -------------------------------------------
252
384
253
385
private val lazilyGeneratedAnonClasses = mutable.Map .empty[Symbol , ClassDef ]
@@ -306,6 +438,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
306
438
*/
307
439
override def apply (cunit : CompilationUnit ): Unit = {
308
440
try {
441
+ statifyCandidateMethodsThatReferenceThis =
442
+ new ThisReferringMethodsTraverser ().methodReferencesThisIn(cunit.body)
443
+
309
444
def collectClassDefs (tree : Tree ): List [ClassDef ] = {
310
445
tree match {
311
446
case EmptyTree => Nil
@@ -455,6 +590,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
455
590
lazilyGeneratedAnonClasses.clear()
456
591
generatedStaticForwarderClasses.clear()
457
592
generatedClasses.clear()
593
+ statifyCandidateMethodsThatReferenceThis = null
458
594
pos2irPosCache.clear()
459
595
}
460
596
}
@@ -1144,16 +1280,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
1144
1280
1145
1281
assert(moduleClass.isModuleClass, moduleClass)
1146
1282
1147
- val hasAnyExistingPublicStaticMethod =
1148
- existingMethods.exists(_.flags.namespace == js.MemberNamespace .PublicStatic )
1149
- if (hasAnyExistingPublicStaticMethod) {
1150
- reporter.error(pos,
1151
- " Unexpected situation: found existing public static methods in " +
1152
- s " the class ${moduleClass.fullName} while trying to generate " +
1153
- " static forwarders for its companion object. " +
1154
- " Please report this as a bug in Scala.js." )
1155
- }
1156
-
1157
1283
def listMembersBasedOnFlags = {
1158
1284
// Copy-pasted from BCodeHelpers.
1159
1285
val ExcludedForwarderFlags : Long = {
@@ -1964,7 +2090,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
1964
2090
} else {
1965
2091
val resultIRType = toIRType(sym.tpe.resultType)
1966
2092
val namespace = {
1967
- if (sym.isStaticMember ) {
2093
+ if (compileAsStaticMethod( sym) ) {
1968
2094
if (sym.isPrivate) js.MemberNamespace .PrivateStatic
1969
2095
else js.MemberNamespace .PublicStatic
1970
2096
} else {
@@ -3453,7 +3579,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
3453
3579
genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args))
3454
3580
} else if (sym.hasAnnotation(JSNativeAnnotation )) {
3455
3581
genJSNativeMemberCall(tree, isStat)
3456
- } else if (sym.isStaticMember ) {
3582
+ } else if (compileAsStaticMethod( sym) ) {
3457
3583
if (sym.isMixinConstructor) {
3458
3584
/* Do not emit a call to the $init$ method of JS traits.
3459
3585
* This exception is necessary because optional JS fields cause the
@@ -6333,7 +6459,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
6333
6459
6334
6460
val formalArgs = params.map(genParamDef(_))
6335
6461
6336
- val (allFormalCaptures, body, allActualCaptures) = if (! target.isStaticMember ) {
6462
+ val (allFormalCaptures, body, allActualCaptures) = if (! compileAsStaticMethod( target) ) {
6337
6463
val thisActualCapture = genExpr(receiver)
6338
6464
val thisFormalCapture = js.ParamDef (
6339
6465
freshLocalIdent(" this" )(receiver.pos), thisOriginalName,
0 commit comments