Skip to content

Commit 7f75e30

Browse files
committed
Scala.js: Implement the PrepJSInterop phase, minus exports handling.
The `PrepJSInterop` phase is responsible for: * Performing all kinds of Scala.js-specific compile-time checks, and emitting the appropriate compile errors. * Perform some transformations that are necessary for JavaScript interop, notably generating exports forwarders. This commit ports all the functionality of `PrepJSInterop` from Scala 2, except the following: * Handling of `scala.Enumeration`s: it is unclear whether we still want to support that in the core, or if it should be handled by an optional compiler plugin in the future. * Exports: they will be done later. * Warnings about duplicate fields in `js.Dynamic.literal`: mostly because they are non-essential. The test cases are ported from the Scala.js compiler tests.
1 parent e1b3d1a commit 7f75e30

File tree

98 files changed

+5063
-13
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+5063
-13
lines changed

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

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ final class JSDefinitions()(using Context) {
3636
def JSPackage_constructorOf(using Context) = JSPackage_constructorOfR.symbol
3737
@threadUnsafe lazy val JSPackage_nativeR = ScalaJSJSPackageClass.requiredMethodRef("native")
3838
def JSPackage_native(using Context) = JSPackage_nativeR.symbol
39+
@threadUnsafe lazy val JSPackage_undefinedR = ScalaJSJSPackageClass.requiredMethodRef("undefined")
40+
def JSPackage_undefined(using Context) = JSPackage_undefinedR.symbol
3941

4042
@threadUnsafe lazy val JSNativeAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.native")
4143
def JSNativeAnnot(using Context) = JSNativeAnnotType.symbol.asClass
@@ -63,6 +65,10 @@ final class JSDefinitions()(using Context) {
6365
@threadUnsafe lazy val JavaScriptExceptionType: TypeRef = requiredClassRef("scala.scalajs.js.JavaScriptException")
6466
def JavaScriptExceptionClass(using Context) = JavaScriptExceptionType.symbol.asClass
6567

68+
@threadUnsafe lazy val JSGlobalAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSGlobal")
69+
def JSGlobalAnnot(using Context) = JSGlobalAnnotType.symbol.asClass
70+
@threadUnsafe lazy val JSImportAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSImport")
71+
def JSImportAnnot(using Context) = JSImportAnnotType.symbol.asClass
6672
@threadUnsafe lazy val JSGlobalScopeAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSGlobalScope")
6773
def JSGlobalScopeAnnot(using Context) = JSGlobalScopeAnnotType.symbol.asClass
6874
@threadUnsafe lazy val JSNameAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSName")
@@ -73,21 +79,24 @@ final class JSDefinitions()(using Context) {
7379
def JSBracketAccessAnnot(using Context) = JSBracketAccessAnnotType.symbol.asClass
7480
@threadUnsafe lazy val JSBracketCallAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSBracketCall")
7581
def JSBracketCallAnnot(using Context) = JSBracketCallAnnotType.symbol.asClass
82+
@threadUnsafe lazy val JSExportTopLevelAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSExportTopLevel")
83+
def JSExportTopLevelAnnot(using Context) = JSExportTopLevelAnnotType.symbol.asClass
7684
@threadUnsafe lazy val JSExportAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSExport")
7785
def JSExportAnnot(using Context) = JSExportAnnotType.symbol.asClass
78-
@threadUnsafe lazy val JSExportDescendentObjectsAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSExportDescendentObjects")
79-
def JSExportDescendentObjectsAnnot(using Context) = JSExportDescendentObjectsAnnotType.symbol.asClass
80-
@threadUnsafe lazy val JSExportDescendentClassesAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSExportDescendentClasses")
81-
def JSExportDescendentClassesAnnot(using Context) = JSExportDescendentClassesAnnotType.symbol.asClass
86+
@threadUnsafe lazy val JSExportStaticAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSExportStatic")
87+
def JSExportStaticAnnot(using Context) = JSExportStaticAnnotType.symbol.asClass
8288
@threadUnsafe lazy val JSExportAllAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSExportAll")
8389
def JSExportAllAnnot(using Context) = JSExportAllAnnotType.symbol.asClass
84-
@threadUnsafe lazy val JSExportNamedAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.JSExportNamed")
85-
def JSExportNamedAnnot(using Context) = JSExportNamedAnnotType.symbol.asClass
86-
@threadUnsafe lazy val RawJSTypeAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.RawJSType")
87-
def RawJSTypeAnnot(using Context) = RawJSTypeAnnotType.symbol.asClass
88-
@threadUnsafe lazy val ExposedJSMemberAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.ExposedJSMember")
90+
@threadUnsafe lazy val JSTypeAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.internal.JSType")
91+
def JSTypeAnnot(using Context) = JSTypeAnnotType.symbol.asClass
92+
@threadUnsafe lazy val JSOptionalAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.internal.JSOptional")
93+
def JSOptionalAnnot(using Context) = JSOptionalAnnotType.symbol.asClass
94+
@threadUnsafe lazy val ExposedJSMemberAnnotType: TypeRef = requiredClassRef("scala.scalajs.js.annotation.internal.ExposedJSMember")
8995
def ExposedJSMemberAnnot(using Context) = ExposedJSMemberAnnotType.symbol.asClass
9096

97+
@threadUnsafe lazy val JSImportNamespaceModuleRef = requiredModuleRef("scala.scalajs.js.annotation.JSImport.Namespace")
98+
def JSImportNamespaceModule(using Context) = JSImportNamespaceModuleRef.symbol
99+
91100
@threadUnsafe lazy val JSAnyModuleRef = requiredModuleRef("scala.scalajs.js.Any")
92101
def JSAnyModule(using Context) = JSAnyModuleRef.symbol
93102
@threadUnsafe lazy val JSAny_fromFunctionR = (0 to 22).map(n => JSAnyModule.requiredMethodRef("fromFunction" + n)).toArray

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class Compiler {
4141
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
4242
List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files
4343
List(new PostTyper) :: // Additional checks and cleanups after type checking
44+
List(new sjs.PrepJSInterop) :: // Additional checks and transformations for Scala.js (Scala.js only)
4445
List(new Staging) :: // Check PCP, heal quoted types and expand macros
4546
List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks
4647
List(new SetRootTree) :: // Set the `rootTreeOrProvider` on class symbols

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ class JavaPlatform extends Platform {
6161
(sym derivesFrom BoxedBooleanClass)
6262
}
6363

64+
def shouldReceiveJavaSerializationMethods(sym: ClassSymbol)(using Context): Boolean =
65+
true
66+
6467
def newClassLoader(bin: AbstractFile)(using Context): SymbolLoader =
6568
new ClassfileLoader(bin)
6669
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ abstract class Platform {
3131
/** The various ways a boxed primitive might materialize at runtime. */
3232
def isMaybeBoxed(sym: ClassSymbol)(using Context): Boolean
3333

34+
/** Is the given class symbol eligible for Java serialization-specific methods? */
35+
def shouldReceiveJavaSerializationMethods(sym: ClassSymbol)(using Context): Boolean
36+
3437
/** Create a new class loader to load class file `bin` */
3538
def newClassLoader(bin: AbstractFile)(using Context): SymbolLoader
3639

@@ -44,4 +47,3 @@ abstract class Platform {
4447
case _ => false
4548
}
4649
}
47-

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,18 @@ package dotty.tools.dotc.config
33
import dotty.tools.dotc.core._
44
import Contexts._
55
import Symbols._
6+
import SymDenotations._
67

78
import dotty.tools.backend.sjs.JSDefinitions
89

10+
import org.scalajs.ir.Trees.JSNativeLoadSpec
11+
12+
object SJSPlatform {
13+
/** The `SJSPlatform` for the current context. */
14+
def sjsPlatform(using Context): SJSPlatform =
15+
ctx.platform.asInstanceOf[SJSPlatform]
16+
}
17+
918
class SJSPlatform()(using Context) extends JavaPlatform {
1019

1120
/** Scala.js-specific definitions. */
@@ -16,4 +25,27 @@ class SJSPlatform()(using Context) extends JavaPlatform {
1625
defn.isFunctionClass(cls)
1726
|| jsDefinitions.isJSFunctionClass(cls)
1827
|| jsDefinitions.isJSThisFunctionClass(cls)
28+
29+
override def shouldReceiveJavaSerializationMethods(sym: ClassSymbol)(using Context): Boolean =
30+
!sym.isSubClass(jsDefinitions.JSAnyClass)
31+
32+
object perRunInfo {
33+
private val jsNativeLoadSpecs = new MutableSymbolMap[JSNativeLoadSpec]
34+
35+
/** Clears all the info at the beginning of a run. */
36+
def clear(): Unit =
37+
jsNativeLoadSpecs.clear()
38+
39+
/** Stores the JS native load spec of a symbol for the current compilation run. */
40+
def storeJSNativeLoadSpec(sym: Symbol, spec: JSNativeLoadSpec): Unit =
41+
jsNativeLoadSpecs(sym) = spec
42+
43+
/** Gets the JS native load spec of a symbol in the current compilation run. */
44+
def jsNativeLoadSpecOf(sym: Symbol): JSNativeLoadSpec =
45+
jsNativeLoadSpecs(sym)
46+
47+
/** Gets the JS native load spec of a symbol in the current compilation run, if it has one. */
48+
def jsNativeLoadSpecOfOption(sym: Symbol): Option[JSNativeLoadSpec] =
49+
jsNativeLoadSpecs.get(sym)
50+
}
1951
}

compiler/src/dotty/tools/dotc/core/Annotations.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ object Annotations {
3333
if (i < args.length) Some(args(i)) else None
3434
}
3535
def argumentConstant(i: Int)(using Context): Option[Constant] =
36-
for (ConstantType(c) <- argument(i) map (_.tpe)) yield c
36+
for (ConstantType(c) <- argument(i) map (_.tpe.widenTermRefExpr.normalized)) yield c
37+
38+
def constantStringArg(i: Int)(using Context): Option[String] =
39+
for (Constant(s: String) <- argumentConstant(i)) yield s
3740

3841
/** The tree evaluaton is in progress. */
3942
def isEvaluating: Boolean = false

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,10 @@ object SymDenotations {
259259
final def addAnnotation(annot: Annotation): Unit =
260260
annotations = annot :: myAnnotations
261261

262+
/** Add the given annotation without parameters to the annotations of this denotation */
263+
final def addAnnotation(cls: ClassSymbol)(using Context): Unit =
264+
addAnnotation(Annotation(cls))
265+
262266
/** Remove annotation with given class from this denotation */
263267
final def removeAnnotation(cls: Symbol)(using Context): Unit =
264268
annotations = myAnnotations.filterNot(_ matches cls)

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,15 @@ object SymUtils {
188188
def hasAnonymousChild(using Context): Boolean =
189189
self.children.exists(_ `eq` self)
190190

191+
/** Is this symbol directly owner by a term symbol, i.e., is it local to a block? */
192+
def isLocalToBlock(using Context): Boolean =
193+
self.owner.isTerm
194+
191195
/** Is symbol directly or indirectly owned by a term symbol? */
192196
@tailrec final def isLocal(using Context): Boolean = {
193197
val owner = self.maybeOwner
194198
if (!owner.exists) false
195-
else if (owner.isTerm) true
199+
else if (isLocalToBlock) true
196200
else if (owner.is(Package)) false
197201
else owner.isLocal
198202
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,11 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
397397
* we do that even for objects that are not serializable at this phase.
398398
*/
399399
def serializableObjectMethod(clazz: ClassSymbol)(using Context): List[Tree] =
400-
if clazz.is(Module) && clazz.isStatic && !hasWriteReplace(clazz) then
400+
if clazz.is(Module)
401+
&& clazz.isStatic
402+
&& !hasWriteReplace(clazz)
403+
&& ctx.platform.shouldReceiveJavaSerializationMethods(clazz)
404+
then
401405
List(
402406
DefDef(writeReplaceDef(clazz),
403407
_ => New(defn.ModuleSerializationProxyClass.typeRef,
@@ -424,6 +428,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
424428
&& !clazz.derivesFrom(defn.JavaEnumClass)
425429
&& clazz.isSerializable
426430
&& !hasReadResolve(clazz)
431+
&& ctx.platform.shouldReceiveJavaSerializationMethods(clazz)
427432
then
428433
List(
429434
DefDef(readResolveDef(clazz),
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package dotty.tools.dotc
2+
package transform
3+
package sjs
4+
5+
import core._
6+
import util.SrcPos
7+
import Annotations._
8+
import Constants._
9+
import Contexts._
10+
import Decorators._
11+
import DenotTransformers._
12+
import Flags._
13+
import NameOps._
14+
import Names._
15+
import Phases._
16+
import Scopes._
17+
import StdNames._
18+
import Symbols._
19+
import SymDenotations._
20+
import SymUtils._
21+
import ast.Trees._
22+
import Types._
23+
24+
import dotty.tools.backend.sjs.JSDefinitions.jsdefn
25+
26+
object JSInteropUtils {
27+
enum JSName {
28+
case Literal(name: String)
29+
case Computed(sym: Symbol)
30+
31+
def displayName(using Context): String = this match {
32+
case Literal(name) => name
33+
case Computed(sym) => sym.fullName.toString()
34+
}
35+
}
36+
37+
extension (sym: Symbol) {
38+
/** Should this symbol be translated into a JS getter? */
39+
def isJSGetter(using Context): Boolean = {
40+
sym.is(Module) || (
41+
sym.is(Method)
42+
&& sym.info.firstParamTypes.isEmpty
43+
&& atPhaseNoLater(erasurePhase)(sym.info.isParameterless))
44+
}
45+
46+
/** Should this symbol be translated into a JS setter? */
47+
def isJSSetter(using Context): Boolean =
48+
sym.originalName.isSetterName && sym.is(Method)
49+
50+
/** Should this symbol be translated into a JS bracket access? */
51+
def isJSBracketAccess(using Context): Boolean =
52+
sym.hasAnnotation(jsdefn.JSBracketAccessAnnot)
53+
54+
/** Should this symbol be translated into a JS bracket call? */
55+
def isJSBracketCall(using Context): Boolean =
56+
sym.hasAnnotation(jsdefn.JSBracketCallAnnot)
57+
58+
/** Gets the unqualified JS name of the symbol.
59+
*
60+
* If it is not explicitly specified with an `@JSName` annotation, the
61+
* JS name is inferred from the Scala name.
62+
*/
63+
def jsName(using Context): JSName = {
64+
sym.getAnnotation(jsdefn.JSNameAnnot).fold[JSName] {
65+
JSName.Literal(defaultJSName)
66+
} { annotation =>
67+
annotation.arguments.head match {
68+
case Literal(Constant(name: String)) => JSName.Literal(name)
69+
case tree => JSName.Computed(tree.symbol)
70+
}
71+
}
72+
}
73+
74+
def defaultJSName(using Context): String =
75+
if (sym.isTerm) sym.asTerm.name.unexpandedName.getterName.toString()
76+
else sym.name.unexpandedName.toString()
77+
}
78+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package dotty.tools.dotc
2+
package transform
3+
package sjs
4+
5+
import dotty.tools.dotc.ast.{Trees, tpd, untpd}
6+
import scala.collection.mutable
7+
import core._
8+
import dotty.tools.dotc.typer.Checking
9+
import dotty.tools.dotc.typer.Inliner
10+
import dotty.tools.dotc.typer.VarianceChecker
11+
import Types._
12+
import Contexts._
13+
import Names._
14+
import Flags._
15+
import DenotTransformers._
16+
import Phases._
17+
import SymDenotations._
18+
import StdNames._
19+
import Annotations._
20+
import Trees._
21+
import Scopes._
22+
import Decorators._
23+
import Symbols._
24+
import SymUtils._
25+
import NameOps._
26+
import reporting._
27+
import util.SrcPos
28+
29+
import dotty.tools.backend.sjs.JSDefinitions.jsdefn
30+
31+
object PrepJSExports {
32+
import tpd._
33+
34+
def registerClassOrModuleExports(sym: Symbol)(using Context): Unit = {
35+
// TODO
36+
}
37+
38+
/** Generate the exporter for the given DefDef
39+
* or ValDef (abstract val in class, val in trait or lazy val;
40+
* these don't get DefDefs until the fields phase)
41+
*
42+
* If this DefDef is a constructor, it is registered to be exported by
43+
* GenJSCode instead and no trees are returned.
44+
*/
45+
def genExportMember(baseSym: Symbol)(using Context): List[Tree] = {
46+
// TODO
47+
Nil
48+
}
49+
}

0 commit comments

Comments
 (0)