Skip to content

Commit

Permalink
Merge pull request #1148 from sjrd/scalajs-gen-exprs
Browse files Browse the repository at this point in the history
Implement most of the Scala.js IR code generator.
  • Loading branch information
odersky committed Mar 18, 2016
2 parents cdbc163 + 122b035 commit d875fef
Show file tree
Hide file tree
Showing 11 changed files with 1,502 additions and 90 deletions.
1,359 changes: 1,293 additions & 66 deletions src/dotty/tools/backend/sjs/JSCodeGen.scala

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions src/dotty/tools/backend/sjs/JSDefinitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import dotty.tools.dotc.core._
import Types._
import Contexts._
import Symbols._
import Names._
import StdNames._
import Decorators._

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

/** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */
private def scalajsClassName(cls: Symbol)(implicit ctx: Context): TypeName =
if (cls.isClass && cls.owner == ScalaJSJSPackageClass) cls.asClass.name
else EmptyTypeName

/** Is the given `cls` a class of the form `scala.scalajs.js.prefixN` where
* `N` is a number.
*
* This is similar to `isVarArityClass` in `Definitions.scala`.
*/
private def isScalaJSVarArityClass(cls: Symbol, prefix: Name): Boolean = {
val name = scalajsClassName(cls)
name.startsWith(prefix) && name.drop(prefix.length).forall(_.isDigit)
}

def isJSFunctionClass(cls: Symbol): Boolean =
isScalaJSVarArityClass(cls, nme.Function)

private val ThisFunctionName = termName("ThisFunction")

def isJSThisFunctionClass(cls: Symbol): Boolean =
isScalaJSVarArityClass(cls, ThisFunctionName)

}
43 changes: 28 additions & 15 deletions src/dotty/tools/backend/sjs/JSEncoding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import ir.{Trees => js, Types => jstpe}

import ScopedVar.withScopedVars
import JSDefinitions._
import JSInterop._

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

val (encodedName, paramsString) =
encodeMethodNameInternal(sym, inRTClass = true)
val methodIdent = js.Ident(encodedName + paramsString,
js.Ident(encodedName + paramsString,
Some(sym.unexpandedName.decoded + paramsString))
}

/** Encodes a constructor symbol of java.lang.String for use in RuntimeString.
*
* - The name is rerouted to `newString`
* - The result type is set to `java.lang.String`
*/
def encodeRTStringCtorSym(sym: Symbol)(
implicit ctx: Context, pos: ir.Position): js.Ident = {
require(sym.owner == defn.StringClass)
require(sym.isClassConstructor && !sym.is(Flags.Private))

(jsdefn.RuntimeStringModuleClass, methodIdent)
val paramTypeNames = sym.info.firstParamTypes.map(internalName(_))
val paramAndResultTypeNames = paramTypeNames :+ ir.Definitions.StringClass
val paramsString = makeParamsString(paramAndResultTypeNames)

js.Ident("newString" + paramsString,
Some(sym.unexpandedName.decoded + paramsString))
}

