Skip to content

Commit 5bd1e50

Browse files
committed
Merge pull request scala-js#2139 from sjrd/no-refl-proxy-in-sjsir
Synthesize reflective proxies at link time.
2 parents ac65737 + cd397f4 commit 5bd1e50

File tree

11 files changed

+286
-194
lines changed

11 files changed

+286
-194
lines changed

cli/src/main/scala/org/scalajs/cli/Scalajsp.scala

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ object Scalajsp {
3131

3232
private case class Options(
3333
infos: Boolean = false,
34-
showReflProxy: Boolean = false,
3534
jar: Option[File] = None,
3635
fileNames: Seq[String] = Seq.empty)
3736

@@ -49,9 +48,6 @@ object Scalajsp {
4948
opt[Unit]('i', "infos")
5049
.action { (_, c) => c.copy(infos = true) }
5150
.text("Show DCE infos instead of trees")
52-
opt[Unit]('p', "reflProxies")
53-
.action { (_, c) => c.copy(showReflProxy = true) }
54-
.text("Show reflective call proxies")
5551
opt[Unit]('s', "supported")
5652
.action { (_,_) => printSupported(); sys.exit() }
5753
.text("Show supported Scala.js IR versions")
@@ -90,15 +86,8 @@ object Scalajsp {
9086
opts: Options): Unit = {
9187
if (opts.infos)
9288
new InfoPrinter(stdout).print(vfile.info)
93-
else {
94-
val (info, tree) = vfile.infoAndTree
95-
val outTree = {
96-
if (opts.showReflProxy) tree
97-
else filterOutReflProxies(tree)
98-
}
99-
100-
new IRTreePrinter(stdout).printTopLevelTree(outTree)
101-
}
89+
else
90+
new IRTreePrinter(stdout).printTopLevelTree(vfile.tree)
10291

10392
stdout.flush()
10493
}
@@ -141,15 +130,4 @@ object Scalajsp {
141130
private val stdout =
142131
new BufferedWriter(new OutputStreamWriter(Console.out, "UTF-8"))
143132

144-
// !!! CODE DUPLICATION with ScalaJSPluginInternal.filterOutReflProxies
145-
private def filterOutReflProxies(tree: ClassDef): ClassDef = {
146-
import ir.Trees._
147-
import ir.Definitions.isReflProxyName
148-
val newDefs = tree.defs.filter {
149-
case MethodDef(_, Ident(name, _), _, _, _) => !isReflProxyName(name)
150-
case _ => true
151-
}
152-
tree.copy(defs = newDefs)(tree.optimizerHints)(tree.pos)
153-
}
154-
155133
}

compiler/src/main/scala/org/scalajs/core/compiler/GenJSCode.scala

Lines changed: 1 addition & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -367,14 +367,9 @@ abstract class GenJSCode extends plugins.PluginComponent
367367
memberExports ++ exportedConstructorsOrAccessors
368368
}
369369

370-
// Generate the reflective call proxies (where required)
371-
val reflProxies =
372-
if (isHijacked) Nil
373-
else genReflCallProxies(sym)
374-
375370
// Hashed definitions of the class
376371
val hashedDefs =
377-
Hashers.hashDefs(generatedMembers ++ exports ++ reflProxies)
372+
Hashers.hashDefs(generatedMembers ++ exports)
378373

379374
// The complete class definition
380375
val kind =
@@ -890,120 +885,6 @@ abstract class GenJSCode extends plugins.PluginComponent
890885
afterSuper)(body.pos)
891886
}
892887

