diff options
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.scala | 1525 |
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")) + } +} |