From 3765ee1a120a02336cac020dceb9007b4eac11a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 13 Dec 2019 15:21:01 +0100 Subject: [PATCH 1/2] Derive the .sjsir file names from the ClassDef's names. Now that we have non-mangled names as `ClassDef`'s names, we can derive the corresponding file names from that, instead of having to carry around the original symbol and an optional suffix. This is a forward port of https://github.com/scala-js/scala-js/commit/44996ce1500e4259b8d207efc31e2de59d8c858c --- .../dotty/tools/backend/sjs/JSCodeGen.scala | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index d28c775023e3..920e56026beb 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -137,7 +137,7 @@ class JSCodeGen()(implicit ctx: Context) { } val allTypeDefs = collectTypeDefs(cunit.tpdTree) - val generatedClasses = mutable.ListBuffer.empty[(Symbol, js.ClassDef)] + val generatedClasses = mutable.ListBuffer.empty[js.ClassDef] // TODO Record anonymous JS function classes @@ -167,20 +167,17 @@ class JSCodeGen()(implicit ctx: Context) { genScalaClass(td) } - generatedClasses += ((sym, tree)) + generatedClasses += tree } } } - val clDefs = generatedClasses.map(_._2).toList - - for ((sym, tree) <- generatedClasses) - genIRFile(cunit, sym, tree) + for (tree <- generatedClasses) + genIRFile(cunit, tree) } - private def genIRFile(cunit: CompilationUnit, sym: Symbol, - tree: ir.Trees.ClassDef): Unit = { - val outfile = getFileFor(cunit, sym, ".sjsir") + private def genIRFile(cunit: CompilationUnit, tree: ir.Trees.ClassDef): Unit = { + val outfile = getFileFor(cunit, tree.name.name, ".sjsir") val output = outfile.bufferedOutput try { ir.Serializers.serialize(output, tree) @@ -189,21 +186,13 @@ class JSCodeGen()(implicit ctx: Context) { } } - private def getFileFor(cunit: CompilationUnit, sym: Symbol, + private def getFileFor(cunit: CompilationUnit, className: ClassName, suffix: String): dotty.tools.io.AbstractFile = { - import dotty.tools.io._ - - val outputDirectory: AbstractFile = - ctx.settings.outputDir.value - - val pathParts = sym.fullName.toString.split("[./]") + val outputDirectory = ctx.settings.outputDir.value + val pathParts = className.nameString.split('.') val dir = pathParts.init.foldLeft(outputDirectory)(_.subdirectoryNamed(_)) - - var filename = pathParts.last - if (sym.is(ModuleClass)) - filename = filename + nme.MODULE_SUFFIX.toString - - dir fileNamed (filename + suffix) + val filename = pathParts.last + dir.fileNamed(filename + suffix) } // Generate a class -------------------------------------------------------- From c99d40723535b6b52016c4e9c3135803f2a7de27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 13 Dec 2019 18:24:58 +0100 Subject: [PATCH 2/2] Upgrade to Scala.js 1.0.0-RC2. This includes changes to the contract of calling static methods in Java-defined classes. They are now actually called as static methods in the IR. We also generate static forwarders for public methods in static objects. These changes are a forward-port of https://github.com/scala-js/scala-js/commit/f94713bfe192b3426836f6c712855f8cc0ddb28b The only real difference is that in scalac, we emit a compile error if the companion class contains *any* public static method, because scalac does not otherwise produce public static methods at the moment. Since dotty does generate public static methods for the bodies of SAMs, we only complain if there is an actual clash. --- .../backend/jvm/DottyBackendInterface.scala | 12 +- .../dotty/tools/backend/sjs/JSCodeGen.scala | 192 ++++++++++++++++-- .../dotty/tools/backend/sjs/JSEncoding.scala | 8 +- project/plugins.sbt | 2 +- 4 files changed, 189 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index a453caa02f62..1cfe4de9497e 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -427,10 +427,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma val Flag_SYNTHETIC: Flags = Flags.Synthetic.bits val Flag_METHOD: Flags = Flags.Method.bits - val ExcludedForwarderFlags: Flags = { - Flags.Specialized | Flags.Lifted | Flags.Protected | Flags.JavaStatic | - Flags.Private | Flags.Macro - }.bits + val ExcludedForwarderFlags: Flags = DottyBackendInterface.ExcludedForwarderFlags.bits def isQualifierSafeToElide(qual: Tree): Boolean = tpd.isIdempotentExpr(qual) @@ -1195,3 +1192,10 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma def currentUnit: CompilationUnit = ctx.compilationUnit } + +object DottyBackendInterface { + val ExcludedForwarderFlags: Flags.FlagSet = { + Flags.Specialized | Flags.Lifted | Flags.Protected | Flags.JavaStatic | + Flags.Private | Flags.Macro + } +} diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 920e56026beb..89915e44eb16 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -67,6 +67,8 @@ class JSCodeGen()(implicit ctx: Context) { // Some state -------------------------------------------------------------- + private val generatedClasses = mutable.ListBuffer.empty[js.ClassDef] + private val currentClassSym = new ScopedVar[Symbol] private val currentMethodSym = new ScopedVar[Symbol] private val localNames = new ScopedVar[LocalNameGenerator] @@ -104,7 +106,11 @@ class JSCodeGen()(implicit ctx: Context) { // Compilation unit -------------------------------------------------------- def run(): Unit = { - genCompilationUnit(ctx.compilationUnit) + try { + genCompilationUnit(ctx.compilationUnit) + } finally { + generatedClasses.clear() + } } /** Generates the Scala.js IR for a compilation unit @@ -137,8 +143,6 @@ class JSCodeGen()(implicit ctx: Context) { } val allTypeDefs = collectTypeDefs(cunit.tpdTree) - val generatedClasses = mutable.ListBuffer.empty[js.ClassDef] - // TODO Record anonymous JS function classes /* Finally, we emit true code for the remaining class defs. */ @@ -215,6 +219,7 @@ class JSCodeGen()(implicit ctx: Context) { }*/ val classIdent = encodeClassNameIdent(sym) + val originalName = originalNameOfClass(sym) val isHijacked = false //isHijackedBoxedClass(sym) // Optimizer hints @@ -308,14 +313,51 @@ class JSCodeGen()(implicit ctx: Context) { val staticInitializerStats = reflectInit.toList if (staticInitializerStats.nonEmpty) - Some(genStaticInitializerWithStats(js.Block(staticInitializerStats))) + List(genStaticInitializerWithStats(js.Block(staticInitializerStats))) else - None + Nil + } + + val allMemberDefsExceptStaticForwarders = + generatedMembers ::: exports ::: optStaticInitializer + + // Add static forwarders + val allMemberDefs = if (!isCandidateForForwarders(sym)) { + allMemberDefsExceptStaticForwarders + } else { + if (isStaticModule(sym)) { + /* If the module class has no linked class, we must create one to + * hold the static forwarders. Otherwise, this is going to be handled + * when generating the companion class. + */ + if (!sym.linkedClass.exists) { + val forwarders = genStaticForwardersFromModuleClass(Nil, sym) + if (forwarders.nonEmpty) { + val forwardersClassDef = js.ClassDef( + js.ClassIdent(ClassName(classIdent.name.nameString.stripSuffix("$"))), + originalName, + ClassKind.Class, + None, + Some(js.ClassIdent(ir.Names.ObjectClass)), + Nil, + None, + None, + forwarders, + Nil + )(js.OptimizerHints.empty) + generatedClasses += forwardersClassDef + } + } + allMemberDefsExceptStaticForwarders + } else { + val forwarders = genStaticForwardersForClassOrInterface( + allMemberDefsExceptStaticForwarders, sym) + allMemberDefsExceptStaticForwarders ::: forwarders + } } // Hashed definitions of the class - val hashedDefs = - ir.Hashers.hashMemberDefs(generatedMembers ++ exports ++ optStaticInitializer) + val hashedDefs = ir.Hashers.hashMemberDefs(allMemberDefs) // The complete class definition val kind = @@ -325,7 +367,7 @@ class JSCodeGen()(implicit ctx: Context) { val classDefinition = js.ClassDef( classIdent, - originalNameOfClass(sym), + originalName, kind, None, Some(encodeClassNameIdent(sym.superClass)), @@ -386,7 +428,7 @@ class JSCodeGen()(implicit ctx: Context) { */ private def genInterface(td: TypeDef): js.ClassDef = { val sym = td.symbol.asClass - implicit val pos: Position = sym.span + implicit val pos: SourcePosition = sym.sourcePos val classIdent = encodeClassNameIdent(sym) @@ -407,9 +449,13 @@ class JSCodeGen()(implicit ctx: Context) { val superInterfaces = genClassInterfaces(sym) + val genMethodsList = generatedMethods.toList + val allMemberDefs = + if (!isCandidateForForwarders(sym)) genMethodsList + else genMethodsList ::: genStaticForwardersForClassOrInterface(genMethodsList, sym) + // Hashed definitions of the interface - val hashedDefs = - ir.Hashers.hashMemberDefs(generatedMethods.toList) + val hashedDefs = ir.Hashers.hashMemberDefs(allMemberDefs) js.ClassDef( classIdent, @@ -435,6 +481,118 @@ class JSCodeGen()(implicit ctx: Context) { } } + // Static forwarders ------------------------------------------------------- + + /* This mimics the logic in BCodeHelpers.addForwarders and the code that + * calls it, except that we never have collisions with existing methods in + * the companion class. This is because in the IR, only methods with the + * same `MethodName` (including signature) and that are also + * `PublicStatic` would collide. There should never be an actual collision + * because the only `PublicStatic` methods that are otherwise generated are + * the bodies of SAMs, which have mangled names. If that assumption is + * broken, an error message is emitted asking the user to report a bug. + * + * It is important that we always emit forwarders, because some Java APIs + * actually have a public static method and a public instance method with + * the same name. For example the class `Integer` has a + * `def hashCode(): Int` and a `static def hashCode(Int): Int`. The JVM + * back-end considers them as colliding because they have the same name, + * but we must not. + */ + + /** Is the given Scala class, interface or module class a candidate for + * static forwarders? + */ + def isCandidateForForwarders(sym: Symbol): Boolean = { + // it must be a top level class + sym.isStatic + } + + /** Gen the static forwarders to the members of a class or interface for + * methods of its companion object. + * + * This is only done if there exists a companion object and it is not a JS + * type. + * + * Precondition: `isCandidateForForwarders(sym)` is true + */ + def genStaticForwardersForClassOrInterface( + existingMembers: List[js.MemberDef], sym: Symbol)( + implicit pos: SourcePosition): List[js.MemberDef] = { + val module = sym.companionModule + if (!module.exists) { + Nil + } else { + val moduleClass = module.moduleClass + if (!isJSType(moduleClass)) + genStaticForwardersFromModuleClass(existingMembers, moduleClass) + else + Nil + } + } + + /** Gen the static forwarders for the methods of a module class. + * + * Precondition: `isCandidateForForwarders(moduleClass)` is true + */ + def genStaticForwardersFromModuleClass(existingMembers: List[js.MemberDef], + moduleClass: Symbol)( + implicit pos: SourcePosition): List[js.MemberDef] = { + + assert(moduleClass.is(ModuleClass), moduleClass) + + val existingPublicStaticMethodNames = existingMembers.collect { + case js.MethodDef(flags, name, _, _, _, _) + if flags.namespace == js.MemberNamespace.PublicStatic => + name.name + }.toSet + + val members = { + import dotty.tools.backend.jvm.DottyBackendInterface.ExcludedForwarderFlags + moduleClass.info.membersBasedOnFlags(required = Flags.Method, + excluded = ExcludedForwarderFlags).map(_.symbol) + } + + def isExcluded(m: Symbol): Boolean = { + def hasAccessBoundary = m.accessBoundary(defn.RootClass) ne defn.RootClass + m.is(Deferred) || m.isConstructor || hasAccessBoundary || (m.owner eq defn.ObjectClass) + } + + val forwarders = for { + m <- members + if !isExcluded(m) + } yield { + withNewLocalNameScope { + val flags = js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic) + val methodIdent = encodeMethodSym(m) + val originalName = originalNameOfMethod(m) + val jsParams = for { + (paramName, paramInfo) <- m.info.paramNamess.flatten.zip(m.info.paramInfoss.flatten) + } yield { + js.ParamDef(freshLocalIdent(paramName), NoOriginalName, + toIRType(paramInfo), mutable = false, rest = false) + } + val resultType = toIRType(m.info.resultType) + + if (existingPublicStaticMethodNames.contains(methodIdent.name)) { + ctx.error( + "Unexpected situation: found existing public static method " + + s"${methodIdent.name.nameString} in the companion class of " + + s"${moduleClass.fullName}; cannot generate a static forwarder " + + "the method of the same name in the object." + + "Please report this as a bug in the Scala.js support in dotty.", + pos) + } + + js.MethodDef(flags, methodIdent, originalName, jsParams, resultType, Some { + genApplyMethod(genLoadModule(moduleClass), m, jsParams.map(_.ref)) + })(OptimizerHints.empty, None) + } + } + + forwarders.toList + } + // Generate the fields of a class ------------------------------------------ /** Gen definitions for the fields of a class. @@ -1305,14 +1463,12 @@ class JSCodeGen()(implicit ctx: Context) { args: List[js.Tree])(implicit pos: SourcePosition): js.Tree = { val className = encodeClassName(clazz) - val moduleClass = clazz.companionModule.moduleClass - val initName = encodeMethodSym(ctor).name val newName = MethodName(newSimpleMethodName, initName.paramTypeRefs, jstpe.ClassRef(className)) val newMethodIdent = js.MethodIdent(newName) - js.Apply(js.ApplyFlags.empty, genLoadModule(moduleClass), newMethodIdent, args)( + js.ApplyStatic(js.ApplyFlags.empty, className, newMethodIdent, args)( jstpe.ClassType(className)) } @@ -1678,7 +1834,7 @@ class JSCodeGen()(implicit ctx: Context) { } else externalEquals // scalastyle:on line.size.limit } - genModuleApplyMethod(equalsMethod, List(lsrc, rsrc)) + genApplyStatic(equalsMethod, List(lsrc, rsrc)) } else { // if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc) if (lsym == defn.StringClass) { @@ -2727,9 +2883,9 @@ class JSCodeGen()(implicit ctx: Context) { } else if (sym == defn.BoxedUnit_TYPE) { js.ClassOf(jstpe.VoidRef) } else { - val inst = genLoadModule(sym.owner) + val className = encodeClassName(sym.owner) val method = encodeStaticMemberSym(sym) - js.Apply(js.ApplyFlags.empty, inst, method, Nil)(toIRType(sym.info)) + js.ApplyStatic(js.ApplyFlags.empty, className, method, Nil)(toIRType(sym.info)) } } @@ -2909,7 +3065,7 @@ class JSCodeGen()(implicit ctx: Context) { } private def isMethodStaticInIR(sym: Symbol): Boolean = - sym.is(JavaStatic, butNot = JavaDefined) + sym.is(JavaStatic) /** Generate a Class[_] value (e.g. coming from classOf[T]) */ private def genClassConstant(tpe: Type)(implicit pos: Position): js.Tree = diff --git a/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala b/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala index 8e65af9adb68..eefa11436bbb 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala @@ -237,13 +237,17 @@ object JSEncoding { js.ClassIdent(encodeClassName(sym)) def encodeClassName(sym: Symbol)(implicit ctx: Context): ClassName = { - if (sym == defn.BoxedUnitClass) { + val sym1 = + if (sym.isAllOf(ModuleClass | JavaDefined)) sym.linkedClass + else sym + + if (sym1 == defn.BoxedUnitClass) { /* Rewire scala.runtime.BoxedUnit to java.lang.Void, as the IR expects. * BoxedUnit$ is a JVM artifact. */ ir.Names.BoxedUnitClass } else { - ClassName(sym.fullName.mangledString) + ClassName(sym1.fullName.mangledString) } } diff --git a/project/plugins.sbt b/project/plugins.sbt index 65a15fe6390d..7a14c675410f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,7 +2,7 @@ // // e.g. addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.0-RC1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.0-RC2") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.6")