From 06a3d47ea9fd1b67b3acba9d115a16d18549e377 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 26 Oct 2016 16:19:35 +0200 Subject: Move sjs, make sure that partest compiles everything in dirs --- sjs/backend/sjs/GenSJSIR.scala | 14 + sjs/backend/sjs/JSCodeGen.scala | 2392 +++++++++++++++++++++++++++++++ sjs/backend/sjs/JSDefinitions.scala | 199 +++ sjs/backend/sjs/JSEncoding.scala | 389 +++++ sjs/backend/sjs/JSInterop.scala | 110 ++ sjs/backend/sjs/JSPositions.scala | 65 + sjs/backend/sjs/JSPrimitives.scala | 118 ++ sjs/backend/sjs/ScopedVar.scala | 38 + sjs/tools/dotc/config/SJSPlatform.scala | 18 + 9 files changed, 3343 insertions(+) create mode 100644 sjs/backend/sjs/GenSJSIR.scala create mode 100644 sjs/backend/sjs/JSCodeGen.scala create mode 100644 sjs/backend/sjs/JSDefinitions.scala create mode 100644 sjs/backend/sjs/JSEncoding.scala create mode 100644 sjs/backend/sjs/JSInterop.scala create mode 100644 sjs/backend/sjs/JSPositions.scala create mode 100644 sjs/backend/sjs/JSPrimitives.scala create mode 100644 sjs/backend/sjs/ScopedVar.scala create mode 100644 sjs/tools/dotc/config/SJSPlatform.scala (limited to 'sjs') diff --git a/sjs/backend/sjs/GenSJSIR.scala b/sjs/backend/sjs/GenSJSIR.scala new file mode 100644 index 000000000..819a8f0e3 --- /dev/null +++ b/sjs/backend/sjs/GenSJSIR.scala @@ -0,0 +1,14 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ +import Contexts._ +import Phases._ + +/** Generates Scala.js IR files for the compilation unit. */ +class GenSJSIR extends Phase { + def phaseName: String = "genSJSIR" + + def run(implicit ctx: Context): Unit = { + new JSCodeGen().run() + } +} diff --git a/sjs/backend/sjs/JSCodeGen.scala b/sjs/backend/sjs/JSCodeGen.scala new file mode 100644 index 000000000..401e01784 --- /dev/null +++ b/sjs/backend/sjs/JSCodeGen.scala @@ -0,0 +1,2392 @@ +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 + } + } + +} diff --git a/sjs/backend/sjs/JSDefinitions.scala b/sjs/backend/sjs/JSDefinitions.scala new file mode 100644 index 000000000..bd0b74031 --- /dev/null +++ b/sjs/backend/sjs/JSDefinitions.scala @@ -0,0 +1,199 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ + +import Types._ +import Contexts._ +import Symbols._ +import Names._ +import StdNames._ +import Decorators._ + +import dotty.tools.dotc.config.SJSPlatform + +object JSDefinitions { + /** The Scala.js-specific definitions for the current context. */ + def jsdefn(implicit ctx: Context): JSDefinitions = + ctx.platform.asInstanceOf[SJSPlatform].jsDefinitions +} + +final class JSDefinitions()(implicit ctx: Context) { + + lazy val InlineAnnotType: TypeRef = ctx.requiredClassRef("scala.inline") + def InlineAnnot(implicit ctx: Context) = InlineAnnotType.symbol.asClass + lazy val NoinlineAnnotType: TypeRef = ctx.requiredClassRef("scala.noinline") + def NoinlineAnnot(implicit ctx: Context) = NoinlineAnnotType.symbol.asClass + + lazy val ScalaJSJSPackageVal = ctx.requiredPackage("scala.scalajs.js") + lazy val ScalaJSJSPackageClass = ScalaJSJSPackageVal.moduleClass.asClass + lazy val JSPackage_typeOfR = ScalaJSJSPackageClass.requiredMethodRef("typeOf") + def JSPackage_typeOf(implicit ctx: Context) = JSPackage_typeOfR.symbol + lazy val JSPackage_constructorOfR = ScalaJSJSPackageClass.requiredMethodRef("constructorOf") + def JSPackage_constructorOf(implicit ctx: Context) = JSPackage_constructorOfR.symbol + lazy val JSPackage_debuggerR = ScalaJSJSPackageClass.requiredMethodRef("debugger") + def JSPackage_debugger(implicit ctx: Context) = JSPackage_debuggerR.symbol + lazy val JSPackage_nativeR = ScalaJSJSPackageClass.requiredMethodRef("native") + def JSPackage_native(implicit ctx: Context) = JSPackage_nativeR.symbol + + lazy val JSNativeAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.native") + def JSNativeAnnot(implicit ctx: Context) = JSNativeAnnotType.symbol.asClass + + lazy val JSAnyType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Any") + def JSAnyClass(implicit ctx: Context) = JSAnyType.symbol.asClass + lazy val JSObjectType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Object") + def JSObjectClass(implicit ctx: Context) = JSObjectType.symbol.asClass + lazy val JSBaseThisFunctionType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.ThisFunction") + def JSBaseThisFunctionClass(implicit ctx: Context) = JSBaseThisFunctionType.symbol.asClass + + lazy val JSDictionaryType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Dictionary") + def JSDictionaryClass(implicit ctx: Context) = JSDictionaryType.symbol.asClass + lazy val JSDictionary_deleteR = JSDictionaryClass.requiredMethodRef("delete") + def JSDictionary_delete(implicit ctx: Context) = JSDictionary_deleteR.symbol + + lazy val JSGlobalScopeType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.GlobalScope") + def JSGlobalScopeClass(implicit ctx: Context) = JSGlobalScopeType.symbol.asClass + + lazy val JSArrayType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Array") + def JSArrayClass(implicit ctx: Context) = JSArrayType.symbol.asClass + + lazy val JSFunctionType = (0 to 22).map(n => ctx.requiredClassRef("scala.scalajs.js.Function" + n)).toArray + def JSFunctionClass(n: Int)(implicit ctx: Context) = JSFunctionType(n).symbol.asClass + lazy val JSThisFunctionType = (0 to 21).map(n => ctx.requiredClassRef("scala.scalajs.js.ThisFunction" + n)).toArray + def JSThisFunctionClass(n: Int)(implicit ctx: Context) = JSThisFunctionType(n).symbol.asClass + + lazy val RuntimeExceptionType: TypeRef = ctx.requiredClassRef("java.lang.RuntimeException") + def RuntimeExceptionClass(implicit ctx: Context) = RuntimeExceptionType.symbol.asClass + lazy val JavaScriptExceptionType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.JavaScriptException") + def JavaScriptExceptionClass(implicit ctx: Context) = JavaScriptExceptionType.symbol.asClass + + lazy val JSNameAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSName") + def JSNameAnnot(implicit ctx: Context) = JSNameAnnotType.symbol.asClass + lazy val JSFullNameAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSFullName") + def JSFullNameAnnot(implicit ctx: Context) = JSFullNameAnnotType.symbol.asClass + lazy val JSBracketAccessAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSBracketAccess") + def JSBracketAccessAnnot(implicit ctx: Context) = JSBracketAccessAnnotType.symbol.asClass + lazy val JSBracketCallAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSBracketCall") + def JSBracketCallAnnot(implicit ctx: Context) = JSBracketCallAnnotType.symbol.asClass + lazy val JSExportAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExport") + def JSExportAnnot(implicit ctx: Context) = JSExportAnnotType.symbol.asClass + lazy val JSExportDescendentObjectsAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportDescendentObjects") + def JSExportDescendentObjectsAnnot(implicit ctx: Context) = JSExportDescendentObjectsAnnotType.symbol.asClass + lazy val JSExportDescendentClassesAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportDescendentClasses") + def JSExportDescendentClassesAnnot(implicit ctx: Context) = JSExportDescendentClassesAnnotType.symbol.asClass + lazy val JSExportAllAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportAll") + def JSExportAllAnnot(implicit ctx: Context) = JSExportAllAnnotType.symbol.asClass + lazy val JSExportNamedAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportNamed") + def JSExportNamedAnnot(implicit ctx: Context) = JSExportNamedAnnotType.symbol.asClass + lazy val RawJSTypeAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.RawJSType") + def RawJSTypeAnnot(implicit ctx: Context) = RawJSTypeAnnotType.symbol.asClass + lazy val ExposedJSMemberAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.ExposedJSMember") + def ExposedJSMemberAnnot(implicit ctx: Context) = ExposedJSMemberAnnotType.symbol.asClass + + lazy val JSAnyModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Any") + def JSAnyModule(implicit ctx: Context) = JSAnyModuleRef.symbol + lazy val JSAny_fromFunctionR = (0 to 22).map(n => JSAnyModule.requiredMethodRef("fromFunction" + n)).toArray + def JSAny_fromFunction(n: Int)(implicit ctx: Context) = JSAny_fromFunctionR(n).symbol + + lazy val JSDynamicModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Dynamic") + def JSDynamicModule(implicit ctx: Context) = JSDynamicModuleRef.symbol + lazy val JSDynamic_newInstanceR = JSDynamicModule.requiredMethodRef("newInstance") + def JSDynamic_newInstance(implicit ctx: Context) = JSDynamic_newInstanceR.symbol + + lazy val JSDynamicLiteralModuleRef = JSDynamicModule.moduleClass.requiredValueRef("literal") + def JSDynamicLiteralModule(implicit ctx: Context) = JSDynamicLiteralModuleRef.symbol + lazy val JSDynamicLiteral_applyDynamicNamedR = JSDynamicLiteralModule.requiredMethodRef("applyDynamicNamed") + def JSDynamicLiteral_applyDynamicNamed(implicit ctx: Context) = JSDynamicLiteral_applyDynamicNamedR.symbol + lazy val JSDynamicLiteral_applyDynamicR = JSDynamicLiteralModule.requiredMethodRef("applyDynamic") + def JSDynamicLiteral_applyDynamic(implicit ctx: Context) = JSDynamicLiteral_applyDynamicR.symbol + + lazy val JSObjectModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Object") + def JSObjectModule(implicit ctx: Context) = JSObjectModuleRef.symbol + lazy val JSObject_hasPropertyR = JSObjectModule.requiredMethodRef("hasProperty") + def JSObject_hasProperty(implicit ctx: Context) = JSObject_hasPropertyR.symbol + lazy val JSObject_propertiesR = JSObjectModule.requiredMethodRef("properties") + def JSObject_properties(implicit ctx: Context) = JSObject_propertiesR.symbol + + lazy val JSArrayModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Array") + def JSArrayModule(implicit ctx: Context) = JSArrayModuleRef.symbol + lazy val JSArray_applyR = JSArrayModule.requiredMethodRef(nme.apply) + def JSArray_apply(implicit ctx: Context) = JSArray_applyR.symbol + + lazy val JSThisFunctionModuleRef = ctx.requiredModuleRef("scala.scalajs.js.ThisFunction") + def JSThisFunctionModule(implicit ctx: Context) = JSThisFunctionModuleRef.symbol + lazy val JSThisFunction_fromFunctionR = (1 to 22).map(n => JSThisFunctionModule.requiredMethodRef("fromFunction" + n)).toArray + def JSThisFunction_fromFunction(n: Int)(implicit ctx: Context) = JSThisFunction_fromFunctionR(n - 1).symbol + + lazy val JSConstructorTagModuleRef = ctx.requiredModuleRef("scala.scalajs.js.ConstructorTag") + def JSConstructorTagModule(implicit ctx: Context) = JSConstructorTagModuleRef.symbol + lazy val JSConstructorTag_materializeR = JSConstructorTagModule.requiredMethodRef("materialize") + def JSConstructorTag_materialize(implicit ctx: Context) = JSConstructorTag_materializeR.symbol + + lazy val RuntimeStringModuleRef = ctx.requiredModuleRef("scala.scalajs.runtime.RuntimeString") + def RuntimeStringModule(implicit ctx: Context) = RuntimeStringModuleRef.symbol + def RuntimeStringModuleClass(implicit ctx: Context) = RuntimeStringModule.moduleClass.asClass + + lazy val BooleanReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.BooleanReflectiveCall") + def BooleanReflectiveCallClass(implicit ctx: Context) = BooleanReflectiveCallType.symbol.asClass + lazy val NumberReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.NumberReflectiveCall") + def NumberReflectiveCallClass(implicit ctx: Context) = NumberReflectiveCallType.symbol.asClass + lazy val IntegerReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.IntegerReflectiveCall") + def IntegerReflectiveCallClass(implicit ctx: Context) = IntegerReflectiveCallType.symbol.asClass + lazy val LongReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.LongReflectiveCall") + def LongReflectiveCallClass(implicit ctx: Context) = LongReflectiveCallType.symbol.asClass + + lazy val RuntimePackageVal = ctx.requiredPackage("scala.scalajs.runtime") + lazy val RuntimePackageClass = RuntimePackageVal.moduleClass.asClass + lazy val RuntimePackage_wrapJavaScriptExceptionR = RuntimePackageClass.requiredMethodRef("wrapJavaScriptException") + def RuntimePackage_typeOf(implicit ctx: Context) = RuntimePackage_wrapJavaScriptExceptionR.symbol + lazy val RuntimePackage_unwrapJavaScriptExceptionR = RuntimePackageClass.requiredMethodRef("unwrapJavaScriptException") + def RuntimePackage_unwrapJavaScriptException(implicit ctx: Context) = RuntimePackage_unwrapJavaScriptExceptionR.symbol + lazy val RuntimePackage_genTraversableOnce2jsArrayR = RuntimePackageClass.requiredMethodRef("genTraversableOnce2jsArray") + def RuntimePackage_genTraversableOnce2jsArray(implicit ctx: Context) = RuntimePackage_genTraversableOnce2jsArrayR.symbol + lazy val RuntimePackage_jsTupleArray2jsObjectR = RuntimePackageClass.requiredMethodRef("jsTupleArray2jsObject") + def RuntimePackage_jsTupleArray2jsObject(implicit ctx: Context) = RuntimePackage_jsTupleArray2jsObjectR.symbol + lazy val RuntimePackage_constructorOfR = RuntimePackageClass.requiredMethodRef("constructorOf") + def RuntimePackage_constructorOf(implicit ctx: Context) = RuntimePackage_constructorOfR.symbol + lazy val RuntimePackage_newConstructorTagR = RuntimePackageClass.requiredMethodRef("newConstructorTag") + def RuntimePackage_newConstructorTag(implicit ctx: Context) = RuntimePackage_newConstructorTagR.symbol + lazy val RuntimePackage_propertiesOfR = RuntimePackageClass.requiredMethodRef("propertiesOf") + def RuntimePackage_propertiesOf(implicit ctx: Context) = RuntimePackage_propertiesOfR.symbol + lazy val RuntimePackage_environmentInfoR = RuntimePackageClass.requiredMethodRef("environmentInfo") + def RuntimePackage_environmentInfo(implicit ctx: Context) = RuntimePackage_environmentInfoR.symbol + lazy val RuntimePackage_linkingInfoR = RuntimePackageClass.requiredMethodRef("linkingInfo") + def RuntimePackage_linkingInfo(implicit ctx: Context) = RuntimePackage_linkingInfoR.symbol + + lazy val WrappedArrayType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.WrappedArray") + def WrappedArrayClass(implicit ctx: Context) = WrappedArrayType.symbol.asClass + + lazy val ScalaRunTime_isArrayR = defn.ScalaRuntimeModule.requiredMethodRef("isArray", List(???, ???)) + def ScalaRunTime_isArray(implicit ctx: Context): Symbol = ScalaRunTime_isArrayR.symbol + + lazy val BoxesRunTime_boxToCharacterR = defn.BoxesRunTimeModule.requiredMethodRef("boxToCharacter") + def BoxesRunTime_boxToCharacter(implicit ctx: Context): Symbol = BoxesRunTime_boxToCharacterR.symbol + lazy val BoxesRunTime_unboxToCharR = defn.BoxesRunTimeModule.requiredMethodRef("unboxToChar") + def BoxesRunTime_unboxToChar(implicit ctx: Context): Symbol = BoxesRunTime_unboxToCharR.symbol + + /** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */ + private def scalajsClassName(cls: Symbol)(implicit ctx: Context): TypeName = + if (cls.isClass && cls.owner == ScalaJSJSPackageClass) cls.asClass.name + else EmptyTypeName + + /** Is the given `cls` a class of the form `scala.scalajs.js.prefixN` where + * `N` is a number. + * + * This is similar to `isVarArityClass` in `Definitions.scala`. + */ + private def isScalaJSVarArityClass(cls: Symbol, prefix: Name): Boolean = { + val name = scalajsClassName(cls) + name.startsWith(prefix) && name.drop(prefix.length).forall(_.isDigit) + } + + def isJSFunctionClass(cls: Symbol): Boolean = + isScalaJSVarArityClass(cls, nme.Function) + + private val ThisFunctionName = termName("ThisFunction") + + def isJSThisFunctionClass(cls: Symbol): Boolean = + isScalaJSVarArityClass(cls, ThisFunctionName) + +} diff --git a/sjs/backend/sjs/JSEncoding.scala b/sjs/backend/sjs/JSEncoding.scala new file mode 100644 index 000000000..e8ea3258b --- /dev/null +++ b/sjs/backend/sjs/JSEncoding.scala @@ -0,0 +1,389 @@ +package dotty.tools.backend.sjs + +import scala.collection.mutable + +import dotty.tools.FatalError + +import dotty.tools.dotc.core._ +import Periods._ +import SymDenotations._ +import Contexts._ +import Types._ +import Symbols._ +import Denotations._ +import NameOps._ +import StdNames._ + +import org.scalajs.core.ir +import ir.{Trees => js, Types => jstpe} + +import ScopedVar.withScopedVars +import JSDefinitions._ +import JSInterop._ + +/** Encoding of symbol names for JavaScript + * + * Some issues that this encoding solves: + * * Overloading: encode the full signature in the JS name + * * Same scope for fields and methods of a class + * * Global access to classes and modules (by their full name) + * + * @author Sébastien Doeraene + */ +object JSEncoding { + + /** Signature separator string (between parameter types) */ + private final val SignatureSep = "__" + + /** Name given to the local Scala.js environment variable */ + private final val ScalaJSEnvironmentName = "ScalaJS" + + implicit class SymOps(val self: Symbol) extends AnyVal { + def unexpandedName(implicit ctx: Context): Names.Name = + self.name.unexpandedName + } + + implicit class MyNameOps(val self: Names.Name) extends AnyVal { + def decoded: String = self.decode.toString + } + + // Fresh local name generator ---------------------------------------------- + + class LocalNameGenerator { + import LocalNameGenerator._ + + private val usedLocalNames = mutable.Set.empty[String] + private val localSymbolNames = mutable.Map.empty[Symbol, String] + + def localSymbolName(sym: Symbol)(implicit ctx: Context): String = + localSymbolNames.getOrElseUpdate(sym, freshName(sym.name.toString)) + + def freshLocalIdent()(implicit pos: ir.Position): js.Ident = + js.Ident(freshName(), None) + + def freshLocalIdent(base: String)(implicit pos: ir.Position): js.Ident = + js.Ident(freshName(base), Some(base)) + + private def freshName(base: String = "x"): String = { + var suffix = 1 + var longName = base + while (usedLocalNames(longName) || isReserved(longName)) { + suffix += 1 + longName = base+"$"+suffix + } + usedLocalNames += longName + mangleJSName(longName) + } + } + + private object LocalNameGenerator { + private val isReserved = + Set("arguments", "eval", ScalaJSEnvironmentName) + } + + // Encoding methods ---------------------------------------------------------- + + def encodeLabelSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position, localNames: LocalNameGenerator): js.Ident = { + require(sym.is(Flags.Label), "encodeLabelSym called with non-label symbol: " + sym) + js.Ident(localNames.localSymbolName(sym), Some(sym.unexpandedName.decoded)) + } + + private def allRefClasses(implicit ctx: Context): Set[Symbol] = { + //TODO + /*(Set(ObjectRefClass, VolatileObjectRefClass) ++ + refClass.values ++ volatileRefClass.values)*/ + Set() + } + + def encodeFieldSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + require(sym.owner.isClass && sym.isTerm && !sym.is(Flags.Method) && !sym.is(Flags.Module), + "encodeFieldSym called with non-field symbol: " + sym) + + val name0 = encodeMemberNameInternal(sym) + val name = + if (name0.charAt(name0.length()-1) != ' ') name0 + else name0.substring(0, name0.length()-1) + + /* We have to special-case fields of Ref types (IntRef, ObjectRef, etc.) + * because they are emitted as private by our .scala source files, but + * they are considered public at use site since their symbols come from + * Java-emitted .class files. + */ + val idSuffix = + if (sym.is(Flags.Private) || allRefClasses.contains(sym.owner)) + sym.owner.asClass.baseClasses.size.toString + else + "f" + + val encodedName = name + "$" + idSuffix + js.Ident(mangleJSName(encodedName), Some(sym.unexpandedName.decoded)) + } + + def encodeMethodSym(sym: Symbol, reflProxy: Boolean = false)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy) + js.Ident(encodedName + paramsString, + Some(sym.unexpandedName.decoded + paramsString)) + } + + def encodeMethodName(sym: Symbol, reflProxy: Boolean = false)( + implicit ctx: Context): String = { + val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy) + encodedName + paramsString + } + + /** Encodes a method symbol of java.lang.String for use in RuntimeString. + * + * This basically means adding an initial parameter of type + * java.lang.String, which is the `this` parameter. + */ + def encodeRTStringMethodSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + require(sym.owner == defn.StringClass) + require(!sym.isClassConstructor && !sym.is(Flags.Private)) + + val (encodedName, paramsString) = + encodeMethodNameInternal(sym, inRTClass = true) + js.Ident(encodedName + paramsString, + Some(sym.unexpandedName.decoded + paramsString)) + } + + /** Encodes a constructor symbol of java.lang.String for use in RuntimeString. + * + * - The name is rerouted to `newString` + * - The result type is set to `java.lang.String` + */ + def encodeRTStringCtorSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + require(sym.owner == defn.StringClass) + require(sym.isClassConstructor && !sym.is(Flags.Private)) + + val paramTypeNames = sym.info.firstParamTypes.map(internalName(_)) + val paramAndResultTypeNames = paramTypeNames :+ ir.Definitions.StringClass + val paramsString = makeParamsString(paramAndResultTypeNames) + + js.Ident("newString" + paramsString, + Some(sym.unexpandedName.decoded + paramsString)) + } + + private def encodeMethodNameInternal(sym: Symbol, + reflProxy: Boolean = false, inRTClass: Boolean = false)( + implicit ctx: Context): (String, String) = { + require(sym.is(Flags.Method), "encodeMethodSym called with non-method symbol: " + sym) + + def name = encodeMemberNameInternal(sym) + + val encodedName = { + if (sym.isClassConstructor) { + "init_" + } else if (sym.is(Flags.Private)) { + (mangleJSName(name) + SignatureSep + "p" + + sym.owner.asClass.baseClasses.size.toString) + } else { + mangleJSName(name) + } + } + + val paramsString = makeParamsString(sym, reflProxy, inRTClass) + + (encodedName, paramsString) + } + + def encodeStaticMemberSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + require(sym.is(Flags.JavaStaticTerm), + "encodeStaticMemberSym called with non-static symbol: " + sym) + js.Ident( + mangleJSName(encodeMemberNameInternal(sym)) + + makeParamsString(List(internalName(sym.info))), + Some(sym.unexpandedName.decoded)) + } + + def encodeLocalSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position, localNames: LocalNameGenerator): js.Ident = { + require(!sym.owner.isClass && sym.isTerm && !sym.is(Flags.Method) && !sym.is(Flags.Module), + "encodeLocalSym called with non-local symbol: " + sym) + js.Ident(localNames.localSymbolName(sym), Some(sym.unexpandedName.decoded)) + } + + def foreignIsImplClass(sym: Symbol)(implicit ctx: Context): Boolean = + sym.name.isImplClassName + + def encodeClassType(sym: Symbol)(implicit ctx: Context): jstpe.Type = { + if (sym == defn.ObjectClass) jstpe.AnyType + else if (isJSType(sym)) jstpe.AnyType + else { + assert(sym != defn.ArrayClass, + "encodeClassType() cannot be called with ArrayClass") + jstpe.ClassType(encodeClassFullName(sym)) + } + } + + def encodeClassFullNameIdent(sym: Symbol)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + js.Ident(encodeClassFullName(sym), Some(sym.fullName.toString)) + } + + def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String = { + if (sym == defn.NothingClass) ir.Definitions.RuntimeNothingClass + else if (sym == defn.NullClass) ir.Definitions.RuntimeNullClass + else ir.Definitions.encodeClassName(sym.fullName.toString) + } + + private def encodeMemberNameInternal(sym: Symbol)( + implicit ctx: Context): String = { + sym.name.toString.replace("_", "$und").replace("~", "$tilde") + } + + def toIRType(tp: Type)(implicit ctx: Context): jstpe.Type = { + val refType = toReferenceTypeInternal(tp) + refType._1 match { + case tpe: jstpe.ClassType => + val sym = refType._2 + if (sym.asClass.isPrimitiveValueClass) { + if (sym == defn.BooleanClass) + jstpe.BooleanType + else if (sym == defn.FloatClass) + jstpe.FloatType + else if (sym == defn.DoubleClass) + jstpe.DoubleType + else if (sym == defn.LongClass) + jstpe.LongType + else if (sym == defn.UnitClass) + jstpe.NoType + else + jstpe.IntType + } else { + if (sym == defn.ObjectClass || isJSType(sym)) + jstpe.AnyType + else if (sym == defn.NothingClass) + jstpe.NothingType + else if (sym == defn.NullClass) + jstpe.NullType + else + tpe + } + + case tpe: jstpe.ArrayType => + tpe + } + } + + def toReferenceType(tp: Type)(implicit ctx: Context): jstpe.ReferenceType = + toReferenceTypeInternal(tp)._1 + + private def toReferenceTypeInternal(tp: Type)( + implicit ctx: Context): (jstpe.ReferenceType, Symbol) = { + + /** + * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int. + * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType. + */ + def primitiveOrClassToRefType(sym: Symbol): (jstpe.ReferenceType, Symbol) = { + assert(sym.isClass, sym) + //assert(sym != defn.ArrayClass || isCompilingArray, sym) + (jstpe.ClassType(encodeClassFullName(sym)), sym) + } + + /** + * When compiling Array.scala, the type parameter T is not erased and shows up in method + * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. + */ + def nonClassTypeRefToRefType(sym: Symbol): (jstpe.ReferenceType, Symbol) = { + //assert(sym.isType && isCompilingArray, sym) + (jstpe.ClassType(ir.Definitions.ObjectClass), defn.ObjectClass) + } + + tp.widenDealias match { + // Array type such as Array[Int] (kept by erasure) + case JavaArrayType(el) => + val elRefType = toReferenceTypeInternal(el) + (jstpe.ArrayType(elRefType._1), elRefType._2) + + case t: TypeRef => + if (!t.symbol.isClass) nonClassTypeRefToRefType(t.symbol) // See comment on nonClassTypeRefToBType + else primitiveOrClassToRefType(t.symbol) // Common reference to a type such as scala.Int or java.lang.String + + case Types.ClassInfo(_, sym, _, _, _) => + /* We get here, for example, for genLoadModule, which invokes + * toTypeKind(moduleClassSymbol.info) + */ + primitiveOrClassToRefType(sym) + + case t: MethodType => // triggers for LabelDefs + toReferenceTypeInternal(t.resultType) + + /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for + * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. + * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. + */ + case a @ AnnotatedType(t, _) => + //debuglog(s"typeKind of annotated type $a") + toReferenceTypeInternal(t) + } + } + + /** Patches the result type of a method symbol to sanitize it. + * + * For some reason, dotc thinks that the `info.resultType`of an + * `isConstructor` method (for classes or traits) is the enclosing class + * or trait, but the bodies and usages act as if the result type was `Unit`. + * + * This method returns `UnitType` for constructor methods, and otherwise + * `sym.info.resultType`. + */ + def patchedResultType(sym: Symbol)(implicit ctx: Context): Type = + if (sym.isConstructor) defn.UnitType + else sym.info.resultType + + // Encoding of method signatures + + private def makeParamsString(sym: Symbol, reflProxy: Boolean, + inRTClass: Boolean)( + implicit ctx: Context): String = { + val tpe = sym.info + + val paramTypeNames0 = tpe.firstParamTypes.map(internalName(_)) + + val hasExplicitThisParameter = + inRTClass || isScalaJSDefinedJSClass(sym.owner) + val paramTypeNames = + if (!hasExplicitThisParameter) paramTypeNames0 + else encodeClassFullName(sym.owner) :: paramTypeNames0 + + val paramAndResultTypeNames = { + if (sym.isClassConstructor) + paramTypeNames + else if (reflProxy) + paramTypeNames :+ "" + else + paramTypeNames :+ internalName(patchedResultType(sym)) + } + makeParamsString(paramAndResultTypeNames) + } + + private def makeParamsString(paramAndResultTypeNames: List[String]) = + paramAndResultTypeNames.mkString(SignatureSep, SignatureSep, "") + + /** Computes the internal name for a type. */ + private def internalName(tpe: Type)(implicit ctx: Context): String = + encodeReferenceType(toReferenceType(tpe)) + + /** Encodes a [[Types.ReferenceType]], such as in an encoded method signature. + */ + private def encodeReferenceType(refType: jstpe.ReferenceType): String = { + refType match { + case jstpe.ClassType(encodedName) => encodedName + case jstpe.ArrayType(base, depth) => "A" * depth + base + } + } + + /** Mangles names that are illegal in JavaScript by prepending a `$`. + * Also mangles names that would collide with these mangled names. + */ + private def mangleJSName(name: String) = + if (js.isKeyword(name) || name(0).isDigit || name(0) == '$') "$" + name + else name +} diff --git a/sjs/backend/sjs/JSInterop.scala b/sjs/backend/sjs/JSInterop.scala new file mode 100644 index 000000000..6d66c3206 --- /dev/null +++ b/sjs/backend/sjs/JSInterop.scala @@ -0,0 +1,110 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ +import Contexts._ +import Flags._ +import Symbols._ +import NameOps._ +import StdNames._ + +import JSDefinitions._ + +/** Management of the interoperability with JavaScript. */ +object JSInterop { + + /** Is this symbol a JavaScript type? */ + def isJSType(sym: Symbol)(implicit ctx: Context): Boolean = { + //sym.hasAnnotation(jsdefn.RawJSTypeAnnot) + ctx.atPhase(ctx.erasurePhase) { implicit ctx => + sym.derivesFrom(jsdefn.JSAnyClass) + } + } + + /** Is this symbol a Scala.js-defined JS class, i.e., a non-native JS class? */ + def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean = + isJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot) + + /** Should this symbol be translated into a JS getter? + * + * This is true for any parameterless method, i.e., defined without `()`. + * Unlike `SymDenotations.isGetter`, it applies to user-defined methods as + * much as *accessor* methods created for `val`s and `var`s. + */ + def isJSGetter(sym: Symbol)(implicit ctx: Context): Boolean = { + sym.info.firstParamTypes.isEmpty && ctx.atPhase(ctx.erasurePhase) { implicit ctx => + sym.info.isParameterless + } + } + + /** Should this symbol be translated into a JS setter? + * + * This is true for any method whose name ends in `_=`. + * Unlike `SymDenotations.isGetter`, it applies to user-defined methods as + * much as *accessor* methods created for `var`s. + */ + def isJSSetter(sym: Symbol)(implicit ctx: Context): Boolean = + sym.name.isSetterName && sym.is(Method) + + /** Should this symbol be translated into a JS bracket access? + * + * This is true for methods annotated with `@JSBracketAccess`. + */ + def isJSBracketAccess(sym: Symbol)(implicit ctx: Context): Boolean = + sym.hasAnnotation(jsdefn.JSBracketAccessAnnot) + + /** Should this symbol be translated into a JS bracket call? + * + * This is true for methods annotated with `@JSBracketCall`. + */ + def isJSBracketCall(sym: Symbol)(implicit ctx: Context): Boolean = + sym.hasAnnotation(jsdefn.JSBracketCallAnnot) + + /** Is this symbol a default param accessor for a JS method? + * + * For default param accessors of *constructors*, we need to test whether + * the companion *class* of the owner is a JS type; not whether the owner + * is a JS type. + */ + def isJSDefaultParam(sym: Symbol)(implicit ctx: Context): Boolean = { + sym.name.isDefaultGetterName && { + val owner = sym.owner + if (owner.is(ModuleClass) && + sym.name.asTermName.defaultGetterToMethod == nme.CONSTRUCTOR) { + isJSType(owner.linkedClass) + } else { + isJSType(owner) + } + } + } + + /** Gets the unqualified JS name of a symbol. + * + * If it is not explicitly specified with an `@JSName` annotation, the + * JS name is inferred from the Scala name. + */ + def jsNameOf(sym: Symbol)(implicit ctx: Context): String = { + sym.getAnnotation(jsdefn.JSNameAnnot).flatMap(_.argumentConstant(0)).fold { + val base = sym.name.unexpandedName.decode.toString.stripSuffix("_=") + if (sym.is(ModuleClass)) base.stripSuffix("$") + else if (!sym.is(Method)) base.stripSuffix(" ") + else base + } { constant => + constant.stringValue + } + } + + /** Gets the fully qualified JS name of a static class of module Symbol. + * + * This is the JS name of the symbol qualified by the fully qualified JS + * name of its original owner if the latter is a native JS object. + */ + def fullJSNameOf(sym: Symbol)(implicit ctx: Context): String = { + assert(sym.isClass, s"fullJSNameOf called for non-class symbol $sym") + sym.getAnnotation(jsdefn.JSFullNameAnnot).flatMap(_.argumentConstant(0)).fold { + jsNameOf(sym) + } { constant => + constant.stringValue + } + } + +} diff --git a/sjs/backend/sjs/JSPositions.scala b/sjs/backend/sjs/JSPositions.scala new file mode 100644 index 000000000..10570da00 --- /dev/null +++ b/sjs/backend/sjs/JSPositions.scala @@ -0,0 +1,65 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ +import Contexts._ +import dotty.tools.dotc.util.Positions +import Positions.Position + +import org.scalajs.core.ir + +/** Conversion utilities from dotty Positions to IR Positions. */ +class JSPositions()(implicit ctx: Context) { + + /** Implicit conversion from dotty Position to ir.Position. */ + implicit def pos2irPos(pos: Positions.Position): ir.Position = { + if (!pos.exists) ir.Position.NoPosition + else { + val source = pos2irPosCache.toIRSource(ctx.compilationUnit.source) + val sourcePos = ctx.compilationUnit.source.atPos(pos) + // dotty positions are 1-based but IR positions are 0-based + ir.Position(source, sourcePos.line-1, sourcePos.column-1) + } + } + + /** Implicitly materializes an ir.Position from an implicit dotty Position. */ + implicit def implicitPos2irPos( + implicit pos: Positions.Position): ir.Position = { + pos2irPos(pos) + } + + private[this] object pos2irPosCache { // scalastyle:ignore + import dotty.tools.dotc.util._ + + private[this] var lastDotcSource: SourceFile = null + private[this] var lastIRSource: ir.Position.SourceFile = null + + def toIRSource(dotcSource: SourceFile): ir.Position.SourceFile = { + if (dotcSource != lastDotcSource) { + lastIRSource = convert(dotcSource) + lastDotcSource = dotcSource + } + lastIRSource + } + + private[this] def convert(dotcSource: SourceFile): ir.Position.SourceFile = { + dotcSource.file.file match { + case null => + new java.net.URI( + "virtualfile", // Pseudo-Scheme + dotcSource.file.path, // Scheme specific part + null // Fragment + ) + case file => + val srcURI = file.toURI + def matches(pat: java.net.URI) = pat.relativize(srcURI) != srcURI + + // TODO + /*scalaJSOpts.sourceURIMaps.collectFirst { + case ScalaJSOptions.URIMap(from, to) if matches(from) => + val relURI = from.relativize(srcURI) + to.fold(relURI)(_.resolve(relURI)) + } getOrElse*/ srcURI + } + } + } +} diff --git a/sjs/backend/sjs/JSPrimitives.scala b/sjs/backend/sjs/JSPrimitives.scala new file mode 100644 index 000000000..6c3c5715c --- /dev/null +++ b/sjs/backend/sjs/JSPrimitives.scala @@ -0,0 +1,118 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ +import Names.TermName +import StdNames._ +import Types._ +import Contexts._ +import Symbols._ + +import dotty.tools.dotc.ast.tpd._ +import dotty.tools.dotc.backend.jvm.DottyPrimitives + +import scala.collection.mutable + +object JSPrimitives { + + final val GETCLASS = 301 // jl.Object.getClass() + + final val F2JS = 302 // js.Any.fromFunctionN + final val F2JSTHIS = 303 // js.ThisFunction.fromFunctionN + + final val DYNNEW = 304 // js.Dynamic.newInstance + final val DYNLIT = 305 // js.Dynamic.literal.applyDynamic{,Named} + final val DICT_DEL = 306 // js.Dictionary.delete + final val ARR_CREATE = 307 // js.Array.apply (array literal syntax) + + final val TYPEOF = 308 // js.typeOf(x) + final val DEBUGGER = 309 // js.debugger() + final val HASPROP = 310 // js.Object.hasProperty(o, p), equiv to `p in o` in JS + final val OBJPROPS = 311 // js.Object.properties(o), equiv to `for (p in o)` in JS + final val JS_NATIVE = 312 // js.native. Marker method. Fails if tried to be emitted. + + final val UNITVAL = 313 // () value, which is undefined + final val UNITTYPE = 314 // BoxedUnit.TYPE (== classOf[Unit]) + + final val CONSTRUCTOROF = 315 // runtime.constructorOf(clazz) + final val ENV_INFO = 316 // runtime.environmentInfo + final val LINKING_INFO = 317 // runtime.linkingInfo + + final val THROW = 318 // .throw + +} + +class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) { + import JSPrimitives._ + import scala.tools.nsc.backend.ScalaPrimitives._ + + private lazy val jsPrimitives: Map[Symbol, Int] = initJSPrimitives(ctx) + + override def getPrimitive(sym: Symbol): Int = + jsPrimitives.getOrElse(sym, super.getPrimitive(sym)) + + override def getPrimitive(app: Apply, tpe: Type)(implicit ctx: Context): Int = + jsPrimitives.getOrElse(app.fun.symbol, super.getPrimitive(app, tpe)) + + override def isPrimitive(fun: Tree): Boolean = + jsPrimitives.contains(fun.symbol(ctx)) || super.isPrimitive(fun) + + /** Initialize the primitive map */ + private def initJSPrimitives(implicit ctx: Context): Map[Symbol, Int] = { + + val primitives = new mutable.HashMap[Symbol, Int]() + + // !!! Code duplicate with DottyPrimitives + /** Add a primitive operation to the map */ + def addPrimitive(s: Symbol, code: Int): Unit = { + assert(!(primitives contains s), "Duplicate primitive " + s) + primitives(s) = code + } + + def addPrimitives(cls: Symbol, method: TermName, code: Int)(implicit ctx: Context): Unit = { + val alts = cls.info.member(method).alternatives.map(_.symbol) + if (alts.isEmpty) { + ctx.error(s"Unknown primitive method $cls.$method") + } else { + for (s <- alts) + addPrimitive(s, code) + } + } + + val jsdefn = JSDefinitions.jsdefn + + addPrimitive(defn.Any_getClass, GETCLASS) + + for (i <- 0 to 22) + addPrimitive(jsdefn.JSAny_fromFunction(i), F2JS) + for (i <- 1 to 22) + addPrimitive(jsdefn.JSThisFunction_fromFunction(i), F2JSTHIS) + + addPrimitive(jsdefn.JSDynamic_newInstance, DYNNEW) + + addPrimitive(jsdefn.JSDynamicLiteral_applyDynamicNamed, DYNLIT) + addPrimitive(jsdefn.JSDynamicLiteral_applyDynamic, DYNLIT) + + addPrimitive(jsdefn.JSDictionary_delete, DICT_DEL) + + //addPrimitive(jsdefn.JSArray_create, ARR_CREATE) + + addPrimitive(jsdefn.JSPackage_typeOf, TYPEOF) + addPrimitive(jsdefn.JSPackage_debugger, DEBUGGER) + addPrimitive(jsdefn.JSPackage_native, JS_NATIVE) + + addPrimitive(jsdefn.JSObject_hasProperty, HASPROP) + addPrimitive(jsdefn.JSObject_properties, OBJPROPS) + + addPrimitive(defn.BoxedUnit_UNIT, UNITVAL) + //addPrimitive(defn.BoxedUnit_TYPE, UNITTYPE) + + //addPrimitive(jsdefn.Runtime_constructorOf, CONSTRUCTOROF) + //addPrimitive(jsdefn.Runtime_environmentInfo, ENV_INFO) + //addPrimitive(jsdefn.Runtime_linkingInfo, LINKING_INFO) + + addPrimitive(defn.throwMethod, THROW) + + primitives.toMap + } + +} diff --git a/sjs/backend/sjs/ScopedVar.scala b/sjs/backend/sjs/ScopedVar.scala new file mode 100644 index 000000000..0e47f7b79 --- /dev/null +++ b/sjs/backend/sjs/ScopedVar.scala @@ -0,0 +1,38 @@ +package dotty.tools.backend.sjs + +import language.implicitConversions + +class ScopedVar[A](init: A) { + import ScopedVar.Assignment + + private var value = init + + def this()(implicit ev: Null <:< A) = this(ev(null)) + + def get: A = value + def :=(newValue: A): Assignment[A] = new Assignment(this, newValue) +} + +object ScopedVar { + class Assignment[T](scVar: ScopedVar[T], value: T) { + private[ScopedVar] def push(): AssignmentStackElement[T] = { + val stack = new AssignmentStackElement(scVar, scVar.value) + scVar.value = value + stack + } + } + + private class AssignmentStackElement[T](scVar: ScopedVar[T], oldValue: T) { + private[ScopedVar] def pop(): Unit = { + scVar.value = oldValue + } + } + + implicit def toValue[T](scVar: ScopedVar[T]): T = scVar.get + + def withScopedVars[T](ass: Assignment[_]*)(body: => T): T = { + val stack = ass.map(_.push()) + try body + finally stack.reverse.foreach(_.pop()) + } +} diff --git a/sjs/tools/dotc/config/SJSPlatform.scala b/sjs/tools/dotc/config/SJSPlatform.scala new file mode 100644 index 000000000..3ec8049ae --- /dev/null +++ b/sjs/tools/dotc/config/SJSPlatform.scala @@ -0,0 +1,18 @@ +package dotty.tools.dotc.config + +import dotty.tools.dotc.core._ +import Contexts._ +import Symbols._ + +import dotty.tools.backend.sjs.JSDefinitions + +class SJSPlatform()(implicit ctx: Context) extends JavaPlatform { + + /** Scala.js-specific definitions. */ + val jsDefinitions: JSDefinitions = new JSDefinitions() + + /** Is the SAMType `cls` also a SAM under the rules of the Scala.js back-end? */ + override def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean = + defn.isFunctionClass(cls) || jsDefinitions.isJSFunctionClass(cls) + +} -- cgit v1.2.3