Skip to content

Commit d875fef

Browse files
committed
Merge pull request #1148 from sjrd/scalajs-gen-exprs
Implement most of the Scala.js IR code generator.
2 parents cdbc163 + 122b035 commit d875fef

11 files changed

+1502
-90
lines changed

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

Lines changed: 1293 additions & 66 deletions
Large diffs are not rendered by default.

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import dotty.tools.dotc.core._
55
import Types._
66
import Contexts._
77
import Symbols._
8+
import Names._
89
import StdNames._
910
import Decorators._
1011

@@ -172,4 +173,27 @@ final class JSDefinitions()(implicit ctx: Context) {
172173
lazy val BoxesRunTime_unboxToCharR = defn.BoxesRunTimeModule.requiredMethodRef("unboxToChar")
173174
def BoxesRunTime_unboxToChar(implicit ctx: Context): Symbol = BoxesRunTime_unboxToCharR.symbol
174175

176+
/** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */
177+
private def scalajsClassName(cls: Symbol)(implicit ctx: Context): TypeName =
178+
if (cls.isClass && cls.owner == ScalaJSJSPackageClass) cls.asClass.name
179+
else EmptyTypeName
180+
181+
/** Is the given `cls` a class of the form `scala.scalajs.js.prefixN` where
182+
* `N` is a number.
183+
*
184+
* This is similar to `isVarArityClass` in `Definitions.scala`.
185+
*/
186+
private def isScalaJSVarArityClass(cls: Symbol, prefix: Name): Boolean = {
187+
val name = scalajsClassName(cls)
188+
name.startsWith(prefix) && name.drop(prefix.length).forall(_.isDigit)
189+
}
190+
191+
def isJSFunctionClass(cls: Symbol): Boolean =
192+
isScalaJSVarArityClass(cls, nme.Function)
193+
194+
private val ThisFunctionName = termName("ThisFunction")
195+
196+
def isJSThisFunctionClass(cls: Symbol): Boolean =
197+
isScalaJSVarArityClass(cls, ThisFunctionName)
198+
175199
}

src/dotty/tools/backend/sjs/JSEncoding.scala

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import ir.{Trees => js, Types => jstpe}
1919

2020
import ScopedVar.withScopedVars
2121
import JSDefinitions._
22+
import JSInterop._
2223

