Skip to content

Scala.js: Support for some more JS-specific features #9602

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 31 additions & 12 deletions compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2472,20 +2472,34 @@ class JSCodeGen()(using genCtx: Context) {
box(call, sym.info.finalResultType)
}

val closure = js.Closure(arrow = true, formalCaptures, formalParams, genBody, actualCaptures)
report.debuglog(closure.toString)

val funInterfaceSym = functionalInterface.tpe.widenDealias.typeSymbol
if (jsdefn.isJSFunctionClass(funInterfaceSym)) {
closure

if (jsdefn.isJSThisFunctionClass(funInterfaceSym)) {
val thisParam :: otherParams = formalParams
js.Closure(
arrow = false,
formalCaptures,
otherParams,
js.Block(
js.VarDef(thisParam.name, thisParam.originalName,
thisParam.ptpe, mutable = false,
js.This()(thisParam.ptpe)(thisParam.pos))(thisParam.pos),
genBody),
actualCaptures)
} else {
assert(!funInterfaceSym.exists || defn.isFunctionClass(funInterfaceSym),
s"Invalid functional interface $funInterfaceSym reached the back-end")
val formalCount = formalParams.size
val cls = ClassName("scala.scalajs.runtime.AnonFunction" + formalCount)
val ctorName = MethodName.constructor(
jstpe.ClassRef(ClassName("scala.scalajs.js.Function" + formalCount)) :: Nil)
js.New(cls, js.MethodIdent(ctorName), List(closure))
val closure = js.Closure(arrow = true, formalCaptures, formalParams, genBody, actualCaptures)

if (jsdefn.isJSFunctionClass(funInterfaceSym)) {
closure
} else {
assert(!funInterfaceSym.exists || defn.isFunctionClass(funInterfaceSym),
s"Invalid functional interface $funInterfaceSym reached the back-end")
val formalCount = formalParams.size
val cls = ClassName("scala.scalajs.runtime.AnonFunction" + formalCount)
val ctorName = MethodName.constructor(
jstpe.ClassRef(ClassName("scala.scalajs.js.Function" + formalCount)) :: Nil)
js.New(cls, js.MethodIdent(ctorName), List(closure))
}
}
}

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

case STRICT_EQ =>
// js.special.strictEquals(arg1, arg2)
val (arg1, arg2) = genArgs2
js.JSBinaryOp(js.JSBinaryOp.===, arg1, arg2)

