Skip to content

Commit 1df0b15

Browse files
Merge pull request #9602 from dotty-staging/sjs-js-specific-features
Scala.js: Support for some more JS-specific features
2 parents 02fdb2e + d6fe8b9 commit 1df0b15

File tree

8 files changed

+121
-44
lines changed

8 files changed

+121
-44
lines changed

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

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2472,20 +2472,34 @@ class JSCodeGen()(using genCtx: Context) {
24722472
box(call, sym.info.finalResultType)
24732473
}
24742474

2475-
val closure = js.Closure(arrow = true, formalCaptures, formalParams, genBody, actualCaptures)
2476-
report.debuglog(closure.toString)
2477-
24782475
val funInterfaceSym = functionalInterface.tpe.widenDealias.typeSymbol
2479-
if (jsdefn.isJSFunctionClass(funInterfaceSym)) {
2480-
closure
2476+
2477+
if (jsdefn.isJSThisFunctionClass(funInterfaceSym)) {
2478+
val thisParam :: otherParams = formalParams
2479+
js.Closure(
2480+
arrow = false,
2481+
formalCaptures,
2482+
otherParams,
2483+
js.Block(
2484+
js.VarDef(thisParam.name, thisParam.originalName,
2485+
thisParam.ptpe, mutable = false,
2486+
js.This()(thisParam.ptpe)(thisParam.pos))(thisParam.pos),
2487+
genBody),
2488+
actualCaptures)
24812489
} else {
2482-
assert(!funInterfaceSym.exists || defn.isFunctionClass(funInterfaceSym),
2483-
s"Invalid functional interface $funInterfaceSym reached the back-end")
2484-
val formalCount = formalParams.size
2485-
val cls = ClassName("scala.scalajs.runtime.AnonFunction" + formalCount)
2486-
val ctorName = MethodName.constructor(
2487-
jstpe.ClassRef(ClassName("scala.scalajs.js.Function" + formalCount)) :: Nil)
2488-
js.New(cls, js.MethodIdent(ctorName), List(closure))
2490+
val closure = js.Closure(arrow = true, formalCaptures, formalParams, genBody, actualCaptures)
2491+
2492+
if (jsdefn.isJSFunctionClass(funInterfaceSym)) {
2493+
closure
2494+
} else {
2495+
assert(!funInterfaceSym.exists || defn.isFunctionClass(funInterfaceSym),
2496+
s"Invalid functional interface $funInterfaceSym reached the back-end")
2497+
val formalCount = formalParams.size
2498+
val cls = ClassName("scala.scalajs.runtime.AnonFunction" + formalCount)
2499+
val ctorName = MethodName.constructor(
2500+
jstpe.ClassRef(ClassName("scala.scalajs.js.Function" + formalCount)) :: Nil)
2501+
js.New(cls, js.MethodIdent(ctorName), List(closure))
2502+
}
24892503
}
24902504
}
24912505

