Skip to content

Commit 0c8c115

Browse files
Merge pull request #9767 from dotty-staging/sjs-various-js-interop-fixes
Scala.js: Various JS interop fixes
2 parents 89db0f8 + 9584666 commit 0c8c115

File tree

7 files changed

+196
-43
lines changed

7 files changed

+196
-43
lines changed

compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala

Lines changed: 81 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ import Symbols._
2323
import Denotations._
2424
import Phases._
2525
import StdNames._
26+
import TypeErasure.ErasedValueType
2627

27-
import dotty.tools.dotc.transform.Erasure
28+
import dotty.tools.dotc.transform.{Erasure, ValueClasses}
29+
import dotty.tools.dotc.transform.SymUtils._
2830
import dotty.tools.dotc.util.SourcePosition
2931
import dotty.tools.dotc.util.Spans.Span
3032
import dotty.tools.dotc.report
@@ -282,7 +284,7 @@ class JSCodeGen()(using genCtx: Context) {
282284

283285
// Generate members (constructor + methods)
284286

285-
val generatedMethods = new mutable.ListBuffer[js.MethodDef]
287+
val generatedNonFieldMembers = new mutable.ListBuffer[js.MemberDef]
286288
val exportedSymbols = new mutable.ListBuffer[Symbol]
287289

288290
val tpl = td.rhs.asInstanceOf[Template]
@@ -297,13 +299,11 @@ class JSCodeGen()(using genCtx: Context) {
297299
val sym = dd.symbol
298300

299301
val isExport = false //jsInterop.isExport(sym)
300-
val isNamedExport = false /*isExport && sym.annotations.exists(
301-
_.symbol == JSExportNamedAnnotation)*/
302302

303-
/*if (isNamedExport)
304-
generatedMethods += genNamedExporterDef(dd)
305-
else*/
306-
generatedMethods ++= genMethod(dd)
303+
if (sym.hasAnnotation(jsdefn.JSNativeAnnot))
304+
generatedNonFieldMembers += genJSNativeMemberDef(dd)
305+
else
306+
generatedNonFieldMembers ++= genMethod(dd)
307307

308308
if (isExport) {
309309
// We add symbols that we have to export here. This way we also
@@ -317,7 +317,7 @@ class JSCodeGen()(using genCtx: Context) {
317317
}
318318

319319
// Generate fields and add to methods + ctors
320-
val generatedMembers = genClassFields(td) ++ generatedMethods.toList
320+
val generatedMembers = genClassFields(td) ++ generatedNonFieldMembers.toList
321321

322322
// Generate the exported members, constructors and accessors
323323
val exports = {
@@ -529,7 +529,6 @@ class JSCodeGen()(using genCtx: Context) {
529529

530530
private def genClassInterfaces(sym: ClassSymbol)(
531531
implicit pos: Position): List[js.ClassIdent] = {
532-
import dotty.tools.dotc.transform.SymUtils._
533532
for {
534533
intf <- sym.directlyInheritedTraits
535534
} yield {
@@ -677,7 +676,10 @@ class JSCodeGen()(using genCtx: Context) {
677676
"genClassFields called with a ClassDef other than the current one")
678677

679678
// Term members that are neither methods nor modules are fields
680-
classSym.info.decls.filter(f => !f.isOneOf(Method | Module) && f.isTerm).map({ f =>
679+
classSym.info.decls.filter { f =>
680+
!f.isOneOf(Method | Module) && f.isTerm
681+
&& !f.hasAnnotation(jsdefn.JSNativeAnnot)
682+
}.map({ f =>
681683
implicit val pos = f.span
682684

683685
val name =
@@ -815,6 +817,17 @@ class JSCodeGen()(using genCtx: Context) {
815817

816818
// Generate a method -------------------------------------------------------
817819

820+
/** Generates the JSNativeMemberDef. */
821+
def genJSNativeMemberDef(tree: DefDef): js.JSNativeMemberDef = {
822+
implicit val pos = tree.span
823+
824+
val sym = tree.symbol
825+
val flags = js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic)
826+
val methodName = encodeMethodSym(sym)
827+
val jsNativeLoadSpec = computeJSNativeLoadSpecOfValDef(sym)
828+
js.JSNativeMemberDef(flags, methodName, jsNativeLoadSpec)
829+
}
830+
818831
private def genMethod(dd: DefDef): Option[js.MethodDef] = {
819832
withScopedVars(
820833
localNames := new LocalNameGenerator
@@ -1227,7 +1240,7 @@ class JSCodeGen()(using genCtx: Context) {
12271240
val sym = lhs0.symbol
12281241
if (sym.is(JavaStaticTerm))
12291242
throw new FatalError(s"Assignment to static member ${sym.fullName} not supported")
1230-
val genRhs = genExpr(rhs)
1243+
def genRhs = genExpr(rhs)
12311244
val lhs = lhs0 match {
12321245
case lhs: Ident => desugarIdent(lhs).getOrElse(lhs)
12331246
case lhs => lhs
@@ -1247,7 +1260,15 @@ class JSCodeGen()(using genCtx: Context) {
12471260

12481261
val genQual = genExpr(qualifier)
12491262

1250-
/*if (isScalaJSDefinedJSClass(sym.owner)) {
1263+
if (sym.hasAnnotation(jsdefn.JSNativeAnnot)) {
1264+
/* This is an assignment to a @js.native field. Since we reject
1265+
* `@js.native var`s as compile errors, this can only happen in
1266+
* the constructor of the enclosing object.
1267+
* We simply ignore the assignment, since the field will not be
1268+
* emitted at all.
1269+
*/
1270+
js.Skip()
1271+
} else /*if (isScalaJSDefinedJSClass(sym.owner)) {
12511272
val genLhs = if (isExposed(sym))
12521273
js.JSBracketSelect(genQual, js.StringLiteral(jsNameOf(sym)))
12531274
else
@@ -1256,12 +1277,12 @@ class JSCodeGen()(using genCtx: Context) {
12561277
ensureBoxed(genRhs,
12571278
enteringPhase(currentRun.posterasurePhase)(rhs.tpe))
12581279
js.Assign(genLhs, boxedRhs)
1259-
} else {*/
1280+
} else*/ {
12601281
js.Assign(
12611282
js.Select(genQual, encodeClassName(sym.owner),
12621283
encodeFieldSym(sym))(toIRType(sym.info)),
12631284
genRhs)
1264-
//}
1285+
}
12651286
case _ =>
12661287
js.Assign(
12671288
js.VarRef(encodeLocalSym(sym))(toIRType(sym.info)),
@@ -2119,6 +2140,8 @@ class JSCodeGen()(using genCtx: Context) {
21192140
genApplyJSMethodGeneric(sym, genExprOrGlobalScope(receiver), genActualJSArgs(sym, args), isStat)(tree.sourcePos)
21202141
/*else
21212142
genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args))*/
2143+
} else if (sym.hasAnnotation(jsdefn.JSNativeAnnot)) {
2144+
genJSNativeMemberCall(tree, isStat)
21222145
} else {
21232146
genApplyMethodMaybeStatically(genExpr(receiver), sym, genActualArgs(sym, args))
21242147
}
@@ -2286,6 +2309,26 @@ class JSCodeGen()(using genCtx: Context) {
22862309
(firstArg.asInstanceOf[js.Tree], args.tail)
22872310
}
22882311

2312+
/** Gen JS code for a call to a native JS def or val. */
2313+
private def genJSNativeMemberCall(tree: Apply, isStat: Boolean): js.Tree = {
2314+
val sym = tree.symbol
2315+
val Apply(_, args) = tree
2316+
2317+
implicit val pos = tree.span
2318+
2319+
val jsNativeMemberValue =
2320+
js.SelectJSNativeMember(encodeClassName(sym.owner), encodeMethodSym(sym))
2321+
2322+
val boxedResult =
2323+
if (sym.isJSGetter) jsNativeMemberValue
2324+
else js.JSFunctionApply(jsNativeMemberValue, genActualJSArgs(sym, args))
2325+
2326+
unbox(boxedResult, atPhase(elimErasedValueTypePhase) {
2327+
sym.info.resultType
2328+
})
2329+
}
2330+
2331+
22892332
/** Gen JS code for a call to a polymorphic method.
22902333
*
22912334
* The only methods that reach the back-end as polymorphic are
@@ -2599,10 +2642,10 @@ class JSCodeGen()(using genCtx: Context) {
25992642
case tpe if isPrimitiveValueType(tpe) =>
26002643
makePrimitiveBox(expr, tpe)
26012644

2602-
/*case tpe: ErasedValueType =>
2603-
val boxedClass = tpe.valueClazz
2645+
case tpe: ErasedValueType =>
2646+
val boxedClass = tpe.tycon.typeSymbol
26042647
val ctor = boxedClass.primaryConstructor
2605-
genNew(boxedClass, ctor, List(expr))*/
2648+
js.New(encodeClassName(boxedClass), encodeMethodSym(ctor), List(expr))
26062649

26072650
case _ =>
26082651
expr
@@ -2626,15 +2669,15 @@ class JSCodeGen()(using genCtx: Context) {
26262669
case tpe if isPrimitiveValueType(tpe) =>
26272670
makePrimitiveUnbox(expr, tpe)
26282671

2629-
/*case tpe: ErasedValueType =>
2630-
val boxedClass = tpe.valueClazz
2631-
val unboxMethod = boxedClass.derivedValueClassUnbox
2672+
case tpe: ErasedValueType =>
2673+
val boxedClass = tpe.tycon.typeSymbol.asClass
2674+
val unboxMethod = ValueClasses.valueClassUnbox(boxedClass)
26322675
val content = genApplyMethod(
2633-
genAsInstanceOf(expr, tpe), unboxMethod, Nil)
2634-
if (unboxMethod.tpe.resultType <:< tpe.erasedUnderlying)
2676+
js.AsInstanceOf(expr, encodeClassType(boxedClass)), unboxMethod, Nil)
2677+
if (unboxMethod.info.resultType <:< tpe.erasedUnderlying)
26352678
content
26362679
else
2637-
fromAny(content, tpe.erasedUnderlying)*/
2680+
unbox(content, tpe.erasedUnderlying)
26382681

26392682
case tpe =>
26402683
genAsInstanceOf(expr, tpe)
@@ -2889,7 +2932,11 @@ class JSCodeGen()(using genCtx: Context) {
28892932
case TYPEOF =>
28902933
// js.typeOf(arg)
28912934
val arg = genArgs1
2892-
genAsInstanceOf(js.JSUnaryOp(js.JSUnaryOp.typeof, arg), defn.StringType)
2935+
val typeofExpr = arg match {
2936+
case arg: js.JSGlobalRef => js.JSTypeOfGlobalRef(arg)
2937+
case _ => js.JSUnaryOp(js.JSUnaryOp.typeof, arg)
2938+
}
2939+
js.AsInstanceOf(typeofExpr, jstpe.ClassType(jsNames.BoxedStringClass))
28932940

28942941
case STRICT_EQ =>
28952942
// js.special.strictEquals(arg1, arg2)
@@ -3497,6 +3544,12 @@ class JSCodeGen()(using genCtx: Context) {
34973544
}
34983545
}
34993546

3547+
private def computeJSNativeLoadSpecOfValDef(sym: Symbol): js.JSNativeLoadSpec = {
3548+
atPhase(picklerPhase.next) {
3549+
computeJSNativeLoadSpecOfInPhase(sym)
3550+
}
3551+
}
3552+
35003553
private def computeJSNativeLoadSpecOfClass(sym: Symbol): Option[js.JSNativeLoadSpec] = {
35013554
if (sym.is(Trait) || sym.hasAnnotation(jsdefn.JSGlobalScopeAnnot)) {
35023555
None
@@ -3587,8 +3640,9 @@ class JSCodeGen()(using genCtx: Context) {
35873640

35883641
private def isPrimitiveValueType(tpe: Type): Boolean = {
35893642
tpe.widenDealias match {
3590-
case JavaArrayType(_) => false
3591-
case t => t.typeSymbol.asClass.isPrimitiveValueClass
3643+
case JavaArrayType(_) => false
3644+
case _: ErasedValueType => false
3645+
case t => t.typeSymbol.asClass.isPrimitiveValueClass
35923646
}
35933647
}
35943648

compiler/src/dotty/tools/dotc/transform/Memoize.scala

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,10 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
9797
case _ => ()
9898
}
9999

100-
def removeUnwantedAnnotations(denot: SymDenotation): Unit =
100+
def removeUnwantedAnnotations(denot: SymDenotation, metaAnnotSym: ClassSymbol): Unit =
101101
if (sym.annotations.nonEmpty) {
102102
val cpy = sym.copySymDenotation()
103-
// Keep @deprecated annotation so that accessors can
104-
// be marked as deprecated in the bytecode.
105-
// TODO check the meta-annotations to know what to keep
106-
cpy.filterAnnotations(_.matches(defn.DeprecatedAnnot))
103+
cpy.filterAnnotations(_.symbol.hasAnnotation(metaAnnotSym))
107104
cpy.installAfter(thisPhase)
108105
}
109106

@@ -141,7 +138,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
141138
else transformFollowingDeep(ref(field))(using ctx.withOwner(sym))
142139
val getterDef = cpy.DefDef(tree)(rhs = getterRhs)
143140
addAnnotations(fieldDef.denot)
144-
removeUnwantedAnnotations(sym)
141+
removeUnwantedAnnotations(sym, defn.GetterMetaAnnot)
145142
Thicket(fieldDef, getterDef)
146143
else if sym.isSetter then
147144
if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // This is intended as an assertion
@@ -162,7 +159,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
162159
if (isErasableBottomField(field, tree.vparamss.head.head.tpt.tpe.classSymbol)) Literal(Constant(()))
163160
else Assign(ref(field), adaptToField(field, ref(tree.vparamss.head.head.symbol)))
164161
val setterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(using ctx.withOwner(sym)))
165-
removeUnwantedAnnotations(sym)
162+
removeUnwantedAnnotations(sym, defn.SetterMetaAnnot)
166163
setterDef
167164
else
168165
// Curiously, some accessors from Scala2 have ' ' suffixes.

compiler/src/dotty/tools/dotc/transform/SelectStatic.scala

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,37 @@ import dotty.tools.dotc.core._
1111
import dotty.tools.dotc.transform.MegaPhase._
1212
import dotty.tools.dotc.transform.SymUtils._
1313

14-
/** Removes selects that would be compiled into GetStatic
15-
* otherwise backend needs to be aware that some qualifiers need to be dropped.
16-
* Similar transformation seems to be performed by flatten in nsc
17-
* @author Dmytro Petrashko
14+
/** Removes `Select`s that would be compiled into `GetStatic`.
15+
*
16+
* Otherwise, the backend needs to be aware that some qualifiers need to be
17+
* dropped.
18+
*
19+
* A tranformation similar to what this phase does seems to be performed by
20+
* flatten in nsc.
21+
*
22+
* The side effects of the qualifier of a dropped `Select` is normally
23+
* retained. As an exception, the qualifier is completely dropped if it is
24+
* a reference to a static owner (see `isStaticOwnerRef`). Concretely, this
25+
* means that in
26+
*
27+
* {{{
28+
* object Foo {
29+
* println("side effects")
30+
*
31+
* object Bar
32+
* class Baz
33+
* }
34+
*
35+
* Foo.Bar
36+
* new Foo.Baz()
37+
* }}}
38+
*
39+
* the `Foo` qualifiers will be dropped, since it is a static object. The
40+
* `println("side effects")` will therefore not be executed.
41+
*
42+
* This intended behavior is equivalent to what scalac does.
43+
*
44+
* @author Dmytro Petrashko
1845
*/
1946
class SelectStatic extends MiniPhase with IdentityDenotTransformer {
2047
import ast.tpd._
@@ -29,13 +56,23 @@ class SelectStatic extends MiniPhase with IdentityDenotTransformer {
2956
sym.isScalaStatic
3057
val isStaticRef = !sym.is(Package) && !sym.maybeOwner.is(Package) && isStaticMember
3158
val tree1 =
32-
if (isStaticRef && !tree.qualifier.symbol.isAllOf(JavaModule) && !tree.qualifier.isType)
33-
Block(List(tree.qualifier), ref(sym))
59+
if isStaticRef && !tree.qualifier.symbol.isAllOf(JavaModule) && !tree.qualifier.isType then
60+
if isStaticOwnerRef(tree.qualifier) then ref(sym)
61+
else Block(List(tree.qualifier), ref(sym))
3462
else tree
3563

3664
normalize(tree1)
3765
}
3866

67+
private def isStaticOwnerRef(tree: Tree)(using Context): Boolean = tree match {
68+
case Ident(_) =>
69+
tree.symbol.is(Module) && tree.symbol.moduleClass.isStaticOwner
70+
case Select(qual, _) =>
71+
isStaticOwnerRef(qual) && tree.symbol.is(Module) && tree.symbol.moduleClass.isStaticOwner
72+
case _ =>
73+
false
74+
}
75+
3976
private def normalize(t: Tree)(using Context) = t match {
4077
case Select(Block(stats, qual), nm) =>
4178
Block(stats, cpy.Select(t)(qual, nm))

project/Build.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,12 @@ object Build {
10091009
scalaJSLinkerConfig ~= { _.withSemantics(build.TestSuiteLinkerOptions.semantics _) },
10101010
scalaJSModuleInitializers in Test ++= build.TestSuiteLinkerOptions.moduleInitializers,
10111011

1012+
jsEnvInput in Test := {
1013+
val resourceDir = fetchScalaJSSource.value / "test-suite/js/src/test/resources"
1014+
val f = (resourceDir / "NonNativeJSTypeTestNatives.js").toPath
1015+
org.scalajs.jsenv.Input.Script(f) +: (jsEnvInput in Test).value
1016+
},
1017+
10121018
managedSources in Compile ++= {
10131019
val dir = fetchScalaJSSource.value / "test-suite/js/src/main/scala"
10141020
val filter = (
@@ -1032,7 +1038,7 @@ object Build {
10321038
++ (dir / "shared/src/test/require-jdk7" ** "*.scala").get
10331039

10341040
++ (dir / "js/src/test/scala/org/scalajs/testsuite/compiler" ** (("*.scala": FileFilter)
1035-
-- "InteroperabilityTest.scala" // compiler crash, related to value classes in JS interop
1041+
-- "InteroperabilityTest.scala" // nested native JS classes + JS exports
10361042
-- "OptimizerTest.scala" // non-native JS classes
10371043
-- "RegressionJSTest.scala" // non-native JS classes
10381044
-- "RuntimeTypesTest.scala" // compile errors: no ClassTag for Null and Nothing
@@ -1049,7 +1055,6 @@ object Build {
10491055
-- "ExportsTest.scala" // JS exports
10501056
-- "IterableTest.scala" // non-native JS classes
10511057
-- "JSExportStaticTest.scala" // JS exports
1052-
-- "JSNativeInPackage.scala" // #9785 tests fail due to js.typeOf(globalVar) being incorrect
10531058
-- "JSOptionalTest.scala" // non-native JS classes
10541059
-- "JSSymbolTest.scala" // non-native JS classes
10551060
-- "MiscInteropTest.scala" // non-native JS classes
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
InnerObject constructor
2+
42
3+
InnerClass constructor
4+
5
5+
42
6+
InnerClass constructor
7+
5
8+
getEnclosing2()
9+
Enclosing2 constructor
10+
Enclosing2.InnerObject constructor
11+
65

0 commit comments

Comments
 (0)