aboutsummaryrefslogtreecommitdiff
path: root/sjs/backend/sjs/JSCodeGen.scala
diff options
context:
space:
mode:
Diffstat (limited to 'sjs/backend/sjs/JSCodeGen.scala')
-rw-r--r--sjs/backend/sjs/JSCodeGen.scala2392
1 files changed, 2392 insertions, 0 deletions
diff --git a/sjs/backend/sjs/JSCodeGen.scala b/sjs/backend/sjs/JSCodeGen.scala
new file mode 100644
index 000000000..401e01784
--- /dev/null
+++ b/sjs/backend/sjs/JSCodeGen.scala
@@ -0,0 +1,2392 @@
+package dotty.tools.backend.sjs
+
+import scala.annotation.switch
+
+import scala.collection.mutable
+
+import dotty.tools.FatalError
+
+import dotty.tools.dotc.CompilationUnit
+import dotty.tools.dotc.ast.tpd
+import dotty.tools.dotc.core.Phases.Phase
+
+import dotty.tools.dotc.core._
+import Periods._
+import SymDenotations._
+import Contexts._
+import Decorators._
+import Flags._
+import dotty.tools.dotc.ast.Trees._
+import Types._
+import Symbols._
+import Denotations._
+import Phases._
+import StdNames._
+
+import dotty.tools.dotc.transform.Erasure
+
+import org.scalajs.core.ir
+import org.scalajs.core.ir.{ClassKind, Position, Trees => js, Types => jstpe}
+import js.OptimizerHints
+
+import JSEncoding._
+import JSInterop._
+import ScopedVar.withScopedVars
+
+/** Main codegen for Scala.js IR.
+ *
+ * [[GenSJSIR]] creates one instance of `JSCodeGen` per compilation unit.
+ * The `run()` method processes the whole compilation unit and generates
+ * `.sjsir` files for it.
+ *
+ * There are 4 main levels of translation:
+ *
+ * - `genCompilationUnit()` iterates through all the type definitions in the
+ * compilation unit. Each generated `js.ClassDef` is serialized to an
+ * `.sjsir` file.
+ * - `genScalaClass()` and other similar methods generate the skeleton of
+ * classes.
+ * - `genMethod()` and similar methods generate the declarations of methods.
+ * - `genStatOrExpr()` and everything else generate the bodies of methods.
+ */
+class JSCodeGen()(implicit ctx: Context) {
+ import tpd._
+
+ private val jsdefn = JSDefinitions.jsdefn
+ private val primitives = new JSPrimitives(ctx)
+
+ private val positionConversions = new JSPositions()(ctx)
+ import positionConversions.{pos2irPos, implicitPos2irPos}
+
+ // Some state --------------------------------------------------------------
+
+ private val currentClassSym = new ScopedVar[Symbol]
+ private val currentMethodSym = new ScopedVar[Symbol]
+ private val localNames = new ScopedVar[LocalNameGenerator]
+ private val thisLocalVarIdent = new ScopedVar[Option[js.Ident]]
+ private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]]
+
+ /** Implicitly materializes the current local name generator. */
+ private implicit def implicitLocalNames: LocalNameGenerator = localNames.get
+
+ /* See genSuperCall()
+ * TODO Can we avoid this unscoped var?
+ */
+ private var isModuleInitialized: Boolean = false
+
+ private def currentClassType = encodeClassType(currentClassSym)
+
+ /** Returns a new fresh local identifier. */
+ private def freshLocalIdent()(implicit pos: Position): js.Ident =
+ localNames.get.freshLocalIdent()
+
+ /** Returns a new fresh local identifier. */
+ private def freshLocalIdent(base: String)(implicit pos: Position): js.Ident =
+ localNames.get.freshLocalIdent(base)
+
+ // Compilation unit --------------------------------------------------------
+
+ def run(): Unit = {
+ genCompilationUnit(ctx.compilationUnit)
+ }
+
+ /** 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
+ *
+ * TODO 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()`
+ * * Normal class -> `genClass()`
+ */
+ private def genCompilationUnit(cunit: CompilationUnit): Unit = {
+ def collectTypeDefs(tree: Tree): List[TypeDef] = {
+ tree match {
+ case EmptyTree => Nil
+ case PackageDef(_, stats) => stats.flatMap(collectTypeDefs)
+ case cd: TypeDef => cd :: Nil
+ case _: ValDef => Nil // module instance
+ }
+ }
+ val allTypeDefs = collectTypeDefs(cunit.tpdTree)
+
+ val generatedClasses = mutable.ListBuffer.empty[(Symbol, js.ClassDef)]
+
+ // TODO Record anonymous JS function classes
+
+ /* Finally, we emit true code for the remaining class defs. */
+ for (td <- allTypeDefs) {
+ val sym = td.symbol
+ implicit val pos: Position = sym.pos
+
+ /* Do not actually emit code for primitive types nor scala.Array. */
+ val isPrimitive =
+ sym.isPrimitiveValueClass || sym == defn.ArrayClass
+
+ if (!isPrimitive) {
+ withScopedVars(
+ currentClassSym := sym
+ ) {
+ val tree = if (isJSType(sym)) {
+ /*assert(!isRawJSFunctionDef(sym),
+ s"Raw JS function def should have been recorded: $cd")*/
+ if (!sym.is(Trait) && isScalaJSDefinedJSClass(sym))
+ genScalaJSDefinedJSClass(td)
+ else
+ genRawJSClassData(td)
+ } else if (sym.is(Trait)) {
+ genInterface(td)
+ } else {
+ genScalaClass(td)
+ }
+
+ generatedClasses += ((sym, tree))
+ }
+ }
+ }
+
+ val clDefs = generatedClasses.map(_._2).toList
+
+ for ((sym, tree) <- generatedClasses) {
+ val writer = new java.io.PrintWriter(System.err)
+ try {
+ new ir.Printers.IRTreePrinter(writer).print(tree)
+ } finally {
+ writer.flush()
+ }
+ genIRFile(cunit, sym, tree)
+ }
+ }
+
+ private def genIRFile(cunit: CompilationUnit, sym: Symbol,
+ tree: ir.Trees.ClassDef): Unit = {
+ val outfile = getFileFor(cunit, sym, ".sjsir")
+ val output = outfile.bufferedOutput
+ try {
+ ir.InfoSerializers.serialize(output, ir.Infos.generateClassInfo(tree))
+ ir.Serializers.serialize(output, tree)
+ } finally {
+ output.close()
+ }
+ }
+
+ private def getFileFor(cunit: CompilationUnit, sym: Symbol,
+ suffix: String) = {
+ import scala.reflect.io._
+
+ val outputDirectory: AbstractFile = // TODO Support virtual files
+ new PlainDirectory(new Directory(new java.io.File(ctx.settings.d.value)))
+
+ val pathParts = sym.fullName.toString.split("[./]")
+ val dir = (outputDirectory /: pathParts.init)(_.subdirectoryNamed(_))
+
+ var filename = pathParts.last
+ if (sym.is(ModuleClass))
+ filename = filename + nme.MODULE_SUFFIX.toString
+
+ dir fileNamed (filename + suffix)
+ }
+
+ // Generate a class --------------------------------------------------------
+
+ /** Gen the IR ClassDef for a Scala class definition (maybe a module class).
+ */
+ private def genScalaClass(td: TypeDef): js.ClassDef = {
+ val sym = td.symbol.asClass
+ implicit val pos: Position = sym.pos
+
+ assert(!sym.is(Trait),
+ "genScalaClass() 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 = false //isHijackedBoxedClass(sym)
+
+ // Optimizer hints
+
+ def isStdLibClassWithAdHocInlineAnnot(sym: Symbol): Boolean = {
+ val fullName = sym.fullName.toString
+ (fullName.startsWith("scala.Tuple") && !fullName.endsWith("$")) ||
+ (fullName.startsWith("scala.collection.mutable.ArrayOps$of"))
+ }
+
+ val shouldMarkInline = (
+ sym.hasAnnotation(jsdefn.InlineAnnot) ||
+ (sym.isAnonymousFunction && !sym.isSubClass(defn.PartialFunctionClass)) ||
+ isStdLibClassWithAdHocInlineAnnot(sym))
+
+ val optimizerHints = {
+ OptimizerHints.empty
+ .withInline(shouldMarkInline)
+ .withNoinline(sym.hasAnnotation(jsdefn.NoinlineAnnot))
+ }
+
+ // Generate members (constructor + methods)
+
+ val generatedMethods = new mutable.ListBuffer[js.MethodDef]
+ val exportedSymbols = new mutable.ListBuffer[Symbol]
+
+ val tpl = td.rhs.asInstanceOf[Template]
+ for (tree <- tpl.constr :: tpl.body) {
+ tree match {
+ case EmptyTree => ()
+
+ case _: ValDef =>
+ () // fields are added via genClassFields()
+
+ case dd: DefDef =>
+ val sym = dd.symbol
+
+ val isExport = false //jsInterop.isExport(sym)
+ val isNamedExport = false /*isExport && sym.annotations.exists(
+ _.symbol == JSExportNamedAnnotation)*/
+
+ /*if (isNamedExport)
+ generatedMethods += genNamedExporterDef(dd)
+ else*/
+ generatedMethods ++= genMethod(dd)
+
+ if (isExport) {
+ // We add symbols that we have to export here. This way we also
+ // get inherited stuff that is implemented in this class.
+ exportedSymbols += sym
+ }
+
+ case _ =>
+ throw new FatalError("Illegal tree in body of genScalaClass(): " + tree)
+ }
+ }
+
+ // Generate fields and add to methods + ctors
+ val generatedMembers = genClassFields(td) ++ generatedMethods.toList
+
+ // Generate the exported members, constructors and accessors
+ val exports = {
+ // Hack to export hello.world
+ if (sym.fullName.toString == "hello.world$") {
+ List(
+ js.ModuleExportDef("hello.world"),
+ js.MethodDef(static = false, js.StringLiteral("main"),
+ Nil, jstpe.AnyType,
+ js.Block(List(
+ js.Apply(js.This()(jstpe.ClassType(classIdent.name)), js.Ident("main__V"), Nil)(jstpe.NoType),
+ js.Undefined())))(
+ OptimizerHints.empty, None))
+ } else {
+ /*
+ // Generate the exported members
+ val memberExports = genMemberExports(sym, exportedSymbols.toList)
+
+ // Generate exported constructors or accessors
+ val exportedConstructorsOrAccessors =
+ if (isStaticModule(sym)) genModuleAccessorExports(sym)
+ else genConstructorExports(sym)
+
+ memberExports ++ exportedConstructorsOrAccessors
+ */
+ Nil
+ }
+ }
+
+ // Hashed definitions of the class
+ val hashedDefs =
+ ir.Hashers.hashDefs(generatedMembers ++ exports)
+
+ // 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. */
+ private def genScalaJSDefinedJSClass(td: TypeDef): js.ClassDef = {
+ ???
+ }
+
+ /** Gen the IR ClassDef for a raw JS class or trait.
+ */
+ private def genRawJSClassData(td: TypeDef): js.ClassDef = {
+ val sym = td.symbol.asClass
+ implicit val pos: Position = sym.pos
+
+ val classIdent = encodeClassFullNameIdent(sym)
+ val superClass =
+ if (sym.is(Trait)) None
+ else Some(encodeClassFullNameIdent(sym.superClass))
+ val jsName =
+ if (sym.is(Trait) || sym.is(ModuleClass)) None
+ else Some(fullJSNameOf(sym))
+
+ js.ClassDef(classIdent, ClassKind.RawJSType,
+ superClass,
+ genClassInterfaces(sym),
+ jsName,
+ Nil)(
+ OptimizerHints.empty)
+ }
+
+ /** Gen the IR ClassDef for an interface definition.
+ */
+ private def genInterface(td: TypeDef): js.ClassDef = {
+ val sym = td.symbol.asClass
+ implicit val pos: Position = sym.pos
+
+ val classIdent = encodeClassFullNameIdent(sym)
+
+ val generatedMethods = new mutable.ListBuffer[js.MethodDef]
+
+ val tpl = td.rhs.asInstanceOf[Template]
+ for (tree <- tpl.constr :: tpl.body) {
+ tree match {
+ case EmptyTree => ()
+ case dd: DefDef => generatedMethods ++= genMethod(dd)
+ case _ =>
+ throw new FatalError("Illegal tree in gen of genInterface(): " + tree)
+ }
+ }
+
+ val superInterfaces = genClassInterfaces(sym)
+
+ // Hashed definitions of the interface
+ val hashedDefs =
+ ir.Hashers.hashDefs(generatedMethods.toList)
+
+ js.ClassDef(classIdent, ClassKind.Interface, None, superInterfaces, None,
+ hashedDefs)(OptimizerHints.empty)
+ }
+
+ private def genClassInterfaces(sym: ClassSymbol)(
+ implicit pos: Position): List[js.Ident] = {
+ import dotty.tools.dotc.transform.SymUtils._
+ for {
+ intf <- sym.directlyInheritedTraits
+ } yield {
+ encodeClassFullNameIdent(intf)
+ }
+ }
+
+ // Generate the fields of a class ------------------------------------------
+
+ /** Gen definitions for the fields of a class.
+ */
+ private def genClassFields(td: TypeDef): List[js.FieldDef] = {
+ val classSym = td.symbol.asClass
+ assert(currentClassSym.get == classSym,
+ "genClassFields called with a ClassDef other than the current one")
+
+ // Non-method term members are fields
+ (for {
+ f <- classSym.info.decls
+ if !f.is(Method) && f.isTerm
+ } yield {
+ implicit val pos: Position = f.pos
+
+ val name =
+ /*if (isExposed(f)) js.StringLiteral(jsNameOf(f))
+ else*/ encodeFieldSym(f)
+
+ val irTpe = //if (!isScalaJSDefinedJSClass(classSym)) {
+ toIRType(f.info)
+ /*} else {
+ 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.
+ *
+ * TODO Evaluate how much of this needs to be adapted for dotc,
+ * which unboxes `null` to the zero of their underlying.
+ */
+ 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 initialized them to
+ * their true zero.
+ */
+ toIRType(f.tpe)
+ }
+ }*/
+
+ js.FieldDef(name, irTpe, f.is(Mutable))
+ }).toList
+ }
+
+ // Generate a method -------------------------------------------------------
+
+ private def genMethod(dd: DefDef): Option[js.MethodDef] = {
+ withScopedVars(
+ localNames := new LocalNameGenerator
+ ) {
+ 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
+ * - Constructors of hijacked classes
+ *
+ * Constructors are emitted by generating their body as a statement.
+ *
+ * Other (normal) methods are emitted with `genMethodBody()`.
+ */
+ private def genMethodWithCurrentLocalNameScope(dd: DefDef): Option[js.MethodDef] = {
+ implicit val pos: Position = dd.pos
+ val sym = dd.symbol
+ val vparamss = dd.vparamss
+ val rhs = dd.rhs
+
+ isModuleInitialized = false
+
+ withScopedVars(
+ currentMethodSym := sym,
+ undefinedDefaultParams := mutable.Set.empty,
+ thisLocalVarIdent := None
+ ) {
+ 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: Position = param.pos
+ js.ParamDef(encodeLocalSym(param), toIRType(param.info),
+ mutable = false, rest = false)
+ }
+
+ /*if (primitives.isPrimitive(sym)) {
+ None
+ } else*/ if (sym.is(Deferred)) {
+ Some(js.MethodDef(static = false, methodName,
+ jsParams, toIRType(patchedResultType(sym)), js.EmptyTree)(
+ OptimizerHints.empty, None))
+ } else /*if (isJSNativeCtorDefaultParam(sym)) {
+ None
+ } else if (sym.isClassConstructor && isHijackedBoxedClass(sym.owner)) {
+ None
+ } else*/ {
+ /*def isTraitImplForwarder = dd.rhs match {
+ case app: Apply => foreignIsImplClass(app.symbol.owner)
+ case _ => false
+ }*/
+
+ val shouldMarkInline = {
+ sym.hasAnnotation(jsdefn.InlineAnnot) ||
+ sym.isAnonymousFunction
+ }
+
+ val shouldMarkNoinline = {
+ sym.hasAnnotation(jsdefn.NoinlineAnnot) /*&&
+ !isTraitImplForwarder*/
+ }
+
+ 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, body1)(optimizerHints, None)
+ } else*/ if (sym.isConstructor) {
+ js.MethodDef(static = false, methodName,
+ jsParams, jstpe.NoType,
+ genStat(rhs))(optimizerHints, None)
+ } else {
+ val resultIRType = toIRType(patchedResultType(sym))
+ genMethodDef(static = false, methodName,
+ params, resultIRType, rhs, optimizerHints)
+ }
+ }
+
+ Some(methodDef)
+ }
+ }
+ }
+
+ /** 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.
+ *
+ * Methods Scala.js-defined JS classes are compiled as static methods taking
+ * an explicit parameter for their `this` value.
+ */
+ private def genMethodDef(static: Boolean, methodName: js.PropertyName,
+ paramsSyms: List[Symbol], resultIRType: jstpe.Type,
+ tree: Tree, optimizerHints: OptimizerHints): js.MethodDef = {
+ implicit val pos: Position = tree.pos
+
+ ctx.debuglog("genMethod " + methodName.name)
+ ctx.debuglog("")
+
+ val jsParams = for (param <- paramsSyms) yield {
+ implicit val pos: Position = param.pos
+ js.ParamDef(encodeLocalSym(param), toIRType(param.info),
+ mutable = false, rest = false)
+ }
+
+ def genBody() =
+ if (resultIRType == jstpe.NoType) genStat(tree)
+ else genExpr(tree)
+
+ //if (!isScalaJSDefinedJSClass(currentClassSym)) {
+ js.MethodDef(static, methodName, jsParams, resultIRType, 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, genBody())(
+ optimizerHints, None)
+ }
+ }*/
+ }
+
+ // Generate statements and expressions -------------------------------------
+
+ /** Gen JS code for a tree in statement position (in the IR).
+ */
+ private def genStat(tree: Tree): js.Tree = {
+ exprToStat(genStatOrExpr(tree, isStat = true))
+ }
+
+ /** Turn a JavaScript expression of type Unit into a statement */
+ private 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: Position = 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).
+ */
+ private 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.
+ */
+ private def genStatOrExpr(tree: Tree, isStat: Boolean): js.Tree = {
+ implicit val pos: Position = tree.pos
+
+ ctx.debuglog(" " + tree)
+ ctx.debuglog("")
+
+ tree match {
+ /** LabelDefs (for while and do..while loops) */
+ /*case lblDf: LabelDef =>
+ genLabelDef(lblDf)*/
+
+ /** Local val or var declaration */
+ case tree @ ValDef(name, _, _) =>
+ /* Must have been eliminated by the tail call transform performed
+ * by genMethodBody(). */
+ assert(name != nme.THIS,
+ s"ValDef(_, nme.THIS, _, _) found at ${tree.pos}")
+
+ val sym = tree.symbol
+ val rhs = tree.rhs
+ val rhsTree = 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 _ =>
+ js.VarDef(encodeLocalSym(sym),
+ toIRType(sym.info), sym.is(Mutable), rhsTree)
+ }
+
+ case If(cond, thenp, elsep) =>
+ js.If(genExpr(cond), genStatOrExpr(thenp, isStat),
+ genStatOrExpr(elsep, isStat))(toIRType(tree.tpe))
+
+ case Return(expr, from) =>
+ // TODO Need to consider `from`?
+ 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 app: Apply =>
+ genApply(app, isStat)
+
+ case app: TypeApply =>
+ genTypeApply(app)
+
+ /*case app: ApplyDynamic =>
+ genApplyDynamic(app)*/
+
+ case tree: This =>
+ if (tree.symbol == currentClassSym.get) {
+ genThis()
+ } else {
+ assert(tree.symbol.is(Module),
+ "Trying to access the this of another class: " +
+ "tree.symbol = " + tree.symbol +
+ ", class symbol = " + currentClassSym.get +
+ " pos:" + pos)
+ genLoadModule(tree.symbol)
+ }
+
+ case Select(qualifier, _) =>
+ val sym = tree.symbol
+ if (sym.is(Module)) {
+ assert(!sym.is(Package), "Cannot use package as value: " + tree)
+ genLoadModule(sym)
+ } else if (sym.is(JavaStatic)) {
+ genLoadStaticField(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))
+ fromAny(boxed,
+ enteringPhase(currentRun.posterasurePhase)(sym.tpe))
+ } else*/ {
+ js.Select(genExpr(qualifier),
+ encodeFieldSym(sym))(toIRType(sym.info))
+ }
+
+ case tree: Ident =>
+ desugarIdent(tree).fold[js.Tree] {
+ val sym = tree.symbol
+ assert(!sym.is(Package), "Cannot use package as value: " + tree)
+ if (sym.is(Module)) {
+ genLoadModule(sym)
+ } else if (undefinedDefaultParams.contains(sym)) {
+ /* This is a default parameter whose assignment was moved to
+ * a local variable. Put an undefined param instead.
+ */
+ js.UndefinedParam()(toIRType(sym.info))
+ } else {
+ js.VarRef(encodeLocalSym(sym))(toIRType(sym.info))
+ }
+ } { select =>
+ genStatOrExpr(select, isStat)
+ }
+
+ case Literal(value) =>
+ import Constants._
+ 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 =>
+ genClassConstant(value.typeValue)
+ /*case EnumTag =>
+ genStaticMember(value.symbolValue)*/
+ }
+
+ case Block(stats, expr) =>
+ js.Block(stats.map(genStat) :+ genStatOrExpr(expr, isStat))
+
+ case Typed(expr, _) =>
+ expr match {
+ case _: Super => genThis()
+ case _ => genExpr(expr)
+ }
+
+ case Assign(lhs0, rhs) =>
+ val sym = lhs0.symbol
+ if (sym.is(JavaStaticTerm))
+ throw new FatalError(s"Assignment to static member ${sym.fullName} not supported")
+ val genRhs = genExpr(rhs)
+ val lhs = lhs0 match {
+ case lhs: Ident => desugarIdent(lhs).getOrElse(lhs)
+ case lhs => lhs
+ }
+ lhs match {
+ case lhs: Select =>
+ val qualifier = lhs.qualifier
+
+ def ctorAssignment = (
+ currentMethodSym.get.name == nme.CONSTRUCTOR &&
+ currentMethodSym.get.owner == qualifier.symbol &&
+ qualifier.isInstanceOf[This]
+ )
+ if (!sym.is(Mutable) && !ctorAssignment)
+ throw new FatalError(s"Assigning to immutable field ${sym.fullName} at $pos")
+
+ val genQual = genExpr(qualifier)
+
+ /*if (isScalaJSDefinedJSClass(sym.owner)) {
+ val genLhs = if (isExposed(sym))
+ js.JSBracketSelect(genQual, js.StringLiteral(jsNameOf(sym)))
+ else
+ js.JSDotSelect(genQual, encodeFieldSym(sym))
+ val boxedRhs =
+ ensureBoxed(genRhs,
+ enteringPhase(currentRun.posterasurePhase)(rhs.tpe))
+ js.Assign(genLhs, boxedRhs)
+ } else {*/
+ js.Assign(
+ js.Select(genQual, encodeFieldSym(sym))(toIRType(sym.info)),
+ genRhs)
+ //}
+ case _ =>
+ js.Assign(
+ js.VarRef(encodeLocalSym(sym))(toIRType(sym.info)),
+ genRhs)
+ }
+
+ /** Array constructor */
+ case javaSeqLiteral: JavaSeqLiteral =>
+ genJavaSeqLiteral(javaSeqLiteral)
+
+ /** A Match reaching the backend is supposed to be optimized as a switch */
+ /*case mtch: Match =>
+ genMatch(mtch, isStat)*/
+
+ case tree: Closure =>
+ genClosure(tree)
+
+ /*case EmptyTree =>
+ js.Skip()*/
+
+ case _ =>
+ throw new FatalError("Unexpected tree in genExpr: " +
+ tree + "/" + tree.getClass + " at: " + (tree.pos: Position))
+ }
+ } // end of genStatOrExpr()
+
+ // !!! DUPLICATE code with DottyBackendInterface
+ private def desugarIdent(i: Ident): Option[Select] = {
+ i.tpe match {
+ case TermRef(prefix: TermRef, name) =>
+ Some(tpd.ref(prefix).select(i.symbol))
+ case TermRef(prefix: ThisType, name) =>
+ Some(tpd.This(prefix.cls).select(i.symbol))
+ /*case TermRef(NoPrefix, name) =>
+ if (i.symbol is Method) Some(This(i.symbol.topLevelClass).select(i.symbol)) // workaround #342 todo: remove after fixed
+ else None*/
+ case _ =>
+ None
+ }
+ }
+
+ private def qualifierOf(fun: Tree): Tree = fun match {
+ case fun: Ident =>
+ fun.tpe match {
+ case TermRef(prefix: TermRef, _) => tpd.ref(prefix)
+ case TermRef(prefix: ThisType, _) => tpd.This(prefix.cls)
+ }
+ case Select(qualifier, _) =>
+ qualifier
+ case TypeApply(fun, _) =>
+ qualifierOf(fun)
+ }
+
+ /** Gen JS this of the current class.
+ * Normally encoded straightforwardly as a JS this.
+ * But must be replaced by the `thisLocalVarIdent` local variable if there
+ * is one.
+ */
+ private def genThis()(implicit pos: Position): js.Tree = {
+ /*if (tryingToGenMethodAsJSFunction) {
+ throw new CancelGenMethodAsJSFunction(
+ "Trying to generate `this` inside the body")
+ }*/
+
+ thisLocalVarIdent.fold[js.Tree] {
+ js.This()(currentClassType)
+ } { thisLocalIdent =>
+ js.VarRef(thisLocalIdent)(currentClassType)
+ }
+ }
+
+ /** 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.
+ */
+ private def genApply(tree: Apply, isStat: Boolean): js.Tree = {
+ implicit val pos: Position = tree.pos
+ val args = tree.args
+ val sym = tree.fun.symbol
+
+ val fun = tree.fun match {
+ case fun: Ident => desugarIdent(fun).getOrElse(fun)
+ case fun => fun
+ }
+
+ fun match {
+ case _ if isJSDefaultParam(sym) =>
+ js.UndefinedParam()(toIRType(sym.info.finalResultType))
+
+ case Select(Super(_, _), _) =>
+ genSuperCall(tree, isStat)
+
+ case Select(New(_), nme.CONSTRUCTOR) =>
+ genApplyNew(tree)
+
+ case _ =>
+ /*if (sym.isLabel) {
+ genLabelApply(tree)
+ } else*/ if (primitives.isPrimitive(tree)) {
+ genPrimitiveOp(tree, isStat)
+ } else if (Erasure.Boxing.isBox(sym)) {
+ // Box a primitive value (cannot be Unit)
+ val arg = args.head
+ makePrimitiveBox(genExpr(arg), arg.tpe)
+ } else if (Erasure.Boxing.isUnbox(sym)) {
+ // Unbox a primitive value (cannot be Unit)
+ val arg = args.head
+ makePrimitiveUnbox(genExpr(arg), tree.tpe)
+ } else {
+ genNormalApply(tree, isStat)
+ }
+ }
+ }
+
+ /** 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 in Scala, the `mix` item is
+ * irrelevant.
+ */
+ private def genSuperCall(tree: Apply, isStat: Boolean): js.Tree = {
+ implicit val pos: Position = tree.pos
+ val Apply(fun @ Select(sup @ Super(_, mix), _), args) = tree
+ val sym = fun.symbol
+
+ if (sym == defn.Any_getClass) {
+ // The only primitive that is also callable as super call
+ js.GetClass(genThis())
+ } else /*if (isScalaJSDefinedJSClass(currentClassSym)) {
+ 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 &&
+ currentMethodSym.get.isClassConstructor) {
+ isModuleInitialized = 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: Position = tree.pos
+
+ val Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) = tree
+ val ctor = fun.symbol
+ val tpe = tpt.tpe
+
+ assert(ctor.isClassConstructor,
+ "'new' call to non-constructor: " + ctor.name)
+
+ if (tpe.isRef(defn.StringClass)) {
+ genNewString(ctor, genActualArgs(ctor, args))
+ } else /*if (isHijackedBoxedClass(tpe.typeSymbol)) {
+ genNewHijackedBoxedClass(tpe.typeSymbol, ctor, args map genExpr)
+ } else if (translatedAnonFunctions contains tpe.typeSymbol) {
+ val functionMaker = translatedAnonFunctions(tpe.typeSymbol)
+ functionMaker(args map genExpr)
+ } else*/ if (isJSType(tpe.widenDealias.typeSymbol)) {
+ val clsSym = tpe.widenDealias.typeSymbol
+ if (clsSym == jsdefn.JSObjectClass && args.isEmpty) js.JSObjectConstr(Nil)
+ else if (clsSym == jsdefn.JSArrayClass && args.isEmpty) js.JSArrayConstr(Nil)
+ else js.JSNew(genLoadJSConstructor(clsSym), genActualJSArgs(ctor, args))
+ } else {
+ toIRType(tpe) match {
+ case cls: jstpe.ClassType =>
+ js.New(cls, encodeMethodSym(ctor), genActualArgs(ctor, args))
+
+ case other =>
+ throw new FatalError(s"Non ClassType cannot be instantiated: $other")
+ }
+ }
+ }
+
+ /** Gen JS code for a primitive method call. */
+ private def genPrimitiveOp(tree: Apply, isStat: Boolean): js.Tree = {
+ import scala.tools.nsc.backend.ScalaPrimitives._
+
+ implicit val pos: Position = tree.pos
+
+ val Apply(fun, args) = tree
+ val receiver = qualifierOf(fun)
+
+ val code = primitives.getPrimitive(tree, receiver.tpe)
+
+ if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code))
+ genSimpleOp(tree, receiver :: args, code)
+ else if (code == CONCAT)
+ genStringConcat(tree, receiver, args)
+ else if (code == HASH)
+ genScalaHash(tree, receiver)
+ else if (isArrayOp(code))
+ genArrayOp(tree, code)
+ else if (code == SYNCHRONIZED)
+ genSynchronized(tree, isStat)
+ else if (isCoercion(code))
+ genCoercion(tree, receiver, code)
+ else if (code == JSPrimitives.THROW)
+ genThrow(tree, args)
+ else /*if (primitives.isJSPrimitive(code))
+ genJSPrimitive(tree, receiver, args, code)
+ else*/
+ throw new FatalError(s"Unknown primitive: ${tree.symbol.fullName} at: $pos")
+ }
+
+ /** Gen JS code for a simple operation (arithmetic, logical, or comparison) */
+ private def genSimpleOp(tree: Apply, args: List[Tree], code: Int): js.Tree = {
+ args match {
+ case List(arg) => genSimpleUnaryOp(tree, arg, code)
+ case List(lhs, rhs) => genSimpleBinaryOp(tree, lhs, rhs, code)
+ case _ => throw new FatalError("Incorrect arity for primitive")
+ }
+ }
+
+ /** Gen JS code for a simple unary operation. */
+ private def genSimpleUnaryOp(tree: Apply, arg: Tree, code: Int): js.Tree = {
+ import scala.tools.nsc.backend.ScalaPrimitives._
+
+ implicit val pos: Position = tree.pos
+
+ val genArg = genExpr(arg)
+ val resultIRType = toIRType(tree.tpe)
+
+ (code: @switch) match {
+ case POS =>
+ genArg
+
+ case NEG =>
+ (resultIRType: @unchecked) match {
+ case jstpe.IntType =>
+ js.BinaryOp(js.BinaryOp.Int_-, js.IntLiteral(0), genArg)
+ case jstpe.LongType =>
+ js.BinaryOp(js.BinaryOp.Long_-, js.LongLiteral(0), genArg)
+ case jstpe.FloatType =>
+ js.BinaryOp(js.BinaryOp.Float_-, js.FloatLiteral(0.0f), genArg)
+ case jstpe.DoubleType =>
+ js.BinaryOp(js.BinaryOp.Double_-, js.DoubleLiteral(0), genArg)
+ }
+
+ case NOT =>
+ (resultIRType: @unchecked) match {
+ case jstpe.IntType =>
+ js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(-1), genArg)
+ case jstpe.LongType =>
+ js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(-1), genArg)
+ }
+
+ case ZNOT =>
+ js.UnaryOp(js.UnaryOp.Boolean_!, genArg)
+
+ case _ =>
+ throw new FatalError("Unknown unary operation code: " + code)
+ }
+ }
+
+ /** Gen JS code for a simple binary operation. */
+ private def genSimpleBinaryOp(tree: Apply, lhs: Tree, rhs: Tree, code: Int): js.Tree = {
+ import scala.tools.nsc.backend.ScalaPrimitives._
+ import js.UnaryOp._
+
+ /* Codes for operation types, in an object so that they can be 'final val'
+ * and be used in switch-matches.
+ */
+ object OpTypes {
+ final val DoubleOp = 1
+ final val FloatOp = 2
+ final val LongOp = 3
+ final val IntOp = 4
+ final val BooleanOp = 5
+ final val AnyOp = 6
+ }
+ import OpTypes._
+
+ implicit val pos: Position = tree.pos
+
+ val lhsIRType = toIRType(lhs.tpe)
+ val rhsIRType = toIRType(rhs.tpe)
+
+ val opType = (lhsIRType, rhsIRType) match {
+ case (jstpe.DoubleType, _) | (_, jstpe.DoubleType) => DoubleOp
+ case (jstpe.FloatType, _) | (_, jstpe.FloatType) => FloatOp
+ case (jstpe.LongType, _) | (_, jstpe.LongType) => LongOp
+ case (jstpe.IntType, _) | (_, jstpe.IntType) => IntOp
+ case (jstpe.BooleanType, jstpe.BooleanType) => BooleanOp
+ case _ => AnyOp
+ }
+
+ if (opType == AnyOp && isUniversalEqualityOp(code)) {
+ genUniversalEqualityOp(lhs, rhs, code)
+ } else if (code == ZOR) {
+ js.If(genExpr(lhs), js.BooleanLiteral(true), genExpr(rhs))(jstpe.BooleanType)
+ } else if (code == ZAND) {
+ js.If(genExpr(lhs), genExpr(rhs), js.BooleanLiteral(false))(jstpe.BooleanType)
+ } else {
+ import js.BinaryOp._
+
+ def coerce(tree: js.Tree, opType: Int): js.Tree = (opType: @switch) match {
+ case DoubleOp =>
+ if (tree.tpe == jstpe.LongType) js.UnaryOp(LongToDouble, tree)
+ else tree
+
+ case FloatOp =>
+ if (tree.tpe == jstpe.FloatType || tree.tpe == jstpe.IntType) tree
+ else js.UnaryOp(DoubleToFloat, coerce(tree, DoubleOp))
+
+ case LongOp =>
+ if (tree.tpe == jstpe.LongType) tree
+ else {
+ assert(tree.tpe == jstpe.IntType)
+ js.UnaryOp(IntToLong, tree)
+ }
+
+ case IntOp =>
+ if (tree.tpe == jstpe.IntType) tree
+ else {
+ assert(tree.tpe == jstpe.LongType)
+ js.UnaryOp(LongToInt, tree)
+ }
+
+ case BooleanOp | AnyOp =>
+ tree
+ }
+
+ val rhsOpType = code match {
+ case LSL | LSR | ASR => IntOp
+ case _ => opType
+ }
+
+ val genLhs = coerce(genExpr(lhs), opType)
+ val genRhs = coerce(genExpr(rhs), rhsOpType)
+
+ val op = (opType: @switch) match {
+ case IntOp =>
+ (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 EQ => Num_==
+ case NE => Num_!=
+ case LT => Num_<
+ case LE => Num_<=
+ case GT => Num_>
+ case GE => Num_>=
+ }
+
+ case FloatOp =>
+ (code: @switch) match {
+ case ADD => Float_+
+ case SUB => Float_-
+ case MUL => Float_*
+ case DIV => Float_/
+ case MOD => Float_%
+
+ case EQ => Num_==
+ case NE => Num_!=
+ case LT => Num_<
+ case LE => Num_<=
+ case GT => Num_>
+ case GE => Num_>=
+ }
+
+ case DoubleOp =>
+ (code: @switch) match {
+ case ADD => Double_+
+ case SUB => Double_-
+ case MUL => Double_*
+ case DIV => Double_/
+ case MOD => Double_%
+
+ case EQ => Num_==
+ case NE => Num_!=
+ case LT => Num_<
+ case LE => Num_<=
+ case GT => Num_>
+ case GE => Num_>=
+ }
+
+ case LongOp =>
+ (code: @switch) match {
+ case ADD => Long_+
+ case SUB => Long_-
+ case MUL => Long_*
+ case DIV => Long_/
+ case MOD => Long_%
+ case OR => Long_|
+ case XOR => Long_^
+ case AND => Long_&
+ case LSL => Long_<<
+ case LSR => Long_>>>
+ case ASR => Long_>>
+
+ case EQ => Long_==
+ case NE => Long_!=
+ case LT => Long_<
+ case LE => Long_<=
+ case GT => Long_>
+ case GE => Long_>=
+ }
+
+ case BooleanOp =>
+ (code: @switch) match {
+ case EQ => Boolean_==
+ case NE => Boolean_!=
+ case OR => Boolean_|
+ case AND => Boolean_&
+ case XOR => Boolean_!=
+ }
+
+ case AnyOp =>
+ /* No @switch because some 2.11 version erroneously report a warning
+ * for switches with less than 3 non-default cases.
+ */
+ code match {
+ case ID => ===
+ case NI => !==
+ }
+ }
+
+ js.BinaryOp(op, genLhs, genRhs)
+ }
+ }
+
+ /** Gen JS code for a universal equality test. */
+ private def genUniversalEqualityOp(lhs: Tree, rhs: Tree, code: Int)(
+ implicit pos: Position): js.Tree = {
+
+ import scala.tools.nsc.backend.ScalaPrimitives._
+
+ val genLhs = genExpr(lhs)
+ val genRhs = genExpr(rhs)
+
+ val bypassEqEq = {
+ // Do not call equals if we have a literal null at either side.
+ genLhs.isInstanceOf[js.Null] ||
+ genRhs.isInstanceOf[js.Null]
+ }
+
+ if (bypassEqEq) {
+ js.BinaryOp(
+ if (code == EQ) js.BinaryOp.=== else js.BinaryOp.!==,
+ genLhs, genRhs)
+ } else {
+ val body = genEqEqPrimitive(lhs.tpe, rhs.tpe, genLhs, genRhs)
+ if (code == EQ) body
+ else js.UnaryOp(js.UnaryOp.Boolean_!, body)
+ }
+ }
+
+ private lazy val externalEqualsNumNum: Symbol =
+ defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumNum)
+ private lazy val externalEqualsNumChar: Symbol =
+ NoSymbol // ctx.requiredMethod(BoxesRunTimeTypeRef, nme.equalsNumChar) // this method is private
+ private lazy val externalEqualsNumObject: Symbol =
+ defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumObject)
+ private lazy val externalEquals: Symbol =
+ defn.BoxesRunTimeClass.info.decl(nme.equals_).suchThat(toDenot(_).info.firstParamTypes.size == 2).symbol
+
+ /** Gen JS code for a call to Any.== */
+ private def genEqEqPrimitive(ltpe: Type, rtpe: Type, lsrc: js.Tree, rsrc: js.Tree)(
+ implicit pos: Position): js.Tree = {
+ ctx.debuglog(s"$ltpe == $rtpe")
+ val lsym = ltpe.widenDealias.typeSymbol.asClass
+ val rsym = rtpe.widenDealias.typeSymbol.asClass
+
+ /* 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 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 = {
+ isJSType(lsym) || isJSType(rsym) || {
+ val p = ctx.platform
+ val areSameFinals = lsym.is(Final) && rsym.is(Final) && (ltpe =:= rtpe)
+ !areSameFinals && p.isMaybeBoxed(lsym) && p.isMaybeBoxed(rsym)
+ }
+ }
+
+ if (mustUseAnyComparator) {
+ val equalsMethod: Symbol = {
+ // scalastyle:off line.size.limit
+ val ptfm = ctx.platform
+ if (lsym.derivesFrom(defn.BoxedNumberClass)) {
+ if (rsym.derivesFrom(defn.BoxedNumberClass)) externalEqualsNumNum
+ else if (rsym.derivesFrom(defn.BoxedCharClass)) externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030
+ else externalEqualsNumObject
+ } else externalEquals
+ // scalastyle:on line.size.limit
+ }
+ genModuleApplyMethod(equalsMethod, List(lsrc, rsrc))
+ } else {
+ // if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc)
+ if (lsym == defn.StringClass) {
+ // 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, defn.Any_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: Position = tree.pos
+
+ val arg = args.head
+
+ /* 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 or String)
+ * so boxing is not necessary (in particular, rhs is never a primitive).
+ */
+ assert(!isPrimitiveValueType(receiver.tpe) || arg.tpe.isRef(defn.StringClass))
+ assert(!isPrimitiveValueType(arg.tpe))
+
+ val genLhs = {
+ val genLhs0 = genExpr(receiver)
+ // Box the receiver if it is a primitive value
+ if (!isPrimitiveValueType(receiver.tpe)) genLhs0
+ else makePrimitiveBox(genLhs0, receiver.tpe)
+ }
+
+ val genRhs = genExpr(arg)
+
+ js.BinaryOp(js.BinaryOp.String_+, genLhs, genRhs)
+ }
+
+ /** Gen JS code for a call to Any.## */
+ private def genScalaHash(tree: Apply, receiver: Tree): js.Tree = {
+ implicit val pos: Position = tree.pos
+
+ genModuleApplyMethod(defn.ScalaRuntimeModule.requiredMethod(nme.hash_),
+ List(genExpr(receiver)))
+ }
+
+ /** Gen JS code for an array operation (get, set or length) */
+ private def genArrayOp(tree: Tree, code: Int): js.Tree = {
+ import scala.tools.nsc.backend.ScalaPrimitives._
+
+ implicit val pos: Position = tree.pos
+
+ val Apply(fun, args) = tree
+ val arrayObj = qualifierOf(fun)
+
+ val genArray = genExpr(arrayObj)
+ val genArgs = args.map(genExpr)
+
+ def elementType: Type = arrayObj.tpe.widenDealias match {
+ case defn.ArrayOf(el) => el
+ case JavaArrayType(el) => el
+ case tpe =>
+ ctx.error(s"expected Array $tpe")
+ ErrorType
+ }
+
+ def genSelect(): js.Tree =
+ js.ArraySelect(genArray, genArgs(0))(toIRType(elementType))
+
+ if (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 (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(), genArgs(1))
+ } else {
+ // length of the array
+ js.ArrayLength(genArray)
+ }
+ }
+
+ /** Gen JS code for a call to AnyRef.synchronized */
+ private def genSynchronized(tree: Apply, isStat: Boolean): js.Tree = {
+ /* JavaScript is single-threaded, so we can drop the
+ * synchronization altogether.
+ */
+ val Apply(fun, List(arg)) = tree
+ val receiver = qualifierOf(fun)
+
+ val genReceiver = genExpr(receiver)
+ val genArg = genStatOrExpr(arg, isStat)
+
+ genReceiver match {
+ case js.This() =>
+ // common case for which there is no side-effect nor NPE
+ genArg
+ case _ =>
+ implicit val pos: Position = tree.pos
+ /* TODO Check for a null receiver?
+ * In theory, it's UB, but that decision should be left for link time.
+ */
+ js.Block(genReceiver, genArg)
+ }
+ }
+
+ /** Gen JS code for a coercion */
+ private def genCoercion(tree: Apply, receiver: Tree, code: Int): js.Tree = {
+ import scala.tools.nsc.backend.ScalaPrimitives._
+
+ implicit val pos: Position = 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 |
+ F2D =>
+ source
+ }
+ }
+
+ /** Gen a call to the special `throw` method. */
+ private def genThrow(tree: Apply, args: List[Tree]): js.Tree = {
+ implicit val pos: Position = tree.pos
+ val exception = args.head
+ val genException = genExpr(exception)
+ js.Throw {
+ if (exception.tpe.widenDealias.typeSymbol.derivesFrom(jsdefn.JavaScriptExceptionClass)) {
+ genModuleApplyMethod(
+ jsdefn.RuntimePackage_unwrapJavaScriptException,
+ List(genException))
+ } else {
+ genException
+ }
+ }
+ }
+
+ /** 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 interop)
+ * * Calls to methods in impl classes of Scala2 traits.
+ * * Regular method call
+ */
+ private def genNormalApply(tree: Apply, isStat: Boolean): js.Tree = {
+ implicit val pos: Position = tree.pos
+
+ val fun = tree.fun match {
+ case fun: Ident => desugarIdent(fun).get
+ case fun: Select => fun
+ }
+ val receiver = fun.qualifier
+ val args = tree.args
+ val sym = fun.symbol
+
+ def isStringMethodFromObject: Boolean = sym.name match {
+ case nme.toString_ | nme.equals_ | nme.hashCode_ => true
+ case _ => false
+ }
+
+ if (sym.owner == defn.StringClass && !isStringMethodFromObject) {
+ genApplyMethodOfString(genExpr(receiver), sym, genActualArgs(sym, args))
+ } else if (isJSType(sym.owner)) {
+ //if (!isScalaJSDefinedJSClass(sym.owner) || isExposed(sym))
+ genApplyJSMethodGeneric(tree, sym, genExpr(receiver), genActualJSArgs(sym, args), isStat)
+ /*else
+ genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args))*/
+ } else if (foreignIsImplClass(sym.owner)) {
+ genTraitImplApply(sym, args.map(genExpr))
+ } else if (sym.isClassConstructor) {
+ // Calls to constructors are always statically linked
+ genApplyMethodStatically(genExpr(receiver), sym, genActualArgs(sym, args))
+ } else {
+ genApplyMethod(genExpr(receiver), sym, genActualArgs(sym, args))
+ }
+ }
+
+ /** Gen JS code for a call to a JS method (of a subclass of `js.Any`).
+ *
+ * Basically it boils down to calling the method as a `JSBracketSelect`,
+ * without name mangling. But other aspects come into play:
+ *
+ * - Operator methods are translated to JS operators (not method calls)
+ * - `apply` is translated as a function call, i.e., `o()` instead of `o.apply()`
+ * - Scala varargs are turned into JS varargs (see `genPrimitiveJSArgs()`)
+ * - Getters and parameterless methods are translated as `JSBracketSelect`
+ * - Setters are translated to `Assign` to `JSBracketSelect`
+ */
+ private def genApplyJSMethodGeneric(tree: Tree, sym: Symbol,
+ receiver: js.Tree, args: List[js.Tree], isStat: Boolean,
+ superIn: Option[Symbol] = None)(
+ implicit pos: Position): js.Tree = {
+
+ implicit val pos: Position = tree.pos
+
+ def noSpread = !args.exists(_.isInstanceOf[js.JSSpread])
+ val argc = args.size // meaningful only for methods that don't have varargs
+
+ def requireNotSuper(): Unit = {
+ if (superIn.isDefined)
+ ctx.error("Illegal super call in Scala.js-defined JS class", tree.pos)
+ }
+
+ def hasExplicitJSEncoding = {
+ sym.hasAnnotation(jsdefn.JSNameAnnot) ||
+ sym.hasAnnotation(jsdefn.JSBracketAccessAnnot) ||
+ sym.hasAnnotation(jsdefn.JSBracketCallAnnot)
+ }
+
+ val boxedResult = sym.name match {
+ case JSUnaryOpMethodName(code) if argc == 0 =>
+ requireNotSuper()
+ js.JSUnaryOp(code, receiver)
+
+ case JSBinaryOpMethodName(code) if argc == 1 =>
+ requireNotSuper()
+ js.JSBinaryOp(code, receiver, args.head)
+
+ case nme.apply if !hasExplicitJSEncoding =>
+ requireNotSuper()
+ if (jsdefn.isJSThisFunctionClass(sym.owner))
+ js.JSBracketMethodApply(receiver, js.StringLiteral("call"), args)
+ else
+ js.JSFunctionApply(receiver, args)
+
+ case _ =>
+ def jsFunName = js.StringLiteral(jsNameOf(sym))
+
+ def genSuperReference(propName: js.Tree): js.Tree = {
+ superIn.fold[js.Tree] {
+ js.JSBracketSelect(receiver, propName)
+ } { superInSym =>
+ js.JSSuperBracketSelect(
+ jstpe.ClassType(encodeClassFullName(superInSym)),
+ receiver, propName)
+ }
+ }
+
+ def genSelectGet(propName: js.Tree): js.Tree =
+ genSuperReference(propName)
+
+ def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree =
+ js.Assign(genSuperReference(propName), value)
+
+ def genCall(methodName: js.Tree, args: List[js.Tree]): js.Tree = {
+ superIn.fold[js.Tree] {
+ js.JSBracketMethodApply(
+ receiver, methodName, args)
+ } { superInSym =>
+ js.JSSuperBracketCall(
+ jstpe.ClassType(encodeClassFullName(superInSym)),
+ receiver, methodName, args)
+ }
+ }
+
+ if (isJSGetter(sym)) {
+ assert(noSpread && argc == 0)
+ genSelectGet(jsFunName)
+ } else if (isJSSetter(sym)) {
+ assert(noSpread && argc == 1)
+ genSelectSet(jsFunName, args.head)
+ } else if (isJSBracketAccess(sym)) {
+ assert(noSpread && (argc == 1 || argc == 2),
+ s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments")
+ args match {
+ case List(keyArg) =>
+ genSelectGet(keyArg)
+ case List(keyArg, valueArg) =>
+ genSelectSet(keyArg, valueArg)
+ }
+ } else if (isJSBracketCall(sym)) {
+ val (methodName, actualArgs) = extractFirstArg(args)
+ genCall(methodName, actualArgs)
+ } else {
+ genCall(jsFunName, args)
+ }
+ }
+
+ if (isStat) {
+ boxedResult
+ } else {
+ val tpe = ctx.atPhase(ctx.elimErasedValueTypePhase) { implicit ctx =>
+ sym.info.finalResultType
+ }
+ unbox(boxedResult, tpe)
+ }
+ }
+
+ private object JSUnaryOpMethodName {
+ private val map = Map(
+ nme.UNARY_+ -> js.JSUnaryOp.+,
+ nme.UNARY_- -> js.JSUnaryOp.-,
+ nme.UNARY_~ -> js.JSUnaryOp.~,
+ nme.UNARY_! -> js.JSUnaryOp.!
+ )
+
+ def unapply(name: Names.TermName): Option[js.JSUnaryOp.Code] =
+ map.get(name)
+ }
+
+ private object JSBinaryOpMethodName {
+ private val map = Map(
+ nme.ADD -> js.JSBinaryOp.+,
+ nme.SUB -> js.JSBinaryOp.-,
+ nme.MUL -> js.JSBinaryOp.*,
+ nme.DIV -> js.JSBinaryOp./,
+ nme.MOD -> js.JSBinaryOp.%,
+
+ nme.LSL -> js.JSBinaryOp.<<,
+ nme.ASR -> js.JSBinaryOp.>>,
+ nme.LSR -> js.JSBinaryOp.>>>,
+ nme.OR -> js.JSBinaryOp.|,
+ nme.AND -> js.JSBinaryOp.&,
+ nme.XOR -> js.JSBinaryOp.^,
+
+ nme.LT -> js.JSBinaryOp.<,
+ nme.LE -> js.JSBinaryOp.<=,
+ nme.GT -> js.JSBinaryOp.>,
+ nme.GE -> js.JSBinaryOp.>=,
+
+ nme.ZAND -> js.JSBinaryOp.&&,
+ nme.ZOR -> js.JSBinaryOp.||
+ )
+
+ def unapply(name: Names.TermName): Option[js.JSBinaryOp.Code] =
+ map.get(name)
+ }
+
+ /** Extract the first argument in a list of actual arguments.
+ *
+ * This is nothing else than decomposing into head and tail, except that
+ * we assert that the first element is not a JSSpread.
+ */
+ private def extractFirstArg(args: List[js.Tree]): (js.Tree, List[js.Tree]) = {
+ assert(args.nonEmpty,
+ "Trying to extract the first argument of an empty argument list")
+ val firstArg = args.head
+ assert(!firstArg.isInstanceOf[js.JSSpread],
+ "Trying to extract the first argument of an argument list starting " +
+ "with a Spread argument: " + firstArg)
+ (firstArg, args.tail)
+ }
+
+ /** Gen JS code for a call to a polymorphic method.
+ *
+ * The only methods that reach the back-end as polymorphic are
+ * `isInstanceOf` and `asInstanceOf`.
+ *
+ * (Well, in fact `DottyRunTime.newRefArray` too, but it is handled as a
+ * primitive instead.)
+ */
+ private def genTypeApply(tree: TypeApply): js.Tree = {
+ implicit val pos: Position = tree.pos
+
+ val TypeApply(fun, targs) = tree
+
+ val sym = fun.symbol
+ val receiver = qualifierOf(fun)
+
+ val to = targs.head.tpe
+
+ assert(!isPrimitiveValueType(receiver.tpe),
+ s"Found receiver of type test with primitive type ${receiver.tpe} at $pos")
+ assert(!isPrimitiveValueType(to),
+ s"Found target type of type test with primitive type ${receiver.tpe} at $pos")
+
+ val genReceiver = genExpr(receiver)
+
+ if (sym == defn.Any_asInstanceOf) {
+ genAsInstanceOf(genReceiver, to)
+ } else if (sym == defn.Any_isInstanceOf) {
+ genIsInstanceOf(tree, genReceiver, to)
+ } else {
+ throw new FatalError(
+ s"Unexpected type application $fun with symbol ${sym.fullName}")
+ }
+ }
+
+ /** Gen JS code for a Java Seq literal. */
+ private def genJavaSeqLiteral(tree: JavaSeqLiteral): js.Tree = {
+ implicit val pos: Position = tree.pos
+
+ val genElems = tree.elems.map(genExpr)
+ val arrayType = toReferenceType(tree.tpe).asInstanceOf[jstpe.ArrayType]
+ js.ArrayValue(arrayType, genElems)
+ }
+
+ /** Gen JS code for a closure.
+ *
+ * Input: a `Closure` tree of the form
+ * {{{
+ * Closure(env, call, functionalInterface)
+ * }}}
+ * representing the pseudo-syntax
+ * {{{
+ * { (p1, ..., pm) => call(env1, ..., envn, p1, ..., pm) }: functionInterface
+ * }}}
+ * where `envi` are identifiers in the local scope. The qualifier of `call`
+ * is also implicitly captured.
+ *
+ * Output: a `js.Closure` tree of the form
+ * {{{
+ * js.Closure(formalCaptures, formalParams, body, actualCaptures)
+ * }}}
+ * representing the pseudo-syntax
+ * {{{
+ * lambda<formalCapture1 = actualCapture1, ..., formalCaptureN = actualCaptureN>(
+ * formalParam1, ..., formalParamM) = body
+ * }}}
+ * where the `actualCaptures` and `body` are, in general, arbitrary
+ * expressions. But in this case, `actualCaptures` will be identifiers from
+ * `env`, and the `body` will be of the form
+ * {{{
+ * call(formalCapture1.ref, ..., formalCaptureN.ref,
+ * formalParam1.ref, ...formalParamM.ref)
+ * }}}
+ *
+ * When the `js.Closure` node is evaluated, i.e., when the closure value is
+ * created, the expressions of the `actualCaptures` are evaluated, and the
+ * results of those evaluations is "stored" in the environment of the
+ * closure as the corresponding `formalCapture`.
+ *
+ * When we later *call* the closure, the `formalCaptures` already have their
+ * values from the environment, and they are available in the `body`. The
+ * `formalParams` of the created closure receive their values from the
+ * actual arguments at the call-site of the closure, and they are also
+ * available in the `body`.
+ */
+ private def genClosure(tree: Closure): js.Tree = {
+ implicit val pos: Position = tree.pos
+ val Closure(env, call, functionalInterface) = tree
+
+ val envSize = env.size
+
+ val (fun, args) = call match {
+ // case Apply(fun, args) => (fun, args) // Conjectured not to happen
+ case t @ Select(_, _) => (t, Nil)
+ case t @ Ident(_) => (t, Nil)
+ }
+ val sym = fun.symbol
+
+ val qualifier = qualifierOf(fun)
+ val allCaptureValues = qualifier :: env
+
+ val (formalCaptures, actualCaptures) = allCaptureValues.map { value =>
+ implicit val pos: Position = value.pos
+ val formalIdent = value match {
+ case Ident(name) => freshLocalIdent(name.toString)
+ case This(_) => freshLocalIdent("this")
+ case _ => freshLocalIdent()
+ }
+ val formalCapture =
+ js.ParamDef(formalIdent, toIRType(value.tpe), mutable = false, rest = false)
+ val actualCapture = genExpr(value)
+ (formalCapture, actualCapture)
+ }.unzip
+
+ val formalParamNames = sym.info.paramNamess.flatten.drop(envSize)
+ val formalParamTypes = sym.info.paramTypess.flatten.drop(envSize)
+ val (formalParams, actualParams) = formalParamNames.zip(formalParamTypes).map {
+ case (name, tpe) =>
+ val formalParam = js.ParamDef(freshLocalIdent(name.toString),
+ jstpe.AnyType, mutable = false, rest = false)
+ val actualParam = unbox(formalParam.ref, tpe)
+ (formalParam, actualParam)
+ }.unzip
+
+ val genBody = {
+ val thisCaptureRef :: argCaptureRefs = formalCaptures.map(_.ref)
+ val call = genApplyMethod(thisCaptureRef, sym, argCaptureRefs ::: actualParams)
+ box(call, sym.info.finalResultType)
+ }
+
+ val closure = js.Closure(formalCaptures, formalParams, genBody, actualCaptures)
+ ctx.debuglog(closure.toString)
+
+ val funInterfaceSym = functionalInterface.tpe.widenDealias.typeSymbol
+ if (jsdefn.isJSFunctionClass(funInterfaceSym)) {
+ closure
+ } else {
+ assert(!funInterfaceSym.exists || defn.isFunctionClass(funInterfaceSym),
+ s"Invalid functional interface $funInterfaceSym reached the back-end")
+ val cls = "sjsr_AnonFunction" + formalParams.size
+ val ctor = js.Ident("init___sjs_js_Function" + formalParams.size)
+ js.New(jstpe.ClassType(cls), ctor, List(closure))
+ }
+ }
+
+ /** Boxes a value of the given type before `elimErasedValueType`.
+ *
+ * This should be used when sending values to a JavaScript context, which
+ * is erased/boxed at the IR level, although it is not erased at the
+ * dotty/JVM level.
+ *
+ * @param expr Tree to be boxed if needed.
+ * @param tpeEnteringElimErasedValueType The type of `expr` as it was
+ * entering the `elimErasedValueType` phase.
+ */
+ private def box(expr: js.Tree, tpeEnteringElimErasedValueType: Type)(
+ implicit pos: Position): js.Tree = {
+
+ tpeEnteringElimErasedValueType match {
+ case tpe if isPrimitiveValueType(tpe) =>
+ makePrimitiveBox(expr, tpe)
+
+ /*case tpe: ErasedValueType =>
+ val boxedClass = tpe.valueClazz
+ val ctor = boxedClass.primaryConstructor
+ genNew(boxedClass, ctor, List(expr))*/
+
+ case _ =>
+ expr
+ }
+ }
+
+ /** Unboxes a value typed as Any to the given type before `elimErasedValueType`.
+ *
+ * This should be used when receiving values from a JavaScript context,
+ * which is erased/boxed at the IR level, although it is not erased at the
+ * dotty/JVM level.
+ *
+ * @param expr Tree to be extracted.
+ * @param tpeEnteringElimErasedValueType The type of `expr` as it was
+ * entering the `elimErasedValueType` phase.
+ */
+ private def unbox(expr: js.Tree, tpeEnteringElimErasedValueType: Type)(
+ implicit pos: Position): js.Tree = {
+
+ tpeEnteringElimErasedValueType match {
+ case tpe if isPrimitiveValueType(tpe) =>
+ makePrimitiveUnbox(expr, tpe)
+
+ /*case tpe: ErasedValueType =>
+ val boxedClass = tpe.valueClazz
+ val unboxMethod = boxedClass.derivedValueClassUnbox
+ val content = genApplyMethod(
+ genAsInstanceOf(expr, tpe), unboxMethod, Nil)
+ if (unboxMethod.tpe.resultType <:< tpe.erasedUnderlying)
+ content
+ else
+ fromAny(content, tpe.erasedUnderlying)*/
+
+ case tpe =>
+ genAsInstanceOf(expr, tpe)
+ }
+ }
+
+ /** Gen JS code for an asInstanceOf cast (for reference types only) */
+ private def genAsInstanceOf(value: js.Tree, to: Type)(
+ implicit pos: Position): js.Tree = {
+
+ val sym = to.widenDealias.typeSymbol
+
+ if (sym == defn.ObjectClass || isJSType(sym)) {
+ /* asInstanceOf[Object] always succeeds, and
+ * asInstanceOf to a raw JS type is completely erased.
+ */
+ value
+ } else {
+ js.AsInstanceOf(value, toReferenceType(to))
+ }
+ }
+
+ /** Gen JS code for an isInstanceOf test (for reference types only) */
+ private def genIsInstanceOf(tree: Tree, value: js.Tree, to: Type): js.Tree = {
+ implicit val pos: Position = tree.pos
+ val sym = to.widenDealias.typeSymbol
+
+ if (sym == defn.ObjectClass) {
+ js.BinaryOp(js.BinaryOp.!==, value, js.Null())
+ } else if (isJSType(sym)) {
+ if (sym.is(Trait)) {
+ ctx.error(
+ s"isInstanceOf[${sym.fullName}] not supported because it is a JS trait",
+ tree.pos)
+ js.BooleanLiteral(true)
+ } else {
+ js.Unbox(js.JSBinaryOp(
+ js.JSBinaryOp.instanceof, value, genLoadJSConstructor(sym)), 'Z')
+ }
+ } else {
+ js.IsInstanceOf(value, toReferenceType(to))
+ }
+ }
+
+ /** Gen a dynamically linked call to a Scala method. */
+ private def genApplyMethod(receiver: js.Tree,
+ methodSym: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ js.Apply(receiver, encodeMethodSym(methodSym), arguments)(
+ toIRType(patchedResultType(methodSym)))
+ }
+
+ /** Gen a statically linked call to an instance method. */
+ private 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 = toIRType(patchedResultType(method))
+ js.ApplyStatically(receiver, jstpe.ClassType(className),
+ methodIdent, arguments)(resultType)
+ }
+
+ /** Gen a call to a static method. */
+ private def genApplyStatic(method: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ val cls = jstpe.ClassType(encodeClassFullName(method.owner))
+ val methodIdent = encodeMethodSym(method)
+ js.ApplyStatic(cls, methodIdent, arguments)(
+ toIRType(patchedResultType(method)))
+ }
+
+ /** Gen a call to a Scala2 impl class method. */
+ private def genTraitImplApply(method: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ genApplyStatic(method, arguments)
+ }
+
+ /** Gen a call to a non-exposed method of a non-native JS class. */
+ private def genApplyJSClassMethod(receiver: js.Tree, method: Symbol,
+ arguments: List[js.Tree])(implicit pos: Position): js.Tree = {
+ genApplyStatic(method, receiver :: arguments)
+ }
+
+ /** Gen a call to a method of a Scala top-level module. */
+ private def genModuleApplyMethod(methodSym: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ genApplyMethod(genLoadModule(methodSym.owner), methodSym, arguments)
+ }
+
+ /** Gen JS code for `new java.lang.String(...)`.
+ *
+ * Rewires the instantiation to calling the appropriate overload of
+ * `newString` in the object `scala.scalajs.runtime.RuntimeString`.
+ */
+ private def genNewString(ctor: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ js.Apply(
+ genLoadModule(jsdefn.RuntimeStringModuleClass),
+ encodeRTStringCtorSym(ctor), arguments)(
+ jstpe.ClassType(ir.Definitions.StringClass))
+ }
+
+ /** Gen a dynamically linked call to a method of java.lang.String.
+ *
+ * Forwards the call to the module scala.scalajs.runtime.RuntimeString.
+ */
+ private def genApplyMethodOfString(receiver: js.Tree,
+ methodSym: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ js.Apply(
+ genLoadModule(jsdefn.RuntimeStringModuleClass),
+ encodeRTStringMethodSym(methodSym),
+ receiver :: arguments)(
+ toIRType(patchedResultType(methodSym)))
+ }
+
+ /** Gen a boxing operation (tpe is the primitive type) */
+ private def makePrimitiveBox(expr: js.Tree, tpe: Type)(
+ implicit pos: Position): js.Tree = {
+ toReferenceType(tpe) match {
+ case jstpe.ClassType(cls) if ir.Definitions.isPrimitiveClass(cls) =>
+ assert(cls.length == 1)
+ (cls.charAt(0): @switch) match {
+ case 'V' =>
+ // must be handled at least for JS interop
+ js.Block(expr, js.Undefined())
+ case 'C' =>
+ genModuleApplyMethod(jsdefn.BoxesRunTime_boxToCharacter, List(expr))
+ case _ =>
+ expr // box is identity for all non-Char types
+ }
+
+ case _ =>
+ throw new FatalError(
+ s"makePrimitiveBox requires a primitive type, found $tpe at $pos")
+ }
+ }
+
+ /** Gen an unboxing operation (tpe is the primitive type) */
+ private def makePrimitiveUnbox(expr: js.Tree, tpe: Type)(
+ implicit pos: Position): js.Tree = {
+ toReferenceType(tpe) match {
+ case jstpe.ClassType(cls) if ir.Definitions.isPrimitiveClass(cls) =>
+ assert(cls.length == 1)
+ (cls.charAt(0): @switch) match {
+ case 'V' =>
+ // must be handled at least for JS interop
+ expr
+ case 'C' =>
+ genModuleApplyMethod(jsdefn.BoxesRunTime_unboxToChar, List(expr))
+ case primitiveCharCode =>
+ js.Unbox(expr, primitiveCharCode)
+ }
+
+ case _ =>
+ throw new FatalError(
+ s"makePrimitiveUnbox requires a primitive type, found $tpe at $pos")
+ }
+ }
+
+ /** Gen actual actual arguments to Scala method call.
+ * Returns a list of the transformed arguments.
+ *
+ * This tries to optimize repeated arguments (varargs) by turning them
+ * into js.WrappedArray instead of Scala wrapped arrays.
+ */
+ private def genActualArgs(sym: Symbol, args: List[Tree])(
+ implicit pos: Position): List[js.Tree] = {
+ args.map(genExpr)
+ /*val wereRepeated = exitingPhase(currentRun.typerPhase) {
+ sym.tpe.params.map(p => isScalaRepeatedParamType(p.tpe))
+ }
+
+ if (wereRepeated.size > args.size) {
+ // Should not happen, but let's not crash
+ args.map(genExpr)
+ } else {
+ /* Arguments that are in excess compared to the type signature after
+ * erasure are lambda-lifted arguments. They cannot be repeated, hence
+ * the extension to `false`.
+ */
+ for ((arg, wasRepeated) <- args.zipAll(wereRepeated, EmptyTree, false)) yield {
+ if (wasRepeated) {
+ tryGenRepeatedParamAsJSArray(arg, handleNil = false).fold {
+ genExpr(arg)
+ } { genArgs =>
+ genNew(WrappedArrayClass, WrappedArray_ctor,
+ List(js.JSArrayConstr(genArgs)))
+ }
+ } else {
+ genExpr(arg)
+ }
+ }
+ }*/
+ }
+
+ /** Gen actual actual arguments to a JS method call.
+ * Returns a list of the transformed arguments.
+ *
+ * - TODO Repeated arguments (varargs) are expanded
+ * - Default arguments are omitted or replaced by undefined
+ * - All arguments are boxed
+ *
+ * Repeated arguments that cannot be expanded at compile time (i.e., if a
+ * Seq is passed to a varargs parameter with the syntax `seq: _*`) will be
+ * wrapped in a [[js.JSSpread]] node to be expanded at runtime.
+ */
+ private def genActualJSArgs(sym: Symbol, args: List[Tree])(
+ implicit pos: Position): List[js.Tree] = {
+
+ def paramNamesAndTypes(implicit ctx: Context): List[(Names.TermName, Type)] =
+ sym.info.paramNamess.flatten.zip(sym.info.paramTypess.flatten)
+
+ val wereRepeated = ctx.atPhase(ctx.elimRepeatedPhase) { implicit ctx =>
+ for ((name, tpe) <- paramNamesAndTypes)
+ yield (name -> tpe.isRepeatedParam)
+ }.toMap
+
+ val paramTypes = ctx.atPhase(ctx.elimErasedValueTypePhase) { implicit ctx =>
+ paramNamesAndTypes
+ }.toMap
+
+ var reversedArgs: List[js.Tree] = Nil
+
+ for ((arg, (paramName, paramType)) <- args.zip(paramNamesAndTypes)) {
+ val wasRepeated = wereRepeated.getOrElse(paramName, false)
+ if (wasRepeated) {
+ reversedArgs =
+ genJSRepeatedParam(arg) reverse_::: reversedArgs
+ } else {
+ val unboxedArg = genExpr(arg)
+ val boxedArg = unboxedArg match {
+ case js.UndefinedParam() =>
+ unboxedArg
+ case _ =>
+ val tpe = paramTypes.getOrElse(paramName, paramType)
+ box(unboxedArg, tpe)
+ }
+ reversedArgs ::= boxedArg
+ }
+ }
+
+ /* Remove all consecutive js.UndefinedParam's at the end of the argument
+ * list. No check is performed whether they may be there, since they will
+ * only be placed where default arguments can be anyway.
+ */
+ reversedArgs = reversedArgs.dropWhile(_.isInstanceOf[js.UndefinedParam])
+
+ /* Find remaining js.UndefinedParam and replace by js.Undefined. This can
+ * happen with named arguments or with multiple argument lists.
+ */
+ reversedArgs = reversedArgs map {
+ case js.UndefinedParam() => js.Undefined()
+ case arg => arg
+ }
+
+ reversedArgs.reverse
+ }
+
+ /** Gen JS code for a repeated param of a JS method.
+ *
+ * In this case `arg` has type `Seq[T]` for some `T`, but the result should
+ * be an expanded list of the elements in the sequence. So this method
+ * takes care of the conversion.
+ *
+ * It is specialized for the shapes of tree generated by the desugaring
+ * of repeated params in Scala, so that these are actually expanded at
+ * compile-time.
+ *
+ * Otherwise, it returns a `JSSpread` with the `Seq` converted to a
+ * `js.Array`.
+ */
+ private def genJSRepeatedParam(arg: Tree): List[js.Tree] = {
+ tryGenRepeatedParamAsJSArray(arg, handleNil = true).getOrElse {
+ /* Fall back to calling runtime.genTraversableOnce2jsArray
+ * to perform the conversion to js.Array, then wrap in a Spread
+ * operator.
+ */
+ implicit val pos: Position = arg.pos
+ val jsArrayArg = genModuleApplyMethod(
+ jsdefn.RuntimePackage_genTraversableOnce2jsArray,
+ List(genExpr(arg)))
+ List(js.JSSpread(jsArrayArg))
+ }
+ }
+
+ /** Try and expand an actual argument to a repeated param `(xs: T*)`.
+ *
+ * This method recognizes the shapes of tree generated by the desugaring
+ * of repeated params in Scala, and expands them.
+ * If `arg` does not have the shape of a generated repeated param, this
+ * method returns `None`.
+ */
+ private def tryGenRepeatedParamAsJSArray(arg: Tree,
+ handleNil: Boolean): Option[List[js.Tree]] = {
+ implicit val pos: Position = arg.pos
+
+ // Given a method `def foo(args: T*)`
+ arg match {
+ // foo(arg1, arg2, ..., argN) where N > 0
+ case MaybeAsInstanceOf(WrapArray(MaybeAsInstanceOf(array: JavaSeqLiteral))) =>
+ /* Value classes in arrays are already boxed, so no need to use
+ * the type before erasure.
+ * TODO Is this true in dotty?
+ */
+ Some(array.elems.map(e => box(genExpr(e), e.tpe)))
+
+ // foo()
+ case Ident(_) if handleNil && arg.symbol == defn.NilModule =>
+ Some(Nil)
+
+ // foo(argSeq: _*) - cannot be optimized
+ case _ =>
+ None
+ }
+ }
+
+ private object MaybeAsInstanceOf {
+ def unapply(tree: Tree): Some[Tree] = tree match {
+ case TypeApply(asInstanceOf_? @ Select(base, _), _)
+ if asInstanceOf_?.symbol == defn.Any_asInstanceOf =>
+ Some(base)
+ case _ =>
+ Some(tree)
+ }
+ }
+
+ private object WrapArray {
+ lazy val isWrapArray: Set[Symbol] = {
+ val names = {
+ defn.ScalaValueClasses().map(sym => nme.wrapXArray(sym.name)) ++
+ Set(nme.wrapRefArray, nme.genericWrapArray)
+ }
+ names.map(defn.ScalaPredefModule.requiredMethod(_)).toSet
+ }
+
+ def unapply(tree: Apply): Option[Tree] = tree match {
+ case Apply(wrapArray_?, List(wrapped)) if isWrapArray(wrapArray_?.symbol) =>
+ Some(wrapped)
+ case _ =>
+ None
+ }
+ }
+
+ /** Gen JS code for loading a Java static field.
+ */
+ private def genLoadStaticField(sym: Symbol)(implicit pos: Position): js.Tree = {
+ /* Actually, there is no static member in Scala.js. If we come here, that
+ * is because we found the symbol in a Java-emitted .class in the
+ * classpath. But the corresponding implementation in Scala.js will
+ * actually be a val in the companion module.
+ */
+
+ if (sym == defn.BoxedUnit_UNIT) {
+ js.Undefined()
+ } else {
+ val instance = genLoadModule(sym.owner)
+ val method = encodeStaticMemberSym(sym)
+ js.Apply(instance, method, Nil)(toIRType(sym.info))
+ }
+ }
+
+ /** Gen JS code for loading a module.
+ *
+ * Can be given either the module symbol, or its module class symbol.
+ */
+ private def genLoadModule(sym0: Symbol)(implicit pos: Position): js.Tree = {
+ require(sym0.is(Module),
+ "genLoadModule called with non-module symbol: " + sym0)
+ val sym1 = if (sym0.isTerm) sym0.moduleClass else sym0
+ val sym = // redirect all static methods of String to RuntimeString
+ if (sym1 == defn.StringModule) jsdefn.RuntimeStringModule.moduleClass
+ else sym1
+
+ if (isJSType(sym)) {
+ if (isScalaJSDefinedJSClass(sym))
+ js.LoadJSModule(jstpe.ClassType(encodeClassFullName(sym)))
+ else if (sym.derivesFrom(jsdefn.JSGlobalScopeClass))
+ genLoadJSGlobal()
+ else
+ genLoadNativeJSModule(sym)
+ } else {
+ js.LoadModule(jstpe.ClassType(encodeClassFullName(sym)))
+ }
+ }
+
+ /** Gen JS code representing the constructor of a JS class. */
+ private def genLoadJSConstructor(sym: Symbol)(
+ implicit pos: Position): js.Tree = {
+ assert(!isStaticModule(sym) && !sym.is(Trait),
+ s"genPrimitiveJSClass called with non-class $sym")
+ js.LoadJSConstructor(jstpe.ClassType(encodeClassFullName(sym)))
+ }
+
+ /** Gen JS code representing a native JS module. */
+ private def genLoadNativeJSModule(sym: Symbol)(
+ implicit pos: Position): js.Tree = {
+ require(sym.is(ModuleClass),
+ s"genLoadNativeJSModule called with non-module $sym")
+ fullJSNameOf(sym).split('.').foldLeft(genLoadJSGlobal()) { (memo, chunk) =>
+ js.JSBracketSelect(memo, js.StringLiteral(chunk))
+ }
+ }
+
+ /** Gen JS code to load the JavaScript global scope. */
+ private def genLoadJSGlobal()(implicit pos: Position): js.Tree = {
+ js.JSBracketSelect(
+ js.JSBracketSelect(js.JSLinkingInfo(), js.StringLiteral("envInfo")),
+ js.StringLiteral("global"))
+ }
+
+ /** Generate a Class[_] value (e.g. coming from classOf[T]) */
+ private def genClassConstant(tpe: Type)(implicit pos: Position): js.Tree =
+ js.ClassOf(toReferenceType(tpe))
+
+ private def isStaticModule(sym: Symbol): Boolean =
+ sym.is(Module) && sym.isStatic
+
+ private def isPrimitiveValueType(tpe: Type): Boolean = {
+ tpe.widenDealias match {
+ case JavaArrayType(_) => false
+ case t => t.typeSymbol.asClass.isPrimitiveValueClass
+ }
+ }
+
+}