@@ -69,6 +69,7 @@ class JSCodeGen()(using genCtx: Context) {
6969 // Some state --------------------------------------------------------------
7070
7171 private val generatedClasses = mutable.ListBuffer .empty[js.ClassDef ]
72+ private val generatedStaticForwarderClasses = mutable.ListBuffer .empty[(Symbol , js.ClassDef )]
7273
7374 private val currentClassSym = new ScopedVar [Symbol ]
7475 private val currentMethodSym = new ScopedVar [Symbol ]
@@ -111,6 +112,7 @@ class JSCodeGen()(using genCtx: Context) {
111112 genCompilationUnit(ctx.compilationUnit)
112113 } finally {
113114 generatedClasses.clear()
115+ generatedStaticForwarderClasses.clear()
114116 }
115117 }
116118
@@ -179,6 +181,39 @@ class JSCodeGen()(using genCtx: Context) {
179181
180182 for (tree <- generatedClasses)
181183 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+ }
182217 }
183218
184219 private def genIRFile (cunit : CompilationUnit , tree : ir.Trees .ClassDef ): Unit = {
@@ -346,7 +381,7 @@ class JSCodeGen()(using genCtx: Context) {
346381 forwarders,
347382 Nil
348383 )(js.OptimizerHints .empty)
349- generatedClasses += forwardersClassDef
384+ generatedStaticForwarderClasses += sym -> forwardersClassDef
350385 }
351386 }
352387 allMemberDefsExceptStaticForwarders
0 commit comments