@@ -69,6 +69,7 @@ class JSCodeGen()(using genCtx: Context) {
69
69
// Some state --------------------------------------------------------------
70
70
71
71
private val generatedClasses = mutable.ListBuffer .empty[js.ClassDef ]
72
+ private val generatedStaticForwarderClasses = mutable.ListBuffer .empty[(Symbol , js.ClassDef )]
72
73
73
74
private val currentClassSym = new ScopedVar [Symbol ]
74
75
private val currentMethodSym = new ScopedVar [Symbol ]
@@ -111,6 +112,7 @@ class JSCodeGen()(using genCtx: Context) {
111
112
genCompilationUnit(ctx.compilationUnit)
112
113
} finally {
113
114
generatedClasses.clear()
115
+ generatedStaticForwarderClasses.clear()
114
116
}
115
117
}
116
118
@@ -179,6 +181,39 @@ class JSCodeGen()(using genCtx: Context) {
179
181
180
182
for (tree <- generatedClasses)
181
183
genIRFile(cunit, tree)
184
+
185
+ if (generatedStaticForwarderClasses.nonEmpty) {
186
+ /* #4148 Add generated static forwarder classes, except those that
187
+ * would collide with regular classes on case insensitive file systems.
188
+ */
189
+
190
+ /* I could not find any reference anywhere about what locale is used
191
+ * by case insensitive file systems to compare case-insensitively.
192
+ * In doubt, force the English locale, which is probably going to do
193
+ * the right thing in virtually all cases (especially if users stick
194
+ * to ASCII class names), and it has the merit of being deterministic,
195
+ * as opposed to using the OS' default locale.
196
+ * The JVM backend performs a similar test to emit a warning for
197
+ * conflicting top-level classes. However, it uses `toLowerCase()`
198
+ * without argument, which is not deterministic.
199
+ */
200
+ def caseInsensitiveNameOf (classDef : js.ClassDef ): String =
201
+ classDef.name.name.nameString.toLowerCase(java.util.Locale .ENGLISH )
202
+
203
+ val generatedCaseInsensitiveNames =
204
+ generatedClasses.map(caseInsensitiveNameOf).toSet
205
+
206
+ for ((site, classDef) <- generatedStaticForwarderClasses) {
207
+ if (! generatedCaseInsensitiveNames.contains(caseInsensitiveNameOf(classDef))) {
208
+ genIRFile(cunit, classDef)
209
+ } else {
210
+ report.warning(
211
+ s " Not generating the static forwarders of ${classDef.name.name.nameString} " +
212
+ " because its name differs only in case from the name of another class or trait in this compilation unit." ,
213
+ site.srcPos)
214
+ }
215
+ }
216
+ }
182
217
}
183
218
184
219
private def genIRFile (cunit : CompilationUnit , tree : ir.Trees .ClassDef ): Unit = {
@@ -346,7 +381,7 @@ class JSCodeGen()(using genCtx: Context) {
346
381
forwarders,
347
382
Nil
348
383
)(js.OptimizerHints .empty)
349
- generatedClasses += forwardersClassDef
384
+ generatedStaticForwarderClasses += sym -> forwardersClassDef
350
385
}
351
386
}
352
387
allMemberDefsExceptStaticForwarders
@@ -523,14 +558,33 @@ class JSCodeGen()(using genCtx: Context) {
523
558
* `def hashCode(): Int` and a `static def hashCode(Int): Int`. The JVM
524
559
* back-end considers them as colliding because they have the same name,
525
560
* but we must not.
561
+ *
562
+ * By default, we only emit forwarders for top-level objects, like the JVM
563
+ * back-end. However, if requested via a compiler option, we enable them
564
+ * for all static objects. This is important so we can implement static
565
+ * methods of nested static classes of JDK APIs (see scala-js/#3950).
526
566
*/
527
567
528
568
/** Is the given Scala class, interface or module class a candidate for
529
569
* static forwarders?
570
+ *
571
+ * - the flag `-XnoForwarders` is not set to true, and
572
+ * - the symbol is static, and
573
+ * - either of both of the following is true:
574
+ * - the flag `-scalajsGenStaticForwardersForNonTopLevelObjects` is set to true, or
575
+ * - the symbol was originally at the package level
576
+ *
577
+ * Other than the Scala.js-specific flag, and the fact that we also consider
578
+ * interfaces, this performs the same tests as the JVM back-end.
530
579
*/
531
580
def isCandidateForForwarders (sym : Symbol ): Boolean = {
532
- // it must be a top level class
533
- sym.isStatic
581
+ ! ctx.settings.XnoForwarders .value && sym.isStatic && {
582
+ ctx.settings.scalajsGenStaticForwardersForNonTopLevelObjects.value || {
583
+ atPhase(flattenPhase) {
584
+ toDenot(sym).owner.is(PackageClass )
585
+ }
586
+ }
587
+ }
534
588
}
535
589
536
590
/** Gen the static forwarders to the members of a class or interface for
0 commit comments