summaryrefslogtreecommitdiff
path: root/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala
diff options
context:
space:
mode:
Diffstat (limited to 'examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala')
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala1525
1 files changed, 1525 insertions, 0 deletions
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala
new file mode 100644
index 0000000..b4d4005
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala
@@ -0,0 +1,1525 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.javascript
+
+import scala.language.implicitConversions
+
+import scala.annotation.switch
+
+import scala.scalajs.ir._
+import Position._
+import Transformers._
+import scala.scalajs.ir.Trees._
+import scala.scalajs.ir.Types._
+
+import scala.scalajs.tools.sem._
+import CheckedBehavior._
+
+import scala.scalajs.tools.javascript.{Trees => js}
+
+/** Desugaring of the IR to regular ES5 JavaScript.
+ *
+ * The major difference between the IR and JS is that most constructs can be
+ * used in expression position. The main work of the desugaring is to
+ * unnest complex constructs in expression position so that they become
+ * statements.
+ *
+ * The general idea is two-folded:
+ * 1) Unnest complex constructs in "argument position":
+ * When a complex construct is used in a non-rhs expression position
+ * (argument to a function, operand, condition of an if, etc.), that we
+ * call "argument position", declare a variable before the statement,
+ * assign the complex construct to it and then use that variable in the
+ * argument position instead.
+ * 2) Push LHS's inside complex RHS's:
+ * When an rhs is a complex construct, push the lhs inside the complex
+ * construct. Are considered lhs:
+ * * Assign, i.e., `x =`
+ * * VarDef, i.e., `var x =`
+ * * Return, i.e., `return`
+ * * (EmptyTree is also used as a trick for code reuse)
+ * In fact, think that, in this context, LHS means: what to do with the
+ * result of evaluating the RHS.
+ *
+ * --------------------------------------------------------------------------
+ *
+ * Typical example, consider the method call:
+ *
+ * obj.meth({
+ * var x = foo(42);
+ * x*x
+ * });
+ *
+ * According to rule 1), the block that is passed as a parameter to obj.meth
+ * is first extracted in a synthetic var:
+ *
+ * var x\$1 = {
+ * var x = foo(42);
+ * x*x
+ * }
+ * obj.meth(x\$1);
+ *
+ * Then, according to rule 2), the lhs `var x\$1 =` is pushed inside the block:
+ *
+ * {
+ * var x = foo(42);
+ * var x\$1 = x*x;
+ * }
+ * obj.meth(x\$1);
+ *
+ * Because bare blocks are non-significant in JS, this is equivalent to
+ *
+ * var x = foo(42);
+ * var x\$1 = x*x;
+ * obj.meth(x\$1);
+ *
+ * --------------------------------------------------------------------------
+ *
+ * JSDesugaring does all this in a single pass, but it helps to think that:
+ * * Rule 1) is implemented by unnest(), and used most notably in
+ * * transformStat() for statement-only constructs
+ * * pushLhsInto() for statement-or-expression constructs
+ * * Rule 2) is implemented by pushLhsInto()
+ * * Emitting the class structure is delegated to [[ScalaJSClassEmitter]].
+ *
+ * There are a few other things that JSDesugaring takes care of:
+ * * Transform Scala expressions into their JS equivalent, taking the
+ * Scala.js class encoding into account.
+ * * And tiny details.
+ *
+ * @author Sébastien Doeraene
+ */
+object JSDesugaring {
+
+ private final val ScalaJSEnvironmentName = "ScalaJS"
+
+ /** Desugars a statement of the IR into ES5 JavaScript. */
+ def desugarJavaScript(tree: Tree, semantics: Semantics): js.Tree = {
+ new JSDesugar(semantics).transformStat(tree)
+ }
+
+ private[javascript] implicit def transformIdent(ident: Ident): js.Ident =
+ js.Ident(ident.name, ident.originalName)(ident.pos)
+
+ private[javascript] def transformParamDef(paramDef: ParamDef): js.ParamDef =
+ js.ParamDef(paramDef.name, paramDef.mutable)(paramDef.pos)
+
+ private class JSDesugar(semantics: Semantics) {
+
+ // Synthetic variables
+
+ var syntheticVarCounter: Int = 0
+
+ def newSyntheticVar()(implicit pos: Position): Ident = {
+ syntheticVarCounter += 1
+ Ident("jsx$" + syntheticVarCounter, None)
+ }
+
+ def resetSyntheticVarCounterIn[A](f: => A): A = {
+ val savedCounter = syntheticVarCounter
+ syntheticVarCounter = 0
+ try f
+ finally syntheticVarCounter = savedCounter
+ }
+
+ // Record names
+
+ def makeRecordFieldIdent(recIdent: Ident, fieldIdent: Ident)(
+ implicit pos: Position): Ident =
+ makeRecordFieldIdent(recIdent.name, recIdent.originalName,
+ fieldIdent.name, fieldIdent.originalName)
+
+ def makeRecordFieldIdent(recIdent: Ident,
+ fieldName: String, fieldOrigiName: Option[String])(
+ implicit pos: Position): Ident =
+ makeRecordFieldIdent(recIdent.name, recIdent.originalName,
+ fieldName, fieldOrigiName)
+
+ def makeRecordFieldIdent(recName: String, recOrigName: Option[String],
+ fieldName: String, fieldOrigName: Option[String])(
+ implicit pos: Position): Ident = {
+ val name = recName + "_$_" + fieldName
+ val originalName = Some(recOrigName.getOrElse(recName) + "." +
+ fieldOrigName.getOrElse(fieldName))
+ Ident(name, originalName)
+ }
+
+ // LHS'es for labeled expressions
+
+ var labeledExprLHSes: Map[Ident, Tree] = Map.empty
+
+ // Now the work
+
+ /** Desugar a statement of the IR into ES5 JS */
+ def transformStat(tree: Tree): js.Tree = {
+ implicit val pos = tree.pos
+
+ tree match {
+ // Statement-only language constructs
+
+ case Skip() =>
+ js.Skip()
+
+ case VarDef(varIdent, RecordType(fields), recMutable, EmptyTree) =>
+ js.Block(for {
+ RecordType.Field(fieldName, fieldOrigName, tpe, fieldMutable) <- fields
+ } yield {
+ transformStat {
+ VarDef(makeRecordFieldIdent(varIdent, fieldName, fieldOrigName),
+ tpe, recMutable || fieldMutable, EmptyTree)
+ }
+ })
+
+ case VarDef(name, _, mutable, EmptyTree) =>
+ js.VarDef(name, mutable, js.EmptyTree)
+
+ case VarDef(_, _, _, rhs) =>
+ pushLhsInto(tree, rhs)
+
+ case Assign(RecordFieldVarRef(lhs), rhs) =>
+ pushLhsInto(Assign(lhs, EmptyTree), rhs)
+
+ case Assign(select @ Select(qualifier, item, mutable), rhs) =>
+ unnest(qualifier, rhs) { (newQualifier, newRhs) =>
+ js.Assign(
+ js.DotSelect(transformExpr(newQualifier), item)(select.pos),
+ transformExpr(newRhs))
+ }
+
+ case Assign(select @ ArraySelect(array, index), rhs) =>
+ unnest(List(array, index, rhs)) {
+ case List(newArray, newIndex, newRhs) =>
+ js.Assign(
+ js.BracketSelect(js.DotSelect(transformExpr(newArray),
+ js.Ident("u"))(select.pos),
+ transformExpr(newIndex))(select.pos),
+ transformExpr(newRhs))
+ }
+
+ case Assign(select @ JSDotSelect(qualifier, item), rhs) =>
+ unnest(qualifier, rhs) { (newQualifier, newRhs) =>
+ js.Assign(
+ js.DotSelect(transformExpr(newQualifier), item)(select.pos),
+ transformExpr(newRhs))
+ }
+
+ case Assign(select @ JSBracketSelect(qualifier, item), rhs) =>
+ unnest(List(qualifier, item, rhs)) {
+ case List(newQualifier, newItem, newRhs) =>
+ js.Assign(
+ js.BracketSelect(transformExpr(newQualifier),
+ transformExpr(newItem))(select.pos),
+ transformExpr(newRhs))
+ }
+
+ case Assign(_ : VarRef, rhs) =>
+ pushLhsInto(tree, rhs)
+
+ case Assign(_, _) =>
+ sys.error(s"Illegal Assign in transformStat: $tree")
+
+ case StoreModule(cls, value) =>
+ assert(cls.className.endsWith("$"),
+ s"Trying to store module for non-module class $cls")
+ val moduleName = cls.className.dropRight(1)
+ unnest(value) { newValue =>
+ js.Assign(
+ js.DotSelect(envField("n"), Ident(moduleName)),
+ transformExpr(newValue))
+ }
+
+ case While(cond, body, label) =>
+ /* We cannot simply unnest(cond) here, because that would eject the
+ * evaluation of the condition out of the loop.
+ */
+ val newLabel = label.map(transformIdent)
+ if (isExpression(cond)) {
+ js.While(transformExpr(cond), transformStat(body), newLabel)
+ } else {
+ js.While(js.BooleanLiteral(true), {
+ unnest(cond) { newCond =>
+ js.If(transformExpr(newCond), transformStat(body), js.Break())
+ }
+ }, newLabel)
+ }
+
+ case DoWhile(body, cond, label) =>
+ /* We cannot simply unnest(cond) here, because that would eject the
+ * evaluation of the condition out of the loop.
+ */
+ val newLabel = label.map(transformIdent)
+ if (isExpression(cond)) {
+ js.DoWhile(transformStat(body), transformExpr(cond), newLabel)
+ } else {
+ /* This breaks 'continue' statements for this loop, but we don't
+ * care because we never emit continue statements for do..while
+ * loops.
+ */
+ js.While(js.BooleanLiteral(true), {
+ js.Block(transformStat(body), {
+ unnest(cond) { newCond =>
+ js.If(transformExpr(newCond), js.Skip(), js.Break())
+ }
+ })
+ }, newLabel)
+ }
+
+ case Debugger() =>
+ js.Debugger()
+
+ case JSDelete(JSDotSelect(obj, prop)) =>
+ unnest(obj) { (newObj) =>
+ js.Delete(js.DotSelect(transformExpr(newObj), prop))
+ }
+
+ case JSDelete(JSBracketSelect(obj, prop)) =>
+ unnest(obj, prop) { (newObj, newProp) =>
+ js.Delete(js.BracketSelect(
+ transformExpr(newObj), transformExpr(newProp)))
+ }
+
+ // Treat 'return' as an LHS
+
+ case Return(expr, label) =>
+ pushLhsInto(tree, expr)
+
+ /* Anything else is an expression => pushLhsInto(EmptyTree, _)
+ * In order not to duplicate all the code of pushLhsInto() here, we
+ * use a trick: EmptyTree is a dummy LHS that says "do nothing
+ * with the result of the rhs".
+ * This is exactly what an expression statement is doing: it evaluates
+ * the expression, but does nothing with its result.
+ */
+
+ case _ =>
+ pushLhsInto(EmptyTree, tree)
+ }
+ }
+
+ private object RecordFieldVarRef {
+ def unapply(tree: Tree): Option[VarRef] = {
+ tree match {
+ case Select(RecordVarRef(VarRef(recIdent, recMutable)),
+ fieldIdent, fieldMutable) =>
+ implicit val pos = tree.pos
+ Some(VarRef(makeRecordFieldIdent(recIdent, fieldIdent),
+ recMutable || fieldMutable)(tree.tpe))
+ case _ =>
+ None
+ }
+ }
+ }
+
+ private object RecordVarRef {
+ def unapply(tree: Tree): Option[VarRef] = {
+ if (!tree.tpe.isInstanceOf[RecordType]) None
+ else {
+ tree match {
+ case tree: VarRef => Some(tree)
+ case Select(RecordVarRef(VarRef(recIdent, recMutable)),
+ fieldIdent, fieldMutable) =>
+ implicit val pos = tree.pos
+ Some(VarRef(makeRecordFieldIdent(recIdent, fieldIdent),
+ recMutable || fieldMutable)(tree.tpe))
+ }
+ }
+ }
+ }
+
+ /** Unnest complex constructs in argument position in temporary variables
+ *
+ * If all the arguments are JS expressions, there is nothing to do.
+ * Any argument that is not a JS expression must be unnested and stored
+ * in a temporary variable before the statement produced by `makeStat`.
+ *
+ * But *this changes the evaluation order!* In order not to lose it, it
+ * is necessary to also unnest arguments that are expressions but that
+ * are supposed to be evaluated before the argument-to-be-unnested and
+ * could have side-effects or even whose evaluation could be influenced
+ * by the side-effects of another unnested argument.
+ *
+ * Without deep effect analysis, which we do not do, we need to take
+ * a very pessimistic approach, and unnest any expression that contains
+ * an identifier (except those after the last non-expression argument).
+ * Hence the predicate `isPureExpressionWithoutIdent`.
+ */
+ def unnest(args: List[Tree])(
+ makeStat: List[Tree] => js.Tree): js.Tree = {
+ if (args forall isExpression) makeStat(args)
+ else {
+ val extractedStatements = new scala.collection.mutable.ListBuffer[js.Tree]
+
+ /* Attention! Everything must be processed recursively
+ * *right-to-left*! Indeed, the point is that noExtractYet will tell
+ * whether anything supposed to be evaluated *after* the currently
+ * being processed expression has been (at least partly) extracted
+ * in temporary variables (or simply statements, in the Block case).
+ * If nothing has, we can keep more in place without having to extract
+ * that expression in a temporary variable.
+ */
+
+ def rec(arg: Tree): Tree = {
+ def noExtractYet = extractedStatements.isEmpty
+
+ if (if (noExtractYet) isExpression(arg) else isPureExpression(arg)) {
+ arg
+ } else {
+ implicit val pos = arg.pos
+ arg match {
+ case Block(stats :+ expr) =>
+ val result = rec(expr) // right-to-left, remember?
+ // Put the stats in a Block because ++=: is not smart
+ js.Block(stats.map(transformStat)) +=: extractedStatements
+ result
+
+ case UnaryOp(op, lhs) =>
+ UnaryOp(op, rec(lhs))
+ case BinaryOp(op, lhs, rhs) =>
+ val newRhs = rec(rhs)
+ BinaryOp(op, rec(lhs), newRhs)
+ case JSBinaryOp(op, lhs, rhs) =>
+ val newRhs = rec(rhs)
+ JSBinaryOp(op, rec(lhs), newRhs)
+ case JSUnaryOp(op, lhs) =>
+ JSUnaryOp(op, rec(lhs))
+ case IsInstanceOf(expr, tpe) =>
+ IsInstanceOf(rec(expr), tpe)
+
+ case AsInstanceOf(expr, tpe)
+ if noExtractYet || semantics.asInstanceOfs == Unchecked =>
+ AsInstanceOf(rec(expr), tpe)
+ case Unbox(expr, tpe)
+ if noExtractYet || semantics.asInstanceOfs == Unchecked =>
+ Unbox(rec(expr), tpe)
+
+ case NewArray(tpe, lengths) =>
+ NewArray(tpe, recs(lengths))
+ case ArrayValue(tpe, elems) =>
+ ArrayValue(tpe, recs(elems))
+ case JSArrayConstr(items) =>
+ JSArrayConstr(recs(items))
+ case JSObjectConstr(items) =>
+ val newValues = recs(items.map(_._2))
+ JSObjectConstr(items.map(_._1) zip newValues)
+ case Closure(captureParams, params, body, captureValues) =>
+ Closure(captureParams, params, body, recs(captureValues))
+
+ case New(cls, constr, args) if noExtractYet =>
+ New(cls, constr, recs(args))
+ case Select(qualifier, item, mutable) if noExtractYet =>
+ Select(rec(qualifier), item, mutable)(arg.tpe)
+ case Apply(receiver, method, args) if noExtractYet =>
+ val newArgs = recs(args)
+ Apply(rec(receiver), method, newArgs)(arg.tpe)
+ case StaticApply(receiver, cls, method, args) if noExtractYet =>
+ val newArgs = recs(args)
+ StaticApply(rec(receiver), cls, method, newArgs)(arg.tpe)
+ case TraitImplApply(impl, method, args) if noExtractYet =>
+ TraitImplApply(impl, method, recs(args))(arg.tpe)
+ case ArrayLength(array) if noExtractYet =>
+ ArrayLength(rec(array))
+ case ArraySelect(array, index) if noExtractYet =>
+ val newIndex = rec(index)
+ ArraySelect(rec(array), newIndex)(arg.tpe)
+ case CallHelper(helper, args) if noExtractYet =>
+ CallHelper(helper, recs(args))(arg.tpe)
+
+ case If(cond, thenp, elsep)
+ if noExtractYet && isExpression(thenp) && isExpression(elsep) =>
+ If(rec(cond), thenp, elsep)(arg.tpe)
+
+ case _ =>
+ val temp = newSyntheticVar()
+ val computeTemp =
+ pushLhsInto(VarDef(temp, arg.tpe, mutable = false, EmptyTree), arg)
+ computeTemp +=: extractedStatements
+ VarRef(temp, mutable = false)(arg.tpe)
+ }
+ }
+ }
+
+ def recs(args: List[Tree]): List[Tree] = {
+ // This is a right-to-left map
+ args.foldRight[List[Tree]](Nil) { (arg, acc) =>
+ rec(arg) :: acc
+ }
+ }
+
+ val newArgs = recs(args)
+
+ assert(extractedStatements.nonEmpty,
+ "Reached computeTemps with no temp to compute")
+
+ val newStatement = makeStat(newArgs)
+ js.Block(extractedStatements.result() ::: List(newStatement))(newStatement.pos)
+ }
+ }
+
+ /** Same as above, for a single argument */
+ def unnest(arg: Tree)(
+ makeStat: Tree => js.Tree): js.Tree = {
+ unnest(List(arg)) {
+ case List(newArg) => makeStat(newArg)
+ }
+ }
+
+ /** Same as above, for two arguments */
+ def unnest(lhs: Tree, rhs: Tree)(
+ makeStat: (Tree, Tree) => js.Tree): js.Tree = {
+ unnest(List(lhs, rhs)) {
+ case List(newLhs, newRhs) => makeStat(newLhs, newRhs)
+ }
+ }
+
+ /** Same as above, for one head argument and a list of arguments */
+ def unnest(arg0: Tree, args: List[Tree])(
+ makeStat: (Tree, List[Tree]) => js.Tree): js.Tree = {
+ unnest(arg0 :: args) { newArgs =>
+ makeStat(newArgs.head, newArgs.tail)
+ }
+ }
+
+ /** Common implementation for the functions below.
+ * A pure expression can be moved around or executed twice, because it
+ * will always produce the same result and never have side-effects.
+ * A side-effect free expression can be elided if its result is not used.
+ */
+ private def isExpressionInternal(tree: Tree,
+ allowUnpure: Boolean, allowSideEffects: Boolean): Boolean = {
+
+ require(!allowSideEffects || allowUnpure)
+
+ def test(tree: Tree): Boolean = tree match {
+ // Atomic expressions
+ case _: Literal => true
+ case _: This => true
+ case _: JSEnvInfo => true
+
+ // Vars and fields (side-effect free, pure if immutable)
+ case VarRef(_, mutable) =>
+ allowUnpure || !mutable
+ case Select(qualifier, item, mutable) =>
+ (allowUnpure || !mutable) && test(qualifier)
+
+ // Expressions preserving pureness
+ case Block(trees) => trees forall test
+ case If(cond, thenp, elsep) => test(cond) && test(thenp) && test(elsep)
+ case BinaryOp(_, lhs, rhs) => test(lhs) && test(rhs)
+ case UnaryOp(_, lhs) => test(lhs)
+ case JSBinaryOp(_, lhs, rhs) => test(lhs) && test(rhs)
+ case JSUnaryOp(_, lhs) => test(lhs)
+ case ArrayLength(array) => test(array)
+ case IsInstanceOf(expr, _) => test(expr)
+
+ // Expressions preserving side-effect freedom
+ case NewArray(tpe, lengths) =>
+ allowUnpure && (lengths forall test)
+ case ArrayValue(tpe, elems) =>
+ allowUnpure && (elems forall test)
+ case ArraySelect(array, index) =>
+ allowUnpure && test(array) && test(index)
+ case JSArrayConstr(items) =>
+ allowUnpure && (items forall test)
+ case JSObjectConstr(items) =>
+ allowUnpure && (items forall (item => test(item._2)))
+ case Closure(captureParams, params, body, captureValues) =>
+ allowUnpure && (captureValues forall test)
+
+ // Scala expressions that can always have side-effects
+ case New(cls, constr, args) =>
+ allowSideEffects && (args forall test)
+ case LoadModule(cls) => // unfortunately
+ allowSideEffects
+ case Apply(receiver, method, args) =>
+ allowSideEffects && test(receiver) && (args forall test)
+ case StaticApply(receiver, cls, method, args) =>
+ allowSideEffects && test(receiver) && (args forall test)
+ case TraitImplApply(impl, method, args) =>
+ allowSideEffects && (args forall test)
+ case GetClass(arg) =>
+ allowSideEffects && test(arg)
+ case CallHelper(helper, args) =>
+ allowSideEffects && (args forall test)
+
+ // Casts
+ case AsInstanceOf(expr, _) =>
+ (allowSideEffects || semantics.asInstanceOfs == Unchecked) && test(expr)
+ case Unbox(expr, _) =>
+ (allowSideEffects || semantics.asInstanceOfs == Unchecked) && test(expr)
+
+ // Because the env is a frozen object, env["global"] is pure
+ case JSBracketSelect(JSEnvInfo(), StringLiteral("global")) => true
+
+ // JavaScript expressions that can always have side-effects
+ case JSNew(fun, args) =>
+ allowSideEffects && test(fun) && (args forall test)
+ case JSDotSelect(qualifier, item) =>
+ allowSideEffects && test(qualifier)
+ case JSBracketSelect(qualifier, item) =>
+ allowSideEffects && test(qualifier) && test(item)
+ case JSFunctionApply(fun, args) =>
+ allowSideEffects && test(fun) && (args forall test)
+ case JSDotMethodApply(receiver, method, args) =>
+ allowSideEffects && test(receiver) && (args forall test)
+ case JSBracketMethodApply(receiver, method, args) =>
+ allowSideEffects && test(receiver) && test(method) && (args forall test)
+
+ // Non-expressions
+ case _ => false
+ }
+ test(tree)
+ }
+
+ /** Test whether the given tree is a standard JS expression.
+ */
+ def isExpression(tree: Tree): Boolean =
+ isExpressionInternal(tree, allowUnpure = true, allowSideEffects = true)
+
+ /** Test whether the given tree is a side-effect-free standard JS expression.
+ */
+ def isSideEffectFreeExpression(tree: Tree): Boolean =
+ isExpressionInternal(tree, allowUnpure = true, allowSideEffects = false)
+
+ /** Test whether the given tree is a pure standard JS expression.
+ */
+ def isPureExpression(tree: Tree): Boolean =
+ isExpressionInternal(tree, allowUnpure = false, allowSideEffects = false)
+
+ def doVarDef(ident: Ident, tpe: Type, mutable: Boolean, rhs: Tree): js.Tree = {
+ implicit val pos = rhs.pos
+ tpe match {
+ case RecordType(fields) =>
+ val elems = (rhs: @unchecked) match {
+ case RecordValue(_, elems) =>
+ elems
+ case VarRef(rhsIdent, rhsMutable) =>
+ for (RecordType.Field(fName, fOrigName, fTpe, fMutable) <- fields)
+ yield VarRef(makeRecordFieldIdent(rhsIdent, fName, fOrigName),
+ rhsMutable || fMutable)(fTpe)
+ }
+ js.Block(for {
+ (RecordType.Field(fName, fOrigName, fTpe, fMutable),
+ fRhs) <- fields zip elems
+ } yield {
+ doVarDef(makeRecordFieldIdent(ident, fName, fOrigName),
+ fTpe, mutable || fMutable, fRhs)
+ })
+
+ case _ =>
+ js.VarDef(ident, mutable, transformExpr(rhs))
+ }
+ }
+
+ def doAssign(lhs: Tree, rhs: Tree): js.Tree = {
+ implicit val pos = rhs.pos
+ lhs.tpe match {
+ case RecordType(fields) =>
+ val VarRef(ident, mutable) = lhs
+ val elems = (rhs: @unchecked) match {
+ case VarRef(rhsIdent, rhsMutable) =>
+ for (RecordType.Field(fName, fOrigName, fTpe, fMutable) <- fields)
+ yield VarRef(makeRecordFieldIdent(rhsIdent, fName, fOrigName),
+ rhsMutable || fMutable)(fTpe)
+ }
+ js.Block(for {
+ (RecordType.Field(fName, fOrigName, fTpe, fMutable),
+ fRhs) <- fields zip elems
+ } yield {
+ doAssign(VarRef(makeRecordFieldIdent(ident, fName, fOrigName),
+ mutable || fMutable)(fTpe), fRhs)
+ })
+
+ case _ =>
+ js.Assign(transformExpr(lhs), transformExpr(rhs))
+ }
+ }
+
+ /** Push an lhs into a (potentially complex) rhs
+ * lhs can be either a EmptyTree, a VarDef, a Assign or a
+ * Return
+ */
+ def pushLhsInto(lhs: Tree, rhs: Tree): js.Tree = {
+ implicit val rhsPos = rhs.pos
+
+ /** Push the current lhs further into a deeper rhs */
+ @inline def redo(newRhs: Tree) = pushLhsInto(lhs, newRhs)
+
+ if (rhs.tpe == NothingType && lhs != EmptyTree) {
+ /* A touch of peephole dead code elimination.
+ * Actually necessary to handle pushing an lhs into an infinite loop,
+ * for example.
+ */
+ val transformedRhs = pushLhsInto(EmptyTree, rhs)
+ lhs match {
+ case VarDef(name, _, mutable, _) =>
+ /* We still need to declare the var, in case it is used somewhere
+ * else in the function, where we can't dce it.
+ */
+ js.Block(js.VarDef(name, true, js.EmptyTree), transformedRhs)
+ case _ =>
+ transformedRhs
+ }
+ } else (rhs match {
+ // Handle the Block before testing whether it is an expression
+
+ case Block(stats :+ expr) =>
+ js.Block((stats map transformStat) :+ redo(expr))
+
+ // Base case, rhs is already a regular JS expression
+
+ case _ if isExpression(rhs) =>
+ (lhs: @unchecked) match {
+ case EmptyTree =>
+ if (isSideEffectFreeExpression(rhs)) js.Skip()
+ else transformExpr(rhs)
+ case VarDef(name, tpe, mutable, _) =>
+ doVarDef(name, tpe, mutable, rhs)
+ case Assign(lhs, _) =>
+ doAssign(lhs, rhs)
+ case Return(_, None) =>
+ js.Return(transformExpr(rhs))
+ case Return(_, label @ Some(l)) =>
+ labeledExprLHSes(l) match {
+ case newLhs @ Return(_, _) =>
+ pushLhsInto(newLhs, rhs) // no need to break here
+ case newLhs =>
+ js.Block(pushLhsInto(newLhs, rhs),
+ js.Break(label.map(transformIdent)))
+ }
+ }
+
+ // Almost base case with RecordValue
+
+ case RecordValue(recTpe, elems) =>
+ (lhs: @unchecked) match {
+ case EmptyTree =>
+ js.Block(elems map transformStat)
+ case VarDef(name, tpe, mutable, _) =>
+ unnest(elems) { newElems =>
+ doVarDef(name, tpe, mutable, RecordValue(recTpe, newElems))
+ }
+ case Assign(lhs, _) =>
+ unnest(elems) { newElems =>
+ val temp = newSyntheticVar()
+ js.Block(
+ doVarDef(temp, recTpe, false, RecordValue(recTpe, newElems)),
+ doAssign(lhs, VarRef(temp, false)(recTpe)))
+ }
+ case Return(_, label @ Some(l)) =>
+ val newLhs = labeledExprLHSes(l)
+ js.Block(pushLhsInto(newLhs, rhs),
+ js.Break(label.map(transformIdent)))
+ }
+
+ // Control flow constructs
+
+ case Labeled(label, tpe, body) =>
+ val savedMap = labeledExprLHSes
+ labeledExprLHSes = labeledExprLHSes + (label -> lhs)
+ try {
+ lhs match {
+ case Return(_, _) => redo(body)
+ case _ => js.Labeled(label, redo(body))
+ }
+ } finally {
+ labeledExprLHSes = savedMap
+ }
+
+ case Return(expr, _) =>
+ pushLhsInto(rhs, expr)
+
+ case Continue(label) =>
+ js.Continue(label.map(transformIdent))
+
+ case If(cond, thenp, elsep) =>
+ unnest(cond) { newCond =>
+ js.If(transformExpr(newCond), redo(thenp), redo(elsep))
+ }
+
+ case Try(block, errVar, handler, finalizer) =>
+ val newHandler =
+ if (handler == EmptyTree) js.EmptyTree else redo(handler)
+ val newFinalizer =
+ if (finalizer == EmptyTree) js.EmptyTree else transformStat(finalizer)
+
+ if (newHandler != js.EmptyTree && newFinalizer != js.EmptyTree) {
+ /* The Google Closure Compiler wrongly eliminates finally blocks, if
+ * the catch block throws an exception.
+ * Issues: #563, google/closure-compiler#186
+ *
+ * Therefore, we desugar
+ *
+ * try { ... } catch { ... } finally { ... }
+ *
+ * into
+ *
+ * try { try { ... } catch { ... } } finally { ... }
+ */
+ js.Try(js.Try(redo(block), errVar, newHandler, js.EmptyTree),
+ errVar, js.EmptyTree, newFinalizer)
+ } else
+ js.Try(redo(block), errVar, newHandler, newFinalizer)
+
+ // TODO Treat throw as an LHS?
+ case Throw(expr) =>
+ unnest(expr) { newExpr =>
+ js.Throw(transformExpr(newExpr))
+ }
+
+ /** Matches are desugared into switches
+ *
+ * A match is different from a switch in two respects, both linked
+ * to match being designed to be used in expression position in
+ * Extended-JS.
+ *
+ * * There is no fall-through from one case to the next one, hence,
+ * no break statement.
+ * * Match supports _alternatives_ explicitly (with a switch, one
+ * would use the fall-through behavior to implement alternatives).
+ */
+ case Match(selector, cases, default) =>
+ unnest(selector) { newSelector =>
+ val newCases = {
+ for {
+ (values, body) <- cases
+ newValues = (values map transformExpr)
+ // add the break statement
+ newBody = js.Block(redo(body), js.Break())
+ // desugar alternatives into several cases falling through
+ caze <- (newValues.init map (v => (v, js.Skip()))) :+ (newValues.last, newBody)
+ } yield {
+ caze
+ }
+ }
+ val newDefault =
+ if (default == EmptyTree) js.EmptyTree
+ else redo(default)
+ js.Switch(transformExpr(newSelector), newCases, newDefault)
+ }
+
+ // Scala expressions (if we reach here their arguments are not expressions)
+
+ case New(cls, ctor, args) =>
+ unnest(args) { newArgs =>
+ redo(New(cls, ctor, newArgs))
+ }
+
+ case Select(qualifier, item, mutable) =>
+ unnest(qualifier) { newQualifier =>
+ redo(Select(newQualifier, item, mutable)(rhs.tpe))
+ }
+
+ case Apply(receiver, method, args) =>
+ unnest(receiver, args) { (newReceiver, newArgs) =>
+ redo(Apply(newReceiver, method, newArgs)(rhs.tpe))
+ }
+
+ case StaticApply(receiver, cls, method, args) =>
+ unnest(receiver, args) { (newReceiver, newArgs) =>
+ redo(StaticApply(newReceiver, cls, method, newArgs)(rhs.tpe))
+ }
+
+ case TraitImplApply(impl, method, args) =>
+ unnest(args) { newArgs =>
+ redo(TraitImplApply(impl, method, newArgs)(rhs.tpe))
+ }
+
+ case UnaryOp(op, lhs) =>
+ unnest(lhs) { newLhs =>
+ redo(UnaryOp(op, newLhs))
+ }
+
+ case BinaryOp(op, lhs, rhs) =>
+ unnest(lhs, rhs) { (newLhs, newRhs) =>
+ redo(BinaryOp(op, newLhs, newRhs))
+ }
+
+ case NewArray(tpe, lengths) =>
+ unnest(lengths) { newLengths =>
+ redo(NewArray(tpe, newLengths))
+ }
+
+ case ArrayValue(tpe, elems) =>
+ unnest(elems) { newElems =>
+ redo(ArrayValue(tpe, newElems))
+ }
+
+ case ArrayLength(array) =>
+ unnest(array) { newArray =>
+ redo(ArrayLength(newArray))
+ }
+
+ case ArraySelect(array, index) =>
+ unnest(array, index) { (newArray, newIndex) =>
+ redo(ArraySelect(newArray, newIndex)(rhs.tpe))
+ }
+
+ case IsInstanceOf(expr, cls) =>
+ unnest(expr) { newExpr =>
+ redo(IsInstanceOf(newExpr, cls))
+ }
+
+ case AsInstanceOf(expr, cls) =>
+ if (semantics.asInstanceOfs == Unchecked) {
+ redo(expr)
+ } else {
+ unnest(expr) { newExpr =>
+ redo(AsInstanceOf(newExpr, cls))
+ }
+ }
+
+ case Unbox(expr, charCode) =>
+ unnest(expr) { newExpr =>
+ redo(Unbox(newExpr, charCode))
+ }
+
+ case GetClass(expr) =>
+ unnest(expr) { newExpr =>
+ redo(GetClass(newExpr))
+ }
+
+ case CallHelper(helper, args) =>
+ unnest(args) { newArgs =>
+ redo(CallHelper(helper, newArgs)(rhs.tpe))
+ }
+
+ // JavaScript expressions (if we reach here their arguments are not expressions)
+
+ case JSNew(ctor, args) =>
+ unnest(ctor :: args) { newCtorAndArgs =>
+ val newCtor :: newArgs = newCtorAndArgs
+ redo(JSNew(newCtor, newArgs))
+ }
+
+ case JSFunctionApply(fun, args) =>
+ unnest(fun :: args) { newFunAndArgs =>
+ val newFun :: newArgs = newFunAndArgs
+ redo(JSFunctionApply(newFun, newArgs))
+ }
+
+ case JSDotMethodApply(receiver, method, args) =>
+ unnest(receiver :: args) { newReceiverAndArgs =>
+ val newReceiver :: newArgs = newReceiverAndArgs
+ redo(JSDotMethodApply(newReceiver, method, newArgs))
+ }
+
+ case JSBracketMethodApply(receiver, method, args) =>
+ unnest(receiver :: method :: args) { newReceiverAndArgs =>
+ val newReceiver :: newMethod :: newArgs = newReceiverAndArgs
+ redo(JSBracketMethodApply(newReceiver, newMethod, newArgs))
+ }
+
+ case JSDotSelect(qualifier, item) =>
+ unnest(qualifier) { newQualifier =>
+ redo(JSDotSelect(newQualifier, item))
+ }
+
+ case JSBracketSelect(qualifier, item) =>
+ unnest(qualifier, item) { (newQualifier, newItem) =>
+ redo(JSBracketSelect(newQualifier, newItem))
+ }
+
+ case JSUnaryOp(op, lhs) =>
+ unnest(lhs) { newLhs =>
+ redo(JSUnaryOp(op, newLhs))
+ }
+
+ case JSBinaryOp("&&", lhs, rhs) =>
+ if (lhs.tpe == BooleanType) {
+ redo(If(lhs, rhs, BooleanLiteral(false))(AnyType))
+ } else {
+ unnest(lhs) { newLhs =>
+ redo(If(newLhs, rhs, newLhs)(AnyType))
+ }
+ }
+
+ case JSBinaryOp("||", lhs, rhs) =>
+ if (lhs.tpe == BooleanType) {
+ redo(If(lhs, BooleanLiteral(true), rhs)(AnyType))
+ } else {
+ unnest(lhs) { newLhs =>
+ redo(If(newLhs, newLhs, rhs)(AnyType))
+ }
+ }
+
+ case JSBinaryOp(op, lhs, rhs) =>
+ unnest(lhs, rhs) { (newLhs, newRhs) =>
+ redo(JSBinaryOp(op, newLhs, newRhs))
+ }
+
+ case JSArrayConstr(items) =>
+ unnest(items) { newItems =>
+ redo(JSArrayConstr(newItems))
+ }
+
+ case JSObjectConstr(fields) =>
+ val names = fields map (_._1)
+ val items = fields map (_._2)
+ unnest(items) { newItems =>
+ redo(JSObjectConstr(names.zip(newItems)))
+ }
+
+ // Closures
+
+ case Closure(captureParams, params, body, captureValues) =>
+ unnest(captureValues) { newCaptureValues =>
+ redo(Closure(captureParams, params, body, newCaptureValues))
+ }
+
+ case _ =>
+ if (lhs == EmptyTree) {
+ /* Go "back" to transformStat() after having dived into
+ * expression statements. Remember that (lhs == EmptyTree)
+ * is a trick that we use to "add" all the code of pushLhsInto()
+ * to transformStat().
+ */
+ rhs match {
+ case _:Skip | _:VarDef | _:Assign | _:While | _:DoWhile |
+ _:Debugger | _:JSDelete | _:StoreModule | _:ClassDef =>
+ transformStat(rhs)
+ case _ =>
+ sys.error("Illegal tree in JSDesugar.pushLhsInto():\n" +
+ "lhs = " + lhs + "\n" + "rhs = " + rhs +
+ " of class " + rhs.getClass)
+ }
+ } else {
+ sys.error("Illegal tree in JSDesugar.pushLhsInto():\n" +
+ "lhs = " + lhs + "\n" + "rhs = " + rhs +
+ " of class " + rhs.getClass)
+ }
+ })
+ }
+
+ // Desugar Scala operations to JavaScript operations -----------------------
+
+ /** Desugar an expression of the IR into ES5 JS */
+ def transformExpr(tree: Tree): js.Tree = {
+ import TreeDSL._
+
+ implicit val pos = tree.pos
+
+ def or0(tree: js.Tree): js.Tree =
+ js.BinaryOp("|", tree, js.IntLiteral(0))
+
+ tree match {
+ // Control flow constructs
+
+ case Block(stats :+ expr) =>
+ js.Block((stats map transformStat) :+ transformExpr(expr))
+
+ // Note that these work even if thenp/elsep is not a BooleanType
+ case If(cond, BooleanLiteral(true), elsep) =>
+ js.BinaryOp("||", transformExpr(cond), transformExpr(elsep))
+ case If(cond, thenp, BooleanLiteral(false)) =>
+ js.BinaryOp("&&", transformExpr(cond), transformExpr(thenp))
+
+ case If(cond, thenp, elsep) =>
+ js.If(transformExpr(cond), transformExpr(thenp), transformExpr(elsep))
+
+ // Scala expressions
+
+ case New(cls, ctor, args) =>
+ js.Apply(js.New(encodeClassVar(cls.className), Nil) DOT ctor,
+ args map transformExpr)
+
+ case LoadModule(cls) =>
+ genLoadModule(cls.className)
+
+ case RecordFieldVarRef(VarRef(name, mutable)) =>
+ js.VarRef(name, mutable)
+
+ case Select(qualifier, item, _) =>
+ transformExpr(qualifier) DOT item
+
+ case Apply(receiver, method, args) =>
+ val newReceiver = transformExpr(receiver)
+ val newArgs = args map transformExpr
+ if (isMaybeHijackedClass(receiver.tpe) &&
+ !Definitions.isReflProxyName(method.name)) {
+ val helperName = hijackedClassMethodToHelperName(method.name)
+ genCallHelper(helperName, newReceiver :: newArgs: _*)
+ } else {
+ js.Apply(newReceiver DOT method, newArgs)
+ }
+
+ case StaticApply(receiver, cls, method, args) =>
+ val fun = encodeClassVar(cls.className).prototype DOT method
+ js.Apply(fun DOT "call", (receiver :: args) map transformExpr)
+
+ case TraitImplApply(impl, method, args) =>
+ js.Apply(envField("i") DOT method, args map transformExpr)
+
+ case UnaryOp(op, lhs) =>
+ import UnaryOp._
+ val newLhs = transformExpr(lhs)
+ (op: @switch) match {
+ case `typeof` => js.UnaryOp("typeof", newLhs)
+ case Boolean_! => js.UnaryOp("!", newLhs)
+ case DoubleToInt => js.BinaryOp("|", newLhs, js.IntLiteral(0))
+
+ case LongToInt => genLongMethodApply(newLhs, LongImpl.toInt)
+ case LongToDouble => genLongMethodApply(newLhs, LongImpl.toDouble)
+
+ case DoubleToFloat => genFround(newLhs)
+
+ case IntToLong =>
+ genNewLong(LongImpl.initFromInt, newLhs)
+ case DoubleToLong =>
+ genLongModuleApply(LongImpl.fromDouble, newLhs)
+ }
+
+ case BinaryOp(op, lhs, rhs) =>
+ import BinaryOp._
+ val lhs1 = lhs match {
+ case UnaryOp(UnaryOp.DoubleToInt, inner)
+ if op == Int_& || op == Int_<< =>
+ /* This case is emitted typically by conversions from
+ * Float/Double to Char/Byte/Short. We have to introduce an
+ * (int) cast in the IR so that it typechecks, but in JavaScript
+ * this is redundant because & and << already convert both their
+ * operands to ints. So we get rid of the conversion here.
+ */
+ inner
+ case _ =>
+ lhs
+ }
+
+ val newLhs = transformExpr(lhs1)
+ val newRhs = transformExpr(rhs)
+
+ (op: @switch) match {
+ case === | Num_== | Boolean_== => js.BinaryOp("===", newLhs, newRhs)
+ case !== | Num_!= | Boolean_!= => js.BinaryOp("!==", newLhs, newRhs)
+
+ case String_+ =>
+ if (lhs.tpe == StringType || rhs.tpe == StringType)
+ js.BinaryOp("+", newLhs, newRhs)
+ else
+ js.BinaryOp("+", js.BinaryOp("+", js.StringLiteral(""), newLhs), newRhs)
+
+ case `in` => js.BinaryOp("in", newLhs, newRhs)
+ case `instanceof` => js.BinaryOp("instanceof", newLhs, newRhs)
+
+ case Int_+ => or0(js.BinaryOp("+", newLhs, newRhs))
+ case Int_- =>
+ lhs match {
+ case IntLiteral(0) => or0(js.UnaryOp("-", newRhs))
+ case _ => or0(js.BinaryOp("-", newLhs, newRhs))
+ }
+ case Int_* => genCallHelper("imul", newLhs, newRhs)
+ case Int_/ => or0(js.BinaryOp("/", newLhs, newRhs))
+ case Int_% => js.BinaryOp("%", newLhs, newRhs)
+
+ case Int_| => js.BinaryOp("|", newLhs, newRhs)
+ case Int_& => js.BinaryOp("&", newLhs, newRhs)
+ case Int_^ =>
+ lhs match {
+ case IntLiteral(-1) => js.UnaryOp("~", newRhs)
+ case _ => js.BinaryOp("^", newLhs, newRhs)
+ }
+ case Int_<< => js.BinaryOp("<<", newLhs, newRhs)
+ case Int_>>> => or0(js.BinaryOp(">>>", newLhs, newRhs))
+ case Int_>> => js.BinaryOp(">>", newLhs, newRhs)
+
+ case Float_+ => genFround(js.BinaryOp("+", newLhs, newRhs))
+ case Float_- =>
+ genFround(lhs match {
+ case DoubleLiteral(0.0) => js.UnaryOp("-", newRhs)
+ case _ => js.BinaryOp("-", newLhs, newRhs)
+ })
+ case Float_* => genFround(js.BinaryOp("*", newLhs, newRhs))
+ case Float_/ => genFround(js.BinaryOp("/", newLhs, newRhs))
+ case Float_% => genFround(js.BinaryOp("%", newLhs, newRhs))
+
+ case Double_+ => js.BinaryOp("+", newLhs, newRhs)
+ case Double_- =>
+ lhs match {
+ case DoubleLiteral(0.0) => js.UnaryOp("-", newRhs)
+ case _ => js.BinaryOp("-", newLhs, newRhs)
+ }
+ case Double_* => js.BinaryOp("*", newLhs, newRhs)
+ case Double_/ => js.BinaryOp("/", newLhs, newRhs)
+ case Double_% => js.BinaryOp("%", newLhs, newRhs)
+
+ case Num_< => js.BinaryOp("<", newLhs, newRhs)
+ case Num_<= => js.BinaryOp("<=", newLhs, newRhs)
+ case Num_> => js.BinaryOp(">", newLhs, newRhs)
+ case Num_>= => js.BinaryOp(">=", newLhs, newRhs)
+
+ case Long_+ => genLongMethodApply(newLhs, LongImpl.+, newRhs)
+ case Long_- =>
+ lhs match {
+ case LongLiteral(0L) => genLongMethodApply(newRhs, LongImpl.UNARY_-)
+ case _ => genLongMethodApply(newLhs, LongImpl.-, newRhs)
+ }
+ case Long_* => genLongMethodApply(newLhs, LongImpl.*, newRhs)
+ case Long_/ => genLongMethodApply(newLhs, LongImpl./, newRhs)
+ case Long_% => genLongMethodApply(newLhs, LongImpl.%, newRhs)
+
+ case Long_| => genLongMethodApply(newLhs, LongImpl.|, newRhs)
+ case Long_& => genLongMethodApply(newLhs, LongImpl.&, newRhs)
+ case Long_^ =>
+ lhs match {
+ case LongLiteral(-1L) => genLongMethodApply(newRhs, LongImpl.UNARY_~)
+ case _ => genLongMethodApply(newLhs, LongImpl.^, newRhs)
+ }
+ case Long_<< => genLongMethodApply(newLhs, LongImpl.<<, newRhs)
+ case Long_>>> => genLongMethodApply(newLhs, LongImpl.>>>, newRhs)
+ case Long_>> => genLongMethodApply(newLhs, LongImpl.>>, newRhs)
+
+ case Long_== => genLongMethodApply(newLhs, LongImpl.===, newRhs)
+ case Long_!= => genLongMethodApply(newLhs, LongImpl.!==, newRhs)
+ case Long_< => genLongMethodApply(newLhs, LongImpl.<, newRhs)
+ case Long_<= => genLongMethodApply(newLhs, LongImpl.<=, newRhs)
+ case Long_> => genLongMethodApply(newLhs, LongImpl.>, newRhs)
+ case Long_>= => genLongMethodApply(newLhs, LongImpl.>=, newRhs)
+
+ case Boolean_| => !(!js.BinaryOp("|", newLhs, newRhs))
+ case Boolean_& => !(!js.BinaryOp("&", newLhs, newRhs))
+ }
+
+ case NewArray(tpe, lengths) =>
+ genCallHelper("newArrayObject",
+ genClassDataOf(tpe), js.ArrayConstr(lengths map transformExpr))
+
+ case ArrayValue(tpe, elems) =>
+ genCallHelper("makeNativeArrayWrapper",
+ genClassDataOf(tpe), js.ArrayConstr(elems map transformExpr))
+
+ case ArrayLength(array) =>
+ js.BracketSelect(js.DotSelect(transformExpr(array),
+ Ident("u")), js.StringLiteral("length"))
+
+ case ArraySelect(array, index) =>
+ js.BracketSelect(js.DotSelect(transformExpr(array),
+ Ident("u")), transformExpr(index))
+
+ case IsInstanceOf(expr, cls) =>
+ genIsInstanceOf(transformExpr(expr), cls)
+
+ case AsInstanceOf(expr, cls) =>
+ val newExpr = transformExpr(expr)
+ if (semantics.asInstanceOfs == Unchecked) newExpr
+ else genAsInstanceOf(newExpr, cls)
+
+ case Unbox(expr, charCode) =>
+ val newExpr = transformExpr(expr)
+
+ if (semantics.asInstanceOfs == Unchecked) {
+ (charCode: @switch) match {
+ case 'Z' => !(!newExpr)
+ case 'B' | 'S' | 'I' => js.BinaryOp("|", newExpr, js.IntLiteral(0))
+ case 'J' => genCallHelper("uJ", newExpr)
+ case 'F' => genFround(newExpr)
+ case 'D' => js.UnaryOp("+", newExpr)
+ }
+ } else {
+ genCallHelper("u"+charCode, newExpr)
+ }
+
+ case GetClass(expr) =>
+ genCallHelper("objectGetClass", transformExpr(expr))
+
+ case CallHelper(helper, args) =>
+ genCallHelper(helper, args map transformExpr: _*)
+
+ // JavaScript expressions
+
+ case JSBracketSelect(JSEnvInfo(), StringLiteral("global")) =>
+ // Shortcut for this field which is heavily used
+ envField("g")
+
+ case JSNew(constr, args) =>
+ js.New(transformExpr(constr), args map transformExpr)
+
+ case JSDotSelect(qualifier, item) =>
+ js.DotSelect(transformExpr(qualifier), item)
+
+ case JSBracketSelect(qualifier, item) =>
+ js.BracketSelect(transformExpr(qualifier), transformExpr(item))
+
+ case JSFunctionApply(fun, args) =>
+ /* Protect the fun so that if it is, e.g.,
+ * path.f
+ * we emit
+ * (0, path.f)(args...)
+ * instead of
+ * path.f(args...)
+ * If we emit the latter, then `this` will be bound to `path` in
+ * `f`, which is sometimes extremely harmful (e.g., for builtin
+ * methods of `window`).
+ */
+ val transformedFun = transformExpr(fun)
+ val protectedFun = transformedFun match {
+ case _:js.DotSelect | _:js.BracketSelect =>
+ js.Block(js.IntLiteral(0), transformedFun)
+ case _ =>
+ transformedFun
+ }
+ js.Apply(protectedFun, args map transformExpr)
+
+ case JSDotMethodApply(receiver, method, args) =>
+ js.Apply(js.DotSelect(transformExpr(receiver), method),
+ args map transformExpr)
+
+ case JSBracketMethodApply(receiver, method, args) =>
+ js.Apply(js.BracketSelect(transformExpr(receiver),
+ transformExpr(method)), args map transformExpr)
+
+ case JSUnaryOp(op, lhs) =>
+ js.UnaryOp(op, transformExpr(lhs))
+
+ case JSBinaryOp(op, lhs, rhs) =>
+ js.BinaryOp(op, transformExpr(lhs), transformExpr(rhs))
+
+ case JSArrayConstr(items) =>
+ js.ArrayConstr(items map transformExpr)
+
+ case JSObjectConstr(fields) =>
+ js.ObjectConstr(fields map {
+ case (name: Ident, value) =>
+ (transformIdent(name), transformExpr(value))
+ case (StringLiteral(name), value) =>
+ (js.StringLiteral(name), transformExpr(value))
+ })
+
+ case JSEnvInfo() =>
+ envField("env")
+
+ // Literals
+
+ case Undefined() => js.Undefined()
+ case Null() => js.Null()
+ case BooleanLiteral(value) => js.BooleanLiteral(value)
+ case IntLiteral(value) => js.IntLiteral(value)
+ case FloatLiteral(value) => js.DoubleLiteral(value.toDouble)
+ case DoubleLiteral(value) => js.DoubleLiteral(value)
+ case StringLiteral(value) => js.StringLiteral(value)
+
+ case LongLiteral(0L) =>
+ genLongModuleApply(LongImpl.Zero)
+ case LongLiteral(value) =>
+ val (l, m, h) = LongImpl.extractParts(value)
+ genNewLong(LongImpl.initFromParts,
+ js.IntLiteral(l), js.IntLiteral(m), js.IntLiteral(h))
+
+ case ClassOf(cls) =>
+ js.Apply(js.DotSelect(genClassDataOf(cls), Ident("getClassOf")), Nil)
+
+ // Atomic expressions
+
+ case VarRef(name, mutable) =>
+ js.VarRef(name, mutable)
+
+ case This() =>
+ js.This()
+
+ case Closure(captureParams, params, body, captureValues) =>
+ val transformedBody = {
+ val withReturn = Return(body, None)
+ transformStat(withReturn) match {
+ case js.Block(stats :+ js.Return(js.Undefined())) => js.Block(stats)
+ case other => other
+ }
+ }
+
+ val innerFunction =
+ js.Function(params.map(transformParamDef), transformedBody)
+
+ if (captureParams.isEmpty) {
+ innerFunction
+ } else {
+ js.Apply(
+ js.Function(captureParams.map(transformParamDef), {
+ js.Return(innerFunction)
+ }),
+ captureValues.map(transformExpr))
+ }
+
+ // Invalid trees
+
+ case _ =>
+ sys.error("Invalid tree in JSDesugar.transformExpr() "+
+ s"of class ${tree.getClass}")
+ }
+ }
+
+ def isMaybeHijackedClass(tpe: Type): Boolean = tpe match {
+ case ClassType(cls) =>
+ Definitions.HijackedClasses.contains(cls) ||
+ Definitions.AncestorsOfHijackedClasses.contains(cls)
+ case AnyType | UndefType | BooleanType | IntType | LongType |
+ FloatType | DoubleType | StringType =>
+ true
+ case _ =>
+ false
+ }
+
+ val hijackedClassMethodToHelperName: Map[String, String] = Map(
+ "toString__T" -> "objectToString",
+ "clone__O" -> "objectClone",
+ "finalize__V" -> "objectFinalize",
+ "notify__V" -> "objectNotify",
+ "notifyAll__V" -> "objectNotifyAll",
+ "equals__O__Z" -> "objectEquals",
+ "hashCode__I" -> "objectHashCode",
+
+ "length__I" -> "charSequenceLength",
+ "charAt__I__C" -> "charSequenceCharAt",
+ "subSequence__I__I__jl_CharSequence" -> "charSequenceSubSequence",
+
+ "compareTo__O__I" -> "comparableCompareTo",
+ "compareTo__jl_Boolean__I" -> "comparableCompareTo",
+ "compareTo__jl_Byte__I" -> "comparableCompareTo",
+ "compareTo__jl_Short__I" -> "comparableCompareTo",
+ "compareTo__jl_Integer__I" -> "comparableCompareTo",
+ "compareTo__jl_Long__I" -> "comparableCompareTo",
+ "compareTo__jl_Float__I" -> "comparableCompareTo",
+ "compareTo__jl_Double__I" -> "comparableCompareTo",
+ "compareTo__jl_String__I" -> "comparableCompareTo",
+
+ "booleanValue__Z" -> "booleanBooleanValue",
+
+ "byteValue__B" -> "numberByteValue",
+ "shortValue__S" -> "numberShortValue",
+ "intValue__I" -> "numberIntValue",
+ "longValue__J" -> "numberLongValue",
+ "floatValue__F" -> "numberFloatValue",
+ "doubleValue__D" -> "numberDoubleValue",
+
+ "isNaN__Z" -> "isNaN",
+ "isInfinite__Z" -> "isInfinite"
+ )
+
+ def genClassDataOf(cls: ReferenceType)(implicit pos: Position): js.Tree = {
+ cls match {
+ case ClassType(className) =>
+ encodeClassField("d", className)
+ case ArrayType(base, dims) =>
+ (1 to dims).foldLeft(encodeClassField("d", base)) { (prev, _) =>
+ js.Apply(js.DotSelect(prev, js.Ident("getArrayOf")), Nil)
+ }
+ }
+ }
+
+ private def genFround(arg: js.Tree)(implicit pos: Position): js.Tree = {
+ genCallHelper("fround", arg)
+ }
+
+ private def genNewLong(ctor: String, args: js.Tree*)(
+ implicit pos: Position): js.Tree = {
+ import TreeDSL._
+ js.Apply(
+ js.New(encodeClassVar(LongImpl.RuntimeLongClass), Nil) DOT ctor,
+ args.toList)
+ }
+
+ private def genLongMethodApply(receiver: js.Tree, methodName: String,
+ args: js.Tree*)(implicit pos: Position): js.Tree = {
+ import TreeDSL._
+ js.Apply(receiver DOT methodName, args.toList)
+ }
+
+ private def genLongModuleApply(methodName: String, args: js.Tree*)(
+ implicit pos: Position): js.Tree = {
+ import TreeDSL._
+ js.Apply(
+ genLoadModule(LongImpl.RuntimeLongModuleClass) DOT methodName,
+ args.toList)
+ }
+
+ private def genLoadModule(moduleClass: String)(
+ implicit pos: Position): js.Tree = {
+ import TreeDSL._
+ assert(moduleClass.endsWith("$"),
+ s"Trying to load module for non-module class $moduleClass")
+ val moduleName = moduleClass.dropRight(1)
+ js.Apply(envField("m") DOT moduleName, Nil)
+ }
+
+ }
+
+ // Helpers
+
+ private[javascript] def genIsInstanceOf(expr: js.Tree, cls: ReferenceType)(
+ implicit pos: Position): js.Tree =
+ genIsAsInstanceOf(expr, cls, test = true)
+
+ private def genAsInstanceOf(expr: js.Tree, cls: ReferenceType)(
+ implicit pos: Position): js.Tree =
+ genIsAsInstanceOf(expr, cls, test = false)
+
+ private def genIsAsInstanceOf(expr: js.Tree, cls: ReferenceType, test: Boolean)(
+ implicit pos: Position): js.Tree = {
+ import Definitions._
+ import TreeDSL._
+
+ cls match {
+ case ClassType(className0) =>
+ val className =
+ if (className0 == BoxedLongClass) LongImpl.RuntimeLongClass
+ else className0
+
+ if (HijackedBoxedClasses.contains(className)) {
+ if (test) {
+ className match {
+ case BoxedUnitClass => expr === js.Undefined()
+ case BoxedBooleanClass => typeof(expr) === "boolean"
+ case BoxedByteClass => genCallHelper("isByte", expr)
+ case BoxedShortClass => genCallHelper("isShort", expr)
+ case BoxedIntegerClass => genCallHelper("isInt", expr)
+ case BoxedFloatClass => genCallHelper("isFloat", expr)
+ case BoxedDoubleClass => typeof(expr) === "number"
+ }
+ } else {
+ className match {
+ case BoxedUnitClass => genCallHelper("asUnit", expr)
+ case BoxedBooleanClass => genCallHelper("asBoolean", expr)
+ case BoxedByteClass => genCallHelper("asByte", expr)
+ case BoxedShortClass => genCallHelper("asShort", expr)
+ case BoxedIntegerClass => genCallHelper("asInt", expr)
+ case BoxedFloatClass => genCallHelper("asFloat", expr)
+ case BoxedDoubleClass => genCallHelper("asDouble", expr)
+ }
+ }
+ } else {
+ js.Apply(
+ envField(if (test) "is" else "as") DOT js.Ident(className),
+ List(expr))
+ }
+
+ case ArrayType(base, depth) =>
+ js.Apply(
+ envField(if (test) "isArrayOf" else "asArrayOf") DOT js.Ident(base),
+ List(expr, js.IntLiteral(depth)))
+ }
+ }
+
+ private[javascript] def genCallHelper(helperName: String, args: js.Tree*)(
+ implicit pos: Position): js.Tree =
+ js.Apply(envField(helperName), args.toList)
+
+ private[javascript] def encodeClassVar(className: String)(
+ implicit pos: Position): js.Tree =
+ encodeClassField("c", className)
+
+ private[javascript] def encodeClassField(field: String, className: String)(
+ implicit pos: Position): js.Tree =
+ js.DotSelect(envField(field), js.Ident(className))
+
+ private[javascript] def envField(field: String)(implicit pos: Position): js.Tree =
+ js.DotSelect(js.VarRef(js.Ident(ScalaJSEnvironmentName), false),
+ js.Ident(field))
+
+ private[javascript] implicit class MyTreeOps(val self: js.Tree) {
+ def prototype(implicit pos: Position): js.Tree =
+ js.DotSelect(self, js.Ident("prototype"))
+ }
+}