private def encodeMethodNameInternal(sym: Symbol,
Expand Down Expand Up @@ -197,7 +213,7 @@ object JSEncoding {

def encodeClassType(sym: Symbol)(implicit ctx: Context): jstpe.Type = {
if (sym == defn.ObjectClass) jstpe.AnyType
else if (isRawJSType(sym)) jstpe.AnyType
else if (isJSType(sym)) jstpe.AnyType
else {
assert(sym != defn.ArrayClass,
"encodeClassType() cannot be called with ArrayClass")
Expand All @@ -210,20 +226,17 @@ object JSEncoding {
js.Ident(encodeClassFullName(sym), Some(sym.fullName.toString))
}

def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String =
ir.Definitions.encodeClassName(sym.fullName.toString)
def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String = {
if (sym == defn.NothingClass) ir.Definitions.RuntimeNothingClass
else if (sym == defn.NullClass) ir.Definitions.RuntimeNullClass
else ir.Definitions.encodeClassName(sym.fullName.toString)
}

private def encodeMemberNameInternal(sym: Symbol)(
implicit ctx: Context): String = {
sym.name.toString.replace("_", "$und")
sym.name.toString.replace("_", "$und").replace("~", "$tilde")
}

def isRawJSType(sym: Symbol)(implicit ctx: Context): Boolean =
sym.hasAnnotation(jsdefn.RawJSTypeAnnot)

def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean =
isRawJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot)

def toIRType(tp: Type)(implicit ctx: Context): jstpe.Type = {
val refType = toReferenceTypeInternal(tp)
refType._1 match {
Expand All @@ -243,7 +256,7 @@ object JSEncoding {
else
jstpe.IntType
} else {
if (sym == defn.ObjectClass || isRawJSType(sym))
if (sym == defn.ObjectClass || isJSType(sym))
jstpe.AnyType
else if (sym == defn.NothingClass)
jstpe.NothingType
Expand Down
110 changes: 110 additions & 0 deletions src/dotty/tools/backend/sjs/JSInterop.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package dotty.tools.backend.sjs

import dotty.tools.dotc.core._
import Contexts._
import Flags._
import Symbols._
import NameOps._
import StdNames._

import JSDefinitions._

/** Management of the interoperability with JavaScript. */
object JSInterop {

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

/** Is this symbol a Scala.js-defined JS class, i.e., a non-native JS class? */
def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean =
isJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot)

/** Should this symbol be translated into a JS getter?
*
* This is true for any parameterless method, i.e., defined without `()`.
* Unlike `SymDenotations.isGetter`, it applies to user-defined methods as
* much as *accessor* methods created for `val`s and `var`s.
*/
def isJSGetter(sym: Symbol)(implicit ctx: Context): Boolean = {
sym.info.firstParamTypes.isEmpty && ctx.atPhase(ctx.erasurePhase) { implicit ctx =>
sym.info.isParameterless
}
}

/** Should this symbol be translated into a JS setter?
*
* This is true for any method whose name ends in `_=`.
* Unlike `SymDenotations.isGetter`, it applies to user-defined methods as
* much as *accessor* methods created for `var`s.
*/
def isJSSetter(sym: Symbol)(implicit ctx: Context): Boolean =
sym.name.isSetterName && sym.is(Method)

/** Should this symbol be translated into a JS bracket access?
*
* This is true for methods annotated with `@JSBracketAccess`.
*/
def isJSBracketAccess(sym: Symbol)(implicit ctx: Context): Boolean =
sym.hasAnnotation(jsdefn.JSBracketAccessAnnot)

/** Should this symbol be translated into a JS bracket call?
*
* This is true for methods annotated with `@JSBracketCall`.
*/
def isJSBracketCall(sym: Symbol)(implicit ctx: Context): Boolean =
sym.hasAnnotation(jsdefn.JSBracketCallAnnot)

/** Is this symbol a default param accessor for a JS method?
*
* For default param accessors of *constructors*, we need to test whether
* the companion *class* of the owner is a JS type; not whether the owner
* is a JS type.
*/
def isJSDefaultParam(sym: Symbol)(implicit ctx: Context): Boolean = {
sym.name.isDefaultGetterName && {
val owner = sym.owner
if (owner.is(ModuleClass) &&
sym.name.asTermName.defaultGetterToMethod == nme.CONSTRUCTOR) {
isJSType(owner.linkedClass)
} else {
isJSType(owner)
}
}
}

/** Gets the unqualified JS name of a symbol.
*
* If it is not explicitly specified with an `@JSName` annotation, the
* JS name is inferred from the Scala name.
*/
def jsNameOf(sym: Symbol)(implicit ctx: Context): String = {
sym.getAnnotation(jsdefn.JSNameAnnot).flatMap(_.argumentConstant(0)).fold {
val base = sym.name.unexpandedName.decode.toString.stripSuffix("_=")
if (sym.is(ModuleClass)) base.stripSuffix("$")
else if (!sym.is(Method)) base.stripSuffix(" ")
else base
} { constant =>
constant.stringValue
}
}

/** Gets the fully qualified JS name of a static class of module Symbol.
*
* This is the JS name of the symbol qualified by the fully qualified JS
* name of its original owner if the latter is a native JS object.
*/
def fullJSNameOf(sym: Symbol)(implicit ctx: Context): String = {
assert(sym.isClass, s"fullJSNameOf called for non-class symbol $sym")
sym.getAnnotation(jsdefn.JSFullNameAnnot).flatMap(_.argumentConstant(0)).fold {
jsNameOf(sym)
} { constant =>
constant.stringValue
}
}

}
17 changes: 17 additions & 0 deletions src/dotty/tools/backend/sjs/JSPrimitives.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ object JSPrimitives {
final val ENV_INFO = 316 // runtime.environmentInfo
final val LINKING_INFO = 317 // runtime.linkingInfo

final val THROW = 318 // <special-ops>.throw

}

class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) {
import JSPrimitives._
import scala.tools.nsc.backend.ScalaPrimitives._

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

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

val jsdefn = JSDefinitions.jsdefn

// For some reason, the JVM primitive set does not register those
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newBooleanArray")), NEW_ZARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newByteArray")), NEW_BARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newShortArray")), NEW_SARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newCharArray")), NEW_CARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newIntArray")), NEW_IARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newLongArray")), NEW_LARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newFloatArray")), NEW_FARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newDoubleArray")), NEW_DARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newRefArray")), NEW_OARRAY)
addPrimitive(defn.DottyArraysModule.requiredMethod(Names.termName("newUnitArray")), NEW_OARRAY)