case IN =>
// js.special.in(arg1, arg2)
val (arg1, arg2) = genArgs2
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/backend/sjs/JSDefinitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ final class JSDefinitions()(using Context) {
@threadUnsafe lazy val JSBaseThisFunctionType: TypeRef = requiredClassRef("scala.scalajs.js.ThisFunction")
def JSBaseThisFunctionClass(using Context) = JSBaseThisFunctionType.symbol.asClass

@threadUnsafe lazy val PseudoUnionType: TypeRef = requiredClassRef("scala.scalajs.js.|")
def PseudoUnionClass(using Context) = PseudoUnionType.symbol.asClass

@threadUnsafe lazy val JSArrayType: TypeRef = requiredClassRef("scala.scalajs.js.Array")
def JSArrayClass(using Context) = JSArrayType.symbol.asClass

Expand Down Expand Up @@ -151,6 +154,8 @@ final class JSDefinitions()(using Context) {
def Special_in(using Context) = Special_inR.symbol
@threadUnsafe lazy val Special_instanceofR = SpecialPackageClass.requiredMethodRef("instanceof")
def Special_instanceof(using Context) = Special_instanceofR.symbol
@threadUnsafe lazy val Special_strictEqualsR = SpecialPackageClass.requiredMethodRef("strictEquals")
def Special_strictEquals(using Context) = Special_strictEqualsR.symbol

@threadUnsafe lazy val WrappedArrayType: TypeRef = requiredClassRef("scala.scalajs.js.WrappedArray")
def WrappedArrayClass(using Context) = WrappedArrayType.symbol.asClass
Expand Down
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/backend/sjs/JSInterop.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ object JSInterop {

/** Is this symbol a JavaScript type? */
def isJSType(sym: Symbol)(using Context): Boolean = {
//sym.hasAnnotation(jsdefn.RawJSTypeAnnot)
atPhase(erasurePhase) {
sym.derivesFrom(jsdefn.JSAnyClass)
sym.derivesFrom(jsdefn.JSAnyClass) || sym == jsdefn.PseudoUnionClass
}
}

Expand Down
12 changes: 7 additions & 5 deletions compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ object JSPrimitives {
final val WITH_CONTEXTUAL_JS_CLASS_VALUE = CREATE_LOCAL_JS_CLASS + 1 // runtime.withContextualJSClassValue
final val LINKING_INFO = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.linkingInfo

final val IN = LINKING_INFO + 1 // js.special.in
final val INSTANCEOF = IN + 1 // js.special.instanceof
final val DELETE = INSTANCEOF + 1 // js.special.delete
final val FORIN = DELETE + 1 // js.special.forin
final val DEBUGGER = FORIN + 1 // js.special.debugger
final val STRICT_EQ = LINKING_INFO + 1 // js.special.strictEquals
final val IN = STRICT_EQ + 1 // js.special.in
final val INSTANCEOF = IN + 1 // js.special.instanceof
final val DELETE = INSTANCEOF + 1 // js.special.delete
final val FORIN = DELETE + 1 // js.special.forin
final val DEBUGGER = FORIN + 1 // js.special.debugger

final val THROW = DEBUGGER + 1

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

addPrimitive(jsdefn.Special_strictEquals, STRICT_EQ)
addPrimitive(jsdefn.Special_in, IN)
addPrimitive(jsdefn.Special_instanceof, INSTANCEOF)
addPrimitive(jsdefn.Special_delete, DELETE)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class Compiler {
List(new ElimOpaque, // Turn opaque into normal aliases
new TryCatchPatterns, // Compile cases in try/catch
new PatternMatcher, // Compile pattern matches
new ExplicitJSClasses, // Make all JS classes explicit (Scala.js only)
new ExplicitOuter, // Add accessors to outer classes from nested ones.
new ExplicitSelf, // Make references to non-trivial self types explicit as casts
new StringInterpolatorOpt, // Optimizes raw and s string interpolators by rewriting them to string concatentations
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/config/SJSPlatform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class SJSPlatform()(using Context) extends JavaPlatform {

/** Is the SAMType `cls` also a SAM under the rules of the Scala.js back-end? */
override def isSam(cls: ClassSymbol)(using Context): Boolean =
defn.isFunctionClass(cls) || jsDefinitions.isJSFunctionClass(cls)
defn.isFunctionClass(cls)
|| jsDefinitions.isJSFunctionClass(cls)
|| jsDefinitions.isJSThisFunctionClass(cls)
}

64 changes: 64 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/ExplicitJSClasses.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package dotty.tools
package dotc
package transform

import MegaPhase._
import core.DenotTransformers._
import core.Symbols._
import core.Contexts._
import core.Phases._
import core.Types._
import core.Flags._
import core.Decorators._
import core.StdNames.nme
import core.Names._
import core.NameOps._
import ast.Trees._
import SymUtils._
import dotty.tools.dotc.ast.tpd

import dotty.tools.backend.sjs.JSDefinitions.jsdefn

/** This phase makes all JS classes explicit (their definitions and references to them).
*
* Ultimately, this will be the equivalent of the two phases `ExplicitInnerJS`
* and `ExplicitLocalJS` from Scala 2. Currently, this phase only performs the
* following transformations:
*
* - Rewrite `js.constructorOf[T]` into `scala.scalajs.runtime.constructorOf(classOf[T])`,
* where the `classOf[T]` is represented as a `Literal`.
*/
class ExplicitJSClasses extends MiniPhase with InfoTransformer { thisPhase =>
import ExplicitJSClasses._
import ast.tpd._

override def phaseName: String = ExplicitJSClasses.name

override def isEnabled(using Context): Boolean =
ctx.settings.scalajs.value

override def runsAfter: Set[String] = Set(PatternMatcher.name, HoistSuperArgs.name)

override def changesMembers: Boolean = true // the phase adds fields for inner JS classes

override def transformInfo(tp: Type, sym: Symbol)(using Context): Type = {
// Currently we don't do anything here. Eventually we'll add fields for inner JS classes.
tp
}

override def infoMayChange(sym: Symbol)(using Context): Boolean =
sym.isClass && !sym.is(JavaDefined)

override def transformTypeApply(tree: TypeApply)(using Context): tpd.Tree = {
tree match {
case TypeApply(fun, tpt :: Nil) if fun.symbol == jsdefn.JSPackage_constructorOf =>
ref(jsdefn.Runtime_constructorOf).appliedTo(clsOf(tpt.tpe))
case _ =>
tree
}
}
}

object ExplicitJSClasses {
val name: String = "explicitJSClasses"
}
32 changes: 9 additions & 23 deletions project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1064,39 +1064,32 @@ object Build {
++ (dir / "shared/src/test/require-jdk7/org/scalajs/testsuite/javalib/util" ** "*.scala").get

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

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

++ (dir / "js/src/test/scala/org/scalajs/testsuite/jsinterop" ** (("*.scala": FileFilter)
-- "AsyncTest.scala" // needs PromiseMock.scala
-- "DynamicTest.scala" // one test requires JS exports, all other tests pass
-- "ExportsTest.scala" // JS exports
-- "FunctionTest.scala" // IR checking errors
-- "IterableTest.scala" // non-native JS classes
-- "JSExportStaticTest.scala" // JS exports
-- "JSNativeInPackage.scala" // IR checking errors
-- "JSNativeInPackage.scala" // tests fail (wrong load spec for JS globals)
-- "JSOptionalTest.scala" // non-native JS classes
-- "JSSymbolTest.scala" // non-native JS classes
-- "MiscInteropTest.scala" // non-native JS classes
-- "ModulesWithGlobalFallbackTest.scala" // non-native JS classes
-- "NestedJSClassTest.scala" // non-native JS classes
-- "NonNativeJSTypeTest.scala" // non-native JS classes
-- "PromiseMock.scala" // non-native JS classes
-- "SpecialTest.scala" // assertion error in ExpandSAMs
-- "SymbolTest.scala" // IR checking errors
-- "ThisFunctionTest.scala" // assertion error in ExpandSAMs
-- "UndefOrTest.scala" // StackOverflow in the compiler
)).get

++ (dir / "js/src/test/scala/org/scalajs/testsuite/junit" ** (("*.scala": FileFilter)
Expand All @@ -1108,20 +1101,15 @@ object Build {
)).get

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

++ (dir / "js/src/test/scala/org/scalajs/testsuite/niobuffer" ** "*.scala").get
++ (dir / "js/src/test/scala/org/scalajs/testsuite/scalalib" ** "*.scala").get

++ (dir / "js/src/test/scala/org/scalajs/testsuite/typedarray" ** (("*.scala": FileFilter)
-- "TypedArrayTest.scala" // assertion error in ExpandSAMs
)).get

++ (dir / "js/src/test/scala/org/scalajs/testsuite/typedarray" ** "*.scala").get
++ (dir / "js/src/test/scala/org/scalajs/testsuite/utils" ** "*.scala").get

++ (dir / "js/src/test/require-2.12" ** (("*.scala": FileFilter)
Expand All @@ -1132,9 +1120,7 @@ object Build {
-- "SAMJSTest.scala" // non-native JS classes
)).get

++ (dir / "js/src/test/scala-new-collections" ** (("*.scala": FileFilter)
-- "WrappedDictionaryToTest.scala" // IR checking errors
)).get
++ (dir / "js/src/test/scala-new-collections" ** "*.scala").get
)
}
)
Expand Down