@@ -2798,6 +2812,11 @@ class JSCodeGen()(using genCtx: Context) {
27982812
val arg = genArgs1
27992813
genAsInstanceOf(js.JSUnaryOp(js.JSUnaryOp.typeof, arg), defn.StringType)
28002814

2815+
case STRICT_EQ =>
2816+
// js.special.strictEquals(arg1, arg2)
2817+
val (arg1, arg2) = genArgs2
2818+
js.JSBinaryOp(js.JSBinaryOp.===, arg1, arg2)
2819+
28012820
case IN =>
28022821
// js.special.in(arg1, arg2)
28032822
val (arg1, arg2) = genArgs2

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ final class JSDefinitions()(using Context) {
4747
@threadUnsafe lazy val JSBaseThisFunctionType: TypeRef = requiredClassRef("scala.scalajs.js.ThisFunction")
4848
def JSBaseThisFunctionClass(using Context) = JSBaseThisFunctionType.symbol.asClass
4949

50+
@threadUnsafe lazy val PseudoUnionType: TypeRef = requiredClassRef("scala.scalajs.js.|")
51+
def PseudoUnionClass(using Context) = PseudoUnionType.symbol.asClass
52+
5053
@threadUnsafe lazy val JSArrayType: TypeRef = requiredClassRef("scala.scalajs.js.Array")
5154
def JSArrayClass(using Context) = JSArrayType.symbol.asClass
5255

@@ -151,6 +154,8 @@ final class JSDefinitions()(using Context) {
151154
def Special_in(using Context) = Special_inR.symbol
152155
@threadUnsafe lazy val Special_instanceofR = SpecialPackageClass.requiredMethodRef("instanceof")
153156
def Special_instanceof(using Context) = Special_instanceofR.symbol
157+
@threadUnsafe lazy val Special_strictEqualsR = SpecialPackageClass.requiredMethodRef("strictEquals")
158+
def Special_strictEquals(using Context) = Special_strictEqualsR.symbol
154159

155160
@threadUnsafe lazy val WrappedArrayType: TypeRef = requiredClassRef("scala.scalajs.js.WrappedArray")
156161
def WrappedArrayClass(using Context) = WrappedArrayType.symbol.asClass

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@ object JSInterop {
1616

1717
/** Is this symbol a JavaScript type? */
1818
def isJSType(sym: Symbol)(using Context): Boolean = {
19-
//sym.hasAnnotation(jsdefn.RawJSTypeAnnot)
2019
atPhase(erasurePhase) {
21-
sym.derivesFrom(jsdefn.JSAnyClass)
20+
sym.derivesFrom(jsdefn.JSAnyClass) || sym == jsdefn.PseudoUnionClass
2221
}
2322
}
2423

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ object JSPrimitives {
3232
final val WITH_CONTEXTUAL_JS_CLASS_VALUE = CREATE_LOCAL_JS_CLASS + 1 // runtime.withContextualJSClassValue
3333
final val LINKING_INFO = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.linkingInfo
3434

35-
final val IN = LINKING_INFO + 1 // js.special.in
36-
final val INSTANCEOF = IN + 1 // js.special.instanceof
37-
final val DELETE = INSTANCEOF + 1 // js.special.delete
38-
final val FORIN = DELETE + 1 // js.special.forin
39-
final val DEBUGGER = FORIN + 1 // js.special.debugger
35+
final val STRICT_EQ = LINKING_INFO + 1 // js.special.strictEquals
36+
final val IN = STRICT_EQ + 1 // js.special.in
37+
final val INSTANCEOF = IN + 1 // js.special.instanceof
38+
final val DELETE = INSTANCEOF + 1 // js.special.delete
39+
final val FORIN = DELETE + 1 // js.special.forin
40+
final val DEBUGGER = FORIN + 1 // js.special.debugger
4041

4142
final val THROW = DEBUGGER + 1
4243

@@ -107,6 +108,7 @@ class JSPrimitives(ictx: Context) extends DottyPrimitives(ictx) {
107108
addPrimitive(jsdefn.Runtime_withContextualJSClassValue, WITH_CONTEXTUAL_JS_CLASS_VALUE)*/
108109
addPrimitive(jsdefn.Runtime_linkingInfo, LINKING_INFO)
109110

111+
addPrimitive(jsdefn.Special_strictEquals, STRICT_EQ)
110112
addPrimitive(jsdefn.Special_in, IN)
111113
addPrimitive(jsdefn.Special_instanceof, INSTANCEOF)
112114
addPrimitive(jsdefn.Special_delete, DELETE)

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class Compiler {
7272
List(new ElimOpaque, // Turn opaque into normal aliases
7373
new TryCatchPatterns, // Compile cases in try/catch
7474
new PatternMatcher, // Compile pattern matches
75+
new ExplicitJSClasses, // Make all JS classes explicit (Scala.js only)
7576
new ExplicitOuter, // Add accessors to outer classes from nested ones.
7677
new ExplicitSelf, // Make references to non-trivial self types explicit as casts
7778
new StringInterpolatorOpt, // Optimizes raw and s string interpolators by rewriting them to string concatentations

compiler/src/dotty/tools/dotc/config/SJSPlatform.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class SJSPlatform()(using Context) extends JavaPlatform {
1313

1414
/** Is the SAMType `cls` also a SAM under the rules of the Scala.js back-end? */
1515
override def isSam(cls: ClassSymbol)(using Context): Boolean =
16-
defn.isFunctionClass(cls) || jsDefinitions.isJSFunctionClass(cls)
16+
defn.isFunctionClass(cls)
17+
|| jsDefinitions.isJSFunctionClass(cls)
18+
|| jsDefinitions.isJSThisFunctionClass(cls)
1719
}
18-
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package dotty.tools
2+
package dotc
3+
package transform
4+
5+
import MegaPhase._
6+
import core.DenotTransformers._
7+
import core.Symbols._
8+
import core.Contexts._
9+
import core.Phases._
10+
import core.Types._
11+
import core.Flags._
12+
import core.Decorators._
13+
import core.StdNames.nme
14+
import core.Names._
15+
import core.NameOps._
16+
import ast.Trees._
17+
import SymUtils._
18+
import dotty.tools.dotc.ast.tpd
19+
20+
import dotty.tools.backend.sjs.JSDefinitions.jsdefn
21+
22+
/** This phase makes all JS classes explicit (their definitions and references to them).
23+
*
24+
* Ultimately, this will be the equivalent of the two phases `ExplicitInnerJS`
25+
* and `ExplicitLocalJS` from Scala 2. Currently, this phase only performs the
26+
* following transformations:
27+
*
28+
* - Rewrite `js.constructorOf[T]` into `scala.scalajs.runtime.constructorOf(classOf[T])`,
29+
* where the `classOf[T]` is represented as a `Literal`.
30+
*/
31+
class ExplicitJSClasses extends MiniPhase with InfoTransformer { thisPhase =>
32+
import ExplicitJSClasses._
33+
import ast.tpd._
34+
35+
override def phaseName: String = ExplicitJSClasses.name
36+
37+
override def isEnabled(using Context): Boolean =
38+
ctx.settings.scalajs.value
39+
40+
override def runsAfter: Set[String] = Set(PatternMatcher.name, HoistSuperArgs.name)
41+
42+
override def changesMembers: Boolean = true // the phase adds fields for inner JS classes
43+
44+
override def transformInfo(tp: Type, sym: Symbol)(using Context): Type = {
45+
// Currently we don't do anything here. Eventually we'll add fields for inner JS classes.
46+
tp
47+
}
48+
49+
override def infoMayChange(sym: Symbol)(using Context): Boolean =
50+
sym.isClass && !sym.is(JavaDefined)
51+
52+
override def transformTypeApply(tree: TypeApply)(using Context): tpd.Tree = {
53+
tree match {
54+
case TypeApply(fun, tpt :: Nil) if fun.symbol == jsdefn.JSPackage_constructorOf =>
55+
ref(jsdefn.Runtime_constructorOf).appliedTo(clsOf(tpt.tpe))
56+
case _ =>
57+
tree
58+
}
59+
}
60+
}
61+
62+
object ExplicitJSClasses {
63+
val name: String = "explicitJSClasses"
64+
}

project/Build.scala

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,39 +1063,32 @@ object Build {
10631063
++ (dir / "shared/src/test/require-jdk7/org/scalajs/testsuite/javalib/util" ** "*.scala").get
10641064

10651065
++ (dir / "js/src/test/scala/org/scalajs/testsuite/compiler" ** (("*.scala": FileFilter)
1066-
-- "InteroperabilityTest.scala" // various compile errors
1067-
-- "OptimizerTest.scala" // compile errors: false + string and () + string
1068-
-- "ReflectionTest.scala" // tests fail
1066+
-- "InteroperabilityTest.scala" // various compile errors, pending update upstream
1067+
-- "OptimizerTest.scala" // compile errors: false + string and () + string, pending update upstream
1068+
-- "ReflectionTest.scala" // tests fail (wrong load spec for JS globals)
10691069
-- "RegressionJSTest.scala" // non-native JS classes
10701070
-- "RuntimeTypesTest.scala" // compile errors: no ClassTag for Null and Nothing
10711071
)).get
10721072

10731073
++ (dir / "js/src/test/scala/org/scalajs/testsuite/javalib" ** (("*.scala": FileFilter)
10741074
-- "FormatterJSTest.scala" // compile error with the f"" interpolator
10751075
-- "ObjectJSTest.scala" // non-native JS classes
1076-
-- "StringBufferJSTest.scala" // IR checking errors
1077-
-- "ThrowableJSTest.scala" // test fails ("java.lang.Error: stub")
10781076
)).get
10791077

10801078
++ (dir / "js/src/test/scala/org/scalajs/testsuite/jsinterop" ** (("*.scala": FileFilter)
10811079
-- "AsyncTest.scala" // needs PromiseMock.scala
10821080
-- "DynamicTest.scala" // one test requires JS exports, all other tests pass
10831081
-- "ExportsTest.scala" // JS exports
1084-
-- "FunctionTest.scala" // IR checking errors
10851082
-- "IterableTest.scala" // non-native JS classes
10861083
-- "JSExportStaticTest.scala" // JS exports
1087-
-- "JSNativeInPackage.scala" // IR checking errors
1084+
-- "JSNativeInPackage.scala" // tests fail (wrong load spec for JS globals)
10881085
-- "JSOptionalTest.scala" // non-native JS classes
10891086
-- "JSSymbolTest.scala" // non-native JS classes
10901087
-- "MiscInteropTest.scala" // non-native JS classes
10911088
-- "ModulesWithGlobalFallbackTest.scala" // non-native JS classes
10921089
-- "NestedJSClassTest.scala" // non-native JS classes
10931090
-- "NonNativeJSTypeTest.scala" // non-native JS classes
10941091
-- "PromiseMock.scala" // non-native JS classes
1095-
-- "SpecialTest.scala" // assertion error in ExpandSAMs
1096-
-- "SymbolTest.scala" // IR checking errors
1097-
-- "ThisFunctionTest.scala" // assertion error in ExpandSAMs
1098-
-- "UndefOrTest.scala" // StackOverflow in the compiler
10991092
)).get
11001093

11011094
++ (dir / "js/src/test/scala/org/scalajs/testsuite/junit" ** (("*.scala": FileFilter)
@@ -1107,20 +1100,15 @@ object Build {
11071100
)).get
11081101

11091102
++ (dir / "js/src/test/scala/org/scalajs/testsuite/library" ** (("*.scala": FileFilter)
1110-
-- "BigIntTest.scala" // StackOverflow in the compiler
1111-
-- "ObjectTest.scala" // compile errors
1103+
-- "BigIntTest.scala" // Ambiguous reference because of new non-shadowing rule in Scala 3, pending update upstream
1104+
-- "ObjectTest.scala" // compile errors caused by #9588
11121105
-- "StackTraceTest.scala" // would require `npm install source-map-support`
1113-
-- "UnionTypeTest.scala" // requires a Scala 2 macro + StackOverflow in the compiler
1114-
-- "WrappedDictionaryTest.scala" // IR checking errors
1106+
-- "UnionTypeTest.scala" // requires a Scala 2 macro
11151107
)).get
11161108

11171109
++ (dir / "js/src/test/scala/org/scalajs/testsuite/niobuffer" ** "*.scala").get
11181110
++ (dir / "js/src/test/scala/org/scalajs/testsuite/scalalib" ** "*.scala").get
1119-
1120-
++ (dir / "js/src/test/scala/org/scalajs/testsuite/typedarray" ** (("*.scala": FileFilter)
1121-
-- "TypedArrayTest.scala" // assertion error in ExpandSAMs
1122-
)).get
1123-
1111+
++ (dir / "js/src/test/scala/org/scalajs/testsuite/typedarray" ** "*.scala").get
11241112
++ (dir / "js/src/test/scala/org/scalajs/testsuite/utils" ** "*.scala").get
11251113

11261114
++ (dir / "js/src/test/require-2.12" ** (("*.scala": FileFilter)
@@ -1131,9 +1119,7 @@ object Build {
11311119
-- "SAMJSTest.scala" // non-native JS classes
11321120
)).get
11331121

1134-
++ (dir / "js/src/test/scala-new-collections" ** (("*.scala": FileFilter)
1135-
-- "WrappedDictionaryToTest.scala" // IR checking errors
1136-
)).get
1122+
++ (dir / "js/src/test/scala-new-collections" ** "*.scala").get
11371123
)
11381124
}
11391125
)

0 commit comments

Comments
 (0)