@@ -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[js.ClassDef ]
141-
142146 // TODO Record anonymous JS function classes
143147
144148 /* Finally, we emit true code for the remaining class defs. */
@@ -215,6 +219,7 @@ class JSCodeGen()(implicit ctx: Context) {
215219 }*/
216220
217221 val classIdent = encodeClassNameIdent(sym)
222+ val originalName = originalNameOfClass(sym)
218223 val isHijacked = false // isHijackedBoxedClass(sym)
219224
220225 // Optimizer hints
@@ -308,14 +313,51 @@ class JSCodeGen()(implicit ctx: Context) {
308313
309314 val staticInitializerStats = reflectInit.toList
310315 if (staticInitializerStats.nonEmpty)
311- Some (genStaticInitializerWithStats(js.Block (staticInitializerStats)))
316+ List (genStaticInitializerWithStats(js.Block (staticInitializerStats)))
312317 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+ }
314357 }
315358
316359 // Hashed definitions of the class
317- val hashedDefs =
318- ir.Hashers .hashMemberDefs(generatedMembers ++ exports ++ optStaticInitializer)
360+ val hashedDefs = ir.Hashers .hashMemberDefs(allMemberDefs)
319361
320362 // The complete class definition
321363 val kind =
@@ -325,7 +367,7 @@ class JSCodeGen()(implicit ctx: Context) {
325367
326368 val classDefinition = js.ClassDef (
327369 classIdent,
328- originalNameOfClass(sym) ,
370+ originalName ,
329371 kind,
330372 None ,
331373 Some (encodeClassNameIdent(sym.superClass)),
@@ -386,7 +428,7 @@ class JSCodeGen()(implicit ctx: Context) {
386428 */
387429 private def genInterface (td : TypeDef ): js.ClassDef = {
388430 val sym = td.symbol.asClass
389- implicit val pos : Position = sym.span
431+ implicit val pos : SourcePosition = sym.sourcePos
390432
391433 val classIdent = encodeClassNameIdent(sym)
392434
@@ -407,9 +449,13 @@ class JSCodeGen()(implicit ctx: Context) {
407449
408450 val superInterfaces = genClassInterfaces(sym)
409451
452+ val genMethodsList = generatedMethods.toList
453+ val allMemberDefs =
454+ if (! isCandidateForForwarders(sym)) genMethodsList
455+ else genMethodsList ::: genStaticForwardersForClassOrInterface(genMethodsList, sym)
456+
410457 // Hashed definitions of the interface
411- val hashedDefs =
412- ir.Hashers .hashMemberDefs(generatedMethods.toList)
458+ val hashedDefs = ir.Hashers .hashMemberDefs(allMemberDefs)
413459
414460 js.ClassDef (
415461 classIdent,
@@ -435,6 +481,126 @@ class JSCodeGen()(implicit ctx: Context) {
435481 }
436482 }
437483
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+ private lazy val dontUseExitingUncurryForForwarders =
535+ scala.util.Properties .versionNumberString.startsWith(" 2.11." )
536+
537+ /** Gen the static forwarders for the methods of a module class.
538+ *
539+ * Precondition: `isCandidateForForwarders(moduleClass)` is true
540+ */
541+ def genStaticForwardersFromModuleClass (existingMembers : List [js.MemberDef ],
542+ moduleClass : Symbol )(
543+ implicit pos : SourcePosition ): List [js.MemberDef ] = {
544+
545+ assert(moduleClass.is(ModuleClass ), moduleClass)
546+
547+ val existingPublicStaticMethodNames = existingMembers.collect {
548+ case js.MethodDef (flags, name, _, _, _, _)
549+ if flags.namespace == js.MemberNamespace .PublicStatic =>
550+ name.name
551+ }.toSet
552+
553+ val members = {
554+ // Copied from DottyBackendInterface.ExcludedForwarderFlags
555+ val ExcludedForwarderFlags = {
556+ Flags .Specialized | Flags .Lifted | Flags .Protected | Flags .JavaStatic |
557+ Flags .Private | Flags .Macro
558+ }
559+
560+ moduleClass.info.membersBasedOnFlags(required = Flags .Method ,
561+ excluded = ExcludedForwarderFlags ).map(_.symbol)
562+ }
563+
564+ def isExcluded (m : Symbol ): Boolean = {
565+ def hasAccessBoundary = m.accessBoundary(defn.RootClass ) ne defn.RootClass
566+ m.is(Deferred ) || m.isConstructor || hasAccessBoundary || (m.owner eq defn.ObjectClass )
567+ }
568+
569+ val forwarders = for {
570+ m <- members
571+ if ! isExcluded(m)
572+ } yield {
573+ withNewLocalNameScope {
574+ val flags = js.MemberFlags .empty.withNamespace(js.MemberNamespace .PublicStatic )
575+ val methodIdent = encodeMethodSym(m)
576+ val originalName = originalNameOfMethod(m)
577+ val jsParams = for {
578+ (paramName, paramInfo) <- m.info.paramNamess.flatten.zip(m.info.paramInfoss.flatten)
579+ } yield {
580+ js.ParamDef (freshLocalIdent(paramName), NoOriginalName ,
581+ toIRType(paramInfo), mutable = false , rest = false )
582+ }
583+ val resultType = toIRType(m.info.resultType)
584+
585+ if (existingPublicStaticMethodNames.contains(methodIdent.name)) {
586+ ctx.error(
587+ " Unexpected situation: found existing public static method " +
588+ s " ${methodIdent.name.nameString} in the companion class of " +
589+ s " ${moduleClass.fullName}; cannot generate a static forwarder " +
590+ " the method of the same name in the object." +
591+ " Please report this as a bug in the Scala.js support in dotty." ,
592+ pos)
593+ }
594+
595+ js.MethodDef (flags, methodIdent, originalName, jsParams, resultType, Some {
596+ genApplyMethod(genLoadModule(moduleClass), m, jsParams.map(_.ref))
597+ })(OptimizerHints .empty, None )
598+ }
599+ }
600+
601+ forwarders.toList
602+ }
603+
438604 // Generate the fields of a class ------------------------------------------
439605
440606 /** Gen definitions for the fields of a class.
@@ -1305,14 +1471,12 @@ class JSCodeGen()(implicit ctx: Context) {
13051471 args : List [js.Tree ])(implicit pos : SourcePosition ): js.Tree = {
13061472
13071473 val className = encodeClassName(clazz)
1308- val moduleClass = clazz.companionModule.moduleClass
1309-
13101474 val initName = encodeMethodSym(ctor).name
13111475 val newName = MethodName (newSimpleMethodName, initName.paramTypeRefs,
13121476 jstpe.ClassRef (className))
13131477 val newMethodIdent = js.MethodIdent (newName)
13141478
1315- js.Apply (js.ApplyFlags .empty, genLoadModule(moduleClass) , newMethodIdent, args)(
1479+ js.ApplyStatic (js.ApplyFlags .empty, className , newMethodIdent, args)(
13161480 jstpe.ClassType (className))
13171481 }
13181482
@@ -1678,7 +1842,7 @@ class JSCodeGen()(implicit ctx: Context) {
16781842 } else externalEquals
16791843 // scalastyle:on line.size.limit
16801844 }
1681- genModuleApplyMethod (equalsMethod, List (lsrc, rsrc))
1845+ genApplyStatic (equalsMethod, List (lsrc, rsrc))
16821846 } else {
16831847 // if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc)
16841848 if (lsym == defn.StringClass ) {
@@ -2727,9 +2891,9 @@ class JSCodeGen()(implicit ctx: Context) {
27272891 } else if (sym == defn.BoxedUnit_TYPE ) {
27282892 js.ClassOf (jstpe.VoidRef )
27292893 } else {
2730- val inst = genLoadModule (sym.owner)
2894+ val className = encodeClassName (sym.owner)
27312895 val method = encodeStaticMemberSym(sym)
2732- js.Apply (js.ApplyFlags .empty, inst , method, Nil )(toIRType(sym.info))
2896+ js.ApplyStatic (js.ApplyFlags .empty, className , method, Nil )(toIRType(sym.info))
27332897 }
27342898 }
27352899
@@ -2909,7 +3073,7 @@ class JSCodeGen()(implicit ctx: Context) {
29093073 }
29103074
29113075 private def isMethodStaticInIR (sym : Symbol ): Boolean =
2912- sym.is(JavaStatic , butNot = JavaDefined )
3076+ sym.is(JavaStatic )
29133077
29143078 /** Generate a Class[_] value (e.g. coming from classOf[T]) */
29153079 private def genClassConstant (tpe : Type )(implicit pos : Position ): js.Tree =
0 commit comments