Permalink
5378 lines (4684 sloc) 198 KB
/* Scala.js compiler
* Copyright 2013 LAMP/EPFL
* @author Sébastien Doeraene
*/
package org.scalajs.core.compiler
import scala.language.implicitConversions
import scala.annotation.switch
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.tools.nsc._
import scala.annotation.tailrec
import org.scalajs.core.ir
import ir.{Trees => js, Types => jstpe, ClassKind, Hashers}
import ir.Trees.OptimizerHints
import util.ScopedVar
import util.VarBox
import ScopedVar.withScopedVars
/** Generate JavaScript code and output it to disk
*
* @author Sébastien Doeraene
*/
abstract class GenJSCode extends plugins.PluginComponent
with TypeKinds
with JSEncoding
with GenJSExports
with GenJSFiles
with PluginComponent210Compat {
val jsAddons: JSGlobalAddons {
val global: GenJSCode.this.global.type
}
val scalaJSOpts: ScalaJSOptions
import global._
import jsAddons._
import rootMirror._
import definitions._
import jsDefinitions._
import jsInterop.{jsNameOf, compat068FullJSNameOf, jsNativeLoadSpecOf}
import JSTreeExtractors._
import treeInfo.hasSynthCaseSymbol
import platform.isMaybeBoxed
val phaseName: String = "jscode"
override val description: String = "generate JavaScript code from ASTs"
/** testing: this will be called when ASTs are generated */
def generatedJSAST(clDefs: List[js.Tree]): Unit
/** Implicit conversion from nsc Position to ir.Position. */
implicit def pos2irPos(pos: Position): ir.Position = {
if (pos == NoPosition) ir.Position.NoPosition
else {
val source = pos2irPosCache.toIRSource(pos.source)
// nsc positions are 1-based but IR positions are 0-based
ir.Position(source, pos.line-1, pos.column-1)
}
}
private[this] object pos2irPosCache { // scalastyle:ignore
import scala.reflect.internal.util._
private[this] var lastNscSource: SourceFile = null
private[this] var lastIRSource: ir.Position.SourceFile = null
def toIRSource(nscSource: SourceFile): ir.Position.SourceFile = {
if (nscSource != lastNscSource) {
lastIRSource = convert(nscSource)
lastNscSource = nscSource
}
lastIRSource
}
private[this] def convert(nscSource: SourceFile): ir.Position.SourceFile = {
nscSource.file.file match {
case null =>
new java.net.URI(
"virtualfile", // Pseudo-Scheme
nscSource.file.path, // Scheme specific part
null // Fragment
)
case file =>
val srcURI = file.toURI
def matches(pat: java.net.URI) = pat.relativize(srcURI) != srcURI
scalaJSOpts.sourceURIMaps.collectFirst {
case ScalaJSOptions.URIMap(from, to) if matches(from) =>
val relURI = from.relativize(srcURI)
to.fold(relURI)(_.resolve(relURI))
} getOrElse srcURI
}
}
def clear(): Unit = {
lastNscSource = null
lastIRSource = null
}
}
/** Materialize implicitly an ir.Position from an implicit nsc Position. */
implicit def implicitPos2irPos(implicit pos: Position): ir.Position = pos
override def newPhase(p: Phase): StdPhase = new JSCodePhase(p)
private object jsnme { // scalastyle:ignore
val anyHash = newTermName("anyHash")
val arg_outer = newTermName("arg$outer")
val newString = newTermName("newString")
}
class JSCodePhase(prev: Phase) extends StdPhase(prev) with JSExportsPhase {
override def name: String = phaseName
override def description: String = GenJSCode.this.description
override def erasedTypes: Boolean = true
// Scoped state ------------------------------------------------------------
// Per class body
val currentClassSym = new ScopedVar[Symbol]
private val unexpectedMutatedFields = new ScopedVar[mutable.Set[Symbol]]
private val generatedSAMWrapperCount = new ScopedVar[VarBox[Int]]
private def currentClassType = encodeClassType(currentClassSym)
// Per method body
private val currentMethodSym = new ScopedVar[Symbol]
private val thisLocalVarIdent = new ScopedVar[Option[js.Ident]]
private val fakeTailJumpParamRepl = new ScopedVar[(Symbol, Symbol)]
private val enclosingLabelDefParams = new ScopedVar[Map[Symbol, List[Symbol]]]
private val isModuleInitialized = new ScopedVar[VarBox[Boolean]]
private val countsOfReturnsToMatchEnd = new ScopedVar[mutable.Map[Symbol, Int]]
private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]]
// For some method bodies
private val mutableLocalVars = new ScopedVar[mutable.Set[Symbol]]
private val mutatedLocalVars = new ScopedVar[mutable.Set[Symbol]]
// For anonymous methods
// These have a default, since we always read them.
private val tryingToGenMethodAsJSFunction = new ScopedVar[Boolean](false)
private val paramAccessorLocals = new ScopedVar(Map.empty[Symbol, js.ParamDef])
private class CancelGenMethodAsJSFunction(message: String)
extends Throwable(message) with scala.util.control.ControlThrowable
// Rewriting of anonymous function classes ---------------------------------
/** Start nested generation of a class.
*
* Fully resets the scoped state (including local name scope).
* Allows to generate an anonymous class as needed.
*/
private def nestedGenerateClass[T](clsSym: Symbol)(body: => T): T = {
withScopedVars(
currentClassSym := clsSym,
unexpectedMutatedFields := mutable.Set.empty,
generatedSAMWrapperCount := null,
currentMethodSym := null,
thisLocalVarIdent := null,
fakeTailJumpParamRepl := null,
enclosingLabelDefParams := null,
isModuleInitialized := null,
countsOfReturnsToMatchEnd := null,
undefinedDefaultParams := null,
mutableLocalVars := null,
mutatedLocalVars := null,
tryingToGenMethodAsJSFunction := false,
paramAccessorLocals := Map.empty
)(withNewLocalNameScope(body))
}
// Global class generation state -------------------------------------------
/** Map a class from this compilation unit to its companion module class.
* This should be accessible through `sym.linkedClassOfClass`, but is
* broken for nested classes. The reverse link is not broken, though,
* which allows us to build this map in [[apply]] for the whole
* compilation unit before processing it.
*/
private var companionModuleClasses: Map[Symbol, Symbol] = Map.empty
private val lazilyGeneratedAnonClasses = mutable.Map.empty[Symbol, ClassDef]
private val generatedClasses =
ListBuffer.empty[(Symbol, Option[String], js.ClassDef)]
private def consumeLazilyGeneratedAnonClass(sym: Symbol): ClassDef = {
/* If we are trying to generate an method as JSFunction, we cannot
* actually consume the symbol, since we might fail trying and retry.
* We will then see the same tree again and not find the symbol anymore.
*
* If we are sure this is the only generation, we remove the symbol to
* make sure we don't generate the same class twice.
*/
val optDef = {
if (tryingToGenMethodAsJSFunction)
lazilyGeneratedAnonClasses.get(sym)
else
lazilyGeneratedAnonClasses.remove(sym)
}
optDef.getOrElse {
sys.error("Couldn't find tree for lazily generated anonymous class " +
s"${sym.fullName} at ${sym.pos}")
}
}
// Top-level apply ---------------------------------------------------------
override def run(): Unit = {
scalaPrimitives.init()
initializeCoreBTypesCompat()
jsPrimitives.init()
super.run()
}
/** Generates the Scala.js IR for a compilation unit
* This method iterates over all the class and interface definitions
* found in the compilation unit and emits their IR (.sjsir).
*
* Some classes are never actually emitted:
* - Classes representing primitive types
* - The scala.Array class
* - Implementation classes for raw JS traits
*
* Some classes representing anonymous functions are not actually emitted.
* Instead, a temporary representation of their `apply` method is built
* and recorded, so that it can be inlined as a JavaScript anonymous
* function in the method that instantiates it.
*
* Other ClassDefs are emitted according to their nature:
* * Scala.js-defined JS class -> `genScalaJSDefinedJSClass()`
* * Other raw JS type (<: js.Any) -> `genRawJSClassData()`
* * Interface -> `genInterface()`
* * Implementation class -> `genImplClass()`
* * Normal class -> `genClass()`
*/
override def apply(cunit: CompilationUnit): Unit = {
try {
def collectClassDefs(tree: Tree): List[ClassDef] = {
tree match {
case EmptyTree => Nil
case PackageDef(_, stats) => stats flatMap collectClassDefs
case cd: ClassDef => cd :: Nil
}
}
val allClassDefs = collectClassDefs(cunit.body)
// Build up companionModuleClasses
companionModuleClasses = (for {
classDef <- allClassDefs
sym = classDef.symbol
if sym.isModuleClass
} yield {
patchedLinkedClassOfClass(sym) -> sym
}).toMap
/* There are three types of anonymous classes we want to generate
* only once we need them so we can inline them at construction site:
*
* - lambdas for js.FunctionN and js.ThisFunctionN (SAMs). (We may not
* generate actual Scala classes for these).
* - anonymous Scala.js defined JS classes. These classes may not have
* their own prototype. Therefore, their constructor *must* be
* inlined.
* - lambdas for scala.FunctionN. This is only an optimization and may
* fail. In the case of failure, we fall back to generating a
* fully-fledged Scala class.
*
* Since for all these, we don't know how they inter-depend, we just
* store them in a map at this point.
*/
val (lazyAnons, fullClassDefs) = allClassDefs.partition { cd =>
val sym = cd.symbol
isRawJSFunctionDef(sym) || sym.isAnonymousFunction ||
isScalaJSDefinedAnonJSClass(sym)
}
lazilyGeneratedAnonClasses ++= lazyAnons.map(cd => cd.symbol -> cd)
/* Finally, we emit true code for the remaining class defs. */
for (cd <- fullClassDefs) {
val sym = cd.symbol
implicit val pos = sym.pos
/* Do not actually emit code for primitive types nor scala.Array. */
val isPrimitive =
isPrimitiveValueClass(sym) || (sym == ArrayClass)
if (!isPrimitive && !isRawJSImplClass(sym)) {
withScopedVars(
currentClassSym := sym,
unexpectedMutatedFields := mutable.Set.empty,
generatedSAMWrapperCount := new VarBox(0)
) {
val tree = if (isRawJSType(sym.tpe)) {
assert(!isRawJSFunctionDef(sym),
s"Raw JS function def should have been recorded: $cd")
if (!sym.isTraitOrInterface && isScalaJSDefinedJSClass(sym))
genScalaJSDefinedJSClass(cd)
else
genRawJSClassData(cd)
} else if (sym.isTraitOrInterface) {
genInterface(cd)
} else if (sym.isImplClass) {
genImplClass(cd)
} else {
genClass(cd)
}
generatedClasses += ((sym, None, tree))
}
}
}
val clDefs = generatedClasses.map(_._3).toList
generatedJSAST(clDefs)
for ((sym, suffix, tree) <- generatedClasses) {
genIRFile(cunit, sym, suffix, tree)
}
} finally {
lazilyGeneratedAnonClasses.clear()
generatedClasses.clear()
companionModuleClasses = Map.empty
pos2irPosCache.clear()
}
}
// Generate a class --------------------------------------------------------
/** Gen the IR ClassDef for a class definition (maybe a module class).
*/
def genClass(cd: ClassDef): js.ClassDef = {
val ClassDef(mods, name, _, impl) = cd
val sym = cd.symbol
implicit val pos = sym.pos
assert(!sym.isTraitOrInterface && !sym.isImplClass,
"genClass() must be called only for normal classes: "+sym)
assert(sym.superClass != NoSymbol, sym)
if (hasDefaultCtorArgsAndRawJSModule(sym)) {
reporter.error(pos,
"Implementation restriction: constructors of " +
"Scala classes cannot have default parameters " +
"if their companion module is JS native.")
}
val classIdent = encodeClassFullNameIdent(sym)
val isHijacked = isHijackedBoxedClass(sym)
// Optimizer hints
def isStdLibClassWithAdHocInlineAnnot(sym: Symbol): Boolean = {
val fullName = sym.fullName
(fullName.startsWith("scala.Tuple") && !fullName.endsWith("$")) ||
(fullName.startsWith("scala.collection.mutable.ArrayOps$of"))
}
val shouldMarkInline = (
sym.hasAnnotation(InlineAnnotationClass) ||
(sym.isAnonymousFunction && !sym.isSubClass(PartialFunctionClass)) ||
isStdLibClassWithAdHocInlineAnnot(sym))
val optimizerHints =
OptimizerHints.empty.
withInline(shouldMarkInline).
withNoinline(sym.hasAnnotation(NoinlineAnnotationClass))
// Generate members (constructor + methods)
val generatedMethods = new ListBuffer[js.MethodDef]
def gen(tree: Tree): Unit = {
tree match {
case EmptyTree => ()
case Template(_, _, body) => body foreach gen
case ValDef(mods, name, tpt, rhs) =>
() // fields are added via genClassFields()
case dd: DefDef =>
if (isNamedExporterDef(dd))
generatedMethods ++= genNamedExporterDef(dd)
else
generatedMethods ++= genMethod(dd)
case _ => abort("Illegal tree in gen of genClass(): " + tree)
}
}
gen(impl)
// Generate fields if necessary (and add to methods + ctors)
val generatedMembers =
if (!isHijacked) genClassFields(cd) ++ generatedMethods.toList
else generatedMethods.toList // No fields needed
// Generate the exported members, constructors and accessors
val exports = {
// Generate the exported members
val memberExports = genMemberExports(sym)
// Generate exported constructors or accessors
val exportedConstructorsOrAccessors =
if (isStaticModule(sym)) genModuleAccessorExports(sym)
else genConstructorExports(sym)
val topLevelExports = genTopLevelExports(sym)
memberExports ++ exportedConstructorsOrAccessors ++ topLevelExports
}
// Static initializer
val optStaticInitializer = {
// Initialization of reflection data, if required
val reflectInit = {
val enableReflectiveInstantiation = {
(sym :: sym.ancestors).exists { ancestor =>
ancestor.hasAnnotation(EnableReflectiveInstantiationAnnotation)
}
}
if (enableReflectiveInstantiation)
genRegisterReflectiveInstantiation(sym)
else
None
}
// Initialization of the module because of field exports
val needsStaticModuleInit =
exports.exists(_.isInstanceOf[js.TopLevelFieldExportDef])
val staticModuleInit =
if (!needsStaticModuleInit) None
else Some(genLoadModule(sym))
val staticInitializerStats =
reflectInit.toList ::: staticModuleInit.toList
if (staticInitializerStats.nonEmpty)
Some(genStaticInitializerWithStats(js.Block(staticInitializerStats)))
else
None
}
// Hashed definitions of the class
val hashedDefs =
Hashers.hashDefs(generatedMembers ++ exports ++ optStaticInitializer)
// The complete class definition
val kind =
if (isStaticModule(sym)) ClassKind.ModuleClass
else if (isHijacked) ClassKind.HijackedClass
else ClassKind.Class
val classDefinition = js.ClassDef(
classIdent,
kind,
Some(encodeClassFullNameIdent(sym.superClass)),
genClassInterfaces(sym),
None,
hashedDefs)(
optimizerHints)
classDefinition
}
/** Gen the IR ClassDef for a Scala.js-defined JS class. */
def genScalaJSDefinedJSClass(cd: ClassDef): js.ClassDef = {
val sym = cd.symbol
implicit val pos = sym.pos
assert(isScalaJSDefinedJSClass(sym),
"genScalaJSDefinedJSClass() must be called only for " +
s"Scala.js-defined JS classes: $sym")
assert(sym.superClass != NoSymbol, sym)
val classIdent = encodeClassFullNameIdent(sym)
// Generate members (constructor + methods)
val constructorTrees = new ListBuffer[DefDef]
val generatedMethods = new ListBuffer[js.MethodDef]
val dispatchMethodNames = new ListBuffer[String]
def gen(tree: Tree): Unit = {
tree match {
case EmptyTree => ()
case Template(_, _, body) => body foreach gen
case ValDef(mods, name, tpt, rhs) =>
() // fields are added via genClassFields()
case dd: DefDef =>
val sym = dd.symbol
val exposed = isExposed(sym)
if (sym.isClassConstructor) {
constructorTrees += dd
} else if (exposed && sym.isAccessor) {
/* Exposed accessors must not be emitted, since the field they
* access is enough.
*/
} else if (sym.hasAnnotation(JSOptionalAnnotation)) {
// Optional methods must not be emitted
} else {
generatedMethods ++= genMethod(dd)
// Collect the names of the dispatchers we have to create
if (exposed && !sym.isDeferred) {
/* We add symbols that we have to expose here. This way we also
* get inherited stuff that is implemented in this class.
*/
dispatchMethodNames += jsNameOf(sym)
}
}
case _ => abort("Illegal tree in gen of genClass(): " + tree)
}
}
gen(cd.impl)
// Static members (exported from the companion object)
val staticMembers = {
/* This should be `sym.linkedClassOfClass`, but it does not work for
* classes and objects nested inside objects.
*/
companionModuleClasses.get(sym).fold[List[js.Tree]] {
Nil
} { companionModuleClass =>
val exports = withScopedVars(currentClassSym := companionModuleClass) {
genStaticExports(companionModuleClass)
}
if (exports.exists(_.isInstanceOf[js.FieldDef])) {
val staticInitializer =
genStaticInitializerWithStats(genLoadModule(companionModuleClass))
exports :+ staticInitializer
} else {
exports
}
}
}
// Generate class-level exporters
val classExports =
if (isStaticModule(sym)) genModuleAccessorExports(sym)
else genJSClassExports(sym)
// Generate fields (and add to methods + ctors)
val generatedMembers = {
genClassFields(cd) :::
genJSClassConstructor(sym, constructorTrees.toList) ::
genJSClassDispatchers(sym, dispatchMethodNames.result().distinct) :::
generatedMethods.toList :::
staticMembers :::
classExports
}
// Hashed definitions of the class
val hashedDefs =
Hashers.hashDefs(generatedMembers)
// The complete class definition
val kind =
if (isStaticModule(sym)) ClassKind.JSModuleClass
else ClassKind.JSClass
val classDefinition = js.ClassDef(
classIdent,
kind,
Some(encodeClassFullNameIdent(sym.superClass)),
genClassInterfaces(sym),
None,
hashedDefs)(
OptimizerHints.empty)
classDefinition
}
/** Generate an instance of an anonymous Scala.js defined class inline
*
* @param sym Class to generate the instance of
* @param args Arguments to the constructor
* @param pos Position of the original New tree
*/
def genAnonSJSDefinedNew(sym: Symbol, args: List[js.Tree],
pos: Position): js.Tree = {
assert(isScalaJSDefinedAnonJSClass(sym),
"Generating AnonSJSDefinedNew of non anonymous SJSDefined JS class")
// Find the ClassDef for this anonymous class
val classDef = consumeLazilyGeneratedAnonClass(sym)
// Generate a normal SJSDefinedJSClass
val origJsClass =
nestedGenerateClass(sym)(genScalaJSDefinedJSClass(classDef))
// Partition class members.
val staticMembers = ListBuffer.empty[js.Tree]
val classMembers = ListBuffer.empty[js.Tree]
var constructor: Option[js.MethodDef] = None
origJsClass.defs.foreach {
case fdef: js.FieldDef =>
classMembers += fdef
case mdef: js.MethodDef =>
mdef.name match {
case _: js.Ident =>
assert(mdef.static, "Non-static method in SJS defined JS class")
staticMembers += mdef
case js.StringLiteral(name) =>
assert(!mdef.static, "Exported static method")
if (name == "constructor") {
assert(constructor.isEmpty, "two ctors in class")
constructor = Some(mdef)
} else {
classMembers += mdef
}
}
case property: js.PropertyDef =>
classMembers += property
case tree =>
sys.error("Unexpected tree: " + tree)
}
// Make new class def with static members only
val newClassDef = {
implicit val pos = origJsClass.pos
val parent = js.Ident(ir.Definitions.ObjectClass)
js.ClassDef(origJsClass.name, ClassKind.AbstractJSType,
Some(parent), interfaces = Nil, jsNativeLoadSpec = None,
staticMembers.toList)(origJsClass.optimizerHints)
}
generatedClasses += ((sym, None, newClassDef))
// Construct inline class definition
val js.MethodDef(_, _, ctorParams, _, Some(ctorBody)) =
constructor.getOrElse(throw new AssertionError("No ctor found"))
val selfName = freshLocalIdent("this")(pos)
def selfRef(implicit pos: ir.Position) =
js.VarRef(selfName)(jstpe.AnyType)
def lambda(params: List[js.ParamDef], body: js.Tree)(
implicit pos: ir.Position) = {
js.Closure(captureParams = Nil, params, body, captureValues = Nil)
}
val memberDefinitions = classMembers.toList.map {
case fdef: js.FieldDef =>
implicit val pos = fdef.pos
val select = fdef.name match {
case lit: js.StringLiteral => js.JSBracketSelect(selfRef, lit)
case ident: js.Ident => js.JSDotSelect(selfRef, ident)
}
js.Assign(select, jstpe.zeroOf(fdef.tpe))
case mdef: js.MethodDef =>
implicit val pos = mdef.pos
val name = mdef.name.asInstanceOf[js.StringLiteral]
val impl = lambda(mdef.args, mdef.body.getOrElse(
throw new AssertionError("Got anon SJS class with abstract method")))
js.Assign(js.JSBracketSelect(selfRef, name), impl)
case pdef: js.PropertyDef =>
implicit val pos = pdef.pos
val name = pdef.name.asInstanceOf[js.StringLiteral]
val jsObject =
js.JSBracketSelect(genLoadGlobal(), js.StringLiteral("Object"))
def field(name: String, value: js.Tree) =
List(js.StringLiteral(name) -> value)
val optGetter = pdef.getterBody map { body =>
js.StringLiteral("get") -> lambda(params = Nil, body)
}
val optSetter = pdef.setterArgAndBody map { case (arg, body) =>
js.StringLiteral("set") -> lambda(params = arg :: Nil, body)
}
val descriptor = js.JSObjectConstr(
optGetter.toList ++
optSetter ++
List(js.StringLiteral("configurable") -> js.BooleanLiteral(true))
)
js.JSBracketMethodApply(jsObject, js.StringLiteral("defineProperty"),
List(selfRef, name, descriptor))
case tree =>
sys.error("Unexpected tree: " + tree)
}
// Transform the constructor body.
val inlinedCtorStats = new ir.Transformers.Transformer {
override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match {
// The super constructor call. Transform this into a simple new call.
case js.JSSuperConstructorCall(args) =>
implicit val pos = tree.pos
val newTree = {
val ident =
origJsClass.superClass.getOrElse(sys.error("No superclass"))
if (args.isEmpty && ident.name == "sjs_js_Object") {
js.JSObjectConstr(Nil)
} else {
val superTpe = jstpe.ClassType(ident.name)
js.JSNew(js.LoadJSConstructor(superTpe), args)
}
}
js.Block(
js.VarDef(selfName, jstpe.AnyType, mutable = false, newTree) ::
memberDefinitions)(NoPosition)
case js.This() => selfRef(tree.pos)
// Don't traverse closure boundaries
case closure: js.Closure =>
val newCaptureValues = closure.captureValues.map(transformExpr)
closure.copy(captureValues = newCaptureValues)(closure.pos)
case tree =>
super.transform(tree, isStat)
}
}.transform(ctorBody, isStat = true)
val invocation = {
implicit val invocationPosition = pos
val closure =
js.Closure(Nil, ctorParams, js.Block(inlinedCtorStats, selfRef), Nil)
js.JSFunctionApply(closure, args)
}
invocation
}
// Generate the class data of a raw JS class -------------------------------
/** Gen the IR ClassDef for a raw JS class or trait.
*/
def genRawJSClassData(cd: ClassDef): js.ClassDef = {
val sym = cd.symbol
implicit val pos = sym.pos
val classIdent = encodeClassFullNameIdent(sym)
val kind = {
if (sym.isTraitOrInterface) ClassKind.AbstractJSType
else if (sym.isModuleClass) ClassKind.NativeJSModuleClass
else ClassKind.NativeJSClass
}
val superClass =
if (sym.isTraitOrInterface) None
else Some(encodeClassFullNameIdent(sym.superClass))
val jsNativeLoadSpec =
if (sym.isTraitOrInterface) None
else Some(jsNativeLoadSpecOf(sym))
js.ClassDef(classIdent, kind, superClass, genClassInterfaces(sym),
jsNativeLoadSpec, Nil)(
OptimizerHints.empty)
}
// Generate an interface ---------------------------------------------------
/** Gen the IR ClassDef for an interface definition.
*/
def genInterface(cd: ClassDef): js.ClassDef = {
val sym = cd.symbol
implicit val pos = sym.pos
val classIdent = encodeClassFullNameIdent(sym)
// fill in class info builder
def gen(tree: Tree): List[js.MethodDef] = {
tree match {
case EmptyTree => Nil
case Template(_, _, body) => body.flatMap(gen)
case dd: DefDef =>
if (isNamedExporterDef(dd))
genNamedExporterDef(dd).toList
else
genMethod(dd).toList
case _ =>
abort("Illegal tree in gen of genInterface(): " + tree)
}
}
val generatedMethods = gen(cd.impl)
val interfaces = genClassInterfaces(sym)
// Hashed definitions of the interface
val hashedDefs =
Hashers.hashDefs(generatedMethods)
js.ClassDef(classIdent, ClassKind.Interface, None, interfaces, None,
hashedDefs)(OptimizerHints.empty)
}
// Generate an implementation class of a trait -----------------------------
/** Gen the IR ClassDef for an implementation class (of a trait).
*/
def genImplClass(cd: ClassDef): js.ClassDef = {
val ClassDef(mods, name, _, impl) = cd
val sym = cd.symbol
implicit val pos = sym.pos
def gen(tree: Tree): List[js.MethodDef] = {
tree match {
case EmptyTree => Nil
case Template(_, _, body) => body.flatMap(gen)
case dd: DefDef =>
assert(!dd.symbol.isDeferred,
s"Found an abstract method in an impl class at $pos: ${dd.symbol.fullName}")
val m = genMethod(dd)
m.toList
case _ => abort("Illegal tree in gen of genImplClass(): " + tree)
}
}
val generatedMethods = gen(impl)
val classIdent = encodeClassFullNameIdent(sym)
val objectClassIdent = encodeClassFullNameIdent(ObjectClass)
// Hashed definitions of the impl class
val hashedDefs =
Hashers.hashDefs(generatedMethods)
js.ClassDef(classIdent, ClassKind.Class,
Some(objectClassIdent), Nil, None,
hashedDefs)(OptimizerHints.empty)
}
private def genClassInterfaces(sym: Symbol)(
implicit pos: Position): List[js.Ident] = {
for {
parent <- sym.info.parents
typeSym = parent.typeSymbol
_ = assert(typeSym != NoSymbol, "parent needs symbol")
if typeSym.isTraitOrInterface
} yield {
encodeClassFullNameIdent(typeSym)
}
}
// Generate the fields of a class ------------------------------------------
/** Gen definitions for the fields of a class.
* The fields are initialized with the zero of their types.
*/
def genClassFields(cd: ClassDef): List[js.FieldDef] = {
val classSym = cd.symbol
assert(currentClassSym.get == classSym,
"genClassFields called with a ClassDef other than the current one")
def isStaticBecauseOfTopLevelExport(f: Symbol): Boolean =
jsInterop.registeredExportsOf(f).head.destination == ExportDestination.TopLevel
// Non-method term members are fields, except for module members.
(for {
f <- classSym.info.decls
if !f.isMethod && f.isTerm && !f.isModule
if !f.hasAnnotation(JSOptionalAnnotation)
static = jsInterop.isFieldStatic(f)
if !static || isStaticBecauseOfTopLevelExport(f)
} yield {
implicit val pos = f.pos
val mutable = {
static || // static fields must always be mutable
suspectFieldMutable(f) || unexpectedMutatedFields.contains(f)
}
val name =
if (isExposed(f)) js.StringLiteral(jsNameOf(f))
else encodeFieldSym(f)
val irTpe = {
if (isScalaJSDefinedJSClass(classSym)) genExposedFieldIRType(f)
else if (static) jstpe.AnyType
else toIRType(f.tpe)
}
js.FieldDef(static, name, irTpe, mutable)
}).toList
}
def genExposedFieldIRType(f: Symbol): jstpe.Type = {
val tpeEnteringPosterasure =
enteringPhase(currentRun.posterasurePhase)(f.tpe)
tpeEnteringPosterasure match {
case tpe: ErasedValueType =>
/* Here, we must store the field as the boxed representation of
* the value class. The default value of that field, as
* initialized at the time the instance is created, will
* therefore be null. This will not match the behavior we would
* get in a Scala class. To match the behavior, we would need to
* initialized to an instance of the boxed representation, with
* an underlying value set to the zero of its type. However we
* cannot implement that, so we live with the discrepancy.
* Anyway, scalac also has problems with uninitialized value
* class values, if they come from a generic context.
*/
jstpe.ClassType(encodeClassFullName(tpe.valueClazz))
case _ if f.tpe.typeSymbol == CharClass =>
/* Will be initialized to null, which will unbox to '\0' when
* read.
*/
jstpe.ClassType(ir.Definitions.BoxedCharacterClass)
case _ =>
/* Other types are not boxed, so we can initialize them to
* their true zero.
*/
toIRType(f.tpe)
}
}
// Static initializers -----------------------------------------------------
private def genStaticInitializerWithStats(stats: js.Tree)(
implicit pos: Position): js.MethodDef = {
js.MethodDef(
static = true,
js.Ident(ir.Definitions.StaticInitializerName),
Nil,
jstpe.NoType,
Some(stats))(
OptimizerHints.empty, None)
}
private def genRegisterReflectiveInstantiation(sym: Symbol)(
implicit pos: Position): Option[js.Tree] = {
if (sym.isModuleClass)
genRegisterReflectiveInstantiationForModuleClass(sym)
else
genRegisterReflectiveInstantiationForNormalClass(sym)
}
private def genRegisterReflectiveInstantiationForModuleClass(sym: Symbol)(
implicit pos: Position): Option[js.Tree] = {
val fqcnArg = js.StringLiteral(sym.fullName + "$")
val runtimeClassArg = js.ClassOf(toReferenceType(sym.info))
val loadModuleFunArg = js.Closure(Nil, Nil, genLoadModule(sym), Nil)
val stat = genApplyMethod(
genLoadModule(ReflectModule),
Reflect_registerLoadableModuleClass,
List(fqcnArg, runtimeClassArg, loadModuleFunArg))
Some(stat)
}
private def genRegisterReflectiveInstantiationForNormalClass(sym: Symbol)(
implicit pos: Position): Option[js.Tree] = {
val ctors =
if (sym.isAbstractClass) Nil
else sym.info.member(nme.CONSTRUCTOR).alternatives.filter(_.isPublic)
if (ctors.isEmpty) {
None
} else {
val constructorsInfos = for {
ctor <- ctors
} yield {
withNewLocalNameScope {
val (parameterTypes, formalParams, actualParams) = (for {
param <- ctor.tpe.params
} yield {
/* Note that we do *not* use `param.tpe` entering posterasure
* (neither to compute `paramType` nor to give to `fromAny`).
* Logic would tell us that we should do so, but we intentionally
* do not to preserve the behavior on the JVM regarding value
* classes. If a constructor takes a value class as parameter, as
* in:
*
* class ValueClass(val underlying: Int) extends AnyVal
* class Foo(val vc: ValueClass)
*
* then, from a reflection point of view, on the JVM, the
* constructor of `Foo` takes an `Int`, not a `ValueClas`. It
* must therefore be identified as the constructor whose
* parameter types is `List(classOf[Int])`, and when invoked
* reflectively, it must be given an `Int` (or `Integer`).
*/
val paramType = js.ClassOf(toReferenceType(param.tpe))
val paramDef = js.ParamDef(encodeLocalSym(param), jstpe.AnyType,
mutable = false, rest = false)
val actualParam = fromAny(paramDef.ref, param.tpe)
(paramType, paramDef, actualParam)
}).unzip3
val paramTypesArray = js.JSArrayConstr(parameterTypes)
val newInstanceFun = js.Closure(Nil, formalParams, {
genNew(sym, ctor, actualParams)
}, Nil)
js.JSArrayConstr(List(paramTypesArray, newInstanceFun))
}
}
val fqcnArg = js.StringLiteral(sym.fullName)
val runtimeClassArg = js.ClassOf(toReferenceType(sym.info))
val ctorsInfosArg = js.JSArrayConstr(constructorsInfos)
val stat = genApplyMethod(
genLoadModule(ReflectModule),
Reflect_registerInstantiatableClass,
List(fqcnArg, runtimeClassArg, ctorsInfosArg))
Some(stat)
}
}
// Constructor of a Scala.js-defined JS class ------------------------------
def genJSClassConstructor(classSym: Symbol,
constructorTrees: List[DefDef]): js.Tree = {
implicit val pos = classSym.pos
if (hasDefaultCtorArgsAndRawJSModule(classSym)) {
reporter.error(pos,
"Implementation restriction: constructors of " +
"Scala.js-defined JS classes cannot have default parameters " +
"if their companion module is JS native.")
js.Skip()
} else {
withNewLocalNameScope {
val ctors: List[js.MethodDef] = constructorTrees.flatMap { tree =>
genMethodWithCurrentLocalNameScope(tree)
}
val dispatch =
genJSConstructorExport(constructorTrees.map(_.symbol))
val js.MethodDef(_, dispatchName, dispatchArgs, dispatchResultType,
Some(dispatchResolution)) = dispatch
val jsConstructorBuilder = mkJSConstructorBuilder(ctors)
val overloadIdent = freshLocalIdent("overload")
// Section containing the overload resolution and casts of parameters
val overloadSelection = mkOverloadSelection(jsConstructorBuilder,
overloadIdent, dispatchResolution)
/* Section containing all the code executed before the call to `this`
* for every secondary constructor.
*/
val prePrimaryCtorBody =
jsConstructorBuilder.mkPrePrimaryCtorBody(overloadIdent)
val primaryCtorBody = jsConstructorBuilder.primaryCtorBody
/* Section containing all the code executed after the call to this for
* every secondary constructor.
*/
val postPrimaryCtorBody =
jsConstructorBuilder.mkPostPrimaryCtorBody(overloadIdent)
val newBody = js.Block(overloadSelection ::: prePrimaryCtorBody ::
primaryCtorBody :: postPrimaryCtorBody :: Nil)
js.MethodDef(static = false, dispatchName, dispatchArgs, jstpe.NoType,
Some(newBody))(dispatch.optimizerHints, None)
}
}
}
private class ConstructorTree(val overrideNum: Int, val method: js.MethodDef,
val subConstructors: List[ConstructorTree]) {
lazy val overrideNumBounds: (Int, Int) =
if (subConstructors.isEmpty) (overrideNum, overrideNum)
else (subConstructors.head.overrideNumBounds._1, overrideNum)
def get(methodName: String): Option[ConstructorTree] = {
if (methodName == this.method.name.name) {
Some(this)
} else {
subConstructors.iterator.map(_.get(methodName)).collectFirst {
case Some(node) => node
}
}
}
def getParamRefs(implicit pos: Position): List[js.VarRef] =
method.args.map(_.ref)
def getAllParamDefsAsVars(implicit pos: Position): List[js.VarDef] = {
val localDefs = method.args.map { pDef =>
js.VarDef(pDef.name, pDef.ptpe, mutable = true, jstpe.zeroOf(pDef.ptpe))
}
localDefs ++ subConstructors.flatMap(_.getAllParamDefsAsVars)
}
}
private class JSConstructorBuilder(root: ConstructorTree) {
def primaryCtorBody: js.Tree = root.method.body.getOrElse(
throw new AssertionError("Found abstract constructor"))
def hasSubConstructors: Boolean = root.subConstructors.nonEmpty
def getOverrideNum(methodName: String): Int =
root.get(methodName).fold(-1)(_.overrideNum)
def getParamRefsFor(methodName: String)(implicit pos: Position): List[js.VarRef] =
root.get(methodName).fold(List.empty[js.VarRef])(_.getParamRefs)
def getAllParamDefsAsVars(implicit pos: Position): List[js.VarDef] =
root.getAllParamDefsAsVars
def mkPrePrimaryCtorBody(overrideNumIdent: js.Ident)(
implicit pos: Position): js.Tree = {
val overrideNumRef = js.VarRef(overrideNumIdent)(jstpe.IntType)
mkSubPreCalls(root, overrideNumRef)
}
def mkPostPrimaryCtorBody(overrideNumIdent: js.Ident)(
implicit pos: Position): js.Tree = {
val overrideNumRef = js.VarRef(overrideNumIdent)(jstpe.IntType)
js.Block(mkSubPostCalls(root, overrideNumRef))
}
private def mkSubPreCalls(constructorTree: ConstructorTree,
overrideNumRef: js.VarRef)(implicit pos: Position): js.Tree = {
val overrideNumss = constructorTree.subConstructors.map(_.overrideNumBounds)
val paramRefs = constructorTree.getParamRefs
val bodies = constructorTree.subConstructors.map { constructorTree =>
mkPrePrimaryCtorBodyOnSndCtr(constructorTree, overrideNumRef, paramRefs)
}
overrideNumss.zip(bodies).foldRight[js.Tree](js.Skip()) {
case ((numBounds, body), acc) =>
val cond = mkOverrideNumsCond(overrideNumRef, numBounds)
js.If(cond, body, acc)(jstpe.BooleanType)
}
}
private def mkPrePrimaryCtorBodyOnSndCtr(constructorTree: ConstructorTree,
overrideNumRef: js.VarRef, outputParams: List[js.VarRef])(
implicit pos: Position): js.Tree = {
val subCalls =
mkSubPreCalls(constructorTree, overrideNumRef)
val preSuperCall = {
constructorTree.method.body.get match {
case js.Block(stats) =>
val beforeSuperCall = stats.takeWhile {
case js.ApplyStatic(_, mtd, _) => !ir.Definitions.isConstructorName(mtd.name)
case _ => true
}
val superCallParams = stats.collectFirst {
case js.ApplyStatic(_, mtd, js.This() :: args)
if ir.Definitions.isConstructorName(mtd.name) =>
zipMap(outputParams, args)(js.Assign(_, _))
}.getOrElse(Nil)
beforeSuperCall ::: superCallParams
case js.ApplyStatic(_, mtd, js.This() :: args)
if ir.Definitions.isConstructorName(mtd.name) =>
zipMap(outputParams, args)(js.Assign(_, _))
case _ => Nil
}
}
js.Block(subCalls :: preSuperCall)
}
private def mkSubPostCalls(constructorTree: ConstructorTree,
overrideNumRef: js.VarRef)(implicit pos: Position): js.Tree = {
val overrideNumss = constructorTree.subConstructors.map(_.overrideNumBounds)
val bodies = constructorTree.subConstructors.map { ct =>
mkPostPrimaryCtorBodyOnSndCtr(ct, overrideNumRef)
}
overrideNumss.zip(bodies).foldRight[js.Tree](js.Skip()) {
case ((numBounds, js.Skip()), acc) => acc
case ((numBounds, body), acc) =>
val cond = mkOverrideNumsCond(overrideNumRef, numBounds)
js.If(cond, body, acc)(jstpe.BooleanType)
}
}
private def mkPostPrimaryCtorBodyOnSndCtr(constructorTree: ConstructorTree,
overrideNumRef: js.VarRef)(implicit pos: Position): js.Tree = {
val postSuperCall = {
constructorTree.method.body.get match {
case js.Block(stats) =>
stats.dropWhile {
case js.ApplyStatic(_, mtd, _) => !ir.Definitions.isConstructorName(mtd.name)
case _ => true
}.tail
case _ => Nil
}
}
js.Block(postSuperCall :+ mkSubPostCalls(constructorTree, overrideNumRef))
}
private def mkOverrideNumsCond(numRef: js.VarRef,
numBounds: (Int, Int))(implicit pos: Position) = numBounds match {
case (lo, hi) if lo == hi =>
js.BinaryOp(js.BinaryOp.===, js.IntLiteral(lo), numRef)
case (lo, hi) if lo == hi - 1 =>
val lhs = js.BinaryOp(js.BinaryOp.===, numRef, js.IntLiteral(lo))
val rhs = js.BinaryOp(js.BinaryOp.===, numRef, js.IntLiteral(hi))
js.If(lhs, js.BooleanLiteral(true), rhs)(jstpe.BooleanType)
case (lo, hi) =>
val lhs = js.BinaryOp(js.BinaryOp.Num_<=, js.IntLiteral(lo), numRef)
val rhs = js.BinaryOp(js.BinaryOp.Num_<=, numRef, js.IntLiteral(hi))
js.BinaryOp(js.BinaryOp.Boolean_&, lhs, rhs)
js.If(lhs, rhs, js.BooleanLiteral(false))(jstpe.BooleanType)
}
}
private def zipMap[T, U, V](xs: List[T], ys: List[U])(
f: (T, U) => V): List[V] = {
for ((x, y) <- xs zip ys) yield f(x, y)
}
/** mkOverloadSelection return a list of `stats` with that starts with:
* 1) The definition for the local variable that will hold the overload
* resolution number.
* 2) The definitions of all local variables that are used as parameters
* in all the constructors.
* 3) The overload resolution match/if statements. For each overload the
* overload number is assigned and the parameters are cast and assigned
* to their corresponding variables.
*/
private def mkOverloadSelection(jsConstructorBuilder: JSConstructorBuilder,
overloadIdent: js.Ident, dispatchResolution: js.Tree)(
implicit pos: Position): List[js.Tree]= {
if (!jsConstructorBuilder.hasSubConstructors) {
dispatchResolution match {
/* Dispatch to constructor with no arguments.
* Contains trivial parameterless call to the constructor.
*/
case js.ApplyStatic(_, mtd, js.This() :: Nil)
if ir.Definitions.isConstructorName(mtd.name) =>
Nil
/* Dispatch to constructor with at least one argument.
* Where js.Block's stats.init corresponds to the parameter casts and
* js.Block's stats.last contains the call to the constructor.
*/
case js.Block(stats) =>
val js.ApplyStatic(_, method, _) = stats.last
val refs = jsConstructorBuilder.getParamRefsFor(method.name)
val paramCasts = stats.init.map(_.asInstanceOf[js.VarDef])
zipMap(refs, paramCasts) { (ref, paramCast) =>
js.VarDef(ref.ident, ref.tpe, mutable = false, paramCast.rhs)
}
}
} else {
val overloadRef = js.VarRef(overloadIdent)(jstpe.IntType)
/* transformDispatch takes the body of the method generated by
* `genJSConstructorExport` and transform it recursively.
*/
def transformDispatch(tree: js.Tree): js.Tree = tree match {
/* Dispatch to constructor with no arguments.
* Contains trivial parameterless call to the constructor.
*/
case js.ApplyStatic(_, method, js.This() :: Nil)
if ir.Definitions.isConstructorName(method.name) =>
js.Assign(overloadRef,
js.IntLiteral(jsConstructorBuilder.getOverrideNum(method.name)))
/* Dispatch to constructor with at least one argument.
* Where js.Block's stats.init corresponds to the parameter casts and
* js.Block's stats.last contains the call to the constructor.
*/
case js.Block(stats) =>
val js.ApplyStatic(_, method, _) = stats.last
val num = jsConstructorBuilder.getOverrideNum(method.name)
val overloadAssign = js.Assign(overloadRef, js.IntLiteral(num))
val refs = jsConstructorBuilder.getParamRefsFor(method.name)
val paramCasts = stats.init.map(_.asInstanceOf[js.VarDef].rhs)
val parameterAssigns = zipMap(refs, paramCasts)(js.Assign(_, _))
js.Block(overloadAssign :: parameterAssigns)
// Parameter count resolution
case js.Match(selector, cases, default) =>
val newCases = cases.map {
case (literals, body) => (literals, transformDispatch(body))
}
val newDefault = transformDispatch(default)
js.Match(selector, newCases, newDefault)(tree.tpe)
// Parameter type resolution
case js.If(cond, thenp, elsep) =>
js.If(cond, transformDispatch(thenp),
transformDispatch(elsep))(tree.tpe)
// Throw(StringLiteral(No matching overload))
case tree: js.Throw =>
tree
}
val newDispatchResolution = transformDispatch(dispatchResolution)
val allParamDefsAsVars = jsConstructorBuilder.getAllParamDefsAsVars
val overrideNumDef =
js.VarDef(overloadIdent, jstpe.IntType, mutable = true, js.IntLiteral(0))
overrideNumDef :: allParamDefsAsVars ::: newDispatchResolution :: Nil
}
}
private def mkJSConstructorBuilder(ctors: List[js.MethodDef])(
implicit pos: Position): JSConstructorBuilder = {
def findCtorForwarderCall(tree: js.Tree): String = tree match {
case js.ApplyStatic(_, method, js.This() :: _)
if ir.Definitions.isConstructorName(method.name) =>
method.name
case js.Block(stats) =>
stats.collectFirst {
case js.ApplyStatic(_, method, js.This() :: _)
if ir.Definitions.isConstructorName(method.name) =>
method.name
}.get
}
val (primaryCtor :: Nil, secondaryCtors) = ctors.partition {
_.body.get match {
case js.Block(stats) =>
stats.exists(_.isInstanceOf[js.JSSuperConstructorCall])
case _: js.JSSuperConstructorCall => true
case _ => false
}
}
val ctorToChildren = secondaryCtors.map { ctor =>
findCtorForwarderCall(ctor.body.get) -> ctor
}.groupBy(_._1).mapValues(_.map(_._2)).withDefaultValue(Nil)
var overrideNum = -1
def mkConstructorTree(method: js.MethodDef): ConstructorTree = {
val methodName = method.name.name
val subCtrTrees = ctorToChildren(methodName).map(mkConstructorTree)
overrideNum += 1
new ConstructorTree(overrideNum, method, subCtrTrees)
}
new JSConstructorBuilder(mkConstructorTree(primaryCtor))
}
// Generate a method -------------------------------------------------------
def genMethod(dd: DefDef): Option[js.MethodDef] = {
withNewLocalNameScope {
genMethodWithCurrentLocalNameScope(dd)
}
}
/** Gen JS code for a method definition in a class or in an impl class.
* On the JS side, method names are mangled to encode the full signature
* of the Scala method, as described in `JSEncoding`, to support
* overloading.
*
* Some methods are not emitted at all:
* * Primitives, since they are never actually called (with exceptions)
* * Abstract methods
* * Constructors of hijacked classes
* * Methods with the {{{@JavaDefaultMethod}}} annotation mixed in classes.
*
* Constructors are emitted by generating their body as a statement.
*
* Interface methods with the {{{@JavaDefaultMethod}}} annotation produce
* default methods forwarding to the trait impl class method.
*
* Other (normal) methods are emitted with `genMethodDef()`.
*/
def genMethodWithCurrentLocalNameScope(dd: DefDef): Option[js.MethodDef] = {
implicit val pos = dd.pos
val DefDef(mods, name, _, vparamss, _, rhs) = dd
val sym = dd.symbol
withScopedVars(
currentMethodSym := sym,
thisLocalVarIdent := None,
fakeTailJumpParamRepl := (NoSymbol, NoSymbol),
enclosingLabelDefParams := Map.empty,
isModuleInitialized := new VarBox(false),
countsOfReturnsToMatchEnd := mutable.Map.empty,
undefinedDefaultParams := mutable.Set.empty
) {
assert(vparamss.isEmpty || vparamss.tail.isEmpty,
"Malformed parameter list: " + vparamss)
val params = if (vparamss.isEmpty) Nil else vparamss.head map (_.symbol)
val isJSClassConstructor =
sym.isClassConstructor && isScalaJSDefinedJSClass(currentClassSym)
val methodName: js.PropertyName = encodeMethodSym(sym)
def jsParams = for (param <- params) yield {
implicit val pos = param.pos
js.ParamDef(encodeLocalSym(param), toIRType(param.tpe),
mutable = false, rest = false)
}
if (scalaPrimitives.isPrimitive(sym) &&
!jsPrimitives.shouldEmitPrimitiveBody(sym)) {
None
} else if (isAbstractMethod(dd)) {
val body = if (scalaUsesImplClasses &&
sym.hasAnnotation(JavaDefaultMethodAnnotation)) {
/* For an interface method with @JavaDefaultMethod, make it a
* default method calling the impl class method.
*/
val implClassSym = sym.owner.implClass
val implMethodSym = implClassSym.info.member(sym.name).suchThat { s =>
s.isMethod &&
s.tpe.params.size == sym.tpe.params.size + 1 &&
s.tpe.params.head.tpe =:= sym.owner.toTypeConstructor &&
s.tpe.params.tail.zip(sym.tpe.params).forall {
case (sParam, symParam) =>
sParam.tpe =:= symParam.tpe
}
}
Some(genTraitImplApply(implMethodSym,
js.This()(currentClassType) :: jsParams.map(_.ref)))
} else {
None
}
Some(js.MethodDef(static = false, methodName,
jsParams, toIRType(sym.tpe.resultType), body)(
OptimizerHints.empty, None))
} else if (isJSNativeCtorDefaultParam(sym)) {
None
} else if (sym.isClassConstructor && isHijackedBoxedClass(sym.owner)) {
None
} else if (scalaUsesImplClasses && !sym.owner.isImplClass &&
sym.hasAnnotation(JavaDefaultMethodAnnotation)) {
// Do not emit trait impl forwarders with @JavaDefaultMethod
None
} else {
withScopedVars(
mutableLocalVars := mutable.Set.empty,
mutatedLocalVars := mutable.Set.empty
) {
def isTraitImplForwarder = dd.rhs match {
case app: Apply => foreignIsImplClass(app.symbol.owner)
case _ => false
}
val shouldMarkInline = {
sym.hasAnnotation(InlineAnnotationClass) ||
sym.name.startsWith(nme.ANON_FUN_NAME) ||
adHocInlineMethods.contains(sym.fullName)
}
val shouldMarkNoinline = {
sym.hasAnnotation(NoinlineAnnotationClass) &&
!isTraitImplForwarder &&
!ignoreNoinlineAnnotation(sym)
}
val optimizerHints =
OptimizerHints.empty.
withInline(shouldMarkInline).
withNoinline(shouldMarkNoinline)
val methodDef = {
if (isJSClassConstructor) {
val body0 = genStat(rhs)
val body1 =
if (!sym.isPrimaryConstructor) body0
else moveAllStatementsAfterSuperConstructorCall(body0)
js.MethodDef(static = false, methodName,
jsParams, jstpe.NoType, Some(body1))(optimizerHints, None)
} else if (sym.isClassConstructor) {
js.MethodDef(static = false, methodName,
jsParams, jstpe.NoType,
Some(genStat(rhs)))(optimizerHints, None)
} else {
val resultIRType = toIRType(sym.tpe.resultType)
genMethodDef(static = sym.owner.isImplClass, methodName,
params, resultIRType, rhs, optimizerHints)
}
}
val methodDefWithoutUselessVars = {
val unmutatedMutableLocalVars =
(mutableLocalVars -- mutatedLocalVars).toList
val mutatedImmutableLocalVals =
(mutatedLocalVars -- mutableLocalVars).toList
if (unmutatedMutableLocalVars.isEmpty &&
mutatedImmutableLocalVals.isEmpty) {
// OK, we're good (common case)
methodDef
} else {
val patches = (
unmutatedMutableLocalVars.map(encodeLocalSym(_).name -> false) :::
mutatedImmutableLocalVals.map(encodeLocalSym(_).name -> true)
).toMap
patchMutableFlagOfLocals(methodDef, patches)
}
}
Some(methodDefWithoutUselessVars)
}
}
}
}
def isAbstractMethod(dd: DefDef): Boolean = {
/* When scalac uses impl classes, we cannot trust `rhs` to be
* `EmptyTree` for deferred methods (probably due to an internal bug
* of scalac), as can be seen in run/t6443.scala.
* However, when it does not use impl class anymore, we have to use
* `rhs == EmptyTree` as predicate, just like the JVM back-end does.
*/
if (scalaUsesImplClasses)
dd.symbol.isDeferred || dd.symbol.owner.isInterface
else
dd.rhs == EmptyTree
}
private val adHocInlineMethods = Set(
"scala.collection.mutable.ArrayOps$ofRef.newBuilder$extension",
"scala.runtime.ScalaRunTime.arrayClass",
"scala.runtime.ScalaRunTime.arrayElementClass"
)
/** Patches the mutable flags of selected locals in a [[js.MethodDef]].
*
* @param patches Map from local name to new value of the mutable flags.
* For locals not in the map, the flag is untouched.
*/
private def patchMutableFlagOfLocals(methodDef: js.MethodDef,
patches: Map[String, Boolean]): js.MethodDef = {
def newMutable(name: String, oldMutable: Boolean): Boolean =
patches.getOrElse(name, oldMutable)
val js.MethodDef(static, methodName, params, resultType, body) = methodDef
val newParams = for {
p @ js.ParamDef(name, ptpe, mutable, rest) <- params
} yield {
js.ParamDef(name, ptpe, newMutable(name.name, mutable), rest)(p.pos)
}
val transformer = new ir.Transformers.Transformer {
override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match {
case js.VarDef(name, vtpe, mutable, rhs) =>
assert(isStat)
super.transform(js.VarDef(
name, vtpe, newMutable(name.name, mutable), rhs)(tree.pos), isStat)
case js.Closure(captureParams, params, body, captureValues) =>
js.Closure(captureParams, params, body,
captureValues.map(transformExpr))(tree.pos)
case _ =>
super.transform(tree, isStat)
}
}
val newBody = body.map(
b => transformer.transform(b, isStat = resultType == jstpe.NoType))
js.MethodDef(static, methodName, newParams, resultType,
newBody)(methodDef.optimizerHints, None)(methodDef.pos)
}
/** Moves all statements after the super constructor call.
*
* This is used for the primary constructor of a Scala.js-defined JS
* class, because those cannot access `this` before the super constructor
* call.
*
* scalac inserts statements before the super constructor call for early
* initializers and param accessor initializers (including val's and var's
* declared in the params). We move those after the super constructor
* call, and are therefore executed later than for a Scala class.
*/
private def moveAllStatementsAfterSuperConstructorCall(
body: js.Tree): js.Tree = {
val bodyStats = body match {
case js.Block(stats) => stats
case _ => body :: Nil
}
val (beforeSuper, superCall :: afterSuper) =
bodyStats.span(!_.isInstanceOf[js.JSSuperConstructorCall])
assert(!beforeSuper.exists(_.isInstanceOf[js.VarDef]),
"Trying to move a local VarDef after the super constructor call " +
"of a Scala.js-defined JS class at ${body.pos}")
js.Block(
superCall ::
beforeSuper :::
afterSuper)(body.pos)
}
/** Generates the MethodDef of a (non-constructor) method
*
* Most normal methods are emitted straightforwardly. If the result
* type is Unit, then the body is emitted as a statement. Otherwise, it is
* emitted as an expression.
*
* The additional complexity of this method handles the transformation of
* a peculiarity of recursive tail calls: the local ValDef that replaces
* `this`.
*/
def genMethodDef(static: Boolean, methodName: js.PropertyName,
paramsSyms: List[Symbol], resultIRType: jstpe.Type,
tree: Tree, optimizerHints: OptimizerHints): js.MethodDef = {
implicit val pos = tree.pos
val jsParams = for (param <- paramsSyms) yield {
implicit val pos = param.pos
js.ParamDef(encodeLocalSym(param), toIRType(param.tpe),
mutable = false, rest = false)
}
val bodyIsStat = resultIRType == jstpe.NoType
def genBody() = tree match {
case Block(
(thisDef @ ValDef(_, nme.THIS, _, initialThis)) :: otherStats,
rhs) =>
// This method has tail jumps
// To be called from within withScopedVars
def genInnerBody() = {
js.Block(otherStats.map(genStat) :+ (
if (bodyIsStat) genStat(rhs)
else genExpr(rhs)))
}
initialThis match {
case Ident(_) =>
// TODO Is this special-case really needed?
withScopedVars(
fakeTailJumpParamRepl := (thisDef.symbol, initialThis.symbol)
) {
genInnerBody()
}
case _ =>
val thisSym = thisDef.symbol
if (thisSym.isMutable)
mutableLocalVars += thisSym
val thisLocalIdent = encodeLocalSym(thisSym)
val genRhs = genExpr(initialThis)
val thisLocalVarDef = js.VarDef(thisLocalIdent,
currentClassType, thisSym.isMutable, genRhs)
val innerBody = {
withScopedVars(
thisLocalVarIdent := Some(thisLocalIdent)
) {
genInnerBody()
}
}
js.Block(thisLocalVarDef, innerBody)
}
case _ =>
if (bodyIsStat) genStat(tree)
else genExpr(tree)
}
if (!isScalaJSDefinedJSClass(currentClassSym)) {
js.MethodDef(static, methodName, jsParams, resultIRType,
Some(genBody()))(optimizerHints, None)
} else {
assert(!static, tree.pos)
withScopedVars(
thisLocalVarIdent := Some(freshLocalIdent("this"))
) {
val thisParamDef = js.ParamDef(thisLocalVarIdent.get.get,
jstpe.AnyType, mutable = false, rest = false)
js.MethodDef(static = true, methodName, thisParamDef :: jsParams,
resultIRType, Some(genBody()))(
optimizerHints, None)
}
}
}
/** Gen JS code for a tree in statement position (in the IR).
*/
def genStat(tree: Tree): js.Tree = {
exprToStat(genStatOrExpr(tree, isStat = true))
}
/** Turn a JavaScript expression of type Unit into a statement */
def exprToStat(tree: js.Tree): js.Tree = {
/* Any JavaScript expression is also a statement, but at least we get rid
* of some pure expressions that come from our own codegen.
*/
implicit val pos = tree.pos
tree match {
case js.Block(stats :+ expr) => js.Block(stats :+ exprToStat(expr))
case _:js.Literal | js.This() => js.Skip()
case _ => tree
}
}
/** Gen JS code for a tree in expression position (in the IR).
*/
def genExpr(tree: Tree): js.Tree = {
val result = genStatOrExpr(tree, isStat = false)
assert(result.tpe != jstpe.NoType,
s"genExpr($tree) returned a tree with type NoType at pos ${tree.pos}")
result
}
/** Gen JS code for a tree in statement or expression position (in the IR).
*
* This is the main transformation method. Each node of the Scala AST
* is transformed into an equivalent portion of the JS AST.
*/
def genStatOrExpr(tree: Tree, isStat: Boolean): js.Tree = {
implicit val pos = tree.pos
tree match {
/** LabelDefs (for while and do..while loops) */
case lblDf: LabelDef =>
genLabelDef(lblDf)
/** Local val or var declaration */
case ValDef(_, name, _, rhs) =>
/* Must have been eliminated by the tail call transform performed
* by genMethodDef(). */
assert(name != nme.THIS,
s"ValDef(_, nme.THIS, _, _) found at ${tree.pos}")
val sym = tree.symbol
val rhsTree =
if (rhs == EmptyTree) genZeroOf(sym.tpe)
else genExpr(rhs)
rhsTree match {
case js.UndefinedParam() =>
// This is an intermediate assignment for default params on a
// js.Any. Add the symbol to the corresponding set to inform
// the Ident resolver how to replace it and don't emit the symbol
undefinedDefaultParams += sym
js.Skip()
case _ =>
if (sym.isMutable)
mutableLocalVars += sym
js.VarDef(encodeLocalSym(sym),
toIRType(sym.tpe), sym.isMutable, rhsTree)
}
case If(cond, thenp, elsep) =>
js.If(genExpr(cond), genStatOrExpr(thenp, isStat),
genStatOrExpr(elsep, isStat))(toIRType(tree.tpe))
case Return(expr) =>
js.Return(toIRType(expr.tpe) match {
case jstpe.NoType => js.Block(genStat(expr), js.Undefined())
case _ => genExpr(expr)
})
case t: Try =>
genTry(t, isStat)
case Throw(expr) =>
val ex = genExpr(expr)
js.Throw {
if (isMaybeJavaScriptException(expr.tpe)) {
genApplyMethod(
genLoadModule(RuntimePackageModule),
Runtime_unwrapJavaScriptException,
List(ex))
} else {
ex
}
}
case app: Apply =>
genApply(app, isStat)
case app: ApplyDynamic =>
genApplyDynamic(app)
case This(qual) =>
if (tree.symbol == currentClassSym.get) {
genThis()
} else {
assert(tree.symbol.isModuleClass,
"Trying to access the this of another class: " +
"tree.symbol = " + tree.symbol +
", class symbol = " + currentClassSym.get +
" compilation unit:" + currentUnit)
genLoadModule(tree.symbol)
}
case Select(qualifier, selector) =>
val sym = tree.symbol
def unboxFieldValue(boxed: js.Tree): js.Tree = {
fromAny(boxed,
enteringPhase(currentRun.posterasurePhase)(sym.tpe))
}
if (sym.isModule) {
assert(!sym.isPackageClass, "Cannot use package as value: " + tree)
genLoadModule(sym)
} else if (sym.isStaticMember) {
genStaticMember(sym)
} else if (paramAccessorLocals contains sym) {
paramAccessorLocals(sym).ref
} else if (isScalaJSDefinedJSClass(sym.owner)) {
val genQual = genExpr(qualifier)
val boxed = if (isExposed(sym))
js.JSBracketSelect(genQual, js.StringLiteral(jsNameOf(sym)))
else
js.JSDotSelect(genQual, encodeFieldSym(sym))
unboxFieldValue(boxed)
} else if (jsInterop.isFieldStatic(sym)) {
unboxFieldValue(genSelectStaticFieldAsBoxed(sym))
} else {
js.Select(genExpr(qualifier),
encodeFieldSym(sym))(toIRType(sym.tpe))
}
case Ident(name) =>
val sym = tree.symbol
if (!sym.hasPackageFlag) {
if (sym.isModule) {
assert(!sym.isPackageClass, "Cannot use package as value: " + tree)
genLoadModule(sym)
} else if (undefinedDefaultParams contains sym) {
// This is a default parameter whose assignment was moved to
// a local variable. Put a literal undefined param again
js.UndefinedParam()(toIRType(sym.tpe))
} else {
js.VarRef(encodeLocalSym(sym))(toIRType(sym.tpe))
}
} else {
sys.error("Cannot use package as value: " + tree)
}
case Literal(value) =>
value.tag match {
case UnitTag =>
js.Skip()
case BooleanTag =>
js.BooleanLiteral(value.booleanValue)
case ByteTag | ShortTag | CharTag | IntTag =>
js.IntLiteral(value.intValue)
case LongTag =>
js.LongLiteral(value.longValue)
case FloatTag =>
js.FloatLiteral(value.floatValue)
case DoubleTag =>
js.DoubleLiteral(value.doubleValue)
case StringTag =>
js.StringLiteral(value.stringValue)
case NullTag =>
js.Null()
case ClazzTag =>
js.ClassOf(toReferenceType(value.typeValue))
case EnumTag =>
genStaticMember(value.symbolValue)
}
case tree: Block =>
genBlock(tree, isStat)
case Typed(Super(_, _), _) =>
genThis()
case Typed(expr, _) =>
genExpr(expr)
case Assign(lhs, rhs) =>
val sym = lhs.symbol
if (sym.isStaticMember)
abort(s"Assignment to static member ${sym.fullName} not supported")
val genRhs = genExpr(rhs)
lhs match {
case Select(qualifier, _) =>
val ctorAssignment = (
currentMethodSym.isClassConstructor &&
currentMethodSym.owner == qualifier.symbol &&
qualifier.isInstanceOf[This]
)
if (!ctorAssignment && !suspectFieldMutable(sym))
unexpectedMutatedFields += sym
val genQual = genExpr(qualifier)
def genBoxedRhs: js.Tree = {
ensureBoxed(genRhs,
enteringPhase(currentRun.posterasurePhase)(rhs.tpe))
}
if (isScalaJSDefinedJSClass(sym.owner)) {
val genLhs = if (isExposed(sym))
js.JSBracketSelect(genQual, js.StringLiteral(jsNameOf(sym)))
else
js.JSDotSelect(genQual, encodeFieldSym(sym))
js.Assign(genLhs, genBoxedRhs)
} else if (jsInterop.isFieldStatic(sym)) {
js.Assign(genSelectStaticFieldAsBoxed(sym), genBoxedRhs)
} else {
js.Assign(
js.Select(genQual, encodeFieldSym(sym))(toIRType(sym.tpe)),
genRhs)
}
case _ =>
mutatedLocalVars += sym
js.Assign(
js.VarRef(encodeLocalSym(sym))(toIRType(sym.tpe)),
genRhs)
}
/** Array constructor */
case av: ArrayValue =>
genArrayValue(av)
/** A Match reaching the backend is supposed to be optimized as a switch */
case mtch: Match =>
genMatch(mtch, isStat)
/** Anonymous function (in 2.12, or with -Ydelambdafy:method in 2.11) */
case fun: Function =>
genAnonFunction(fun)
case EmptyTree =>
js.Skip()
case _ =>
abort("Unexpected tree in genExpr: " +
tree + "/" + tree.getClass + " at: " + tree.pos)
}
} // end of GenJSCode.genExpr()
/** Gen JS this of the current class.
* Normally encoded straightforwardly as a JS this.
* But must be replaced by the tail-jump-this local variable if there
* is one.
*/
private def genThis()(implicit pos: Position): js.Tree = {
thisLocalVarIdent.fold[js.Tree] {
if (tryingToGenMethodAsJSFunction) {
throw new CancelGenMethodAsJSFunction(
"Trying to generate `this` inside the body")
}
js.This()(currentClassType)
} { thisLocalIdent =>
js.VarRef(thisLocalIdent)(currentClassType)
}
}
private def genSelectStaticFieldAsBoxed(sym: Symbol)(
implicit pos: Position): js.Tree = {
val exportInfos = jsInterop.staticFieldInfoOf(sym)
(exportInfos.head.destination: @unchecked) match {
case ExportDestination.TopLevel =>
val cls = jstpe.ClassType(encodeClassFullName(sym.owner))
js.SelectStatic(cls, encodeFieldSym(sym))(jstpe.AnyType)
case ExportDestination.Static =>
val exportInfo = exportInfos.head
val companionClass = patchedLinkedClassOfClass(sym.owner)
js.JSBracketSelect(
genPrimitiveJSClass(companionClass),
js.StringLiteral(exportInfo.jsName))
}
}
/** Gen JS code for LabelDef
* The only LabelDefs that can reach here are the desugaring of
* while and do..while loops. All other LabelDefs (for tail calls or
* matches) are caught upstream and transformed in ad hoc ways.
*
* So here we recognize all the possible forms of trees that can result
* of while or do..while loops, and we reconstruct the loop for emission
* to JS.
*/
def genLabelDef(tree: LabelDef): js.Tree = {
implicit val pos = tree.pos
val sym = tree.symbol
tree match {
// while (cond) { body }
case LabelDef(lname, Nil,
If(cond,
Block(bodyStats, Apply(target @ Ident(lname2), Nil)),
Literal(_))) if (target.symbol == sym) =>
js.While(genExpr(cond), js.Block(bodyStats map genStat))
// while (cond) { body }; result
case LabelDef(lname, Nil,
Block(List(
If(cond,
Block(bodyStats, Apply(target @ Ident(lname2), Nil)),
Literal(_))),
result)) if (target.symbol == sym) =>
js.Block(
js.While(genExpr(cond), js.Block(bodyStats map genStat)),
genExpr(result))
// while (true) { body }
case LabelDef(lname, Nil,
Block(bodyStats,
Apply(target @ Ident(lname2), Nil))) if (target.symbol == sym) =>
js.While(js.BooleanLiteral(true), js.Block(bodyStats map genStat))
// while (false) { body }
case LabelDef(lname, Nil, Literal(Constant(()))) =>
js.Skip()
// do { body } while (cond)
case LabelDef(lname, Nil,
Block(bodyStats,
If(cond,
Apply(target @ Ident(lname2), Nil),
Literal(_)))) if (target.symbol == sym) =>
js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond))
// do { body } while (cond); result
case LabelDef(lname, Nil,
Block(
bodyStats :+
If(cond,
Apply(target @ Ident(lname2), Nil),
Literal(_)),
result)) if (target.symbol == sym) =>
js.Block(
js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond)),
genExpr(result))
/* Arbitrary other label - we can jump to it from inside it.
* This is typically for the label-defs implementing tail-calls.
* It can also handle other weird LabelDefs generated by some compiler
* plugins (see for example #1148).
*/
case LabelDef(labelName, labelParams, rhs) =>
val labelParamSyms = labelParams.map(_.symbol) map {
s => if (s == fakeTailJumpParamRepl._1) fakeTailJumpParamRepl._2 else s
}
withScopedVars(
enclosingLabelDefParams :=
enclosingLabelDefParams.get + (tree.symbol -> labelParamSyms)
) {
val bodyType = toIRType(tree.tpe)
val labelIdent = encodeLabelSym(tree.symbol)
val blockLabelIdent = freshLocalIdent()
js.Labeled(blockLabelIdent, bodyType, {
js.While(js.BooleanLiteral(true), {
if (bodyType == jstpe.NoType)
js.Block(genStat(rhs), js.Return(js.Undefined(), Some(blockLabelIdent)))
else
js.Return(genExpr(rhs), Some(blockLabelIdent))
}, Some(labelIdent))
})
}
}
}
/** Gen JS code for a try..catch or try..finally block
*
* try..finally blocks are compiled straightforwardly to try..finally
* blocks of JS.
*
* try..catch blocks are a bit more subtle, as JS does not have
* type-based selection of exceptions to catch. We thus encode explicitly
* the type tests, like in:
*
* try { ... }
* catch (e) {
* if (e.isInstanceOf[IOException]) { ... }
* else if (e.isInstanceOf[Exception]) { ... }
* else {
* throw e; // default, re-throw
* }
* }
*/
def genTry(tree: Try, isStat: Boolean): js.Tree = {
implicit val pos = tree.pos
val Try(block, catches, finalizer) = tree
val blockAST = genStatOrExpr(block, isStat)
val resultType = toIRType(tree.tpe)
val handled =
if (catches.isEmpty) blockAST
else genTryCatch(blockAST, catches, resultType, isStat)
genStat(finalizer) match {
case js.Skip() => handled
case ast => js.TryFinally(handled, ast)
}
}
private def genTryCatch(body: js.Tree, catches: List[CaseDef],
resultType: jstpe.Type,
isStat: Boolean)(implicit pos: Position): js.Tree = {
val exceptIdent = freshLocalIdent("e")
val origExceptVar = js.VarRef(exceptIdent)(jstpe.AnyType)
val mightCatchJavaScriptException = catches.exists { caseDef =>
caseDef.pat match {
case Typed(Ident(nme.WILDCARD), tpt) =>
isMaybeJavaScriptException(tpt.tpe)
case Ident(nme.WILDCARD) =>
true
case pat @ Bind(_, _) =>
isMaybeJavaScriptException(pat.symbol.tpe)
}
}
val (exceptValDef, exceptVar) = if (mightCatchJavaScriptException) {
val valDef = js.VarDef(freshLocalIdent("e"),
encodeClassType(ThrowableClass), mutable = false, {
genApplyMethod(
genLoadModule(RuntimePackageModule),
Runtime_wrapJavaScriptException,
List(origExceptVar))
})
(valDef, valDef.ref)
} else {
(js.Skip(), origExceptVar)
}
val elseHandler: js.Tree = js.Throw(origExceptVar)
val handler = catches.foldRight(elseHandler) { (caseDef, elsep) =>
implicit val pos = caseDef.pos
val CaseDef(pat, _, body) = caseDef
// Extract exception type and variable
val (tpe, boundVar) = (pat match {
case Typed(Ident(nme.WILDCARD), tpt) =>
(tpt.tpe, None)
case Ident(nme.WILDCARD) =>
(ThrowableClass.tpe, None)
case Bind(_, _) =>
(pat.symbol.tpe, Some(encodeLocalSym(pat.symbol)))
})
// Generate the body that must be executed if the exception matches
val bodyWithBoundVar = (boundVar match {
case None =>
genStatOrExpr(body, isStat)
case Some(bv) =>
val castException = genAsInstanceOf(exceptVar, tpe)
js.Block(
js.VarDef(bv, toIRType(tpe), mutable = false, castException),
genStatOrExpr(body, isStat))
})
// Generate the test
if (tpe == ThrowableClass.tpe) {
bodyWithBoundVar
} else {
val cond = genIsInstanceOf(exceptVar, tpe)
js.If(cond, bodyWithBoundVar, elsep)(resultType)
}
}
js.TryCatch(body, exceptIdent,
js.Block(exceptValDef, handler))(resultType)
}
/** Gen JS code for an Apply node (method call)
*
* There's a whole bunch of varieties of Apply nodes: regular method
* calls, super calls, constructor calls, isInstanceOf/asInstanceOf,
* primitives, JS calls, etc. They are further dispatched in here.
*/
def genApply(tree: Apply, isStat: Boolean): js.Tree = {
implicit val pos = tree.pos
val Apply(fun, args) = tree
val sym = fun.symbol
def isRawJSDefaultParam: Boolean = {
if (isCtorDefaultParam(sym)) {
isRawJSCtorDefaultParam(sym)
} else {
sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) &&
isRawJSType(sym.owner.tpe) && {
/* If this is a default parameter accessor on a
* ScalaJSDefinedJSClass, we need to know if the method for which we
* are the default parameter is exposed or not.
* We do this by removing the $default suffix from the method name,
* and looking up a member with that name in the owner.
* Note that this does not work for local methods. But local methods
* are never exposed.
* Further note that overloads are easy, because either all or none
* of them are exposed.
*/
def isAttachedMethodExposed = {
val methodName = nme.defaultGetterToMethod(sym.name)
val ownerMethod = sym.owner.info.decl(methodName)
ownerMethod.filter(isExposed).exists
}
!isScalaJSDefinedJSClass(sym.owner) || isAttachedMethodExposed
}
}
}
fun match {
case TypeApply(_, _) =>
genApplyTypeApply(tree, isStat)
case _ if isRawJSDefaultParam =>
js.UndefinedParam()(toIRType(sym.tpe.resultType))
case Select(Super(_, _), _) =>
genSuperCall(tree, isStat)
case Select(New(_), nme.CONSTRUCTOR) =>
genApplyNew(tree)
case _ =>
if (sym.isLabel) {
genLabelApply(tree)
} else if (scalaPrimitives.isPrimitive(sym)) {
genPrimitiveOp(tree, isStat)
} else if (currentRun.runDefinitions.isBox(sym)) {
// Box a primitive value (cannot be Unit)
val arg = args.head
makePrimitiveBox(genExpr(arg), arg.tpe)
} else if (currentRun.runDefinitions.isUnbox(sym)) {
// Unbox a primitive value (cannot be Unit)
val arg = args.head
makePrimitiveUnbox(genExpr(arg), tree.tpe)
} else {
genNormalApply(tree, isStat)
}
}
}
/** Gen an Apply with a TypeApply method.
*
* Until 2.12.0-M5, only `isInstanceOf` and `asInstanceOf` kept their type
* argument until the backend. Since 2.12.0-RC1, `AnyRef.synchronized`
* does so too.
*/
private def genApplyTypeApply(tree: Apply, isStat: Boolean): js.Tree = {
implicit val pos = tree.pos
val Apply(TypeApply(fun @ Select(obj, _), targs), args) = tree
val sym = fun.symbol
sym match {
case Object_isInstanceOf =>
genIsAsInstanceOf(obj, targs, cast = false)
case Object_asInstanceOf =>
genIsAsInstanceOf(obj, targs, cast = true)
case Object_synchronized =>
genSynchronized(obj, args.head, isStat)
case _ =>
abort("Unexpected type application " + fun +
"[sym: " + sym.fullName + "]" + " in: " + tree)
}
}
/** Gen `isInstanceOf` or `asInstanceOf`. */
private def genIsAsInstanceOf(obj: Tree, targs: List[Tree], cast: Boolean)(
implicit pos: Position): js.Tree = {
val to = targs.head.tpe
val l = toTypeKind(obj.tpe)
val r = toTypeKind(to)
val source = genExpr(obj)
if (l.isValueType && r.isValueType) {
if (cast)
genConversion(l, r, source)
else
js.BooleanLiteral(l == r)
} else if (l.isValueType) {
val result = if (cast) {
val ctor = ClassCastExceptionClass.info.member(
nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty)
js.Throw(genNew(ClassCastExceptionClass, ctor, Nil))
} else {
js.BooleanLiteral(false)
}
js.Block(source, result) // eval and discard source
} else if (r.isValueType) {
assert(!cast, s"Unexpected asInstanceOf from ref type to value type")
genIsInstanceOf(source, boxedClass(to.typeSymbol).tpe)
} else {
if (cast)
genAsInstanceOf(source, to)
else
genIsInstanceOf(source, to)
}
}
/** Gen JS code for a super call, of the form Class.super[mix].fun(args).
*
* This does not include calls defined in mixin traits, as these are
* already desugared by the 'mixin' phase. Only calls to super classes
* remain.
* Since a class has exactly one direct superclass, and calling a method
* two classes above the current one is invalid, the `mix` item is
* irrelevant.
*/
private def genSuperCall(tree: Apply, isStat: Boolean): js.Tree = {
implicit val pos = tree.pos
val Apply(fun @ Select(sup @ Super(_, mix), _), args) = tree
val sym = fun.symbol
if (isScalaJSDefinedJSClass(currentClassSym)) {
if (sym.isMixinConstructor) {
/* Do not emit a call to the $init$ method of JS traits.
* This exception is necessary because @JSOptional fields cause the
* creation of a $init$ method, which we must not call.
*/
js.Skip()
} else {
genJSSuperCall(tree, isStat)
}
} else {
val superCall = genApplyMethodStatically(
genThis()(sup.pos), sym, genActualArgs(sym, args))
// Initialize the module instance just after the super constructor call.
if (isStaticModule(currentClassSym) && !isModuleInitialized.value &&
currentMethodSym.isClassConstructor) {
isModuleInitialized.value = true
val thisType = jstpe.ClassType(encodeClassFullName(currentClassSym))
val initModule = js.StoreModule(thisType, js.This()(thisType))
js.Block(superCall, initModule)
} else {
superCall
}
}
}
/** Gen JS code for a constructor call (new).
* Further refined into:
* * new String(...)
* * new of a hijacked boxed class
* * new of an anonymous function class that was recorded as JS function
* * new of a raw JS class
* * new Array
* * regular new
*/
private def genApplyNew(tree: Apply): js.Tree = {
implicit val pos = tree.pos
val Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) = tree
val ctor = fun.symbol
val tpe = tpt.tpe
val clsSym = tpe.typeSymbol
assert(ctor.isClassConstructor,
"'new' call to non-constructor: " + ctor.name)
if (isStringType(tpe)) {
genNewString(tree)
} else if (isHijackedBoxedClass(clsSym)) {
genNewHijackedBoxedClass(clsSym, ctor, args map genExpr)
} else if (isRawJSFunctionDef(clsSym)) {
val classDef = consumeLazilyGeneratedAnonClass(clsSym)
genRawJSFunction(classDef, args.map(genExpr))
} else if (clsSym.isAnonymousFunction) {
val classDef = consumeLazilyGeneratedAnonClass(clsSym)
tryGenAnonFunctionClass(classDef, args.map(genExpr)).getOrElse {
// Cannot optimize anonymous function class. Generate full class.
generatedClasses +=
((clsSym, None, nestedGenerateClass(clsSym)(genClass(classDef))))
genNew(clsSym, ctor, genActualArgs(ctor, args))
}
} else if (isRawJSType(tpe)) {
genPrimitiveJSNew(tree)
} else {
toTypeKind(tpe) match {
case arr @ ARRAY(elem) =>
genNewArray(arr.toIRType, args map genExpr)
case rt @ REFERENCE(cls) =>
genNew(cls, ctor, genActualArgs(ctor, args))
case generatedType =>
abort(s"Non reference type cannot be instantiated: $generatedType")
}
}
}
/** Gen jump to a label.
* Most label-applys are caught upstream (while and do..while loops,
* jumps to next case of a pattern match), but some are still handled here:
* * Jumps to enclosing label-defs, including tail-recursive calls
* * Jump to the end of a pattern match
*/
private def genLabelApply(tree: Apply): js.Tree = {
implicit val pos = tree.pos
val Apply(fun, args) = tree
val sym = fun.symbol
if (enclosingLabelDefParams.contains(sym)) {
genEnclosingLabelApply(tree)
} else if (countsOfReturnsToMatchEnd.contains(sym)) {
/* Jump the to the end-label of a pattern match
* Such labels have exactly one argument, which is the result of
* the pattern match (of type BoxedUnit if the match is in statement
* position). We simply `return` the argument as the result of the
* labeled block surrounding the match.
*/
countsOfReturnsToMatchEnd(sym) += 1
js.Return(genExpr(args.head), Some(encodeLabelSym(sym)))
} else {
/* No other label apply should ever happen. If it does, then we
* have missed a pattern of LabelDef/LabelApply and some new
* translation must be found for it.
*/
abort("Found unknown label apply at "+tree.pos+": "+tree)
}
}
/** Gen a label-apply to an enclosing label def.
*
* This is typically used for tail-recursive calls.
*
* Basically this is compiled into
* continue labelDefIdent;
* but arguments need to be updated beforehand.
*
* Since the rhs for the new value of an argument can depend on the value
* of another argument (and since deciding if it is indeed the case is
* impossible in general), new values are computed in temporary variables
* first, then copied to the actual variables representing the argument.
*
* Trivial assignments (arg1 = arg1) are eliminated.
*
* If, after elimination of trivial assignments, only one assignment
* remains, then we do not use a temporary variable for this one.
*/
private def genEnclosingLabelApply(tree: Apply): js.Tree = {
implicit val pos = tree.pos
val Apply(fun, args) = tree
val sym = fun.symbol
// Prepare quadruplets of (formalArg, irType, tempVar, actualArg)
// Do not include trivial assignments (when actualArg == formalArg)
val formalArgs = enclosingLabelDefParams(sym)
val actualArgs = args map genExpr
val quadruplets = {
for {
(formalArgSym, actualArg) <- formalArgs zip actualArgs
formalArg = encodeLocalSym(formalArgSym)
if (actualArg match {
case js.VarRef(`formalArg`) => false
case _ => true
})
} yield {
mutatedLocalVars += formalArgSym
val tpe = toIRType(formalArgSym.tpe)
(js.VarRef(formalArg)(tpe), tpe,
freshLocalIdent("temp$" + formalArg.name),
actualArg)
}
}
// The actual jump (continue labelDefIdent;)
val jump = js.Continue(Some(encodeLabelSym(sym)))
quadruplets match {
case Nil => jump
case (formalArg, argType, _, actualArg) :: Nil =>
js.Block(
js.Assign(formalArg, actualArg),
jump)
case _ =>
val tempAssignments =
for ((_, argType, tempArg, actualArg) <- quadruplets)
yield js.VarDef(tempArg, argType, mutable = false, actualArg)
val trueAssignments =
for ((formalArg, argType, tempArg, _) <- quadruplets)
yield js.Assign(formalArg, js.VarRef(tempArg)(argType))
js.Block(tempAssignments ++ trueAssignments :+ jump)
}
}
/** Gen a "normal" apply (to a true method).
*
* But even these are further refined into:
* * Methods of java.lang.String, which are redirected to the
* RuntimeString trait implementation.
* * Calls to methods of raw JS types (Scala.js -> JS bridge)
* * Calls to methods in impl classes of traits.
* * Regular method call
*/
private def genNormalApply(tree: Apply, isStat: Boolean): js.Tree = {
implicit val pos = tree.pos
val Apply(fun @ Select(receiver, _), args) = tree
val sym = fun.symbol
def isStringMethodFromObject: Boolean = sym.name match {
case nme.toString_ | nme.equals_ | nme.hashCode_ => true
case _ => false
}
if (sym.owner == StringClass && !isStringMethodFromObject) {
genStringCall(tree)
} else if (isRawJSType(receiver.tpe) && sym.owner != ObjectClass) {
if (!isScalaJSDefinedJSClass(sym.owner) || isExposed(sym))
genPrimitiveJSCall(tree, isStat)
else
genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args))
} else if (foreignIsImplClass(sym.owner)) {
genTraitImplApply(sym, args map genExpr)
} else if (sym.isClassConstructor) {
/* See #66: we have to emit a statically linked call to avoid calling a
* constructor with the same signature in a subclass. */
genApplyMethodStatically(genExpr(receiver), sym, genActualArgs(sym, args))
} else {
genApplyMethod(genExpr(receiver), sym, genActualArgs(sym, args))
}
}
def genApplyMethodStatically(receiver: js.Tree, method: Symbol,
arguments: List[js.Tree])(implicit pos: Position): js.Tree = {
val className = encodeClassFullName(method.owner)
val methodIdent = encodeMethodSym(method)
val resultType =
if (method.isClassConstructor) jstpe.NoType
else toIRType(method.tpe.resultType)
js.ApplyStatically(receiver, jstpe.ClassType(className),
methodIdent, arguments)(resultType)
}
def genTraitImplApply(method: Symbol, arguments: List[js.Tree])(
implicit pos: Position): js.Tree = {
if (method.isMixinConstructor && isRawJSImplClass(method.owner)) {
/* Do not emit a call to the $init$ method of JS traits.
* This exception is necessary because @JSOptional fields cause the
* creation of a $init$ method, which we must not call.
*/
js.Skip()
} else {
genApplyStatic(method, arguments)
}
}
def genApplyJSClassMethod(receiver: js.Tree, method: Symbol,
arguments: List[js.Tree])(implicit pos: Position): js.Tree = {
genApplyStatic(method, receiver :: arguments)
}
def genApplyStatic(method: Symbol, arguments: List[js.Tree])(
implicit pos: Position): js.Tree = {
val cls = encodeClassFullName(method.owner)
val methodIdent = encodeMethodSym(method)
genApplyStatic(cls, methodIdent, arguments,
toIRType(method.tpe.resultType))
}
def genApplyStatic(cls: String, methodIdent: js.Ident,
arguments: List[js.Tree], resultType: jstpe.Type)(
implicit pos: Position): js.Tree = {
js.ApplyStatic(jstpe.ClassType(cls), methodIdent,
arguments)(resultType)
}
/** Gen JS code for a conversion between primitive value types */
def genConversion(from: TypeKind, to: TypeKind, value: js.Tree)(
implicit pos: Position): js.Tree = {
def int0 = js.IntLiteral(0)
def int1 = js.IntLiteral(1)
def long0 = js.LongLiteral(0L)
def long1 = js.LongLiteral(1L)
def float0 = js.FloatLiteral(0.0f)
def float1 = js.FloatLiteral(1.0f)
// scalastyle:off disallow.space.before.token
(from, to) match {
case (INT(_), BOOL) => js.BinaryOp(js.BinaryOp.Num_!=, value, int0)
case (LONG, BOOL) => js.BinaryOp(js.BinaryOp.Long_!=, value, long0)
case (FLOAT(_), BOOL) => js.BinaryOp(js.BinaryOp.Num_!=, value, float0)
case (BOOL, INT(_)) => js.If(value, int1, int0 )(jstpe.IntType)
case (BOOL, LONG) => js.If(value, long1, long0 )(jstpe.LongType)
case (BOOL, FLOAT(_)) => js.If(value, float1, float0)(jstpe.FloatType)
case _ => value
}
// scalastyle:on disallow.space.before.token
}
/** Gen JS code for an isInstanceOf test (for reference types only) */
def genIsInstanceOf(value: js.Tree, to: Type)(
implicit pos: Position): js.Tree = {
val sym = to.typeSymbol
if (sym == ObjectClass) {
js.BinaryOp(js.BinaryOp.!==, value, js.Null())
} else if (isRawJSType(to)) {
if (sym.isTrait) {
reporter.error(pos,
s"isInstanceOf[${sym.fullName}] not supported because it is a raw JS trait")
js.BooleanLiteral(true)
} else {
js.Unbox(js.JSBinaryOp(
js.JSBinaryOp.instanceof, value, genPrimitiveJSClass(sym)), 'Z')
}
} else {
js.IsInstanceOf(value, toReferenceType(to))
}
}
/** Gen JS code for an asInstanceOf cast (for reference types only) */
def genAsInstanceOf(value: js.Tree, to: Type)(
implicit pos: Position): js.Tree = {
def default: js.Tree =
js.AsInstanceOf(value, toReferenceType(to))
val sym = to.typeSymbol
if (sym == ObjectClass || isRawJSType(to)) {
/* asInstanceOf[Object] always succeeds, and
* asInstanceOf to a raw JS type is completely erased.
*/
value
} else if (FunctionClass.seq contains to.typeSymbol) {
/* Don't hide a JSFunctionToScala inside a useless cast, otherwise
* the optimization avoiding double-wrapping in genApply() will not
* be able to kick in.
*/
value match {
case JSFunctionToScala(fun, _) => value
case _ => default
}
} else {
default
}
}
/** Gen JS code for a call to a Scala method.
* This also registers that the given method is called by the current
* method in the method info builder.
*/
def genApplyMethod(receiver: js.Tree,
methodSym: Symbol, arguments: List[js.Tree])(
implicit pos: Position): js.Tree = {
genApplyMethod(receiver, encodeMethodSym(methodSym),
arguments, toIRType(methodSym.tpe.resultType))
}
/** Gen JS code for a call to a Scala method.
* This also registers that the given method is called by the current
* method in the method info builder.
*/
def genApplyMethod(receiver: js.Tree, methodIdent: js.Ident,
arguments: List[js.Tree], resultType: jstpe.Type)(
implicit pos: Position): js.Tree = {
js.Apply(receiver, methodIdent, arguments)(resultType)
}
/** Gen JS code for a call to a Scala class constructor.
*
* This also registers that the given class is instantiated by the current
* method, and that the given constructor is called, in the method info
* builder.
*/
def genNew(clazz: Symbol, ctor: Symbol, arguments: List[js.Tree])(
implicit pos: Position): js.Tree = {
assert(!isRawJSFunctionDef(clazz),
s"Trying to instantiate a raw JS function def $clazz")
val className = encodeClassFullName(clazz)
val ctorIdent = encodeMethodSym(ctor)
js.New(jstpe.ClassType(className), ctorIdent, arguments)
}
/** Gen JS code for a call to a constructor of a hijacked boxed class.
* All of these have 2 constructors: one with the primitive
* value, which is erased, and one with a String, which is
* equivalent to BoxedClass.valueOf(arg).
*/
private def genNewHijackedBoxedClass(clazz: Symbol, ctor: Symbol,
arguments: List[js.Tree])(implicit pos: Position): js.Tree = {
assert(arguments.size == 1)
if (isStringType(ctor.tpe.params.head.tpe)) {
// BoxedClass.valueOf(arg)
val companion = clazz.companionModule.moduleClass
val valueOf = getMemberMethod(companion, nme.valueOf) suchThat { s =>
s.tpe.params.size == 1 && isStringType(s.tpe.params.head.tpe)
}
genApplyMethod(genLoadModule(companion), valueOf, arguments)
} else {
// erased
arguments.head
}
}
/** Gen JS code for creating a new Array: new Array[T](length)
* For multidimensional arrays (dimensions > 1), the arguments can
* specify up to `dimensions` lengths for the first dimensions of the
* array.
*/
def genNewArray(arrayType: jstpe.ArrayType, arguments: List[js.Tree])(
implicit pos: Position): js.Tree = {
assert(arguments.length <= arrayType.dimensions,
"too many arguments for array constructor: found " + arguments.length +
" but array has only " + arrayType.dimensions + " dimension(s)")
js.NewArray(arrayType, arguments)
}
/** Gen JS code for an array literal.
*/
def genArrayValue(tree: Tree): js.Tree = {
implicit val pos = tree.pos
val ArrayValue(tpt @ TypeTree(), elems) = tree
val arrType = toReferenceType(tree.tpe).asInstanceOf[jstpe.ArrayType]
js.ArrayValue(arrType, elems map genExpr)
}
/** Gen JS code for a Match, i.e., a switch-able pattern match.
*
* In most cases, this straightforwardly translates to a Match in the IR,
* which will eventually become a `switch` in JavaScript.
*
* However, sometimes there is a guard in here, despite the fact that
* matches cannot have guards (in the JVM nor in the IR). The JVM backend
* emits a jump to the default clause when a guard is not fulfilled. We
* cannot do that, since we do not have arbitrary jumps. We therefore use
* a funny encoding with two nested `Labeled` blocks. For example,
* {{{
* x match {
* case 1 if y > 0 => a
* case 2 => b
* case _ => c
* }
* }}}
* arrives at the back-end as
* {{{
* x match {
* case 1 =>
* if (y > 0)
* a
* else
* default()
* case 2 =>
* b
* case _ =>
* default() {
* c
* }
* }
* }}}
* which we then translate into the following IR:
* {{{
* matchResult[I]: {
* default[V]: {
* x match {
* case 1 =>
* return(matchResult) if (y > 0)
* a
* else
* return(default) (void 0)
* case 2 =>
* return(matchResult) b
* case _ =>
* ()
* }
* }
* c
* }
* }}}
*/
def genMatch(tree: Tree, isStat: Boolean): js.Tree = {
implicit val pos = tree.pos
val Match(selector, cases) = tree
val expr = genExpr(selector)
val resultType = toIRType(tree.tpe)
val defaultLabelSym = cases.collectFirst {
case CaseDef(Ident(nme.WILDCARD), EmptyTree,
body @ LabelDef(_, Nil, rhs)) if hasSynthCaseSymbol(body) =>
body.symbol
}.getOrElse(NoSymbol)
var clauses: List[(List[js.Literal], js.Tree)] = Nil
var optElseClause: Option[js.Tree] = None
var optElseClauseLabel: Option[js.Ident] = None
def genJumpToElseClause(implicit pos: ir.Position): js.Tree = {
if (optElseClauseLabel.isEmpty)
optElseClauseLabel = Some(freshLocalIdent("default"))
js.Return(js.Undefined(), optElseClauseLabel)
}
for (caze @ CaseDef(pat, guard, body) <- cases) {
assert(guard == EmptyTree)
def genBody(body: Tree): js.Tree = body match {
case app @ Apply(_, Nil) if app.symbol == defaultLabelSym =>
genJumpToElseClause
case Block(List(app @ Apply(_, Nil)), _) if app.symbol == defaultLabelSym =>
genJumpToElseClause
case If(cond, thenp, elsep) =>
js.If(genExpr(cond), genBody(thenp), genBody(elsep))(
resultType)(body.pos)
/* For #1955. If we receive a tree with the shape
* if (cond) {
* thenp
* } else {
* elsep
* }
* scala.runtime.BoxedUnit.UNIT
* we rewrite it as
* if (cond) {
* thenp
* scala.runtime.BoxedUnit.UNIT
* } else {
* elsep
* scala.runtime.BoxedUnit.UNIT
* }
* so that it fits the shape of if/elses we can deal with.
*/
case Block(List(If(cond, thenp, elsep)), s: Select)
if s.symbol == definitions.BoxedUnit_UNIT =>
val newThenp = Block(thenp, s).setType(s.tpe).setPos(thenp.pos)
val newElsep = Block(elsep, s).setType(s.tpe).setPos(elsep.pos)
js.If(genExpr(cond), genBody(newThenp), genBody(newElsep))(
resultType)(body.pos)
case _ =>
genStatOrExpr(body, isStat)
}
def genLiteral(lit: Literal): js.Literal =
genExpr(lit).asInstanceOf[js.Literal]
pat match {
case lit: Literal =>
clauses = (List(genLiteral(lit)), genBody(body)) :: clauses
case Ident(nme.WILDCARD) =>
optElseClause = Some(body match {
case LabelDef(_, Nil, rhs) if hasSynthCaseSymbol(body) =>
genBody(rhs)
case _ =>
genBody(body)
})
case Alternative(alts) =>
val genAlts = {
alts map {
case lit: Literal => genLiteral(lit)
case _ =>
abort("Invalid case in alternative in switch-like pattern match: " +
tree + " at: " + tree.pos)
}
}
clauses = (genAlts, genBody(body)) :: clauses
case _ =>
abort("Invalid case statement in switch-like pattern match: " +
tree + " at: " + (tree.pos))
}
}
val elseClause = optElseClause.getOrElse(
throw new AssertionError("No elseClause in pattern match"))
optElseClauseLabel.fold[js.Tree] {
js.Match(expr, clauses.reverse, elseClause)(resultType)
} { elseClauseLabel =>
val matchResultLabel = freshLocalIdent("matchResult")
val patchedClauses = for ((alts, body) <- clauses) yield {
implicit val pos = body.pos
val lab = Some(matchResultLabel)
val newBody =
if (isStat) js.Block(body, js.Return(js.Undefined(), lab))
else js.Return(body, lab)
(alts, newBody)
}
js.Labeled(matchResultLabel, resultType, js.Block(List(
js.Labeled(elseClauseLabel, jstpe.NoType, {
js.Match(expr, patchedClauses.reverse, js.Skip())(jstpe.NoType)
}),
elseClause
)))
}
}
private def genBlock(tree: Block, isStat: Boolean): js.Tree = {
implicit val pos = tree.pos
val Block(stats, expr) = tree
/** Predicate satisfied by LabelDefs produced by the pattern matcher */
def isCaseLabelDef(tree: Tree) =
tree.isInstanceOf[LabelDef] && hasSynthCaseSymbol(tree)
def translateMatch(expr: LabelDef) = {
/* Block that appeared as the result of a translated match
* Such blocks are recognized by having at least one element that is
* a so-called case-label-def.
* The method `genTranslatedMatch()` takes care of compiling the
* actual match.
*
* The assumption is once we encounter a case, the remainder of the
* block will consist of cases.
* The prologue may be empty, usually it is the valdef that stores
* the scrut.
*/
val (prologue, cases) = stats.span(s => !isCaseLabelDef(s))
assert(cases.forall(isCaseLabelDef),
"Assumption on the form of translated matches broken: " + tree)
val genPrologue = prologue map genStat
val translatedMatch =
genTranslatedMatch(cases.map(_.asInstanceOf[LabelDef]), expr)
js.Block(genPrologue :+ translatedMatch)
}
expr match {
case expr: LabelDef if isCaseLabelDef(expr) =>
translateMatch(expr)
// Sometimes the pattern matcher casts its final result
case Apply(TypeApply(Select(expr: LabelDef, nme.asInstanceOf_Ob), _), _)
if isCaseLabelDef(expr) =>
translateMatch(expr)
case _ =>
assert(!stats.exists(isCaseLabelDef), "Found stats with case label " +
s"def in non-match block at ${tree.pos}: $tree")
/* Normal block */
val statements = stats map genStat
val expression = genStatOrExpr(expr, isStat)
js.Block(statements :+ expression)
}
}
/** Gen JS code for a translated match
*
* This implementation relies heavily on the patterns of trees emitted
* by the current pattern match phase (as of Scala 2.10).
*
* The trees output by the pattern matcher are assumed to follow these
* rules:
* * Each case LabelDef (in `cases`) must not take any argument.
* * The last one must be a catch-all (case _ =>) that never falls through.
* * Jumps to the `matchEnd` are allowed anywhere in the body of the
* corresponding case label-defs, but not outside.
* * Jumps to case label-defs are restricted to jumping to the very next
* case, and only in positions denoted by <jump> in:
* <case-body> ::=
* If(_, <case-body>, <case-body>)
* | Block(_, <case-body>)
* | <jump>
* | _
* These restrictions, together with the fact that we are in statement
* position (thanks to the above transformation), mean that they can be
* simply replaced by `skip`.
*
* To implement jumps to `matchEnd`, which have one argument which is the
* result of the match, we enclose all the cases in one big labeled block.
* Jumps are then compiled as `return`s out of the block.
*/
def genTranslatedMatch(cases: List[LabelDef],
matchEnd: LabelDef)(implicit pos: Position): js.Tree = {
val matchEndSym = matchEnd.symbol
countsOfReturnsToMatchEnd(matchEndSym) = 0
val nextCaseSyms = (cases.tail map (_.symbol)) :+ NoSymbol
val translatedCases = for {
(LabelDef(_, Nil, rhs), nextCaseSym) <- cases zip nextCaseSyms
} yield {
def genCaseBody(tree: Tree): js.Tree = {
implicit val pos = tree.pos
tree match {
case If(cond, thenp, elsep) =>
js.If(genExpr(cond), genCaseBody(thenp), genCaseBody(elsep))(
jstpe.NoType)
case Block(stats, expr) =>
js.Block((stats map genStat) :+ genCaseBody(expr))
case Apply(_, Nil) if tree.symbol == nextCaseSym =>
js.Skip()
case _ =>
genStat(tree)
}
}
genCaseBody(rhs)
}
val returnCount = countsOfReturnsToMatchEnd.remove(matchEndSym).get
val LabelDef(_, List(matchEndParam), matchEndBody) = matchEnd
val innerResultType = toIRType(matchEndParam.tpe)
val optimized = genOptimizedLabeled(encodeLabelSym(matchEndSym),
innerResultType, translatedCases, returnCount)
matchEndBody match {
case Ident(_) if matchEndParam.symbol == matchEndBody.symbol =>
// matchEnd is identity.
optimized
case Literal(Constant(())) =>
// Unit return type.
optimized
case _ =>
// matchEnd does something.
val ident = encodeLocalSym(matchEndParam.symbol)
js.Block(
js.VarDef(ident, innerResultType, mutable = false, optimized),
genExpr(matchEndBody))
}
}
/** Gen JS code for a Labeled block from a pattern match, while trying
* to optimize it away as an If chain.
*
* It is important to do so at compile-time because, when successful, the
* resulting IR can be much better optimized by the optimizer.
*
* The optimizer also does something similar, but *after* it has processed
* the body of the Labeled block, at which point it has already lost any
* information about stack-allocated values.
*
* !!! There is quite of bit of code duplication with
* OptimizerCore.tryOptimizePatternMatch.
*/
def genOptimizedLabeled(label: js.Ident, tpe: jstpe.Type,
translatedCases: List[js.Tree], returnCount: Int)(
implicit pos: Position): js.Tree = {
def default: js.Tree =
js.Labeled(label, tpe, js.Block(translatedCases))
@tailrec
def createRevAlts(xs: List[js.Tree],
acc: List[(js.Tree, js.Tree)]): (List[(js.Tree, js.Tree)], js.Tree) = xs match {
case js.If(cond, body, js.Skip()) :: xr =>
createRevAlts(xr, (cond, body) :: acc)
case remaining =>
(acc, js.Block(remaining)(remaining.head.pos))
}
val (revAlts, elsep) = createRevAlts(translatedCases, Nil)
if (revAlts.size == returnCount - 1) {
def tryDropReturn(body: js.Tree): Option[js.Tree] = body match {
case jse.BlockOrAlone(prep, js.Return(result, Some(`label`))) =>
Some(js.Block(prep :+ result)(body.pos))
case _ =>
None
}
@tailrec
def constructOptimized(revAlts: List[(js.Tree, js.Tree)],
elsep: js.Tree): js.Tree = {
revAlts match {
case (cond, body) :: revAltsRest =>
// cannot use flatMap due to tailrec
tryDropReturn(body) match {
case Some(newBody) =>
constructOptimized(revAltsRest,
js.If(cond, newBody, elsep)(tpe)(cond.pos))
case None =>
default
}
case Nil =>
elsep
}
}
tryDropReturn(elsep).fold(default)(constructOptimized(revAlts, _))
} else {
default
}
}
/** Gen JS code for a primitive method call */
private def genPrimitiveOp(tree: Apply, isStat: Boolean): js.Tree = {
import scalaPrimitives._
implicit val pos = tree.pos
val sym = tree.symbol
val Apply(fun @ Select(receiver, _), args) = tree
val code = scalaPrimitives.getPrimitive(sym, receiver.tpe)
if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code))
genSimpleOp(tree, receiver :: args, code)
else if (code == scalaPrimitives.CONCAT)
genStringConcat(tree, receiver, args)
else if (code == HASH)
genScalaHash(tree, receiver)
else if (isArrayOp(code))
genArrayOp(tree, code)
else if (code == SYNCHRONIZED)
genSynchronized(receiver, args.head, isStat)
else if (isCoercion(code))
genCoercion(tree, receiver, code)
else if (jsPrimitives.isJavaScriptPrimitive(code))
genJSPrimitive(tree, receiver, args, code)
else
abort("Unknown primitive operation: " + sym.fullName + "(" +
fun.symbol.simpleName + ") " + " at: " + (tree.pos))
}
/** Gen JS code for a simple operation (arithmetic, logical, or comparison) */
private def genSimpleOp(tree: Apply, args: List[Tree], code: Int): js.Tree = {
import scalaPrimitives._
implicit val pos = tree.pos
val isShift = isShiftOp(code)
def isLongOp(ltpe: Type, rtpe: Type) = {
if (isShift) {
isLongType(ltpe)
} else {
(isLongType(ltpe) || isLongType(rtpe)) &&
!(toTypeKind(ltpe).isInstanceOf[FLOAT] ||
toTypeKind(rtpe).isInstanceOf[FLOAT] ||
isStringType(ltpe) || isStringType(rtpe))
}
}
val sources = args map genExpr
val resultType = toIRType(tree.tpe)
sources match {
// Unary operation
case List(source) =>
(code match {
case POS =>
source
case NEG =>
(resultType: @unchecked) match {
case jstpe.IntType =>
js.BinaryOp(js.BinaryOp.Int_-, js.IntLiteral(0), source)
case jstpe.LongType =>
js.BinaryOp(js.BinaryOp.Long_-, js.LongLiteral(0), source)
case jstpe.FloatType =>
js.BinaryOp(js.BinaryOp.Float_-, js.FloatLiteral(0.0f), source)
case jstpe.DoubleType =>
js.BinaryOp(js.BinaryOp.Double_-, js.DoubleLiteral(0), source)
}
case NOT =>
(resultType: @unchecked) match {
case jstpe.IntType =>
js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(-1), source)
case jstpe.LongType =>
js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(-1), source)
}
case ZNOT =>
js.UnaryOp(js.UnaryOp.Boolean_!, source)
case _ =>
abort("Unknown unary operation code: " + code)
})
// Binary operation on Longs
case List(lsrc, rsrc) if isLongOp(args(0).tpe, args(1).tpe) =>
def toLong(tree: js.Tree, tpe: Type) =
if (isLongType(tpe)) tree
else js.UnaryOp(js.UnaryOp.IntToLong, tree)
def toInt(tree: js.Tree, tpe: Type) =
if (isLongType(tpe)) js.UnaryOp(js.UnaryOp.LongToInt, rsrc)
else tree
val ltree = toLong(lsrc, args(0).tpe)
def rtree = toLong(rsrc, args(1).tpe)
def rtreeInt = toInt(rsrc, args(1).tpe)
import js.BinaryOp._
(code: @switch) match {
case ADD => js.BinaryOp(Long_+, ltree, rtree)
case SUB => js.BinaryOp(Long_-, ltree, rtree)
case MUL => js.BinaryOp(Long_*, ltree, rtree)
case DIV => js.BinaryOp(Long_/, ltree, rtree)
case MOD => js.BinaryOp(Long_%, ltree, rtree)
case OR => js.BinaryOp(Long_|, ltree, rtree)
case XOR => js.BinaryOp(Long_^, ltree, rtree)
case AND => js.BinaryOp(Long_&, ltree, rtree)
case LSL => js.BinaryOp(Long_<<, ltree, rtreeInt)
case LSR => js.BinaryOp(Long_>>>, ltree, rtreeInt)
case ASR => js.BinaryOp(Long_>>, ltree, rtreeInt)
case EQ => js.BinaryOp(Long_==, ltree, rtree)
case NE => js.BinaryOp(Long_!=, ltree, rtree)
case LT => js.BinaryOp(Long_<, ltree, rtree)
case LE => js.BinaryOp(Long_<=, ltree, rtree)
case GT => js.BinaryOp(Long_>, ltree, rtree)
case GE => js.BinaryOp(Long_>=, ltree, rtree)
case _ =>
abort("Unknown binary operation code: " + code)
}
// Binary operation
case List(lsrc_in, rsrc_in) =>
val leftKind = toTypeKind(args(0).tpe)
val rightKind = toTypeKind(args(1).tpe)
val opType = (leftKind, rightKind) match {
case (DoubleKind, _) | (_, DoubleKind) => jstpe.DoubleType
case (FloatKind, _) | (_, FloatKind) => jstpe.FloatType
case (INT(_), _) | (_, INT(_)) => jstpe.IntType
case (BooleanKind, BooleanKind) => jstpe.BooleanType
case _ => jstpe.AnyType
}
def convertArg(tree: js.Tree, kind: TypeKind) = {
/* If we end up with a long, the op type must be float or double,
* so we can first eliminate the Long case by converting to Double.
*
* Unless it is a shift operation, in which case the op type would
* be int.
*/
val notLong = {
if (kind != LongKind) tree
else if (isShift) js.UnaryOp(js.UnaryOp.LongToInt, tree)
else js.UnaryOp(js.UnaryOp.LongToDouble, tree)
}
if (opType != jstpe.FloatType) notLong
else if (kind == FloatKind) notLong
else js.UnaryOp(js.UnaryOp.DoubleToFloat, notLong)
}
val lsrc = convertArg(lsrc_in, leftKind)
val rsrc = convertArg(rsrc_in, rightKind)
def genEquality(eqeq: Boolean, not: Boolean) = {
opType match {
case jstpe.IntType | jstpe.DoubleType | jstpe.FloatType =>
js.BinaryOp(
if (not) js.BinaryOp.Num_!= else js.BinaryOp.Num_==,
lsrc, rsrc)
case jstpe.BooleanType =>
js.BinaryOp(
if (not) js.BinaryOp.Boolean_!= else js.BinaryOp.Boolean_==,
lsrc, rsrc)
case _ =>
if (eqeq &&
// don't call equals if we have a literal null at either side
!lsrc.isInstanceOf[js.Null] &&
!rsrc.isInstanceOf[js.Null] &&
// Arrays, Null, Nothing do not have an equals() method
leftKind.isInstanceOf[REFERENCE]) {
val body = genEqEqPrimitive(args(0).tpe, args(1).tpe, lsrc, rsrc)
if (not) js.UnaryOp(js.UnaryOp.Boolean_!, body) else body
} else {
js.BinaryOp(
if (not) js.BinaryOp.!== else js.BinaryOp.===,
lsrc, rsrc)
}
}
}
(code: @switch) match {
case EQ => genEquality(eqeq = true, not = false)
case NE => genEquality(eqeq = true, not = true)
case ID => genEquality(eqeq = false, not = false)
case NI => genEquality(eqeq = false, not = true)
case ZOR => js.If(lsrc, js.BooleanLiteral(true), rsrc)(jstpe.BooleanType)
case ZAND => js.If(lsrc, rsrc, js.BooleanLiteral(false))(jstpe.BooleanType)
case _ =>
import js.BinaryOp._
val op = (resultType: @unchecked) match {
case jstpe.IntType =>
(code: @switch) match {
case ADD => Int_+
case SUB => Int_-
case MUL => Int_*
case DIV => Int_/
case MOD => Int_%
case OR => Int_|
case AND => Int_&
case XOR => Int_^
case LSL => Int_<<
case LSR => Int_>>>
case ASR => Int_>>
}
case jstpe.FloatType =>
(code: @switch) match {
case ADD => Float_+
case SUB => Float_-
case MUL => Float_*
case DIV => Float_/
case MOD => Float_%
}
case jstpe.DoubleType =>
(code: @switch) match {
case ADD => Double_+
case SUB => Double_-
case MUL => Double_*
case DIV => Double_/
case MOD => Double_%
}
case jstpe.BooleanType =>
(code: @switch) match {
case LT => Num_<
case LE => Num_<=
case GT => Num_>
case GE => Num_>=
case OR => Boolean_|
case AND => Boolean_&
case XOR => Boolean_!=
}
}
js.BinaryOp(op, lsrc, rsrc)
}
case _ =>
abort("Too many arguments for primitive function: " + tree)
}
}
/** Gen JS code for a call to Any.== */
def genEqEqPrimitive(ltpe: Type, rtpe: Type, lsrc: js.Tree, rsrc: js.Tree)(
implicit pos: Position): js.Tree = {
/* True if the equality comparison is between values that require the
* use of the rich equality comparator
* (scala.runtime.BoxesRunTime.equals).
* This is the case when either side of the comparison might have a
* run-time type subtype of java.lang.Number or java.lang.Character,
* **which includes when either is a raw JS type**.
* When it is statically known that both sides are equal and subtypes of
* Number or Character, not using the rich equality is possible (their
* own equals method will do ok.)
*/
val mustUseAnyComparator: Boolean = isRawJSType(ltpe) || isRawJSType(rtpe) || {
val areSameFinals = ltpe.isFinalType && rtpe.isFinalType && (ltpe =:= rtpe)
!areSameFinals && isMaybeBoxed(ltpe.typeSymbol) && isMaybeBoxed(rtpe.typeSymbol)
}
if (mustUseAnyComparator) {
val equalsMethod: Symbol = {
// scalastyle:off line.size.limit
val ptfm = platform.asInstanceOf[backend.JavaPlatform with ThisPlatform] // 2.10 compat
if (ltpe <:< BoxedNumberClass.tpe) {
if (rtpe <:< BoxedNumberClass.tpe) ptfm.externalEqualsNumNum
else if (rtpe <:< BoxedCharacterClass.tpe) ptfm.externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030
else ptfm.externalEqualsNumObject
} else ptfm.externalEquals
// scalastyle:on line.size.limit
}
val moduleClass = equalsMethod.owner
val instance = genLoadModule(moduleClass)
genApplyMethod(instance, equalsMethod, List(lsrc, rsrc))
} else {
// if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc)
if (isStringType(ltpe)) {
// String.equals(that) === (this eq that)
js.BinaryOp(js.BinaryOp.===, lsrc, rsrc)
} else {
/* This requires to evaluate both operands in local values first.
* The optimizer will eliminate them if possible.
*/
val ltemp = js.VarDef(freshLocalIdent(), lsrc.tpe, mutable = false, lsrc)
val rtemp = js.VarDef(freshLocalIdent(), rsrc.tpe, mutable = false, rsrc)
js.Block(
ltemp,
rtemp,
js.If(js.BinaryOp(js.BinaryOp.===, ltemp.ref, js.Null()),
js.BinaryOp(js.BinaryOp.===, rtemp.ref, js.Null()),
genApplyMethod(ltemp.ref, Object_equals, List(rtemp.ref)))(
jstpe.BooleanType))
}
}
}
/** Gen JS code for string concatenation.
*/
private def genStringConcat(tree: Apply, receiver: Tree,
args: List[Tree]): js.Tree = {
implicit val pos = tree.pos
/* Primitive number types such as scala.Int have a
* def +(s: String): String
* method, which is why we have to box the lhs sometimes.
* Otherwise, both lhs and rhs are already reference types (Any of String)
* so boxing is not necessary (in particular, rhs is never a primitive).
*/
assert(!isPrimitiveValueType(receiver.tpe) || isStringType(args.head.tpe))
assert(!isPrimitiveValueType(args.head.tpe))
val rhs = genExpr(args.head)
val lhs = {
val lhs0 = genExpr(receiver)
// Box the receiver if it is a primitive value
if (!isPrimitiveValueType(receiver.tpe)) lhs0
else makePrimitiveBox(lhs0, receiver.tpe)
}
js.BinaryOp(js.BinaryOp.String_+, lhs, rhs)
}
/** Gen JS code for a call to `Any.##`.
*
* This method unconditionally generates a call to `Statics.anyHash`.
* On the JVM, `anyHash` is only called as of 2.12.0-M5. Previous versions
* emitted a call to `ScalaRunTime.hash`. However, since our `anyHash`
* is always consistent with `ScalaRunTime.hash`, we always use it.
*/
private def genScalaHash(tree: Apply, receiver: Tree): js.Tree = {
implicit val pos = tree.pos
val instance = genLoadModule(RuntimeStaticsModule)
val arguments = List(genExpr(receiver))
val sym = getMember(RuntimeStaticsModule, jsnme.anyHash)
genApplyMethod(instance, sym, arguments)
}
/** Gen JS code for an array operation (get, set or length) */
private def genArrayOp(tree: Tree, code: Int): js.Tree = {
import scalaPrimitives._
implicit val pos = tree.pos
val Apply(Select(arrayObj, _), args) = tree
val arrayValue = genExpr(arrayObj)
val arguments = args map genExpr
def genSelect() = {
val elemIRType =
toTypeKind(arrayObj.tpe).asInstanceOf[ARRAY].elem.toIRType
js.ArraySelect(arrayValue, arguments(0))(elemIRType)
}
if (scalaPrimitives.isArrayGet(code)) {
// get an item of the array
assert(args.length == 1,
s"Array get requires 1 argument, found ${args.length} in $tree")
genSelect()
} else if (scalaPrimitives.isArraySet(code)) {
// set an item of the array
assert(args.length == 2,
s"Array set requires 2 arguments, found ${args.length} in $tree")
js.Assign(genSelect(), arguments(1))
} else {
// length of the array
js.ArrayLength(arrayValue)
}
}
/** Gen JS code for a call to AnyRef.synchronized */
private def genSynchronized(receiver: Tree, arg: Tree, isStat: Boolean)(
implicit pos: Position): js.Tree = {
/* JavaScript is single-threaded, so we can drop the
* synchronization altogether.
*/
val newReceiver = genExpr(receiver)
val newArg = genStatOrExpr(arg, isStat)
newReceiver match {
case js.This() =>
// common case for which there is no side-effect nor NPE
newArg
case _ =>
val NPECtor = getMemberMethod(NullPointerExceptionClass,
nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty)
js.Block(
js.If(js.BinaryOp(js.BinaryOp.===, newReceiver, js.Null()),
js.Throw(genNew(NullPointerExceptionClass, NPECtor, Nil)),
js.Skip())(jstpe.NoType),
newArg)
}
}
/** Gen JS code for a coercion */
private def genCoercion(tree: Apply, receiver: Tree, code: Int): js.Tree = {
import scalaPrimitives._
implicit val pos = tree.pos
val source = genExpr(receiver)
def source2int = (code: @switch) match {
case F2C | D2C | F2B | D2B | F2S | D2S | F2I | D2I =>
js.UnaryOp(js.UnaryOp.DoubleToInt, source)
case L2C | L2B | L2S | L2I =>
js.UnaryOp(js.UnaryOp.LongToInt, source)
case _ =>
source
}
(code: @switch) match {
// To Char, need to crop at unsigned 16-bit
case B2C | S2C | I2C | L2C | F2C | D2C =>
js.BinaryOp(js.BinaryOp.Int_&, source2int, js.IntLiteral(0xffff))
// To Byte, need to crop at signed 8-bit
case C2B | S2B | I2B | L2B | F2B | D2B =>
// note: & 0xff would not work because of negative values
js.BinaryOp(js.BinaryOp.Int_>>,
js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(24)),
js.IntLiteral(24))
// To Short, need to crop at signed 16-bit
case C2S | I2S | L2S | F2S | D2S =>
// note: & 0xffff would not work because of negative values
js.BinaryOp(js.BinaryOp.Int_>>,
js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(16)),
js.IntLiteral(16))
// To Int, need to crop at signed 32-bit
case L2I | F2I | D2I =>
source2int
// Any int to Long
case C2L | B2L | S2L | I2L =>
js.UnaryOp(js.UnaryOp.IntToLong, source)
// Any double to Long
case F2L | D2L =>
js.UnaryOp(js.UnaryOp.DoubleToLong, source)
// Long to Double
case L2D =>
js.UnaryOp(js.UnaryOp.LongToDouble, source)
// Any int, or Double, to Float
case C2F | B2F | S2F | I2F | D2F =>
js.UnaryOp(js.UnaryOp.DoubleToFloat, source)
// Long to Float === Long to Double to Float
case L2F =>
js.UnaryOp(js.UnaryOp.DoubleToFloat,
js.UnaryOp(js.UnaryOp.LongToDouble, source))
// Identities and IR upcasts
case C2C | B2B | S2S | I2I | L2L | F2F | D2D |
C2I | C2D |
B2S | B2I | B2D |
S2I | S2D |
I2D