diff options
Diffstat (limited to 'examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala')
-rw-r--r-- | examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala | 3911 |
1 files changed, 3911 insertions, 0 deletions
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala new file mode 100644 index 0000000..f9885a0 --- /dev/null +++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala @@ -0,0 +1,3911 @@ +/* Scala.js compiler + * Copyright 2013 LAMP/EPFL + * @author Sébastien Doeraene + */ + +package scala.scalajs.compiler + +import scala.language.implicitConversions + +import scala.annotation.switch + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + +import scala.tools.nsc._ + +import scala.annotation.tailrec + +import scala.scalajs.ir +import ir.{Trees => js, Types => jstpe, ClassKind, Hashers} + +import util.ScopedVar +import ScopedVar.withScopedVars + +/** Generate JavaScript code and output it to disk + * + * @author Sébastien Doeraene + */ +abstract class GenJSCode extends plugins.PluginComponent + with TypeKinds + with JSEncoding + with GenJSExports + with ClassInfos + with GenJSFiles + with Compat210Component { + + val jsAddons: JSGlobalAddons { + val global: GenJSCode.this.global.type + } + + val scalaJSOpts: ScalaJSOptions + + import global._ + import jsAddons._ + import rootMirror._ + import definitions._ + import jsDefinitions._ + import JSTreeExtractors._ + + import treeInfo.hasSynthCaseSymbol + + import platform.isMaybeBoxed + + val phaseName = "jscode" + + /** testing: this will be called when ASTs are generated */ + def generatedJSAST(clDefs: List[js.Tree]): Unit + + /** Implicit conversion from nsc Position to ir.Position. */ + implicit def pos2irPos(pos: Position): ir.Position = { + if (pos == NoPosition) ir.Position.NoPosition + else { + val source = pos2irPosCache.toIRSource(pos.source) + // nsc positions are 1-based but IR positions are 0-based + ir.Position(source, pos.line-1, pos.column-1) + } + } + + private[this] object pos2irPosCache { + import scala.reflect.internal.util._ + + private[this] var lastNscSource: SourceFile = null + private[this] var lastIRSource: ir.Position.SourceFile = null + + def toIRSource(nscSource: SourceFile): ir.Position.SourceFile = { + if (nscSource != lastNscSource) { + lastIRSource = convert(nscSource) + lastNscSource = nscSource + } + lastIRSource + } + + private[this] def convert(nscSource: SourceFile): ir.Position.SourceFile = { + nscSource.file.file match { + case null => + new java.net.URI( + "virtualfile", // Pseudo-Scheme + nscSource.file.path, // Scheme specific part + null // Fragment + ) + case file => + val srcURI = file.toURI + def matches(pat: java.net.URI) = pat.relativize(srcURI) != srcURI + + scalaJSOpts.sourceURIMaps.collectFirst { + case ScalaJSOptions.URIMap(from, to) if matches(from) => + val relURI = from.relativize(srcURI) + to.fold(relURI)(_.resolve(relURI)) + } getOrElse srcURI + } + } + + def clear(): Unit = { + lastNscSource = null + lastIRSource = null + } + } + + /** Materialize implicitly an ir.Position from an implicit nsc Position. */ + implicit def implicitPos2irPos(implicit pos: Position): ir.Position = pos + + override def newPhase(p: Phase) = new JSCodePhase(p) + + private object jsnme { + val arg_outer = newTermName("arg$outer") + val newString = newTermName("newString") + } + + class JSCodePhase(prev: Phase) extends StdPhase(prev) with JSExportsPhase { + + override def name = phaseName + override def description = "Generate JavaScript code from ASTs" + override def erasedTypes = true + + // Some state -------------------------------------------------------------- + + val currentClassSym = new ScopedVar[Symbol] + val currentClassInfoBuilder = new ScopedVar[ClassInfoBuilder] + val currentMethodSym = new ScopedVar[Symbol] + val currentMethodInfoBuilder = new ScopedVar[MethodInfoBuilder] + val methodTailJumpThisSym = new ScopedVar[Symbol](NoSymbol) + val fakeTailJumpParamRepl = new ScopedVar[(Symbol, Symbol)]((NoSymbol, NoSymbol)) + val enclosingLabelDefParams = new ScopedVar(Map.empty[Symbol, List[Symbol]]) + val mutableLocalVars = new ScopedVar[mutable.Set[Symbol]] + val mutatedLocalVars = new ScopedVar[mutable.Set[Symbol]] + val paramAccessorLocals = new ScopedVar(Map.empty[Symbol, js.ParamDef]) + + var isModuleInitialized: Boolean = false // see genApply for super calls + + def currentClassType = encodeClassType(currentClassSym) + + val tryingToGenMethodAsJSFunction = new ScopedVar[Boolean](false) + class CancelGenMethodAsJSFunction(message: String) + extends Throwable(message) with scala.util.control.ControlThrowable + + // Rewriting of anonymous function classes --------------------------------- + + private val translatedAnonFunctions = + mutable.Map.empty[Symbol, + (/*ctor args:*/ List[js.Tree] => /*instance:*/ js.Tree, ClassInfoBuilder)] + private val instantiatedAnonFunctions = + mutable.Set.empty[Symbol] + private val undefinedDefaultParams = + mutable.Set.empty[Symbol] + + // Top-level apply --------------------------------------------------------- + + override def run() { + scalaPrimitives.init() + jsPrimitives.init() + super.run() + } + + /** Generates the Scala.js IR for a compilation unit + * This method iterates over all the class and interface definitions + * found in the compilation unit and emits their IR (.sjsir). + * + * Some classes are never actually emitted: + * - Classes representing primitive types + * - The scala.Array class + * - Implementation classes for raw JS traits + * + * Some classes representing anonymous functions are not actually emitted. + * Instead, a temporary representation of their `apply` method is built + * and recorded, so that it can be inlined as a JavaScript anonymous + * function in the method that instantiates it. + * + * Other ClassDefs are emitted according to their nature: + * * Raw JS type (<: js.Any) -> `genRawJSClassData()` + * * Interface -> `genInterface()` + * * Implementation class -> `genImplClass()` + * * Normal class -> `genClass()` + */ + override def apply(cunit: CompilationUnit) { + try { + val generatedClasses = ListBuffer.empty[(Symbol, js.ClassDef, ClassInfoBuilder)] + + def collectClassDefs(tree: Tree): List[ClassDef] = { + tree match { + case EmptyTree => Nil + case PackageDef(_, stats) => stats flatMap collectClassDefs + case cd: ClassDef => cd :: Nil + } + } + val allClassDefs = collectClassDefs(cunit.body) + + /* First gen and record lambdas for js.FunctionN and js.ThisFunctionN. + * Since they are SAMs, there cannot be dependencies within this set, + * and hence we are sure we can record them before they are used, + * which is critical for these. + */ + val nonRawJSFunctionDefs = allClassDefs filterNot { cd => + if (isRawJSFunctionDef(cd.symbol)) { + genAndRecordRawJSFunctionClass(cd) + true + } else { + false + } + } + + /* Then try to gen and record lambdas for scala.FunctionN. + * These may fail, and sometimes because of dependencies. Since there + * appears to be more forward dependencies than backward dependencies + * (at least for non-nested lambdas, which we cannot translate anyway), + * we process class defs in reverse order here. + */ + val fullClassDefs = (nonRawJSFunctionDefs.reverse filterNot { cd => + cd.symbol.isAnonymousFunction && tryGenAndRecordAnonFunctionClass(cd) + }).reverse + + /* Finally, we emit true code for the remaining class defs. */ + for (cd <- fullClassDefs) { + val sym = cd.symbol + implicit val pos = sym.pos + + /* Do not actually emit code for primitive types nor scala.Array. */ + val isPrimitive = + isPrimitiveValueClass(sym) || (sym == ArrayClass) + + /* Similarly, do not emit code for impl classes of raw JS traits. */ + val isRawJSImplClass = + sym.isImplClass && isRawJSType( + sym.owner.info.decl(sym.name.dropRight(nme.IMPL_CLASS_SUFFIX.length)).tpe) + + if (!isPrimitive && !isRawJSImplClass) { + withScopedVars( + currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass), + currentClassSym := sym + ) { + val tree = if (isRawJSType(sym.tpe)) { + assert(!isRawJSFunctionDef(sym), + s"Raw JS function def should have been recorded: $cd") + genRawJSClassData(cd) + } else if (sym.isInterface) { + genInterface(cd) + } else if (sym.isImplClass) { + genImplClass(cd) + } else { + genClass(cd) + } + generatedClasses += ((sym, tree, currentClassInfoBuilder.get)) + } + } + } + + val clDefs = generatedClasses.map(_._2).toList + generatedJSAST(clDefs) + + for ((sym, tree, infoBuilder) <- generatedClasses) { + genIRFile(cunit, sym, tree, infoBuilder.result()) + } + } finally { + translatedAnonFunctions.clear() + instantiatedAnonFunctions.clear() + undefinedDefaultParams.clear() + pos2irPosCache.clear() + } + } + + // Generate a class -------------------------------------------------------- + + /** Gen the IR ClassDef for a class definition (maybe a module class). + */ + def genClass(cd: ClassDef): js.ClassDef = { + val ClassDef(mods, name, _, impl) = cd + val sym = cd.symbol + implicit val pos = sym.pos + + assert(!sym.isInterface && !sym.isImplClass, + "genClass() must be called only for normal classes: "+sym) + assert(sym.superClass != NoSymbol, sym) + + val classIdent = encodeClassFullNameIdent(sym) + val isHijacked = isHijackedBoxedClass(sym) + + // Optimizer hints + + def isStdLibClassWithAdHocInlineAnnot(sym: Symbol): Boolean = { + val fullName = sym.fullName + (fullName.startsWith("scala.Tuple") && !fullName.endsWith("$")) || + (fullName.startsWith("scala.collection.mutable.ArrayOps$of")) + } + + if (sym.hasAnnotation(InlineAnnotationClass) || + (sym.isAnonymousFunction && !sym.isSubClass(PartialFunctionClass)) || + isStdLibClassWithAdHocInlineAnnot(sym)) + currentClassInfoBuilder.optimizerHints = + currentClassInfoBuilder.optimizerHints.copy(hasInlineAnnot = true) + + // Generate members (constructor + methods) + + val generatedMembers = new ListBuffer[js.Tree] + val exportedSymbols = new ListBuffer[Symbol] + + if (!isHijacked) + generatedMembers ++= genClassFields(cd) + + def gen(tree: Tree): Unit = { + tree match { + case EmptyTree => () + case Template(_, _, body) => body foreach gen + + case ValDef(mods, name, tpt, rhs) => + () // fields are added via genClassFields() + + case dd: DefDef => + val sym = dd.symbol + + val isExport = jsInterop.isExport(sym) + val isNamedExport = isExport && sym.annotations.exists( + _.symbol == JSExportNamedAnnotation) + + if (isNamedExport) + generatedMembers += genNamedExporterDef(dd) + else + generatedMembers ++= genMethod(dd) + + if (isExport) { + // We add symbols that we have to export here. This way we also + // get inherited stuff that is implemented in this class. + exportedSymbols += sym + } + + case _ => abort("Illegal tree in gen of genClass(): " + tree) + } + } + + gen(impl) + + // Create method info builder for exported stuff + val exports = withScopedVars( + currentMethodInfoBuilder := currentClassInfoBuilder.addMethod( + dceExportName + classIdent.name, isExported = true) + ) { + // Generate the exported members + val memberExports = genMemberExports(sym, exportedSymbols.toList) + + // Generate exported constructors or accessors + val exportedConstructorsOrAccessors = + if (isStaticModule(sym)) genModuleAccessorExports(sym) + else genConstructorExports(sym) + if (exportedConstructorsOrAccessors.nonEmpty) + currentClassInfoBuilder.isExported = true + + memberExports ++ exportedConstructorsOrAccessors + } + + // Generate the reflective call proxies (where required) + val reflProxies = + if (isHijacked) Nil + else genReflCallProxies(sym) + + // Hashed definitions of the class + val hashedDefs = + Hashers.hashDefs(generatedMembers.toList ++ exports ++ reflProxies) + + // The complete class definition + val kind = + if (sym.isModuleClass) ClassKind.ModuleClass + else if (isHijacked) ClassKind.HijackedClass + else ClassKind.Class + + val classDefinition = js.ClassDef( + classIdent, + kind, + Some(encodeClassFullNameIdent(sym.superClass)), + sym.ancestors.map(encodeClassFullNameIdent), + hashedDefs) + + classDefinition + } + + // Generate the class data of a raw JS class ------------------------------- + + /** Gen the IR ClassDef for a raw JS class or trait. + */ + def genRawJSClassData(cd: ClassDef): js.ClassDef = { + val sym = cd.symbol + implicit val pos = sym.pos + + // Check that RawJS type is not exported + for (exp <- jsInterop.exportsOf(sym)) + reporter.error(exp.pos, "You may not export a class extending js.Any") + + val classIdent = encodeClassFullNameIdent(sym) + js.ClassDef(classIdent, ClassKind.RawJSType, None, Nil, Nil) + } + + // Generate an interface --------------------------------------------------- + + /** Gen the IR ClassDef for an interface definition. + */ + def genInterface(cd: ClassDef): js.ClassDef = { + val sym = cd.symbol + implicit val pos = sym.pos + + val classIdent = encodeClassFullNameIdent(sym) + + // fill in class info builder + def gen(tree: Tree) { + tree match { + case EmptyTree => () + case Template(_, _, body) => body foreach gen + case dd: DefDef => + currentClassInfoBuilder.addMethod( + encodeMethodName(dd.symbol), isAbstract = true) + case _ => abort("Illegal tree in gen of genInterface(): " + tree) + } + } + gen(cd.impl) + + // Check that interface/trait is not exported + for (exp <- jsInterop.exportsOf(sym)) + reporter.error(exp.pos, "You may not export a trait") + + js.ClassDef(classIdent, ClassKind.Interface, None, + sym.ancestors.map(encodeClassFullNameIdent), Nil) + } + + // Generate an implementation class of a trait ----------------------------- + + /** Gen the IR ClassDef for an implementation class (of a trait). + */ + def genImplClass(cd: ClassDef): js.ClassDef = { + val ClassDef(mods, name, _, impl) = cd + val sym = cd.symbol + implicit val pos = sym.pos + + def gen(tree: Tree): List[js.MethodDef] = { + tree match { + case EmptyTree => Nil + case Template(_, _, body) => body.flatMap(gen) + + case dd: DefDef => + val m = genMethod(dd) + m.toList + + case _ => abort("Illegal tree in gen of genImplClass(): " + tree) + } + } + val generatedMethods = gen(impl) + + js.ClassDef(encodeClassFullNameIdent(sym), ClassKind.TraitImpl, + None, Nil, generatedMethods) + } + + // Generate the fields of a class ------------------------------------------ + + /** Gen definitions for the fields of a class. + * The fields are initialized with the zero of their types. + */ + def genClassFields(cd: ClassDef): List[js.VarDef] = withScopedVars( + currentMethodInfoBuilder := + currentClassInfoBuilder.addMethod("__init__") + ) { + // Non-method term members are fields, except for module members. + (for { + f <- currentClassSym.info.decls + if !f.isMethod && f.isTerm && !f.isModule + } yield { + implicit val pos = f.pos + js.VarDef(encodeFieldSym(f), toIRType(f.tpe), + mutable = f.isMutable, genZeroOf(f.tpe)) + }).toList + } + + // Generate a method ------------------------------------------------------- + + def genMethod(dd: DefDef): Option[js.MethodDef] = withNewLocalNameScope { + genMethodWithInfoBuilder(dd).map(_._1) + } + + /** Gen JS code for a method definition in a class or in an impl class. + * On the JS side, method names are mangled to encode the full signature + * of the Scala method, as described in `JSEncoding`, to support + * overloading. + * + * Some methods are not emitted at all: + * * Primitives, since they are never actually called + * * Abstract methods + * * Constructors of hijacked classes + * * Trivial constructors, which only call their super constructor, with + * the same signature, and the same arguments. The JVM needs these + * constructors, but not JavaScript. Since there are lots of them, we + * take the trouble of recognizing and removing them. + * + * Constructors are emitted by generating their body as a statement, then + * return `this`. + * + * Other (normal) methods are emitted with `genMethodBody()`. + */ + def genMethodWithInfoBuilder( + dd: DefDef): Option[(js.MethodDef, MethodInfoBuilder)] = { + + implicit val pos = dd.pos + val DefDef(mods, name, _, vparamss, _, rhs) = dd + val sym = dd.symbol + + isModuleInitialized = false + + val result = withScopedVars( + currentMethodSym := sym, + methodTailJumpThisSym := NoSymbol, + fakeTailJumpParamRepl := (NoSymbol, NoSymbol), + enclosingLabelDefParams := Map.empty + ) { + assert(vparamss.isEmpty || vparamss.tail.isEmpty, + "Malformed parameter list: " + vparamss) + val params = if (vparamss.isEmpty) Nil else vparamss.head map (_.symbol) + + assert(!sym.owner.isInterface, + "genMethod() must not be called for methods in interfaces: "+sym) + + val methodIdent = encodeMethodSym(sym) + + def createInfoBuilder(isAbstract: Boolean = false) = { + currentClassInfoBuilder.addMethod(methodIdent.name, + isAbstract = isAbstract, + isExported = sym.isClassConstructor && + jsInterop.exportsOf(sym).nonEmpty) + } + + if (scalaPrimitives.isPrimitive(sym)) { + None + } else if (sym.isDeferred) { + createInfoBuilder(isAbstract = true) + None + } else if (isRawJSCtorDefaultParam(sym)) { + None + } else if (isTrivialConstructor(sym, params, rhs)) { + createInfoBuilder().callsMethod(sym.owner.superClass, methodIdent) + None + } else if (sym.isClassConstructor && isHijackedBoxedClass(sym.owner)) { + None + } else { + withScopedVars( + currentMethodInfoBuilder := createInfoBuilder(), + mutableLocalVars := mutable.Set.empty, + mutatedLocalVars := mutable.Set.empty + ) { + def shouldMarkInline = { + sym.hasAnnotation(InlineAnnotationClass) || + sym.name.startsWith(nme.ANON_FUN_NAME) + } + currentMethodInfoBuilder.optimizerHints = + currentMethodInfoBuilder.optimizerHints.copy( + isAccessor = sym.isAccessor, + hasInlineAnnot = shouldMarkInline) + + val methodDef = { + if (sym.isClassConstructor) { + val jsParams = for (param <- params) yield { + implicit val pos = param.pos + js.ParamDef(encodeLocalSym(param), toIRType(param.tpe), + mutable = false) + } + js.MethodDef(methodIdent, jsParams, currentClassType, + js.Block(genStat(rhs), genThis()))(None) + } else { + val resultIRType = toIRType(sym.tpe.resultType) + genMethodDef(methodIdent, params, resultIRType, rhs) + } + } + + val methodDefWithoutUselessVars = { + val unmutatedMutableLocalVars = + (mutableLocalVars -- mutatedLocalVars).toList + val mutatedImmutableLocalVals = + (mutatedLocalVars -- mutableLocalVars).toList + if (unmutatedMutableLocalVars.isEmpty && + mutatedImmutableLocalVals.isEmpty) { + // OK, we're good (common case) + methodDef + } else { + val patches = ( + unmutatedMutableLocalVars.map(encodeLocalSym(_).name -> false) ::: + mutatedImmutableLocalVals.map(encodeLocalSym(_).name -> true) + ).toMap + patchMutableFlagOfLocals(methodDef, patches) + } + } + + Some((methodDefWithoutUselessVars, currentMethodInfoBuilder.get)) + } + } + } + + result + } + + private def isTrivialConstructor(sym: Symbol, params: List[Symbol], + rhs: Tree): Boolean = { + if (!sym.isClassConstructor) { + false + } else { + rhs match { + // Shape of a constructor that only calls super + case Block(List(Apply(fun @ Select(_:Super, _), args)), Literal(_)) => + val callee = fun.symbol + implicit val dummyPos = NoPosition + + // Does the callee have the same signature as sym + if (encodeMethodSym(sym) == encodeMethodSym(callee)) { + // Test whether args are trivial forwarders + assert(args.size == params.size, "Argument count mismatch") + params.zip(args) forall { case (param, arg) => + arg.symbol == param + } + } else { + false + } + + case _ => false + } + } + } + + /** Patches the mutable flags of selected locals in a [[js.MethodDef]]. + * + * @param patches Map from local name to new value of the mutable flags. + * For locals not in the map, the flag is untouched. + */ + private def patchMutableFlagOfLocals(methodDef: js.MethodDef, + patches: Map[String, Boolean]): js.MethodDef = { + + def newMutable(name: String, oldMutable: Boolean): Boolean = + patches.getOrElse(name, oldMutable) + + val js.MethodDef(methodName, params, resultType, body) = methodDef + val newParams = for { + p @ js.ParamDef(name, ptpe, mutable) <- params + } yield { + js.ParamDef(name, ptpe, newMutable(name.name, mutable))(p.pos) + } + val transformer = new ir.Transformers.Transformer { + override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match { + case js.VarDef(name, vtpe, mutable, rhs) => + assert(isStat) + super.transform(js.VarDef( + name, vtpe, newMutable(name.name, mutable), rhs)(tree.pos), isStat) + case js.VarRef(name, mutable) => + js.VarRef(name, newMutable(name.name, mutable))(tree.tpe)(tree.pos) + case js.Closure(captureParams, params, body, captureValues) => + js.Closure(captureParams, params, body, + captureValues.map(transformExpr))(tree.pos) + case _ => + super.transform(tree, isStat) + } + } + val newBody = + transformer.transform(body, isStat = resultType == jstpe.NoType) + js.MethodDef(methodName, newParams, resultType, newBody)(None)(methodDef.pos) + } + + /** + * Generates reflective proxy methods for methods in sym + * + * Reflective calls don't depend on the return type, so it's hard to + * generate calls without using runtime reflection to list the methods. We + * generate a method to be used for reflective calls (without return + * type in the name). + * + * There are cases where non-trivial overloads cause ambiguous situations: + * + * {{{ + * object A { + * def foo(x: Option[Int]): String + * def foo(x: Option[String]): Int + * } + * }}} + * + * This is completely legal code, but due to the same erased parameter + * type of the {{{foo}}} overloads, they cannot be disambiguated in a + * reflective call, as the exact return type is unknown at the call site. + * + * Cases like the upper currently fail on the JVM backend at runtime. The + * Scala.js backend uses the following rules for selection (which will + * also cause runtime failures): + * + * - If a proxy with the same signature (method name and parameters) + * exists in the superclass, no proxy is generated (proxy is inherited) + * - If no proxy exists in the superclass, a proxy is generated for the + * first method with matching signatures. + */ + def genReflCallProxies(sym: Symbol): List[js.MethodDef] = { + import scala.reflect.internal.Flags + + // Flags of members we do not want to consider for reflective call proxys + val excludedFlags = ( + Flags.BRIDGE | + Flags.PRIVATE | + Flags.MACRO + ) + + /** Check if two method symbols conform in name and parameter types */ + def weakMatch(s1: Symbol)(s2: Symbol) = { + val p1 = s1.tpe.params + val p2 = s2.tpe.params + s1 == s2 || // Shortcut + s1.name == s2.name && + p1.size == p2.size && + (p1 zip p2).forall { case (s1,s2) => + s1.tpe =:= s2.tpe + } + } + + /** Check if the symbol's owner's superclass has a matching member (and + * therefore an existing proxy). + */ + def superHasProxy(s: Symbol) = { + val alts = sym.superClass.tpe.findMember( + name = s.name, + excludedFlags = excludedFlags, + requiredFlags = Flags.METHOD, + stableOnly = false).alternatives + alts.exists(weakMatch(s) _) + } + + // Query candidate methods + val methods = sym.tpe.findMembers( + excludedFlags = excludedFlags, + requiredFlags = Flags.METHOD) + + val candidates = methods filterNot { s => + s.isConstructor || + superHasProxy(s) || + jsInterop.isExport(s) + } + + val proxies = candidates filter { + c => candidates.find(weakMatch(c) _).get == c + } + + proxies.map(genReflCallProxy _).toList + } + + /** actually generates reflective call proxy for the given method symbol */ + private def genReflCallProxy(sym: Symbol): js.MethodDef = { + implicit val pos = sym.pos + + val proxyIdent = encodeMethodSym(sym, reflProxy = true) + + withNewLocalNameScope { + withScopedVars( + currentMethodInfoBuilder := + currentClassInfoBuilder.addMethod(proxyIdent.name) + ) { + val jsParams = for (param <- sym.tpe.params) yield { + implicit val pos = param.pos + js.ParamDef(encodeLocalSym(param), toIRType(param.tpe), + mutable = false) + } + + val call = genApplyMethod(genThis(), sym.owner, sym, + jsParams.map(_.ref)) + val body = ensureBoxed(call, + enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType)) + + js.MethodDef(proxyIdent, jsParams, jstpe.AnyType, body)(None) + } + } + } + + /** Generates the MethodDef of a (non-constructor) method + * + * Most normal methods are emitted straightforwardly. If the result + * type is Unit, then the body is emitted as a statement. Otherwise, it is + * emitted as an expression. + * + * The additional complexity of this method handles the transformation of + * a peculiarity of recursive tail calls: the local ValDef that replaces + * `this`. + */ + def genMethodDef(methodIdent: js.Ident, paramsSyms: List[Symbol], + resultIRType: jstpe.Type, tree: Tree): js.MethodDef = { + implicit val pos = tree.pos + + val jsParams = for (param <- paramsSyms) yield { + implicit val pos = param.pos + js.ParamDef(encodeLocalSym(param), toIRType(param.tpe), mutable = false) + } + + val bodyIsStat = resultIRType == jstpe.NoType + + val body = tree match { + case Block( + (thisDef @ ValDef(_, nme.THIS, _, initialThis)) :: otherStats, + rhs) => + // This method has tail jumps + withScopedVars( + (initialThis match { + case This(_) => + Seq(methodTailJumpThisSym := thisDef.symbol, + fakeTailJumpParamRepl := (NoSymbol, NoSymbol)) + case Ident(_) => + Seq(methodTailJumpThisSym := NoSymbol, + fakeTailJumpParamRepl := (thisDef.symbol, initialThis.symbol)) + }): _* + ) { + val innerBody = js.Block(otherStats.map(genStat) :+ ( + if (bodyIsStat) genStat(rhs) + else genExpr(rhs))) + + if (methodTailJumpThisSym.get == NoSymbol) { + innerBody + } else { + if (methodTailJumpThisSym.isMutable) + mutableLocalVars += methodTailJumpThisSym + js.Block( + js.VarDef(encodeLocalSym(methodTailJumpThisSym), + currentClassType, methodTailJumpThisSym.isMutable, + js.This()(currentClassType)), + innerBody) + } + } + + case _ => + if (bodyIsStat) genStat(tree) + else genExpr(tree) + } + + js.MethodDef(methodIdent, jsParams, resultIRType, body)(None) + } + + /** Gen JS code for a tree in statement position (in the IR). + */ + def genStat(tree: Tree): js.Tree = { + exprToStat(genStatOrExpr(tree, isStat = true)) + } + + /** Turn a JavaScript expression of type Unit into a statement */ + def exprToStat(tree: js.Tree): js.Tree = { + /* Any JavaScript expression is also a statement, but at least we get rid + * of some pure expressions that come from our own codegen. + */ + implicit val pos = tree.pos + tree match { + case js.Block(stats :+ expr) => js.Block(stats :+ exprToStat(expr)) + case _:js.Literal | js.This() => js.Skip() + case _ => tree + } + } + + /** Gen JS code for a tree in expression position (in the IR). + */ + def genExpr(tree: Tree): js.Tree = { + val result = genStatOrExpr(tree, isStat = false) + assert(result.tpe != jstpe.NoType, + s"genExpr($tree) returned a tree with type NoType at pos ${tree.pos}") + result + } + + /** Gen JS code for a tree in statement or expression position (in the IR). + * + * This is the main transformation method. Each node of the Scala AST + * is transformed into an equivalent portion of the JS AST. + */ + def genStatOrExpr(tree: Tree, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + + tree match { + /** LabelDefs (for while and do..while loops) */ + case lblDf: LabelDef => + genLabelDef(lblDf) + + /** Local val or var declaration */ + case ValDef(_, name, _, rhs) => + /* Must have been eliminated by the tail call transform performed + * by genMethodBody(). */ + assert(name != nme.THIS, + s"ValDef(_, nme.THIS, _, _) found at ${tree.pos}") + + val sym = tree.symbol + val rhsTree = + if (rhs == EmptyTree) genZeroOf(sym.tpe) + else genExpr(rhs) + + rhsTree match { + case js.UndefinedParam() => + // This is an intermediate assignment for default params on a + // js.Any. Add the symbol to the corresponding set to inform + // the Ident resolver how to replace it and don't emit the symbol + undefinedDefaultParams += sym + js.Skip() + case _ => + if (sym.isMutable) + mutableLocalVars += sym + js.VarDef(encodeLocalSym(sym), + toIRType(sym.tpe), sym.isMutable, rhsTree) + } + + case If(cond, thenp, elsep) => + js.If(genExpr(cond), genStatOrExpr(thenp, isStat), + genStatOrExpr(elsep, isStat))(toIRType(tree.tpe)) + + case Return(expr) => + js.Return(toIRType(expr.tpe) match { + case jstpe.NoType => js.Block(genStat(expr), js.Undefined()) + case _ => genExpr(expr) + }) + + case t: Try => + genTry(t, isStat) + + case Throw(expr) => + val ex = genExpr(expr) + js.Throw { + if (isMaybeJavaScriptException(expr.tpe)) { + genApplyMethod( + genLoadModule(RuntimePackageModule), + RuntimePackageModule.moduleClass, + Runtime_unwrapJavaScriptException, + List(ex)) + } else { + ex + } + } + + case app: Apply => + genApply(app, isStat) + + case app: ApplyDynamic => + genApplyDynamic(app) + + case This(qual) => + if (tree.symbol == currentClassSym.get) { + genThis() + } else { + assert(tree.symbol.isModuleClass, + "Trying to access the this of another class: " + + "tree.symbol = " + tree.symbol + + ", class symbol = " + currentClassSym.get + + " compilation unit:" + currentUnit) + genLoadModule(tree.symbol) + } + + case Select(qualifier, selector) => + val sym = tree.symbol + if (sym.isModule) { + assert(!sym.isPackageClass, "Cannot use package as value: " + tree) + genLoadModule(sym) + } else if (sym.isStaticMember) { + genStaticMember(sym) + } else if (paramAccessorLocals contains sym) { + paramAccessorLocals(sym).ref + } else { + js.Select(genExpr(qualifier), encodeFieldSym(sym), + mutable = sym.isMutable)(toIRType(sym.tpe)) + } + + case Ident(name) => + val sym = tree.symbol + if (!sym.hasPackageFlag) { + if (sym.isModule) { + assert(!sym.isPackageClass, "Cannot use package as value: " + tree) + genLoadModule(sym) + } else if (undefinedDefaultParams contains sym) { + // This is a default parameter whose assignment was moved to + // a local variable. Put a literal undefined param again + js.UndefinedParam()(toIRType(sym.tpe)) + } else { + js.VarRef(encodeLocalSym(sym), sym.isMutable)(toIRType(sym.tpe)) + } + } else { + sys.error("Cannot use package as value: " + tree) + } + + case Literal(value) => + value.tag match { + case UnitTag => + js.Skip() + case BooleanTag => + js.BooleanLiteral(value.booleanValue) + case ByteTag | ShortTag | CharTag | IntTag => + js.IntLiteral(value.intValue) + case LongTag => + js.LongLiteral(value.longValue) + case FloatTag => + js.FloatLiteral(value.floatValue) + case DoubleTag => + js.DoubleLiteral(value.doubleValue) + case StringTag => + js.StringLiteral(value.stringValue) + case NullTag => + js.Null() + case ClazzTag => + genClassConstant(value.typeValue) + case EnumTag => + genStaticMember(value.symbolValue) + } + + case tree: Block => + genBlock(tree, isStat) + + case Typed(Super(_, _), _) => + genThis() + + case Typed(expr, _) => + genExpr(expr) + + case Assign(lhs, rhs) => + val sym = lhs.symbol + if (sym.isStaticMember) + abort(s"Assignment to static member ${sym.fullName} not supported") + val genLhs = lhs match { + case Select(qualifier, _) => + js.Select(genExpr(qualifier), encodeFieldSym(sym), + mutable = sym.isMutable)(toIRType(sym.tpe)) + case _ => + mutatedLocalVars += sym + js.VarRef(encodeLocalSym(sym), sym.isMutable)(toIRType(sym.tpe)) + } + js.Assign(genLhs, genExpr(rhs)) + + /** Array constructor */ + case av: ArrayValue => + genArrayValue(av) + + /** A Match reaching the backend is supposed to be optimized as a switch */ + case mtch: Match => + genMatch(mtch, isStat) + + /** Anonymous function (only with -Ydelambdafy:method) */ + case fun: Function => + genAnonFunction(fun) + + case EmptyTree => + js.Skip() + + case _ => + abort("Unexpected tree in genExpr: " + + tree + "/" + tree.getClass + " at: " + tree.pos) + } + } // end of GenJSCode.genExpr() + + /** Gen JS this of the current class. + * Normally encoded straightforwardly as a JS this. + * But must be replaced by the tail-jump-this local variable if there + * is one. + */ + private def genThis()(implicit pos: Position): js.Tree = { + if (methodTailJumpThisSym.get != NoSymbol) { + js.VarRef( + encodeLocalSym(methodTailJumpThisSym), + methodTailJumpThisSym.isMutable)(currentClassType) + } else { + if (tryingToGenMethodAsJSFunction) + throw new CancelGenMethodAsJSFunction( + "Trying to generate `this` inside the body") + js.This()(currentClassType) + } + } + + /** Gen JS code for LabelDef + * The only LabelDefs that can reach here are the desugaring of + * while and do..while loops. All other LabelDefs (for tail calls or + * matches) are caught upstream and transformed in ad hoc ways. + * + * So here we recognize all the possible forms of trees that can result + * of while or do..while loops, and we reconstruct the loop for emission + * to JS. + */ + def genLabelDef(tree: LabelDef): js.Tree = { + implicit val pos = tree.pos + val sym = tree.symbol + + tree match { + // while (cond) { body } + case LabelDef(lname, Nil, + If(cond, + Block(bodyStats, Apply(target @ Ident(lname2), Nil)), + Literal(_))) if (target.symbol == sym) => + js.While(genExpr(cond), js.Block(bodyStats map genStat)) + + // while (cond) { body }; result + case LabelDef(lname, Nil, + Block(List( + If(cond, + Block(bodyStats, Apply(target @ Ident(lname2), Nil)), + Literal(_))), + result)) if (target.symbol == sym) => + js.Block( + js.While(genExpr(cond), js.Block(bodyStats map genStat)), + genExpr(result)) + + // while (true) { body } + case LabelDef(lname, Nil, + Block(bodyStats, + Apply(target @ Ident(lname2), Nil))) if (target.symbol == sym) => + js.While(js.BooleanLiteral(true), js.Block(bodyStats map genStat)) + + // while (false) { body } + case LabelDef(lname, Nil, Literal(Constant(()))) => + js.Skip() + + // do { body } while (cond) + case LabelDef(lname, Nil, + Block(bodyStats, + If(cond, + Apply(target @ Ident(lname2), Nil), + Literal(_)))) if (target.symbol == sym) => + js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond)) + + // do { body } while (cond); result + case LabelDef(lname, Nil, + Block( + bodyStats :+ + If(cond, + Apply(target @ Ident(lname2), Nil), + Literal(_)), + result)) if (target.symbol == sym) => + js.Block( + js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond)), + genExpr(result)) + + /* Arbitrary other label - we can jump to it from inside it. + * This is typically for the label-defs implementing tail-calls. + * It can also handle other weird LabelDefs generated by some compiler + * plugins (see for example #1148). + */ + case LabelDef(labelName, labelParams, rhs) => + val labelParamSyms = labelParams.map(_.symbol) map { + s => if (s == fakeTailJumpParamRepl._1) fakeTailJumpParamRepl._2 else s + } + + withScopedVars( + enclosingLabelDefParams := + enclosingLabelDefParams.get + (tree.symbol -> labelParamSyms) + ) { + val bodyType = toIRType(tree.tpe) + val labelIdent = encodeLabelSym(tree.symbol) + val blockLabelIdent = freshLocalIdent() + + js.Labeled(blockLabelIdent, bodyType, { + js.While(js.BooleanLiteral(true), { + if (bodyType == jstpe.NoType) + js.Block(genStat(rhs), js.Return(js.Undefined(), Some(blockLabelIdent))) + else + js.Return(genExpr(rhs), Some(blockLabelIdent)) + }, Some(labelIdent)) + }) + } + } + } + + /** Gen JS code for a try..catch or try..finally block + * + * try..finally blocks are compiled straightforwardly to try..finally + * blocks of JS. + * + * try..catch blocks are a bit more subtle, as JS does not have + * type-based selection of exceptions to catch. We thus encode explicitly + * the type tests, like in: + * + * try { ... } + * catch (e) { + * if (e.isInstanceOf[IOException]) { ... } + * else if (e.isInstanceOf[Exception]) { ... } + * else { + * throw e; // default, re-throw + * } + * } + */ + def genTry(tree: Try, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + val Try(block, catches, finalizer) = tree + + val blockAST = genStatOrExpr(block, isStat) + + val exceptIdent = freshLocalIdent("e") + val origExceptVar = js.VarRef(exceptIdent, mutable = false)(jstpe.AnyType) + + val resultType = toIRType(tree.tpe) + + val handlerAST = { + if (catches.isEmpty) { + js.EmptyTree + } else { + val mightCatchJavaScriptException = catches.exists { caseDef => + caseDef.pat match { + case Typed(Ident(nme.WILDCARD), tpt) => + isMaybeJavaScriptException(tpt.tpe) + case Ident(nme.WILDCARD) => + true + case pat @ Bind(_, _) => + isMaybeJavaScriptException(pat.symbol.tpe) + } + } + + val (exceptValDef, exceptVar) = if (mightCatchJavaScriptException) { + val valDef = js.VarDef(freshLocalIdent("e"), + encodeClassType(ThrowableClass), mutable = false, { + genApplyMethod( + genLoadModule(RuntimePackageModule), + RuntimePackageModule.moduleClass, + Runtime_wrapJavaScriptException, + List(origExceptVar)) + }) + (valDef, valDef.ref) + } else { + (js.Skip(), origExceptVar) + } + + val elseHandler: js.Tree = js.Throw(origExceptVar) + + val handler0 = catches.foldRight(elseHandler) { (caseDef, elsep) => + implicit val pos = caseDef.pos + val CaseDef(pat, _, body) = caseDef + + // Extract exception type and variable + val (tpe, boundVar) = (pat match { + case Typed(Ident(nme.WILDCARD), tpt) => + (tpt.tpe, None) + case Ident(nme.WILDCARD) => + (ThrowableClass.tpe, None) + case Bind(_, _) => + (pat.symbol.tpe, Some(encodeLocalSym(pat.symbol))) + }) + + // Generate the body that must be executed if the exception matches + val bodyWithBoundVar = (boundVar match { + case None => + genStatOrExpr(body, isStat) + case Some(bv) => + val castException = genAsInstanceOf(exceptVar, tpe) + js.Block( + js.VarDef(bv, toIRType(tpe), mutable = false, castException), + genStatOrExpr(body, isStat)) + }) + + // Generate the test + if (tpe == ThrowableClass.tpe) { + bodyWithBoundVar + } else { + val cond = genIsInstanceOf(exceptVar, tpe) + js.If(cond, bodyWithBoundVar, elsep)(resultType) + } + } + + js.Block( + exceptValDef, + handler0) + } + } + + val finalizerAST = genStat(finalizer) match { + case js.Skip() => js.EmptyTree + case ast => ast + } + + if (handlerAST == js.EmptyTree && finalizerAST == js.EmptyTree) blockAST + else js.Try(blockAST, exceptIdent, handlerAST, finalizerAST)(resultType) + } + + /** Gen JS code for an Apply node (method call) + * + * There's a whole bunch of varieties of Apply nodes: regular method + * calls, super calls, constructor calls, isInstanceOf/asInstanceOf, + * primitives, JS calls, etc. They are further dispatched in here. + */ + def genApply(tree: Apply, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + val Apply(fun, args) = tree + + fun match { + case TypeApply(_, _) => + genApplyTypeApply(tree) + + case Select(Super(_, _), _) => + genSuperCall(tree) + + case Select(New(_), nme.CONSTRUCTOR) => + genApplyNew(tree) + + case _ => + val sym = fun.symbol + + if (sym.isLabel) { + genLabelApply(tree) + } else if (scalaPrimitives.isPrimitive(sym)) { + genPrimitiveOp(tree, isStat) + } else if (currentRun.runDefinitions.isBox(sym)) { + // Box a primitive value (cannot be Unit) + val arg = args.head + makePrimitiveBox(genExpr(arg), arg.tpe) + } else if (currentRun.runDefinitions.isUnbox(sym)) { + // Unbox a primitive value (cannot be Unit) + val arg = args.head + makePrimitiveUnbox(genExpr(arg), tree.tpe) + } else { + genNormalApply(tree, isStat) + } + } + } + + /** Gen an Apply with a TypeApply method. + * Only isInstanceOf and asInstanceOf keep their type argument until the + * backend. + */ + private def genApplyTypeApply(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(TypeApply(fun @ Select(obj, _), targs), _) = tree + val sym = fun.symbol + + val cast = sym match { + case Object_isInstanceOf => false + case Object_asInstanceOf => true + case _ => + abort("Unexpected type application " + fun + + "[sym: " + sym.fullName + "]" + " in: " + tree) + } + + val to = targs.head.tpe + val l = toTypeKind(obj.tpe) + val r = toTypeKind(to) + val source = genExpr(obj) + + if (l.isValueType && r.isValueType) { + if (cast) + genConversion(l, r, source) + else + js.BooleanLiteral(l == r) + } else if (l.isValueType) { + val result = if (cast) { + val ctor = ClassCastExceptionClass.info.member( + nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty) + js.Throw(genNew(ClassCastExceptionClass, ctor, Nil)) + } else { + js.BooleanLiteral(false) + } + js.Block(source, result) // eval and discard source + } else if (r.isValueType) { + assert(!cast, s"Unexpected asInstanceOf from ref type to value type") + genIsInstanceOf(source, boxedClass(to.typeSymbol).tpe) + } else { + if (cast) + genAsInstanceOf(source, to) + else + genIsInstanceOf(source, to) + } + } + + /** Gen JS code for a super call, of the form Class.super[mix].fun(args). + * + * This does not include calls defined in mixin traits, as these are + * already desugared by the 'mixin' phase. Only calls to super classes + * remain. + * Since a class has exactly one direct superclass, and calling a method + * two classes above the current one is invalid, the `mix` item is + * irrelevant. + */ + private def genSuperCall(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(fun @ Select(sup @ Super(_, mix), _), args) = tree + val sym = fun.symbol + + if (sym == Object_getClass) { + // The only primitive that is also callable as super call + js.GetClass(genThis()) + } else { + val superCall = genStaticApplyMethod( + genThis()(sup.pos), sym, genActualArgs(sym, args)) + + // Initialize the module instance just after the super constructor call. + if (isStaticModule(currentClassSym) && !isModuleInitialized && + currentMethodSym.isClassConstructor) { + isModuleInitialized = true + val thisType = jstpe.ClassType(encodeClassFullName(currentClassSym)) + val initModule = js.StoreModule(thisType, js.This()(thisType)) + js.Block(superCall, initModule, js.This()(thisType)) + } else { + superCall + } + } + } + + /** Gen JS code for a constructor call (new). + * Further refined into: + * * new String(...) + * * new of a hijacked boxed class + * * new of an anonymous function class that was recorded as JS function + * * new of a raw JS class + * * new Array + * * regular new + */ + private def genApplyNew(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) = tree + val ctor = fun.symbol + val tpe = tpt.tpe + + assert(ctor.isClassConstructor, + "'new' call to non-constructor: " + ctor.name) + + if (isStringType(tpe)) { + genNewString(tree) + } else if (isHijackedBoxedClass(tpe.typeSymbol)) { + genNewHijackedBoxedClass(tpe.typeSymbol, ctor, args map genExpr) + } else if (translatedAnonFunctions contains tpe.typeSymbol) { + val (functionMaker, funInfo) = translatedAnonFunctions(tpe.typeSymbol) + currentMethodInfoBuilder.createsAnonFunction(funInfo) + functionMaker(args map genExpr) + } else if (isRawJSType(tpe)) { + genPrimitiveJSNew(tree) + } else { + toTypeKind(tpe) match { + case arr @ ARRAY(elem) => + genNewArray(arr.toIRType, args map genExpr) + case rt @ REFERENCE(cls) => + genNew(cls, ctor, genActualArgs(ctor, args)) + case generatedType => + abort(s"Non reference type cannot be instantiated: $generatedType") + } + } + } + + /** Gen jump to a label. + * Most label-applys are caught upstream (while and do..while loops, + * jumps to next case of a pattern match), but some are still handled here: + * * Jumps to enclosing label-defs, including tail-recursive calls + * * Jump to the end of a pattern match + */ + private def genLabelApply(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(fun, args) = tree + val sym = fun.symbol + + if (enclosingLabelDefParams.contains(sym)) { + genEnclosingLabelApply(tree) + } else if (sym.name.toString() startsWith "matchEnd") { + /* Jump the to the end-label of a pattern match + * Such labels have exactly one argument, which is the result of + * the pattern match (of type BoxedUnit if the match is in statement + * position). We simply `return` the argument as the result of the + * labeled block surrounding the match. + */ + js.Return(genExpr(args.head), Some(encodeLabelSym(sym))) + } else { + /* No other label apply should ever happen. If it does, then we + * have missed a pattern of LabelDef/LabelApply and some new + * translation must be found for it. + */ + abort("Found unknown label apply at "+tree.pos+": "+tree) + } + } + + /** Gen a label-apply to an enclosing label def. + * + * This is typically used for tail-recursive calls. + * + * Basically this is compiled into + * continue labelDefIdent; + * but arguments need to be updated beforehand. + * + * Since the rhs for the new value of an argument can depend on the value + * of another argument (and since deciding if it is indeed the case is + * impossible in general), new values are computed in temporary variables + * first, then copied to the actual variables representing the argument. + * + * Trivial assignments (arg1 = arg1) are eliminated. + * + * If, after elimination of trivial assignments, only one assignment + * remains, then we do not use a temporary variable for this one. + */ + private def genEnclosingLabelApply(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(fun, args) = tree + val sym = fun.symbol + + // Prepare quadruplets of (formalArg, irType, tempVar, actualArg) + // Do not include trivial assignments (when actualArg == formalArg) + val formalArgs = enclosingLabelDefParams(sym) + val actualArgs = args map genExpr + val quadruplets = { + for { + (formalArgSym, actualArg) <- formalArgs zip actualArgs + formalArg = encodeLocalSym(formalArgSym) + if (actualArg match { + case js.VarRef(`formalArg`, _) => false + case _ => true + }) + } yield { + mutatedLocalVars += formalArgSym + val tpe = toIRType(formalArgSym.tpe) + (js.VarRef(formalArg, formalArgSym.isMutable)(tpe), tpe, + freshLocalIdent("temp$" + formalArg.name), + actualArg) + } + } + + // The actual jump (continue labelDefIdent;) + val jump = js.Continue(Some(encodeLabelSym(sym))) + + quadruplets match { + case Nil => jump + + case (formalArg, argType, _, actualArg) :: Nil => + js.Block( + js.Assign(formalArg, actualArg), + jump) + + case _ => + val tempAssignments = + for ((_, argType, tempArg, actualArg) <- quadruplets) + yield js.VarDef(tempArg, argType, mutable = false, actualArg) + val trueAssignments = + for ((formalArg, argType, tempArg, _) <- quadruplets) + yield js.Assign( + formalArg, + js.VarRef(tempArg, mutable = false)(argType)) + js.Block(tempAssignments ++ trueAssignments :+ jump) + } + } + + /** Gen a "normal" apply (to a true method). + * + * But even these are further refined into: + * * Methods of java.lang.String, which are redirected to the + * RuntimeString trait implementation. + * * Calls to methods of raw JS types (Scala.js -> JS bridge) + * * Calls to methods in impl classes of traits. + * * Regular method call + */ + private def genNormalApply(tree: Apply, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + val Apply(fun @ Select(receiver, _), args) = tree + val sym = fun.symbol + + def isStringMethodFromObject: Boolean = sym.name match { + case nme.toString_ | nme.equals_ | nme.hashCode_ => true + case _ => false + } + + if (sym.owner == StringClass && !isStringMethodFromObject) { + genStringCall(tree) + } else if (isRawJSType(receiver.tpe) && sym.owner != ObjectClass) { + genPrimitiveJSCall(tree, isStat) + } else if (foreignIsImplClass(sym.owner)) { + genTraitImplApply(sym, args map genExpr) + } else if (isRawJSCtorDefaultParam(sym)) { + js.UndefinedParam()(toIRType(sym.tpe.resultType)) + } else if (sym.isClassConstructor) { + /* See #66: we have to emit a static call to avoid calling a + * constructor with the same signature in a subclass */ + genStaticApplyMethod(genExpr(receiver), sym, genActualArgs(sym, args)) + } else { + genApplyMethod(genExpr(receiver), receiver.tpe, sym, genActualArgs(sym, args)) + } + } + + def genStaticApplyMethod(receiver: js.Tree, method: Symbol, + arguments: List[js.Tree])(implicit pos: Position): js.Tree = { + val classIdent = encodeClassFullNameIdent(method.owner) + val methodIdent = encodeMethodSym(method) + currentMethodInfoBuilder.callsMethodStatic(classIdent, methodIdent) + js.StaticApply(receiver, jstpe.ClassType(classIdent.name), methodIdent, + arguments)(toIRType(method.tpe.resultType)) + } + + def genTraitImplApply(method: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + val implIdent = encodeClassFullNameIdent(method.owner) + val methodIdent = encodeMethodSym(method) + genTraitImplApply(implIdent, methodIdent, arguments, + toIRType(method.tpe.resultType)) + } + + def genTraitImplApply(implIdent: js.Ident, methodIdent: js.Ident, + arguments: List[js.Tree], resultType: jstpe.Type)( + implicit pos: Position): js.Tree = { + currentMethodInfoBuilder.callsMethod(implIdent, methodIdent) + js.TraitImplApply(jstpe.ClassType(implIdent.name), methodIdent, + arguments)(resultType) + } + + /** Gen JS code for a conversion between primitive value types */ + def genConversion(from: TypeKind, to: TypeKind, value: js.Tree)( + implicit pos: Position): js.Tree = { + def int0 = js.IntLiteral(0) + def int1 = js.IntLiteral(1) + def long0 = js.LongLiteral(0L) + def long1 = js.LongLiteral(1L) + def float0 = js.FloatLiteral(0.0f) + def float1 = js.FloatLiteral(1.0f) + + (from, to) match { + case (INT(_), BOOL) => js.BinaryOp(js.BinaryOp.Num_!=, value, int0) + case (LONG, BOOL) => js.BinaryOp(js.BinaryOp.Long_!=, value, long0) + case (FLOAT(_), BOOL) => js.BinaryOp(js.BinaryOp.Num_!=, value, float0) + + case (BOOL, INT(_)) => js.If(value, int1, int0 )(jstpe.IntType) + case (BOOL, LONG) => js.If(value, long1, long0 )(jstpe.LongType) + case (BOOL, FLOAT(_)) => js.If(value, float1, float0)(jstpe.FloatType) + + case _ => value + } + } + + /** Gen JS code for an isInstanceOf test (for reference types only) */ + def genIsInstanceOf(value: js.Tree, to: Type)( + implicit pos: Position): js.Tree = { + + def genTypeOfTest(typeString: String) = { + js.BinaryOp(js.BinaryOp.===, + js.UnaryOp(js.UnaryOp.typeof, value), + js.StringLiteral(typeString)) + } + + if (isRawJSType(to)) { + to.typeSymbol match { + case JSNumberClass => genTypeOfTest("number") + case JSStringClass => genTypeOfTest("string") + case JSBooleanClass => genTypeOfTest("boolean") + case JSUndefinedClass => genTypeOfTest("undefined") + case sym if sym.isTrait => + reporter.error(pos, + s"isInstanceOf[${sym.fullName}] not supported because it is a raw JS trait") + js.BooleanLiteral(true) + case sym => + js.BinaryOp(js.BinaryOp.instanceof, value, genGlobalJSObject(sym)) + } + } else { + val refType = toReferenceType(to) + currentMethodInfoBuilder.accessesClassData(refType) + js.IsInstanceOf(value, refType) + } + } + + /** Gen JS code for an asInstanceOf cast (for reference types only) */ + def genAsInstanceOf(value: js.Tree, to: Type)( + implicit pos: Position): js.Tree = { + + def default: js.Tree = { + val refType = toReferenceType(to) + currentMethodInfoBuilder.accessesClassData(refType) + js.AsInstanceOf(value, refType) + } + + if (isRawJSType(to)) { + // asInstanceOf on JavaScript is completely erased + value + } else if (FunctionClass.seq contains to.typeSymbol) { + /* Don't hide a JSFunctionToScala inside a useless cast, otherwise + * the optimization avoiding double-wrapping in genApply() will not + * be able to kick in. + */ + value match { + case JSFunctionToScala(fun, _) => value + case _ => default + } + } else { + default + } + } + + /** Gen JS code for a call to a Scala method. + * This also registers that the given method is called by the current + * method in the method info builder. + */ + def genApplyMethod(receiver: js.Tree, receiverType: Type, + methodSym: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + genApplyMethod(receiver, receiverType.typeSymbol, methodSym, arguments) + } + + /** Gen JS code for a call to a Scala method. + * This also registers that the given method is called by the current + * method in the method info builder. + */ + def genApplyMethod(receiver: js.Tree, receiverTypeSym: Symbol, + methodSym: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + genApplyMethod(receiver, receiverTypeSym, + encodeMethodSym(methodSym), arguments, + toIRType(methodSym.tpe.resultType)) + } + + /** Gen JS code for a call to a Scala method. + * This also registers that the given method is called by the current + * method in the method info builder. + */ + def genApplyMethod(receiver: js.Tree, receiverType: Type, + methodIdent: js.Ident, arguments: List[js.Tree], resultType: jstpe.Type)( + implicit pos: Position): js.Tree = { + genApplyMethod(receiver, receiverType.typeSymbol, methodIdent, + arguments, resultType) + } + + /** Gen JS code for a call to a Scala method. + * This also registers that the given method is called by the current + * method in the method info builder. + */ + def genApplyMethod(receiver: js.Tree, receiverTypeSym: Symbol, + methodIdent: js.Ident, arguments: List[js.Tree], resultType: jstpe.Type)( + implicit pos: Position): js.Tree = { + currentMethodInfoBuilder.callsMethod(receiverTypeSym, methodIdent) + js.Apply(receiver, methodIdent, arguments)(resultType) + } + + /** Gen JS code for a call to a Scala class constructor. + * + * This also registers that the given class is instantiated by the current + * method, and that the given constructor is called, in the method info + * builder. + */ + def genNew(clazz: Symbol, ctor: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + if (clazz.isAnonymousFunction) + instantiatedAnonFunctions += clazz + assert(!isRawJSFunctionDef(clazz), + s"Trying to instantiate a raw JS function def $clazz") + val ctorIdent = encodeMethodSym(ctor) + currentMethodInfoBuilder.instantiatesClass(clazz) + currentMethodInfoBuilder.callsMethod(clazz, ctorIdent) + js.New(jstpe.ClassType(encodeClassFullName(clazz)), + ctorIdent, arguments) + } + + /** Gen JS code for a call to a constructor of a hijacked boxed class. + * All of these have 2 constructors: one with the primitive + * value, which is erased, and one with a String, which is + * equivalent to BoxedClass.valueOf(arg). + */ + private def genNewHijackedBoxedClass(clazz: Symbol, ctor: Symbol, + arguments: List[js.Tree])(implicit pos: Position): js.Tree = { + assert(arguments.size == 1) + if (isStringType(ctor.tpe.params.head.tpe)) { + // BoxedClass.valueOf(arg) + val companion = clazz.companionModule.moduleClass + val valueOf = getMemberMethod(companion, nme.valueOf) suchThat { s => + s.tpe.params.size == 1 && isStringType(s.tpe.params.head.tpe) + } + genApplyMethod(genLoadModule(companion), companion, valueOf, arguments) + } else { + // erased + arguments.head + } + } + + /** Gen JS code for creating a new Array: new Array[T](length) + * For multidimensional arrays (dimensions > 1), the arguments can + * specify up to `dimensions` lengths for the first dimensions of the + * array. + */ + def genNewArray(arrayType: jstpe.ArrayType, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + assert(arguments.length <= arrayType.dimensions, + "too many arguments for array constructor: found " + arguments.length + + " but array has only " + arrayType.dimensions + " dimension(s)") + + currentMethodInfoBuilder.accessesClassData(arrayType) + js.NewArray(arrayType, arguments) + } + + /** Gen JS code for an array literal. + */ + def genArrayValue(tree: Tree): js.Tree = { + implicit val pos = tree.pos + val ArrayValue(tpt @ TypeTree(), elems) = tree + + val arrType = toReferenceType(tree.tpe).asInstanceOf[jstpe.ArrayType] + currentMethodInfoBuilder.accessesClassData(arrType) + js.ArrayValue(arrType, elems map genExpr) + } + + /** Gen JS code for a Match, i.e., a switch-able pattern match + * Eventually, this is compiled into a JS switch construct. But because + * we can be in expression position, and a JS switch cannot be given a + * meaning in expression position, we emit a JS "match" construct (which + * does not need the `break`s in each case. `JSDesugaring` will transform + * that in a switch. + * + * Some caveat here. It may happen that there is a guard in here, despite + * the fact that switches cannot have guards (in the JVM nor in JS). + * The JVM backend emits a jump to the default clause when a guard is not + * fulfilled. We cannot do that. Instead, currently we duplicate the body + * of the default case in the else branch of the guard test. + */ + def genMatch(tree: Tree, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + val Match(selector, cases) = tree + + val expr = genExpr(selector) + val resultType = toIRType(tree.tpe) + + val List(defaultBody0) = for { + CaseDef(Ident(nme.WILDCARD), EmptyTree, body) <- cases + } yield body + + val (defaultBody, defaultLabelSym) = defaultBody0 match { + case LabelDef(_, Nil, rhs) if hasSynthCaseSymbol(defaultBody0) => + (rhs, defaultBody0.symbol) + case _ => + (defaultBody0, NoSymbol) + } + + val genDefaultBody = genStatOrExpr(defaultBody, isStat) + + var clauses: List[(List[js.Literal], js.Tree)] = Nil + var elseClause: js.Tree = js.EmptyTree + + for (caze @ CaseDef(pat, guard, body) <- cases) { + assert(guard == EmptyTree) + + def genBody() = body match { + // Yes, this will duplicate the default body in the output + case If(cond, thenp, app @ Apply(_, Nil)) + if app.symbol == defaultLabelSym => + js.If(genExpr(cond), genStatOrExpr(thenp, isStat), genDefaultBody)( + resultType)(body.pos) + case If(cond, thenp, Block(List(app @ Apply(_, Nil)), _)) + if app.symbol == defaultLabelSym => + js.If(genExpr(cond), genStatOrExpr(thenp, isStat), genDefaultBody)( + resultType)(body.pos) + + case _ => + genStatOrExpr(body, isStat) + } + + def genLiteral(lit: Literal): js.Literal = + genExpr(lit).asInstanceOf[js.Literal] + + pat match { + case lit: Literal => + clauses = (List(genLiteral(lit)), genBody()) :: clauses + case Ident(nme.WILDCARD) => + elseClause = genDefaultBody + case Alternative(alts) => + val genAlts = { + alts map { + case lit: Literal => genLiteral(lit) + case _ => + abort("Invalid case in alternative in switch-like pattern match: " + + tree + " at: " + tree.pos) + } + } + clauses = (genAlts, genBody()) :: clauses + case _ => + abort("Invalid case statement in switch-like pattern match: " + + tree + " at: " + (tree.pos)) + } + } + + js.Match(expr, clauses.reverse, elseClause)(resultType) + } + + private def genBlock(tree: Block, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + val Block(stats, expr) = tree + + /** Predicate satisfied by LabelDefs produced by the pattern matcher */ + def isCaseLabelDef(tree: Tree) = + tree.isInstanceOf[LabelDef] && hasSynthCaseSymbol(tree) + + def translateMatch(expr: LabelDef) = { + /* Block that appeared as the result of a translated match + * Such blocks are recognized by having at least one element that is + * a so-called case-label-def. + * The method `genTranslatedMatch()` takes care of compiling the + * actual match. + * + * The assumption is once we encounter a case, the remainder of the + * block will consist of cases. + * The prologue may be empty, usually it is the valdef that stores + * the scrut. + */ + val (prologue, cases) = stats.span(s => !isCaseLabelDef(s)) + assert(cases.forall(isCaseLabelDef), + "Assumption on the form of translated matches broken: " + tree) + + val genPrologue = prologue map genStat + val translatedMatch = + genTranslatedMatch(cases.map(_.asInstanceOf[LabelDef]), expr) + + js.Block(genPrologue :+ translatedMatch) + } + + expr match { + case expr: LabelDef if isCaseLabelDef(expr) => + translateMatch(expr) + + // Sometimes the pattern matcher casts its final result + case Apply(TypeApply(Select(expr: LabelDef, nme.asInstanceOf_Ob), _), _) + if isCaseLabelDef(expr) => + translateMatch(expr) + + case _ => + assert(!stats.exists(isCaseLabelDef), "Found stats with case label " + + s"def in non-match block at ${tree.pos}: $tree") + + /* Normal block */ + val statements = stats map genStat + val expression = genStatOrExpr(expr, isStat) + js.Block(statements :+ expression) + } + } + + /** Gen JS code for a translated match + * + * This implementation relies heavily on the patterns of trees emitted + * by the current pattern match phase (as of Scala 2.10). + * + * The trees output by the pattern matcher are assumed to follow these + * rules: + * * Each case LabelDef (in `cases`) must not take any argument. + * * The last one must be a catch-all (case _ =>) that never falls through. + * * Jumps to the `matchEnd` are allowed anywhere in the body of the + * corresponding case label-defs, but not outside. + * * Jumps to case label-defs are restricted to jumping to the very next + * case, and only in positions denoted by <jump> in: + * <case-body> ::= + * If(_, <case-body>, <case-body>) + * | Block(_, <case-body>) + * | <jump> + * | _ + * These restrictions, together with the fact that we are in statement + * position (thanks to the above transformation), mean that they can be + * simply replaced by `skip`. + * + * To implement jumps to `matchEnd`, which have one argument which is the + * result of the match, we enclose all the cases in one big labeled block. + * Jumps are then compiled as `return`s out of the block. + */ + def genTranslatedMatch(cases: List[LabelDef], + matchEnd: LabelDef)(implicit pos: Position): js.Tree = { + + val nextCaseSyms = (cases.tail map (_.symbol)) :+ NoSymbol + + val translatedCases = for { + (LabelDef(_, Nil, rhs), nextCaseSym) <- cases zip nextCaseSyms + } yield { + def genCaseBody(tree: Tree): js.Tree = { + implicit val pos = tree.pos + tree match { + case If(cond, thenp, elsep) => + js.If(genExpr(cond), genCaseBody(thenp), genCaseBody(elsep))( + jstpe.NoType) + + case Block(stats, expr) => + js.Block((stats map genStat) :+ genCaseBody(expr)) + + case Apply(_, Nil) if tree.symbol == nextCaseSym => + js.Skip() + + case _ => + genStat(tree) + } + } + + genCaseBody(rhs) + } + + js.Labeled(encodeLabelSym(matchEnd.symbol), toIRType(matchEnd.tpe), + js.Block(translatedCases)) + } + + /** Gen JS code for a primitive method call */ + private def genPrimitiveOp(tree: Apply, isStat: Boolean): js.Tree = { + import scalaPrimitives._ + + implicit val pos = tree.pos + + val sym = tree.symbol + val Apply(fun @ Select(receiver, _), args) = tree + + val code = scalaPrimitives.getPrimitive(sym, receiver.tpe) + + if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code)) + genSimpleOp(tree, receiver :: args, code) + else if (code == scalaPrimitives.CONCAT) + genStringConcat(tree, receiver, args) + else if (code == HASH) + genScalaHash(tree, receiver) + else if (isArrayOp(code)) + genArrayOp(tree, code) + else if (code == SYNCHRONIZED) + genSynchronized(tree, isStat) + else if (isCoercion(code)) + genCoercion(tree, receiver, code) + else if (jsPrimitives.isJavaScriptPrimitive(code)) + genJSPrimitive(tree, receiver, args, code) + else + abort("Unknown primitive operation: " + sym.fullName + "(" + + fun.symbol.simpleName + ") " + " at: " + (tree.pos)) + } + + /** Gen JS code for a simple operation (arithmetic, logical, or comparison) */ + private def genSimpleOp(tree: Apply, args: List[Tree], code: Int): js.Tree = { + import scalaPrimitives._ + + implicit val pos = tree.pos + + def isLongOp(ltpe: Type, rtpe: Type) = + (isLongType(ltpe) || isLongType(rtpe)) && + !(toTypeKind(ltpe).isInstanceOf[FLOAT] || + toTypeKind(rtpe).isInstanceOf[FLOAT] || + isStringType(ltpe) || isStringType(rtpe)) + + val sources = args map genExpr + + val resultType = toIRType(tree.tpe) + + sources match { + // Unary operation + case List(source) => + (code match { + case POS => + source + case NEG => + (resultType: @unchecked) match { + case jstpe.IntType => + js.BinaryOp(js.BinaryOp.Int_-, js.IntLiteral(0), source) + case jstpe.LongType => + js.BinaryOp(js.BinaryOp.Long_-, js.LongLiteral(0), source) + case jstpe.FloatType => + js.BinaryOp(js.BinaryOp.Float_-, js.FloatLiteral(0.0f), source) + case jstpe.DoubleType => + js.BinaryOp(js.BinaryOp.Double_-, js.DoubleLiteral(0), source) + } + case NOT => + (resultType: @unchecked) match { + case jstpe.IntType => + js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(-1), source) + case jstpe.LongType => + js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(-1), source) + } + case ZNOT => + js.UnaryOp(js.UnaryOp.Boolean_!, source) + case _ => + abort("Unknown unary operation code: " + code) + }) + + // Binary operation on Longs + case List(lsrc, rsrc) if isLongOp(args(0).tpe, args(1).tpe) => + def toLong(tree: js.Tree, tpe: Type) = + if (isLongType(tpe)) tree + else js.UnaryOp(js.UnaryOp.IntToLong, tree) + + def toInt(tree: js.Tree, tpe: Type) = + if (isLongType(tpe)) js.UnaryOp(js.UnaryOp.LongToInt, rsrc) + else tree + + val ltree = toLong(lsrc, args(0).tpe) + def rtree = toLong(rsrc, args(1).tpe) + def rtreeInt = toInt(rsrc, args(1).tpe) + + import js.BinaryOp._ + (code: @switch) match { + case ADD => js.BinaryOp(Long_+, ltree, rtree) + case SUB => js.BinaryOp(Long_-, ltree, rtree) + case MUL => js.BinaryOp(Long_*, ltree, rtree) + case DIV => js.BinaryOp(Long_/, ltree, rtree) + case MOD => js.BinaryOp(Long_%, ltree, rtree) + case OR => js.BinaryOp(Long_|, ltree, rtree) + case XOR => js.BinaryOp(Long_^, ltree, rtree) + case AND => js.BinaryOp(Long_&, ltree, rtree) + case LSL => js.BinaryOp(Long_<<, ltree, rtreeInt) + case LSR => js.BinaryOp(Long_>>>, ltree, rtreeInt) + case ASR => js.BinaryOp(Long_>>, ltree, rtreeInt) + case EQ => js.BinaryOp(Long_==, ltree, rtree) + case NE => js.BinaryOp(Long_!=, ltree, rtree) + case LT => js.BinaryOp(Long_<, ltree, rtree) + case LE => js.BinaryOp(Long_<=, ltree, rtree) + case GT => js.BinaryOp(Long_>, ltree, rtree) + case GE => js.BinaryOp(Long_>=, ltree, rtree) + case _ => + abort("Unknown binary operation code: " + code) + } + + // Binary operation + case List(lsrc_in, rsrc_in) => + def convertArg(tree: js.Tree, tpe: Type) = { + val kind = toTypeKind(tpe) + + // If we end up with a long, target must be float or double + val fromLong = + if (kind == LongKind) js.UnaryOp(js.UnaryOp.LongToDouble, tree) + else tree + + if (resultType != jstpe.FloatType) fromLong + else if (kind == FloatKind) fromLong + else js.UnaryOp(js.UnaryOp.DoubleToFloat, fromLong) + } + + val lsrc = convertArg(lsrc_in, args(0).tpe) + val rsrc = convertArg(rsrc_in, args(1).tpe) + + def genEquality(eqeq: Boolean, not: Boolean) = { + val typeKind = toTypeKind(args(0).tpe) + typeKind match { + case INT(_) | LONG | FLOAT(_) => + /* Note that LONG happens when a fromLong() had to do something, + * which means we're effectively in the FLOAT case. */ + js.BinaryOp(if (not) js.BinaryOp.Num_!= else js.BinaryOp.Num_==, lsrc, rsrc) + case BOOL => + js.BinaryOp(if (not) js.BinaryOp.Boolean_!= else js.BinaryOp.Boolean_==, lsrc, rsrc) + case REFERENCE(_) => + if (eqeq && + // don't call equals if we have a literal null at either side + !lsrc.isInstanceOf[js.Null] && + !rsrc.isInstanceOf[js.Null]) { + val body = genEqEqPrimitive(args(0).tpe, args(1).tpe, lsrc, rsrc) + if (not) js.UnaryOp(js.UnaryOp.Boolean_!, body) else body + } else { + js.BinaryOp(if (not) js.BinaryOp.!== else js.BinaryOp.===, lsrc, rsrc) + } + case _ => + // Arrays, Null, Nothing do not have an equals() method. + js.BinaryOp(if (not) js.BinaryOp.!== else js.BinaryOp.===, lsrc, rsrc) + } + } + + (code: @switch) match { + case EQ => genEquality(eqeq = true, not = false) + case NE => genEquality(eqeq = true, not = true) + case ID => genEquality(eqeq = false, not = false) + case NI => genEquality(eqeq = false, not = true) + + case ZOR => js.If(lsrc, js.BooleanLiteral(true), rsrc)(jstpe.BooleanType) + case ZAND => js.If(lsrc, rsrc, js.BooleanLiteral(false))(jstpe.BooleanType) + + case _ => + import js.BinaryOp._ + val op = (resultType: @unchecked) match { + case jstpe.IntType => + (code: @switch) match { + case ADD => Int_+ + case SUB => Int_- + case MUL => Int_* + case DIV => Int_/ + case MOD => Int_% + case OR => Int_| + case AND => Int_& + case XOR => Int_^ + case LSL => Int_<< + case LSR => Int_>>> + case ASR => Int_>> + } + case jstpe.FloatType => + (code: @switch) match { + case ADD => Float_+ + case SUB => Float_- + case MUL => Float_* + case DIV => Float_/ + case MOD => Float_% + } + case jstpe.DoubleType => + (code: @switch) match { + case ADD => Double_+ + case SUB => Double_- + case MUL => Double_* + case DIV => Double_/ + case MOD => Double_% + } + case jstpe.BooleanType => + (code: @switch) match { + case LT => Num_< + case LE => Num_<= + case GT => Num_> + case GE => Num_>= + case OR => Boolean_| + case AND => Boolean_& + case XOR => Boolean_!= + } + } + js.BinaryOp(op, lsrc, rsrc) + } + + case _ => + abort("Too many arguments for primitive function: " + tree) + } + } + + /** Gen JS code for a call to Any.== */ + def genEqEqPrimitive(ltpe: Type, rtpe: Type, lsrc: js.Tree, rsrc: js.Tree)( + implicit pos: Position): js.Tree = { + /* True if the equality comparison is between values that require the + * use of the rich equality comparator + * (scala.runtime.BoxesRunTime.equals). + * This is the case when either side of the comparison might have a + * run-time type subtype of java.lang.Number or java.lang.Character, + * **which includes when either is a raw JS type**. + * When it is statically known that both sides are equal and subtypes of + * Number or Character, not using the rich equality is possible (their + * own equals method will do ok.) + */ + val mustUseAnyComparator: Boolean = isRawJSType(ltpe) || isRawJSType(rtpe) || { + val areSameFinals = ltpe.isFinalType && rtpe.isFinalType && (ltpe =:= rtpe) + !areSameFinals && isMaybeBoxed(ltpe.typeSymbol) && isMaybeBoxed(rtpe.typeSymbol) + } + + if (mustUseAnyComparator) { + val equalsMethod: Symbol = { + val ptfm = platform.asInstanceOf[backend.JavaPlatform with ThisPlatform] // 2.10 compat + if (ltpe <:< BoxedNumberClass.tpe) { + if (rtpe <:< BoxedNumberClass.tpe) ptfm.externalEqualsNumNum + else if (rtpe <:< BoxedCharacterClass.tpe) ptfm.externalEqualsNumChar + else ptfm.externalEqualsNumObject + } else ptfm.externalEquals + } + val moduleClass = equalsMethod.owner + val instance = genLoadModule(moduleClass) + genApplyMethod(instance, moduleClass, equalsMethod, List(lsrc, rsrc)) + } else { + // if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc) + if (isStringType(ltpe)) { + // String.equals(that) === (this eq that) + js.BinaryOp(js.BinaryOp.===, lsrc, rsrc) + } else { + /* This requires to evaluate both operands in local values first. + * The optimizer will eliminate them if possible. + */ + val ltemp = js.VarDef(freshLocalIdent(), lsrc.tpe, mutable = false, lsrc) + val rtemp = js.VarDef(freshLocalIdent(), rsrc.tpe, mutable = false, rsrc) + js.Block( + ltemp, + rtemp, + js.If(js.BinaryOp(js.BinaryOp.===, ltemp.ref, js.Null()), + js.BinaryOp(js.BinaryOp.===, rtemp.ref, js.Null()), + genApplyMethod(ltemp.ref, ltpe, Object_equals, List(rtemp.ref)))( + jstpe.BooleanType)) + } + } + } + + /** Gen JS code for string concatenation. + */ + private def genStringConcat(tree: Apply, receiver: Tree, + args: List[Tree]): js.Tree = { + implicit val pos = tree.pos + + /* Primitive number types such as scala.Int have a + * def +(s: String): String + * method, which is why we have to box the lhs sometimes. + * Otherwise, both lhs and rhs are already reference types (Any of String) + * so boxing is not necessary (in particular, rhs is never a primitive). + */ + assert(!isPrimitiveValueType(receiver.tpe) || isStringType(args.head.tpe)) + assert(!isPrimitiveValueType(args.head.tpe)) + + val rhs = genExpr(args.head) + + val lhs = { + val lhs0 = genExpr(receiver) + // Box the receiver if it is a primitive value + if (!isPrimitiveValueType(receiver.tpe)) lhs0 + else makePrimitiveBox(lhs0, receiver.tpe) + } + + js.BinaryOp(js.BinaryOp.String_+, lhs, rhs) + } + + /** Gen JS code for a call to Any.## */ + private def genScalaHash(tree: Apply, receiver: Tree): js.Tree = { + implicit val pos = tree.pos + + val instance = genLoadModule(ScalaRunTimeModule) + val arguments = List(genExpr(receiver)) + val sym = getMember(ScalaRunTimeModule, nme.hash_) + + genApplyMethod(instance, ScalaRunTimeModule.moduleClass, sym, arguments) + } + + /** Gen JS code for an array operation (get, set or length) */ + private def genArrayOp(tree: Tree, code: Int): js.Tree = { + import scalaPrimitives._ + + implicit val pos = tree.pos + + val Apply(Select(arrayObj, _), args) = tree + val arrayValue = genExpr(arrayObj) + val arguments = args map genExpr + + def genSelect() = { + val elemIRType = + toTypeKind(arrayObj.tpe).asInstanceOf[ARRAY].elem.toIRType + js.ArraySelect(arrayValue, arguments(0))(elemIRType) + } + + if (scalaPrimitives.isArrayGet(code)) { + // get an item of the array + assert(args.length == 1, + s"Array get requires 1 argument, found ${args.length} in $tree") + genSelect() + } else if (scalaPrimitives.isArraySet(code)) { + // set an item of the array + assert(args.length == 2, + s"Array set requires 2 arguments, found ${args.length} in $tree") + js.Assign(genSelect(), arguments(1)) + } else { + // length of the array + js.ArrayLength(arrayValue) + } + } + + /** Gen JS code for a call to AnyRef.synchronized */ + private def genSynchronized(tree: Apply, isStat: Boolean): js.Tree = { + /* JavaScript is single-threaded, so we can drop the + * synchronization altogether. + */ + val Apply(Select(receiver, _), List(arg)) = tree + val newReceiver = genExpr(receiver) + val newArg = genStatOrExpr(arg, isStat) + newReceiver match { + case js.This() => + // common case for which there is no side-effect nor NPE + newArg + case _ => + implicit val pos = tree.pos + val NPECtor = getMemberMethod(NullPointerExceptionClass, + nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty) + js.Block( + js.If(js.BinaryOp(js.BinaryOp.===, newReceiver, js.Null()), + js.Throw(genNew(NullPointerExceptionClass, NPECtor, Nil)), + js.Skip())(jstpe.NoType), + newArg) + } + } + + /** Gen JS code for a coercion */ + private def genCoercion(tree: Apply, receiver: Tree, code: Int): js.Tree = { + import scalaPrimitives._ + + implicit val pos = tree.pos + + val source = genExpr(receiver) + + def source2int = (code: @switch) match { + case F2C | D2C | F2B | D2B | F2S | D2S | F2I | D2I => + js.UnaryOp(js.UnaryOp.DoubleToInt, source) + case L2C | L2B | L2S | L2I => + js.UnaryOp(js.UnaryOp.LongToInt, source) + case _ => + source + } + + (code: @switch) match { + // To Char, need to crop at unsigned 16-bit + case B2C | S2C | I2C | L2C | F2C | D2C => + js.BinaryOp(js.BinaryOp.Int_&, source2int, js.IntLiteral(0xffff)) + + // To Byte, need to crop at signed 8-bit + case C2B | S2B | I2B | L2B | F2B | D2B => + // note: & 0xff would not work because of negative values + js.BinaryOp(js.BinaryOp.Int_>>, + js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(24)), + js.IntLiteral(24)) + + // To Short, need to crop at signed 16-bit + case C2S | I2S | L2S | F2S | D2S => + // note: & 0xffff would not work because of negative values + js.BinaryOp(js.BinaryOp.Int_>>, + js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(16)), + js.IntLiteral(16)) + + // To Int, need to crop at signed 32-bit + case L2I | F2I | D2I => + source2int + + // Any int to Long + case C2L | B2L | S2L | I2L => + js.UnaryOp(js.UnaryOp.IntToLong, source) + + // Any double to Long + case F2L | D2L => + js.UnaryOp(js.UnaryOp.DoubleToLong, source) + + // Long to Double + case L2D => + js.UnaryOp(js.UnaryOp.LongToDouble, source) + + // Any int, or Double, to Float + case C2F | B2F | S2F | I2F | D2F => + js.UnaryOp(js.UnaryOp.DoubleToFloat, source) + + // Long to Float === Long to Double to Float + case L2F => + js.UnaryOp(js.UnaryOp.DoubleToFloat, + js.UnaryOp(js.UnaryOp.LongToDouble, source)) + + // Identities and IR upcasts + case C2C | B2B | S2S | I2I | L2L | F2F | D2D | + C2I | C2D | + B2S | B2I | B2D | + S2I | S2D | + I2D | + F2D => + source + } + } + + /** Gen JS code for an ApplyDynamic + * ApplyDynamic nodes appear as the result of calls to methods of a + * structural type. + * + * Most unfortunately, earlier phases of the compiler assume too much + * about the backend, namely, they believe arguments and the result must + * be boxed, and do the boxing themselves. This decision should be left + * to the backend, but it's not, so we have to undo these boxes. + * Note that this applies to parameter types only. The return type is boxed + * anyway since we do not know it's exact type. + * + * This then generates a call to the reflective call proxy for the given + * arguments. + */ + private def genApplyDynamic(tree: ApplyDynamic): js.Tree = { + implicit val pos = tree.pos + + val sym = tree.symbol + val params = sym.tpe.params + + /** check if the method we are invoking is eq or ne. they cannot be + * overridden since they are final. If this is true, we only emit a + * `===` or `!==`. + */ + val isEqOrNeq = (sym.name == nme.eq || sym.name == nme.ne) && + params.size == 1 && params.head.tpe.typeSymbol == ObjectClass + + /** check if the method we are invoking conforms to a method on + * scala.Array. If this is the case, we check that case specially at + * runtime to avoid having reflective call proxies on scala.Array. + * (Also, note that the element type of Array#update is not erased and + * therefore the method name mangling would turn out wrong) + * + * Note that we cannot check if the expected return type is correct, + * since this type information is already erased. + */ + def isArrayLikeOp = { + sym.name == nme.update && + params.size == 2 && params.head.tpe.typeSymbol == IntClass || + sym.name == nme.apply && + params.size == 1 && params.head.tpe.typeSymbol == IntClass || + sym.name == nme.length && + params.size == 0 || + sym.name == nme.clone_ && + params.size == 0 + } + + /** + * Tests whether one of our reflective "boxes" for primitive types + * implements the particular method. If this is the case + * (result != NoSymbol), we generate a runtime instance check if we are + * dealing with the appropriate primitive type. + */ + def matchingSymIn(clazz: Symbol) = clazz.tpe.member(sym.name).suchThat { s => + val sParams = s.tpe.params + !s.isBridge && + params.size == sParams.size && + (params zip sParams).forall { case (s1,s2) => + s1.tpe =:= s2.tpe + } + } + + val ApplyDynamic(receiver, args) = tree + + if (isEqOrNeq) { + // Just emit a boxed equality check + val jsThis = genExpr(receiver) + val jsThat = genExpr(args.head) + val op = if (sym.name == nme.eq) js.BinaryOp.=== else js.BinaryOp.!== + ensureBoxed(js.BinaryOp(op, jsThis, jsThat), BooleanClass.tpe) + } else { + // Create a fully-fledged reflective call + val receiverType = toIRType(receiver.tpe) + val callTrgIdent = freshLocalIdent() + val callTrgVarDef = + js.VarDef(callTrgIdent, receiverType, mutable = false, genExpr(receiver)) + val callTrg = js.VarRef(callTrgIdent, mutable = false)(receiverType) + + val arguments = args zip sym.tpe.params map { case (arg, param) => + /* No need for enteringPosterasure, because value classes are not + * supported as parameters of methods in structural types. + * We could do it for safety and future-proofing anyway, except that + * I am weary of calling enteringPosterasure for a reflective method + * symbol. + * + * Note also that this will typically unbox a primitive value that + * has just been boxed, or will .asInstanceOf[T] an expression which + * is already of type T. But the optimizer will get rid of that, and + * reflective calls are not numerous, so we don't complicate the + * compiler to eliminate them early. + */ + fromAny(genExpr(arg), param.tpe) + } + + val proxyIdent = encodeMethodSym(sym, reflProxy = true) + var callStatement: js.Tree = + genApplyMethod(callTrg, receiver.tpe, proxyIdent, arguments, + jstpe.AnyType) + + if (isArrayLikeOp) { + def genRTCall(method: Symbol, args: js.Tree*) = + genApplyMethod(genLoadModule(ScalaRunTimeModule), + ScalaRunTimeModule.moduleClass, method, args.toList) + val isArrayTree = + genRTCall(ScalaRunTime_isArray, callTrg, js.IntLiteral(1)) + callStatement = js.If(isArrayTree, { + sym.name match { + case nme.update => + js.Block( + genRTCall(currentRun.runDefinitions.arrayUpdateMethod, + callTrg, arguments(0), arguments(1)), + js.Undefined()) // Boxed Unit + case nme.apply => + genRTCall(currentRun.runDefinitions.arrayApplyMethod, callTrg, + arguments(0)) + case nme.length => + genRTCall(currentRun.runDefinitions.arrayLengthMethod, callTrg) + case nme.clone_ => + genApplyMethod(callTrg, receiver.tpe, Object_clone, arguments) + } + }, { + callStatement + })(jstpe.AnyType) + } + + for { + (primTypeOf, reflBoxClass) <- Seq( + ("string", StringClass), + ("number", NumberReflectiveCallClass), + ("boolean", BooleanReflectiveCallClass) + ) + implMethodSym = matchingSymIn(reflBoxClass) + if implMethodSym != NoSymbol && implMethodSym.isPublic + } { + callStatement = js.If( + js.BinaryOp(js.BinaryOp.===, + js.UnaryOp(js.UnaryOp.typeof, callTrg), + js.StringLiteral(primTypeOf)), { + if (implMethodSym.owner == ObjectClass) { + // If the method is defined on Object, we can call it normally. + genApplyMethod(callTrg, receiver.tpe, implMethodSym, arguments) + } else { + if (primTypeOf == "string") { + val (rtModuleClass, methodIdent) = + encodeRTStringMethodSym(implMethodSym) + val retTpe = implMethodSym.tpe.resultType + val castCallTrg = fromAny(callTrg, StringClass.toTypeConstructor) + val rawApply = genApplyMethod( + genLoadModule(rtModuleClass), + rtModuleClass, + methodIdent, + castCallTrg :: arguments, + toIRType(retTpe)) + // Box the result of the implementing method if required + if (isPrimitiveValueType(retTpe)) + makePrimitiveBox(rawApply, retTpe) + else + rawApply + } else { + val (reflBoxClassPatched, callTrg1) = { + def isIntOrLongKind(kind: TypeKind) = kind match { + case _:INT | LONG => true + case _ => false + } + if (primTypeOf == "number" && + toTypeKind(implMethodSym.tpe.resultType) == DoubleKind && + isIntOrLongKind(toTypeKind(sym.tpe.resultType))) { + // This must be an Int, and not a Double + (IntegerReflectiveCallClass, + js.AsInstanceOf(callTrg, + toReferenceType(BoxedIntClass.toTypeConstructor))) + } else { + (reflBoxClass, callTrg) + } + } + val castCallTrg = + fromAny(callTrg1, + reflBoxClassPatched.primaryConstructor.tpe.params.head.tpe) + val reflBox = genNew(reflBoxClassPatched, + reflBoxClassPatched.primaryConstructor, List(castCallTrg)) + genApplyMethod( + reflBox, + reflBoxClassPatched, + proxyIdent, + arguments, + jstpe.AnyType) + } + } + }, { // else + callStatement + })(jstpe.AnyType) + } + + js.Block(callTrgVarDef, callStatement) + } + } + + /** Ensures that the value of the given tree is boxed. + * @param expr Tree to be boxed if needed. + * @param tpeEnteringPosterasure The type of `expr` as it was entering + * the posterasure phase. + */ + def ensureBoxed(expr: js.Tree, tpeEnteringPosterasure: Type)( + implicit pos: Position): js.Tree = { + + tpeEnteringPosterasure match { + case tpe if isPrimitiveValueType(tpe) => + makePrimitiveBox(expr, tpe) + + case tpe: ErasedValueType => + val boxedClass = tpe.valueClazz + val ctor = boxedClass.primaryConstructor + genNew(boxedClass, ctor, List(expr)) + + case _ => + expr + } + } + + /** Extracts a value typed as Any to the given type after posterasure. + * @param expr Tree to be extracted. + * @param tpeEnteringPosterasure The type of `expr` as it was entering + * the posterasure phase. + */ + def fromAny(expr: js.Tree, tpeEnteringPosterasure: Type)( + implicit pos: Position): js.Tree = { + + tpeEnteringPosterasure match { + case tpe if isPrimitiveValueType(tpe) => + makePrimitiveUnbox(expr, tpe) + + case tpe: ErasedValueType => + val boxedClass = tpe.valueClazz + val unboxMethod = boxedClass.derivedValueClassUnbox + val content = genApplyMethod( + genAsInstanceOf(expr, tpe), + boxedClass, unboxMethod, Nil) + if (unboxMethod.tpe.resultType <:< tpe.erasedUnderlying) + content + else + fromAny(content, tpe.erasedUnderlying) + + case tpe => + genAsInstanceOf(expr, tpe) + } + } + + /** Gen a boxing operation (tpe is the primitive type) */ + def makePrimitiveBox(expr: js.Tree, tpe: Type)( + implicit pos: Position): js.Tree = { + toTypeKind(tpe) match { + case VOID => // must be handled at least for JS interop + js.Block(expr, js.Undefined()) + case kind: ValueTypeKind => + if (kind == CharKind) { + genApplyMethod( + genLoadModule(BoxesRunTimeClass), + BoxesRunTimeClass, + BoxesRunTime_boxToCharacter, + List(expr)) + } else { + expr // box is identity for all non-Char types + } + case _ => + abort(s"makePrimitiveBox requires a primitive type, found $tpe at $pos") + } + } + + /** Gen an unboxing operation (tpe is the primitive type) */ + def makePrimitiveUnbox(expr: js.Tree, tpe: Type)( + implicit pos: Position): js.Tree = { + toTypeKind(tpe) match { + case VOID => // must be handled at least for JS interop + expr + case kind: ValueTypeKind => + if (kind == CharKind) { + genApplyMethod( + genLoadModule(BoxesRunTimeClass), + BoxesRunTimeClass, + BoxesRunTime_unboxToChar, + List(expr)) + } else { + js.Unbox(expr, kind.primitiveCharCode) + } + case _ => + abort(s"makePrimitiveUnbox requires a primitive type, found $tpe at $pos") + } + } + + private def lookupModuleClass(name: String) = { + val module = getModuleIfDefined(name) + if (module == NoSymbol) NoSymbol + else module.moduleClass + } + + lazy val ReflectArrayModuleClass = lookupModuleClass("java.lang.reflect.Array") + lazy val UtilArraysModuleClass = lookupModuleClass("java.util.Arrays") + + /** Gen JS code for a Scala.js-specific primitive method */ + private def genJSPrimitive(tree: Apply, receiver0: Tree, + args: List[Tree], code: Int): js.Tree = { + import jsPrimitives._ + + implicit val pos = tree.pos + + def receiver = genExpr(receiver0) + val genArgArray = genPrimitiveJSArgs(tree.symbol, args) + + lazy val js.JSArrayConstr(genArgs) = genArgArray + + def extractFirstArg() = { + (genArgArray: @unchecked) match { + case js.JSArrayConstr(firstArg :: otherArgs) => + (firstArg, js.JSArrayConstr(otherArgs)) + case js.JSBracketMethodApply( + js.JSArrayConstr(firstArg :: firstPart), concat, otherParts) => + (firstArg, js.JSBracketMethodApply( + js.JSArrayConstr(firstPart), concat, otherParts)) + } + } + + if (code == DYNNEW) { + // js.Dynamic.newInstance(clazz)(actualArgs:_*) + val (jsClass, actualArgArray) = extractFirstArg() + actualArgArray match { + case js.JSArrayConstr(actualArgs) => + js.JSNew(jsClass, actualArgs) + case _ => + genNewJSWithVarargs(jsClass, actualArgArray) + } + } else if (code == DYNAPPLY) { + // js.Dynamic.applyDynamic(methodName)(actualArgs:_*) + val (methodName, actualArgArray) = extractFirstArg() + actualArgArray match { + case js.JSArrayConstr(actualArgs) => + js.JSBracketMethodApply(receiver, methodName, actualArgs) + case _ => + genApplyJSMethodWithVarargs(receiver, methodName, actualArgArray) + } + } else if (code == DYNLITN) { + // We have a call of the form: + // js.Dynamic.literal(name1 = ..., name2 = ...) + // Translate to: + // {"name1": ..., "name2": ... } + extractFirstArg() match { + case (js.StringLiteral("apply"), + js.JSArrayConstr(jse.LitNamed(pairs))) => + js.JSObjectConstr(pairs) + case (js.StringLiteral(name), _) if name != "apply" => + reporter.error(pos, + s"js.Dynamic.literal does not have a method named $name") + js.Undefined() + case _ => + reporter.error(pos, + "js.Dynamic.literal.applyDynamicNamed may not be called directly") + js.Undefined() + } + } else if (code == DYNLIT) { + // We have a call of some other form + // js.Dynamic.literal(...) + // Translate to: + // var obj = {}; + // obj[...] = ...; + // obj + + // Extract first arg to future proof against varargs + extractFirstArg() match { + // case js.Dynamic.literal("name1" -> ..., "name2" -> ...) + case (js.StringLiteral("apply"), + js.JSArrayConstr(jse.LitNamed(pairs))) => + js.JSObjectConstr(pairs) + + // case js.Dynamic.literal(x, y) + case (js.StringLiteral("apply"), js.JSArrayConstr(tups)) => + // Create tmp variable + val resIdent = freshLocalIdent("obj") + val resVarDef = js.VarDef(resIdent, jstpe.AnyType, mutable = false, + js.JSObjectConstr(Nil)) + val res = resVarDef.ref + + // Assign fields + val tuple2Type = encodeClassType(TupleClass(2)) + val assigns = tups flatMap { + // special case for literals + case jse.Tuple2(name, value) => + js.Assign(js.JSBracketSelect(res, name), value) :: Nil + case tupExpr => + val tupIdent = freshLocalIdent("tup") + val tup = js.VarRef(tupIdent, mutable = false)(tuple2Type) + js.VarDef(tupIdent, tuple2Type, mutable = false, tupExpr) :: + js.Assign(js.JSBracketSelect(res, + genApplyMethod(tup, TupleClass(2), js.Ident("$$und1__O"), Nil, jstpe.AnyType)), + genApplyMethod(tup, TupleClass(2), js.Ident("$$und2__O"), Nil, jstpe.AnyType)) :: Nil + } + + js.Block(resVarDef +: assigns :+ res: _*) + + /* Here we would need the case where the varargs are passed in + * as non-literal list: + * js.Dynamic.literal(x: _*) + * However, Scala does not currently support this + */ + + // case where another method is called + case (js.StringLiteral(name), _) if name != "apply" => + reporter.error(pos, + s"js.Dynamic.literal does not have a method named $name") + js.Undefined() + case _ => + reporter.error(pos, + "js.Dynamic.literal.applyDynamic may not be called directly") + js.Undefined() + } + } else if (code == ARR_CREATE) { + // js.Array.create(elements: _*) + genArgArray + } else (genArgs match { + case Nil => + code match { + case GETCLASS => js.GetClass(receiver) + case ENV_INFO => js.JSEnvInfo() + case DEBUGGER => js.Debugger() + case UNDEFVAL => js.Undefined() + case UNITVAL => js.Undefined() + case UNITTYPE => genClassConstant(UnitTpe) + case JS_NATIVE => + reporter.error(pos, "js.native may only be used as stub implementation in facade types") + js.Undefined() + } + + case List(arg) => + + /** Factorization of F2JS and F2JSTHIS. */ + def genFunctionToJSFunction(isThisFunction: Boolean): js.Tree = { + val arity = { + val funName = tree.fun.symbol.name.encoded + assert(funName.startsWith("fromFunction")) + funName.stripPrefix("fromFunction").toInt + } + val inputClass = FunctionClass(arity) + val inputIRType = encodeClassType(inputClass) + val applyMeth = getMemberMethod(inputClass, nme.apply) suchThat { s => + val ps = s.paramss + ps.size == 1 && + ps.head.size == arity && + ps.head.forall(_.tpe.typeSymbol == ObjectClass) + } + val fCaptureParam = js.ParamDef(js.Ident("f"), inputIRType, + mutable = false) + val jsArity = + if (isThisFunction) arity - 1 + else arity + val jsParams = (1 to jsArity).toList map { + x => js.ParamDef(js.Ident("arg"+x), jstpe.AnyType, + mutable = false) + } + js.Closure( + List(fCaptureParam), + jsParams, + genApplyMethod( + fCaptureParam.ref, + inputClass, applyMeth, + if (isThisFunction) + js.This()(jstpe.AnyType) :: jsParams.map(_.ref) + else + jsParams.map(_.ref)), + List(arg)) + } + + code match { + /** Convert a scala.FunctionN f to a js.FunctionN. */ + case F2JS => + arg match { + /* This case will happen every time we have a Scala lambda + * in js.FunctionN position. We remove the JS function to + * Scala function wrapper, instead of adding a Scala function + * to JS function wrapper. + */ + case JSFunctionToScala(fun, arity) => + fun + case _ => + genFunctionToJSFunction(isThisFunction = false) + } + + /** Convert a scala.FunctionN f to a js.ThisFunction{N-1}. */ + case F2JSTHIS => + genFunctionToJSFunction(isThisFunction = true) + + case DYNSELECT => + // js.Dynamic.selectDynamic(arg) + js.JSBracketSelect(receiver, arg) + + case DICT_DEL => + // js.Dictionary.delete(arg) + js.JSDelete(js.JSBracketSelect(receiver, arg)) + + case ISUNDEF => + // js.isUndefined(arg) + js.BinaryOp(js.BinaryOp.===, arg, js.Undefined()) + case TYPEOF => + // js.typeOf(arg) + js.UnaryOp(js.UnaryOp.typeof, arg) + + case OBJPROPS => + // js.Object.properties(arg) + genApplyMethod( + genLoadModule(RuntimePackageModule), + RuntimePackageModule.moduleClass, + Runtime_propertiesOf, + List(arg)) + } + + case List(arg1, arg2) => + code match { + case DYNUPDATE => + // js.Dynamic.updateDynamic(arg1)(arg2) + js.Assign(js.JSBracketSelect(receiver, arg1), arg2) + + case HASPROP => + // js.Object.hasProperty(arg1, arg2) + /* Here we have an issue with evaluation order of arg1 and arg2, + * since the obvious translation is `arg2 in arg1`, but then + * arg2 is evaluated before arg1. Since this is not a commonly + * used operator, we don't try to avoid unnessary temp vars, and + * simply always evaluate arg1 in a temp before doing the `in`. + */ + val temp = freshLocalIdent() + js.Block( + js.VarDef(temp, jstpe.AnyType, mutable = false, arg1), + js.BinaryOp(js.BinaryOp.in, arg2, + js.VarRef(temp, mutable = false)(jstpe.AnyType))) + } + }) + } + + /** Gen JS code for a primitive JS call (to a method of a subclass of js.Any) + * This is the typed Scala.js to JS bridge feature. Basically it boils + * down to calling the method without name mangling. But other aspects + * come into play: + * * Operator methods are translated to JS operators (not method calls) + * * apply is translated as a function call, i.e. o() instead of o.apply() + * * Scala varargs are turned into JS varargs (see genPrimitiveJSArgs()) + * * Getters and parameterless methods are translated as Selects + * * Setters are translated to Assigns of Selects + */ + private def genPrimitiveJSCall(tree: Apply, isStat: Boolean): js.Tree = { + implicit val pos = tree.pos + + val sym = tree.symbol + val Apply(fun @ Select(receiver0, _), args0) = tree + + val funName = sym.unexpandedName.decoded + val receiver = genExpr(receiver0) + val argArray = genPrimitiveJSArgs(sym, args0) + + // valid only for methods that don't have any varargs + lazy val js.JSArrayConstr(args) = argArray + lazy val argc = args.length + + def hasExplicitJSEncoding = + sym.hasAnnotation(JSNameAnnotation) || + sym.hasAnnotation(JSBracketAccessAnnotation) + + val boxedResult = funName match { + case "unary_+" | "unary_-" | "unary_~" | "unary_!" => + assert(argc == 0) + js.JSUnaryOp(funName.substring(funName.length-1), receiver) + + case "+" | "-" | "*" | "/" | "%" | "<<" | ">>" | ">>>" | + "&" | "|" | "^" | "&&" | "||" | "<" | ">" | "<=" | ">=" => + assert(argc == 1) + js.JSBinaryOp(funName, receiver, args.head) + + case "apply" if receiver0.tpe.typeSymbol.isSubClass(JSThisFunctionClass) => + js.JSBracketMethodApply(receiver, js.StringLiteral("call"), args) + + case "apply" if !hasExplicitJSEncoding => + argArray match { + case js.JSArrayConstr(args) => + js.JSFunctionApply(receiver, args) + case _ => + js.JSBracketMethodApply( + receiver, js.StringLiteral("apply"), List(js.Null(), argArray)) + } + + case _ => + def jsFunName = jsNameOf(sym) + + if (sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM)) { + js.UndefinedParam()(toIRType(sym.tpe.resultType)) + } else if (jsInterop.isJSGetter(sym)) { + assert(argc == 0) + js.JSBracketSelect(receiver, js.StringLiteral(jsFunName)) + } else if (jsInterop.isJSSetter(sym)) { + assert(argc == 1) + js.Assign( + js.JSBracketSelect(receiver, + js.StringLiteral(jsFunName.stripSuffix("_="))), + args.head) + } else if (jsInterop.isJSBracketAccess(sym)) { + assert(argArray.isInstanceOf[js.JSArrayConstr] && (argc == 1 || argc == 2), + s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments") + args match { + case List(keyArg) => + js.JSBracketSelect(receiver, keyArg) + case List(keyArg, valueArg) => + js.Assign( + js.JSBracketSelect(receiver, keyArg), + valueArg) + } + } else { + argArray match { + case js.JSArrayConstr(args) => + js.JSBracketMethodApply( + receiver, js.StringLiteral(jsFunName), args) + case _ => + genApplyJSMethodWithVarargs(receiver, + js.StringLiteral(jsFunName), argArray) + } + } + } + + boxedResult match { + case js.UndefinedParam() | js.Assign(_, _) => + boxedResult + case _ if isStat => + boxedResult + case _ => + fromAny(boxedResult, + enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType)) + } + } + + /** Gen JS code to call a primitive JS method with variadic parameters. */ + private def genApplyJSMethodWithVarargs(receiver: js.Tree, + methodName: js.Tree, argArray: js.Tree)( + implicit pos: Position): js.Tree = { + // We need to evaluate `receiver` only once + val receiverValDef = + js.VarDef(freshLocalIdent(), receiver.tpe, mutable = false, receiver) + js.Block( + receiverValDef, + js.JSBracketMethodApply( + js.JSBracketSelect(receiverValDef.ref, methodName), + js.StringLiteral("apply"), + List(receiverValDef.ref, argArray))) + } + + /** Gen JS code to instantiate a JS class with variadic parameters. */ + private def genNewJSWithVarargs(jsClass: js.Tree, argArray: js.Tree)( + implicit pos: Position): js.Tree = { + genApplyMethod( + genLoadModule(RuntimePackageModule), + RuntimePackageModule.moduleClass, + Runtime_newJSObjectWithVarargs, + List(jsClass, argArray)) + } + + /** Gen JS code for new java.lang.String(...) + * Proxies calls to method newString on object + * scala.scalajs.runtime.RuntimeString with proper arguments + */ + private def genNewString(tree: Apply): js.Tree = { + implicit val pos = tree.pos + val Apply(fun @ Select(_, _), args0) = tree + + val ctor = fun.symbol + val args = args0 map genExpr + + // Filter members of target module for matching member + val compMembers = for { + mem <- RuntimeStringModule.tpe.members + if mem.name == jsnme.newString && ctor.tpe.matches(mem.tpe) + } yield mem + + if (compMembers.isEmpty) { + reporter.error(pos, + s"""Could not find implementation for constructor of java.lang.String + |with type ${ctor.tpe}. Constructors on java.lang.String + |are forwarded to the companion object of + |scala.scalajs.runtime.RuntimeString""".stripMargin) + js.Undefined() + } else { + assert(compMembers.size == 1, + s"""For constructor with type ${ctor.tpe} on java.lang.String, + |found multiple companion module members.""".stripMargin) + + // Emit call to companion object + genApplyMethod( + genLoadModule(RuntimeStringModule), + RuntimeStringModule.moduleClass, + compMembers.head, + args) + } + } + + /** Gen JS code for calling a method on java.lang.String. + * + * Forwards call on java.lang.String to the module + * scala.scalajs.runtime.RuntimeString. + */ + private def genStringCall(tree: Apply): js.Tree = { + implicit val pos = tree.pos + + val sym = tree.symbol + + // Deconstruct tree and create receiver and argument JS expressions + val Apply(Select(receiver0, _), args0) = tree + val receiver = genExpr(receiver0) + val args = args0 map genExpr + + // Emit call to the RuntimeString module + val (rtModuleClass, methodIdent) = encodeRTStringMethodSym(sym) + genApplyMethod( + genLoadModule(rtModuleClass), + rtModuleClass, + methodIdent, + receiver :: args, + toIRType(tree.tpe)) + } + + /** Gen JS code for a new of a raw JS class (subclass of js.Any) */ + private def genPrimitiveJSNew(tree: Apply): js.Tree = { + implicit val pos = tree.pos + + val Apply(fun @ Select(New(tpt), _), args0) = tree + val cls = tpt.tpe.typeSymbol + val ctor = fun.symbol + + genPrimitiveJSArgs(ctor, args0) match { + case js.JSArrayConstr(args) => + if (cls == JSObjectClass && args.isEmpty) js.JSObjectConstr(Nil) + else if (cls == JSArrayClass && args.isEmpty) js.JSArrayConstr(Nil) + else js.JSNew(genPrimitiveJSClass(cls), args) + case argArray => + genNewJSWithVarargs(genPrimitiveJSClass(cls), argArray) + } + } + + /** Gen JS code representing a JS class (subclass of js.Any) */ + private def genPrimitiveJSClass(sym: Symbol)( + implicit pos: Position): js.Tree = { + genGlobalJSObject(sym) + } + + /** Gen JS code representing a JS module (var of the global scope) */ + private def genPrimitiveJSModule(sym: Symbol)( + implicit pos: Position): js.Tree = { + genGlobalJSObject(sym) + } + + /** Gen JS code representing a JS object (class or module) in global scope + */ + private def genGlobalJSObject(sym: Symbol)( + implicit pos: Position): js.Tree = { + jsNameOf(sym).split('.').foldLeft(genLoadGlobal()) { (memo, chunk) => + js.JSBracketSelect(memo, js.StringLiteral(chunk)) + } + } + + /** Gen actual actual arguments to Scala method call. + * Returns a list of the transformed arguments. + * + * This tries to optimized repeated arguments (varargs) by turning them + * into js.WrappedArray instead of Scala wrapped arrays. + */ + private def genActualArgs(sym: Symbol, args: List[Tree])( + implicit pos: Position): List[js.Tree] = { + val wereRepeated = exitingPhase(currentRun.typerPhase) { + sym.tpe.params.map(p => isScalaRepeatedParamType(p.tpe)) + } + + if (wereRepeated.size > args.size) { + // Should not happen, but let's not crash + args.map(genExpr) + } else { + /* Arguments that are in excess compared to the type signature after + * erasure are lambda-lifted arguments. They cannot be repeated, hence + * the extension to `false`. + */ + for ((arg, wasRepeated) <- args.zipAll(wereRepeated, EmptyTree, false)) yield { + if (wasRepeated) { + tryGenRepeatedParamAsJSArray(arg, handleNil = false).fold { + genExpr(arg) + } { argArray => + genNew(WrappedArrayClass, WrappedArray_ctor, List(argArray)) + } + } else { + genExpr(arg) + } + } + } + } + + /** Gen actual actual arguments to a primitive JS call + * This handles repeated arguments (varargs) by turning them into + * JS varargs, i.e., by expanding them into normal arguments. + * + * Returns an only tree which is a JS array of the arguments. In most + * cases, it will be a js.JSArrayConstr with the expanded arguments. It will + * not if a Seq is passed to a varargs argument with the syntax seq: _*. + */ + private def genPrimitiveJSArgs(sym: Symbol, args: List[Tree])( + implicit pos: Position): js.Tree = { + val wereRepeated = exitingPhase(currentRun.typerPhase) { + for { + params <- sym.tpe.paramss + param <- params + } yield isScalaRepeatedParamType(param.tpe) + } + + var reversedParts: List[js.Tree] = Nil + var reversedPartUnderConstruction: List[js.Tree] = Nil + + def closeReversedPartUnderConstruction() = { + if (!reversedPartUnderConstruction.isEmpty) { + val part = reversedPartUnderConstruction.reverse + reversedParts ::= js.JSArrayConstr(part) + reversedPartUnderConstruction = Nil + } + } + + val paramTpes = enteringPhase(currentRun.posterasurePhase) { + for (param <- sym.tpe.params) + yield param.tpe + } + + for (((arg, wasRepeated), tpe) <- (args zip wereRepeated) zip paramTpes) { + if (wasRepeated) { + genPrimitiveJSRepeatedParam(arg) match { + case js.JSArrayConstr(jsArgs) => + reversedPartUnderConstruction = + jsArgs reverse_::: reversedPartUnderConstruction + case jsArgArray => + closeReversedPartUnderConstruction() + reversedParts ::= jsArgArray + } + } else { + val unboxedArg = genExpr(arg) + val boxedArg = unboxedArg match { + case js.UndefinedParam() => unboxedArg + case _ => ensureBoxed(unboxedArg, tpe) + } + reversedPartUnderConstruction ::= boxedArg + } + } + closeReversedPartUnderConstruction() + + // Find js.UndefinedParam at the end of the argument list. No check is + // performed whether they may be there, since they will only be placed + // where default arguments can be anyway + reversedParts = reversedParts match { + case Nil => Nil + case js.JSArrayConstr(params) :: others => + val nparams = + params.reverse.dropWhile(_.isInstanceOf[js.UndefinedParam]).reverse + js.JSArrayConstr(nparams) :: others + case parts => parts + } + + // Find remaining js.UndefinedParam and replace by js.Undefined. This can + // happen with named arguments or when multiple argument lists are present + reversedParts = reversedParts map { + case js.JSArrayConstr(params) => + val nparams = params map { + case js.UndefinedParam() => js.Undefined() + case param => param + } + js.JSArrayConstr(nparams) + case part => part + } + + reversedParts match { + case Nil => js.JSArrayConstr(Nil) + case List(part) => part + case _ => + val partHead :: partTail = reversedParts.reverse + js.JSBracketMethodApply( + partHead, js.StringLiteral("concat"), partTail) + } + } + + /** Gen JS code for a repeated param of a primitive JS method + * In this case `arg` has type Seq[T] for some T, but the result should + * have type js.Array[T]. So this method takes care of the conversion. + * It is specialized for the shapes of tree generated by the desugaring + * of repeated params in Scala, so that these produce a js.JSArrayConstr. + */ + private def genPrimitiveJSRepeatedParam(arg: Tree): js.Tree = { + tryGenRepeatedParamAsJSArray(arg, handleNil = true) getOrElse { + /* Fall back to calling runtime.genTraversableOnce2jsArray + * to perform the conversion. + */ + implicit val pos = arg.pos + genApplyMethod( + genLoadModule(RuntimePackageModule), + RuntimePackageModule.moduleClass, + Runtime_genTraversableOnce2jsArray, + List(genExpr(arg))) + } + } + + /** Try and gen a js.Array for a repeated param (xs: T*). + * It is specialized for the shapes of tree generated by the desugaring + * of repeated params in Scala, so that these produce a js.JSArrayConstr. + * If `arg` does not have the shape of a generated repeated param, this + * method returns `None`. + */ + private def tryGenRepeatedParamAsJSArray(arg: Tree, + handleNil: Boolean): Option[js.Tree] = { + implicit val pos = arg.pos + + // Given a method `def foo(args: T*)` + arg match { + // foo(arg1, arg2, ..., argN) where N > 0 + case MaybeAsInstanceOf(WrapArray( + MaybeAsInstanceOf(ArrayValue(tpt, elems)))) => + /* Value classes in arrays are already boxed, so no need to use + * the type before erasure. + */ + val elemTpe = tpt.tpe + Some(js.JSArrayConstr(elems.map(e => ensureBoxed(genExpr(e), elemTpe)))) + + // foo() + case Select(_, _) if handleNil && arg.symbol == NilModule => + Some(js.JSArrayConstr(Nil)) + + // foo(argSeq:_*) - cannot be optimized + case _ => + None + } + } + + object MaybeAsInstanceOf { + def unapply(tree: Tree): Some[Tree] = tree match { + case Apply(TypeApply(asInstanceOf_? @ Select(base, _), _), _) + if asInstanceOf_?.symbol == Object_asInstanceOf => + Some(base) + case _ => + Some(tree) + } + } + + object WrapArray { + lazy val isWrapArray: Set[Symbol] = Seq( + nme.wrapRefArray, + nme.wrapByteArray, + nme.wrapShortArray, + nme.wrapCharArray, + nme.wrapIntArray, + nme.wrapLongArray, + nme.wrapFloatArray, + nme.wrapDoubleArray, + nme.wrapBooleanArray, + nme.wrapUnitArray, + nme.genericWrapArray).map(getMemberMethod(PredefModule, _)).toSet + + def unapply(tree: Apply): Option[Tree] = tree match { + case Apply(wrapArray_?, List(wrapped)) + if isWrapArray(wrapArray_?.symbol) => + Some(wrapped) + case _ => + None + } + } + + // Synthesizers for raw JS functions --------------------------------------- + + /** Try and gen and record JS code for an anonymous function class. + * + * Returns true if the class could be rewritten that way, false otherwise. + * + * We make the following assumptions on the form of such classes: + * - It is an anonymous function + * - Includes being anonymous, final, and having exactly one constructor + * - It is not a PartialFunction + * - It has no field other than param accessors + * - It has exactly one constructor + * - It has exactly one non-bridge method apply if it is not specialized, + * or a method apply$...$sp and a forwarder apply if it is specialized. + * - As a precaution: it is synthetic + * + * From a class looking like this: + * + * final class <anon>(outer, capture1, ..., captureM) extends AbstractionFunctionN[...] { + * def apply(param1, ..., paramN) = { + * <body> + * } + * } + * new <anon>(o, c1, ..., cM) + * + * we generate a function maker that emits: + * + * lambda<o, c1, ..., cM>[notype]( + * outer, capture1, ..., captureM, param1, ..., paramN) { + * <body> + * } + * + * so that, at instantiation point, we can write: + * + * new AnonFunctionN(functionMaker(this, captured1, ..., capturedM)) + * + * Trickier things apply when the function is specialized. + */ + private def tryGenAndRecordAnonFunctionClass(cd: ClassDef): Boolean = { + implicit val pos = cd.pos + val sym = cd.symbol + assert(sym.isAnonymousFunction, + s"tryGenAndRecordAnonFunctionClass called with non-anonymous function $cd") + + withScopedVars( + currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass), + currentClassSym := sym + ) { + val (functionMakerBase, arity) = + tryGenAndRecordAnonFunctionClassGeneric(cd) { msg => + return false + } + val functionMaker = { capturedArgs: List[js.Tree] => + JSFunctionToScala(functionMakerBase(capturedArgs), arity) + } + + translatedAnonFunctions += + sym -> (functionMaker, currentClassInfoBuilder.get) + } + true + } + + /** Constructor and extractor object for a tree that converts a JavaScript + * function into a Scala function. + */ + private object JSFunctionToScala { + private val AnonFunPrefScala = + "scala.scalajs.runtime.AnonFunction" + private val AnonFunPrefJS = + "sjsr_AnonFunction" + + def apply(jsFunction: js.Tree, arity: Int)( + implicit pos: Position): js.Tree = { + val clsSym = getRequiredClass(AnonFunPrefScala + arity) + val ctor = clsSym.tpe.member(nme.CONSTRUCTOR) + genNew(clsSym, ctor, List(jsFunction)) + } + + def unapply(tree: js.New): Option[(js.Tree, Int)] = tree match { + case js.New(jstpe.ClassType(wrapperName), _, List(fun)) + if wrapperName.startsWith(AnonFunPrefJS) => + val arityStr = wrapperName.substring(AnonFunPrefJS.length) + try { + Some((fun, arityStr.toInt)) + } catch { + case e: NumberFormatException => None + } + + case _ => + None + } + } + + /** Gen and record JS code for a raw JS function class. + * + * This is called when emitting a ClassDef that represents an anonymous + * class extending `js.FunctionN`. These are generated by the SAM + * synthesizer when the target type is a `js.FunctionN`. Since JS + * functions are not classes, we deconstruct the ClassDef, then + * reconstruct it to be a genuine Closure. + * + * Compared to `tryGenAndRecordAnonFunctionClass()`, this function must + * always succeed, because we really cannot afford keeping them as + * anonymous classes. The good news is that it can do so, because the + * body of SAM lambdas is hoisted in the enclosing class. Hence, the + * apply() method is just a forwarder to calling that hoisted method. + * + * From a class looking like this: + * + * final class <anon>(outer, capture1, ..., captureM) extends js.FunctionN[...] { + * def apply(param1, ..., paramN) = { + * outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM) + * } + * } + * new <anon>(o, c1, ..., cM) + * + * we generate a function maker that emits: + * + * lambda<o, c1, ..., cM>[notype]( + * outer, capture1, ..., captureM, param1, ..., paramN) { + * outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM) + * } + * + * The function maker is recorded in `translatedAnonFunctions` to be + * fetched later by the translation for New. + */ + def genAndRecordRawJSFunctionClass(cd: ClassDef): Unit = { + val sym = cd.symbol + assert(isRawJSFunctionDef(sym), + s"genAndRecordRawJSFunctionClass called with non-JS function $cd") + + withScopedVars( + currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass), + currentClassSym := sym + ) { + val (functionMaker, _) = + tryGenAndRecordAnonFunctionClassGeneric(cd) { msg => + abort(s"Could not generate raw function maker for JS function: $msg") + } + + translatedAnonFunctions += + sym -> (functionMaker, currentClassInfoBuilder.get) + } + } + + /** Code common to tryGenAndRecordAnonFunctionClass and + * genAndRecordRawJSFunctionClass. + */ + private def tryGenAndRecordAnonFunctionClassGeneric(cd: ClassDef)( + fail: (=> String) => Nothing): (List[js.Tree] => js.Tree, Int) = { + implicit val pos = cd.pos + val sym = cd.symbol + + // First checks + + if (sym.isSubClass(PartialFunctionClass)) + fail(s"Cannot rewrite PartialFunction $cd") + if (instantiatedAnonFunctions contains sym) { + // when the ordering we're given is evil (it happens!) + fail(s"Abort function rewrite because it was already instantiated: $cd") + } + + // First step: find the apply method def, and collect param accessors + + var paramAccessors: List[Symbol] = Nil + var applyDef: DefDef = null + + def gen(tree: Tree): Unit = { + tree match { + case EmptyTree => () + case Template(_, _, body) => body foreach gen + case vd @ ValDef(mods, name, tpt, rhs) => + val fsym = vd.symbol + if (!fsym.isParamAccessor) + fail(s"Found field $fsym which is not a param accessor in anon function $cd") + + if (fsym.isPrivate) { + paramAccessors ::= fsym + } else { + // Uh oh ... an inner something will try to access my fields + fail(s"Found a non-private field $fsym in $cd") + } + case dd: DefDef => + val ddsym = dd.symbol + if (ddsym.isClassConstructor) { + if (!ddsym.isPrimaryConstructor) + fail(s"Non-primary constructor $ddsym in anon function $cd") + } else { + val name = dd.name.toString + if (name == "apply" || (ddsym.isSpecialized && name.startsWith("apply$"))) { + if ((applyDef eq null) || ddsym.isSpecialized) + applyDef = dd + } else { + // Found a method we cannot encode in the rewriting + fail(s"Found a non-apply method $ddsym in $cd") + } + } + case _ => + fail("Illegal tree in gen of genAndRecordAnonFunctionClass(): " + tree) + } + } + gen(cd.impl) + paramAccessors = paramAccessors.reverse // preserve definition order + + if (applyDef eq null) + fail(s"Did not find any apply method in anon function $cd") + + withNewLocalNameScope { + // Second step: build the list of useful constructor parameters + + val ctorParams = sym.primaryConstructor.tpe.params + + if (paramAccessors.size != ctorParams.size && + !(paramAccessors.size == ctorParams.size-1 && + ctorParams.head.unexpandedName == jsnme.arg_outer)) { + fail( + s"Have param accessors $paramAccessors but "+ + s"ctor params $ctorParams in anon function $cd") + } + + val hasUnusedOuterCtorParam = paramAccessors.size != ctorParams.size + val usedCtorParams = + if (hasUnusedOuterCtorParam) ctorParams.tail + else ctorParams + val ctorParamDefs = usedCtorParams map { p => + // in the apply method's context + js.ParamDef(encodeLocalSym(p)(p.pos), toIRType(p.tpe), + mutable = false)(p.pos) + } + + // Third step: emit the body of the apply method def + + val (applyMethod, methodInfoBuilder) = withScopedVars( + paramAccessorLocals := (paramAccessors zip ctorParamDefs).toMap, + tryingToGenMethodAsJSFunction := true + ) { + try { + genMethodWithInfoBuilder(applyDef).getOrElse( + abort(s"Oops, $applyDef did not produce a method")) + } catch { + case e: CancelGenMethodAsJSFunction => + fail(e.getMessage) + } + } + + withScopedVars( + currentMethodInfoBuilder := methodInfoBuilder + ) { + // Fourth step: patch the body to unbox parameters and box result + + val js.MethodDef(_, params, _, body) = applyMethod + val (patchedParams, patchedBody) = + patchFunBodyWithBoxes(applyDef.symbol, params, body) + + // Fifth step: build the function maker + + val isThisFunction = JSThisFunctionClasses.exists(sym isSubClass _) + assert(!isThisFunction || patchedParams.nonEmpty, + s"Empty param list in ThisFunction: $cd") + + val functionMaker = { capturedArgs0: List[js.Tree] => + val capturedArgs = + if (hasUnusedOuterCtorParam) capturedArgs0.tail + else capturedArgs0 + assert(capturedArgs.size == ctorParamDefs.size) + + if (isThisFunction) { + val thisParam :: actualParams = patchedParams + js.Closure( + ctorParamDefs, + actualParams, + js.Block( + js.VarDef(thisParam.name, thisParam.ptpe, mutable = false, + js.This()(thisParam.ptpe)(thisParam.pos))(thisParam.pos), + patchedBody), + capturedArgs) + } else { + js.Closure(ctorParamDefs, patchedParams, patchedBody, capturedArgs) + } + } + + val arity = params.size + + (functionMaker, arity) + } + } + } + + /** Generate JS code for an anonymous function + * + * Anonymous functions survive until the backend only under + * -Ydelambdafy:method + * and when they do, their body is always of the form + * EnclosingClass.this.someMethod(arg1, ..., argN, capture1, ..., captureM) + * where argI are the formal arguments of the lambda, and captureI are + * local variables or the enclosing def. + * + * We translate them by instantiating scala.scalajs.runtime.AnonFunctionN + * with a JS closure: + * + * new ScalaJS.c.sjsr_AnonFunctionN().init___xyz( + * lambda<this, capture1, ..., captureM>( + * _this, capture1, ..., captureM, arg1, ..., argN) { + * _this.someMethod(arg1, ..., argN, capture1, ..., captureM) + * } + * ) + * + * In addition, input params are unboxed before use, and the result of + * someMethod() is boxed back. + */ + private def genAnonFunction(originalFunction: Function): js.Tree = { + implicit val pos = originalFunction.pos + val Function(paramTrees, Apply( + targetTree @ Select(receiver, _), allArgs0)) = originalFunction + + val target = targetTree.symbol + val params = paramTrees.map(_.symbol) + + val allArgs = allArgs0 map genExpr + + val formalArgs = params map { p => + js.ParamDef(encodeLocalSym(p)(p.pos), toIRType(p.tpe), + mutable = false)(p.pos) + } + + val isInImplClass = target.owner.isImplClass + + def makeCaptures(actualCaptures: List[js.Tree]) = { + (actualCaptures map { c => (c: @unchecked) match { + case js.VarRef(ident, _) => + (js.ParamDef(ident, c.tpe, mutable = false)(c.pos), + js.VarRef(ident, false)(c.tpe)(c.pos)) + }}).unzip + } + + val (allFormalCaptures, body, allActualCaptures) = if (!isInImplClass) { + val thisActualCapture = genExpr(receiver) + val thisFormalCapture = js.ParamDef( + freshLocalIdent("this")(receiver.pos), + thisActualCapture.tpe, mutable = false)(receiver.pos) + val thisCaptureArg = thisFormalCapture.ref + val (actualArgs, actualCaptures) = allArgs.splitAt(formalArgs.size) + val (formalCaptures, captureArgs) = makeCaptures(actualCaptures) + val body = genApplyMethod(thisCaptureArg, receiver.tpe, target, + actualArgs ::: captureArgs) + + (thisFormalCapture :: formalCaptures, + body, thisActualCapture :: actualCaptures) + } else { + val (thisActualCapture :: actualArgs, actualCaptures) = + allArgs.splitAt(formalArgs.size+1) + val (thisFormalCapture :: formalCaptures, thisCaptureArg :: captureArgs) = + makeCaptures(thisActualCapture :: actualCaptures) + val body = genTraitImplApply(target, + thisCaptureArg :: actualArgs ::: captureArgs) + + (thisFormalCapture :: formalCaptures, + body, thisActualCapture :: actualCaptures) + } + + val (patchedFormalArgs, patchedBody) = + patchFunBodyWithBoxes(target, formalArgs, body) + val closure = js.Closure( + allFormalCaptures, + patchedFormalArgs, + patchedBody, + allActualCaptures) + + JSFunctionToScala(closure, params.size) + } + + private def patchFunBodyWithBoxes(methodSym: Symbol, + params: List[js.ParamDef], body: js.Tree)( + implicit pos: Position): (List[js.ParamDef], js.Tree) = { + val methodType = enteringPhase(currentRun.posterasurePhase)(methodSym.tpe) + + val (patchedParams, paramsLocal) = (for { + (param, paramSym) <- params zip methodType.params + } yield { + val paramTpe = enteringPhase(currentRun.posterasurePhase)(paramSym.tpe) + val paramName = param.name + val js.Ident(name, origName) = paramName + val newOrigName = origName.getOrElse(name) + val newNameIdent = freshLocalIdent(newOrigName)(paramName.pos) + val patchedParam = js.ParamDef(newNameIdent, jstpe.AnyType, + mutable = false)(param.pos) + val paramLocal = js.VarDef(paramName, param.ptpe, mutable = false, + fromAny(patchedParam.ref, paramTpe)) + (patchedParam, paramLocal) + }).unzip + + val patchedBody = js.Block( + paramsLocal :+ ensureBoxed(body, methodType.resultType)) + + (patchedParams, patchedBody) + } + + // Utilities --------------------------------------------------------------- + + /** Generate a literal "zero" for the requested type */ + def genZeroOf(tpe: Type)(implicit pos: Position): js.Tree = toTypeKind(tpe) match { + case VOID => abort("Cannot call genZeroOf(VOID)") + case BOOL => js.BooleanLiteral(false) + case LONG => js.LongLiteral(0L) + case INT(_) => js.IntLiteral(0) + case FloatKind => js.FloatLiteral(0.0f) + case DoubleKind => js.DoubleLiteral(0.0) + case _ => js.Null() + } + + /** Generate loading of a module value + * Can be given either the module symbol, or its module class symbol. + */ + def genLoadModule(sym0: Symbol)(implicit pos: Position): js.Tree = { + require(sym0.isModuleOrModuleClass, + "genLoadModule called with non-module symbol: " + sym0) + val sym1 = if (sym0.isModule) sym0.moduleClass else sym0 + val sym = // redirect all static methods of String to RuntimeString + if (sym1 == StringModule) RuntimeStringModule.moduleClass + else sym1 + + val isGlobalScope = sym.tpe.typeSymbol isSubClass JSGlobalScopeClass + + if (isGlobalScope) genLoadGlobal() + else if (isRawJSType(sym.tpe)) genPrimitiveJSModule(sym) + else { + if (!foreignIsImplClass(sym)) + currentMethodInfoBuilder.accessesModule(sym) + js.LoadModule(jstpe.ClassType(encodeClassFullName(sym))) + } + } + + /** Gen JS code to load the global scope. */ + private def genLoadGlobal()(implicit pos: Position): js.Tree = + js.JSBracketSelect(js.JSEnvInfo(), js.StringLiteral("global")) + + /** Generate access to a static member */ + private def genStaticMember(sym: Symbol)(implicit pos: Position) = { + /* Actually, there is no static member in Scala.js. If we come here, that + * is because we found the symbol in a Java-emitted .class in the + * classpath. But the corresponding implementation in Scala.js will + * actually be a val in the companion module. + * We cannot use the .class files produced by our reimplementations of + * these classes (in which the symbol would be a Scala accessor) because + * that crashes the rest of scalac (at least for some choice symbols). + * Hence we cheat here. + */ + import scalaPrimitives._ + import jsPrimitives._ + if (isPrimitive(sym)) { + getPrimitive(sym) match { + case UNITVAL => js.Undefined() + case UNITTYPE => genClassConstant(UnitTpe) + } + } else { + val instance = genLoadModule(sym.owner) + val method = encodeStaticMemberSym(sym) + currentMethodInfoBuilder.callsMethod(sym.owner, method) + js.Apply(instance, method, Nil)(toIRType(sym.tpe)) + } + } + + /** Generate a Class[_] value (e.g. coming from classOf[T]) */ + private def genClassConstant(tpe: Type)(implicit pos: Position): js.Tree = { + val refType = toReferenceType(tpe) + currentMethodInfoBuilder.accessesClassData(refType) + js.ClassOf(refType) + } + } + + /** Tests whether the given type represents a raw JavaScript type, + * i.e., whether it extends scala.scalajs.js.Any. + */ + def isRawJSType(tpe: Type): Boolean = + tpe.typeSymbol.annotations.find(_.tpe =:= RawJSTypeAnnot.tpe).isDefined + + /** Test whether `sym` is the symbol of a raw JS function definition */ + private def isRawJSFunctionDef(sym: Symbol): Boolean = + sym.isAnonymousClass && AllJSFunctionClasses.exists(sym isSubClass _) + + private def isRawJSCtorDefaultParam(sym: Symbol) = { + sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) && + sym.owner.isModuleClass && + isRawJSType(patchedLinkedClassOfClass(sym.owner).tpe) && + nme.defaultGetterToMethod(sym.name) == nme.CONSTRUCTOR + } + + private def patchedLinkedClassOfClass(sym: Symbol): Symbol = { + /* Work around a bug of scalac with linkedClassOfClass where package + * objects are involved (the companion class would somehow exist twice + * in the scope, making an assertion fail in Symbol.suchThat). + * Basically this inlines linkedClassOfClass up to companionClass, + * then replaces the `suchThat` by a `filter` and `head`. + */ + val flatOwnerInfo = { + // inline Symbol.flatOwnerInfo because it is protected + if (sym.needsFlatClasses) + sym.info + sym.owner.rawInfo + } + val result = flatOwnerInfo.decl(sym.name).filter(_ isCoDefinedWith sym) + if (!result.isOverloaded) result + else result.alternatives.head + } + + private def isStringType(tpe: Type): Boolean = + tpe.typeSymbol == StringClass + + private def isLongType(tpe: Type): Boolean = + tpe.typeSymbol == LongClass + + private lazy val BoxedBooleanClass = boxedClass(BooleanClass) + private lazy val BoxedByteClass = boxedClass(ByteClass) + private lazy val BoxedShortClass = boxedClass(ShortClass) + private lazy val BoxedIntClass = boxedClass(IntClass) + private lazy val BoxedLongClass = boxedClass(LongClass) + private lazy val BoxedFloatClass = boxedClass(FloatClass) + private lazy val BoxedDoubleClass = boxedClass(DoubleClass) + + private lazy val NumberClass = requiredClass[java.lang.Number] + + private lazy val HijackedNumberClasses = + Seq(BoxedByteClass, BoxedShortClass, BoxedIntClass, BoxedLongClass, + BoxedFloatClass, BoxedDoubleClass) + private lazy val HijackedBoxedClasses = + Seq(BoxedUnitClass, BoxedBooleanClass) ++ HijackedNumberClasses + + protected lazy val isHijackedBoxedClass: Set[Symbol] = + HijackedBoxedClasses.toSet + + private lazy val InlineAnnotationClass = requiredClass[scala.inline] + + private def isMaybeJavaScriptException(tpe: Type) = + JavaScriptExceptionClass isSubClass tpe.typeSymbol + + /** Get JS name of Symbol if it was specified with JSName annotation, or + * infers a default from the Scala name. */ + def jsNameOf(sym: Symbol): String = + sym.getAnnotation(JSNameAnnotation).flatMap(_.stringArg(0)).getOrElse( + sym.unexpandedName.decoded) + + def isStaticModule(sym: Symbol): Boolean = + sym.isModuleClass && !sym.isImplClass && !sym.isLifted +} |