Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 83b9ec9

Browse files
committed
Force super classes to generate default bridges on lookup
- Fixes #2520 (make default bridge generation smarter) - Avoids traversal order dependent behavior. Since we potentially need to do a default target check on every method lookup, we memoize calculated default targets. Note that this might generate more default bridges than actually necessary. However, since they will not be marked as reachable, they will not even be synthesized.
1 parent 3bf6d9d commit 83b9ec9

File tree

2 files changed

+99
-37
lines changed

2 files changed

+99
-37
lines changed

linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -667,50 +667,40 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
667667
assert(isScalaClass || isInterface,
668668
s"Cannot call lookupMethod($methodName) on non Scala class $this")
669669

670-
@tailrec
671-
def tryLookupInherited(ancestorInfo: ClassInfo): Option[MethodInfo] = {
672-
ancestorInfo.publicMethodInfos.get(methodName) match {
673-
case Some(m) if !m.isAbstract && (!m.nonExistent || ancestorInfo == this) =>
674-
Some(m)
675-
case _ =>
676-
ancestorInfo.superClass match {
677-
case Some(superClass) => tryLookupInherited(superClass)
678-
case None => None
670+
publicMethodInfos.get(methodName) match {
671+
case Some(m) if !m.isAbstract => Some(m)
672+
673+
case _ =>
674+
val candidate = superClass
675+
.flatMap(_.tryLookupMethod(methodName))
676+
.filterNot(_.nonExistent)
677+
678+
if (allowAddingSyntheticMethods) {
679+
def maybeDefaultTarget = getDefaultTarget(methodName)
680+
681+
def needsDefaultOverride(method: MethodInfo): Boolean = {
682+
/* The .get is OK, since we only get here if:
683+
* - This class doesn't implement the method directly.
684+
* - The superClass has found a default target.
685+
* In this case, we always find at least one target.
686+
*/
687+
method.isDefaultBridge && method.defaultBridgeTarget != maybeDefaultTarget.get.owner.className
679688
}
680-
}
681-
}
682-
val existing =
683-
if (isScalaClass) tryLookupInherited(this)
684-
else publicMethodInfos.get(methodName).filter(!_.isAbstract)
685689

686-
if (!allowAddingSyntheticMethods) {
687-
existing
688-
} else if (existing.exists(m => !m.isDefaultBridge || m.owner == this)) {
689-
/* If we found a non-bridge, it must be the right target.
690-
* If we found a bridge directly in this class/interface, it must also
691-
* be the right target.
692-
*/
693-
existing
694-
} else {
695-
// Try and find the target of a possible default bridge
696-
findDefaultTarget(methodName).fold {
697-
assert(existing.isEmpty)
698-
existing
699-
} { defaultTarget =>
700-
if (existing.exists(_.defaultBridgeTarget == defaultTarget.owner.className)) {
701-
/* If we found an existing bridge targeting the right method, we
702-
* can reuse it.
703-
* We also get here with None when there is no target whatsoever.
704-
*/
705-
existing
690+
candidate
691+
.filterNot(needsDefaultOverride(_))
692+
.orElse(maybeDefaultTarget.map(createDefaultBridge(_)))
706693
} else {
707-
// Otherwise, create a new default bridge
708-
Some(createDefaultBridge(defaultTarget))
694+
candidate
709695
}
710-
}
711696
}
712697
}
713698

699+
private val defaultTargets = mutable.Map.empty[MethodName, Option[MethodInfo]]
700+
701+
private def getDefaultTarget(methodName: MethodName): Option[MethodInfo] =
702+
defaultTargets.getOrElseUpdate(methodName, findDefaultTarget(methodName))
703+
714704
/** Resolves an inherited default method.
715705
*
716706
* This lookup is specified by the JVM resolution rules for default
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Scala.js (https://www.scala-js.org/)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package org.scalajs.linker
14+
15+
import org.junit.Test
16+
import org.junit.Assert._
17+
18+
import org.scalajs.ir.ClassKind
19+
import org.scalajs.ir.Names._
20+
import org.scalajs.ir.Trees._
21+
import org.scalajs.ir.Types._
22+
23+
import org.scalajs.junit.async._
24+
25+
import org.scalajs.linker.interface.StandardConfig
26+
import org.scalajs.linker.standard._
27+
28+
import org.scalajs.linker.testutils.TestIRBuilder._
29+
import org.scalajs.linker.testutils.LinkingUtils._
30+
31+
class BaseLinkerTest {
32+
import scala.concurrent.ExecutionContext.Implicits.global
33+
34+
@Test
35+
def noUnnecessaryDefaultBridges(): AsyncResult = await {
36+
val fooName = m("foo", Nil, IntRef)
37+
val classDefs = Seq(
38+
classDef(
39+
"Intf",
40+
kind = ClassKind.Interface,
41+
methods = List(
42+
MethodDef(EMF, fooName, NON, Nil, IntType, Some(int(1)))(EOH, UNV))
43+
),
44+
classDef(
45+
"Base",
46+
kind = ClassKind.Class,
47+
superClass = Some(ObjectClass),
48+
interfaces = List("Intf"),
49+
methods = List(trivialCtor("Base"))
50+
),
51+
classDef(
52+
"Sub",
53+
kind = ClassKind.Class,
54+
superClass = Some("Base"),
55+
methods = List(trivialCtor("Sub"))
56+
),
57+
mainTestClassDef(
58+
consoleLog(Apply(EAF, New("Sub", NoArgConstructorName, Nil), fooName, Nil)(IntType))
59+
)
60+
)
61+
62+
val config = StandardConfig().withOptimizer(false)
63+
64+
for (moduleSet <- linkToModuleSet(classDefs, MainTestModuleInitializers, config = config)) yield {
65+
val clazz = findClass(moduleSet, "Sub").get
66+
assertFalse(clazz.methods.exists(_.name.name == fooName))
67+
}
68+
}
69+
70+
private def findClass(moduleSet: ModuleSet, name: ClassName): Option[LinkedClass] =
71+
moduleSet.modules.flatMap(_.classDefs).find(_.className == name)
72+
}

0 commit comments

Comments
 (0)