summaryrefslogtreecommitdiff
path: root/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler')
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ClassInfos.scala143
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/Compat210Component.scala108
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala3911
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSExports.scala751
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSFiles.scala51
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSDefinitions.scala128
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala261
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala244
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSPrimitives.scala119
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSTreeExtractors.scala66
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala251
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala621
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSOptions.scala30
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSPlugin.scala143
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/TypeKinds.scala252
-rw-r--r--examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/util/ScopedVar.scala38
16 files changed, 7117 insertions, 0 deletions
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ClassInfos.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ClassInfos.scala
new file mode 100644
index 0000000..026d664
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ClassInfos.scala
@@ -0,0 +1,143 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.language.implicitConversions
+
+import scala.collection.mutable
+import scala.tools.nsc._
+
+import java.io.{ File, PrintWriter, BufferedOutputStream, FileOutputStream }
+
+import scala.scalajs.ir
+import ir.{Trees => js, Types => jstpe, ClassKind}
+import ir.Infos._
+
+trait ClassInfos extends SubComponent { self: GenJSCode =>
+ import global._
+ import jsAddons._
+
+ /** Class data that are never eliminated by dce, so we don't need to
+ * record them.
+ */
+ private val AlwaysPresentClassData = {
+ import ir.Definitions._
+ Set("V", "Z", "C", "B", "S", "I", "J", "F", "D",
+ ObjectClass, StringClass)
+ }
+
+ class ClassInfoBuilder(val symbol: ClassSymbol) {
+ val name = classNameOf(symbol)
+ val encodedName = encodeClassFullName(symbol)
+ var isExported: Boolean = false
+ val ancestorCount = symbol.ancestors.count(!_.isInterface)
+ val kind = {
+ if (isStaticModule(symbol)) ClassKind.ModuleClass
+ else if (symbol.isInterface) ClassKind.Interface
+ else if (isRawJSType(symbol.tpe)) ClassKind.RawJSType
+ else if (isHijackedBoxedClass(symbol)) ClassKind.HijackedClass
+ else if (symbol.isImplClass) ClassKind.TraitImpl
+ else ClassKind.Class
+ }
+ val superClass =
+ if (kind.isClass || kind == ClassKind.HijackedClass)
+ encodeClassFullName(symbol.superClass)
+ else
+ ""
+ val ancestors = (symbol :: symbol.ancestors) map encodeClassFullName
+
+ var optimizerHints: OptimizerHints = OptimizerHints.empty
+
+ val methodInfos = mutable.ListBuffer.empty[MethodInfoBuilder]
+
+ def addMethod(encodedName: String, isAbstract: Boolean = false,
+ isExported: Boolean = false): MethodInfoBuilder = {
+ val b = new MethodInfoBuilder(encodedName, isAbstract, isExported)
+ methodInfos += b
+ b
+ }
+
+ def result(): ClassInfo = {
+ ClassInfo(name, encodedName, isExported, ancestorCount, kind,
+ superClass, ancestors, optimizerHints,
+ methodInfos.map(_.result()).result())
+ }
+ }
+
+ class MethodInfoBuilder(val encodedName: String,
+ val isAbstract: Boolean = false,
+ val isExported: Boolean = false) {
+
+ val calledMethods = mutable.Set.empty[(String, String)] // (tpe, method)
+ val calledMethodsStatic = mutable.Set.empty[(String, String)] // (class, method)
+ val instantiatedClasses = mutable.Set.empty[String]
+ val accessedModules = mutable.Set.empty[String]
+ val accessedClassData = mutable.Set.empty[String]
+ var optimizerHints: OptimizerHints = OptimizerHints.empty
+
+ def callsMethod(ownerIdent: js.Ident, method: js.Ident): Unit =
+ calledMethods += ((patchClassName(ownerIdent.name), method.name))
+
+ def callsMethod(owner: Symbol, method: js.Ident): Unit =
+ calledMethods += ((patchClassName(encodeClassFullName(owner)), method.name))
+
+ def callsMethodStatic(ownerIdent: js.Ident, method: js.Ident): Unit =
+ calledMethodsStatic += ((patchClassName(ownerIdent.name), method.name))
+
+ def instantiatesClass(classSym: Symbol): Unit =
+ instantiatedClasses += patchClassName(encodeClassFullName(classSym))
+
+ def accessesModule(moduleClassSym: Symbol): Unit =
+ accessedModules += patchModuleName(encodeModuleFullName(moduleClassSym))
+
+ def accessesClassData(refType: jstpe.ReferenceType): Unit = {
+ val className = refType match {
+ case jstpe.ClassType(name) => name
+ case jstpe.ArrayType(base, _) => base
+ }
+ if (!AlwaysPresentClassData.contains(className))
+ accessedClassData += className
+ }
+
+ def createsAnonFunction(funInfo: ClassInfoBuilder): Unit = {
+ for (methodInfo <- funInfo.methodInfos) {
+ calledMethods ++= methodInfo.calledMethods
+ calledMethodsStatic ++= methodInfo.calledMethodsStatic
+ instantiatedClasses ++= methodInfo.instantiatedClasses
+ accessedModules ++= methodInfo.accessedModules
+ accessedClassData ++= methodInfo.accessedClassData
+ }
+ }
+
+ private def patchClassName(name: String): String = name match {
+ case "jl_String$" => "sjsr_RuntimeString$"
+ case _ => name
+ }
+
+ private def patchModuleName(name: String): String = name match {
+ case "jl_String" => "sjsr_RuntimeString"
+ case _ => name
+ }
+
+ def result(): MethodInfo = {
+ MethodInfo(
+ encodedName,
+ isAbstract,
+ isExported,
+ calledMethods.toList.groupBy(_._1).mapValues(_.map(_._2)),
+ calledMethodsStatic.toList.groupBy(_._1).mapValues(_.map(_._2)),
+ instantiatedClasses.toList,
+ accessedModules.result.toList,
+ accessedClassData.result.toList,
+ optimizerHints
+ )
+ }
+ }
+
+ private def classNameOf(sym: Symbol): String =
+ if (needsModuleClassSuffix(sym)) sym.fullName + nme.MODULE_SUFFIX_STRING
+ else sym.fullName
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/Compat210Component.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/Compat210Component.scala
new file mode 100644
index 0000000..f357337
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/Compat210Component.scala
@@ -0,0 +1,108 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.tools.nsc._
+
+/** Hacks to have our source code compatible with 2.10 and 2.11.
+ * It exposes 2.11 API in a 2.10 compiler.
+ *
+ * @author Sébastien Doeraene
+ */
+trait Compat210Component {
+
+ val global: Global
+
+ import global._
+
+ // unexpandedName replaces originalName
+
+ implicit final class SymbolCompat(self: Symbol) {
+ def unexpandedName: Name = self.originalName
+ def originalName: Name = sys.error("infinite loop in Compat")
+
+ def isLocalToBlock: Boolean = self.isLocal
+ }
+
+ // enteringPhase/exitingPhase replace beforePhase/afterPhase
+
+ @inline final def enteringPhase[T](ph: Phase)(op: => T): T = {
+ global.enteringPhase(ph)(op)
+ }
+
+ @inline final def exitingPhase[T](ph: Phase)(op: => T): T = {
+ global.exitingPhase(ph)(op)
+ }
+
+ private implicit final class GlobalCompat(
+ self: Compat210Component.this.global.type) {
+
+ def enteringPhase[T](ph: Phase)(op: => T): T = self.beforePhase(ph)(op)
+ def beforePhase[T](ph: Phase)(op: => T): T = sys.error("infinite loop in Compat")
+
+ def exitingPhase[T](ph: Phase)(op: => T): T = self.afterPhase(ph)(op)
+ def afterPhase[T](ph: Phase)(op: => T): T = sys.error("infinite loop in Compat")
+ }
+
+ // ErasedValueType has a different encoding
+
+ implicit final class ErasedValueTypeCompat(self: global.ErasedValueType) {
+ def valueClazz: Symbol = self.original.typeSymbol
+ def erasedUnderlying: Type =
+ enteringPhase(currentRun.erasurePhase)(
+ erasure.erasedValueClassArg(self.original))
+ def original: TypeRef = sys.error("infinite loop in Compat")
+ }
+
+ // repeatedToSingle
+
+ @inline final def repeatedToSingle(t: Type) =
+ global.definitions.repeatedToSingle(t)
+
+ private implicit final class DefinitionsCompat(
+ self: Compat210Component.this.global.definitions.type) {
+
+ def repeatedToSingle(t: Type) = t match {
+ case TypeRef(_, self.RepeatedParamClass, arg :: Nil) => arg
+ case _ => t
+ }
+
+ }
+
+ // run.runDefinitions bundles methods and state related to the run
+ // that were previously in definitions itself
+
+ implicit final class RunCompat(self: Run) {
+ val runDefinitions: Compat210Component.this.global.definitions.type =
+ global.definitions
+ }
+
+ // Mode.FUNmode replaces analyzer.FUNmode
+
+ object Mode {
+ import Compat210Component.AnalyzerCompat
+ // No type ascription! Type is different in 2.10 / 2.11
+ val FUNmode = analyzer.FUNmode
+ }
+}
+
+object Compat210Component {
+ private object LowPriorityMode {
+ object Mode {
+ def FUNmode = sys.error("infinite loop in Compat")
+ }
+ }
+
+ private implicit final class AnalyzerCompat(self: scala.tools.nsc.typechecker.Analyzer) {
+ def FUNmode = {
+ import Compat210Component.LowPriorityMode._
+ {
+ import scala.reflect.internal._
+ Mode.FUNmode
+ }
+ }
+ }
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala
new file mode 100644
index 0000000..f9885a0
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala
@@ -0,0 +1,3911 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.language.implicitConversions
+
+import scala.annotation.switch
+
+import scala.collection.mutable
+import scala.collection.mutable.ListBuffer
+
+import scala.tools.nsc._
+
+import scala.annotation.tailrec
+
+import scala.scalajs.ir
+import ir.{Trees => js, Types => jstpe, ClassKind, Hashers}
+
+import util.ScopedVar
+import ScopedVar.withScopedVars
+
+/** Generate JavaScript code and output it to disk
+ *
+ * @author Sébastien Doeraene
+ */
+abstract class GenJSCode extends plugins.PluginComponent
+ with TypeKinds
+ with JSEncoding
+ with GenJSExports
+ with ClassInfos
+ with GenJSFiles
+ with Compat210Component {
+
+ val jsAddons: JSGlobalAddons {
+ val global: GenJSCode.this.global.type
+ }
+
+ val scalaJSOpts: ScalaJSOptions
+
+ import global._
+ import jsAddons._
+ import rootMirror._
+ import definitions._
+ import jsDefinitions._
+ import JSTreeExtractors._
+
+ import treeInfo.hasSynthCaseSymbol
+
+ import platform.isMaybeBoxed
+
+ val phaseName = "jscode"
+
+ /** testing: this will be called when ASTs are generated */
+ def generatedJSAST(clDefs: List[js.Tree]): Unit
+
+ /** Implicit conversion from nsc Position to ir.Position. */
+ implicit def pos2irPos(pos: Position): ir.Position = {
+ if (pos == NoPosition) ir.Position.NoPosition
+ else {
+ val source = pos2irPosCache.toIRSource(pos.source)
+ // nsc positions are 1-based but IR positions are 0-based
+ ir.Position(source, pos.line-1, pos.column-1)
+ }
+ }
+
+ private[this] object pos2irPosCache {
+ import scala.reflect.internal.util._
+
+ private[this] var lastNscSource: SourceFile = null
+ private[this] var lastIRSource: ir.Position.SourceFile = null
+
+ def toIRSource(nscSource: SourceFile): ir.Position.SourceFile = {
+ if (nscSource != lastNscSource) {
+ lastIRSource = convert(nscSource)
+ lastNscSource = nscSource
+ }
+ lastIRSource
+ }
+
+ private[this] def convert(nscSource: SourceFile): ir.Position.SourceFile = {
+ nscSource.file.file match {
+ case null =>
+ new java.net.URI(
+ "virtualfile", // Pseudo-Scheme
+ nscSource.file.path, // Scheme specific part
+ null // Fragment
+ )
+ case file =>
+ val srcURI = file.toURI
+ def matches(pat: java.net.URI) = pat.relativize(srcURI) != srcURI
+
+ scalaJSOpts.sourceURIMaps.collectFirst {
+ case ScalaJSOptions.URIMap(from, to) if matches(from) =>
+ val relURI = from.relativize(srcURI)
+ to.fold(relURI)(_.resolve(relURI))
+ } getOrElse srcURI
+ }
+ }
+
+ def clear(): Unit = {
+ lastNscSource = null
+ lastIRSource = null
+ }
+ }
+
+ /** Materialize implicitly an ir.Position from an implicit nsc Position. */
+ implicit def implicitPos2irPos(implicit pos: Position): ir.Position = pos
+
+ override def newPhase(p: Phase) = new JSCodePhase(p)
+
+ private object jsnme {
+ val arg_outer = newTermName("arg$outer")
+ val newString = newTermName("newString")
+ }
+
+ class JSCodePhase(prev: Phase) extends StdPhase(prev) with JSExportsPhase {
+
+ override def name = phaseName
+ override def description = "Generate JavaScript code from ASTs"
+ override def erasedTypes = true
+
+ // Some state --------------------------------------------------------------
+
+ val currentClassSym = new ScopedVar[Symbol]
+ val currentClassInfoBuilder = new ScopedVar[ClassInfoBuilder]
+ val currentMethodSym = new ScopedVar[Symbol]
+ val currentMethodInfoBuilder = new ScopedVar[MethodInfoBuilder]
+ val methodTailJumpThisSym = new ScopedVar[Symbol](NoSymbol)
+ val fakeTailJumpParamRepl = new ScopedVar[(Symbol, Symbol)]((NoSymbol, NoSymbol))
+ val enclosingLabelDefParams = new ScopedVar(Map.empty[Symbol, List[Symbol]])
+ val mutableLocalVars = new ScopedVar[mutable.Set[Symbol]]
+ val mutatedLocalVars = new ScopedVar[mutable.Set[Symbol]]
+ val paramAccessorLocals = new ScopedVar(Map.empty[Symbol, js.ParamDef])
+
+ var isModuleInitialized: Boolean = false // see genApply for super calls
+
+ def currentClassType = encodeClassType(currentClassSym)
+
+ val tryingToGenMethodAsJSFunction = new ScopedVar[Boolean](false)
+ class CancelGenMethodAsJSFunction(message: String)
+ extends Throwable(message) with scala.util.control.ControlThrowable
+
+ // Rewriting of anonymous function classes ---------------------------------
+
+ private val translatedAnonFunctions =
+ mutable.Map.empty[Symbol,
+ (/*ctor args:*/ List[js.Tree] => /*instance:*/ js.Tree, ClassInfoBuilder)]
+ private val instantiatedAnonFunctions =
+ mutable.Set.empty[Symbol]
+ private val undefinedDefaultParams =
+ mutable.Set.empty[Symbol]
+
+ // Top-level apply ---------------------------------------------------------
+
+ override def run() {
+ scalaPrimitives.init()
+ jsPrimitives.init()
+ super.run()
+ }
+
+ /** Generates the Scala.js IR for a compilation unit
+ * This method iterates over all the class and interface definitions
+ * found in the compilation unit and emits their IR (.sjsir).
+ *
+ * Some classes are never actually emitted:
+ * - Classes representing primitive types
+ * - The scala.Array class
+ * - Implementation classes for raw JS traits
+ *
+ * Some classes representing anonymous functions are not actually emitted.
+ * Instead, a temporary representation of their `apply` method is built
+ * and recorded, so that it can be inlined as a JavaScript anonymous
+ * function in the method that instantiates it.
+ *
+ * Other ClassDefs are emitted according to their nature:
+ * * Raw JS type (<: js.Any) -> `genRawJSClassData()`
+ * * Interface -> `genInterface()`
+ * * Implementation class -> `genImplClass()`
+ * * Normal class -> `genClass()`
+ */
+ override def apply(cunit: CompilationUnit) {
+ try {
+ val generatedClasses = ListBuffer.empty[(Symbol, js.ClassDef, ClassInfoBuilder)]
+
+ def collectClassDefs(tree: Tree): List[ClassDef] = {
+ tree match {
+ case EmptyTree => Nil
+ case PackageDef(_, stats) => stats flatMap collectClassDefs
+ case cd: ClassDef => cd :: Nil
+ }
+ }
+ val allClassDefs = collectClassDefs(cunit.body)
+
+ /* First gen and record lambdas for js.FunctionN and js.ThisFunctionN.
+ * Since they are SAMs, there cannot be dependencies within this set,
+ * and hence we are sure we can record them before they are used,
+ * which is critical for these.
+ */
+ val nonRawJSFunctionDefs = allClassDefs filterNot { cd =>
+ if (isRawJSFunctionDef(cd.symbol)) {
+ genAndRecordRawJSFunctionClass(cd)
+ true
+ } else {
+ false
+ }
+ }
+
+ /* Then try to gen and record lambdas for scala.FunctionN.
+ * These may fail, and sometimes because of dependencies. Since there
+ * appears to be more forward dependencies than backward dependencies
+ * (at least for non-nested lambdas, which we cannot translate anyway),
+ * we process class defs in reverse order here.
+ */
+ val fullClassDefs = (nonRawJSFunctionDefs.reverse filterNot { cd =>
+ cd.symbol.isAnonymousFunction && tryGenAndRecordAnonFunctionClass(cd)
+ }).reverse
+
+ /* Finally, we emit true code for the remaining class defs. */
+ for (cd <- fullClassDefs) {
+ val sym = cd.symbol
+ implicit val pos = sym.pos
+
+ /* Do not actually emit code for primitive types nor scala.Array. */
+ val isPrimitive =
+ isPrimitiveValueClass(sym) || (sym == ArrayClass)
+
+ /* Similarly, do not emit code for impl classes of raw JS traits. */
+ val isRawJSImplClass =
+ sym.isImplClass && isRawJSType(
+ sym.owner.info.decl(sym.name.dropRight(nme.IMPL_CLASS_SUFFIX.length)).tpe)
+
+ if (!isPrimitive && !isRawJSImplClass) {
+ withScopedVars(
+ currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass),
+ currentClassSym := sym
+ ) {
+ val tree = if (isRawJSType(sym.tpe)) {
+ assert(!isRawJSFunctionDef(sym),
+ s"Raw JS function def should have been recorded: $cd")
+ genRawJSClassData(cd)
+ } else if (sym.isInterface) {
+ genInterface(cd)
+ } else if (sym.isImplClass) {
+ genImplClass(cd)
+ } else {
+ genClass(cd)
+ }
+ generatedClasses += ((sym, tree, currentClassInfoBuilder.get))
+ }
+ }
+ }
+
+ val clDefs = generatedClasses.map(_._2).toList
+ generatedJSAST(clDefs)
+
+ for ((sym, tree, infoBuilder) <- generatedClasses) {
+ genIRFile(cunit, sym, tree, infoBuilder.result())
+ }
+ } finally {
+ translatedAnonFunctions.clear()
+ instantiatedAnonFunctions.clear()
+ undefinedDefaultParams.clear()
+ pos2irPosCache.clear()
+ }
+ }
+
+ // Generate a class --------------------------------------------------------
+
+ /** Gen the IR ClassDef for a class definition (maybe a module class).
+ */
+ def genClass(cd: ClassDef): js.ClassDef = {
+ val ClassDef(mods, name, _, impl) = cd
+ val sym = cd.symbol
+ implicit val pos = sym.pos
+
+ assert(!sym.isInterface && !sym.isImplClass,
+ "genClass() must be called only for normal classes: "+sym)
+ assert(sym.superClass != NoSymbol, sym)
+
+ val classIdent = encodeClassFullNameIdent(sym)
+ val isHijacked = isHijackedBoxedClass(sym)
+
+ // Optimizer hints
+
+ def isStdLibClassWithAdHocInlineAnnot(sym: Symbol): Boolean = {
+ val fullName = sym.fullName
+ (fullName.startsWith("scala.Tuple") && !fullName.endsWith("$")) ||
+ (fullName.startsWith("scala.collection.mutable.ArrayOps$of"))
+ }
+
+ if (sym.hasAnnotation(InlineAnnotationClass) ||
+ (sym.isAnonymousFunction && !sym.isSubClass(PartialFunctionClass)) ||
+ isStdLibClassWithAdHocInlineAnnot(sym))
+ currentClassInfoBuilder.optimizerHints =
+ currentClassInfoBuilder.optimizerHints.copy(hasInlineAnnot = true)
+
+ // Generate members (constructor + methods)
+
+ val generatedMembers = new ListBuffer[js.Tree]
+ val exportedSymbols = new ListBuffer[Symbol]
+
+ if (!isHijacked)
+ generatedMembers ++= genClassFields(cd)
+
+ def gen(tree: Tree): Unit = {
+ tree match {
+ case EmptyTree => ()
+ case Template(_, _, body) => body foreach gen
+
+ case ValDef(mods, name, tpt, rhs) =>
+ () // fields are added via genClassFields()
+
+ case dd: DefDef =>
+ val sym = dd.symbol
+
+ val isExport = jsInterop.isExport(sym)
+ val isNamedExport = isExport && sym.annotations.exists(
+ _.symbol == JSExportNamedAnnotation)
+
+ if (isNamedExport)
+ generatedMembers += genNamedExporterDef(dd)
+ else
+ generatedMembers ++= genMethod(dd)
+
+ if (isExport) {
+ // We add symbols that we have to export here. This way we also
+ // get inherited stuff that is implemented in this class.
+ exportedSymbols += sym
+ }
+
+ case _ => abort("Illegal tree in gen of genClass(): " + tree)
+ }
+ }
+
+ gen(impl)
+
+ // Create method info builder for exported stuff
+ val exports = withScopedVars(
+ currentMethodInfoBuilder := currentClassInfoBuilder.addMethod(
+ dceExportName + classIdent.name, isExported = true)
+ ) {
+ // Generate the exported members
+ val memberExports = genMemberExports(sym, exportedSymbols.toList)
+
+ // Generate exported constructors or accessors
+ val exportedConstructorsOrAccessors =
+ if (isStaticModule(sym)) genModuleAccessorExports(sym)
+ else genConstructorExports(sym)
+ if (exportedConstructorsOrAccessors.nonEmpty)
+ currentClassInfoBuilder.isExported = true
+
+ memberExports ++ exportedConstructorsOrAccessors
+ }
+
+ // Generate the reflective call proxies (where required)
+ val reflProxies =
+ if (isHijacked) Nil
+ else genReflCallProxies(sym)
+
+ // Hashed definitions of the class
+ val hashedDefs =
+ Hashers.hashDefs(generatedMembers.toList ++ exports ++ reflProxies)
+
+ // The complete class definition
+ val kind =
+ if (sym.isModuleClass) ClassKind.ModuleClass
+ else if (isHijacked) ClassKind.HijackedClass
+ else ClassKind.Class
+
+ val classDefinition = js.ClassDef(
+ classIdent,
+ kind,
+ Some(encodeClassFullNameIdent(sym.superClass)),
+ sym.ancestors.map(encodeClassFullNameIdent),
+ hashedDefs)
+
+ classDefinition
+ }
+
+ // Generate the class data of a raw JS class -------------------------------
+
+ /** Gen the IR ClassDef for a raw JS class or trait.
+ */
+ def genRawJSClassData(cd: ClassDef): js.ClassDef = {
+ val sym = cd.symbol
+ implicit val pos = sym.pos
+
+ // Check that RawJS type is not exported
+ for (exp <- jsInterop.exportsOf(sym))
+ reporter.error(exp.pos, "You may not export a class extending js.Any")
+
+ val classIdent = encodeClassFullNameIdent(sym)
+ js.ClassDef(classIdent, ClassKind.RawJSType, None, Nil, Nil)
+ }
+
+ // Generate an interface ---------------------------------------------------
+
+ /** Gen the IR ClassDef for an interface definition.
+ */
+ def genInterface(cd: ClassDef): js.ClassDef = {
+ val sym = cd.symbol
+ implicit val pos = sym.pos
+
+ val classIdent = encodeClassFullNameIdent(sym)
+
+ // fill in class info builder
+ def gen(tree: Tree) {
+ tree match {
+ case EmptyTree => ()
+ case Template(_, _, body) => body foreach gen
+ case dd: DefDef =>
+ currentClassInfoBuilder.addMethod(
+ encodeMethodName(dd.symbol), isAbstract = true)
+ case _ => abort("Illegal tree in gen of genInterface(): " + tree)
+ }
+ }
+ gen(cd.impl)
+
+ // Check that interface/trait is not exported
+ for (exp <- jsInterop.exportsOf(sym))
+ reporter.error(exp.pos, "You may not export a trait")
+
+ js.ClassDef(classIdent, ClassKind.Interface, None,
+ sym.ancestors.map(encodeClassFullNameIdent), Nil)
+ }
+
+ // Generate an implementation class of a trait -----------------------------
+
+ /** Gen the IR ClassDef for an implementation class (of a trait).
+ */
+ def genImplClass(cd: ClassDef): js.ClassDef = {
+ val ClassDef(mods, name, _, impl) = cd
+ val sym = cd.symbol
+ implicit val pos = sym.pos
+
+ def gen(tree: Tree): List[js.MethodDef] = {
+ tree match {
+ case EmptyTree => Nil
+ case Template(_, _, body) => body.flatMap(gen)
+
+ case dd: DefDef =>
+ val m = genMethod(dd)
+ m.toList
+
+ case _ => abort("Illegal tree in gen of genImplClass(): " + tree)
+ }
+ }
+ val generatedMethods = gen(impl)
+
+ js.ClassDef(encodeClassFullNameIdent(sym), ClassKind.TraitImpl,
+ None, Nil, generatedMethods)
+ }
+
+ // Generate the fields of a class ------------------------------------------
+
+ /** Gen definitions for the fields of a class.
+ * The fields are initialized with the zero of their types.
+ */
+ def genClassFields(cd: ClassDef): List[js.VarDef] = withScopedVars(
+ currentMethodInfoBuilder :=
+ currentClassInfoBuilder.addMethod("__init__")
+ ) {
+ // Non-method term members are fields, except for module members.
+ (for {
+ f <- currentClassSym.info.decls
+ if !f.isMethod && f.isTerm && !f.isModule
+ } yield {
+ implicit val pos = f.pos
+ js.VarDef(encodeFieldSym(f), toIRType(f.tpe),
+ mutable = f.isMutable, genZeroOf(f.tpe))
+ }).toList
+ }
+
+ // Generate a method -------------------------------------------------------
+
+ def genMethod(dd: DefDef): Option[js.MethodDef] = withNewLocalNameScope {
+ genMethodWithInfoBuilder(dd).map(_._1)
+ }
+
+ /** Gen JS code for a method definition in a class or in an impl class.
+ * On the JS side, method names are mangled to encode the full signature
+ * of the Scala method, as described in `JSEncoding`, to support
+ * overloading.
+ *
+ * Some methods are not emitted at all:
+ * * Primitives, since they are never actually called
+ * * Abstract methods
+ * * Constructors of hijacked classes
+ * * Trivial constructors, which only call their super constructor, with
+ * the same signature, and the same arguments. The JVM needs these
+ * constructors, but not JavaScript. Since there are lots of them, we
+ * take the trouble of recognizing and removing them.
+ *
+ * Constructors are emitted by generating their body as a statement, then
+ * return `this`.
+ *
+ * Other (normal) methods are emitted with `genMethodBody()`.
+ */
+ def genMethodWithInfoBuilder(
+ dd: DefDef): Option[(js.MethodDef, MethodInfoBuilder)] = {
+
+ implicit val pos = dd.pos
+ val DefDef(mods, name, _, vparamss, _, rhs) = dd
+ val sym = dd.symbol
+
+ isModuleInitialized = false
+
+ val result = withScopedVars(
+ currentMethodSym := sym,
+ methodTailJumpThisSym := NoSymbol,
+ fakeTailJumpParamRepl := (NoSymbol, NoSymbol),
+ enclosingLabelDefParams := Map.empty
+ ) {
+ assert(vparamss.isEmpty || vparamss.tail.isEmpty,
+ "Malformed parameter list: " + vparamss)
+ val params = if (vparamss.isEmpty) Nil else vparamss.head map (_.symbol)
+
+ assert(!sym.owner.isInterface,
+ "genMethod() must not be called for methods in interfaces: "+sym)
+
+ val methodIdent = encodeMethodSym(sym)
+
+ def createInfoBuilder(isAbstract: Boolean = false) = {
+ currentClassInfoBuilder.addMethod(methodIdent.name,
+ isAbstract = isAbstract,
+ isExported = sym.isClassConstructor &&
+ jsInterop.exportsOf(sym).nonEmpty)
+ }
+
+ if (scalaPrimitives.isPrimitive(sym)) {
+ None
+ } else if (sym.isDeferred) {
+ createInfoBuilder(isAbstract = true)
+ None
+ } else if (isRawJSCtorDefaultParam(sym)) {
+ None
+ } else if (isTrivialConstructor(sym, params, rhs)) {
+ createInfoBuilder().callsMethod(sym.owner.superClass, methodIdent)
+ None
+ } else if (sym.isClassConstructor && isHijackedBoxedClass(sym.owner)) {
+ None
+ } else {
+ withScopedVars(
+ currentMethodInfoBuilder := createInfoBuilder(),
+ mutableLocalVars := mutable.Set.empty,
+ mutatedLocalVars := mutable.Set.empty
+ ) {
+ def shouldMarkInline = {
+ sym.hasAnnotation(InlineAnnotationClass) ||
+ sym.name.startsWith(nme.ANON_FUN_NAME)
+ }
+ currentMethodInfoBuilder.optimizerHints =
+ currentMethodInfoBuilder.optimizerHints.copy(
+ isAccessor = sym.isAccessor,
+ hasInlineAnnot = shouldMarkInline)
+
+ val methodDef = {
+ if (sym.isClassConstructor) {
+ val jsParams = for (param <- params) yield {
+ implicit val pos = param.pos
+ js.ParamDef(encodeLocalSym(param), toIRType(param.tpe),
+ mutable = false)
+ }
+ js.MethodDef(methodIdent, jsParams, currentClassType,
+ js.Block(genStat(rhs), genThis()))(None)
+ } else {
+ val resultIRType = toIRType(sym.tpe.resultType)
+ genMethodDef(methodIdent, params, resultIRType, rhs)
+ }
+ }
+
+ val methodDefWithoutUselessVars = {
+ val unmutatedMutableLocalVars =
+ (mutableLocalVars -- mutatedLocalVars).toList
+ val mutatedImmutableLocalVals =
+ (mutatedLocalVars -- mutableLocalVars).toList
+ if (unmutatedMutableLocalVars.isEmpty &&
+ mutatedImmutableLocalVals.isEmpty) {
+ // OK, we're good (common case)
+ methodDef
+ } else {
+ val patches = (
+ unmutatedMutableLocalVars.map(encodeLocalSym(_).name -> false) :::
+ mutatedImmutableLocalVals.map(encodeLocalSym(_).name -> true)
+ ).toMap
+ patchMutableFlagOfLocals(methodDef, patches)
+ }
+ }
+
+ Some((methodDefWithoutUselessVars, currentMethodInfoBuilder.get))
+ }
+ }
+ }
+
+ result
+ }
+
+ private def isTrivialConstructor(sym: Symbol, params: List[Symbol],
+ rhs: Tree): Boolean = {
+ if (!sym.isClassConstructor) {
+ false
+ } else {
+ rhs match {
+ // Shape of a constructor that only calls super
+ case Block(List(Apply(fun @ Select(_:Super, _), args)), Literal(_)) =>
+ val callee = fun.symbol
+ implicit val dummyPos = NoPosition
+
+ // Does the callee have the same signature as sym
+ if (encodeMethodSym(sym) == encodeMethodSym(callee)) {
+ // Test whether args are trivial forwarders
+ assert(args.size == params.size, "Argument count mismatch")
+ params.zip(args) forall { case (param, arg) =>
+ arg.symbol == param
+ }
+ } else {
+ false
+ }
+
+ case _ => false
+ }
+ }
+ }
+
+ /** Patches the mutable flags of selected locals in a [[js.MethodDef]].
+ *
+ * @param patches Map from local name to new value of the mutable flags.
+ * For locals not in the map, the flag is untouched.
+ */
+ private def patchMutableFlagOfLocals(methodDef: js.MethodDef,
+ patches: Map[String, Boolean]): js.MethodDef = {
+
+ def newMutable(name: String, oldMutable: Boolean): Boolean =
+ patches.getOrElse(name, oldMutable)
+
+ val js.MethodDef(methodName, params, resultType, body) = methodDef
+ val newParams = for {
+ p @ js.ParamDef(name, ptpe, mutable) <- params
+ } yield {
+ js.ParamDef(name, ptpe, newMutable(name.name, mutable))(p.pos)
+ }
+ val transformer = new ir.Transformers.Transformer {
+ override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match {
+ case js.VarDef(name, vtpe, mutable, rhs) =>
+ assert(isStat)
+ super.transform(js.VarDef(
+ name, vtpe, newMutable(name.name, mutable), rhs)(tree.pos), isStat)
+ case js.VarRef(name, mutable) =>
+ js.VarRef(name, newMutable(name.name, mutable))(tree.tpe)(tree.pos)
+ case js.Closure(captureParams, params, body, captureValues) =>
+ js.Closure(captureParams, params, body,
+ captureValues.map(transformExpr))(tree.pos)
+ case _ =>
+ super.transform(tree, isStat)
+ }
+ }
+ val newBody =
+ transformer.transform(body, isStat = resultType == jstpe.NoType)
+ js.MethodDef(methodName, newParams, resultType, newBody)(None)(methodDef.pos)
+ }
+
+ /**
+ * Generates reflective proxy methods for methods in sym
+ *
+ * Reflective calls don't depend on the return type, so it's hard to
+ * generate calls without using runtime reflection to list the methods. We
+ * generate a method to be used for reflective calls (without return
+ * type in the name).
+ *
+ * There are cases where non-trivial overloads cause ambiguous situations:
+ *
+ * {{{
+ * object A {
+ * def foo(x: Option[Int]): String
+ * def foo(x: Option[String]): Int
+ * }
+ * }}}
+ *
+ * This is completely legal code, but due to the same erased parameter
+ * type of the {{{foo}}} overloads, they cannot be disambiguated in a
+ * reflective call, as the exact return type is unknown at the call site.
+ *
+ * Cases like the upper currently fail on the JVM backend at runtime. The
+ * Scala.js backend uses the following rules for selection (which will
+ * also cause runtime failures):
+ *
+ * - If a proxy with the same signature (method name and parameters)
+ * exists in the superclass, no proxy is generated (proxy is inherited)
+ * - If no proxy exists in the superclass, a proxy is generated for the
+ * first method with matching signatures.
+ */
+ def genReflCallProxies(sym: Symbol): List[js.MethodDef] = {
+ import scala.reflect.internal.Flags
+
+ // Flags of members we do not want to consider for reflective call proxys
+ val excludedFlags = (
+ Flags.BRIDGE |
+ Flags.PRIVATE |
+ Flags.MACRO
+ )
+
+ /** Check if two method symbols conform in name and parameter types */
+ def weakMatch(s1: Symbol)(s2: Symbol) = {
+ val p1 = s1.tpe.params
+ val p2 = s2.tpe.params
+ s1 == s2 || // Shortcut
+ s1.name == s2.name &&
+ p1.size == p2.size &&
+ (p1 zip p2).forall { case (s1,s2) =>
+ s1.tpe =:= s2.tpe
+ }
+ }
+
+ /** Check if the symbol's owner's superclass has a matching member (and
+ * therefore an existing proxy).
+ */
+ def superHasProxy(s: Symbol) = {
+ val alts = sym.superClass.tpe.findMember(
+ name = s.name,
+ excludedFlags = excludedFlags,
+ requiredFlags = Flags.METHOD,
+ stableOnly = false).alternatives
+ alts.exists(weakMatch(s) _)
+ }
+
+ // Query candidate methods
+ val methods = sym.tpe.findMembers(
+ excludedFlags = excludedFlags,
+ requiredFlags = Flags.METHOD)
+
+ val candidates = methods filterNot { s =>
+ s.isConstructor ||
+ superHasProxy(s) ||
+ jsInterop.isExport(s)
+ }
+
+ val proxies = candidates filter {
+ c => candidates.find(weakMatch(c) _).get == c
+ }
+
+ proxies.map(genReflCallProxy _).toList
+ }
+
+ /** actually generates reflective call proxy for the given method symbol */
+ private def genReflCallProxy(sym: Symbol): js.MethodDef = {
+ implicit val pos = sym.pos
+
+ val proxyIdent = encodeMethodSym(sym, reflProxy = true)
+
+ withNewLocalNameScope {
+ withScopedVars(
+ currentMethodInfoBuilder :=
+ currentClassInfoBuilder.addMethod(proxyIdent.name)
+ ) {
+ val jsParams = for (param <- sym.tpe.params) yield {
+ implicit val pos = param.pos
+ js.ParamDef(encodeLocalSym(param), toIRType(param.tpe),
+ mutable = false)
+ }
+
+ val call = genApplyMethod(genThis(), sym.owner, sym,
+ jsParams.map(_.ref))
+ val body = ensureBoxed(call,
+ enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType))
+
+ js.MethodDef(proxyIdent, jsParams, jstpe.AnyType, body)(None)
+ }
+ }
+ }
+
+ /** Generates the MethodDef of a (non-constructor) method
+ *
+ * Most normal methods are emitted straightforwardly. If the result
+ * type is Unit, then the body is emitted as a statement. Otherwise, it is
+ * emitted as an expression.
+ *
+ * The additional complexity of this method handles the transformation of
+ * a peculiarity of recursive tail calls: the local ValDef that replaces
+ * `this`.
+ */
+ def genMethodDef(methodIdent: js.Ident, paramsSyms: List[Symbol],
+ resultIRType: jstpe.Type, tree: Tree): js.MethodDef = {
+ implicit val pos = tree.pos
+
+ val jsParams = for (param <- paramsSyms) yield {
+ implicit val pos = param.pos
+ js.ParamDef(encodeLocalSym(param), toIRType(param.tpe), mutable = false)
+ }
+
+ val bodyIsStat = resultIRType == jstpe.NoType
+
+ val body = tree match {
+ case Block(
+ (thisDef @ ValDef(_, nme.THIS, _, initialThis)) :: otherStats,
+ rhs) =>
+ // This method has tail jumps
+ withScopedVars(
+ (initialThis match {
+ case This(_) =>
+ Seq(methodTailJumpThisSym := thisDef.symbol,
+ fakeTailJumpParamRepl := (NoSymbol, NoSymbol))
+ case Ident(_) =>
+ Seq(methodTailJumpThisSym := NoSymbol,
+ fakeTailJumpParamRepl := (thisDef.symbol, initialThis.symbol))
+ }): _*
+ ) {
+ val innerBody = js.Block(otherStats.map(genStat) :+ (
+ if (bodyIsStat) genStat(rhs)
+ else genExpr(rhs)))
+
+ if (methodTailJumpThisSym.get == NoSymbol) {
+ innerBody
+ } else {
+ if (methodTailJumpThisSym.isMutable)
+ mutableLocalVars += methodTailJumpThisSym
+ js.Block(
+ js.VarDef(encodeLocalSym(methodTailJumpThisSym),
+ currentClassType, methodTailJumpThisSym.isMutable,
+ js.This()(currentClassType)),
+ innerBody)
+ }
+ }
+
+ case _ =>
+ if (bodyIsStat) genStat(tree)
+ else genExpr(tree)
+ }
+
+ js.MethodDef(methodIdent, jsParams, resultIRType, body)(None)
+ }
+
+ /** Gen JS code for a tree in statement position (in the IR).
+ */
+ def genStat(tree: Tree): js.Tree = {
+ exprToStat(genStatOrExpr(tree, isStat = true))
+ }
+
+ /** Turn a JavaScript expression of type Unit into a statement */
+ def exprToStat(tree: js.Tree): js.Tree = {
+ /* Any JavaScript expression is also a statement, but at least we get rid
+ * of some pure expressions that come from our own codegen.
+ */
+ implicit val pos = tree.pos
+ tree match {
+ case js.Block(stats :+ expr) => js.Block(stats :+ exprToStat(expr))
+ case _:js.Literal | js.This() => js.Skip()
+ case _ => tree
+ }
+ }
+
+ /** Gen JS code for a tree in expression position (in the IR).
+ */
+ def genExpr(tree: Tree): js.Tree = {
+ val result = genStatOrExpr(tree, isStat = false)
+ assert(result.tpe != jstpe.NoType,
+ s"genExpr($tree) returned a tree with type NoType at pos ${tree.pos}")
+ result
+ }
+
+ /** Gen JS code for a tree in statement or expression position (in the IR).
+ *
+ * This is the main transformation method. Each node of the Scala AST
+ * is transformed into an equivalent portion of the JS AST.
+ */
+ def genStatOrExpr(tree: Tree, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+
+ tree match {
+ /** LabelDefs (for while and do..while loops) */
+ case lblDf: LabelDef =>
+ genLabelDef(lblDf)
+
+ /** Local val or var declaration */
+ case ValDef(_, name, _, rhs) =>
+ /* Must have been eliminated by the tail call transform performed
+ * by genMethodBody(). */
+ assert(name != nme.THIS,
+ s"ValDef(_, nme.THIS, _, _) found at ${tree.pos}")
+
+ val sym = tree.symbol
+ val rhsTree =
+ if (rhs == EmptyTree) genZeroOf(sym.tpe)
+ else genExpr(rhs)
+
+ rhsTree match {
+ case js.UndefinedParam() =>
+ // This is an intermediate assignment for default params on a
+ // js.Any. Add the symbol to the corresponding set to inform
+ // the Ident resolver how to replace it and don't emit the symbol
+ undefinedDefaultParams += sym
+ js.Skip()
+ case _ =>
+ if (sym.isMutable)
+ mutableLocalVars += sym
+ js.VarDef(encodeLocalSym(sym),
+ toIRType(sym.tpe), sym.isMutable, rhsTree)
+ }
+
+ case If(cond, thenp, elsep) =>
+ js.If(genExpr(cond), genStatOrExpr(thenp, isStat),
+ genStatOrExpr(elsep, isStat))(toIRType(tree.tpe))
+
+ case Return(expr) =>
+ js.Return(toIRType(expr.tpe) match {
+ case jstpe.NoType => js.Block(genStat(expr), js.Undefined())
+ case _ => genExpr(expr)
+ })
+
+ case t: Try =>
+ genTry(t, isStat)
+
+ case Throw(expr) =>
+ val ex = genExpr(expr)
+ js.Throw {
+ if (isMaybeJavaScriptException(expr.tpe)) {
+ genApplyMethod(
+ genLoadModule(RuntimePackageModule),
+ RuntimePackageModule.moduleClass,
+ Runtime_unwrapJavaScriptException,
+ List(ex))
+ } else {
+ ex
+ }
+ }
+
+ case app: Apply =>
+ genApply(app, isStat)
+
+ case app: ApplyDynamic =>
+ genApplyDynamic(app)
+
+ case This(qual) =>
+ if (tree.symbol == currentClassSym.get) {
+ genThis()
+ } else {
+ assert(tree.symbol.isModuleClass,
+ "Trying to access the this of another class: " +
+ "tree.symbol = " + tree.symbol +
+ ", class symbol = " + currentClassSym.get +
+ " compilation unit:" + currentUnit)
+ genLoadModule(tree.symbol)
+ }
+
+ case Select(qualifier, selector) =>
+ val sym = tree.symbol
+ if (sym.isModule) {
+ assert(!sym.isPackageClass, "Cannot use package as value: " + tree)
+ genLoadModule(sym)
+ } else if (sym.isStaticMember) {
+ genStaticMember(sym)
+ } else if (paramAccessorLocals contains sym) {
+ paramAccessorLocals(sym).ref
+ } else {
+ js.Select(genExpr(qualifier), encodeFieldSym(sym),
+ mutable = sym.isMutable)(toIRType(sym.tpe))
+ }
+
+ case Ident(name) =>
+ val sym = tree.symbol
+ if (!sym.hasPackageFlag) {
+ if (sym.isModule) {
+ assert(!sym.isPackageClass, "Cannot use package as value: " + tree)
+ genLoadModule(sym)
+ } else if (undefinedDefaultParams contains sym) {
+ // This is a default parameter whose assignment was moved to
+ // a local variable. Put a literal undefined param again
+ js.UndefinedParam()(toIRType(sym.tpe))
+ } else {
+ js.VarRef(encodeLocalSym(sym), sym.isMutable)(toIRType(sym.tpe))
+ }
+ } else {
+ sys.error("Cannot use package as value: " + tree)
+ }
+
+ case Literal(value) =>
+ value.tag match {
+ case UnitTag =>
+ js.Skip()
+ case BooleanTag =>
+ js.BooleanLiteral(value.booleanValue)
+ case ByteTag | ShortTag | CharTag | IntTag =>
+ js.IntLiteral(value.intValue)
+ case LongTag =>
+ js.LongLiteral(value.longValue)
+ case FloatTag =>
+ js.FloatLiteral(value.floatValue)
+ case DoubleTag =>
+ js.DoubleLiteral(value.doubleValue)
+ case StringTag =>
+ js.StringLiteral(value.stringValue)
+ case NullTag =>
+ js.Null()
+ case ClazzTag =>
+ genClassConstant(value.typeValue)
+ case EnumTag =>
+ genStaticMember(value.symbolValue)
+ }
+
+ case tree: Block =>
+ genBlock(tree, isStat)
+
+ case Typed(Super(_, _), _) =>
+ genThis()
+
+ case Typed(expr, _) =>
+ genExpr(expr)
+
+ case Assign(lhs, rhs) =>
+ val sym = lhs.symbol
+ if (sym.isStaticMember)
+ abort(s"Assignment to static member ${sym.fullName} not supported")
+ val genLhs = lhs match {
+ case Select(qualifier, _) =>
+ js.Select(genExpr(qualifier), encodeFieldSym(sym),
+ mutable = sym.isMutable)(toIRType(sym.tpe))
+ case _ =>
+ mutatedLocalVars += sym
+ js.VarRef(encodeLocalSym(sym), sym.isMutable)(toIRType(sym.tpe))
+ }
+ js.Assign(genLhs, genExpr(rhs))
+
+ /** Array constructor */
+ case av: ArrayValue =>
+ genArrayValue(av)
+
+ /** A Match reaching the backend is supposed to be optimized as a switch */
+ case mtch: Match =>
+ genMatch(mtch, isStat)
+
+ /** Anonymous function (only with -Ydelambdafy:method) */
+ case fun: Function =>
+ genAnonFunction(fun)
+
+ case EmptyTree =>
+ js.Skip()
+
+ case _ =>
+ abort("Unexpected tree in genExpr: " +
+ tree + "/" + tree.getClass + " at: " + tree.pos)
+ }
+ } // end of GenJSCode.genExpr()
+
+ /** Gen JS this of the current class.
+ * Normally encoded straightforwardly as a JS this.
+ * But must be replaced by the tail-jump-this local variable if there
+ * is one.
+ */
+ private def genThis()(implicit pos: Position): js.Tree = {
+ if (methodTailJumpThisSym.get != NoSymbol) {
+ js.VarRef(
+ encodeLocalSym(methodTailJumpThisSym),
+ methodTailJumpThisSym.isMutable)(currentClassType)
+ } else {
+ if (tryingToGenMethodAsJSFunction)
+ throw new CancelGenMethodAsJSFunction(
+ "Trying to generate `this` inside the body")
+ js.This()(currentClassType)
+ }
+ }
+
+ /** Gen JS code for LabelDef
+ * The only LabelDefs that can reach here are the desugaring of
+ * while and do..while loops. All other LabelDefs (for tail calls or
+ * matches) are caught upstream and transformed in ad hoc ways.
+ *
+ * So here we recognize all the possible forms of trees that can result
+ * of while or do..while loops, and we reconstruct the loop for emission
+ * to JS.
+ */
+ def genLabelDef(tree: LabelDef): js.Tree = {
+ implicit val pos = tree.pos
+ val sym = tree.symbol
+
+ tree match {
+ // while (cond) { body }
+ case LabelDef(lname, Nil,
+ If(cond,
+ Block(bodyStats, Apply(target @ Ident(lname2), Nil)),
+ Literal(_))) if (target.symbol == sym) =>
+ js.While(genExpr(cond), js.Block(bodyStats map genStat))
+
+ // while (cond) { body }; result
+ case LabelDef(lname, Nil,
+ Block(List(
+ If(cond,
+ Block(bodyStats, Apply(target @ Ident(lname2), Nil)),
+ Literal(_))),
+ result)) if (target.symbol == sym) =>
+ js.Block(
+ js.While(genExpr(cond), js.Block(bodyStats map genStat)),
+ genExpr(result))
+
+ // while (true) { body }
+ case LabelDef(lname, Nil,
+ Block(bodyStats,
+ Apply(target @ Ident(lname2), Nil))) if (target.symbol == sym) =>
+ js.While(js.BooleanLiteral(true), js.Block(bodyStats map genStat))
+
+ // while (false) { body }
+ case LabelDef(lname, Nil, Literal(Constant(()))) =>
+ js.Skip()
+
+ // do { body } while (cond)
+ case LabelDef(lname, Nil,
+ Block(bodyStats,
+ If(cond,
+ Apply(target @ Ident(lname2), Nil),
+ Literal(_)))) if (target.symbol == sym) =>
+ js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond))
+
+ // do { body } while (cond); result
+ case LabelDef(lname, Nil,
+ Block(
+ bodyStats :+
+ If(cond,
+ Apply(target @ Ident(lname2), Nil),
+ Literal(_)),
+ result)) if (target.symbol == sym) =>
+ js.Block(
+ js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond)),
+ genExpr(result))
+
+ /* Arbitrary other label - we can jump to it from inside it.
+ * This is typically for the label-defs implementing tail-calls.
+ * It can also handle other weird LabelDefs generated by some compiler
+ * plugins (see for example #1148).
+ */
+ case LabelDef(labelName, labelParams, rhs) =>
+ val labelParamSyms = labelParams.map(_.symbol) map {
+ s => if (s == fakeTailJumpParamRepl._1) fakeTailJumpParamRepl._2 else s
+ }
+
+ withScopedVars(
+ enclosingLabelDefParams :=
+ enclosingLabelDefParams.get + (tree.symbol -> labelParamSyms)
+ ) {
+ val bodyType = toIRType(tree.tpe)
+ val labelIdent = encodeLabelSym(tree.symbol)
+ val blockLabelIdent = freshLocalIdent()
+
+ js.Labeled(blockLabelIdent, bodyType, {
+ js.While(js.BooleanLiteral(true), {
+ if (bodyType == jstpe.NoType)
+ js.Block(genStat(rhs), js.Return(js.Undefined(), Some(blockLabelIdent)))
+ else
+ js.Return(genExpr(rhs), Some(blockLabelIdent))
+ }, Some(labelIdent))
+ })
+ }
+ }
+ }
+
+ /** Gen JS code for a try..catch or try..finally block
+ *
+ * try..finally blocks are compiled straightforwardly to try..finally
+ * blocks of JS.
+ *
+ * try..catch blocks are a bit more subtle, as JS does not have
+ * type-based selection of exceptions to catch. We thus encode explicitly
+ * the type tests, like in:
+ *
+ * try { ... }
+ * catch (e) {
+ * if (e.isInstanceOf[IOException]) { ... }
+ * else if (e.isInstanceOf[Exception]) { ... }
+ * else {
+ * throw e; // default, re-throw
+ * }
+ * }
+ */
+ def genTry(tree: Try, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Try(block, catches, finalizer) = tree
+
+ val blockAST = genStatOrExpr(block, isStat)
+
+ val exceptIdent = freshLocalIdent("e")
+ val origExceptVar = js.VarRef(exceptIdent, mutable = false)(jstpe.AnyType)
+
+ val resultType = toIRType(tree.tpe)
+
+ val handlerAST = {
+ if (catches.isEmpty) {
+ js.EmptyTree
+ } else {
+ val mightCatchJavaScriptException = catches.exists { caseDef =>
+ caseDef.pat match {
+ case Typed(Ident(nme.WILDCARD), tpt) =>
+ isMaybeJavaScriptException(tpt.tpe)
+ case Ident(nme.WILDCARD) =>
+ true
+ case pat @ Bind(_, _) =>
+ isMaybeJavaScriptException(pat.symbol.tpe)
+ }
+ }
+
+ val (exceptValDef, exceptVar) = if (mightCatchJavaScriptException) {
+ val valDef = js.VarDef(freshLocalIdent("e"),
+ encodeClassType(ThrowableClass), mutable = false, {
+ genApplyMethod(
+ genLoadModule(RuntimePackageModule),
+ RuntimePackageModule.moduleClass,
+ Runtime_wrapJavaScriptException,
+ List(origExceptVar))
+ })
+ (valDef, valDef.ref)
+ } else {
+ (js.Skip(), origExceptVar)
+ }
+
+ val elseHandler: js.Tree = js.Throw(origExceptVar)
+
+ val handler0 = catches.foldRight(elseHandler) { (caseDef, elsep) =>
+ implicit val pos = caseDef.pos
+ val CaseDef(pat, _, body) = caseDef
+
+ // Extract exception type and variable
+ val (tpe, boundVar) = (pat match {
+ case Typed(Ident(nme.WILDCARD), tpt) =>
+ (tpt.tpe, None)
+ case Ident(nme.WILDCARD) =>
+ (ThrowableClass.tpe, None)
+ case Bind(_, _) =>
+ (pat.symbol.tpe, Some(encodeLocalSym(pat.symbol)))
+ })
+
+ // Generate the body that must be executed if the exception matches
+ val bodyWithBoundVar = (boundVar match {
+ case None =>
+ genStatOrExpr(body, isStat)
+ case Some(bv) =>
+ val castException = genAsInstanceOf(exceptVar, tpe)
+ js.Block(
+ js.VarDef(bv, toIRType(tpe), mutable = false, castException),
+ genStatOrExpr(body, isStat))
+ })
+
+ // Generate the test
+ if (tpe == ThrowableClass.tpe) {
+ bodyWithBoundVar
+ } else {
+ val cond = genIsInstanceOf(exceptVar, tpe)
+ js.If(cond, bodyWithBoundVar, elsep)(resultType)
+ }
+ }
+
+ js.Block(
+ exceptValDef,
+ handler0)
+ }
+ }
+
+ val finalizerAST = genStat(finalizer) match {
+ case js.Skip() => js.EmptyTree
+ case ast => ast
+ }
+
+ if (handlerAST == js.EmptyTree && finalizerAST == js.EmptyTree) blockAST
+ else js.Try(blockAST, exceptIdent, handlerAST, finalizerAST)(resultType)
+ }
+
+ /** Gen JS code for an Apply node (method call)
+ *
+ * There's a whole bunch of varieties of Apply nodes: regular method
+ * calls, super calls, constructor calls, isInstanceOf/asInstanceOf,
+ * primitives, JS calls, etc. They are further dispatched in here.
+ */
+ def genApply(tree: Apply, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun, args) = tree
+
+ fun match {
+ case TypeApply(_, _) =>
+ genApplyTypeApply(tree)
+
+ case Select(Super(_, _), _) =>
+ genSuperCall(tree)
+
+ case Select(New(_), nme.CONSTRUCTOR) =>
+ genApplyNew(tree)
+
+ case _ =>
+ val sym = fun.symbol
+
+ if (sym.isLabel) {
+ genLabelApply(tree)
+ } else if (scalaPrimitives.isPrimitive(sym)) {
+ genPrimitiveOp(tree, isStat)
+ } else if (currentRun.runDefinitions.isBox(sym)) {
+ // Box a primitive value (cannot be Unit)
+ val arg = args.head
+ makePrimitiveBox(genExpr(arg), arg.tpe)
+ } else if (currentRun.runDefinitions.isUnbox(sym)) {
+ // Unbox a primitive value (cannot be Unit)
+ val arg = args.head
+ makePrimitiveUnbox(genExpr(arg), tree.tpe)
+ } else {
+ genNormalApply(tree, isStat)
+ }
+ }
+ }
+
+ /** Gen an Apply with a TypeApply method.
+ * Only isInstanceOf and asInstanceOf keep their type argument until the
+ * backend.
+ */
+ private def genApplyTypeApply(tree: Apply): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(TypeApply(fun @ Select(obj, _), targs), _) = tree
+ val sym = fun.symbol
+
+ val cast = sym match {
+ case Object_isInstanceOf => false
+ case Object_asInstanceOf => true
+ case _ =>
+ abort("Unexpected type application " + fun +
+ "[sym: " + sym.fullName + "]" + " in: " + tree)
+ }
+
+ val to = targs.head.tpe
+ val l = toTypeKind(obj.tpe)
+ val r = toTypeKind(to)
+ val source = genExpr(obj)
+
+ if (l.isValueType && r.isValueType) {
+ if (cast)
+ genConversion(l, r, source)
+ else
+ js.BooleanLiteral(l == r)
+ } else if (l.isValueType) {
+ val result = if (cast) {
+ val ctor = ClassCastExceptionClass.info.member(
+ nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty)
+ js.Throw(genNew(ClassCastExceptionClass, ctor, Nil))
+ } else {
+ js.BooleanLiteral(false)
+ }
+ js.Block(source, result) // eval and discard source
+ } else if (r.isValueType) {
+ assert(!cast, s"Unexpected asInstanceOf from ref type to value type")
+ genIsInstanceOf(source, boxedClass(to.typeSymbol).tpe)
+ } else {
+ if (cast)
+ genAsInstanceOf(source, to)
+ else
+ genIsInstanceOf(source, to)
+ }
+ }
+
+ /** Gen JS code for a super call, of the form Class.super[mix].fun(args).
+ *
+ * This does not include calls defined in mixin traits, as these are
+ * already desugared by the 'mixin' phase. Only calls to super classes
+ * remain.
+ * Since a class has exactly one direct superclass, and calling a method
+ * two classes above the current one is invalid, the `mix` item is
+ * irrelevant.
+ */
+ private def genSuperCall(tree: Apply): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun @ Select(sup @ Super(_, mix), _), args) = tree
+ val sym = fun.symbol
+
+ if (sym == Object_getClass) {
+ // The only primitive that is also callable as super call
+ js.GetClass(genThis())
+ } else {
+ val superCall = genStaticApplyMethod(
+ genThis()(sup.pos), sym, genActualArgs(sym, args))
+
+ // Initialize the module instance just after the super constructor call.
+ if (isStaticModule(currentClassSym) && !isModuleInitialized &&
+ currentMethodSym.isClassConstructor) {
+ isModuleInitialized = true
+ val thisType = jstpe.ClassType(encodeClassFullName(currentClassSym))
+ val initModule = js.StoreModule(thisType, js.This()(thisType))
+ js.Block(superCall, initModule, js.This()(thisType))
+ } else {
+ superCall
+ }
+ }
+ }
+
+ /** Gen JS code for a constructor call (new).
+ * Further refined into:
+ * * new String(...)
+ * * new of a hijacked boxed class
+ * * new of an anonymous function class that was recorded as JS function
+ * * new of a raw JS class
+ * * new Array
+ * * regular new
+ */
+ private def genApplyNew(tree: Apply): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) = tree
+ val ctor = fun.symbol
+ val tpe = tpt.tpe
+
+ assert(ctor.isClassConstructor,
+ "'new' call to non-constructor: " + ctor.name)
+
+ if (isStringType(tpe)) {
+ genNewString(tree)
+ } else if (isHijackedBoxedClass(tpe.typeSymbol)) {
+ genNewHijackedBoxedClass(tpe.typeSymbol, ctor, args map genExpr)
+ } else if (translatedAnonFunctions contains tpe.typeSymbol) {
+ val (functionMaker, funInfo) = translatedAnonFunctions(tpe.typeSymbol)
+ currentMethodInfoBuilder.createsAnonFunction(funInfo)
+ functionMaker(args map genExpr)
+ } else if (isRawJSType(tpe)) {
+ genPrimitiveJSNew(tree)
+ } else {
+ toTypeKind(tpe) match {
+ case arr @ ARRAY(elem) =>
+ genNewArray(arr.toIRType, args map genExpr)
+ case rt @ REFERENCE(cls) =>
+ genNew(cls, ctor, genActualArgs(ctor, args))
+ case generatedType =>
+ abort(s"Non reference type cannot be instantiated: $generatedType")
+ }
+ }
+ }
+
+ /** Gen jump to a label.
+ * Most label-applys are caught upstream (while and do..while loops,
+ * jumps to next case of a pattern match), but some are still handled here:
+ * * Jumps to enclosing label-defs, including tail-recursive calls
+ * * Jump to the end of a pattern match
+ */
+ private def genLabelApply(tree: Apply): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun, args) = tree
+ val sym = fun.symbol
+
+ if (enclosingLabelDefParams.contains(sym)) {
+ genEnclosingLabelApply(tree)
+ } else if (sym.name.toString() startsWith "matchEnd") {
+ /* Jump the to the end-label of a pattern match
+ * Such labels have exactly one argument, which is the result of
+ * the pattern match (of type BoxedUnit if the match is in statement
+ * position). We simply `return` the argument as the result of the
+ * labeled block surrounding the match.
+ */
+ js.Return(genExpr(args.head), Some(encodeLabelSym(sym)))
+ } else {
+ /* No other label apply should ever happen. If it does, then we
+ * have missed a pattern of LabelDef/LabelApply and some new
+ * translation must be found for it.
+ */
+ abort("Found unknown label apply at "+tree.pos+": "+tree)
+ }
+ }
+
+ /** Gen a label-apply to an enclosing label def.
+ *
+ * This is typically used for tail-recursive calls.
+ *
+ * Basically this is compiled into
+ * continue labelDefIdent;
+ * but arguments need to be updated beforehand.
+ *
+ * Since the rhs for the new value of an argument can depend on the value
+ * of another argument (and since deciding if it is indeed the case is
+ * impossible in general), new values are computed in temporary variables
+ * first, then copied to the actual variables representing the argument.
+ *
+ * Trivial assignments (arg1 = arg1) are eliminated.
+ *
+ * If, after elimination of trivial assignments, only one assignment
+ * remains, then we do not use a temporary variable for this one.
+ */
+ private def genEnclosingLabelApply(tree: Apply): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun, args) = tree
+ val sym = fun.symbol
+
+ // Prepare quadruplets of (formalArg, irType, tempVar, actualArg)
+ // Do not include trivial assignments (when actualArg == formalArg)
+ val formalArgs = enclosingLabelDefParams(sym)
+ val actualArgs = args map genExpr
+ val quadruplets = {
+ for {
+ (formalArgSym, actualArg) <- formalArgs zip actualArgs
+ formalArg = encodeLocalSym(formalArgSym)
+ if (actualArg match {
+ case js.VarRef(`formalArg`, _) => false
+ case _ => true
+ })
+ } yield {
+ mutatedLocalVars += formalArgSym
+ val tpe = toIRType(formalArgSym.tpe)
+ (js.VarRef(formalArg, formalArgSym.isMutable)(tpe), tpe,
+ freshLocalIdent("temp$" + formalArg.name),
+ actualArg)
+ }
+ }
+
+ // The actual jump (continue labelDefIdent;)
+ val jump = js.Continue(Some(encodeLabelSym(sym)))
+
+ quadruplets match {
+ case Nil => jump
+
+ case (formalArg, argType, _, actualArg) :: Nil =>
+ js.Block(
+ js.Assign(formalArg, actualArg),
+ jump)
+
+ case _ =>
+ val tempAssignments =
+ for ((_, argType, tempArg, actualArg) <- quadruplets)
+ yield js.VarDef(tempArg, argType, mutable = false, actualArg)
+ val trueAssignments =
+ for ((formalArg, argType, tempArg, _) <- quadruplets)
+ yield js.Assign(
+ formalArg,
+ js.VarRef(tempArg, mutable = false)(argType))
+ js.Block(tempAssignments ++ trueAssignments :+ jump)
+ }
+ }
+
+ /** Gen a "normal" apply (to a true method).
+ *
+ * But even these are further refined into:
+ * * Methods of java.lang.String, which are redirected to the
+ * RuntimeString trait implementation.
+ * * Calls to methods of raw JS types (Scala.js -> JS bridge)
+ * * Calls to methods in impl classes of traits.
+ * * Regular method call
+ */
+ private def genNormalApply(tree: Apply, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun @ Select(receiver, _), args) = tree
+ val sym = fun.symbol
+
+ def isStringMethodFromObject: Boolean = sym.name match {
+ case nme.toString_ | nme.equals_ | nme.hashCode_ => true
+ case _ => false
+ }
+
+ if (sym.owner == StringClass && !isStringMethodFromObject) {
+ genStringCall(tree)
+ } else if (isRawJSType(receiver.tpe) && sym.owner != ObjectClass) {
+ genPrimitiveJSCall(tree, isStat)
+ } else if (foreignIsImplClass(sym.owner)) {
+ genTraitImplApply(sym, args map genExpr)
+ } else if (isRawJSCtorDefaultParam(sym)) {
+ js.UndefinedParam()(toIRType(sym.tpe.resultType))
+ } else if (sym.isClassConstructor) {
+ /* See #66: we have to emit a static call to avoid calling a
+ * constructor with the same signature in a subclass */
+ genStaticApplyMethod(genExpr(receiver), sym, genActualArgs(sym, args))
+ } else {
+ genApplyMethod(genExpr(receiver), receiver.tpe, sym, genActualArgs(sym, args))
+ }
+ }
+
+ def genStaticApplyMethod(receiver: js.Tree, method: Symbol,
+ arguments: List[js.Tree])(implicit pos: Position): js.Tree = {
+ val classIdent = encodeClassFullNameIdent(method.owner)
+ val methodIdent = encodeMethodSym(method)
+ currentMethodInfoBuilder.callsMethodStatic(classIdent, methodIdent)
+ js.StaticApply(receiver, jstpe.ClassType(classIdent.name), methodIdent,
+ arguments)(toIRType(method.tpe.resultType))
+ }
+
+ def genTraitImplApply(method: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ val implIdent = encodeClassFullNameIdent(method.owner)
+ val methodIdent = encodeMethodSym(method)
+ genTraitImplApply(implIdent, methodIdent, arguments,
+ toIRType(method.tpe.resultType))
+ }
+
+ def genTraitImplApply(implIdent: js.Ident, methodIdent: js.Ident,
+ arguments: List[js.Tree], resultType: jstpe.Type)(
+ implicit pos: Position): js.Tree = {
+ currentMethodInfoBuilder.callsMethod(implIdent, methodIdent)
+ js.TraitImplApply(jstpe.ClassType(implIdent.name), methodIdent,
+ arguments)(resultType)
+ }
+
+ /** Gen JS code for a conversion between primitive value types */
+ def genConversion(from: TypeKind, to: TypeKind, value: js.Tree)(
+ implicit pos: Position): js.Tree = {
+ def int0 = js.IntLiteral(0)
+ def int1 = js.IntLiteral(1)
+ def long0 = js.LongLiteral(0L)
+ def long1 = js.LongLiteral(1L)
+ def float0 = js.FloatLiteral(0.0f)
+ def float1 = js.FloatLiteral(1.0f)
+
+ (from, to) match {
+ case (INT(_), BOOL) => js.BinaryOp(js.BinaryOp.Num_!=, value, int0)
+ case (LONG, BOOL) => js.BinaryOp(js.BinaryOp.Long_!=, value, long0)
+ case (FLOAT(_), BOOL) => js.BinaryOp(js.BinaryOp.Num_!=, value, float0)
+
+ case (BOOL, INT(_)) => js.If(value, int1, int0 )(jstpe.IntType)
+ case (BOOL, LONG) => js.If(value, long1, long0 )(jstpe.LongType)
+ case (BOOL, FLOAT(_)) => js.If(value, float1, float0)(jstpe.FloatType)
+
+ case _ => value
+ }
+ }
+
+ /** Gen JS code for an isInstanceOf test (for reference types only) */
+ def genIsInstanceOf(value: js.Tree, to: Type)(
+ implicit pos: Position): js.Tree = {
+
+ def genTypeOfTest(typeString: String) = {
+ js.BinaryOp(js.BinaryOp.===,
+ js.UnaryOp(js.UnaryOp.typeof, value),
+ js.StringLiteral(typeString))
+ }
+
+ if (isRawJSType(to)) {
+ to.typeSymbol match {
+ case JSNumberClass => genTypeOfTest("number")
+ case JSStringClass => genTypeOfTest("string")
+ case JSBooleanClass => genTypeOfTest("boolean")
+ case JSUndefinedClass => genTypeOfTest("undefined")
+ case sym if sym.isTrait =>
+ reporter.error(pos,
+ s"isInstanceOf[${sym.fullName}] not supported because it is a raw JS trait")
+ js.BooleanLiteral(true)
+ case sym =>
+ js.BinaryOp(js.BinaryOp.instanceof, value, genGlobalJSObject(sym))
+ }
+ } else {
+ val refType = toReferenceType(to)
+ currentMethodInfoBuilder.accessesClassData(refType)
+ js.IsInstanceOf(value, refType)
+ }
+ }
+
+ /** Gen JS code for an asInstanceOf cast (for reference types only) */
+ def genAsInstanceOf(value: js.Tree, to: Type)(
+ implicit pos: Position): js.Tree = {
+
+ def default: js.Tree = {
+ val refType = toReferenceType(to)
+ currentMethodInfoBuilder.accessesClassData(refType)
+ js.AsInstanceOf(value, refType)
+ }
+
+ if (isRawJSType(to)) {
+ // asInstanceOf on JavaScript is completely erased
+ value
+ } else if (FunctionClass.seq contains to.typeSymbol) {
+ /* Don't hide a JSFunctionToScala inside a useless cast, otherwise
+ * the optimization avoiding double-wrapping in genApply() will not
+ * be able to kick in.
+ */
+ value match {
+ case JSFunctionToScala(fun, _) => value
+ case _ => default
+ }
+ } else {
+ default
+ }
+ }
+
+ /** Gen JS code for a call to a Scala method.
+ * This also registers that the given method is called by the current
+ * method in the method info builder.
+ */
+ def genApplyMethod(receiver: js.Tree, receiverType: Type,
+ methodSym: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ genApplyMethod(receiver, receiverType.typeSymbol, methodSym, arguments)
+ }
+
+ /** Gen JS code for a call to a Scala method.
+ * This also registers that the given method is called by the current
+ * method in the method info builder.
+ */
+ def genApplyMethod(receiver: js.Tree, receiverTypeSym: Symbol,
+ methodSym: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ genApplyMethod(receiver, receiverTypeSym,
+ encodeMethodSym(methodSym), arguments,
+ toIRType(methodSym.tpe.resultType))
+ }
+
+ /** Gen JS code for a call to a Scala method.
+ * This also registers that the given method is called by the current
+ * method in the method info builder.
+ */
+ def genApplyMethod(receiver: js.Tree, receiverType: Type,
+ methodIdent: js.Ident, arguments: List[js.Tree], resultType: jstpe.Type)(
+ implicit pos: Position): js.Tree = {
+ genApplyMethod(receiver, receiverType.typeSymbol, methodIdent,
+ arguments, resultType)
+ }
+
+ /** Gen JS code for a call to a Scala method.
+ * This also registers that the given method is called by the current
+ * method in the method info builder.
+ */
+ def genApplyMethod(receiver: js.Tree, receiverTypeSym: Symbol,
+ methodIdent: js.Ident, arguments: List[js.Tree], resultType: jstpe.Type)(
+ implicit pos: Position): js.Tree = {
+ currentMethodInfoBuilder.callsMethod(receiverTypeSym, methodIdent)
+ js.Apply(receiver, methodIdent, arguments)(resultType)
+ }
+
+ /** Gen JS code for a call to a Scala class constructor.
+ *
+ * This also registers that the given class is instantiated by the current
+ * method, and that the given constructor is called, in the method info
+ * builder.
+ */
+ def genNew(clazz: Symbol, ctor: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ if (clazz.isAnonymousFunction)
+ instantiatedAnonFunctions += clazz
+ assert(!isRawJSFunctionDef(clazz),
+ s"Trying to instantiate a raw JS function def $clazz")
+ val ctorIdent = encodeMethodSym(ctor)
+ currentMethodInfoBuilder.instantiatesClass(clazz)
+ currentMethodInfoBuilder.callsMethod(clazz, ctorIdent)
+ js.New(jstpe.ClassType(encodeClassFullName(clazz)),
+ ctorIdent, arguments)
+ }
+
+ /** Gen JS code for a call to a constructor of a hijacked boxed class.
+ * All of these have 2 constructors: one with the primitive
+ * value, which is erased, and one with a String, which is
+ * equivalent to BoxedClass.valueOf(arg).
+ */
+ private def genNewHijackedBoxedClass(clazz: Symbol, ctor: Symbol,
+ arguments: List[js.Tree])(implicit pos: Position): js.Tree = {
+ assert(arguments.size == 1)
+ if (isStringType(ctor.tpe.params.head.tpe)) {
+ // BoxedClass.valueOf(arg)
+ val companion = clazz.companionModule.moduleClass
+ val valueOf = getMemberMethod(companion, nme.valueOf) suchThat { s =>
+ s.tpe.params.size == 1 && isStringType(s.tpe.params.head.tpe)
+ }
+ genApplyMethod(genLoadModule(companion), companion, valueOf, arguments)
+ } else {
+ // erased
+ arguments.head
+ }
+ }
+
+ /** Gen JS code for creating a new Array: new Array[T](length)
+ * For multidimensional arrays (dimensions > 1), the arguments can
+ * specify up to `dimensions` lengths for the first dimensions of the
+ * array.
+ */
+ def genNewArray(arrayType: jstpe.ArrayType, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ assert(arguments.length <= arrayType.dimensions,
+ "too many arguments for array constructor: found " + arguments.length +
+ " but array has only " + arrayType.dimensions + " dimension(s)")
+
+ currentMethodInfoBuilder.accessesClassData(arrayType)
+ js.NewArray(arrayType, arguments)
+ }
+
+ /** Gen JS code for an array literal.
+ */
+ def genArrayValue(tree: Tree): js.Tree = {
+ implicit val pos = tree.pos
+ val ArrayValue(tpt @ TypeTree(), elems) = tree
+
+ val arrType = toReferenceType(tree.tpe).asInstanceOf[jstpe.ArrayType]
+ currentMethodInfoBuilder.accessesClassData(arrType)
+ js.ArrayValue(arrType, elems map genExpr)
+ }
+
+ /** Gen JS code for a Match, i.e., a switch-able pattern match
+ * Eventually, this is compiled into a JS switch construct. But because
+ * we can be in expression position, and a JS switch cannot be given a
+ * meaning in expression position, we emit a JS "match" construct (which
+ * does not need the `break`s in each case. `JSDesugaring` will transform
+ * that in a switch.
+ *
+ * Some caveat here. It may happen that there is a guard in here, despite
+ * the fact that switches cannot have guards (in the JVM nor in JS).
+ * The JVM backend emits a jump to the default clause when a guard is not
+ * fulfilled. We cannot do that. Instead, currently we duplicate the body
+ * of the default case in the else branch of the guard test.
+ */
+ def genMatch(tree: Tree, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Match(selector, cases) = tree
+
+ val expr = genExpr(selector)
+ val resultType = toIRType(tree.tpe)
+
+ val List(defaultBody0) = for {
+ CaseDef(Ident(nme.WILDCARD), EmptyTree, body) <- cases
+ } yield body
+
+ val (defaultBody, defaultLabelSym) = defaultBody0 match {
+ case LabelDef(_, Nil, rhs) if hasSynthCaseSymbol(defaultBody0) =>
+ (rhs, defaultBody0.symbol)
+ case _ =>
+ (defaultBody0, NoSymbol)
+ }
+
+ val genDefaultBody = genStatOrExpr(defaultBody, isStat)
+
+ var clauses: List[(List[js.Literal], js.Tree)] = Nil
+ var elseClause: js.Tree = js.EmptyTree
+
+ for (caze @ CaseDef(pat, guard, body) <- cases) {
+ assert(guard == EmptyTree)
+
+ def genBody() = body match {
+ // Yes, this will duplicate the default body in the output
+ case If(cond, thenp, app @ Apply(_, Nil))
+ if app.symbol == defaultLabelSym =>
+ js.If(genExpr(cond), genStatOrExpr(thenp, isStat), genDefaultBody)(
+ resultType)(body.pos)
+ case If(cond, thenp, Block(List(app @ Apply(_, Nil)), _))
+ if app.symbol == defaultLabelSym =>
+ js.If(genExpr(cond), genStatOrExpr(thenp, isStat), genDefaultBody)(
+ resultType)(body.pos)
+
+ case _ =>
+ genStatOrExpr(body, isStat)
+ }
+
+ def genLiteral(lit: Literal): js.Literal =
+ genExpr(lit).asInstanceOf[js.Literal]
+
+ pat match {
+ case lit: Literal =>
+ clauses = (List(genLiteral(lit)), genBody()) :: clauses
+ case Ident(nme.WILDCARD) =>
+ elseClause = genDefaultBody
+ case Alternative(alts) =>
+ val genAlts = {
+ alts map {
+ case lit: Literal => genLiteral(lit)
+ case _ =>
+ abort("Invalid case in alternative in switch-like pattern match: " +
+ tree + " at: " + tree.pos)
+ }
+ }
+ clauses = (genAlts, genBody()) :: clauses
+ case _ =>
+ abort("Invalid case statement in switch-like pattern match: " +
+ tree + " at: " + (tree.pos))
+ }
+ }
+
+ js.Match(expr, clauses.reverse, elseClause)(resultType)
+ }
+
+ private def genBlock(tree: Block, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Block(stats, expr) = tree
+
+ /** Predicate satisfied by LabelDefs produced by the pattern matcher */
+ def isCaseLabelDef(tree: Tree) =
+ tree.isInstanceOf[LabelDef] && hasSynthCaseSymbol(tree)
+
+ def translateMatch(expr: LabelDef) = {
+ /* Block that appeared as the result of a translated match
+ * Such blocks are recognized by having at least one element that is
+ * a so-called case-label-def.
+ * The method `genTranslatedMatch()` takes care of compiling the
+ * actual match.
+ *
+ * The assumption is once we encounter a case, the remainder of the
+ * block will consist of cases.
+ * The prologue may be empty, usually it is the valdef that stores
+ * the scrut.
+ */
+ val (prologue, cases) = stats.span(s => !isCaseLabelDef(s))
+ assert(cases.forall(isCaseLabelDef),
+ "Assumption on the form of translated matches broken: " + tree)
+
+ val genPrologue = prologue map genStat
+ val translatedMatch =
+ genTranslatedMatch(cases.map(_.asInstanceOf[LabelDef]), expr)
+
+ js.Block(genPrologue :+ translatedMatch)
+ }
+
+ expr match {
+ case expr: LabelDef if isCaseLabelDef(expr) =>
+ translateMatch(expr)
+
+ // Sometimes the pattern matcher casts its final result
+ case Apply(TypeApply(Select(expr: LabelDef, nme.asInstanceOf_Ob), _), _)
+ if isCaseLabelDef(expr) =>
+ translateMatch(expr)
+
+ case _ =>
+ assert(!stats.exists(isCaseLabelDef), "Found stats with case label " +
+ s"def in non-match block at ${tree.pos}: $tree")
+
+ /* Normal block */
+ val statements = stats map genStat
+ val expression = genStatOrExpr(expr, isStat)
+ js.Block(statements :+ expression)
+ }
+ }
+
+ /** Gen JS code for a translated match
+ *
+ * This implementation relies heavily on the patterns of trees emitted
+ * by the current pattern match phase (as of Scala 2.10).
+ *
+ * The trees output by the pattern matcher are assumed to follow these
+ * rules:
+ * * Each case LabelDef (in `cases`) must not take any argument.
+ * * The last one must be a catch-all (case _ =>) that never falls through.
+ * * Jumps to the `matchEnd` are allowed anywhere in the body of the
+ * corresponding case label-defs, but not outside.
+ * * Jumps to case label-defs are restricted to jumping to the very next
+ * case, and only in positions denoted by <jump> in:
+ * <case-body> ::=
+ * If(_, <case-body>, <case-body>)
+ * | Block(_, <case-body>)
+ * | <jump>
+ * | _
+ * These restrictions, together with the fact that we are in statement
+ * position (thanks to the above transformation), mean that they can be
+ * simply replaced by `skip`.
+ *
+ * To implement jumps to `matchEnd`, which have one argument which is the
+ * result of the match, we enclose all the cases in one big labeled block.
+ * Jumps are then compiled as `return`s out of the block.
+ */
+ def genTranslatedMatch(cases: List[LabelDef],
+ matchEnd: LabelDef)(implicit pos: Position): js.Tree = {
+
+ val nextCaseSyms = (cases.tail map (_.symbol)) :+ NoSymbol
+
+ val translatedCases = for {
+ (LabelDef(_, Nil, rhs), nextCaseSym) <- cases zip nextCaseSyms
+ } yield {
+ def genCaseBody(tree: Tree): js.Tree = {
+ implicit val pos = tree.pos
+ tree match {
+ case If(cond, thenp, elsep) =>
+ js.If(genExpr(cond), genCaseBody(thenp), genCaseBody(elsep))(
+ jstpe.NoType)
+
+ case Block(stats, expr) =>
+ js.Block((stats map genStat) :+ genCaseBody(expr))
+
+ case Apply(_, Nil) if tree.symbol == nextCaseSym =>
+ js.Skip()
+
+ case _ =>
+ genStat(tree)
+ }
+ }
+
+ genCaseBody(rhs)
+ }
+
+ js.Labeled(encodeLabelSym(matchEnd.symbol), toIRType(matchEnd.tpe),
+ js.Block(translatedCases))
+ }
+
+ /** Gen JS code for a primitive method call */
+ private def genPrimitiveOp(tree: Apply, isStat: Boolean): js.Tree = {
+ import scalaPrimitives._
+
+ implicit val pos = tree.pos
+
+ val sym = tree.symbol
+ val Apply(fun @ Select(receiver, _), args) = tree
+
+ val code = scalaPrimitives.getPrimitive(sym, receiver.tpe)
+
+ if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code))
+ genSimpleOp(tree, receiver :: args, code)
+ else if (code == scalaPrimitives.CONCAT)
+ genStringConcat(tree, receiver, args)
+ else if (code == HASH)
+ genScalaHash(tree, receiver)
+ else if (isArrayOp(code))
+ genArrayOp(tree, code)
+ else if (code == SYNCHRONIZED)
+ genSynchronized(tree, isStat)
+ else if (isCoercion(code))
+ genCoercion(tree, receiver, code)
+ else if (jsPrimitives.isJavaScriptPrimitive(code))
+ genJSPrimitive(tree, receiver, args, code)
+ else
+ abort("Unknown primitive operation: " + sym.fullName + "(" +
+ fun.symbol.simpleName + ") " + " at: " + (tree.pos))
+ }
+
+ /** Gen JS code for a simple operation (arithmetic, logical, or comparison) */
+ private def genSimpleOp(tree: Apply, args: List[Tree], code: Int): js.Tree = {
+ import scalaPrimitives._
+
+ implicit val pos = tree.pos
+
+ def isLongOp(ltpe: Type, rtpe: Type) =
+ (isLongType(ltpe) || isLongType(rtpe)) &&
+ !(toTypeKind(ltpe).isInstanceOf[FLOAT] ||
+ toTypeKind(rtpe).isInstanceOf[FLOAT] ||
+ isStringType(ltpe) || isStringType(rtpe))
+
+ val sources = args map genExpr
+
+ val resultType = toIRType(tree.tpe)
+
+ sources match {
+ // Unary operation
+ case List(source) =>
+ (code match {
+ case POS =>
+ source
+ case NEG =>
+ (resultType: @unchecked) match {
+ case jstpe.IntType =>
+ js.BinaryOp(js.BinaryOp.Int_-, js.IntLiteral(0), source)
+ case jstpe.LongType =>
+ js.BinaryOp(js.BinaryOp.Long_-, js.LongLiteral(0), source)
+ case jstpe.FloatType =>
+ js.BinaryOp(js.BinaryOp.Float_-, js.FloatLiteral(0.0f), source)
+ case jstpe.DoubleType =>
+ js.BinaryOp(js.BinaryOp.Double_-, js.DoubleLiteral(0), source)
+ }
+ case NOT =>
+ (resultType: @unchecked) match {
+ case jstpe.IntType =>
+ js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(-1), source)
+ case jstpe.LongType =>
+ js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(-1), source)
+ }
+ case ZNOT =>
+ js.UnaryOp(js.UnaryOp.Boolean_!, source)
+ case _ =>
+ abort("Unknown unary operation code: " + code)
+ })
+
+ // Binary operation on Longs
+ case List(lsrc, rsrc) if isLongOp(args(0).tpe, args(1).tpe) =>
+ def toLong(tree: js.Tree, tpe: Type) =
+ if (isLongType(tpe)) tree
+ else js.UnaryOp(js.UnaryOp.IntToLong, tree)
+
+ def toInt(tree: js.Tree, tpe: Type) =
+ if (isLongType(tpe)) js.UnaryOp(js.UnaryOp.LongToInt, rsrc)
+ else tree
+
+ val ltree = toLong(lsrc, args(0).tpe)
+ def rtree = toLong(rsrc, args(1).tpe)
+ def rtreeInt = toInt(rsrc, args(1).tpe)
+
+ import js.BinaryOp._
+ (code: @switch) match {
+ case ADD => js.BinaryOp(Long_+, ltree, rtree)
+ case SUB => js.BinaryOp(Long_-, ltree, rtree)
+ case MUL => js.BinaryOp(Long_*, ltree, rtree)
+ case DIV => js.BinaryOp(Long_/, ltree, rtree)
+ case MOD => js.BinaryOp(Long_%, ltree, rtree)
+ case OR => js.BinaryOp(Long_|, ltree, rtree)
+ case XOR => js.BinaryOp(Long_^, ltree, rtree)
+ case AND => js.BinaryOp(Long_&, ltree, rtree)
+ case LSL => js.BinaryOp(Long_<<, ltree, rtreeInt)
+ case LSR => js.BinaryOp(Long_>>>, ltree, rtreeInt)
+ case ASR => js.BinaryOp(Long_>>, ltree, rtreeInt)
+ case EQ => js.BinaryOp(Long_==, ltree, rtree)
+ case NE => js.BinaryOp(Long_!=, ltree, rtree)
+ case LT => js.BinaryOp(Long_<, ltree, rtree)
+ case LE => js.BinaryOp(Long_<=, ltree, rtree)
+ case GT => js.BinaryOp(Long_>, ltree, rtree)
+ case GE => js.BinaryOp(Long_>=, ltree, rtree)
+ case _ =>
+ abort("Unknown binary operation code: " + code)
+ }
+
+ // Binary operation
+ case List(lsrc_in, rsrc_in) =>
+ def convertArg(tree: js.Tree, tpe: Type) = {
+ val kind = toTypeKind(tpe)
+
+ // If we end up with a long, target must be float or double
+ val fromLong =
+ if (kind == LongKind) js.UnaryOp(js.UnaryOp.LongToDouble, tree)
+ else tree
+
+ if (resultType != jstpe.FloatType) fromLong
+ else if (kind == FloatKind) fromLong
+ else js.UnaryOp(js.UnaryOp.DoubleToFloat, fromLong)
+ }
+
+ val lsrc = convertArg(lsrc_in, args(0).tpe)
+ val rsrc = convertArg(rsrc_in, args(1).tpe)
+
+ def genEquality(eqeq: Boolean, not: Boolean) = {
+ val typeKind = toTypeKind(args(0).tpe)
+ typeKind match {
+ case INT(_) | LONG | FLOAT(_) =>
+ /* Note that LONG happens when a fromLong() had to do something,
+ * which means we're effectively in the FLOAT case. */
+ js.BinaryOp(if (not) js.BinaryOp.Num_!= else js.BinaryOp.Num_==, lsrc, rsrc)
+ case BOOL =>
+ js.BinaryOp(if (not) js.BinaryOp.Boolean_!= else js.BinaryOp.Boolean_==, lsrc, rsrc)
+ case REFERENCE(_) =>
+ if (eqeq &&
+ // don't call equals if we have a literal null at either side
+ !lsrc.isInstanceOf[js.Null] &&
+ !rsrc.isInstanceOf[js.Null]) {
+ val body = genEqEqPrimitive(args(0).tpe, args(1).tpe, lsrc, rsrc)
+ if (not) js.UnaryOp(js.UnaryOp.Boolean_!, body) else body
+ } else {
+ js.BinaryOp(if (not) js.BinaryOp.!== else js.BinaryOp.===, lsrc, rsrc)
+ }
+ case _ =>
+ // Arrays, Null, Nothing do not have an equals() method.
+ js.BinaryOp(if (not) js.BinaryOp.!== else js.BinaryOp.===, lsrc, rsrc)
+ }
+ }
+
+ (code: @switch) match {
+ case EQ => genEquality(eqeq = true, not = false)
+ case NE => genEquality(eqeq = true, not = true)
+ case ID => genEquality(eqeq = false, not = false)
+ case NI => genEquality(eqeq = false, not = true)
+
+ case ZOR => js.If(lsrc, js.BooleanLiteral(true), rsrc)(jstpe.BooleanType)
+ case ZAND => js.If(lsrc, rsrc, js.BooleanLiteral(false))(jstpe.BooleanType)
+
+ case _ =>
+ import js.BinaryOp._
+ val op = (resultType: @unchecked) match {
+ case jstpe.IntType =>
+ (code: @switch) match {
+ case ADD => Int_+
+ case SUB => Int_-
+ case MUL => Int_*
+ case DIV => Int_/
+ case MOD => Int_%
+ case OR => Int_|
+ case AND => Int_&
+ case XOR => Int_^
+ case LSL => Int_<<
+ case LSR => Int_>>>
+ case ASR => Int_>>
+ }
+ case jstpe.FloatType =>
+ (code: @switch) match {
+ case ADD => Float_+
+ case SUB => Float_-
+ case MUL => Float_*
+ case DIV => Float_/
+ case MOD => Float_%
+ }
+ case jstpe.DoubleType =>
+ (code: @switch) match {
+ case ADD => Double_+
+ case SUB => Double_-
+ case MUL => Double_*
+ case DIV => Double_/
+ case MOD => Double_%
+ }
+ case jstpe.BooleanType =>
+ (code: @switch) match {
+ case LT => Num_<
+ case LE => Num_<=
+ case GT => Num_>
+ case GE => Num_>=
+ case OR => Boolean_|
+ case AND => Boolean_&
+ case XOR => Boolean_!=
+ }
+ }
+ js.BinaryOp(op, lsrc, rsrc)
+ }
+
+ case _ =>
+ abort("Too many arguments for primitive function: " + tree)
+ }
+ }
+
+ /** Gen JS code for a call to Any.== */
+ def genEqEqPrimitive(ltpe: Type, rtpe: Type, lsrc: js.Tree, rsrc: js.Tree)(
+ implicit pos: Position): js.Tree = {
+ /* True if the equality comparison is between values that require the
+ * use of the rich equality comparator
+ * (scala.runtime.BoxesRunTime.equals).
+ * This is the case when either side of the comparison might have a
+ * run-time type subtype of java.lang.Number or java.lang.Character,
+ * **which includes when either is a raw JS type**.
+ * When it is statically known that both sides are equal and subtypes of
+ * Number or Character, not using the rich equality is possible (their
+ * own equals method will do ok.)
+ */
+ val mustUseAnyComparator: Boolean = isRawJSType(ltpe) || isRawJSType(rtpe) || {
+ val areSameFinals = ltpe.isFinalType && rtpe.isFinalType && (ltpe =:= rtpe)
+ !areSameFinals && isMaybeBoxed(ltpe.typeSymbol) && isMaybeBoxed(rtpe.typeSymbol)
+ }
+
+ if (mustUseAnyComparator) {
+ val equalsMethod: Symbol = {
+ val ptfm = platform.asInstanceOf[backend.JavaPlatform with ThisPlatform] // 2.10 compat
+ if (ltpe <:< BoxedNumberClass.tpe) {
+ if (rtpe <:< BoxedNumberClass.tpe) ptfm.externalEqualsNumNum
+ else if (rtpe <:< BoxedCharacterClass.tpe) ptfm.externalEqualsNumChar
+ else ptfm.externalEqualsNumObject
+ } else ptfm.externalEquals
+ }
+ val moduleClass = equalsMethod.owner
+ val instance = genLoadModule(moduleClass)
+ genApplyMethod(instance, moduleClass, equalsMethod, List(lsrc, rsrc))
+ } else {
+ // if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc)
+ if (isStringType(ltpe)) {
+ // String.equals(that) === (this eq that)
+ js.BinaryOp(js.BinaryOp.===, lsrc, rsrc)
+ } else {
+ /* This requires to evaluate both operands in local values first.
+ * The optimizer will eliminate them if possible.
+ */
+ val ltemp = js.VarDef(freshLocalIdent(), lsrc.tpe, mutable = false, lsrc)
+ val rtemp = js.VarDef(freshLocalIdent(), rsrc.tpe, mutable = false, rsrc)
+ js.Block(
+ ltemp,
+ rtemp,
+ js.If(js.BinaryOp(js.BinaryOp.===, ltemp.ref, js.Null()),
+ js.BinaryOp(js.BinaryOp.===, rtemp.ref, js.Null()),
+ genApplyMethod(ltemp.ref, ltpe, Object_equals, List(rtemp.ref)))(
+ jstpe.BooleanType))
+ }
+ }
+ }
+
+ /** Gen JS code for string concatenation.
+ */
+ private def genStringConcat(tree: Apply, receiver: Tree,
+ args: List[Tree]): js.Tree = {
+ implicit val pos = tree.pos
+
+ /* Primitive number types such as scala.Int have a
+ * def +(s: String): String
+ * method, which is why we have to box the lhs sometimes.
+ * Otherwise, both lhs and rhs are already reference types (Any of String)
+ * so boxing is not necessary (in particular, rhs is never a primitive).
+ */
+ assert(!isPrimitiveValueType(receiver.tpe) || isStringType(args.head.tpe))
+ assert(!isPrimitiveValueType(args.head.tpe))
+
+ val rhs = genExpr(args.head)
+
+ val lhs = {
+ val lhs0 = genExpr(receiver)
+ // Box the receiver if it is a primitive value
+ if (!isPrimitiveValueType(receiver.tpe)) lhs0
+ else makePrimitiveBox(lhs0, receiver.tpe)
+ }
+
+ js.BinaryOp(js.BinaryOp.String_+, lhs, rhs)
+ }
+
+ /** Gen JS code for a call to Any.## */
+ private def genScalaHash(tree: Apply, receiver: Tree): js.Tree = {
+ implicit val pos = tree.pos
+
+ val instance = genLoadModule(ScalaRunTimeModule)
+ val arguments = List(genExpr(receiver))
+ val sym = getMember(ScalaRunTimeModule, nme.hash_)
+
+ genApplyMethod(instance, ScalaRunTimeModule.moduleClass, sym, arguments)
+ }
+
+ /** Gen JS code for an array operation (get, set or length) */
+ private def genArrayOp(tree: Tree, code: Int): js.Tree = {
+ import scalaPrimitives._
+
+ implicit val pos = tree.pos
+
+ val Apply(Select(arrayObj, _), args) = tree
+ val arrayValue = genExpr(arrayObj)
+ val arguments = args map genExpr
+
+ def genSelect() = {
+ val elemIRType =
+ toTypeKind(arrayObj.tpe).asInstanceOf[ARRAY].elem.toIRType
+ js.ArraySelect(arrayValue, arguments(0))(elemIRType)
+ }
+
+ if (scalaPrimitives.isArrayGet(code)) {
+ // get an item of the array
+ assert(args.length == 1,
+ s"Array get requires 1 argument, found ${args.length} in $tree")
+ genSelect()
+ } else if (scalaPrimitives.isArraySet(code)) {
+ // set an item of the array
+ assert(args.length == 2,
+ s"Array set requires 2 arguments, found ${args.length} in $tree")
+ js.Assign(genSelect(), arguments(1))
+ } else {
+ // length of the array
+ js.ArrayLength(arrayValue)
+ }
+ }
+
+ /** Gen JS code for a call to AnyRef.synchronized */
+ private def genSynchronized(tree: Apply, isStat: Boolean): js.Tree = {
+ /* JavaScript is single-threaded, so we can drop the
+ * synchronization altogether.
+ */
+ val Apply(Select(receiver, _), List(arg)) = tree
+ val newReceiver = genExpr(receiver)
+ val newArg = genStatOrExpr(arg, isStat)
+ newReceiver match {
+ case js.This() =>
+ // common case for which there is no side-effect nor NPE
+ newArg
+ case _ =>
+ implicit val pos = tree.pos
+ val NPECtor = getMemberMethod(NullPointerExceptionClass,
+ nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty)
+ js.Block(
+ js.If(js.BinaryOp(js.BinaryOp.===, newReceiver, js.Null()),
+ js.Throw(genNew(NullPointerExceptionClass, NPECtor, Nil)),
+ js.Skip())(jstpe.NoType),
+ newArg)
+ }
+ }
+
+ /** Gen JS code for a coercion */
+ private def genCoercion(tree: Apply, receiver: Tree, code: Int): js.Tree = {
+ import scalaPrimitives._
+
+ implicit val pos = tree.pos
+
+ val source = genExpr(receiver)
+
+ def source2int = (code: @switch) match {
+ case F2C | D2C | F2B | D2B | F2S | D2S | F2I | D2I =>
+ js.UnaryOp(js.UnaryOp.DoubleToInt, source)
+ case L2C | L2B | L2S | L2I =>
+ js.UnaryOp(js.UnaryOp.LongToInt, source)
+ case _ =>
+ source
+ }
+
+ (code: @switch) match {
+ // To Char, need to crop at unsigned 16-bit
+ case B2C | S2C | I2C | L2C | F2C | D2C =>
+ js.BinaryOp(js.BinaryOp.Int_&, source2int, js.IntLiteral(0xffff))
+
+ // To Byte, need to crop at signed 8-bit
+ case C2B | S2B | I2B | L2B | F2B | D2B =>
+ // note: & 0xff would not work because of negative values
+ js.BinaryOp(js.BinaryOp.Int_>>,
+ js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(24)),
+ js.IntLiteral(24))
+
+ // To Short, need to crop at signed 16-bit
+ case C2S | I2S | L2S | F2S | D2S =>
+ // note: & 0xffff would not work because of negative values
+ js.BinaryOp(js.BinaryOp.Int_>>,
+ js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(16)),
+ js.IntLiteral(16))
+
+ // To Int, need to crop at signed 32-bit
+ case L2I | F2I | D2I =>
+ source2int
+
+ // Any int to Long
+ case C2L | B2L | S2L | I2L =>
+ js.UnaryOp(js.UnaryOp.IntToLong, source)
+
+ // Any double to Long
+ case F2L | D2L =>
+ js.UnaryOp(js.UnaryOp.DoubleToLong, source)
+
+ // Long to Double
+ case L2D =>
+ js.UnaryOp(js.UnaryOp.LongToDouble, source)
+
+ // Any int, or Double, to Float
+ case C2F | B2F | S2F | I2F | D2F =>
+ js.UnaryOp(js.UnaryOp.DoubleToFloat, source)
+
+ // Long to Float === Long to Double to Float
+ case L2F =>
+ js.UnaryOp(js.UnaryOp.DoubleToFloat,
+ js.UnaryOp(js.UnaryOp.LongToDouble, source))
+
+ // Identities and IR upcasts
+ case C2C | B2B | S2S | I2I | L2L | F2F | D2D |
+ C2I | C2D |
+ B2S | B2I | B2D |
+ S2I | S2D |
+ I2D |
+ F2D =>
+ source
+ }
+ }
+
+ /** Gen JS code for an ApplyDynamic
+ * ApplyDynamic nodes appear as the result of calls to methods of a
+ * structural type.
+ *
+ * Most unfortunately, earlier phases of the compiler assume too much
+ * about the backend, namely, they believe arguments and the result must
+ * be boxed, and do the boxing themselves. This decision should be left
+ * to the backend, but it's not, so we have to undo these boxes.
+ * Note that this applies to parameter types only. The return type is boxed
+ * anyway since we do not know it's exact type.
+ *
+ * This then generates a call to the reflective call proxy for the given
+ * arguments.
+ */
+ private def genApplyDynamic(tree: ApplyDynamic): js.Tree = {
+ implicit val pos = tree.pos
+
+ val sym = tree.symbol
+ val params = sym.tpe.params
+
+ /** check if the method we are invoking is eq or ne. they cannot be
+ * overridden since they are final. If this is true, we only emit a
+ * `===` or `!==`.
+ */
+ val isEqOrNeq = (sym.name == nme.eq || sym.name == nme.ne) &&
+ params.size == 1 && params.head.tpe.typeSymbol == ObjectClass
+
+ /** check if the method we are invoking conforms to a method on
+ * scala.Array. If this is the case, we check that case specially at
+ * runtime to avoid having reflective call proxies on scala.Array.
+ * (Also, note that the element type of Array#update is not erased and
+ * therefore the method name mangling would turn out wrong)
+ *
+ * Note that we cannot check if the expected return type is correct,
+ * since this type information is already erased.
+ */
+ def isArrayLikeOp = {
+ sym.name == nme.update &&
+ params.size == 2 && params.head.tpe.typeSymbol == IntClass ||
+ sym.name == nme.apply &&
+ params.size == 1 && params.head.tpe.typeSymbol == IntClass ||
+ sym.name == nme.length &&
+ params.size == 0 ||
+ sym.name == nme.clone_ &&
+ params.size == 0
+ }
+
+ /**
+ * Tests whether one of our reflective "boxes" for primitive types
+ * implements the particular method. If this is the case
+ * (result != NoSymbol), we generate a runtime instance check if we are
+ * dealing with the appropriate primitive type.
+ */
+ def matchingSymIn(clazz: Symbol) = clazz.tpe.member(sym.name).suchThat { s =>
+ val sParams = s.tpe.params
+ !s.isBridge &&
+ params.size == sParams.size &&
+ (params zip sParams).forall { case (s1,s2) =>
+ s1.tpe =:= s2.tpe
+ }
+ }
+
+ val ApplyDynamic(receiver, args) = tree
+
+ if (isEqOrNeq) {
+ // Just emit a boxed equality check
+ val jsThis = genExpr(receiver)
+ val jsThat = genExpr(args.head)
+ val op = if (sym.name == nme.eq) js.BinaryOp.=== else js.BinaryOp.!==
+ ensureBoxed(js.BinaryOp(op, jsThis, jsThat), BooleanClass.tpe)
+ } else {
+ // Create a fully-fledged reflective call
+ val receiverType = toIRType(receiver.tpe)
+ val callTrgIdent = freshLocalIdent()
+ val callTrgVarDef =
+ js.VarDef(callTrgIdent, receiverType, mutable = false, genExpr(receiver))
+ val callTrg = js.VarRef(callTrgIdent, mutable = false)(receiverType)
+
+ val arguments = args zip sym.tpe.params map { case (arg, param) =>
+ /* No need for enteringPosterasure, because value classes are not
+ * supported as parameters of methods in structural types.
+ * We could do it for safety and future-proofing anyway, except that
+ * I am weary of calling enteringPosterasure for a reflective method
+ * symbol.
+ *
+ * Note also that this will typically unbox a primitive value that
+ * has just been boxed, or will .asInstanceOf[T] an expression which
+ * is already of type T. But the optimizer will get rid of that, and
+ * reflective calls are not numerous, so we don't complicate the
+ * compiler to eliminate them early.
+ */
+ fromAny(genExpr(arg), param.tpe)
+ }
+
+ val proxyIdent = encodeMethodSym(sym, reflProxy = true)
+ var callStatement: js.Tree =
+ genApplyMethod(callTrg, receiver.tpe, proxyIdent, arguments,
+ jstpe.AnyType)
+
+ if (isArrayLikeOp) {
+ def genRTCall(method: Symbol, args: js.Tree*) =
+ genApplyMethod(genLoadModule(ScalaRunTimeModule),
+ ScalaRunTimeModule.moduleClass, method, args.toList)
+ val isArrayTree =
+ genRTCall(ScalaRunTime_isArray, callTrg, js.IntLiteral(1))
+ callStatement = js.If(isArrayTree, {
+ sym.name match {
+ case nme.update =>
+ js.Block(
+ genRTCall(currentRun.runDefinitions.arrayUpdateMethod,
+ callTrg, arguments(0), arguments(1)),
+ js.Undefined()) // Boxed Unit
+ case nme.apply =>
+ genRTCall(currentRun.runDefinitions.arrayApplyMethod, callTrg,
+ arguments(0))
+ case nme.length =>
+ genRTCall(currentRun.runDefinitions.arrayLengthMethod, callTrg)
+ case nme.clone_ =>
+ genApplyMethod(callTrg, receiver.tpe, Object_clone, arguments)
+ }
+ }, {
+ callStatement
+ })(jstpe.AnyType)
+ }
+
+ for {
+ (primTypeOf, reflBoxClass) <- Seq(
+ ("string", StringClass),
+ ("number", NumberReflectiveCallClass),
+ ("boolean", BooleanReflectiveCallClass)
+ )
+ implMethodSym = matchingSymIn(reflBoxClass)
+ if implMethodSym != NoSymbol && implMethodSym.isPublic
+ } {
+ callStatement = js.If(
+ js.BinaryOp(js.BinaryOp.===,
+ js.UnaryOp(js.UnaryOp.typeof, callTrg),
+ js.StringLiteral(primTypeOf)), {
+ if (implMethodSym.owner == ObjectClass) {
+ // If the method is defined on Object, we can call it normally.
+ genApplyMethod(callTrg, receiver.tpe, implMethodSym, arguments)
+ } else {
+ if (primTypeOf == "string") {
+ val (rtModuleClass, methodIdent) =
+ encodeRTStringMethodSym(implMethodSym)
+ val retTpe = implMethodSym.tpe.resultType
+ val castCallTrg = fromAny(callTrg, StringClass.toTypeConstructor)
+ val rawApply = genApplyMethod(
+ genLoadModule(rtModuleClass),
+ rtModuleClass,
+ methodIdent,
+ castCallTrg :: arguments,
+ toIRType(retTpe))
+ // Box the result of the implementing method if required
+ if (isPrimitiveValueType(retTpe))
+ makePrimitiveBox(rawApply, retTpe)
+ else
+ rawApply
+ } else {
+ val (reflBoxClassPatched, callTrg1) = {
+ def isIntOrLongKind(kind: TypeKind) = kind match {
+ case _:INT | LONG => true
+ case _ => false
+ }
+ if (primTypeOf == "number" &&
+ toTypeKind(implMethodSym.tpe.resultType) == DoubleKind &&
+ isIntOrLongKind(toTypeKind(sym.tpe.resultType))) {
+ // This must be an Int, and not a Double
+ (IntegerReflectiveCallClass,
+ js.AsInstanceOf(callTrg,
+ toReferenceType(BoxedIntClass.toTypeConstructor)))
+ } else {
+ (reflBoxClass, callTrg)
+ }
+ }
+ val castCallTrg =
+ fromAny(callTrg1,
+ reflBoxClassPatched.primaryConstructor.tpe.params.head.tpe)
+ val reflBox = genNew(reflBoxClassPatched,
+ reflBoxClassPatched.primaryConstructor, List(castCallTrg))
+ genApplyMethod(
+ reflBox,
+ reflBoxClassPatched,
+ proxyIdent,
+ arguments,
+ jstpe.AnyType)
+ }
+ }
+ }, { // else
+ callStatement
+ })(jstpe.AnyType)
+ }
+
+ js.Block(callTrgVarDef, callStatement)
+ }
+ }
+
+ /** Ensures that the value of the given tree is boxed.
+ * @param expr Tree to be boxed if needed.
+ * @param tpeEnteringPosterasure The type of `expr` as it was entering
+ * the posterasure phase.
+ */
+ def ensureBoxed(expr: js.Tree, tpeEnteringPosterasure: Type)(
+ implicit pos: Position): js.Tree = {
+
+ tpeEnteringPosterasure match {
+ case tpe if isPrimitiveValueType(tpe) =>
+ makePrimitiveBox(expr, tpe)
+
+ case tpe: ErasedValueType =>
+ val boxedClass = tpe.valueClazz
+ val ctor = boxedClass.primaryConstructor
+ genNew(boxedClass, ctor, List(expr))
+
+ case _ =>
+ expr
+ }
+ }
+
+ /** Extracts a value typed as Any to the given type after posterasure.
+ * @param expr Tree to be extracted.
+ * @param tpeEnteringPosterasure The type of `expr` as it was entering
+ * the posterasure phase.
+ */
+ def fromAny(expr: js.Tree, tpeEnteringPosterasure: Type)(
+ implicit pos: Position): js.Tree = {
+
+ tpeEnteringPosterasure match {
+ case tpe if isPrimitiveValueType(tpe) =>
+ makePrimitiveUnbox(expr, tpe)
+
+ case tpe: ErasedValueType =>
+ val boxedClass = tpe.valueClazz
+ val unboxMethod = boxedClass.derivedValueClassUnbox
+ val content = genApplyMethod(
+ genAsInstanceOf(expr, tpe),
+ boxedClass, unboxMethod, Nil)
+ if (unboxMethod.tpe.resultType <:< tpe.erasedUnderlying)
+ content
+ else
+ fromAny(content, tpe.erasedUnderlying)
+
+ case tpe =>
+ genAsInstanceOf(expr, tpe)
+ }
+ }
+
+ /** Gen a boxing operation (tpe is the primitive type) */
+ def makePrimitiveBox(expr: js.Tree, tpe: Type)(
+ implicit pos: Position): js.Tree = {
+ toTypeKind(tpe) match {
+ case VOID => // must be handled at least for JS interop
+ js.Block(expr, js.Undefined())
+ case kind: ValueTypeKind =>
+ if (kind == CharKind) {
+ genApplyMethod(
+ genLoadModule(BoxesRunTimeClass),
+ BoxesRunTimeClass,
+ BoxesRunTime_boxToCharacter,
+ List(expr))
+ } else {
+ expr // box is identity for all non-Char types
+ }
+ case _ =>
+ abort(s"makePrimitiveBox requires a primitive type, found $tpe at $pos")
+ }
+ }
+
+ /** Gen an unboxing operation (tpe is the primitive type) */
+ def makePrimitiveUnbox(expr: js.Tree, tpe: Type)(
+ implicit pos: Position): js.Tree = {
+ toTypeKind(tpe) match {
+ case VOID => // must be handled at least for JS interop
+ expr
+ case kind: ValueTypeKind =>
+ if (kind == CharKind) {
+ genApplyMethod(
+ genLoadModule(BoxesRunTimeClass),
+ BoxesRunTimeClass,
+ BoxesRunTime_unboxToChar,
+ List(expr))
+ } else {
+ js.Unbox(expr, kind.primitiveCharCode)
+ }
+ case _ =>
+ abort(s"makePrimitiveUnbox requires a primitive type, found $tpe at $pos")
+ }
+ }
+
+ private def lookupModuleClass(name: String) = {
+ val module = getModuleIfDefined(name)
+ if (module == NoSymbol) NoSymbol
+ else module.moduleClass
+ }
+
+ lazy val ReflectArrayModuleClass = lookupModuleClass("java.lang.reflect.Array")
+ lazy val UtilArraysModuleClass = lookupModuleClass("java.util.Arrays")
+
+ /** Gen JS code for a Scala.js-specific primitive method */
+ private def genJSPrimitive(tree: Apply, receiver0: Tree,
+ args: List[Tree], code: Int): js.Tree = {
+ import jsPrimitives._
+
+ implicit val pos = tree.pos
+
+ def receiver = genExpr(receiver0)
+ val genArgArray = genPrimitiveJSArgs(tree.symbol, args)
+
+ lazy val js.JSArrayConstr(genArgs) = genArgArray
+
+ def extractFirstArg() = {
+ (genArgArray: @unchecked) match {
+ case js.JSArrayConstr(firstArg :: otherArgs) =>
+ (firstArg, js.JSArrayConstr(otherArgs))
+ case js.JSBracketMethodApply(
+ js.JSArrayConstr(firstArg :: firstPart), concat, otherParts) =>
+ (firstArg, js.JSBracketMethodApply(
+ js.JSArrayConstr(firstPart), concat, otherParts))
+ }
+ }
+
+ if (code == DYNNEW) {
+ // js.Dynamic.newInstance(clazz)(actualArgs:_*)
+ val (jsClass, actualArgArray) = extractFirstArg()
+ actualArgArray match {
+ case js.JSArrayConstr(actualArgs) =>
+ js.JSNew(jsClass, actualArgs)
+ case _ =>
+ genNewJSWithVarargs(jsClass, actualArgArray)
+ }
+ } else if (code == DYNAPPLY) {
+ // js.Dynamic.applyDynamic(methodName)(actualArgs:_*)
+ val (methodName, actualArgArray) = extractFirstArg()
+ actualArgArray match {
+ case js.JSArrayConstr(actualArgs) =>
+ js.JSBracketMethodApply(receiver, methodName, actualArgs)
+ case _ =>
+ genApplyJSMethodWithVarargs(receiver, methodName, actualArgArray)
+ }
+ } else if (code == DYNLITN) {
+ // We have a call of the form:
+ // js.Dynamic.literal(name1 = ..., name2 = ...)
+ // Translate to:
+ // {"name1": ..., "name2": ... }
+ extractFirstArg() match {
+ case (js.StringLiteral("apply"),
+ js.JSArrayConstr(jse.LitNamed(pairs))) =>
+ js.JSObjectConstr(pairs)
+ case (js.StringLiteral(name), _) if name != "apply" =>
+ reporter.error(pos,
+ s"js.Dynamic.literal does not have a method named $name")
+ js.Undefined()
+ case _ =>
+ reporter.error(pos,
+ "js.Dynamic.literal.applyDynamicNamed may not be called directly")
+ js.Undefined()
+ }
+ } else if (code == DYNLIT) {
+ // We have a call of some other form
+ // js.Dynamic.literal(...)
+ // Translate to:
+ // var obj = {};
+ // obj[...] = ...;
+ // obj
+
+ // Extract first arg to future proof against varargs
+ extractFirstArg() match {
+ // case js.Dynamic.literal("name1" -> ..., "name2" -> ...)
+ case (js.StringLiteral("apply"),
+ js.JSArrayConstr(jse.LitNamed(pairs))) =>
+ js.JSObjectConstr(pairs)
+
+ // case js.Dynamic.literal(x, y)
+ case (js.StringLiteral("apply"), js.JSArrayConstr(tups)) =>
+ // Create tmp variable
+ val resIdent = freshLocalIdent("obj")
+ val resVarDef = js.VarDef(resIdent, jstpe.AnyType, mutable = false,
+ js.JSObjectConstr(Nil))
+ val res = resVarDef.ref
+
+ // Assign fields
+ val tuple2Type = encodeClassType(TupleClass(2))
+ val assigns = tups flatMap {
+ // special case for literals
+ case jse.Tuple2(name, value) =>
+ js.Assign(js.JSBracketSelect(res, name), value) :: Nil
+ case tupExpr =>
+ val tupIdent = freshLocalIdent("tup")
+ val tup = js.VarRef(tupIdent, mutable = false)(tuple2Type)
+ js.VarDef(tupIdent, tuple2Type, mutable = false, tupExpr) ::
+ js.Assign(js.JSBracketSelect(res,
+ genApplyMethod(tup, TupleClass(2), js.Ident("$$und1__O"), Nil, jstpe.AnyType)),
+ genApplyMethod(tup, TupleClass(2), js.Ident("$$und2__O"), Nil, jstpe.AnyType)) :: Nil
+ }
+
+ js.Block(resVarDef +: assigns :+ res: _*)
+
+ /* Here we would need the case where the varargs are passed in
+ * as non-literal list:
+ * js.Dynamic.literal(x: _*)
+ * However, Scala does not currently support this
+ */
+
+ // case where another method is called
+ case (js.StringLiteral(name), _) if name != "apply" =>
+ reporter.error(pos,
+ s"js.Dynamic.literal does not have a method named $name")
+ js.Undefined()
+ case _ =>
+ reporter.error(pos,
+ "js.Dynamic.literal.applyDynamic may not be called directly")
+ js.Undefined()
+ }
+ } else if (code == ARR_CREATE) {
+ // js.Array.create(elements: _*)
+ genArgArray
+ } else (genArgs match {
+ case Nil =>
+ code match {
+ case GETCLASS => js.GetClass(receiver)
+ case ENV_INFO => js.JSEnvInfo()
+ case DEBUGGER => js.Debugger()
+ case UNDEFVAL => js.Undefined()
+ case UNITVAL => js.Undefined()
+ case UNITTYPE => genClassConstant(UnitTpe)
+ case JS_NATIVE =>
+ reporter.error(pos, "js.native may only be used as stub implementation in facade types")
+ js.Undefined()
+ }
+
+ case List(arg) =>
+
+ /** Factorization of F2JS and F2JSTHIS. */
+ def genFunctionToJSFunction(isThisFunction: Boolean): js.Tree = {
+ val arity = {
+ val funName = tree.fun.symbol.name.encoded
+ assert(funName.startsWith("fromFunction"))
+ funName.stripPrefix("fromFunction").toInt
+ }
+ val inputClass = FunctionClass(arity)
+ val inputIRType = encodeClassType(inputClass)
+ val applyMeth = getMemberMethod(inputClass, nme.apply) suchThat { s =>
+ val ps = s.paramss
+ ps.size == 1 &&
+ ps.head.size == arity &&
+ ps.head.forall(_.tpe.typeSymbol == ObjectClass)
+ }
+ val fCaptureParam = js.ParamDef(js.Ident("f"), inputIRType,
+ mutable = false)
+ val jsArity =
+ if (isThisFunction) arity - 1
+ else arity
+ val jsParams = (1 to jsArity).toList map {
+ x => js.ParamDef(js.Ident("arg"+x), jstpe.AnyType,
+ mutable = false)
+ }
+ js.Closure(
+ List(fCaptureParam),
+ jsParams,
+ genApplyMethod(
+ fCaptureParam.ref,
+ inputClass, applyMeth,
+ if (isThisFunction)
+ js.This()(jstpe.AnyType) :: jsParams.map(_.ref)
+ else
+ jsParams.map(_.ref)),
+ List(arg))
+ }
+
+ code match {
+ /** Convert a scala.FunctionN f to a js.FunctionN. */
+ case F2JS =>
+ arg match {
+ /* This case will happen every time we have a Scala lambda
+ * in js.FunctionN position. We remove the JS function to
+ * Scala function wrapper, instead of adding a Scala function
+ * to JS function wrapper.
+ */
+ case JSFunctionToScala(fun, arity) =>
+ fun
+ case _ =>
+ genFunctionToJSFunction(isThisFunction = false)
+ }
+
+ /** Convert a scala.FunctionN f to a js.ThisFunction{N-1}. */
+ case F2JSTHIS =>
+ genFunctionToJSFunction(isThisFunction = true)
+
+ case DYNSELECT =>
+ // js.Dynamic.selectDynamic(arg)
+ js.JSBracketSelect(receiver, arg)
+
+ case DICT_DEL =>
+ // js.Dictionary.delete(arg)
+ js.JSDelete(js.JSBracketSelect(receiver, arg))
+
+ case ISUNDEF =>
+ // js.isUndefined(arg)
+ js.BinaryOp(js.BinaryOp.===, arg, js.Undefined())
+ case TYPEOF =>
+ // js.typeOf(arg)
+ js.UnaryOp(js.UnaryOp.typeof, arg)
+
+ case OBJPROPS =>
+ // js.Object.properties(arg)
+ genApplyMethod(
+ genLoadModule(RuntimePackageModule),
+ RuntimePackageModule.moduleClass,
+ Runtime_propertiesOf,
+ List(arg))
+ }
+
+ case List(arg1, arg2) =>
+ code match {
+ case DYNUPDATE =>
+ // js.Dynamic.updateDynamic(arg1)(arg2)
+ js.Assign(js.JSBracketSelect(receiver, arg1), arg2)
+
+ case HASPROP =>
+ // js.Object.hasProperty(arg1, arg2)
+ /* Here we have an issue with evaluation order of arg1 and arg2,
+ * since the obvious translation is `arg2 in arg1`, but then
+ * arg2 is evaluated before arg1. Since this is not a commonly
+ * used operator, we don't try to avoid unnessary temp vars, and
+ * simply always evaluate arg1 in a temp before doing the `in`.
+ */
+ val temp = freshLocalIdent()
+ js.Block(
+ js.VarDef(temp, jstpe.AnyType, mutable = false, arg1),
+ js.BinaryOp(js.BinaryOp.in, arg2,
+ js.VarRef(temp, mutable = false)(jstpe.AnyType)))
+ }
+ })
+ }
+
+ /** Gen JS code for a primitive JS call (to a method of a subclass of js.Any)
+ * This is the typed Scala.js to JS bridge feature. Basically it boils
+ * down to calling the method without name mangling. But other aspects
+ * come into play:
+ * * Operator methods are translated to JS operators (not method calls)
+ * * apply is translated as a function call, i.e. o() instead of o.apply()
+ * * Scala varargs are turned into JS varargs (see genPrimitiveJSArgs())
+ * * Getters and parameterless methods are translated as Selects
+ * * Setters are translated to Assigns of Selects
+ */
+ private def genPrimitiveJSCall(tree: Apply, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+
+ val sym = tree.symbol
+ val Apply(fun @ Select(receiver0, _), args0) = tree
+
+ val funName = sym.unexpandedName.decoded
+ val receiver = genExpr(receiver0)
+ val argArray = genPrimitiveJSArgs(sym, args0)
+
+ // valid only for methods that don't have any varargs
+ lazy val js.JSArrayConstr(args) = argArray
+ lazy val argc = args.length
+
+ def hasExplicitJSEncoding =
+ sym.hasAnnotation(JSNameAnnotation) ||
+ sym.hasAnnotation(JSBracketAccessAnnotation)
+
+ val boxedResult = funName match {
+ case "unary_+" | "unary_-" | "unary_~" | "unary_!" =>
+ assert(argc == 0)
+ js.JSUnaryOp(funName.substring(funName.length-1), receiver)
+
+ case "+" | "-" | "*" | "/" | "%" | "<<" | ">>" | ">>>" |
+ "&" | "|" | "^" | "&&" | "||" | "<" | ">" | "<=" | ">=" =>
+ assert(argc == 1)
+ js.JSBinaryOp(funName, receiver, args.head)
+
+ case "apply" if receiver0.tpe.typeSymbol.isSubClass(JSThisFunctionClass) =>
+ js.JSBracketMethodApply(receiver, js.StringLiteral("call"), args)
+
+ case "apply" if !hasExplicitJSEncoding =>
+ argArray match {
+ case js.JSArrayConstr(args) =>
+ js.JSFunctionApply(receiver, args)
+ case _ =>
+ js.JSBracketMethodApply(
+ receiver, js.StringLiteral("apply"), List(js.Null(), argArray))
+ }
+
+ case _ =>
+ def jsFunName = jsNameOf(sym)
+
+ if (sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM)) {
+ js.UndefinedParam()(toIRType(sym.tpe.resultType))
+ } else if (jsInterop.isJSGetter(sym)) {
+ assert(argc == 0)
+ js.JSBracketSelect(receiver, js.StringLiteral(jsFunName))
+ } else if (jsInterop.isJSSetter(sym)) {
+ assert(argc == 1)
+ js.Assign(
+ js.JSBracketSelect(receiver,
+ js.StringLiteral(jsFunName.stripSuffix("_="))),
+ args.head)
+ } else if (jsInterop.isJSBracketAccess(sym)) {
+ assert(argArray.isInstanceOf[js.JSArrayConstr] && (argc == 1 || argc == 2),
+ s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments")
+ args match {
+ case List(keyArg) =>
+ js.JSBracketSelect(receiver, keyArg)
+ case List(keyArg, valueArg) =>
+ js.Assign(
+ js.JSBracketSelect(receiver, keyArg),
+ valueArg)
+ }
+ } else {
+ argArray match {
+ case js.JSArrayConstr(args) =>
+ js.JSBracketMethodApply(
+ receiver, js.StringLiteral(jsFunName), args)
+ case _ =>
+ genApplyJSMethodWithVarargs(receiver,
+ js.StringLiteral(jsFunName), argArray)
+ }
+ }
+ }
+
+ boxedResult match {
+ case js.UndefinedParam() | js.Assign(_, _) =>
+ boxedResult
+ case _ if isStat =>
+ boxedResult
+ case _ =>
+ fromAny(boxedResult,
+ enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType))
+ }
+ }
+
+ /** Gen JS code to call a primitive JS method with variadic parameters. */
+ private def genApplyJSMethodWithVarargs(receiver: js.Tree,
+ methodName: js.Tree, argArray: js.Tree)(
+ implicit pos: Position): js.Tree = {
+ // We need to evaluate `receiver` only once
+ val receiverValDef =
+ js.VarDef(freshLocalIdent(), receiver.tpe, mutable = false, receiver)
+ js.Block(
+ receiverValDef,
+ js.JSBracketMethodApply(
+ js.JSBracketSelect(receiverValDef.ref, methodName),
+ js.StringLiteral("apply"),
+ List(receiverValDef.ref, argArray)))
+ }
+
+ /** Gen JS code to instantiate a JS class with variadic parameters. */
+ private def genNewJSWithVarargs(jsClass: js.Tree, argArray: js.Tree)(
+ implicit pos: Position): js.Tree = {
+ genApplyMethod(
+ genLoadModule(RuntimePackageModule),
+ RuntimePackageModule.moduleClass,
+ Runtime_newJSObjectWithVarargs,
+ List(jsClass, argArray))
+ }
+
+ /** Gen JS code for new java.lang.String(...)
+ * Proxies calls to method newString on object
+ * scala.scalajs.runtime.RuntimeString with proper arguments
+ */
+ private def genNewString(tree: Apply): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun @ Select(_, _), args0) = tree
+
+ val ctor = fun.symbol
+ val args = args0 map genExpr
+
+ // Filter members of target module for matching member
+ val compMembers = for {
+ mem <- RuntimeStringModule.tpe.members
+ if mem.name == jsnme.newString && ctor.tpe.matches(mem.tpe)
+ } yield mem
+
+ if (compMembers.isEmpty) {
+ reporter.error(pos,
+ s"""Could not find implementation for constructor of java.lang.String
+ |with type ${ctor.tpe}. Constructors on java.lang.String
+ |are forwarded to the companion object of
+ |scala.scalajs.runtime.RuntimeString""".stripMargin)
+ js.Undefined()
+ } else {
+ assert(compMembers.size == 1,
+ s"""For constructor with type ${ctor.tpe} on java.lang.String,
+ |found multiple companion module members.""".stripMargin)
+
+ // Emit call to companion object
+ genApplyMethod(
+ genLoadModule(RuntimeStringModule),
+ RuntimeStringModule.moduleClass,
+ compMembers.head,
+ args)
+ }
+ }
+
+ /** Gen JS code for calling a method on java.lang.String.
+ *
+ * Forwards call on java.lang.String to the module
+ * scala.scalajs.runtime.RuntimeString.
+ */
+ private def genStringCall(tree: Apply): js.Tree = {
+ implicit val pos = tree.pos
+
+ val sym = tree.symbol
+
+ // Deconstruct tree and create receiver and argument JS expressions
+ val Apply(Select(receiver0, _), args0) = tree
+ val receiver = genExpr(receiver0)
+ val args = args0 map genExpr
+
+ // Emit call to the RuntimeString module
+ val (rtModuleClass, methodIdent) = encodeRTStringMethodSym(sym)
+ genApplyMethod(
+ genLoadModule(rtModuleClass),
+ rtModuleClass,
+ methodIdent,
+ receiver :: args,
+ toIRType(tree.tpe))
+ }
+
+ /** Gen JS code for a new of a raw JS class (subclass of js.Any) */
+ private def genPrimitiveJSNew(tree: Apply): js.Tree = {
+ implicit val pos = tree.pos
+
+ val Apply(fun @ Select(New(tpt), _), args0) = tree
+ val cls = tpt.tpe.typeSymbol
+ val ctor = fun.symbol
+
+ genPrimitiveJSArgs(ctor, args0) match {
+ case js.JSArrayConstr(args) =>
+ if (cls == JSObjectClass && args.isEmpty) js.JSObjectConstr(Nil)
+ else if (cls == JSArrayClass && args.isEmpty) js.JSArrayConstr(Nil)
+ else js.JSNew(genPrimitiveJSClass(cls), args)
+ case argArray =>
+ genNewJSWithVarargs(genPrimitiveJSClass(cls), argArray)
+ }
+ }
+
+ /** Gen JS code representing a JS class (subclass of js.Any) */
+ private def genPrimitiveJSClass(sym: Symbol)(
+ implicit pos: Position): js.Tree = {
+ genGlobalJSObject(sym)
+ }
+
+ /** Gen JS code representing a JS module (var of the global scope) */
+ private def genPrimitiveJSModule(sym: Symbol)(
+ implicit pos: Position): js.Tree = {
+ genGlobalJSObject(sym)
+ }
+
+ /** Gen JS code representing a JS object (class or module) in global scope
+ */
+ private def genGlobalJSObject(sym: Symbol)(
+ implicit pos: Position): js.Tree = {
+ jsNameOf(sym).split('.').foldLeft(genLoadGlobal()) { (memo, chunk) =>
+ js.JSBracketSelect(memo, js.StringLiteral(chunk))
+ }
+ }
+
+ /** Gen actual actual arguments to Scala method call.
+ * Returns a list of the transformed arguments.
+ *
+ * This tries to optimized repeated arguments (varargs) by turning them
+ * into js.WrappedArray instead of Scala wrapped arrays.
+ */
+ private def genActualArgs(sym: Symbol, args: List[Tree])(
+ implicit pos: Position): List[js.Tree] = {
+ val wereRepeated = exitingPhase(currentRun.typerPhase) {
+ sym.tpe.params.map(p => isScalaRepeatedParamType(p.tpe))
+ }
+
+ if (wereRepeated.size > args.size) {
+ // Should not happen, but let's not crash
+ args.map(genExpr)
+ } else {
+ /* Arguments that are in excess compared to the type signature after
+ * erasure are lambda-lifted arguments. They cannot be repeated, hence
+ * the extension to `false`.
+ */
+ for ((arg, wasRepeated) <- args.zipAll(wereRepeated, EmptyTree, false)) yield {
+ if (wasRepeated) {
+ tryGenRepeatedParamAsJSArray(arg, handleNil = false).fold {
+ genExpr(arg)
+ } { argArray =>
+ genNew(WrappedArrayClass, WrappedArray_ctor, List(argArray))
+ }
+ } else {
+ genExpr(arg)
+ }
+ }
+ }
+ }
+
+ /** Gen actual actual arguments to a primitive JS call
+ * This handles repeated arguments (varargs) by turning them into
+ * JS varargs, i.e., by expanding them into normal arguments.
+ *
+ * Returns an only tree which is a JS array of the arguments. In most
+ * cases, it will be a js.JSArrayConstr with the expanded arguments. It will
+ * not if a Seq is passed to a varargs argument with the syntax seq: _*.
+ */
+ private def genPrimitiveJSArgs(sym: Symbol, args: List[Tree])(
+ implicit pos: Position): js.Tree = {
+ val wereRepeated = exitingPhase(currentRun.typerPhase) {
+ for {
+ params <- sym.tpe.paramss
+ param <- params
+ } yield isScalaRepeatedParamType(param.tpe)
+ }
+
+ var reversedParts: List[js.Tree] = Nil
+ var reversedPartUnderConstruction: List[js.Tree] = Nil
+
+ def closeReversedPartUnderConstruction() = {
+ if (!reversedPartUnderConstruction.isEmpty) {
+ val part = reversedPartUnderConstruction.reverse
+ reversedParts ::= js.JSArrayConstr(part)
+ reversedPartUnderConstruction = Nil
+ }
+ }
+
+ val paramTpes = enteringPhase(currentRun.posterasurePhase) {
+ for (param <- sym.tpe.params)
+ yield param.tpe
+ }
+
+ for (((arg, wasRepeated), tpe) <- (args zip wereRepeated) zip paramTpes) {
+ if (wasRepeated) {
+ genPrimitiveJSRepeatedParam(arg) match {
+ case js.JSArrayConstr(jsArgs) =>
+ reversedPartUnderConstruction =
+ jsArgs reverse_::: reversedPartUnderConstruction
+ case jsArgArray =>
+ closeReversedPartUnderConstruction()
+ reversedParts ::= jsArgArray
+ }
+ } else {
+ val unboxedArg = genExpr(arg)
+ val boxedArg = unboxedArg match {
+ case js.UndefinedParam() => unboxedArg
+ case _ => ensureBoxed(unboxedArg, tpe)
+ }
+ reversedPartUnderConstruction ::= boxedArg
+ }
+ }
+ closeReversedPartUnderConstruction()
+
+ // Find js.UndefinedParam at the end of the argument list. No check is
+ // performed whether they may be there, since they will only be placed
+ // where default arguments can be anyway
+ reversedParts = reversedParts match {
+ case Nil => Nil
+ case js.JSArrayConstr(params) :: others =>
+ val nparams =
+ params.reverse.dropWhile(_.isInstanceOf[js.UndefinedParam]).reverse
+ js.JSArrayConstr(nparams) :: others
+ case parts => parts
+ }
+
+ // Find remaining js.UndefinedParam and replace by js.Undefined. This can
+ // happen with named arguments or when multiple argument lists are present
+ reversedParts = reversedParts map {
+ case js.JSArrayConstr(params) =>
+ val nparams = params map {
+ case js.UndefinedParam() => js.Undefined()
+ case param => param
+ }
+ js.JSArrayConstr(nparams)
+ case part => part
+ }
+
+ reversedParts match {
+ case Nil => js.JSArrayConstr(Nil)
+ case List(part) => part
+ case _ =>
+ val partHead :: partTail = reversedParts.reverse
+ js.JSBracketMethodApply(
+ partHead, js.StringLiteral("concat"), partTail)
+ }
+ }
+
+ /** Gen JS code for a repeated param of a primitive JS method
+ * In this case `arg` has type Seq[T] for some T, but the result should
+ * have type js.Array[T]. So this method takes care of the conversion.
+ * It is specialized for the shapes of tree generated by the desugaring
+ * of repeated params in Scala, so that these produce a js.JSArrayConstr.
+ */
+ private def genPrimitiveJSRepeatedParam(arg: Tree): js.Tree = {
+ tryGenRepeatedParamAsJSArray(arg, handleNil = true) getOrElse {
+ /* Fall back to calling runtime.genTraversableOnce2jsArray
+ * to perform the conversion.
+ */
+ implicit val pos = arg.pos
+ genApplyMethod(
+ genLoadModule(RuntimePackageModule),
+ RuntimePackageModule.moduleClass,
+ Runtime_genTraversableOnce2jsArray,
+ List(genExpr(arg)))
+ }
+ }
+
+ /** Try and gen a js.Array for a repeated param (xs: T*).
+ * It is specialized for the shapes of tree generated by the desugaring
+ * of repeated params in Scala, so that these produce a js.JSArrayConstr.
+ * If `arg` does not have the shape of a generated repeated param, this
+ * method returns `None`.
+ */
+ private def tryGenRepeatedParamAsJSArray(arg: Tree,
+ handleNil: Boolean): Option[js.Tree] = {
+ implicit val pos = arg.pos
+
+ // Given a method `def foo(args: T*)`
+ arg match {
+ // foo(arg1, arg2, ..., argN) where N > 0
+ case MaybeAsInstanceOf(WrapArray(
+ MaybeAsInstanceOf(ArrayValue(tpt, elems)))) =>
+ /* Value classes in arrays are already boxed, so no need to use
+ * the type before erasure.
+ */
+ val elemTpe = tpt.tpe
+ Some(js.JSArrayConstr(elems.map(e => ensureBoxed(genExpr(e), elemTpe))))
+
+ // foo()
+ case Select(_, _) if handleNil && arg.symbol == NilModule =>
+ Some(js.JSArrayConstr(Nil))
+
+ // foo(argSeq:_*) - cannot be optimized
+ case _ =>
+ None
+ }
+ }
+
+ object MaybeAsInstanceOf {
+ def unapply(tree: Tree): Some[Tree] = tree match {
+ case Apply(TypeApply(asInstanceOf_? @ Select(base, _), _), _)
+ if asInstanceOf_?.symbol == Object_asInstanceOf =>
+ Some(base)
+ case _ =>
+ Some(tree)
+ }
+ }
+
+ object WrapArray {
+ lazy val isWrapArray: Set[Symbol] = Seq(
+ nme.wrapRefArray,
+ nme.wrapByteArray,
+ nme.wrapShortArray,
+ nme.wrapCharArray,
+ nme.wrapIntArray,
+ nme.wrapLongArray,
+ nme.wrapFloatArray,
+ nme.wrapDoubleArray,
+ nme.wrapBooleanArray,
+ nme.wrapUnitArray,
+ nme.genericWrapArray).map(getMemberMethod(PredefModule, _)).toSet
+
+ def unapply(tree: Apply): Option[Tree] = tree match {
+ case Apply(wrapArray_?, List(wrapped))
+ if isWrapArray(wrapArray_?.symbol) =>
+ Some(wrapped)
+ case _ =>
+ None
+ }
+ }
+
+ // Synthesizers for raw JS functions ---------------------------------------
+
+ /** Try and gen and record JS code for an anonymous function class.
+ *
+ * Returns true if the class could be rewritten that way, false otherwise.
+ *
+ * We make the following assumptions on the form of such classes:
+ * - It is an anonymous function
+ * - Includes being anonymous, final, and having exactly one constructor
+ * - It is not a PartialFunction
+ * - It has no field other than param accessors
+ * - It has exactly one constructor
+ * - It has exactly one non-bridge method apply if it is not specialized,
+ * or a method apply$...$sp and a forwarder apply if it is specialized.
+ * - As a precaution: it is synthetic
+ *
+ * From a class looking like this:
+ *
+ * final class <anon>(outer, capture1, ..., captureM) extends AbstractionFunctionN[...] {
+ * def apply(param1, ..., paramN) = {
+ * <body>
+ * }
+ * }
+ * new <anon>(o, c1, ..., cM)
+ *
+ * we generate a function maker that emits:
+ *
+ * lambda<o, c1, ..., cM>[notype](
+ * outer, capture1, ..., captureM, param1, ..., paramN) {
+ * <body>
+ * }
+ *
+ * so that, at instantiation point, we can write:
+ *
+ * new AnonFunctionN(functionMaker(this, captured1, ..., capturedM))
+ *
+ * Trickier things apply when the function is specialized.
+ */
+ private def tryGenAndRecordAnonFunctionClass(cd: ClassDef): Boolean = {
+ implicit val pos = cd.pos
+ val sym = cd.symbol
+ assert(sym.isAnonymousFunction,
+ s"tryGenAndRecordAnonFunctionClass called with non-anonymous function $cd")
+
+ withScopedVars(
+ currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass),
+ currentClassSym := sym
+ ) {
+ val (functionMakerBase, arity) =
+ tryGenAndRecordAnonFunctionClassGeneric(cd) { msg =>
+ return false
+ }
+ val functionMaker = { capturedArgs: List[js.Tree] =>
+ JSFunctionToScala(functionMakerBase(capturedArgs), arity)
+ }
+
+ translatedAnonFunctions +=
+ sym -> (functionMaker, currentClassInfoBuilder.get)
+ }
+ true
+ }
+
+ /** Constructor and extractor object for a tree that converts a JavaScript
+ * function into a Scala function.
+ */
+ private object JSFunctionToScala {
+ private val AnonFunPrefScala =
+ "scala.scalajs.runtime.AnonFunction"
+ private val AnonFunPrefJS =
+ "sjsr_AnonFunction"
+
+ def apply(jsFunction: js.Tree, arity: Int)(
+ implicit pos: Position): js.Tree = {
+ val clsSym = getRequiredClass(AnonFunPrefScala + arity)
+ val ctor = clsSym.tpe.member(nme.CONSTRUCTOR)
+ genNew(clsSym, ctor, List(jsFunction))
+ }
+
+ def unapply(tree: js.New): Option[(js.Tree, Int)] = tree match {
+ case js.New(jstpe.ClassType(wrapperName), _, List(fun))
+ if wrapperName.startsWith(AnonFunPrefJS) =>
+ val arityStr = wrapperName.substring(AnonFunPrefJS.length)
+ try {
+ Some((fun, arityStr.toInt))
+ } catch {
+ case e: NumberFormatException => None
+ }
+
+ case _ =>
+ None
+ }
+ }
+
+ /** Gen and record JS code for a raw JS function class.
+ *
+ * This is called when emitting a ClassDef that represents an anonymous
+ * class extending `js.FunctionN`. These are generated by the SAM
+ * synthesizer when the target type is a `js.FunctionN`. Since JS
+ * functions are not classes, we deconstruct the ClassDef, then
+ * reconstruct it to be a genuine Closure.
+ *
+ * Compared to `tryGenAndRecordAnonFunctionClass()`, this function must
+ * always succeed, because we really cannot afford keeping them as
+ * anonymous classes. The good news is that it can do so, because the
+ * body of SAM lambdas is hoisted in the enclosing class. Hence, the
+ * apply() method is just a forwarder to calling that hoisted method.
+ *
+ * From a class looking like this:
+ *
+ * final class <anon>(outer, capture1, ..., captureM) extends js.FunctionN[...] {
+ * def apply(param1, ..., paramN) = {
+ * outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM)
+ * }
+ * }
+ * new <anon>(o, c1, ..., cM)
+ *
+ * we generate a function maker that emits:
+ *
+ * lambda<o, c1, ..., cM>[notype](
+ * outer, capture1, ..., captureM, param1, ..., paramN) {
+ * outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM)
+ * }
+ *
+ * The function maker is recorded in `translatedAnonFunctions` to be
+ * fetched later by the translation for New.
+ */
+ def genAndRecordRawJSFunctionClass(cd: ClassDef): Unit = {
+ val sym = cd.symbol
+ assert(isRawJSFunctionDef(sym),
+ s"genAndRecordRawJSFunctionClass called with non-JS function $cd")
+
+ withScopedVars(
+ currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass),
+ currentClassSym := sym
+ ) {
+ val (functionMaker, _) =
+ tryGenAndRecordAnonFunctionClassGeneric(cd) { msg =>
+ abort(s"Could not generate raw function maker for JS function: $msg")
+ }
+
+ translatedAnonFunctions +=
+ sym -> (functionMaker, currentClassInfoBuilder.get)
+ }
+ }
+
+ /** Code common to tryGenAndRecordAnonFunctionClass and
+ * genAndRecordRawJSFunctionClass.
+ */
+ private def tryGenAndRecordAnonFunctionClassGeneric(cd: ClassDef)(
+ fail: (=> String) => Nothing): (List[js.Tree] => js.Tree, Int) = {
+ implicit val pos = cd.pos
+ val sym = cd.symbol
+
+ // First checks
+
+ if (sym.isSubClass(PartialFunctionClass))
+ fail(s"Cannot rewrite PartialFunction $cd")
+ if (instantiatedAnonFunctions contains sym) {
+ // when the ordering we're given is evil (it happens!)
+ fail(s"Abort function rewrite because it was already instantiated: $cd")
+ }
+
+ // First step: find the apply method def, and collect param accessors
+
+ var paramAccessors: List[Symbol] = Nil
+ var applyDef: DefDef = null
+
+ def gen(tree: Tree): Unit = {
+ tree match {
+ case EmptyTree => ()
+ case Template(_, _, body) => body foreach gen
+ case vd @ ValDef(mods, name, tpt, rhs) =>
+ val fsym = vd.symbol
+ if (!fsym.isParamAccessor)
+ fail(s"Found field $fsym which is not a param accessor in anon function $cd")
+
+ if (fsym.isPrivate) {
+ paramAccessors ::= fsym
+ } else {
+ // Uh oh ... an inner something will try to access my fields
+ fail(s"Found a non-private field $fsym in $cd")
+ }
+ case dd: DefDef =>
+ val ddsym = dd.symbol
+ if (ddsym.isClassConstructor) {
+ if (!ddsym.isPrimaryConstructor)
+ fail(s"Non-primary constructor $ddsym in anon function $cd")
+ } else {
+ val name = dd.name.toString
+ if (name == "apply" || (ddsym.isSpecialized && name.startsWith("apply$"))) {
+ if ((applyDef eq null) || ddsym.isSpecialized)
+ applyDef = dd
+ } else {
+ // Found a method we cannot encode in the rewriting
+ fail(s"Found a non-apply method $ddsym in $cd")
+ }
+ }
+ case _ =>
+ fail("Illegal tree in gen of genAndRecordAnonFunctionClass(): " + tree)
+ }
+ }
+ gen(cd.impl)
+ paramAccessors = paramAccessors.reverse // preserve definition order
+
+ if (applyDef eq null)
+ fail(s"Did not find any apply method in anon function $cd")
+
+ withNewLocalNameScope {
+ // Second step: build the list of useful constructor parameters
+
+ val ctorParams = sym.primaryConstructor.tpe.params
+
+ if (paramAccessors.size != ctorParams.size &&
+ !(paramAccessors.size == ctorParams.size-1 &&
+ ctorParams.head.unexpandedName == jsnme.arg_outer)) {
+ fail(
+ s"Have param accessors $paramAccessors but "+
+ s"ctor params $ctorParams in anon function $cd")
+ }
+
+ val hasUnusedOuterCtorParam = paramAccessors.size != ctorParams.size
+ val usedCtorParams =
+ if (hasUnusedOuterCtorParam) ctorParams.tail
+ else ctorParams
+ val ctorParamDefs = usedCtorParams map { p =>
+ // in the apply method's context
+ js.ParamDef(encodeLocalSym(p)(p.pos), toIRType(p.tpe),
+ mutable = false)(p.pos)
+ }
+
+ // Third step: emit the body of the apply method def
+
+ val (applyMethod, methodInfoBuilder) = withScopedVars(
+ paramAccessorLocals := (paramAccessors zip ctorParamDefs).toMap,
+ tryingToGenMethodAsJSFunction := true
+ ) {
+ try {
+ genMethodWithInfoBuilder(applyDef).getOrElse(
+ abort(s"Oops, $applyDef did not produce a method"))
+ } catch {
+ case e: CancelGenMethodAsJSFunction =>
+ fail(e.getMessage)
+ }
+ }
+
+ withScopedVars(
+ currentMethodInfoBuilder := methodInfoBuilder
+ ) {
+ // Fourth step: patch the body to unbox parameters and box result
+
+ val js.MethodDef(_, params, _, body) = applyMethod
+ val (patchedParams, patchedBody) =
+ patchFunBodyWithBoxes(applyDef.symbol, params, body)
+
+ // Fifth step: build the function maker
+
+ val isThisFunction = JSThisFunctionClasses.exists(sym isSubClass _)
+ assert(!isThisFunction || patchedParams.nonEmpty,
+ s"Empty param list in ThisFunction: $cd")
+
+ val functionMaker = { capturedArgs0: List[js.Tree] =>
+ val capturedArgs =
+ if (hasUnusedOuterCtorParam) capturedArgs0.tail
+ else capturedArgs0
+ assert(capturedArgs.size == ctorParamDefs.size)
+
+ if (isThisFunction) {
+ val thisParam :: actualParams = patchedParams
+ js.Closure(
+ ctorParamDefs,
+ actualParams,
+ js.Block(
+ js.VarDef(thisParam.name, thisParam.ptpe, mutable = false,
+ js.This()(thisParam.ptpe)(thisParam.pos))(thisParam.pos),
+ patchedBody),
+ capturedArgs)
+ } else {
+ js.Closure(ctorParamDefs, patchedParams, patchedBody, capturedArgs)
+ }
+ }
+
+ val arity = params.size
+
+ (functionMaker, arity)
+ }
+ }
+ }
+
+ /** Generate JS code for an anonymous function
+ *
+ * Anonymous functions survive until the backend only under
+ * -Ydelambdafy:method
+ * and when they do, their body is always of the form
+ * EnclosingClass.this.someMethod(arg1, ..., argN, capture1, ..., captureM)
+ * where argI are the formal arguments of the lambda, and captureI are
+ * local variables or the enclosing def.
+ *
+ * We translate them by instantiating scala.scalajs.runtime.AnonFunctionN
+ * with a JS closure:
+ *
+ * new ScalaJS.c.sjsr_AnonFunctionN().init___xyz(
+ * lambda<this, capture1, ..., captureM>(
+ * _this, capture1, ..., captureM, arg1, ..., argN) {
+ * _this.someMethod(arg1, ..., argN, capture1, ..., captureM)
+ * }
+ * )
+ *
+ * In addition, input params are unboxed before use, and the result of
+ * someMethod() is boxed back.
+ */
+ private def genAnonFunction(originalFunction: Function): js.Tree = {
+ implicit val pos = originalFunction.pos
+ val Function(paramTrees, Apply(
+ targetTree @ Select(receiver, _), allArgs0)) = originalFunction
+
+ val target = targetTree.symbol
+ val params = paramTrees.map(_.symbol)
+
+ val allArgs = allArgs0 map genExpr
+
+ val formalArgs = params map { p =>
+ js.ParamDef(encodeLocalSym(p)(p.pos), toIRType(p.tpe),
+ mutable = false)(p.pos)
+ }
+
+ val isInImplClass = target.owner.isImplClass
+
+ def makeCaptures(actualCaptures: List[js.Tree]) = {
+ (actualCaptures map { c => (c: @unchecked) match {
+ case js.VarRef(ident, _) =>
+ (js.ParamDef(ident, c.tpe, mutable = false)(c.pos),
+ js.VarRef(ident, false)(c.tpe)(c.pos))
+ }}).unzip
+ }
+
+ val (allFormalCaptures, body, allActualCaptures) = if (!isInImplClass) {
+ val thisActualCapture = genExpr(receiver)
+ val thisFormalCapture = js.ParamDef(
+ freshLocalIdent("this")(receiver.pos),
+ thisActualCapture.tpe, mutable = false)(receiver.pos)
+ val thisCaptureArg = thisFormalCapture.ref
+ val (actualArgs, actualCaptures) = allArgs.splitAt(formalArgs.size)
+ val (formalCaptures, captureArgs) = makeCaptures(actualCaptures)
+ val body = genApplyMethod(thisCaptureArg, receiver.tpe, target,
+ actualArgs ::: captureArgs)
+
+ (thisFormalCapture :: formalCaptures,
+ body, thisActualCapture :: actualCaptures)
+ } else {
+ val (thisActualCapture :: actualArgs, actualCaptures) =
+ allArgs.splitAt(formalArgs.size+1)
+ val (thisFormalCapture :: formalCaptures, thisCaptureArg :: captureArgs) =
+ makeCaptures(thisActualCapture :: actualCaptures)
+ val body = genTraitImplApply(target,
+ thisCaptureArg :: actualArgs ::: captureArgs)
+
+ (thisFormalCapture :: formalCaptures,
+ body, thisActualCapture :: actualCaptures)
+ }
+
+ val (patchedFormalArgs, patchedBody) =
+ patchFunBodyWithBoxes(target, formalArgs, body)
+ val closure = js.Closure(
+ allFormalCaptures,
+ patchedFormalArgs,
+ patchedBody,
+ allActualCaptures)
+
+ JSFunctionToScala(closure, params.size)
+ }
+
+ private def patchFunBodyWithBoxes(methodSym: Symbol,
+ params: List[js.ParamDef], body: js.Tree)(
+ implicit pos: Position): (List[js.ParamDef], js.Tree) = {
+ val methodType = enteringPhase(currentRun.posterasurePhase)(methodSym.tpe)
+
+ val (patchedParams, paramsLocal) = (for {
+ (param, paramSym) <- params zip methodType.params
+ } yield {
+ val paramTpe = enteringPhase(currentRun.posterasurePhase)(paramSym.tpe)
+ val paramName = param.name
+ val js.Ident(name, origName) = paramName
+ val newOrigName = origName.getOrElse(name)
+ val newNameIdent = freshLocalIdent(newOrigName)(paramName.pos)
+ val patchedParam = js.ParamDef(newNameIdent, jstpe.AnyType,
+ mutable = false)(param.pos)
+ val paramLocal = js.VarDef(paramName, param.ptpe, mutable = false,
+ fromAny(patchedParam.ref, paramTpe))
+ (patchedParam, paramLocal)
+ }).unzip
+
+ val patchedBody = js.Block(
+ paramsLocal :+ ensureBoxed(body, methodType.resultType))
+
+ (patchedParams, patchedBody)
+ }
+
+ // Utilities ---------------------------------------------------------------
+
+ /** Generate a literal "zero" for the requested type */
+ def genZeroOf(tpe: Type)(implicit pos: Position): js.Tree = toTypeKind(tpe) match {
+ case VOID => abort("Cannot call genZeroOf(VOID)")
+ case BOOL => js.BooleanLiteral(false)
+ case LONG => js.LongLiteral(0L)
+ case INT(_) => js.IntLiteral(0)
+ case FloatKind => js.FloatLiteral(0.0f)
+ case DoubleKind => js.DoubleLiteral(0.0)
+ case _ => js.Null()
+ }
+
+ /** Generate loading of a module value
+ * Can be given either the module symbol, or its module class symbol.
+ */
+ def genLoadModule(sym0: Symbol)(implicit pos: Position): js.Tree = {
+ require(sym0.isModuleOrModuleClass,
+ "genLoadModule called with non-module symbol: " + sym0)
+ val sym1 = if (sym0.isModule) sym0.moduleClass else sym0
+ val sym = // redirect all static methods of String to RuntimeString
+ if (sym1 == StringModule) RuntimeStringModule.moduleClass
+ else sym1
+
+ val isGlobalScope = sym.tpe.typeSymbol isSubClass JSGlobalScopeClass
+
+ if (isGlobalScope) genLoadGlobal()
+ else if (isRawJSType(sym.tpe)) genPrimitiveJSModule(sym)
+ else {
+ if (!foreignIsImplClass(sym))
+ currentMethodInfoBuilder.accessesModule(sym)
+ js.LoadModule(jstpe.ClassType(encodeClassFullName(sym)))
+ }
+ }
+
+ /** Gen JS code to load the global scope. */
+ private def genLoadGlobal()(implicit pos: Position): js.Tree =
+ js.JSBracketSelect(js.JSEnvInfo(), js.StringLiteral("global"))
+
+ /** Generate access to a static member */
+ private def genStaticMember(sym: Symbol)(implicit pos: Position) = {
+ /* Actually, there is no static member in Scala.js. If we come here, that
+ * is because we found the symbol in a Java-emitted .class in the
+ * classpath. But the corresponding implementation in Scala.js will
+ * actually be a val in the companion module.
+ * We cannot use the .class files produced by our reimplementations of
+ * these classes (in which the symbol would be a Scala accessor) because
+ * that crashes the rest of scalac (at least for some choice symbols).
+ * Hence we cheat here.
+ */
+ import scalaPrimitives._
+ import jsPrimitives._
+ if (isPrimitive(sym)) {
+ getPrimitive(sym) match {
+ case UNITVAL => js.Undefined()
+ case UNITTYPE => genClassConstant(UnitTpe)
+ }
+ } else {
+ val instance = genLoadModule(sym.owner)
+ val method = encodeStaticMemberSym(sym)
+ currentMethodInfoBuilder.callsMethod(sym.owner, method)
+ js.Apply(instance, method, Nil)(toIRType(sym.tpe))
+ }
+ }
+
+ /** Generate a Class[_] value (e.g. coming from classOf[T]) */
+ private def genClassConstant(tpe: Type)(implicit pos: Position): js.Tree = {
+ val refType = toReferenceType(tpe)
+ currentMethodInfoBuilder.accessesClassData(refType)
+ js.ClassOf(refType)
+ }
+ }
+
+ /** Tests whether the given type represents a raw JavaScript type,
+ * i.e., whether it extends scala.scalajs.js.Any.
+ */
+ def isRawJSType(tpe: Type): Boolean =
+ tpe.typeSymbol.annotations.find(_.tpe =:= RawJSTypeAnnot.tpe).isDefined
+
+ /** Test whether `sym` is the symbol of a raw JS function definition */
+ private def isRawJSFunctionDef(sym: Symbol): Boolean =
+ sym.isAnonymousClass && AllJSFunctionClasses.exists(sym isSubClass _)
+
+ private def isRawJSCtorDefaultParam(sym: Symbol) = {
+ sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) &&
+ sym.owner.isModuleClass &&
+ isRawJSType(patchedLinkedClassOfClass(sym.owner).tpe) &&
+ nme.defaultGetterToMethod(sym.name) == nme.CONSTRUCTOR
+ }
+
+ private def patchedLinkedClassOfClass(sym: Symbol): Symbol = {
+ /* Work around a bug of scalac with linkedClassOfClass where package
+ * objects are involved (the companion class would somehow exist twice
+ * in the scope, making an assertion fail in Symbol.suchThat).
+ * Basically this inlines linkedClassOfClass up to companionClass,
+ * then replaces the `suchThat` by a `filter` and `head`.
+ */
+ val flatOwnerInfo = {
+ // inline Symbol.flatOwnerInfo because it is protected
+ if (sym.needsFlatClasses)
+ sym.info
+ sym.owner.rawInfo
+ }
+ val result = flatOwnerInfo.decl(sym.name).filter(_ isCoDefinedWith sym)
+ if (!result.isOverloaded) result
+ else result.alternatives.head
+ }
+
+ private def isStringType(tpe: Type): Boolean =
+ tpe.typeSymbol == StringClass
+
+ private def isLongType(tpe: Type): Boolean =
+ tpe.typeSymbol == LongClass
+
+ private lazy val BoxedBooleanClass = boxedClass(BooleanClass)
+ private lazy val BoxedByteClass = boxedClass(ByteClass)
+ private lazy val BoxedShortClass = boxedClass(ShortClass)
+ private lazy val BoxedIntClass = boxedClass(IntClass)
+ private lazy val BoxedLongClass = boxedClass(LongClass)
+ private lazy val BoxedFloatClass = boxedClass(FloatClass)
+ private lazy val BoxedDoubleClass = boxedClass(DoubleClass)
+
+ private lazy val NumberClass = requiredClass[java.lang.Number]
+
+ private lazy val HijackedNumberClasses =
+ Seq(BoxedByteClass, BoxedShortClass, BoxedIntClass, BoxedLongClass,
+ BoxedFloatClass, BoxedDoubleClass)
+ private lazy val HijackedBoxedClasses =
+ Seq(BoxedUnitClass, BoxedBooleanClass) ++ HijackedNumberClasses
+
+ protected lazy val isHijackedBoxedClass: Set[Symbol] =
+ HijackedBoxedClasses.toSet
+
+ private lazy val InlineAnnotationClass = requiredClass[scala.inline]
+
+ private def isMaybeJavaScriptException(tpe: Type) =
+ JavaScriptExceptionClass isSubClass tpe.typeSymbol
+
+ /** Get JS name of Symbol if it was specified with JSName annotation, or
+ * infers a default from the Scala name. */
+ def jsNameOf(sym: Symbol): String =
+ sym.getAnnotation(JSNameAnnotation).flatMap(_.stringArg(0)).getOrElse(
+ sym.unexpandedName.decoded)
+
+ def isStaticModule(sym: Symbol): Boolean =
+ sym.isModuleClass && !sym.isImplClass && !sym.isLifted
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSExports.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSExports.scala
new file mode 100644
index 0000000..92dc26b
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSExports.scala
@@ -0,0 +1,751 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.collection.mutable
+
+import scala.tools.nsc._
+import scala.math.PartialOrdering
+import scala.reflect.internal.Flags
+
+import scala.scalajs.ir
+import ir.{Trees => js, Types => jstpe}
+
+import util.ScopedVar
+import ScopedVar.withScopedVars
+
+/** Generation of exports for JavaScript
+ *
+ * @author Sébastien Doeraene
+ */
+trait GenJSExports extends SubComponent { self: GenJSCode =>
+ import global._
+ import jsAddons._
+ import definitions._
+ import jsDefinitions._
+
+ trait JSExportsPhase { this: JSCodePhase =>
+
+ /**
+ * Generate exporter methods for a class
+ * @param classSym symbol of class we export for
+ * @param decldExports symbols exporter methods that have been encountered in
+ * the class' tree. This is not the same as classSym.info.delcs since
+ * inherited concrete methods from traits should be in this param, too
+ */
+ def genMemberExports(
+ classSym: Symbol,
+ decldExports: List[Symbol]): List[js.Tree] = {
+
+ val newlyDecldExports = decldExports.filterNot { isOverridingExport _ }
+ val newlyDecldExportNames =
+ newlyDecldExports.map(_.name.toTermName).toList.distinct
+
+ newlyDecldExportNames map { genMemberExport(classSym, _) }
+ }
+
+ def genConstructorExports(classSym: Symbol): List[js.ConstructorExportDef] = {
+ val constructors = classSym.tpe.member(nme.CONSTRUCTOR).alternatives
+
+ // Generate exports from constructors and their annotations
+ val ctorExports = for {
+ ctor <- constructors
+ exp <- jsInterop.exportsOf(ctor)
+ } yield (exp, ctor)
+
+ val exports = for {
+ (jsName, specs) <- ctorExports.groupBy(_._1.jsName) // group by exported name
+ } yield {
+ val (namedExports, normalExports) = specs.partition(_._1.isNamed)
+
+ val normalCtors = normalExports.map(s => ExportedSymbol(s._2))
+ val namedCtors = for {
+ (exp, ctor) <- namedExports
+ } yield {
+ implicit val pos = exp.pos
+ ExportedBody(List(JSAnyTpe),
+ genNamedExporterBody(ctor, genFormalArg(1).ref),
+ nme.CONSTRUCTOR.toString, pos)
+ }
+
+ val ctors = normalCtors ++ namedCtors
+
+ implicit val pos = ctors.head.pos
+
+ val js.MethodDef(_, args, _, body) =
+ withNewLocalNameScope(genExportMethod(ctors, jsName))
+
+ js.ConstructorExportDef(jsName, args, body)
+ }
+
+ exports.toList
+ }
+
+ def genModuleAccessorExports(classSym: Symbol): List[js.ModuleExportDef] = {
+ for {
+ exp <- jsInterop.exportsOf(classSym)
+ } yield {
+ implicit val pos = exp.pos
+
+ if (exp.isNamed)
+ reporter.error(pos, "You may not use @JSNamedExport on an object")
+
+ js.ModuleExportDef(exp.jsName)
+ }
+ }
+
+ /** Generate the exporter proxy for a named export */
+ def genNamedExporterDef(dd: DefDef): js.MethodDef = {
+ implicit val pos = dd.pos
+
+ val sym = dd.symbol
+
+ val Block(Apply(fun, _) :: Nil, _) = dd.rhs
+ val trgSym = fun.symbol
+
+ val inArg =
+ js.ParamDef(js.Ident("namedParams"), jstpe.AnyType, mutable = false)
+ val inArgRef = inArg.ref
+
+ val methodIdent = encodeMethodSym(sym)
+
+ withScopedVars(
+ currentMethodInfoBuilder :=
+ currentClassInfoBuilder.addMethod(methodIdent.name)
+ ) {
+ js.MethodDef(methodIdent, List(inArg), toIRType(sym.tpe.resultType),
+ genNamedExporterBody(trgSym, inArg.ref))(None)
+ }
+ }
+
+ private def genNamedExporterBody(trgSym: Symbol, inArg: js.Tree)(
+ implicit pos: Position) = {
+
+ if (hasRepeatedParam(trgSym)) {
+ reporter.error(pos,
+ "You may not name-export a method with a *-parameter")
+ }
+
+ val jsArgs = for {
+ (pSym, index) <- trgSym.info.params.zipWithIndex
+ } yield {
+ val rhs = js.JSBracketSelect(inArg,
+ js.StringLiteral(pSym.name.decoded))
+ js.VarDef(js.Ident("namedArg$" + index), jstpe.AnyType,
+ mutable = false, rhs = rhs)
+ }
+
+ val jsArgRefs = jsArgs.map(_.ref)
+
+ // Generate JS code to prepare arguments (default getters and unboxes)
+ val jsArgPrep = genPrepareArgs(jsArgRefs, trgSym)
+ val jsResult = genResult(trgSym, jsArgPrep.map(_.ref))
+
+ js.Block(jsArgs ++ jsArgPrep :+ jsResult)
+ }
+
+ private def genMemberExport(classSym: Symbol, name: TermName): js.Tree = {
+ val alts = classSym.info.member(name).alternatives
+
+ assert(!alts.isEmpty,
+ s"Ended up with no alternatives for ${classSym.fullName}::$name. " +
+ s"Original set was ${alts} with types ${alts.map(_.tpe)}")
+
+ val (jsName, isProp) = jsInterop.jsExportInfo(name)
+
+ // Check if we have a conflicting export of the other kind
+ val conflicting =
+ classSym.info.member(jsInterop.scalaExportName(jsName, !isProp))
+
+ if (conflicting != NoSymbol) {
+ val kind = if (isProp) "property" else "method"
+ val alts = conflicting.alternatives
+
+ reporter.error(alts.head.pos,
+ s"Exported $kind $jsName conflicts with ${alts.head.fullName}")
+ }
+
+ withNewLocalNameScope {
+ if (isProp)
+ genExportProperty(alts, jsName)
+ else
+ genExportMethod(alts.map(ExportedSymbol), jsName)
+ }
+ }
+
+ private def genExportProperty(alts: List[Symbol], jsName: String) = {
+ assert(!alts.isEmpty)
+ implicit val pos = alts.head.pos
+
+ // Separate getters and setters. Somehow isJSGetter doesn't work here. Hence
+ // we just check the parameter list length.
+ val (getter, setters) = alts.partition(_.tpe.params.isEmpty)
+
+ // if we have more than one getter, something went horribly wrong
+ assert(getter.size <= 1,
+ s"Found more than one getter to export for name ${jsName}.")
+
+ val getTree =
+ if (getter.isEmpty) js.EmptyTree
+ else genApplyForSym(getter.head)
+
+ val setTree =
+ if (setters.isEmpty) js.EmptyTree
+ else genExportSameArgc(setters.map(ExportedSymbol), 0) // we only have 1 argument
+
+ js.PropertyDef(js.StringLiteral(jsName), getTree, genFormalArg(1), setTree)
+ }
+
+ /** generates the exporter function (i.e. exporter for non-properties) for
+ * a given name */
+ private def genExportMethod(alts0: List[Exported], jsName: String) = {
+ assert(alts0.nonEmpty,
+ "need at least one alternative to generate exporter method")
+
+ implicit val pos = alts0.head.pos
+
+ val alts = {
+ // toString() is always exported. We might need to add it here
+ // to get correct overloading.
+ if (jsName == "toString" && alts0.forall(_.params.nonEmpty))
+ ExportedSymbol(Object_toString) :: alts0
+ else
+ alts0
+ }
+
+ // Factor out methods with variable argument lists. Note that they can
+ // only be at the end of the lists as enforced by PrepJSExports
+ val (varArgMeths, normalMeths) = alts.partition(_.hasRepeatedParam)
+
+ // Highest non-repeated argument count
+ val maxArgc = (
+ // We have argc - 1, since a repeated parameter list may also be empty
+ // (unlike a normal parameter)
+ varArgMeths.map(_.params.size - 1) ++
+ normalMeths.map(_.params.size)
+ ).max
+
+ val formalArgs = genFormalArgs(maxArgc)
+
+ // Calculates possible arg counts for normal method
+ def argCounts(ex: Exported) = ex match {
+ case ExportedSymbol(sym) =>
+ val params = sym.tpe.params
+ // Find default param
+ val dParam = params.indexWhere { _.hasFlag(Flags.DEFAULTPARAM) }
+ if (dParam == -1) Seq(params.size)
+ else dParam to params.size
+ case ex: ExportedBody =>
+ List(ex.params.size)
+ }
+
+ // Generate tuples (argc, method)
+ val methodArgCounts = {
+ // Normal methods
+ for {
+ method <- normalMeths
+ argc <- argCounts(method)
+ } yield (argc, method)
+ } ++ {
+ // Repeated parameter methods
+ for {
+ method <- varArgMeths
+ argc <- method.params.size - 1 to maxArgc
+ } yield (argc, method)
+ }
+
+ // Create a map: argCount -> methods (methods may appear multiple times)
+ val methodByArgCount =
+ methodArgCounts.groupBy(_._1).mapValues(_.map(_._2).toSet)
+
+ // Create tuples: (methods, argCounts). This will be the cases we generate
+ val caseDefinitions =
+ methodByArgCount.groupBy(_._2).mapValues(_.keySet)
+
+ // Verify stuff about caseDefinitions
+ assert({
+ val argcs = caseDefinitions.values.flatten.toList
+ argcs == argcs.distinct &&
+ argcs.forall(_ <= maxArgc)
+ }, "every argc should appear only once and be lower than max")
+
+ // Generate a case block for each (methods, argCounts) tuple
+ val cases = for {
+ (methods, argcs) <- caseDefinitions
+ if methods.nonEmpty && argcs.nonEmpty
+
+ // exclude default case we're generating anyways for varargs
+ if methods != varArgMeths.toSet
+
+ // body of case to disambiguates methods with current count
+ caseBody =
+ genExportSameArgc(methods.toList, 0, Some(argcs.min))
+
+ // argc in reverse order
+ argcList = argcs.toList.sortBy(- _)
+ } yield (argcList.map(js.IntLiteral(_)), caseBody)
+
+ val hasVarArg = varArgMeths.nonEmpty
+
+ def defaultCase = {
+ if (!hasVarArg)
+ genThrowTypeError()
+ else
+ genExportSameArgc(varArgMeths, 0)
+ }
+
+ val body = {
+ if (cases.isEmpty)
+ defaultCase
+ else if (cases.size == 1 && !hasVarArg)
+ cases.head._2
+ else {
+ js.Match(
+ js.Unbox(js.JSBracketSelect(
+ js.VarRef(js.Ident("arguments"), false)(jstpe.AnyType),
+ js.StringLiteral("length")),
+ 'I'),
+ cases.toList, defaultCase)(jstpe.AnyType)
+ }
+ }
+
+ js.MethodDef(js.StringLiteral(jsName), formalArgs, jstpe.AnyType, body)(None)
+ }
+
+ /**
+ * Resolve method calls to [[alts]] while assuming they have the same
+ * parameter count.
+ * @param alts Alternative methods
+ * @param paramIndex Index where to start disambiguation
+ * @param maxArgc only use that many arguments
+ */
+ private def genExportSameArgc(alts: List[Exported],
+ paramIndex: Int, maxArgc: Option[Int] = None): js.Tree = {
+
+ implicit val pos = alts.head.pos
+
+ if (alts.size == 1)
+ alts.head.body
+ else if (maxArgc.exists(_ <= paramIndex) ||
+ !alts.exists(_.params.size > paramIndex)) {
+ // We reach here in three cases:
+ // 1. The parameter list has been exhausted
+ // 2. The optional argument count restriction has triggered
+ // 3. We only have (more than once) repeated parameters left
+ // Therefore, we should fail
+ reporter.error(pos,
+ s"""Cannot disambiguate overloads for exported method ${alts.head.name} with types
+ | ${alts.map(_.typeInfo).mkString("\n ")}""".stripMargin)
+ js.Undefined()
+ } else {
+
+ val altsByTypeTest = groupByWithoutHashCode(alts) {
+ case ExportedSymbol(alt) =>
+ // get parameter type while resolving repeated params
+ val tpe = enteringPhase(currentRun.uncurryPhase) {
+ val ps = alt.paramss.flatten
+ if (ps.size <= paramIndex || isRepeated(ps(paramIndex))) {
+ assert(isRepeated(ps.last))
+ repeatedToSingle(ps.last.tpe)
+ } else {
+ enteringPhase(currentRun.posterasurePhase) {
+ ps(paramIndex).tpe
+ }
+ }
+ }
+
+ typeTestForTpe(tpe)
+
+ case ex: ExportedBody =>
+ typeTestForTpe(ex.params(paramIndex))
+ }
+
+ if (altsByTypeTest.size == 1) {
+ // Testing this parameter is not doing any us good
+ genExportSameArgc(alts, paramIndex+1, maxArgc)
+ } else {
+ // Sort them so that, e.g., isInstanceOf[String]
+ // comes before isInstanceOf[Object]
+ val sortedAltsByTypeTest = topoSortDistinctsBy(
+ altsByTypeTest)(_._1)(RTTypeTest.Ordering)
+
+ val defaultCase = genThrowTypeError()
+
+ sortedAltsByTypeTest.foldRight[js.Tree](defaultCase) { (elem, elsep) =>
+ val (typeTest, subAlts) = elem
+ implicit val pos = subAlts.head.pos
+
+ val param = genFormalArg(paramIndex+1)
+ val genSubAlts = genExportSameArgc(subAlts, paramIndex+1, maxArgc)
+
+ def hasDefaultParam = subAlts.exists {
+ case ExportedSymbol(p) =>
+ val params = p.tpe.params
+ params.size > paramIndex &&
+ params(paramIndex).hasFlag(Flags.DEFAULTPARAM)
+ case _: ExportedBody => false
+ }
+
+ val optCond = typeTest match {
+ case HijackedTypeTest(boxedClassName, _) =>
+ Some(js.IsInstanceOf(param.ref, jstpe.ClassType(boxedClassName)))
+
+ case InstanceOfTypeTest(tpe) =>
+ Some(genIsInstanceOf(param.ref, tpe))
+
+ case NoTypeTest =>
+ None
+ }
+
+ optCond.fold[js.Tree] {
+ genSubAlts // note: elsep is discarded, obviously
+ } { cond =>
+ val condOrUndef = if (!hasDefaultParam) cond else {
+ js.If(cond, js.BooleanLiteral(true),
+ js.BinaryOp(js.BinaryOp.===, param.ref, js.Undefined()))(
+ jstpe.BooleanType)
+ }
+ js.If(condOrUndef, genSubAlts, elsep)(jstpe.AnyType)
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Generate a call to the method [[sym]] while using the formalArguments
+ * and potentially the argument array. Also inserts default parameters if
+ * required.
+ */
+ private def genApplyForSym(sym: Symbol): js.Tree = {
+ implicit val pos = sym.pos
+
+ // the (single) type of the repeated parameter if any
+ val repeatedTpe = enteringPhase(currentRun.uncurryPhase) {
+ for {
+ param <- sym.paramss.flatten.lastOption
+ if isRepeated(param)
+ } yield repeatedToSingle(param.tpe)
+ }
+
+ val normalArgc = sym.tpe.params.size -
+ (if (repeatedTpe.isDefined) 1 else 0)
+
+ // optional repeated parameter list
+ val jsVarArg = repeatedTpe map { tpe =>
+ // Copy arguments that go to vararg into an array, put it in a wrapper
+
+ val countIdent = freshLocalIdent("count")
+ val count = js.VarRef(countIdent, mutable = false)(jstpe.IntType)
+
+ val counterIdent = freshLocalIdent("i")
+ val counter = js.VarRef(counterIdent, mutable = true)(jstpe.IntType)
+
+ val arrayIdent = freshLocalIdent("varargs")
+ val array = js.VarRef(arrayIdent, mutable = false)(jstpe.AnyType)
+
+ val arguments = js.VarRef(js.Ident("arguments"),
+ mutable = false)(jstpe.AnyType)
+ val argLen = js.Unbox(
+ js.JSBracketSelect(arguments, js.StringLiteral("length")), 'I')
+ val argOffset = js.IntLiteral(normalArgc)
+
+ val jsArrayCtor =
+ js.JSBracketSelect(
+ js.JSBracketSelect(js.JSEnvInfo(), js.StringLiteral("global")),
+ js.StringLiteral("Array"))
+
+ js.Block(
+ // var i = 0
+ js.VarDef(counterIdent, jstpe.IntType, mutable = true,
+ rhs = js.IntLiteral(0)),
+ // val count = arguments.length - <normalArgc>
+ js.VarDef(countIdent, jstpe.IntType, mutable = false,
+ rhs = js.BinaryOp(js.BinaryOp.Int_-, argLen, argOffset)),
+ // val varargs = new Array(count)
+ js.VarDef(arrayIdent, jstpe.AnyType, mutable = false,
+ rhs = js.JSNew(jsArrayCtor, List(count))),
+ // while (i < count)
+ js.While(js.BinaryOp(js.BinaryOp.Num_<, counter, count), js.Block(
+ // varargs[i] = arguments[<normalArgc> + i];
+ js.Assign(
+ js.JSBracketSelect(array, counter),
+ js.JSBracketSelect(arguments,
+ js.BinaryOp(js.BinaryOp.Int_+, argOffset, counter))),
+ // i = i + 1 (++i won't work, desugar eliminates it)
+ js.Assign(counter, js.BinaryOp(js.BinaryOp.Int_+,
+ counter, js.IntLiteral(1)))
+ )),
+ // new WrappedArray(varargs)
+ genNew(WrappedArrayClass, WrappedArray_ctor, List(array))
+ )
+ }
+
+ // normal arguments
+ val jsArgs = genFormalArgs(normalArgc)
+ val jsArgRefs = jsArgs.map(_.ref)
+
+ // Generate JS code to prepare arguments (default getters and unboxes)
+ val jsArgPrep = genPrepareArgs(jsArgRefs, sym)
+ val jsResult = genResult(sym, jsArgPrep.map(_.ref) ++ jsVarArg)
+
+ js.Block(jsArgPrep :+ jsResult)
+ }
+
+ /** Generate the necessary JavaScript code to prepare the arguments of an
+ * exported method (unboxing and default parameter handling)
+ */
+ private def genPrepareArgs(jsArgs: List[js.VarRef], sym: Symbol)(
+ implicit pos: Position): List[js.VarDef] = {
+
+ val result = new mutable.ListBuffer[js.VarDef]
+
+ val funTpe = enteringPhase(currentRun.posterasurePhase)(sym.tpe)
+ for {
+ (jsArg, (param, i)) <- jsArgs zip funTpe.params.zipWithIndex
+ } yield {
+ // Code to verify the type of the argument (if it is defined)
+ val verifiedArg = {
+ val tpePosterasure =
+ enteringPhase(currentRun.posterasurePhase)(param.tpe)
+ tpePosterasure match {
+ case tpe if isPrimitiveValueType(tpe) =>
+ val unboxed = makePrimitiveUnbox(jsArg, tpe)
+ // Ensure we don't convert null to a primitive value type
+ js.If(js.BinaryOp(js.BinaryOp.===, jsArg, js.Null()),
+ genThrowTypeError(s"Found null, expected $tpe"),
+ unboxed)(unboxed.tpe)
+ case tpe: ErasedValueType =>
+ val boxedClass = tpe.valueClazz
+ val unboxMethod = boxedClass.derivedValueClassUnbox
+ genApplyMethod(
+ genAsInstanceOf(jsArg, tpe),
+ boxedClass, unboxMethod, Nil)
+ case tpe =>
+ genAsInstanceOf(jsArg, tpe)
+ }
+ }
+
+ // If argument is undefined and there is a default getter, call it
+ val verifiedOrDefault = if (param.hasFlag(Flags.DEFAULTPARAM)) {
+ js.If(js.BinaryOp(js.BinaryOp.===, jsArg, js.Undefined()), {
+ val trgSym = {
+ if (sym.isClassConstructor) sym.owner.companionModule.moduleClass
+ else sym.owner
+ }
+ val defaultGetter = trgSym.tpe.member(
+ nme.defaultGetterName(sym.name, i+1))
+
+ assert(defaultGetter.exists,
+ s"need default getter for method ${sym.fullName}")
+ assert(!defaultGetter.isOverloaded)
+
+ val trgTree = {
+ if (sym.isClassConstructor) genLoadModule(trgSym)
+ else js.This()(encodeClassType(trgSym))
+ }
+
+ // Pass previous arguments to defaultGetter
+ genApplyMethod(trgTree, trgSym, defaultGetter,
+ result.take(defaultGetter.tpe.params.size).toList.map(_.ref))
+ }, {
+ // Otherwise, unbox the argument
+ verifiedArg
+ })(verifiedArg.tpe)
+ } else {
+ // Otherwise, it is always the unboxed argument
+ verifiedArg
+ }
+
+ result +=
+ js.VarDef(js.Ident("prep"+jsArg.ident.name, jsArg.ident.originalName),
+ verifiedOrDefault.tpe, mutable = false, verifiedOrDefault)
+ }
+
+ result.toList
+ }
+
+ /** Generate the final forwarding call to the exported method.
+ * Attention: This method casts the arguments to the right type. The IR
+ * checker will not detect if you pass in a wrongly typed argument.
+ */
+ private def genResult(sym: Symbol,
+ args: List[js.Tree])(implicit pos: Position) = {
+ val thisType =
+ if (sym.owner == ObjectClass) jstpe.ClassType(ir.Definitions.ObjectClass)
+ else encodeClassType(sym.owner)
+ val call = genApplyMethod(js.This()(thisType), sym.owner, sym, args)
+ ensureBoxed(call,
+ enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType))
+ }
+
+ private sealed abstract class Exported {
+ def pos: Position
+ def params: List[Type]
+ def body: js.Tree
+ def name: String
+ def typeInfo: String
+ def hasRepeatedParam: Boolean
+ }
+
+ private case class ExportedSymbol(sym: Symbol) extends Exported {
+ def pos: Position = sym.pos
+ def params: List[Type] = sym.tpe.params.map(_.tpe)
+ def body: js.Tree = genApplyForSym(sym)
+ def name: String = sym.name.toString
+ def typeInfo: String = sym.tpe.toString
+ def hasRepeatedParam: Boolean = GenJSExports.this.hasRepeatedParam(sym)
+ }
+
+ private case class ExportedBody(params: List[Type], body: js.Tree,
+ name: String, pos: Position) extends Exported {
+ def typeInfo: String = params.mkString("(", ", ", ")")
+ val hasRepeatedParam: Boolean = false
+ }
+ }
+
+ private def isOverridingExport(sym: Symbol): Boolean = {
+ lazy val osym = sym.nextOverriddenSymbol
+ sym.isOverridingSymbol && !osym.owner.isInterface
+ }
+
+ private sealed abstract class RTTypeTest
+
+ private final case class HijackedTypeTest(
+ boxedClassName: String, rank: Int) extends RTTypeTest
+
+ private final case class InstanceOfTypeTest(tpe: Type) extends RTTypeTest {
+ override def equals(that: Any): Boolean = {
+ that match {
+ case InstanceOfTypeTest(thatTpe) => tpe =:= thatTpe
+ case _ => false
+ }
+ }
+ }
+
+ private case object NoTypeTest extends RTTypeTest
+
+ private object RTTypeTest {
+ implicit object Ordering extends PartialOrdering[RTTypeTest] {
+ override def tryCompare(lhs: RTTypeTest, rhs: RTTypeTest): Option[Int] = {
+ if (lteq(lhs, rhs)) if (lteq(rhs, lhs)) Some(0) else Some(-1)
+ else if (lteq(rhs, lhs)) Some(1) else None
+ }
+
+ override def lteq(lhs: RTTypeTest, rhs: RTTypeTest): Boolean = {
+ (lhs, rhs) match {
+ // NoTypeTest is always last
+ case (_, NoTypeTest) => true
+ case (NoTypeTest, _) => false
+
+ case (HijackedTypeTest(_, rank1), HijackedTypeTest(_, rank2)) =>
+ rank1 <= rank2
+
+ case (InstanceOfTypeTest(t1), InstanceOfTypeTest(t2)) =>
+ t1 <:< t2
+
+ case (_:HijackedTypeTest, _:InstanceOfTypeTest) => true
+ case (_:InstanceOfTypeTest, _:HijackedTypeTest) => false
+ }
+ }
+
+ override def equiv(lhs: RTTypeTest, rhs: RTTypeTest): Boolean = {
+ lhs == rhs
+ }
+ }
+ }
+
+ // Very simple O(n²) topological sort for elements assumed to be distinct
+ private def topoSortDistinctsBy[A <: AnyRef, B](coll: List[A])(f: A => B)(
+ implicit ord: PartialOrdering[B]): List[A] = {
+
+ @scala.annotation.tailrec
+ def loop(coll: List[A], acc: List[A]): List[A] = {
+ if (coll.isEmpty) acc
+ else if (coll.tail.isEmpty) coll.head :: acc
+ else {
+ val (lhs, rhs) = coll.span(x => !coll.forall(
+ y => (x eq y) || !ord.lteq(f(x), f(y))))
+ assert(!rhs.isEmpty, s"cycle while ordering $coll")
+ loop(lhs ::: rhs.tail, rhs.head :: acc)
+ }
+ }
+
+ loop(coll, Nil)
+ }
+
+ private def typeTestForTpe(tpe: Type): RTTypeTest = {
+ tpe match {
+ case tpe: ErasedValueType =>
+ InstanceOfTypeTest(tpe.valueClazz.typeConstructor)
+
+ case _ =>
+ import ir.{Definitions => Defs}
+ (toTypeKind(tpe): @unchecked) match {
+ case VoidKind => HijackedTypeTest(Defs.BoxedUnitClass, 0)
+ case BooleanKind => HijackedTypeTest(Defs.BoxedBooleanClass, 1)
+ case ByteKind => HijackedTypeTest(Defs.BoxedByteClass, 2)
+ case ShortKind => HijackedTypeTest(Defs.BoxedShortClass, 3)
+ case IntKind => HijackedTypeTest(Defs.BoxedIntegerClass, 4)
+ case FloatKind => HijackedTypeTest(Defs.BoxedFloatClass, 5)
+ case DoubleKind => HijackedTypeTest(Defs.BoxedDoubleClass, 6)
+
+ case CharKind => InstanceOfTypeTest(boxedClass(CharClass).tpe)
+ case LongKind => InstanceOfTypeTest(boxedClass(LongClass).tpe)
+
+ case REFERENCE(cls) =>
+ if (cls == StringClass) HijackedTypeTest(Defs.StringClass, 7)
+ else if (cls == ObjectClass) NoTypeTest
+ else if (isRawJSType(tpe)) {
+ cls match {
+ case JSUndefinedClass => HijackedTypeTest(Defs.BoxedUnitClass, 0)
+ case JSBooleanClass => HijackedTypeTest(Defs.BoxedBooleanClass, 1)
+ case JSNumberClass => HijackedTypeTest(Defs.BoxedDoubleClass, 6)
+ case JSStringClass => HijackedTypeTest(Defs.StringClass, 7)
+ case _ => NoTypeTest
+ }
+ } else InstanceOfTypeTest(tpe)
+
+ case ARRAY(_) => InstanceOfTypeTest(tpe)
+ }
+ }
+ }
+
+ // Group-by that does not rely on hashCode(), only equals() - O(n²)
+ private def groupByWithoutHashCode[A, B](
+ coll: List[A])(f: A => B): List[(B, List[A])] = {
+
+ import scala.collection.mutable.ArrayBuffer
+ val m = new ArrayBuffer[(B, List[A])]
+ m.sizeHint(coll.length)
+
+ for (elem <- coll) {
+ val key = f(elem)
+ val index = m.indexWhere(_._1 == key)
+ if (index < 0) m += ((key, List(elem)))
+ else m(index) = (key, elem :: m(index)._2)
+ }
+
+ m.toList
+ }
+
+ private def genThrowTypeError(msg: String = "No matching overload")(
+ implicit pos: Position): js.Tree = {
+ js.Throw(js.StringLiteral(msg))
+ }
+
+ private def genFormalArgs(count: Int)(implicit pos: Position): List[js.ParamDef] =
+ (1 to count map genFormalArg).toList
+
+ private def genFormalArg(index: Int)(implicit pos: Position): js.ParamDef =
+ js.ParamDef(js.Ident("arg$" + index), jstpe.AnyType, mutable = false)
+
+ private def hasRepeatedParam(sym: Symbol) =
+ enteringPhase(currentRun.uncurryPhase) {
+ sym.paramss.flatten.lastOption.exists(isRepeated _)
+ }
+
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSFiles.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSFiles.scala
new file mode 100644
index 0000000..f754e70
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/GenJSFiles.scala
@@ -0,0 +1,51 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.tools.nsc._
+import scala.tools.nsc.io.AbstractFile
+import scala.reflect.internal.pickling.PickleBuffer
+
+import java.io._
+
+import scala.scalajs.ir
+import ir.Infos._
+
+/** Send JS ASTs to files
+ *
+ * @author Sébastien Doeraene
+ */
+trait GenJSFiles extends SubComponent { self: GenJSCode =>
+ import global._
+ import jsAddons._
+
+ def genIRFile(cunit: CompilationUnit, sym: Symbol, tree: ir.Trees.ClassDef,
+ classInfo: ClassInfo): Unit = {
+ val outfile = getFileFor(cunit, sym, ".sjsir")
+ val output = outfile.bufferedOutput
+ try {
+ ir.InfoSerializers.serialize(output, classInfo)
+ ir.Serializers.serialize(output, tree)
+ } finally {
+ output.close()
+ }
+ }
+
+ private def getFileFor(cunit: CompilationUnit, sym: Symbol,
+ suffix: String) = {
+ val baseDir: AbstractFile =
+ settings.outputDirs.outputDirFor(cunit.source.file)
+
+ val pathParts = sym.fullName.split("[./]")
+ val dir = (baseDir /: pathParts.init)(_.subdirectoryNamed(_))
+
+ var filename = pathParts.last
+ if (sym.isModuleClass && !sym.isImplClass)
+ filename = filename + nme.MODULE_SUFFIX_STRING
+
+ dir fileNamed (filename + suffix)
+ }
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSDefinitions.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSDefinitions.scala
new file mode 100644
index 0000000..b8a483a
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSDefinitions.scala
@@ -0,0 +1,128 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.tools.nsc._
+
+/** Core definitions for Scala.js
+ *
+ * @author Sébastien Doeraene
+ */
+trait JSDefinitions { self: JSGlobalAddons =>
+ import global._
+
+ object jsDefinitions extends JSDefinitionsClass
+
+ import definitions._
+ import rootMirror._
+
+ class JSDefinitionsClass {
+
+ lazy val ScalaJSJSPackage = getPackage(newTermNameCached("scala.scalajs.js")) // compat 2.10/2.11
+ lazy val JSPackage_undefined = getMemberMethod(ScalaJSJSPackage, newTermName("undefined"))
+ lazy val JSPackage_isUndefined = getMemberMethod(ScalaJSJSPackage, newTermName("isUndefined"))
+ lazy val JSPackage_typeOf = getMemberMethod(ScalaJSJSPackage, newTermName("typeOf"))
+ lazy val JSPackage_debugger = getMemberMethod(ScalaJSJSPackage, newTermName("debugger"))
+ lazy val JSPackage_native = getMemberMethod(ScalaJSJSPackage, newTermName("native"))
+
+ lazy val ScalaJSJSPrimPackage = getPackage(newTermNameCached("scala.scalajs.js.prim")) // compat 2.10/2.11
+
+ lazy val JSAnyClass = getRequiredClass("scala.scalajs.js.Any")
+ lazy val JSDynamicClass = getRequiredClass("scala.scalajs.js.Dynamic")
+ lazy val JSDynamic_selectDynamic = getMemberMethod(JSDynamicClass, newTermName("selectDynamic"))
+ lazy val JSDynamic_updateDynamic = getMemberMethod(JSDynamicClass, newTermName("updateDynamic"))
+ lazy val JSDynamic_applyDynamic = getMemberMethod(JSDynamicClass, newTermName("applyDynamic"))
+ lazy val JSDictionaryClass = getRequiredClass("scala.scalajs.js.Dictionary")
+ lazy val JSDictionary_delete = getMemberMethod(JSDictionaryClass, newTermName("delete"))
+ lazy val JSNumberClass = getRequiredClass("scala.scalajs.js.prim.Number")
+ lazy val JSBooleanClass = getRequiredClass("scala.scalajs.js.prim.Boolean")
+ lazy val JSStringClass = getRequiredClass("scala.scalajs.js.prim.String")
+ lazy val JSUndefinedClass = getRequiredClass("scala.scalajs.js.prim.Undefined")
+ lazy val JSObjectClass = getRequiredClass("scala.scalajs.js.Object")
+ lazy val JSThisFunctionClass = getRequiredClass("scala.scalajs.js.ThisFunction")
+
+ lazy val JSGlobalScopeClass = getRequiredClass("scala.scalajs.js.GlobalScope")
+
+ lazy val UndefOrClass = getRequiredClass("scala.scalajs.js.UndefOr")
+
+ lazy val JSArrayClass = getRequiredClass("scala.scalajs.js.Array")
+ lazy val JSArray_apply = getMemberMethod(JSArrayClass, newTermName("apply"))
+ lazy val JSArray_update = getMemberMethod(JSArrayClass, newTermName("update"))
+
+ lazy val JSFunctionClasses = (0 to 22) map (n => getRequiredClass("scala.scalajs.js.Function"+n))
+ lazy val JSThisFunctionClasses = (0 to 21) map (n => getRequiredClass("scala.scalajs.js.ThisFunction"+n))
+ lazy val AllJSFunctionClasses = JSFunctionClasses ++ JSThisFunctionClasses
+
+ lazy val RuntimeExceptionClass = requiredClass[RuntimeException]
+ lazy val JavaScriptExceptionClass = getClassIfDefined("scala.scalajs.js.JavaScriptException")
+
+ lazy val JSNameAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSName")
+ lazy val JSBracketAccessAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSBracketAccess")
+ lazy val JSExportAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExport")
+ lazy val JSExportDescendentObjectsAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportDescendentObjects")
+ lazy val JSExportDescendentClassesAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportDescendentClasses")
+ lazy val JSExportAllAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportAll")
+ lazy val JSExportNamedAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportNamed")
+
+ lazy val JSAnyTpe = JSAnyClass.toTypeConstructor
+ lazy val JSDynamicTpe = JSDynamicClass.toTypeConstructor
+ lazy val JSNumberTpe = JSNumberClass.toTypeConstructor
+ lazy val JSBooleanTpe = JSBooleanClass.toTypeConstructor
+ lazy val JSStringTpe = JSStringClass.toTypeConstructor
+ lazy val JSUndefinedTpe = JSUndefinedClass.toTypeConstructor
+ lazy val JSObjectTpe = JSObjectClass.toTypeConstructor
+
+ lazy val JSGlobalScopeTpe = JSGlobalScopeClass.toTypeConstructor
+
+ lazy val JSFunctionTpes = JSFunctionClasses.map(_.toTypeConstructor)
+
+ lazy val JSAnyModule = JSAnyClass.companionModule
+ def JSAny_fromFunction(arity: Int) = getMemberMethod(JSAnyModule, newTermName("fromFunction"+arity))
+
+ lazy val JSDynamicModule = JSDynamicClass.companionModule
+ lazy val JSDynamic_newInstance = getMemberMethod(JSDynamicModule, newTermName("newInstance"))
+ lazy val JSDynamicLiteral = getMemberModule(JSDynamicModule, newTermName("literal"))
+ lazy val JSDynamicLiteral_applyDynamicNamed = getMemberMethod(JSDynamicLiteral, newTermName("applyDynamicNamed"))
+ lazy val JSDynamicLiteral_applyDynamic = getMemberMethod(JSDynamicLiteral, newTermName("applyDynamic"))
+
+ lazy val JSObjectModule = JSObjectClass.companionModule
+ lazy val JSObject_hasProperty = getMemberMethod(JSObjectModule, newTermName("hasProperty"))
+ lazy val JSObject_properties = getMemberMethod(JSObjectModule, newTermName("properties"))
+
+ lazy val JSArrayModule = JSArrayClass.companionModule
+ lazy val JSArray_create = getMemberMethod(JSArrayModule, newTermName("apply"))
+
+ lazy val JSThisFunctionModule = JSThisFunctionClass.companionModule
+ def JSThisFunction_fromFunction(arity: Int) = getMemberMethod(JSThisFunctionModule, newTermName("fromFunction"+arity))
+
+ lazy val RawJSTypeAnnot = getClassIfDefined("scala.scalajs.js.annotation.RawJSType")
+
+ lazy val RuntimeStringModule = getRequiredModule("scala.scalajs.runtime.RuntimeString")
+ lazy val RuntimeStringModuleClass = RuntimeStringModule.moduleClass
+
+ lazy val BooleanReflectiveCallClass = getRequiredClass("scala.scalajs.runtime.BooleanReflectiveCall")
+ lazy val NumberReflectiveCallClass = getRequiredClass("scala.scalajs.runtime.NumberReflectiveCall")
+ lazy val IntegerReflectiveCallClass = getRequiredClass("scala.scalajs.runtime.IntegerReflectiveCall")
+
+ lazy val RuntimePackageModule = getPackageObject("scala.scalajs.runtime")
+ lazy val Runtime_wrapJavaScriptException = getMemberMethod(RuntimePackageModule, newTermName("wrapJavaScriptException"))
+ lazy val Runtime_unwrapJavaScriptException = getMemberMethod(RuntimePackageModule, newTermName("unwrapJavaScriptException"))
+ lazy val Runtime_genTraversableOnce2jsArray = getMemberMethod(RuntimePackageModule, newTermName("genTraversableOnce2jsArray"))
+ lazy val Runtime_newJSObjectWithVarargs = getMemberMethod(RuntimePackageModule, newTermName("newJSObjectWithVarargs"))
+ lazy val Runtime_propertiesOf = getMemberMethod(RuntimePackageModule, newTermName("propertiesOf"))
+
+ lazy val WrappedArrayClass = getRequiredClass("scala.scalajs.js.WrappedArray")
+ lazy val WrappedArray_ctor = WrappedArrayClass.primaryConstructor
+
+ // This is a def, since similar symbols (arrayUpdateMethod, etc.) are in runDefinitions
+ // (rather than definitions) and we weren't sure if it is safe to make this a lazy val
+ def ScalaRunTime_isArray = getMemberMethod(ScalaRunTimeModule, newTermName("isArray")).suchThat(_.tpe.params.size == 2)
+
+ lazy val BoxesRunTime_boxToCharacter = getMemberMethod(BoxesRunTimeModule, newTermName("boxToCharacter"))
+ lazy val BoxesRunTime_unboxToChar = getMemberMethod(BoxesRunTimeModule, newTermName("unboxToChar"))
+
+ }
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala
new file mode 100644
index 0000000..bc7f8be
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala
@@ -0,0 +1,261 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.collection.mutable
+
+import scala.tools.nsc._
+
+import scala.scalajs.ir
+import ir.{Trees => js, Types => jstpe}
+
+import util.ScopedVar
+import ScopedVar.withScopedVars
+
+/** 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
+ */
+trait JSEncoding extends SubComponent { self: GenJSCode =>
+ import global._
+ import jsAddons._
+
+ /** Outer separator string (between parameter types) */
+ final val OuterSep = "__"
+
+ /** Inner separator character (replace dots in full names) */
+ final val InnerSep = "_"
+
+ /** Name given to the local Scala.js environment variable */
+ final val ScalaJSEnvironmentName = "ScalaJS"
+
+ /** Name given to all exported stuff of a class for DCE */
+ final val dceExportName = "<exported>"
+
+ // Fresh local name generator ----------------------------------------------
+
+ private val usedLocalNames = new ScopedVar[mutable.Set[String]]
+ private val localSymbolNames = new ScopedVar[mutable.Map[Symbol, String]]
+ private val isKeywordOrReserved =
+ js.isKeyword ++ Seq("arguments", "eval", ScalaJSEnvironmentName)
+
+ def withNewLocalNameScope[A](body: => A): A =
+ withScopedVars(
+ usedLocalNames := mutable.Set.empty,
+ localSymbolNames := mutable.Map.empty
+ )(body)
+
+ private def freshName(base: String = "x"): String = {
+ var suffix = 1
+ var longName = base
+ while (usedLocalNames(longName) || isKeywordOrReserved(longName)) {
+ suffix += 1
+ longName = base+"$"+suffix
+ }
+ usedLocalNames += longName
+ longName
+ }
+
+ 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 localSymbolName(sym: Symbol): String =
+ localSymbolNames.getOrElseUpdate(sym, freshName(sym.name.toString))
+
+ // Encoding methods ----------------------------------------------------------
+
+ def encodeLabelSym(sym: Symbol)(implicit pos: Position): js.Ident = {
+ require(sym.isLabel, "encodeLabelSym called with non-label symbol: " + sym)
+ js.Ident(localSymbolName(sym), Some(sym.unexpandedName.decoded))
+ }
+
+ private lazy val allRefClasses: Set[Symbol] = {
+ import definitions._
+ (Set(ObjectRefClass, VolatileObjectRefClass) ++
+ refClass.values ++ volatileRefClass.values)
+ }
+
+ def encodeFieldSym(sym: Symbol)(implicit pos: Position): js.Ident = {
+ require(sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule,
+ "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.isPrivate || allRefClasses.contains(sym.owner))
+ sym.owner.ancestors.count(!_.isInterface).toString
+ else
+ "f"
+
+ val encodedName = name + "$" + idSuffix
+ js.Ident(mangleJSName(encodedName), Some(sym.unexpandedName.decoded))
+ }
+
+ def encodeMethodSym(sym: Symbol, reflProxy: Boolean = false)
+ (implicit pos: 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): 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 pos: Position): (Symbol, js.Ident) = {
+ require(sym.isMethod, "encodeMethodSym called with non-method symbol: " + sym)
+ require(sym.owner == definitions.StringClass)
+ require(!sym.isClassConstructor && !sym.isPrivate)
+
+ val (encodedName, paramsString) =
+ encodeMethodNameInternal(sym, inRTClass = true)
+ val methodIdent = js.Ident(encodedName + paramsString,
+ Some(sym.unexpandedName.decoded + paramsString))
+
+ (jsDefinitions.RuntimeStringModuleClass, methodIdent)
+ }
+
+ private def encodeMethodNameInternal(sym: Symbol,
+ reflProxy: Boolean = false,
+ inRTClass: Boolean = false): (String, String) = {
+ require(sym.isMethod, "encodeMethodSym called with non-method symbol: " + sym)
+
+ def name = encodeMemberNameInternal(sym)
+
+ val encodedName = {
+ if (sym.isClassConstructor)
+ "init" + InnerSep
+ else if (foreignIsImplClass(sym.owner))
+ encodeClassFullName(sym.owner) + OuterSep + name
+ else if (sym.isPrivate)
+ mangleJSName(name) + OuterSep + "p" +
+ sym.owner.ancestors.count(!_.isInterface).toString
+ else
+ mangleJSName(name)
+ }
+
+ val paramsString = makeParamsString(sym, reflProxy, inRTClass)
+
+ (encodedName, paramsString)
+ }
+
+ def encodeStaticMemberSym(sym: Symbol)(implicit pos: Position): js.Ident = {
+ require(sym.isStaticMember,
+ "encodeStaticMemberSym called with non-static symbol: " + sym)
+ js.Ident(
+ mangleJSName(encodeMemberNameInternal(sym)) +
+ makeParamsString(List(internalName(sym.tpe))),
+ Some(sym.unexpandedName.decoded))
+ }
+
+ def encodeLocalSym(sym: Symbol)(implicit pos: Position): js.Ident = {
+ require(!sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule,
+ "encodeLocalSym called with non-local symbol: " + sym)
+ js.Ident(mangleJSName(localSymbolName(sym)), Some(sym.unexpandedName.decoded))
+ }
+
+ def foreignIsImplClass(sym: Symbol): Boolean =
+ sym.isModuleClass && nme.isImplClassName(sym.name)
+
+ def encodeClassType(sym: Symbol): jstpe.Type = {
+ if (sym == definitions.ObjectClass) jstpe.AnyType
+ else if (isRawJSType(sym.toTypeConstructor)) jstpe.AnyType
+ else {
+ assert(sym != definitions.ArrayClass,
+ "encodeClassType() cannot be called with ArrayClass")
+ jstpe.ClassType(encodeClassFullName(sym))
+ }
+ }
+
+ def encodeClassFullNameIdent(sym: Symbol)(implicit pos: Position): js.Ident = {
+ js.Ident(encodeClassFullName(sym), Some(sym.fullName))
+ }
+
+ def encodeModuleFullNameIdent(sym: Symbol)(implicit pos: Position): js.Ident = {
+ js.Ident(encodeModuleFullName(sym), Some(sym.fullName))
+ }
+
+ def encodeClassFullName(sym: Symbol): String = {
+ ir.Definitions.encodeClassName(
+ sym.fullName + (if (needsModuleClassSuffix(sym)) "$" else ""))
+ }
+
+ def needsModuleClassSuffix(sym: Symbol): Boolean =
+ sym.isModuleClass && !foreignIsImplClass(sym)
+
+ def encodeModuleFullName(sym: Symbol): String =
+ ir.Definitions.encodeClassName(sym.fullName + "$").dropRight(1)
+
+ private def encodeMemberNameInternal(sym: Symbol): String =
+ sym.name.toString.replace("_", "$und")
+
+ // Encoding of method signatures
+
+ private def makeParamsString(sym: Symbol, reflProxy: Boolean,
+ inRTClass: Boolean): String = {
+ val tpe = sym.tpe
+ val paramTypeNames = tpe.params map (p => internalName(p.tpe))
+ val paramAndResultTypeNames = {
+ if (sym.isClassConstructor)
+ paramTypeNames
+ else if (reflProxy)
+ paramTypeNames :+ ""
+ else {
+ val paramAndResultTypeNames0 =
+ paramTypeNames :+ internalName(tpe.resultType)
+ if (!inRTClass) paramAndResultTypeNames0
+ else internalName(sym.owner.toTypeConstructor) +: paramAndResultTypeNames0
+ }
+ }
+ makeParamsString(paramAndResultTypeNames)
+ }
+
+ private def makeParamsString(paramAndResultTypeNames: List[String]) =
+ paramAndResultTypeNames.mkString(OuterSep, OuterSep, "")
+
+ /** Computes the internal name for a type. */
+ private def internalName(tpe: Type): String = internalName(toTypeKind(tpe))
+
+ private def internalName(kind: TypeKind): String = kind match {
+ case VOID => "V"
+ case kind: ValueTypeKind => kind.primitiveCharCode.toString()
+ case NOTHING => ir.Definitions.RuntimeNothingClass
+ case NULL => ir.Definitions.RuntimeNullClass
+ case REFERENCE(cls) => encodeClassFullName(cls)
+ case ARRAY(elem) => "A"+internalName(elem)
+ }
+
+ /** 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/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala
new file mode 100644
index 0000000..3621050
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala
@@ -0,0 +1,244 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.tools.nsc._
+
+import scala.collection.mutable
+
+/** Additions to Global meaningful for the JavaScript backend
+ *
+ * @author Sébastien Doeraene
+ */
+trait JSGlobalAddons extends JSDefinitions
+ with Compat210Component {
+ val global: Global
+
+ import global._
+ import jsDefinitions._
+ import definitions._
+
+ /** JavaScript primitives, used in jscode */
+ object jsPrimitives extends JSPrimitives {
+ val global: JSGlobalAddons.this.global.type = JSGlobalAddons.this.global
+ val jsAddons: ThisJSGlobalAddons =
+ JSGlobalAddons.this.asInstanceOf[ThisJSGlobalAddons]
+ }
+
+ /** global javascript interop related helpers */
+ object jsInterop {
+ import scala.reflect.NameTransformer
+ import scala.reflect.internal.Flags
+
+ private val exportPrefix = "$js$exported$"
+ private val methodExportPrefix = exportPrefix + "meth$"
+ private val propExportPrefix = exportPrefix + "prop$"
+
+ case class ExportInfo(jsName: String, pos: Position, isNamed: Boolean)
+
+ /** retrieves the names a sym should be exported to from its annotations
+ *
+ * Note that for accessor symbols, the annotations of the accessed symbol
+ * are used, rather than the annotations of the accessor itself.
+ */
+ def exportsOf(sym: Symbol): List[ExportInfo] = {
+ val exports = directExportsOf(sym) ++ inheritedExportsOf(sym)
+
+ // Calculate the distinct exports for this symbol (eliminate double
+ // occurrences of (name, isNamed) pairs).
+ val buf = new mutable.ListBuffer[ExportInfo]
+ val seen = new mutable.HashSet[(String, Boolean)]
+ for (exp <- exports) {
+ if (!seen.contains((exp.jsName, exp.isNamed))) {
+ buf += exp
+ seen += ((exp.jsName, exp.isNamed))
+ }
+ }
+
+ buf.toList
+ }
+
+ private def directExportsOf(sym: Symbol): List[ExportInfo] = {
+ val trgSym = {
+ // For accessors, look on the val/var def
+ if (sym.isAccessor) sym.accessed
+ // For primary class constructors, look on the class itself
+ else if (sym.isPrimaryConstructor && !sym.owner.isModuleClass) sym.owner
+ else sym
+ }
+
+ // Annotations that are directly on the member
+ val directAnnots = for {
+ annot <- trgSym.annotations
+ if annot.symbol == JSExportAnnotation ||
+ annot.symbol == JSExportNamedAnnotation
+ } yield annot
+
+ // Annotations for this member on the whole unit
+ val unitAnnots = {
+ if (sym.isMethod && sym.isPublic &&
+ !sym.isConstructor && !sym.isSynthetic)
+ sym.owner.annotations.filter(_.symbol == JSExportAllAnnotation)
+ else
+ Nil
+ }
+
+ for {
+ annot <- directAnnots ++ unitAnnots
+ } yield {
+ // Is this a named export or a normal one?
+ val named = annot.symbol == JSExportNamedAnnotation
+
+ def explicitName = annot.stringArg(0).getOrElse {
+ reporter.error(annot.pos,
+ s"The argument to ${annot.symbol.name} must be a literal string")
+ "dummy"
+ }
+
+ val name =
+ if (annot.args.nonEmpty) explicitName
+ else if (sym.isConstructor) decodedFullName(sym.owner)
+ else if (sym.isModuleClass) decodedFullName(sym)
+ else sym.unexpandedName.decoded.stripSuffix("_=")
+
+ // Enforce that methods ending with _= are exported as setters
+ if (sym.isMethod && !sym.isConstructor &&
+ sym.name.decoded.endsWith("_=") && !isJSSetter(sym)) {
+ reporter.error(annot.pos, "A method ending in _= will be exported " +
+ s"as setter. But ${sym.name.decoded} does not have the right " +
+ "signature to do so (single argument, unit return type).")
+ }
+
+ // Enforce no __ in name
+ if (name.contains("__")) {
+ // Get position for error message
+ val pos = if (annot.stringArg(0).isDefined)
+ annot.args.head.pos
+ else trgSym.pos
+
+ reporter.error(pos,
+ "An exported name may not contain a double underscore (`__`)")
+ }
+
+ // Make sure we do not override the default export of toString
+ if (!sym.isConstructor && name == "toString" && !named &&
+ sym.name != nme.toString_ && sym.tpe.params.isEmpty &&
+ !isJSGetter(sym)) {
+ reporter.error(annot.pos, "You may not export a zero-argument " +
+ "method named other than 'toString' under the name 'toString'")
+ }
+
+ if (named && isJSProperty(sym)) {
+ reporter.error(annot.pos,
+ "You may not export a getter or a setter as a named export")
+ }
+
+ ExportInfo(name, annot.pos, named)
+ }
+ }
+
+ private def inheritedExportsOf(sym: Symbol): List[ExportInfo] = {
+ // The symbol from which we (potentially) inherit exports. It also
+ // gives the exports their name
+ val trgSym = {
+ if (sym.isModuleClass)
+ sym
+ else if (sym.isConstructor && sym.isPublic &&
+ sym.owner.isConcreteClass && !sym.owner.isModuleClass)
+ sym.owner
+ else NoSymbol
+ }
+
+ if (trgSym == NoSymbol) {
+ Nil
+ } else {
+ val trgAnnot =
+ if (sym.isModuleClass) JSExportDescendentObjectsAnnotation
+ else JSExportDescendentClassesAnnotation
+
+ val forcingSym =
+ trgSym.ancestors.find(_.annotations.exists(_.symbol == trgAnnot))
+
+ val name = decodedFullName(trgSym)
+
+ forcingSym.map { fs =>
+ // Enfore no __ in name
+ if (name.contains("__")) {
+ // Get all annotation positions for error message
+ reporter.error(sym.pos,
+ s"""${trgSym.name} may not have a double underscore (`__`) in its fully qualified
+ |name, since it is forced to be exported by a @${trgAnnot.name} on ${fs}""".stripMargin)
+ }
+
+ ExportInfo(name, sym.pos, false)
+ }.toList
+ }
+ }
+
+ /** Just like sym.fullName, but does not encode components */
+ private def decodedFullName(sym: Symbol): String = {
+ if (sym.isRoot || sym.isRootPackage || sym == NoSymbol) sym.name.decoded
+ else if (sym.owner.isEffectiveRoot) sym.name.decoded
+ else decodedFullName(sym.effectiveOwner.enclClass) + '.' + sym.name.decoded
+ }
+
+ /** creates a name for an export specification */
+ def scalaExportName(jsName: String, isProp: Boolean): TermName = {
+ val pref = if (isProp) propExportPrefix else methodExportPrefix
+ val encname = NameTransformer.encode(jsName)
+ newTermName(pref + encname)
+ }
+
+ /** checks if the given symbol is a JSExport */
+ def isExport(sym: Symbol): Boolean =
+ sym.unexpandedName.startsWith(exportPrefix) &&
+ !sym.hasFlag(Flags.DEFAULTPARAM)
+
+ /** retrieves the originally assigned jsName of this export and whether it
+ * is a property
+ */
+ def jsExportInfo(name: Name): (String, Boolean) = {
+ def dropPrefix(prefix: String) ={
+ if (name.startsWith(prefix)) {
+ // We can't decode right away due to $ separators
+ val enc = name.encoded.substring(prefix.length)
+ Some(NameTransformer.decode(enc))
+ } else None
+ }
+
+ dropPrefix(methodExportPrefix).map((_,false)) orElse
+ dropPrefix(propExportPrefix).map((_,true)) getOrElse
+ sys.error("non-exported name passed to jsInfoSpec")
+ }
+
+ def isJSProperty(sym: Symbol): Boolean = isJSGetter(sym) || isJSSetter(sym)
+
+ /** has this symbol to be translated into a JS getter (both directions)? */
+ def isJSGetter(sym: Symbol): Boolean = {
+ sym.tpe.params.isEmpty && enteringPhase(currentRun.uncurryPhase) {
+ sym.tpe.isInstanceOf[NullaryMethodType]
+ }
+ }
+
+ /** has this symbol to be translated into a JS setter (both directions)? */
+ def isJSSetter(sym: Symbol) = {
+ sym.unexpandedName.decoded.endsWith("_=") &&
+ sym.tpe.resultType.typeSymbol == UnitClass &&
+ enteringPhase(currentRun.uncurryPhase) {
+ sym.tpe.paramss match {
+ case List(List(arg)) => !isScalaRepeatedParamType(arg.tpe)
+ case _ => false
+ }
+ }
+ }
+
+ /** has this symbol to be translated into a JS bracket access (JS to Scala) */
+ def isJSBracketAccess(sym: Symbol) =
+ sym.hasAnnotation(JSBracketAccessAnnotation)
+
+ }
+
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSPrimitives.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSPrimitives.scala
new file mode 100644
index 0000000..b8c20e6
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSPrimitives.scala
@@ -0,0 +1,119 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.tools.nsc._
+
+import scala.collection.mutable
+
+/** Extension of ScalaPrimitives for primitives only relevant to the JS backend
+ *
+ * @author Sébastie Doeraene
+ */
+abstract class JSPrimitives {
+ val global: Global
+
+ type ThisJSGlobalAddons = JSGlobalAddons {
+ val global: JSPrimitives.this.global.type
+ }
+
+ val jsAddons: ThisJSGlobalAddons
+
+ import global._
+ import jsAddons._
+ import definitions._
+ import rootMirror._
+ import jsDefinitions._
+ import scalaPrimitives._
+
+ val GETCLASS = 301 // Object.getClass()
+
+ val F2JS = 305 // FunctionN to js.FunctionN
+ val F2JSTHIS = 306 // FunctionN to js.ThisFunction{N-1}
+
+ val DYNNEW = 321 // Instantiate a new JavaScript object
+
+ val DYNSELECT = 330 // js.Dynamic.selectDynamic
+ val DYNUPDATE = 331 // js.Dynamic.updateDynamic
+ val DYNAPPLY = 332 // js.Dynamic.applyDynamic
+ val DYNLITN = 333 // js.Dynamic.literal.applyDynamicNamed
+ val DYNLIT = 334 // js.Dynamic.literal.applyDynamic
+
+ val DICT_DEL = 335 // js.Dictionary.delete
+
+ val ARR_CREATE = 337 // js.Array.apply (array literal syntax)
+
+ val UNDEFVAL = 342 // js.undefined
+ val ISUNDEF = 343 // js.isUndefined
+ val TYPEOF = 344 // typeof x
+ val DEBUGGER = 345 // js.debugger()
+ val HASPROP = 346 // js.Object.hasProperty(o, p), equiv to `p in o` in JS
+ val OBJPROPS = 347 // js.Object.properties(o), equiv to `for (p in o)` in JS
+ val JS_NATIVE = 348 // js.native. Marker method. Fails if tried to be emitted.
+
+ val UNITVAL = 349 // () value, which is undefined
+ val UNITTYPE = 350 // BoxedUnit.TYPE (== classOf[Unit])
+
+ val ENV_INFO = 353 // __ScalaJSEnv via helper
+
+ /** Initialize the map of primitive methods (for GenJSCode) */
+ def init(): Unit = initWithPrimitives(addPrimitive)
+
+ /** Init the map of primitive methods for Scala.js (for PrepJSInterop) */
+ def initPrepJSPrimitives(): Unit = {
+ scalaJSPrimitives.clear()
+ initWithPrimitives(scalaJSPrimitives.put)
+ }
+
+ /** Only call from PrepJSInterop. In GenJSCode, use
+ * scalaPrimitives.isPrimitive instead
+ */
+ def isJavaScriptPrimitive(sym: Symbol): Boolean =
+ scalaJSPrimitives.contains(sym)
+
+ private val scalaJSPrimitives = mutable.Map.empty[Symbol, Int]
+
+ private def initWithPrimitives(addPrimitive: (Symbol, Int) => Unit): Unit = {
+ addPrimitive(Object_getClass, GETCLASS)
+
+ for (i <- 0 to 22)
+ addPrimitive(JSAny_fromFunction(i), F2JS)
+ for (i <- 1 to 22)
+ addPrimitive(JSThisFunction_fromFunction(i), F2JSTHIS)
+
+ addPrimitive(JSDynamic_newInstance, DYNNEW)
+
+ addPrimitive(JSDynamic_selectDynamic, DYNSELECT)
+ addPrimitive(JSDynamic_updateDynamic, DYNUPDATE)
+ addPrimitive(JSDynamic_applyDynamic, DYNAPPLY)
+ addPrimitive(JSDynamicLiteral_applyDynamicNamed, DYNLITN)
+ addPrimitive(JSDynamicLiteral_applyDynamic, DYNLIT)
+
+ addPrimitive(JSDictionary_delete, DICT_DEL)
+
+ addPrimitive(JSArray_create, ARR_CREATE)
+
+ val ntModule = getRequiredModule("scala.reflect.NameTransformer")
+
+ addPrimitive(JSPackage_typeOf, TYPEOF)
+ addPrimitive(JSPackage_debugger, DEBUGGER)
+ addPrimitive(JSPackage_undefined, UNDEFVAL)
+ addPrimitive(JSPackage_isUndefined, ISUNDEF)
+ addPrimitive(JSPackage_native, JS_NATIVE)
+
+ addPrimitive(JSObject_hasProperty, HASPROP)
+ addPrimitive(JSObject_properties, OBJPROPS)
+
+ addPrimitive(BoxedUnit_UNIT, UNITVAL)
+ addPrimitive(BoxedUnit_TYPE, UNITTYPE)
+
+ addPrimitive(getMember(RuntimePackageModule,
+ newTermName("environmentInfo")), ENV_INFO)
+ }
+
+ def isJavaScriptPrimitive(code: Int) =
+ code >= 300 && code < 360
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSTreeExtractors.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSTreeExtractors.scala
new file mode 100644
index 0000000..a18ad88
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/JSTreeExtractors.scala
@@ -0,0 +1,66 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Tobias Schlatter
+ */
+
+package scala.scalajs.compiler
+
+import scala.annotation.tailrec
+
+import scala.scalajs.ir.Trees._
+import scala.scalajs.ir.Types._
+
+/** Useful extractors for JavaScript trees */
+object JSTreeExtractors {
+
+ object jse {
+ /**
+ * A literally named sequence (like in a call to applyDynamicNamed)
+ *
+ * Example (Scala): method(("name1", x), ("name2", y))
+ */
+ object LitNamed {
+ def unapply(exprs: List[Tree]) = unapply0(exprs, Nil)
+
+ @tailrec
+ private def unapply0(
+ exprs: List[Tree],
+ acc: List[(StringLiteral, Tree)]
+ ): Option[List[(StringLiteral, Tree)]] = exprs match {
+ case Tuple2(name: StringLiteral, value) :: xs =>
+ unapply0(xs, (name, value) :: acc)
+ case Nil => Some(acc.reverse)
+ case _ => None
+ }
+ }
+
+ /**
+ * A literal Tuple2
+ *
+ * Example (Scala): (x, y)
+ * But also (Scala): x -> y
+ */
+ object Tuple2 {
+ def unapply(tree: Tree): Option[(Tree, Tree)] = tree match {
+ // case (x, y)
+ case New(ClassType("T2"), Ident("init___O__O", _),
+ List(_1, _2)) =>
+ Some((_1, _2))
+ // case x -> y
+ case Apply(
+ LoadModule(ClassType("s_Predef$ArrowAssoc$")),
+ Ident("$$minus$greater$extension__O__O__T2", _),
+ List(
+ Apply(
+ LoadModule(ClassType("s_Predef$")),
+ Ident("any2ArrowAssoc__O__O", _),
+ List(_1)),
+ _2)) =>
+ Some((_1, _2))
+ case _ =>
+ None
+ }
+ }
+ }
+
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala
new file mode 100644
index 0000000..9223061
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala
@@ -0,0 +1,251 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Tobias Schlatter
+ */
+
+package scala.scalajs.compiler
+
+import scala.annotation.tailrec
+
+import scala.tools.nsc.NoPhase
+
+/**
+ * Prepare export generation
+ *
+ * Helpers for transformation of @JSExport annotations
+ */
+trait PrepJSExports { this: PrepJSInterop =>
+
+ import global._
+ import jsAddons._
+ import definitions._
+ import jsDefinitions._
+
+ import scala.reflect.internal.Flags
+
+ /** Whether the given symbol has a visibility that allows exporting */
+ def hasLegalExportVisibility(sym: Symbol): Boolean =
+ sym.isPublic || sym.isProtected && !sym.isProtectedLocal
+
+ def genExportMember(ddef: DefDef): List[Tree] = {
+ val baseSym = ddef.symbol
+ val clsSym = baseSym.owner
+
+ val exports = jsInterop.exportsOf(baseSym)
+
+ // Helper function for errors
+ def err(msg: String) = { reporter.error(exports.head.pos, msg); Nil }
+ def memType = if (baseSym.isConstructor) "constructor" else "method"
+
+ if (exports.isEmpty)
+ Nil
+ else if (!hasLegalExportVisibility(baseSym))
+ err(s"You may only export public and protected ${memType}s")
+ else if (baseSym.isMacro)
+ err("You may not export a macro")
+ else if (scalaPrimitives.isPrimitive(baseSym))
+ err("You may not export a primitive")
+ else if (hasIllegalRepeatedParam(baseSym))
+ err(s"In an exported $memType, a *-parameter must come last " +
+ "(through all parameter lists)")
+ else if (hasIllegalDefaultParam(baseSym))
+ err(s"In an exported $memType, all parameters with defaults " +
+ "must be at the end")
+ else if (currentRun.uncurryPhase == NoPhase) {
+ /* When using scaladoc, the uncurry phase does not exist. This makes
+ * our code down below blow up (see bug #323). So we do not do anything
+ * more here if the phase does not exist. It's no big deal because we do
+ * not need exports for scaladoc.
+ */
+ Nil
+ } else if (baseSym.isConstructor) {
+ // we can generate constructors entirely in the backend, since they
+ // do not need inheritance and such. But we want to check their sanity
+ // here by previous tests and the following ones.
+
+ if (!hasLegalExportVisibility(clsSym))
+ err("You may only export public and protected classes")
+ else if (clsSym.isLocalToBlock)
+ err("You may not export a local class")
+ else if (clsSym.isNestedClass)
+ err("You may not export a nested class. Create an exported factory " +
+ "method in the outer class to work around this limitation.")
+ else Nil
+
+ } else {
+ assert(!baseSym.isBridge)
+
+ // Reset interface flag: Any trait will contain non-empty methods
+ clsSym.resetFlag(Flags.INTERFACE)
+
+ // Actually generate exporter methods
+ exports.flatMap { exp =>
+ if (exp.isNamed)
+ genNamedExport(baseSym, exp.jsName, exp.pos) :: Nil
+ else
+ genExportDefs(baseSym, exp.jsName, exp.pos)
+ }
+ }
+ }
+
+ /** generate an exporter for a DefDef including default parameter methods */
+ private def genExportDefs(defSym: Symbol, jsName: String, pos: Position) = {
+ val clsSym = defSym.owner
+ val scalaName =
+ jsInterop.scalaExportName(jsName, jsInterop.isJSProperty(defSym))
+
+ // Create symbol for new method
+ val expSym = defSym.cloneSymbol
+
+ // Set position of symbol
+ expSym.pos = pos
+
+ // Alter type for new method (lift return type to Any)
+ // The return type is lifted, in order to avoid bridge
+ // construction and to detect methods whose signature only differs
+ // in the return type.
+ // Attention: This will cause boxes for primitive value types and value
+ // classes. However, since we have restricted the return types, we can
+ // always safely remove these boxes again in the back-end.
+ if (!defSym.isConstructor)
+ expSym.setInfo(retToAny(expSym.tpe))
+
+ // Change name for new method
+ expSym.name = scalaName
+
+ // Update flags
+ expSym.setFlag(Flags.SYNTHETIC)
+ expSym.resetFlag(
+ Flags.DEFERRED | // We always have a body
+ Flags.ACCESSOR | // We are never a "direct" accessor
+ Flags.CASEACCESSOR | // And a fortiori not a case accessor
+ Flags.LAZY | // We are not a lazy val (even if we export one)
+ Flags.OVERRIDE // Synthetic methods need not bother with this
+ )
+
+ // Remove export annotations
+ expSym.removeAnnotation(JSExportAnnotation)
+ expSym.removeAnnotation(JSExportNamedAnnotation)
+
+ // Add symbol to class
+ clsSym.info.decls.enter(expSym)
+
+ // Construct exporter DefDef tree
+ val exporter = genProxyDefDef(clsSym, defSym, expSym, pos)
+
+ // Construct exporters for default getters
+ val defaultGetters = for {
+ (param, i) <- expSym.paramss.flatten.zipWithIndex
+ if param.hasFlag(Flags.DEFAULTPARAM)
+ } yield genExportDefaultGetter(clsSym, defSym, expSym, i + 1, pos)
+
+ exporter :: defaultGetters
+ }
+
+ /** Generate a dummy DefDef tree for a named export. This tree is captured
+ * by GenJSCode again to generate the required JavaScript logic.
+ */
+ private def genNamedExport(defSym: Symbol, jsName: String, pos: Position) = {
+ val clsSym = defSym.owner
+ val scalaName = jsInterop.scalaExportName(jsName, false)
+
+ // Create symbol for the new exporter method
+ val expSym = clsSym.newMethodSymbol(scalaName, pos,
+ Flags.SYNTHETIC | Flags.FINAL)
+
+ // Mark the symbol to be a named export
+ expSym.addAnnotation(JSExportNamedAnnotation)
+
+ // Create a single parameter of type Any
+ val param = expSym.newValueParameter(newTermName("namedArgs"), pos)
+ param.setInfo(AnyTpe)
+
+ // Set method type
+ expSym.setInfo(MethodType(param :: Nil, AnyClass.tpe))
+
+ // Register method to parent
+ clsSym.info.decls.enter(expSym)
+
+ // Placeholder tree
+ def ph = Ident(Predef_???)
+
+ // Create a call to the forwarded method with ??? as args
+ val sel: Tree = Select(This(clsSym), defSym)
+ val call = (sel /: defSym.paramss) {
+ (fun, params) => Apply(fun, List.fill(params.size)(ph))
+ }
+
+ // rhs is a block to prevent boxing of result
+ typer.typedDefDef(DefDef(expSym, Block(call, ph)))
+ }
+
+ private def genExportDefaultGetter(clsSym: Symbol, trgMethod: Symbol,
+ exporter: Symbol, paramPos: Int, pos: Position) = {
+
+ // Get default getter method we'll copy
+ val trgGetter =
+ clsSym.tpe.member(nme.defaultGetterName(trgMethod.name, paramPos))
+
+ assert(trgGetter.exists)
+
+ // Although the following must be true in a correct program, we cannot
+ // assert, since a graceful failure message is only generated later
+ if (!trgGetter.isOverloaded) {
+ val expGetter = trgGetter.cloneSymbol
+
+ expGetter.name = nme.defaultGetterName(exporter.name, paramPos)
+ expGetter.pos = pos
+
+ clsSym.info.decls.enter(expGetter)
+
+ genProxyDefDef(clsSym, trgGetter, expGetter, pos)
+
+ } else EmptyTree
+ }
+
+ /** generate a DefDef tree (from [[proxySym]]) that calls [[trgSym]] */
+ private def genProxyDefDef(clsSym: Symbol, trgSym: Symbol,
+ proxySym: Symbol, pos: Position) = atPos(pos) {
+
+ // Helper to ascribe repeated argument lists when calling
+ def spliceParam(sym: Symbol) = {
+ if (isRepeated(sym))
+ Typed(Ident(sym), Ident(tpnme.WILDCARD_STAR))
+ else
+ Ident(sym)
+ }
+
+ // Construct proxied function call
+ val sel: Tree = Select(This(clsSym), trgSym)
+ val rhs = (sel /: proxySym.paramss) {
+ (fun,params) => Apply(fun, params map spliceParam)
+ }
+
+ typer.typedDefDef(DefDef(proxySym, rhs))
+ }
+
+ /** changes the return type of the method type tpe to Any. returns new type */
+ private def retToAny(tpe: Type): Type = tpe match {
+ case MethodType(params, result) => MethodType(params, retToAny(result))
+ case NullaryMethodType(result) => NullaryMethodType(AnyClass.tpe)
+ case PolyType(tparams, result) => PolyType(tparams, retToAny(result))
+ case _ => AnyClass.tpe
+ }
+
+ /** checks whether this type has a repeated parameter elsewhere than at the end
+ * of all the params
+ */
+ private def hasIllegalRepeatedParam(sym: Symbol): Boolean = {
+ val params = sym.paramss.flatten
+ params.nonEmpty && params.init.exists(isRepeated _)
+ }
+
+ /** checks whether there are default parameters not at the end of
+ * the flattened parameter list
+ */
+ private def hasIllegalDefaultParam(sym: Symbol): Boolean = {
+ val isDefParam = (_: Symbol).hasFlag(Flags.DEFAULTPARAM)
+ sym.paramss.flatten.reverse.dropWhile(isDefParam).exists(isDefParam)
+ }
+
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala
new file mode 100644
index 0000000..437576a
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala
@@ -0,0 +1,621 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Tobias Schlatter
+ */
+
+package scala.scalajs.compiler
+
+import scala.tools.nsc
+import nsc._
+
+import scala.collection.immutable.ListMap
+import scala.collection.mutable
+
+/** Prepares classes extending js.Any for JavaScript interop
+ *
+ * This phase does:
+ * - Sanity checks for js.Any hierarchy
+ * - Annotate subclasses of js.Any to be treated specially
+ * - Rewrite calls to scala.Enumeration.Value (include name string)
+ * - Create JSExport methods: Dummy methods that are propagated
+ * through the whole compiler chain to mark exports. This allows
+ * exports to have the same semantics than methods.
+ *
+ * @author Tobias Schlatter
+ */
+abstract class PrepJSInterop extends plugins.PluginComponent
+ with PrepJSExports
+ with transform.Transform
+ with Compat210Component {
+ val jsAddons: JSGlobalAddons {
+ val global: PrepJSInterop.this.global.type
+ }
+
+ val scalaJSOpts: ScalaJSOptions
+
+ import global._
+ import jsAddons._
+ import definitions._
+ import rootMirror._
+ import jsDefinitions._
+
+ val phaseName = "jsinterop"
+
+ override def newPhase(p: nsc.Phase) = new JSInteropPhase(p)
+ class JSInteropPhase(prev: nsc.Phase) extends Phase(prev) {
+ override def name = phaseName
+ override def description = "Prepare ASTs for JavaScript interop"
+ override def run(): Unit = {
+ jsPrimitives.initPrepJSPrimitives()
+ super.run()
+ }
+ }
+
+ override protected def newTransformer(unit: CompilationUnit) =
+ new JSInteropTransformer(unit)
+
+ private object jsnme {
+ val hasNext = newTermName("hasNext")
+ val next = newTermName("next")
+ val nextName = newTermName("nextName")
+ val x = newTermName("x")
+ val Value = newTermName("Value")
+ val Val = newTermName("Val")
+ }
+
+ private object jstpnme {
+ val scala_ = newTypeName("scala") // not defined in 2.10's tpnme
+ }
+
+ class JSInteropTransformer(unit: CompilationUnit) extends Transformer {
+
+ // Force evaluation of JSDynamicLiteral: Strangely, we are unable to find
+ // nested objects in the JSCode phase (probably after flatten).
+ // Therefore we force the symbol of js.Dynamic.literal here in order to
+ // have access to it in JSCode.
+ JSDynamicLiteral
+
+ var inJSAnyMod = false
+ var inJSAnyCls = false
+ var inScalaCls = false
+ /** are we inside a subclass of scala.Enumeration */
+ var inScalaEnum = false
+ /** are we inside the implementation of scala.Enumeration? */
+ var inEnumImpl = false
+
+ def jsAnyClassOnly = !inJSAnyCls && allowJSAny
+ def allowImplDef = !inJSAnyCls && !inJSAnyMod
+ def allowJSAny = !inScalaCls
+ def inJSAny = inJSAnyMod || inJSAnyCls
+
+ /** DefDefs in class templates that export methods to JavaScript */
+ val exporters = mutable.Map.empty[Symbol, mutable.ListBuffer[Tree]]
+
+ override def transform(tree: Tree): Tree = postTransform { tree match {
+ // Catch special case of ClassDef in ModuleDef
+ case cldef: ClassDef if jsAnyClassOnly && isJSAny(cldef) =>
+ transformJSAny(cldef)
+
+ // Catch forbidden implDefs
+ case idef: ImplDef if !allowImplDef =>
+ reporter.error(idef.pos, "Traits, classes and objects extending " +
+ "js.Any may not have inner traits, classes or objects")
+ super.transform(tree)
+
+ // Handle js.Anys
+ case idef: ImplDef if isJSAny(idef) =>
+ transformJSAny(idef)
+
+ // Catch the definition of scala.Enumeration itself
+ case cldef: ClassDef if cldef.symbol == ScalaEnumClass =>
+ enterEnumImpl { super.transform(cldef) }
+
+ // Catch Scala Enumerations to transform calls to scala.Enumeration.Value
+ case cldef: ClassDef if isScalaEnum(cldef) =>
+ enterScalaCls {
+ enterScalaEnum {
+ super.transform(cldef)
+ }
+ }
+ case idef: ImplDef if isScalaEnum(idef) =>
+ enterScalaEnum { super.transform(idef) }
+
+ // Catch (Scala) ClassDefs to forbid js.Anys
+ case cldef: ClassDef =>
+ enterScalaCls { super.transform(cldef) }
+
+ // Catch ValorDefDef in js.Any
+ case vddef: ValOrDefDef if inJSAny =>
+ transformValOrDefDefInRawJSType(vddef)
+
+ // Catch ValDefs in enumerations with simple calls to Value
+ case ValDef(mods, name, tpt, ScalaEnumValue.NoName(optPar)) if inScalaEnum =>
+ val nrhs = ScalaEnumValName(tree.symbol.owner, tree.symbol, optPar)
+ treeCopy.ValDef(tree, mods, name, transform(tpt), nrhs)
+
+ // Catch Select on Enumeration.Value we couldn't transform but need to
+ // we ignore the implementation of scala.Enumeration itself
+ case ScalaEnumValue.NoName(_) if !inEnumImpl =>
+ reporter.warning(tree.pos,
+ """Couldn't transform call to Enumeration.Value.
+ |The resulting program is unlikely to function properly as this
+ |operation requires reflection.""".stripMargin)
+ super.transform(tree)
+
+ case ScalaEnumValue.NullName() if !inEnumImpl =>
+ reporter.warning(tree.pos,
+ """Passing null as name to Enumeration.Value
+ |requires reflection at runtime. The resulting
+ |program is unlikely to function properly.""".stripMargin)
+ super.transform(tree)
+
+ case ScalaEnumVal.NoName(_) if !inEnumImpl =>
+ reporter.warning(tree.pos,
+ """Calls to the non-string constructors of Enumeration.Val
+ |require reflection at runtime. The resulting
+ |program is unlikely to function properly.""".stripMargin)
+ super.transform(tree)
+
+ case ScalaEnumVal.NullName() if !inEnumImpl =>
+ reporter.warning(tree.pos,
+ """Passing null as name to a constructor of Enumeration.Val
+ |requires reflection at runtime. The resulting
+ |program is unlikely to function properly.""".stripMargin)
+ super.transform(tree)
+
+ // Catch calls to Predef.classOf[T]. These should NEVER reach this phase
+ // but unfortunately do. In normal cases, the typer phase replaces these
+ // calls by a literal constant of the given type. However, when we compile
+ // the scala library itself and Predef.scala is in the sources, this does
+ // not happen.
+ //
+ // The trees reach this phase under the form:
+ //
+ // scala.this.Predef.classOf[T]
+ //
+ // If we encounter such a tree, depending on the plugin options, we fail
+ // here or silently fix those calls.
+ case TypeApply(
+ classOfTree @ Select(Select(This(jstpnme.scala_), nme.Predef), nme.classOf),
+ List(tpeArg)) =>
+ if (scalaJSOpts.fixClassOf) {
+ // Replace call by literal constant containing type
+ if (typer.checkClassType(tpeArg)) {
+ typer.typed { Literal(Constant(tpeArg.tpe.dealias.widen)) }
+ } else {
+ reporter.error(tpeArg.pos, s"Type ${tpeArg} is not a class type")
+ EmptyTree
+ }
+ } else {
+ reporter.error(classOfTree.pos,
+ """This classOf resulted in an unresolved classOf in the jscode
+ |phase. This is most likely a bug in the Scala compiler. ScalaJS
+ |is probably able to work around this bug. Enable the workaround
+ |by passing the fixClassOf option to the plugin.""".stripMargin)
+ EmptyTree
+ }
+
+ // Exporter generation
+ case ddef: DefDef =>
+ // Generate exporters for this ddef if required
+ exporters.getOrElseUpdate(ddef.symbol.owner,
+ mutable.ListBuffer.empty) ++= genExportMember(ddef)
+
+ super.transform(tree)
+
+ // Module export sanity check (export generated in JSCode phase)
+ case modDef: ModuleDef =>
+ val sym = modDef.symbol
+
+ def condErr(msg: String) = {
+ for (exp <- jsInterop.exportsOf(sym)) {
+ reporter.error(exp.pos, msg)
+ }
+ }
+
+ if (!hasLegalExportVisibility(sym))
+ condErr("You may only export public and protected objects")
+ else if (sym.isLocalToBlock)
+ condErr("You may not export a local object")
+ else if (!sym.owner.hasPackageFlag)
+ condErr("You may not export a nested object")
+
+ super.transform(modDef)
+
+ // Fix for issue with calls to js.Dynamic.x()
+ // Rewrite (obj: js.Dynamic).x(...) to obj.applyDynamic("x")(...)
+ case Select(Select(trg, jsnme.x), nme.apply) if isJSDynamic(trg) =>
+ val newTree = atPos(tree.pos) {
+ Apply(
+ Select(super.transform(trg), newTermName("applyDynamic")),
+ List(Literal(Constant("x")))
+ )
+ }
+ typer.typed(newTree, Mode.FUNmode, tree.tpe)
+
+
+ // Fix for issue with calls to js.Dynamic.x()
+ // Rewrite (obj: js.Dynamic).x to obj.selectDynamic("x")
+ case Select(trg, jsnme.x) if isJSDynamic(trg) =>
+ val newTree = atPos(tree.pos) {
+ Apply(
+ Select(super.transform(trg), newTermName("selectDynamic")),
+ List(Literal(Constant("x")))
+ )
+ }
+ typer.typed(newTree, Mode.FUNmode, tree.tpe)
+
+ case _ => super.transform(tree)
+ } }
+
+ private def postTransform(tree: Tree) = tree match {
+ case Template(parents, self, body) =>
+ val clsSym = tree.symbol.owner
+ val exports = exporters.get(clsSym).toIterable.flatten
+ // Add exports to the template
+ treeCopy.Template(tree, parents, self, body ++ exports)
+
+ case memDef: MemberDef =>
+ val sym = memDef.symbol
+ if (sym.isLocalToBlock && !sym.owner.isCaseApplyOrUnapply) {
+ // We exclude case class apply (and unapply) to work around SI-8826
+ for (exp <- jsInterop.exportsOf(sym)) {
+ val msg = {
+ val base = "You may not export a local definition"
+ if (sym.owner.isPrimaryConstructor)
+ base + ". To export a (case) class field, use the " +
+ "meta-annotation scala.annotation.meta.field like this: " +
+ "@(JSExport @field)."
+ else
+ base
+ }
+ reporter.error(exp.pos, msg)
+ }
+ }
+ memDef
+
+ case _ => tree
+ }
+
+ /**
+ * Performs checks and rewrites specific to classes / objects extending
+ * js.Any
+ */
+ private def transformJSAny(implDef: ImplDef) = {
+ val sym = implDef.symbol
+
+ lazy val badParent = sym.info.parents.find(t => !(t <:< JSAnyClass.tpe))
+ val inScalaJSJSPackage =
+ sym.enclosingPackage == ScalaJSJSPackage ||
+ sym.enclosingPackage == ScalaJSJSPrimPackage
+
+ implDef match {
+ // Check that we do not have a case modifier
+ case _ if implDef.mods.hasFlag(Flag.CASE) =>
+ reporter.error(implDef.pos, "Classes and objects extending " +
+ "js.Any may not have a case modifier")
+
+ // Check that we do not extends a trait that does not extends js.Any
+ case _ if !inScalaJSJSPackage && !badParent.isEmpty &&
+ !isJSLambda(sym) =>
+ val badName = badParent.get.typeSymbol.fullName
+ reporter.error(implDef.pos, s"${sym.nameString} extends ${badName} " +
+ "which does not extend js.Any.")
+
+ // Check that we are not an anonymous class
+ case cldef: ClassDef
+ if cldef.symbol.isAnonymousClass && !isJSLambda(sym) =>
+ reporter.error(implDef.pos, "Anonymous classes may not " +
+ "extend js.Any")
+
+ // Check if we may have a js.Any here
+ case cldef: ClassDef if !allowJSAny && !jsAnyClassOnly &&
+ !isJSLambda(sym) =>
+ reporter.error(implDef.pos, "Classes extending js.Any may not be " +
+ "defined inside a class or trait")
+
+ case _: ModuleDef if !allowJSAny =>
+ reporter.error(implDef.pos, "Objects extending js.Any may not be " +
+ "defined inside a class or trait")
+
+ case _ if sym.isLocalToBlock && !isJSLambda(sym) =>
+ reporter.error(implDef.pos, "Local classes and objects may not " +
+ "extend js.Any")
+
+ // Check that this is not a class extending js.GlobalScope
+ case _: ClassDef if isJSGlobalScope(implDef) &&
+ implDef.symbol != JSGlobalScopeClass =>
+ reporter.error(implDef.pos, "Only objects may extend js.GlobalScope")
+
+ case _ =>
+ // We cannot use sym directly, since the symbol
+ // of a module is not its type's symbol but the value it declares
+ val tSym = sym.tpe.typeSymbol
+
+ tSym.setAnnotations(rawJSAnnot :: sym.annotations)
+
+ }
+
+ if (implDef.isInstanceOf[ModuleDef])
+ enterJSAnyMod { super.transform(implDef) }
+ else
+ enterJSAnyCls { super.transform(implDef) }
+ }
+
+ /** Verify a ValOrDefDef inside a js.Any */
+ private def transformValOrDefDefInRawJSType(tree: ValOrDefDef) = {
+ val sym = tree.symbol
+
+ val exports = jsInterop.exportsOf(sym)
+
+ if (exports.nonEmpty) {
+ val memType = if (sym.isConstructor) "constructor" else "method"
+ reporter.error(exports.head.pos,
+ s"You may not export a $memType of a subclass of js.Any")
+ }
+
+ if (isNonJSScalaSetter(sym)) {
+ // Forbid setters with non-unit return type
+ reporter.error(tree.pos, "Setters that do not return Unit are " +
+ "not allowed in types extending js.Any")
+ }
+
+ if (sym.hasAnnotation(NativeAttr)) {
+ // Native methods are not allowed
+ reporter.error(tree.pos, "Methods in a js.Any may not be @native")
+ }
+
+ for {
+ annot <- sym.getAnnotation(JSNameAnnotation)
+ if annot.stringArg(0).isEmpty
+ } {
+ reporter.error(annot.pos,
+ "The argument to JSName must be a literal string")
+ }
+
+ if (sym.isPrimaryConstructor || sym.isValueParameter ||
+ sym.isParamWithDefault || sym.isAccessor && !sym.isDeferred ||
+ sym.isParamAccessor || sym.isSynthetic ||
+ AllJSFunctionClasses.contains(sym.owner)) {
+ /* Ignore (i.e. allow) primary ctor, parameters, default parameter
+ * getters, accessors, param accessors, synthetic methods (to avoid
+ * double errors with case classes, e.g. generated copy method) and
+ * js.Functions and js.ThisFunctions (they need abstract methods for SAM
+ * treatment.
+ */
+ } else if (jsPrimitives.isJavaScriptPrimitive(sym)) {
+ // Force rhs of a primitive to be `sys.error("stub")` except for the
+ // js.native primitive which displays an elaborate error message
+ if (sym != JSPackage_native) {
+ tree.rhs match {
+ case Apply(trg, Literal(Constant("stub")) :: Nil)
+ if trg.symbol == definitions.Sys_error =>
+ case _ =>
+ reporter.error(tree.pos,
+ "The body of a primitive must be `sys.error(\"stub\")`.")
+ }
+ }
+ } else if (sym.isConstructor) {
+ // Force secondary ctor to have only a call to the primary ctor inside
+ tree.rhs match {
+ case Block(List(Apply(trg, _)), Literal(Constant(())))
+ if trg.symbol.isPrimaryConstructor &&
+ trg.symbol.owner == sym.owner =>
+ // everything is fine here
+ case _ =>
+ reporter.error(tree.pos, "A secondary constructor of a class " +
+ "extending js.Any may only call the primary constructor")
+ }
+ } else {
+ // Check that the tree's body is either empty or calls js.native
+ tree.rhs match {
+ case sel: Select if sel.symbol == JSPackage_native =>
+ case _ =>
+ val pos = if (tree.rhs != EmptyTree) tree.rhs.pos else tree.pos
+ reporter.warning(pos, "Members of traits, classes and objects " +
+ "extending js.Any may only contain members that call js.native. " +
+ "This will be enforced in 1.0.")
+ }
+
+ if (sym.tpe.resultType.typeSymbol == NothingClass &&
+ tree.tpt.asInstanceOf[TypeTree].original == null) {
+ // Warn if resultType is Nothing and not ascribed
+ val name = sym.name.decoded.trim
+ reporter.warning(tree.pos, s"The type of $name got inferred " +
+ "as Nothing. To suppress this warning, explicitly ascribe " +
+ "the type.")
+ }
+ }
+
+ super.transform(tree)
+ }
+
+ private def enterJSAnyCls[T](body: =>T) = {
+ val old = inJSAnyCls
+ inJSAnyCls = true
+ val res = body
+ inJSAnyCls = old
+ res
+ }
+
+ private def enterJSAnyMod[T](body: =>T) = {
+ val old = inJSAnyMod
+ inJSAnyMod = true
+ val res = body
+ inJSAnyMod = old
+ res
+ }
+
+ private def enterScalaCls[T](body: =>T) = {
+ val old = inScalaCls
+ inScalaCls = true
+ val res = body
+ inScalaCls = old
+ res
+ }
+
+ private def enterScalaEnum[T](body: =>T) = {
+ val old = inScalaEnum
+ inScalaEnum = true
+ val res = body
+ inScalaEnum = old
+ res
+ }
+
+ private def enterEnumImpl[T](body: =>T) = {
+ val old = inEnumImpl
+ inEnumImpl = true
+ val res = body
+ inEnumImpl = old
+ res
+ }
+
+ }
+
+ def isJSAny(sym: Symbol): Boolean =
+ sym.tpe.typeSymbol isSubClass JSAnyClass
+
+ private def isJSAny(implDef: ImplDef): Boolean = isJSAny(implDef.symbol)
+
+ private def isJSGlobalScope(implDef: ImplDef) =
+ implDef.symbol.tpe.typeSymbol isSubClass JSGlobalScopeClass
+
+ private def isJSLambda(sym: Symbol) = sym.isAnonymousClass &&
+ AllJSFunctionClasses.exists(sym.tpe.typeSymbol isSubClass _)
+
+ private def isScalaEnum(implDef: ImplDef) =
+ implDef.symbol.tpe.typeSymbol isSubClass ScalaEnumClass
+
+ private def isJSDynamic(tree: Tree) = tree.tpe.typeSymbol == JSDynamicClass
+
+ /**
+ * is this symbol a setter that has a non-unit return type
+ *
+ * these setters don't make sense in JS (in JS, assignment returns
+ * the assigned value) and are therefore not allowed in facade types
+ */
+ private def isNonJSScalaSetter(sym: Symbol) = nme.isSetterName(sym.name) && {
+ sym.tpe.paramss match {
+ case List(List(arg)) =>
+ !isScalaRepeatedParamType(arg.tpe) &&
+ sym.tpe.resultType.typeSymbol != UnitClass
+ case _ => false
+ }
+ }
+
+ trait ScalaEnumFctExtractors {
+ protected val methSym: Symbol
+
+ protected def resolve(ptpes: Symbol*) = {
+ val res = methSym suchThat {
+ _.tpe.params.map(_.tpe.typeSymbol) == ptpes.toList
+ }
+ assert(res != NoSymbol)
+ res
+ }
+
+ protected val noArg = resolve()
+ protected val nameArg = resolve(StringClass)
+ protected val intArg = resolve(IntClass)
+ protected val fullMeth = resolve(IntClass, StringClass)
+
+ /**
+ * Extractor object for calls to the targeted symbol that do not have an
+ * explicit name in the parameters
+ *
+ * Extracts:
+ * - `sel: Select` where sel.symbol is targeted symbol (no arg)
+ * - Apply(meth, List(param)) where meth.symbol is targeted symbol (i: Int)
+ */
+ object NoName {
+ def unapply(t: Tree) = t match {
+ case sel: Select if sel.symbol == noArg =>
+ Some(None)
+ case Apply(meth, List(param)) if meth.symbol == intArg =>
+ Some(Some(param))
+ case _ =>
+ None
+ }
+ }
+
+ object NullName {
+ def unapply(tree: Tree) = tree match {
+ case Apply(meth, List(Literal(Constant(null)))) =>
+ meth.symbol == nameArg
+ case Apply(meth, List(_, Literal(Constant(null)))) =>
+ meth.symbol == fullMeth
+ case _ => false
+ }
+ }
+
+ }
+
+ private object ScalaEnumValue extends {
+ protected val methSym = getMemberMethod(ScalaEnumClass, jsnme.Value)
+ } with ScalaEnumFctExtractors
+
+ private object ScalaEnumVal extends {
+ protected val methSym = {
+ val valSym = getMemberClass(ScalaEnumClass, jsnme.Val)
+ valSym.tpe.member(nme.CONSTRUCTOR)
+ }
+ } with ScalaEnumFctExtractors
+
+ /**
+ * Construct a call to Enumeration.Value
+ * @param thisSym ClassSymbol of enclosing class
+ * @param nameOrig Symbol of ValDef where this call will be placed
+ * (determines the string passed to Value)
+ * @param intParam Optional tree with Int passed to Value
+ * @return Typed tree with appropriate call to Value
+ */
+ private def ScalaEnumValName(
+ thisSym: Symbol,
+ nameOrig: Symbol,
+ intParam: Option[Tree]) = {
+
+ val defaultName = nameOrig.asTerm.getterName.encoded
+
+
+ // Construct the following tree
+ //
+ // if (nextName != null && nextName.hasNext)
+ // nextName.next()
+ // else
+ // <defaultName>
+ //
+ val nextNameTree = Select(This(thisSym), jsnme.nextName)
+ val nullCompTree =
+ Apply(Select(nextNameTree, nme.NE), Literal(Constant(null)) :: Nil)
+ val hasNextTree = Select(nextNameTree, jsnme.hasNext)
+ val condTree = Apply(Select(nullCompTree, nme.ZAND), hasNextTree :: Nil)
+ val nameTree = If(condTree,
+ Apply(Select(nextNameTree, jsnme.next), Nil),
+ Literal(Constant(defaultName)))
+ val params = intParam.toList :+ nameTree
+
+ typer.typed {
+ Apply(Select(This(thisSym), jsnme.Value), params)
+ }
+ }
+
+ private def rawJSAnnot =
+ Annotation(RawJSTypeAnnot.tpe, List.empty, ListMap.empty)
+
+ private lazy val ScalaEnumClass = getRequiredClass("scala.Enumeration")
+
+ /** checks if the primary constructor of the ClassDef `cldef` does not
+ * take any arguments
+ */
+ private def primCtorNoArg(cldef: ClassDef) =
+ getPrimCtor(cldef.symbol.tpe).map(_.paramss == List(List())).getOrElse(true)
+
+ /** return the MethodSymbol of the primary constructor of the given type
+ * if it exists
+ */
+ private def getPrimCtor(tpe: Type) =
+ tpe.declaration(nme.CONSTRUCTOR).alternatives.collectFirst {
+ case ctor: MethodSymbol if ctor.isPrimaryConstructor => ctor
+ }
+
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSOptions.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSOptions.scala
new file mode 100644
index 0000000..72912bf
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSOptions.scala
@@ -0,0 +1,30 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Tobias Schlatter
+ */
+
+package scala.scalajs.compiler
+
+import java.net.URI
+
+/** This trait allows to query all options to the ScalaJS plugin
+ *
+ * Also see the help text in ScalaJSPlugin for information about particular
+ * options.
+ */
+trait ScalaJSOptions {
+ import ScalaJSOptions.URIMap
+
+ /** should calls to Predef.classOf[T] be fixed in the jsinterop phase.
+ * If false, bad calls to classOf will cause an error. */
+ def fixClassOf: Boolean
+
+ /** which source locations in source maps should be relativized (or where
+ * should they be mapped to)? */
+ def sourceURIMaps: List[URIMap]
+
+}
+
+object ScalaJSOptions {
+ case class URIMap(from: URI, to: Option[URI])
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSPlugin.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSPlugin.scala
new file mode 100644
index 0000000..c3916ab
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSPlugin.scala
@@ -0,0 +1,143 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.tools.nsc._
+import scala.tools.nsc.plugins.{
+ Plugin => NscPlugin, PluginComponent => NscPluginComponent
+}
+import scala.collection.{ mutable, immutable }
+
+import java.net.{ URI, URISyntaxException }
+
+import scala.scalajs.ir.Trees
+
+/** Main entry point for the Scala.js compiler plugin
+ *
+ * @author Sébastien Doeraene
+ */
+class ScalaJSPlugin(val global: Global) extends NscPlugin {
+ import global._
+
+ val name = "scalajs"
+ val description = "Compile to JavaScript"
+ val components = {
+ if (global.forScaladoc)
+ List[NscPluginComponent](PrepInteropComponent)
+ else
+ List[NscPluginComponent](PrepInteropComponent, GenCodeComponent)
+ }
+
+ /** Called when the JS ASTs are generated. Override for testing */
+ def generatedJSAST(clDefs: List[Trees.Tree]): Unit = {}
+
+ /** Addons for JavaScript platform */
+ object jsAddons extends {
+ val global: ScalaJSPlugin.this.global.type = ScalaJSPlugin.this.global
+ } with JSGlobalAddons with Compat210Component
+
+ object scalaJSOpts extends ScalaJSOptions {
+ import ScalaJSOptions.URIMap
+ var fixClassOf: Boolean = false
+ lazy val sourceURIMaps: List[URIMap] = {
+ if (_sourceURIMaps.nonEmpty)
+ _sourceURIMaps.reverse
+ else
+ relSourceMap.toList.map(URIMap(_, absSourceMap))
+ }
+ var _sourceURIMaps: List[URIMap] = Nil
+ var relSourceMap: Option[URI] = None
+ var absSourceMap: Option[URI] = None
+ }
+
+ object PrepInteropComponent extends {
+ val global: ScalaJSPlugin.this.global.type = ScalaJSPlugin.this.global
+ val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons
+ val scalaJSOpts = ScalaJSPlugin.this.scalaJSOpts
+ override val runsAfter = List("typer")
+ override val runsBefore = List("pickle")
+ } with PrepJSInterop
+
+ object GenCodeComponent extends {
+ val global: ScalaJSPlugin.this.global.type = ScalaJSPlugin.this.global
+ val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons
+ val scalaJSOpts = ScalaJSPlugin.this.scalaJSOpts
+ override val runsAfter = List("mixin")
+ override val runsBefore = List("delambdafy", "cleanup", "terminal")
+ } with GenJSCode {
+ def generatedJSAST(clDefs: List[Trees.Tree]) =
+ ScalaJSPlugin.this.generatedJSAST(clDefs)
+ }
+
+ override def processOptions(options: List[String],
+ error: String => Unit): Unit = {
+ import ScalaJSOptions.URIMap
+ import scalaJSOpts._
+
+ for (option <- options) {
+ if (option == "fixClassOf") {
+ fixClassOf = true
+
+ } else if (option.startsWith("mapSourceURI:")) {
+ val uris = option.stripPrefix("mapSourceURI:").split("->")
+
+ if (uris.length != 1 && uris.length != 2) {
+ error("relocateSourceMap needs one or two URIs as argument.")
+ } else {
+ try {
+ val from = new URI(uris.head)
+ val to = uris.lift(1).map(str => new URI(str))
+ _sourceURIMaps ::= URIMap(from, to)
+ } catch {
+ case e: URISyntaxException =>
+ error(s"${e.getInput} is not a valid URI")
+ }
+ }
+
+ // The following options are deprecated (how do we show this to the user?)
+ } else if (option.startsWith("relSourceMap:")) {
+ val uriStr = option.stripPrefix("relSourceMap:")
+ try { relSourceMap = Some(new URI(uriStr)) }
+ catch {
+ case e: URISyntaxException => error(s"$uriStr is not a valid URI")
+ }
+ } else if (option.startsWith("absSourceMap:")) {
+ val uriStr = option.stripPrefix("absSourceMap:")
+ try { absSourceMap = Some(new URI(uriStr)) }
+ catch {
+ case e: URISyntaxException => error(s"$uriStr is not a valid URI")
+ }
+ } else {
+ error("Option not understood: " + option)
+ }
+ }
+
+ // Verify constraints
+ if (_sourceURIMaps.nonEmpty && relSourceMap.isDefined)
+ error("You may not use mapSourceURI and relSourceMap together. " +
+ "Use another mapSourceURI option without second URI.")
+ else if (_sourceURIMaps.nonEmpty && absSourceMap.isDefined)
+ error("You may not use mapSourceURI and absSourceMap together. " +
+ "Use another mapSourceURI option.")
+ else if (absSourceMap.isDefined && relSourceMap.isEmpty)
+ error("absSourceMap requires the use of relSourceMap")
+ }
+
+ override val optionsHelp: Option[String] = Some(s"""
+ | -P:$name:mapSourceURI:FROM_URI[->TO_URI]
+ | change the location the source URIs in the emitted IR point to
+ | - strips away the prefix FROM_URI (if it matches)
+ | - optionally prefixes the TO_URI, where stripping has been performed
+ | - any number of occurences are allowed. Processing is done on a first match basis.
+ | -P:$name:fixClassOf repair calls to Predef.classOf that reach ScalaJS
+ | WARNING: This is a tremendous hack! Expect ugly errors if you use this option.
+ |Deprecated options
+ | -P:$name:relSourceMap:<URI> relativize emitted source maps with <URI>
+ | -P:$name:absSourceMap:<URI> absolutize emitted source maps with <URI>
+ | This option requires the use of relSourceMap
+ """.stripMargin)
+
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/TypeKinds.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/TypeKinds.scala
new file mode 100644
index 0000000..774be68
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/TypeKinds.scala
@@ -0,0 +1,252 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.tools.nsc._
+
+import scala.scalajs.ir
+import ir.{Definitions, Types}
+
+/** Glue representation of types as seen from the IR but still with a
+ * reference to the Symbols.
+ *
+ * @author Sébastien Doeraene
+ */
+trait TypeKinds extends SubComponent { this: GenJSCode =>
+ import global._
+ import jsAddons._
+ import definitions._
+
+ lazy val ObjectReference = REFERENCE(definitions.ObjectClass)
+
+ lazy val VoidKind = VOID
+ lazy val BooleanKind = BOOL
+ lazy val CharKind = INT(CharClass)
+ lazy val ByteKind = INT(ByteClass)
+ lazy val ShortKind = INT(ShortClass)
+ lazy val IntKind = INT(IntClass)
+ lazy val LongKind = LONG
+ lazy val FloatKind = FLOAT(FloatClass)
+ lazy val DoubleKind = FLOAT(DoubleClass)
+
+ /** TypeKinds for Scala primitive types. */
+ lazy val primitiveTypeMap: Map[Symbol, TypeKind] = {
+ import definitions._
+ Map(
+ UnitClass -> VoidKind,
+ BooleanClass -> BooleanKind,
+ CharClass -> CharKind,
+ ByteClass -> ByteKind,
+ ShortClass -> ShortKind,
+ IntClass -> IntKind,
+ LongClass -> LongKind,
+ FloatClass -> FloatKind,
+ DoubleClass -> DoubleKind
+ )
+ }
+
+ /** Glue representation of types as seen from the IR but still with a
+ * reference to the Symbols.
+ */
+ sealed abstract class TypeKind {
+ def isReferenceType = false
+ def isArrayType = false
+ def isValueType = false
+
+ def toIRType: Types.Type
+ def toReferenceType: Types.ReferenceType
+ }
+
+ sealed abstract class TypeKindButArray extends TypeKind {
+ protected def typeSymbol: Symbol
+
+ override def toReferenceType: Types.ClassType =
+ Types.ClassType(encodeClassFullName(typeSymbol))
+ }
+
+ /** The void, for trees that can only appear in statement position. */
+ case object VOID extends TypeKindButArray {
+ protected def typeSymbol = UnitClass
+ def toIRType: Types.NoType.type = Types.NoType
+ }
+
+ sealed abstract class ValueTypeKind extends TypeKindButArray {
+ override def isValueType = true
+
+ val primitiveCharCode: Char = typeSymbol match {
+ case BooleanClass => 'Z'
+ case CharClass => 'C'
+ case ByteClass => 'B'
+ case ShortClass => 'S'
+ case IntClass => 'I'
+ case LongClass => 'J'
+ case FloatClass => 'F'
+ case DoubleClass => 'D'
+ case x => abort("Unknown primitive type: " + x.fullName)
+ }
+ }
+
+ /** Integer number (Byte, Short, Char or Int). */
+ case class INT private[TypeKinds] (typeSymbol: Symbol) extends ValueTypeKind {
+ def toIRType: Types.IntType.type = Types.IntType
+ }
+
+ /** Long */
+ case object LONG extends ValueTypeKind {
+ protected def typeSymbol = definitions.LongClass
+ def toIRType: Types.LongType.type = Types.LongType
+ }
+
+ /** Floating-point number (Float or Double). */
+ case class FLOAT private[TypeKinds] (typeSymbol: Symbol) extends ValueTypeKind {
+ def toIRType: Types.Type =
+ if (typeSymbol == FloatClass) Types.FloatType
+ else Types.DoubleType
+ }
+
+ /** Boolean */
+ case object BOOL extends ValueTypeKind {
+ protected def typeSymbol = definitions.BooleanClass
+ def toIRType: Types.BooleanType.type = Types.BooleanType
+ }
+
+ /** Nothing */
+ case object NOTHING extends TypeKindButArray {
+ protected def typeSymbol = definitions.NothingClass
+ def toIRType: Types.NothingType.type = Types.NothingType
+ override def toReferenceType: Types.ClassType =
+ Types.ClassType(Definitions.RuntimeNothingClass)
+ }
+
+ /** Null */
+ case object NULL extends TypeKindButArray {
+ protected def typeSymbol = definitions.NullClass
+ def toIRType: Types.NullType.type = Types.NullType
+ override def toReferenceType: Types.ClassType =
+ Types.ClassType(Definitions.RuntimeNullClass)
+ }
+
+ /** An object */
+ case class REFERENCE private[TypeKinds] (typeSymbol: Symbol) extends TypeKindButArray {
+ override def toString(): String = "REFERENCE(" + typeSymbol.fullName + ")"
+ override def isReferenceType = true
+
+ def toIRType: Types.Type = encodeClassType(typeSymbol)
+ }
+
+ /** An array */
+ case class ARRAY private[TypeKinds] (elem: TypeKind) extends TypeKind {
+ override def toString = "ARRAY[" + elem + "]"
+ override def isArrayType = true
+
+ def dimensions: Int = elem match {
+ case a: ARRAY => a.dimensions + 1
+ case _ => 1
+ }
+
+ override def toIRType: Types.ArrayType = toReferenceType
+
+ override def toReferenceType: Types.ArrayType = {
+ Types.ArrayType(
+ elementKind.toReferenceType.className,
+ dimensions)
+ }
+
+ /** The ultimate element type of this array. */
+ def elementKind: TypeKindButArray = elem match {
+ case a: ARRAY => a.elementKind
+ case k: TypeKindButArray => k
+ }
+ }
+
+ ////////////////// Conversions //////////////////////////////
+
+ def toIRType(t: Type): Types.Type =
+ toTypeKind(t).toIRType
+
+ def toReferenceType(t: Type): Types.ReferenceType =
+ toTypeKind(t).toReferenceType
+
+ // The following code is a hard copy-and-paste from backend.icode.TypeKinds
+
+ /** Return the TypeKind of the given type
+ *
+ * Call to .normalize fixes #3003 (follow type aliases). Otherwise,
+ * arrayOrClassType below would return ObjectReference.
+ */
+ def toTypeKind(t: Type): TypeKind = t.normalize match {
+ case ThisType(ArrayClass) => ObjectReference
+ case ThisType(sym) => newReference(sym)
+ case SingleType(_, sym) => primitiveOrRefType(sym)
+ case ConstantType(_) => toTypeKind(t.underlying)
+ case TypeRef(_, sym, args) => primitiveOrClassType(sym, args)
+ case ClassInfoType(_, _, ArrayClass) => abort("ClassInfoType to ArrayClass!")
+ case ClassInfoType(_, _, sym) => primitiveOrRefType(sym)
+
+ // !!! Iulian says types which make no sense after erasure should not reach here,
+ // which includes the ExistentialType, AnnotatedType, RefinedType. I don't know
+ // if the first two cases exist because they do or as a defensive measure, but
+ // at the time I added it, RefinedTypes were indeed reaching here.
+ // !!! Removed in JavaScript backend because I do not know what to do with lub
+ //case ExistentialType(_, t) => toTypeKind(t)
+ // Apparently, this case does occur (see pos/CustomGlobal.scala)
+ case t: AnnotatedType => toTypeKind(t.underlying)
+ //case RefinedType(parents, _) => parents map toTypeKind reduceLeft lub
+
+ /* This case is not in scalac. We need it for the test
+ * run/valueclasses-classtag-existential. I have no idea how icode does
+ * not fail this test: we do everything the same as icode up to here.
+ */
+ case tpe: ErasedValueType => newReference(tpe.valueClazz)
+
+ // For sure WildcardTypes shouldn't reach here either, but when
+ // debugging such situations this may come in handy.
+ // case WildcardType => REFERENCE(ObjectClass)
+ case norm => abort(
+ "Unknown type: %s, %s [%s, %s] TypeRef? %s".format(
+ t, norm, t.getClass, norm.getClass, t.isInstanceOf[TypeRef]
+ )
+ )
+ }
+
+ /** Return the type kind of a class, possibly an array type.
+ */
+ private def arrayOrClassType(sym: Symbol, targs: List[Type]) = sym match {
+ case ArrayClass => ARRAY(toTypeKind(targs.head))
+ case _ if sym.isClass => newReference(sym)
+ case _ =>
+ assert(sym.isType, sym) // it must be compiling Array[a]
+ ObjectReference
+ }
+
+ /** Interfaces have to be handled delicately to avoid introducing
+ * spurious errors, but if we treat them all as AnyRef we lose too
+ * much information.
+ */
+ private def newReference(sym: Symbol): TypeKind = sym match {
+ case NothingClass => NOTHING
+ case NullClass => NULL
+ case _ =>
+ // Can't call .toInterface (at this phase) or we trip an assertion.
+ // See PackratParser#grow for a method which fails with an apparent mismatch
+ // between "object PackratParsers$class" and "trait PackratParsers"
+ if (sym.isImplClass) {
+ // pos/spec-List.scala is the sole failure if we don't check for NoSymbol
+ val traitSym = sym.owner.info.decl(tpnme.interfaceName(sym.name))
+ if (traitSym != NoSymbol)
+ REFERENCE(traitSym)
+ else
+ REFERENCE(sym)
+ } else {
+ REFERENCE(sym)
+ }
+ }
+
+ private def primitiveOrRefType(sym: Symbol) =
+ primitiveTypeMap.getOrElse(sym, newReference(sym))
+ private def primitiveOrClassType(sym: Symbol, targs: List[Type]) =
+ primitiveTypeMap.getOrElse(sym, arrayOrClassType(sym, targs))
+}
diff --git a/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/util/ScopedVar.scala b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/util/ScopedVar.scala
new file mode 100644
index 0000000..3924955
--- /dev/null
+++ b/examples/scala-js/compiler/src/main/scala/scala/scalajs/compiler/util/ScopedVar.scala
@@ -0,0 +1,38 @@
+package scala.scalajs.compiler.util
+
+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())
+ }
+}