893-
/**
894-
* Generates reflective proxy methods for methods in sym
895-
*
896-
* Reflective calls don't depend on the return type, so it's hard to
897-
* generate calls without using runtime reflection to list the methods. We
898-
* generate a method to be used for reflective calls (without return
899-
* type in the name).
900-
*
901-
* There are cases where non-trivial overloads cause ambiguous situations:
902-
*
903-
* {{{
904-
* object A {
905-
* def foo(x: Option[Int]): String
906-
* def foo(x: Option[String]): Int
907-
* }
908-
* }}}
909-
*
910-
* This is completely legal code, but due to the same erased parameter
911-
* type of the {{{foo}}} overloads, they cannot be disambiguated in a
912-
* reflective call, as the exact return type is unknown at the call site.
913-
*
914-
* Cases like the upper currently fail on the JVM backend at runtime. The
915-
* Scala.js backend uses the following rules for selection (which will
916-
* also cause runtime failures):
917-
*
918-
* - If a proxy with the same signature (method name and parameters)
919-
* exists in the superclass, no proxy is generated (proxy is inherited)
920-
* - If no proxy exists in the superclass, a proxy is generated for the
921-
* first method with matching signatures.
922-
*/
923-
def genReflCallProxies(sym: Symbol): List[js.MethodDef] = {
924-
import scala.reflect.internal.Flags
925-
926-
// Flags of members we do not want to consider for reflective call proxys
927-
val excludedFlags = (
928-
Flags.BRIDGE |
929-
Flags.PRIVATE |
930-
Flags.MACRO
931-
)
932-
933-
/** Check if two method symbols conform in name and parameter types */
934-
def weakMatch(s1: Symbol)(s2: Symbol) = {
935-
val p1 = s1.tpe.params
936-
val p2 = s2.tpe.params
937-
s1 == s2 || // Shortcut
938-
s1.name == s2.name &&
939-
p1.size == p2.size &&
940-
(p1 zip p2).forall { case (s1,s2) =>
941-
s1.tpe =:= s2.tpe
942-
}
943-
}
944-
945-
/** Check if the symbol's owner's superclass has a matching member (and
946-
* therefore an existing proxy).
947-
*/
948-
def superHasProxy(s: Symbol) = {
949-
val alts = sym.superClass.tpe.findMember(
950-
name = s.name,
951-
excludedFlags = excludedFlags,
952-
requiredFlags = Flags.METHOD,
953-
stableOnly = false).alternatives
954-
alts.exists(weakMatch(s) _)
955-
}
956-
957-
// Query candidate methods
958-
val methods = sym.tpe.findMembers(
959-
excludedFlags = excludedFlags,
960-
requiredFlags = Flags.METHOD)
961-
962-
val candidates = methods filterNot { s =>
963-
s.isConstructor ||
964-
superHasProxy(s) ||
965-
jsInterop.isExport(s)
966-
}
967-
968-
val proxies = candidates filter {
969-
c => candidates.find(weakMatch(c) _).get == c
970-
}
971-
972-
proxies.map(genReflCallProxy _).toList
973-
}
974-
975-
/** actually generates reflective call proxy for the given method symbol */
976-
private def genReflCallProxy(sym: Symbol): js.MethodDef = {
977-
implicit val pos = sym.pos
978-
979-
val proxyIdent = encodeMethodSym(sym, reflProxy = true)
980-
981-
withNewLocalNameScope {
982-
val jsParams = for (param <- sym.tpe.params) yield {
983-
implicit val pos = param.pos
984-
js.ParamDef(encodeLocalSym(param), toIRType(param.tpe),
985-
mutable = false, rest = false)
986-
}
987-
988-
val call = genApplyMethod(genThis(), sym, jsParams.map(_.ref))
989-
val resTpeEnteringPosterasure = enteringPhase(currentRun.posterasurePhase) {
990-
sym.tpe match {
991-
case _: ExistentialType =>
992-
/* We should not see an ExistentialType here. This is a
993-
* scalac 2.10 bug. We assume no boxing is required. See #1581.
994-
*/
995-
ObjectTpe
996-
case symTpe =>
997-
symTpe.resultType
998-
}
999-
}
1000-
val body = ensureBoxed(call, resTpeEnteringPosterasure)
1001-
1002-
js.MethodDef(static = false, proxyIdent, jsParams, jstpe.AnyType,
1003-
body)(OptimizerHints.empty, None)
1004-
}
1005-
}
1006-
1007888
/** Generates the MethodDef of a (non-constructor) method
1008889
*
1009890
* Most normal methods are emitted straightforwardly. If the result

ir/src/main/scala/org/scalajs/core/ir/InfoSerializers.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ object InfoSerializers {
101101

102102
import input._
103103

104+
val useHacks065 =
105+
Set("0.6.0", "0.6.3", "0.6.4", "0.6.5").contains(version)
106+
104107
val encodedName = readUTF()
105108
val isExported = readBoolean()
106109
val kind = ClassKind.fromByte(readByte())
@@ -126,7 +129,12 @@ object InfoSerializers {
126129
accessedClassData)
127130
}
128131

129-
val methods = readList(readMethod())
132+
val methods0 = readList(readMethod())
133+
val methods = if (useHacks065) {
134+
methods0.filter(m => !Definitions.isReflProxyName(m.encodedName))
135+
} else {
136+
methods0
137+
}
130138

131139
val info = ClassInfo(encodedName, isExported, kind,
132140
superClass, interfaces, methods)

ir/src/main/scala/org/scalajs/core/ir/Serializers.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,17 @@ object Serializers {
712712
val superClass = readOptIdent()
713713
val parents = readIdents()
714714
val jsName = Some(readString()).filter(_ != "")
715-
val defs = readTrees()
715+
val defs0 = readTrees()
716+
val defs = if (useHacks065) {
717+
defs0.filter {
718+
case MethodDef(_, Ident(name, _), _, _, _) =>
719+
!Definitions.isReflProxyName(name)
720+
case _ =>
721+
true
722+
}
723+
} else {
724+
defs0
725+
}
716726
val optimizerHints = new OptimizerHints(readInt())
717727
ClassDef(name, kind, superClass, parents, jsName, defs)(optimizerHints)
718728

project/BinaryIncompatibilities.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,44 @@ object BinaryIncompatibilities {
1212
)
1313

1414
val SbtPlugin = Seq(
15+
// private, not an issue
16+
ProblemFilters.exclude[MissingMethodProblem](
17+
"org.scalajs.sbtplugin.ScalaJSPluginInternal.org$scalajs$sbtplugin$ScalaJSPluginInternal$$filterOutReflProxies")
1518
)
1619

1720
val TestAdapter = Seq(
1821
)
1922

2023
val CLI = Seq(
24+
// private, not an issue
25+
ProblemFilters.exclude[MissingTypesProblem](
26+
"org.scalajs.cli.Scalajsp$Options$"),
27+
ProblemFilters.exclude[MissingMethodProblem](
28+
"org.scalajs.cli.Scalajsp#Options.this"),
29+
ProblemFilters.exclude[IncompatibleResultTypeProblem](
30+
"org.scalajs.cli.Scalajsp#Options.<init>$default$2"),
31+
ProblemFilters.exclude[IncompatibleResultTypeProblem](
32+
"org.scalajs.cli.Scalajsp#Options.<init>$default$3"),
33+
ProblemFilters.exclude[MissingMethodProblem](
34+
"org.scalajs.cli.Scalajsp#Options.<init>$default$4"),
35+
ProblemFilters.exclude[MissingMethodProblem](
36+
"org.scalajs.cli.Scalajsp#Options.apply"),
37+
ProblemFilters.exclude[IncompatibleResultTypeProblem](
38+
"org.scalajs.cli.Scalajsp#Options.apply$default$2"),
39+
ProblemFilters.exclude[IncompatibleResultTypeProblem](
40+
"org.scalajs.cli.Scalajsp#Options.apply$default$3"),
41+
ProblemFilters.exclude[MissingMethodProblem](
42+
"org.scalajs.cli.Scalajsp#Options.apply$default$4"),
43+
ProblemFilters.exclude[MissingMethodProblem](
44+
"org.scalajs.cli.Scalajsp#Options.copy"),
45+
ProblemFilters.exclude[IncompatibleResultTypeProblem](
46+
"org.scalajs.cli.Scalajsp#Options.copy$default$2"),
47+
ProblemFilters.exclude[IncompatibleResultTypeProblem](
48+
"org.scalajs.cli.Scalajsp#Options.copy$default$3"),
49+
ProblemFilters.exclude[MissingMethodProblem](
50+
"org.scalajs.cli.Scalajsp#Options.copy$default$4"),
51+
ProblemFilters.exclude[MissingMethodProblem](
52+
"org.scalajs.cli.Scalajsp#Options.showReflProxy")
2153
)
2254

2355
val Library = Seq(

sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/ScalaJSPluginInternal.scala

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,12 @@ object ScalaJSPluginInternal {
143143

144144
private def scalajspSettings: Seq[Setting[_]] = {
145145
case class Options(
146-
infos: Boolean = false,
147-
showReflProxy: Boolean = false
146+
infos: Boolean = false
148147
)
149148

150149
val optionsParser: Parser[Options] = {
151150
token(OptSpace ~> (
152151
(literal("-i") | "--infos") ^^^ ((_: Options).copy(infos = true))
153-
| (literal("-p") | "--reflProxies") ^^^ ((_: Options).copy(showReflProxy = true))
154152
)).* map {
155153
fns => Function.chain(fns)(Options())
156154
}
@@ -182,32 +180,15 @@ object ScalaJSPluginInternal {
182180
val vfile = ScalajspUtils.loadIRFile(cp, relPath)
183181

184182
val stdout = new java.io.PrintWriter(System.out)
185-
if (options.infos) {
183+
if (options.infos)
186184
new InfoPrinter(stdout).print(vfile.info)
187-
} else {
188-
val (info, tree) = vfile.infoAndTree
189-
val outTree = {
190-
if (options.showReflProxy) tree
191-
else filterOutReflProxies(tree)
192-
}
193-
new IRTreePrinter(stdout).printTopLevelTree(outTree)
194-
}
185+
else
186+
new IRTreePrinter(stdout).printTopLevelTree(vfile.tree)
195187
stdout.flush()
196188
}
197189
)
198190
}
199191

200-
// !!! CODE DUPLICATION with Scalajsp.filterOutReflProxies
201-
private def filterOutReflProxies(tree: ir.Trees.ClassDef): ir.Trees.ClassDef = {
202-
import ir.Trees._
203-
import ir.Definitions.isReflProxyName
204-
val newDefs = tree.defs.filter {
205-
case MethodDef(_, Ident(name, _), _, _, _) => !isReflProxyName(name)
206-
case _ => true
207-
}
208-
tree.copy(defs = newDefs)(tree.optimizerHints)(tree.pos)
209-
}
210-
211192
val scalaJSConfigSettings: Seq[Setting[_]] = Seq(
212193
incOptions ~= scalaJSPatchIncOptions
213194
) ++ (

test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/ReflectiveCallTest.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,24 @@ object ReflectiveCallTest extends JasmineTest {
221221
expect(call(new C)).toEqual(1)
222222
}
223223

224+
it("should be bug-compatible with Scala/JVM for inherited overloads") {
225+
class Base {
226+
def foo(x: Option[Int]): String = "a"
227+
}
228+
229+
class Sub extends Base {
230+
def foo(x: Option[String]): Int = 1
231+
}
232+
233+
val sub = new Sub
234+
235+
val x: { def foo(x: Option[Int]): Any } = sub
236+
expect(x.foo(Some(1)).asInstanceOf[js.Any]).toEqual(1) // here is the "bug"
237+
238+
val y: { def foo(x: Option[String]): Any } = sub
239+
expect(y.foo(Some("hello")).asInstanceOf[js.Any]).toEqual(1)
240+
}
241+
224242
it("should work on java.lang.Object.{ notify, notifyAll } - #303") {
225243
type ObjNotifyLike = Any {
226244
def notify(): Unit

tools/shared/src/main/scala/org/scalajs/core/tools/optimizer/Analysis.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ object Analysis {
7575
final case object None extends MethodSyntheticKind
7676
// TODO Get rid of InheritedConstructor when we can break binary compat
7777
final case object InheritedConstructor extends MethodSyntheticKind
78+
final case class ReflectiveProxy(target: String) extends MethodSyntheticKind
7879
}
7980

8081
sealed trait Error {

0 commit comments

Comments
 (0)