2324
/** Encoding of symbol names for JavaScript
2425
*
@@ -139,17 +140,32 @@ object JSEncoding {
139140
* java.lang.String, which is the `this` parameter.
140141
*/
141142
def encodeRTStringMethodSym(sym: Symbol)(
142-
implicit ctx: Context, pos: ir.Position): (Symbol, js.Ident) = {
143-
require(sym.is(Flags.Method), "encodeMethodSym called with non-method symbol: " + sym)
143+
implicit ctx: Context, pos: ir.Position): js.Ident = {
144144
require(sym.owner == defn.StringClass)
145145
require(!sym.isClassConstructor && !sym.is(Flags.Private))
146146

147147
val (encodedName, paramsString) =
148148
encodeMethodNameInternal(sym, inRTClass = true)
149-
val methodIdent = js.Ident(encodedName + paramsString,
149+
js.Ident(encodedName + paramsString,
150150
Some(sym.unexpandedName.decoded + paramsString))
151+
}
152+
153+
/** Encodes a constructor symbol of java.lang.String for use in RuntimeString.
154+
*
155+
* - The name is rerouted to `newString`
156+
* - The result type is set to `java.lang.String`
157+
*/
158+
def encodeRTStringCtorSym(sym: Symbol)(
159+
implicit ctx: Context, pos: ir.Position): js.Ident = {
160+
require(sym.owner == defn.StringClass)
161+
require(sym.isClassConstructor && !sym.is(Flags.Private))
151162

152-
(jsdefn.RuntimeStringModuleClass, methodIdent)
163+
val paramTypeNames = sym.info.firstParamTypes.map(internalName(_))
164+
val paramAndResultTypeNames = paramTypeNames :+ ir.Definitions.StringClass
165+
val paramsString = makeParamsString(paramAndResultTypeNames)
166+
167+
js.Ident("newString" + paramsString,
168+
Some(sym.unexpandedName.decoded + paramsString))
153169
}
154170

155171
private def encodeMethodNameInternal(sym: Symbol,
@@ -197,7 +213,7 @@ object JSEncoding {
197213

198214
def encodeClassType(sym: Symbol)(implicit ctx: Context): jstpe.Type = {
199215
if (sym == defn.ObjectClass) jstpe.AnyType
200-
else if (isRawJSType(sym)) jstpe.AnyType
216+
else if (isJSType(sym)) jstpe.AnyType
201217
else {
202218
assert(sym != defn.ArrayClass,
203219
"encodeClassType() cannot be called with ArrayClass")
@@ -210,20 +226,17 @@ object JSEncoding {
210226
js.Ident(encodeClassFullName(sym), Some(sym.fullName.toString))
211227
}
212228

213-
def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String =
214-
ir.Definitions.encodeClassName(sym.fullName.toString)
229+
def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String = {
230+
if (sym == defn.NothingClass) ir.Definitions.RuntimeNothingClass
231+
else if (sym == defn.NullClass) ir.Definitions.RuntimeNullClass
232+
else ir.Definitions.encodeClassName(sym.fullName.toString)
233+
}
215234

216235
private def encodeMemberNameInternal(sym: Symbol)(
217236
implicit ctx: Context): String = {
218-
sym.name.toString.replace("_", "$und")
237+
sym.name.toString.replace("_", "$und").replace("~", "$tilde")
219238
}
220239

221-
def isRawJSType(sym: Symbol)(implicit ctx: Context): Boolean =
222-
sym.hasAnnotation(jsdefn.RawJSTypeAnnot)
223-
224-
def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean =
225-
isRawJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot)
226-
227240
def toIRType(tp: Type)(implicit ctx: Context): jstpe.Type = {
228241
val refType = toReferenceTypeInternal(tp)
229242
refType._1 match {
@@ -243,7 +256,7 @@ object JSEncoding {
243256
else
244257
jstpe.IntType
245258
} else {
246-
if (sym == defn.ObjectClass || isRawJSType(sym))
259+
if (sym == defn.ObjectClass || isJSType(sym))
247260
jstpe.AnyType
248261
else if (sym == defn.NothingClass)
249262
jstpe.NothingType
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package dotty.tools.backend.sjs
2+
3+
import dotty.tools.dotc.core._
4+
import Contexts._
5+
import Flags._
6+
import Symbols._
7+
import NameOps._
8+
import StdNames._
9+
10+
import JSDefinitions._
11+
12+
/** Management of the interoperability with JavaScript. */
13+
object JSInterop {
14+
15+
/** Is this symbol a JavaScript type? */
16+
def isJSType(sym: Symbol)(implicit ctx: Context): Boolean = {
17+
//sym.hasAnnotation(jsdefn.RawJSTypeAnnot)
18+
ctx.atPhase(ctx.erasurePhase) { implicit ctx =>
19+
sym.derivesFrom(jsdefn.JSAnyClass)
20+
}
21+
}
22+
23+
/** Is this symbol a Scala.js-defined JS class, i.e., a non-native JS class? */
24+
def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean =
25+
isJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot)
26+
27+
/** Should this symbol be translated into a JS getter?
28+
*
29+
* This is true for any parameterless method, i.e., defined without `()`.
30+
* Unlike `SymDenotations.isGetter`, it applies to user-defined methods as
31+
* much as *accessor* methods created for `val`s and `var`s.
32+
*/
33+
def isJSGetter(sym: Symbol)(implicit ctx: Context): Boolean = {
34+
sym.info.firstParamTypes.isEmpty && ctx.atPhase(ctx.erasurePhase) { implicit ctx =>
35+
sym.info.isParameterless
36+
}
37+
}
38+
39+
/** Should this symbol be translated into a JS setter?
40+
*
41+
* This is true for any method whose name ends in `_=`.
42+
* Unlike `SymDenotations.isGetter`, it applies to user-defined methods as
43+
* much as *accessor* methods created for `var`s.
44+
*/
45+
def isJSSetter(sym: Symbol)(implicit ctx: Context): Boolean =
46+
sym.name.isSetterName && sym.is(Method)
47+
48+
/** Should this symbol be translated into a JS bracket access?
49+
*
50+
* This is true for methods annotated with `@JSBracketAccess`.
51+
*/
52+
def isJSBracketAccess(sym: Symbol)(implicit ctx: Context): Boolean =
53+
sym.hasAnnotation(jsdefn.JSBracketAccessAnnot)
54+
55+
/** Should this symbol be translated into a JS bracket call?
56+
*
57+
* This is true for methods annotated with `@JSBracketCall`.
58+
*/
59+
def isJSBracketCall(sym: Symbol)(implicit ctx: Context): Boolean =
60+
sym.hasAnnotation(jsdefn.JSBracketCallAnnot)
61+
62+
/** Is this symbol a default param accessor for a JS method?
63+
*
64+
* For default param accessors of *constructors*, we need to test whether
65+
* the companion *class* of the owner is a JS type; not whether the owner
66+
* is a JS type.
67+
*/
68+
def isJSDefaultParam(sym: Symbol)(implicit ctx: Context): Boolean = {
69+
sym.name.isDefaultGetterName && {
70+
val owner = sym.owner
71+
if (owner.is(ModuleClass) &&
72+
sym.name.asTermName.defaultGetterToMethod == nme.CONSTRUCTOR) {
73+
isJSType(owner.linkedClass)
74+
} else {
75+
isJSType(owner)
76+
}
77+
}
78+
}
79+
80+
/** Gets the unqualified JS name of a symbol.
81+
*
82+
* If it is not explicitly specified with an `@JSName` annotation, the
83+
* JS name is inferred from the Scala name.
84+
*/
85+
def jsNameOf(sym: Symbol)(implicit ctx: Context): String = {
86+
sym.getAnnotation(jsdefn.JSNameAnnot).flatMap(_.argumentConstant(0)).fold {
87+
val base = sym.name.unexpandedName.decode.toString.stripSuffix("_=")
88+
if (sym.is(ModuleClass)) base.stripSuffix("$")
89+
else if (!sym.is(Method)) base.stripSuffix(" ")
90+
else base
91+
} { constant =>
92+
constant.stringValue
93+
}
94+
}
95+
96+
/** Gets the fully qualified JS name of a static class of module Symbol.
97+
*
98+
* This is the JS name of the symbol qualified by the fully qualified JS
99+
* name of its original owner if the latter is a native JS object.
100+
*/
101+
def fullJSNameOf(sym: Symbol)(implicit ctx: Context): String = {
102+
assert(sym.isClass, s"fullJSNameOf called for non-class symbol $sym")
103+
sym.getAnnotation(jsdefn.JSFullNameAnnot).flatMap(_.argumentConstant(0)).fold {
104+
jsNameOf(sym)
105+
} { constant =>
106+
constant.stringValue
107+
}
108+
}
109+
110+
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,13 @@ object JSPrimitives {
3737
final val ENV_INFO = 316 // runtime.environmentInfo
3838
final val LINKING_INFO = 317 // runtime.linkingInfo
3939

40+
final val THROW = 318 // <special-ops>.throw
41+
4042
}
4143

4244
class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) {
4345
import JSPrimitives._
46+
import scala.tools.nsc.backend.ScalaPrimitives._
4447

4548
private lazy val jsPrimitives: Map[Symbol, Int] = initJSPrimitives(ctx)
4649

@@ -77,6 +80,18 @@ class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) {
7780

7881
val jsdefn = JSDefinitions.jsdefn
7982

83+
// For some reason, the JVM primitive set does not register those
84+
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newBooleanArray")), NEW_ZARRAY)
85+
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newByteArray")), NEW_BARRAY)
86+
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newShortArray")), NEW_SARRAY)
87+
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newCharArray")), NEW_CARRAY)
88+
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newIntArray")), NEW_IARRAY)
89+
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newLongArray")), NEW_LARRAY)
90+
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newFloatArray")), NEW_FARRAY)
91+
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newDoubleArray")), NEW_DARRAY)
92+
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newRefArray")), NEW_OARRAY)
93+
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newUnitArray")), NEW_OARRAY)
94+
8095
addPrimitive(defn.Any_getClass, GETCLASS)
8196

8297
for (i <- 0 to 22)
@@ -107,6 +122,8 @@ class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) {
107122
//addPrimitive(jsdefn.Runtime_environmentInfo, ENV_INFO)
108123
//addPrimitive(jsdefn.Runtime_linkingInfo, LINKING_INFO)
109124

125+
addPrimitive(defn.throwMethod, THROW)
126+
110127
primitives.toMap
111128
}
112129

src/dotty/tools/dotc/Compiler.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,11 @@ class Compiler {
104104
def rootContext(implicit ctx: Context): Context = {
105105
ctx.initialize()(ctx)
106106
val actualPhases = if (ctx.settings.scalajs.value) {
107-
phases
107+
// Remove phases that Scala.js does not want
108+
phases.mapConserve(_.filter {
109+
case _: FunctionalInterfaces => false
110+
case _ => true
111+
}).filter(_.nonEmpty)
108112
} else {
109113
// Remove Scala.js-related phases
110114
phases.mapConserve(_.filter {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import ClassPath.{ JavaContext, DefaultJavaContext }
77
import core._
88
import Symbols._, Types._, Contexts._, Denotations._, SymDenotations._, StdNames._, Names._
99
import Flags._, Scopes._, Decorators._, NameOps._, util.Positions._
10+
import transform.ExplicitOuter, transform.SymUtils._
1011

1112
class JavaPlatform extends Platform {
1213

@@ -38,6 +39,14 @@ class JavaPlatform extends Platform {
3839

3940
def rootLoader(root: TermSymbol)(implicit ctx: Context): SymbolLoader = new ctx.base.loaders.PackageLoader(root, classPath)
4041

42+
/** Is the SAMType `cls` also a SAM under the rules of the JVM? */
43+
def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
44+
cls.is(NoInitsTrait) &&
45+
cls.superClass == defn.ObjectClass &&
46+
cls.directlyInheritedTraits.forall(_.is(NoInits)) &&
47+
!ExplicitOuter.needsOuterIfReferenced(cls) &&
48+
cls.typeRef.fields.isEmpty // Superaccessors already show up as abstract methods here, so no test necessary
49+
4150
/** We could get away with excluding BoxedBooleanClass for the
4251
* purpose of equality testing since it need not compare equal
4352
* to anything but other booleans, but it should be present in

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ abstract class Platform {
2727
/** Any platform-specific phases. */
2828
//def platformPhases: List[SubComponent]
2929

30+
/** Is the SAMType `cls` also a SAM under the rules of the platform? */
31+
def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean
32+
3033
/** The various ways a boxed primitive might materialize at runtime. */
3134
def isMaybeBoxed(sym: ClassSymbol)(implicit ctx: Context): Boolean
3235

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dotty.tools.dotc.config
22

33
import dotty.tools.dotc.core._
44
import Contexts._
5+
import Symbols._
56

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

@@ -10,4 +11,8 @@ class SJSPlatform()(implicit ctx: Context) extends JavaPlatform {
1011
/** Scala.js-specific definitions. */
1112
val jsDefinitions: JSDefinitions = new JSDefinitions()
1213

14+
/** Is the SAMType `cls` also a SAM under the rules of the Scala.js back-end? */
15+
override def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
16+
defn.isFunctionClass(cls) || jsDefinitions.isJSFunctionClass(cls)
17+
1318
}

src/dotty/tools/dotc/core/Phases.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,10 @@ object Phases {
233233
private val picklerCache = new PhaseCache(classOf[Pickler])
234234

235235
private val refChecksCache = new PhaseCache(classOf[RefChecks])
236+
private val elimRepeatedCache = new PhaseCache(classOf[ElimRepeated])
236237
private val extensionMethodsCache = new PhaseCache(classOf[ExtensionMethods])
237238
private val erasureCache = new PhaseCache(classOf[Erasure])
239+
private val elimErasedValueTypeCache = new PhaseCache(classOf[ElimErasedValueType])
238240
private val patmatCache = new PhaseCache(classOf[PatternMatcher])
239241
private val lambdaLiftCache = new PhaseCache(classOf[LambdaLift])
240242
private val flattenCache = new PhaseCache(classOf[Flatten])
@@ -245,8 +247,10 @@ object Phases {
245247
def typerPhase = typerCache.phase
246248
def picklerPhase = picklerCache.phase
247249
def refchecksPhase = refChecksCache.phase
250+
def elimRepeatedPhase = elimRepeatedCache.phase
248251
def extensionMethodsPhase = extensionMethodsCache.phase
249252
def erasurePhase = erasureCache.phase
253+
def elimErasedValueTypePhase = elimErasedValueTypeCache.phase
250254
def patmatPhase = patmatCache.phase
251255
def lambdaLiftPhase = lambdaLiftCache.phase
252256
def flattenPhase = flattenCache.phase

src/dotty/tools/dotc/transform/ExpandSAMs.scala

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,17 @@ class ExpandSAMs extends MiniPhaseTransform { thisTransformer =>
2525

2626
import ast.tpd._
2727

28-
/** Is SAMType `cls` also a SAM under the rules of the JVM? */
29-
def isJvmSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
30-
cls.is(NoInitsTrait) &&
31-
cls.superClass == defn.ObjectClass &&
32-
cls.directlyInheritedTraits.forall(_.is(NoInits)) &&
33-
!ExplicitOuter.needsOuterIfReferenced(cls) &&
34-
cls.typeRef.fields.isEmpty // Superaccessors already show up as abstract methods here, so no test necessary
28+
/** Is the SAMType `cls` also a SAM under the rules of the platform? */
29+
def isPlatformSam(cls: ClassSymbol)(implicit ctx: Context): Boolean =
30+
ctx.platform.isSam(cls)
3531

3632
override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = tree match {
3733
case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol =>
3834
tpt.tpe match {
3935
case NoType => tree // it's a plain function
4036
case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass) =>
4137
toPartialFunction(tree)
42-
case tpe @ SAMType(_) if isJvmSam(tpe.classSymbol.asClass) =>
38+
case tpe @ SAMType(_) if isPlatformSam(tpe.classSymbol.asClass) =>
4339
tree
4440
case tpe =>
4541
val Seq(samDenot) = tpe.abstractTermMembers.filter(!_.symbol.is(SuperAccessor))

0 commit comments

Comments
 (0)