diff options
author | Haoyi Li <haoyi@haoyi-mbp.corp.dropbox.com> | 2014-11-26 00:45:31 -0800 |
---|---|---|
committer | Haoyi Li <haoyi@haoyi-mbp.corp.dropbox.com> | 2014-11-26 00:45:31 -0800 |
commit | 24f31e120f9537faede7a174bb09ee35f64e1ce4 (patch) | |
tree | 06ffc3ecc7847789008352b7e2b7c040dad48907 /examples/scala-js/compiler/src/main/scala/scala | |
parent | b89ce9cbf79363f8cab09186a5d7ba94bc0af02a (diff) | |
parent | 2c4b142503bd2d871e6818b5cab8c38627d9e4a0 (diff) | |
download | hands-on-scala-js-24f31e120f9537faede7a174bb09ee35f64e1ce4.tar.gz hands-on-scala-js-24f31e120f9537faede7a174bb09ee35f64e1ce4.tar.bz2 hands-on-scala-js-24f31e120f9537faede7a174bb09ee35f64e1ce4.zip |
Merge commit '2c4b142503bd2d871e6818b5cab8c38627d9e4a0' as 'examples/scala-js'
Diffstat (limited to 'examples/scala-js/compiler/src/main/scala/scala')
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()) + } +} |