@@ -67,6 +67,8 @@ class JSCodeGen()(implicit ctx: Context) {
67
67
68
68
// Some state --------------------------------------------------------------
69
69
70
+ private val generatedClasses = mutable.ListBuffer .empty[js.ClassDef ]
71
+
70
72
private val currentClassSym = new ScopedVar [Symbol ]
71
73
private val currentMethodSym = new ScopedVar [Symbol ]
72
74
private val localNames = new ScopedVar [LocalNameGenerator ]
@@ -104,7 +106,11 @@ class JSCodeGen()(implicit ctx: Context) {
104
106
// Compilation unit --------------------------------------------------------
105
107
106
108
def run (): Unit = {
107
- genCompilationUnit(ctx.compilationUnit)
109
+ try {
110
+ genCompilationUnit(ctx.compilationUnit)
111
+ } finally {
112
+ generatedClasses.clear()
113
+ }
108
114
}
109
115
110
116
/** Generates the Scala.js IR for a compilation unit
@@ -137,8 +143,6 @@ class JSCodeGen()(implicit ctx: Context) {
137
143
}
138
144
val allTypeDefs = collectTypeDefs(cunit.tpdTree)
139
145
140
- val generatedClasses = mutable.ListBuffer .empty[js.ClassDef ]
141
-
142
146
// TODO Record anonymous JS function classes
143
147
144
148
/* Finally, we emit true code for the remaining class defs. */
@@ -215,6 +219,7 @@ class JSCodeGen()(implicit ctx: Context) {
215
219
}*/
216
220
217
221
val classIdent = encodeClassNameIdent(sym)
222
+ val originalName = originalNameOfClass(sym)
218
223
val isHijacked = false // isHijackedBoxedClass(sym)
219
224
220
225
// Optimizer hints
@@ -308,14 +313,51 @@ class JSCodeGen()(implicit ctx: Context) {
308
313
309
314
val staticInitializerStats = reflectInit.toList
310
315
if (staticInitializerStats.nonEmpty)
311
- Some (genStaticInitializerWithStats(js.Block (staticInitializerStats)))
316
+ List (genStaticInitializerWithStats(js.Block (staticInitializerStats)))
312
317
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
+ }
314
357
}
315
358
316
359
// Hashed definitions of the class
317
- val hashedDefs =
318
- ir.Hashers .hashMemberDefs(generatedMembers ++ exports ++ optStaticInitializer)
360
+ val hashedDefs = ir.Hashers .hashMemberDefs(allMemberDefs)
319
361
320
362
// The complete class definition
321
363
val kind =
@@ -325,7 +367,7 @@ class JSCodeGen()(implicit ctx: Context) {
325
367
326
368
val classDefinition = js.ClassDef (
327
369
classIdent,
328
- originalNameOfClass(sym) ,
370
+ originalName ,
329
371
kind,
330
372
None ,
331
373
Some (encodeClassNameIdent(sym.superClass)),
@@ -386,7 +428,7 @@ class JSCodeGen()(implicit ctx: Context) {
386
428
*/
387
429
private def genInterface (td : TypeDef ): js.ClassDef = {
388
430
val sym = td.symbol.asClass
389
- implicit val pos : Position = sym.span
431
+ implicit val pos : SourcePosition = sym.sourcePos
390
432
391
433
val classIdent = encodeClassNameIdent(sym)
392
434
@@ -407,9 +449,13 @@ class JSCodeGen()(implicit ctx: Context) {
407
449
408
450
val superInterfaces = genClassInterfaces(sym)
409
451
452
+ val genMethodsList = generatedMethods.toList
453
+ val allMemberDefs =
454
+ if (! isCandidateForForwarders(sym)) genMethodsList
455
+ else genMethodsList ::: genStaticForwardersForClassOrInterface(genMethodsList, sym)
456
+
410
457
// Hashed definitions of the interface
411
- val hashedDefs =
412
- ir.Hashers .hashMemberDefs(generatedMethods.toList)
458
+ val hashedDefs = ir.Hashers .hashMemberDefs(allMemberDefs)
413
459
414
460
js.ClassDef (
415
461
classIdent,
@@ -435,6 +481,118 @@ class JSCodeGen()(implicit ctx: Context) {
435
481
}
436
482
}
437
483
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
+
438
596
// Generate the fields of a class ------------------------------------------
439
597
440
598
/** Gen definitions for the fields of a class.
@@ -1305,14 +1463,12 @@ class JSCodeGen()(implicit ctx: Context) {
1305
1463
args : List [js.Tree ])(implicit pos : SourcePosition ): js.Tree = {
1306
1464
1307
1465
val className = encodeClassName(clazz)
1308
- val moduleClass = clazz.companionModule.moduleClass
1309
-
1310
1466
val initName = encodeMethodSym(ctor).name
1311
1467
val newName = MethodName (newSimpleMethodName, initName.paramTypeRefs,
1312
1468
jstpe.ClassRef (className))
1313
1469
val newMethodIdent = js.MethodIdent (newName)
1314
1470
1315
- js.Apply (js.ApplyFlags .empty, genLoadModule(moduleClass) , newMethodIdent, args)(
1471
+ js.ApplyStatic (js.ApplyFlags .empty, className , newMethodIdent, args)(
1316
1472
jstpe.ClassType (className))
1317
1473
}
1318
1474
@@ -1678,7 +1834,7 @@ class JSCodeGen()(implicit ctx: Context) {
1678
1834
} else externalEquals
1679
1835
// scalastyle:on line.size.limit
1680
1836
}
1681
- genModuleApplyMethod (equalsMethod, List (lsrc, rsrc))
1837
+ genApplyStatic (equalsMethod, List (lsrc, rsrc))
1682
1838
} else {
1683
1839
// if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc)
1684
1840
if (lsym == defn.StringClass ) {
@@ -2727,9 +2883,9 @@ class JSCodeGen()(implicit ctx: Context) {
2727
2883
} else if (sym == defn.BoxedUnit_TYPE ) {
2728
2884
js.ClassOf (jstpe.VoidRef )
2729
2885
} else {
2730
- val inst = genLoadModule (sym.owner)
2886
+ val className = encodeClassName (sym.owner)
2731
2887
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))
2733
2889
}
2734
2890
}
2735
2891
@@ -2909,7 +3065,7 @@ class JSCodeGen()(implicit ctx: Context) {
2909
3065
}
2910
3066
2911
3067
private def isMethodStaticInIR (sym : Symbol ): Boolean =
2912
- sym.is(JavaStatic , butNot = JavaDefined )
3068
+ sym.is(JavaStatic )
2913
3069
2914
3070
/** Generate a Class[_] value (e.g. coming from classOf[T]) */
2915
3071
private def genClassConstant (tpe : Type )(implicit pos : Position ): js.Tree =
0 commit comments