addPrimitive(defn.Any_getClass, GETCLASS)

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

addPrimitive(defn.throwMethod, THROW)

primitives.toMap
}

Expand Down
6 changes: 5 additions & 1 deletion src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ class Compiler {
def rootContext(implicit ctx: Context): Context = {
ctx.initialize()(ctx)
val actualPhases = if (ctx.settings.scalajs.value) {
phases
// Remove phases that Scala.js does not want
phases.mapConserve(_.filter {
case _: FunctionalInterfaces => false
case _ => true
}).filter(_.nonEmpty)
} else {
// Remove Scala.js-related phases
phases.mapConserve(_.filter {
Expand Down
9 changes: 9 additions & 0 deletions src/dotty/tools/dotc/config/JavaPlatform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ClassPath.{ JavaContext, DefaultJavaContext }
import core._
import Symbols._, Types._, Contexts._, Denotations._, SymDenotations._, StdNames._, Names._
import Flags._, Scopes._, Decorators._, NameOps._, util.Positions._
import transform.ExplicitOuter, transform.SymUtils._

class JavaPlatform extends Platform {

Expand Down Expand Up @@ -38,6 +39,14 @@ class JavaPlatform extends Platform {

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

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

/** We could get away with excluding BoxedBooleanClass for the
* purpose of equality testing since it need not compare equal
* to anything but other booleans, but it should be present in
Expand Down
3 changes: 3 additions & 0 deletions src/dotty/tools/dotc/config/Platform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ abstract class Platform {
/** Any platform-specific phases. */
//def platformPhases: List[SubComponent]

/** Is the SAMType `cls` also a SAM under the rules of the platform? */
def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean

/** The various ways a boxed primitive might materialize at runtime. */
def isMaybeBoxed(sym: ClassSymbol)(implicit ctx: Context): Boolean

Expand Down
5 changes: 5 additions & 0 deletions src/dotty/tools/dotc/config/SJSPlatform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dotty.tools.dotc.config

import dotty.tools.dotc.core._
import Contexts._
import Symbols._

import dotty.tools.backend.sjs.JSDefinitions

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

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

}
4 changes: 4 additions & 0 deletions src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,10 @@ object Phases {
private val picklerCache = new PhaseCache(classOf[Pickler])

private val refChecksCache = new PhaseCache(classOf[RefChecks])
private val elimRepeatedCache = new PhaseCache(classOf[ElimRepeated])
private val extensionMethodsCache = new PhaseCache(classOf[ExtensionMethods])
private val erasureCache = new PhaseCache(classOf[Erasure])
private val elimErasedValueTypeCache = new PhaseCache(classOf[ElimErasedValueType])
private val patmatCache = new PhaseCache(classOf[PatternMatcher])
private val lambdaLiftCache = new PhaseCache(classOf[LambdaLift])
private val flattenCache = new PhaseCache(classOf[Flatten])
Expand All @@ -245,8 +247,10 @@ object Phases {
def typerPhase = typerCache.phase
def picklerPhase = picklerCache.phase
def refchecksPhase = refChecksCache.phase
def elimRepeatedPhase = elimRepeatedCache.phase
def extensionMethodsPhase = extensionMethodsCache.phase
def erasurePhase = erasureCache.phase
def elimErasedValueTypePhase = elimErasedValueTypeCache.phase
def patmatPhase = patmatCache.phase
def lambdaLiftPhase = lambdaLiftCache.phase
def flattenPhase = flattenCache.phase
Expand Down
12 changes: 4 additions & 8 deletions src/dotty/tools/dotc/transform/ExpandSAMs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,17 @@ class ExpandSAMs extends MiniPhaseTransform { thisTransformer =>

import ast.tpd._

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

override def transformBlock(tree: Block)(implicit ctx: Context, info: TransformerInfo): Tree = tree match {
case Block(stats @ (fn: DefDef) :: Nil, Closure(_, fnRef, tpt)) if fnRef.symbol == fn.symbol =>
tpt.tpe match {
case NoType => tree // it's a plain function
case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass) =>
toPartialFunction(tree)
case tpe @ SAMType(_) if isJvmSam(tpe.classSymbol.asClass) =>
case tpe @ SAMType(_) if isPlatformSam(tpe.classSymbol.asClass) =>
tree
case tpe =>
val Seq(samDenot) = tpe.abstractTermMembers.filter(!_.symbol.is(SuperAccessor))
Expand Down

0 comments on commit d875fef

Please sign in to comment.