@@ -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[(Symbol , 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. */
@@ -167,20 +171,17 @@ class JSCodeGen()(implicit ctx: Context) {
167
171
genScalaClass(td)
168
172
}
169
173
170
- generatedClasses += ((sym, tree))
174
+ generatedClasses += tree
171
175
}
172
176
}
173
177
}
174
178
175
- val clDefs = generatedClasses.map(_._2).toList
176
-
177
- for ((sym, tree) <- generatedClasses)
178
- genIRFile(cunit, sym, tree)
179
+ for (tree <- generatedClasses)
180
+ genIRFile(cunit, tree)
179
181
}
180
182
181
- private def genIRFile (cunit : CompilationUnit , sym : Symbol ,
182
- tree : ir.Trees .ClassDef ): Unit = {
183
- val outfile = getFileFor(cunit, sym, " .sjsir" )
183
+ private def genIRFile (cunit : CompilationUnit , tree : ir.Trees .ClassDef ): Unit = {
184
+ val outfile = getFileFor(cunit, tree.name.name, " .sjsir" )
184
185
val output = outfile.bufferedOutput
185
186
try {
186
187
ir.Serializers .serialize(output, tree)
@@ -189,21 +190,13 @@ class JSCodeGen()(implicit ctx: Context) {
189
190
}
190
191
}
191
192
192
- private def getFileFor (cunit : CompilationUnit , sym : Symbol ,
193
+ private def getFileFor (cunit : CompilationUnit , className : ClassName ,
193
194
suffix : String ): dotty.tools.io.AbstractFile = {
194
- import dotty .tools .io ._
195
-
196
- val outputDirectory : AbstractFile =
197
- ctx.settings.outputDir.value
198
-
199
- val pathParts = sym.fullName.toString.split(" [./]" )
195
+ val outputDirectory = ctx.settings.outputDir.value
196
+ val pathParts = className.nameString.split('.' )
200
197
val dir = pathParts.init.foldLeft(outputDirectory)(_.subdirectoryNamed(_))
201
-
202
- var filename = pathParts.last
203
- if (sym.is(ModuleClass ))
204
- filename = filename + nme.MODULE_SUFFIX .toString
205
-
206
- dir fileNamed (filename + suffix)
198
+ val filename = pathParts.last
199
+ dir.fileNamed(filename + suffix)
207
200
}
208
201
209
202
// Generate a class --------------------------------------------------------
@@ -226,6 +219,7 @@ class JSCodeGen()(implicit ctx: Context) {
226
219
}*/
227
220
228
221
val classIdent = encodeClassNameIdent(sym)
222
+ val originalName = originalNameOfClass(sym)
229
223
val isHijacked = false // isHijackedBoxedClass(sym)
230
224
231
225
// Optimizer hints
@@ -319,14 +313,51 @@ class JSCodeGen()(implicit ctx: Context) {
319
313
320
314
val staticInitializerStats = reflectInit.toList
321
315
if (staticInitializerStats.nonEmpty)
322
- Some (genStaticInitializerWithStats(js.Block (staticInitializerStats)))
316
+ List (genStaticInitializerWithStats(js.Block (staticInitializerStats)))
323
317
else
324
- 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
+ }
325
357
}
326
358
327
359
// Hashed definitions of the class
328
- val hashedDefs =
329
- ir.Hashers .hashMemberDefs(generatedMembers ++ exports ++ optStaticInitializer)
360
+ val hashedDefs = ir.Hashers .hashMemberDefs(allMemberDefs)
330
361
331
362
// The complete class definition
332
363
val kind =
@@ -336,7 +367,7 @@ class JSCodeGen()(implicit ctx: Context) {
336
367
337
368
val classDefinition = js.ClassDef (
338
369
classIdent,
339
- originalNameOfClass(sym) ,
370
+ originalName ,
340
371
kind,
341
372
None ,
342
373
Some (encodeClassNameIdent(sym.superClass)),
@@ -397,7 +428,7 @@ class JSCodeGen()(implicit ctx: Context) {
397
428
*/
398
429
private def genInterface (td : TypeDef ): js.ClassDef = {
399
430
val sym = td.symbol.asClass
400
- implicit val pos : Position = sym.span
431
+ implicit val pos : SourcePosition = sym.sourcePos
401
432
402
433
val classIdent = encodeClassNameIdent(sym)
403
434
@@ -418,9 +449,13 @@ class JSCodeGen()(implicit ctx: Context) {
418
449
419
450
val superInterfaces = genClassInterfaces(sym)
420
451
452
+ val genMethodsList = generatedMethods.toList
453
+ val allMemberDefs =
454
+ if (! isCandidateForForwarders(sym)) genMethodsList
455
+ else genMethodsList ::: genStaticForwardersForClassOrInterface(genMethodsList, sym)
456
+
421
457
// Hashed definitions of the interface
422
- val hashedDefs =
423
- ir.Hashers .hashMemberDefs(generatedMethods.toList)
458
+ val hashedDefs = ir.Hashers .hashMemberDefs(allMemberDefs)
424
459
425
460
js.ClassDef (
426
461
classIdent,
@@ -446,6 +481,118 @@ class JSCodeGen()(implicit ctx: Context) {
446
481
}
447
482
}
448
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
+
449
596
// Generate the fields of a class ------------------------------------------
450
597
451
598
/** Gen definitions for the fields of a class.
@@ -1316,14 +1463,12 @@ class JSCodeGen()(implicit ctx: Context) {
1316
1463
args : List [js.Tree ])(implicit pos : SourcePosition ): js.Tree = {
1317
1464
1318
1465
val className = encodeClassName(clazz)
1319
- val moduleClass = clazz.companionModule.moduleClass
1320
-
1321
1466
val initName = encodeMethodSym(ctor).name
1322
1467
val newName = MethodName (newSimpleMethodName, initName.paramTypeRefs,
1323
1468
jstpe.ClassRef (className))
1324
1469
val newMethodIdent = js.MethodIdent (newName)
1325
1470
1326
- js.Apply (js.ApplyFlags .empty, genLoadModule(moduleClass) , newMethodIdent, args)(
1471
+ js.ApplyStatic (js.ApplyFlags .empty, className , newMethodIdent, args)(
1327
1472
jstpe.ClassType (className))
1328
1473
}
1329
1474
@@ -1689,7 +1834,7 @@ class JSCodeGen()(implicit ctx: Context) {
1689
1834
} else externalEquals
1690
1835
// scalastyle:on line.size.limit
1691
1836
}
1692
- genModuleApplyMethod (equalsMethod, List (lsrc, rsrc))
1837
+ genApplyStatic (equalsMethod, List (lsrc, rsrc))
1693
1838
} else {
1694
1839
// if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc)
1695
1840
if (lsym == defn.StringClass ) {
@@ -2738,9 +2883,9 @@ class JSCodeGen()(implicit ctx: Context) {
2738
2883
} else if (sym == defn.BoxedUnit_TYPE ) {
2739
2884
js.ClassOf (jstpe.VoidRef )
2740
2885
} else {
2741
- val inst = genLoadModule (sym.owner)
2886
+ val className = encodeClassName (sym.owner)
2742
2887
val method = encodeStaticMemberSym(sym)
2743
- js.Apply (js.ApplyFlags .empty, inst , method, Nil )(toIRType(sym.info))
2888
+ js.ApplyStatic (js.ApplyFlags .empty, className , method, Nil )(toIRType(sym.info))
2744
2889
}
2745
2890
}
2746
2891
@@ -2920,7 +3065,7 @@ class JSCodeGen()(implicit ctx: Context) {
2920
3065
}
2921
3066
2922
3067
private def isMethodStaticInIR (sym : Symbol ): Boolean =
2923
- sym.is(JavaStatic , butNot = JavaDefined )
3068
+ sym.is(JavaStatic )
2924
3069
2925
3070
/** Generate a Class[_] value (e.g. coming from classOf[T]) */
2926
3071
private def genClassConstant (tpe : Type )(implicit pos : Position ): js.Tree =
0 commit comments