diff options
Diffstat (limited to 'src/dotty/tools')
-rw-r--r-- | src/dotty/tools/backend/sjs/GenSJSIR.scala | 14 | ||||
-rw-r--r-- | src/dotty/tools/backend/sjs/JSCodeGen.scala | 1180 | ||||
-rw-r--r-- | src/dotty/tools/backend/sjs/JSDefinitions.scala | 175 | ||||
-rw-r--r-- | src/dotty/tools/backend/sjs/JSEncoding.scala | 376 | ||||
-rw-r--r-- | src/dotty/tools/backend/sjs/JSPositions.scala | 65 | ||||
-rw-r--r-- | src/dotty/tools/backend/sjs/JSPrimitives.scala | 113 | ||||
-rw-r--r-- | src/dotty/tools/backend/sjs/ScopedVar.scala | 38 | ||||
-rw-r--r-- | src/dotty/tools/dotc/Compiler.scala | 17 | ||||
-rw-r--r-- | src/dotty/tools/dotc/config/SJSPlatform.scala | 13 | ||||
-rw-r--r-- | src/dotty/tools/dotc/config/ScalaSettings.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/core/Contexts.scala | 25 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala | 12 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/Memoize.scala | 1 | ||||
-rw-r--r-- | src/dotty/tools/dotc/transform/Pickler.scala | 2 |
14 files changed, 2022 insertions, 10 deletions
diff --git a/src/dotty/tools/backend/sjs/GenSJSIR.scala b/src/dotty/tools/backend/sjs/GenSJSIR.scala new file mode 100644 index 000000000..819a8f0e3 --- /dev/null +++ b/src/dotty/tools/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/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 + +} diff --git a/src/dotty/tools/backend/sjs/JSDefinitions.scala b/src/dotty/tools/backend/sjs/JSDefinitions.scala new file mode 100644 index 000000000..0f4415b31 --- /dev/null +++ b/src/dotty/tools/backend/sjs/JSDefinitions.scala @@ -0,0 +1,175 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ + +import Types._ +import Contexts._ +import Symbols._ +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 + +} diff --git a/src/dotty/tools/backend/sjs/JSEncoding.scala b/src/dotty/tools/backend/sjs/JSEncoding.scala new file mode 100644 index 000000000..b35be3264 --- /dev/null +++ b/src/dotty/tools/backend/sjs/JSEncoding.scala @@ -0,0 +1,376 @@ +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._ + +/** 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): (Symbol, js.Ident) = { + require(sym.is(Flags.Method), "encodeMethodSym called with non-method symbol: " + sym) + require(sym.owner == defn.StringClass) + require(!sym.isClassConstructor && !sym.is(Flags.Private)) + + val (encodedName, paramsString) = + encodeMethodNameInternal(sym, inRTClass = true) + val methodIdent = js.Ident(encodedName + paramsString, + Some(sym.unexpandedName.decoded + paramsString)) + + (jsdefn.RuntimeStringModuleClass, methodIdent) + } + + 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 (isRawJSType(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 = + ir.Definitions.encodeClassName(sym.fullName.toString) + + private def encodeMemberNameInternal(sym: Symbol)( + implicit ctx: Context): String = { + sym.name.toString.replace("_", "$und") + } + + def isRawJSType(sym: Symbol)(implicit ctx: Context): Boolean = + sym.hasAnnotation(jsdefn.RawJSTypeAnnot) + + def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean = + isRawJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot) + + 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 || isRawJSType(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/src/dotty/tools/backend/sjs/JSPositions.scala b/src/dotty/tools/backend/sjs/JSPositions.scala new file mode 100644 index 000000000..10570da00 --- /dev/null +++ b/src/dotty/tools/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/src/dotty/tools/backend/sjs/JSPrimitives.scala b/src/dotty/tools/backend/sjs/JSPrimitives.scala new file mode 100644 index 000000000..47d705fe8 --- /dev/null +++ b/src/dotty/tools/backend/sjs/JSPrimitives.scala @@ -0,0 +1,113 @@ +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 + +} + +class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) { + import JSPrimitives._ + + 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) + + primitives.toMap + } + +} diff --git a/src/dotty/tools/backend/sjs/ScopedVar.scala b/src/dotty/tools/backend/sjs/ScopedVar.scala new file mode 100644 index 000000000..0e47f7b79 --- /dev/null +++ b/src/dotty/tools/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/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index f12ab66c5..be4477ee2 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -16,6 +16,7 @@ import core.DenotTransformers.DenotTransformer import core.Denotations.SingleDenotation import dotty.tools.backend.jvm.{LabelDefs, GenBCode} +import dotty.tools.backend.sjs.GenSJSIR class Compiler { @@ -82,6 +83,7 @@ class Compiler { List(new ExpandPrivate, new CollectEntryPoints, new LabelDefs), + List(new GenSJSIR), List(new GenBCode) ) @@ -99,8 +101,17 @@ class Compiler { * imports For each element of RootImports, an import context */ def rootContext(implicit ctx: Context): Context = { - ctx.definitions.init(ctx) - ctx.setPhasePlan(phases) + ctx.initialize()(ctx) + val actualPhases = if (ctx.settings.scalajs.value) { + phases + } else { + // Remove Scala.js-related phases + phases.mapConserve(_.filter { + case _: GenSJSIR => false + case _ => true + }).filter(_.nonEmpty) + } + ctx.setPhasePlan(actualPhases) val rootScope = new MutableScope val bootstrap = ctx.fresh .setPeriod(Period(nextRunId, FirstPhaseId)) @@ -111,7 +122,7 @@ class Compiler { .setTyper(new Typer) .setMode(Mode.ImplicitsEnabled) .setTyperState(new MutableTyperState(ctx.typerState, ctx.typerState.reporter, isCommittable = true)) - ctx.definitions.init(start) // set context of definitions to start + ctx.initialize()(start) // re-initialize the base context with start def addImport(ctx: Context, refFn: () => TermRef) = ctx.fresh.setImportInfo(ImportInfo.rootImport(refFn)(ctx)) (start.setRunInfo(new RunInfo(start)) /: defn.RootImportFns)(addImport) diff --git a/src/dotty/tools/dotc/config/SJSPlatform.scala b/src/dotty/tools/dotc/config/SJSPlatform.scala new file mode 100644 index 000000000..fec9c25a1 --- /dev/null +++ b/src/dotty/tools/dotc/config/SJSPlatform.scala @@ -0,0 +1,13 @@ +package dotty.tools.dotc.config + +import dotty.tools.dotc.core._ +import Contexts._ + +import dotty.tools.backend.sjs.JSDefinitions + +class SJSPlatform()(implicit ctx: Context) extends JavaPlatform { + + /** Scala.js-specific definitions. */ + val jsDefinitions: JSDefinitions = new JSDefinitions() + +} diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index 65bc9ba23..aa264329c 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -29,6 +29,7 @@ class ScalaSettings extends Settings.SettingGroup { val target = ChoiceSetting("-target", "target", "Target platform for object files. All JVM 1.5 targets are deprecated.", List("jvm-1.5", "jvm-1.5-fjbg", "jvm-1.5-asm", "jvm-1.6", "jvm-1.7", "jvm-1.8", "msil"), "jvm-1.8") + val scalajs = BooleanSetting("-scalajs", "Compile in Scala.js mode (requires scalajs-library.jar on the classpath).") val unchecked = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.") val uniqid = BooleanSetting("-uniqid", "Uniquely tag all identifiers in debugging output.") val usejavacp = BooleanSetting("-usejavacp", "Utilize the java.class.path in classpath resolution.") diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index f0537dffa..2fc958a49 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -26,7 +26,7 @@ import reporting._ import collection.mutable import collection.immutable.BitSet import printing._ -import config.{Settings, ScalaSettings, Platform, JavaPlatform} +import config.{Settings, ScalaSettings, Platform, JavaPlatform, SJSPlatform} import language.implicitConversions import DenotTransformers.DenotTransformer @@ -514,8 +514,21 @@ object Contexts { /** The symbol loaders */ val loaders = new SymbolLoaders + /** The platform, initialized by `initPlatform()`. */ + private var _platform: Platform = _ + /** The platform */ - val platform: Platform = new JavaPlatform + def platform: Platform = { + if (_platform == null) { + throw new IllegalStateException( + "initialize() must be called before accessing platform") + } + _platform + } + + protected def newPlatform(implicit ctx: Context): Platform = + if (settings.scalajs.value) new SJSPlatform + else new JavaPlatform /** The loader that loads the members of _root_ */ def rootLoader(root: TermSymbol)(implicit ctx: Context): SymbolLoader = platform.rootLoader(root) @@ -526,6 +539,14 @@ object Contexts { /** The standard definitions */ val definitions = new Definitions + /** Initializes the `ContextBase` with a starting context. + * This initializes the `platform` and the `definitions`. + */ + def initialize()(implicit ctx: Context): Unit = { + _platform = newPlatform + definitions.init + } + def squashed(p: Phase): Phase = { allPhases.find(_.period.containsPhaseId(p.id)).getOrElse(NoPhase) } diff --git a/src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala b/src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala index 60e8edc26..ca06938dc 100644 --- a/src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala +++ b/src/dotty/tools/dotc/transform/LinkScala2ImplClasses.scala @@ -48,11 +48,15 @@ class LinkScala2ImplClasses extends MiniPhaseTransform with IdentityDenotTransfo } } - private def implMethod(meth: Symbol)(implicit ctx: Context): Symbol = - meth.owner.implClass.info - .decl(if (meth.isConstructor) nme.TRAIT_CONSTRUCTOR else meth.name) + private def implMethod(meth: Symbol)(implicit ctx: Context): Symbol = { + val implInfo = meth.owner.implClass.info + if (meth.isConstructor) + implInfo.decl(nme.TRAIT_CONSTRUCTOR).symbol + else + implInfo.decl(meth.name) .suchThat(c => FullParameterization.memberSignature(c.info) == meth.signature) .symbol + } private val Scala2xTrait = allOf(Scala2x, Trait) -}
\ No newline at end of file +} diff --git a/src/dotty/tools/dotc/transform/Memoize.scala b/src/dotty/tools/dotc/transform/Memoize.scala index fbf8ed763..b775496ae 100644 --- a/src/dotty/tools/dotc/transform/Memoize.scala +++ b/src/dotty/tools/dotc/transform/Memoize.scala @@ -91,6 +91,7 @@ import Decorators._ } } else if (sym.isSetter) { if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs } // this is intended as an assertion + field.setFlag(Mutable) // necessary for vals mixed in from Scala2 traits val initializer = Assign(ref(field), ref(tree.vparamss.head.head.symbol)) cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(ctx.withOwner(sym), info)) } diff --git a/src/dotty/tools/dotc/transform/Pickler.scala b/src/dotty/tools/dotc/transform/Pickler.scala index 8040c86d4..c5b223d53 100644 --- a/src/dotty/tools/dotc/transform/Pickler.scala +++ b/src/dotty/tools/dotc/transform/Pickler.scala @@ -80,7 +80,7 @@ class Pickler extends Phase { private def testUnpickler(units: List[CompilationUnit])(implicit ctx: Context): Unit = { pickling.println(i"testing unpickler at run ${ctx.runId}") - ctx.definitions.init + ctx.initialize() val unpicklers = for (unit <- units; (cls, pickler) <- unit.picklers) yield { val unpickler = new DottyUnpickler(pickler.assembleParts()) |