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