Skip to content

Commit 1f8d5cd

Browse files
committed
Upgrade to Scala.js 1.0.0-RC2.
This includes changes to the contract of calling static methods in Java-defined classes. They are now actually called as static methods in the IR. We also generate static forwarders for public methods in static objects. These changes are a forward-port of scala-js/scala-js@f94713b The only real difference is that in scalac, we emit a compile error if the companion class contains *any* public static method, because scalac does not otherwise produce public static methods at the moment. Since dotty does generate public static methods for the bodies of SAMs, we only complain if there is an actual clash.
1 parent 3765ee1 commit 1f8d5cd

File tree

3 files changed

+189
-21
lines changed

3 files changed

+189
-21
lines changed

compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala

Lines changed: 182 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ class JSCodeGen()(implicit ctx: Context) {
6767

6868
// Some state --------------------------------------------------------------
6969

70+
private val generatedClasses = mutable.ListBuffer.empty[js.ClassDef]
71+
7072
private val currentClassSym = new ScopedVar[Symbol]
7173
private val currentMethodSym = new ScopedVar[Symbol]
7274
private val localNames = new ScopedVar[LocalNameGenerator]
@@ -104,7 +106,11 @@ class JSCodeGen()(implicit ctx: Context) {
104106
// Compilation unit --------------------------------------------------------
105107

106108
def run(): Unit = {
107-
genCompilationUnit(ctx.compilationUnit)
109+
try {
110+
genCompilationUnit(ctx.compilationUnit)
111+
} finally {
112+
generatedClasses.clear()
113+
}
108114
}
109115

110116
/** Generates the Scala.js IR for a compilation unit
@@ -137,8 +143,6 @@ class JSCodeGen()(implicit ctx: Context) {
137143
}
138144
val allTypeDefs = collectTypeDefs(cunit.tpdTree)
139145

140-
val generatedClasses = mutable.ListBuffer.empty[js.ClassDef]
141-
142146
// TODO Record anonymous JS function classes
143147

144148
/* Finally, we emit true code for the remaining class defs. */
@@ -215,6 +219,7 @@ class JSCodeGen()(implicit ctx: Context) {
215219
}*/
216220

217221
val classIdent = encodeClassNameIdent(sym)
222+
val originalName = originalNameOfClass(sym)
218223
val isHijacked = false //isHijackedBoxedClass(sym)
219224

220225
// Optimizer hints
@@ -308,14 +313,51 @@ class JSCodeGen()(implicit ctx: Context) {
308313

309314
val staticInitializerStats = reflectInit.toList
310315
if (staticInitializerStats.nonEmpty)
311-
Some(genStaticInitializerWithStats(js.Block(staticInitializerStats)))
316+
List(genStaticInitializerWithStats(js.Block(staticInitializerStats)))
312317
else
313-
None
318+
Nil
319+
}
320+
321+
val allMemberDefsExceptStaticForwarders =
322+
generatedMembers ::: exports ::: optStaticInitializer
323+
324+
// Add static forwarders
325+
val allMemberDefs = if (!isCandidateForForwarders(sym)) {
326+
allMemberDefsExceptStaticForwarders
327+
} else {
328+
if (isStaticModule(sym)) {
329+
/* If the module class has no linked class, we must create one to
330+
* hold the static forwarders. Otherwise, this is going to be handled
331+
* when generating the companion class.
332+
*/
333+
if (!sym.linkedClass.exists) {
334+
val forwarders = genStaticForwardersFromModuleClass(Nil, sym)
335+
if (forwarders.nonEmpty) {
336+
val forwardersClassDef = js.ClassDef(
337+
js.ClassIdent(ClassName(classIdent.name.nameString.stripSuffix("$"))),
338+
originalName,
339+
ClassKind.Class,
340+
None,
341+
Some(js.ClassIdent(ir.Names.ObjectClass)),
342+
Nil,
343+
None,
344+
None,
345+
forwarders,
346+
Nil
347+
)(js.OptimizerHints.empty)
348+
generatedClasses += forwardersClassDef
349+
}
350+
}
351+
allMemberDefsExceptStaticForwarders
352+
} else {
353+
val forwarders = genStaticForwardersForClassOrInterface(
354+
allMemberDefsExceptStaticForwarders, sym)
355+
allMemberDefsExceptStaticForwarders ::: forwarders
356+
}
314357
}
315358

316359
// Hashed definitions of the class
317-
val hashedDefs =
318-
ir.Hashers.hashMemberDefs(generatedMembers ++ exports ++ optStaticInitializer)
360+
val hashedDefs = ir.Hashers.hashMemberDefs(allMemberDefs)
319361

320362
// The complete class definition
321363
val kind =
@@ -325,7 +367,7 @@ class JSCodeGen()(implicit ctx: Context) {
325367

326368
val classDefinition = js.ClassDef(
327369
classIdent,
328-
originalNameOfClass(sym),
370+
originalName,
329371
kind,
330372
None,
331373
Some(encodeClassNameIdent(sym.superClass)),
@@ -386,7 +428,7 @@ class JSCodeGen()(implicit ctx: Context) {
386428
*/
387429
private def genInterface(td: TypeDef): js.ClassDef = {
388430
val sym = td.symbol.asClass
389-
implicit val pos: Position = sym.span
431+
implicit val pos: SourcePosition = sym.sourcePos
390432

391433
val classIdent = encodeClassNameIdent(sym)
392434

@@ -407,9 +449,13 @@ class JSCodeGen()(implicit ctx: Context) {
407449

408450
val superInterfaces = genClassInterfaces(sym)
409451

452+
val genMethodsList = generatedMethods.toList
453+
val allMemberDefs =
454+
if (!isCandidateForForwarders(sym)) genMethodsList
455+
else genMethodsList ::: genStaticForwardersForClassOrInterface(genMethodsList, sym)
456+
410457
// Hashed definitions of the interface
411-
val hashedDefs =
412-
ir.Hashers.hashMemberDefs(generatedMethods.toList)
458+
val hashedDefs = ir.Hashers.hashMemberDefs(allMemberDefs)
413459

414460
js.ClassDef(
415461
classIdent,
@@ -435,6 +481,126 @@ class JSCodeGen()(implicit ctx: Context) {
435481
}
436482
}
437483

484+
// Static forwarders -------------------------------------------------------
485+
486+
/* This mimics the logic in BCodeHelpers.addForwarders and the code that
487+
* calls it, except that we never have collisions with existing methods in
488+
* the companion class. This is because in the IR, only methods with the
489+
* same `MethodName` (including signature) and that are also
490+
* `PublicStatic` would collide. There should never be an actual collision
491+
* because the only `PublicStatic` methods that are otherwise generated are
492+
* the bodies of SAMs, which have mangled names. If that assumption is
493+
* broken, an error message is emitted asking the user to report a bug.
494+
*
495+
* It is important that we always emit forwarders, because some Java APIs
496+
* actually have a public static method and a public instance method with
497+
* the same name. For example the class `Integer` has a
498+
* `def hashCode(): Int` and a `static def hashCode(Int): Int`. The JVM
499+
* back-end considers them as colliding because they have the same name,
500+
* but we must not.
501+
*/
502+
503+
/** Is the given Scala class, interface or module class a candidate for
504+
* static forwarders?
505+
*/
506+
def isCandidateForForwarders(sym: Symbol): Boolean = {
507+
// it must be a top level class
508+
sym.isStatic
509+
}
510+
511+
/** Gen the static forwarders to the members of a class or interface for
512+
* methods of its companion object.
513+
*
514+
* This is only done if there exists a companion object and it is not a JS
515+
* type.
516+
*
517+
* Precondition: `isCandidateForForwarders(sym)` is true
518+
*/
519+
def genStaticForwardersForClassOrInterface(
520+
existingMembers: List[js.MemberDef], sym: Symbol)(
521+
implicit pos: SourcePosition): List[js.MemberDef] = {
522+
val module = sym.companionModule
523+
if (!module.exists) {
524+
Nil
525+
} else {
526+
val moduleClass = module.moduleClass
527+
if (!isJSType(moduleClass))
528+
genStaticForwardersFromModuleClass(existingMembers, moduleClass)
529+
else
530+
Nil
531+
}
532+
}
533+
534+
private lazy val dontUseExitingUncurryForForwarders =
535+
scala.util.Properties.versionNumberString.startsWith("2.11.")
536+
537+
/** Gen the static forwarders for the methods of a module class.
538+
*
539+
* Precondition: `isCandidateForForwarders(moduleClass)` is true
540+
*/
541+
def genStaticForwardersFromModuleClass(existingMembers: List[js.MemberDef],
542+
moduleClass: Symbol)(
543+
implicit pos: SourcePosition): List[js.MemberDef] = {
544+
545+
assert(moduleClass.is(ModuleClass), moduleClass)
546+
547+
val existingPublicStaticMethodNames = existingMembers.collect {
548+
case js.MethodDef(flags, name, _, _, _, _)
549+
if flags.namespace == js.MemberNamespace.PublicStatic =>
550+
name.name
551+
}.toSet
552+
553+
val members = {
554+
// Copied from DottyBackendInterface.ExcludedForwarderFlags
555+
val ExcludedForwarderFlags = {
556+
Flags.Specialized | Flags.Lifted | Flags.Protected | Flags.JavaStatic |
557+
Flags.Private | Flags.Macro
558+
}
559+
560+
moduleClass.info.membersBasedOnFlags(required = Flags.Method,
561+
excluded = ExcludedForwarderFlags).map(_.symbol)
562+
}
563+
564+
def isExcluded(m: Symbol): Boolean = {
565+
def hasAccessBoundary = m.accessBoundary(defn.RootClass) ne defn.RootClass
566+
m.is(Deferred) || m.isConstructor || hasAccessBoundary || (m.owner eq defn.ObjectClass)
567+
}
568+
569+
val forwarders = for {
570+
m <- members
571+
if !isExcluded(m)
572+
} yield {
573+
withNewLocalNameScope {
574+
val flags = js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic)
575+
val methodIdent = encodeMethodSym(m)
576+
val originalName = originalNameOfMethod(m)
577+
val jsParams = for {
578+
(paramName, paramInfo) <- m.info.paramNamess.flatten.zip(m.info.paramInfoss.flatten)
579+
} yield {
580+
js.ParamDef(freshLocalIdent(paramName), NoOriginalName,
581+
toIRType(paramInfo), mutable = false, rest = false)
582+
}
583+
val resultType = toIRType(m.info.resultType)
584+
585+
if (existingPublicStaticMethodNames.contains(methodIdent.name)) {
586+
ctx.error(
587+
"Unexpected situation: found existing public static method " +
588+
s"${methodIdent.name.nameString} in the companion class of " +
589+
s"${moduleClass.fullName}; cannot generate a static forwarder " +
590+
"the method of the same name in the object." +
591+
"Please report this as a bug in the Scala.js support in dotty.",
592+
pos)
593+
}
594+
595+
js.MethodDef(flags, methodIdent, originalName, jsParams, resultType, Some {
596+
genApplyMethod(genLoadModule(moduleClass), m, jsParams.map(_.ref))
597+
})(OptimizerHints.empty, None)
598+
}
599+
}
600+
601+
forwarders.toList
602+
}
603+
438604
// Generate the fields of a class ------------------------------------------
439605

440606
/** Gen definitions for the fields of a class.
@@ -1305,14 +1471,12 @@ class JSCodeGen()(implicit ctx: Context) {
13051471
args: List[js.Tree])(implicit pos: SourcePosition): js.Tree = {
13061472

13071473
val className = encodeClassName(clazz)
1308-
val moduleClass = clazz.companionModule.moduleClass
1309-
13101474
val initName = encodeMethodSym(ctor).name
13111475
val newName = MethodName(newSimpleMethodName, initName.paramTypeRefs,
13121476
jstpe.ClassRef(className))
13131477
val newMethodIdent = js.MethodIdent(newName)
13141478

1315-
js.Apply(js.ApplyFlags.empty, genLoadModule(moduleClass), newMethodIdent, args)(
1479+
js.ApplyStatic(js.ApplyFlags.empty, className, newMethodIdent, args)(
13161480
jstpe.ClassType(className))
13171481
}
13181482

@@ -1678,7 +1842,7 @@ class JSCodeGen()(implicit ctx: Context) {
16781842
} else externalEquals
16791843
// scalastyle:on line.size.limit
16801844
}
1681-
genModuleApplyMethod(equalsMethod, List(lsrc, rsrc))
1845+
genApplyStatic(equalsMethod, List(lsrc, rsrc))
16821846
} else {
16831847
// if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc)
16841848
if (lsym == defn.StringClass) {
@@ -2727,9 +2891,9 @@ class JSCodeGen()(implicit ctx: Context) {
27272891
} else if (sym == defn.BoxedUnit_TYPE) {
27282892
js.ClassOf(jstpe.VoidRef)
27292893
} else {
2730-
val inst = genLoadModule(sym.owner)
2894+
val className = encodeClassName(sym.owner)
27312895
val method = encodeStaticMemberSym(sym)
2732-
js.Apply(js.ApplyFlags.empty, inst, method, Nil)(toIRType(sym.info))
2896+
js.ApplyStatic(js.ApplyFlags.empty, className, method, Nil)(toIRType(sym.info))
27332897
}
27342898
}
27352899

@@ -2909,7 +3073,7 @@ class JSCodeGen()(implicit ctx: Context) {
29093073
}
29103074

29113075
private def isMethodStaticInIR(sym: Symbol): Boolean =
2912-
sym.is(JavaStatic, butNot = JavaDefined)
3076+
sym.is(JavaStatic)
29133077

29143078
/** Generate a Class[_] value (e.g. coming from classOf[T]) */
29153079
private def genClassConstant(tpe: Type)(implicit pos: Position): js.Tree =

compiler/src/dotty/tools/backend/sjs/JSEncoding.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,17 @@ object JSEncoding {
237237
js.ClassIdent(encodeClassName(sym))
238238

239239
def encodeClassName(sym: Symbol)(implicit ctx: Context): ClassName = {
240-
if (sym == defn.BoxedUnitClass) {
240+
val sym1 =
241+
if (sym.isAllOf(ModuleClass | JavaDefined)) sym.linkedClass
242+
else sym
243+
244+
if (sym1 == defn.BoxedUnitClass) {
241245
/* Rewire scala.runtime.BoxedUnit to java.lang.Void, as the IR expects.
242246
* BoxedUnit$ is a JVM artifact.
243247
*/
244248
ir.Names.BoxedUnitClass
245249
} else {
246-
ClassName(sym.fullName.mangledString)
250+
ClassName(sym1.fullName.mangledString)
247251
}
248252
}
249253

project/plugins.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// e.g. addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.1.0")
44

5-
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.0-RC1")
5+
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.0-RC2")
66

77
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.6")
88

0 commit comments

Comments
 (0)