@@ -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[(Symbol , js.ClassDef )]
141-
142146 // TODO Record anonymous JS function classes
143147
144148 /* Finally, we emit true code for the remaining class defs. */
@@ -167,20 +171,17 @@ class JSCodeGen()(implicit ctx: Context) {
167171 genScalaClass(td)
168172 }
169173
170- generatedClasses += ((sym, tree))
174+ generatedClasses += tree
171175 }
172176 }
173177 }
174178
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)
179181 }
180182
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" )
184185 val output = outfile.bufferedOutput
185186 try {
186187 ir.Serializers .serialize(output, tree)
@@ -189,21 +190,13 @@ class JSCodeGen()(implicit ctx: Context) {
189190 }
190191 }
191192
192- private def getFileFor (cunit : CompilationUnit , sym : Symbol ,
193+ private def getFileFor (cunit : CompilationUnit , className : ClassName ,
193194 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('.' )
200197 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)
207200 }
208201
209202 // Generate a class --------------------------------------------------------
@@ -226,6 +219,7 @@ class JSCodeGen()(implicit ctx: Context) {
226219 }*/
227220
228221 val classIdent = encodeClassNameIdent(sym)
222+ val originalName = originalNameOfClass(sym)
229223 val isHijacked = false // isHijackedBoxedClass(sym)
230224
231225 // Optimizer hints
@@ -319,14 +313,51 @@ class JSCodeGen()(implicit ctx: Context) {
319313
320314 val staticInitializerStats = reflectInit.toList
321315 if (staticInitializerStats.nonEmpty)
322- Some (genStaticInitializerWithStats(js.Block (staticInitializerStats)))
316+ List (genStaticInitializerWithStats(js.Block (staticInitializerStats)))
323317 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+ }
325357 }
326358
327359 // Hashed definitions of the class
328- val hashedDefs =
329- ir.Hashers .hashMemberDefs(generatedMembers ++ exports ++ optStaticInitializer)
360+ val hashedDefs = ir.Hashers .hashMemberDefs(allMemberDefs)
330361
331362 // The complete class definition
332363 val kind =
@@ -336,7 +367,7 @@ class JSCodeGen()(implicit ctx: Context) {
336367
337368 val classDefinition = js.ClassDef (
338369 classIdent,
339- originalNameOfClass(sym) ,
370+ originalName ,
340371 kind,
341372 None ,
342373 Some (encodeClassNameIdent(sym.superClass)),
@@ -397,7 +428,7 @@ class JSCodeGen()(implicit ctx: Context) {
397428 */
398429 private def genInterface (td : TypeDef ): js.ClassDef = {
399430 val sym = td.symbol.asClass
400- implicit val pos : Position = sym.span
431+ implicit val pos : SourcePosition = sym.sourcePos
401432
402433 val classIdent = encodeClassNameIdent(sym)
403434
@@ -418,9 +449,13 @@ class JSCodeGen()(implicit ctx: Context) {
418449
419450 val superInterfaces = genClassInterfaces(sym)
420451
452+ val genMethodsList = generatedMethods.toList
453+ val allMemberDefs =
454+ if (! isCandidateForForwarders(sym)) genMethodsList
455+ else genMethodsList ::: genStaticForwardersForClassOrInterface(genMethodsList, sym)
456+
421457 // Hashed definitions of the interface
422- val hashedDefs =
423- ir.Hashers .hashMemberDefs(generatedMethods.toList)
458+ val hashedDefs = ir.Hashers .hashMemberDefs(allMemberDefs)
424459
425460 js.ClassDef (
426461 classIdent,
@@ -446,6 +481,118 @@ class JSCodeGen()(implicit ctx: Context) {
446481 }
447482 }
448483
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+
449596 // Generate the fields of a class ------------------------------------------
450597
451598 /** Gen definitions for the fields of a class.
@@ -1316,14 +1463,12 @@ class JSCodeGen()(implicit ctx: Context) {
13161463 args : List [js.Tree ])(implicit pos : SourcePosition ): js.Tree = {
13171464
13181465 val className = encodeClassName(clazz)
1319- val moduleClass = clazz.companionModule.moduleClass
1320-
13211466 val initName = encodeMethodSym(ctor).name
13221467 val newName = MethodName (newSimpleMethodName, initName.paramTypeRefs,
13231468 jstpe.ClassRef (className))
13241469 val newMethodIdent = js.MethodIdent (newName)
13251470
1326- js.Apply (js.ApplyFlags .empty, genLoadModule(moduleClass) , newMethodIdent, args)(
1471+ js.ApplyStatic (js.ApplyFlags .empty, className , newMethodIdent, args)(
13271472 jstpe.ClassType (className))
13281473 }
13291474
@@ -1689,7 +1834,7 @@ class JSCodeGen()(implicit ctx: Context) {
16891834 } else externalEquals
16901835 // scalastyle:on line.size.limit
16911836 }
1692- genModuleApplyMethod (equalsMethod, List (lsrc, rsrc))
1837+ genApplyStatic (equalsMethod, List (lsrc, rsrc))
16931838 } else {
16941839 // if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc)
16951840 if (lsym == defn.StringClass ) {
@@ -2738,9 +2883,9 @@ class JSCodeGen()(implicit ctx: Context) {
27382883 } else if (sym == defn.BoxedUnit_TYPE ) {
27392884 js.ClassOf (jstpe.VoidRef )
27402885 } else {
2741- val inst = genLoadModule (sym.owner)
2886+ val className = encodeClassName (sym.owner)
27422887 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))
27442889 }
27452890 }
27462891
@@ -2920,7 +3065,7 @@ class JSCodeGen()(implicit ctx: Context) {
29203065 }
29213066
29223067 private def isMethodStaticInIR (sym : Symbol ): Boolean =
2923- sym.is(JavaStatic , butNot = JavaDefined )
3068+ sym.is(JavaStatic )
29243069
29253070 /** Generate a Class[_] value (e.g. coming from classOf[T]) */
29263071 private def genClassConstant (tpe : Type )(implicit pos : Position ): js.Tree =
0 commit comments