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, 0 insertions, 3911 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 deleted file mode 100644 index f9885a0..0000000 --- a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala +++ /dev/null @@ -1,3911 +0,0 @@ -/* 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 -} |