Skip to content

Commit c99d407

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 c99d407

File tree

4 files changed

+189
-25
lines changed

4 files changed

+189
-25
lines changed

compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
427427

428428
val Flag_SYNTHETIC: Flags = Flags.Synthetic.bits
429429
val Flag_METHOD: Flags = Flags.Method.bits
430-
val ExcludedForwarderFlags: Flags = {
431-
Flags.Specialized | Flags.Lifted | Flags.Protected | Flags.JavaStatic |
432-
Flags.Private | Flags.Macro
433-
}.bits
430+
val ExcludedForwarderFlags: Flags = DottyBackendInterface.ExcludedForwarderFlags.bits
434431

435432
def isQualifierSafeToElide(qual: Tree): Boolean = tpd.isIdempotentExpr(qual)
436433

@@ -1195,3 +1192,10 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
11951192

11961193
def currentUnit: CompilationUnit = ctx.compilationUnit
11971194
}
1195+
1196+
object DottyBackendInterface {
1197+
val ExcludedForwarderFlags: Flags.FlagSet = {
1198+
Flags.Specialized | Flags.Lifted | Flags.Protected | Flags.JavaStatic |
1199+
Flags.Private | Flags.Macro
1200+
}
1201+
}

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

Lines changed: 174 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,118 @@ 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+
/** Gen the static forwarders for the methods of a module class.
535+
*
536+
* Precondition: `isCandidateForForwarders(moduleClass)` is true
537+
*/
538+
def genStaticForwardersFromModuleClass(existingMembers: List[js.MemberDef],
539+
moduleClass: Symbol)(
540+
implicit pos: SourcePosition): List[js.MemberDef] = {
541+
542+
assert(moduleClass.is(ModuleClass), moduleClass)
543+
544+
val existingPublicStaticMethodNames = existingMembers.collect {
545+
case js.MethodDef(flags, name, _, _, _, _)
546+
if flags.namespace == js.MemberNamespace.PublicStatic =>
547+
name.name
548+
}.toSet
549+
550+
val members = {
551+
import dotty.tools.backend.jvm.DottyBackendInterface.ExcludedForwarderFlags
552+
moduleClass.info.membersBasedOnFlags(required = Flags.Method,
553+
excluded = ExcludedForwarderFlags).map(_.symbol)
554+
}
555+
556+
def isExcluded(m: Symbol): Boolean = {
557+
def hasAccessBoundary = m.accessBoundary(defn.RootClass) ne defn.RootClass
558+
m.is(Deferred) || m.isConstructor || hasAccessBoundary || (m.owner eq defn.ObjectClass)
559+
}
560+
561+
val forwarders = for {
562+
m <- members
563+
if !isExcluded(m)
564+
} yield {
565+
withNewLocalNameScope {
566+
val flags = js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic)
567+
val methodIdent = encodeMethodSym(m)
568+
val originalName = originalNameOfMethod(m)
569+
val jsParams = for {
570+
(paramName, paramInfo) <- m.info.paramNamess.flatten.zip(m.info.paramInfoss.flatten)
571+
} yield {
572+
js.ParamDef(freshLocalIdent(paramName), NoOriginalName,
573+
toIRType(paramInfo), mutable = false, rest = false)
574+
}
575+
val resultType = toIRType(m.info.resultType)
576+
577+
if (existingPublicStaticMethodNames.contains(methodIdent.name)) {
578+
ctx.error(
579+
"Unexpected situation: found existing public static method " +
580+
s"${methodIdent.name.nameString} in the companion class of " +
581+
s"${moduleClass.fullName}; cannot generate a static forwarder " +
582+
"the method of the same name in the object." +
583+
"Please report this as a bug in the Scala.js support in dotty.",
584+
pos)
585+
}
586+
587+
js.MethodDef(flags, methodIdent, originalName, jsParams, resultType, Some {
588+
genApplyMethod(genLoadModule(moduleClass), m, jsParams.map(_.ref))
589+
})(OptimizerHints.empty, None)
590+
}
591+
}
592+
593+
forwarders.toList
594+
}
595+
438596
// Generate the fields of a class ------------------------------------------
439597

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

13071465
val className = encodeClassName(clazz)
1308-
val moduleClass = clazz.companionModule.moduleClass
1309-
13101466
val initName = encodeMethodSym(ctor).name
13111467
val newName = MethodName(newSimpleMethodName, initName.paramTypeRefs,
13121468
jstpe.ClassRef(className))
13131469
val newMethodIdent = js.MethodIdent(newName)
13141470

1315-
js.Apply(js.ApplyFlags.empty, genLoadModule(moduleClass), newMethodIdent, args)(
1471+
js.ApplyStatic(js.ApplyFlags.empty, className, newMethodIdent, args)(
13161472
jstpe.ClassType(className))
13171473
}
13181474

@@ -1678,7 +1834,7 @@ class JSCodeGen()(implicit ctx: Context) {
16781834
} else externalEquals
16791835
// scalastyle:on line.size.limit
16801836
}
1681-
genModuleApplyMethod(equalsMethod, List(lsrc, rsrc))
1837+
genApplyStatic(equalsMethod, List(lsrc, rsrc))
16821838
} else {
16831839
// if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc)
16841840
if (lsym == defn.StringClass) {
@@ -2727,9 +2883,9 @@ class JSCodeGen()(implicit ctx: Context) {
27272883
} else if (sym == defn.BoxedUnit_TYPE) {
27282884
js.ClassOf(jstpe.VoidRef)
27292885
} else {
2730-
val inst = genLoadModule(sym.owner)
2886+
val className = encodeClassName(sym.owner)
27312887
val method = encodeStaticMemberSym(sym)
2732-
js.Apply(js.ApplyFlags.empty, inst, method, Nil)(toIRType(sym.info))
2888+
js.ApplyStatic(js.ApplyFlags.empty, className, method, Nil)(toIRType(sym.info))
27332889
}
27342890
}
27352891

@@ -2909,7 +3065,7 @@ class JSCodeGen()(implicit ctx: Context) {
29093065
}
29103066

29113067
private def isMethodStaticInIR(sym: Symbol): Boolean =
2912-
sym.is(JavaStatic, butNot = JavaDefined)
3068+
sym.is(JavaStatic)
29133069

29143070
/** Generate a Class[_] value (e.g. coming from classOf[T]) */
29153071
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)