diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 8328afd52573..6e2449b5c299 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -927,7 +927,7 @@ class JSCodeGen()(using genCtx: Context) { val className = encodeClassName(classSym) val body = js.Block( js.LoadModule(className), - js.SelectStatic(className, fieldIdent)(irTpe)) + js.SelectStatic(fieldIdent)(irTpe)) staticGetterDefs += js.MethodDef( js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic), encodeStaticMemberSym(f), originalName, Nil, irTpe, @@ -1146,42 +1146,72 @@ class JSCodeGen()(using genCtx: Context) { private def genPrimaryJSClassCtor(dd: DefDef): PrimaryJSCtor = { val sym = dd.symbol - val Block(stats, _) = dd.rhs: @unchecked assert(sym.isPrimaryConstructor, s"called with non-primary ctor: $sym") + var preSuperStats = List.newBuilder[js.Tree] var jsSuperCall: Option[js.JSSuperConstructorCall] = None - val jsStats = List.newBuilder[js.Tree] + val postSuperStats = List.newBuilder[js.Tree] - /* Move all statements after the super constructor call since JS - * cannot access `this` before the super constructor call. + /* Move param accessor initializers after the super constructor call since + * JS cannot access `this` before the super constructor call. * * dotc inserts statements before the super constructor call for param * accessor initializers (including val's and var's declared in the - * params). We move those after the super constructor call, and are - * therefore executed later than for a Scala class. + * params). Those statements are assignments whose rhs'es are always simple + * Idents (the constructor params). + * + * There can also be local `val`s before the super constructor call for + * default arguments to the super constructor. These must remain before. + * + * Our strategy is therefore to move only the field assignments after the + * super constructor call. They are therefore executed later than for a + * Scala class (as specified for non-native JS classes semantics). + * However, side effects and evaluation order of all the other + * computations remains unchanged. */ withPerMethodBodyState(sym) { - stats.foreach { - case tree @ Apply(fun @ Select(Super(This(_), _), _), args) - if fun.symbol.isClassConstructor => - assert(jsSuperCall.isEmpty, s"Found 2 JS Super calls at ${dd.sourcePos}") - implicit val pos: Position = tree.span - jsSuperCall = Some(js.JSSuperConstructorCall(genActualJSArgs(fun.symbol, args))) + def isThisField(tree: Tree): Boolean = tree match { + case Select(ths: This, _) => ths.symbol == currentClassSym.get + case tree: Ident => desugarIdent(tree).exists(isThisField(_)) + case _ => false + } - case stat => - val jsStat = genStat(stat) - assert(jsSuperCall.isDefined || !jsStat.isInstanceOf[js.VarDef], - "Trying to move a local VarDef after the super constructor call of a non-native JS class at " + - dd.sourcePos) - jsStats += jsStat + def rec(tree: Tree): Unit = { + tree match { + case Block(stats, expr) => + stats.foreach(rec(_)) + rec(expr) + + case tree @ Apply(fun @ Select(Super(This(_), _), _), args) + if fun.symbol.isClassConstructor => + assert(jsSuperCall.isEmpty, s"Found 2 JS Super calls at ${dd.sourcePos}") + implicit val pos: Position = tree.span + jsSuperCall = Some(js.JSSuperConstructorCall(genActualJSArgs(fun.symbol, args))) + + case tree if jsSuperCall.isDefined => + // Once we're past the super constructor call, everything goes after. + postSuperStats += genStat(tree) + + case Assign(lhs, Ident(_)) if isThisField(lhs) => + /* If that shape appears before the jsSuperCall, it is a param + * accessor initializer. We move it. + */ + postSuperStats += genStat(tree) + + case stat => + // Other statements are left before. + preSuperStats += genStat(stat) + } } + + rec(dd.rhs) } assert(jsSuperCall.isDefined, s"Did not find Super call in primary JS construtor at ${dd.sourcePos}") new PrimaryJSCtor(sym, genParamsAndInfo(sym, dd.paramss), - js.JSConstructorBody(Nil, jsSuperCall.get, jsStats.result())(dd.span)) + js.JSConstructorBody(preSuperStats.result(), jsSuperCall.get, postSuperStats.result())(dd.span)) } private def genSecondaryJSClassCtor(dd: DefDef): SplitSecondaryJSCtor = { @@ -2213,10 +2243,7 @@ class JSCodeGen()(using genCtx: Context) { if (isStaticModule(currentClassSym) && !isModuleInitialized.get.value && currentMethodSym.get.isClassConstructor) { isModuleInitialized.get.value = true - val className = encodeClassName(currentClassSym) - val thisType = jstpe.ClassType(className) - val initModule = js.StoreModule(className, js.This()(thisType)) - js.Block(superCall, initModule) + js.Block(superCall, js.StoreModule()) } else { superCall } @@ -4433,13 +4460,12 @@ class JSCodeGen()(using genCtx: Context) { js.JSSelect(qual, genPrivateFieldsSymbol()), encodeFieldSymAsStringLiteral(sym)) } else { - js.JSPrivateSelect(qual, encodeClassName(sym.owner), - encodeFieldSym(sym)) + js.JSPrivateSelect(qual, encodeFieldSym(sym)) } (f, true) } else if (sym.hasAnnotation(jsdefn.JSExportTopLevelAnnot)) { - val f = js.SelectStatic(encodeClassName(sym.owner), encodeFieldSym(sym))(jstpe.AnyType) + val f = js.SelectStatic(encodeFieldSym(sym))(jstpe.AnyType) (f, true) } else if (sym.hasAnnotation(jsdefn.JSExportStaticAnnot)) { val jsName = sym.getAnnotation(jsdefn.JSExportStaticAnnot).get.argumentConstantString(0).getOrElse { @@ -4465,9 +4491,9 @@ class JSCodeGen()(using genCtx: Context) { val f = if sym.is(JavaStatic) then - js.SelectStatic(className, fieldIdent)(irType) + js.SelectStatic(fieldIdent)(irType) else - js.Select(qual, className, fieldIdent)(irType) + js.Select(qual, fieldIdent)(irType) (f, boxed) } diff --git a/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala b/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala index f2b90d5b1161..098f592daa30 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSEncoding.scala @@ -17,7 +17,7 @@ import dotty.tools.dotc.transform.sjs.JSSymUtils.* import org.scalajs.ir import org.scalajs.ir.{Trees => js, Types => jstpe} -import org.scalajs.ir.Names.{LocalName, LabelName, FieldName, SimpleMethodName, MethodName, ClassName} +import org.scalajs.ir.Names.{LocalName, LabelName, SimpleFieldName, FieldName, SimpleMethodName, MethodName, ClassName} import org.scalajs.ir.OriginalName import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.UTF8String @@ -173,7 +173,7 @@ object JSEncoding { } def encodeFieldSym(sym: Symbol)(implicit ctx: Context, pos: ir.Position): js.FieldIdent = - js.FieldIdent(FieldName(encodeFieldSymAsString(sym))) + js.FieldIdent(FieldName(encodeClassName(sym.owner), SimpleFieldName(encodeFieldSymAsString(sym)))) def encodeFieldSymAsStringLiteral(sym: Symbol)(implicit ctx: Context, pos: ir.Position): js.StringLiteral = js.StringLiteral(encodeFieldSymAsString(sym)) diff --git a/compiler/src/dotty/tools/dotc/transform/sjs/ExplicitJSClasses.scala b/compiler/src/dotty/tools/dotc/transform/sjs/ExplicitJSClasses.scala index 853fead6f799..5c7119860ae4 100644 --- a/compiler/src/dotty/tools/dotc/transform/sjs/ExplicitJSClasses.scala +++ b/compiler/src/dotty/tools/dotc/transform/sjs/ExplicitJSClasses.scala @@ -637,7 +637,11 @@ class ExplicitJSClasses extends MiniPhase with InfoTransformer { thisPhase => private def maybeWrapSuperCallWithContextualJSClassValue(tree: Tree)(using Context): Tree = { methPart(tree) match { case Select(sup: Super, _) if isInnerOrLocalJSClass(sup.symbol.asClass.superClass) => - wrapWithContextualJSClassValue(sup.symbol.asClass.superClass.typeRef)(tree) + val superClass = sup.symbol.asClass.superClass + val jsClassTypeInSuperClass = superClass.typeRef + // scala-js#4801 Rebase the super class type on the current class' this type + val jsClassTypeAsSeenFromThis = jsClassTypeInSuperClass.asSeenFrom(currentClass.thisType, superClass) + wrapWithContextualJSClassValue(jsClassTypeAsSeenFromThis)(tree) case _ => tree } diff --git a/compiler/test/dotty/Properties.scala b/compiler/test/dotty/Properties.scala index e085b0de4875..86e0788a3b8f 100644 --- a/compiler/test/dotty/Properties.scala +++ b/compiler/test/dotty/Properties.scala @@ -103,6 +103,9 @@ object Properties { /** scalajs-javalib jar */ def scalaJSJavalib: String = sys.props("dotty.tests.classes.scalaJSJavalib") + /** scalajs-scalalib jar */ + def scalaJSScalalib: String = sys.props("dotty.tests.classes.scalaJSScalalib") + /** scalajs-library jar */ def scalaJSLibrary: String = sys.props("dotty.tests.classes.scalaJSLibrary") } diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 086d590fbfc7..e97ef47e6fef 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -52,6 +52,7 @@ object TestConfiguration { lazy val scalaJSClasspath = mkClasspath(List( Properties.scalaJSJavalib, + Properties.scalaJSScalalib, Properties.scalaJSLibrary, Properties.dottyLibraryJS )) diff --git a/project/Build.scala b/project/Build.scala index 6985560c90b7..b409cca7c496 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1507,7 +1507,8 @@ object Build { "isNoModule" -> (moduleKind == ModuleKind.NoModule), "isESModule" -> (moduleKind == ModuleKind.ESModule), "isCommonJSModule" -> (moduleKind == ModuleKind.CommonJSModule), - "isFullOpt" -> (stage == FullOptStage), + "usesClosureCompiler" -> linkerConfig.closureCompiler, + "hasMinifiedNames" -> (linkerConfig.closureCompiler || linkerConfig.minify), "compliantAsInstanceOfs" -> (sems.asInstanceOfs == CheckedBehavior.Compliant), "compliantArrayIndexOutOfBounds" -> (sems.arrayIndexOutOfBounds == CheckedBehavior.Compliant), "compliantArrayStores" -> (sems.arrayStores == CheckedBehavior.Compliant), @@ -1580,6 +1581,7 @@ object Build { -- "ReflectiveCallTest.scala" // uses many forms of structural calls that are not allowed in Scala 3 anymore -- "UTF16Test.scala" // refutable pattern match -- "CharsetTest.scala" // bogus @tailrec that Scala 2 ignores but Scala 3 flags as an error + -- "ClassDiffersOnlyInCaseTest.scala" // looks like the Scala 3 compiler itself does not deal with that )).get ++ (dir / "shared/src/test/require-sam" ** "*.scala").get @@ -1648,6 +1650,7 @@ object Build { Seq( "-Ddotty.tests.classes.dottyLibraryJS=" + dottyLibraryJSJar, "-Ddotty.tests.classes.scalaJSJavalib=" + findArtifactPath(externalJSDeps, "scalajs-javalib"), + "-Ddotty.tests.classes.scalaJSScalalib=" + findArtifactPath(externalJSDeps, "scalajs-scalalib_2.13"), "-Ddotty.tests.classes.scalaJSLibrary=" + findArtifactPath(externalJSDeps, "scalajs-library_2.13"), ) }, diff --git a/project/plugins.sbt b/project/plugins.sbt index d378848561b8..59e58007a4a0 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -6,7 +6,7 @@ libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21")