diff options
Diffstat (limited to 'src/dotty/tools/backend/sjs/JSCodeGen.scala')
-rw-r--r-- | src/dotty/tools/backend/sjs/JSCodeGen.scala | 1180 |
1 files changed, 1180 insertions, 0 deletions
diff --git a/src/dotty/tools/backend/sjs/JSCodeGen.scala b/src/dotty/tools/backend/sjs/JSCodeGen.scala new file mode 100644 index 000000000..be4e56375 --- /dev/null +++ b/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -0,0 +1,1180 @@ +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 Flags._ +import dotty.tools.dotc.ast.Trees._ +import Types._ +import Symbols._ +import Denotations._ +import Phases._ +import dotty.tools.dotc.util.Positions +import Positions.Position +import StdNames._ + +import dotty.tools.dotc.transform.Erasure + +import org.scalajs.core.ir +import org.scalajs.core.ir.{ClassKind, Trees => js, Types => jstpe} +import js.OptimizerHints + +import JSEncoding._ +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) + + // 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 (isRawJSType(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 = { + ??? + } + + /** 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) + } + } + + /* Work around https://github.com/scala-js/scala-js/issues/2259 + * TODO Remove this when we upgrade to Scala.js 0.6.8. + */ + val methodDef1 = if (!sym.owner.is(Trait)) { + methodDef + } else { + val workaroundBody = js.Block( + js.Apply(js.ClassOf(jstpe.ClassType(encodeClassFullName(sym.owner))), + js.Ident("isPrimitive__Z"), Nil)(jstpe.BooleanType), + methodDef.body) + methodDef.copy(body = workaroundBody)( + methodDef.optimizerHints, methodDef.hash) + } + + Some(methodDef1) + } + } + } + + /** 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: ir.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 Throw(expr) => + val ex = genExpr(expr) + js.Throw { + if (isMaybeJavaScriptException(expr.tpe)) { + genApplyMethod( + genLoadModule(RuntimePackageModule), + Runtime_unwrapJavaScriptException, + List(ex)) + } else { + ex + } + }*/ + + case app: Apply => + genApply(app, isStat) + + /*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.isStaticMember) { + genStaticMember(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 av: ArrayValue => + genArrayValue(av) + + /** A Match reaching the backend is supposed to be optimized as a switch */ + case mtch: Match => + genMatch(mtch, isStat) + + /** Anonymous function (only with -Ydelambdafy:method) */ + case fun: Function => + genAnonFunction(fun) + + case EmptyTree => + js.Skip()*/ + + case _ => + throw new FatalError("Unexpected tree in genExpr: " + + tree + "/" + tree.getClass + " at: " + tree.pos) + } + } // 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 + } + } + + /** 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 + } + + def isRawJSDefaultParam: Boolean = { + false /* + if (isCtorDefaultParam(sym)) { + isRawJSCtorDefaultParam(sym) + } else { + sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) && + isRawJSType(sym.owner.tpe) + }*/ + } + + fun match { + /*case _: TypeApply => + genApplyTypeApply(tree)*/ + + /*case _ if isRawJSDefaultParam => + js.UndefinedParam()(toIRType(sym.tpe.resultType))*/ + + 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 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) { + genStringCall(tree) + } else if (isRawJSType(sym.owner)) { + if (!isScalaJSDefinedJSClass(sym.owner) || isExposed(sym)) + genPrimitiveJSCall(tree, 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 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 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) + } + } + }*/ + } + + /** Generate loading of a module value + * 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 + + //val isGlobalScope = sym.tpe.typeSymbol isSubClass JSGlobalScopeClass + + /*if (isGlobalScope) { + genLoadGlobal() + } else if (isJSNativeClass(sym)) { + genPrimitiveJSModule(sym) + } else {*/ + val cls = jstpe.ClassType(encodeClassFullName(sym)) + if (isRawJSType(sym)) js.LoadJSModule(cls) + else js.LoadModule(cls) + //} + } + + /** 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 + +} |