From 06a3d47ea9fd1b67b3acba9d115a16d18549e377 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 26 Oct 2016 16:19:35 +0200 Subject: Move sjs, make sure that partest compiles everything in dirs --- library/src/scalaShadowing/language.scala | 198 ++ project/Build.scala | 86 +- sjs/backend/sjs/GenSJSIR.scala | 14 + sjs/backend/sjs/JSCodeGen.scala | 2392 +++++++++++++++++++++++ sjs/backend/sjs/JSDefinitions.scala | 199 ++ sjs/backend/sjs/JSEncoding.scala | 389 ++++ sjs/backend/sjs/JSInterop.scala | 110 ++ sjs/backend/sjs/JSPositions.scala | 65 + sjs/backend/sjs/JSPrimitives.scala | 118 ++ sjs/backend/sjs/ScopedVar.scala | 38 + sjs/tools/dotc/config/SJSPlatform.scala | 18 + src/dotty/tools/backend/sjs/GenSJSIR.scala | 14 - src/dotty/tools/backend/sjs/JSCodeGen.scala | 2392 ----------------------- src/dotty/tools/backend/sjs/JSDefinitions.scala | 199 -- src/dotty/tools/backend/sjs/JSEncoding.scala | 389 ---- src/dotty/tools/backend/sjs/JSInterop.scala | 110 -- src/dotty/tools/backend/sjs/JSPositions.scala | 65 - src/dotty/tools/backend/sjs/JSPrimitives.scala | 118 -- src/dotty/tools/backend/sjs/ScopedVar.scala | 38 - src/dotty/tools/dotc/Compiler.scala | 17 +- src/dotty/tools/dotc/config/PathResolver.scala | 24 +- src/dotty/tools/dotc/config/SJSPlatform.scala | 18 - src/dotty/tools/dotc/core/Contexts.scala | 5 +- src/scalaShadowing/language.scala | 198 -- test/dotc/tests.scala | 55 +- test/dotty/partest/DPConsoleRunner.scala | 102 +- test/test/CompilerTest.scala | 27 +- tests/repl/imports.check | 6 +- 28 files changed, 3683 insertions(+), 3721 deletions(-) create mode 100644 library/src/scalaShadowing/language.scala create mode 100644 sjs/backend/sjs/GenSJSIR.scala create mode 100644 sjs/backend/sjs/JSCodeGen.scala create mode 100644 sjs/backend/sjs/JSDefinitions.scala create mode 100644 sjs/backend/sjs/JSEncoding.scala create mode 100644 sjs/backend/sjs/JSInterop.scala create mode 100644 sjs/backend/sjs/JSPositions.scala create mode 100644 sjs/backend/sjs/JSPrimitives.scala create mode 100644 sjs/backend/sjs/ScopedVar.scala create mode 100644 sjs/tools/dotc/config/SJSPlatform.scala delete mode 100644 src/dotty/tools/backend/sjs/GenSJSIR.scala delete mode 100644 src/dotty/tools/backend/sjs/JSCodeGen.scala delete mode 100644 src/dotty/tools/backend/sjs/JSDefinitions.scala delete mode 100644 src/dotty/tools/backend/sjs/JSEncoding.scala delete mode 100644 src/dotty/tools/backend/sjs/JSInterop.scala delete mode 100644 src/dotty/tools/backend/sjs/JSPositions.scala delete mode 100644 src/dotty/tools/backend/sjs/JSPrimitives.scala delete mode 100644 src/dotty/tools/backend/sjs/ScopedVar.scala delete mode 100644 src/dotty/tools/dotc/config/SJSPlatform.scala delete mode 100644 src/scalaShadowing/language.scala diff --git a/library/src/scalaShadowing/language.scala b/library/src/scalaShadowing/language.scala new file mode 100644 index 000000000..e2fc5ec61 --- /dev/null +++ b/library/src/scalaShadowing/language.scala @@ -0,0 +1,198 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2015, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ +package scalaShadowing + +/** + * The `scala.language` object controls the language features available to the programmer, as proposed in the + * [[https://docs.google.com/document/d/1nlkvpoIRkx7at1qJEZafJwthZ3GeIklTFhqmXMvTX9Q/edit '''SIP-18 document''']]. + * + * Each of these features has to be explicitly imported into the current scope to become available: + * {{{ + * import language.postfixOps // or language._ + * List(1, 2, 3) reverse + * }}} + * + * The language features are: + * - [[dynamics `dynamics`]] enables defining calls rewriting using the [[scala.Dynamic `Dynamic`]] trait + * - [[postfixOps `postfixOps`]] enables postfix operators + * - [[reflectiveCalls `reflectiveCalls`]] enables using structural types + * - [[implicitConversions `implicitConversions`]] enables defining implicit methods and members + * - [[higherKinds `higherKinds`]] enables writing higher-kinded types + * - [[existentials `existentials`]] enables writing existential types + * - [[experimental `experimental`]] contains newer features that have not yet been tested in production + * + * and, for dotty: + * + * - [[Scala2 `Scala2`] backwards compatibility mode for Scala2 + * - [[noAtoTupling `noAutoTupling`]] disable auto-tupling + * + * @groupname production Language Features + * @groupname experimental Experimental Language Features + * @groupprio experimental 10 + * + * Dotty-specific features come at the end. + * + * Note: Due to the more restricted language import mechanism in dotty (only + * imports count, implicits are disregarded) we don't need the constructions + * of the inherited language features. A simple object for each feature is + * sufficient. + */ +object language { + + import languageFeature._ + + /** Where enabled, direct or indirect subclasses of trait scala.Dynamic can + * be defined. Unless dynamics is enabled, a definition of a class, trait, + * or object that has Dynamic as a base trait is rejected. Dynamic member + * selection of existing subclasses of trait Dynamic are unaffected; + * they can be used anywhere. + * + * '''Why introduce the feature?''' To enable flexible DSLs and convenient interfacing + * with dynamic languages. + * + * '''Why control it?''' Dynamic member selection can undermine static checkability + * of programs. Furthermore, dynamic member selection often relies on reflection, + * which is not available on all platforms. + * + * @group production + */ + @volatile implicit lazy val dynamics: dynamics = languageFeature.dynamics + + /** Only where enabled, postfix operator notation `(expr op)` will be allowed. + * + * '''Why keep the feature?''' Several DSLs written in Scala need the notation. + * + * '''Why control it?''' Postfix operators interact poorly with semicolon inference. + * Most programmers avoid them for this reason. + * + * @group production + */ + @volatile implicit lazy val postfixOps: postfixOps = languageFeature.postfixOps + + /** Only where enabled, accesses to members of structural types that need + * reflection are supported. Reminder: A structural type is a type of the form + * `Parents { Decls }` where `Decls` contains declarations of new members that do + * not override any member in `Parents`. To access one of these members, a + * reflective call is needed. + * + * '''Why keep the feature?''' Structural types provide great flexibility because + * they avoid the need to define inheritance hierarchies a priori. Besides, + * their definition falls out quite naturally from Scala’s concept of type refinement. + * + * '''Why control it?''' Reflection is not available on all platforms. Popular tools + * such as ProGuard have problems dealing with it. Even where reflection is available, + * reflective dispatch can lead to surprising performance degradations. + * + * @group production + */ + @volatile implicit lazy val reflectiveCalls: reflectiveCalls = languageFeature.reflectiveCalls + + /** Only where enabled, definitions of implicit conversions are allowed. An + * implicit conversion is an implicit value of unary function type `A => B`, + * or an implicit method that has in its first parameter section a single, + * non-implicit parameter. Examples: + * + * {{{ + * implicit def stringToInt(s: String): Int = s.length + * implicit val conv = (s: String) => s.length + * implicit def listToX(xs: List[T])(implicit f: T => X): X = ... + * }}} + * + * implicit values of other types are not affected, and neither are implicit + * classes. + * + * '''Why keep the feature?''' Implicit conversions are central to many aspects + * of Scala’s core libraries. + * + * '''Why control it?''' Implicit conversions are known to cause many pitfalls + * if over-used. And there is a tendency to over-use them because they look + * very powerful and their effects seem to be easy to understand. Also, in + * most situations using implicit parameters leads to a better design than + * implicit conversions. + * + * @group production + */ + @volatile implicit lazy val implicitConversions: implicitConversions = languageFeature.implicitConversions + + /** Only where this flag is enabled, higher-kinded types can be written. + * + * '''Why keep the feature?''' Higher-kinded types enable the definition of very general + * abstractions such as functor, monad, or arrow. A significant set of advanced + * libraries relies on them. Higher-kinded types are also at the core of the + * scala-virtualized effort to produce high-performance parallel DSLs through staging. + * + * '''Why control it?''' Higher kinded types in Scala lead to a Turing-complete + * type system, where compiler termination is no longer guaranteed. They tend + * to be useful mostly for type-level computation and for highly generic design + * patterns. The level of abstraction implied by these design patterns is often + * a barrier to understanding for newcomers to a Scala codebase. Some syntactic + * aspects of higher-kinded types are hard to understand for the uninitiated and + * type inference is less effective for them than for normal types. Because we are + * not completely happy with them yet, it is possible that some aspects of + * higher-kinded types will change in future versions of Scala. So an explicit + * enabling also serves as a warning that code involving higher-kinded types + * might have to be slightly revised in the future. + * + * @group production + */ + @volatile implicit lazy val higherKinds: higherKinds = languageFeature.higherKinds + + /** Only where enabled, existential types that cannot be expressed as wildcard + * types can be written and are allowed in inferred types of values or return + * types of methods. Existential types with wildcard type syntax such as `List[_]`, + * or `Map[String, _]` are not affected. + * + * '''Why keep the feature?''' Existential types are needed to make sense of Java’s wildcard + * types and raw types and the erased types of run-time values. + * + * '''Why control it?''' Having complex existential types in a code base usually makes + * application code very brittle, with a tendency to produce type errors with + * obscure error messages. Therefore, going overboard with existential types + * is generally perceived not to be a good idea. Also, complicated existential types + * might be no longer supported in a future simplification of the language. + * + * @group production + */ + @volatile implicit lazy val existentials: existentials = languageFeature.existentials + + /** The experimental object contains features that have been recently added but have not + * been thoroughly tested in production yet. + * + * Experimental features '''may undergo API changes''' in future releases, so production + * code should not rely on them. + * + * Programmers are encouraged to try out experimental features and + * [[http://issues.scala-lang.org report any bugs or API inconsistencies]] + * they encounter so they can be improved in future releases. + * + * @group experimental + */ + object experimental { + + import languageFeature.experimental._ + + /** Where enabled, macro definitions are allowed. Macro implementations and + * macro applications are unaffected; they can be used anywhere. + * + * '''Why introduce the feature?''' Macros promise to make the language more regular, + * replacing ad-hoc language constructs with a general powerful abstraction + * capability that can express them. Macros are also a more disciplined and + * powerful replacement for compiler plugins. + * + * '''Why control it?''' For their very power, macros can lead to code that is hard + * to debug and understand. + */ + @volatile implicit lazy val macros: macros = languageFeature.experimental.macros + } + + /** Where imported, a backwards compatibility mode for Scala2 is enabled */ + object Scala2 + + /** Where imported, auto-tupling is disabled */ + object noAutoTupling +} diff --git a/project/Build.scala b/project/Build.scala index e7fab03eb..6341f08c4 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -135,9 +135,13 @@ object DottyBuild extends Build { // command line arguments get passed to the last task in an aliased // sequence (see partest alias below), so this works. val args = Def.spaceDelimited("").parsed - val jars = Seq((packageBin in Compile).value.getAbsolutePath) ++ - getJarPaths(partestDeps.value, ivyPaths.value.ivyHome) - val dottyJars = "-dottyJars " + (jars.length + 1) + " dotty-lib.jar" + " " + jars.mkString(" ") + val jars = List( + (packageBin in Compile).value.getAbsolutePath, + (packageBin in (`dotty-library`, Compile)).value.getAbsolutePath, + (packageBin in (`dotty-interfaces`, Compile)).value.getAbsolutePath + ) ++ getJarPaths(partestDeps.value, ivyPaths.value.ivyHome) + val dottyJars = + s"""-dottyJars ${jars.length + 2} dotty.jar dotty-lib.jar ${jars.mkString(" ")}""" // Provide the jars required on the classpath of run tests runTask(Test, "dotty.partest.DPConsoleRunner", dottyJars + " " + args.mkString(" ")) }, @@ -147,32 +151,33 @@ object DottyBuild extends Build { * of scalajs-ir built with a different Scala compiler, we add its * sources instead of depending on the binaries. */ - ivyConfigurations += config("sourcedeps").hide, - libraryDependencies += - "org.scala-js" %% "scalajs-ir" % scalaJSVersion % "sourcedeps", - sourceGenerators in Compile += Def.task { - val s = streams.value - val cacheDir = s.cacheDirectory - val trgDir = (sourceManaged in Compile).value / "scalajs-ir-src" - - val report = updateClassifiers.value - val scalaJSIRSourcesJar = report.select( - configuration = Set("sourcedeps"), - module = (_: ModuleID).name.startsWith("scalajs-ir_"), - artifact = artifactFilter(`type` = "src")).headOption.getOrElse { - sys.error(s"Could not fetch scalajs-ir sources") - } - - FileFunction.cached(cacheDir / s"fetchScalaJSIRSource", - FilesInfo.lastModified, FilesInfo.exists) { dependencies => - s.log.info(s"Unpacking scalajs-ir sources to $trgDir...") - if (trgDir.exists) - IO.delete(trgDir) - IO.createDirectory(trgDir) - IO.unzip(scalaJSIRSourcesJar, trgDir) - (trgDir ** "*.scala").get.toSet - } (Set(scalaJSIRSourcesJar)).toSeq - }.taskValue, + //TODO: disabling until moved to separate project + //ivyConfigurations += config("sourcedeps").hide, + //libraryDependencies += + // "org.scala-js" %% "scalajs-ir" % scalaJSVersion % "sourcedeps", + //sourceGenerators in Compile += Def.task { + // val s = streams.value + // val cacheDir = s.cacheDirectory + // val trgDir = (sourceManaged in Compile).value / "scalajs-ir-src" + + // val report = updateClassifiers.value + // val scalaJSIRSourcesJar = report.select( + // configuration = Set("sourcedeps"), + // module = (_: ModuleID).name.startsWith("scalajs-ir_"), + // artifact = artifactFilter(`type` = "src")).headOption.getOrElse { + // sys.error(s"Could not fetch scalajs-ir sources") + // } + + // FileFunction.cached(cacheDir / s"fetchScalaJSIRSource", + // FilesInfo.lastModified, FilesInfo.exists) { dependencies => + // s.log.info(s"Unpacking scalajs-ir sources to $trgDir...") + // if (trgDir.exists) + // IO.delete(trgDir) + // IO.createDirectory(trgDir) + // IO.unzip(scalaJSIRSourcesJar, trgDir) + // (trgDir ** "*.scala").get.toSet + // } (Set(scalaJSIRSourcesJar)).toSeq + //}.taskValue, // Adjust classpath for running dotty mainClass in (Compile, run) := Some("dotty.tools.dotc.Main"), @@ -185,29 +190,34 @@ object DottyBuild extends Build { // http://grokbase.com/t/gg/simple-build-tool/135ke5y90p/sbt-setting-jvm-boot-paramaters-for-scala javaOptions <++= (dependencyClasspath in Runtime, packageBin in Compile) map { (attList, bin) => - // put the Scala {library, reflect} in the classpath + // put needed dependencies on classpath: val path = for { file <- attList.map(_.data) path = file.getAbsolutePath + // FIXME: when we snip the cord, this should go bye-bye + if path.contains("scala-library") || + // FIXME: currently needed for tests referencing scalac internals + path.contains("scala-reflect") || + // FIXME: currently needed for tests referencing scalac internals + path.contains("scala-compile") || + // FIXME: should go away when xml literal parsing is removed + path.contains("scala-xml") || + // needed for the xsbti interface + path.contains("sbt-interface") } yield "-Xbootclasspath/p:" + path - // dotty itself needs to be in the bootclasspath - val fullpath = /*("-Xbootclasspath/p:" + "dotty.jar") ::*/ ("-Xbootclasspath/a:" + bin) :: path.toList - // System.err.println("BOOTPATH: " + fullpath) val travis_build = // propagate if this is a travis build if (sys.props.isDefinedAt(JENKINS_BUILD)) List(s"-D$JENKINS_BUILD=${sys.props(JENKINS_BUILD)}") ::: jenkinsMemLimit - else - List() + else List() val tuning = if (sys.props.isDefinedAt("Oshort")) // Optimize for short-running applications, see https://github.com/lampepfl/dotty/issues/222 List("-XX:+TieredCompilation", "-XX:TieredStopAtLevel=1") - else - List() + else List() - ("-DpartestParentID=" + pid) :: tuning ::: agentOptions ::: travis_build ::: fullpath + ("-DpartestParentID=" + pid) :: tuning ::: agentOptions ::: travis_build ::: path.toList } ). settings( diff --git a/sjs/backend/sjs/GenSJSIR.scala b/sjs/backend/sjs/GenSJSIR.scala new file mode 100644 index 000000000..819a8f0e3 --- /dev/null +++ b/sjs/backend/sjs/GenSJSIR.scala @@ -0,0 +1,14 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ +import Contexts._ +import Phases._ + +/** Generates Scala.js IR files for the compilation unit. */ +class GenSJSIR extends Phase { + def phaseName: String = "genSJSIR" + + def run(implicit ctx: Context): Unit = { + new JSCodeGen().run() + } +} diff --git a/sjs/backend/sjs/JSCodeGen.scala b/sjs/backend/sjs/JSCodeGen.scala new file mode 100644 index 000000000..401e01784 --- /dev/null +++ b/sjs/backend/sjs/JSCodeGen.scala @@ -0,0 +1,2392 @@ +package dotty.tools.backend.sjs + +import scala.annotation.switch + +import scala.collection.mutable + +import dotty.tools.FatalError + +import dotty.tools.dotc.CompilationUnit +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Phases.Phase + +import dotty.tools.dotc.core._ +import Periods._ +import SymDenotations._ +import Contexts._ +import Decorators._ +import Flags._ +import dotty.tools.dotc.ast.Trees._ +import Types._ +import Symbols._ +import Denotations._ +import Phases._ +import StdNames._ + +import dotty.tools.dotc.transform.Erasure + +import org.scalajs.core.ir +import org.scalajs.core.ir.{ClassKind, Position, Trees => js, Types => jstpe} +import js.OptimizerHints + +import JSEncoding._ +import JSInterop._ +import ScopedVar.withScopedVars + +/** Main codegen for Scala.js IR. + * + * [[GenSJSIR]] creates one instance of `JSCodeGen` per compilation unit. + * The `run()` method processes the whole compilation unit and generates + * `.sjsir` files for it. + * + * There are 4 main levels of translation: + * + * - `genCompilationUnit()` iterates through all the type definitions in the + * compilation unit. Each generated `js.ClassDef` is serialized to an + * `.sjsir` file. + * - `genScalaClass()` and other similar methods generate the skeleton of + * classes. + * - `genMethod()` and similar methods generate the declarations of methods. + * - `genStatOrExpr()` and everything else generate the bodies of methods. + */ +class JSCodeGen()(implicit ctx: Context) { + import tpd._ + + private val jsdefn = JSDefinitions.jsdefn + private val primitives = new JSPrimitives(ctx) + + private val positionConversions = new JSPositions()(ctx) + import positionConversions.{pos2irPos, implicitPos2irPos} + + // Some state -------------------------------------------------------------- + + private val currentClassSym = new ScopedVar[Symbol] + private val currentMethodSym = new ScopedVar[Symbol] + private val localNames = new ScopedVar[LocalNameGenerator] + private val thisLocalVarIdent = new ScopedVar[Option[js.Ident]] + private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]] + + /** Implicitly materializes the current local name generator. */ + private implicit def implicitLocalNames: LocalNameGenerator = localNames.get + + /* See genSuperCall() + * TODO Can we avoid this unscoped var? + */ + private var isModuleInitialized: Boolean = false + + private def currentClassType = encodeClassType(currentClassSym) + + /** Returns a new fresh local identifier. */ + private def freshLocalIdent()(implicit pos: Position): js.Ident = + localNames.get.freshLocalIdent() + + /** Returns a new fresh local identifier. */ + private def freshLocalIdent(base: String)(implicit pos: Position): js.Ident = + localNames.get.freshLocalIdent(base) + + // Compilation unit -------------------------------------------------------- + + def run(): Unit = { + genCompilationUnit(ctx.compilationUnit) + } + + /** Generates the Scala.js IR for a compilation unit + * This method iterates over all the class and interface definitions + * found in the compilation unit and emits their IR (.sjsir). + * + * Some classes are never actually emitted: + * - Classes representing primitive types + * - The scala.Array class + * + * TODO Some classes representing anonymous functions are not actually emitted. + * Instead, a temporary representation of their `apply` method is built + * and recorded, so that it can be inlined as a JavaScript anonymous + * function in the method that instantiates it. + * + * Other ClassDefs are emitted according to their nature: + * * Scala.js-defined JS class -> `genScalaJSDefinedJSClass()` + * * Other raw JS type (<: js.Any) -> `genRawJSClassData()` + * * Interface -> `genInterface()` + * * Normal class -> `genClass()` + */ + private def genCompilationUnit(cunit: CompilationUnit): Unit = { + def collectTypeDefs(tree: Tree): List[TypeDef] = { + tree match { + case EmptyTree => Nil + case PackageDef(_, stats) => stats.flatMap(collectTypeDefs) + case cd: TypeDef => cd :: Nil + case _: ValDef => Nil // module instance + } + } + val allTypeDefs = collectTypeDefs(cunit.tpdTree) + + val generatedClasses = mutable.ListBuffer.empty[(Symbol, js.ClassDef)] + + // TODO Record anonymous JS function classes + + /* Finally, we emit true code for the remaining class defs. */ + for (td <- allTypeDefs) { + val sym = td.symbol + implicit val pos: Position = sym.pos + + /* Do not actually emit code for primitive types nor scala.Array. */ + val isPrimitive = + sym.isPrimitiveValueClass || sym == defn.ArrayClass + + if (!isPrimitive) { + withScopedVars( + currentClassSym := sym + ) { + val tree = if (isJSType(sym)) { + /*assert(!isRawJSFunctionDef(sym), + s"Raw JS function def should have been recorded: $cd")*/ + if (!sym.is(Trait) && isScalaJSDefinedJSClass(sym)) + genScalaJSDefinedJSClass(td) + else + genRawJSClassData(td) + } else if (sym.is(Trait)) { + genInterface(td) + } else { + genScalaClass(td) + } + + generatedClasses += ((sym, tree)) + } + } + } + + val clDefs = generatedClasses.map(_._2).toList + + for ((sym, tree) <- generatedClasses) { + val writer = new java.io.PrintWriter(System.err) + try { + new ir.Printers.IRTreePrinter(writer).print(tree) + } finally { + writer.flush() + } + genIRFile(cunit, sym, tree) + } + } + + private def genIRFile(cunit: CompilationUnit, sym: Symbol, + tree: ir.Trees.ClassDef): Unit = { + val outfile = getFileFor(cunit, sym, ".sjsir") + val output = outfile.bufferedOutput + try { + ir.InfoSerializers.serialize(output, ir.Infos.generateClassInfo(tree)) + ir.Serializers.serialize(output, tree) + } finally { + output.close() + } + } + + private def getFileFor(cunit: CompilationUnit, sym: Symbol, + suffix: String) = { + import scala.reflect.io._ + + val outputDirectory: AbstractFile = // TODO Support virtual files + new PlainDirectory(new Directory(new java.io.File(ctx.settings.d.value))) + + val pathParts = sym.fullName.toString.split("[./]") + val dir = (outputDirectory /: pathParts.init)(_.subdirectoryNamed(_)) + + var filename = pathParts.last + if (sym.is(ModuleClass)) + filename = filename + nme.MODULE_SUFFIX.toString + + dir fileNamed (filename + suffix) + } + + // Generate a class -------------------------------------------------------- + + /** Gen the IR ClassDef for a Scala class definition (maybe a module class). + */ + private def genScalaClass(td: TypeDef): js.ClassDef = { + val sym = td.symbol.asClass + implicit val pos: Position = sym.pos + + assert(!sym.is(Trait), + "genScalaClass() must be called only for normal classes: "+sym) + assert(sym.superClass != NoSymbol, sym) + + /*if (hasDefaultCtorArgsAndRawJSModule(sym)) { + reporter.error(pos, + "Implementation restriction: constructors of " + + "Scala classes cannot have default parameters " + + "if their companion module is JS native.") + }*/ + + val classIdent = encodeClassFullNameIdent(sym) + val isHijacked = false //isHijackedBoxedClass(sym) + + // Optimizer hints + + def isStdLibClassWithAdHocInlineAnnot(sym: Symbol): Boolean = { + val fullName = sym.fullName.toString + (fullName.startsWith("scala.Tuple") && !fullName.endsWith("$")) || + (fullName.startsWith("scala.collection.mutable.ArrayOps$of")) + } + + val shouldMarkInline = ( + sym.hasAnnotation(jsdefn.InlineAnnot) || + (sym.isAnonymousFunction && !sym.isSubClass(defn.PartialFunctionClass)) || + isStdLibClassWithAdHocInlineAnnot(sym)) + + val optimizerHints = { + OptimizerHints.empty + .withInline(shouldMarkInline) + .withNoinline(sym.hasAnnotation(jsdefn.NoinlineAnnot)) + } + + // Generate members (constructor + methods) + + val generatedMethods = new mutable.ListBuffer[js.MethodDef] + val exportedSymbols = new mutable.ListBuffer[Symbol] + + val tpl = td.rhs.asInstanceOf[Template] + for (tree <- tpl.constr :: tpl.body) { + tree match { + case EmptyTree => () + + case _: ValDef => + () // fields are added via genClassFields() + + case dd: DefDef => + val sym = dd.symbol + + val isExport = false //jsInterop.isExport(sym) + val isNamedExport = false /*isExport && sym.annotations.exists( + _.symbol == JSExportNamedAnnotation)*/ + + /*if (isNamedExport) + generatedMethods += genNamedExporterDef(dd) + else*/ + generatedMethods ++= genMethod(dd) + + if (isExport) { + // We add symbols that we have to export here. This way we also + // get inherited stuff that is implemented in this class. + exportedSymbols += sym + } + + case _ => + throw new FatalError("Illegal tree in body of genScalaClass(): " + tree) + } + } + + // Generate fields and add to methods + ctors + val generatedMembers = genClassFields(td) ++ generatedMethods.toList + + // Generate the exported members, constructors and accessors + val exports = { + // Hack to export hello.world + if (sym.fullName.toString == "hello.world$") { + List( + js.ModuleExportDef("hello.world"), + js.MethodDef(static = false, js.StringLiteral("main"), + Nil, jstpe.AnyType, + js.Block(List( + js.Apply(js.This()(jstpe.ClassType(classIdent.name)), js.Ident("main__V"), Nil)(jstpe.NoType), + js.Undefined())))( + OptimizerHints.empty, None)) + } else { + /* + // Generate the exported members + val memberExports = genMemberExports(sym, exportedSymbols.toList) + + // Generate exported constructors or accessors + val exportedConstructorsOrAccessors = + if (isStaticModule(sym)) genModuleAccessorExports(sym) + else genConstructorExports(sym) + + memberExports ++ exportedConstructorsOrAccessors + */ + Nil + } + } + + // Hashed definitions of the class + val hashedDefs = + ir.Hashers.hashDefs(generatedMembers ++ exports) + + // The complete class definition + val kind = + if (isStaticModule(sym)) ClassKind.ModuleClass + else if (isHijacked) ClassKind.HijackedClass + else ClassKind.Class + + val classDefinition = js.ClassDef( + classIdent, + kind, + Some(encodeClassFullNameIdent(sym.superClass)), + genClassInterfaces(sym), + None, + hashedDefs)( + optimizerHints) + + classDefinition + } + + /** Gen the IR ClassDef for a Scala.js-defined JS class. */ + private def genScalaJSDefinedJSClass(td: TypeDef): js.ClassDef = { + ??? + } + + /** Gen the IR ClassDef for a raw JS class or trait. + */ + private def genRawJSClassData(td: TypeDef): js.ClassDef = { + val sym = td.symbol.asClass + implicit val pos: Position = sym.pos + + val classIdent = encodeClassFullNameIdent(sym) + val superClass = + if (sym.is(Trait)) None + else Some(encodeClassFullNameIdent(sym.superClass)) + val jsName = + if (sym.is(Trait) || sym.is(ModuleClass)) None + else Some(fullJSNameOf(sym)) + + js.ClassDef(classIdent, ClassKind.RawJSType, + superClass, + genClassInterfaces(sym), + jsName, + Nil)( + OptimizerHints.empty) + } + + /** Gen the IR ClassDef for an interface definition. + */ + private def genInterface(td: TypeDef): js.ClassDef = { + val sym = td.symbol.asClass + implicit val pos: Position = sym.pos + + val classIdent = encodeClassFullNameIdent(sym) + + val generatedMethods = new mutable.ListBuffer[js.MethodDef] + + val tpl = td.rhs.asInstanceOf[Template] + for (tree <- tpl.constr :: tpl.body) { + tree match { + case EmptyTree => () + case dd: DefDef => generatedMethods ++= genMethod(dd) + case _ => + throw new FatalError("Illegal tree in gen of genInterface(): " + tree) + } + } + + val superInterfaces = genClassInterfaces(sym) + + // Hashed definitions of the interface + val hashedDefs = + ir.Hashers.hashDefs(generatedMethods.toList) + + js.ClassDef(classIdent, ClassKind.Interface, None, superInterfaces, None, + hashedDefs)(OptimizerHints.empty) + } + + private def genClassInterfaces(sym: ClassSymbol)( + implicit pos: Position): List[js.Ident] = { + import dotty.tools.dotc.transform.SymUtils._ + for { + intf <- sym.directlyInheritedTraits + } yield { + encodeClassFullNameIdent(intf) + } + } + + // Generate the fields of a class ------------------------------------------ + + /** Gen definitions for the fields of a class. + */ + private def genClassFields(td: TypeDef): List[js.FieldDef] = { + val classSym = td.symbol.asClass + assert(currentClassSym.get == classSym, + "genClassFields called with a ClassDef other than the current one") + + // Non-method term members are fields + (for { + f <- classSym.info.decls + if !f.is(Method) && f.isTerm + } yield { + implicit val pos: Position = f.pos + + val name = + /*if (isExposed(f)) js.StringLiteral(jsNameOf(f)) + else*/ encodeFieldSym(f) + + val irTpe = //if (!isScalaJSDefinedJSClass(classSym)) { + toIRType(f.info) + /*} else { + val tpeEnteringPosterasure = + enteringPhase(currentRun.posterasurePhase)(f.tpe) + tpeEnteringPosterasure match { + case tpe: ErasedValueType => + /* Here, we must store the field as the boxed representation of + * the value class. The default value of that field, as + * initialized at the time the instance is created, will + * therefore be null. This will not match the behavior we would + * get in a Scala class. To match the behavior, we would need to + * initialized to an instance of the boxed representation, with + * an underlying value set to the zero of its type. However we + * cannot implement that, so we live with the discrepancy. + * Anyway, scalac also has problems with uninitialized value + * class values, if they come from a generic context. + * + * TODO Evaluate how much of this needs to be adapted for dotc, + * which unboxes `null` to the zero of their underlying. + */ + jstpe.ClassType(encodeClassFullName(tpe.valueClazz)) + + case _ if f.tpe.typeSymbol == CharClass => + /* Will be initialized to null, which will unbox to '\0' when + * read. + */ + jstpe.ClassType(ir.Definitions.BoxedCharacterClass) + + case _ => + /* Other types are not boxed, so we can initialized them to + * their true zero. + */ + toIRType(f.tpe) + } + }*/ + + js.FieldDef(name, irTpe, f.is(Mutable)) + }).toList + } + + // Generate a method ------------------------------------------------------- + + private def genMethod(dd: DefDef): Option[js.MethodDef] = { + withScopedVars( + localNames := new LocalNameGenerator + ) { + genMethodWithCurrentLocalNameScope(dd) + } + } + + /** Gen JS code for a method definition in a class or in an impl class. + * On the JS side, method names are mangled to encode the full signature + * of the Scala method, as described in `JSEncoding`, to support + * overloading. + * + * Some methods are not emitted at all: + * - Primitives, since they are never actually called + * - Constructors of hijacked classes + * + * Constructors are emitted by generating their body as a statement. + * + * Other (normal) methods are emitted with `genMethodBody()`. + */ + private def genMethodWithCurrentLocalNameScope(dd: DefDef): Option[js.MethodDef] = { + implicit val pos: Position = dd.pos + val sym = dd.symbol + val vparamss = dd.vparamss + val rhs = dd.rhs + + isModuleInitialized = false + + withScopedVars( + currentMethodSym := sym, + undefinedDefaultParams := mutable.Set.empty, + thisLocalVarIdent := None + ) { + assert(vparamss.isEmpty || vparamss.tail.isEmpty, + "Malformed parameter list: " + vparamss) + val params = if (vparamss.isEmpty) Nil else vparamss.head.map(_.symbol) + + val isJSClassConstructor = + sym.isClassConstructor && isScalaJSDefinedJSClass(currentClassSym) + + val methodName: js.PropertyName = encodeMethodSym(sym) + + def jsParams = for (param <- params) yield { + implicit val pos: Position = param.pos + js.ParamDef(encodeLocalSym(param), toIRType(param.info), + mutable = false, rest = false) + } + + /*if (primitives.isPrimitive(sym)) { + None + } else*/ if (sym.is(Deferred)) { + Some(js.MethodDef(static = false, methodName, + jsParams, toIRType(patchedResultType(sym)), js.EmptyTree)( + OptimizerHints.empty, None)) + } else /*if (isJSNativeCtorDefaultParam(sym)) { + None + } else if (sym.isClassConstructor && isHijackedBoxedClass(sym.owner)) { + None + } else*/ { + /*def isTraitImplForwarder = dd.rhs match { + case app: Apply => foreignIsImplClass(app.symbol.owner) + case _ => false + }*/ + + val shouldMarkInline = { + sym.hasAnnotation(jsdefn.InlineAnnot) || + sym.isAnonymousFunction + } + + val shouldMarkNoinline = { + sym.hasAnnotation(jsdefn.NoinlineAnnot) /*&& + !isTraitImplForwarder*/ + } + + val optimizerHints = { + OptimizerHints.empty + .withInline(shouldMarkInline) + .withNoinline(shouldMarkNoinline) + } + + val methodDef = { + /*if (isJSClassConstructor) { + val body0 = genStat(rhs) + val body1 = + if (!sym.isPrimaryConstructor) body0 + else moveAllStatementsAfterSuperConstructorCall(body0) + js.MethodDef(static = false, methodName, + jsParams, jstpe.NoType, body1)(optimizerHints, None) + } else*/ if (sym.isConstructor) { + js.MethodDef(static = false, methodName, + jsParams, jstpe.NoType, + genStat(rhs))(optimizerHints, None) + } else { + val resultIRType = toIRType(patchedResultType(sym)) + genMethodDef(static = false, methodName, + params, resultIRType, rhs, optimizerHints) + } + } + + Some(methodDef) + } + } + } + + /** Generates the MethodDef of a (non-constructor) method + * + * Most normal methods are emitted straightforwardly. If the result + * type is Unit, then the body is emitted as a statement. Otherwise, it is + * emitted as an expression. + * + * Methods Scala.js-defined JS classes are compiled as static methods taking + * an explicit parameter for their `this` value. + */ + private def genMethodDef(static: Boolean, methodName: js.PropertyName, + paramsSyms: List[Symbol], resultIRType: jstpe.Type, + tree: Tree, optimizerHints: OptimizerHints): js.MethodDef = { + implicit val pos: Position = tree.pos + + ctx.debuglog("genMethod " + methodName.name) + ctx.debuglog("") + + val jsParams = for (param <- paramsSyms) yield { + implicit val pos: Position = param.pos + js.ParamDef(encodeLocalSym(param), toIRType(param.info), + mutable = false, rest = false) + } + + def genBody() = + if (resultIRType == jstpe.NoType) genStat(tree) + else genExpr(tree) + + //if (!isScalaJSDefinedJSClass(currentClassSym)) { + js.MethodDef(static, methodName, jsParams, resultIRType, genBody())( + optimizerHints, None) + /*} else { + assert(!static, tree.pos) + + withScopedVars( + thisLocalVarIdent := Some(freshLocalIdent("this")) + ) { + val thisParamDef = js.ParamDef(thisLocalVarIdent.get.get, + jstpe.AnyType, mutable = false, rest = false) + + js.MethodDef(static = true, methodName, thisParamDef :: jsParams, + resultIRType, genBody())( + optimizerHints, None) + } + }*/ + } + + // Generate statements and expressions ------------------------------------- + + /** Gen JS code for a tree in statement position (in the IR). + */ + private def genStat(tree: Tree): js.Tree = { + exprToStat(genStatOrExpr(tree, isStat = true)) + } + + /** Turn a JavaScript expression of type Unit into a statement */ + private def exprToStat(tree: js.Tree): js.Tree = { + /* Any JavaScript expression is also a statement, but at least we get rid + * of some pure expressions that come from our own codegen. + */ + implicit val pos: Position = tree.pos + tree match { + case js.Block(stats :+ expr) => js.Block(stats :+ exprToStat(expr)) + case _:js.Literal | js.This() => js.Skip() + case _ => tree + } + } + + /** Gen JS code for a tree in expression position (in the IR). + */ + private def genExpr(tree: Tree): js.Tree = { + val result = genStatOrExpr(tree, isStat = false) + assert(result.tpe != jstpe.NoType, + s"genExpr($tree) returned a tree with type NoType at pos ${tree.pos}") + result + } + + /** Gen JS code for a tree in statement or expression position (in the IR). + * + * This is the main transformation method. Each node of the Scala AST + * is transformed into an equivalent portion of the JS AST. + */ + private def genStatOrExpr(tree: Tree, isStat: Boolean): js.Tree = { + implicit val pos: Position = tree.pos + + ctx.debuglog(" " + tree) + ctx.debuglog("") + + tree match { + /** LabelDefs (for while and do..while loops) */ + /*case lblDf: LabelDef => + genLabelDef(lblDf)*/ + + /** Local val or var declaration */ + case tree @ ValDef(name, _, _) => + /* Must have been eliminated by the tail call transform performed + * by genMethodBody(). */ + assert(name != nme.THIS, + s"ValDef(_, nme.THIS, _, _) found at ${tree.pos}") + + val sym = tree.symbol + val rhs = tree.rhs + val rhsTree = genExpr(rhs) + + rhsTree match { + case js.UndefinedParam() => + /* This is an intermediate assignment for default params on a + * js.Any. Add the symbol to the corresponding set to inform + * the Ident resolver how to replace it and don't emit the symbol. + */ + undefinedDefaultParams += sym + js.Skip() + case _ => + js.VarDef(encodeLocalSym(sym), + toIRType(sym.info), sym.is(Mutable), rhsTree) + } + + case If(cond, thenp, elsep) => + js.If(genExpr(cond), genStatOrExpr(thenp, isStat), + genStatOrExpr(elsep, isStat))(toIRType(tree.tpe)) + + case Return(expr, from) => + // TODO Need to consider `from`? + js.Return(toIRType(expr.tpe) match { + case jstpe.NoType => js.Block(genStat(expr), js.Undefined()) + case _ => genExpr(expr) + }) + + /*case t: Try => + genTry(t, isStat)*/ + + case app: Apply => + genApply(app, isStat) + + case app: TypeApply => + genTypeApply(app) + + /*case app: ApplyDynamic => + genApplyDynamic(app)*/ + + case tree: This => + if (tree.symbol == currentClassSym.get) { + genThis() + } else { + assert(tree.symbol.is(Module), + "Trying to access the this of another class: " + + "tree.symbol = " + tree.symbol + + ", class symbol = " + currentClassSym.get + + " pos:" + pos) + genLoadModule(tree.symbol) + } + + case Select(qualifier, _) => + val sym = tree.symbol + if (sym.is(Module)) { + assert(!sym.is(Package), "Cannot use package as value: " + tree) + genLoadModule(sym) + } else if (sym.is(JavaStatic)) { + genLoadStaticField(sym) + } else /*if (paramAccessorLocals contains sym) { + paramAccessorLocals(sym).ref + } else if (isScalaJSDefinedJSClass(sym.owner)) { + val genQual = genExpr(qualifier) + val boxed = if (isExposed(sym)) + js.JSBracketSelect(genQual, js.StringLiteral(jsNameOf(sym))) + else + js.JSDotSelect(genQual, encodeFieldSym(sym)) + fromAny(boxed, + enteringPhase(currentRun.posterasurePhase)(sym.tpe)) + } else*/ { + js.Select(genExpr(qualifier), + encodeFieldSym(sym))(toIRType(sym.info)) + } + + case tree: Ident => + desugarIdent(tree).fold[js.Tree] { + val sym = tree.symbol + assert(!sym.is(Package), "Cannot use package as value: " + tree) + if (sym.is(Module)) { + genLoadModule(sym) + } else if (undefinedDefaultParams.contains(sym)) { + /* This is a default parameter whose assignment was moved to + * a local variable. Put an undefined param instead. + */ + js.UndefinedParam()(toIRType(sym.info)) + } else { + js.VarRef(encodeLocalSym(sym))(toIRType(sym.info)) + } + } { select => + genStatOrExpr(select, isStat) + } + + case Literal(value) => + import Constants._ + value.tag match { + case UnitTag => + js.Skip() + case BooleanTag => + js.BooleanLiteral(value.booleanValue) + case ByteTag | ShortTag | CharTag | IntTag => + js.IntLiteral(value.intValue) + case LongTag => + js.LongLiteral(value.longValue) + case FloatTag => + js.FloatLiteral(value.floatValue) + case DoubleTag => + js.DoubleLiteral(value.doubleValue) + case StringTag => + js.StringLiteral(value.stringValue) + case NullTag => + js.Null() + case ClazzTag => + genClassConstant(value.typeValue) + /*case EnumTag => + genStaticMember(value.symbolValue)*/ + } + + case Block(stats, expr) => + js.Block(stats.map(genStat) :+ genStatOrExpr(expr, isStat)) + + case Typed(expr, _) => + expr match { + case _: Super => genThis() + case _ => genExpr(expr) + } + + case Assign(lhs0, rhs) => + val sym = lhs0.symbol + if (sym.is(JavaStaticTerm)) + throw new FatalError(s"Assignment to static member ${sym.fullName} not supported") + val genRhs = genExpr(rhs) + val lhs = lhs0 match { + case lhs: Ident => desugarIdent(lhs).getOrElse(lhs) + case lhs => lhs + } + lhs match { + case lhs: Select => + val qualifier = lhs.qualifier + + def ctorAssignment = ( + currentMethodSym.get.name == nme.CONSTRUCTOR && + currentMethodSym.get.owner == qualifier.symbol && + qualifier.isInstanceOf[This] + ) + if (!sym.is(Mutable) && !ctorAssignment) + throw new FatalError(s"Assigning to immutable field ${sym.fullName} at $pos") + + val genQual = genExpr(qualifier) + + /*if (isScalaJSDefinedJSClass(sym.owner)) { + val genLhs = if (isExposed(sym)) + js.JSBracketSelect(genQual, js.StringLiteral(jsNameOf(sym))) + else + js.JSDotSelect(genQual, encodeFieldSym(sym)) + val boxedRhs = + ensureBoxed(genRhs, + enteringPhase(currentRun.posterasurePhase)(rhs.tpe)) + js.Assign(genLhs, boxedRhs) + } else {*/ + js.Assign( + js.Select(genQual, encodeFieldSym(sym))(toIRType(sym.info)), + genRhs) + //} + case _ => + js.Assign( + js.VarRef(encodeLocalSym(sym))(toIRType(sym.info)), + genRhs) + } + + /** Array constructor */ + case javaSeqLiteral: JavaSeqLiteral => + genJavaSeqLiteral(javaSeqLiteral) + + /** A Match reaching the backend is supposed to be optimized as a switch */ + /*case mtch: Match => + genMatch(mtch, isStat)*/ + + case tree: Closure => + genClosure(tree) + + /*case EmptyTree => + js.Skip()*/ + + case _ => + throw new FatalError("Unexpected tree in genExpr: " + + tree + "/" + tree.getClass + " at: " + (tree.pos: Position)) + } + } // end of genStatOrExpr() + + // !!! DUPLICATE code with DottyBackendInterface + private def desugarIdent(i: Ident): Option[Select] = { + i.tpe match { + case TermRef(prefix: TermRef, name) => + Some(tpd.ref(prefix).select(i.symbol)) + case TermRef(prefix: ThisType, name) => + Some(tpd.This(prefix.cls).select(i.symbol)) + /*case TermRef(NoPrefix, name) => + if (i.symbol is Method) Some(This(i.symbol.topLevelClass).select(i.symbol)) // workaround #342 todo: remove after fixed + else None*/ + case _ => + None + } + } + + private def qualifierOf(fun: Tree): Tree = fun match { + case fun: Ident => + fun.tpe match { + case TermRef(prefix: TermRef, _) => tpd.ref(prefix) + case TermRef(prefix: ThisType, _) => tpd.This(prefix.cls) + } + case Select(qualifier, _) => + qualifier + case TypeApply(fun, _) => + qualifierOf(fun) + } + + /** Gen JS this of the current class. + * Normally encoded straightforwardly as a JS this. + * But must be replaced by the `thisLocalVarIdent` local variable if there + * is one. + */ + private def genThis()(implicit pos: Position): js.Tree = { + /*if (tryingToGenMethodAsJSFunction) { + throw new CancelGenMethodAsJSFunction( + "Trying to generate `this` inside the body") + }*/ + + thisLocalVarIdent.fold[js.Tree] { + js.This()(currentClassType) + } { thisLocalIdent => + js.VarRef(thisLocalIdent)(currentClassType) + } + } + + /** Gen JS code for an Apply node (method call) + * + * There's a whole bunch of varieties of Apply nodes: regular method + * calls, super calls, constructor calls, isInstanceOf/asInstanceOf, + * primitives, JS calls, etc. They are further dispatched in here. + */ + private def genApply(tree: Apply, isStat: Boolean): js.Tree = { + implicit val pos: Position = tree.pos + val args = tree.args + val sym = tree.fun.symbol + + val fun = tree.fun match { + case fun: Ident => desugarIdent(fun).getOrElse(fun) + case fun => fun + } + + fun match { + case _ if isJSDefaultParam(sym) => + js.UndefinedParam()(toIRType(sym.info.finalResultType)) + + case Select(Super(_, _), _) => + genSuperCall(tree, isStat) + + case Select(New(_), nme.CONSTRUCTOR) => + genApplyNew(tree) + + case _ => + /*if (sym.isLabel) { + genLabelApply(tree) + } else*/ if (primitives.isPrimitive(tree)) { + genPrimitiveOp(tree, isStat) + } else if (Erasure.Boxing.isBox(sym)) { + // Box a primitive value (cannot be Unit) + val arg = args.head + makePrimitiveBox(genExpr(arg), arg.tpe) + } else if (Erasure.Boxing.isUnbox(sym)) { + // Unbox a primitive value (cannot be Unit) + val arg = args.head + makePrimitiveUnbox(genExpr(arg), tree.tpe) + } else { + genNormalApply(tree, isStat) + } + } + } + + /** Gen JS code for a super call, of the form Class.super[mix].fun(args). + * + * This does not include calls defined in mixin traits, as these are + * already desugared by the 'mixin' phase. Only calls to super classes + * remain. + * + * Since a class has exactly one direct superclass, and calling a method + * two classes above the current one is invalid in Scala, the `mix` item is + * irrelevant. + */ + private def genSuperCall(tree: Apply, isStat: Boolean): js.Tree = { + implicit val pos: Position = tree.pos + val Apply(fun @ Select(sup @ Super(_, mix), _), args) = tree + val sym = fun.symbol + + if (sym == defn.Any_getClass) { + // The only primitive that is also callable as super call + js.GetClass(genThis()) + } else /*if (isScalaJSDefinedJSClass(currentClassSym)) { + genJSSuperCall(tree, isStat) + } else*/ { + val superCall = genApplyMethodStatically( + genThis()(sup.pos), sym, genActualArgs(sym, args)) + + // Initialize the module instance just after the super constructor call. + if (isStaticModule(currentClassSym) && !isModuleInitialized && + currentMethodSym.get.isClassConstructor) { + isModuleInitialized = true + val thisType = jstpe.ClassType(encodeClassFullName(currentClassSym)) + val initModule = js.StoreModule(thisType, js.This()(thisType)) + js.Block(superCall, initModule) + } else { + superCall + } + } + } + + /** Gen JS code for a constructor call (new). + * Further refined into: + * * new String(...) + * * new of a hijacked boxed class + * * new of an anonymous function class that was recorded as JS function + * * new of a raw JS class + * * new Array + * * regular new + */ + private def genApplyNew(tree: Apply): js.Tree = { + implicit val pos: Position = tree.pos + + val Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) = tree + val ctor = fun.symbol + val tpe = tpt.tpe + + assert(ctor.isClassConstructor, + "'new' call to non-constructor: " + ctor.name) + + if (tpe.isRef(defn.StringClass)) { + genNewString(ctor, genActualArgs(ctor, args)) + } else /*if (isHijackedBoxedClass(tpe.typeSymbol)) { + genNewHijackedBoxedClass(tpe.typeSymbol, ctor, args map genExpr) + } else if (translatedAnonFunctions contains tpe.typeSymbol) { + val functionMaker = translatedAnonFunctions(tpe.typeSymbol) + functionMaker(args map genExpr) + } else*/ if (isJSType(tpe.widenDealias.typeSymbol)) { + val clsSym = tpe.widenDealias.typeSymbol + if (clsSym == jsdefn.JSObjectClass && args.isEmpty) js.JSObjectConstr(Nil) + else if (clsSym == jsdefn.JSArrayClass && args.isEmpty) js.JSArrayConstr(Nil) + else js.JSNew(genLoadJSConstructor(clsSym), genActualJSArgs(ctor, args)) + } else { + toIRType(tpe) match { + case cls: jstpe.ClassType => + js.New(cls, encodeMethodSym(ctor), genActualArgs(ctor, args)) + + case other => + throw new FatalError(s"Non ClassType cannot be instantiated: $other") + } + } + } + + /** Gen JS code for a primitive method call. */ + private def genPrimitiveOp(tree: Apply, isStat: Boolean): js.Tree = { + import scala.tools.nsc.backend.ScalaPrimitives._ + + implicit val pos: Position = tree.pos + + val Apply(fun, args) = tree + val receiver = qualifierOf(fun) + + val code = primitives.getPrimitive(tree, receiver.tpe) + + if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code)) + genSimpleOp(tree, receiver :: args, code) + else if (code == CONCAT) + genStringConcat(tree, receiver, args) + else if (code == HASH) + genScalaHash(tree, receiver) + else if (isArrayOp(code)) + genArrayOp(tree, code) + else if (code == SYNCHRONIZED) + genSynchronized(tree, isStat) + else if (isCoercion(code)) + genCoercion(tree, receiver, code) + else if (code == JSPrimitives.THROW) + genThrow(tree, args) + else /*if (primitives.isJSPrimitive(code)) + genJSPrimitive(tree, receiver, args, code) + else*/ + throw new FatalError(s"Unknown primitive: ${tree.symbol.fullName} at: $pos") + } + + /** Gen JS code for a simple operation (arithmetic, logical, or comparison) */ + private def genSimpleOp(tree: Apply, args: List[Tree], code: Int): js.Tree = { + args match { + case List(arg) => genSimpleUnaryOp(tree, arg, code) + case List(lhs, rhs) => genSimpleBinaryOp(tree, lhs, rhs, code) + case _ => throw new FatalError("Incorrect arity for primitive") + } + } + + /** Gen JS code for a simple unary operation. */ + private def genSimpleUnaryOp(tree: Apply, arg: Tree, code: Int): js.Tree = { + import scala.tools.nsc.backend.ScalaPrimitives._ + + implicit val pos: Position = tree.pos + + val genArg = genExpr(arg) + val resultIRType = toIRType(tree.tpe) + + (code: @switch) match { + case POS => + genArg + + case NEG => + (resultIRType: @unchecked) match { + case jstpe.IntType => + js.BinaryOp(js.BinaryOp.Int_-, js.IntLiteral(0), genArg) + case jstpe.LongType => + js.BinaryOp(js.BinaryOp.Long_-, js.LongLiteral(0), genArg) + case jstpe.FloatType => + js.BinaryOp(js.BinaryOp.Float_-, js.FloatLiteral(0.0f), genArg) + case jstpe.DoubleType => + js.BinaryOp(js.BinaryOp.Double_-, js.DoubleLiteral(0), genArg) + } + + case NOT => + (resultIRType: @unchecked) match { + case jstpe.IntType => + js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(-1), genArg) + case jstpe.LongType => + js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(-1), genArg) + } + + case ZNOT => + js.UnaryOp(js.UnaryOp.Boolean_!, genArg) + + case _ => + throw new FatalError("Unknown unary operation code: " + code) + } + } + + /** Gen JS code for a simple binary operation. */ + private def genSimpleBinaryOp(tree: Apply, lhs: Tree, rhs: Tree, code: Int): js.Tree = { + import scala.tools.nsc.backend.ScalaPrimitives._ + import js.UnaryOp._ + + /* Codes for operation types, in an object so that they can be 'final val' + * and be used in switch-matches. + */ + object OpTypes { + final val DoubleOp = 1 + final val FloatOp = 2 + final val LongOp = 3 + final val IntOp = 4 + final val BooleanOp = 5 + final val AnyOp = 6 + } + import OpTypes._ + + implicit val pos: Position = tree.pos + + val lhsIRType = toIRType(lhs.tpe) + val rhsIRType = toIRType(rhs.tpe) + + val opType = (lhsIRType, rhsIRType) match { + case (jstpe.DoubleType, _) | (_, jstpe.DoubleType) => DoubleOp + case (jstpe.FloatType, _) | (_, jstpe.FloatType) => FloatOp + case (jstpe.LongType, _) | (_, jstpe.LongType) => LongOp + case (jstpe.IntType, _) | (_, jstpe.IntType) => IntOp + case (jstpe.BooleanType, jstpe.BooleanType) => BooleanOp + case _ => AnyOp + } + + if (opType == AnyOp && isUniversalEqualityOp(code)) { + genUniversalEqualityOp(lhs, rhs, code) + } else if (code == ZOR) { + js.If(genExpr(lhs), js.BooleanLiteral(true), genExpr(rhs))(jstpe.BooleanType) + } else if (code == ZAND) { + js.If(genExpr(lhs), genExpr(rhs), js.BooleanLiteral(false))(jstpe.BooleanType) + } else { + import js.BinaryOp._ + + def coerce(tree: js.Tree, opType: Int): js.Tree = (opType: @switch) match { + case DoubleOp => + if (tree.tpe == jstpe.LongType) js.UnaryOp(LongToDouble, tree) + else tree + + case FloatOp => + if (tree.tpe == jstpe.FloatType || tree.tpe == jstpe.IntType) tree + else js.UnaryOp(DoubleToFloat, coerce(tree, DoubleOp)) + + case LongOp => + if (tree.tpe == jstpe.LongType) tree + else { + assert(tree.tpe == jstpe.IntType) + js.UnaryOp(IntToLong, tree) + } + + case IntOp => + if (tree.tpe == jstpe.IntType) tree + else { + assert(tree.tpe == jstpe.LongType) + js.UnaryOp(LongToInt, tree) + } + + case BooleanOp | AnyOp => + tree + } + + val rhsOpType = code match { + case LSL | LSR | ASR => IntOp + case _ => opType + } + + val genLhs = coerce(genExpr(lhs), opType) + val genRhs = coerce(genExpr(rhs), rhsOpType) + + val op = (opType: @switch) match { + case IntOp => + (code: @switch) match { + case ADD => Int_+ + case SUB => Int_- + case MUL => Int_* + case DIV => Int_/ + case MOD => Int_% + case OR => Int_| + case AND => Int_& + case XOR => Int_^ + case LSL => Int_<< + case LSR => Int_>>> + case ASR => Int_>> + + case EQ => Num_== + case NE => Num_!= + case LT => Num_< + case LE => Num_<= + case GT => Num_> + case GE => Num_>= + } + + case FloatOp => + (code: @switch) match { + case ADD => Float_+ + case SUB => Float_- + case MUL => Float_* + case DIV => Float_/ + case MOD => Float_% + + case EQ => Num_== + case NE => Num_!= + case LT => Num_< + case LE => Num_<= + case GT => Num_> + case GE => Num_>= + } + + case DoubleOp => + (code: @switch) match { + case ADD => Double_+ + case SUB => Double_- + case MUL => Double_* + case DIV => Double_/ + case MOD => Double_% + + case EQ => Num_== + case NE => Num_!= + case LT => Num_< + case LE => Num_<= + case GT => Num_> + case GE => Num_>= + } + + case LongOp => + (code: @switch) match { + case ADD => Long_+ + case SUB => Long_- + case MUL => Long_* + case DIV => Long_/ + case MOD => Long_% + case OR => Long_| + case XOR => Long_^ + case AND => Long_& + case LSL => Long_<< + case LSR => Long_>>> + case ASR => Long_>> + + case EQ => Long_== + case NE => Long_!= + case LT => Long_< + case LE => Long_<= + case GT => Long_> + case GE => Long_>= + } + + case BooleanOp => + (code: @switch) match { + case EQ => Boolean_== + case NE => Boolean_!= + case OR => Boolean_| + case AND => Boolean_& + case XOR => Boolean_!= + } + + case AnyOp => + /* No @switch because some 2.11 version erroneously report a warning + * for switches with less than 3 non-default cases. + */ + code match { + case ID => === + case NI => !== + } + } + + js.BinaryOp(op, genLhs, genRhs) + } + } + + /** Gen JS code for a universal equality test. */ + private def genUniversalEqualityOp(lhs: Tree, rhs: Tree, code: Int)( + implicit pos: Position): js.Tree = { + + import scala.tools.nsc.backend.ScalaPrimitives._ + + val genLhs = genExpr(lhs) + val genRhs = genExpr(rhs) + + val bypassEqEq = { + // Do not call equals if we have a literal null at either side. + genLhs.isInstanceOf[js.Null] || + genRhs.isInstanceOf[js.Null] + } + + if (bypassEqEq) { + js.BinaryOp( + if (code == EQ) js.BinaryOp.=== else js.BinaryOp.!==, + genLhs, genRhs) + } else { + val body = genEqEqPrimitive(lhs.tpe, rhs.tpe, genLhs, genRhs) + if (code == EQ) body + else js.UnaryOp(js.UnaryOp.Boolean_!, body) + } + } + + private lazy val externalEqualsNumNum: Symbol = + defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumNum) + private lazy val externalEqualsNumChar: Symbol = + NoSymbol // ctx.requiredMethod(BoxesRunTimeTypeRef, nme.equalsNumChar) // this method is private + private lazy val externalEqualsNumObject: Symbol = + defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumObject) + private lazy val externalEquals: Symbol = + defn.BoxesRunTimeClass.info.decl(nme.equals_).suchThat(toDenot(_).info.firstParamTypes.size == 2).symbol + + /** Gen JS code for a call to Any.== */ + private def genEqEqPrimitive(ltpe: Type, rtpe: Type, lsrc: js.Tree, rsrc: js.Tree)( + implicit pos: Position): js.Tree = { + ctx.debuglog(s"$ltpe == $rtpe") + val lsym = ltpe.widenDealias.typeSymbol.asClass + val rsym = rtpe.widenDealias.typeSymbol.asClass + + /* True if the equality comparison is between values that require the + * use of the rich equality comparator + * (scala.runtime.BoxesRunTime.equals). + * This is the case when either side of the comparison might have a + * run-time type subtype of java.lang.Number or java.lang.Character, + * **which includes when either is a JS type**. + * When it is statically known that both sides are equal and subtypes of + * Number or Character, not using the rich equality is possible (their + * own equals method will do ok.) + */ + val mustUseAnyComparator: Boolean = { + isJSType(lsym) || isJSType(rsym) || { + val p = ctx.platform + val areSameFinals = lsym.is(Final) && rsym.is(Final) && (ltpe =:= rtpe) + !areSameFinals && p.isMaybeBoxed(lsym) && p.isMaybeBoxed(rsym) + } + } + + if (mustUseAnyComparator) { + val equalsMethod: Symbol = { + // scalastyle:off line.size.limit + val ptfm = ctx.platform + if (lsym.derivesFrom(defn.BoxedNumberClass)) { + if (rsym.derivesFrom(defn.BoxedNumberClass)) externalEqualsNumNum + else if (rsym.derivesFrom(defn.BoxedCharClass)) externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030 + else externalEqualsNumObject + } else externalEquals + // scalastyle:on line.size.limit + } + genModuleApplyMethod(equalsMethod, List(lsrc, rsrc)) + } else { + // if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc) + if (lsym == defn.StringClass) { + // String.equals(that) === (this eq that) + js.BinaryOp(js.BinaryOp.===, lsrc, rsrc) + } else { + /* This requires to evaluate both operands in local values first. + * The optimizer will eliminate them if possible. + */ + val ltemp = js.VarDef(freshLocalIdent(), lsrc.tpe, mutable = false, lsrc) + val rtemp = js.VarDef(freshLocalIdent(), rsrc.tpe, mutable = false, rsrc) + js.Block( + ltemp, + rtemp, + js.If(js.BinaryOp(js.BinaryOp.===, ltemp.ref, js.Null()), + js.BinaryOp(js.BinaryOp.===, rtemp.ref, js.Null()), + genApplyMethod(ltemp.ref, defn.Any_equals, List(rtemp.ref)))( + jstpe.BooleanType)) + } + } + } + + /** Gen JS code for string concatenation. + */ + private def genStringConcat(tree: Apply, receiver: Tree, + args: List[Tree]): js.Tree = { + implicit val pos: Position = tree.pos + + val arg = args.head + + /* Primitive number types such as scala.Int have a + * def +(s: String): String + * method, which is why we have to box the lhs sometimes. + * Otherwise, both lhs and rhs are already reference types (Any or String) + * so boxing is not necessary (in particular, rhs is never a primitive). + */ + assert(!isPrimitiveValueType(receiver.tpe) || arg.tpe.isRef(defn.StringClass)) + assert(!isPrimitiveValueType(arg.tpe)) + + val genLhs = { + val genLhs0 = genExpr(receiver) + // Box the receiver if it is a primitive value + if (!isPrimitiveValueType(receiver.tpe)) genLhs0 + else makePrimitiveBox(genLhs0, receiver.tpe) + } + + val genRhs = genExpr(arg) + + js.BinaryOp(js.BinaryOp.String_+, genLhs, genRhs) + } + + /** Gen JS code for a call to Any.## */ + private def genScalaHash(tree: Apply, receiver: Tree): js.Tree = { + implicit val pos: Position = tree.pos + + genModuleApplyMethod(defn.ScalaRuntimeModule.requiredMethod(nme.hash_), + List(genExpr(receiver))) + } + + /** Gen JS code for an array operation (get, set or length) */ + private def genArrayOp(tree: Tree, code: Int): js.Tree = { + import scala.tools.nsc.backend.ScalaPrimitives._ + + implicit val pos: Position = tree.pos + + val Apply(fun, args) = tree + val arrayObj = qualifierOf(fun) + + val genArray = genExpr(arrayObj) + val genArgs = args.map(genExpr) + + def elementType: Type = arrayObj.tpe.widenDealias match { + case defn.ArrayOf(el) => el + case JavaArrayType(el) => el + case tpe => + ctx.error(s"expected Array $tpe") + ErrorType + } + + def genSelect(): js.Tree = + js.ArraySelect(genArray, genArgs(0))(toIRType(elementType)) + + if (isArrayGet(code)) { + // get an item of the array + assert(args.length == 1, + s"Array get requires 1 argument, found ${args.length} in $tree") + genSelect() + } else if (isArraySet(code)) { + // set an item of the array + assert(args.length == 2, + s"Array set requires 2 arguments, found ${args.length} in $tree") + js.Assign(genSelect(), genArgs(1)) + } else { + // length of the array + js.ArrayLength(genArray) + } + } + + /** Gen JS code for a call to AnyRef.synchronized */ + private def genSynchronized(tree: Apply, isStat: Boolean): js.Tree = { + /* JavaScript is single-threaded, so we can drop the + * synchronization altogether. + */ + val Apply(fun, List(arg)) = tree + val receiver = qualifierOf(fun) + + val genReceiver = genExpr(receiver) + val genArg = genStatOrExpr(arg, isStat) + + genReceiver match { + case js.This() => + // common case for which there is no side-effect nor NPE + genArg + case _ => + implicit val pos: Position = tree.pos + /* TODO Check for a null receiver? + * In theory, it's UB, but that decision should be left for link time. + */ + js.Block(genReceiver, genArg) + } + } + + /** Gen JS code for a coercion */ + private def genCoercion(tree: Apply, receiver: Tree, code: Int): js.Tree = { + import scala.tools.nsc.backend.ScalaPrimitives._ + + implicit val pos: Position = tree.pos + + val source = genExpr(receiver) + + def source2int = (code: @switch) match { + case F2C | D2C | F2B | D2B | F2S | D2S | F2I | D2I => + js.UnaryOp(js.UnaryOp.DoubleToInt, source) + case L2C | L2B | L2S | L2I => + js.UnaryOp(js.UnaryOp.LongToInt, source) + case _ => + source + } + + (code: @switch) match { + // To Char, need to crop at unsigned 16-bit + case B2C | S2C | I2C | L2C | F2C | D2C => + js.BinaryOp(js.BinaryOp.Int_&, source2int, js.IntLiteral(0xffff)) + + // To Byte, need to crop at signed 8-bit + case C2B | S2B | I2B | L2B | F2B | D2B => + // note: & 0xff would not work because of negative values + js.BinaryOp(js.BinaryOp.Int_>>, + js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(24)), + js.IntLiteral(24)) + + // To Short, need to crop at signed 16-bit + case C2S | I2S | L2S | F2S | D2S => + // note: & 0xffff would not work because of negative values + js.BinaryOp(js.BinaryOp.Int_>>, + js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(16)), + js.IntLiteral(16)) + + // To Int, need to crop at signed 32-bit + case L2I | F2I | D2I => + source2int + + // Any int to Long + case C2L | B2L | S2L | I2L => + js.UnaryOp(js.UnaryOp.IntToLong, source) + + // Any double to Long + case F2L | D2L => + js.UnaryOp(js.UnaryOp.DoubleToLong, source) + + // Long to Double + case L2D => + js.UnaryOp(js.UnaryOp.LongToDouble, source) + + // Any int, or Double, to Float + case C2F | B2F | S2F | I2F | D2F => + js.UnaryOp(js.UnaryOp.DoubleToFloat, source) + + // Long to Float === Long to Double to Float + case L2F => + js.UnaryOp(js.UnaryOp.DoubleToFloat, + js.UnaryOp(js.UnaryOp.LongToDouble, source)) + + // Identities and IR upcasts + case C2C | B2B | S2S | I2I | L2L | F2F | D2D | + C2I | C2D | + B2S | B2I | B2D | + S2I | S2D | + I2D | + F2D => + source + } + } + + /** Gen a call to the special `throw` method. */ + private def genThrow(tree: Apply, args: List[Tree]): js.Tree = { + implicit val pos: Position = tree.pos + val exception = args.head + val genException = genExpr(exception) + js.Throw { + if (exception.tpe.widenDealias.typeSymbol.derivesFrom(jsdefn.JavaScriptExceptionClass)) { + genModuleApplyMethod( + jsdefn.RuntimePackage_unwrapJavaScriptException, + List(genException)) + } else { + genException + } + } + } + + /** Gen a "normal" apply (to a true method). + * + * But even these are further refined into: + * * Methods of java.lang.String, which are redirected to the + * RuntimeString trait implementation. + * * Calls to methods of raw JS types (Scala.js -> JS interop) + * * Calls to methods in impl classes of Scala2 traits. + * * Regular method call + */ + private def genNormalApply(tree: Apply, isStat: Boolean): js.Tree = { + implicit val pos: Position = tree.pos + + val fun = tree.fun match { + case fun: Ident => desugarIdent(fun).get + case fun: Select => fun + } + val receiver = fun.qualifier + val args = tree.args + val sym = fun.symbol + + def isStringMethodFromObject: Boolean = sym.name match { + case nme.toString_ | nme.equals_ | nme.hashCode_ => true + case _ => false + } + + if (sym.owner == defn.StringClass && !isStringMethodFromObject) { + genApplyMethodOfString(genExpr(receiver), sym, genActualArgs(sym, args)) + } else if (isJSType(sym.owner)) { + //if (!isScalaJSDefinedJSClass(sym.owner) || isExposed(sym)) + genApplyJSMethodGeneric(tree, sym, genExpr(receiver), genActualJSArgs(sym, args), isStat) + /*else + genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args))*/ + } else if (foreignIsImplClass(sym.owner)) { + genTraitImplApply(sym, args.map(genExpr)) + } else if (sym.isClassConstructor) { + // Calls to constructors are always statically linked + genApplyMethodStatically(genExpr(receiver), sym, genActualArgs(sym, args)) + } else { + genApplyMethod(genExpr(receiver), sym, genActualArgs(sym, args)) + } + } + + /** Gen JS code for a call to a JS method (of a subclass of `js.Any`). + * + * Basically it boils down to calling the method as a `JSBracketSelect`, + * without name mangling. But other aspects come into play: + * + * - Operator methods are translated to JS operators (not method calls) + * - `apply` is translated as a function call, i.e., `o()` instead of `o.apply()` + * - Scala varargs are turned into JS varargs (see `genPrimitiveJSArgs()`) + * - Getters and parameterless methods are translated as `JSBracketSelect` + * - Setters are translated to `Assign` to `JSBracketSelect` + */ + private def genApplyJSMethodGeneric(tree: Tree, sym: Symbol, + receiver: js.Tree, args: List[js.Tree], isStat: Boolean, + superIn: Option[Symbol] = None)( + implicit pos: Position): js.Tree = { + + implicit val pos: Position = tree.pos + + def noSpread = !args.exists(_.isInstanceOf[js.JSSpread]) + val argc = args.size // meaningful only for methods that don't have varargs + + def requireNotSuper(): Unit = { + if (superIn.isDefined) + ctx.error("Illegal super call in Scala.js-defined JS class", tree.pos) + } + + def hasExplicitJSEncoding = { + sym.hasAnnotation(jsdefn.JSNameAnnot) || + sym.hasAnnotation(jsdefn.JSBracketAccessAnnot) || + sym.hasAnnotation(jsdefn.JSBracketCallAnnot) + } + + val boxedResult = sym.name match { + case JSUnaryOpMethodName(code) if argc == 0 => + requireNotSuper() + js.JSUnaryOp(code, receiver) + + case JSBinaryOpMethodName(code) if argc == 1 => + requireNotSuper() + js.JSBinaryOp(code, receiver, args.head) + + case nme.apply if !hasExplicitJSEncoding => + requireNotSuper() + if (jsdefn.isJSThisFunctionClass(sym.owner)) + js.JSBracketMethodApply(receiver, js.StringLiteral("call"), args) + else + js.JSFunctionApply(receiver, args) + + case _ => + def jsFunName = js.StringLiteral(jsNameOf(sym)) + + def genSuperReference(propName: js.Tree): js.Tree = { + superIn.fold[js.Tree] { + js.JSBracketSelect(receiver, propName) + } { superInSym => + js.JSSuperBracketSelect( + jstpe.ClassType(encodeClassFullName(superInSym)), + receiver, propName) + } + } + + def genSelectGet(propName: js.Tree): js.Tree = + genSuperReference(propName) + + def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree = + js.Assign(genSuperReference(propName), value) + + def genCall(methodName: js.Tree, args: List[js.Tree]): js.Tree = { + superIn.fold[js.Tree] { + js.JSBracketMethodApply( + receiver, methodName, args) + } { superInSym => + js.JSSuperBracketCall( + jstpe.ClassType(encodeClassFullName(superInSym)), + receiver, methodName, args) + } + } + + if (isJSGetter(sym)) { + assert(noSpread && argc == 0) + genSelectGet(jsFunName) + } else if (isJSSetter(sym)) { + assert(noSpread && argc == 1) + genSelectSet(jsFunName, args.head) + } else if (isJSBracketAccess(sym)) { + assert(noSpread && (argc == 1 || argc == 2), + s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments") + args match { + case List(keyArg) => + genSelectGet(keyArg) + case List(keyArg, valueArg) => + genSelectSet(keyArg, valueArg) + } + } else if (isJSBracketCall(sym)) { + val (methodName, actualArgs) = extractFirstArg(args) + genCall(methodName, actualArgs) + } else { + genCall(jsFunName, args) + } + } + + if (isStat) { + boxedResult + } else { + val tpe = ctx.atPhase(ctx.elimErasedValueTypePhase) { implicit ctx => + sym.info.finalResultType + } + unbox(boxedResult, tpe) + } + } + + private object JSUnaryOpMethodName { + private val map = Map( + nme.UNARY_+ -> js.JSUnaryOp.+, + nme.UNARY_- -> js.JSUnaryOp.-, + nme.UNARY_~ -> js.JSUnaryOp.~, + nme.UNARY_! -> js.JSUnaryOp.! + ) + + def unapply(name: Names.TermName): Option[js.JSUnaryOp.Code] = + map.get(name) + } + + private object JSBinaryOpMethodName { + private val map = Map( + nme.ADD -> js.JSBinaryOp.+, + nme.SUB -> js.JSBinaryOp.-, + nme.MUL -> js.JSBinaryOp.*, + nme.DIV -> js.JSBinaryOp./, + nme.MOD -> js.JSBinaryOp.%, + + nme.LSL -> js.JSBinaryOp.<<, + nme.ASR -> js.JSBinaryOp.>>, + nme.LSR -> js.JSBinaryOp.>>>, + nme.OR -> js.JSBinaryOp.|, + nme.AND -> js.JSBinaryOp.&, + nme.XOR -> js.JSBinaryOp.^, + + nme.LT -> js.JSBinaryOp.<, + nme.LE -> js.JSBinaryOp.<=, + nme.GT -> js.JSBinaryOp.>, + nme.GE -> js.JSBinaryOp.>=, + + nme.ZAND -> js.JSBinaryOp.&&, + nme.ZOR -> js.JSBinaryOp.|| + ) + + def unapply(name: Names.TermName): Option[js.JSBinaryOp.Code] = + map.get(name) + } + + /** Extract the first argument in a list of actual arguments. + * + * This is nothing else than decomposing into head and tail, except that + * we assert that the first element is not a JSSpread. + */ + private def extractFirstArg(args: List[js.Tree]): (js.Tree, List[js.Tree]) = { + assert(args.nonEmpty, + "Trying to extract the first argument of an empty argument list") + val firstArg = args.head + assert(!firstArg.isInstanceOf[js.JSSpread], + "Trying to extract the first argument of an argument list starting " + + "with a Spread argument: " + firstArg) + (firstArg, args.tail) + } + + /** Gen JS code for a call to a polymorphic method. + * + * The only methods that reach the back-end as polymorphic are + * `isInstanceOf` and `asInstanceOf`. + * + * (Well, in fact `DottyRunTime.newRefArray` too, but it is handled as a + * primitive instead.) + */ + private def genTypeApply(tree: TypeApply): js.Tree = { + implicit val pos: Position = tree.pos + + val TypeApply(fun, targs) = tree + + val sym = fun.symbol + val receiver = qualifierOf(fun) + + val to = targs.head.tpe + + assert(!isPrimitiveValueType(receiver.tpe), + s"Found receiver of type test with primitive type ${receiver.tpe} at $pos") + assert(!isPrimitiveValueType(to), + s"Found target type of type test with primitive type ${receiver.tpe} at $pos") + + val genReceiver = genExpr(receiver) + + if (sym == defn.Any_asInstanceOf) { + genAsInstanceOf(genReceiver, to) + } else if (sym == defn.Any_isInstanceOf) { + genIsInstanceOf(tree, genReceiver, to) + } else { + throw new FatalError( + s"Unexpected type application $fun with symbol ${sym.fullName}") + } + } + + /** Gen JS code for a Java Seq literal. */ + private def genJavaSeqLiteral(tree: JavaSeqLiteral): js.Tree = { + implicit val pos: Position = tree.pos + + val genElems = tree.elems.map(genExpr) + val arrayType = toReferenceType(tree.tpe).asInstanceOf[jstpe.ArrayType] + js.ArrayValue(arrayType, genElems) + } + + /** Gen JS code for a closure. + * + * Input: a `Closure` tree of the form + * {{{ + * Closure(env, call, functionalInterface) + * }}} + * representing the pseudo-syntax + * {{{ + * { (p1, ..., pm) => call(env1, ..., envn, p1, ..., pm) }: functionInterface + * }}} + * where `envi` are identifiers in the local scope. The qualifier of `call` + * is also implicitly captured. + * + * Output: a `js.Closure` tree of the form + * {{{ + * js.Closure(formalCaptures, formalParams, body, actualCaptures) + * }}} + * representing the pseudo-syntax + * {{{ + * lambda( + * formalParam1, ..., formalParamM) = body + * }}} + * where the `actualCaptures` and `body` are, in general, arbitrary + * expressions. But in this case, `actualCaptures` will be identifiers from + * `env`, and the `body` will be of the form + * {{{ + * call(formalCapture1.ref, ..., formalCaptureN.ref, + * formalParam1.ref, ...formalParamM.ref) + * }}} + * + * When the `js.Closure` node is evaluated, i.e., when the closure value is + * created, the expressions of the `actualCaptures` are evaluated, and the + * results of those evaluations is "stored" in the environment of the + * closure as the corresponding `formalCapture`. + * + * When we later *call* the closure, the `formalCaptures` already have their + * values from the environment, and they are available in the `body`. The + * `formalParams` of the created closure receive their values from the + * actual arguments at the call-site of the closure, and they are also + * available in the `body`. + */ + private def genClosure(tree: Closure): js.Tree = { + implicit val pos: Position = tree.pos + val Closure(env, call, functionalInterface) = tree + + val envSize = env.size + + val (fun, args) = call match { + // case Apply(fun, args) => (fun, args) // Conjectured not to happen + case t @ Select(_, _) => (t, Nil) + case t @ Ident(_) => (t, Nil) + } + val sym = fun.symbol + + val qualifier = qualifierOf(fun) + val allCaptureValues = qualifier :: env + + val (formalCaptures, actualCaptures) = allCaptureValues.map { value => + implicit val pos: Position = value.pos + val formalIdent = value match { + case Ident(name) => freshLocalIdent(name.toString) + case This(_) => freshLocalIdent("this") + case _ => freshLocalIdent() + } + val formalCapture = + js.ParamDef(formalIdent, toIRType(value.tpe), mutable = false, rest = false) + val actualCapture = genExpr(value) + (formalCapture, actualCapture) + }.unzip + + val formalParamNames = sym.info.paramNamess.flatten.drop(envSize) + val formalParamTypes = sym.info.paramTypess.flatten.drop(envSize) + val (formalParams, actualParams) = formalParamNames.zip(formalParamTypes).map { + case (name, tpe) => + val formalParam = js.ParamDef(freshLocalIdent(name.toString), + jstpe.AnyType, mutable = false, rest = false) + val actualParam = unbox(formalParam.ref, tpe) + (formalParam, actualParam) + }.unzip + + val genBody = { + val thisCaptureRef :: argCaptureRefs = formalCaptures.map(_.ref) + val call = genApplyMethod(thisCaptureRef, sym, argCaptureRefs ::: actualParams) + box(call, sym.info.finalResultType) + } + + val closure = js.Closure(formalCaptures, formalParams, genBody, actualCaptures) + ctx.debuglog(closure.toString) + + val funInterfaceSym = functionalInterface.tpe.widenDealias.typeSymbol + if (jsdefn.isJSFunctionClass(funInterfaceSym)) { + closure + } else { + assert(!funInterfaceSym.exists || defn.isFunctionClass(funInterfaceSym), + s"Invalid functional interface $funInterfaceSym reached the back-end") + val cls = "sjsr_AnonFunction" + formalParams.size + val ctor = js.Ident("init___sjs_js_Function" + formalParams.size) + js.New(jstpe.ClassType(cls), ctor, List(closure)) + } + } + + /** Boxes a value of the given type before `elimErasedValueType`. + * + * This should be used when sending values to a JavaScript context, which + * is erased/boxed at the IR level, although it is not erased at the + * dotty/JVM level. + * + * @param expr Tree to be boxed if needed. + * @param tpeEnteringElimErasedValueType The type of `expr` as it was + * entering the `elimErasedValueType` phase. + */ + private def box(expr: js.Tree, tpeEnteringElimErasedValueType: Type)( + implicit pos: Position): js.Tree = { + + tpeEnteringElimErasedValueType match { + case tpe if isPrimitiveValueType(tpe) => + makePrimitiveBox(expr, tpe) + + /*case tpe: ErasedValueType => + val boxedClass = tpe.valueClazz + val ctor = boxedClass.primaryConstructor + genNew(boxedClass, ctor, List(expr))*/ + + case _ => + expr + } + } + + /** Unboxes a value typed as Any to the given type before `elimErasedValueType`. + * + * This should be used when receiving values from a JavaScript context, + * which is erased/boxed at the IR level, although it is not erased at the + * dotty/JVM level. + * + * @param expr Tree to be extracted. + * @param tpeEnteringElimErasedValueType The type of `expr` as it was + * entering the `elimErasedValueType` phase. + */ + private def unbox(expr: js.Tree, tpeEnteringElimErasedValueType: Type)( + implicit pos: Position): js.Tree = { + + tpeEnteringElimErasedValueType match { + case tpe if isPrimitiveValueType(tpe) => + makePrimitiveUnbox(expr, tpe) + + /*case tpe: ErasedValueType => + val boxedClass = tpe.valueClazz + val unboxMethod = boxedClass.derivedValueClassUnbox + val content = genApplyMethod( + genAsInstanceOf(expr, tpe), unboxMethod, Nil) + if (unboxMethod.tpe.resultType <:< tpe.erasedUnderlying) + content + else + fromAny(content, tpe.erasedUnderlying)*/ + + case tpe => + genAsInstanceOf(expr, tpe) + } + } + + /** Gen JS code for an asInstanceOf cast (for reference types only) */ + private def genAsInstanceOf(value: js.Tree, to: Type)( + implicit pos: Position): js.Tree = { + + val sym = to.widenDealias.typeSymbol + + if (sym == defn.ObjectClass || isJSType(sym)) { + /* asInstanceOf[Object] always succeeds, and + * asInstanceOf to a raw JS type is completely erased. + */ + value + } else { + js.AsInstanceOf(value, toReferenceType(to)) + } + } + + /** Gen JS code for an isInstanceOf test (for reference types only) */ + private def genIsInstanceOf(tree: Tree, value: js.Tree, to: Type): js.Tree = { + implicit val pos: Position = tree.pos + val sym = to.widenDealias.typeSymbol + + if (sym == defn.ObjectClass) { + js.BinaryOp(js.BinaryOp.!==, value, js.Null()) + } else if (isJSType(sym)) { + if (sym.is(Trait)) { + ctx.error( + s"isInstanceOf[${sym.fullName}] not supported because it is a JS trait", + tree.pos) + js.BooleanLiteral(true) + } else { + js.Unbox(js.JSBinaryOp( + js.JSBinaryOp.instanceof, value, genLoadJSConstructor(sym)), 'Z') + } + } else { + js.IsInstanceOf(value, toReferenceType(to)) + } + } + + /** Gen a dynamically linked call to a Scala method. */ + private def genApplyMethod(receiver: js.Tree, + methodSym: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + js.Apply(receiver, encodeMethodSym(methodSym), arguments)( + toIRType(patchedResultType(methodSym))) + } + + /** Gen a statically linked call to an instance method. */ + private def genApplyMethodStatically(receiver: js.Tree, method: Symbol, + arguments: List[js.Tree])(implicit pos: Position): js.Tree = { + val className = encodeClassFullName(method.owner) + val methodIdent = encodeMethodSym(method) + val resultType = toIRType(patchedResultType(method)) + js.ApplyStatically(receiver, jstpe.ClassType(className), + methodIdent, arguments)(resultType) + } + + /** Gen a call to a static method. */ + private def genApplyStatic(method: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + val cls = jstpe.ClassType(encodeClassFullName(method.owner)) + val methodIdent = encodeMethodSym(method) + js.ApplyStatic(cls, methodIdent, arguments)( + toIRType(patchedResultType(method))) + } + + /** Gen a call to a Scala2 impl class method. */ + private def genTraitImplApply(method: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + genApplyStatic(method, arguments) + } + + /** Gen a call to a non-exposed method of a non-native JS class. */ + private def genApplyJSClassMethod(receiver: js.Tree, method: Symbol, + arguments: List[js.Tree])(implicit pos: Position): js.Tree = { + genApplyStatic(method, receiver :: arguments) + } + + /** Gen a call to a method of a Scala top-level module. */ + private def genModuleApplyMethod(methodSym: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + genApplyMethod(genLoadModule(methodSym.owner), methodSym, arguments) + } + + /** Gen JS code for `new java.lang.String(...)`. + * + * Rewires the instantiation to calling the appropriate overload of + * `newString` in the object `scala.scalajs.runtime.RuntimeString`. + */ + private def genNewString(ctor: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + js.Apply( + genLoadModule(jsdefn.RuntimeStringModuleClass), + encodeRTStringCtorSym(ctor), arguments)( + jstpe.ClassType(ir.Definitions.StringClass)) + } + + /** Gen a dynamically linked call to a method of java.lang.String. + * + * Forwards the call to the module scala.scalajs.runtime.RuntimeString. + */ + private def genApplyMethodOfString(receiver: js.Tree, + methodSym: Symbol, arguments: List[js.Tree])( + implicit pos: Position): js.Tree = { + js.Apply( + genLoadModule(jsdefn.RuntimeStringModuleClass), + encodeRTStringMethodSym(methodSym), + receiver :: arguments)( + toIRType(patchedResultType(methodSym))) + } + + /** Gen a boxing operation (tpe is the primitive type) */ + private def makePrimitiveBox(expr: js.Tree, tpe: Type)( + implicit pos: Position): js.Tree = { + toReferenceType(tpe) match { + case jstpe.ClassType(cls) if ir.Definitions.isPrimitiveClass(cls) => + assert(cls.length == 1) + (cls.charAt(0): @switch) match { + case 'V' => + // must be handled at least for JS interop + js.Block(expr, js.Undefined()) + case 'C' => + genModuleApplyMethod(jsdefn.BoxesRunTime_boxToCharacter, List(expr)) + case _ => + expr // box is identity for all non-Char types + } + + case _ => + throw new FatalError( + s"makePrimitiveBox requires a primitive type, found $tpe at $pos") + } + } + + /** Gen an unboxing operation (tpe is the primitive type) */ + private def makePrimitiveUnbox(expr: js.Tree, tpe: Type)( + implicit pos: Position): js.Tree = { + toReferenceType(tpe) match { + case jstpe.ClassType(cls) if ir.Definitions.isPrimitiveClass(cls) => + assert(cls.length == 1) + (cls.charAt(0): @switch) match { + case 'V' => + // must be handled at least for JS interop + expr + case 'C' => + genModuleApplyMethod(jsdefn.BoxesRunTime_unboxToChar, List(expr)) + case primitiveCharCode => + js.Unbox(expr, primitiveCharCode) + } + + case _ => + throw new FatalError( + s"makePrimitiveUnbox requires a primitive type, found $tpe at $pos") + } + } + + /** Gen actual actual arguments to Scala method call. + * Returns a list of the transformed arguments. + * + * This tries to optimize repeated arguments (varargs) by turning them + * into js.WrappedArray instead of Scala wrapped arrays. + */ + private def genActualArgs(sym: Symbol, args: List[Tree])( + implicit pos: Position): List[js.Tree] = { + args.map(genExpr) + /*val wereRepeated = exitingPhase(currentRun.typerPhase) { + sym.tpe.params.map(p => isScalaRepeatedParamType(p.tpe)) + } + + if (wereRepeated.size > args.size) { + // Should not happen, but let's not crash + args.map(genExpr) + } else { + /* Arguments that are in excess compared to the type signature after + * erasure are lambda-lifted arguments. They cannot be repeated, hence + * the extension to `false`. + */ + for ((arg, wasRepeated) <- args.zipAll(wereRepeated, EmptyTree, false)) yield { + if (wasRepeated) { + tryGenRepeatedParamAsJSArray(arg, handleNil = false).fold { + genExpr(arg) + } { genArgs => + genNew(WrappedArrayClass, WrappedArray_ctor, + List(js.JSArrayConstr(genArgs))) + } + } else { + genExpr(arg) + } + } + }*/ + } + + /** Gen actual actual arguments to a JS method call. + * Returns a list of the transformed arguments. + * + * - TODO Repeated arguments (varargs) are expanded + * - Default arguments are omitted or replaced by undefined + * - All arguments are boxed + * + * Repeated arguments that cannot be expanded at compile time (i.e., if a + * Seq is passed to a varargs parameter with the syntax `seq: _*`) will be + * wrapped in a [[js.JSSpread]] node to be expanded at runtime. + */ + private def genActualJSArgs(sym: Symbol, args: List[Tree])( + implicit pos: Position): List[js.Tree] = { + + def paramNamesAndTypes(implicit ctx: Context): List[(Names.TermName, Type)] = + sym.info.paramNamess.flatten.zip(sym.info.paramTypess.flatten) + + val wereRepeated = ctx.atPhase(ctx.elimRepeatedPhase) { implicit ctx => + for ((name, tpe) <- paramNamesAndTypes) + yield (name -> tpe.isRepeatedParam) + }.toMap + + val paramTypes = ctx.atPhase(ctx.elimErasedValueTypePhase) { implicit ctx => + paramNamesAndTypes + }.toMap + + var reversedArgs: List[js.Tree] = Nil + + for ((arg, (paramName, paramType)) <- args.zip(paramNamesAndTypes)) { + val wasRepeated = wereRepeated.getOrElse(paramName, false) + if (wasRepeated) { + reversedArgs = + genJSRepeatedParam(arg) reverse_::: reversedArgs + } else { + val unboxedArg = genExpr(arg) + val boxedArg = unboxedArg match { + case js.UndefinedParam() => + unboxedArg + case _ => + val tpe = paramTypes.getOrElse(paramName, paramType) + box(unboxedArg, tpe) + } + reversedArgs ::= boxedArg + } + } + + /* Remove all consecutive js.UndefinedParam's at the end of the argument + * list. No check is performed whether they may be there, since they will + * only be placed where default arguments can be anyway. + */ + reversedArgs = reversedArgs.dropWhile(_.isInstanceOf[js.UndefinedParam]) + + /* Find remaining js.UndefinedParam and replace by js.Undefined. This can + * happen with named arguments or with multiple argument lists. + */ + reversedArgs = reversedArgs map { + case js.UndefinedParam() => js.Undefined() + case arg => arg + } + + reversedArgs.reverse + } + + /** Gen JS code for a repeated param of a JS method. + * + * In this case `arg` has type `Seq[T]` for some `T`, but the result should + * be an expanded list of the elements in the sequence. So this method + * takes care of the conversion. + * + * It is specialized for the shapes of tree generated by the desugaring + * of repeated params in Scala, so that these are actually expanded at + * compile-time. + * + * Otherwise, it returns a `JSSpread` with the `Seq` converted to a + * `js.Array`. + */ + private def genJSRepeatedParam(arg: Tree): List[js.Tree] = { + tryGenRepeatedParamAsJSArray(arg, handleNil = true).getOrElse { + /* Fall back to calling runtime.genTraversableOnce2jsArray + * to perform the conversion to js.Array, then wrap in a Spread + * operator. + */ + implicit val pos: Position = arg.pos + val jsArrayArg = genModuleApplyMethod( + jsdefn.RuntimePackage_genTraversableOnce2jsArray, + List(genExpr(arg))) + List(js.JSSpread(jsArrayArg)) + } + } + + /** Try and expand an actual argument to a repeated param `(xs: T*)`. + * + * This method recognizes the shapes of tree generated by the desugaring + * of repeated params in Scala, and expands them. + * If `arg` does not have the shape of a generated repeated param, this + * method returns `None`. + */ + private def tryGenRepeatedParamAsJSArray(arg: Tree, + handleNil: Boolean): Option[List[js.Tree]] = { + implicit val pos: Position = arg.pos + + // Given a method `def foo(args: T*)` + arg match { + // foo(arg1, arg2, ..., argN) where N > 0 + case MaybeAsInstanceOf(WrapArray(MaybeAsInstanceOf(array: JavaSeqLiteral))) => + /* Value classes in arrays are already boxed, so no need to use + * the type before erasure. + * TODO Is this true in dotty? + */ + Some(array.elems.map(e => box(genExpr(e), e.tpe))) + + // foo() + case Ident(_) if handleNil && arg.symbol == defn.NilModule => + Some(Nil) + + // foo(argSeq: _*) - cannot be optimized + case _ => + None + } + } + + private object MaybeAsInstanceOf { + def unapply(tree: Tree): Some[Tree] = tree match { + case TypeApply(asInstanceOf_? @ Select(base, _), _) + if asInstanceOf_?.symbol == defn.Any_asInstanceOf => + Some(base) + case _ => + Some(tree) + } + } + + private object WrapArray { + lazy val isWrapArray: Set[Symbol] = { + val names = { + defn.ScalaValueClasses().map(sym => nme.wrapXArray(sym.name)) ++ + Set(nme.wrapRefArray, nme.genericWrapArray) + } + names.map(defn.ScalaPredefModule.requiredMethod(_)).toSet + } + + def unapply(tree: Apply): Option[Tree] = tree match { + case Apply(wrapArray_?, List(wrapped)) if isWrapArray(wrapArray_?.symbol) => + Some(wrapped) + case _ => + None + } + } + + /** Gen JS code for loading a Java static field. + */ + private def genLoadStaticField(sym: Symbol)(implicit pos: Position): js.Tree = { + /* Actually, there is no static member in Scala.js. If we come here, that + * is because we found the symbol in a Java-emitted .class in the + * classpath. But the corresponding implementation in Scala.js will + * actually be a val in the companion module. + */ + + if (sym == defn.BoxedUnit_UNIT) { + js.Undefined() + } else { + val instance = genLoadModule(sym.owner) + val method = encodeStaticMemberSym(sym) + js.Apply(instance, method, Nil)(toIRType(sym.info)) + } + } + + /** Gen JS code for loading a module. + * + * Can be given either the module symbol, or its module class symbol. + */ + private def genLoadModule(sym0: Symbol)(implicit pos: Position): js.Tree = { + require(sym0.is(Module), + "genLoadModule called with non-module symbol: " + sym0) + val sym1 = if (sym0.isTerm) sym0.moduleClass else sym0 + val sym = // redirect all static methods of String to RuntimeString + if (sym1 == defn.StringModule) jsdefn.RuntimeStringModule.moduleClass + else sym1 + + if (isJSType(sym)) { + if (isScalaJSDefinedJSClass(sym)) + js.LoadJSModule(jstpe.ClassType(encodeClassFullName(sym))) + else if (sym.derivesFrom(jsdefn.JSGlobalScopeClass)) + genLoadJSGlobal() + else + genLoadNativeJSModule(sym) + } else { + js.LoadModule(jstpe.ClassType(encodeClassFullName(sym))) + } + } + + /** Gen JS code representing the constructor of a JS class. */ + private def genLoadJSConstructor(sym: Symbol)( + implicit pos: Position): js.Tree = { + assert(!isStaticModule(sym) && !sym.is(Trait), + s"genPrimitiveJSClass called with non-class $sym") + js.LoadJSConstructor(jstpe.ClassType(encodeClassFullName(sym))) + } + + /** Gen JS code representing a native JS module. */ + private def genLoadNativeJSModule(sym: Symbol)( + implicit pos: Position): js.Tree = { + require(sym.is(ModuleClass), + s"genLoadNativeJSModule called with non-module $sym") + fullJSNameOf(sym).split('.').foldLeft(genLoadJSGlobal()) { (memo, chunk) => + js.JSBracketSelect(memo, js.StringLiteral(chunk)) + } + } + + /** Gen JS code to load the JavaScript global scope. */ + private def genLoadJSGlobal()(implicit pos: Position): js.Tree = { + js.JSBracketSelect( + js.JSBracketSelect(js.JSLinkingInfo(), js.StringLiteral("envInfo")), + js.StringLiteral("global")) + } + + /** Generate a Class[_] value (e.g. coming from classOf[T]) */ + private def genClassConstant(tpe: Type)(implicit pos: Position): js.Tree = + js.ClassOf(toReferenceType(tpe)) + + private def isStaticModule(sym: Symbol): Boolean = + sym.is(Module) && sym.isStatic + + private def isPrimitiveValueType(tpe: Type): Boolean = { + tpe.widenDealias match { + case JavaArrayType(_) => false + case t => t.typeSymbol.asClass.isPrimitiveValueClass + } + } + +} diff --git a/sjs/backend/sjs/JSDefinitions.scala b/sjs/backend/sjs/JSDefinitions.scala new file mode 100644 index 000000000..bd0b74031 --- /dev/null +++ b/sjs/backend/sjs/JSDefinitions.scala @@ -0,0 +1,199 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ + +import Types._ +import Contexts._ +import Symbols._ +import Names._ +import StdNames._ +import Decorators._ + +import dotty.tools.dotc.config.SJSPlatform + +object JSDefinitions { + /** The Scala.js-specific definitions for the current context. */ + def jsdefn(implicit ctx: Context): JSDefinitions = + ctx.platform.asInstanceOf[SJSPlatform].jsDefinitions +} + +final class JSDefinitions()(implicit ctx: Context) { + + lazy val InlineAnnotType: TypeRef = ctx.requiredClassRef("scala.inline") + def InlineAnnot(implicit ctx: Context) = InlineAnnotType.symbol.asClass + lazy val NoinlineAnnotType: TypeRef = ctx.requiredClassRef("scala.noinline") + def NoinlineAnnot(implicit ctx: Context) = NoinlineAnnotType.symbol.asClass + + lazy val ScalaJSJSPackageVal = ctx.requiredPackage("scala.scalajs.js") + lazy val ScalaJSJSPackageClass = ScalaJSJSPackageVal.moduleClass.asClass + lazy val JSPackage_typeOfR = ScalaJSJSPackageClass.requiredMethodRef("typeOf") + def JSPackage_typeOf(implicit ctx: Context) = JSPackage_typeOfR.symbol + lazy val JSPackage_constructorOfR = ScalaJSJSPackageClass.requiredMethodRef("constructorOf") + def JSPackage_constructorOf(implicit ctx: Context) = JSPackage_constructorOfR.symbol + lazy val JSPackage_debuggerR = ScalaJSJSPackageClass.requiredMethodRef("debugger") + def JSPackage_debugger(implicit ctx: Context) = JSPackage_debuggerR.symbol + lazy val JSPackage_nativeR = ScalaJSJSPackageClass.requiredMethodRef("native") + def JSPackage_native(implicit ctx: Context) = JSPackage_nativeR.symbol + + lazy val JSNativeAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.native") + def JSNativeAnnot(implicit ctx: Context) = JSNativeAnnotType.symbol.asClass + + lazy val JSAnyType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Any") + def JSAnyClass(implicit ctx: Context) = JSAnyType.symbol.asClass + lazy val JSObjectType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Object") + def JSObjectClass(implicit ctx: Context) = JSObjectType.symbol.asClass + lazy val JSBaseThisFunctionType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.ThisFunction") + def JSBaseThisFunctionClass(implicit ctx: Context) = JSBaseThisFunctionType.symbol.asClass + + lazy val JSDictionaryType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Dictionary") + def JSDictionaryClass(implicit ctx: Context) = JSDictionaryType.symbol.asClass + lazy val JSDictionary_deleteR = JSDictionaryClass.requiredMethodRef("delete") + def JSDictionary_delete(implicit ctx: Context) = JSDictionary_deleteR.symbol + + lazy val JSGlobalScopeType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.GlobalScope") + def JSGlobalScopeClass(implicit ctx: Context) = JSGlobalScopeType.symbol.asClass + + lazy val JSArrayType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Array") + def JSArrayClass(implicit ctx: Context) = JSArrayType.symbol.asClass + + lazy val JSFunctionType = (0 to 22).map(n => ctx.requiredClassRef("scala.scalajs.js.Function" + n)).toArray + def JSFunctionClass(n: Int)(implicit ctx: Context) = JSFunctionType(n).symbol.asClass + lazy val JSThisFunctionType = (0 to 21).map(n => ctx.requiredClassRef("scala.scalajs.js.ThisFunction" + n)).toArray + def JSThisFunctionClass(n: Int)(implicit ctx: Context) = JSThisFunctionType(n).symbol.asClass + + lazy val RuntimeExceptionType: TypeRef = ctx.requiredClassRef("java.lang.RuntimeException") + def RuntimeExceptionClass(implicit ctx: Context) = RuntimeExceptionType.symbol.asClass + lazy val JavaScriptExceptionType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.JavaScriptException") + def JavaScriptExceptionClass(implicit ctx: Context) = JavaScriptExceptionType.symbol.asClass + + lazy val JSNameAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSName") + def JSNameAnnot(implicit ctx: Context) = JSNameAnnotType.symbol.asClass + lazy val JSFullNameAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSFullName") + def JSFullNameAnnot(implicit ctx: Context) = JSFullNameAnnotType.symbol.asClass + lazy val JSBracketAccessAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSBracketAccess") + def JSBracketAccessAnnot(implicit ctx: Context) = JSBracketAccessAnnotType.symbol.asClass + lazy val JSBracketCallAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSBracketCall") + def JSBracketCallAnnot(implicit ctx: Context) = JSBracketCallAnnotType.symbol.asClass + lazy val JSExportAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExport") + def JSExportAnnot(implicit ctx: Context) = JSExportAnnotType.symbol.asClass + lazy val JSExportDescendentObjectsAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportDescendentObjects") + def JSExportDescendentObjectsAnnot(implicit ctx: Context) = JSExportDescendentObjectsAnnotType.symbol.asClass + lazy val JSExportDescendentClassesAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportDescendentClasses") + def JSExportDescendentClassesAnnot(implicit ctx: Context) = JSExportDescendentClassesAnnotType.symbol.asClass + lazy val JSExportAllAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportAll") + def JSExportAllAnnot(implicit ctx: Context) = JSExportAllAnnotType.symbol.asClass + lazy val JSExportNamedAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportNamed") + def JSExportNamedAnnot(implicit ctx: Context) = JSExportNamedAnnotType.symbol.asClass + lazy val RawJSTypeAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.RawJSType") + def RawJSTypeAnnot(implicit ctx: Context) = RawJSTypeAnnotType.symbol.asClass + lazy val ExposedJSMemberAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.ExposedJSMember") + def ExposedJSMemberAnnot(implicit ctx: Context) = ExposedJSMemberAnnotType.symbol.asClass + + lazy val JSAnyModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Any") + def JSAnyModule(implicit ctx: Context) = JSAnyModuleRef.symbol + lazy val JSAny_fromFunctionR = (0 to 22).map(n => JSAnyModule.requiredMethodRef("fromFunction" + n)).toArray + def JSAny_fromFunction(n: Int)(implicit ctx: Context) = JSAny_fromFunctionR(n).symbol + + lazy val JSDynamicModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Dynamic") + def JSDynamicModule(implicit ctx: Context) = JSDynamicModuleRef.symbol + lazy val JSDynamic_newInstanceR = JSDynamicModule.requiredMethodRef("newInstance") + def JSDynamic_newInstance(implicit ctx: Context) = JSDynamic_newInstanceR.symbol + + lazy val JSDynamicLiteralModuleRef = JSDynamicModule.moduleClass.requiredValueRef("literal") + def JSDynamicLiteralModule(implicit ctx: Context) = JSDynamicLiteralModuleRef.symbol + lazy val JSDynamicLiteral_applyDynamicNamedR = JSDynamicLiteralModule.requiredMethodRef("applyDynamicNamed") + def JSDynamicLiteral_applyDynamicNamed(implicit ctx: Context) = JSDynamicLiteral_applyDynamicNamedR.symbol + lazy val JSDynamicLiteral_applyDynamicR = JSDynamicLiteralModule.requiredMethodRef("applyDynamic") + def JSDynamicLiteral_applyDynamic(implicit ctx: Context) = JSDynamicLiteral_applyDynamicR.symbol + + lazy val JSObjectModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Object") + def JSObjectModule(implicit ctx: Context) = JSObjectModuleRef.symbol + lazy val JSObject_hasPropertyR = JSObjectModule.requiredMethodRef("hasProperty") + def JSObject_hasProperty(implicit ctx: Context) = JSObject_hasPropertyR.symbol + lazy val JSObject_propertiesR = JSObjectModule.requiredMethodRef("properties") + def JSObject_properties(implicit ctx: Context) = JSObject_propertiesR.symbol + + lazy val JSArrayModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Array") + def JSArrayModule(implicit ctx: Context) = JSArrayModuleRef.symbol + lazy val JSArray_applyR = JSArrayModule.requiredMethodRef(nme.apply) + def JSArray_apply(implicit ctx: Context) = JSArray_applyR.symbol + + lazy val JSThisFunctionModuleRef = ctx.requiredModuleRef("scala.scalajs.js.ThisFunction") + def JSThisFunctionModule(implicit ctx: Context) = JSThisFunctionModuleRef.symbol + lazy val JSThisFunction_fromFunctionR = (1 to 22).map(n => JSThisFunctionModule.requiredMethodRef("fromFunction" + n)).toArray + def JSThisFunction_fromFunction(n: Int)(implicit ctx: Context) = JSThisFunction_fromFunctionR(n - 1).symbol + + lazy val JSConstructorTagModuleRef = ctx.requiredModuleRef("scala.scalajs.js.ConstructorTag") + def JSConstructorTagModule(implicit ctx: Context) = JSConstructorTagModuleRef.symbol + lazy val JSConstructorTag_materializeR = JSConstructorTagModule.requiredMethodRef("materialize") + def JSConstructorTag_materialize(implicit ctx: Context) = JSConstructorTag_materializeR.symbol + + lazy val RuntimeStringModuleRef = ctx.requiredModuleRef("scala.scalajs.runtime.RuntimeString") + def RuntimeStringModule(implicit ctx: Context) = RuntimeStringModuleRef.symbol + def RuntimeStringModuleClass(implicit ctx: Context) = RuntimeStringModule.moduleClass.asClass + + lazy val BooleanReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.BooleanReflectiveCall") + def BooleanReflectiveCallClass(implicit ctx: Context) = BooleanReflectiveCallType.symbol.asClass + lazy val NumberReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.NumberReflectiveCall") + def NumberReflectiveCallClass(implicit ctx: Context) = NumberReflectiveCallType.symbol.asClass + lazy val IntegerReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.IntegerReflectiveCall") + def IntegerReflectiveCallClass(implicit ctx: Context) = IntegerReflectiveCallType.symbol.asClass + lazy val LongReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.LongReflectiveCall") + def LongReflectiveCallClass(implicit ctx: Context) = LongReflectiveCallType.symbol.asClass + + lazy val RuntimePackageVal = ctx.requiredPackage("scala.scalajs.runtime") + lazy val RuntimePackageClass = RuntimePackageVal.moduleClass.asClass + lazy val RuntimePackage_wrapJavaScriptExceptionR = RuntimePackageClass.requiredMethodRef("wrapJavaScriptException") + def RuntimePackage_typeOf(implicit ctx: Context) = RuntimePackage_wrapJavaScriptExceptionR.symbol + lazy val RuntimePackage_unwrapJavaScriptExceptionR = RuntimePackageClass.requiredMethodRef("unwrapJavaScriptException") + def RuntimePackage_unwrapJavaScriptException(implicit ctx: Context) = RuntimePackage_unwrapJavaScriptExceptionR.symbol + lazy val RuntimePackage_genTraversableOnce2jsArrayR = RuntimePackageClass.requiredMethodRef("genTraversableOnce2jsArray") + def RuntimePackage_genTraversableOnce2jsArray(implicit ctx: Context) = RuntimePackage_genTraversableOnce2jsArrayR.symbol + lazy val RuntimePackage_jsTupleArray2jsObjectR = RuntimePackageClass.requiredMethodRef("jsTupleArray2jsObject") + def RuntimePackage_jsTupleArray2jsObject(implicit ctx: Context) = RuntimePackage_jsTupleArray2jsObjectR.symbol + lazy val RuntimePackage_constructorOfR = RuntimePackageClass.requiredMethodRef("constructorOf") + def RuntimePackage_constructorOf(implicit ctx: Context) = RuntimePackage_constructorOfR.symbol + lazy val RuntimePackage_newConstructorTagR = RuntimePackageClass.requiredMethodRef("newConstructorTag") + def RuntimePackage_newConstructorTag(implicit ctx: Context) = RuntimePackage_newConstructorTagR.symbol + lazy val RuntimePackage_propertiesOfR = RuntimePackageClass.requiredMethodRef("propertiesOf") + def RuntimePackage_propertiesOf(implicit ctx: Context) = RuntimePackage_propertiesOfR.symbol + lazy val RuntimePackage_environmentInfoR = RuntimePackageClass.requiredMethodRef("environmentInfo") + def RuntimePackage_environmentInfo(implicit ctx: Context) = RuntimePackage_environmentInfoR.symbol + lazy val RuntimePackage_linkingInfoR = RuntimePackageClass.requiredMethodRef("linkingInfo") + def RuntimePackage_linkingInfo(implicit ctx: Context) = RuntimePackage_linkingInfoR.symbol + + lazy val WrappedArrayType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.WrappedArray") + def WrappedArrayClass(implicit ctx: Context) = WrappedArrayType.symbol.asClass + + lazy val ScalaRunTime_isArrayR = defn.ScalaRuntimeModule.requiredMethodRef("isArray", List(???, ???)) + def ScalaRunTime_isArray(implicit ctx: Context): Symbol = ScalaRunTime_isArrayR.symbol + + lazy val BoxesRunTime_boxToCharacterR = defn.BoxesRunTimeModule.requiredMethodRef("boxToCharacter") + def BoxesRunTime_boxToCharacter(implicit ctx: Context): Symbol = BoxesRunTime_boxToCharacterR.symbol + lazy val BoxesRunTime_unboxToCharR = defn.BoxesRunTimeModule.requiredMethodRef("unboxToChar") + def BoxesRunTime_unboxToChar(implicit ctx: Context): Symbol = BoxesRunTime_unboxToCharR.symbol + + /** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */ + private def scalajsClassName(cls: Symbol)(implicit ctx: Context): TypeName = + if (cls.isClass && cls.owner == ScalaJSJSPackageClass) cls.asClass.name + else EmptyTypeName + + /** Is the given `cls` a class of the form `scala.scalajs.js.prefixN` where + * `N` is a number. + * + * This is similar to `isVarArityClass` in `Definitions.scala`. + */ + private def isScalaJSVarArityClass(cls: Symbol, prefix: Name): Boolean = { + val name = scalajsClassName(cls) + name.startsWith(prefix) && name.drop(prefix.length).forall(_.isDigit) + } + + def isJSFunctionClass(cls: Symbol): Boolean = + isScalaJSVarArityClass(cls, nme.Function) + + private val ThisFunctionName = termName("ThisFunction") + + def isJSThisFunctionClass(cls: Symbol): Boolean = + isScalaJSVarArityClass(cls, ThisFunctionName) + +} diff --git a/sjs/backend/sjs/JSEncoding.scala b/sjs/backend/sjs/JSEncoding.scala new file mode 100644 index 000000000..e8ea3258b --- /dev/null +++ b/sjs/backend/sjs/JSEncoding.scala @@ -0,0 +1,389 @@ +package dotty.tools.backend.sjs + +import scala.collection.mutable + +import dotty.tools.FatalError + +import dotty.tools.dotc.core._ +import Periods._ +import SymDenotations._ +import Contexts._ +import Types._ +import Symbols._ +import Denotations._ +import NameOps._ +import StdNames._ + +import org.scalajs.core.ir +import ir.{Trees => js, Types => jstpe} + +import ScopedVar.withScopedVars +import JSDefinitions._ +import JSInterop._ + +/** Encoding of symbol names for JavaScript + * + * Some issues that this encoding solves: + * * Overloading: encode the full signature in the JS name + * * Same scope for fields and methods of a class + * * Global access to classes and modules (by their full name) + * + * @author Sébastien Doeraene + */ +object JSEncoding { + + /** Signature separator string (between parameter types) */ + private final val SignatureSep = "__" + + /** Name given to the local Scala.js environment variable */ + private final val ScalaJSEnvironmentName = "ScalaJS" + + implicit class SymOps(val self: Symbol) extends AnyVal { + def unexpandedName(implicit ctx: Context): Names.Name = + self.name.unexpandedName + } + + implicit class MyNameOps(val self: Names.Name) extends AnyVal { + def decoded: String = self.decode.toString + } + + // Fresh local name generator ---------------------------------------------- + + class LocalNameGenerator { + import LocalNameGenerator._ + + private val usedLocalNames = mutable.Set.empty[String] + private val localSymbolNames = mutable.Map.empty[Symbol, String] + + def localSymbolName(sym: Symbol)(implicit ctx: Context): String = + localSymbolNames.getOrElseUpdate(sym, freshName(sym.name.toString)) + + def freshLocalIdent()(implicit pos: ir.Position): js.Ident = + js.Ident(freshName(), None) + + def freshLocalIdent(base: String)(implicit pos: ir.Position): js.Ident = + js.Ident(freshName(base), Some(base)) + + private def freshName(base: String = "x"): String = { + var suffix = 1 + var longName = base + while (usedLocalNames(longName) || isReserved(longName)) { + suffix += 1 + longName = base+"$"+suffix + } + usedLocalNames += longName + mangleJSName(longName) + } + } + + private object LocalNameGenerator { + private val isReserved = + Set("arguments", "eval", ScalaJSEnvironmentName) + } + + // Encoding methods ---------------------------------------------------------- + + def encodeLabelSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position, localNames: LocalNameGenerator): js.Ident = { + require(sym.is(Flags.Label), "encodeLabelSym called with non-label symbol: " + sym) + js.Ident(localNames.localSymbolName(sym), Some(sym.unexpandedName.decoded)) + } + + private def allRefClasses(implicit ctx: Context): Set[Symbol] = { + //TODO + /*(Set(ObjectRefClass, VolatileObjectRefClass) ++ + refClass.values ++ volatileRefClass.values)*/ + Set() + } + + def encodeFieldSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + require(sym.owner.isClass && sym.isTerm && !sym.is(Flags.Method) && !sym.is(Flags.Module), + "encodeFieldSym called with non-field symbol: " + sym) + + val name0 = encodeMemberNameInternal(sym) + val name = + if (name0.charAt(name0.length()-1) != ' ') name0 + else name0.substring(0, name0.length()-1) + + /* We have to special-case fields of Ref types (IntRef, ObjectRef, etc.) + * because they are emitted as private by our .scala source files, but + * they are considered public at use site since their symbols come from + * Java-emitted .class files. + */ + val idSuffix = + if (sym.is(Flags.Private) || allRefClasses.contains(sym.owner)) + sym.owner.asClass.baseClasses.size.toString + else + "f" + + val encodedName = name + "$" + idSuffix + js.Ident(mangleJSName(encodedName), Some(sym.unexpandedName.decoded)) + } + + def encodeMethodSym(sym: Symbol, reflProxy: Boolean = false)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy) + js.Ident(encodedName + paramsString, + Some(sym.unexpandedName.decoded + paramsString)) + } + + def encodeMethodName(sym: Symbol, reflProxy: Boolean = false)( + implicit ctx: Context): String = { + val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy) + encodedName + paramsString + } + + /** Encodes a method symbol of java.lang.String for use in RuntimeString. + * + * This basically means adding an initial parameter of type + * java.lang.String, which is the `this` parameter. + */ + def encodeRTStringMethodSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + require(sym.owner == defn.StringClass) + require(!sym.isClassConstructor && !sym.is(Flags.Private)) + + val (encodedName, paramsString) = + encodeMethodNameInternal(sym, inRTClass = true) + js.Ident(encodedName + paramsString, + Some(sym.unexpandedName.decoded + paramsString)) + } + + /** Encodes a constructor symbol of java.lang.String for use in RuntimeString. + * + * - The name is rerouted to `newString` + * - The result type is set to `java.lang.String` + */ + def encodeRTStringCtorSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + require(sym.owner == defn.StringClass) + require(sym.isClassConstructor && !sym.is(Flags.Private)) + + val paramTypeNames = sym.info.firstParamTypes.map(internalName(_)) + val paramAndResultTypeNames = paramTypeNames :+ ir.Definitions.StringClass + val paramsString = makeParamsString(paramAndResultTypeNames) + + js.Ident("newString" + paramsString, + Some(sym.unexpandedName.decoded + paramsString)) + } + + private def encodeMethodNameInternal(sym: Symbol, + reflProxy: Boolean = false, inRTClass: Boolean = false)( + implicit ctx: Context): (String, String) = { + require(sym.is(Flags.Method), "encodeMethodSym called with non-method symbol: " + sym) + + def name = encodeMemberNameInternal(sym) + + val encodedName = { + if (sym.isClassConstructor) { + "init_" + } else if (sym.is(Flags.Private)) { + (mangleJSName(name) + SignatureSep + "p" + + sym.owner.asClass.baseClasses.size.toString) + } else { + mangleJSName(name) + } + } + + val paramsString = makeParamsString(sym, reflProxy, inRTClass) + + (encodedName, paramsString) + } + + def encodeStaticMemberSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + require(sym.is(Flags.JavaStaticTerm), + "encodeStaticMemberSym called with non-static symbol: " + sym) + js.Ident( + mangleJSName(encodeMemberNameInternal(sym)) + + makeParamsString(List(internalName(sym.info))), + Some(sym.unexpandedName.decoded)) + } + + def encodeLocalSym(sym: Symbol)( + implicit ctx: Context, pos: ir.Position, localNames: LocalNameGenerator): js.Ident = { + require(!sym.owner.isClass && sym.isTerm && !sym.is(Flags.Method) && !sym.is(Flags.Module), + "encodeLocalSym called with non-local symbol: " + sym) + js.Ident(localNames.localSymbolName(sym), Some(sym.unexpandedName.decoded)) + } + + def foreignIsImplClass(sym: Symbol)(implicit ctx: Context): Boolean = + sym.name.isImplClassName + + def encodeClassType(sym: Symbol)(implicit ctx: Context): jstpe.Type = { + if (sym == defn.ObjectClass) jstpe.AnyType + else if (isJSType(sym)) jstpe.AnyType + else { + assert(sym != defn.ArrayClass, + "encodeClassType() cannot be called with ArrayClass") + jstpe.ClassType(encodeClassFullName(sym)) + } + } + + def encodeClassFullNameIdent(sym: Symbol)( + implicit ctx: Context, pos: ir.Position): js.Ident = { + js.Ident(encodeClassFullName(sym), Some(sym.fullName.toString)) + } + + def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String = { + if (sym == defn.NothingClass) ir.Definitions.RuntimeNothingClass + else if (sym == defn.NullClass) ir.Definitions.RuntimeNullClass + else ir.Definitions.encodeClassName(sym.fullName.toString) + } + + private def encodeMemberNameInternal(sym: Symbol)( + implicit ctx: Context): String = { + sym.name.toString.replace("_", "$und").replace("~", "$tilde") + } + + def toIRType(tp: Type)(implicit ctx: Context): jstpe.Type = { + val refType = toReferenceTypeInternal(tp) + refType._1 match { + case tpe: jstpe.ClassType => + val sym = refType._2 + if (sym.asClass.isPrimitiveValueClass) { + if (sym == defn.BooleanClass) + jstpe.BooleanType + else if (sym == defn.FloatClass) + jstpe.FloatType + else if (sym == defn.DoubleClass) + jstpe.DoubleType + else if (sym == defn.LongClass) + jstpe.LongType + else if (sym == defn.UnitClass) + jstpe.NoType + else + jstpe.IntType + } else { + if (sym == defn.ObjectClass || isJSType(sym)) + jstpe.AnyType + else if (sym == defn.NothingClass) + jstpe.NothingType + else if (sym == defn.NullClass) + jstpe.NullType + else + tpe + } + + case tpe: jstpe.ArrayType => + tpe + } + } + + def toReferenceType(tp: Type)(implicit ctx: Context): jstpe.ReferenceType = + toReferenceTypeInternal(tp)._1 + + private def toReferenceTypeInternal(tp: Type)( + implicit ctx: Context): (jstpe.ReferenceType, Symbol) = { + + /** + * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int. + * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType. + */ + def primitiveOrClassToRefType(sym: Symbol): (jstpe.ReferenceType, Symbol) = { + assert(sym.isClass, sym) + //assert(sym != defn.ArrayClass || isCompilingArray, sym) + (jstpe.ClassType(encodeClassFullName(sym)), sym) + } + + /** + * When compiling Array.scala, the type parameter T is not erased and shows up in method + * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. + */ + def nonClassTypeRefToRefType(sym: Symbol): (jstpe.ReferenceType, Symbol) = { + //assert(sym.isType && isCompilingArray, sym) + (jstpe.ClassType(ir.Definitions.ObjectClass), defn.ObjectClass) + } + + tp.widenDealias match { + // Array type such as Array[Int] (kept by erasure) + case JavaArrayType(el) => + val elRefType = toReferenceTypeInternal(el) + (jstpe.ArrayType(elRefType._1), elRefType._2) + + case t: TypeRef => + if (!t.symbol.isClass) nonClassTypeRefToRefType(t.symbol) // See comment on nonClassTypeRefToBType + else primitiveOrClassToRefType(t.symbol) // Common reference to a type such as scala.Int or java.lang.String + + case Types.ClassInfo(_, sym, _, _, _) => + /* We get here, for example, for genLoadModule, which invokes + * toTypeKind(moduleClassSymbol.info) + */ + primitiveOrClassToRefType(sym) + + case t: MethodType => // triggers for LabelDefs + toReferenceTypeInternal(t.resultType) + + /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for + * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. + * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. + */ + case a @ AnnotatedType(t, _) => + //debuglog(s"typeKind of annotated type $a") + toReferenceTypeInternal(t) + } + } + + /** Patches the result type of a method symbol to sanitize it. + * + * For some reason, dotc thinks that the `info.resultType`of an + * `isConstructor` method (for classes or traits) is the enclosing class + * or trait, but the bodies and usages act as if the result type was `Unit`. + * + * This method returns `UnitType` for constructor methods, and otherwise + * `sym.info.resultType`. + */ + def patchedResultType(sym: Symbol)(implicit ctx: Context): Type = + if (sym.isConstructor) defn.UnitType + else sym.info.resultType + + // Encoding of method signatures + + private def makeParamsString(sym: Symbol, reflProxy: Boolean, + inRTClass: Boolean)( + implicit ctx: Context): String = { + val tpe = sym.info + + val paramTypeNames0 = tpe.firstParamTypes.map(internalName(_)) + + val hasExplicitThisParameter = + inRTClass || isScalaJSDefinedJSClass(sym.owner) + val paramTypeNames = + if (!hasExplicitThisParameter) paramTypeNames0 + else encodeClassFullName(sym.owner) :: paramTypeNames0 + + val paramAndResultTypeNames = { + if (sym.isClassConstructor) + paramTypeNames + else if (reflProxy) + paramTypeNames :+ "" + else + paramTypeNames :+ internalName(patchedResultType(sym)) + } + makeParamsString(paramAndResultTypeNames) + } + + private def makeParamsString(paramAndResultTypeNames: List[String]) = + paramAndResultTypeNames.mkString(SignatureSep, SignatureSep, "") + + /** Computes the internal name for a type. */ + private def internalName(tpe: Type)(implicit ctx: Context): String = + encodeReferenceType(toReferenceType(tpe)) + + /** Encodes a [[Types.ReferenceType]], such as in an encoded method signature. + */ + private def encodeReferenceType(refType: jstpe.ReferenceType): String = { + refType match { + case jstpe.ClassType(encodedName) => encodedName + case jstpe.ArrayType(base, depth) => "A" * depth + base + } + } + + /** Mangles names that are illegal in JavaScript by prepending a `$`. + * Also mangles names that would collide with these mangled names. + */ + private def mangleJSName(name: String) = + if (js.isKeyword(name) || name(0).isDigit || name(0) == '$') "$" + name + else name +} diff --git a/sjs/backend/sjs/JSInterop.scala b/sjs/backend/sjs/JSInterop.scala new file mode 100644 index 000000000..6d66c3206 --- /dev/null +++ b/sjs/backend/sjs/JSInterop.scala @@ -0,0 +1,110 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ +import Contexts._ +import Flags._ +import Symbols._ +import NameOps._ +import StdNames._ + +import JSDefinitions._ + +/** Management of the interoperability with JavaScript. */ +object JSInterop { + + /** Is this symbol a JavaScript type? */ + def isJSType(sym: Symbol)(implicit ctx: Context): Boolean = { + //sym.hasAnnotation(jsdefn.RawJSTypeAnnot) + ctx.atPhase(ctx.erasurePhase) { implicit ctx => + sym.derivesFrom(jsdefn.JSAnyClass) + } + } + + /** Is this symbol a Scala.js-defined JS class, i.e., a non-native JS class? */ + def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean = + isJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot) + + /** Should this symbol be translated into a JS getter? + * + * This is true for any parameterless method, i.e., defined without `()`. + * Unlike `SymDenotations.isGetter`, it applies to user-defined methods as + * much as *accessor* methods created for `val`s and `var`s. + */ + def isJSGetter(sym: Symbol)(implicit ctx: Context): Boolean = { + sym.info.firstParamTypes.isEmpty && ctx.atPhase(ctx.erasurePhase) { implicit ctx => + sym.info.isParameterless + } + } + + /** Should this symbol be translated into a JS setter? + * + * This is true for any method whose name ends in `_=`. + * Unlike `SymDenotations.isGetter`, it applies to user-defined methods as + * much as *accessor* methods created for `var`s. + */ + def isJSSetter(sym: Symbol)(implicit ctx: Context): Boolean = + sym.name.isSetterName && sym.is(Method) + + /** Should this symbol be translated into a JS bracket access? + * + * This is true for methods annotated with `@JSBracketAccess`. + */ + def isJSBracketAccess(sym: Symbol)(implicit ctx: Context): Boolean = + sym.hasAnnotation(jsdefn.JSBracketAccessAnnot) + + /** Should this symbol be translated into a JS bracket call? + * + * This is true for methods annotated with `@JSBracketCall`. + */ + def isJSBracketCall(sym: Symbol)(implicit ctx: Context): Boolean = + sym.hasAnnotation(jsdefn.JSBracketCallAnnot) + + /** Is this symbol a default param accessor for a JS method? + * + * For default param accessors of *constructors*, we need to test whether + * the companion *class* of the owner is a JS type; not whether the owner + * is a JS type. + */ + def isJSDefaultParam(sym: Symbol)(implicit ctx: Context): Boolean = { + sym.name.isDefaultGetterName && { + val owner = sym.owner + if (owner.is(ModuleClass) && + sym.name.asTermName.defaultGetterToMethod == nme.CONSTRUCTOR) { + isJSType(owner.linkedClass) + } else { + isJSType(owner) + } + } + } + + /** Gets the unqualified JS name of a symbol. + * + * If it is not explicitly specified with an `@JSName` annotation, the + * JS name is inferred from the Scala name. + */ + def jsNameOf(sym: Symbol)(implicit ctx: Context): String = { + sym.getAnnotation(jsdefn.JSNameAnnot).flatMap(_.argumentConstant(0)).fold { + val base = sym.name.unexpandedName.decode.toString.stripSuffix("_=") + if (sym.is(ModuleClass)) base.stripSuffix("$") + else if (!sym.is(Method)) base.stripSuffix(" ") + else base + } { constant => + constant.stringValue + } + } + + /** Gets the fully qualified JS name of a static class of module Symbol. + * + * This is the JS name of the symbol qualified by the fully qualified JS + * name of its original owner if the latter is a native JS object. + */ + def fullJSNameOf(sym: Symbol)(implicit ctx: Context): String = { + assert(sym.isClass, s"fullJSNameOf called for non-class symbol $sym") + sym.getAnnotation(jsdefn.JSFullNameAnnot).flatMap(_.argumentConstant(0)).fold { + jsNameOf(sym) + } { constant => + constant.stringValue + } + } + +} diff --git a/sjs/backend/sjs/JSPositions.scala b/sjs/backend/sjs/JSPositions.scala new file mode 100644 index 000000000..10570da00 --- /dev/null +++ b/sjs/backend/sjs/JSPositions.scala @@ -0,0 +1,65 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ +import Contexts._ +import dotty.tools.dotc.util.Positions +import Positions.Position + +import org.scalajs.core.ir + +/** Conversion utilities from dotty Positions to IR Positions. */ +class JSPositions()(implicit ctx: Context) { + + /** Implicit conversion from dotty Position to ir.Position. */ + implicit def pos2irPos(pos: Positions.Position): ir.Position = { + if (!pos.exists) ir.Position.NoPosition + else { + val source = pos2irPosCache.toIRSource(ctx.compilationUnit.source) + val sourcePos = ctx.compilationUnit.source.atPos(pos) + // dotty positions are 1-based but IR positions are 0-based + ir.Position(source, sourcePos.line-1, sourcePos.column-1) + } + } + + /** Implicitly materializes an ir.Position from an implicit dotty Position. */ + implicit def implicitPos2irPos( + implicit pos: Positions.Position): ir.Position = { + pos2irPos(pos) + } + + private[this] object pos2irPosCache { // scalastyle:ignore + import dotty.tools.dotc.util._ + + private[this] var lastDotcSource: SourceFile = null + private[this] var lastIRSource: ir.Position.SourceFile = null + + def toIRSource(dotcSource: SourceFile): ir.Position.SourceFile = { + if (dotcSource != lastDotcSource) { + lastIRSource = convert(dotcSource) + lastDotcSource = dotcSource + } + lastIRSource + } + + private[this] def convert(dotcSource: SourceFile): ir.Position.SourceFile = { + dotcSource.file.file match { + case null => + new java.net.URI( + "virtualfile", // Pseudo-Scheme + dotcSource.file.path, // Scheme specific part + null // Fragment + ) + case file => + val srcURI = file.toURI + def matches(pat: java.net.URI) = pat.relativize(srcURI) != srcURI + + // TODO + /*scalaJSOpts.sourceURIMaps.collectFirst { + case ScalaJSOptions.URIMap(from, to) if matches(from) => + val relURI = from.relativize(srcURI) + to.fold(relURI)(_.resolve(relURI)) + } getOrElse*/ srcURI + } + } + } +} diff --git a/sjs/backend/sjs/JSPrimitives.scala b/sjs/backend/sjs/JSPrimitives.scala new file mode 100644 index 000000000..6c3c5715c --- /dev/null +++ b/sjs/backend/sjs/JSPrimitives.scala @@ -0,0 +1,118 @@ +package dotty.tools.backend.sjs + +import dotty.tools.dotc.core._ +import Names.TermName +import StdNames._ +import Types._ +import Contexts._ +import Symbols._ + +import dotty.tools.dotc.ast.tpd._ +import dotty.tools.dotc.backend.jvm.DottyPrimitives + +import scala.collection.mutable + +object JSPrimitives { + + final val GETCLASS = 301 // jl.Object.getClass() + + final val F2JS = 302 // js.Any.fromFunctionN + final val F2JSTHIS = 303 // js.ThisFunction.fromFunctionN + + final val DYNNEW = 304 // js.Dynamic.newInstance + final val DYNLIT = 305 // js.Dynamic.literal.applyDynamic{,Named} + final val DICT_DEL = 306 // js.Dictionary.delete + final val ARR_CREATE = 307 // js.Array.apply (array literal syntax) + + final val TYPEOF = 308 // js.typeOf(x) + final val DEBUGGER = 309 // js.debugger() + final val HASPROP = 310 // js.Object.hasProperty(o, p), equiv to `p in o` in JS + final val OBJPROPS = 311 // js.Object.properties(o), equiv to `for (p in o)` in JS + final val JS_NATIVE = 312 // js.native. Marker method. Fails if tried to be emitted. + + final val UNITVAL = 313 // () value, which is undefined + final val UNITTYPE = 314 // BoxedUnit.TYPE (== classOf[Unit]) + + final val CONSTRUCTOROF = 315 // runtime.constructorOf(clazz) + final val ENV_INFO = 316 // runtime.environmentInfo + final val LINKING_INFO = 317 // runtime.linkingInfo + + final val THROW = 318 // .throw + +} + +class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) { + import JSPrimitives._ + import scala.tools.nsc.backend.ScalaPrimitives._ + + private lazy val jsPrimitives: Map[Symbol, Int] = initJSPrimitives(ctx) + + override def getPrimitive(sym: Symbol): Int = + jsPrimitives.getOrElse(sym, super.getPrimitive(sym)) + + override def getPrimitive(app: Apply, tpe: Type)(implicit ctx: Context): Int = + jsPrimitives.getOrElse(app.fun.symbol, super.getPrimitive(app, tpe)) + + override def isPrimitive(fun: Tree): Boolean = + jsPrimitives.contains(fun.symbol(ctx)) || super.isPrimitive(fun) + + /** Initialize the primitive map */ + private def initJSPrimitives(implicit ctx: Context): Map[Symbol, Int] = { + + val primitives = new mutable.HashMap[Symbol, Int]() + + // !!! Code duplicate with DottyPrimitives + /** Add a primitive operation to the map */ + def addPrimitive(s: Symbol, code: Int): Unit = { + assert(!(primitives contains s), "Duplicate primitive " + s) + primitives(s) = code + } + + def addPrimitives(cls: Symbol, method: TermName, code: Int)(implicit ctx: Context): Unit = { + val alts = cls.info.member(method).alternatives.map(_.symbol) + if (alts.isEmpty) { + ctx.error(s"Unknown primitive method $cls.$method") + } else { + for (s <- alts) + addPrimitive(s, code) + } + } + + val jsdefn = JSDefinitions.jsdefn + + addPrimitive(defn.Any_getClass, GETCLASS) + + for (i <- 0 to 22) + addPrimitive(jsdefn.JSAny_fromFunction(i), F2JS) + for (i <- 1 to 22) + addPrimitive(jsdefn.JSThisFunction_fromFunction(i), F2JSTHIS) + + addPrimitive(jsdefn.JSDynamic_newInstance, DYNNEW) + + addPrimitive(jsdefn.JSDynamicLiteral_applyDynamicNamed, DYNLIT) + addPrimitive(jsdefn.JSDynamicLiteral_applyDynamic, DYNLIT) + + addPrimitive(jsdefn.JSDictionary_delete, DICT_DEL) + + //addPrimitive(jsdefn.JSArray_create, ARR_CREATE) + + addPrimitive(jsdefn.JSPackage_typeOf, TYPEOF) + addPrimitive(jsdefn.JSPackage_debugger, DEBUGGER) + addPrimitive(jsdefn.JSPackage_native, JS_NATIVE) + + addPrimitive(jsdefn.JSObject_hasProperty, HASPROP) + addPrimitive(jsdefn.JSObject_properties, OBJPROPS) + + addPrimitive(defn.BoxedUnit_UNIT, UNITVAL) + //addPrimitive(defn.BoxedUnit_TYPE, UNITTYPE) + + //addPrimitive(jsdefn.Runtime_constructorOf, CONSTRUCTOROF) + //addPrimitive(jsdefn.Runtime_environmentInfo, ENV_INFO) + //addPrimitive(jsdefn.Runtime_linkingInfo, LINKING_INFO) + + addPrimitive(defn.throwMethod, THROW) + + primitives.toMap + } + +} diff --git a/sjs/backend/sjs/ScopedVar.scala b/sjs/backend/sjs/ScopedVar.scala new file mode 100644 index 000000000..0e47f7b79 --- /dev/null +++ b/sjs/backend/sjs/ScopedVar.scala @@ -0,0 +1,38 @@ +package dotty.tools.backend.sjs + +import language.implicitConversions + +class ScopedVar[A](init: A) { + import ScopedVar.Assignment + + private var value = init + + def this()(implicit ev: Null <:< A) = this(ev(null)) + + def get: A = value + def :=(newValue: A): Assignment[A] = new Assignment(this, newValue) +} + +object ScopedVar { + class Assignment[T](scVar: ScopedVar[T], value: T) { + private[ScopedVar] def push(): AssignmentStackElement[T] = { + val stack = new AssignmentStackElement(scVar, scVar.value) + scVar.value = value + stack + } + } + + private class AssignmentStackElement[T](scVar: ScopedVar[T], oldValue: T) { + private[ScopedVar] def pop(): Unit = { + scVar.value = oldValue + } + } + + implicit def toValue[T](scVar: ScopedVar[T]): T = scVar.get + + def withScopedVars[T](ass: Assignment[_]*)(body: => T): T = { + val stack = ass.map(_.push()) + try body + finally stack.reverse.foreach(_.pop()) + } +} diff --git a/sjs/tools/dotc/config/SJSPlatform.scala b/sjs/tools/dotc/config/SJSPlatform.scala new file mode 100644 index 000000000..3ec8049ae --- /dev/null +++ b/sjs/tools/dotc/config/SJSPlatform.scala @@ -0,0 +1,18 @@ +package dotty.tools.dotc.config + +import dotty.tools.dotc.core._ +import Contexts._ +import Symbols._ + +import dotty.tools.backend.sjs.JSDefinitions + +class SJSPlatform()(implicit ctx: Context) extends JavaPlatform { + + /** Scala.js-specific definitions. */ + val jsDefinitions: JSDefinitions = new JSDefinitions() + + /** Is the SAMType `cls` also a SAM under the rules of the Scala.js back-end? */ + override def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean = + defn.isFunctionClass(cls) || jsDefinitions.isJSFunctionClass(cls) + +} diff --git a/src/dotty/tools/backend/sjs/GenSJSIR.scala b/src/dotty/tools/backend/sjs/GenSJSIR.scala deleted file mode 100644 index 819a8f0e3..000000000 --- a/src/dotty/tools/backend/sjs/GenSJSIR.scala +++ /dev/null @@ -1,14 +0,0 @@ -package dotty.tools.backend.sjs - -import dotty.tools.dotc.core._ -import Contexts._ -import Phases._ - -/** Generates Scala.js IR files for the compilation unit. */ -class GenSJSIR extends Phase { - def phaseName: String = "genSJSIR" - - def run(implicit ctx: Context): Unit = { - new JSCodeGen().run() - } -} diff --git a/src/dotty/tools/backend/sjs/JSCodeGen.scala b/src/dotty/tools/backend/sjs/JSCodeGen.scala deleted file mode 100644 index 401e01784..000000000 --- a/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ /dev/null @@ -1,2392 +0,0 @@ -package dotty.tools.backend.sjs - -import scala.annotation.switch - -import scala.collection.mutable - -import dotty.tools.FatalError - -import dotty.tools.dotc.CompilationUnit -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Phases.Phase - -import dotty.tools.dotc.core._ -import Periods._ -import SymDenotations._ -import Contexts._ -import Decorators._ -import Flags._ -import dotty.tools.dotc.ast.Trees._ -import Types._ -import Symbols._ -import Denotations._ -import Phases._ -import StdNames._ - -import dotty.tools.dotc.transform.Erasure - -import org.scalajs.core.ir -import org.scalajs.core.ir.{ClassKind, Position, Trees => js, Types => jstpe} -import js.OptimizerHints - -import JSEncoding._ -import JSInterop._ -import ScopedVar.withScopedVars - -/** Main codegen for Scala.js IR. - * - * [[GenSJSIR]] creates one instance of `JSCodeGen` per compilation unit. - * The `run()` method processes the whole compilation unit and generates - * `.sjsir` files for it. - * - * There are 4 main levels of translation: - * - * - `genCompilationUnit()` iterates through all the type definitions in the - * compilation unit. Each generated `js.ClassDef` is serialized to an - * `.sjsir` file. - * - `genScalaClass()` and other similar methods generate the skeleton of - * classes. - * - `genMethod()` and similar methods generate the declarations of methods. - * - `genStatOrExpr()` and everything else generate the bodies of methods. - */ -class JSCodeGen()(implicit ctx: Context) { - import tpd._ - - private val jsdefn = JSDefinitions.jsdefn - private val primitives = new JSPrimitives(ctx) - - private val positionConversions = new JSPositions()(ctx) - import positionConversions.{pos2irPos, implicitPos2irPos} - - // Some state -------------------------------------------------------------- - - private val currentClassSym = new ScopedVar[Symbol] - private val currentMethodSym = new ScopedVar[Symbol] - private val localNames = new ScopedVar[LocalNameGenerator] - private val thisLocalVarIdent = new ScopedVar[Option[js.Ident]] - private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]] - - /** Implicitly materializes the current local name generator. */ - private implicit def implicitLocalNames: LocalNameGenerator = localNames.get - - /* See genSuperCall() - * TODO Can we avoid this unscoped var? - */ - private var isModuleInitialized: Boolean = false - - private def currentClassType = encodeClassType(currentClassSym) - - /** Returns a new fresh local identifier. */ - private def freshLocalIdent()(implicit pos: Position): js.Ident = - localNames.get.freshLocalIdent() - - /** Returns a new fresh local identifier. */ - private def freshLocalIdent(base: String)(implicit pos: Position): js.Ident = - localNames.get.freshLocalIdent(base) - - // Compilation unit -------------------------------------------------------- - - def run(): Unit = { - genCompilationUnit(ctx.compilationUnit) - } - - /** Generates the Scala.js IR for a compilation unit - * This method iterates over all the class and interface definitions - * found in the compilation unit and emits their IR (.sjsir). - * - * Some classes are never actually emitted: - * - Classes representing primitive types - * - The scala.Array class - * - * TODO Some classes representing anonymous functions are not actually emitted. - * Instead, a temporary representation of their `apply` method is built - * and recorded, so that it can be inlined as a JavaScript anonymous - * function in the method that instantiates it. - * - * Other ClassDefs are emitted according to their nature: - * * Scala.js-defined JS class -> `genScalaJSDefinedJSClass()` - * * Other raw JS type (<: js.Any) -> `genRawJSClassData()` - * * Interface -> `genInterface()` - * * Normal class -> `genClass()` - */ - private def genCompilationUnit(cunit: CompilationUnit): Unit = { - def collectTypeDefs(tree: Tree): List[TypeDef] = { - tree match { - case EmptyTree => Nil - case PackageDef(_, stats) => stats.flatMap(collectTypeDefs) - case cd: TypeDef => cd :: Nil - case _: ValDef => Nil // module instance - } - } - val allTypeDefs = collectTypeDefs(cunit.tpdTree) - - val generatedClasses = mutable.ListBuffer.empty[(Symbol, js.ClassDef)] - - // TODO Record anonymous JS function classes - - /* Finally, we emit true code for the remaining class defs. */ - for (td <- allTypeDefs) { - val sym = td.symbol - implicit val pos: Position = sym.pos - - /* Do not actually emit code for primitive types nor scala.Array. */ - val isPrimitive = - sym.isPrimitiveValueClass || sym == defn.ArrayClass - - if (!isPrimitive) { - withScopedVars( - currentClassSym := sym - ) { - val tree = if (isJSType(sym)) { - /*assert(!isRawJSFunctionDef(sym), - s"Raw JS function def should have been recorded: $cd")*/ - if (!sym.is(Trait) && isScalaJSDefinedJSClass(sym)) - genScalaJSDefinedJSClass(td) - else - genRawJSClassData(td) - } else if (sym.is(Trait)) { - genInterface(td) - } else { - genScalaClass(td) - } - - generatedClasses += ((sym, tree)) - } - } - } - - val clDefs = generatedClasses.map(_._2).toList - - for ((sym, tree) <- generatedClasses) { - val writer = new java.io.PrintWriter(System.err) - try { - new ir.Printers.IRTreePrinter(writer).print(tree) - } finally { - writer.flush() - } - genIRFile(cunit, sym, tree) - } - } - - private def genIRFile(cunit: CompilationUnit, sym: Symbol, - tree: ir.Trees.ClassDef): Unit = { - val outfile = getFileFor(cunit, sym, ".sjsir") - val output = outfile.bufferedOutput - try { - ir.InfoSerializers.serialize(output, ir.Infos.generateClassInfo(tree)) - ir.Serializers.serialize(output, tree) - } finally { - output.close() - } - } - - private def getFileFor(cunit: CompilationUnit, sym: Symbol, - suffix: String) = { - import scala.reflect.io._ - - val outputDirectory: AbstractFile = // TODO Support virtual files - new PlainDirectory(new Directory(new java.io.File(ctx.settings.d.value))) - - val pathParts = sym.fullName.toString.split("[./]") - val dir = (outputDirectory /: pathParts.init)(_.subdirectoryNamed(_)) - - var filename = pathParts.last - if (sym.is(ModuleClass)) - filename = filename + nme.MODULE_SUFFIX.toString - - dir fileNamed (filename + suffix) - } - - // Generate a class -------------------------------------------------------- - - /** Gen the IR ClassDef for a Scala class definition (maybe a module class). - */ - private def genScalaClass(td: TypeDef): js.ClassDef = { - val sym = td.symbol.asClass - implicit val pos: Position = sym.pos - - assert(!sym.is(Trait), - "genScalaClass() must be called only for normal classes: "+sym) - assert(sym.superClass != NoSymbol, sym) - - /*if (hasDefaultCtorArgsAndRawJSModule(sym)) { - reporter.error(pos, - "Implementation restriction: constructors of " + - "Scala classes cannot have default parameters " + - "if their companion module is JS native.") - }*/ - - val classIdent = encodeClassFullNameIdent(sym) - val isHijacked = false //isHijackedBoxedClass(sym) - - // Optimizer hints - - def isStdLibClassWithAdHocInlineAnnot(sym: Symbol): Boolean = { - val fullName = sym.fullName.toString - (fullName.startsWith("scala.Tuple") && !fullName.endsWith("$")) || - (fullName.startsWith("scala.collection.mutable.ArrayOps$of")) - } - - val shouldMarkInline = ( - sym.hasAnnotation(jsdefn.InlineAnnot) || - (sym.isAnonymousFunction && !sym.isSubClass(defn.PartialFunctionClass)) || - isStdLibClassWithAdHocInlineAnnot(sym)) - - val optimizerHints = { - OptimizerHints.empty - .withInline(shouldMarkInline) - .withNoinline(sym.hasAnnotation(jsdefn.NoinlineAnnot)) - } - - // Generate members (constructor + methods) - - val generatedMethods = new mutable.ListBuffer[js.MethodDef] - val exportedSymbols = new mutable.ListBuffer[Symbol] - - val tpl = td.rhs.asInstanceOf[Template] - for (tree <- tpl.constr :: tpl.body) { - tree match { - case EmptyTree => () - - case _: ValDef => - () // fields are added via genClassFields() - - case dd: DefDef => - val sym = dd.symbol - - val isExport = false //jsInterop.isExport(sym) - val isNamedExport = false /*isExport && sym.annotations.exists( - _.symbol == JSExportNamedAnnotation)*/ - - /*if (isNamedExport) - generatedMethods += genNamedExporterDef(dd) - else*/ - generatedMethods ++= genMethod(dd) - - if (isExport) { - // We add symbols that we have to export here. This way we also - // get inherited stuff that is implemented in this class. - exportedSymbols += sym - } - - case _ => - throw new FatalError("Illegal tree in body of genScalaClass(): " + tree) - } - } - - // Generate fields and add to methods + ctors - val generatedMembers = genClassFields(td) ++ generatedMethods.toList - - // Generate the exported members, constructors and accessors - val exports = { - // Hack to export hello.world - if (sym.fullName.toString == "hello.world$") { - List( - js.ModuleExportDef("hello.world"), - js.MethodDef(static = false, js.StringLiteral("main"), - Nil, jstpe.AnyType, - js.Block(List( - js.Apply(js.This()(jstpe.ClassType(classIdent.name)), js.Ident("main__V"), Nil)(jstpe.NoType), - js.Undefined())))( - OptimizerHints.empty, None)) - } else { - /* - // Generate the exported members - val memberExports = genMemberExports(sym, exportedSymbols.toList) - - // Generate exported constructors or accessors - val exportedConstructorsOrAccessors = - if (isStaticModule(sym)) genModuleAccessorExports(sym) - else genConstructorExports(sym) - - memberExports ++ exportedConstructorsOrAccessors - */ - Nil - } - } - - // Hashed definitions of the class - val hashedDefs = - ir.Hashers.hashDefs(generatedMembers ++ exports) - - // The complete class definition - val kind = - if (isStaticModule(sym)) ClassKind.ModuleClass - else if (isHijacked) ClassKind.HijackedClass - else ClassKind.Class - - val classDefinition = js.ClassDef( - classIdent, - kind, - Some(encodeClassFullNameIdent(sym.superClass)), - genClassInterfaces(sym), - None, - hashedDefs)( - optimizerHints) - - classDefinition - } - - /** Gen the IR ClassDef for a Scala.js-defined JS class. */ - private def genScalaJSDefinedJSClass(td: TypeDef): js.ClassDef = { - ??? - } - - /** Gen the IR ClassDef for a raw JS class or trait. - */ - private def genRawJSClassData(td: TypeDef): js.ClassDef = { - val sym = td.symbol.asClass - implicit val pos: Position = sym.pos - - val classIdent = encodeClassFullNameIdent(sym) - val superClass = - if (sym.is(Trait)) None - else Some(encodeClassFullNameIdent(sym.superClass)) - val jsName = - if (sym.is(Trait) || sym.is(ModuleClass)) None - else Some(fullJSNameOf(sym)) - - js.ClassDef(classIdent, ClassKind.RawJSType, - superClass, - genClassInterfaces(sym), - jsName, - Nil)( - OptimizerHints.empty) - } - - /** Gen the IR ClassDef for an interface definition. - */ - private def genInterface(td: TypeDef): js.ClassDef = { - val sym = td.symbol.asClass - implicit val pos: Position = sym.pos - - val classIdent = encodeClassFullNameIdent(sym) - - val generatedMethods = new mutable.ListBuffer[js.MethodDef] - - val tpl = td.rhs.asInstanceOf[Template] - for (tree <- tpl.constr :: tpl.body) { - tree match { - case EmptyTree => () - case dd: DefDef => generatedMethods ++= genMethod(dd) - case _ => - throw new FatalError("Illegal tree in gen of genInterface(): " + tree) - } - } - - val superInterfaces = genClassInterfaces(sym) - - // Hashed definitions of the interface - val hashedDefs = - ir.Hashers.hashDefs(generatedMethods.toList) - - js.ClassDef(classIdent, ClassKind.Interface, None, superInterfaces, None, - hashedDefs)(OptimizerHints.empty) - } - - private def genClassInterfaces(sym: ClassSymbol)( - implicit pos: Position): List[js.Ident] = { - import dotty.tools.dotc.transform.SymUtils._ - for { - intf <- sym.directlyInheritedTraits - } yield { - encodeClassFullNameIdent(intf) - } - } - - // Generate the fields of a class ------------------------------------------ - - /** Gen definitions for the fields of a class. - */ - private def genClassFields(td: TypeDef): List[js.FieldDef] = { - val classSym = td.symbol.asClass - assert(currentClassSym.get == classSym, - "genClassFields called with a ClassDef other than the current one") - - // Non-method term members are fields - (for { - f <- classSym.info.decls - if !f.is(Method) && f.isTerm - } yield { - implicit val pos: Position = f.pos - - val name = - /*if (isExposed(f)) js.StringLiteral(jsNameOf(f)) - else*/ encodeFieldSym(f) - - val irTpe = //if (!isScalaJSDefinedJSClass(classSym)) { - toIRType(f.info) - /*} else { - val tpeEnteringPosterasure = - enteringPhase(currentRun.posterasurePhase)(f.tpe) - tpeEnteringPosterasure match { - case tpe: ErasedValueType => - /* Here, we must store the field as the boxed representation of - * the value class. The default value of that field, as - * initialized at the time the instance is created, will - * therefore be null. This will not match the behavior we would - * get in a Scala class. To match the behavior, we would need to - * initialized to an instance of the boxed representation, with - * an underlying value set to the zero of its type. However we - * cannot implement that, so we live with the discrepancy. - * Anyway, scalac also has problems with uninitialized value - * class values, if they come from a generic context. - * - * TODO Evaluate how much of this needs to be adapted for dotc, - * which unboxes `null` to the zero of their underlying. - */ - jstpe.ClassType(encodeClassFullName(tpe.valueClazz)) - - case _ if f.tpe.typeSymbol == CharClass => - /* Will be initialized to null, which will unbox to '\0' when - * read. - */ - jstpe.ClassType(ir.Definitions.BoxedCharacterClass) - - case _ => - /* Other types are not boxed, so we can initialized them to - * their true zero. - */ - toIRType(f.tpe) - } - }*/ - - js.FieldDef(name, irTpe, f.is(Mutable)) - }).toList - } - - // Generate a method ------------------------------------------------------- - - private def genMethod(dd: DefDef): Option[js.MethodDef] = { - withScopedVars( - localNames := new LocalNameGenerator - ) { - genMethodWithCurrentLocalNameScope(dd) - } - } - - /** Gen JS code for a method definition in a class or in an impl class. - * On the JS side, method names are mangled to encode the full signature - * of the Scala method, as described in `JSEncoding`, to support - * overloading. - * - * Some methods are not emitted at all: - * - Primitives, since they are never actually called - * - Constructors of hijacked classes - * - * Constructors are emitted by generating their body as a statement. - * - * Other (normal) methods are emitted with `genMethodBody()`. - */ - private def genMethodWithCurrentLocalNameScope(dd: DefDef): Option[js.MethodDef] = { - implicit val pos: Position = dd.pos - val sym = dd.symbol - val vparamss = dd.vparamss - val rhs = dd.rhs - - isModuleInitialized = false - - withScopedVars( - currentMethodSym := sym, - undefinedDefaultParams := mutable.Set.empty, - thisLocalVarIdent := None - ) { - assert(vparamss.isEmpty || vparamss.tail.isEmpty, - "Malformed parameter list: " + vparamss) - val params = if (vparamss.isEmpty) Nil else vparamss.head.map(_.symbol) - - val isJSClassConstructor = - sym.isClassConstructor && isScalaJSDefinedJSClass(currentClassSym) - - val methodName: js.PropertyName = encodeMethodSym(sym) - - def jsParams = for (param <- params) yield { - implicit val pos: Position = param.pos - js.ParamDef(encodeLocalSym(param), toIRType(param.info), - mutable = false, rest = false) - } - - /*if (primitives.isPrimitive(sym)) { - None - } else*/ if (sym.is(Deferred)) { - Some(js.MethodDef(static = false, methodName, - jsParams, toIRType(patchedResultType(sym)), js.EmptyTree)( - OptimizerHints.empty, None)) - } else /*if (isJSNativeCtorDefaultParam(sym)) { - None - } else if (sym.isClassConstructor && isHijackedBoxedClass(sym.owner)) { - None - } else*/ { - /*def isTraitImplForwarder = dd.rhs match { - case app: Apply => foreignIsImplClass(app.symbol.owner) - case _ => false - }*/ - - val shouldMarkInline = { - sym.hasAnnotation(jsdefn.InlineAnnot) || - sym.isAnonymousFunction - } - - val shouldMarkNoinline = { - sym.hasAnnotation(jsdefn.NoinlineAnnot) /*&& - !isTraitImplForwarder*/ - } - - val optimizerHints = { - OptimizerHints.empty - .withInline(shouldMarkInline) - .withNoinline(shouldMarkNoinline) - } - - val methodDef = { - /*if (isJSClassConstructor) { - val body0 = genStat(rhs) - val body1 = - if (!sym.isPrimaryConstructor) body0 - else moveAllStatementsAfterSuperConstructorCall(body0) - js.MethodDef(static = false, methodName, - jsParams, jstpe.NoType, body1)(optimizerHints, None) - } else*/ if (sym.isConstructor) { - js.MethodDef(static = false, methodName, - jsParams, jstpe.NoType, - genStat(rhs))(optimizerHints, None) - } else { - val resultIRType = toIRType(patchedResultType(sym)) - genMethodDef(static = false, methodName, - params, resultIRType, rhs, optimizerHints) - } - } - - Some(methodDef) - } - } - } - - /** Generates the MethodDef of a (non-constructor) method - * - * Most normal methods are emitted straightforwardly. If the result - * type is Unit, then the body is emitted as a statement. Otherwise, it is - * emitted as an expression. - * - * Methods Scala.js-defined JS classes are compiled as static methods taking - * an explicit parameter for their `this` value. - */ - private def genMethodDef(static: Boolean, methodName: js.PropertyName, - paramsSyms: List[Symbol], resultIRType: jstpe.Type, - tree: Tree, optimizerHints: OptimizerHints): js.MethodDef = { - implicit val pos: Position = tree.pos - - ctx.debuglog("genMethod " + methodName.name) - ctx.debuglog("") - - val jsParams = for (param <- paramsSyms) yield { - implicit val pos: Position = param.pos - js.ParamDef(encodeLocalSym(param), toIRType(param.info), - mutable = false, rest = false) - } - - def genBody() = - if (resultIRType == jstpe.NoType) genStat(tree) - else genExpr(tree) - - //if (!isScalaJSDefinedJSClass(currentClassSym)) { - js.MethodDef(static, methodName, jsParams, resultIRType, genBody())( - optimizerHints, None) - /*} else { - assert(!static, tree.pos) - - withScopedVars( - thisLocalVarIdent := Some(freshLocalIdent("this")) - ) { - val thisParamDef = js.ParamDef(thisLocalVarIdent.get.get, - jstpe.AnyType, mutable = false, rest = false) - - js.MethodDef(static = true, methodName, thisParamDef :: jsParams, - resultIRType, genBody())( - optimizerHints, None) - } - }*/ - } - - // Generate statements and expressions ------------------------------------- - - /** Gen JS code for a tree in statement position (in the IR). - */ - private def genStat(tree: Tree): js.Tree = { - exprToStat(genStatOrExpr(tree, isStat = true)) - } - - /** Turn a JavaScript expression of type Unit into a statement */ - private def exprToStat(tree: js.Tree): js.Tree = { - /* Any JavaScript expression is also a statement, but at least we get rid - * of some pure expressions that come from our own codegen. - */ - implicit val pos: Position = tree.pos - tree match { - case js.Block(stats :+ expr) => js.Block(stats :+ exprToStat(expr)) - case _:js.Literal | js.This() => js.Skip() - case _ => tree - } - } - - /** Gen JS code for a tree in expression position (in the IR). - */ - private def genExpr(tree: Tree): js.Tree = { - val result = genStatOrExpr(tree, isStat = false) - assert(result.tpe != jstpe.NoType, - s"genExpr($tree) returned a tree with type NoType at pos ${tree.pos}") - result - } - - /** Gen JS code for a tree in statement or expression position (in the IR). - * - * This is the main transformation method. Each node of the Scala AST - * is transformed into an equivalent portion of the JS AST. - */ - private def genStatOrExpr(tree: Tree, isStat: Boolean): js.Tree = { - implicit val pos: Position = tree.pos - - ctx.debuglog(" " + tree) - ctx.debuglog("") - - tree match { - /** LabelDefs (for while and do..while loops) */ - /*case lblDf: LabelDef => - genLabelDef(lblDf)*/ - - /** Local val or var declaration */ - case tree @ ValDef(name, _, _) => - /* Must have been eliminated by the tail call transform performed - * by genMethodBody(). */ - assert(name != nme.THIS, - s"ValDef(_, nme.THIS, _, _) found at ${tree.pos}") - - val sym = tree.symbol - val rhs = tree.rhs - val rhsTree = genExpr(rhs) - - rhsTree match { - case js.UndefinedParam() => - /* This is an intermediate assignment for default params on a - * js.Any. Add the symbol to the corresponding set to inform - * the Ident resolver how to replace it and don't emit the symbol. - */ - undefinedDefaultParams += sym - js.Skip() - case _ => - js.VarDef(encodeLocalSym(sym), - toIRType(sym.info), sym.is(Mutable), rhsTree) - } - - case If(cond, thenp, elsep) => - js.If(genExpr(cond), genStatOrExpr(thenp, isStat), - genStatOrExpr(elsep, isStat))(toIRType(tree.tpe)) - - case Return(expr, from) => - // TODO Need to consider `from`? - js.Return(toIRType(expr.tpe) match { - case jstpe.NoType => js.Block(genStat(expr), js.Undefined()) - case _ => genExpr(expr) - }) - - /*case t: Try => - genTry(t, isStat)*/ - - case app: Apply => - genApply(app, isStat) - - case app: TypeApply => - genTypeApply(app) - - /*case app: ApplyDynamic => - genApplyDynamic(app)*/ - - case tree: This => - if (tree.symbol == currentClassSym.get) { - genThis() - } else { - assert(tree.symbol.is(Module), - "Trying to access the this of another class: " + - "tree.symbol = " + tree.symbol + - ", class symbol = " + currentClassSym.get + - " pos:" + pos) - genLoadModule(tree.symbol) - } - - case Select(qualifier, _) => - val sym = tree.symbol - if (sym.is(Module)) { - assert(!sym.is(Package), "Cannot use package as value: " + tree) - genLoadModule(sym) - } else if (sym.is(JavaStatic)) { - genLoadStaticField(sym) - } else /*if (paramAccessorLocals contains sym) { - paramAccessorLocals(sym).ref - } else if (isScalaJSDefinedJSClass(sym.owner)) { - val genQual = genExpr(qualifier) - val boxed = if (isExposed(sym)) - js.JSBracketSelect(genQual, js.StringLiteral(jsNameOf(sym))) - else - js.JSDotSelect(genQual, encodeFieldSym(sym)) - fromAny(boxed, - enteringPhase(currentRun.posterasurePhase)(sym.tpe)) - } else*/ { - js.Select(genExpr(qualifier), - encodeFieldSym(sym))(toIRType(sym.info)) - } - - case tree: Ident => - desugarIdent(tree).fold[js.Tree] { - val sym = tree.symbol - assert(!sym.is(Package), "Cannot use package as value: " + tree) - if (sym.is(Module)) { - genLoadModule(sym) - } else if (undefinedDefaultParams.contains(sym)) { - /* This is a default parameter whose assignment was moved to - * a local variable. Put an undefined param instead. - */ - js.UndefinedParam()(toIRType(sym.info)) - } else { - js.VarRef(encodeLocalSym(sym))(toIRType(sym.info)) - } - } { select => - genStatOrExpr(select, isStat) - } - - case Literal(value) => - import Constants._ - value.tag match { - case UnitTag => - js.Skip() - case BooleanTag => - js.BooleanLiteral(value.booleanValue) - case ByteTag | ShortTag | CharTag | IntTag => - js.IntLiteral(value.intValue) - case LongTag => - js.LongLiteral(value.longValue) - case FloatTag => - js.FloatLiteral(value.floatValue) - case DoubleTag => - js.DoubleLiteral(value.doubleValue) - case StringTag => - js.StringLiteral(value.stringValue) - case NullTag => - js.Null() - case ClazzTag => - genClassConstant(value.typeValue) - /*case EnumTag => - genStaticMember(value.symbolValue)*/ - } - - case Block(stats, expr) => - js.Block(stats.map(genStat) :+ genStatOrExpr(expr, isStat)) - - case Typed(expr, _) => - expr match { - case _: Super => genThis() - case _ => genExpr(expr) - } - - case Assign(lhs0, rhs) => - val sym = lhs0.symbol - if (sym.is(JavaStaticTerm)) - throw new FatalError(s"Assignment to static member ${sym.fullName} not supported") - val genRhs = genExpr(rhs) - val lhs = lhs0 match { - case lhs: Ident => desugarIdent(lhs).getOrElse(lhs) - case lhs => lhs - } - lhs match { - case lhs: Select => - val qualifier = lhs.qualifier - - def ctorAssignment = ( - currentMethodSym.get.name == nme.CONSTRUCTOR && - currentMethodSym.get.owner == qualifier.symbol && - qualifier.isInstanceOf[This] - ) - if (!sym.is(Mutable) && !ctorAssignment) - throw new FatalError(s"Assigning to immutable field ${sym.fullName} at $pos") - - val genQual = genExpr(qualifier) - - /*if (isScalaJSDefinedJSClass(sym.owner)) { - val genLhs = if (isExposed(sym)) - js.JSBracketSelect(genQual, js.StringLiteral(jsNameOf(sym))) - else - js.JSDotSelect(genQual, encodeFieldSym(sym)) - val boxedRhs = - ensureBoxed(genRhs, - enteringPhase(currentRun.posterasurePhase)(rhs.tpe)) - js.Assign(genLhs, boxedRhs) - } else {*/ - js.Assign( - js.Select(genQual, encodeFieldSym(sym))(toIRType(sym.info)), - genRhs) - //} - case _ => - js.Assign( - js.VarRef(encodeLocalSym(sym))(toIRType(sym.info)), - genRhs) - } - - /** Array constructor */ - case javaSeqLiteral: JavaSeqLiteral => - genJavaSeqLiteral(javaSeqLiteral) - - /** A Match reaching the backend is supposed to be optimized as a switch */ - /*case mtch: Match => - genMatch(mtch, isStat)*/ - - case tree: Closure => - genClosure(tree) - - /*case EmptyTree => - js.Skip()*/ - - case _ => - throw new FatalError("Unexpected tree in genExpr: " + - tree + "/" + tree.getClass + " at: " + (tree.pos: Position)) - } - } // end of genStatOrExpr() - - // !!! DUPLICATE code with DottyBackendInterface - private def desugarIdent(i: Ident): Option[Select] = { - i.tpe match { - case TermRef(prefix: TermRef, name) => - Some(tpd.ref(prefix).select(i.symbol)) - case TermRef(prefix: ThisType, name) => - Some(tpd.This(prefix.cls).select(i.symbol)) - /*case TermRef(NoPrefix, name) => - if (i.symbol is Method) Some(This(i.symbol.topLevelClass).select(i.symbol)) // workaround #342 todo: remove after fixed - else None*/ - case _ => - None - } - } - - private def qualifierOf(fun: Tree): Tree = fun match { - case fun: Ident => - fun.tpe match { - case TermRef(prefix: TermRef, _) => tpd.ref(prefix) - case TermRef(prefix: ThisType, _) => tpd.This(prefix.cls) - } - case Select(qualifier, _) => - qualifier - case TypeApply(fun, _) => - qualifierOf(fun) - } - - /** Gen JS this of the current class. - * Normally encoded straightforwardly as a JS this. - * But must be replaced by the `thisLocalVarIdent` local variable if there - * is one. - */ - private def genThis()(implicit pos: Position): js.Tree = { - /*if (tryingToGenMethodAsJSFunction) { - throw new CancelGenMethodAsJSFunction( - "Trying to generate `this` inside the body") - }*/ - - thisLocalVarIdent.fold[js.Tree] { - js.This()(currentClassType) - } { thisLocalIdent => - js.VarRef(thisLocalIdent)(currentClassType) - } - } - - /** Gen JS code for an Apply node (method call) - * - * There's a whole bunch of varieties of Apply nodes: regular method - * calls, super calls, constructor calls, isInstanceOf/asInstanceOf, - * primitives, JS calls, etc. They are further dispatched in here. - */ - private def genApply(tree: Apply, isStat: Boolean): js.Tree = { - implicit val pos: Position = tree.pos - val args = tree.args - val sym = tree.fun.symbol - - val fun = tree.fun match { - case fun: Ident => desugarIdent(fun).getOrElse(fun) - case fun => fun - } - - fun match { - case _ if isJSDefaultParam(sym) => - js.UndefinedParam()(toIRType(sym.info.finalResultType)) - - case Select(Super(_, _), _) => - genSuperCall(tree, isStat) - - case Select(New(_), nme.CONSTRUCTOR) => - genApplyNew(tree) - - case _ => - /*if (sym.isLabel) { - genLabelApply(tree) - } else*/ if (primitives.isPrimitive(tree)) { - genPrimitiveOp(tree, isStat) - } else if (Erasure.Boxing.isBox(sym)) { - // Box a primitive value (cannot be Unit) - val arg = args.head - makePrimitiveBox(genExpr(arg), arg.tpe) - } else if (Erasure.Boxing.isUnbox(sym)) { - // Unbox a primitive value (cannot be Unit) - val arg = args.head - makePrimitiveUnbox(genExpr(arg), tree.tpe) - } else { - genNormalApply(tree, isStat) - } - } - } - - /** Gen JS code for a super call, of the form Class.super[mix].fun(args). - * - * This does not include calls defined in mixin traits, as these are - * already desugared by the 'mixin' phase. Only calls to super classes - * remain. - * - * Since a class has exactly one direct superclass, and calling a method - * two classes above the current one is invalid in Scala, the `mix` item is - * irrelevant. - */ - private def genSuperCall(tree: Apply, isStat: Boolean): js.Tree = { - implicit val pos: Position = tree.pos - val Apply(fun @ Select(sup @ Super(_, mix), _), args) = tree - val sym = fun.symbol - - if (sym == defn.Any_getClass) { - // The only primitive that is also callable as super call - js.GetClass(genThis()) - } else /*if (isScalaJSDefinedJSClass(currentClassSym)) { - genJSSuperCall(tree, isStat) - } else*/ { - val superCall = genApplyMethodStatically( - genThis()(sup.pos), sym, genActualArgs(sym, args)) - - // Initialize the module instance just after the super constructor call. - if (isStaticModule(currentClassSym) && !isModuleInitialized && - currentMethodSym.get.isClassConstructor) { - isModuleInitialized = true - val thisType = jstpe.ClassType(encodeClassFullName(currentClassSym)) - val initModule = js.StoreModule(thisType, js.This()(thisType)) - js.Block(superCall, initModule) - } else { - superCall - } - } - } - - /** Gen JS code for a constructor call (new). - * Further refined into: - * * new String(...) - * * new of a hijacked boxed class - * * new of an anonymous function class that was recorded as JS function - * * new of a raw JS class - * * new Array - * * regular new - */ - private def genApplyNew(tree: Apply): js.Tree = { - implicit val pos: Position = tree.pos - - val Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) = tree - val ctor = fun.symbol - val tpe = tpt.tpe - - assert(ctor.isClassConstructor, - "'new' call to non-constructor: " + ctor.name) - - if (tpe.isRef(defn.StringClass)) { - genNewString(ctor, genActualArgs(ctor, args)) - } else /*if (isHijackedBoxedClass(tpe.typeSymbol)) { - genNewHijackedBoxedClass(tpe.typeSymbol, ctor, args map genExpr) - } else if (translatedAnonFunctions contains tpe.typeSymbol) { - val functionMaker = translatedAnonFunctions(tpe.typeSymbol) - functionMaker(args map genExpr) - } else*/ if (isJSType(tpe.widenDealias.typeSymbol)) { - val clsSym = tpe.widenDealias.typeSymbol - if (clsSym == jsdefn.JSObjectClass && args.isEmpty) js.JSObjectConstr(Nil) - else if (clsSym == jsdefn.JSArrayClass && args.isEmpty) js.JSArrayConstr(Nil) - else js.JSNew(genLoadJSConstructor(clsSym), genActualJSArgs(ctor, args)) - } else { - toIRType(tpe) match { - case cls: jstpe.ClassType => - js.New(cls, encodeMethodSym(ctor), genActualArgs(ctor, args)) - - case other => - throw new FatalError(s"Non ClassType cannot be instantiated: $other") - } - } - } - - /** Gen JS code for a primitive method call. */ - private def genPrimitiveOp(tree: Apply, isStat: Boolean): js.Tree = { - import scala.tools.nsc.backend.ScalaPrimitives._ - - implicit val pos: Position = tree.pos - - val Apply(fun, args) = tree - val receiver = qualifierOf(fun) - - val code = primitives.getPrimitive(tree, receiver.tpe) - - if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code)) - genSimpleOp(tree, receiver :: args, code) - else if (code == CONCAT) - genStringConcat(tree, receiver, args) - else if (code == HASH) - genScalaHash(tree, receiver) - else if (isArrayOp(code)) - genArrayOp(tree, code) - else if (code == SYNCHRONIZED) - genSynchronized(tree, isStat) - else if (isCoercion(code)) - genCoercion(tree, receiver, code) - else if (code == JSPrimitives.THROW) - genThrow(tree, args) - else /*if (primitives.isJSPrimitive(code)) - genJSPrimitive(tree, receiver, args, code) - else*/ - throw new FatalError(s"Unknown primitive: ${tree.symbol.fullName} at: $pos") - } - - /** Gen JS code for a simple operation (arithmetic, logical, or comparison) */ - private def genSimpleOp(tree: Apply, args: List[Tree], code: Int): js.Tree = { - args match { - case List(arg) => genSimpleUnaryOp(tree, arg, code) - case List(lhs, rhs) => genSimpleBinaryOp(tree, lhs, rhs, code) - case _ => throw new FatalError("Incorrect arity for primitive") - } - } - - /** Gen JS code for a simple unary operation. */ - private def genSimpleUnaryOp(tree: Apply, arg: Tree, code: Int): js.Tree = { - import scala.tools.nsc.backend.ScalaPrimitives._ - - implicit val pos: Position = tree.pos - - val genArg = genExpr(arg) - val resultIRType = toIRType(tree.tpe) - - (code: @switch) match { - case POS => - genArg - - case NEG => - (resultIRType: @unchecked) match { - case jstpe.IntType => - js.BinaryOp(js.BinaryOp.Int_-, js.IntLiteral(0), genArg) - case jstpe.LongType => - js.BinaryOp(js.BinaryOp.Long_-, js.LongLiteral(0), genArg) - case jstpe.FloatType => - js.BinaryOp(js.BinaryOp.Float_-, js.FloatLiteral(0.0f), genArg) - case jstpe.DoubleType => - js.BinaryOp(js.BinaryOp.Double_-, js.DoubleLiteral(0), genArg) - } - - case NOT => - (resultIRType: @unchecked) match { - case jstpe.IntType => - js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(-1), genArg) - case jstpe.LongType => - js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(-1), genArg) - } - - case ZNOT => - js.UnaryOp(js.UnaryOp.Boolean_!, genArg) - - case _ => - throw new FatalError("Unknown unary operation code: " + code) - } - } - - /** Gen JS code for a simple binary operation. */ - private def genSimpleBinaryOp(tree: Apply, lhs: Tree, rhs: Tree, code: Int): js.Tree = { - import scala.tools.nsc.backend.ScalaPrimitives._ - import js.UnaryOp._ - - /* Codes for operation types, in an object so that they can be 'final val' - * and be used in switch-matches. - */ - object OpTypes { - final val DoubleOp = 1 - final val FloatOp = 2 - final val LongOp = 3 - final val IntOp = 4 - final val BooleanOp = 5 - final val AnyOp = 6 - } - import OpTypes._ - - implicit val pos: Position = tree.pos - - val lhsIRType = toIRType(lhs.tpe) - val rhsIRType = toIRType(rhs.tpe) - - val opType = (lhsIRType, rhsIRType) match { - case (jstpe.DoubleType, _) | (_, jstpe.DoubleType) => DoubleOp - case (jstpe.FloatType, _) | (_, jstpe.FloatType) => FloatOp - case (jstpe.LongType, _) | (_, jstpe.LongType) => LongOp - case (jstpe.IntType, _) | (_, jstpe.IntType) => IntOp - case (jstpe.BooleanType, jstpe.BooleanType) => BooleanOp - case _ => AnyOp - } - - if (opType == AnyOp && isUniversalEqualityOp(code)) { - genUniversalEqualityOp(lhs, rhs, code) - } else if (code == ZOR) { - js.If(genExpr(lhs), js.BooleanLiteral(true), genExpr(rhs))(jstpe.BooleanType) - } else if (code == ZAND) { - js.If(genExpr(lhs), genExpr(rhs), js.BooleanLiteral(false))(jstpe.BooleanType) - } else { - import js.BinaryOp._ - - def coerce(tree: js.Tree, opType: Int): js.Tree = (opType: @switch) match { - case DoubleOp => - if (tree.tpe == jstpe.LongType) js.UnaryOp(LongToDouble, tree) - else tree - - case FloatOp => - if (tree.tpe == jstpe.FloatType || tree.tpe == jstpe.IntType) tree - else js.UnaryOp(DoubleToFloat, coerce(tree, DoubleOp)) - - case LongOp => - if (tree.tpe == jstpe.LongType) tree - else { - assert(tree.tpe == jstpe.IntType) - js.UnaryOp(IntToLong, tree) - } - - case IntOp => - if (tree.tpe == jstpe.IntType) tree - else { - assert(tree.tpe == jstpe.LongType) - js.UnaryOp(LongToInt, tree) - } - - case BooleanOp | AnyOp => - tree - } - - val rhsOpType = code match { - case LSL | LSR | ASR => IntOp - case _ => opType - } - - val genLhs = coerce(genExpr(lhs), opType) - val genRhs = coerce(genExpr(rhs), rhsOpType) - - val op = (opType: @switch) match { - case IntOp => - (code: @switch) match { - case ADD => Int_+ - case SUB => Int_- - case MUL => Int_* - case DIV => Int_/ - case MOD => Int_% - case OR => Int_| - case AND => Int_& - case XOR => Int_^ - case LSL => Int_<< - case LSR => Int_>>> - case ASR => Int_>> - - case EQ => Num_== - case NE => Num_!= - case LT => Num_< - case LE => Num_<= - case GT => Num_> - case GE => Num_>= - } - - case FloatOp => - (code: @switch) match { - case ADD => Float_+ - case SUB => Float_- - case MUL => Float_* - case DIV => Float_/ - case MOD => Float_% - - case EQ => Num_== - case NE => Num_!= - case LT => Num_< - case LE => Num_<= - case GT => Num_> - case GE => Num_>= - } - - case DoubleOp => - (code: @switch) match { - case ADD => Double_+ - case SUB => Double_- - case MUL => Double_* - case DIV => Double_/ - case MOD => Double_% - - case EQ => Num_== - case NE => Num_!= - case LT => Num_< - case LE => Num_<= - case GT => Num_> - case GE => Num_>= - } - - case LongOp => - (code: @switch) match { - case ADD => Long_+ - case SUB => Long_- - case MUL => Long_* - case DIV => Long_/ - case MOD => Long_% - case OR => Long_| - case XOR => Long_^ - case AND => Long_& - case LSL => Long_<< - case LSR => Long_>>> - case ASR => Long_>> - - case EQ => Long_== - case NE => Long_!= - case LT => Long_< - case LE => Long_<= - case GT => Long_> - case GE => Long_>= - } - - case BooleanOp => - (code: @switch) match { - case EQ => Boolean_== - case NE => Boolean_!= - case OR => Boolean_| - case AND => Boolean_& - case XOR => Boolean_!= - } - - case AnyOp => - /* No @switch because some 2.11 version erroneously report a warning - * for switches with less than 3 non-default cases. - */ - code match { - case ID => === - case NI => !== - } - } - - js.BinaryOp(op, genLhs, genRhs) - } - } - - /** Gen JS code for a universal equality test. */ - private def genUniversalEqualityOp(lhs: Tree, rhs: Tree, code: Int)( - implicit pos: Position): js.Tree = { - - import scala.tools.nsc.backend.ScalaPrimitives._ - - val genLhs = genExpr(lhs) - val genRhs = genExpr(rhs) - - val bypassEqEq = { - // Do not call equals if we have a literal null at either side. - genLhs.isInstanceOf[js.Null] || - genRhs.isInstanceOf[js.Null] - } - - if (bypassEqEq) { - js.BinaryOp( - if (code == EQ) js.BinaryOp.=== else js.BinaryOp.!==, - genLhs, genRhs) - } else { - val body = genEqEqPrimitive(lhs.tpe, rhs.tpe, genLhs, genRhs) - if (code == EQ) body - else js.UnaryOp(js.UnaryOp.Boolean_!, body) - } - } - - private lazy val externalEqualsNumNum: Symbol = - defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumNum) - private lazy val externalEqualsNumChar: Symbol = - NoSymbol // ctx.requiredMethod(BoxesRunTimeTypeRef, nme.equalsNumChar) // this method is private - private lazy val externalEqualsNumObject: Symbol = - defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumObject) - private lazy val externalEquals: Symbol = - defn.BoxesRunTimeClass.info.decl(nme.equals_).suchThat(toDenot(_).info.firstParamTypes.size == 2).symbol - - /** Gen JS code for a call to Any.== */ - private def genEqEqPrimitive(ltpe: Type, rtpe: Type, lsrc: js.Tree, rsrc: js.Tree)( - implicit pos: Position): js.Tree = { - ctx.debuglog(s"$ltpe == $rtpe") - val lsym = ltpe.widenDealias.typeSymbol.asClass - val rsym = rtpe.widenDealias.typeSymbol.asClass - - /* True if the equality comparison is between values that require the - * use of the rich equality comparator - * (scala.runtime.BoxesRunTime.equals). - * This is the case when either side of the comparison might have a - * run-time type subtype of java.lang.Number or java.lang.Character, - * **which includes when either is a JS type**. - * When it is statically known that both sides are equal and subtypes of - * Number or Character, not using the rich equality is possible (their - * own equals method will do ok.) - */ - val mustUseAnyComparator: Boolean = { - isJSType(lsym) || isJSType(rsym) || { - val p = ctx.platform - val areSameFinals = lsym.is(Final) && rsym.is(Final) && (ltpe =:= rtpe) - !areSameFinals && p.isMaybeBoxed(lsym) && p.isMaybeBoxed(rsym) - } - } - - if (mustUseAnyComparator) { - val equalsMethod: Symbol = { - // scalastyle:off line.size.limit - val ptfm = ctx.platform - if (lsym.derivesFrom(defn.BoxedNumberClass)) { - if (rsym.derivesFrom(defn.BoxedNumberClass)) externalEqualsNumNum - else if (rsym.derivesFrom(defn.BoxedCharClass)) externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030 - else externalEqualsNumObject - } else externalEquals - // scalastyle:on line.size.limit - } - genModuleApplyMethod(equalsMethod, List(lsrc, rsrc)) - } else { - // if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc) - if (lsym == defn.StringClass) { - // String.equals(that) === (this eq that) - js.BinaryOp(js.BinaryOp.===, lsrc, rsrc) - } else { - /* This requires to evaluate both operands in local values first. - * The optimizer will eliminate them if possible. - */ - val ltemp = js.VarDef(freshLocalIdent(), lsrc.tpe, mutable = false, lsrc) - val rtemp = js.VarDef(freshLocalIdent(), rsrc.tpe, mutable = false, rsrc) - js.Block( - ltemp, - rtemp, - js.If(js.BinaryOp(js.BinaryOp.===, ltemp.ref, js.Null()), - js.BinaryOp(js.BinaryOp.===, rtemp.ref, js.Null()), - genApplyMethod(ltemp.ref, defn.Any_equals, List(rtemp.ref)))( - jstpe.BooleanType)) - } - } - } - - /** Gen JS code for string concatenation. - */ - private def genStringConcat(tree: Apply, receiver: Tree, - args: List[Tree]): js.Tree = { - implicit val pos: Position = tree.pos - - val arg = args.head - - /* Primitive number types such as scala.Int have a - * def +(s: String): String - * method, which is why we have to box the lhs sometimes. - * Otherwise, both lhs and rhs are already reference types (Any or String) - * so boxing is not necessary (in particular, rhs is never a primitive). - */ - assert(!isPrimitiveValueType(receiver.tpe) || arg.tpe.isRef(defn.StringClass)) - assert(!isPrimitiveValueType(arg.tpe)) - - val genLhs = { - val genLhs0 = genExpr(receiver) - // Box the receiver if it is a primitive value - if (!isPrimitiveValueType(receiver.tpe)) genLhs0 - else makePrimitiveBox(genLhs0, receiver.tpe) - } - - val genRhs = genExpr(arg) - - js.BinaryOp(js.BinaryOp.String_+, genLhs, genRhs) - } - - /** Gen JS code for a call to Any.## */ - private def genScalaHash(tree: Apply, receiver: Tree): js.Tree = { - implicit val pos: Position = tree.pos - - genModuleApplyMethod(defn.ScalaRuntimeModule.requiredMethod(nme.hash_), - List(genExpr(receiver))) - } - - /** Gen JS code for an array operation (get, set or length) */ - private def genArrayOp(tree: Tree, code: Int): js.Tree = { - import scala.tools.nsc.backend.ScalaPrimitives._ - - implicit val pos: Position = tree.pos - - val Apply(fun, args) = tree - val arrayObj = qualifierOf(fun) - - val genArray = genExpr(arrayObj) - val genArgs = args.map(genExpr) - - def elementType: Type = arrayObj.tpe.widenDealias match { - case defn.ArrayOf(el) => el - case JavaArrayType(el) => el - case tpe => - ctx.error(s"expected Array $tpe") - ErrorType - } - - def genSelect(): js.Tree = - js.ArraySelect(genArray, genArgs(0))(toIRType(elementType)) - - if (isArrayGet(code)) { - // get an item of the array - assert(args.length == 1, - s"Array get requires 1 argument, found ${args.length} in $tree") - genSelect() - } else if (isArraySet(code)) { - // set an item of the array - assert(args.length == 2, - s"Array set requires 2 arguments, found ${args.length} in $tree") - js.Assign(genSelect(), genArgs(1)) - } else { - // length of the array - js.ArrayLength(genArray) - } - } - - /** Gen JS code for a call to AnyRef.synchronized */ - private def genSynchronized(tree: Apply, isStat: Boolean): js.Tree = { - /* JavaScript is single-threaded, so we can drop the - * synchronization altogether. - */ - val Apply(fun, List(arg)) = tree - val receiver = qualifierOf(fun) - - val genReceiver = genExpr(receiver) - val genArg = genStatOrExpr(arg, isStat) - - genReceiver match { - case js.This() => - // common case for which there is no side-effect nor NPE - genArg - case _ => - implicit val pos: Position = tree.pos - /* TODO Check for a null receiver? - * In theory, it's UB, but that decision should be left for link time. - */ - js.Block(genReceiver, genArg) - } - } - - /** Gen JS code for a coercion */ - private def genCoercion(tree: Apply, receiver: Tree, code: Int): js.Tree = { - import scala.tools.nsc.backend.ScalaPrimitives._ - - implicit val pos: Position = tree.pos - - val source = genExpr(receiver) - - def source2int = (code: @switch) match { - case F2C | D2C | F2B | D2B | F2S | D2S | F2I | D2I => - js.UnaryOp(js.UnaryOp.DoubleToInt, source) - case L2C | L2B | L2S | L2I => - js.UnaryOp(js.UnaryOp.LongToInt, source) - case _ => - source - } - - (code: @switch) match { - // To Char, need to crop at unsigned 16-bit - case B2C | S2C | I2C | L2C | F2C | D2C => - js.BinaryOp(js.BinaryOp.Int_&, source2int, js.IntLiteral(0xffff)) - - // To Byte, need to crop at signed 8-bit - case C2B | S2B | I2B | L2B | F2B | D2B => - // note: & 0xff would not work because of negative values - js.BinaryOp(js.BinaryOp.Int_>>, - js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(24)), - js.IntLiteral(24)) - - // To Short, need to crop at signed 16-bit - case C2S | I2S | L2S | F2S | D2S => - // note: & 0xffff would not work because of negative values - js.BinaryOp(js.BinaryOp.Int_>>, - js.BinaryOp(js.BinaryOp.Int_<<, source2int, js.IntLiteral(16)), - js.IntLiteral(16)) - - // To Int, need to crop at signed 32-bit - case L2I | F2I | D2I => - source2int - - // Any int to Long - case C2L | B2L | S2L | I2L => - js.UnaryOp(js.UnaryOp.IntToLong, source) - - // Any double to Long - case F2L | D2L => - js.UnaryOp(js.UnaryOp.DoubleToLong, source) - - // Long to Double - case L2D => - js.UnaryOp(js.UnaryOp.LongToDouble, source) - - // Any int, or Double, to Float - case C2F | B2F | S2F | I2F | D2F => - js.UnaryOp(js.UnaryOp.DoubleToFloat, source) - - // Long to Float === Long to Double to Float - case L2F => - js.UnaryOp(js.UnaryOp.DoubleToFloat, - js.UnaryOp(js.UnaryOp.LongToDouble, source)) - - // Identities and IR upcasts - case C2C | B2B | S2S | I2I | L2L | F2F | D2D | - C2I | C2D | - B2S | B2I | B2D | - S2I | S2D | - I2D | - F2D => - source - } - } - - /** Gen a call to the special `throw` method. */ - private def genThrow(tree: Apply, args: List[Tree]): js.Tree = { - implicit val pos: Position = tree.pos - val exception = args.head - val genException = genExpr(exception) - js.Throw { - if (exception.tpe.widenDealias.typeSymbol.derivesFrom(jsdefn.JavaScriptExceptionClass)) { - genModuleApplyMethod( - jsdefn.RuntimePackage_unwrapJavaScriptException, - List(genException)) - } else { - genException - } - } - } - - /** Gen a "normal" apply (to a true method). - * - * But even these are further refined into: - * * Methods of java.lang.String, which are redirected to the - * RuntimeString trait implementation. - * * Calls to methods of raw JS types (Scala.js -> JS interop) - * * Calls to methods in impl classes of Scala2 traits. - * * Regular method call - */ - private def genNormalApply(tree: Apply, isStat: Boolean): js.Tree = { - implicit val pos: Position = tree.pos - - val fun = tree.fun match { - case fun: Ident => desugarIdent(fun).get - case fun: Select => fun - } - val receiver = fun.qualifier - val args = tree.args - val sym = fun.symbol - - def isStringMethodFromObject: Boolean = sym.name match { - case nme.toString_ | nme.equals_ | nme.hashCode_ => true - case _ => false - } - - if (sym.owner == defn.StringClass && !isStringMethodFromObject) { - genApplyMethodOfString(genExpr(receiver), sym, genActualArgs(sym, args)) - } else if (isJSType(sym.owner)) { - //if (!isScalaJSDefinedJSClass(sym.owner) || isExposed(sym)) - genApplyJSMethodGeneric(tree, sym, genExpr(receiver), genActualJSArgs(sym, args), isStat) - /*else - genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args))*/ - } else if (foreignIsImplClass(sym.owner)) { - genTraitImplApply(sym, args.map(genExpr)) - } else if (sym.isClassConstructor) { - // Calls to constructors are always statically linked - genApplyMethodStatically(genExpr(receiver), sym, genActualArgs(sym, args)) - } else { - genApplyMethod(genExpr(receiver), sym, genActualArgs(sym, args)) - } - } - - /** Gen JS code for a call to a JS method (of a subclass of `js.Any`). - * - * Basically it boils down to calling the method as a `JSBracketSelect`, - * without name mangling. But other aspects come into play: - * - * - Operator methods are translated to JS operators (not method calls) - * - `apply` is translated as a function call, i.e., `o()` instead of `o.apply()` - * - Scala varargs are turned into JS varargs (see `genPrimitiveJSArgs()`) - * - Getters and parameterless methods are translated as `JSBracketSelect` - * - Setters are translated to `Assign` to `JSBracketSelect` - */ - private def genApplyJSMethodGeneric(tree: Tree, sym: Symbol, - receiver: js.Tree, args: List[js.Tree], isStat: Boolean, - superIn: Option[Symbol] = None)( - implicit pos: Position): js.Tree = { - - implicit val pos: Position = tree.pos - - def noSpread = !args.exists(_.isInstanceOf[js.JSSpread]) - val argc = args.size // meaningful only for methods that don't have varargs - - def requireNotSuper(): Unit = { - if (superIn.isDefined) - ctx.error("Illegal super call in Scala.js-defined JS class", tree.pos) - } - - def hasExplicitJSEncoding = { - sym.hasAnnotation(jsdefn.JSNameAnnot) || - sym.hasAnnotation(jsdefn.JSBracketAccessAnnot) || - sym.hasAnnotation(jsdefn.JSBracketCallAnnot) - } - - val boxedResult = sym.name match { - case JSUnaryOpMethodName(code) if argc == 0 => - requireNotSuper() - js.JSUnaryOp(code, receiver) - - case JSBinaryOpMethodName(code) if argc == 1 => - requireNotSuper() - js.JSBinaryOp(code, receiver, args.head) - - case nme.apply if !hasExplicitJSEncoding => - requireNotSuper() - if (jsdefn.isJSThisFunctionClass(sym.owner)) - js.JSBracketMethodApply(receiver, js.StringLiteral("call"), args) - else - js.JSFunctionApply(receiver, args) - - case _ => - def jsFunName = js.StringLiteral(jsNameOf(sym)) - - def genSuperReference(propName: js.Tree): js.Tree = { - superIn.fold[js.Tree] { - js.JSBracketSelect(receiver, propName) - } { superInSym => - js.JSSuperBracketSelect( - jstpe.ClassType(encodeClassFullName(superInSym)), - receiver, propName) - } - } - - def genSelectGet(propName: js.Tree): js.Tree = - genSuperReference(propName) - - def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree = - js.Assign(genSuperReference(propName), value) - - def genCall(methodName: js.Tree, args: List[js.Tree]): js.Tree = { - superIn.fold[js.Tree] { - js.JSBracketMethodApply( - receiver, methodName, args) - } { superInSym => - js.JSSuperBracketCall( - jstpe.ClassType(encodeClassFullName(superInSym)), - receiver, methodName, args) - } - } - - if (isJSGetter(sym)) { - assert(noSpread && argc == 0) - genSelectGet(jsFunName) - } else if (isJSSetter(sym)) { - assert(noSpread && argc == 1) - genSelectSet(jsFunName, args.head) - } else if (isJSBracketAccess(sym)) { - assert(noSpread && (argc == 1 || argc == 2), - s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments") - args match { - case List(keyArg) => - genSelectGet(keyArg) - case List(keyArg, valueArg) => - genSelectSet(keyArg, valueArg) - } - } else if (isJSBracketCall(sym)) { - val (methodName, actualArgs) = extractFirstArg(args) - genCall(methodName, actualArgs) - } else { - genCall(jsFunName, args) - } - } - - if (isStat) { - boxedResult - } else { - val tpe = ctx.atPhase(ctx.elimErasedValueTypePhase) { implicit ctx => - sym.info.finalResultType - } - unbox(boxedResult, tpe) - } - } - - private object JSUnaryOpMethodName { - private val map = Map( - nme.UNARY_+ -> js.JSUnaryOp.+, - nme.UNARY_- -> js.JSUnaryOp.-, - nme.UNARY_~ -> js.JSUnaryOp.~, - nme.UNARY_! -> js.JSUnaryOp.! - ) - - def unapply(name: Names.TermName): Option[js.JSUnaryOp.Code] = - map.get(name) - } - - private object JSBinaryOpMethodName { - private val map = Map( - nme.ADD -> js.JSBinaryOp.+, - nme.SUB -> js.JSBinaryOp.-, - nme.MUL -> js.JSBinaryOp.*, - nme.DIV -> js.JSBinaryOp./, - nme.MOD -> js.JSBinaryOp.%, - - nme.LSL -> js.JSBinaryOp.<<, - nme.ASR -> js.JSBinaryOp.>>, - nme.LSR -> js.JSBinaryOp.>>>, - nme.OR -> js.JSBinaryOp.|, - nme.AND -> js.JSBinaryOp.&, - nme.XOR -> js.JSBinaryOp.^, - - nme.LT -> js.JSBinaryOp.<, - nme.LE -> js.JSBinaryOp.<=, - nme.GT -> js.JSBinaryOp.>, - nme.GE -> js.JSBinaryOp.>=, - - nme.ZAND -> js.JSBinaryOp.&&, - nme.ZOR -> js.JSBinaryOp.|| - ) - - def unapply(name: Names.TermName): Option[js.JSBinaryOp.Code] = - map.get(name) - } - - /** Extract the first argument in a list of actual arguments. - * - * This is nothing else than decomposing into head and tail, except that - * we assert that the first element is not a JSSpread. - */ - private def extractFirstArg(args: List[js.Tree]): (js.Tree, List[js.Tree]) = { - assert(args.nonEmpty, - "Trying to extract the first argument of an empty argument list") - val firstArg = args.head - assert(!firstArg.isInstanceOf[js.JSSpread], - "Trying to extract the first argument of an argument list starting " + - "with a Spread argument: " + firstArg) - (firstArg, args.tail) - } - - /** Gen JS code for a call to a polymorphic method. - * - * The only methods that reach the back-end as polymorphic are - * `isInstanceOf` and `asInstanceOf`. - * - * (Well, in fact `DottyRunTime.newRefArray` too, but it is handled as a - * primitive instead.) - */ - private def genTypeApply(tree: TypeApply): js.Tree = { - implicit val pos: Position = tree.pos - - val TypeApply(fun, targs) = tree - - val sym = fun.symbol - val receiver = qualifierOf(fun) - - val to = targs.head.tpe - - assert(!isPrimitiveValueType(receiver.tpe), - s"Found receiver of type test with primitive type ${receiver.tpe} at $pos") - assert(!isPrimitiveValueType(to), - s"Found target type of type test with primitive type ${receiver.tpe} at $pos") - - val genReceiver = genExpr(receiver) - - if (sym == defn.Any_asInstanceOf) { - genAsInstanceOf(genReceiver, to) - } else if (sym == defn.Any_isInstanceOf) { - genIsInstanceOf(tree, genReceiver, to) - } else { - throw new FatalError( - s"Unexpected type application $fun with symbol ${sym.fullName}") - } - } - - /** Gen JS code for a Java Seq literal. */ - private def genJavaSeqLiteral(tree: JavaSeqLiteral): js.Tree = { - implicit val pos: Position = tree.pos - - val genElems = tree.elems.map(genExpr) - val arrayType = toReferenceType(tree.tpe).asInstanceOf[jstpe.ArrayType] - js.ArrayValue(arrayType, genElems) - } - - /** Gen JS code for a closure. - * - * Input: a `Closure` tree of the form - * {{{ - * Closure(env, call, functionalInterface) - * }}} - * representing the pseudo-syntax - * {{{ - * { (p1, ..., pm) => call(env1, ..., envn, p1, ..., pm) }: functionInterface - * }}} - * where `envi` are identifiers in the local scope. The qualifier of `call` - * is also implicitly captured. - * - * Output: a `js.Closure` tree of the form - * {{{ - * js.Closure(formalCaptures, formalParams, body, actualCaptures) - * }}} - * representing the pseudo-syntax - * {{{ - * lambda( - * formalParam1, ..., formalParamM) = body - * }}} - * where the `actualCaptures` and `body` are, in general, arbitrary - * expressions. But in this case, `actualCaptures` will be identifiers from - * `env`, and the `body` will be of the form - * {{{ - * call(formalCapture1.ref, ..., formalCaptureN.ref, - * formalParam1.ref, ...formalParamM.ref) - * }}} - * - * When the `js.Closure` node is evaluated, i.e., when the closure value is - * created, the expressions of the `actualCaptures` are evaluated, and the - * results of those evaluations is "stored" in the environment of the - * closure as the corresponding `formalCapture`. - * - * When we later *call* the closure, the `formalCaptures` already have their - * values from the environment, and they are available in the `body`. The - * `formalParams` of the created closure receive their values from the - * actual arguments at the call-site of the closure, and they are also - * available in the `body`. - */ - private def genClosure(tree: Closure): js.Tree = { - implicit val pos: Position = tree.pos - val Closure(env, call, functionalInterface) = tree - - val envSize = env.size - - val (fun, args) = call match { - // case Apply(fun, args) => (fun, args) // Conjectured not to happen - case t @ Select(_, _) => (t, Nil) - case t @ Ident(_) => (t, Nil) - } - val sym = fun.symbol - - val qualifier = qualifierOf(fun) - val allCaptureValues = qualifier :: env - - val (formalCaptures, actualCaptures) = allCaptureValues.map { value => - implicit val pos: Position = value.pos - val formalIdent = value match { - case Ident(name) => freshLocalIdent(name.toString) - case This(_) => freshLocalIdent("this") - case _ => freshLocalIdent() - } - val formalCapture = - js.ParamDef(formalIdent, toIRType(value.tpe), mutable = false, rest = false) - val actualCapture = genExpr(value) - (formalCapture, actualCapture) - }.unzip - - val formalParamNames = sym.info.paramNamess.flatten.drop(envSize) - val formalParamTypes = sym.info.paramTypess.flatten.drop(envSize) - val (formalParams, actualParams) = formalParamNames.zip(formalParamTypes).map { - case (name, tpe) => - val formalParam = js.ParamDef(freshLocalIdent(name.toString), - jstpe.AnyType, mutable = false, rest = false) - val actualParam = unbox(formalParam.ref, tpe) - (formalParam, actualParam) - }.unzip - - val genBody = { - val thisCaptureRef :: argCaptureRefs = formalCaptures.map(_.ref) - val call = genApplyMethod(thisCaptureRef, sym, argCaptureRefs ::: actualParams) - box(call, sym.info.finalResultType) - } - - val closure = js.Closure(formalCaptures, formalParams, genBody, actualCaptures) - ctx.debuglog(closure.toString) - - val funInterfaceSym = functionalInterface.tpe.widenDealias.typeSymbol - if (jsdefn.isJSFunctionClass(funInterfaceSym)) { - closure - } else { - assert(!funInterfaceSym.exists || defn.isFunctionClass(funInterfaceSym), - s"Invalid functional interface $funInterfaceSym reached the back-end") - val cls = "sjsr_AnonFunction" + formalParams.size - val ctor = js.Ident("init___sjs_js_Function" + formalParams.size) - js.New(jstpe.ClassType(cls), ctor, List(closure)) - } - } - - /** Boxes a value of the given type before `elimErasedValueType`. - * - * This should be used when sending values to a JavaScript context, which - * is erased/boxed at the IR level, although it is not erased at the - * dotty/JVM level. - * - * @param expr Tree to be boxed if needed. - * @param tpeEnteringElimErasedValueType The type of `expr` as it was - * entering the `elimErasedValueType` phase. - */ - private def box(expr: js.Tree, tpeEnteringElimErasedValueType: Type)( - implicit pos: Position): js.Tree = { - - tpeEnteringElimErasedValueType match { - case tpe if isPrimitiveValueType(tpe) => - makePrimitiveBox(expr, tpe) - - /*case tpe: ErasedValueType => - val boxedClass = tpe.valueClazz - val ctor = boxedClass.primaryConstructor - genNew(boxedClass, ctor, List(expr))*/ - - case _ => - expr - } - } - - /** Unboxes a value typed as Any to the given type before `elimErasedValueType`. - * - * This should be used when receiving values from a JavaScript context, - * which is erased/boxed at the IR level, although it is not erased at the - * dotty/JVM level. - * - * @param expr Tree to be extracted. - * @param tpeEnteringElimErasedValueType The type of `expr` as it was - * entering the `elimErasedValueType` phase. - */ - private def unbox(expr: js.Tree, tpeEnteringElimErasedValueType: Type)( - implicit pos: Position): js.Tree = { - - tpeEnteringElimErasedValueType match { - case tpe if isPrimitiveValueType(tpe) => - makePrimitiveUnbox(expr, tpe) - - /*case tpe: ErasedValueType => - val boxedClass = tpe.valueClazz - val unboxMethod = boxedClass.derivedValueClassUnbox - val content = genApplyMethod( - genAsInstanceOf(expr, tpe), unboxMethod, Nil) - if (unboxMethod.tpe.resultType <:< tpe.erasedUnderlying) - content - else - fromAny(content, tpe.erasedUnderlying)*/ - - case tpe => - genAsInstanceOf(expr, tpe) - } - } - - /** Gen JS code for an asInstanceOf cast (for reference types only) */ - private def genAsInstanceOf(value: js.Tree, to: Type)( - implicit pos: Position): js.Tree = { - - val sym = to.widenDealias.typeSymbol - - if (sym == defn.ObjectClass || isJSType(sym)) { - /* asInstanceOf[Object] always succeeds, and - * asInstanceOf to a raw JS type is completely erased. - */ - value - } else { - js.AsInstanceOf(value, toReferenceType(to)) - } - } - - /** Gen JS code for an isInstanceOf test (for reference types only) */ - private def genIsInstanceOf(tree: Tree, value: js.Tree, to: Type): js.Tree = { - implicit val pos: Position = tree.pos - val sym = to.widenDealias.typeSymbol - - if (sym == defn.ObjectClass) { - js.BinaryOp(js.BinaryOp.!==, value, js.Null()) - } else if (isJSType(sym)) { - if (sym.is(Trait)) { - ctx.error( - s"isInstanceOf[${sym.fullName}] not supported because it is a JS trait", - tree.pos) - js.BooleanLiteral(true) - } else { - js.Unbox(js.JSBinaryOp( - js.JSBinaryOp.instanceof, value, genLoadJSConstructor(sym)), 'Z') - } - } else { - js.IsInstanceOf(value, toReferenceType(to)) - } - } - - /** Gen a dynamically linked call to a Scala method. */ - private def genApplyMethod(receiver: js.Tree, - methodSym: Symbol, arguments: List[js.Tree])( - implicit pos: Position): js.Tree = { - js.Apply(receiver, encodeMethodSym(methodSym), arguments)( - toIRType(patchedResultType(methodSym))) - } - - /** Gen a statically linked call to an instance method. */ - private def genApplyMethodStatically(receiver: js.Tree, method: Symbol, - arguments: List[js.Tree])(implicit pos: Position): js.Tree = { - val className = encodeClassFullName(method.owner) - val methodIdent = encodeMethodSym(method) - val resultType = toIRType(patchedResultType(method)) - js.ApplyStatically(receiver, jstpe.ClassType(className), - methodIdent, arguments)(resultType) - } - - /** Gen a call to a static method. */ - private def genApplyStatic(method: Symbol, arguments: List[js.Tree])( - implicit pos: Position): js.Tree = { - val cls = jstpe.ClassType(encodeClassFullName(method.owner)) - val methodIdent = encodeMethodSym(method) - js.ApplyStatic(cls, methodIdent, arguments)( - toIRType(patchedResultType(method))) - } - - /** Gen a call to a Scala2 impl class method. */ - private def genTraitImplApply(method: Symbol, arguments: List[js.Tree])( - implicit pos: Position): js.Tree = { - genApplyStatic(method, arguments) - } - - /** Gen a call to a non-exposed method of a non-native JS class. */ - private def genApplyJSClassMethod(receiver: js.Tree, method: Symbol, - arguments: List[js.Tree])(implicit pos: Position): js.Tree = { - genApplyStatic(method, receiver :: arguments) - } - - /** Gen a call to a method of a Scala top-level module. */ - private def genModuleApplyMethod(methodSym: Symbol, arguments: List[js.Tree])( - implicit pos: Position): js.Tree = { - genApplyMethod(genLoadModule(methodSym.owner), methodSym, arguments) - } - - /** Gen JS code for `new java.lang.String(...)`. - * - * Rewires the instantiation to calling the appropriate overload of - * `newString` in the object `scala.scalajs.runtime.RuntimeString`. - */ - private def genNewString(ctor: Symbol, arguments: List[js.Tree])( - implicit pos: Position): js.Tree = { - js.Apply( - genLoadModule(jsdefn.RuntimeStringModuleClass), - encodeRTStringCtorSym(ctor), arguments)( - jstpe.ClassType(ir.Definitions.StringClass)) - } - - /** Gen a dynamically linked call to a method of java.lang.String. - * - * Forwards the call to the module scala.scalajs.runtime.RuntimeString. - */ - private def genApplyMethodOfString(receiver: js.Tree, - methodSym: Symbol, arguments: List[js.Tree])( - implicit pos: Position): js.Tree = { - js.Apply( - genLoadModule(jsdefn.RuntimeStringModuleClass), - encodeRTStringMethodSym(methodSym), - receiver :: arguments)( - toIRType(patchedResultType(methodSym))) - } - - /** Gen a boxing operation (tpe is the primitive type) */ - private def makePrimitiveBox(expr: js.Tree, tpe: Type)( - implicit pos: Position): js.Tree = { - toReferenceType(tpe) match { - case jstpe.ClassType(cls) if ir.Definitions.isPrimitiveClass(cls) => - assert(cls.length == 1) - (cls.charAt(0): @switch) match { - case 'V' => - // must be handled at least for JS interop - js.Block(expr, js.Undefined()) - case 'C' => - genModuleApplyMethod(jsdefn.BoxesRunTime_boxToCharacter, List(expr)) - case _ => - expr // box is identity for all non-Char types - } - - case _ => - throw new FatalError( - s"makePrimitiveBox requires a primitive type, found $tpe at $pos") - } - } - - /** Gen an unboxing operation (tpe is the primitive type) */ - private def makePrimitiveUnbox(expr: js.Tree, tpe: Type)( - implicit pos: Position): js.Tree = { - toReferenceType(tpe) match { - case jstpe.ClassType(cls) if ir.Definitions.isPrimitiveClass(cls) => - assert(cls.length == 1) - (cls.charAt(0): @switch) match { - case 'V' => - // must be handled at least for JS interop - expr - case 'C' => - genModuleApplyMethod(jsdefn.BoxesRunTime_unboxToChar, List(expr)) - case primitiveCharCode => - js.Unbox(expr, primitiveCharCode) - } - - case _ => - throw new FatalError( - s"makePrimitiveUnbox requires a primitive type, found $tpe at $pos") - } - } - - /** Gen actual actual arguments to Scala method call. - * Returns a list of the transformed arguments. - * - * This tries to optimize repeated arguments (varargs) by turning them - * into js.WrappedArray instead of Scala wrapped arrays. - */ - private def genActualArgs(sym: Symbol, args: List[Tree])( - implicit pos: Position): List[js.Tree] = { - args.map(genExpr) - /*val wereRepeated = exitingPhase(currentRun.typerPhase) { - sym.tpe.params.map(p => isScalaRepeatedParamType(p.tpe)) - } - - if (wereRepeated.size > args.size) { - // Should not happen, but let's not crash - args.map(genExpr) - } else { - /* Arguments that are in excess compared to the type signature after - * erasure are lambda-lifted arguments. They cannot be repeated, hence - * the extension to `false`. - */ - for ((arg, wasRepeated) <- args.zipAll(wereRepeated, EmptyTree, false)) yield { - if (wasRepeated) { - tryGenRepeatedParamAsJSArray(arg, handleNil = false).fold { - genExpr(arg) - } { genArgs => - genNew(WrappedArrayClass, WrappedArray_ctor, - List(js.JSArrayConstr(genArgs))) - } - } else { - genExpr(arg) - } - } - }*/ - } - - /** Gen actual actual arguments to a JS method call. - * Returns a list of the transformed arguments. - * - * - TODO Repeated arguments (varargs) are expanded - * - Default arguments are omitted or replaced by undefined - * - All arguments are boxed - * - * Repeated arguments that cannot be expanded at compile time (i.e., if a - * Seq is passed to a varargs parameter with the syntax `seq: _*`) will be - * wrapped in a [[js.JSSpread]] node to be expanded at runtime. - */ - private def genActualJSArgs(sym: Symbol, args: List[Tree])( - implicit pos: Position): List[js.Tree] = { - - def paramNamesAndTypes(implicit ctx: Context): List[(Names.TermName, Type)] = - sym.info.paramNamess.flatten.zip(sym.info.paramTypess.flatten) - - val wereRepeated = ctx.atPhase(ctx.elimRepeatedPhase) { implicit ctx => - for ((name, tpe) <- paramNamesAndTypes) - yield (name -> tpe.isRepeatedParam) - }.toMap - - val paramTypes = ctx.atPhase(ctx.elimErasedValueTypePhase) { implicit ctx => - paramNamesAndTypes - }.toMap - - var reversedArgs: List[js.Tree] = Nil - - for ((arg, (paramName, paramType)) <- args.zip(paramNamesAndTypes)) { - val wasRepeated = wereRepeated.getOrElse(paramName, false) - if (wasRepeated) { - reversedArgs = - genJSRepeatedParam(arg) reverse_::: reversedArgs - } else { - val unboxedArg = genExpr(arg) - val boxedArg = unboxedArg match { - case js.UndefinedParam() => - unboxedArg - case _ => - val tpe = paramTypes.getOrElse(paramName, paramType) - box(unboxedArg, tpe) - } - reversedArgs ::= boxedArg - } - } - - /* Remove all consecutive js.UndefinedParam's at the end of the argument - * list. No check is performed whether they may be there, since they will - * only be placed where default arguments can be anyway. - */ - reversedArgs = reversedArgs.dropWhile(_.isInstanceOf[js.UndefinedParam]) - - /* Find remaining js.UndefinedParam and replace by js.Undefined. This can - * happen with named arguments or with multiple argument lists. - */ - reversedArgs = reversedArgs map { - case js.UndefinedParam() => js.Undefined() - case arg => arg - } - - reversedArgs.reverse - } - - /** Gen JS code for a repeated param of a JS method. - * - * In this case `arg` has type `Seq[T]` for some `T`, but the result should - * be an expanded list of the elements in the sequence. So this method - * takes care of the conversion. - * - * It is specialized for the shapes of tree generated by the desugaring - * of repeated params in Scala, so that these are actually expanded at - * compile-time. - * - * Otherwise, it returns a `JSSpread` with the `Seq` converted to a - * `js.Array`. - */ - private def genJSRepeatedParam(arg: Tree): List[js.Tree] = { - tryGenRepeatedParamAsJSArray(arg, handleNil = true).getOrElse { - /* Fall back to calling runtime.genTraversableOnce2jsArray - * to perform the conversion to js.Array, then wrap in a Spread - * operator. - */ - implicit val pos: Position = arg.pos - val jsArrayArg = genModuleApplyMethod( - jsdefn.RuntimePackage_genTraversableOnce2jsArray, - List(genExpr(arg))) - List(js.JSSpread(jsArrayArg)) - } - } - - /** Try and expand an actual argument to a repeated param `(xs: T*)`. - * - * This method recognizes the shapes of tree generated by the desugaring - * of repeated params in Scala, and expands them. - * If `arg` does not have the shape of a generated repeated param, this - * method returns `None`. - */ - private def tryGenRepeatedParamAsJSArray(arg: Tree, - handleNil: Boolean): Option[List[js.Tree]] = { - implicit val pos: Position = arg.pos - - // Given a method `def foo(args: T*)` - arg match { - // foo(arg1, arg2, ..., argN) where N > 0 - case MaybeAsInstanceOf(WrapArray(MaybeAsInstanceOf(array: JavaSeqLiteral))) => - /* Value classes in arrays are already boxed, so no need to use - * the type before erasure. - * TODO Is this true in dotty? - */ - Some(array.elems.map(e => box(genExpr(e), e.tpe))) - - // foo() - case Ident(_) if handleNil && arg.symbol == defn.NilModule => - Some(Nil) - - // foo(argSeq: _*) - cannot be optimized - case _ => - None - } - } - - private object MaybeAsInstanceOf { - def unapply(tree: Tree): Some[Tree] = tree match { - case TypeApply(asInstanceOf_? @ Select(base, _), _) - if asInstanceOf_?.symbol == defn.Any_asInstanceOf => - Some(base) - case _ => - Some(tree) - } - } - - private object WrapArray { - lazy val isWrapArray: Set[Symbol] = { - val names = { - defn.ScalaValueClasses().map(sym => nme.wrapXArray(sym.name)) ++ - Set(nme.wrapRefArray, nme.genericWrapArray) - } - names.map(defn.ScalaPredefModule.requiredMethod(_)).toSet - } - - def unapply(tree: Apply): Option[Tree] = tree match { - case Apply(wrapArray_?, List(wrapped)) if isWrapArray(wrapArray_?.symbol) => - Some(wrapped) - case _ => - None - } - } - - /** Gen JS code for loading a Java static field. - */ - private def genLoadStaticField(sym: Symbol)(implicit pos: Position): js.Tree = { - /* Actually, there is no static member in Scala.js. If we come here, that - * is because we found the symbol in a Java-emitted .class in the - * classpath. But the corresponding implementation in Scala.js will - * actually be a val in the companion module. - */ - - if (sym == defn.BoxedUnit_UNIT) { - js.Undefined() - } else { - val instance = genLoadModule(sym.owner) - val method = encodeStaticMemberSym(sym) - js.Apply(instance, method, Nil)(toIRType(sym.info)) - } - } - - /** Gen JS code for loading a module. - * - * Can be given either the module symbol, or its module class symbol. - */ - private def genLoadModule(sym0: Symbol)(implicit pos: Position): js.Tree = { - require(sym0.is(Module), - "genLoadModule called with non-module symbol: " + sym0) - val sym1 = if (sym0.isTerm) sym0.moduleClass else sym0 - val sym = // redirect all static methods of String to RuntimeString - if (sym1 == defn.StringModule) jsdefn.RuntimeStringModule.moduleClass - else sym1 - - if (isJSType(sym)) { - if (isScalaJSDefinedJSClass(sym)) - js.LoadJSModule(jstpe.ClassType(encodeClassFullName(sym))) - else if (sym.derivesFrom(jsdefn.JSGlobalScopeClass)) - genLoadJSGlobal() - else - genLoadNativeJSModule(sym) - } else { - js.LoadModule(jstpe.ClassType(encodeClassFullName(sym))) - } - } - - /** Gen JS code representing the constructor of a JS class. */ - private def genLoadJSConstructor(sym: Symbol)( - implicit pos: Position): js.Tree = { - assert(!isStaticModule(sym) && !sym.is(Trait), - s"genPrimitiveJSClass called with non-class $sym") - js.LoadJSConstructor(jstpe.ClassType(encodeClassFullName(sym))) - } - - /** Gen JS code representing a native JS module. */ - private def genLoadNativeJSModule(sym: Symbol)( - implicit pos: Position): js.Tree = { - require(sym.is(ModuleClass), - s"genLoadNativeJSModule called with non-module $sym") - fullJSNameOf(sym).split('.').foldLeft(genLoadJSGlobal()) { (memo, chunk) => - js.JSBracketSelect(memo, js.StringLiteral(chunk)) - } - } - - /** Gen JS code to load the JavaScript global scope. */ - private def genLoadJSGlobal()(implicit pos: Position): js.Tree = { - js.JSBracketSelect( - js.JSBracketSelect(js.JSLinkingInfo(), js.StringLiteral("envInfo")), - js.StringLiteral("global")) - } - - /** Generate a Class[_] value (e.g. coming from classOf[T]) */ - private def genClassConstant(tpe: Type)(implicit pos: Position): js.Tree = - js.ClassOf(toReferenceType(tpe)) - - private def isStaticModule(sym: Symbol): Boolean = - sym.is(Module) && sym.isStatic - - private def isPrimitiveValueType(tpe: Type): Boolean = { - tpe.widenDealias match { - case JavaArrayType(_) => false - case t => t.typeSymbol.asClass.isPrimitiveValueClass - } - } - -} diff --git a/src/dotty/tools/backend/sjs/JSDefinitions.scala b/src/dotty/tools/backend/sjs/JSDefinitions.scala deleted file mode 100644 index bd0b74031..000000000 --- a/src/dotty/tools/backend/sjs/JSDefinitions.scala +++ /dev/null @@ -1,199 +0,0 @@ -package dotty.tools.backend.sjs - -import dotty.tools.dotc.core._ - -import Types._ -import Contexts._ -import Symbols._ -import Names._ -import StdNames._ -import Decorators._ - -import dotty.tools.dotc.config.SJSPlatform - -object JSDefinitions { - /** The Scala.js-specific definitions for the current context. */ - def jsdefn(implicit ctx: Context): JSDefinitions = - ctx.platform.asInstanceOf[SJSPlatform].jsDefinitions -} - -final class JSDefinitions()(implicit ctx: Context) { - - lazy val InlineAnnotType: TypeRef = ctx.requiredClassRef("scala.inline") - def InlineAnnot(implicit ctx: Context) = InlineAnnotType.symbol.asClass - lazy val NoinlineAnnotType: TypeRef = ctx.requiredClassRef("scala.noinline") - def NoinlineAnnot(implicit ctx: Context) = NoinlineAnnotType.symbol.asClass - - lazy val ScalaJSJSPackageVal = ctx.requiredPackage("scala.scalajs.js") - lazy val ScalaJSJSPackageClass = ScalaJSJSPackageVal.moduleClass.asClass - lazy val JSPackage_typeOfR = ScalaJSJSPackageClass.requiredMethodRef("typeOf") - def JSPackage_typeOf(implicit ctx: Context) = JSPackage_typeOfR.symbol - lazy val JSPackage_constructorOfR = ScalaJSJSPackageClass.requiredMethodRef("constructorOf") - def JSPackage_constructorOf(implicit ctx: Context) = JSPackage_constructorOfR.symbol - lazy val JSPackage_debuggerR = ScalaJSJSPackageClass.requiredMethodRef("debugger") - def JSPackage_debugger(implicit ctx: Context) = JSPackage_debuggerR.symbol - lazy val JSPackage_nativeR = ScalaJSJSPackageClass.requiredMethodRef("native") - def JSPackage_native(implicit ctx: Context) = JSPackage_nativeR.symbol - - lazy val JSNativeAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.native") - def JSNativeAnnot(implicit ctx: Context) = JSNativeAnnotType.symbol.asClass - - lazy val JSAnyType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Any") - def JSAnyClass(implicit ctx: Context) = JSAnyType.symbol.asClass - lazy val JSObjectType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Object") - def JSObjectClass(implicit ctx: Context) = JSObjectType.symbol.asClass - lazy val JSBaseThisFunctionType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.ThisFunction") - def JSBaseThisFunctionClass(implicit ctx: Context) = JSBaseThisFunctionType.symbol.asClass - - lazy val JSDictionaryType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Dictionary") - def JSDictionaryClass(implicit ctx: Context) = JSDictionaryType.symbol.asClass - lazy val JSDictionary_deleteR = JSDictionaryClass.requiredMethodRef("delete") - def JSDictionary_delete(implicit ctx: Context) = JSDictionary_deleteR.symbol - - lazy val JSGlobalScopeType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.GlobalScope") - def JSGlobalScopeClass(implicit ctx: Context) = JSGlobalScopeType.symbol.asClass - - lazy val JSArrayType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.Array") - def JSArrayClass(implicit ctx: Context) = JSArrayType.symbol.asClass - - lazy val JSFunctionType = (0 to 22).map(n => ctx.requiredClassRef("scala.scalajs.js.Function" + n)).toArray - def JSFunctionClass(n: Int)(implicit ctx: Context) = JSFunctionType(n).symbol.asClass - lazy val JSThisFunctionType = (0 to 21).map(n => ctx.requiredClassRef("scala.scalajs.js.ThisFunction" + n)).toArray - def JSThisFunctionClass(n: Int)(implicit ctx: Context) = JSThisFunctionType(n).symbol.asClass - - lazy val RuntimeExceptionType: TypeRef = ctx.requiredClassRef("java.lang.RuntimeException") - def RuntimeExceptionClass(implicit ctx: Context) = RuntimeExceptionType.symbol.asClass - lazy val JavaScriptExceptionType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.JavaScriptException") - def JavaScriptExceptionClass(implicit ctx: Context) = JavaScriptExceptionType.symbol.asClass - - lazy val JSNameAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSName") - def JSNameAnnot(implicit ctx: Context) = JSNameAnnotType.symbol.asClass - lazy val JSFullNameAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSFullName") - def JSFullNameAnnot(implicit ctx: Context) = JSFullNameAnnotType.symbol.asClass - lazy val JSBracketAccessAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSBracketAccess") - def JSBracketAccessAnnot(implicit ctx: Context) = JSBracketAccessAnnotType.symbol.asClass - lazy val JSBracketCallAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSBracketCall") - def JSBracketCallAnnot(implicit ctx: Context) = JSBracketCallAnnotType.symbol.asClass - lazy val JSExportAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExport") - def JSExportAnnot(implicit ctx: Context) = JSExportAnnotType.symbol.asClass - lazy val JSExportDescendentObjectsAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportDescendentObjects") - def JSExportDescendentObjectsAnnot(implicit ctx: Context) = JSExportDescendentObjectsAnnotType.symbol.asClass - lazy val JSExportDescendentClassesAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportDescendentClasses") - def JSExportDescendentClassesAnnot(implicit ctx: Context) = JSExportDescendentClassesAnnotType.symbol.asClass - lazy val JSExportAllAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportAll") - def JSExportAllAnnot(implicit ctx: Context) = JSExportAllAnnotType.symbol.asClass - lazy val JSExportNamedAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.JSExportNamed") - def JSExportNamedAnnot(implicit ctx: Context) = JSExportNamedAnnotType.symbol.asClass - lazy val RawJSTypeAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.RawJSType") - def RawJSTypeAnnot(implicit ctx: Context) = RawJSTypeAnnotType.symbol.asClass - lazy val ExposedJSMemberAnnotType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.annotation.ExposedJSMember") - def ExposedJSMemberAnnot(implicit ctx: Context) = ExposedJSMemberAnnotType.symbol.asClass - - lazy val JSAnyModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Any") - def JSAnyModule(implicit ctx: Context) = JSAnyModuleRef.symbol - lazy val JSAny_fromFunctionR = (0 to 22).map(n => JSAnyModule.requiredMethodRef("fromFunction" + n)).toArray - def JSAny_fromFunction(n: Int)(implicit ctx: Context) = JSAny_fromFunctionR(n).symbol - - lazy val JSDynamicModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Dynamic") - def JSDynamicModule(implicit ctx: Context) = JSDynamicModuleRef.symbol - lazy val JSDynamic_newInstanceR = JSDynamicModule.requiredMethodRef("newInstance") - def JSDynamic_newInstance(implicit ctx: Context) = JSDynamic_newInstanceR.symbol - - lazy val JSDynamicLiteralModuleRef = JSDynamicModule.moduleClass.requiredValueRef("literal") - def JSDynamicLiteralModule(implicit ctx: Context) = JSDynamicLiteralModuleRef.symbol - lazy val JSDynamicLiteral_applyDynamicNamedR = JSDynamicLiteralModule.requiredMethodRef("applyDynamicNamed") - def JSDynamicLiteral_applyDynamicNamed(implicit ctx: Context) = JSDynamicLiteral_applyDynamicNamedR.symbol - lazy val JSDynamicLiteral_applyDynamicR = JSDynamicLiteralModule.requiredMethodRef("applyDynamic") - def JSDynamicLiteral_applyDynamic(implicit ctx: Context) = JSDynamicLiteral_applyDynamicR.symbol - - lazy val JSObjectModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Object") - def JSObjectModule(implicit ctx: Context) = JSObjectModuleRef.symbol - lazy val JSObject_hasPropertyR = JSObjectModule.requiredMethodRef("hasProperty") - def JSObject_hasProperty(implicit ctx: Context) = JSObject_hasPropertyR.symbol - lazy val JSObject_propertiesR = JSObjectModule.requiredMethodRef("properties") - def JSObject_properties(implicit ctx: Context) = JSObject_propertiesR.symbol - - lazy val JSArrayModuleRef = ctx.requiredModuleRef("scala.scalajs.js.Array") - def JSArrayModule(implicit ctx: Context) = JSArrayModuleRef.symbol - lazy val JSArray_applyR = JSArrayModule.requiredMethodRef(nme.apply) - def JSArray_apply(implicit ctx: Context) = JSArray_applyR.symbol - - lazy val JSThisFunctionModuleRef = ctx.requiredModuleRef("scala.scalajs.js.ThisFunction") - def JSThisFunctionModule(implicit ctx: Context) = JSThisFunctionModuleRef.symbol - lazy val JSThisFunction_fromFunctionR = (1 to 22).map(n => JSThisFunctionModule.requiredMethodRef("fromFunction" + n)).toArray - def JSThisFunction_fromFunction(n: Int)(implicit ctx: Context) = JSThisFunction_fromFunctionR(n - 1).symbol - - lazy val JSConstructorTagModuleRef = ctx.requiredModuleRef("scala.scalajs.js.ConstructorTag") - def JSConstructorTagModule(implicit ctx: Context) = JSConstructorTagModuleRef.symbol - lazy val JSConstructorTag_materializeR = JSConstructorTagModule.requiredMethodRef("materialize") - def JSConstructorTag_materialize(implicit ctx: Context) = JSConstructorTag_materializeR.symbol - - lazy val RuntimeStringModuleRef = ctx.requiredModuleRef("scala.scalajs.runtime.RuntimeString") - def RuntimeStringModule(implicit ctx: Context) = RuntimeStringModuleRef.symbol - def RuntimeStringModuleClass(implicit ctx: Context) = RuntimeStringModule.moduleClass.asClass - - lazy val BooleanReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.BooleanReflectiveCall") - def BooleanReflectiveCallClass(implicit ctx: Context) = BooleanReflectiveCallType.symbol.asClass - lazy val NumberReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.NumberReflectiveCall") - def NumberReflectiveCallClass(implicit ctx: Context) = NumberReflectiveCallType.symbol.asClass - lazy val IntegerReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.IntegerReflectiveCall") - def IntegerReflectiveCallClass(implicit ctx: Context) = IntegerReflectiveCallType.symbol.asClass - lazy val LongReflectiveCallType: TypeRef = ctx.requiredClassRef("scala.scalajs.runtime.LongReflectiveCall") - def LongReflectiveCallClass(implicit ctx: Context) = LongReflectiveCallType.symbol.asClass - - lazy val RuntimePackageVal = ctx.requiredPackage("scala.scalajs.runtime") - lazy val RuntimePackageClass = RuntimePackageVal.moduleClass.asClass - lazy val RuntimePackage_wrapJavaScriptExceptionR = RuntimePackageClass.requiredMethodRef("wrapJavaScriptException") - def RuntimePackage_typeOf(implicit ctx: Context) = RuntimePackage_wrapJavaScriptExceptionR.symbol - lazy val RuntimePackage_unwrapJavaScriptExceptionR = RuntimePackageClass.requiredMethodRef("unwrapJavaScriptException") - def RuntimePackage_unwrapJavaScriptException(implicit ctx: Context) = RuntimePackage_unwrapJavaScriptExceptionR.symbol - lazy val RuntimePackage_genTraversableOnce2jsArrayR = RuntimePackageClass.requiredMethodRef("genTraversableOnce2jsArray") - def RuntimePackage_genTraversableOnce2jsArray(implicit ctx: Context) = RuntimePackage_genTraversableOnce2jsArrayR.symbol - lazy val RuntimePackage_jsTupleArray2jsObjectR = RuntimePackageClass.requiredMethodRef("jsTupleArray2jsObject") - def RuntimePackage_jsTupleArray2jsObject(implicit ctx: Context) = RuntimePackage_jsTupleArray2jsObjectR.symbol - lazy val RuntimePackage_constructorOfR = RuntimePackageClass.requiredMethodRef("constructorOf") - def RuntimePackage_constructorOf(implicit ctx: Context) = RuntimePackage_constructorOfR.symbol - lazy val RuntimePackage_newConstructorTagR = RuntimePackageClass.requiredMethodRef("newConstructorTag") - def RuntimePackage_newConstructorTag(implicit ctx: Context) = RuntimePackage_newConstructorTagR.symbol - lazy val RuntimePackage_propertiesOfR = RuntimePackageClass.requiredMethodRef("propertiesOf") - def RuntimePackage_propertiesOf(implicit ctx: Context) = RuntimePackage_propertiesOfR.symbol - lazy val RuntimePackage_environmentInfoR = RuntimePackageClass.requiredMethodRef("environmentInfo") - def RuntimePackage_environmentInfo(implicit ctx: Context) = RuntimePackage_environmentInfoR.symbol - lazy val RuntimePackage_linkingInfoR = RuntimePackageClass.requiredMethodRef("linkingInfo") - def RuntimePackage_linkingInfo(implicit ctx: Context) = RuntimePackage_linkingInfoR.symbol - - lazy val WrappedArrayType: TypeRef = ctx.requiredClassRef("scala.scalajs.js.WrappedArray") - def WrappedArrayClass(implicit ctx: Context) = WrappedArrayType.symbol.asClass - - lazy val ScalaRunTime_isArrayR = defn.ScalaRuntimeModule.requiredMethodRef("isArray", List(???, ???)) - def ScalaRunTime_isArray(implicit ctx: Context): Symbol = ScalaRunTime_isArrayR.symbol - - lazy val BoxesRunTime_boxToCharacterR = defn.BoxesRunTimeModule.requiredMethodRef("boxToCharacter") - def BoxesRunTime_boxToCharacter(implicit ctx: Context): Symbol = BoxesRunTime_boxToCharacterR.symbol - lazy val BoxesRunTime_unboxToCharR = defn.BoxesRunTimeModule.requiredMethodRef("unboxToChar") - def BoxesRunTime_unboxToChar(implicit ctx: Context): Symbol = BoxesRunTime_unboxToCharR.symbol - - /** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */ - private def scalajsClassName(cls: Symbol)(implicit ctx: Context): TypeName = - if (cls.isClass && cls.owner == ScalaJSJSPackageClass) cls.asClass.name - else EmptyTypeName - - /** Is the given `cls` a class of the form `scala.scalajs.js.prefixN` where - * `N` is a number. - * - * This is similar to `isVarArityClass` in `Definitions.scala`. - */ - private def isScalaJSVarArityClass(cls: Symbol, prefix: Name): Boolean = { - val name = scalajsClassName(cls) - name.startsWith(prefix) && name.drop(prefix.length).forall(_.isDigit) - } - - def isJSFunctionClass(cls: Symbol): Boolean = - isScalaJSVarArityClass(cls, nme.Function) - - private val ThisFunctionName = termName("ThisFunction") - - def isJSThisFunctionClass(cls: Symbol): Boolean = - isScalaJSVarArityClass(cls, ThisFunctionName) - -} diff --git a/src/dotty/tools/backend/sjs/JSEncoding.scala b/src/dotty/tools/backend/sjs/JSEncoding.scala deleted file mode 100644 index e8ea3258b..000000000 --- a/src/dotty/tools/backend/sjs/JSEncoding.scala +++ /dev/null @@ -1,389 +0,0 @@ -package dotty.tools.backend.sjs - -import scala.collection.mutable - -import dotty.tools.FatalError - -import dotty.tools.dotc.core._ -import Periods._ -import SymDenotations._ -import Contexts._ -import Types._ -import Symbols._ -import Denotations._ -import NameOps._ -import StdNames._ - -import org.scalajs.core.ir -import ir.{Trees => js, Types => jstpe} - -import ScopedVar.withScopedVars -import JSDefinitions._ -import JSInterop._ - -/** Encoding of symbol names for JavaScript - * - * Some issues that this encoding solves: - * * Overloading: encode the full signature in the JS name - * * Same scope for fields and methods of a class - * * Global access to classes and modules (by their full name) - * - * @author Sébastien Doeraene - */ -object JSEncoding { - - /** Signature separator string (between parameter types) */ - private final val SignatureSep = "__" - - /** Name given to the local Scala.js environment variable */ - private final val ScalaJSEnvironmentName = "ScalaJS" - - implicit class SymOps(val self: Symbol) extends AnyVal { - def unexpandedName(implicit ctx: Context): Names.Name = - self.name.unexpandedName - } - - implicit class MyNameOps(val self: Names.Name) extends AnyVal { - def decoded: String = self.decode.toString - } - - // Fresh local name generator ---------------------------------------------- - - class LocalNameGenerator { - import LocalNameGenerator._ - - private val usedLocalNames = mutable.Set.empty[String] - private val localSymbolNames = mutable.Map.empty[Symbol, String] - - def localSymbolName(sym: Symbol)(implicit ctx: Context): String = - localSymbolNames.getOrElseUpdate(sym, freshName(sym.name.toString)) - - def freshLocalIdent()(implicit pos: ir.Position): js.Ident = - js.Ident(freshName(), None) - - def freshLocalIdent(base: String)(implicit pos: ir.Position): js.Ident = - js.Ident(freshName(base), Some(base)) - - private def freshName(base: String = "x"): String = { - var suffix = 1 - var longName = base - while (usedLocalNames(longName) || isReserved(longName)) { - suffix += 1 - longName = base+"$"+suffix - } - usedLocalNames += longName - mangleJSName(longName) - } - } - - private object LocalNameGenerator { - private val isReserved = - Set("arguments", "eval", ScalaJSEnvironmentName) - } - - // Encoding methods ---------------------------------------------------------- - - def encodeLabelSym(sym: Symbol)( - implicit ctx: Context, pos: ir.Position, localNames: LocalNameGenerator): js.Ident = { - require(sym.is(Flags.Label), "encodeLabelSym called with non-label symbol: " + sym) - js.Ident(localNames.localSymbolName(sym), Some(sym.unexpandedName.decoded)) - } - - private def allRefClasses(implicit ctx: Context): Set[Symbol] = { - //TODO - /*(Set(ObjectRefClass, VolatileObjectRefClass) ++ - refClass.values ++ volatileRefClass.values)*/ - Set() - } - - def encodeFieldSym(sym: Symbol)( - implicit ctx: Context, pos: ir.Position): js.Ident = { - require(sym.owner.isClass && sym.isTerm && !sym.is(Flags.Method) && !sym.is(Flags.Module), - "encodeFieldSym called with non-field symbol: " + sym) - - val name0 = encodeMemberNameInternal(sym) - val name = - if (name0.charAt(name0.length()-1) != ' ') name0 - else name0.substring(0, name0.length()-1) - - /* We have to special-case fields of Ref types (IntRef, ObjectRef, etc.) - * because they are emitted as private by our .scala source files, but - * they are considered public at use site since their symbols come from - * Java-emitted .class files. - */ - val idSuffix = - if (sym.is(Flags.Private) || allRefClasses.contains(sym.owner)) - sym.owner.asClass.baseClasses.size.toString - else - "f" - - val encodedName = name + "$" + idSuffix - js.Ident(mangleJSName(encodedName), Some(sym.unexpandedName.decoded)) - } - - def encodeMethodSym(sym: Symbol, reflProxy: Boolean = false)( - implicit ctx: Context, pos: ir.Position): js.Ident = { - val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy) - js.Ident(encodedName + paramsString, - Some(sym.unexpandedName.decoded + paramsString)) - } - - def encodeMethodName(sym: Symbol, reflProxy: Boolean = false)( - implicit ctx: Context): String = { - val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy) - encodedName + paramsString - } - - /** Encodes a method symbol of java.lang.String for use in RuntimeString. - * - * This basically means adding an initial parameter of type - * java.lang.String, which is the `this` parameter. - */ - def encodeRTStringMethodSym(sym: Symbol)( - implicit ctx: Context, pos: ir.Position): js.Ident = { - require(sym.owner == defn.StringClass) - require(!sym.isClassConstructor && !sym.is(Flags.Private)) - - val (encodedName, paramsString) = - encodeMethodNameInternal(sym, inRTClass = true) - js.Ident(encodedName + paramsString, - Some(sym.unexpandedName.decoded + paramsString)) - } - - /** Encodes a constructor symbol of java.lang.String for use in RuntimeString. - * - * - The name is rerouted to `newString` - * - The result type is set to `java.lang.String` - */ - def encodeRTStringCtorSym(sym: Symbol)( - implicit ctx: Context, pos: ir.Position): js.Ident = { - require(sym.owner == defn.StringClass) - require(sym.isClassConstructor && !sym.is(Flags.Private)) - - val paramTypeNames = sym.info.firstParamTypes.map(internalName(_)) - val paramAndResultTypeNames = paramTypeNames :+ ir.Definitions.StringClass - val paramsString = makeParamsString(paramAndResultTypeNames) - - js.Ident("newString" + paramsString, - Some(sym.unexpandedName.decoded + paramsString)) - } - - private def encodeMethodNameInternal(sym: Symbol, - reflProxy: Boolean = false, inRTClass: Boolean = false)( - implicit ctx: Context): (String, String) = { - require(sym.is(Flags.Method), "encodeMethodSym called with non-method symbol: " + sym) - - def name = encodeMemberNameInternal(sym) - - val encodedName = { - if (sym.isClassConstructor) { - "init_" - } else if (sym.is(Flags.Private)) { - (mangleJSName(name) + SignatureSep + "p" + - sym.owner.asClass.baseClasses.size.toString) - } else { - mangleJSName(name) - } - } - - val paramsString = makeParamsString(sym, reflProxy, inRTClass) - - (encodedName, paramsString) - } - - def encodeStaticMemberSym(sym: Symbol)( - implicit ctx: Context, pos: ir.Position): js.Ident = { - require(sym.is(Flags.JavaStaticTerm), - "encodeStaticMemberSym called with non-static symbol: " + sym) - js.Ident( - mangleJSName(encodeMemberNameInternal(sym)) + - makeParamsString(List(internalName(sym.info))), - Some(sym.unexpandedName.decoded)) - } - - def encodeLocalSym(sym: Symbol)( - implicit ctx: Context, pos: ir.Position, localNames: LocalNameGenerator): js.Ident = { - require(!sym.owner.isClass && sym.isTerm && !sym.is(Flags.Method) && !sym.is(Flags.Module), - "encodeLocalSym called with non-local symbol: " + sym) - js.Ident(localNames.localSymbolName(sym), Some(sym.unexpandedName.decoded)) - } - - def foreignIsImplClass(sym: Symbol)(implicit ctx: Context): Boolean = - sym.name.isImplClassName - - def encodeClassType(sym: Symbol)(implicit ctx: Context): jstpe.Type = { - if (sym == defn.ObjectClass) jstpe.AnyType - else if (isJSType(sym)) jstpe.AnyType - else { - assert(sym != defn.ArrayClass, - "encodeClassType() cannot be called with ArrayClass") - jstpe.ClassType(encodeClassFullName(sym)) - } - } - - def encodeClassFullNameIdent(sym: Symbol)( - implicit ctx: Context, pos: ir.Position): js.Ident = { - js.Ident(encodeClassFullName(sym), Some(sym.fullName.toString)) - } - - def encodeClassFullName(sym: Symbol)(implicit ctx: Context): String = { - if (sym == defn.NothingClass) ir.Definitions.RuntimeNothingClass - else if (sym == defn.NullClass) ir.Definitions.RuntimeNullClass - else ir.Definitions.encodeClassName(sym.fullName.toString) - } - - private def encodeMemberNameInternal(sym: Symbol)( - implicit ctx: Context): String = { - sym.name.toString.replace("_", "$und").replace("~", "$tilde") - } - - def toIRType(tp: Type)(implicit ctx: Context): jstpe.Type = { - val refType = toReferenceTypeInternal(tp) - refType._1 match { - case tpe: jstpe.ClassType => - val sym = refType._2 - if (sym.asClass.isPrimitiveValueClass) { - if (sym == defn.BooleanClass) - jstpe.BooleanType - else if (sym == defn.FloatClass) - jstpe.FloatType - else if (sym == defn.DoubleClass) - jstpe.DoubleType - else if (sym == defn.LongClass) - jstpe.LongType - else if (sym == defn.UnitClass) - jstpe.NoType - else - jstpe.IntType - } else { - if (sym == defn.ObjectClass || isJSType(sym)) - jstpe.AnyType - else if (sym == defn.NothingClass) - jstpe.NothingType - else if (sym == defn.NullClass) - jstpe.NullType - else - tpe - } - - case tpe: jstpe.ArrayType => - tpe - } - } - - def toReferenceType(tp: Type)(implicit ctx: Context): jstpe.ReferenceType = - toReferenceTypeInternal(tp)._1 - - private def toReferenceTypeInternal(tp: Type)( - implicit ctx: Context): (jstpe.ReferenceType, Symbol) = { - - /** - * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int. - * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType. - */ - def primitiveOrClassToRefType(sym: Symbol): (jstpe.ReferenceType, Symbol) = { - assert(sym.isClass, sym) - //assert(sym != defn.ArrayClass || isCompilingArray, sym) - (jstpe.ClassType(encodeClassFullName(sym)), sym) - } - - /** - * When compiling Array.scala, the type parameter T is not erased and shows up in method - * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. - */ - def nonClassTypeRefToRefType(sym: Symbol): (jstpe.ReferenceType, Symbol) = { - //assert(sym.isType && isCompilingArray, sym) - (jstpe.ClassType(ir.Definitions.ObjectClass), defn.ObjectClass) - } - - tp.widenDealias match { - // Array type such as Array[Int] (kept by erasure) - case JavaArrayType(el) => - val elRefType = toReferenceTypeInternal(el) - (jstpe.ArrayType(elRefType._1), elRefType._2) - - case t: TypeRef => - if (!t.symbol.isClass) nonClassTypeRefToRefType(t.symbol) // See comment on nonClassTypeRefToBType - else primitiveOrClassToRefType(t.symbol) // Common reference to a type such as scala.Int or java.lang.String - - case Types.ClassInfo(_, sym, _, _, _) => - /* We get here, for example, for genLoadModule, which invokes - * toTypeKind(moduleClassSymbol.info) - */ - primitiveOrClassToRefType(sym) - - case t: MethodType => // triggers for LabelDefs - toReferenceTypeInternal(t.resultType) - - /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for - * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. - * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. - */ - case a @ AnnotatedType(t, _) => - //debuglog(s"typeKind of annotated type $a") - toReferenceTypeInternal(t) - } - } - - /** Patches the result type of a method symbol to sanitize it. - * - * For some reason, dotc thinks that the `info.resultType`of an - * `isConstructor` method (for classes or traits) is the enclosing class - * or trait, but the bodies and usages act as if the result type was `Unit`. - * - * This method returns `UnitType` for constructor methods, and otherwise - * `sym.info.resultType`. - */ - def patchedResultType(sym: Symbol)(implicit ctx: Context): Type = - if (sym.isConstructor) defn.UnitType - else sym.info.resultType - - // Encoding of method signatures - - private def makeParamsString(sym: Symbol, reflProxy: Boolean, - inRTClass: Boolean)( - implicit ctx: Context): String = { - val tpe = sym.info - - val paramTypeNames0 = tpe.firstParamTypes.map(internalName(_)) - - val hasExplicitThisParameter = - inRTClass || isScalaJSDefinedJSClass(sym.owner) - val paramTypeNames = - if (!hasExplicitThisParameter) paramTypeNames0 - else encodeClassFullName(sym.owner) :: paramTypeNames0 - - val paramAndResultTypeNames = { - if (sym.isClassConstructor) - paramTypeNames - else if (reflProxy) - paramTypeNames :+ "" - else - paramTypeNames :+ internalName(patchedResultType(sym)) - } - makeParamsString(paramAndResultTypeNames) - } - - private def makeParamsString(paramAndResultTypeNames: List[String]) = - paramAndResultTypeNames.mkString(SignatureSep, SignatureSep, "") - - /** Computes the internal name for a type. */ - private def internalName(tpe: Type)(implicit ctx: Context): String = - encodeReferenceType(toReferenceType(tpe)) - - /** Encodes a [[Types.ReferenceType]], such as in an encoded method signature. - */ - private def encodeReferenceType(refType: jstpe.ReferenceType): String = { - refType match { - case jstpe.ClassType(encodedName) => encodedName - case jstpe.ArrayType(base, depth) => "A" * depth + base - } - } - - /** Mangles names that are illegal in JavaScript by prepending a `$`. - * Also mangles names that would collide with these mangled names. - */ - private def mangleJSName(name: String) = - if (js.isKeyword(name) || name(0).isDigit || name(0) == '$') "$" + name - else name -} diff --git a/src/dotty/tools/backend/sjs/JSInterop.scala b/src/dotty/tools/backend/sjs/JSInterop.scala deleted file mode 100644 index 6d66c3206..000000000 --- a/src/dotty/tools/backend/sjs/JSInterop.scala +++ /dev/null @@ -1,110 +0,0 @@ -package dotty.tools.backend.sjs - -import dotty.tools.dotc.core._ -import Contexts._ -import Flags._ -import Symbols._ -import NameOps._ -import StdNames._ - -import JSDefinitions._ - -/** Management of the interoperability with JavaScript. */ -object JSInterop { - - /** Is this symbol a JavaScript type? */ - def isJSType(sym: Symbol)(implicit ctx: Context): Boolean = { - //sym.hasAnnotation(jsdefn.RawJSTypeAnnot) - ctx.atPhase(ctx.erasurePhase) { implicit ctx => - sym.derivesFrom(jsdefn.JSAnyClass) - } - } - - /** Is this symbol a Scala.js-defined JS class, i.e., a non-native JS class? */ - def isScalaJSDefinedJSClass(sym: Symbol)(implicit ctx: Context): Boolean = - isJSType(sym) && !sym.hasAnnotation(jsdefn.JSNativeAnnot) - - /** Should this symbol be translated into a JS getter? - * - * This is true for any parameterless method, i.e., defined without `()`. - * Unlike `SymDenotations.isGetter`, it applies to user-defined methods as - * much as *accessor* methods created for `val`s and `var`s. - */ - def isJSGetter(sym: Symbol)(implicit ctx: Context): Boolean = { - sym.info.firstParamTypes.isEmpty && ctx.atPhase(ctx.erasurePhase) { implicit ctx => - sym.info.isParameterless - } - } - - /** Should this symbol be translated into a JS setter? - * - * This is true for any method whose name ends in `_=`. - * Unlike `SymDenotations.isGetter`, it applies to user-defined methods as - * much as *accessor* methods created for `var`s. - */ - def isJSSetter(sym: Symbol)(implicit ctx: Context): Boolean = - sym.name.isSetterName && sym.is(Method) - - /** Should this symbol be translated into a JS bracket access? - * - * This is true for methods annotated with `@JSBracketAccess`. - */ - def isJSBracketAccess(sym: Symbol)(implicit ctx: Context): Boolean = - sym.hasAnnotation(jsdefn.JSBracketAccessAnnot) - - /** Should this symbol be translated into a JS bracket call? - * - * This is true for methods annotated with `@JSBracketCall`. - */ - def isJSBracketCall(sym: Symbol)(implicit ctx: Context): Boolean = - sym.hasAnnotation(jsdefn.JSBracketCallAnnot) - - /** Is this symbol a default param accessor for a JS method? - * - * For default param accessors of *constructors*, we need to test whether - * the companion *class* of the owner is a JS type; not whether the owner - * is a JS type. - */ - def isJSDefaultParam(sym: Symbol)(implicit ctx: Context): Boolean = { - sym.name.isDefaultGetterName && { - val owner = sym.owner - if (owner.is(ModuleClass) && - sym.name.asTermName.defaultGetterToMethod == nme.CONSTRUCTOR) { - isJSType(owner.linkedClass) - } else { - isJSType(owner) - } - } - } - - /** Gets the unqualified JS name of a symbol. - * - * If it is not explicitly specified with an `@JSName` annotation, the - * JS name is inferred from the Scala name. - */ - def jsNameOf(sym: Symbol)(implicit ctx: Context): String = { - sym.getAnnotation(jsdefn.JSNameAnnot).flatMap(_.argumentConstant(0)).fold { - val base = sym.name.unexpandedName.decode.toString.stripSuffix("_=") - if (sym.is(ModuleClass)) base.stripSuffix("$") - else if (!sym.is(Method)) base.stripSuffix(" ") - else base - } { constant => - constant.stringValue - } - } - - /** Gets the fully qualified JS name of a static class of module Symbol. - * - * This is the JS name of the symbol qualified by the fully qualified JS - * name of its original owner if the latter is a native JS object. - */ - def fullJSNameOf(sym: Symbol)(implicit ctx: Context): String = { - assert(sym.isClass, s"fullJSNameOf called for non-class symbol $sym") - sym.getAnnotation(jsdefn.JSFullNameAnnot).flatMap(_.argumentConstant(0)).fold { - jsNameOf(sym) - } { constant => - constant.stringValue - } - } - -} diff --git a/src/dotty/tools/backend/sjs/JSPositions.scala b/src/dotty/tools/backend/sjs/JSPositions.scala deleted file mode 100644 index 10570da00..000000000 --- a/src/dotty/tools/backend/sjs/JSPositions.scala +++ /dev/null @@ -1,65 +0,0 @@ -package dotty.tools.backend.sjs - -import dotty.tools.dotc.core._ -import Contexts._ -import dotty.tools.dotc.util.Positions -import Positions.Position - -import org.scalajs.core.ir - -/** Conversion utilities from dotty Positions to IR Positions. */ -class JSPositions()(implicit ctx: Context) { - - /** Implicit conversion from dotty Position to ir.Position. */ - implicit def pos2irPos(pos: Positions.Position): ir.Position = { - if (!pos.exists) ir.Position.NoPosition - else { - val source = pos2irPosCache.toIRSource(ctx.compilationUnit.source) - val sourcePos = ctx.compilationUnit.source.atPos(pos) - // dotty positions are 1-based but IR positions are 0-based - ir.Position(source, sourcePos.line-1, sourcePos.column-1) - } - } - - /** Implicitly materializes an ir.Position from an implicit dotty Position. */ - implicit def implicitPos2irPos( - implicit pos: Positions.Position): ir.Position = { - pos2irPos(pos) - } - - private[this] object pos2irPosCache { // scalastyle:ignore - import dotty.tools.dotc.util._ - - private[this] var lastDotcSource: SourceFile = null - private[this] var lastIRSource: ir.Position.SourceFile = null - - def toIRSource(dotcSource: SourceFile): ir.Position.SourceFile = { - if (dotcSource != lastDotcSource) { - lastIRSource = convert(dotcSource) - lastDotcSource = dotcSource - } - lastIRSource - } - - private[this] def convert(dotcSource: SourceFile): ir.Position.SourceFile = { - dotcSource.file.file match { - case null => - new java.net.URI( - "virtualfile", // Pseudo-Scheme - dotcSource.file.path, // Scheme specific part - null // Fragment - ) - case file => - val srcURI = file.toURI - def matches(pat: java.net.URI) = pat.relativize(srcURI) != srcURI - - // TODO - /*scalaJSOpts.sourceURIMaps.collectFirst { - case ScalaJSOptions.URIMap(from, to) if matches(from) => - val relURI = from.relativize(srcURI) - to.fold(relURI)(_.resolve(relURI)) - } getOrElse*/ srcURI - } - } - } -} diff --git a/src/dotty/tools/backend/sjs/JSPrimitives.scala b/src/dotty/tools/backend/sjs/JSPrimitives.scala deleted file mode 100644 index 6c3c5715c..000000000 --- a/src/dotty/tools/backend/sjs/JSPrimitives.scala +++ /dev/null @@ -1,118 +0,0 @@ -package dotty.tools.backend.sjs - -import dotty.tools.dotc.core._ -import Names.TermName -import StdNames._ -import Types._ -import Contexts._ -import Symbols._ - -import dotty.tools.dotc.ast.tpd._ -import dotty.tools.dotc.backend.jvm.DottyPrimitives - -import scala.collection.mutable - -object JSPrimitives { - - final val GETCLASS = 301 // jl.Object.getClass() - - final val F2JS = 302 // js.Any.fromFunctionN - final val F2JSTHIS = 303 // js.ThisFunction.fromFunctionN - - final val DYNNEW = 304 // js.Dynamic.newInstance - final val DYNLIT = 305 // js.Dynamic.literal.applyDynamic{,Named} - final val DICT_DEL = 306 // js.Dictionary.delete - final val ARR_CREATE = 307 // js.Array.apply (array literal syntax) - - final val TYPEOF = 308 // js.typeOf(x) - final val DEBUGGER = 309 // js.debugger() - final val HASPROP = 310 // js.Object.hasProperty(o, p), equiv to `p in o` in JS - final val OBJPROPS = 311 // js.Object.properties(o), equiv to `for (p in o)` in JS - final val JS_NATIVE = 312 // js.native. Marker method. Fails if tried to be emitted. - - final val UNITVAL = 313 // () value, which is undefined - final val UNITTYPE = 314 // BoxedUnit.TYPE (== classOf[Unit]) - - final val CONSTRUCTOROF = 315 // runtime.constructorOf(clazz) - final val ENV_INFO = 316 // runtime.environmentInfo - final val LINKING_INFO = 317 // runtime.linkingInfo - - final val THROW = 318 // .throw - -} - -class JSPrimitives(ctx: Context) extends DottyPrimitives(ctx) { - import JSPrimitives._ - import scala.tools.nsc.backend.ScalaPrimitives._ - - private lazy val jsPrimitives: Map[Symbol, Int] = initJSPrimitives(ctx) - - override def getPrimitive(sym: Symbol): Int = - jsPrimitives.getOrElse(sym, super.getPrimitive(sym)) - - override def getPrimitive(app: Apply, tpe: Type)(implicit ctx: Context): Int = - jsPrimitives.getOrElse(app.fun.symbol, super.getPrimitive(app, tpe)) - - override def isPrimitive(fun: Tree): Boolean = - jsPrimitives.contains(fun.symbol(ctx)) || super.isPrimitive(fun) - - /** Initialize the primitive map */ - private def initJSPrimitives(implicit ctx: Context): Map[Symbol, Int] = { - - val primitives = new mutable.HashMap[Symbol, Int]() - - // !!! Code duplicate with DottyPrimitives - /** Add a primitive operation to the map */ - def addPrimitive(s: Symbol, code: Int): Unit = { - assert(!(primitives contains s), "Duplicate primitive " + s) - primitives(s) = code - } - - def addPrimitives(cls: Symbol, method: TermName, code: Int)(implicit ctx: Context): Unit = { - val alts = cls.info.member(method).alternatives.map(_.symbol) - if (alts.isEmpty) { - ctx.error(s"Unknown primitive method $cls.$method") - } else { - for (s <- alts) - addPrimitive(s, code) - } - } - - val jsdefn = JSDefinitions.jsdefn - - addPrimitive(defn.Any_getClass, GETCLASS) - - for (i <- 0 to 22) - addPrimitive(jsdefn.JSAny_fromFunction(i), F2JS) - for (i <- 1 to 22) - addPrimitive(jsdefn.JSThisFunction_fromFunction(i), F2JSTHIS) - - addPrimitive(jsdefn.JSDynamic_newInstance, DYNNEW) - - addPrimitive(jsdefn.JSDynamicLiteral_applyDynamicNamed, DYNLIT) - addPrimitive(jsdefn.JSDynamicLiteral_applyDynamic, DYNLIT) - - addPrimitive(jsdefn.JSDictionary_delete, DICT_DEL) - - //addPrimitive(jsdefn.JSArray_create, ARR_CREATE) - - addPrimitive(jsdefn.JSPackage_typeOf, TYPEOF) - addPrimitive(jsdefn.JSPackage_debugger, DEBUGGER) - addPrimitive(jsdefn.JSPackage_native, JS_NATIVE) - - addPrimitive(jsdefn.JSObject_hasProperty, HASPROP) - addPrimitive(jsdefn.JSObject_properties, OBJPROPS) - - addPrimitive(defn.BoxedUnit_UNIT, UNITVAL) - //addPrimitive(defn.BoxedUnit_TYPE, UNITTYPE) - - //addPrimitive(jsdefn.Runtime_constructorOf, CONSTRUCTOROF) - //addPrimitive(jsdefn.Runtime_environmentInfo, ENV_INFO) - //addPrimitive(jsdefn.Runtime_linkingInfo, LINKING_INFO) - - addPrimitive(defn.throwMethod, THROW) - - primitives.toMap - } - -} diff --git a/src/dotty/tools/backend/sjs/ScopedVar.scala b/src/dotty/tools/backend/sjs/ScopedVar.scala deleted file mode 100644 index 0e47f7b79..000000000 --- a/src/dotty/tools/backend/sjs/ScopedVar.scala +++ /dev/null @@ -1,38 +0,0 @@ -package dotty.tools.backend.sjs - -import language.implicitConversions - -class ScopedVar[A](init: A) { - import ScopedVar.Assignment - - private var value = init - - def this()(implicit ev: Null <:< A) = this(ev(null)) - - def get: A = value - def :=(newValue: A): Assignment[A] = new Assignment(this, newValue) -} - -object ScopedVar { - class Assignment[T](scVar: ScopedVar[T], value: T) { - private[ScopedVar] def push(): AssignmentStackElement[T] = { - val stack = new AssignmentStackElement(scVar, scVar.value) - scVar.value = value - stack - } - } - - private class AssignmentStackElement[T](scVar: ScopedVar[T], oldValue: T) { - private[ScopedVar] def pop(): Unit = { - scVar.value = oldValue - } - } - - implicit def toValue[T](scVar: ScopedVar[T]): T = scVar.get - - def withScopedVars[T](ass: Assignment[_]*)(body: => T): T = { - val stack = ass.map(_.push()) - try body - finally stack.reverse.foreach(_.pop()) - } -} diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index 4bc5263e9..ad3249be2 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -17,7 +17,6 @@ import core.DenotTransformers.DenotTransformer import core.Denotations.SingleDenotation import dotty.tools.backend.jvm.{LabelDefs, GenBCode, CollectSuperCalls} -import dotty.tools.backend.sjs.GenSJSIR /** The central class of the dotc compiler. The job of a compiler is to create * runs, which process given `phases` in a given `rootContext`. @@ -98,7 +97,6 @@ class Compiler { new DropInlined, // Drop Inlined nodes, since backend has no use for them new MoveStatics, // Move static methods to companion classes new LabelDefs), // Converts calls to labels to jumps - List(new GenSJSIR), // Generate .js code List(new GenBCode) // Generate JVM bytecode ) @@ -117,20 +115,7 @@ class Compiler { */ def rootContext(implicit ctx: Context): Context = { ctx.initialize()(ctx) - val actualPhases = if (ctx.settings.scalajs.value) { - // Remove phases that Scala.js does not want - phases.mapConserve(_.filter { - case _: FunctionalInterfaces => false - case _ => true - }).filter(_.nonEmpty) - } else { - // Remove Scala.js-related phases - phases.mapConserve(_.filter { - case _: GenSJSIR => false - case _ => true - }).filter(_.nonEmpty) - } - ctx.setPhasePlan(actualPhases) + ctx.setPhasePlan(phases) val rootScope = new MutableScope val bootstrap = ctx.fresh .setPeriod(Period(nextRunId, FirstPhaseId)) diff --git a/src/dotty/tools/dotc/config/PathResolver.scala b/src/dotty/tools/dotc/config/PathResolver.scala index 1f9ee7eec..14a44531a 100644 --- a/src/dotty/tools/dotc/config/PathResolver.scala +++ b/src/dotty/tools/dotc/config/PathResolver.scala @@ -46,18 +46,7 @@ object PathResolver { def classPathEnv = envOrElse("CLASSPATH", "") def sourcePathEnv = envOrElse("SOURCEPATH", "") - def javaBootClassPath = - propOrElse("sun.boot.class.path", searchForBootClasspath) - .split(":") - .filterNot { jar => - // let's blacklist locally compiled classes: - jar.contains("/dotty/library/target/classes") || - jar.contains("/dotty/library/target/scala-2.11/classes") || - jar.contains("/dotty/interfaces/target/classes") || - jar.contains("/dotty/target/scala-2.11/classes") || - jar.contains("/dotty/target/classes") - } - .mkString(":") + def javaBootClassPath = propOrElse("sun.boot.class.path", searchForBootClasspath) def javaExtDirs = propOrEmpty("java.ext.dirs") def scalaHome = propOrEmpty("scala.home") @@ -266,8 +255,15 @@ class PathResolver(implicit ctx: Context) { def containers = Calculated.containers lazy val result: JavaClassPath = { - val (dottyJars, others) = containers.partition(_.name.contains("dotty")) - val cp = new JavaClassPath((dottyJars ++ others).toIndexedSeq, context) + // Prioritize `dotty.jar` and `dotty-lib.jar` to shadow others + val (dottyJars, others) = + containers.partition(x => x.name.contains("dotty-lib.jar") || x.name.contains("dotty.jar")) + // Then any jars with `dotty` in the name - putting them before scala-library + val (dottyCp, remaining) = + others.partition(_.name.contains("dotty-")) + + val cp = new JavaClassPath((dottyJars ++ dottyCp ++ remaining).toIndexedSeq, context) + if (settings.Ylogcp.value) { Console.println("Classpath built from " + settings.toConciseString(ctx.sstate)) Console.println("Defaults: " + PathResolver.Defaults) diff --git a/src/dotty/tools/dotc/config/SJSPlatform.scala b/src/dotty/tools/dotc/config/SJSPlatform.scala deleted file mode 100644 index 3ec8049ae..000000000 --- a/src/dotty/tools/dotc/config/SJSPlatform.scala +++ /dev/null @@ -1,18 +0,0 @@ -package dotty.tools.dotc.config - -import dotty.tools.dotc.core._ -import Contexts._ -import Symbols._ - -import dotty.tools.backend.sjs.JSDefinitions - -class SJSPlatform()(implicit ctx: Context) extends JavaPlatform { - - /** Scala.js-specific definitions. */ - val jsDefinitions: JSDefinitions = new JSDefinitions() - - /** Is the SAMType `cls` also a SAM under the rules of the Scala.js back-end? */ - override def isSam(cls: ClassSymbol)(implicit ctx: Context): Boolean = - defn.isFunctionClass(cls) || jsDefinitions.isJSFunctionClass(cls) - -} diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index edc68588d..639c4d111 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -27,7 +27,7 @@ import reporting._ import collection.mutable import collection.immutable.BitSet import printing._ -import config.{Settings, ScalaSettings, Platform, JavaPlatform, SJSPlatform} +import config.{Settings, ScalaSettings, Platform, JavaPlatform} import language.implicitConversions import DenotTransformers.DenotTransformer import util.Property.Key @@ -550,8 +550,7 @@ object Contexts { } protected def newPlatform(implicit ctx: Context): Platform = - if (settings.scalajs.value) new SJSPlatform - else new JavaPlatform + new JavaPlatform /** The loader that loads the members of _root_ */ def rootLoader(root: TermSymbol)(implicit ctx: Context): SymbolLoader = platform.rootLoader(root) diff --git a/src/scalaShadowing/language.scala b/src/scalaShadowing/language.scala deleted file mode 100644 index a74c9c671..000000000 --- a/src/scalaShadowing/language.scala +++ /dev/null @@ -1,198 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2015, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ -package scalaShadowing - -/** - * The `scala.language` object controls the language features available to the programmer, as proposed in the - * [[https://docs.google.com/document/d/1nlkvpoIRkx7at1qJEZafJwthZ3GeIklTFhqmXMvTX9Q/edit '''SIP-18 document''']]. - * - * Each of these features has to be explicitly imported into the current scope to become available: - * {{{ - * import language.postfixOps // or language._ - * List(1, 2, 3) reverse - * }}} - * - * The language features are: - * - [[dynamics `dynamics`]] enables defining calls rewriting using the [[scala.Dynamic `Dynamic`]] trait - * - [[postfixOps `postfixOps`]] enables postfix operators - * - [[reflectiveCalls `reflectiveCalls`]] enables using structural types - * - [[implicitConversions `implicitConversions`]] enables defining implicit methods and members - * - [[higherKinds `higherKinds`]] enables writing higher-kinded types - * - [[existentials `existentials`]] enables writing existential types - * - [[experimental `experimental`]] contains newer features that have not yet been tested in production - * - * and, for dotty: - * - * - [[Scala2 `Scala2`] backwards compatibility mode for Scala2 - * - [[noAtoTupling `noAutoTupling`]] disable auto-tupling - * - * @groupname production Language Features - * @groupname experimental Experimental Language Features - * @groupprio experimental 10 - * - * Dotty-specific features come at the end. - * - * Note: Due to the more restricted language import mechanism in dotty (only - * imports count, implicits are disregarded) we don't need the constructions - * of the inherited language features. A simple object for each feature is - * sufficient. - */ -object language { - - import languageFeature._ - - /** Where enabled, direct or indirect subclasses of trait scala.Dynamic can - * be defined. Unless dynamics is enabled, a definition of a class, trait, - * or object that has Dynamic as a base trait is rejected. Dynamic member - * selection of existing subclasses of trait Dynamic are unaffected; - * they can be used anywhere. - * - * '''Why introduce the feature?''' To enable flexible DSLs and convenient interfacing - * with dynamic languages. - * - * '''Why control it?''' Dynamic member selection can undermine static checkability - * of programs. Furthermore, dynamic member selection often relies on reflection, - * which is not available on all platforms. - * - * @group production - */ - @volatile implicit lazy val dynamics: dynamics = languageFeature.dynamics - - /** Only where enabled, postfix operator notation `(expr op)` will be allowed. - * - * '''Why keep the feature?''' Several DSLs written in Scala need the notation. - * - * '''Why control it?''' Postfix operators interact poorly with semicolon inference. - * Most programmers avoid them for this reason. - * - * @group production - */ - @volatile implicit lazy val postfixOps: postfixOps = languageFeature.postfixOps - - /** Only where enabled, accesses to members of structural types that need - * reflection are supported. Reminder: A structural type is a type of the form - * `Parents { Decls }` where `Decls` contains declarations of new members that do - * not override any member in `Parents`. To access one of these members, a - * reflective call is needed. - * - * '''Why keep the feature?''' Structural types provide great flexibility because - * they avoid the need to define inheritance hierarchies a priori. Besides, - * their definition falls out quite naturally from Scala’s concept of type refinement. - * - * '''Why control it?''' Reflection is not available on all platforms. Popular tools - * such as ProGuard have problems dealing with it. Even where reflection is available, - * reflective dispatch can lead to surprising performance degradations. - * - * @group production - */ - @volatile implicit lazy val reflectiveCalls: reflectiveCalls = languageFeature.reflectiveCalls - - /** Only where enabled, definitions of implicit conversions are allowed. An - * implicit conversion is an implicit value of unary function type `A => B`, - * or an implicit method that has in its first parameter section a single, - * non-implicit parameter. Examples: - * - * {{{ - * implicit def stringToInt(s: String): Int = s.length - * implicit val conv = (s: String) => s.length - * implicit def listToX(xs: List[T])(implicit f: T => X): X = ... - * }}} - * - * implicit values of other types are not affected, and neither are implicit - * classes. - * - * '''Why keep the feature?''' Implicit conversions are central to many aspects - * of Scala’s core libraries. - * - * '''Why control it?''' Implicit conversions are known to cause many pitfalls - * if over-used. And there is a tendency to over-use them because they look - * very powerful and their effects seem to be easy to understand. Also, in - * most situations using implicit parameters leads to a better design than - * implicit conversions. - * - * @group production - */ - @volatile implicit lazy val implicitConversions: implicitConversions = languageFeature.implicitConversions - - /** Only where this flag is enabled, higher-kinded types can be written. - * - * '''Why keep the feature?''' Higher-kinded types enable the definition of very general - * abstractions such as functor, monad, or arrow. A significant set of advanced - * libraries relies on them. Higher-kinded types are also at the core of the - * scala-virtualized effort to produce high-performance parallel DSLs through staging. - * - * '''Why control it?''' Higher kinded types in Scala lead to a Turing-complete - * type system, where compiler termination is no longer guaranteed. They tend - * to be useful mostly for type-level computation and for highly generic design - * patterns. The level of abstraction implied by these design patterns is often - * a barrier to understanding for newcomers to a Scala codebase. Some syntactic - * aspects of higher-kinded types are hard to understand for the uninitiated and - * type inference is less effective for them than for normal types. Because we are - * not completely happy with them yet, it is possible that some aspects of - * higher-kinded types will change in future versions of Scala. So an explicit - * enabling also serves as a warning that code involving higher-kinded types - * might have to be slightly revised in the future. - * - * @group production - */ - @volatile implicit lazy val higherKinds: higherKinds = languageFeature.higherKinds - - /** Only where enabled, existential types that cannot be expressed as wildcard - * types can be written and are allowed in inferred types of values or return - * types of methods. Existential types with wildcard type syntax such as `List[_]`, - * or `Map[String, _]` are not affected. - * - * '''Why keep the feature?''' Existential types are needed to make sense of Java’s wildcard - * types and raw types and the erased types of run-time values. - * - * '''Why control it?''' Having complex existential types in a code base usually makes - * application code very brittle, with a tendency to produce type errors with - * obscure error messages. Therefore, going overboard with existential types - * is generally perceived not to be a good idea. Also, complicated existential types - * might be no longer supported in a future simplification of the language. - * - * @group production - */ - @volatile implicit lazy val existentials: existentials = languageFeature.existentials - - /** The experimental object contains features that have been recently added but have not - * been thoroughly tested in production yet. - * - * Experimental features '''may undergo API changes''' in future releases, so production - * code should not rely on them. - * - * Programmers are encouraged to try out experimental features and - * [[http://issues.scala-lang.org report any bugs or API inconsistencies]] - * they encounter so they can be improved in future releases. - * - * @group experimental - */ - object experimental { - - import languageFeature.experimental._ - - /** Where enabled, macro definitions are allowed. Macro implementations and - * macro applications are unaffected; they can be used anywhere. - * - * '''Why introduce the feature?''' Macros promise to make the language more regular, - * replacing ad-hoc language constructs with a general powerful abstraction - * capability that can express them. Macros are also a more disciplined and - * powerful replacement for compiler plugins. - * - * '''Why control it?''' For their very power, macros can lead to code that is hard - * to debug and understand. - */ - @volatile implicit lazy val macros: macros = languageFeature.experimental.macros - } - - /** Where imported, a backwards compatibility mode for Scala2 is enabled */ - object Scala2 - - /** Where imported, auto-tupling is disabled */ - object noAutoTupling -} diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 747396c73..d96111dee 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -3,6 +3,7 @@ package dotc import test._ import org.junit.{Before, Test} +import java.io.{ File => JFile } import scala.reflect.io.Directory import scala.io.Source @@ -23,20 +24,34 @@ class tests extends CompilerTest { "-d", defaultOutputDir ) - val classPath = List( + val checkOptions = List( "-Yno-deep-subtypes", "-Yno-double-bindings", "-Yforce-sbt-phases", - "-color:never", - "-classpath", - "./library/target/scala-2.11/dotty-library_2.11-0.1-SNAPSHOT.jar" + - ":./interfaces/target/dotty-interfaces-0.1-SNAPSHOT.jar" + "-color:never" ) + val classPath = { + val paths = List( + "./library/target/scala-2.11/dotty-library_2.11-0.1-SNAPSHOT.jar", + "./target/scala-2.11/dotty-compiler_2.11-0.1-SNAPSHOT.jar", + "./interfaces/target/dotty-interfaces-0.1-SNAPSHOT.jar" + ).map { p => + val file = new JFile(p) + assert( + file.exists, + s"""File "$p" couldn't be found. Run `packageAll` from build tool before testing""" + ) + file.getAbsolutePath + }.mkString(":") + + List("-classpath", paths) + } + implicit val defaultOptions = noCheckOptions ++ { if (isRunByJenkins) List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef") // should be Ycheck:all, but #725 else List("-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef") - } ++ classPath + } ++ checkOptions ++ classPath val testPickling = List("-Xprint-types", "-Ytest-pickler", "-Ystop-after:pickler", "-Yprintpos") @@ -189,32 +204,10 @@ class tests extends CompilerTest { @Test def compileIndexedSeq = compileLine("./scala-scala/src/library/scala/collection/immutable/IndexedSeq.scala") // Not a junit test anymore since it is order dependent - def dottyBootedLib = compileDir( - libDir, - ".", - List( - "-deep", "-Ycheck-reentrant", "-strict", "-classpath", defaultOutputDir + - ":./target/scala-2.11/dotty-compiler_2.11-0.1-SNAPSHOT.jar" + - ":./interfaces/target/dotty-interfaces-0.1-SNAPSHOT.jar" + - ":./library/target/scala-2.11/dotty-library_2.11-0.1-SNAPSHOT.jar" - ) - )(allowDeepSubtypes) // note the -deep argument + def dottyBootedLib = compileDir(libDir, ".")(allowDeepSubtypes) // note the -deep argument // Not a junit test anymore since it is order dependent - def dottyDependsOnBootedLib = compileDir( - dottyDir, - ".", - List( - "-deep", "-Ycheck-reentrant", "-strict", "-classpath", defaultOutputDir + - ":./dotty-lib.jar" + - ":./interfaces/target/dotty-interfaces-0.1-SNAPSHOT.jar" + - // this needs to get compiled together with the compiler: - //":./target/scala-2.11/src_managed/main/scalajs-ir-src/" - // but falling back to: - ":/home/fixel/.ivy2/cache/org.scala-js/scalajs-ir_2.11/jars/scalajs-ir_2.11-0.6.8.jar" - // for the time being. - ) - )(allowDeepSubtypes) // note the -deep argument + @Test def dottyDependsOnBootedLib = compileDir(dottyDir, ".")(allowDeepSubtypes) // note the -deep argument @Test def dotc_ast = compileDir(dotcDir, "ast") @Test def dotc_config = compileDir(dotcDir, "config") @@ -279,7 +272,7 @@ class tests extends CompilerTest { "scalaPrimitives.scala" ) map (s"${backendDir}jvm/" + _), testPickling) - @Test def tasty_backend_sjs = compileDir(s"${backendDir}", "sjs", testPickling) + //@Test def tasty_backend_sjs = compileDir(s"${backendDir}", "sjs", testPickling) @Test def tasty_dotc = compileDir(toolsDir, "dotc", testPickling) @Test def tasty_dotc_ast = compileDir(dotcDir, "ast", testPickling) diff --git a/test/dotty/partest/DPConsoleRunner.scala b/test/dotty/partest/DPConsoleRunner.scala index 27d53159b..4552d1137 100644 --- a/test/dotty/partest/DPConsoleRunner.scala +++ b/test/dotty/partest/DPConsoleRunner.scala @@ -30,10 +30,6 @@ object DPConsoleRunner { case Nil => sys.error("Error: DPConsoleRunner needs \"-dottyJars *\".") case jarFinder(nr, jarString) :: Nil => val jars = jarString.split(" ").toList - println("------------------------------------------------------------") - println("jars:") - jars.foreach(println) - println("------------------------------------------------------------") val count = nr.toInt if (jars.length < count) sys.error("Error: DPConsoleRunner found wrong number of dottyJars: " + jars + ", expected: " + nr) @@ -155,11 +151,7 @@ class DPTestRunner(testFile: File, suiteRunner: DPSuiteRunner) extends nest.Runn "-d", outDir.getAbsolutePath, "-classpath", - joinPaths(outDir :: extraClasspath.filter { fp => - fp.endsWith("dotty-lib.jar") || - fp.endsWith("scala-library-2.11.5.jar") || - fp.endsWith("scala-reflect-2.11.5.jar") - }) + joinPaths(outDir :: extraClasspath ++ testClassPath) ) ++ files.map(_.getAbsolutePath) pushTranscript(args mkString " ") @@ -172,58 +164,35 @@ class DPTestRunner(testFile: File, suiteRunner: DPSuiteRunner) extends nest.Runn } } - override def run(): TestState = { - if (kind == "run") { - // javac runner, for one, would merely append to an existing log file, so - // just delete it before we start - logFile.delete() - runTestCommon(execTest(outDir, logFile) && diffIsOk) - lastState - } else super.run() - } - - // Re-implemented for running tests - def execTest(outDir: File, logFile: File): Boolean = { - val argsFile = testFile changeExtension "javaopts" - val argString = file2String(argsFile) - if (argString != "") NestUI.verbose( - "Found javaopts file '%s', using options: '%s'".format(argsFile, argString) - ) - - val classpath = joinPaths { - val sep = sys.props("path.separator") - val fps = extraClasspath.filter { fp => - fp.endsWith("dotty-lib.jar") || - fp.endsWith("scala-library-2.11.5.jar") || - fp.endsWith("scala-reflect-2.11.5.jar") + // Overriden in order to recursively get all sources that should be handed to + // the compiler. Otherwise only sources in the top dir is compiled - works + // because the compiler is on the classpath. + override def sources(file: File): List[File] = + if (file.isDirectory) + file.listFiles.toList.flatMap { f => + if (f.isDirectory) sources(f) + else if (f.isJavaOrScala) List(f) + else Nil } - - fps ++ fileManager.testClassPath - } - - val javaOpts: List[String] = ( - suiteRunner.javaOpts.split(' ') ++ - extraJavaOptions ++ - argString.split(' ') - ).map(_.trim).filter(_ != "").toList - - val cmd: List[String] = (suiteRunner.javaCmdPath :: javaOpts) ++ ( - "-classpath" :: join(outDir.toString, classpath) :: - "Test" :: "jvm" :: // default argument to Test class in super is "jvm" - Nil - ) - - pushTranscript((cmd mkString s" \\$EOL ") + " > " + logFile.getName) - nextTestAction(runCommand(cmd, logFile)) { - case false => - //_transcript append EOL + logFile.fileContents - // think this is equivalent: - val contents = logFile.fileContents - println(contents) - pushTranscript(contents) - genFail("non-zero exit code") - } - } + else List(file) + + // Enable me to "fix" the depth issue - remove once completed + //override def compilationRounds(file: File): List[CompileRound] = { + // val srcs = sources(file) match { + // case Nil => + // System.err.println { + // s"""|================================================================================ + // |Warning! You attempted to compile sources from: + // | $file + // |but partest was unable to find any sources - uncomment DPConsoleRunner#sources + // |================================================================================""".stripMargin + // } + // List(new File("./examples/hello.scala")) // "just compile some crap" - Guillaume + // case xs => + // xs + // } + // (groupedFiles(srcs) map mixedCompileGroup).flatten + //} // FIXME: This is copy-pasted from nest.Runner where it is private // Remove this once https://github.com/scala/scala-partest/pull/61 is merged @@ -371,16 +340,17 @@ class DPTestRunner(testFile: File, suiteRunner: DPSuiteRunner) extends nest.Runn } // override to add dotty and scala jars to classpath - override def extraClasspath = { - val cp = suiteRunner.fileManager.asInstanceOf[DottyFileManager].extraJarList ::: super.extraClasspath - //println(s"extraClasspath: $cp") - cp - } + override def extraClasspath = + suiteRunner.fileManager.asInstanceOf[DottyFileManager].extraJarList ::: super.extraClasspath // override to keep class files if failed and delete clog if ok - override def cleanup = if (lastState.isOk) { + override def cleanup = if (lastState.isOk) try { logFile.delete cLogFile.delete Directory(outDir).deleteRecursively + } catch { + case t: Throwable => + println("whhhhhhhhhhhhhhhhhhhhhhhhhhhaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaat") + throw t } } diff --git a/test/test/CompilerTest.scala b/test/test/CompilerTest.scala index 5a4a9830a..05a7d62b8 100644 --- a/test/test/CompilerTest.scala +++ b/test/test/CompilerTest.scala @@ -442,8 +442,14 @@ abstract class CompilerTest { nr: Int = 0, oldOutput: String = defaultOutputDir): Unit = { val partestOutput = dest.jfile.getParentFile + JFile.separator + dest.stripExtension + "-" + kind + ".obj" - val flags = oldFlags.map(f => if (f == oldOutput) partestOutput else f) ++ - List(s"-classpath $partestOutput") // Required for separate compilation tests + + val altOutput = + source.getParentFile.getAbsolutePath.map(x => if (x == JFile.separatorChar) '_' else x) + + val (beforeCp, remaining) = oldFlags + .map(f => if (f == oldOutput) partestOutput else f) + .span(_ != "-classpath") + val flags = beforeCp ++ List("-classpath", (partestOutput :: remaining.drop(1)).mkString(":")) val difference = getExisting(dest).isDifferent(source, flags, nerr) difference match { @@ -451,8 +457,12 @@ abstract class CompilerTest { case ExistsSame => // nothing else to do case ExistsDifferent => val nextDest = dest.parent / (dest match { - case d: Directory => Directory(replaceVersion(d.name, nr)) - case f => SFile(replaceVersion(f.stripExtension, nr)).addExtension(f.extension) + case d: Directory => + val newVersion = replaceVersion(d.name, nr).getOrElse(altOutput) + Directory(newVersion) + case f => + val newVersion = replaceVersion(f.stripExtension, nr).getOrElse(altOutput) + SFile(newVersion).addExtension(f.extension) }) computeDestAndCopyFiles(source, nextDest, kind, flags, nerr, nr + 1, partestOutput) } @@ -555,13 +565,12 @@ abstract class CompilerTest { import scala.util.matching.Regex val nrFinder = """(.*_v)(\d+)""".r /** Changes the version number suffix in the name (without extension). */ - private def replaceVersion(name: String, nr: Int): String = { + private def replaceVersion(name: String, nr: Int): Option[String] = { val nrString = nr.toString name match { - case nrFinder(prefix, `nrString`) => prefix + (nr + 1) - case _ => - assert(nr == 0, "DPCompilerTest couldn't create new version of files, match error") - name + "_v1" + case nrFinder(prefix, `nrString`) => Some(prefix + (nr + 1)) + case _ if nr != 0 => None + case _ => Some(name + "_v1") } } diff --git a/tests/repl/imports.check b/tests/repl/imports.check index 4ca59e4ee..7e078fe00 100644 --- a/tests/repl/imports.check +++ b/tests/repl/imports.check @@ -16,13 +16,13 @@ scala> buf += xs scala> buf ++= xs res1: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 3) scala> import util.foo --- Error: ---------------------------------------------------------------------------------------------------- +-- Error: ------------------------------------------------------------ 8 |import util.foo | ^^^ | foo is not a member of util scala> import util.foo.bar --- [E008] Member Not Found Error: ---------------------------------------------------------------------------- +-- [E008] Member Not Found Error: ------------------------------------ 8 |import util.foo.bar | ^^^^^^^^ - | value `foo` is not a member of util.type - did you mean `util.Left`? + | value `foo` is not a member of util.type - did you mean `util.Left`? scala> :quit -- cgit v1.2.3