summaryrefslogtreecommitdiff
path: root/examples/scala-js/tools
diff options
context:
space:
mode:
Diffstat (limited to 'examples/scala-js/tools')
-rw-r--r--examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/classpath/builder/NodeFileSystem.scala67
-rw-r--r--examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/io/NodeVirtualFiles.scala62
-rw-r--r--examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/json/Impl.scala36
-rw-r--r--examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/JasmineReporter.scala71
-rw-r--r--examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/QuickLinker.scala37
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/JarLibClasspathBuilder.scala13
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PartialClasspathBuilder.scala38
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PhysicalFileSystem.scala41
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/io/FileVirtualFiles.scala157
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/json/Impl.scala38
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstBuilder.scala47
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstTransformer.scala397
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ConcurrencyUtils.scala74
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/LoggerErrorManager.scala38
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ParIncOptimizer.scala188
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala216
-rw-r--r--examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala88
-rw-r--r--examples/scala-js/tools/jvm/src/test/resources/test.jar0
-rw-r--r--examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/ClasspathElementsTraverserTest.scala42
-rw-r--r--examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/JarBuilderTest.scala74
-rw-r--r--examples/scala-js/tools/scalajsenv.js772
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/CompleteClasspath.scala35
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ComplianceRequirement.scala7
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/Exceptions.scala42
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/IRClasspath.scala69
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/LinkedClasspath.scala26
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/PartialClasspath.scala99
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ResolvedJSDependency.scala10
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractJarLibClasspathBuilder.scala53
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractPartialClasspathBuilder.scala47
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathContentHandler.scala25
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathElementsTraverser.scala38
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/DirTraverser.scala60
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/FileSystem.scala57
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/JarTraverser.scala85
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/corelib/CoreJSLibs.scala113
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSEnv.scala19
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSRunner.scala19
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSEnv.scala38
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSRunner.scala22
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ConsoleJSConsole.scala17
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSConsole.scala15
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSEnv.scala20
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSRunner.scala15
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/NullJSConsole.scala5
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/CacheUtils.scala52
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/IO.scala116
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/MemFiles.scala105
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/VirtualFiles.scala169
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala1525
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/LongImpl.scala116
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Printers.scala420
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/ScalaJSClassEmitter.scala569
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/TreeDSL.scala50
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Trees.scala194
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Exceptions.scala59
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/FlatJSDependency.scala17
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependency.scala66
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependencyManifest.scala130
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Origin.scala28
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/ResolutionInfo.scala21
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/AbstractJSONImpl.scala32
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONDeserializer.scala30
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjBuilder.scala20
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjExtractor.scala13
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONSerializer.scala32
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/package.scala26
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Level.scala24
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Logger.scala25
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/NullLogger.scala7
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/ScalaConsoleLogger.scala15
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/Analyzer.scala587
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/GenIncOptimizer.scala921
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala854
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IncOptimizer.scala158
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/JSTreeBuilder.scala16
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/OptimizerCore.scala3572
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSOptimizer.scala552
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/CheckedBehavior.scala24
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/Semantics.scala97
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/JSFileBuilder.scala144
-rw-r--r--examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala213
82 files changed, 14431 insertions, 0 deletions
diff --git a/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/classpath/builder/NodeFileSystem.scala b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/classpath/builder/NodeFileSystem.scala
new file mode 100644
index 0000000..d1eee10
--- /dev/null
+++ b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/classpath/builder/NodeFileSystem.scala
@@ -0,0 +1,67 @@
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.io._
+
+import scala.scalajs.js
+
+import scala.collection.immutable.Traversable
+
+import java.io._
+
+/** FileSystem implementation using Node.js */
+trait NodeFileSystem extends FileSystem {
+
+ import NodeFileSystem.fs
+
+ type File = String
+
+ private def stats(f: String) = fs.statSync(f)
+
+ val DummyVersion: String = "DUMMY_FILE"
+
+ def isDirectory(f: String): Boolean =
+ stats(f).isDirectory().asInstanceOf[Boolean]
+
+ def isFile(f: String): Boolean =
+ stats(f).isFile().asInstanceOf[Boolean]
+
+ def isJSFile(f: String): Boolean =
+ isFile(f) && f.endsWith(".js")
+
+ def isIRFile(f: String): Boolean =
+ isFile(f) && f.endsWith(".sjsir")
+
+ def isJARFile(f: String): Boolean =
+ isFile(f) && f.endsWith(".jar")
+
+ def exists(f: String): Boolean =
+ fs.existsSync(f).asInstanceOf[Boolean]
+
+ def getName(f: String): String =
+ VirtualFile.nameFromPath(f)
+
+ def getAbsolutePath(f: String): String =
+ fs.realpathSync(f).asInstanceOf[String]
+
+ def getVersion(f: String): String =
+ stats(f).mtime.asInstanceOf[js.Date].getTime.toString
+
+ def listFiles(d: String): Traversable[String] = {
+ require(isDirectory(d))
+ val prefix = if (d.endsWith("/")) d else d + "/"
+
+ fs.readdirSync(d).asInstanceOf[js.Array[String]].toList.map(prefix + _)
+ }
+
+ def toJSFile(f: String): VirtualJSFile = new NodeVirtualJSFile(f)
+ def toIRFile(f: String): VirtualScalaJSIRFile = new NodeVirtualScalaJSIRFile(f)
+ def toReader(f: String): Reader =
+ new NodeVirtualTextFile(f).reader
+ def toInputStream(f: String): InputStream =
+ new NodeVirtualBinaryFile(f).inputStream
+
+}
+
+private object NodeFileSystem {
+ private val fs = js.Dynamic.global.require("fs")
+}
diff --git a/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/io/NodeVirtualFiles.scala b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/io/NodeVirtualFiles.scala
new file mode 100644
index 0000000..6a0c3ee
--- /dev/null
+++ b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/io/NodeVirtualFiles.scala
@@ -0,0 +1,62 @@
+package scala.scalajs.tools.io
+
+import scala.scalajs.js
+import scala.scalajs.js.typedarray._
+
+import java.io._
+import java.net.URI
+
+class NodeVirtualFile(override val path: String) extends VirtualFile {
+ import NodeFS.fs
+
+ override def version: Option[String] = {
+ val stat = fs.statSync(path)
+ if (js.isUndefined(stat.mtime))
+ None
+ else
+ Some(stat.mtime.asInstanceOf[js.Date].getTime.toString)
+ }
+
+ override def exists: Boolean =
+ fs.existsSync(path).asInstanceOf[Boolean]
+
+ override def toURI: URI = {
+ val abspath = fs.realpathSync(path).asInstanceOf[String]
+ new URI("file", abspath, null)
+ }
+}
+
+class NodeVirtualTextFile(p: String) extends NodeVirtualFile(p)
+ with VirtualTextFile {
+ import NodeFS.fs
+
+ override def content: String = {
+ val options = js.Dynamic.literal(encoding = "UTF-8")
+ fs.readFileSync(path, options).asInstanceOf[String]
+ }
+}
+
+class NodeVirtualBinaryFile(p: String) extends NodeVirtualFile(p)
+ with VirtualBinaryFile {
+ import NodeFS.fs
+
+ private def buf: ArrayBuffer =
+ new Uint8Array(fs.readFileSync(path).asInstanceOf[js.Array[Int]]).buffer
+
+ override def content: Array[Byte] = new Int8Array(buf).toArray
+ override def inputStream: InputStream = new ArrayBufferInputStream(buf)
+}
+
+class NodeVirtualJSFile(p: String) extends NodeVirtualTextFile(p)
+ with VirtualJSFile {
+
+ /** Always returns None. We can't read them on JS anyway */
+ override def sourceMap: Option[String] = None
+}
+
+class NodeVirtualScalaJSIRFile(p: String)
+ extends NodeVirtualBinaryFile(p) with VirtualSerializedScalaJSIRFile
+
+private[io] object NodeFS {
+ val fs = js.Dynamic.global.require("fs")
+}
diff --git a/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/json/Impl.scala b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/json/Impl.scala
new file mode 100644
index 0000000..89c7255
--- /dev/null
+++ b/examples/scala-js/tools/js/src/main/scala/scala/scalajs/tools/json/Impl.scala
@@ -0,0 +1,36 @@
+package scala.scalajs.tools.json
+
+import scala.scalajs.tools.io.IO
+
+import scala.scalajs.js
+
+import java.io.{Writer, Reader}
+
+private[json] object Impl extends AbstractJSONImpl {
+
+ type Repr = js.Any
+
+ def fromString(x: String): Repr = x
+ def fromNumber(x: Number): Repr = x.doubleValue()
+ def fromBoolean(x: Boolean): Repr = x
+ def fromList(x: List[Repr]): Repr = js.Array(x: _*)
+ def fromMap(x: Map[String, Repr]): Repr = js.Dictionary(x.toSeq: _*)
+
+ def toString(x: Repr): String = x.asInstanceOf[String]
+ def toNumber(x: Repr): Number = x.asInstanceOf[Double]
+ def toBoolean(x: Repr): Boolean = x.asInstanceOf[Boolean]
+ def toList(x: Repr): List[Repr] = x.asInstanceOf[js.Array[Repr]].toList
+ def toMap(x: Repr): Map[String, Repr] =
+ x.asInstanceOf[js.Dictionary[Repr]].toMap
+
+ def serialize(x: Repr): String = js.JSON.stringify(x)
+
+ def serialize(x: Repr, writer: Writer): Unit =
+ writer.write(serialize(x))
+
+ def deserialize(str: String): Repr = js.JSON.parse(str)
+
+ def deserialize(reader: Reader): Repr =
+ deserialize(IO.readReaderToString(reader))
+
+}
diff --git a/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/JasmineReporter.scala b/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/JasmineReporter.scala
new file mode 100644
index 0000000..7b63871
--- /dev/null
+++ b/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/JasmineReporter.scala
@@ -0,0 +1,71 @@
+package scala.scalajs.tools.js.test
+
+import org.scalajs.jasmine.Suite
+
+import org.scalajs.jasminetest._
+
+import scala.scalajs.js.annotation.JSExport
+
+import scala.scalajs.testbridge._
+
+object JSConsoleTestOutput extends TestOutput {
+
+ type Color = Null
+
+ val errorColor: Color = null
+ val successColor: Color = null
+ val infoColor: Color = null
+
+ def color(message: String, color: Color): String = message
+
+ def error(message: String, stack: Array[StackTraceElement]): Unit =
+ withStack(message, stack)
+
+ def error(message: String): Unit = println(message)
+
+ def failure(message: String, stack: Array[StackTraceElement]): Unit =
+ withStack(message, stack)
+
+ def failure(message: String): Unit = println(message)
+ def succeeded(message: String): Unit = println(message)
+ def skipped(message: String): Unit = println(message)
+ def pending(message: String): Unit = println(message)
+ def ignored(message: String): Unit = println(message)
+ def canceled(message: String): Unit = println(message)
+
+ object log extends TestOutputLog {
+ def info(message: String): Unit = println(message)
+ def warn(message: String): Unit = println(message)
+ def error(message: String): Unit = println(message)
+ }
+
+ private def withStack(message: String, stack: Array[StackTraceElement]) =
+ println(message + stack.mkString("\n", "\n", ""))
+
+}
+
+@JSExport("scalajs.JasmineConsoleReporter")
+class JasmineConsoleReporter(throwOnFail: Boolean = false)
+ extends JasmineTestReporter(JSConsoleTestOutput) {
+
+ private var suiteFails: Int = 0
+ private var suiteCount: Int = 0
+
+ override def reportSuiteResults(suite: Suite): Unit = {
+ super.reportSuiteResults(suite)
+ if (suite.results().failedCount > 0)
+ suiteFails += 1
+ suiteCount += 1
+ }
+
+ override def reportRunnerResults(): Unit = {
+ super.reportRunnerResults()
+ val failed = suiteFails > 0
+ val resStr = if (failed) "Failed" else "Passed"
+ println(s"$resStr: Total $suiteCount, Failed $suiteFails")
+
+ if (failed && throwOnFail)
+ sys.error("Jasmine test suite failed.")
+ }
+
+}
diff --git a/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/QuickLinker.scala b/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/QuickLinker.scala
new file mode 100644
index 0000000..580c4c5
--- /dev/null
+++ b/examples/scala-js/tools/js/src/test/scala/scala/scalajs/tools/js/test/QuickLinker.scala
@@ -0,0 +1,37 @@
+package scala.scalajs.tools.js.test
+
+import scala.scalajs.tools.sem.Semantics
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.logging._
+import scala.scalajs.tools.classpath._
+import scala.scalajs.tools.classpath.builder._
+import scala.scalajs.tools.optimizer._
+
+import scala.scalajs.js.annotation.JSExport
+
+@JSExport("scalajs.QuickLinker")
+object QuickLinker {
+
+ /** Link a Scala.js application on Node.js */
+ @JSExport
+ def linkNode(cpEntries: String*): String = {
+ val builder = new AbstractPartialClasspathBuilder with NodeFileSystem
+ val cp = builder.build(cpEntries.toList)
+
+ val complete = cp.resolve()
+
+ val optimizer = new ScalaJSOptimizer(Semantics.Defaults.optimized)
+
+ val out = WritableMemVirtualJSFile("out.js")
+
+ import ScalaJSOptimizer._
+ val optimized = optimizer.optimizeCP(
+ Inputs(complete),
+ OutputConfig(out),
+ new ScalaConsoleLogger
+ )
+
+ out.content
+ }
+
+}
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/JarLibClasspathBuilder.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/JarLibClasspathBuilder.scala
new file mode 100644
index 0000000..5bc488c
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/JarLibClasspathBuilder.scala
@@ -0,0 +1,13 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+class JarLibClasspathBuilder extends AbstractJarLibClasspathBuilder
+ with PhysicalFileSystem
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PartialClasspathBuilder.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PartialClasspathBuilder.scala
new file mode 100644
index 0000000..626c74c
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PartialClasspathBuilder.scala
@@ -0,0 +1,38 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.classpath._
+import scala.collection.immutable.Seq
+
+/**
+ * Allows to create a PartialClasspathBuilder from a (filesystem) classpath
+ *
+ * Rules for classpath reading:
+ * - IR goes to scalaJSIR
+ * - Descends into JARs
+ * - Entries stay in order of ‘cp‘, IR remains unordered
+ * - Earlier IR entries shadow later IR entries with the same relative path
+ * - JS goes to availableLibs (earlier libs take precedence)
+ * - JS_DEPENDENCIES are added to dependencies
+ */
+class PartialClasspathBuilder extends AbstractPartialClasspathBuilder
+ with PhysicalFileSystem
+
+object PartialClasspathBuilder {
+ /** Convenience method. The same as
+ *
+ * {{{
+ * (new PartialClasspathBuilder).build(cp)
+ * }}}
+ */
+ def build(cp: Seq[java.io.File]): PartialClasspath =
+ (new PartialClasspathBuilder).build(cp)
+}
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PhysicalFileSystem.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PhysicalFileSystem.scala
new file mode 100644
index 0000000..a0dd7a5
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PhysicalFileSystem.scala
@@ -0,0 +1,41 @@
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.io._
+
+import scala.collection.immutable.Traversable
+
+import java.io._
+
+/** FileSystem implementation using java.io._ */
+trait PhysicalFileSystem extends FileSystem {
+
+ type File = java.io.File
+
+ val DummyVersion: String = "DUMMY_FILE"
+
+ def isDirectory(f: File): Boolean = f.isDirectory
+ def isFile(f: File): Boolean = f.isFile
+ def isJSFile(f: File): Boolean = f.isFile && f.getName.endsWith(".js")
+ def isIRFile(f: File): Boolean = f.isFile && f.getName.endsWith(".sjsir")
+ def isJARFile(f: File): Boolean = f.isFile && f.getName.endsWith(".jar")
+ def exists(f: File): Boolean = f.exists
+
+ def getName(f: File): String = f.getName
+ def getAbsolutePath(f: File): String = f.getAbsolutePath
+ def getVersion(f: File): String = f.lastModified.toString
+
+ def listFiles(d: File): Traversable[File] = {
+ require(d.isDirectory)
+ Option(d.listFiles).map(_.toList).getOrElse {
+ throw new IOException(s"Couldn't list files in $d")
+ }
+ }
+
+ def toJSFile(f: File): VirtualJSFile = FileVirtualJSFile(f)
+ def toIRFile(f: File): VirtualScalaJSIRFile = FileVirtualScalaJSIRFile(f)
+ def toReader(f: File): Reader =
+ new BufferedReader(new FileReader(f))
+ def toInputStream(f: File): InputStream =
+ new BufferedInputStream(new FileInputStream(f))
+
+}
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/io/FileVirtualFiles.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/io/FileVirtualFiles.scala
new file mode 100644
index 0000000..da29225
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/io/FileVirtualFiles.scala
@@ -0,0 +1,157 @@
+package scala.scalajs.tools.io
+
+import scala.annotation.tailrec
+
+import java.io._
+import java.net.URI
+
+/** A [[VirtualFile]] implemented by an actual file on the file system. */
+class FileVirtualFile(val file: File) extends VirtualFile {
+ import FileVirtualFile._
+
+ override def path = file.getPath
+
+ override def name = file.getName
+
+ override def version: Option[String] = {
+ if (!file.isFile) None
+ else Some(file.lastModified.toString)
+ }
+
+ override def exists: Boolean = file.exists
+
+ override def toURI: URI = file.toURI
+}
+
+object FileVirtualFile extends (File => FileVirtualFile) {
+ def apply(f: File): FileVirtualFile =
+ new FileVirtualFile(f)
+
+ /** Tests whether the given file has the specified extension.
+ * Extension contain the '.', so a typical value for `ext` would be ".js".
+ * The comparison is case-sensitive.
+ */
+ def hasExtension(file: File, ext: String): Boolean =
+ file.getName.endsWith(ext)
+
+ /** Returns a new file with the same parent as the given file but a different
+ * name.
+ */
+ def withName(file: File, newName: String): File =
+ new File(file.getParentFile(), newName)
+
+ /** Returns a new file with the same path as the given file but a different
+ * extension.
+ * Extension contain the '.', so a typical value for `ext` would be ".js".
+ * Precondition: hasExtension(file, oldExt)
+ */
+ def withExtension(file: File, oldExt: String, newExt: String): File = {
+ require(hasExtension(file, oldExt),
+ s"File $file does not have extension '$oldExt'")
+ withName(file, file.getName.stripSuffix(oldExt) + newExt)
+ }
+}
+
+/** A [[VirtualTextFile]] implemented by an actual file on the file system. */
+class FileVirtualTextFile(f: File) extends FileVirtualFile(f)
+ with VirtualTextFile {
+ import FileVirtualTextFile._
+
+ override def content: String = readFileToString(file)
+ override def reader: Reader = new BufferedReader(new FileReader(f))
+}
+
+object FileVirtualTextFile extends (File => FileVirtualTextFile) {
+ def apply(f: File): FileVirtualTextFile =
+ new FileVirtualTextFile(f)
+
+ /** Reads the entire content of a file as a UTF-8 string. */
+ def readFileToString(file: File): String = {
+ val stream = new FileInputStream(file)
+ try IO.readInputStreamToString(stream)
+ finally stream.close()
+ }
+}
+
+trait WritableFileVirtualTextFile extends FileVirtualTextFile
+ with WritableVirtualTextFile {
+ override def contentWriter: Writer = {
+ new BufferedWriter(new OutputStreamWriter(
+ new FileOutputStream(file), "UTF-8"))
+ }
+}
+
+object WritableFileVirtualTextFile {
+ def apply(f: File): WritableFileVirtualTextFile =
+ new FileVirtualTextFile(f) with WritableFileVirtualTextFile
+}
+
+/** A [[VirtualBinaryFile]] implemented by an actual file on the file system. */
+class FileVirtualBinaryFile(f: File) extends FileVirtualFile(f)
+ with VirtualBinaryFile {
+ import FileVirtualBinaryFile._
+
+ override def inputStream: InputStream =
+ new BufferedInputStream(new FileInputStream(file))
+
+ override def content: Array[Byte] =
+ readFileToByteArray(file)
+}
+
+object FileVirtualBinaryFile extends (File => FileVirtualBinaryFile) {
+ def apply(f: File): FileVirtualBinaryFile =
+ new FileVirtualBinaryFile(f)
+
+ /** Reads the entire content of a file as byte array. */
+ def readFileToByteArray(file: File): Array[Byte] = {
+ val stream = new FileInputStream(file)
+ try IO.readInputStreamToByteArray(stream)
+ finally stream.close()
+ }
+}
+
+class FileVirtualJSFile(f: File) extends FileVirtualTextFile(f)
+ with VirtualJSFile {
+ import FileVirtualFile._
+ import FileVirtualTextFile._
+
+ val sourceMapFile: File = withExtension(file, ".js", ".js.map")
+
+ override def sourceMap: Option[String] = {
+ if (sourceMapFile.exists) Some(readFileToString(sourceMapFile))
+ else None
+ }
+}
+
+object FileVirtualJSFile extends (File => FileVirtualJSFile) {
+ def apply(f: File): FileVirtualJSFile =
+ new FileVirtualJSFile(f)
+}
+
+trait WritableFileVirtualJSFile extends FileVirtualJSFile
+ with WritableFileVirtualTextFile
+ with WritableVirtualJSFile {
+
+ override def sourceMapWriter: Writer = {
+ new BufferedWriter(new OutputStreamWriter(
+ new FileOutputStream(sourceMapFile), "UTF-8"))
+ }
+}
+
+object WritableFileVirtualJSFile {
+ def apply(f: File): WritableFileVirtualJSFile =
+ new FileVirtualJSFile(f) with WritableFileVirtualJSFile
+}
+
+class FileVirtualScalaJSIRFile(f: File)
+ extends FileVirtualBinaryFile(f) with VirtualSerializedScalaJSIRFile
+
+object FileVirtualScalaJSIRFile extends (File => FileVirtualScalaJSIRFile) {
+ import FileVirtualFile._
+
+ def apply(f: File): FileVirtualScalaJSIRFile =
+ new FileVirtualScalaJSIRFile(f)
+
+ def isScalaJSIRFile(file: File): Boolean =
+ hasExtension(file, ".sjsir")
+}
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/json/Impl.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/json/Impl.scala
new file mode 100644
index 0000000..ea847e3
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/json/Impl.scala
@@ -0,0 +1,38 @@
+package scala.scalajs.tools.json
+
+import org.json.simple.JSONValue
+
+import scala.collection.JavaConverters._
+
+import java.io.{Writer, Reader}
+
+private[json] object Impl extends AbstractJSONImpl {
+
+ type Repr = Object
+
+ def fromString(x: String): Repr = x
+ def fromNumber(x: Number): Repr = x
+ def fromBoolean(x: Boolean): Repr = java.lang.Boolean.valueOf(x)
+ def fromList(x: List[Repr]): Repr = x.asJava
+ def fromMap(x: Map[String, Repr]): Repr = x.asJava
+
+ def toString(x: Repr): String = x.asInstanceOf[String]
+ def toNumber(x: Repr): Number = x.asInstanceOf[Number]
+ def toBoolean(x: Repr): Boolean =
+ x.asInstanceOf[java.lang.Boolean].booleanValue()
+ def toList(x: Repr): List[Repr] =
+ x.asInstanceOf[java.util.List[Repr]].asScala.toList
+ def toMap(x: Repr): Map[String, Repr] =
+ x.asInstanceOf[java.util.Map[String, Repr]].asScala.toMap
+
+ def serialize(x: Repr): String =
+ JSONValue.toJSONString(x)
+
+ def serialize(x: Repr, writer: Writer): Unit =
+ JSONValue.writeJSONString(x, writer)
+
+ def deserialize(str: String): Repr = JSONValue.parse(str)
+
+ def deserialize(reader: Reader): Repr = JSONValue.parse(reader)
+
+}
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstBuilder.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstBuilder.scala
new file mode 100644
index 0000000..8d2eb2b
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstBuilder.scala
@@ -0,0 +1,47 @@
+package scala.scalajs.tools.optimizer
+
+import scala.scalajs.ir
+import ir.Position.NoPosition
+
+import scala.scalajs.tools.javascript.Trees.Tree
+
+import com.google.javascript.rhino._
+import com.google.javascript.rhino.jstype.{StaticSourceFile, SimpleSourceFile}
+import com.google.javascript.jscomp._
+
+import scala.collection.mutable
+
+import java.net.URI
+
+class ClosureAstBuilder(
+ relativizeBaseURI: Option[URI] = None) extends JSTreeBuilder {
+
+ private val transformer = new ClosureAstTransformer(relativizeBaseURI)
+ private val treeBuf = mutable.ListBuffer.empty[Node]
+
+ def addJSTree(tree: Tree): Unit =
+ treeBuf += transformer.transformStat(tree)(NoPosition)
+
+ lazy val closureAST: SourceAst = {
+ val root = transformer.setNodePosition(IR.script(treeBuf: _*), NoPosition)
+
+ treeBuf.clear()
+
+ new ClosureAstBuilder.ScalaJSSourceAst(root)
+ }
+
+}
+
+object ClosureAstBuilder {
+ // Dummy Source AST class
+
+ class ScalaJSSourceAst(root: Node) extends SourceAst {
+ def getAstRoot(compiler: AbstractCompiler): Node = root
+ def clearAst(): Unit = () // Just for GC. Nonsensical here.
+ def getInputId(): InputId = root.getInputId()
+ def getSourceFile(): SourceFile =
+ root.getStaticSourceFile().asInstanceOf[SourceFile]
+ def setSourceFile(file: SourceFile): Unit =
+ if (getSourceFile() ne file) throw new IllegalStateException
+ }
+}
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstTransformer.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstTransformer.scala
new file mode 100644
index 0000000..af22501
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstTransformer.scala
@@ -0,0 +1,397 @@
+package scala.scalajs.tools.optimizer
+
+import scala.scalajs.ir
+import ir.Position
+import ir.Position.NoPosition
+
+import scala.scalajs.tools.javascript.Trees._
+
+import com.google.javascript.rhino._
+import com.google.javascript.jscomp._
+
+import scala.collection.mutable
+import scala.annotation.tailrec
+
+import java.net.URI
+
+class ClosureAstTransformer(val relativizeBaseURI: Option[URI] = None) {
+
+ private val inputId = new InputId("Scala.js IR")
+
+ private val dummySourceName = new java.net.URI("virtualfile:scala.js-ir")
+
+ def transformStat(tree: Tree)(implicit parentPos: Position): Node =
+ innerTransformStat(tree, tree.pos orElse parentPos)
+
+ private def innerTransformStat(tree: Tree, pos_in: Position): Node = {
+ implicit val pos = pos_in
+
+ wrapTransform(tree) {
+ case VarDef(ident, _, EmptyTree) =>
+ new Node(Token.VAR, transformName(ident))
+ case VarDef(ident, _, rhs) =>
+ val node = transformName(ident)
+ node.addChildToFront(transformExpr(rhs))
+ new Node(Token.VAR, node)
+ case Skip() =>
+ new Node(Token.EMPTY)
+ case Block(stats) =>
+ transformBlock(stats, pos)
+ case Labeled(label, body) =>
+ new Node(Token.LABEL, transformLabel(label), transformBlock(body))
+ case Return(EmptyTree) =>
+ new Node(Token.RETURN)
+ case Return(expr) =>
+ new Node(Token.RETURN, transformExpr(expr))
+ case If(cond, thenp, Skip()) =>
+ new Node(Token.IF, transformExpr(cond), transformBlock(thenp))
+ case If(cond, thenp, elsep) =>
+ new Node(Token.IF, transformExpr(cond),
+ transformBlock(thenp), transformBlock(elsep))
+ case While(cond, body, None) =>
+ new Node(Token.WHILE, transformExpr(cond), transformBlock(body))
+ case While(cond, body, Some(label)) =>
+ val whileNode =
+ new Node(Token.WHILE, transformExpr(cond), transformBlock(body))
+ new Node(Token.LABEL, transformLabel(label),
+ setNodePosition(whileNode, pos))
+ case DoWhile(body, cond, None) =>
+ new Node(Token.DO, transformBlock(body), transformExpr(cond))
+ case DoWhile(body, cond, Some(label)) =>
+ val doNode =
+ new Node(Token.DO, transformBlock(body), transformExpr(cond))
+ new Node(Token.LABEL, transformLabel(label),
+ setNodePosition(doNode, pos))
+ case Try(block, errVar, handler, EmptyTree) =>
+ val catchPos = handler.pos orElse pos
+ val catchNode =
+ new Node(Token.CATCH, transformName(errVar), transformBlock(handler))
+ val blockNode =
+ new Node(Token.BLOCK, setNodePosition(catchNode, catchPos))
+ new Node(Token.TRY, transformBlock(block),
+ setNodePosition(blockNode, catchPos))
+ case Try(block, _, EmptyTree, finalizer) =>
+ val blockNode = setNodePosition(new Node(Token.BLOCK), pos)
+ new Node(Token.TRY, transformBlock(block), blockNode,
+ transformBlock(finalizer))
+ case Try(block, errVar, handler, finalizer) =>
+ val catchPos = handler.pos orElse pos
+ val catchNode =
+ new Node(Token.CATCH, transformName(errVar), transformBlock(handler))
+ val blockNode =
+ new Node(Token.BLOCK, setNodePosition(catchNode, catchPos))
+ new Node(Token.TRY, transformBlock(block),
+ setNodePosition(blockNode, catchPos), transformBlock(finalizer))
+ case Throw(expr) =>
+ new Node(Token.THROW, transformExpr(expr))
+ case Break(None) =>
+ new Node(Token.BREAK)
+ case Break(Some(label)) =>
+ new Node(Token.BREAK, transformLabel(label))
+ case Continue(None) =>
+ new Node(Token.CONTINUE)
+ case Continue(Some(label)) =>
+ new Node(Token.CONTINUE, transformLabel(label))
+
+ case Switch(selector, cases, default) =>
+ val switchNode = new Node(Token.SWITCH, transformExpr(selector))
+
+ for ((expr, body) <- cases) {
+ val bodyNode = transformBlock(body)
+ bodyNode.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true)
+ val caseNode = new Node(Token.CASE, transformExpr(expr), bodyNode)
+ switchNode.addChildToBack(
+ setNodePosition(caseNode, expr.pos orElse pos))
+ }
+
+ if (default != EmptyTree) {
+ val bodyNode = transformBlock(default)
+ bodyNode.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true)
+ val caseNode = new Node(Token.DEFAULT_CASE, bodyNode)
+ switchNode.addChildToBack(
+ setNodePosition(caseNode, default.pos orElse pos))
+ }
+
+ switchNode
+
+ case Debugger() =>
+ new Node(Token.DEBUGGER)
+ case _ =>
+ // We just assume it is an expression
+ new Node(Token.EXPR_RESULT, transformExpr(tree))
+ }
+ }
+
+ def transformExpr(tree: Tree)(implicit parentPos: Position): Node =
+ innerTransformExpr(tree, tree.pos orElse parentPos)
+
+ private def innerTransformExpr(tree: Tree, pos_in: Position): Node = {
+ implicit val pos = pos_in
+
+ wrapTransform(tree) {
+ case Block(exprs) =>
+ exprs.map(transformExpr).reduceRight { (expr1, expr2) =>
+ setNodePosition(new Node(Token.COMMA, expr1, expr2), pos)
+ }
+ case If(cond, thenp, elsep) =>
+ new Node(Token.HOOK, transformExpr(cond),
+ transformExpr(thenp), transformExpr(elsep))
+ case Assign(lhs, rhs) =>
+ new Node(Token.ASSIGN, transformExpr(lhs), transformExpr(rhs))
+ case New(ctor, args) =>
+ val node = new Node(Token.NEW, transformExpr(ctor))
+ args.foreach(arg => node.addChildToBack(transformExpr(arg)))
+ node
+ case DotSelect(qualifier, item) =>
+ new Node(Token.GETPROP, transformExpr(qualifier), transformString(item))
+ case BracketSelect(qualifier, item) =>
+ new Node(Token.GETELEM, transformExpr(qualifier), transformExpr(item))
+
+ case Apply(fun, args) =>
+ val node = new Node(Token.CALL, transformExpr(fun))
+ args.foreach(arg => node.addChildToBack(transformExpr(arg)))
+
+ // Closure needs to know (from the parser), if the call has a bound
+ // `this` or not. Since JSDesugar inserts protects calls if necessary,
+ // it is sufficient to check if we have a select as target
+ if (!fun.isInstanceOf[DotSelect] &&
+ !fun.isInstanceOf[BracketSelect])
+ node.putBooleanProp(Node.FREE_CALL, true)
+
+ node
+
+ case Delete(prop) =>
+ new Node(Token.DELPROP, transformExpr(prop))
+ case UnaryOp(op, lhs) =>
+ mkUnaryOp(op, transformExpr(lhs))
+ case BinaryOp(op, lhs, rhs) =>
+ mkBinaryOp(op, transformExpr(lhs), transformExpr(rhs))
+ case ArrayConstr(items) =>
+ val node = new Node(Token.ARRAYLIT)
+ items.foreach(i => node.addChildToBack(transformExpr(i)))
+ node
+
+ case ObjectConstr(fields) =>
+ val node = new Node(Token.OBJECTLIT)
+
+ for ((name, expr) <- fields) {
+ val fldNode = transformStringKey(name)
+ fldNode.addChildToBack(transformExpr(expr))
+ node.addChildToBack(fldNode)
+ }
+
+ node
+
+ case Undefined() =>
+ new Node(Token.VOID, setNodePosition(Node.newNumber(0.0), pos))
+ case Null() =>
+ new Node(Token.NULL)
+ case BooleanLiteral(value) =>
+ if (value) new Node(Token.TRUE) else new Node(Token.FALSE)
+ case IntLiteral(value) =>
+ Node.newNumber(value)
+ case DoubleLiteral(value) =>
+ Node.newNumber(value)
+ case StringLiteral(value) =>
+ Node.newString(value)
+ case VarRef(ident, _) =>
+ transformName(ident)
+ case This() =>
+ new Node(Token.THIS)
+
+ case Function(args, body) =>
+ // Note that a Function may also be a statement (when it is named),
+ // but Scala.js does not have such an IR node
+ val paramList = new Node(Token.PARAM_LIST)
+ args.foreach(arg => paramList.addChildToBack(transformParam(arg)))
+
+ val emptyName = setNodePosition(Node.newString(Token.NAME, ""), pos)
+
+ new Node(Token.FUNCTION, emptyName, paramList, transformBlock(body))
+
+ case _ =>
+ throw new TransformException(s"Unknown tree of class ${tree.getClass()}")
+ }
+ }
+
+ def transformParam(param: ParamDef)(implicit parentPos: Position): Node =
+ transformName(param.name)
+
+ def transformName(ident: Ident)(implicit parentPos: Position): Node =
+ setNodePosition(Node.newString(Token.NAME, ident.name),
+ ident.pos orElse parentPos)
+
+ def transformLabel(ident: Ident)(implicit parentPos: Position): Node =
+ setNodePosition(Node.newString(Token.LABEL_NAME, ident.name),
+ ident.pos orElse parentPos)
+
+ def transformString(pName: PropertyName)(implicit parentPos: Position): Node =
+ setNodePosition(Node.newString(pName.name), pName.pos orElse parentPos)
+
+ def transformStringKey(pName: PropertyName)(
+ implicit parentPos: Position): Node = {
+ val node = Node.newString(Token.STRING_KEY, pName.name)
+
+ if (pName.isInstanceOf[StringLiteral])
+ node.setQuotedString()
+
+ setNodePosition(node, pName.pos orElse parentPos)
+ }
+
+ def transformBlock(tree: Tree)(implicit parentPos: Position): Node = {
+ val pos = if (tree.pos.isDefined) tree.pos else parentPos
+ wrapTransform(tree) {
+ case Block(stats) =>
+ transformBlock(stats, pos)
+ case tree =>
+ transformBlock(List(tree), pos)
+ } (pos)
+ }
+
+ def transformBlock(stats: List[Tree], blockPos: Position): Node = {
+ @inline def ctorDoc(node: Node) = {
+ val b = new JSDocInfoBuilder(false)
+ b.recordConstructor()
+ b.build(node)
+ }
+
+ val block = new Node(Token.BLOCK)
+
+ // The Rhino IR attaches DocComments to the following nodes (rather than
+ // having individual nodes). We preprocess these here.
+ @tailrec
+ def loop(ts: List[Tree], nextIsCtor: Boolean = false): Unit = ts match {
+ case DocComment(text) :: tss if text.startsWith("@constructor") =>
+ loop(tss, nextIsCtor = true)
+ case DocComment(text) :: tss =>
+ loop(tss)
+ case t :: tss =>
+ val node = transformStat(t)(blockPos)
+ if (nextIsCtor) {
+ // The @constructor must be propagated through an ExprResult node
+ val trg =
+ if (node.isExprResult()) node.getChildAtIndex(0)
+ else node
+
+ trg.setJSDocInfo(ctorDoc(trg))
+ }
+
+ block.addChildToBack(node)
+
+ loop(tss)
+ case Nil =>
+ }
+
+ loop(stats)
+
+ block
+ }
+
+ @inline
+ private def wrapTransform(tree: Tree)(body: Tree => Node)(
+ implicit pos: Position): Node = {
+ try {
+ setNodePosition(body(tree), pos)
+ } catch {
+ case e: TransformException =>
+ throw e // pass through
+ case e: RuntimeException =>
+ throw new TransformException(tree, e)
+ }
+ }
+
+ def setNodePosition(node: Node, pos: ir.Position): node.type = {
+ if (pos != ir.Position.NoPosition) {
+ attachSourceFile(node, pos.source)
+ node.setLineno(pos.line+1)
+ node.setCharno(pos.column)
+ } else {
+ attachSourceFile(node, dummySourceName)
+ }
+ node
+ }
+
+ private def attachSourceFile(node: Node, source: URI): node.type = {
+ val str = sourceUriToString(source)
+
+ node.setInputId(inputId)
+ node.setStaticSourceFile(new SourceFile(str))
+
+ node
+ }
+
+ private def sourceUriToString(uri: URI): String = {
+ val relURI = relativizeBaseURI.fold(uri)(ir.Utils.relativize(_, uri))
+ ir.Utils.fixFileURI(relURI).toASCIIString
+ }
+
+ // Helpers for IR
+ @inline
+ private def mkUnaryOp(op: String, lhs: Node): Node = {
+ val tok = op match {
+ case "!" => Token.NOT
+ case "~" => Token.BITNOT
+ case "+" => Token.POS
+ case "-" => Token.NEG
+ case "typeof" => Token.TYPEOF
+ case _ => throw new TransformException(s"Unhandled unary op: $op")
+ }
+
+ new Node(tok, lhs)
+ }
+
+ @inline
+ private def mkBinaryOp(op: String, lhs: Node, rhs: Node): Node = {
+ val tok = op match {
+ case "|" => Token.BITOR
+ case "&" => Token.BITAND
+ case "^" => Token.BITXOR
+ case "==" => Token.EQ
+ case "!=" => Token.NE
+ case "<" => Token.LT
+ case "<=" => Token.LE
+ case ">" => Token.GT
+ case ">=" => Token.GE
+ case "<<" => Token.LSH
+ case ">>" => Token.RSH
+ case ">>>" => Token.URSH
+ case "+" => Token.ADD
+ case "-" => Token.SUB
+ case "*" => Token.MUL
+ case "/" => Token.DIV
+ case "%" => Token.MOD
+ case "===" => Token.SHEQ
+ case "!==" => Token.SHNE
+ case "in" => Token.IN
+ case "instanceof" => Token.INSTANCEOF
+ case "||" => Token.OR
+ case "&&" => Token.AND
+ case _ =>
+ throw new TransformException(s"Unhandled binary op: $op")
+ }
+
+ new Node(tok, lhs, rhs)
+ }
+
+ // Exception wrapper in transforms
+
+ class TransformException private (msg: String, e: Throwable)
+ extends RuntimeException(msg, e) {
+
+ def this(tree: Tree, e: Throwable) =
+ this(TransformException.mkMsg(tree), e)
+
+ def this(msg: String) = this(msg, null)
+ }
+
+ object TransformException {
+ import ir.Printers._
+ import java.io._
+
+ private def mkMsg(tree: Tree): String = {
+ "Exception while translating Scala.js JS tree to GCC IR at tree:\n" +
+ tree.show
+ }
+ }
+
+}
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ConcurrencyUtils.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ConcurrencyUtils.scala
new file mode 100644
index 0000000..471ed65
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ConcurrencyUtils.scala
@@ -0,0 +1,74 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import scala.annotation.tailrec
+
+import scala.collection.concurrent.TrieMap
+
+import java.util.concurrent.atomic._
+
+private[optimizer] object ConcurrencyUtils {
+
+ /** An atomic accumulator supports adding single elements and retrieving and
+ * deleting all contained elements */
+ type AtomicAcc[T] = AtomicReference[List[T]]
+
+ object AtomicAcc {
+ @inline final def empty[T]: AtomicAcc[T] =
+ new AtomicReference[List[T]](Nil)
+ @inline final def apply[T](l: List[T]): AtomicAcc[T] =
+ new AtomicReference(l)
+ }
+
+ implicit class AtomicAccOps[T](val acc: AtomicAcc[T]) extends AnyVal {
+ @inline final def size: Int = acc.get.size
+
+ @inline
+ final def +=(x: T): Unit = AtomicAccOps.append(acc, x)
+
+ @inline
+ final def removeAll(): List[T] = AtomicAccOps.removeAll(acc)
+ }
+
+ object AtomicAccOps {
+ @inline
+ @tailrec
+ private final def append[T](acc: AtomicAcc[T], x: T): Boolean = {
+ val oldV = acc.get
+ val newV = x :: oldV
+ acc.compareAndSet(oldV, newV) || append(acc, x)
+ }
+
+ @inline
+ private final def removeAll[T](acc: AtomicAcc[T]): List[T] =
+ acc.getAndSet(Nil)
+ }
+
+ type TrieSet[T] = TrieMap[T, Null]
+
+ implicit class TrieSetOps[T](val set: TrieSet[T]) extends AnyVal {
+ @inline final def +=(x: T): Unit = set.put(x, null)
+ }
+
+ object TrieSet {
+ @inline final def empty[T]: TrieSet[T] = TrieMap.empty
+ }
+
+ implicit class TrieMapOps[K,V](val map: TrieMap[K,V]) extends AnyVal {
+ @inline final def getOrPut(k: K, default: => V): V = {
+ map.get(k).getOrElse {
+ val v = default
+ map.putIfAbsent(k, v).getOrElse(v)
+ }
+ }
+ }
+
+}
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/LoggerErrorManager.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/LoggerErrorManager.scala
new file mode 100644
index 0000000..d51dd7b
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/LoggerErrorManager.scala
@@ -0,0 +1,38 @@
+package scala.scalajs.tools.optimizer
+
+import com.google.javascript.jscomp.{ BasicErrorManager, CheckLevel, JSError }
+
+import scala.scalajs.tools.logging.Logger
+
+/** A Google Closure Error Manager that forwards to a tools.logging.Logger */
+class LoggerErrorManager(private val log: Logger) extends BasicErrorManager {
+
+ /** Ugly hack to disable FRACTIONAL_BITWISE_OPERAND warnings. Since its
+ * DiagnosticType (PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND) is
+ * package private.
+ */
+ override def report(level: CheckLevel, error: JSError): Unit = {
+ if (error.getType.key == "JSC_FRACTIONAL_BITWISE_OPERAND")
+ super.report(CheckLevel.OFF, error)
+ else
+ super.report(level, error)
+ }
+
+ def println(level: CheckLevel, error: JSError): Unit = level match {
+ case CheckLevel.WARNING => log.warn (s"Closure: ${error}")
+ case CheckLevel.ERROR => log.error(s"Closure: ${error}")
+ case CheckLevel.OFF =>
+ }
+
+ protected def printSummary(): Unit = {
+ val msg = s"Closure: ${getErrorCount} error(s), ${getWarningCount} warning(s)"
+
+ if (getErrorCount > 0)
+ log.error(msg)
+ else if (getWarningCount > 0)
+ log.warn(msg)
+ else
+ log.info(msg)
+ }
+
+}
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ParIncOptimizer.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ParIncOptimizer.scala
new file mode 100644
index 0000000..422238e
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ParIncOptimizer.scala
@@ -0,0 +1,188 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import scala.collection.{GenTraversableOnce, GenIterable}
+import scala.collection.concurrent.TrieMap
+import scala.collection.parallel.mutable.{ParTrieMap, ParArray}
+
+import java.util.concurrent.atomic._
+
+import scala.scalajs.tools.sem.Semantics
+
+import ConcurrencyUtils._
+
+class ParIncOptimizer(semantics: Semantics) extends GenIncOptimizer(semantics) {
+
+ protected object CollOps extends GenIncOptimizer.AbsCollOps {
+ type Map[K, V] = TrieMap[K, V]
+ type ParMap[K, V] = ParTrieMap[K, V]
+ type AccMap[K, V] = TrieMap[K, AtomicAcc[V]]
+ type ParIterable[V] = ParArray[V]
+ type Addable[V] = AtomicAcc[V]
+
+ def emptyAccMap[K, V]: AccMap[K, V] = TrieMap.empty
+ def emptyMap[K, V]: Map[K, V] = TrieMap.empty
+ def emptyParMap[K, V]: ParMap[K, V] = ParTrieMap.empty
+ def emptyParIterable[V]: ParIterable[V] = ParArray.empty
+
+ // Operations on ParMap
+ def put[K, V](map: ParMap[K, V], k: K, v: V): Unit = map.put(k, v)
+ def remove[K, V](map: ParMap[K, V], k: K): Option[V] = map.remove(k)
+
+ def retain[K, V](map: ParMap[K, V])(p: (K, V) => Boolean): Unit = {
+ map.foreach { case (k, v) =>
+ if (!p(k, v))
+ map.remove(k)
+ }
+ }
+
+ // Operations on AccMap
+ def acc[K, V](map: AccMap[K, V], k: K, v: V): Unit =
+ map.getOrPut(k, AtomicAcc.empty) += v
+
+ def getAcc[K, V](map: AccMap[K, V], k: K): GenIterable[V] =
+ map.get(k).fold[Iterable[V]](Nil)(_.removeAll()).toParArray
+
+ def parFlatMapKeys[A, B](map: AccMap[A, _])(
+ f: A => GenTraversableOnce[B]): GenIterable[B] =
+ map.keys.flatMap(f).toParArray
+
+ // Operations on ParIterable
+ def prepAdd[V](it: ParIterable[V]): Addable[V] =
+ AtomicAcc(it.toList)
+
+ def add[V](addable: Addable[V], v: V): Unit =
+ addable += v
+
+ def finishAdd[V](addable: Addable[V]): ParIterable[V] =
+ addable.removeAll().toParArray
+ }
+
+ private val _interfaces = TrieMap.empty[String, InterfaceType]
+ protected def getInterface(encodedName: String): InterfaceType =
+ _interfaces.getOrPut(encodedName, new ParInterfaceType(encodedName))
+
+ private val methodsToProcess: AtomicAcc[MethodImpl] = AtomicAcc.empty
+ protected def scheduleMethod(method: MethodImpl): Unit =
+ methodsToProcess += method
+
+ protected def newMethodImpl(owner: MethodContainer,
+ encodedName: String): MethodImpl = new ParMethodImpl(owner, encodedName)
+
+ protected def processAllTaggedMethods(): Unit = {
+ val methods = methodsToProcess.removeAll().toParArray
+ logProcessingMethods(methods.count(!_.deleted))
+ for (method <- methods)
+ method.process()
+ }
+
+ private class ParInterfaceType(encName: String) extends InterfaceType(encName) {
+ private val ancestorsAskers = TrieSet.empty[MethodImpl]
+ private val dynamicCallers = TrieMap.empty[String, TrieSet[MethodImpl]]
+ private val staticCallers = TrieMap.empty[String, TrieSet[MethodImpl]]
+
+ private var _ancestors: List[String] = encodedName :: Nil
+
+ private val _instantiatedSubclasses: TrieSet[Class] = TrieSet.empty
+
+ /** PROCESS PASS ONLY. Concurrency safe except with
+ * [[addInstantiatedSubclass]] and [[removeInstantiatedSubclass]]
+ */
+ def instantiatedSubclasses: Iterable[Class] =
+ _instantiatedSubclasses.keys
+
+ /** UPDATE PASS ONLY. Concurrency safe except with
+ * [[instantiatedSubclasses]]
+ */
+ def addInstantiatedSubclass(x: Class): Unit =
+ _instantiatedSubclasses += x
+
+ /** UPDATE PASS ONLY. Concurrency safe except with
+ * [[instantiatedSubclasses]]
+ */
+ def removeInstantiatedSubclass(x: Class): Unit =
+ _instantiatedSubclasses -= x
+
+ /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]] */
+ def ancestors: List[String] = _ancestors
+
+ /** UPDATE PASS ONLY. Not concurrency safe. */
+ def ancestors_=(v: List[String]): Unit = {
+ if (v != _ancestors) {
+ _ancestors = v
+ ancestorsAskers.keysIterator.foreach(_.tag())
+ ancestorsAskers.clear()
+ }
+ }
+
+ /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]]. */
+ def registerAskAncestors(asker: MethodImpl): Unit =
+ ancestorsAskers += asker
+
+ /** PROCESS PASS ONLY. */
+ def registerDynamicCaller(methodName: String, caller: MethodImpl): Unit =
+ dynamicCallers.getOrPut(methodName, TrieSet.empty) += caller
+
+ /** PROCESS PASS ONLY. */
+ def registerStaticCaller(methodName: String, caller: MethodImpl): Unit =
+ staticCallers.getOrPut(methodName, TrieSet.empty) += caller
+
+ /** UPDATE PASS ONLY. */
+ def unregisterDependee(dependee: MethodImpl): Unit = {
+ ancestorsAskers -= dependee
+ dynamicCallers.valuesIterator.foreach(_ -= dependee)
+ staticCallers.valuesIterator.foreach(_ -= dependee)
+ }
+
+ /** UPDATE PASS ONLY. */
+ def tagDynamicCallersOf(methodName: String): Unit =
+ dynamicCallers.remove(methodName).foreach(_.keysIterator.foreach(_.tag()))
+
+ /** UPDATE PASS ONLY. */
+ def tagStaticCallersOf(methodName: String): Unit =
+ staticCallers.remove(methodName).foreach(_.keysIterator.foreach(_.tag()))
+ }
+
+ private class ParMethodImpl(owner: MethodContainer,
+ encodedName: String) extends MethodImpl(owner, encodedName) {
+
+ private val bodyAskers = TrieSet.empty[MethodImpl]
+
+ /** PROCESS PASS ONLY. */
+ def registerBodyAsker(asker: MethodImpl): Unit =
+ bodyAskers += asker
+
+ /** UPDATE PASS ONLY. */
+ def unregisterDependee(dependee: MethodImpl): Unit =
+ bodyAskers -= dependee
+
+ /** UPDATE PASS ONLY. */
+ def tagBodyAskers(): Unit = {
+ bodyAskers.keysIterator.foreach(_.tag())
+ bodyAskers.clear()
+ }
+
+ private val _registeredTo = AtomicAcc.empty[Unregisterable]
+ private val tagged = new AtomicBoolean(false)
+
+ protected def registeredTo(intf: Unregisterable): Unit =
+ _registeredTo += intf
+
+ protected def unregisterFromEverywhere(): Unit = {
+ _registeredTo.removeAll().foreach(_.unregisterDependee(this))
+ }
+
+ protected def protectTag(): Boolean = !tagged.getAndSet(true)
+ protected def resetTag(): Unit = tagged.set(false)
+
+ }
+
+}
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala
new file mode 100644
index 0000000..146b2b8
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala
@@ -0,0 +1,216 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import scala.scalajs.tools.sem.Semantics
+import scala.scalajs.tools.classpath._
+import scala.scalajs.tools.logging._
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.corelib.CoreJSLibs
+
+import com.google.javascript.jscomp.{
+ SourceFile => ClosureSource,
+ Compiler => ClosureCompiler,
+ CompilerOptions => ClosureOptions,
+ _
+}
+import scala.collection.JavaConverters._
+import scala.collection.immutable.{Seq, Traversable}
+
+import java.net.URI
+
+/** Scala.js Closure optimizer: does advanced optimizations with Closure. */
+class ScalaJSClosureOptimizer(semantics: Semantics) {
+ import ScalaJSClosureOptimizer._
+
+ private def toClosureSource(file: VirtualJSFile) =
+ ClosureSource.fromReader(file.toURI.toString(), file.reader)
+
+ private def toClosureInput(file: VirtualJSFile) =
+ new CompilerInput(toClosureSource(file))
+
+ /** Fully optimizes an [[IRClasspath]] by asking the ScalaJSOptimizer to
+ * emit a closure AST and then compiling this AST directly
+ */
+ def optimizeCP(optimizer: ScalaJSOptimizer,
+ inputs: Inputs[ScalaJSOptimizer.Inputs[IRClasspath]],
+ outCfg: OutputConfig, logger: Logger): LinkedClasspath = {
+
+ val cp = inputs.input.input
+
+ CacheUtils.cached(cp.version, outCfg.output, outCfg.cache) {
+ logger.info(s"Full Optimizing ${outCfg.output.path}")
+
+ val irFastOptInput =
+ inputs.input.copy(input = inputs.input.input.scalaJSIR)
+ val irFullOptInput = inputs.copy(input = irFastOptInput)
+
+ optimizeIR(optimizer, irFullOptInput, outCfg, logger)
+ }
+
+ new LinkedClasspath(cp.jsLibs, outCfg.output, cp.requiresDOM, cp.version)
+ }
+
+ def optimizeIR(optimizer: ScalaJSOptimizer,
+ inputs: Inputs[ScalaJSOptimizer.Inputs[Traversable[VirtualScalaJSIRFile]]],
+ outCfg: OutputConfig, logger: Logger): Unit = {
+
+ // Build Closure IR via ScalaJSOptimizer
+ val builder = new ClosureAstBuilder(outCfg.relativizeSourceMapBase)
+
+ optimizer.optimizeIR(inputs.input, outCfg, builder, logger)
+
+ // Build a Closure JSModule which includes the core libs
+ val module = new JSModule("Scala.js")
+
+ for (lib <- CoreJSLibs.libs(semantics))
+ module.add(toClosureInput(lib))
+
+ val ast = builder.closureAST
+ module.add(new CompilerInput(ast, ast.getInputId(), false))
+
+ for (export <- inputs.additionalExports)
+ module.add(toClosureInput(export))
+
+ // Compile the module
+ val closureExterns =
+ (ScalaJSExternsFile +: inputs.additionalExterns).map(toClosureSource)
+
+ val options = closureOptions(outCfg)
+ val compiler = closureCompiler(logger)
+
+ val result = GenIncOptimizer.logTime(logger, "Closure Compiler pass") {
+ compiler.compileModules(
+ closureExterns.asJava, List(module).asJava, options)
+ }
+
+ GenIncOptimizer.logTime(logger, "Write Closure result") {
+ writeResult(result, compiler, outCfg.output)
+ }
+ }
+
+ private def writeResult(result: Result, compiler: ClosureCompiler,
+ output: WritableVirtualJSFile): Unit = {
+
+ val outputContent = if (result.errors.nonEmpty) ""
+ else "(function(){'use strict';" + compiler.toSource + "}).call(this);\n"
+
+ val sourceMap = Option(compiler.getSourceMap())
+
+ // Write optimized code
+ val w = output.contentWriter
+ try {
+ w.write(outputContent)
+ if (sourceMap.isDefined)
+ w.write("//# sourceMappingURL=" + output.name + ".map\n")
+ } finally w.close()
+
+ // Write source map (if available)
+ sourceMap.foreach { sm =>
+ val w = output.sourceMapWriter
+ try sm.appendTo(w, output.name)
+ finally w.close()
+ }
+ }
+
+ private def closureOptions(optConfig: OptimizerConfig,
+ noSourceMap: Boolean = false) = {
+
+ val options = new ClosureOptions
+ options.prettyPrint = optConfig.prettyPrint
+ CompilationLevel.ADVANCED_OPTIMIZATIONS.setOptionsForCompilationLevel(options)
+ options.setLanguageIn(ClosureOptions.LanguageMode.ECMASCRIPT5)
+ options.setCheckGlobalThisLevel(CheckLevel.OFF)
+
+ if (!noSourceMap && optConfig.wantSourceMap) {
+ options.setSourceMapOutputPath(optConfig.output.name + ".map")
+ options.setSourceMapDetailLevel(SourceMap.DetailLevel.ALL)
+ }
+
+ options
+ }
+
+ private def closureCompiler(logger: Logger) = {
+ val compiler = new ClosureCompiler
+ compiler.setErrorManager(new LoggerErrorManager(logger))
+ compiler
+ }
+}
+
+object ScalaJSClosureOptimizer {
+ /** Inputs of the Scala.js Closure optimizer. */
+ final case class Inputs[T](
+ /** Input to optimize (classpath or file-list) */
+ input: T,
+ /** Additional externs files to be given to Closure. */
+ additionalExterns: Seq[VirtualJSFile] = Nil,
+ /** Additional exports to be given to Closure.
+ * These files are just appended to the classpath, given to Closure,
+ * but not used in the Scala.js optimizer pass when direct optimizing
+ */
+ additionalExports: Seq[VirtualJSFile] = Nil
+ )
+
+ /** Configuration the closure part of the optimizer needs.
+ * See [[OutputConfig]] for a description of the fields.
+ */
+ trait OptimizerConfig {
+ val output: WritableVirtualJSFile
+ val cache: Option[WritableVirtualTextFile]
+ val wantSourceMap: Boolean
+ val prettyPrint: Boolean
+ val relativizeSourceMapBase: Option[URI]
+ }
+
+ /** Configuration for the output of the Scala.js Closure optimizer */
+ final case class OutputConfig(
+ /** Writer for the output */
+ output: WritableVirtualJSFile,
+ /** Cache file */
+ cache: Option[WritableVirtualTextFile] = None,
+ /** If true, performs expensive checks of the IR for the used parts. */
+ checkIR: Boolean = false,
+ /** If true, the optimizer removes trees that have not been used in the
+ * last run from the cache. Otherwise, all trees that has been used once,
+ * are kept in memory. */
+ unCache: Boolean = true,
+ /** If true, no optimizations are performed */
+ disableOptimizer: Boolean = false,
+ /** If true, nothing is performed incrementally */
+ batchMode: Boolean = false,
+ /** Ask to produce source map for the output */
+ wantSourceMap: Boolean = false,
+ /** Pretty-print the output. */
+ prettyPrint: Boolean = false,
+ /** Base path to relativize paths in the source map */
+ relativizeSourceMapBase: Option[URI] = None
+ ) extends OptimizerConfig with ScalaJSOptimizer.OptimizerConfig
+
+ /** Minimal set of externs to compile Scala.js-emitted code with Closure. */
+ val ScalaJSExterns = """
+ /** @constructor */
+ function Object() {}
+ Object.protoype.toString = function() {};
+ /** @constructor */
+ function Array() {}
+ Array.prototype.length = 0;
+ /** @constructor */
+ function Function() {}
+ Function.prototype.constructor = function() {};
+ Function.prototype.call = function() {};
+ Function.prototype.apply = function() {};
+ var global = {};
+ var __ScalaJSEnv = {};
+ """
+
+ val ScalaJSExternsFile = new MemVirtualJSFile("ScalaJSExterns.js").
+ withContent(ScalaJSExterns)
+
+}
diff --git a/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala
new file mode 100644
index 0000000..6f856a9
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala
@@ -0,0 +1,88 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.sourcemap
+
+import scala.scalajs.tools.classpath._
+
+import com.google.debugging.sourcemap._
+
+class SourceMapper(classpath: CompleteClasspath) {
+
+ def map(ste: StackTraceElement, columnNumber: Int): StackTraceElement = {
+ val mapped = for {
+ sourceMap <- findSourceMap(ste.getFileName)
+ } yield map(ste, columnNumber, sourceMap)
+
+ mapped.getOrElse(ste)
+ }
+
+ def map(ste: StackTraceElement, columnNumber: Int,
+ sourceMap: String): StackTraceElement = {
+
+ val sourceMapConsumer =
+ SourceMapConsumerFactory
+ .parse(sourceMap)
+ .asInstanceOf[SourceMapConsumerV3]
+
+ /* **Attention**
+ * - StackTrace positions are 1-based
+ * - SourceMapConsumer.getMappingForLine() is 1-based
+ */
+
+ val lineNumber = ste.getLineNumber
+ val column =
+ if (columnNumber == -1) getFirstColumn(sourceMapConsumer, lineNumber)
+ else columnNumber
+
+ val originalMapping =
+ sourceMapConsumer.getMappingForLine(lineNumber, column)
+
+ if (originalMapping != null) {
+ new StackTraceElement(
+ ste.getClassName,
+ ste.getMethodName,
+ originalMapping.getOriginalFile,
+ originalMapping.getLineNumber)
+ } else ste
+ }
+
+ /** both `lineNumber` and the resulting column are 1-based */
+ private def getFirstColumn(sourceMapConsumer: SourceMapConsumerV3,
+ lineNumber1Based: Int) = {
+
+ /* **Attention**
+ * - SourceMapConsumerV3.EntryVisitor is 0-based!!!
+ */
+
+ val lineNumber = lineNumber1Based - 1
+
+ var column: Option[Int] = None
+
+ sourceMapConsumer.visitMappings(
+ new SourceMapConsumerV3.EntryVisitor {
+ def visit(sourceName: String,
+ symbolName: String,
+ sourceStartPosition: FilePosition,
+ startPosition: FilePosition,
+ endPosition: FilePosition): Unit =
+ if (!column.isDefined && startPosition.getLine == lineNumber)
+ column = Some(startPosition.getColumn)
+ })
+
+ val column0Based = column.getOrElse(0)
+ column0Based + 1
+ }
+
+ private def findSourceMap(path: String) = {
+ val candidates = classpath.allCode.filter(_.path == path)
+ if (candidates.size != 1) None // better no sourcemap than a wrong one
+ else candidates.head.sourceMap
+ }
+}
diff --git a/examples/scala-js/tools/jvm/src/test/resources/test.jar b/examples/scala-js/tools/jvm/src/test/resources/test.jar
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/test/resources/test.jar
diff --git a/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/ClasspathElementsTraverserTest.scala b/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/ClasspathElementsTraverserTest.scala
new file mode 100644
index 0000000..72d01e4
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/ClasspathElementsTraverserTest.scala
@@ -0,0 +1,42 @@
+package scala.scalajs.tools.classpath.builder.test
+
+import scala.scalajs.tools.classpath.builder._
+import scala.scalajs.tools.jsdep.JSDependencyManifest
+import scala.scalajs.tools.io.VirtualScalaJSIRFile
+import scala.scalajs.tools.io.VirtualJSFile
+
+import java.io.File
+
+import org.junit.Test
+import org.junit.Assert._
+
+class ClasspathElementsTraverserTest {
+ class TestElementTraverser extends ClasspathElementsTraverser with PhysicalFileSystem {
+ protected def handleDepManifest(m: => JSDependencyManifest): Unit = ???
+ protected def handleIR(relPath: String, ir: => VirtualScalaJSIRFile): Unit = ???
+ protected def handleJS(js: => VirtualJSFile): Unit = ???
+
+ def test(f: File): String = traverseClasspathElements(Seq(f))
+ }
+
+ val cpElementTraverser = new TestElementTraverser()
+
+ @Test
+ def testNotExistingJarFile(): Unit = {
+ val res = cpElementTraverser.test(new File("dummy.jar"))
+ assertTrue(res.endsWith(cpElementTraverser.DummyVersion))
+ }
+
+ @Test
+ def testExistingDirectory(): Unit = {
+ val res = cpElementTraverser.test(new File("tools/jvm/src/test/resources"))
+ assertTrue(res.indexOf(cpElementTraverser.DummyVersion) < 0)
+ }
+
+ @Test
+ def testExistingJarFile(): Unit = {
+ val res = cpElementTraverser.test(new File("tools/jvm/src/test/resources/test.jar"))
+ assertTrue(res.indexOf(cpElementTraverser.DummyVersion) < 0)
+ }
+}
+
diff --git a/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/JarBuilderTest.scala b/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/JarBuilderTest.scala
new file mode 100644
index 0000000..6ed68fc
--- /dev/null
+++ b/examples/scala-js/tools/jvm/src/test/scala/scala/scalajs/tools/classpath/builder/test/JarBuilderTest.scala
@@ -0,0 +1,74 @@
+package scala.scalajs.tools.classpath.builder.test
+
+import scala.scalajs.tools.classpath.builder._
+
+import org.junit.Test
+import org.junit.Assert._
+
+class JarBuilderTest {
+
+ val jsFileContent = "window.alert('Hello World');"
+ val jsFileName = "lib.js"
+
+ private def getJARInputStream = {
+ // Keep imports local so we don't accidentally use something we shouldn't
+ import java.util.zip._
+ import java.io._
+
+ // Write a ZIP to a byte array
+ val outBuf = new ByteArrayOutputStream
+ val out = new ZipOutputStream(outBuf)
+
+ out.putNextEntry(new ZipEntry(jsFileName))
+ val w = new OutputStreamWriter(out, "UTF-8")
+ w.write(jsFileContent)
+ w.flush()
+
+ out.close()
+
+ new ByteArrayInputStream(outBuf.toByteArray)
+ }
+
+ /** Trivial FS that has only one jar */
+ trait TrivialFS extends FileSystem {
+ // Keep imports local so we don't accidentally use something we shouldn't
+ import java.io.{InputStream, Reader}
+ import scala.scalajs.tools.io._
+ import scala.collection.immutable.Traversable
+
+ type File = Unit
+
+ val DummyVersion: String = "DUMMY"
+
+ def isDirectory(f: File): Boolean = false
+ def isFile(f: File): Boolean = true
+ def isJSFile(f: File): Boolean = false
+ def isIRFile(f: File): Boolean = false
+ def isJARFile(f: File): Boolean = true
+ def exists(f: File): Boolean = true
+
+ def getName(f: File): String = "jar"
+ def getAbsolutePath(f: File): String = "jar"
+ def getVersion(f: File): String = ""
+
+ def listFiles(d: File): Traversable[File] = ???
+
+ def toJSFile(f: File): VirtualJSFile = ???
+ def toIRFile(f: File): VirtualScalaJSIRFile = ???
+ def toReader(f: File): Reader = ???
+ def toInputStream(f: File): InputStream = getJARInputStream
+ }
+
+ class TestJARBuilder extends AbstractJarLibClasspathBuilder with TrivialFS
+
+ @Test
+ def readInMemoryJarClasspath {
+ val builder = new TestJARBuilder
+ val cp = builder.build(Seq(()))
+
+ assertArrayEquals(
+ Array[Object](jsFileName -> jsFileContent),
+ cp.availableLibs.mapValues(_.content).toArray[Object])
+ }
+
+}
diff --git a/examples/scala-js/tools/scalajsenv.js b/examples/scala-js/tools/scalajsenv.js
new file mode 100644
index 0000000..01a84ba
--- /dev/null
+++ b/examples/scala-js/tools/scalajsenv.js
@@ -0,0 +1,772 @@
+/* Scala.js runtime support
+ * Copyright 2013 LAMP/EPFL
+ * Author: Sébastien Doeraene
+ */
+
+/* ---------------------------------- *
+ * The top-level Scala.js environment *
+ * ---------------------------------- */
+
+var ScalaJS = {};
+
+// Get the environment info
+ScalaJS.env = (typeof __ScalaJSEnv === "object" && __ScalaJSEnv) ? __ScalaJSEnv : {};
+
+// Global scope
+ScalaJS.g =
+ (typeof ScalaJS.env["global"] === "object" && ScalaJS.env["global"])
+ ? ScalaJS.env["global"]
+ : ((typeof global === "object" && global && global["Object"] === Object) ? global : this);
+ScalaJS.env["global"] = ScalaJS.g;
+
+// Where to send exports
+ScalaJS.e =
+ (typeof ScalaJS.env["exportsNamespace"] === "object" && ScalaJS.env["exportsNamespace"])
+ ? ScalaJS.env["exportsNamespace"] : ScalaJS.g;
+ScalaJS.env["exportsNamespace"] = ScalaJS.e;
+
+// Freeze the environment info
+ScalaJS.g["Object"]["freeze"](ScalaJS.env);
+
+// Other fields
+ScalaJS.d = {}; // Data for types
+ScalaJS.c = {}; // Scala.js constructors
+ScalaJS.h = {}; // Inheritable constructors (without initialization code)
+ScalaJS.i = {}; // Implementation class modules
+ScalaJS.n = {}; // Module instances
+ScalaJS.m = {}; // Module accessors
+ScalaJS.is = {}; // isInstanceOf methods
+ScalaJS.isArrayOf = {}; // isInstanceOfArrayOf methods
+//!if asInstanceOfs != Unchecked
+ScalaJS.as = {}; // asInstanceOf methods
+ScalaJS.asArrayOf = {}; // asInstanceOfArrayOf methods
+//!endif
+ScalaJS.lastIDHash = 0; // last value attributed to an id hash code
+
+// Core mechanism
+
+ScalaJS.makeIsArrayOfPrimitive = function(primitiveData) {
+ return function(obj, depth) {
+ return !!(obj && obj.$classData &&
+ (obj.$classData.arrayDepth === depth) &&
+ (obj.$classData.arrayBase === primitiveData));
+ }
+};
+
+//!if asInstanceOfs != Unchecked
+ScalaJS.makeAsArrayOfPrimitive = function(isInstanceOfFunction, arrayEncodedName) {
+ return function(obj, depth) {
+ if (isInstanceOfFunction(obj, depth) || (obj === null))
+ return obj;
+ else
+ ScalaJS.throwArrayCastException(obj, arrayEncodedName, depth);
+ }
+};
+//!endif
+
+/** Encode a property name for runtime manipulation
+ * Usage:
+ * env.propertyName({someProp:0})
+ * Returns:
+ * "someProp"
+ * Useful when the property is renamed by a global optimizer (like Closure)
+ * but we must still get hold of a string of that name for runtime
+ * reflection.
+ */
+ScalaJS.propertyName = function(obj) {
+ var result;
+ for (var prop in obj)
+ result = prop;
+ return result;
+};
+
+// Runtime functions
+
+ScalaJS.isScalaJSObject = function(obj) {
+ return !!(obj && obj.$classData);
+};
+
+//!if asInstanceOfs != Unchecked
+ScalaJS.throwClassCastException = function(instance, classFullName) {
+//!if asInstanceOfs == Compliant
+ throw new ScalaJS.c.jl_ClassCastException().init___T(
+ instance + " is not an instance of " + classFullName);
+//!else
+ throw new ScalaJS.c.sjsr_UndefinedBehaviorError().init___jl_Throwable(
+ new ScalaJS.c.jl_ClassCastException().init___T(
+ instance + " is not an instance of " + classFullName));
+//!endif
+};
+
+ScalaJS.throwArrayCastException = function(instance, classArrayEncodedName, depth) {
+ for (; depth; --depth)
+ classArrayEncodedName = "[" + classArrayEncodedName;
+ ScalaJS.throwClassCastException(instance, classArrayEncodedName);
+};
+//!endif
+
+ScalaJS.makeNativeArrayWrapper = function(arrayClassData, nativeArray) {
+ return new arrayClassData.constr(nativeArray);
+};
+
+ScalaJS.newArrayObject = function(arrayClassData, lengths) {
+ return ScalaJS.newArrayObjectInternal(arrayClassData, lengths, 0);
+};
+
+ScalaJS.newArrayObjectInternal = function(arrayClassData, lengths, lengthIndex) {
+ var result = new arrayClassData.constr(lengths[lengthIndex]);
+
+ if (lengthIndex < lengths.length-1) {
+ var subArrayClassData = arrayClassData.componentData;
+ var subLengthIndex = lengthIndex+1;
+ var underlying = result.u;
+ for (var i = 0; i < underlying.length; i++) {
+ underlying[i] = ScalaJS.newArrayObjectInternal(
+ subArrayClassData, lengths, subLengthIndex);
+ }
+ }
+
+ return result;
+};
+
+ScalaJS.checkNonNull = function(obj) {
+ return obj !== null ? obj : ScalaJS.throwNullPointerException();
+};
+
+ScalaJS.throwNullPointerException = function() {
+ throw new ScalaJS.c.jl_NullPointerException().init___();
+};
+
+ScalaJS.objectToString = function(instance) {
+ if (instance === void 0)
+ return "undefined";
+ else
+ return instance.toString();
+};
+
+ScalaJS.objectGetClass = function(instance) {
+ switch (typeof instance) {
+ case "string":
+ return ScalaJS.d.T.getClassOf();
+ case "number":
+ var v = instance | 0;
+ if (v === instance) { // is the value integral?
+ if (ScalaJS.isByte(v))
+ return ScalaJS.d.jl_Byte.getClassOf();
+ else if (ScalaJS.isShort(v))
+ return ScalaJS.d.jl_Short.getClassOf();
+ else
+ return ScalaJS.d.jl_Integer.getClassOf();
+ } else {
+ if (ScalaJS.isFloat(instance))
+ return ScalaJS.d.jl_Float.getClassOf();
+ else
+ return ScalaJS.d.jl_Double.getClassOf();
+ }
+ case "boolean":
+ return ScalaJS.d.jl_Boolean.getClassOf();
+ case "undefined":
+ return ScalaJS.d.sr_BoxedUnit.getClassOf();
+ default:
+ if (instance === null)
+ ScalaJS.throwNullPointerException();
+ else if (ScalaJS.is.sjsr_RuntimeLong(instance))
+ return ScalaJS.d.jl_Long.getClassOf();
+ else if (ScalaJS.isScalaJSObject(instance))
+ return instance.$classData.getClassOf();
+ else
+ return null; // Exception?
+ }
+};
+
+ScalaJS.objectClone = function(instance) {
+ if (ScalaJS.isScalaJSObject(instance) || (instance === null))
+ return instance.clone__O();
+ else
+ throw new ScalaJS.c.jl_CloneNotSupportedException().init___();
+};
+
+ScalaJS.objectNotify = function(instance) {
+ // final and no-op in java.lang.Object
+ if (instance === null)
+ instance.notify__V();
+};
+
+ScalaJS.objectNotifyAll = function(instance) {
+ // final and no-op in java.lang.Object
+ if (instance === null)
+ instance.notifyAll__V();
+};
+
+ScalaJS.objectFinalize = function(instance) {
+ if (ScalaJS.isScalaJSObject(instance) || (instance === null))
+ instance.finalize__V();
+ // else no-op
+};
+
+ScalaJS.objectEquals = function(instance, rhs) {
+ if (ScalaJS.isScalaJSObject(instance) || (instance === null))
+ return instance.equals__O__Z(rhs);
+ else if (typeof instance === "number")
+ return typeof rhs === "number" && ScalaJS.numberEquals(instance, rhs);
+ else
+ return instance === rhs;
+};
+
+ScalaJS.numberEquals = function(lhs, rhs) {
+ return (
+ lhs === rhs // 0.0 === -0.0 to prioritize the Int case over the Double case
+ ) || (
+ // are they both NaN?
+ (lhs !== lhs) && (rhs !== rhs)
+ );
+};
+
+ScalaJS.objectHashCode = function(instance) {
+ switch (typeof instance) {
+ case "string":
+ return ScalaJS.m.sjsr_RuntimeString().hashCode__T__I(instance);
+ case "number":
+ return ScalaJS.m.sjsr_Bits().numberHashCode__D__I(instance);
+ case "boolean":
+ return instance ? 1231 : 1237;
+ case "undefined":
+ return 0;
+ default:
+ if (ScalaJS.isScalaJSObject(instance) || instance === null)
+ return instance.hashCode__I();
+ else
+ return 42; // TODO?
+ }
+};
+
+ScalaJS.comparableCompareTo = function(instance, rhs) {
+ switch (typeof instance) {
+ case "string":
+//!if asInstanceOfs != Unchecked
+ ScalaJS.as.T(rhs);
+//!endif
+ return instance === rhs ? 0 : (instance < rhs ? -1 : 1);
+ case "number":
+//!if asInstanceOfs != Unchecked
+ ScalaJS.as.jl_Number(rhs);
+//!endif
+ return ScalaJS.numberEquals(instance, rhs) ? 0 : (instance < rhs ? -1 : 1);
+ case "boolean":
+//!if asInstanceOfs != Unchecked
+ ScalaJS.asBoolean(rhs);
+//!endif
+ return instance - rhs; // yes, this gives the right result
+ default:
+ return instance.compareTo__O__I(rhs);
+ }
+};
+
+ScalaJS.charSequenceLength = function(instance) {
+ if (typeof(instance) === "string")
+//!if asInstanceOfs != Unchecked
+ return ScalaJS.uI(instance["length"]);
+//!else
+ return instance["length"] | 0;
+//!endif
+ else
+ return instance.length__I();
+};
+
+ScalaJS.charSequenceCharAt = function(instance, index) {
+ if (typeof(instance) === "string")
+//!if asInstanceOfs != Unchecked
+ return ScalaJS.uI(instance["charCodeAt"](index)) & 0xffff;
+//!else
+ return instance["charCodeAt"](index) & 0xffff;
+//!endif
+ else
+ return instance.charAt__I__C(index);
+};
+
+ScalaJS.charSequenceSubSequence = function(instance, start, end) {
+ if (typeof(instance) === "string")
+//!if asInstanceOfs != Unchecked
+ return ScalaJS.as.T(instance["substring"](start, end));
+//!else
+ return instance["substring"](start, end);
+//!endif
+ else
+ return instance.subSequence__I__I__jl_CharSequence(start, end);
+};
+
+ScalaJS.booleanBooleanValue = function(instance) {
+ if (typeof instance === "boolean") return instance;
+ else return instance.booleanValue__Z();
+};
+
+ScalaJS.numberByteValue = function(instance) {
+ if (typeof instance === "number") return (instance << 24) >> 24;
+ else return instance.byteValue__B();
+};
+ScalaJS.numberShortValue = function(instance) {
+ if (typeof instance === "number") return (instance << 16) >> 16;
+ else return instance.shortValue__S();
+};
+ScalaJS.numberIntValue = function(instance) {
+ if (typeof instance === "number") return instance | 0;
+ else return instance.intValue__I();
+};
+ScalaJS.numberLongValue = function(instance) {
+ if (typeof instance === "number")
+ return ScalaJS.m.sjsr_RuntimeLong().fromDouble__D__sjsr_RuntimeLong(instance);
+ else
+ return instance.longValue__J();
+};
+ScalaJS.numberFloatValue = function(instance) {
+ if (typeof instance === "number") return ScalaJS.fround(instance);
+ else return instance.floatValue__F();
+};
+ScalaJS.numberDoubleValue = function(instance) {
+ if (typeof instance === "number") return instance;
+ else return instance.doubleValue__D();
+};
+
+ScalaJS.isNaN = function(instance) {
+ return instance !== instance;
+};
+
+ScalaJS.isInfinite = function(instance) {
+ return !ScalaJS.g["isFinite"](instance) && !ScalaJS.isNaN(instance);
+};
+
+ScalaJS.propertiesOf = function(obj) {
+ var result = [];
+ for (var prop in obj)
+ result["push"](prop);
+ return result;
+};
+
+ScalaJS.systemArraycopy = function(src, srcPos, dest, destPos, length) {
+ var srcu = src.u;
+ var destu = dest.u;
+ if (srcu !== destu || destPos < srcPos || srcPos + length < destPos) {
+ for (var i = 0; i < length; i++)
+ destu[destPos+i] = srcu[srcPos+i];
+ } else {
+ for (var i = length-1; i >= 0; i--)
+ destu[destPos+i] = srcu[srcPos+i];
+ }
+};
+
+ScalaJS.systemIdentityHashCode = function(obj) {
+ if (ScalaJS.isScalaJSObject(obj)) {
+ var hash = obj["$idHashCode$0"];
+ if (hash !== void 0) {
+ return hash;
+ } else {
+ hash = (ScalaJS.lastIDHash + 1) | 0;
+ ScalaJS.lastIDHash = hash;
+ obj["$idHashCode$0"] = hash;
+ return hash;
+ }
+ } else if (obj === null) {
+ return 0;
+ } else {
+ return ScalaJS.objectHashCode(obj);
+ }
+};
+
+// is/as for hijacked boxed classes (the non-trivial ones)
+
+ScalaJS.isByte = function(v) {
+ return (v << 24 >> 24) === v;
+};
+
+ScalaJS.isShort = function(v) {
+ return (v << 16 >> 16) === v;
+};
+
+ScalaJS.isInt = function(v) {
+ return (v | 0) === v;
+};
+
+ScalaJS.isFloat = function(v) {
+ return v !== v || ScalaJS.fround(v) === v;
+};
+
+//!if asInstanceOfs != Unchecked
+ScalaJS.asUnit = function(v) {
+ if (v === void 0)
+ return v;
+ else
+ ScalaJS.throwClassCastException(v, "scala.runtime.BoxedUnit");
+};
+
+ScalaJS.asBoolean = function(v) {
+ if (typeof v === "boolean" || v === null)
+ return v;
+ else
+ ScalaJS.throwClassCastException(v, "java.lang.Boolean");
+};
+
+ScalaJS.asByte = function(v) {
+ if (ScalaJS.isByte(v) || v === null)
+ return v;
+ else
+ ScalaJS.throwClassCastException(v, "java.lang.Byte");
+};
+
+ScalaJS.asShort = function(v) {
+ if (ScalaJS.isShort(v) || v === null)
+ return v;
+ else
+ ScalaJS.throwClassCastException(v, "java.lang.Short");
+};
+
+ScalaJS.asInt = function(v) {
+ if (ScalaJS.isInt(v) || v === null)
+ return v;
+ else
+ ScalaJS.throwClassCastException(v, "java.lang.Integer");
+};
+
+ScalaJS.asFloat = function(v) {
+ if (ScalaJS.isFloat(v) || v === null)
+ return v;
+ else
+ ScalaJS.throwClassCastException(v, "java.lang.Float");
+};
+
+ScalaJS.asDouble = function(v) {
+ if (typeof v === "number" || v === null)
+ return v;
+ else
+ ScalaJS.throwClassCastException(v, "java.lang.Double");
+};
+//!endif
+
+// Unboxes
+
+//!if asInstanceOfs != Unchecked
+ScalaJS.uZ = function(value) {
+ return !!ScalaJS.asBoolean(value);
+};
+ScalaJS.uB = function(value) {
+ return ScalaJS.asByte(value) | 0;
+};
+ScalaJS.uS = function(value) {
+ return ScalaJS.asShort(value) | 0;
+};
+ScalaJS.uI = function(value) {
+ return ScalaJS.asInt(value) | 0;
+};
+ScalaJS.uJ = function(value) {
+ return null === value ? ScalaJS.m.sjsr_RuntimeLong().Zero$1
+ : ScalaJS.as.sjsr_RuntimeLong(value);
+};
+ScalaJS.uF = function(value) {
+ /* Here, it is fine to use + instead of fround, because asFloat already
+ * ensures that the result is either null or a float.
+ */
+ return +ScalaJS.asFloat(value);
+};
+ScalaJS.uD = function(value) {
+ return +ScalaJS.asDouble(value);
+};
+//!else
+ScalaJS.uJ = function(value) {
+ return null === value ? ScalaJS.m.sjsr_RuntimeLong().Zero$1 : value;
+};
+//!endif
+
+// TypeArray conversions
+
+ScalaJS.byteArray2TypedArray = function(value) { return new Int8Array(value.u); };
+ScalaJS.shortArray2TypedArray = function(value) { return new Int16Array(value.u); };
+ScalaJS.charArray2TypedArray = function(value) { return new Uint16Array(value.u); };
+ScalaJS.intArray2TypedArray = function(value) { return new Int32Array(value.u); };
+ScalaJS.floatArray2TypedArray = function(value) { return new Float32Array(value.u); };
+ScalaJS.doubleArray2TypedArray = function(value) { return new Float64Array(value.u); };
+
+ScalaJS.typedArray2ByteArray = function(value) {
+ var arrayClassData = ScalaJS.d.B.getArrayOf();
+ return new arrayClassData.constr(new Int8Array(value));
+};
+ScalaJS.typedArray2ShortArray = function(value) {
+ var arrayClassData = ScalaJS.d.S.getArrayOf();
+ return new arrayClassData.constr(new Int16Array(value));
+};
+ScalaJS.typedArray2CharArray = function(value) {
+ var arrayClassData = ScalaJS.d.C.getArrayOf();
+ return new arrayClassData.constr(new Uint16Array(value));
+};
+ScalaJS.typedArray2IntArray = function(value) {
+ var arrayClassData = ScalaJS.d.I.getArrayOf();
+ return new arrayClassData.constr(new Int32Array(value));
+};
+ScalaJS.typedArray2FloatArray = function(value) {
+ var arrayClassData = ScalaJS.d.F.getArrayOf();
+ return new arrayClassData.constr(new Float32Array(value));
+};
+ScalaJS.typedArray2DoubleArray = function(value) {
+ var arrayClassData = ScalaJS.d.D.getArrayOf();
+ return new arrayClassData.constr(new Float64Array(value));
+};
+
+/* We have to force a non-elidable *read* of ScalaJS.e, otherwise Closure will
+ * eliminate it altogether, along with all the exports, which is ... er ...
+ * plain wrong.
+ */
+this["__ScalaJSExportsNamespace"] = ScalaJS.e;
+
+// Type data constructors
+
+/** @constructor */
+ScalaJS.PrimitiveTypeData = function(zero, arrayEncodedName, displayName) {
+ // Runtime support
+ this.constr = undefined;
+ this.parentData = undefined;
+ this.ancestors = {};
+ this.componentData = null;
+ this.zero = zero;
+ this.arrayEncodedName = arrayEncodedName;
+ this._classOf = undefined;
+ this._arrayOf = undefined;
+ this.isArrayOf = function(obj, depth) { return false; };
+
+ // java.lang.Class support
+ this["name"] = displayName;
+ this["isPrimitive"] = true;
+ this["isInterface"] = false;
+ this["isArrayClass"] = false;
+ this["isInstance"] = function(obj) { return false; };
+};
+
+/** @constructor */
+ScalaJS.ClassTypeData = function(internalNameObj, isInterface, fullName,
+ parentData, ancestors, isInstance, isArrayOf) {
+ var internalName = ScalaJS.propertyName(internalNameObj);
+
+ isInstance = isInstance || function(obj) {
+ return !!(obj && obj.$classData && obj.$classData.ancestors[internalName]);
+ };
+
+ isArrayOf = isArrayOf || function(obj, depth) {
+ return !!(obj && obj.$classData && (obj.$classData.arrayDepth === depth)
+ && obj.$classData.arrayBase.ancestors[internalName])
+ };
+
+ // Runtime support
+ this.constr = undefined;
+ this.parentData = parentData;
+ this.ancestors = ancestors;
+ this.componentData = null;
+ this.zero = null;
+ this.arrayEncodedName = "L"+fullName+";";
+ this._classOf = undefined;
+ this._arrayOf = undefined;
+ this.isArrayOf = isArrayOf;
+
+ // java.lang.Class support
+ this["name"] = fullName;
+ this["isPrimitive"] = false;
+ this["isInterface"] = isInterface;
+ this["isArrayClass"] = false;
+ this["isInstance"] = isInstance;
+};
+
+/** @constructor */
+ScalaJS.ArrayTypeData = function(componentData) {
+ // The constructor
+
+ var componentZero = componentData.zero;
+
+ // The zero for the Long runtime representation
+ // is a special case here, since the class has not
+ // been defined yet, when this file is read
+ if (componentZero == "longZero")
+ componentZero = ScalaJS.m.sjsr_RuntimeLong().Zero$1;
+
+ /** @constructor */
+ var ArrayClass = function(arg) {
+ if (typeof(arg) === "number") {
+ // arg is the length of the array
+ this.u = new Array(arg);
+ for (var i = 0; i < arg; i++)
+ this.u[i] = componentZero;
+ } else {
+ // arg is a native array that we wrap
+ this.u = arg;
+ }
+ }
+ ArrayClass.prototype = new ScalaJS.h.O;
+ ArrayClass.prototype.constructor = ArrayClass;
+ ArrayClass.prototype.$classData = this;
+
+ ArrayClass.prototype.clone__O = function() {
+ if (this.u instanceof Array)
+ return new ArrayClass(this.u["slice"](0));
+ else
+ // The underlying Array is a TypedArray
+ return new ArrayClass(this.u.constructor(this.u));
+ };
+
+ // Don't generate reflective call proxies. The compiler special cases
+ // reflective calls to methods on scala.Array
+
+ // The data
+
+ var encodedName = "[" + componentData.arrayEncodedName;
+ var componentBase = componentData.arrayBase || componentData;
+ var componentDepth = componentData.arrayDepth || 0;
+ var arrayDepth = componentDepth + 1;
+
+ var isInstance = function(obj) {
+ return componentBase.isArrayOf(obj, arrayDepth);
+ }
+
+ // Runtime support
+ this.constr = ArrayClass;
+ this.parentData = ScalaJS.d.O;
+ this.ancestors = {O: 1};
+ this.componentData = componentData;
+ this.arrayBase = componentBase;
+ this.arrayDepth = arrayDepth;
+ this.zero = null;
+ this.arrayEncodedName = encodedName;
+ this._classOf = undefined;
+ this._arrayOf = undefined;
+ this.isArrayOf = undefined;
+
+ // java.lang.Class support
+ this["name"] = encodedName;
+ this["isPrimitive"] = false;
+ this["isInterface"] = false;
+ this["isArrayClass"] = true;
+ this["isInstance"] = isInstance;
+};
+
+ScalaJS.ClassTypeData.prototype.getClassOf = function() {
+ if (!this._classOf)
+ this._classOf = new ScalaJS.c.jl_Class().init___jl_ScalaJSClassData(this);
+ return this._classOf;
+};
+
+ScalaJS.ClassTypeData.prototype.getArrayOf = function() {
+ if (!this._arrayOf)
+ this._arrayOf = new ScalaJS.ArrayTypeData(this);
+ return this._arrayOf;
+};
+
+// java.lang.Class support
+
+ScalaJS.ClassTypeData.prototype["getFakeInstance"] = function() {
+ if (this === ScalaJS.d.T)
+ return "some string";
+ else if (this === ScalaJS.d.jl_Boolean)
+ return false;
+ else if (this === ScalaJS.d.jl_Byte ||
+ this === ScalaJS.d.jl_Short ||
+ this === ScalaJS.d.jl_Integer ||
+ this === ScalaJS.d.jl_Float ||
+ this === ScalaJS.d.jl_Double)
+ return 0;
+ else if (this === ScalaJS.d.jl_Long)
+ return ScalaJS.m.sjsr_RuntimeLong().Zero$1;
+ else if (this === ScalaJS.d.sr_BoxedUnit)
+ return void 0;
+ else
+ return {$classData: this};
+};
+
+ScalaJS.ClassTypeData.prototype["getSuperclass"] = function() {
+ return this.parentData ? this.parentData.getClassOf() : null;
+};
+
+ScalaJS.ClassTypeData.prototype["getComponentType"] = function() {
+ return this.componentData ? this.componentData.getClassOf() : null;
+};
+
+ScalaJS.ClassTypeData.prototype["newArrayOfThisClass"] = function(lengths) {
+ var arrayClassData = this;
+ for (var i = 0; i < lengths.length; i++)
+ arrayClassData = arrayClassData.getArrayOf();
+ return ScalaJS.newArrayObject(arrayClassData, lengths);
+};
+
+ScalaJS.PrimitiveTypeData.prototype = ScalaJS.ClassTypeData.prototype;
+ScalaJS.ArrayTypeData.prototype = ScalaJS.ClassTypeData.prototype;
+
+// Create primitive types
+
+ScalaJS.d.V = new ScalaJS.PrimitiveTypeData(undefined, "V", "void");
+ScalaJS.d.Z = new ScalaJS.PrimitiveTypeData(false, "Z", "boolean");
+ScalaJS.d.C = new ScalaJS.PrimitiveTypeData(0, "C", "char");
+ScalaJS.d.B = new ScalaJS.PrimitiveTypeData(0, "B", "byte");
+ScalaJS.d.S = new ScalaJS.PrimitiveTypeData(0, "S", "short");
+ScalaJS.d.I = new ScalaJS.PrimitiveTypeData(0, "I", "int");
+ScalaJS.d.J = new ScalaJS.PrimitiveTypeData("longZero", "J", "long");
+ScalaJS.d.F = new ScalaJS.PrimitiveTypeData(0.0, "F", "float");
+ScalaJS.d.D = new ScalaJS.PrimitiveTypeData(0.0, "D", "double");
+
+// Instance tests for array of primitives
+
+ScalaJS.isArrayOf.Z = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.Z);
+ScalaJS.d.Z.isArrayOf = ScalaJS.isArrayOf.Z;
+
+ScalaJS.isArrayOf.C = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.C);
+ScalaJS.d.C.isArrayOf = ScalaJS.isArrayOf.C;
+
+ScalaJS.isArrayOf.B = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.B);
+ScalaJS.d.B.isArrayOf = ScalaJS.isArrayOf.B;
+
+ScalaJS.isArrayOf.S = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.S);
+ScalaJS.d.S.isArrayOf = ScalaJS.isArrayOf.S;
+
+ScalaJS.isArrayOf.I = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.I);
+ScalaJS.d.I.isArrayOf = ScalaJS.isArrayOf.I;
+
+ScalaJS.isArrayOf.J = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.J);
+ScalaJS.d.J.isArrayOf = ScalaJS.isArrayOf.J;
+
+ScalaJS.isArrayOf.F = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.F);
+ScalaJS.d.F.isArrayOf = ScalaJS.isArrayOf.F;
+
+ScalaJS.isArrayOf.D = ScalaJS.makeIsArrayOfPrimitive(ScalaJS.d.D);
+ScalaJS.d.D.isArrayOf = ScalaJS.isArrayOf.D;
+
+//!if asInstanceOfs != Unchecked
+// asInstanceOfs for array of primitives
+ScalaJS.asArrayOf.Z = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.Z, "Z");
+ScalaJS.asArrayOf.C = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.C, "C");
+ScalaJS.asArrayOf.B = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.B, "B");
+ScalaJS.asArrayOf.S = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.S, "S");
+ScalaJS.asArrayOf.I = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.I, "I");
+ScalaJS.asArrayOf.J = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.J, "J");
+ScalaJS.asArrayOf.F = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.F, "F");
+ScalaJS.asArrayOf.D = ScalaJS.makeAsArrayOfPrimitive(ScalaJS.isArrayOf.D, "D");
+//!endif
+
+// Polyfills
+
+ScalaJS.imul = ScalaJS.g["Math"]["imul"] || (function(a, b) {
+ // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul
+ var ah = (a >>> 16) & 0xffff;
+ var al = a & 0xffff;
+ var bh = (b >>> 16) & 0xffff;
+ var bl = b & 0xffff;
+ // the shift by 0 fixes the sign on the high part
+ // the final |0 converts the unsigned value into a signed value
+ return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0) | 0);
+});
+
+ScalaJS.fround = ScalaJS.g["Math"]["fround"] ||
+//!if floats == Strict
+ (ScalaJS.g["Float32Array"] ? (function(v) {
+ var array = new ScalaJS.g["Float32Array"](1);
+ array[0] = v;
+ return array[0];
+ }) : (function(v) {
+ return ScalaJS.m.sjsr_package().froundPolyfill__D__D(+v);
+ }));
+//!else
+ (function(v) {
+ return +v;
+ });
+//!endif
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/CompleteClasspath.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/CompleteClasspath.scala
new file mode 100644
index 0000000..6646a7b
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/CompleteClasspath.scala
@@ -0,0 +1,35 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath
+
+import scala.collection.immutable.Seq
+
+import scala.scalajs.tools.io.VirtualJSFile
+import scala.scalajs.tools.jsdep.ResolutionInfo
+
+/** A classpath where nothing is missing. Therefore:
+ * - All JS libraries are resolved and ordered
+ * - The CoreJSLibs are present
+ * - Nothing can be added anymore
+ */
+abstract class CompleteClasspath(
+ /** Resolved JS libraries */
+ val jsLibs: Seq[ResolvedJSDependency],
+ val requiresDOM: Boolean,
+ val version: Option[String]
+) {
+
+ /** Fully linked Scala.js code */
+ def scalaJSCode: VirtualJSFile
+
+ /** All code in this complete classpath */
+ final def allCode: Seq[VirtualJSFile] = jsLibs.map(_.lib) :+ scalaJSCode
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ComplianceRequirement.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ComplianceRequirement.scala
new file mode 100644
index 0000000..f6ec36f
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ComplianceRequirement.scala
@@ -0,0 +1,7 @@
+package scala.scalajs.tools.classpath
+
+import scala.scalajs.tools.jsdep.Origin
+
+/** Expresses a requirement for a given semantic to be compliant */
+final class ComplianceRequirement(
+ val semantics: String, val origins: List[Origin])
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/Exceptions.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/Exceptions.scala
new file mode 100644
index 0000000..62bf75f
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/Exceptions.scala
@@ -0,0 +1,42 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath
+
+import scala.scalajs.tools.jsdep.ResolutionInfo
+
+class MissingJSLibException(val dependencies: List[ResolutionInfo])
+ extends Exception(MissingJSLibException.mkMsg(dependencies))
+
+object MissingJSLibException {
+ private def mkMsg(deps: List[ResolutionInfo]): String = {
+ val msg = new StringBuilder()
+ msg.append("Missing dependencies: \n")
+ for (d <- deps) {
+ msg.append(s"- ${d.resourceName}")
+ msg.append(s" originating from: ${d.origins.mkString(", ")}\n")
+ }
+ msg.toString()
+ }
+}
+
+class BadComplianceException(val unmet: List[ComplianceRequirement])
+ extends Exception(BadComplianceException.mkMsg(unmet))
+
+object BadComplianceException {
+ private def mkMsg(unmets: List[ComplianceRequirement]): String = {
+ val msg = new StringBuilder()
+ msg.append("Unmet required semantic compliance(s): \n")
+ for (unmet <- unmets) {
+ msg.append(s"- ${unmet.semantics}")
+ msg.append(s" originating from: ${unmet.origins.mkString(", ")}\n")
+ }
+ msg.toString
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/IRClasspath.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/IRClasspath.scala
new file mode 100644
index 0000000..a92293b
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/IRClasspath.scala
@@ -0,0 +1,69 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath
+
+import scala.collection.immutable.{Seq, Traversable}
+
+import scala.scalajs.tools.sem.Semantics
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.logging._
+import scala.scalajs.tools.optimizer.ScalaJSOptimizer
+import scala.scalajs.tools.jsdep.ResolutionInfo
+
+/** A [[CompleteClasspath]] that contains only IR as scalaJSCode */
+final class IRClasspath(
+ /** The JS libraries the IR code depends on */
+ jsLibs: Seq[ResolvedJSDependency],
+ val requiredCompliance: Traversable[ComplianceRequirement],
+ /** The IR itself. Ancestor count is used for later ordering */
+ val scalaJSIR: Traversable[VirtualScalaJSIRFile],
+ requiresDOM: Boolean,
+ version: Option[String]
+) extends CompleteClasspath(jsLibs, requiresDOM, version) {
+
+ /** Orders and optimizes the contained IR.
+ *
+ * Consider using [[ScalaJSOptimizer]] for a canonical way to do so. It
+ * allows to persist the resulting file and create a source map, as well as
+ * using non-default [[Semantics]].
+ */
+ override lazy val scalaJSCode: VirtualJSFile = {
+ import ScalaJSOptimizer._
+
+ val outName = "temporary-fastOpt.js"
+
+ if (scalaJSIR.nonEmpty) {
+ val semantics = Semantics.compliantTo(requiredCompliance.map(_.semantics))
+ val output = WritableMemVirtualJSFile(outName)
+ new ScalaJSOptimizer(semantics).optimizeCP(
+ Inputs(this),
+ OutputConfig(output),
+ NullLogger)
+ output
+ } else {
+ // We cannot run the optimizer without IR, because it will complain about
+ // java.lang.Object missing. However, an empty JS file is perfectly valid
+ // for no IR at all.
+ VirtualJSFile.empty(outName)
+ }
+ }
+
+ /** Checks whether the given semantics are compliant with the requirements of
+ * this CompleteClasspath. Throws an exception otherwise.
+ */
+ final def checkCompliance(semantics: Semantics): Unit = {
+ val unmet = requiredCompliance filterNot { compliance =>
+ semantics.isCompliant(compliance.semantics)
+ }
+
+ if (unmet.nonEmpty)
+ throw new BadComplianceException(unmet.toList)
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/LinkedClasspath.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/LinkedClasspath.scala
new file mode 100644
index 0000000..3ace785
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/LinkedClasspath.scala
@@ -0,0 +1,26 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath
+
+import scala.scalajs.tools.io.VirtualJSFile
+import scala.scalajs.tools.jsdep.ResolutionInfo
+
+import scala.collection.immutable.Seq
+
+/** A [[CompleteClasspath]] that is fully linked (either with the
+ * [[ScalaJSOptimizer]] or the Closure Optimizer. It contains only a single
+ * file that is scalaJSCode.
+ */
+final class LinkedClasspath(
+ jsLibs: Seq[ResolvedJSDependency],
+ val scalaJSCode: VirtualJSFile,
+ requiresDOM: Boolean,
+ version: Option[String]
+) extends CompleteClasspath(jsLibs, requiresDOM, version)
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/PartialClasspath.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/PartialClasspath.scala
new file mode 100644
index 0000000..949cd6e
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/PartialClasspath.scala
@@ -0,0 +1,99 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath
+
+import scala.collection.immutable.{Seq, Traversable}
+
+import scala.scalajs.tools.jsdep._
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.corelib.CoreJSLibs
+
+/** A partial Scala.js classpath is a collection of:
+ * - Scala.js binary files *.sjsir
+ * - Native JavaScript libraries
+ * - Description of dependencies on other JavaScript libraries
+ *
+ * PartialClasspaths can be combined (using [[merge]]) and eventually resolved
+ * to a [[CompleteIRClasspath]]
+ */
+final class PartialClasspath(
+ /** Description of JS libraries the content of this classpath depends on */
+ val dependencies: Traversable[JSDependencyManifest],
+ /** JS libraries this partial classpath provides */
+ val availableLibs: Map[String, VirtualJSFile],
+ /** Scala.js IR contained in this PartialClasspath (unordered) */
+ val scalaJSIR: Traversable[VirtualScalaJSIRFile],
+ val version: Option[String]
+) {
+ import PartialClasspath.DependencyFilter
+
+ /** Merges another [[PartialClasspath]] with this one. This means:
+ * - Concatenate/merge dependencies
+ * - Merge availableLibs (libs in that shadow libs in this)
+ * - Merge Scala.js IR
+ */
+ def merge(that: PartialClasspath): PartialClasspath = {
+ new PartialClasspath(
+ this.dependencies ++ that.dependencies,
+ this.availableLibs ++ that.availableLibs,
+ this.scalaJSIR ++ that.scalaJSIR,
+ CacheUtils.joinVersions(this.version, that.version))
+ }
+
+ /** Construct a [[IRClasspath]] out of this [[PartialClasspath]] by
+ * resolving library dependencies (and failing if they are not met)
+ */
+ def resolve(filter: DependencyFilter = identity): IRClasspath = {
+ new IRClasspath(resolveDependencies(filter), mergeCompliance(), scalaJSIR,
+ dependencies.exists(_.requiresDOM), version)
+ }
+
+ /** Constructs an ordered list of JS libraries to include. Fails if:
+ * - Dependencies have cycles
+ * - Not all dependencies are available
+ */
+ protected def resolveDependencies(
+ filter: DependencyFilter): List[ResolvedJSDependency] = {
+ val flatDeps = filter(dependencies.flatMap(_.flatten))
+ val includeList = JSDependencyManifest.createIncludeList(flatDeps)
+
+ val missingDeps = includeList.filterNot { info =>
+ availableLibs.contains(info.resourceName)
+ }
+
+ if (missingDeps.nonEmpty)
+ throw new MissingJSLibException(missingDeps)
+
+ for (info <- includeList)
+ yield new ResolvedJSDependency(availableLibs(info.resourceName), info)
+ }
+
+ protected def mergeCompliance(): Traversable[ComplianceRequirement] = {
+ val flatTups = for {
+ dependency <- dependencies
+ semantics <- dependency.compliantSemantics
+ } yield (semantics, dependency.origin)
+
+ for {
+ (semantics, tups) <- flatTups.groupBy(_._1)
+ } yield new ComplianceRequirement(semantics, tups.map(_._2).toList)
+ }
+
+}
+
+object PartialClasspath {
+
+ type DependencyFilter =
+ Traversable[FlatJSDependency] => Traversable[FlatJSDependency]
+
+ /** Creates an empty PartialClasspath */
+ def empty: PartialClasspath =
+ new PartialClasspath(Nil, Map.empty, Nil, Some(""))
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ResolvedJSDependency.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ResolvedJSDependency.scala
new file mode 100644
index 0000000..12ae8dc
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/ResolvedJSDependency.scala
@@ -0,0 +1,10 @@
+package scala.scalajs.tools.classpath
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.jsdep._
+
+/** A dependency on a native JavaScript library that has been successfully
+ * resolved
+ */
+final class ResolvedJSDependency(
+ val lib: VirtualJSFile, val info: ResolutionInfo)
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractJarLibClasspathBuilder.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractJarLibClasspathBuilder.scala
new file mode 100644
index 0000000..77f8509
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractJarLibClasspathBuilder.scala
@@ -0,0 +1,53 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.collection.mutable
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.jsdep.JSDependencyManifest
+import scala.scalajs.tools.classpath._
+
+/** reads a ScalaJS library JAR into a CP
+ * - IR files go to scalaJSCode
+ * - JS files go to availableLibs
+ * - Reads a potential top-level JS_DEPENDENCIES file
+ */
+trait AbstractJarLibClasspathBuilder extends JarTraverser {
+
+ private val irFiles = mutable.ListBuffer.empty[VirtualScalaJSIRFile]
+ private val jsFiles = mutable.Map.empty[String, VirtualJSFile]
+ private var dependency: Option[JSDependencyManifest] = None
+
+ def build(jar: File): PartialClasspath = {
+ val v = traverseJar(jar)
+ new PartialClasspath(dependency.toList,
+ jsFiles.toMap, irFiles.toList, Some(v))
+ }
+
+ override protected def handleIR(relPath: String,
+ ir: => VirtualScalaJSIRFile): Unit = {
+ // We don't need to implement shadowing here: We have only a single JAR
+ irFiles += ir
+ }
+
+ override protected def handleJS(js: => VirtualJSFile): Unit = {
+ val file = js
+ if (!jsFiles.contains(file.name))
+ jsFiles += file.name -> file
+ }
+
+ override protected def handleDepManifest(m: => JSDependencyManifest): Unit = {
+ if (dependency.isDefined)
+ sys.error("A JAR cannot have multiple JS dependency manifests")
+ dependency = Some(m)
+ }
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractPartialClasspathBuilder.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractPartialClasspathBuilder.scala
new file mode 100644
index 0000000..9889f4c
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/AbstractPartialClasspathBuilder.scala
@@ -0,0 +1,47 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.jsdep.JSDependencyManifest
+import scala.scalajs.tools.classpath._
+import scala.scalajs.tools.io._
+
+import scala.collection.mutable
+import scala.collection.immutable.Seq
+
+trait AbstractPartialClasspathBuilder extends ClasspathContentHandler
+ with ClasspathElementsTraverser {
+
+ private val jsDepManifests = mutable.ListBuffer.empty[JSDependencyManifest]
+ private val irFiles = mutable.Map.empty[String, VirtualScalaJSIRFile]
+ private val otherJSFiles = mutable.Map.empty[String, VirtualJSFile]
+
+ override protected def handleIR(relPath: String,
+ ir: => VirtualScalaJSIRFile): Unit = {
+ if (!irFiles.contains(relPath))
+ irFiles += relPath -> ir
+ }
+
+ override protected def handleJS(js: => VirtualJSFile): Unit = {
+ val file = js
+ if (!otherJSFiles.contains(file.name))
+ otherJSFiles += file.name -> file
+ }
+
+ override protected def handleDepManifest(m: => JSDependencyManifest): Unit = {
+ jsDepManifests += m
+ }
+
+ def build(cp: Seq[File]): PartialClasspath = {
+ val version = traverseClasspathElements(cp)
+ new PartialClasspath(jsDepManifests.toList, otherJSFiles.toMap,
+ irFiles.values.toList, Some(version))
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathContentHandler.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathContentHandler.scala
new file mode 100644
index 0000000..71106c2
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathContentHandler.scala
@@ -0,0 +1,25 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.jsdep._
+import scala.scalajs.tools.classpath._
+
+import java.io._
+
+import scala.collection.immutable.Seq
+
+/** Base-trait used by traversers to handle content with callbacks */
+trait ClasspathContentHandler {
+ protected def handleIR(relPath: String, ir: => VirtualScalaJSIRFile): Unit
+ protected def handleJS(js: => VirtualJSFile): Unit
+ protected def handleDepManifest(m: => JSDependencyManifest): Unit
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathElementsTraverser.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathElementsTraverser.scala
new file mode 100644
index 0000000..9403ce3
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/ClasspathElementsTraverser.scala
@@ -0,0 +1,38 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.io._
+
+/** A helper trait to traverse an arbitrary classpath element
+ * (i.e. a JAR or a directory).
+ */
+trait ClasspathElementsTraverser extends JarTraverser
+ with DirTraverser
+ with FileSystem {
+
+ protected def traverseClasspathElements(cp: Seq[File]): String =
+ CacheUtils.joinVersions(cp.map(readEntriesInClasspathElement _): _*)
+
+ /** Adds the Scala.js classpath entries in a directory or jar.
+ * Returns the accumulated version
+ */
+ private def readEntriesInClasspathElement(element: File): String = {
+ if (!exists(element))
+ getDummyVersion(element)
+ else if (isDirectory(element))
+ traverseDir(element)
+ else if (isJARFile(element))
+ traverseJar(element)
+ else
+ sys.error(s"$element (in classpath) exists and is neither JAR or directory")
+ }
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/DirTraverser.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/DirTraverser.scala
new file mode 100644
index 0000000..6609b29
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/DirTraverser.scala
@@ -0,0 +1,60 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.jsdep.JSDependencyManifest
+
+import scala.collection.mutable
+
+trait DirTraverser extends ClasspathContentHandler with FileSystem {
+
+ /** Traverses elements, returns a version string */
+ protected def traverseDir(dir: File): String = {
+ val versions = mutable.SortedSet.empty[String]
+
+ recurseDir(dir, "", versions)
+
+ // Construct version
+ CacheUtils.joinVersions(versions.toSeq: _*)
+ }
+
+ /** Recursively adds the Scala.js classpath entries in a directory */
+ private def recurseDir(dir: File, dirPath: String,
+ versions: mutable.SortedSet[String]): Unit = {
+ val files = listFiles(dir)
+ for (file <- files) {
+ val name = getName(file)
+ if (isDirectory(file)) {
+ recurseDir(file, dirPath + name + "/", versions)
+ } else {
+ val path = dirPath + name
+ path match {
+ case JSDependencyManifest.ManifestFileName =>
+ versions += getGlobalVersion(file)
+ val reader = toReader(file)
+ try handleDepManifest(JSDependencyManifest.read(reader))
+ finally reader.close()
+
+ case _ if isJSFile(file) =>
+ versions += getGlobalVersion(file)
+ handleJS(toJSFile(file))
+
+ case _ if isIRFile(file) =>
+ versions += getGlobalVersion(file)
+ handleIR(path, toIRFile(file))
+
+ case _ => // ignore other files
+ }
+ }
+ }
+ }
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/FileSystem.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/FileSystem.scala
new file mode 100644
index 0000000..99a8ca2
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/FileSystem.scala
@@ -0,0 +1,57 @@
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.io._
+
+import scala.collection.immutable.Traversable
+
+import java.io.{InputStream, Reader}
+
+/** Abstraction of a FileSystem, so classpath builders can be used with virtual
+ * file systems
+ */
+trait FileSystem {
+
+ type File
+
+ /** Dummy version constant to identify files for which a version can not be
+ * found.
+ * This constant should never collide with the result of getVersion.
+ */
+ val DummyVersion: String
+
+ def isDirectory(f: File): Boolean
+ def isFile(f: File): Boolean
+ def isJSFile(f: File): Boolean
+ def isIRFile(f: File): Boolean
+ def isJARFile(f: File): Boolean
+ def exists(f: File): Boolean
+
+ def getName(f: File): String
+ /** A string that uniquely identifies this file's location */
+ def getAbsolutePath(f: File): String
+ /** A string that identifies the version of a file: If it equals the version
+ * of another file with the same absolute path, the two files must be equal.
+ * This is usually the lastModified date, but ordering is not required
+ */
+ def getVersion(f: File): String
+ /** A string that globally identifies the version of a file: If it equals the
+ * global version of any other file, they must equal.
+ */
+ def getGlobalVersion(f: File): String =
+ CacheUtils.joinVersions(getAbsolutePath(f), getVersion(f))
+
+ /** A string that globally identifies a file for which a version can not be
+ * found. Example: a file that does not exists.
+ */
+ def getDummyVersion(f: File): String =
+ CacheUtils.joinVersions(getAbsolutePath(f), DummyVersion)
+
+ /** List files in a directory */
+ def listFiles(d: File): Traversable[File]
+
+ def toJSFile(f: File): VirtualJSFile
+ def toIRFile(f: File): VirtualScalaJSIRFile
+ def toReader(f: File): Reader
+ def toInputStream(f: File): InputStream
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/JarTraverser.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/JarTraverser.scala
new file mode 100644
index 0000000..bbf5270
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/classpath/builder/JarTraverser.scala
@@ -0,0 +1,85 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.collection.mutable
+import scala.annotation.tailrec
+
+import java.util.zip._
+import java.io.{InputStream, InputStreamReader, Reader}
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.jsdep.JSDependencyManifest
+
+trait JarTraverser extends ClasspathContentHandler with FileSystem {
+
+ private val jsFiles = mutable.Map.empty[String, MemVirtualJSFile]
+
+ /** Traverse a Jar and return a version */
+ protected def traverseJar(jar: File): String = {
+ val zipStream = new ZipInputStream(toInputStream(jar))
+
+ try readEntries(zipStream, getAbsolutePath(jar))
+ finally zipStream.close()
+
+ for {
+ (_, jsFile) <- jsFiles
+ if jsFile.content != "" // drop if this is just a lone sourcemap
+ } handleJS(jsFile)
+
+ getGlobalVersion(jar)
+ }
+
+ private def getOrCreateJSFile(relPath: String, fullPath: String, fn: String) =
+ jsFiles.getOrElseUpdate(relPath, new MemVirtualJSFile(fullPath) {
+ override val name = fn
+ })
+
+ @tailrec
+ private def readEntries(in: ZipInputStream, jarPath: String): Unit = {
+ val entry = in.getNextEntry()
+ if (entry != null) {
+ readEntry(entry, in, jarPath)
+ readEntries(in, jarPath)
+ }
+ }
+
+ private def readEntry(entry: ZipEntry, in: InputStream, jarPath: String) = {
+ val longName = entry.getName
+ val shortName = VirtualFile.nameFromPath(longName)
+ val fullPath = jarPath + "#" + longName
+
+ def entryReader: Reader = new InputStreamReader(in, "UTF-8")
+ def entryContent: String = IO.readInputStreamToString(in)
+ def entryBinaryContent: Array[Byte] = IO.readInputStreamToByteArray(in)
+ def entryVersion: Option[String] = Some(entry.getTime.toString)
+
+ if (longName == JSDependencyManifest.ManifestFileName)
+ handleDepManifest(JSDependencyManifest.read(entryReader))
+ else if (longName.endsWith(".js")) {
+ val relPath = longName
+ getOrCreateJSFile(relPath, fullPath, shortName)
+ .withContent(entryContent)
+ .withVersion(entryVersion)
+ } else if (longName.endsWith(".js.map")) {
+ // assume the source map of a JS file
+ val relPath = longName.dropRight(".map".length)
+ getOrCreateJSFile(relPath, fullPath, shortName)
+ .withSourceMap(Some(entryContent))
+ } else if (longName.endsWith(".sjsir")) {
+ val vf = new MemVirtualSerializedScalaJSIRFile(fullPath) {
+ override val name = shortName
+ }.withContent(entryBinaryContent)
+ .withVersion(entryVersion)
+
+ handleIR(longName, vf)
+ }
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/corelib/CoreJSLibs.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/corelib/CoreJSLibs.scala
new file mode 100644
index 0000000..ecbea1f
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/corelib/CoreJSLibs.scala
@@ -0,0 +1,113 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.corelib
+
+import java.net.URI
+
+import scala.scalajs.ir.ScalaJSVersions
+import scala.scalajs.tools.io._
+
+import scala.scalajs.tools.sem._
+
+import scala.collection.immutable.Seq
+import scala.collection.mutable
+
+object CoreJSLibs {
+
+ private val cachedLibBySemantics =
+ mutable.HashMap.empty[Semantics, VirtualJSFile]
+
+ private val ScalaJSEnvLines =
+ ScalaJSEnvHolder.scalajsenv.split("\n|\r\n?")
+
+ private val gitHubBaseURI =
+ new URI("https://raw.githubusercontent.com/scala-js/scala-js/")
+
+ def libs(semantics: Semantics): Seq[VirtualJSFile] = synchronized {
+ Seq(cachedLibBySemantics.getOrElseUpdate(semantics, makeLib(semantics)))
+ }
+
+ private def makeLib(semantics: Semantics): VirtualJSFile = {
+ new ScalaJSEnvVirtualJSFile(makeContent(semantics))
+ }
+
+ private def makeContent(semantics: Semantics): String = {
+ // This is a basic sort-of-C-style preprocessor
+
+ def getOption(name: String): String = name match {
+ case "asInstanceOfs" =>
+ semantics.asInstanceOfs.toString()
+ case "floats" =>
+ if (semantics.strictFloats) "Strict"
+ else "Loose"
+ }
+
+ var skipping = false
+ var skipDepth = 0
+ val lines = for (line <- ScalaJSEnvLines) yield {
+ val includeThisLine = if (skipping) {
+ if (line == "//!else" && skipDepth == 1) {
+ skipping = false
+ skipDepth = 0
+ } else if (line == "//!endif") {
+ skipDepth -= 1
+ if (skipDepth == 0)
+ skipping = false
+ } else if (line.startsWith("//!if ")) {
+ skipDepth += 1
+ }
+ false
+ } else {
+ if (line.startsWith("//!")) {
+ if (line.startsWith("//!if ")) {
+ val Array(_, option, op, value) = line.split(" ")
+ val optionValue = getOption(option)
+ val success = op match {
+ case "==" => optionValue == value
+ case "!=" => optionValue != value
+ }
+ if (!success) {
+ skipping = true
+ skipDepth = 1
+ }
+ } else if (line == "//!else") {
+ skipping = true
+ skipDepth = 1
+ } else if (line == "//!endif") {
+ // nothing to do
+ } else {
+ throw new MatchError(line)
+ }
+ false
+ } else {
+ true
+ }
+ }
+ if (includeThisLine) line
+ else "" // blank line preserves line numbers in source maps
+ }
+
+ lines.mkString("", "\n", "\n")
+ }
+
+ private class ScalaJSEnvVirtualJSFile(override val content: String) extends VirtualJSFile {
+ override def path: String = "scalajsenv.js"
+ override def version: Option[String] = Some("")
+ override def exists: Boolean = true
+
+ override def toURI: URI = {
+ if (!ScalaJSVersions.currentIsSnapshot)
+ gitHubBaseURI.resolve(s"v${ScalaJSVersions.current}/tools/$path")
+ else
+ super.toURI
+ }
+ }
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSEnv.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSEnv.scala
new file mode 100644
index 0000000..d439ae2
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSEnv.scala
@@ -0,0 +1,19 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.env
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.classpath._
+import scala.scalajs.tools.logging._
+
+trait AsyncJSEnv extends JSEnv {
+ def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile,
+ logger: Logger, console: JSConsole): AsyncJSRunner
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSRunner.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSRunner.scala
new file mode 100644
index 0000000..09e2dda
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/AsyncJSRunner.scala
@@ -0,0 +1,19 @@
+package scala.scalajs.tools.env
+
+import scala.concurrent.Future
+
+trait AsyncJSRunner {
+ /** Start the associated run and returns a Future that completes when the run
+ * terminates.
+ */
+ def start(): Future[Unit]
+
+ /** Abort the associated run */
+ def stop(): Unit
+
+ /** Checks whether this async runner is still running */
+ def isRunning(): Boolean
+
+ /** Await completion of the started Run. Throws if the run failed */
+ def await(): Unit
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSEnv.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSEnv.scala
new file mode 100644
index 0000000..882e46a
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSEnv.scala
@@ -0,0 +1,38 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.env
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.classpath._
+import scala.scalajs.tools.logging._
+
+/** An [[AsyncJSEnv]] that provides communication to and from the JS VM.
+ *
+ * Inside the VM there is a global JavaScript object named `scalajsCom` that
+ * can be used to control the message channel. It's operations are:
+ * {{{
+ * // initialize com (with callback)
+ * scalajsCom.init(function(msg) { console.log("Received: " + msg); });
+ *
+ * // send a message to host system
+ * scalajsCom.send("my message");
+ *
+ * // close com (releases callback, allowing VM to terminate)
+ * scalajsCom.close();
+ * }}}
+ */
+trait ComJSEnv extends AsyncJSEnv {
+ def comRunner(classpath: CompleteClasspath, code: VirtualJSFile,
+ logger: Logger, console: JSConsole): ComJSRunner
+}
+
+object ComJSEnv {
+ class ComClosedException extends Exception("JSCom has been closed")
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSRunner.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSRunner.scala
new file mode 100644
index 0000000..44302b8
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ComJSRunner.scala
@@ -0,0 +1,22 @@
+package scala.scalajs.tools.env
+
+trait ComJSRunner extends AsyncJSRunner {
+
+ /** Send a message to the JS VM. Throws if the message cannot be sent. */
+ def send(msg: String): Unit
+
+ /** Block until a message is received. Throws a [[ComClosedExcpetion]]
+ * if the channel is closed before a message is received.
+ */
+ def receive(): String
+
+ /** Close the communication channel. Allows the VM to terminate if it is
+ * still waiting for callback. The JVM side **must** call close in
+ * order to be able to expect termination of the VM.
+ *
+ * Calling [[stop]] on a [ComJSRunner]] automatically closes the
+ * channel.
+ */
+ def close(): Unit
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ConsoleJSConsole.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ConsoleJSConsole.scala
new file mode 100644
index 0000000..5b3d055
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/ConsoleJSConsole.scala
@@ -0,0 +1,17 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.env
+
+/** A JS console that prints on the console */
+object ConsoleJSConsole extends JSConsole {
+ override def log(msg: Any): Unit = {
+ Console.println(msg)
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSConsole.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSConsole.scala
new file mode 100644
index 0000000..a93768f
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSConsole.scala
@@ -0,0 +1,15 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.env
+
+/** Trait representing a JS console */
+trait JSConsole {
+ def log(msg: Any): Unit
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSEnv.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSEnv.scala
new file mode 100644
index 0000000..f1fbf44
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSEnv.scala
@@ -0,0 +1,20 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.env
+
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.classpath._
+import scala.scalajs.tools.logging._
+
+trait JSEnv {
+ /** Prepare a runner for the code in the virtual file. */
+ def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile,
+ logger: Logger, console: JSConsole): JSRunner
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSRunner.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSRunner.scala
new file mode 100644
index 0000000..460fff0
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/JSRunner.scala
@@ -0,0 +1,15 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.env
+
+trait JSRunner {
+ /** Run the associated JS code. Throw if an error occurs. */
+ def run(): Unit
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/NullJSConsole.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/NullJSConsole.scala
new file mode 100644
index 0000000..8147bbe
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/env/NullJSConsole.scala
@@ -0,0 +1,5 @@
+package scala.scalajs.tools.env
+
+object NullJSConsole extends JSConsole {
+ def log(msg: Any): Unit = {}
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/CacheUtils.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/CacheUtils.scala
new file mode 100644
index 0000000..14773f8
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/CacheUtils.scala
@@ -0,0 +1,52 @@
+package scala.scalajs.tools.io
+
+object CacheUtils {
+
+ def joinVersions(vs: Option[String]*): Option[String] = {
+ val bld = new StringBuilder
+
+ @scala.annotation.tailrec
+ def loop(vs: Seq[Option[String]]): Option[String] = {
+ vs match {
+ case Some(v) :: vss =>
+ bld.append(mangleVersionString(v))
+ loop(vss)
+ case None :: _ =>
+ None
+ case Nil =>
+ Some(bld.toString)
+ }
+ }
+
+ loop(vs.toList)
+ }
+
+ def joinVersions(vs: String*): String =
+ vs.map(mangleVersionString _).mkString
+
+ private def mangleVersionString(str: String) = s"${str.length}:$str"
+
+ def cached(version: Option[String], output: VirtualFile,
+ cache: Option[WritableVirtualTextFile])(action: => Unit): Unit = {
+
+ val upToDate = output.exists && (
+ for {
+ v <- version
+ c <- cache if c.exists
+ } yield c.content == v
+ ).getOrElse(false)
+
+ // Are we outdated?
+ if (!upToDate) {
+ action
+
+ // Write cache
+ for (c <- cache; v <- version) {
+ val w = c.contentWriter
+ try w.write(v)
+ finally w.close()
+ }
+ }
+ }
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/IO.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/IO.scala
new file mode 100644
index 0000000..b69b07c
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/IO.scala
@@ -0,0 +1,116 @@
+package scala.scalajs.tools.io
+
+import scala.annotation.tailrec
+
+import scala.reflect.ClassTag
+
+import java.io._
+
+object IO {
+ /** Returns the lines in an input stream.
+ * Lines do not contain the new line characters.
+ */
+ def readLines(stream: InputStream): List[String] =
+ readLines(new InputStreamReader(stream))
+
+ /** Returns the lines in a string.
+ * Lines do not contain the new line characters.
+ */
+ def readLines(content: String): List[String] =
+ readLines(new StringReader(content))
+
+ /** Returns the lines in a reader.
+ * Lines do not contain the new line characters.
+ */
+ def readLines(reader: Reader): List[String] = {
+ val br = new BufferedReader(reader)
+ try {
+ val builder = List.newBuilder[String]
+ @tailrec
+ def loop(): Unit = {
+ val line = br.readLine()
+ if (line ne null) {
+ builder += line
+ loop()
+ }
+ }
+ loop()
+ builder.result()
+ } finally {
+ br.close()
+ }
+ }
+
+ /** Reads the entire content of a reader as a string. */
+ def readReaderToString(reader: Reader): String = {
+ val buffer = newBuffer[Char]
+ val builder = new StringBuilder
+ @tailrec
+ def loop(): Unit = {
+ val len = reader.read(buffer)
+ if (len > 0) {
+ builder.appendAll(buffer, 0, len)
+ loop()
+ }
+ }
+ loop()
+ builder.toString()
+ }
+
+ /** Reads the entire content of an input stream as a UTF-8 string. */
+ def readInputStreamToString(stream: InputStream): String = {
+ val reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"))
+ readReaderToString(reader)
+ }
+
+ /** Reads the entire content of an input stream as a byte array. */
+ def readInputStreamToByteArray(stream: InputStream): Array[Byte] = {
+ val builder = new ByteArrayOutputStream()
+ val buffer = newBuffer[Byte]
+ @tailrec
+ def loop(): Unit = {
+ val size = stream.read(buffer)
+ if (size > 0) {
+ builder.write(buffer, 0, size)
+ loop()
+ }
+ }
+ loop()
+ builder.toByteArray()
+ }
+
+ /** Concatenates a bunch of VirtualTextFiles to a WritableVirtualTextFile.
+ * Adds a '\n' after each file.
+ */
+ def concatFiles(output: WritableVirtualTextFile,
+ files: Seq[VirtualTextFile]): Unit = {
+ val buffer = newBuffer[Char]
+ val out = output.contentWriter
+
+ try {
+ for (file <- files) {
+ val reader = file.reader
+
+ @tailrec
+ def loop(): Unit = {
+ val size = reader.read(buffer)
+ if (size > 0) {
+ out.write(buffer, 0, size)
+ loop()
+ }
+ }
+
+ try loop()
+ finally reader.close()
+
+ // New line after each file
+ out.write('\n')
+ }
+ } finally {
+ out.close()
+ }
+ }
+
+ @inline
+ private def newBuffer[T : ClassTag] = new Array[T](4096)
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/MemFiles.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/MemFiles.scala
new file mode 100644
index 0000000..68f66dc
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/MemFiles.scala
@@ -0,0 +1,105 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.io
+
+import java.io._
+
+/** A base class for simple in-memory mutable virtual files. */
+class MemVirtualFile(val path: String) extends VirtualFile {
+ private[this] var _version: Option[String] = None
+
+ override def version: Option[String] = _version
+ def version_=(v: Option[String]): Unit = _version = v
+
+ final def withVersion(v: Option[String]): this.type = {
+ version = v
+ this
+ }
+
+ override def exists: Boolean = true
+}
+
+/** A simple in-memory mutable virtual text file. */
+class MemVirtualTextFile(p: String) extends MemVirtualFile(p)
+ with VirtualTextFile {
+ private[this] var _content: String = ""
+
+ override def content: String = _content
+ def content_=(v: String): Unit = _content = v
+
+ final def withContent(v: String): this.type = {
+ content = v
+ this
+ }
+}
+
+trait WritableMemVirtualTextFile extends MemVirtualTextFile
+ with WritableVirtualTextFile {
+ def contentWriter: Writer = new StringWriter {
+ override def close(): Unit = {
+ super.close()
+ WritableMemVirtualTextFile.this.content = this.toString
+ }
+ }
+}
+
+object WritableMemVirtualTextFile {
+ def apply(path: String): WritableMemVirtualTextFile =
+ new MemVirtualTextFile(path) with WritableMemVirtualTextFile
+}
+
+/** A simple in-memory mutable virtual binary file. */
+class MemVirtualBinaryFile(p: String) extends MemVirtualFile(p)
+ with VirtualBinaryFile {
+ private[this] var _content: Array[Byte] = new Array[Byte](0)
+
+ override def content: Array[Byte] = _content
+ def content_=(v: Array[Byte]): Unit = _content = v
+
+ final def withContent(v: Array[Byte]): this.type = {
+ content = v
+ this
+ }
+}
+
+/** A simple in-memory mutable virtual JS file. */
+class MemVirtualJSFile(p: String) extends MemVirtualTextFile(p)
+ with VirtualJSFile {
+ private[this] var _sourceMap: Option[String] = None
+
+ override def sourceMap: Option[String] = _sourceMap
+ def sourceMap_=(v: Option[String]): Unit = _sourceMap = v
+
+ final def withSourceMap(v: Option[String]): this.type = {
+ sourceMap = v
+ this
+ }
+}
+
+trait WritableMemVirtualJSFile extends MemVirtualJSFile
+ with WritableVirtualJSFile
+ with WritableMemVirtualTextFile {
+
+ def sourceMapWriter: Writer = new StringWriter {
+ override def close(): Unit = {
+ super.close()
+ WritableMemVirtualJSFile.this.sourceMap = Some(this.toString)
+ }
+ }
+}
+
+object WritableMemVirtualJSFile {
+ def apply(path: String): WritableMemVirtualJSFile =
+ new MemVirtualJSFile(path) with WritableMemVirtualJSFile
+}
+
+/** A simple in-memory mutable virtual serialized Scala.js IR file. */
+class MemVirtualSerializedScalaJSIRFile(p: String) extends MemVirtualBinaryFile(p)
+ with VirtualSerializedScalaJSIRFile
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/VirtualFiles.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/VirtualFiles.scala
new file mode 100644
index 0000000..c62ab5c
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/io/VirtualFiles.scala
@@ -0,0 +1,169 @@
+package scala.scalajs.tools.io
+
+import java.io._
+import java.net.URI
+
+import scala.scalajs.ir
+import scala.scalajs.tools.sourcemap._
+
+/** A virtual input file.
+ */
+trait VirtualFile {
+ /** Path of the file, including everything.
+ * Unique if possible (used for lookup). */
+ def path: String
+
+ /** Name of the file/writer, including extension */
+ def name: String = VirtualFile.nameFromPath(path)
+
+ /** Optionally returns an implementation-dependent "version" token.
+ * Versions are compared with ==.
+ * If non-empty, a different version must be returned when the content
+ * changes. It should be equal if the content has not changed, but it is
+ * not mandatory.
+ * Such a token can be used by caches: the file need not be read and
+ * processed again if its version has not changed.
+ */
+ def version: Option[String] = None
+
+ /** Whether this file exists. Reading a non-existent file may fail */
+ def exists: Boolean
+
+ /** URI for this virtual file */
+ def toURI: URI = {
+ new URI(
+ "virtualfile", // Pseudo-Scheme
+ path, // Scheme specific part
+ null // Fragment
+ )
+ }
+}
+
+object VirtualFile {
+ /** Splits at the last slash and returns remainder */
+ def nameFromPath(path: String): String = {
+ val pos = path.lastIndexOf('/')
+ if (pos == -1) path
+ else path.substring(pos + 1)
+ }
+}
+
+/** A virtual input file.
+ */
+trait VirtualTextFile extends VirtualFile {
+ /** Returns the content of the file. */
+ def content: String
+
+ /** Returns a new Reader of the file. */
+ def reader: Reader = new StringReader(content)
+
+ /** Returns the lines in the content.
+ * Lines do not contain the new line characters.
+ */
+ def readLines(): List[String] = IO.readLines(reader)
+}
+
+object VirtualTextFile {
+ def empty(path: String): VirtualTextFile =
+ new MemVirtualTextFile(path)
+}
+
+trait WritableVirtualTextFile extends VirtualTextFile {
+ def contentWriter: Writer
+}
+
+/** A virtual binary input file.
+ */
+trait VirtualBinaryFile extends VirtualFile {
+ /** Returns the content of the file. */
+ def content: Array[Byte]
+
+ /** Returns a new InputStream of the file. */
+ def inputStream: InputStream = new ByteArrayInputStream(content)
+}
+
+/** A virtual input file which contains JavaScript code.
+ * It may have a source map associated with it.
+ */
+trait VirtualJSFile extends VirtualTextFile {
+ /** Optionally, content of the source map file associated with this
+ * JavaScript source.
+ */
+ def sourceMap: Option[String] = None
+}
+
+object VirtualJSFile {
+ def empty(path: String): VirtualJSFile =
+ new MemVirtualJSFile(path).withVersion(Some(path))
+}
+
+trait WritableVirtualJSFile extends WritableVirtualTextFile with VirtualJSFile {
+ def sourceMapWriter: Writer
+}
+
+/** A virtual Scala.js IR file.
+ * It contains the class info and the IR tree.
+ */
+trait VirtualScalaJSIRFile extends VirtualFile {
+ /** Rough class info of this file. */
+ def roughInfo: ir.Infos.RoughClassInfo = info
+
+ /** Class info of this file. */
+ def info: ir.Infos.ClassInfo =
+ infoAndTree._1
+
+ /** IR Tree of this file. */
+ def tree: ir.Trees.ClassDef =
+ infoAndTree._2
+
+ /** Class info and IR tree of this file. */
+ def infoAndTree: (ir.Infos.ClassInfo, ir.Trees.ClassDef)
+}
+
+/** Base trait for virtual Scala.js IR files that are serialized as binary file.
+ */
+trait VirtualSerializedScalaJSIRFile extends VirtualBinaryFile with VirtualScalaJSIRFile {
+ /** Rough class info of this file. */
+ override def roughInfo: ir.Infos.RoughClassInfo = {
+ // Overridden to read only the necessary parts
+ val stream = inputStream
+ try {
+ ir.InfoSerializers.deserializeRoughInfo(stream)
+ } catch {
+ case e: IOException =>
+ throw new IOException(s"Failed to deserialize rough info of $path", e)
+ } finally {
+ stream.close()
+ }
+ }
+
+ /** Class info of this file. */
+ override def info: ir.Infos.ClassInfo = {
+ // Overridden to read only the necessary parts
+ val stream = inputStream
+ try {
+ ir.InfoSerializers.deserializeFullInfo(stream)
+ } catch {
+ case e: IOException =>
+ throw new IOException(s"Failed to deserialize info of $path", e)
+ } finally {
+ stream.close()
+ }
+ }
+
+ /** Class info and IR tree of this file. */
+ override def infoAndTree: (ir.Infos.ClassInfo, ir.Trees.ClassDef) = {
+ val stream = inputStream
+ try {
+ val (version, info) = ir.InfoSerializers.deserializeVersionFullInfo(stream)
+ val tree = ir.Serializers.deserialize(
+ stream, version).asInstanceOf[ir.Trees.ClassDef]
+ (info, tree)
+ } catch {
+ case e: IOException =>
+ throw new IOException(s"Failed to deserialize $path", e)
+ } finally {
+ stream.close()
+ }
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala
new file mode 100644
index 0000000..b4d4005
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/JSDesugaring.scala
@@ -0,0 +1,1525 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.javascript
+
+import scala.language.implicitConversions
+
+import scala.annotation.switch
+
+import scala.scalajs.ir._
+import Position._
+import Transformers._
+import scala.scalajs.ir.Trees._
+import scala.scalajs.ir.Types._
+
+import scala.scalajs.tools.sem._
+import CheckedBehavior._
+
+import scala.scalajs.tools.javascript.{Trees => js}
+
+/** Desugaring of the IR to regular ES5 JavaScript.
+ *
+ * The major difference between the IR and JS is that most constructs can be
+ * used in expression position. The main work of the desugaring is to
+ * unnest complex constructs in expression position so that they become
+ * statements.
+ *
+ * The general idea is two-folded:
+ * 1) Unnest complex constructs in "argument position":
+ * When a complex construct is used in a non-rhs expression position
+ * (argument to a function, operand, condition of an if, etc.), that we
+ * call "argument position", declare a variable before the statement,
+ * assign the complex construct to it and then use that variable in the
+ * argument position instead.
+ * 2) Push LHS's inside complex RHS's:
+ * When an rhs is a complex construct, push the lhs inside the complex
+ * construct. Are considered lhs:
+ * * Assign, i.e., `x =`
+ * * VarDef, i.e., `var x =`
+ * * Return, i.e., `return`
+ * * (EmptyTree is also used as a trick for code reuse)
+ * In fact, think that, in this context, LHS means: what to do with the
+ * result of evaluating the RHS.
+ *
+ * --------------------------------------------------------------------------
+ *
+ * Typical example, consider the method call:
+ *
+ * obj.meth({
+ * var x = foo(42);
+ * x*x
+ * });
+ *
+ * According to rule 1), the block that is passed as a parameter to obj.meth
+ * is first extracted in a synthetic var:
+ *
+ * var x\$1 = {
+ * var x = foo(42);
+ * x*x
+ * }
+ * obj.meth(x\$1);
+ *
+ * Then, according to rule 2), the lhs `var x\$1 =` is pushed inside the block:
+ *
+ * {
+ * var x = foo(42);
+ * var x\$1 = x*x;
+ * }
+ * obj.meth(x\$1);
+ *
+ * Because bare blocks are non-significant in JS, this is equivalent to
+ *
+ * var x = foo(42);
+ * var x\$1 = x*x;
+ * obj.meth(x\$1);
+ *
+ * --------------------------------------------------------------------------
+ *
+ * JSDesugaring does all this in a single pass, but it helps to think that:
+ * * Rule 1) is implemented by unnest(), and used most notably in
+ * * transformStat() for statement-only constructs
+ * * pushLhsInto() for statement-or-expression constructs
+ * * Rule 2) is implemented by pushLhsInto()
+ * * Emitting the class structure is delegated to [[ScalaJSClassEmitter]].
+ *
+ * There are a few other things that JSDesugaring takes care of:
+ * * Transform Scala expressions into their JS equivalent, taking the
+ * Scala.js class encoding into account.
+ * * And tiny details.
+ *
+ * @author Sébastien Doeraene
+ */
+object JSDesugaring {
+
+ private final val ScalaJSEnvironmentName = "ScalaJS"
+
+ /** Desugars a statement of the IR into ES5 JavaScript. */
+ def desugarJavaScript(tree: Tree, semantics: Semantics): js.Tree = {
+ new JSDesugar(semantics).transformStat(tree)
+ }
+
+ private[javascript] implicit def transformIdent(ident: Ident): js.Ident =
+ js.Ident(ident.name, ident.originalName)(ident.pos)
+
+ private[javascript] def transformParamDef(paramDef: ParamDef): js.ParamDef =
+ js.ParamDef(paramDef.name, paramDef.mutable)(paramDef.pos)
+
+ private class JSDesugar(semantics: Semantics) {
+
+ // Synthetic variables
+
+ var syntheticVarCounter: Int = 0
+
+ def newSyntheticVar()(implicit pos: Position): Ident = {
+ syntheticVarCounter += 1
+ Ident("jsx$" + syntheticVarCounter, None)
+ }
+
+ def resetSyntheticVarCounterIn[A](f: => A): A = {
+ val savedCounter = syntheticVarCounter
+ syntheticVarCounter = 0
+ try f
+ finally syntheticVarCounter = savedCounter
+ }
+
+ // Record names
+
+ def makeRecordFieldIdent(recIdent: Ident, fieldIdent: Ident)(
+ implicit pos: Position): Ident =
+ makeRecordFieldIdent(recIdent.name, recIdent.originalName,
+ fieldIdent.name, fieldIdent.originalName)
+
+ def makeRecordFieldIdent(recIdent: Ident,
+ fieldName: String, fieldOrigiName: Option[String])(
+ implicit pos: Position): Ident =
+ makeRecordFieldIdent(recIdent.name, recIdent.originalName,
+ fieldName, fieldOrigiName)
+
+ def makeRecordFieldIdent(recName: String, recOrigName: Option[String],
+ fieldName: String, fieldOrigName: Option[String])(
+ implicit pos: Position): Ident = {
+ val name = recName + "_$_" + fieldName
+ val originalName = Some(recOrigName.getOrElse(recName) + "." +
+ fieldOrigName.getOrElse(fieldName))
+ Ident(name, originalName)
+ }
+
+ // LHS'es for labeled expressions
+
+ var labeledExprLHSes: Map[Ident, Tree] = Map.empty
+
+ // Now the work
+
+ /** Desugar a statement of the IR into ES5 JS */
+ def transformStat(tree: Tree): js.Tree = {
+ implicit val pos = tree.pos
+
+ tree match {
+ // Statement-only language constructs
+
+ case Skip() =>
+ js.Skip()
+
+ case VarDef(varIdent, RecordType(fields), recMutable, EmptyTree) =>
+ js.Block(for {
+ RecordType.Field(fieldName, fieldOrigName, tpe, fieldMutable) <- fields
+ } yield {
+ transformStat {
+ VarDef(makeRecordFieldIdent(varIdent, fieldName, fieldOrigName),
+ tpe, recMutable || fieldMutable, EmptyTree)
+ }
+ })
+
+ case VarDef(name, _, mutable, EmptyTree) =>
+ js.VarDef(name, mutable, js.EmptyTree)
+
+ case VarDef(_, _, _, rhs) =>
+ pushLhsInto(tree, rhs)
+
+ case Assign(RecordFieldVarRef(lhs), rhs) =>
+ pushLhsInto(Assign(lhs, EmptyTree), rhs)
+
+ case Assign(select @ Select(qualifier, item, mutable), rhs) =>
+ unnest(qualifier, rhs) { (newQualifier, newRhs) =>
+ js.Assign(
+ js.DotSelect(transformExpr(newQualifier), item)(select.pos),
+ transformExpr(newRhs))
+ }
+
+ case Assign(select @ ArraySelect(array, index), rhs) =>
+ unnest(List(array, index, rhs)) {
+ case List(newArray, newIndex, newRhs) =>
+ js.Assign(
+ js.BracketSelect(js.DotSelect(transformExpr(newArray),
+ js.Ident("u"))(select.pos),
+ transformExpr(newIndex))(select.pos),
+ transformExpr(newRhs))
+ }
+
+ case Assign(select @ JSDotSelect(qualifier, item), rhs) =>
+ unnest(qualifier, rhs) { (newQualifier, newRhs) =>
+ js.Assign(
+ js.DotSelect(transformExpr(newQualifier), item)(select.pos),
+ transformExpr(newRhs))
+ }
+
+ case Assign(select @ JSBracketSelect(qualifier, item), rhs) =>
+ unnest(List(qualifier, item, rhs)) {
+ case List(newQualifier, newItem, newRhs) =>
+ js.Assign(
+ js.BracketSelect(transformExpr(newQualifier),
+ transformExpr(newItem))(select.pos),
+ transformExpr(newRhs))
+ }
+
+ case Assign(_ : VarRef, rhs) =>
+ pushLhsInto(tree, rhs)
+
+ case Assign(_, _) =>
+ sys.error(s"Illegal Assign in transformStat: $tree")
+
+ case StoreModule(cls, value) =>
+ assert(cls.className.endsWith("$"),
+ s"Trying to store module for non-module class $cls")
+ val moduleName = cls.className.dropRight(1)
+ unnest(value) { newValue =>
+ js.Assign(
+ js.DotSelect(envField("n"), Ident(moduleName)),
+ transformExpr(newValue))
+ }
+
+ case While(cond, body, label) =>
+ /* We cannot simply unnest(cond) here, because that would eject the
+ * evaluation of the condition out of the loop.
+ */
+ val newLabel = label.map(transformIdent)
+ if (isExpression(cond)) {
+ js.While(transformExpr(cond), transformStat(body), newLabel)
+ } else {
+ js.While(js.BooleanLiteral(true), {
+ unnest(cond) { newCond =>
+ js.If(transformExpr(newCond), transformStat(body), js.Break())
+ }
+ }, newLabel)
+ }
+
+ case DoWhile(body, cond, label) =>
+ /* We cannot simply unnest(cond) here, because that would eject the
+ * evaluation of the condition out of the loop.
+ */
+ val newLabel = label.map(transformIdent)
+ if (isExpression(cond)) {
+ js.DoWhile(transformStat(body), transformExpr(cond), newLabel)
+ } else {
+ /* This breaks 'continue' statements for this loop, but we don't
+ * care because we never emit continue statements for do..while
+ * loops.
+ */
+ js.While(js.BooleanLiteral(true), {
+ js.Block(transformStat(body), {
+ unnest(cond) { newCond =>
+ js.If(transformExpr(newCond), js.Skip(), js.Break())
+ }
+ })
+ }, newLabel)
+ }
+
+ case Debugger() =>
+ js.Debugger()
+
+ case JSDelete(JSDotSelect(obj, prop)) =>
+ unnest(obj) { (newObj) =>
+ js.Delete(js.DotSelect(transformExpr(newObj), prop))
+ }
+
+ case JSDelete(JSBracketSelect(obj, prop)) =>
+ unnest(obj, prop) { (newObj, newProp) =>
+ js.Delete(js.BracketSelect(
+ transformExpr(newObj), transformExpr(newProp)))
+ }
+
+ // Treat 'return' as an LHS
+
+ case Return(expr, label) =>
+ pushLhsInto(tree, expr)
+
+ /* Anything else is an expression => pushLhsInto(EmptyTree, _)
+ * In order not to duplicate all the code of pushLhsInto() here, we
+ * use a trick: EmptyTree is a dummy LHS that says "do nothing
+ * with the result of the rhs".
+ * This is exactly what an expression statement is doing: it evaluates
+ * the expression, but does nothing with its result.
+ */
+
+ case _ =>
+ pushLhsInto(EmptyTree, tree)
+ }
+ }
+
+ private object RecordFieldVarRef {
+ def unapply(tree: Tree): Option[VarRef] = {
+ tree match {
+ case Select(RecordVarRef(VarRef(recIdent, recMutable)),
+ fieldIdent, fieldMutable) =>
+ implicit val pos = tree.pos
+ Some(VarRef(makeRecordFieldIdent(recIdent, fieldIdent),
+ recMutable || fieldMutable)(tree.tpe))
+ case _ =>
+ None
+ }
+ }
+ }
+
+ private object RecordVarRef {
+ def unapply(tree: Tree): Option[VarRef] = {
+ if (!tree.tpe.isInstanceOf[RecordType]) None
+ else {
+ tree match {
+ case tree: VarRef => Some(tree)
+ case Select(RecordVarRef(VarRef(recIdent, recMutable)),
+ fieldIdent, fieldMutable) =>
+ implicit val pos = tree.pos
+ Some(VarRef(makeRecordFieldIdent(recIdent, fieldIdent),
+ recMutable || fieldMutable)(tree.tpe))
+ }
+ }
+ }
+ }
+
+ /** Unnest complex constructs in argument position in temporary variables
+ *
+ * If all the arguments are JS expressions, there is nothing to do.
+ * Any argument that is not a JS expression must be unnested and stored
+ * in a temporary variable before the statement produced by `makeStat`.
+ *
+ * But *this changes the evaluation order!* In order not to lose it, it
+ * is necessary to also unnest arguments that are expressions but that
+ * are supposed to be evaluated before the argument-to-be-unnested and
+ * could have side-effects or even whose evaluation could be influenced
+ * by the side-effects of another unnested argument.
+ *
+ * Without deep effect analysis, which we do not do, we need to take
+ * a very pessimistic approach, and unnest any expression that contains
+ * an identifier (except those after the last non-expression argument).
+ * Hence the predicate `isPureExpressionWithoutIdent`.
+ */
+ def unnest(args: List[Tree])(
+ makeStat: List[Tree] => js.Tree): js.Tree = {
+ if (args forall isExpression) makeStat(args)
+ else {
+ val extractedStatements = new scala.collection.mutable.ListBuffer[js.Tree]
+
+ /* Attention! Everything must be processed recursively
+ * *right-to-left*! Indeed, the point is that noExtractYet will tell
+ * whether anything supposed to be evaluated *after* the currently
+ * being processed expression has been (at least partly) extracted
+ * in temporary variables (or simply statements, in the Block case).
+ * If nothing has, we can keep more in place without having to extract
+ * that expression in a temporary variable.
+ */
+
+ def rec(arg: Tree): Tree = {
+ def noExtractYet = extractedStatements.isEmpty
+
+ if (if (noExtractYet) isExpression(arg) else isPureExpression(arg)) {
+ arg
+ } else {
+ implicit val pos = arg.pos
+ arg match {
+ case Block(stats :+ expr) =>
+ val result = rec(expr) // right-to-left, remember?
+ // Put the stats in a Block because ++=: is not smart
+ js.Block(stats.map(transformStat)) +=: extractedStatements
+ result
+
+ case UnaryOp(op, lhs) =>
+ UnaryOp(op, rec(lhs))
+ case BinaryOp(op, lhs, rhs) =>
+ val newRhs = rec(rhs)
+ BinaryOp(op, rec(lhs), newRhs)
+ case JSBinaryOp(op, lhs, rhs) =>
+ val newRhs = rec(rhs)
+ JSBinaryOp(op, rec(lhs), newRhs)
+ case JSUnaryOp(op, lhs) =>
+ JSUnaryOp(op, rec(lhs))
+ case IsInstanceOf(expr, tpe) =>
+ IsInstanceOf(rec(expr), tpe)
+
+ case AsInstanceOf(expr, tpe)
+ if noExtractYet || semantics.asInstanceOfs == Unchecked =>
+ AsInstanceOf(rec(expr), tpe)
+ case Unbox(expr, tpe)
+ if noExtractYet || semantics.asInstanceOfs == Unchecked =>
+ Unbox(rec(expr), tpe)
+
+ case NewArray(tpe, lengths) =>
+ NewArray(tpe, recs(lengths))
+ case ArrayValue(tpe, elems) =>
+ ArrayValue(tpe, recs(elems))
+ case JSArrayConstr(items) =>
+ JSArrayConstr(recs(items))
+ case JSObjectConstr(items) =>
+ val newValues = recs(items.map(_._2))
+ JSObjectConstr(items.map(_._1) zip newValues)
+ case Closure(captureParams, params, body, captureValues) =>
+ Closure(captureParams, params, body, recs(captureValues))
+
+ case New(cls, constr, args) if noExtractYet =>
+ New(cls, constr, recs(args))
+ case Select(qualifier, item, mutable) if noExtractYet =>
+ Select(rec(qualifier), item, mutable)(arg.tpe)
+ case Apply(receiver, method, args) if noExtractYet =>
+ val newArgs = recs(args)
+ Apply(rec(receiver), method, newArgs)(arg.tpe)
+ case StaticApply(receiver, cls, method, args) if noExtractYet =>
+ val newArgs = recs(args)
+ StaticApply(rec(receiver), cls, method, newArgs)(arg.tpe)
+ case TraitImplApply(impl, method, args) if noExtractYet =>
+ TraitImplApply(impl, method, recs(args))(arg.tpe)
+ case ArrayLength(array) if noExtractYet =>
+ ArrayLength(rec(array))
+ case ArraySelect(array, index) if noExtractYet =>
+ val newIndex = rec(index)
+ ArraySelect(rec(array), newIndex)(arg.tpe)
+ case CallHelper(helper, args) if noExtractYet =>
+ CallHelper(helper, recs(args))(arg.tpe)
+
+ case If(cond, thenp, elsep)
+ if noExtractYet && isExpression(thenp) && isExpression(elsep) =>
+ If(rec(cond), thenp, elsep)(arg.tpe)
+
+ case _ =>
+ val temp = newSyntheticVar()
+ val computeTemp =
+ pushLhsInto(VarDef(temp, arg.tpe, mutable = false, EmptyTree), arg)
+ computeTemp +=: extractedStatements
+ VarRef(temp, mutable = false)(arg.tpe)
+ }
+ }
+ }
+
+ def recs(args: List[Tree]): List[Tree] = {
+ // This is a right-to-left map
+ args.foldRight[List[Tree]](Nil) { (arg, acc) =>
+ rec(arg) :: acc
+ }
+ }
+
+ val newArgs = recs(args)
+
+ assert(extractedStatements.nonEmpty,
+ "Reached computeTemps with no temp to compute")
+
+ val newStatement = makeStat(newArgs)
+ js.Block(extractedStatements.result() ::: List(newStatement))(newStatement.pos)
+ }
+ }
+
+ /** Same as above, for a single argument */
+ def unnest(arg: Tree)(
+ makeStat: Tree => js.Tree): js.Tree = {
+ unnest(List(arg)) {
+ case List(newArg) => makeStat(newArg)
+ }
+ }
+
+ /** Same as above, for two arguments */
+ def unnest(lhs: Tree, rhs: Tree)(
+ makeStat: (Tree, Tree) => js.Tree): js.Tree = {
+ unnest(List(lhs, rhs)) {
+ case List(newLhs, newRhs) => makeStat(newLhs, newRhs)
+ }
+ }
+
+ /** Same as above, for one head argument and a list of arguments */
+ def unnest(arg0: Tree, args: List[Tree])(
+ makeStat: (Tree, List[Tree]) => js.Tree): js.Tree = {
+ unnest(arg0 :: args) { newArgs =>
+ makeStat(newArgs.head, newArgs.tail)
+ }
+ }
+
+ /** Common implementation for the functions below.
+ * A pure expression can be moved around or executed twice, because it
+ * will always produce the same result and never have side-effects.
+ * A side-effect free expression can be elided if its result is not used.
+ */
+ private def isExpressionInternal(tree: Tree,
+ allowUnpure: Boolean, allowSideEffects: Boolean): Boolean = {
+
+ require(!allowSideEffects || allowUnpure)
+
+ def test(tree: Tree): Boolean = tree match {
+ // Atomic expressions
+ case _: Literal => true
+ case _: This => true
+ case _: JSEnvInfo => true
+
+ // Vars and fields (side-effect free, pure if immutable)
+ case VarRef(_, mutable) =>
+ allowUnpure || !mutable
+ case Select(qualifier, item, mutable) =>
+ (allowUnpure || !mutable) && test(qualifier)
+
+ // Expressions preserving pureness
+ case Block(trees) => trees forall test
+ case If(cond, thenp, elsep) => test(cond) && test(thenp) && test(elsep)
+ case BinaryOp(_, lhs, rhs) => test(lhs) && test(rhs)
+ case UnaryOp(_, lhs) => test(lhs)
+ case JSBinaryOp(_, lhs, rhs) => test(lhs) && test(rhs)
+ case JSUnaryOp(_, lhs) => test(lhs)
+ case ArrayLength(array) => test(array)
+ case IsInstanceOf(expr, _) => test(expr)
+
+ // Expressions preserving side-effect freedom
+ case NewArray(tpe, lengths) =>
+ allowUnpure && (lengths forall test)
+ case ArrayValue(tpe, elems) =>
+ allowUnpure && (elems forall test)
+ case ArraySelect(array, index) =>
+ allowUnpure && test(array) && test(index)
+ case JSArrayConstr(items) =>
+ allowUnpure && (items forall test)
+ case JSObjectConstr(items) =>
+ allowUnpure && (items forall (item => test(item._2)))
+ case Closure(captureParams, params, body, captureValues) =>
+ allowUnpure && (captureValues forall test)
+
+ // Scala expressions that can always have side-effects
+ case New(cls, constr, args) =>
+ allowSideEffects && (args forall test)
+ case LoadModule(cls) => // unfortunately
+ allowSideEffects
+ case Apply(receiver, method, args) =>
+ allowSideEffects && test(receiver) && (args forall test)
+ case StaticApply(receiver, cls, method, args) =>
+ allowSideEffects && test(receiver) && (args forall test)
+ case TraitImplApply(impl, method, args) =>
+ allowSideEffects && (args forall test)
+ case GetClass(arg) =>
+ allowSideEffects && test(arg)
+ case CallHelper(helper, args) =>
+ allowSideEffects && (args forall test)
+
+ // Casts
+ case AsInstanceOf(expr, _) =>
+ (allowSideEffects || semantics.asInstanceOfs == Unchecked) && test(expr)
+ case Unbox(expr, _) =>
+ (allowSideEffects || semantics.asInstanceOfs == Unchecked) && test(expr)
+
+ // Because the env is a frozen object, env["global"] is pure
+ case JSBracketSelect(JSEnvInfo(), StringLiteral("global")) => true
+
+ // JavaScript expressions that can always have side-effects
+ case JSNew(fun, args) =>
+ allowSideEffects && test(fun) && (args forall test)
+ case JSDotSelect(qualifier, item) =>
+ allowSideEffects && test(qualifier)
+ case JSBracketSelect(qualifier, item) =>
+ allowSideEffects && test(qualifier) && test(item)
+ case JSFunctionApply(fun, args) =>
+ allowSideEffects && test(fun) && (args forall test)
+ case JSDotMethodApply(receiver, method, args) =>
+ allowSideEffects && test(receiver) && (args forall test)
+ case JSBracketMethodApply(receiver, method, args) =>
+ allowSideEffects && test(receiver) && test(method) && (args forall test)
+
+ // Non-expressions
+ case _ => false
+ }
+ test(tree)
+ }
+
+ /** Test whether the given tree is a standard JS expression.
+ */
+ def isExpression(tree: Tree): Boolean =
+ isExpressionInternal(tree, allowUnpure = true, allowSideEffects = true)
+
+ /** Test whether the given tree is a side-effect-free standard JS expression.
+ */
+ def isSideEffectFreeExpression(tree: Tree): Boolean =
+ isExpressionInternal(tree, allowUnpure = true, allowSideEffects = false)
+
+ /** Test whether the given tree is a pure standard JS expression.
+ */
+ def isPureExpression(tree: Tree): Boolean =
+ isExpressionInternal(tree, allowUnpure = false, allowSideEffects = false)
+
+ def doVarDef(ident: Ident, tpe: Type, mutable: Boolean, rhs: Tree): js.Tree = {
+ implicit val pos = rhs.pos
+ tpe match {
+ case RecordType(fields) =>
+ val elems = (rhs: @unchecked) match {
+ case RecordValue(_, elems) =>
+ elems
+ case VarRef(rhsIdent, rhsMutable) =>
+ for (RecordType.Field(fName, fOrigName, fTpe, fMutable) <- fields)
+ yield VarRef(makeRecordFieldIdent(rhsIdent, fName, fOrigName),
+ rhsMutable || fMutable)(fTpe)
+ }
+ js.Block(for {
+ (RecordType.Field(fName, fOrigName, fTpe, fMutable),
+ fRhs) <- fields zip elems
+ } yield {
+ doVarDef(makeRecordFieldIdent(ident, fName, fOrigName),
+ fTpe, mutable || fMutable, fRhs)
+ })
+
+ case _ =>
+ js.VarDef(ident, mutable, transformExpr(rhs))
+ }
+ }
+
+ def doAssign(lhs: Tree, rhs: Tree): js.Tree = {
+ implicit val pos = rhs.pos
+ lhs.tpe match {
+ case RecordType(fields) =>
+ val VarRef(ident, mutable) = lhs
+ val elems = (rhs: @unchecked) match {
+ case VarRef(rhsIdent, rhsMutable) =>
+ for (RecordType.Field(fName, fOrigName, fTpe, fMutable) <- fields)
+ yield VarRef(makeRecordFieldIdent(rhsIdent, fName, fOrigName),
+ rhsMutable || fMutable)(fTpe)
+ }
+ js.Block(for {
+ (RecordType.Field(fName, fOrigName, fTpe, fMutable),
+ fRhs) <- fields zip elems
+ } yield {
+ doAssign(VarRef(makeRecordFieldIdent(ident, fName, fOrigName),
+ mutable || fMutable)(fTpe), fRhs)
+ })
+
+ case _ =>
+ js.Assign(transformExpr(lhs), transformExpr(rhs))
+ }
+ }
+
+ /** Push an lhs into a (potentially complex) rhs
+ * lhs can be either a EmptyTree, a VarDef, a Assign or a
+ * Return
+ */
+ def pushLhsInto(lhs: Tree, rhs: Tree): js.Tree = {
+ implicit val rhsPos = rhs.pos
+
+ /** Push the current lhs further into a deeper rhs */
+ @inline def redo(newRhs: Tree) = pushLhsInto(lhs, newRhs)
+
+ if (rhs.tpe == NothingType && lhs != EmptyTree) {
+ /* A touch of peephole dead code elimination.
+ * Actually necessary to handle pushing an lhs into an infinite loop,
+ * for example.
+ */
+ val transformedRhs = pushLhsInto(EmptyTree, rhs)
+ lhs match {
+ case VarDef(name, _, mutable, _) =>
+ /* We still need to declare the var, in case it is used somewhere
+ * else in the function, where we can't dce it.
+ */
+ js.Block(js.VarDef(name, true, js.EmptyTree), transformedRhs)
+ case _ =>
+ transformedRhs
+ }
+ } else (rhs match {
+ // Handle the Block before testing whether it is an expression
+
+ case Block(stats :+ expr) =>
+ js.Block((stats map transformStat) :+ redo(expr))
+
+ // Base case, rhs is already a regular JS expression
+
+ case _ if isExpression(rhs) =>
+ (lhs: @unchecked) match {
+ case EmptyTree =>
+ if (isSideEffectFreeExpression(rhs)) js.Skip()
+ else transformExpr(rhs)
+ case VarDef(name, tpe, mutable, _) =>
+ doVarDef(name, tpe, mutable, rhs)
+ case Assign(lhs, _) =>
+ doAssign(lhs, rhs)
+ case Return(_, None) =>
+ js.Return(transformExpr(rhs))
+ case Return(_, label @ Some(l)) =>
+ labeledExprLHSes(l) match {
+ case newLhs @ Return(_, _) =>
+ pushLhsInto(newLhs, rhs) // no need to break here
+ case newLhs =>
+ js.Block(pushLhsInto(newLhs, rhs),
+ js.Break(label.map(transformIdent)))
+ }
+ }
+
+ // Almost base case with RecordValue
+
+ case RecordValue(recTpe, elems) =>
+ (lhs: @unchecked) match {
+ case EmptyTree =>
+ js.Block(elems map transformStat)
+ case VarDef(name, tpe, mutable, _) =>
+ unnest(elems) { newElems =>
+ doVarDef(name, tpe, mutable, RecordValue(recTpe, newElems))
+ }
+ case Assign(lhs, _) =>
+ unnest(elems) { newElems =>
+ val temp = newSyntheticVar()
+ js.Block(
+ doVarDef(temp, recTpe, false, RecordValue(recTpe, newElems)),
+ doAssign(lhs, VarRef(temp, false)(recTpe)))
+ }
+ case Return(_, label @ Some(l)) =>
+ val newLhs = labeledExprLHSes(l)
+ js.Block(pushLhsInto(newLhs, rhs),
+ js.Break(label.map(transformIdent)))
+ }
+
+ // Control flow constructs
+
+ case Labeled(label, tpe, body) =>
+ val savedMap = labeledExprLHSes
+ labeledExprLHSes = labeledExprLHSes + (label -> lhs)
+ try {
+ lhs match {
+ case Return(_, _) => redo(body)
+ case _ => js.Labeled(label, redo(body))
+ }
+ } finally {
+ labeledExprLHSes = savedMap
+ }
+
+ case Return(expr, _) =>
+ pushLhsInto(rhs, expr)
+
+ case Continue(label) =>
+ js.Continue(label.map(transformIdent))
+
+ case If(cond, thenp, elsep) =>
+ unnest(cond) { newCond =>
+ js.If(transformExpr(newCond), redo(thenp), redo(elsep))
+ }
+
+ case Try(block, errVar, handler, finalizer) =>
+ val newHandler =
+ if (handler == EmptyTree) js.EmptyTree else redo(handler)
+ val newFinalizer =
+ if (finalizer == EmptyTree) js.EmptyTree else transformStat(finalizer)
+
+ if (newHandler != js.EmptyTree && newFinalizer != js.EmptyTree) {
+ /* The Google Closure Compiler wrongly eliminates finally blocks, if
+ * the catch block throws an exception.
+ * Issues: #563, google/closure-compiler#186
+ *
+ * Therefore, we desugar
+ *
+ * try { ... } catch { ... } finally { ... }
+ *
+ * into
+ *
+ * try { try { ... } catch { ... } } finally { ... }
+ */
+ js.Try(js.Try(redo(block), errVar, newHandler, js.EmptyTree),
+ errVar, js.EmptyTree, newFinalizer)
+ } else
+ js.Try(redo(block), errVar, newHandler, newFinalizer)
+
+ // TODO Treat throw as an LHS?
+ case Throw(expr) =>
+ unnest(expr) { newExpr =>
+ js.Throw(transformExpr(newExpr))
+ }
+
+ /** Matches are desugared into switches
+ *
+ * A match is different from a switch in two respects, both linked
+ * to match being designed to be used in expression position in
+ * Extended-JS.
+ *
+ * * There is no fall-through from one case to the next one, hence,
+ * no break statement.
+ * * Match supports _alternatives_ explicitly (with a switch, one
+ * would use the fall-through behavior to implement alternatives).
+ */
+ case Match(selector, cases, default) =>
+ unnest(selector) { newSelector =>
+ val newCases = {
+ for {
+ (values, body) <- cases
+ newValues = (values map transformExpr)
+ // add the break statement
+ newBody = js.Block(redo(body), js.Break())
+ // desugar alternatives into several cases falling through
+ caze <- (newValues.init map (v => (v, js.Skip()))) :+ (newValues.last, newBody)
+ } yield {
+ caze
+ }
+ }
+ val newDefault =
+ if (default == EmptyTree) js.EmptyTree
+ else redo(default)
+ js.Switch(transformExpr(newSelector), newCases, newDefault)
+ }
+
+ // Scala expressions (if we reach here their arguments are not expressions)
+
+ case New(cls, ctor, args) =>
+ unnest(args) { newArgs =>
+ redo(New(cls, ctor, newArgs))
+ }
+
+ case Select(qualifier, item, mutable) =>
+ unnest(qualifier) { newQualifier =>
+ redo(Select(newQualifier, item, mutable)(rhs.tpe))
+ }
+
+ case Apply(receiver, method, args) =>
+ unnest(receiver, args) { (newReceiver, newArgs) =>
+ redo(Apply(newReceiver, method, newArgs)(rhs.tpe))
+ }
+
+ case StaticApply(receiver, cls, method, args) =>
+ unnest(receiver, args) { (newReceiver, newArgs) =>
+ redo(StaticApply(newReceiver, cls, method, newArgs)(rhs.tpe))
+ }
+
+ case TraitImplApply(impl, method, args) =>
+ unnest(args) { newArgs =>
+ redo(TraitImplApply(impl, method, newArgs)(rhs.tpe))
+ }
+
+ case UnaryOp(op, lhs) =>
+ unnest(lhs) { newLhs =>
+ redo(UnaryOp(op, newLhs))
+ }
+
+ case BinaryOp(op, lhs, rhs) =>
+ unnest(lhs, rhs) { (newLhs, newRhs) =>
+ redo(BinaryOp(op, newLhs, newRhs))
+ }
+
+ case NewArray(tpe, lengths) =>
+ unnest(lengths) { newLengths =>
+ redo(NewArray(tpe, newLengths))
+ }
+
+ case ArrayValue(tpe, elems) =>
+ unnest(elems) { newElems =>
+ redo(ArrayValue(tpe, newElems))
+ }
+
+ case ArrayLength(array) =>
+ unnest(array) { newArray =>
+ redo(ArrayLength(newArray))
+ }
+
+ case ArraySelect(array, index) =>
+ unnest(array, index) { (newArray, newIndex) =>
+ redo(ArraySelect(newArray, newIndex)(rhs.tpe))
+ }
+
+ case IsInstanceOf(expr, cls) =>
+ unnest(expr) { newExpr =>
+ redo(IsInstanceOf(newExpr, cls))
+ }
+
+ case AsInstanceOf(expr, cls) =>
+ if (semantics.asInstanceOfs == Unchecked) {
+ redo(expr)
+ } else {
+ unnest(expr) { newExpr =>
+ redo(AsInstanceOf(newExpr, cls))
+ }
+ }
+
+ case Unbox(expr, charCode) =>
+ unnest(expr) { newExpr =>
+ redo(Unbox(newExpr, charCode))
+ }
+
+ case GetClass(expr) =>
+ unnest(expr) { newExpr =>
+ redo(GetClass(newExpr))
+ }
+
+ case CallHelper(helper, args) =>
+ unnest(args) { newArgs =>
+ redo(CallHelper(helper, newArgs)(rhs.tpe))
+ }
+
+ // JavaScript expressions (if we reach here their arguments are not expressions)
+
+ case JSNew(ctor, args) =>
+ unnest(ctor :: args) { newCtorAndArgs =>
+ val newCtor :: newArgs = newCtorAndArgs
+ redo(JSNew(newCtor, newArgs))
+ }
+
+ case JSFunctionApply(fun, args) =>
+ unnest(fun :: args) { newFunAndArgs =>
+ val newFun :: newArgs = newFunAndArgs
+ redo(JSFunctionApply(newFun, newArgs))
+ }
+
+ case JSDotMethodApply(receiver, method, args) =>
+ unnest(receiver :: args) { newReceiverAndArgs =>
+ val newReceiver :: newArgs = newReceiverAndArgs
+ redo(JSDotMethodApply(newReceiver, method, newArgs))
+ }
+
+ case JSBracketMethodApply(receiver, method, args) =>
+ unnest(receiver :: method :: args) { newReceiverAndArgs =>
+ val newReceiver :: newMethod :: newArgs = newReceiverAndArgs
+ redo(JSBracketMethodApply(newReceiver, newMethod, newArgs))
+ }
+
+ case JSDotSelect(qualifier, item) =>
+ unnest(qualifier) { newQualifier =>
+ redo(JSDotSelect(newQualifier, item))
+ }
+
+ case JSBracketSelect(qualifier, item) =>
+ unnest(qualifier, item) { (newQualifier, newItem) =>
+ redo(JSBracketSelect(newQualifier, newItem))
+ }
+
+ case JSUnaryOp(op, lhs) =>
+ unnest(lhs) { newLhs =>
+ redo(JSUnaryOp(op, newLhs))
+ }
+
+ case JSBinaryOp("&&", lhs, rhs) =>
+ if (lhs.tpe == BooleanType) {
+ redo(If(lhs, rhs, BooleanLiteral(false))(AnyType))
+ } else {
+ unnest(lhs) { newLhs =>
+ redo(If(newLhs, rhs, newLhs)(AnyType))
+ }
+ }
+
+ case JSBinaryOp("||", lhs, rhs) =>
+ if (lhs.tpe == BooleanType) {
+ redo(If(lhs, BooleanLiteral(true), rhs)(AnyType))
+ } else {
+ unnest(lhs) { newLhs =>
+ redo(If(newLhs, newLhs, rhs)(AnyType))
+ }
+ }
+
+ case JSBinaryOp(op, lhs, rhs) =>
+ unnest(lhs, rhs) { (newLhs, newRhs) =>
+ redo(JSBinaryOp(op, newLhs, newRhs))
+ }
+
+ case JSArrayConstr(items) =>
+ unnest(items) { newItems =>
+ redo(JSArrayConstr(newItems))
+ }
+
+ case JSObjectConstr(fields) =>
+ val names = fields map (_._1)
+ val items = fields map (_._2)
+ unnest(items) { newItems =>
+ redo(JSObjectConstr(names.zip(newItems)))
+ }
+
+ // Closures
+
+ case Closure(captureParams, params, body, captureValues) =>
+ unnest(captureValues) { newCaptureValues =>
+ redo(Closure(captureParams, params, body, newCaptureValues))
+ }
+
+ case _ =>
+ if (lhs == EmptyTree) {
+ /* Go "back" to transformStat() after having dived into
+ * expression statements. Remember that (lhs == EmptyTree)
+ * is a trick that we use to "add" all the code of pushLhsInto()
+ * to transformStat().
+ */
+ rhs match {
+ case _:Skip | _:VarDef | _:Assign | _:While | _:DoWhile |
+ _:Debugger | _:JSDelete | _:StoreModule | _:ClassDef =>
+ transformStat(rhs)
+ case _ =>
+ sys.error("Illegal tree in JSDesugar.pushLhsInto():\n" +
+ "lhs = " + lhs + "\n" + "rhs = " + rhs +
+ " of class " + rhs.getClass)
+ }
+ } else {
+ sys.error("Illegal tree in JSDesugar.pushLhsInto():\n" +
+ "lhs = " + lhs + "\n" + "rhs = " + rhs +
+ " of class " + rhs.getClass)
+ }
+ })
+ }
+
+ // Desugar Scala operations to JavaScript operations -----------------------
+
+ /** Desugar an expression of the IR into ES5 JS */
+ def transformExpr(tree: Tree): js.Tree = {
+ import TreeDSL._
+
+ implicit val pos = tree.pos
+
+ def or0(tree: js.Tree): js.Tree =
+ js.BinaryOp("|", tree, js.IntLiteral(0))
+
+ tree match {
+ // Control flow constructs
+
+ case Block(stats :+ expr) =>
+ js.Block((stats map transformStat) :+ transformExpr(expr))
+
+ // Note that these work even if thenp/elsep is not a BooleanType
+ case If(cond, BooleanLiteral(true), elsep) =>
+ js.BinaryOp("||", transformExpr(cond), transformExpr(elsep))
+ case If(cond, thenp, BooleanLiteral(false)) =>
+ js.BinaryOp("&&", transformExpr(cond), transformExpr(thenp))
+
+ case If(cond, thenp, elsep) =>
+ js.If(transformExpr(cond), transformExpr(thenp), transformExpr(elsep))
+
+ // Scala expressions
+
+ case New(cls, ctor, args) =>
+ js.Apply(js.New(encodeClassVar(cls.className), Nil) DOT ctor,
+ args map transformExpr)
+
+ case LoadModule(cls) =>
+ genLoadModule(cls.className)
+
+ case RecordFieldVarRef(VarRef(name, mutable)) =>
+ js.VarRef(name, mutable)
+
+ case Select(qualifier, item, _) =>
+ transformExpr(qualifier) DOT item
+
+ case Apply(receiver, method, args) =>
+ val newReceiver = transformExpr(receiver)
+ val newArgs = args map transformExpr
+ if (isMaybeHijackedClass(receiver.tpe) &&
+ !Definitions.isReflProxyName(method.name)) {
+ val helperName = hijackedClassMethodToHelperName(method.name)
+ genCallHelper(helperName, newReceiver :: newArgs: _*)
+ } else {
+ js.Apply(newReceiver DOT method, newArgs)
+ }
+
+ case StaticApply(receiver, cls, method, args) =>
+ val fun = encodeClassVar(cls.className).prototype DOT method
+ js.Apply(fun DOT "call", (receiver :: args) map transformExpr)
+
+ case TraitImplApply(impl, method, args) =>
+ js.Apply(envField("i") DOT method, args map transformExpr)
+
+ case UnaryOp(op, lhs) =>
+ import UnaryOp._
+ val newLhs = transformExpr(lhs)
+ (op: @switch) match {
+ case `typeof` => js.UnaryOp("typeof", newLhs)
+ case Boolean_! => js.UnaryOp("!", newLhs)
+ case DoubleToInt => js.BinaryOp("|", newLhs, js.IntLiteral(0))
+
+ case LongToInt => genLongMethodApply(newLhs, LongImpl.toInt)
+ case LongToDouble => genLongMethodApply(newLhs, LongImpl.toDouble)
+
+ case DoubleToFloat => genFround(newLhs)
+
+ case IntToLong =>
+ genNewLong(LongImpl.initFromInt, newLhs)
+ case DoubleToLong =>
+ genLongModuleApply(LongImpl.fromDouble, newLhs)
+ }
+
+ case BinaryOp(op, lhs, rhs) =>
+ import BinaryOp._
+ val lhs1 = lhs match {
+ case UnaryOp(UnaryOp.DoubleToInt, inner)
+ if op == Int_& || op == Int_<< =>
+ /* This case is emitted typically by conversions from
+ * Float/Double to Char/Byte/Short. We have to introduce an
+ * (int) cast in the IR so that it typechecks, but in JavaScript
+ * this is redundant because & and << already convert both their
+ * operands to ints. So we get rid of the conversion here.
+ */
+ inner
+ case _ =>
+ lhs
+ }
+
+ val newLhs = transformExpr(lhs1)
+ val newRhs = transformExpr(rhs)
+
+ (op: @switch) match {
+ case === | Num_== | Boolean_== => js.BinaryOp("===", newLhs, newRhs)
+ case !== | Num_!= | Boolean_!= => js.BinaryOp("!==", newLhs, newRhs)
+
+ case String_+ =>
+ if (lhs.tpe == StringType || rhs.tpe == StringType)
+ js.BinaryOp("+", newLhs, newRhs)
+ else
+ js.BinaryOp("+", js.BinaryOp("+", js.StringLiteral(""), newLhs), newRhs)
+
+ case `in` => js.BinaryOp("in", newLhs, newRhs)
+ case `instanceof` => js.BinaryOp("instanceof", newLhs, newRhs)
+
+ case Int_+ => or0(js.BinaryOp("+", newLhs, newRhs))
+ case Int_- =>
+ lhs match {
+ case IntLiteral(0) => or0(js.UnaryOp("-", newRhs))
+ case _ => or0(js.BinaryOp("-", newLhs, newRhs))
+ }
+ case Int_* => genCallHelper("imul", newLhs, newRhs)
+ case Int_/ => or0(js.BinaryOp("/", newLhs, newRhs))
+ case Int_% => js.BinaryOp("%", newLhs, newRhs)
+
+ case Int_| => js.BinaryOp("|", newLhs, newRhs)
+ case Int_& => js.BinaryOp("&", newLhs, newRhs)
+ case Int_^ =>
+ lhs match {
+ case IntLiteral(-1) => js.UnaryOp("~", newRhs)
+ case _ => js.BinaryOp("^", newLhs, newRhs)
+ }
+ case Int_<< => js.BinaryOp("<<", newLhs, newRhs)
+ case Int_>>> => or0(js.BinaryOp(">>>", newLhs, newRhs))
+ case Int_>> => js.BinaryOp(">>", newLhs, newRhs)
+
+ case Float_+ => genFround(js.BinaryOp("+", newLhs, newRhs))
+ case Float_- =>
+ genFround(lhs match {
+ case DoubleLiteral(0.0) => js.UnaryOp("-", newRhs)
+ case _ => js.BinaryOp("-", newLhs, newRhs)
+ })
+ case Float_* => genFround(js.BinaryOp("*", newLhs, newRhs))
+ case Float_/ => genFround(js.BinaryOp("/", newLhs, newRhs))
+ case Float_% => genFround(js.BinaryOp("%", newLhs, newRhs))
+
+ case Double_+ => js.BinaryOp("+", newLhs, newRhs)
+ case Double_- =>
+ lhs match {
+ case DoubleLiteral(0.0) => js.UnaryOp("-", newRhs)
+ case _ => js.BinaryOp("-", newLhs, newRhs)
+ }
+ case Double_* => js.BinaryOp("*", newLhs, newRhs)
+ case Double_/ => js.BinaryOp("/", newLhs, newRhs)
+ case Double_% => js.BinaryOp("%", newLhs, newRhs)
+
+ case Num_< => js.BinaryOp("<", newLhs, newRhs)
+ case Num_<= => js.BinaryOp("<=", newLhs, newRhs)
+ case Num_> => js.BinaryOp(">", newLhs, newRhs)
+ case Num_>= => js.BinaryOp(">=", newLhs, newRhs)
+
+ case Long_+ => genLongMethodApply(newLhs, LongImpl.+, newRhs)
+ case Long_- =>
+ lhs match {
+ case LongLiteral(0L) => genLongMethodApply(newRhs, LongImpl.UNARY_-)
+ case _ => genLongMethodApply(newLhs, LongImpl.-, newRhs)
+ }
+ case Long_* => genLongMethodApply(newLhs, LongImpl.*, newRhs)
+ case Long_/ => genLongMethodApply(newLhs, LongImpl./, newRhs)
+ case Long_% => genLongMethodApply(newLhs, LongImpl.%, newRhs)
+
+ case Long_| => genLongMethodApply(newLhs, LongImpl.|, newRhs)
+ case Long_& => genLongMethodApply(newLhs, LongImpl.&, newRhs)
+ case Long_^ =>
+ lhs match {
+ case LongLiteral(-1L) => genLongMethodApply(newRhs, LongImpl.UNARY_~)
+ case _ => genLongMethodApply(newLhs, LongImpl.^, newRhs)
+ }
+ case Long_<< => genLongMethodApply(newLhs, LongImpl.<<, newRhs)
+ case Long_>>> => genLongMethodApply(newLhs, LongImpl.>>>, newRhs)
+ case Long_>> => genLongMethodApply(newLhs, LongImpl.>>, newRhs)
+
+ case Long_== => genLongMethodApply(newLhs, LongImpl.===, newRhs)
+ case Long_!= => genLongMethodApply(newLhs, LongImpl.!==, newRhs)
+ case Long_< => genLongMethodApply(newLhs, LongImpl.<, newRhs)
+ case Long_<= => genLongMethodApply(newLhs, LongImpl.<=, newRhs)
+ case Long_> => genLongMethodApply(newLhs, LongImpl.>, newRhs)
+ case Long_>= => genLongMethodApply(newLhs, LongImpl.>=, newRhs)
+
+ case Boolean_| => !(!js.BinaryOp("|", newLhs, newRhs))
+ case Boolean_& => !(!js.BinaryOp("&", newLhs, newRhs))
+ }
+
+ case NewArray(tpe, lengths) =>
+ genCallHelper("newArrayObject",
+ genClassDataOf(tpe), js.ArrayConstr(lengths map transformExpr))
+
+ case ArrayValue(tpe, elems) =>
+ genCallHelper("makeNativeArrayWrapper",
+ genClassDataOf(tpe), js.ArrayConstr(elems map transformExpr))
+
+ case ArrayLength(array) =>
+ js.BracketSelect(js.DotSelect(transformExpr(array),
+ Ident("u")), js.StringLiteral("length"))
+
+ case ArraySelect(array, index) =>
+ js.BracketSelect(js.DotSelect(transformExpr(array),
+ Ident("u")), transformExpr(index))
+
+ case IsInstanceOf(expr, cls) =>
+ genIsInstanceOf(transformExpr(expr), cls)
+
+ case AsInstanceOf(expr, cls) =>
+ val newExpr = transformExpr(expr)
+ if (semantics.asInstanceOfs == Unchecked) newExpr
+ else genAsInstanceOf(newExpr, cls)
+
+ case Unbox(expr, charCode) =>
+ val newExpr = transformExpr(expr)
+
+ if (semantics.asInstanceOfs == Unchecked) {
+ (charCode: @switch) match {
+ case 'Z' => !(!newExpr)
+ case 'B' | 'S' | 'I' => js.BinaryOp("|", newExpr, js.IntLiteral(0))
+ case 'J' => genCallHelper("uJ", newExpr)
+ case 'F' => genFround(newExpr)
+ case 'D' => js.UnaryOp("+", newExpr)
+ }
+ } else {
+ genCallHelper("u"+charCode, newExpr)
+ }
+
+ case GetClass(expr) =>
+ genCallHelper("objectGetClass", transformExpr(expr))
+
+ case CallHelper(helper, args) =>
+ genCallHelper(helper, args map transformExpr: _*)
+
+ // JavaScript expressions
+
+ case JSBracketSelect(JSEnvInfo(), StringLiteral("global")) =>
+ // Shortcut for this field which is heavily used
+ envField("g")
+
+ case JSNew(constr, args) =>
+ js.New(transformExpr(constr), args map transformExpr)
+
+ case JSDotSelect(qualifier, item) =>
+ js.DotSelect(transformExpr(qualifier), item)
+
+ case JSBracketSelect(qualifier, item) =>
+ js.BracketSelect(transformExpr(qualifier), transformExpr(item))
+
+ case JSFunctionApply(fun, args) =>
+ /* Protect the fun so that if it is, e.g.,
+ * path.f
+ * we emit
+ * (0, path.f)(args...)
+ * instead of
+ * path.f(args...)
+ * If we emit the latter, then `this` will be bound to `path` in
+ * `f`, which is sometimes extremely harmful (e.g., for builtin
+ * methods of `window`).
+ */
+ val transformedFun = transformExpr(fun)
+ val protectedFun = transformedFun match {
+ case _:js.DotSelect | _:js.BracketSelect =>
+ js.Block(js.IntLiteral(0), transformedFun)
+ case _ =>
+ transformedFun
+ }
+ js.Apply(protectedFun, args map transformExpr)
+
+ case JSDotMethodApply(receiver, method, args) =>
+ js.Apply(js.DotSelect(transformExpr(receiver), method),
+ args map transformExpr)
+
+ case JSBracketMethodApply(receiver, method, args) =>
+ js.Apply(js.BracketSelect(transformExpr(receiver),
+ transformExpr(method)), args map transformExpr)
+
+ case JSUnaryOp(op, lhs) =>
+ js.UnaryOp(op, transformExpr(lhs))
+
+ case JSBinaryOp(op, lhs, rhs) =>
+ js.BinaryOp(op, transformExpr(lhs), transformExpr(rhs))
+
+ case JSArrayConstr(items) =>
+ js.ArrayConstr(items map transformExpr)
+
+ case JSObjectConstr(fields) =>
+ js.ObjectConstr(fields map {
+ case (name: Ident, value) =>
+ (transformIdent(name), transformExpr(value))
+ case (StringLiteral(name), value) =>
+ (js.StringLiteral(name), transformExpr(value))
+ })
+
+ case JSEnvInfo() =>
+ envField("env")
+
+ // Literals
+
+ case Undefined() => js.Undefined()
+ case Null() => js.Null()
+ case BooleanLiteral(value) => js.BooleanLiteral(value)
+ case IntLiteral(value) => js.IntLiteral(value)
+ case FloatLiteral(value) => js.DoubleLiteral(value.toDouble)
+ case DoubleLiteral(value) => js.DoubleLiteral(value)
+ case StringLiteral(value) => js.StringLiteral(value)
+
+ case LongLiteral(0L) =>
+ genLongModuleApply(LongImpl.Zero)
+ case LongLiteral(value) =>
+ val (l, m, h) = LongImpl.extractParts(value)
+ genNewLong(LongImpl.initFromParts,
+ js.IntLiteral(l), js.IntLiteral(m), js.IntLiteral(h))
+
+ case ClassOf(cls) =>
+ js.Apply(js.DotSelect(genClassDataOf(cls), Ident("getClassOf")), Nil)
+
+ // Atomic expressions
+
+ case VarRef(name, mutable) =>
+ js.VarRef(name, mutable)
+
+ case This() =>
+ js.This()
+
+ case Closure(captureParams, params, body, captureValues) =>
+ val transformedBody = {
+ val withReturn = Return(body, None)
+ transformStat(withReturn) match {
+ case js.Block(stats :+ js.Return(js.Undefined())) => js.Block(stats)
+ case other => other
+ }
+ }
+
+ val innerFunction =
+ js.Function(params.map(transformParamDef), transformedBody)
+
+ if (captureParams.isEmpty) {
+ innerFunction
+ } else {
+ js.Apply(
+ js.Function(captureParams.map(transformParamDef), {
+ js.Return(innerFunction)
+ }),
+ captureValues.map(transformExpr))
+ }
+
+ // Invalid trees
+
+ case _ =>
+ sys.error("Invalid tree in JSDesugar.transformExpr() "+
+ s"of class ${tree.getClass}")
+ }
+ }
+
+ def isMaybeHijackedClass(tpe: Type): Boolean = tpe match {
+ case ClassType(cls) =>
+ Definitions.HijackedClasses.contains(cls) ||
+ Definitions.AncestorsOfHijackedClasses.contains(cls)
+ case AnyType | UndefType | BooleanType | IntType | LongType |
+ FloatType | DoubleType | StringType =>
+ true
+ case _ =>
+ false
+ }
+
+ val hijackedClassMethodToHelperName: Map[String, String] = Map(
+ "toString__T" -> "objectToString",
+ "clone__O" -> "objectClone",
+ "finalize__V" -> "objectFinalize",
+ "notify__V" -> "objectNotify",
+ "notifyAll__V" -> "objectNotifyAll",
+ "equals__O__Z" -> "objectEquals",
+ "hashCode__I" -> "objectHashCode",
+
+ "length__I" -> "charSequenceLength",
+ "charAt__I__C" -> "charSequenceCharAt",
+ "subSequence__I__I__jl_CharSequence" -> "charSequenceSubSequence",
+
+ "compareTo__O__I" -> "comparableCompareTo",
+ "compareTo__jl_Boolean__I" -> "comparableCompareTo",
+ "compareTo__jl_Byte__I" -> "comparableCompareTo",
+ "compareTo__jl_Short__I" -> "comparableCompareTo",
+ "compareTo__jl_Integer__I" -> "comparableCompareTo",
+ "compareTo__jl_Long__I" -> "comparableCompareTo",
+ "compareTo__jl_Float__I" -> "comparableCompareTo",
+ "compareTo__jl_Double__I" -> "comparableCompareTo",
+ "compareTo__jl_String__I" -> "comparableCompareTo",
+
+ "booleanValue__Z" -> "booleanBooleanValue",
+
+ "byteValue__B" -> "numberByteValue",
+ "shortValue__S" -> "numberShortValue",
+ "intValue__I" -> "numberIntValue",
+ "longValue__J" -> "numberLongValue",
+ "floatValue__F" -> "numberFloatValue",
+ "doubleValue__D" -> "numberDoubleValue",
+
+ "isNaN__Z" -> "isNaN",
+ "isInfinite__Z" -> "isInfinite"
+ )
+
+ def genClassDataOf(cls: ReferenceType)(implicit pos: Position): js.Tree = {
+ cls match {
+ case ClassType(className) =>
+ encodeClassField("d", className)
+ case ArrayType(base, dims) =>
+ (1 to dims).foldLeft(encodeClassField("d", base)) { (prev, _) =>
+ js.Apply(js.DotSelect(prev, js.Ident("getArrayOf")), Nil)
+ }
+ }
+ }
+
+ private def genFround(arg: js.Tree)(implicit pos: Position): js.Tree = {
+ genCallHelper("fround", arg)
+ }
+
+ private def genNewLong(ctor: String, args: js.Tree*)(
+ implicit pos: Position): js.Tree = {
+ import TreeDSL._
+ js.Apply(
+ js.New(encodeClassVar(LongImpl.RuntimeLongClass), Nil) DOT ctor,
+ args.toList)
+ }
+
+ private def genLongMethodApply(receiver: js.Tree, methodName: String,
+ args: js.Tree*)(implicit pos: Position): js.Tree = {
+ import TreeDSL._
+ js.Apply(receiver DOT methodName, args.toList)
+ }
+
+ private def genLongModuleApply(methodName: String, args: js.Tree*)(
+ implicit pos: Position): js.Tree = {
+ import TreeDSL._
+ js.Apply(
+ genLoadModule(LongImpl.RuntimeLongModuleClass) DOT methodName,
+ args.toList)
+ }
+
+ private def genLoadModule(moduleClass: String)(
+ implicit pos: Position): js.Tree = {
+ import TreeDSL._
+ assert(moduleClass.endsWith("$"),
+ s"Trying to load module for non-module class $moduleClass")
+ val moduleName = moduleClass.dropRight(1)
+ js.Apply(envField("m") DOT moduleName, Nil)
+ }
+
+ }
+
+ // Helpers
+
+ private[javascript] def genIsInstanceOf(expr: js.Tree, cls: ReferenceType)(
+ implicit pos: Position): js.Tree =
+ genIsAsInstanceOf(expr, cls, test = true)
+
+ private def genAsInstanceOf(expr: js.Tree, cls: ReferenceType)(
+ implicit pos: Position): js.Tree =
+ genIsAsInstanceOf(expr, cls, test = false)
+
+ private def genIsAsInstanceOf(expr: js.Tree, cls: ReferenceType, test: Boolean)(
+ implicit pos: Position): js.Tree = {
+ import Definitions._
+ import TreeDSL._
+
+ cls match {
+ case ClassType(className0) =>
+ val className =
+ if (className0 == BoxedLongClass) LongImpl.RuntimeLongClass
+ else className0
+
+ if (HijackedBoxedClasses.contains(className)) {
+ if (test) {
+ className match {
+ case BoxedUnitClass => expr === js.Undefined()
+ case BoxedBooleanClass => typeof(expr) === "boolean"
+ case BoxedByteClass => genCallHelper("isByte", expr)
+ case BoxedShortClass => genCallHelper("isShort", expr)
+ case BoxedIntegerClass => genCallHelper("isInt", expr)
+ case BoxedFloatClass => genCallHelper("isFloat", expr)
+ case BoxedDoubleClass => typeof(expr) === "number"
+ }
+ } else {
+ className match {
+ case BoxedUnitClass => genCallHelper("asUnit", expr)
+ case BoxedBooleanClass => genCallHelper("asBoolean", expr)
+ case BoxedByteClass => genCallHelper("asByte", expr)
+ case BoxedShortClass => genCallHelper("asShort", expr)
+ case BoxedIntegerClass => genCallHelper("asInt", expr)
+ case BoxedFloatClass => genCallHelper("asFloat", expr)
+ case BoxedDoubleClass => genCallHelper("asDouble", expr)
+ }
+ }
+ } else {
+ js.Apply(
+ envField(if (test) "is" else "as") DOT js.Ident(className),
+ List(expr))
+ }
+
+ case ArrayType(base, depth) =>
+ js.Apply(
+ envField(if (test) "isArrayOf" else "asArrayOf") DOT js.Ident(base),
+ List(expr, js.IntLiteral(depth)))
+ }
+ }
+
+ private[javascript] def genCallHelper(helperName: String, args: js.Tree*)(
+ implicit pos: Position): js.Tree =
+ js.Apply(envField(helperName), args.toList)
+
+ private[javascript] def encodeClassVar(className: String)(
+ implicit pos: Position): js.Tree =
+ encodeClassField("c", className)
+
+ private[javascript] def encodeClassField(field: String, className: String)(
+ implicit pos: Position): js.Tree =
+ js.DotSelect(envField(field), js.Ident(className))
+
+ private[javascript] def envField(field: String)(implicit pos: Position): js.Tree =
+ js.DotSelect(js.VarRef(js.Ident(ScalaJSEnvironmentName), false),
+ js.Ident(field))
+
+ private[javascript] implicit class MyTreeOps(val self: js.Tree) {
+ def prototype(implicit pos: Position): js.Tree =
+ js.DotSelect(self, js.Ident("prototype"))
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/LongImpl.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/LongImpl.scala
new file mode 100644
index 0000000..70b81a3
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/LongImpl.scala
@@ -0,0 +1,116 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.javascript
+
+object LongImpl {
+ final val RuntimeLongClass = "sjsr_RuntimeLong"
+ final val RuntimeLongModuleClass = "sjsr_RuntimeLong$"
+
+ private final val SigUnary = "__sjsr_RuntimeLong"
+ private final val SigBinary = "__sjsr_RuntimeLong__sjsr_RuntimeLong"
+ private final val SigShift = "__I__sjsr_RuntimeLong"
+ private final val SigCompare = "__sjsr_RuntimeLong__Z"
+
+ final val UNARY_- = "unary$und$minus" + SigUnary
+ final val UNARY_~ = "unary$und$tilde" + SigUnary
+
+ final val + = "$$plus" + SigBinary
+ final val - = "$$minus" + SigBinary
+ final val * = "$$times" + SigBinary
+ final val / = "$$div" + SigBinary
+ final val % = "$$percent" + SigBinary
+
+ final val | = "$$bar" + SigBinary
+ final val & = "$$amp" + SigBinary
+ final val ^ = "$$up" + SigBinary
+
+ final val << = "$$less$less" + SigShift
+ final val >>> = "$$greater$greater$greater" + SigShift
+ final val >> = "$$greater$greater" + SigShift
+
+ final val === = "equals" + SigCompare
+ final val !== = "notEquals" + SigCompare
+ final val < = "$$less" + SigCompare
+ final val <= = "$$less$eq" + SigCompare
+ final val > = "$$greater" + SigCompare
+ final val >= = "$$greater$eq" + SigCompare
+
+ final val toInt = "toInt" + "__I"
+ final val toDouble = "toDouble" + "__D"
+
+ final val byteValue = "byteValue__B"
+ final val shortValue = "shortValue__S"
+ final val intValue = "intValue__I"
+ final val longValue = "longValue__J"
+ final val floatValue = "floatValue__F"
+ final val doubleValue = "doubleValue__D"
+
+ final val equals_ = "equals__O__Z"
+ final val hashCode_ = "hashCode__I"
+ final val compareTo = "compareTo__jl_Long__I"
+ final val compareToO = "compareTo__O__I"
+
+ private val OperatorMethods = Set(
+ UNARY_-, UNARY_~, this.+, this.-, *, /, %, |, &, ^, <<, >>>, >>,
+ ===, !==, <, <=, >, >=, toInt, toDouble)
+
+ private val BoxedLongMethods = Set(
+ byteValue, shortValue, intValue, longValue, floatValue, doubleValue,
+ equals_, hashCode_, compareTo, compareToO)
+
+ val AllMethods = OperatorMethods ++ BoxedLongMethods
+
+ // Methods used for intrinsics
+
+ final val bitCount = "bitCount__I"
+ final val signum = "signum__sjsr_RuntimeLong"
+ final val numberOfLeadingZeros = "numberOfLeadingZeros__I"
+ final val numberOfTrailingZeros = "numberOfTrailingZeros__I"
+ final val toBinaryString = "toBinaryString__T"
+ final val toHexString = "toHexString__T"
+ final val toOctalString = "toOctalString__T"
+
+ val AllIntrinsicMethods = Set(
+ bitCount, signum, numberOfLeadingZeros, numberOfTrailingZeros,
+ toBinaryString, toHexString, toOctalString)
+
+ // Constructors
+
+ final val initFromParts = "init___I__I__I"
+ final val initFromInt = "init___I"
+
+ val AllConstructors = Set(
+ initFromParts, initFromInt)
+
+ // Methods on the companion
+
+ final val fromDouble = "fromDouble__D__sjsr_RuntimeLong"
+
+ final val Zero = "Zero__sjsr_RuntimeLong"
+
+ val AllModuleMethods = Set(
+ fromDouble, Zero)
+
+ // Boldly copied from library/scala.scalajs.runtime.RuntimeLong
+
+ /** Number of relevant bits in l and m each. */
+ private final val BITS = 22
+ /** Number of relevant bits in l and m together. */
+ private final val BITS01 = 2 * BITS
+ /** Number of relevant bits in h. */
+ private final val BITS2 = 64 - BITS01
+ /** Bitmask for l and m. */
+ private final val MASK = (1 << BITS) - 1
+ /** Bitmask for h. */
+ private final val MASK_2 = (1 << BITS2) - 1
+
+ def extractParts(value: Long): (Int, Int, Int) =
+ (value.toInt & MASK, (value >> BITS).toInt & MASK, (value >> BITS01).toInt & MASK_2)
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Printers.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Printers.scala
new file mode 100644
index 0000000..264c548
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Printers.scala
@@ -0,0 +1,420 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.javascript
+
+import scala.annotation.switch
+
+import scala.util.control.Breaks
+
+import java.io.Writer
+import java.net.URI
+
+import scala.scalajs.ir
+import ir.Position
+import ir.Position.NoPosition
+import ir.Printers.IndentationManager
+import ir.Utils.escapeJS
+
+import Trees._
+
+import scala.scalajs.tools.sourcemap.SourceMapWriter
+
+object Printers {
+
+ class JSTreePrinter(protected val out: Writer) extends IndentationManager {
+ def printTopLevelTree(tree: Tree) {
+ tree match {
+ case Skip() =>
+ // do not print anything
+ case Block(stats) =>
+ for (stat <- stats)
+ printTopLevelTree(stat)
+ case _ =>
+ printStat(tree)
+ if (shouldPrintSepAfterTree(tree))
+ print(";")
+ println()
+ }
+ }
+
+ protected def shouldPrintSepAfterTree(tree: Tree): Boolean =
+ !tree.isInstanceOf[DocComment]
+
+ protected def printBlock(tree: Tree): Unit = {
+ val trees = tree match {
+ case Block(trees) => trees
+ case _ => List(tree)
+ }
+ print("{"); indent(); println()
+ printSeq(trees) { x =>
+ printStat(x)
+ } { x =>
+ if (shouldPrintSepAfterTree(x))
+ print(";")
+ println()
+ }
+ undent(); println(); print("}")
+ }
+
+ protected def printSig(args: List[ParamDef]): Unit = {
+ printRow(args, "(", ", ", ")")
+ print(" ")
+ }
+
+ protected def printArgs(args: List[Tree]): Unit = {
+ printRow(args, "(", ", ", ")")
+ }
+
+ def printStat(tree: Tree): Unit =
+ printTree(tree, isStat = true)
+
+ def printTree(tree: Tree, isStat: Boolean): Unit = {
+ tree match {
+ case EmptyTree =>
+ print("<empty>")
+
+ // Comments
+
+ case DocComment(text) =>
+ val lines = text.split("\n").toList
+ if (lines.tail.isEmpty) {
+ print("/** ", lines.head, " */")
+ } else {
+ print("/** ", lines.head); println()
+ for (line <- lines.tail) {
+ print(" * ", line); println()
+ }
+ print(" */")
+ }
+
+ // Definitions
+
+ case VarDef(ident, mutable, rhs) =>
+ print("var ", ident)
+ if (rhs != EmptyTree)
+ print(" = ", rhs)
+
+ case ParamDef(ident, mutable) =>
+ print(ident)
+
+ // Control flow constructs
+
+ case Skip() =>
+ print("/*<skip>*/")
+
+ case tree @ Block(trees) =>
+ if (isStat)
+ printBlock(tree)
+ else
+ printRow(trees, "(", ", ", ")")
+
+ case Labeled(label, body) =>
+ print(label, ": ")
+ printBlock(body)
+
+ case Assign(lhs, rhs) =>
+ print(lhs, " = ", rhs)
+
+ case Return(expr) =>
+ print("return ", expr)
+
+ case If(cond, thenp, elsep) =>
+ if (isStat) {
+ print("if (", cond, ") ")
+ printBlock(thenp)
+ elsep match {
+ case Skip() => ()
+ case If(_, _, _) =>
+ print(" else ")
+ printTree(elsep, isStat)
+ case _ =>
+ print(" else ")
+ printBlock(elsep)
+ }
+ } else {
+ print("(", cond, " ? ", thenp, " : ", elsep, ")")
+ }
+
+ case While(cond, body, label) =>
+ if (label.isDefined)
+ print(label.get, ": ")
+ print("while (", cond, ") ")
+ printBlock(body)
+
+ case DoWhile(body, cond, label) =>
+ if (label.isDefined)
+ print(label.get, ": ")
+ print("do ")
+ printBlock(body)
+ print(" while (", cond, ")")
+
+ case Try(block, errVar, handler, finalizer) =>
+ print("try ")
+ printBlock(block)
+ if (handler != EmptyTree) {
+ print(" catch (", errVar, ") ")
+ printBlock(handler)
+ }
+ if (finalizer != EmptyTree) {
+ print(" finally ")
+ printBlock(finalizer)
+ }
+
+ case Throw(expr) =>
+ print("throw ", expr)
+
+ case Break(label) =>
+ if (label.isEmpty) print("break")
+ else print("break ", label.get)
+
+ case Continue(label) =>
+ if (label.isEmpty) print("continue")
+ else print("continue ", label.get)
+
+ case Switch(selector, cases, default) =>
+ print("switch (", selector, ") ")
+ print("{"); indent
+ for ((value, body) <- cases) {
+ println()
+ print("case ", value, ":"); indent; println()
+ printStat(body)
+ print(";")
+ undent
+ }
+ if (default != EmptyTree) {
+ println()
+ print("default:"); indent; println()
+ printStat(default)
+ print(";")
+ undent
+ }
+ undent; println(); print("}")
+
+ case Debugger() =>
+ print("debugger")
+
+ // Expressions
+
+ case New(ctor, args) =>
+ def containsOnlySelectsFromAtom(tree: Tree): Boolean = tree match {
+ case DotSelect(qual, _) => containsOnlySelectsFromAtom(qual)
+ case BracketSelect(qual, _) => containsOnlySelectsFromAtom(qual)
+ case VarRef(_, _) => true
+ case This() => true
+ case _ => false // in particular, Apply
+ }
+ if (containsOnlySelectsFromAtom(ctor))
+ print("new ", ctor)
+ else
+ print("new (", ctor, ")")
+ printArgs(args)
+
+ case DotSelect(qualifier, item) =>
+ print(qualifier, ".", item)
+
+ case BracketSelect(qualifier, item) =>
+ print(qualifier, "[", item, "]")
+
+ case Apply(fun, args) =>
+ print(fun)
+ printArgs(args)
+
+ case Delete(prop) =>
+ print("delete ", prop)
+
+ case UnaryOp("typeof", lhs) =>
+ print("typeof(", lhs, ")")
+
+ case UnaryOp(op, lhs) =>
+ print("(", op, lhs, ")")
+
+ case BinaryOp(op, lhs, rhs) =>
+ print("(", lhs, " ", op, " ", rhs, ")")
+
+ case ArrayConstr(items) =>
+ printRow(items, "[", ", ", "]")
+
+ case ObjectConstr(Nil) =>
+ print("{}")
+
+ case ObjectConstr(fields) =>
+ print("{"); indent; println()
+ printSeq(fields) {
+ case (name, value) => print(name, ": ", value)
+ } { _ =>
+ print(",")
+ println()
+ }
+ undent; println(); print("}")
+
+ // Literals
+
+ case Undefined() =>
+ print("(void 0)")
+
+ case Null() =>
+ print("null")
+
+ case BooleanLiteral(value) =>
+ print(if (value) "true" else "false")
+
+ case IntLiteral(value) =>
+ if (value >= 0)
+ print(value)
+ else
+ print("(", value, ")")
+
+ case DoubleLiteral(value) =>
+ if (value == 0 && 1 / value < 0)
+ print("(-0)")
+ else if (value >= 0)
+ print(value)
+ else
+ print("(", value, ")")
+
+ case StringLiteral(value) =>
+ print("\"", escapeJS(value), "\"")
+
+ // Atomic expressions
+
+ case VarRef(ident, _) =>
+ print(ident)
+
+ case This() =>
+ print("this")
+
+ case Function(args, body) =>
+ print("(function")
+ printSig(args)
+ printBlock(body)
+ print(")")
+
+ case _ =>
+ print(s"<error, elem of class ${tree.getClass()}>")
+ }
+ }
+
+ protected def printIdent(ident: Ident): Unit =
+ printString(escapeJS(ident.name))
+
+ def printOne(arg: Any): Unit = arg match {
+ case tree: Tree =>
+ printTree(tree, isStat = false)
+ case ident: Ident =>
+ printIdent(ident)
+ case arg =>
+ printString(if (arg == null) "null" else arg.toString)
+ }
+
+ protected def printString(s: String): Unit = {
+ out.write(s)
+ }
+
+ // Make it public
+ override def println(): Unit = super.println()
+
+ def complete(): Unit = ()
+ }
+
+ class JSTreePrinterWithSourceMap(_out: Writer,
+ sourceMap: SourceMapWriter) extends JSTreePrinter(_out) {
+
+ private var column = 0
+
+ override def printTree(tree: Tree, isStat: Boolean): Unit = {
+ val pos = tree.pos
+ if (pos.isDefined)
+ sourceMap.startNode(column, pos)
+
+ super.printTree(tree, isStat)
+
+ if (pos.isDefined)
+ sourceMap.endNode(column)
+ }
+
+ override protected def printIdent(ident: Ident): Unit = {
+ if (ident.pos.isDefined)
+ sourceMap.startNode(column, ident.pos, ident.originalName)
+ super.printIdent(ident)
+ if (ident.pos.isDefined)
+ sourceMap.endNode(column)
+ }
+
+ override def println(): Unit = {
+ super.println()
+ sourceMap.nextLine()
+ column = this.indentMargin
+ }
+
+ override protected def printString(s: String): Unit = {
+ // assume no EOL char in s, and assume s only has ASCII characters
+ super.printString(s)
+ column += s.length()
+ }
+
+ override def complete(): Unit = {
+ sourceMap.complete()
+ super.complete()
+ }
+ }
+
+ /** Prints a tree to find original locations based on line numbers.
+ * @param untilLine last 0-based line the positions should be recorded for
+ */
+ class ReverseSourceMapPrinter(untilLine: Int)
+ extends JSTreePrinter(ReverseSourceMapPrinter.NullWriter) {
+
+ private val positions = Array.fill(untilLine+1)(NoPosition)
+ private var curLine = 0
+
+ private val doneBreak = new Breaks
+
+ def apply(x: Int): Position = positions(x)
+
+ def reverseSourceMap(tree: Tree): Unit = doneBreak.breakable {
+ printTopLevelTree(tree)
+ }
+
+ override def printTree(tree: Tree, isStat: Boolean): Unit = {
+ if (positions(curLine).isEmpty)
+ positions(curLine) = tree.pos
+
+ super.printTree(tree, isStat)
+ }
+
+ override protected def printIdent(ident: Ident): Unit = {
+ if (positions(curLine).isEmpty)
+ positions(curLine) = ident.pos
+
+ super.printIdent(ident)
+ }
+
+ override def println(): Unit = {
+ super.println()
+ curLine += 1
+ if (curLine > untilLine)
+ doneBreak.break()
+ }
+
+ override protected def printString(s: String): Unit = {
+ // assume no EOL char in s, and assume s only has ASCII characters
+ // therefore, we fully ignore the string
+ }
+ }
+
+ object ReverseSourceMapPrinter {
+ private object NullWriter extends Writer {
+ def close(): Unit = ()
+ def flush(): Unit = ()
+ def write(buf: Array[Char], off: Int, len: Int): Unit = ()
+ }
+ }
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/ScalaJSClassEmitter.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/ScalaJSClassEmitter.scala
new file mode 100644
index 0000000..b249f88
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/ScalaJSClassEmitter.scala
@@ -0,0 +1,569 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.javascript
+
+import scala.scalajs.ir._
+import Position._
+import Transformers._
+import scala.scalajs.ir.Trees._
+import Types._
+
+import scala.scalajs.tools.sem._
+import CheckedBehavior.Unchecked
+
+import scala.scalajs.tools.javascript.{Trees => js}
+
+/** Defines methods to emit Scala.js classes to JavaScript code.
+ * The results are completely desugared.
+ */
+final class ScalaJSClassEmitter(semantics: Semantics) {
+
+ import JSDesugaring._
+
+ /** Desugar a Scala.js class into ECMAScript 5 constructs */
+ def genClassDef(tree: ClassDef): js.Tree = {
+ implicit val pos = tree.pos
+ val kind = tree.kind
+
+ var reverseParts: List[js.Tree] = Nil
+
+ if (kind == ClassKind.TraitImpl) {
+ reverseParts ::= genTraitImpl(tree)
+ } else {
+ if (kind.isClass)
+ reverseParts ::= genClass(tree)
+ if (kind.isClass || kind == ClassKind.Interface ||
+ tree.name.name == Definitions.StringClass)
+ reverseParts ::= genInstanceTests(tree)
+ reverseParts ::= genArrayInstanceTests(tree)
+ reverseParts ::= genTypeData(tree)
+ if (kind.isClass)
+ reverseParts ::= genSetTypeData(tree)
+ if (kind == ClassKind.ModuleClass)
+ reverseParts ::= genModuleAccessor(tree)
+ if (kind.isClass)
+ reverseParts ::= genClassExports(tree)
+ }
+
+ js.Block(reverseParts.reverse)
+ }
+
+ def genClass(tree: ClassDef): js.Tree = {
+ val className = tree.name.name
+ val typeFunctionDef = genConstructor(tree)
+ val memberDefs = tree.defs collect {
+ case m: MethodDef =>
+ genMethod(className, m)
+ case p: PropertyDef =>
+ genProperty(className, p)
+ }
+
+ js.Block(typeFunctionDef :: memberDefs)(tree.pos)
+ }
+
+ /** Generates the JS constructor for a class. */
+ def genConstructor(tree: ClassDef): js.Tree = {
+ assert(tree.kind.isClass)
+
+ val classIdent = tree.name
+ val className = classIdent.name
+ val tpe = ClassType(className)
+
+ assert(tree.parent.isDefined || className == Definitions.ObjectClass,
+ "Class $className is missing a parent class")
+
+ val ctorFun = {
+ val superCtorCall = tree.parent.fold[js.Tree] {
+ js.Skip()(tree.pos)
+ } { parentIdent =>
+ implicit val pos = tree.pos
+ js.Apply(
+ js.DotSelect(encodeClassVar(parentIdent.name), js.Ident("call")),
+ List(js.This()))
+ }
+ val fieldDefs = for {
+ field @ VarDef(name, vtpe, mutable, rhs) <- tree.defs
+ } yield {
+ implicit val pos = field.pos
+ desugarJavaScript(
+ Assign(Select(This()(tpe), name, mutable)(vtpe), rhs),
+ semantics)
+ }
+ js.Function(Nil,
+ js.Block(superCtorCall :: fieldDefs)(tree.pos))(tree.pos)
+ }
+
+ {
+ implicit val pos = tree.pos
+ val typeVar = encodeClassVar(className)
+ val docComment = js.DocComment("@constructor")
+ val ctorDef = js.Assign(typeVar, ctorFun)
+
+ val chainProto = tree.parent.fold[js.Tree] {
+ js.Skip()
+ } { parentIdent =>
+ js.Block(
+ js.Assign(typeVar.prototype,
+ js.New(js.DotSelect(envField("h"), parentIdent), Nil)),
+ genAddToPrototype(className, js.Ident("constructor"), typeVar)
+ )
+ }
+
+ val inheritableCtorDef = {
+ val inheritableCtorVar =
+ js.DotSelect(envField("h"), classIdent)
+ js.Block(
+ js.DocComment("@constructor"),
+ js.Assign(inheritableCtorVar, js.Function(Nil, js.Skip())),
+ js.Assign(inheritableCtorVar.prototype, typeVar.prototype)
+ )
+ }
+
+ js.Block(docComment, ctorDef, chainProto, inheritableCtorDef)
+ }
+ }
+
+ /** Generates a method. */
+ def genMethod(className: String, method: MethodDef): js.Tree = {
+ implicit val pos = method.pos
+ val methodFun = js.Function(method.args.map(transformParamDef),
+ desugarBody(method.body, method.resultType == NoType))
+ genAddToPrototype(className, method.name, methodFun)
+ }
+
+ /** Generates a property. */
+ def genProperty(className: String, property: PropertyDef): js.Tree = {
+ implicit val pos = property.pos
+ val classType = ClassType(className)
+
+ // defineProperty method
+ val defProp =
+ js.BracketSelect(js.VarRef(js.Ident("Object"), false),
+ js.StringLiteral("defineProperty"))
+
+ // class prototype
+ val proto = encodeClassVar(className).prototype
+
+ // property name
+ val name = property.name match {
+ case StringLiteral(value) =>
+ js.StringLiteral(value)
+ case id: Ident =>
+ // We need to work around the closure compiler. Call propertyName to
+ // get a string representation of the optimized name
+ genCallHelper("propertyName",
+ js.ObjectConstr(transformIdent(id) -> js.IntLiteral(0) :: Nil))
+ }
+
+ // Options passed to the defineProperty method
+ val descriptor = js.ObjectConstr {
+ // Basic config
+ val base =
+ js.StringLiteral("enumerable") -> js.BooleanLiteral(true) :: Nil
+
+ // Optionally add getter
+ val wget =
+ if (property.getterBody == EmptyTree) base
+ else js.StringLiteral("get") ->
+ js.Function(Nil, desugarBody(property.getterBody, isStat = false)) :: base
+
+ // Optionally add setter
+ if (property.setterBody == EmptyTree) wget
+ else js.StringLiteral("set") ->
+ js.Function(transformParamDef(property.setterArg) :: Nil,
+ desugarBody(property.setterBody, isStat = true)) :: wget
+ }
+
+ js.Apply(defProp, proto :: name :: descriptor :: Nil)
+ }
+
+ /** Generate `classVar.prototype.name = value` */
+ def genAddToPrototype(className: String, name: js.PropertyName,
+ value: js.Tree)(implicit pos: Position): js.Tree = {
+ val proto = encodeClassVar(className).prototype
+ val select = name match {
+ case name: js.Ident => js.DotSelect(proto, name)
+ case name: js.StringLiteral => js.BracketSelect(proto, name)
+ }
+ js.Assign(select, value)
+ }
+
+ /** Generate `classVar.prototype.name = value` */
+ def genAddToPrototype(className: String, name: PropertyName,
+ value: js.Tree)(implicit pos: Position): js.Tree = {
+ val newName = name match {
+ case ident: Ident => transformIdent(ident)
+ case StringLiteral(value) => js.StringLiteral(value)
+ }
+ genAddToPrototype(className, newName, value)
+ }
+
+ def genInstanceTests(tree: ClassDef): js.Tree = {
+ import Definitions._
+ import TreeDSL._
+
+ implicit val pos = tree.pos
+
+ val classIdent = transformIdent(tree.name)
+ val className = classIdent.name
+ val displayName = decodeClassName(className)
+
+ val isAncestorOfString =
+ AncestorsOfStringClass.contains(className)
+ val isAncestorOfHijackedNumberClass =
+ AncestorsOfHijackedNumberClasses.contains(className)
+ val isAncestorOfBoxedBooleanClass =
+ AncestorsOfBoxedBooleanClass.contains(className)
+
+ val objParam = js.ParamDef(Ident("obj"), mutable = false)
+ val obj = objParam.ref
+
+ val createIsStat = {
+ envField("is") DOT classIdent :=
+ js.Function(List(objParam), js.Return(className match {
+ case Definitions.ObjectClass =>
+ js.BinaryOp("!==", obj, js.Null())
+
+ case Definitions.StringClass =>
+ js.UnaryOp("typeof", obj) === js.StringLiteral("string")
+
+ case _ =>
+ var test = (obj && (obj DOT "$classData") &&
+ (obj DOT "$classData" DOT "ancestors" DOT classIdent))
+
+ if (isAncestorOfString)
+ test = test || (
+ js.UnaryOp("typeof", obj) === js.StringLiteral("string"))
+ if (isAncestorOfHijackedNumberClass)
+ test = test || (
+ js.UnaryOp("typeof", obj) === js.StringLiteral("number"))
+ if (isAncestorOfBoxedBooleanClass)
+ test = test || (
+ js.UnaryOp("typeof", obj) === js.StringLiteral("boolean"))
+
+ !(!test)
+ }))
+ }
+
+ val createAsStat = if (semantics.asInstanceOfs == Unchecked) {
+ js.Skip()
+ } else {
+ envField("as") DOT classIdent :=
+ js.Function(List(objParam), js.Return(className match {
+ case Definitions.ObjectClass =>
+ obj
+
+ case _ =>
+ js.If(js.Apply(envField("is") DOT classIdent, List(obj)) ||
+ (obj === js.Null()), {
+ obj
+ }, {
+ genCallHelper("throwClassCastException",
+ obj, js.StringLiteral(displayName))
+ })
+ }))
+ }
+
+ js.Block(createIsStat, createAsStat)
+ }
+
+ def genArrayInstanceTests(tree: ClassDef): js.Tree = {
+ import Definitions._
+ import TreeDSL._
+
+ implicit val pos = tree.pos
+
+ val classIdent = transformIdent(tree.name)
+ val className = classIdent.name
+ val displayName = decodeClassName(className)
+
+ val objParam = js.ParamDef(Ident("obj"), mutable = false)
+ val obj = objParam.ref
+
+ val depthParam = js.ParamDef(Ident("depth"), mutable = false)
+ val depth = depthParam.ref
+
+ val createIsArrayOfStat = {
+ envField("isArrayOf") DOT classIdent :=
+ js.Function(List(objParam, depthParam), className match {
+ case Definitions.ObjectClass =>
+ val dataVarDef = js.VarDef(Ident("data"), false, {
+ obj && (obj DOT "$classData")
+ })
+ val data = dataVarDef.ref
+ js.Block(
+ dataVarDef,
+ js.If(!data, {
+ js.Return(js.BooleanLiteral(false))
+ }, {
+ val arrayDepthVarDef = js.VarDef(Ident("arrayDepth"), false, {
+ (data DOT "arrayDepth") || js.IntLiteral(0)
+ })
+ val arrayDepth = arrayDepthVarDef.ref
+ js.Block(
+ arrayDepthVarDef,
+ js.Return {
+ // Array[A] </: Array[Array[A]]
+ !js.BinaryOp("<", arrayDepth, depth) && (
+ // Array[Array[A]] <: Array[Object]
+ js.BinaryOp(">", arrayDepth, depth) ||
+ // Array[Int] </: Array[Object]
+ !js.BracketSelect(data DOT "arrayBase", js.StringLiteral("isPrimitive"))
+ )
+ })
+ }))
+
+ case _ =>
+ js.Return(!(!(obj && (obj DOT "$classData") &&
+ ((obj DOT "$classData" DOT "arrayDepth") === depth) &&
+ (obj DOT "$classData" DOT "arrayBase" DOT "ancestors" DOT classIdent))))
+ })
+ }
+
+ val createAsArrayOfStat = if (semantics.asInstanceOfs == Unchecked) {
+ js.Skip()
+ } else {
+ envField("asArrayOf") DOT classIdent :=
+ js.Function(List(objParam, depthParam), js.Return {
+ js.If(js.Apply(envField("isArrayOf") DOT classIdent, List(obj, depth)) ||
+ (obj === js.Null()), {
+ obj
+ }, {
+ genCallHelper("throwArrayCastException",
+ obj, js.StringLiteral("L"+displayName+";"), depth)
+ })
+ })
+ }
+
+ js.Block(createIsArrayOfStat, createAsArrayOfStat)
+ }
+
+ def genTypeData(tree: ClassDef): js.Tree = {
+ import Definitions._
+ import TreeDSL._
+
+ implicit val pos = tree.pos
+
+ val classIdent = transformIdent(tree.name)
+ val className = classIdent.name
+ val kind = tree.kind
+ assert(kind.isType)
+
+ val isObjectClass =
+ className == ObjectClass
+ val isHijackedBoxedClass =
+ HijackedBoxedClasses.contains(className)
+ val isAncestorOfHijackedClass =
+ AncestorsOfHijackedClasses.contains(className)
+
+ val parentData = tree.parent.fold[js.Tree] {
+ if (isObjectClass) js.Null()
+ else js.Undefined()
+ } { parent =>
+ envField("d") DOT parent
+ }
+
+ val ancestorsRecord = js.ObjectConstr(
+ for (ancestor <- classIdent :: tree.ancestors.map(transformIdent))
+ yield (ancestor, js.IntLiteral(1)))
+
+ val typeData = js.New(envField("ClassTypeData"), List(
+ js.ObjectConstr(List(classIdent -> js.IntLiteral(0))),
+ js.BooleanLiteral(kind == ClassKind.Interface),
+ js.StringLiteral(decodeClassName(className)),
+ parentData,
+ ancestorsRecord
+ ) ++ (
+ // Optional parameter isInstance
+ if (isObjectClass) {
+ /* Object has special ScalaJS.is.O *and* ScalaJS.isArrayOf.O. */
+ List(
+ envField("is") DOT classIdent,
+ envField("isArrayOf") DOT classIdent)
+ } else if (isHijackedBoxedClass) {
+ /* Hijacked boxed classes have a special isInstanceOf test. */
+ val xParam = js.ParamDef(Ident("x"), mutable = false)
+ List(js.Function(List(xParam), js.Return {
+ genIsInstanceOf(xParam.ref, ClassType(className))
+ }))
+ } else if (isAncestorOfHijackedClass || className == StringClass) {
+ /* java.lang.String and ancestors of hijacked classes have a normal
+ * ScalaJS.is.pack_Class test but with a non-standard behavior. */
+ List(envField("is") DOT classIdent)
+ } else {
+ // For other classes, the isInstance function can be inferred.
+ Nil
+ }
+ ))
+
+ envField("d") DOT classIdent := typeData
+ }
+
+ def genSetTypeData(tree: ClassDef): js.Tree = {
+ import TreeDSL._
+
+ implicit val pos = tree.pos
+
+ assert(tree.kind.isClass)
+
+ encodeClassVar(tree.name.name).prototype DOT "$classData" :=
+ envField("d") DOT tree.name
+ }
+
+ def genModuleAccessor(tree: ClassDef): js.Tree = {
+ import TreeDSL._
+
+ implicit val pos = tree.pos
+
+ val classIdent = transformIdent(tree.name)
+ val className = classIdent.name
+ val tpe = ClassType(className)
+
+ require(tree.kind == ClassKind.ModuleClass,
+ s"genModuleAccessor called with non-module class: $className")
+ assert(className.endsWith("$"))
+
+ val moduleName = className.dropRight(1)
+ val moduleIdent = Ident(moduleName)
+
+ val moduleInstanceVar = envField("n") DOT moduleIdent
+ val accessorVar = envField("m") DOT moduleIdent
+
+ val createModuleInstanceField = {
+ moduleInstanceVar := js.Undefined()
+ }
+
+ val createAccessor = {
+ accessorVar := js.Function(Nil, js.Block(
+ js.If(!(moduleInstanceVar), {
+ moduleInstanceVar :=
+ js.Apply(js.New(encodeClassVar(className), Nil) DOT js.Ident("init___"), Nil)
+ }, js.Skip()),
+ js.Return(moduleInstanceVar)
+ ))
+ }
+
+ js.Block(createModuleInstanceField, createAccessor)
+ }
+
+ def genClassExports(tree: ClassDef): js.Tree = {
+ val exports = tree.defs collect {
+ case e: ConstructorExportDef =>
+ genConstructorExportDef(tree, e)
+ case e: ModuleExportDef =>
+ genModuleExportDef(tree, e)
+ }
+
+ js.Block(exports)(tree.pos)
+ }
+
+ def genConstructorExportDef(cd: ClassDef, tree: ConstructorExportDef): js.Tree = {
+ import TreeDSL._
+
+ implicit val pos = tree.pos
+ val classType = ClassType(cd.name.name)
+ val ConstructorExportDef(fullName, args, body) = tree
+
+ val baseCtor = envField("c") DOT cd.name
+ val (createNamespace, expCtorVar) = genCreateNamespaceInExports(fullName)
+
+ js.Block(
+ createNamespace,
+ js.DocComment("@constructor"),
+ expCtorVar := js.Function(args.map(transformParamDef), js.Block(
+ js.Apply(js.DotSelect(baseCtor, js.Ident("call")), List(js.This())),
+ desugarBody(body, isStat = true)
+ )),
+ expCtorVar DOT "prototype" := baseCtor DOT "prototype"
+ )
+ }
+
+ def genModuleExportDef(cd: ClassDef, tree: ModuleExportDef): js.Tree = {
+ import TreeDSL._
+
+ implicit val pos = tree.pos
+
+ val baseAccessor =
+ envField("m") DOT cd.name.name.dropRight(1)
+ val (createNamespace, expAccessorVar) =
+ genCreateNamespaceInExports(tree.fullName)
+
+ js.Block(
+ createNamespace,
+ expAccessorVar := baseAccessor
+ )
+ }
+
+ def genTraitImpl(tree: ClassDef): js.Tree = {
+ val traitImplName = tree.name.name
+ val defs = tree.defs collect {
+ case m: MethodDef =>
+ genTraitImplMethod(traitImplName, m)
+ }
+ js.Block(defs)(tree.pos)
+ }
+
+ def genTraitImplMethod(traitImplName: String, tree: MethodDef): js.Tree = {
+ implicit val pos = tree.pos
+ val MethodDef(name: Ident, args, resultType, body) = tree
+ js.Assign(
+ js.DotSelect(envField("i"), name),
+ js.Function(args.map(transformParamDef),
+ desugarBody(body, resultType == NoType)))
+ }
+
+ /** Generate a dummy parent. Used by ScalaJSOptimizer */
+ def genDummyParent(name: String): js.Tree = {
+ implicit val pos = Position.NoPosition
+
+ js.Block(
+ js.DocComment("@constructor (dummy parent)"))
+ js.Assign(js.DotSelect(envField("h"), js.Ident(name)),
+ js.Function(Nil, js.Skip())
+ )
+ }
+
+ // Helpers
+
+ /** Desugars a function body of the IR into ES5 JavaScript. */
+ private def desugarBody(tree: Tree, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val withReturn =
+ if (isStat) tree
+ else Return(tree)
+ desugarJavaScript(withReturn, semantics) match {
+ case js.Block(stats :+ js.Return(js.Undefined())) => js.Block(stats)
+ case other => other
+ }
+ }
+
+ /** Gen JS code for assigning an rhs to a qualified name in the exports scope.
+ * For example, given the qualified name "foo.bar.Something", generates:
+ *
+ * ScalaJS.e["foo"] = ScalaJS.e["foo"] || {};
+ * ScalaJS.e["foo"]["bar"] = ScalaJS.e["foo"]["bar"] || {};
+ *
+ * Returns (statements, ScalaJS.e["foo"]["bar"]["Something"])
+ */
+ private def genCreateNamespaceInExports(qualName: String)(
+ implicit pos: Position): (js.Tree, js.Tree) = {
+ val parts = qualName.split("\\.")
+ val statements = List.newBuilder[js.Tree]
+ var namespace = envField("e")
+ for (i <- 0 until parts.length-1) {
+ namespace = js.BracketSelect(namespace, js.StringLiteral(parts(i)))
+ statements +=
+ js.Assign(namespace, js.BinaryOp("||", namespace, js.ObjectConstr(Nil)))
+ }
+ val lhs = js.BracketSelect(namespace, js.StringLiteral(parts.last))
+ (js.Block(statements.result()), lhs)
+ }
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/TreeDSL.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/TreeDSL.scala
new file mode 100644
index 0000000..3ac54d8
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/TreeDSL.scala
@@ -0,0 +1,50 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.javascript
+
+import scala.language.implicitConversions
+
+import scala.scalajs.ir.Position
+
+import Trees._
+
+private[javascript] object TreeDSL {
+ implicit class TreeOps(val self: Tree) extends AnyVal {
+ /** Select a member */
+ def DOT(field: Ident)(implicit pos: Position): DotSelect =
+ DotSelect(self, field)
+
+ /** Select a member */
+ def DOT(field: String)(implicit pos: Position): DotSelect =
+ DotSelect(self, Ident(field))
+
+ // Some operators that we use
+
+ def ===(that: Tree)(implicit pos: Position): Tree =
+ BinaryOp("===", self, that)
+ def ===(that: String)(implicit pos: Position): Tree =
+ BinaryOp("===", self, StringLiteral(that))
+
+ def unary_!()(implicit pos: Position): Tree =
+ UnaryOp("!", self)
+ def &&(that: Tree)(implicit pos: Position): Tree =
+ BinaryOp("&&", self, that)
+ def ||(that: Tree)(implicit pos: Position): Tree =
+ BinaryOp("||", self, that)
+
+ // Other constructs
+
+ def :=(that: Tree)(implicit pos: Position): Tree =
+ Assign(self, that)
+ }
+
+ def typeof(expr: Tree)(implicit pos: Position): Tree =
+ UnaryOp("typeof", expr)
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Trees.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Trees.scala
new file mode 100644
index 0000000..0b86d1b
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/javascript/Trees.scala
@@ -0,0 +1,194 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.javascript
+
+import scala.annotation.switch
+
+import scala.scalajs.ir
+import ir.Position
+import ir.Position.NoPosition
+
+object Trees {
+ import ir.Trees.requireValidIdent
+
+ /** AST node of JavaScript. */
+ abstract sealed class Tree {
+ val pos: Position
+
+ def show: String = {
+ val writer = new java.io.StringWriter
+ val printer = new Printers.JSTreePrinter(writer)
+ printer.printTree(this, isStat = true)
+ writer.toString()
+ }
+ }
+
+ case object EmptyTree extends Tree {
+ val pos = NoPosition
+ }
+
+ // Comments
+
+ case class DocComment(text: String)(implicit val pos: Position) extends Tree
+
+ // Identifiers and properties
+
+ sealed trait PropertyName {
+ def name: String
+ def pos: Position
+ }
+
+ case class Ident(name: String, originalName: Option[String])(
+ implicit val pos: Position) extends PropertyName {
+ requireValidIdent(name)
+ }
+
+ object Ident {
+ def apply(name: String)(implicit pos: Position): Ident =
+ new Ident(name, Some(name))
+ }
+
+ // Definitions
+
+ case class VarDef(name: Ident, mutable: Boolean, rhs: Tree)(implicit val pos: Position) extends Tree {
+ def ref(implicit pos: Position): Tree =
+ VarRef(name, mutable = mutable)
+ }
+
+ case class ParamDef(name: Ident, mutable: Boolean)(implicit val pos: Position) extends Tree {
+ def ref(implicit pos: Position): Tree =
+ VarRef(name, mutable = mutable)
+ }
+
+ // Control flow constructs
+
+ case class Skip()(implicit val pos: Position) extends Tree
+
+ class Block private (val stats: List[Tree])(implicit val pos: Position) extends Tree {
+ override def toString(): String =
+ stats.mkString("Block(", ",", ")")
+ }
+
+ object Block {
+ def apply(stats: List[Tree])(implicit pos: Position): Tree = {
+ val flattenedStats = stats flatMap {
+ case Skip() => Nil
+ case Block(subStats) => subStats
+ case other => other :: Nil
+ }
+ flattenedStats match {
+ case Nil => Skip()
+ case only :: Nil => only
+ case _ => new Block(flattenedStats)
+ }
+ }
+
+ def apply(stats: Tree*)(implicit pos: Position): Tree =
+ apply(stats.toList)
+
+ def unapply(block: Block): Some[List[Tree]] = Some(block.stats)
+ }
+
+ case class Labeled(label: Ident, body: Tree)(implicit val pos: Position) extends Tree
+
+ case class Assign(lhs: Tree, rhs: Tree)(implicit val pos: Position) extends Tree {
+ require(lhs match {
+ case _:VarRef | _:DotSelect | _:BracketSelect => true
+ case _ => false
+ }, s"Invalid lhs for Assign: $lhs")
+ }
+
+ case class Return(expr: Tree)(implicit val pos: Position) extends Tree
+
+ case class If(cond: Tree, thenp: Tree, elsep: Tree)(implicit val pos: Position) extends Tree
+
+ case class While(cond: Tree, body: Tree, label: Option[Ident] = None)(implicit val pos: Position) extends Tree
+
+ case class DoWhile(body: Tree, cond: Tree, label: Option[Ident] = None)(implicit val pos: Position) extends Tree
+
+ case class Try(block: Tree, errVar: Ident, handler: Tree, finalizer: Tree)(implicit val pos: Position) extends Tree
+
+ case class Throw(expr: Tree)(implicit val pos: Position) extends Tree
+
+ case class Break(label: Option[Ident] = None)(implicit val pos: Position) extends Tree
+
+ case class Continue(label: Option[Ident] = None)(implicit val pos: Position) extends Tree
+
+ case class Switch(selector: Tree, cases: List[(Tree, Tree)], default: Tree)(implicit val pos: Position) extends Tree
+
+ case class Debugger()(implicit val pos: Position) extends Tree
+
+ // Expressions
+
+ case class New(ctor: Tree, args: List[Tree])(implicit val pos: Position) extends Tree
+
+ case class DotSelect(qualifier: Tree, item: Ident)(implicit val pos: Position) extends Tree
+
+ case class BracketSelect(qualifier: Tree, item: Tree)(implicit val pos: Position) extends Tree
+
+ /** Syntactic apply.
+ * It is a method call if fun is a dot-select or bracket-select. It is a
+ * function call otherwise.
+ */
+ case class Apply(fun: Tree, args: List[Tree])(implicit val pos: Position) extends Tree
+
+ case class Delete(prop: Tree)(implicit val pos: Position) extends Tree {
+ require(prop match {
+ case _:DotSelect | _:BracketSelect => true
+ case _ => false
+ }, s"Invalid prop for Delete: $prop")
+ }
+
+ /** Unary operation (always preserves pureness).
+ *
+ * Operations which do not preserve pureness are not allowed in this tree.
+ * These are notably ++ and --
+ */
+ case class UnaryOp(op: String, lhs: Tree)(implicit val pos: Position) extends Tree
+
+ /** Binary operation (always preserves pureness).
+ *
+ * Operations which do not preserve pureness are not allowed in this tree.
+ * These are notably +=, -=, *=, /= and %=
+ */
+ case class BinaryOp(op: String, lhs: Tree, rhs: Tree)(implicit val pos: Position) extends Tree
+
+ case class ArrayConstr(items: List[Tree])(implicit val pos: Position) extends Tree
+
+ case class ObjectConstr(fields: List[(PropertyName, Tree)])(implicit val pos: Position) extends Tree
+
+ // Literals
+
+ /** Marker for literals. Literals are always pure. */
+ sealed trait Literal extends Tree
+
+ case class Undefined()(implicit val pos: Position) extends Literal
+
+ case class Null()(implicit val pos: Position) extends Literal
+
+ case class BooleanLiteral(value: Boolean)(implicit val pos: Position) extends Literal
+
+ case class IntLiteral(value: Int)(implicit val pos: Position) extends Literal
+
+ case class DoubleLiteral(value: Double)(implicit val pos: Position) extends Literal
+
+ case class StringLiteral(value: String)(
+ implicit val pos: Position) extends Literal with PropertyName {
+ override def name = value
+ }
+
+ // Atomic expressions
+
+ case class VarRef(ident: Ident, mutable: Boolean)(implicit val pos: Position) extends Tree
+
+ case class This()(implicit val pos: Position) extends Tree
+
+ case class Function(args: List[ParamDef], body: Tree)(implicit val pos: Position) extends Tree
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Exceptions.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Exceptions.scala
new file mode 100644
index 0000000..dd7f635
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Exceptions.scala
@@ -0,0 +1,59 @@
+package scala.scalajs.tools.jsdep
+
+abstract class DependencyException(msg: String) extends Exception(msg)
+
+class MissingDependencyException(
+ val originatingLib: FlatJSDependency,
+ val missingLib: String
+) extends DependencyException(
+ s"The JS dependency ${originatingLib.resourceName} declared " +
+ s"from ${originatingLib.origin} has an unmet transitive " +
+ s"dependency $missingLib")
+
+class CyclicDependencyException(
+ val participants: List[ResolutionInfo]
+) extends DependencyException(
+ CyclicDependencyException.mkMsg(participants))
+
+object CyclicDependencyException {
+ private def mkMsg(parts: List[ResolutionInfo]) = {
+ val lookup = parts.map(p => (p.resourceName, p)).toMap
+
+ val msg = new StringBuilder()
+ msg.append("There is a loop in the following JS dependencies:\n")
+
+ def str(info: ResolutionInfo) =
+ s"${info.resourceName} from: ${info.origins.mkString(", ")}"
+
+ for (dep <- parts) {
+ msg.append(s" ${str(dep)} which depends on\n")
+ for (name <- dep.dependencies) {
+ val rdep = lookup(name)
+ msg.append(s" - ${str(rdep)}\n")
+ }
+ }
+
+ msg.toString()
+ }
+}
+
+class ConflictingNameException(
+ val participants: List[FlatJSDependency]
+) extends DependencyException(
+ ConflictingNameException.mkMsg(participants))
+
+object ConflictingNameException {
+ private def mkMsg(parts: List[FlatJSDependency]) = {
+ val resName = parts.head.resourceName
+
+ val msg = new StringBuilder()
+ msg.append(s"Name conflicts in:\n")
+
+ for (p <- parts) {
+ msg.append(p)
+ msg.append('\n')
+ }
+
+ sys.error(msg.toString())
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/FlatJSDependency.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/FlatJSDependency.scala
new file mode 100644
index 0000000..0c55e88
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/FlatJSDependency.scala
@@ -0,0 +1,17 @@
+package scala.scalajs.tools.jsdep
+
+import scala.scalajs.ir.Trees.isValidIdentifier
+
+/** The same as a [[JSDependency]] but containing the origin from the containing
+ * JSDependencyManifest. This class is used for filtering of dependencies.
+ */
+final class FlatJSDependency(
+ val origin: Origin,
+ val resourceName: String,
+ val dependencies: List[String] = Nil,
+ val commonJSName: Option[String] = None) {
+
+ require(commonJSName.forall(isValidIdentifier),
+ "commonJSName must be a valid JavaScript identifier")
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependency.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependency.scala
new file mode 100644
index 0000000..2e6f8d1
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependency.scala
@@ -0,0 +1,66 @@
+package scala.scalajs.tools.jsdep
+
+import scala.scalajs.tools.json._
+
+import scala.scalajs.ir.Trees.isValidIdentifier
+
+/** Expresses a dependency on a raw JS library and the JS libraries this library
+ * itself depends on.
+ *
+ * Both the [[resourceName]] and each element of [[dependencies]] is the
+ * unqualified filename of the library (e.g. "jquery.js").
+ *
+ * @param resourceName Filename of the JavaScript file to include
+ * (e.g. "jquery.js")
+ * @param dependencies Filenames of JavaScript files that must be included
+ * before this JavaScript file.
+ * @param commonJSName A JavaScript variable name this dependency should be
+ * required in a commonJS environment (n.b. Node.js). Should only be set if
+ * the JavaScript library will register its exports.
+ */
+final class JSDependency(
+ val resourceName: String,
+ val dependencies: List[String] = Nil,
+ val commonJSName: Option[String] = None) {
+
+ require(commonJSName.forall(isValidIdentifier),
+ "commonJSName must be a valid JavaScript identifier")
+
+ def dependsOn(names: String*): JSDependency =
+ copy(dependencies = dependencies ++ names)
+ def commonJSName(name: String): JSDependency =
+ copy(commonJSName = Some(name))
+ def withOrigin(origin: Origin): FlatJSDependency =
+ new FlatJSDependency(origin, resourceName, dependencies, commonJSName)
+
+ private def copy(
+ resourceName: String = this.resourceName,
+ dependencies: List[String] = this.dependencies,
+ commonJSName: Option[String] = this.commonJSName) = {
+ new JSDependency(resourceName, dependencies, commonJSName)
+ }
+}
+
+object JSDependency {
+
+ implicit object JSDepJSONSerializer extends JSONSerializer[JSDependency] {
+ def serialize(x: JSDependency): JSON = {
+ new JSONObjBuilder()
+ .fld("resourceName", x.resourceName)
+ .opt("dependencies",
+ if (x.dependencies.nonEmpty) Some(x.dependencies) else None)
+ .opt("commonJSName", x.commonJSName)
+ .toJSON
+ }
+ }
+
+ implicit object JSDepJSONDeserializer extends JSONDeserializer[JSDependency] {
+ def deserialize(x: JSON): JSDependency = {
+ val obj = new JSONObjExtractor(x)
+ new JSDependency(
+ obj.fld[String] ("resourceName"),
+ obj.opt[List[String]]("dependencies").getOrElse(Nil),
+ obj.opt[String] ("commonJSName"))
+ }
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependencyManifest.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependencyManifest.scala
new file mode 100644
index 0000000..24491b4
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/JSDependencyManifest.scala
@@ -0,0 +1,130 @@
+package scala.scalajs.tools.jsdep
+
+import scala.scalajs.tools.json._
+import scala.scalajs.tools.io._
+
+import scala.collection.immutable.{Seq, Traversable}
+
+import java.io.{Reader, Writer}
+
+/** The information written to a "JS_DEPENDENCIES" manifest file. */
+final class JSDependencyManifest(
+ val origin: Origin,
+ val libDeps: List[JSDependency],
+ val requiresDOM: Boolean,
+ val compliantSemantics: List[String]) {
+ def flatten: List[FlatJSDependency] = libDeps.map(_.withOrigin(origin))
+}
+
+object JSDependencyManifest {
+
+ final val ManifestFileName = "JS_DEPENDENCIES"
+
+ def createIncludeList(
+ flatDeps: Traversable[FlatJSDependency]): List[ResolutionInfo] = {
+ val jsDeps = mergeManifests(flatDeps)
+
+ // Verify all dependencies are met
+ for {
+ lib <- flatDeps
+ dep <- lib.dependencies
+ if !jsDeps.contains(dep)
+ } throw new MissingDependencyException(lib, dep)
+
+ // Sort according to dependencies and return
+
+ // Very simple O(n²) topological sort for elements assumed to be distinct
+ // Copied :( from GenJSExports (but different exception)
+ @scala.annotation.tailrec
+ def loop(coll: List[ResolutionInfo],
+ acc: List[ResolutionInfo]): List[ResolutionInfo] = {
+
+ if (coll.isEmpty) acc
+ else if (coll.tail.isEmpty) coll.head :: acc
+ else {
+ val (selected, pending) = coll.partition { x =>
+ coll forall { y => (x eq y) || !y.dependencies.contains(x.resourceName) }
+ }
+
+ if (selected.nonEmpty)
+ loop(pending, selected ::: acc)
+ else
+ throw new CyclicDependencyException(pending)
+ }
+ }
+
+ loop(jsDeps.values.toList, Nil)
+ }
+
+ /** Merges multiple JSDependencyManifests into a map of map:
+ * resourceName -> ResolutionInfo
+ */
+ private def mergeManifests(flatDeps: Traversable[FlatJSDependency]) = {
+ @inline
+ def hasConflict(x: FlatJSDependency, y: FlatJSDependency) = (
+ x.commonJSName.isDefined &&
+ y.commonJSName.isDefined &&
+ (x.resourceName == y.resourceName ^
+ x.commonJSName == y.commonJSName)
+ )
+
+ val conflicts = flatDeps.filter(x =>
+ flatDeps.exists(y => hasConflict(x,y)))
+
+ if (conflicts.nonEmpty)
+ throw new ConflictingNameException(conflicts.toList)
+
+ flatDeps.groupBy(_.resourceName).mapValues { sameName =>
+ new ResolutionInfo(
+ resourceName = sameName.head.resourceName,
+ dependencies = sameName.flatMap(_.dependencies).toSet,
+ origins = sameName.map(_.origin).toList,
+ commonJSName = sameName.flatMap(_.commonJSName).headOption
+ )
+ }
+ }
+
+ implicit object JSDepManJSONSerializer extends JSONSerializer[JSDependencyManifest] {
+ @inline def optList[T](x: List[T]): Option[List[T]] =
+ if (x.nonEmpty) Some(x) else None
+
+ def serialize(x: JSDependencyManifest): JSON = {
+ new JSONObjBuilder()
+ .fld("origin", x.origin)
+ .opt("libDeps", optList(x.libDeps))
+ .opt("requiresDOM", if (x.requiresDOM) Some(true) else None)
+ .opt("compliantSemantics", optList(x.compliantSemantics))
+ .toJSON
+ }
+ }
+
+ implicit object JSDepManJSONDeserializer extends JSONDeserializer[JSDependencyManifest] {
+ def deserialize(x: JSON): JSDependencyManifest = {
+ val obj = new JSONObjExtractor(x)
+ new JSDependencyManifest(
+ obj.fld[Origin] ("origin"),
+ obj.opt[List[JSDependency]]("libDeps").getOrElse(Nil),
+ obj.opt[Boolean] ("requiresDOM").getOrElse(false),
+ obj.opt[List[String]] ("compliantSemantics").getOrElse(Nil))
+ }
+ }
+
+ def write(dep: JSDependencyManifest, output: WritableVirtualTextFile): Unit = {
+ val writer = output.contentWriter
+ try write(dep, writer)
+ finally writer.close()
+ }
+
+ def write(dep: JSDependencyManifest, writer: Writer): Unit =
+ writeJSON(dep.toJSON, writer)
+
+ def read(file: VirtualTextFile): JSDependencyManifest = {
+ val reader = file.reader
+ try read(reader)
+ finally reader.close()
+ }
+
+ def read(reader: Reader): JSDependencyManifest =
+ fromJSON[JSDependencyManifest](readJSON(reader))
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Origin.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Origin.scala
new file mode 100644
index 0000000..a2c6b2d
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/Origin.scala
@@ -0,0 +1,28 @@
+package scala.scalajs.tools.jsdep
+
+import scala.scalajs.tools.json._
+
+/** The place a JSDependency originated from */
+final class Origin(val moduleName: String, val configuration: String) {
+ override def toString(): String = s"$moduleName:$configuration"
+}
+
+object Origin {
+ implicit object OriginJSONSerializer extends JSONSerializer[Origin] {
+ def serialize(x: Origin): JSON = {
+ new JSONObjBuilder()
+ .fld("moduleName", x.moduleName)
+ .fld("configuration", x.configuration)
+ .toJSON
+ }
+ }
+
+ implicit object OriginDeserializer extends JSONDeserializer[Origin] {
+ def deserialize(x: JSON): Origin = {
+ val obj = new JSONObjExtractor(x)
+ new Origin(
+ obj.fld[String]("moduleName"),
+ obj.fld[String]("configuration"))
+ }
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/ResolutionInfo.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/ResolutionInfo.scala
new file mode 100644
index 0000000..2aa177e
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/jsdep/ResolutionInfo.scala
@@ -0,0 +1,21 @@
+package scala.scalajs.tools.jsdep
+
+import scala.scalajs.ir.Trees.isValidIdentifier
+
+/** Information about a resolved JSDependency
+ *
+ * @param resourceName Filename of the JavaScript file
+ * @param dependencies Filenames this dependency depends on
+ * @param origins Who declared this dependency
+ * @param commonJSName Variable name in commonJS environments
+ */
+final class ResolutionInfo(
+ val resourceName: String,
+ val dependencies: Set[String],
+ val origins: List[Origin],
+ val commonJSName: Option[String]) {
+
+ require(commonJSName.forall(isValidIdentifier),
+ "commonJSName must be a valid JavaScript identifier")
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/AbstractJSONImpl.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/AbstractJSONImpl.scala
new file mode 100644
index 0000000..ad5d79e
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/AbstractJSONImpl.scala
@@ -0,0 +1,32 @@
+package scala.scalajs.tools.json
+
+import java.io.{Reader, Writer}
+
+/** A JSON implementation. Has a representation type and methods to convert
+ * this type to/from primitives, lists and maps.
+ *
+ * Further, it can write/read a value of this type to a string.
+ */
+private[json] trait AbstractJSONImpl {
+
+ type Repr
+
+ def fromString(x: String): Repr
+ def fromNumber(x: Number): Repr
+ def fromBoolean(x: Boolean): Repr
+ def fromList(x: List[Repr]): Repr
+ def fromMap(x: Map[String, Repr]): Repr
+
+ def toString(x: Repr): String
+ def toNumber(x: Repr): Number
+ def toBoolean(x: Repr): Boolean
+ def toList(x: Repr): List[Repr]
+ def toMap(x: Repr): Map[String, Repr]
+
+ def serialize(x: Repr): String
+ def serialize(x: Repr, writer: Writer): Unit
+
+ def deserialize(str: String): Repr
+ def deserialize(reader: Reader): Repr
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONDeserializer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONDeserializer.scala
new file mode 100644
index 0000000..e854e9a
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONDeserializer.scala
@@ -0,0 +1,30 @@
+package scala.scalajs.tools.json
+
+trait JSONDeserializer[T] {
+ def deserialize(x: JSON): T
+}
+
+object JSONDeserializer {
+
+ implicit object stringJSON extends JSONDeserializer[String] {
+ def deserialize(x: JSON): String = Impl.toString(x)
+ }
+
+ implicit object intJSON extends JSONDeserializer[Int] {
+ def deserialize(x: JSON): Int = Impl.toNumber(x).intValue()
+ }
+
+ implicit object booleanJSON extends JSONDeserializer[Boolean] {
+ def deserialize(x: JSON): Boolean = Impl.toBoolean(x)
+ }
+
+ implicit def listJSON[T : JSONDeserializer] = new JSONDeserializer[List[T]] {
+ def deserialize(x: JSON): List[T] = Impl.toList(x).map(fromJSON[T] _)
+ }
+
+ implicit def mapJSON[V : JSONDeserializer] = new JSONDeserializer[Map[String, V]] {
+ def deserialize(x: JSON): Map[String, V] =
+ Impl.toMap(x).mapValues(fromJSON[V] _)
+ }
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjBuilder.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjBuilder.scala
new file mode 100644
index 0000000..dd98f49
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjBuilder.scala
@@ -0,0 +1,20 @@
+package scala.scalajs.tools.json
+
+import scala.collection.mutable
+
+class JSONObjBuilder {
+
+ private val flds = mutable.Map.empty[String, JSON]
+
+ def fld[T : JSONSerializer](name: String, v: T): this.type = {
+ flds.put(name, v.toJSON)
+ this
+ }
+
+ def opt[T : JSONSerializer](name: String, v: Option[T]): this.type = {
+ v.foreach(v => flds.put(name, v.toJSON))
+ this
+ }
+
+ def toJSON: JSON = Impl.fromMap(flds.toMap)
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjExtractor.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjExtractor.scala
new file mode 100644
index 0000000..e49f7e4
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONObjExtractor.scala
@@ -0,0 +1,13 @@
+package scala.scalajs.tools.json
+
+import scala.collection.mutable
+
+class JSONObjExtractor(rawData: JSON) {
+ private val data = Impl.toMap(rawData)
+
+ def fld[T : JSONDeserializer](name: String): T =
+ fromJSON[T](data(name))
+
+ def opt[T : JSONDeserializer](name: String): Option[T] =
+ data.get(name).map(fromJSON[T] _)
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONSerializer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONSerializer.scala
new file mode 100644
index 0000000..e26c92a
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/JSONSerializer.scala
@@ -0,0 +1,32 @@
+package scala.scalajs.tools.json
+
+trait JSONSerializer[T] {
+ def serialize(x: T): JSON
+}
+
+object JSONSerializer {
+
+ implicit object stringJSON extends JSONSerializer[String] {
+ def serialize(x: String): JSON = Impl.fromString(x)
+ }
+
+ implicit object intJSON extends JSONSerializer[Int] {
+ def serialize(x: Int): JSON = Impl.fromNumber(x)
+ }
+
+ implicit object booleanJSON extends JSONSerializer[Boolean] {
+ def serialize(x: Boolean): JSON = Impl.fromBoolean(x)
+ }
+
+ implicit def listJSON[T : JSONSerializer] = new JSONSerializer[List[T]] {
+ def serialize(x: List[T]): JSON = Impl.fromList(x.map(_.toJSON))
+ }
+
+ implicit def mapJSON[V : JSONSerializer] = {
+ new JSONSerializer[Map[String, V]] {
+ def serialize(x: Map[String, V]): JSON =
+ Impl.fromMap(x.mapValues(_.toJSON))
+ }
+ }
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/package.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/package.scala
new file mode 100644
index 0000000..551893a
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/json/package.scala
@@ -0,0 +1,26 @@
+package scala.scalajs.tools
+
+import java.io.{Reader, Writer}
+
+/** Some type-class lightweight wrappers around simple-json.
+ *
+ * They allow to write [[xyz.toJSON]] to obtain classes that can be
+ * serialized by simple-json and [[fromJSON[T](xyz)]] to get an
+ * object back.
+ */
+package object json {
+ type JSON = Impl.Repr
+
+ implicit class JSONPimp[T : JSONSerializer](x: T) {
+ def toJSON: JSON = implicitly[JSONSerializer[T]].serialize(x)
+ }
+
+ def fromJSON[T](x: JSON)(implicit d: JSONDeserializer[T]): T =
+ d.deserialize(x)
+
+ def writeJSON(x: JSON, writer: Writer): Unit = Impl.serialize(x, writer)
+ def jsonToString(x: JSON): String = Impl.serialize(x)
+ def readJSON(str: String): JSON = Impl.deserialize(str)
+ def readJSON(reader: Reader): JSON = Impl.deserialize(reader)
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Level.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Level.scala
new file mode 100644
index 0000000..fbbf39d
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Level.scala
@@ -0,0 +1,24 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.logging
+
+import scala.math.Ordered
+
+abstract sealed class Level extends Ordered[Level] { x =>
+ protected val order: Int
+ def compare(y: Level) = x.order - y.order
+}
+
+object Level {
+ case object Error extends Level { protected val order = 4 }
+ case object Warn extends Level { protected val order = 3 }
+ case object Info extends Level { protected val order = 2 }
+ case object Debug extends Level { protected val order = 1 }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Logger.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Logger.scala
new file mode 100644
index 0000000..3664f51
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/Logger.scala
@@ -0,0 +1,25 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.logging
+
+/** Abstract logger for our tools. Designed after sbt's Loggers. */
+trait Logger {
+ def log(level: Level, message: => String): Unit
+ def success(message: => String): Unit
+ def trace(t: => Throwable): Unit
+
+ def error(message: => String): Unit = log(Level.Error, message)
+ def warn(message: => String): Unit = log(Level.Warn, message)
+ def info(message: => String): Unit = log(Level.Info, message)
+ def debug(message: => String): Unit = log(Level.Debug, message)
+
+ def time(title: String, nanos: Long): Unit =
+ debug(s"$title: ${nanos / 1000} us")
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/NullLogger.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/NullLogger.scala
new file mode 100644
index 0000000..0e36f89
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/NullLogger.scala
@@ -0,0 +1,7 @@
+package scala.scalajs.tools.logging
+
+object NullLogger extends Logger {
+ def log(level: Level, message: => String): Unit = {}
+ def success(message: => String): Unit = {}
+ def trace(t: => Throwable): Unit = {}
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/ScalaConsoleLogger.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/ScalaConsoleLogger.scala
new file mode 100644
index 0000000..e2c9efc
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/logging/ScalaConsoleLogger.scala
@@ -0,0 +1,15 @@
+package scala.scalajs.tools.logging
+
+class ScalaConsoleLogger(minLevel: Level = Level.Debug) extends Logger {
+
+ def log(level: Level, message: =>String): Unit = if (level >= minLevel) {
+ if (level == Level.Warn || level == Level.Error)
+ scala.Console.err.println(message)
+ else
+ scala.Console.out.println(message)
+ }
+ def success(message: => String): Unit = info(message)
+ def trace(t: => Throwable): Unit =
+ // This is error level, so no checking
+ t.printStackTrace()
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/Analyzer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/Analyzer.scala
new file mode 100644
index 0000000..9cdd764
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/Analyzer.scala
@@ -0,0 +1,587 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import scala.annotation.tailrec
+
+import scala.collection.mutable
+
+import scala.scalajs.ir
+import ir.{ClassKind, Definitions, Infos}
+
+import scala.scalajs.tools.sem._
+import scala.scalajs.tools.javascript.LongImpl
+import scala.scalajs.tools.logging._
+
+import ScalaJSOptimizer._
+
+class Analyzer(logger0: Logger, semantics: Semantics,
+ allData: Seq[Infos.ClassInfo], globalWarnEnabled: Boolean,
+ isBeforeOptimizer: Boolean) {
+ /* Set this to true to debug the DCE analyzer.
+ * We don't rely on config to disable 'debug' messages because we want
+ * to use 'debug' for displaying more stack trace info that the user can
+ * see with the 'last' command.
+ */
+ val DebugAnalyzer = false
+
+ object logger extends Logger {
+ var indentation: String = ""
+
+ def indent(): Unit = indentation += " "
+ def undent(): Unit = indentation = indentation.substring(2)
+
+ def log(level: Level, message: => String) =
+ logger0.log(level, indentation+message)
+ def success(message: => String) =
+ logger0.success(indentation+message)
+ def trace(t: => Throwable) =
+ logger0.trace(t)
+
+ def indented[A](body: => A): A = {
+ indent()
+ try body
+ finally undent()
+ }
+
+ def debugIndent[A](message: => String)(body: => A): A = {
+ if (DebugAnalyzer) {
+ debug(message)
+ indented(body)
+ } else {
+ body
+ }
+ }
+
+ def temporarilyNotIndented[A](body: => A): A = {
+ val savedIndent = indentation
+ indentation = ""
+ try body
+ finally indentation = savedIndent
+ }
+ }
+
+ sealed trait From
+ case class FromMethod(methodInfo: MethodInfo) extends From
+ case object FromCore extends From
+ case object FromExports extends From
+ case object FromManual extends From
+
+ var allAvailable: Boolean = true
+
+ val classInfos: mutable.Map[String, ClassInfo] = {
+ val cs = for (classData <- allData)
+ yield (classData.encodedName, new ClassInfo(classData))
+ mutable.Map.empty[String, ClassInfo] ++ cs
+ }
+
+ def lookupClass(encodedName: String): ClassInfo = {
+ classInfos.get(encodedName) match {
+ case Some(info) => info
+ case None =>
+ val c = new ClassInfo(createMissingClassInfo(encodedName))
+ classInfos += encodedName -> c
+ c.nonExistent = true
+ c.linkClasses()
+ c
+ }
+ }
+
+ def lookupModule(encodedName: String): ClassInfo = {
+ lookupClass(encodedName+"$")
+ }
+
+ linkClasses()
+
+ def linkClasses(): Unit = {
+ if (!classInfos.contains(ir.Definitions.ObjectClass))
+ sys.error("Fatal error: could not find java.lang.Object on the classpath")
+ for (classInfo <- classInfos.values.toList)
+ classInfo.linkClasses()
+ }
+
+ def computeReachability(manuallyReachable: Seq[ManualReachability],
+ noWarnMissing: Seq[NoWarnMissing]): Unit = {
+ // Stuff reachable from core symbols always should warn
+ reachCoreSymbols()
+
+ // Disable warnings as requested
+ noWarnMissing.foreach(disableWarning _)
+
+ // Reach all user stuff
+ manuallyReachable.foreach(reachManually _)
+ for (classInfo <- classInfos.values)
+ classInfo.reachExports()
+ }
+
+ /** Reach symbols used directly by scalajsenv.js. */
+ def reachCoreSymbols(): Unit = {
+ import semantics._
+ import CheckedBehavior._
+
+ implicit val from = FromCore
+
+ def instantiateClassWith(className: String, constructor: String): ClassInfo = {
+ val info = lookupClass(className)
+ info.instantiated()
+ info.callMethod(constructor)
+ info
+ }
+
+ val ObjectClass = instantiateClassWith("O", "init___")
+ ObjectClass.callMethod("toString__T")
+ ObjectClass.callMethod("equals__O__Z")
+
+ instantiateClassWith("jl_NullPointerException", "init___")
+
+ if (asInstanceOfs != Unchecked)
+ instantiateClassWith("jl_ClassCastException", "init___T")
+
+ if (asInstanceOfs == Fatal)
+ instantiateClassWith("sjsr_UndefinedBehaviorError", "init___jl_Throwable")
+
+ instantiateClassWith("jl_Class", "init___jl_ScalaJSClassData")
+
+ val RTStringModuleClass = lookupClass("sjsr_RuntimeString$")
+ RTStringModuleClass.accessModule()
+ RTStringModuleClass.callMethod("hashCode__T__I")
+
+ val RTLongClass = lookupClass(LongImpl.RuntimeLongClass)
+ RTLongClass.instantiated()
+ for (method <- LongImpl.AllConstructors ++ LongImpl.AllMethods)
+ RTLongClass.callMethod(method)
+
+ if (isBeforeOptimizer) {
+ for (method <- LongImpl.AllIntrinsicMethods)
+ RTLongClass.callMethod(method)
+ }
+
+ val RTLongModuleClass = lookupClass(LongImpl.RuntimeLongModuleClass)
+ RTLongModuleClass.accessModule()
+ for (method <- LongImpl.AllModuleMethods)
+ RTLongModuleClass.callMethod(method)
+
+ if (isBeforeOptimizer) {
+ for (hijacked <- Definitions.HijackedClasses)
+ lookupClass(hijacked).instantiated()
+ } else {
+ for (hijacked <- Definitions.HijackedClasses)
+ lookupClass(hijacked).accessData()
+ }
+
+ if (semantics.strictFloats) {
+ val RuntimePackage = lookupClass("sjsr_package$")
+ RuntimePackage.accessModule()
+ RuntimePackage.callMethod("froundPolyfill__D__D")
+ }
+
+ val BitsModuleClass = lookupClass("sjsr_Bits$")
+ BitsModuleClass.accessModule()
+ BitsModuleClass.callMethod("numberHashCode__D__I")
+ }
+
+ def reachManually(info: ManualReachability) = {
+ implicit val from = FromManual
+
+ // Don't lookupClass here, since we don't want to create any
+ // symbols. If a symbol doesn't exist, we fail.
+ info match {
+ case ReachObject(name) => classInfos(name + "$").accessModule()
+ case Instantiate(name) => classInfos(name).instantiated()
+ case ReachMethod(className, methodName, static) =>
+ classInfos(className).callMethod(methodName, static)
+ }
+ }
+
+ def disableWarning(noWarn: NoWarnMissing) = noWarn match {
+ case NoWarnClass(className) =>
+ lookupClass(className).warnEnabled = false
+ case NoWarnMethod(className, methodName) =>
+ lookupClass(className).lookupMethod(methodName).warnEnabled = false
+ }
+
+ class ClassInfo(data: Infos.ClassInfo) {
+ val encodedName = data.encodedName
+ val ancestorCount = data.ancestorCount
+ val isStaticModule = data.kind == ClassKind.ModuleClass
+ val isInterface = data.kind == ClassKind.Interface
+ val isImplClass = data.kind == ClassKind.TraitImpl
+ val isRawJSType = data.kind == ClassKind.RawJSType
+ val isHijackedClass = data.kind == ClassKind.HijackedClass
+ val isClass = !isInterface && !isImplClass && !isRawJSType
+ val isExported = data.isExported
+
+ val hasData = !isImplClass
+ val hasMoreThanData = isClass && !isHijackedClass
+
+ var superClass: ClassInfo = _
+ val ancestors = mutable.ListBuffer.empty[ClassInfo]
+ val descendants = mutable.ListBuffer.empty[ClassInfo]
+
+ var nonExistent: Boolean = false
+ var warnEnabled: Boolean = true
+
+ def linkClasses(): Unit = {
+ if (data.superClass != "")
+ superClass = lookupClass(data.superClass)
+ ancestors ++= data.ancestors.map(lookupClass)
+ for (ancestor <- ancestors)
+ ancestor.descendants += this
+ }
+
+ lazy val descendentClasses = descendants.filter(_.isClass)
+
+ def optimizerHints: Infos.OptimizerHints = data.optimizerHints
+
+ var isInstantiated: Boolean = false
+ var isAnySubclassInstantiated: Boolean = false
+ var isModuleAccessed: Boolean = false
+ var isDataAccessed: Boolean = false
+
+ var instantiatedFrom: Option[From] = None
+
+ val delayedCalls = mutable.Map.empty[String, From]
+
+ def isNeededAtAll =
+ isDataAccessed ||
+ isAnySubclassInstantiated ||
+ (isImplClass && methodInfos.values.exists(_.isReachable))
+
+ lazy val methodInfos: mutable.Map[String, MethodInfo] = {
+ val ms = for (methodData <- data.methods)
+ yield (methodData.encodedName, new MethodInfo(this, methodData))
+ mutable.Map.empty[String, MethodInfo] ++ ms
+ }
+
+ def lookupMethod(methodName: String): MethodInfo = {
+ tryLookupMethod(methodName).getOrElse {
+ val syntheticData = createMissingMethodInfo(methodName)
+ val m = new MethodInfo(this, syntheticData)
+ m.nonExistent = true
+ methodInfos += methodName -> m
+ m
+ }
+ }
+
+ def tryLookupMethod(methodName: String): Option[MethodInfo] = {
+ assert(isClass || isImplClass,
+ s"Cannot call lookupMethod($methodName) on non-class $this")
+ @tailrec
+ def loop(ancestorInfo: ClassInfo): Option[MethodInfo] = {
+ if (ancestorInfo ne null) {
+ ancestorInfo.methodInfos.get(methodName) match {
+ case Some(m) if !m.isAbstract => Some(m)
+ case _ => loop(ancestorInfo.superClass)
+ }
+ } else {
+ None
+ }
+ }
+ loop(this)
+ }
+
+ override def toString(): String = encodedName
+
+ /** Start reachability algorithm with the exports for that class. */
+ def reachExports(): Unit = {
+ implicit val from = FromExports
+
+ // Myself
+ if (isExported) {
+ assert(!isImplClass, "An implementation class must not be exported")
+ if (isStaticModule) accessModule()
+ else instantiated()
+ }
+
+ // My methods
+ for (methodInfo <- methodInfos.values) {
+ if (methodInfo.isExported)
+ callMethod(methodInfo.encodedName)
+ }
+ }
+
+ def accessModule()(implicit from: From): Unit = {
+ assert(isStaticModule, s"Cannot call accessModule() on non-module $this")
+ if (!isModuleAccessed) {
+ logger.debugIndent(s"$this.isModuleAccessed = true") {
+ isModuleAccessed = true
+ instantiated()
+ callMethod("init___")
+ }
+ }
+ }
+
+ def instantiated()(implicit from: From): Unit = {
+ if (!isInstantiated && isClass) {
+ logger.debugIndent(s"$this.isInstantiated = true") {
+ isInstantiated = true
+ instantiatedFrom = Some(from)
+ ancestors.foreach(_.subclassInstantiated())
+ }
+
+ for ((methodName, from) <- delayedCalls)
+ delayedCallMethod(methodName)(from)
+ }
+ }
+
+ private def subclassInstantiated()(implicit from: From): Unit = {
+ if (!isAnySubclassInstantiated && isClass) {
+ logger.debugIndent(s"$this.isAnySubclassInstantiated = true") {
+ isAnySubclassInstantiated = true
+ if (instantiatedFrom.isEmpty)
+ instantiatedFrom = Some(from)
+ accessData()
+ methodInfos.get("__init__").foreach(_.reachStatic())
+ }
+ }
+ }
+
+ def accessData()(implicit from: From): Unit = {
+ if (!isDataAccessed && hasData) {
+ checkExistent()
+ if (DebugAnalyzer)
+ logger.debug(s"$this.isDataAccessed = true")
+ isDataAccessed = true
+ }
+ }
+
+ def checkExistent()(implicit from: From): Unit = {
+ if (nonExistent) {
+ if (warnEnabled && globalWarnEnabled) {
+ logger.warn(s"Referring to non-existent class $encodedName")
+ warnCallStack()
+ }
+ nonExistent = false
+ allAvailable = false
+ }
+ }
+
+ def callMethod(methodName: String, static: Boolean = false)(
+ implicit from: From): Unit = {
+ logger.debugIndent(s"calling${if (static) " static" else ""} $this.$methodName") {
+ if (isImplClass) {
+ // methods in impl classes are always implicitly called statically
+ lookupMethod(methodName).reachStatic()
+ } else if (isConstructorName(methodName)) {
+ // constructors are always implicitly called statically
+ lookupMethod(methodName).reachStatic()
+ } else if (static) {
+ assert(!isReflProxyName(methodName),
+ s"Trying to call statically refl proxy $this.$methodName")
+ lookupMethod(methodName).reachStatic()
+ } else {
+ for (descendentClass <- descendentClasses) {
+ if (descendentClass.isInstantiated)
+ descendentClass.delayedCallMethod(methodName)
+ else
+ descendentClass.delayedCalls += ((methodName, from))
+ }
+ }
+ }
+ }
+
+ private def delayedCallMethod(methodName: String)(implicit from: From): Unit = {
+ if (isReflProxyName(methodName)) {
+ tryLookupMethod(methodName).foreach(_.reach(this))
+ } else {
+ lookupMethod(methodName).reach(this)
+ }
+ }
+ }
+
+ class MethodInfo(val owner: ClassInfo, data: Infos.MethodInfo) {
+
+ val encodedName = data.encodedName
+ val isAbstract = data.isAbstract
+ val isExported = data.isExported
+ val isReflProxy = isReflProxyName(encodedName)
+
+ def optimizerHints: Infos.OptimizerHints = data.optimizerHints
+
+ var isReachable: Boolean = false
+
+ var calledFrom: Option[From] = None
+ var instantiatedSubclass: Option[ClassInfo] = None
+
+ var nonExistent: Boolean = false
+ var warnEnabled: Boolean = true
+
+ override def toString(): String = s"$owner.$encodedName"
+
+ def reachStatic()(implicit from: From): Unit = {
+ assert(!isAbstract,
+ s"Trying to reach statically the abstract method $this")
+
+ checkExistent()
+
+ if (!isReachable) {
+ logger.debugIndent(s"$this.isReachable = true") {
+ isReachable = true
+ calledFrom = Some(from)
+ doReach()
+ }
+ }
+ }
+
+ def reach(inClass: ClassInfo)(implicit from: From): Unit = {
+ assert(owner.isClass,
+ s"Trying to reach dynamically the non-class method $this")
+ assert(!isConstructorName(encodedName),
+ s"Trying to reach dynamically the constructor $this")
+
+ checkExistent()
+
+ if (!isReachable) {
+ logger.debugIndent(s"$this.isReachable = true") {
+ isReachable = true
+ calledFrom = Some(from)
+ instantiatedSubclass = Some(inClass)
+ doReach()
+ }
+ }
+ }
+
+ private def checkExistent()(implicit from: From) = {
+ if (nonExistent) {
+ if (warnEnabled && owner.warnEnabled && globalWarnEnabled) {
+ logger.temporarilyNotIndented {
+ logger.warn(s"Referring to non-existent method $this")
+ warnCallStack()
+ }
+ }
+ allAvailable = false
+ }
+ }
+
+ private[this] def doReach(): Unit = {
+ logger.debugIndent(s"$this.doReach()") {
+ implicit val from = FromMethod(this)
+
+ if (owner.isImplClass)
+ owner.checkExistent()
+
+ for (moduleName <- data.accessedModules) {
+ lookupModule(moduleName).accessModule()
+ }
+
+ for (className <- data.instantiatedClasses) {
+ lookupClass(className).instantiated()
+ }
+
+ for (className <- data.accessedClassData) {
+ lookupClass(className).accessData()
+ }
+
+ for ((className, methods) <- data.calledMethods) {
+ val classInfo = lookupClass(className)
+ for (methodName <- methods)
+ classInfo.callMethod(methodName)
+ }
+
+ for ((className, methods) <- data.calledMethodsStatic) {
+ val classInfo = lookupClass(className)
+ for (methodName <- methods)
+ classInfo.callMethod(methodName, static = true)
+ }
+ }
+ }
+ }
+
+ def isReflProxyName(encodedName: String): Boolean = {
+ encodedName.endsWith("__") &&
+ (encodedName != "init___") && (encodedName != "__init__")
+ }
+
+ def isConstructorName(encodedName: String): Boolean =
+ encodedName.startsWith("init___") || (encodedName == "__init__")
+
+ private def createMissingClassInfo(encodedName: String): Infos.ClassInfo = {
+ val kind =
+ if (encodedName.endsWith("$")) ClassKind.ModuleClass
+ else if (encodedName.endsWith("$class")) ClassKind.TraitImpl
+ else ClassKind.Class
+ Infos.ClassInfo(
+ name = s"<$encodedName>",
+ encodedName = encodedName,
+ isExported = false,
+ ancestorCount = if (kind.isClass) 1 else 0,
+ kind = kind,
+ superClass = if (kind.isClass) "O" else "",
+ ancestors = List(encodedName, "O"),
+ methods = List(
+ createMissingMethodInfo("__init__"),
+ createMissingMethodInfo("init___"))
+ )
+ }
+
+ private def createMissingMethodInfo(encodedName: String,
+ isAbstract: Boolean = false): Infos.MethodInfo = {
+ Infos.MethodInfo(encodedName = encodedName, isAbstract = isAbstract)
+ }
+
+ def warnCallStack()(implicit from: From): Unit = {
+ val seenInfos = mutable.Set.empty[AnyRef]
+
+ def rec(level: Level, optFrom: Option[From],
+ verb: String = "called"): Unit = {
+ val involvedClasses = new mutable.ListBuffer[ClassInfo]
+
+ def onlyOnce(info: AnyRef): Boolean = {
+ if (seenInfos.add(info)) {
+ true
+ } else {
+ logger.log(level, " (already seen, not repeating call stack)")
+ false
+ }
+ }
+
+ @tailrec
+ def loopTrace(optFrom: Option[From], verb: String = "called"): Unit = {
+ optFrom match {
+ case None =>
+ logger.log(level, s"$verb from ... er ... nowhere!? (this is a bug in dce)")
+ case Some(from) =>
+ from match {
+ case FromMethod(methodInfo) =>
+ logger.log(level, s"$verb from $methodInfo")
+ if (onlyOnce(methodInfo)) {
+ methodInfo.instantiatedSubclass.foreach(involvedClasses += _)
+ loopTrace(methodInfo.calledFrom)
+ }
+ case FromCore =>
+ logger.log(level, s"$verb from scalajs-corejslib.js")
+ case FromExports =>
+ logger.log(level, "exported to JavaScript with @JSExport")
+ case FromManual =>
+ logger.log(level, "manually made reachable")
+ }
+ }
+ }
+
+ logger.indented {
+ loopTrace(optFrom, verb = verb)
+ }
+
+ if (involvedClasses.nonEmpty) {
+ logger.log(level, "involving instantiated classes:")
+ logger.indented {
+ for (classInfo <- involvedClasses.result().distinct) {
+ logger.log(level, s"$classInfo")
+ if (onlyOnce(classInfo))
+ rec(Level.Debug, classInfo.instantiatedFrom, verb = "instantiated")
+ // recurse with Debug log level not to overwhelm the user
+ }
+ }
+ }
+ }
+
+ rec(Level.Warn, Some(from))
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/GenIncOptimizer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/GenIncOptimizer.scala
new file mode 100644
index 0000000..47e1f87
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/GenIncOptimizer.scala
@@ -0,0 +1,921 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import language.higherKinds
+
+import scala.annotation.{switch, tailrec}
+
+import scala.collection.{GenMap, GenTraversableOnce, GenIterable, GenIterableLike}
+import scala.collection.mutable
+
+import scala.scalajs.ir._
+import Definitions.isConstructorName
+import Infos.OptimizerHints
+import Trees._
+import Types._
+
+import scala.scalajs.tools.sem._
+
+import scala.scalajs.tools.javascript
+import javascript.Trees.{Tree => JSTree}
+import javascript.ScalaJSClassEmitter
+
+import scala.scalajs.tools.logging._
+
+/** Incremental optimizer.
+ * An incremental optimizer consumes the reachability analysis produced by
+ * an [[Analyzer]], as well as trees for classes, trait impls, etc., and
+ * optimizes them in an incremental way.
+ * It maintains state between runs to do a minimal amount of work on every
+ * run, based on detecting what parts of the program must be re-optimized,
+ * and keeping optimized results from previous runs for the rest.
+ */
+abstract class GenIncOptimizer(semantics: Semantics) {
+ import GenIncOptimizer._
+
+ protected val CollOps: AbsCollOps
+
+ private val classEmitter = new ScalaJSClassEmitter(semantics)
+
+ private var logger: Logger = _
+
+ /** Are we in batch mode? I.e., are we running from scratch?
+ * Various parts of the algorithm can be skipped entirely when running in
+ * batch mode.
+ */
+ private var batchMode: Boolean = false
+
+ /** Should positions be considered when comparing tree hashes */
+ private var considerPositions: Boolean = _
+
+ private var objectClass: Class = _
+ private val classes = CollOps.emptyMap[String, Class]
+ private val traitImpls = CollOps.emptyParMap[String, TraitImpl]
+
+ protected def getInterface(encodedName: String): InterfaceType
+
+ /** Schedule a method for processing in the PROCESS PASS */
+ protected def scheduleMethod(method: MethodImpl): Unit
+
+ protected def newMethodImpl(owner: MethodContainer,
+ encodedName: String): MethodImpl
+
+ def findTraitImpl(encodedName: String): TraitImpl = traitImpls(encodedName)
+ def findClass(encodedName: String): Class = classes(encodedName)
+
+ def getTraitImpl(encodedName: String): Option[TraitImpl] = traitImpls.get(encodedName)
+ def getClass(encodedName: String): Option[Class] = classes.get(encodedName)
+
+ type GetClassTreeIfChanged =
+ (String, Option[String]) => Option[(ClassDef, Option[String])]
+
+ private def withLogger[A](logger: Logger)(body: => A): A = {
+ assert(this.logger == null)
+ this.logger = logger
+ try body
+ finally this.logger = null
+ }
+
+ /** Update the incremental analyzer with a new run. */
+ def update(analyzer: Analyzer,
+ getClassTreeIfChanged: GetClassTreeIfChanged, considerPositions: Boolean,
+ logger: Logger): Unit = withLogger(logger) {
+
+ batchMode = objectClass == null
+ this.considerPositions = considerPositions
+ logger.debug(s"Optimizer batch mode: $batchMode")
+
+ logTime(logger, "Incremental part of inc. optimizer") {
+ /* UPDATE PASS */
+ updateAndTagEverything(analyzer, getClassTreeIfChanged)
+ }
+
+ logTime(logger, "Optimizer part of inc. optimizer") {
+ /* PROCESS PASS */
+ processAllTaggedMethods()
+ }
+ }
+
+ /** Incremental part: update state and detect what needs to be re-optimized.
+ * UPDATE PASS ONLY. (This IS the update pass).
+ */
+ private def updateAndTagEverything(analyzer: Analyzer,
+ getClassTreeIfChanged: GetClassTreeIfChanged): Unit = {
+
+ val neededClasses = CollOps.emptyParMap[String, analyzer.ClassInfo]
+ val neededTraitImpls = CollOps.emptyParMap[String, analyzer.ClassInfo]
+ for {
+ classInfo <- analyzer.classInfos.values
+ if classInfo.isNeededAtAll
+ } {
+ if (classInfo.isClass && classInfo.isAnySubclassInstantiated)
+ CollOps.put(neededClasses, classInfo.encodedName, classInfo)
+ else if (classInfo.isImplClass)
+ CollOps.put(neededTraitImpls, classInfo.encodedName, classInfo)
+ }
+
+ /* Remove deleted trait impls, and update existing trait impls.
+ * We don't even have to notify callers in case of additions or removals
+ * because callers have got to be invalidated by themselves.
+ * Only changed methods need to trigger notifications.
+ *
+ * Non-batch mode only.
+ */
+ assert(!batchMode || traitImpls.isEmpty)
+ if (!batchMode) {
+ CollOps.retain(traitImpls) { (traitImplName, traitImpl) =>
+ CollOps.remove(neededTraitImpls, traitImplName).fold {
+ /* Deleted trait impl. Mark all its methods as deleted, and remove it
+ * from known trait impls.
+ */
+ traitImpl.methods.values.foreach(_.delete())
+
+ false
+ } { traitImplInfo =>
+ /* Existing trait impl. Update it. */
+ val (added, changed, removed) =
+ traitImpl.updateWith(traitImplInfo, getClassTreeIfChanged)
+ for (method <- changed)
+ traitImpl.myInterface.tagStaticCallersOf(method)
+
+ true
+ }
+ }
+ }
+
+ /* Add new trait impls.
+ * Easy, we don't have to notify anyone.
+ */
+ for (traitImplInfo <- neededTraitImpls.values) {
+ val traitImpl = new TraitImpl(traitImplInfo.encodedName)
+ CollOps.put(traitImpls, traitImpl.encodedName, traitImpl)
+ traitImpl.updateWith(traitImplInfo, getClassTreeIfChanged)
+ }
+
+ if (!batchMode) {
+ /* Class removals:
+ * * If a class is deleted or moved, delete its entire subtree (because
+ * all its descendants must also be deleted or moved).
+ * * If an existing class was instantiated but is no more, notify callers
+ * of its methods.
+ *
+ * Non-batch mode only.
+ */
+ val objectClassStillExists =
+ objectClass.walkClassesForDeletions(neededClasses.get(_))
+ assert(objectClassStillExists, "Uh oh, java.lang.Object was deleted!")
+
+ /* Class changes:
+ * * Delete removed methods, update existing ones, add new ones
+ * * Update the list of ancestors
+ * * Class newly instantiated
+ *
+ * Non-batch mode only.
+ */
+ objectClass.walkForChanges(
+ CollOps.remove(neededClasses, _).get,
+ getClassTreeIfChanged,
+ Set.empty)
+ }
+
+ /* Class additions:
+ * * Add new classes (including those that have moved from elsewhere).
+ * In batch mode, we avoid doing notifications.
+ */
+
+ // Group children by (immediate) parent
+ val newChildrenByParent = CollOps.emptyAccMap[String, Analyzer#ClassInfo]
+
+ for (classInfo <- neededClasses.values) {
+ val superInfo = classInfo.superClass
+ if (superInfo == null) {
+ assert(batchMode, "Trying to add java.lang.Object in incremental mode")
+ objectClass = new Class(None, classInfo.encodedName)
+ classes += classInfo.encodedName -> objectClass
+ objectClass.setupAfterCreation(classInfo, getClassTreeIfChanged)
+ } else {
+ CollOps.acc(newChildrenByParent, superInfo.encodedName, classInfo)
+ }
+ }
+
+ val getNewChildren =
+ (name: String) => CollOps.getAcc(newChildrenByParent, name)
+
+ // Walk the tree to add children
+ if (batchMode) {
+ objectClass.walkForAdditions(getNewChildren, getClassTreeIfChanged)
+ } else {
+ val existingParents =
+ CollOps.parFlatMapKeys(newChildrenByParent)(classes.get)
+ for (parent <- existingParents)
+ parent.walkForAdditions(getNewChildren, getClassTreeIfChanged)
+ }
+
+ }
+
+ /** Optimizer part: process all methods that need reoptimizing.
+ * PROCESS PASS ONLY. (This IS the process pass).
+ */
+ protected def processAllTaggedMethods(): Unit
+
+ protected def logProcessingMethods(count: Int): Unit =
+ logger.debug(s"Optimizing $count methods.")
+
+ /** Base class for [[Class]] and [[TraitImpl]]. */
+ abstract class MethodContainer(val encodedName: String) {
+ def thisType: Type
+
+ val myInterface = getInterface(encodedName)
+
+ val methods = mutable.Map.empty[String, MethodImpl]
+
+ var lastVersion: Option[String] = None
+
+ private def reachableMethodsOf(info: Analyzer#ClassInfo): Set[String] = {
+ (for {
+ methodInfo <- info.methodInfos.values
+ if methodInfo.isReachable && !methodInfo.isAbstract
+ } yield {
+ methodInfo.encodedName
+ }).toSet
+ }
+
+ /** UPDATE PASS ONLY. Global concurrency safe but not on same instance */
+ def updateWith(info: Analyzer#ClassInfo,
+ getClassTreeIfChanged: GetClassTreeIfChanged): (Set[String], Set[String], Set[String]) = {
+ myInterface.ancestors = info.ancestors.map(_.encodedName).toList
+
+ val addedMethods = Set.newBuilder[String]
+ val changedMethods = Set.newBuilder[String]
+ val deletedMethods = Set.newBuilder[String]
+
+ val reachableMethods = reachableMethodsOf(info)
+ val methodSetChanged = methods.keySet != reachableMethods
+ if (methodSetChanged) {
+ // Remove deleted methods
+ methods retain { (methodName, method) =>
+ if (reachableMethods.contains(methodName)) {
+ true
+ } else {
+ deletedMethods += methodName
+ method.delete()
+ false
+ }
+ }
+ // Clear lastVersion if there are new methods
+ if (reachableMethods.exists(!methods.contains(_)))
+ lastVersion = None
+ }
+ for ((tree, version) <- getClassTreeIfChanged(encodedName, lastVersion)) {
+ lastVersion = version
+ this match {
+ case cls: Class =>
+ cls.isModuleClass = tree.kind == ClassKind.ModuleClass
+ cls.fields = for (field @ VarDef(_, _, _, _) <- tree.defs) yield field
+ case _ =>
+ }
+ tree.defs.foreach {
+ case methodDef: MethodDef if methodDef.name.isInstanceOf[Ident] &&
+ reachableMethods.contains(methodDef.name.name) =>
+ val methodName = methodDef.name.name
+
+ val methodInfo = info.methodInfos(methodName)
+ methods.get(methodName).fold {
+ addedMethods += methodName
+ val method = newMethodImpl(this, methodName)
+ method.updateWith(methodInfo, methodDef)
+ methods(methodName) = method
+ method
+ } { method =>
+ if (method.updateWith(methodInfo, methodDef))
+ changedMethods += methodName
+ method
+ }
+
+ case _ => // ignore
+ }
+ }
+
+ (addedMethods.result(), changedMethods.result(), deletedMethods.result())
+ }
+ }
+
+ /** Class in the class hierarchy (not an interface).
+ * A class may be a module class.
+ * A class knows its superclass and the interfaces it implements. It also
+ * maintains a list of its direct subclasses, so that the instances of
+ * [[Class]] form a tree of the class hierarchy.
+ */
+ class Class(val superClass: Option[Class],
+ _encodedName: String) extends MethodContainer(_encodedName) {
+ if (encodedName == Definitions.ObjectClass) {
+ assert(superClass.isEmpty)
+ assert(objectClass == null)
+ } else {
+ assert(superClass.isDefined)
+ }
+
+ /** Parent chain from this to Object. */
+ val parentChain: List[Class] =
+ this :: superClass.fold[List[Class]](Nil)(_.parentChain)
+
+ /** Reverse parent chain from Object to this. */
+ val reverseParentChain: List[Class] =
+ parentChain.reverse
+
+ def thisType: Type = ClassType(encodedName)
+
+ var interfaces: Set[InterfaceType] = Set.empty
+ var subclasses: CollOps.ParIterable[Class] = CollOps.emptyParIterable
+ var isInstantiated: Boolean = false
+
+ var isModuleClass: Boolean = false
+ var hasElidableModuleAccessor: Boolean = false
+
+ var fields: List[VarDef] = Nil
+ var isInlineable: Boolean = false
+ var tryNewInlineable: Option[RecordValue] = None
+
+ override def toString(): String =
+ encodedName
+
+ /** Walk the class hierarchy tree for deletions.
+ * This includes "deleting" classes that were previously instantiated but
+ * are no more.
+ * UPDATE PASS ONLY. Not concurrency safe on same instance.
+ */
+ def walkClassesForDeletions(
+ getClassInfoIfNeeded: String => Option[Analyzer#ClassInfo]): Boolean = {
+ def sameSuperClass(info: Analyzer#ClassInfo): Boolean =
+ if (info.superClass == null) superClass.isEmpty
+ else superClass.exists(_.encodedName == info.superClass.encodedName)
+
+ getClassInfoIfNeeded(encodedName) match {
+ case Some(classInfo) if sameSuperClass(classInfo) =>
+ // Class still exists. Recurse.
+ subclasses = subclasses.filter(
+ _.walkClassesForDeletions(getClassInfoIfNeeded))
+ if (isInstantiated && !classInfo.isInstantiated)
+ notInstantiatedAnymore()
+ true
+ case _ =>
+ // Class does not exist or has been moved. Delete the entire subtree.
+ deleteSubtree()
+ false
+ }
+ }
+
+ /** Delete this class and all its subclasses. UPDATE PASS ONLY. */
+ def deleteSubtree(): Unit = {
+ delete()
+ for (subclass <- subclasses)
+ subclass.deleteSubtree()
+ }
+
+ /** UPDATE PASS ONLY. */
+ private def delete(): Unit = {
+ if (isInstantiated)
+ notInstantiatedAnymore()
+ for (method <- methods.values)
+ method.delete()
+ classes -= encodedName
+ /* Note: no need to tag methods that call *statically* one of the methods
+ * of the deleted classes, since they've got to be invalidated by
+ * themselves.
+ */
+ }
+
+ /** UPDATE PASS ONLY. */
+ def notInstantiatedAnymore(): Unit = {
+ assert(isInstantiated)
+ isInstantiated = false
+ for (intf <- interfaces) {
+ intf.removeInstantiatedSubclass(this)
+ for (methodName <- allMethods().keys)
+ intf.tagDynamicCallersOf(methodName)
+ }
+ }
+
+ /** UPDATE PASS ONLY. */
+ def walkForChanges(
+ getClassInfo: String => Analyzer#ClassInfo,
+ getClassTreeIfChanged: GetClassTreeIfChanged,
+ parentMethodAttributeChanges: Set[String]): Unit = {
+
+ val classInfo = getClassInfo(encodedName)
+
+ val (addedMethods, changedMethods, deletedMethods) =
+ updateWith(classInfo, getClassTreeIfChanged)
+
+ val oldInterfaces = interfaces
+ val newInterfaces =
+ classInfo.ancestors.map(info => getInterface(info.encodedName)).toSet
+ interfaces = newInterfaces
+
+ val methodAttributeChanges =
+ (parentMethodAttributeChanges -- methods.keys ++
+ addedMethods ++ changedMethods ++ deletedMethods)
+
+ // Tag callers with dynamic calls
+ val wasInstantiated = isInstantiated
+ isInstantiated = classInfo.isInstantiated
+ assert(!(wasInstantiated && !isInstantiated),
+ "(wasInstantiated && !isInstantiated) should have been handled "+
+ "during deletion phase")
+
+ if (isInstantiated) {
+ if (wasInstantiated) {
+ val existingInterfaces = oldInterfaces.intersect(newInterfaces)
+ for {
+ intf <- existingInterfaces
+ methodName <- methodAttributeChanges
+ } {
+ intf.tagDynamicCallersOf(methodName)
+ }
+ if (newInterfaces.size != oldInterfaces.size ||
+ newInterfaces.size != existingInterfaces.size) {
+ val allMethodNames = allMethods().keys
+ for {
+ intf <- oldInterfaces ++ newInterfaces -- existingInterfaces
+ methodName <- allMethodNames
+ } {
+ intf.tagDynamicCallersOf(methodName)
+ }
+ }
+ } else {
+ val allMethodNames = allMethods().keys
+ for (intf <- interfaces) {
+ intf.addInstantiatedSubclass(this)
+ for (methodName <- allMethodNames)
+ intf.tagDynamicCallersOf(methodName)
+ }
+ }
+ }
+
+ // Tag callers with static calls
+ for (methodName <- methodAttributeChanges)
+ myInterface.tagStaticCallersOf(methodName)
+
+ // Module class specifics
+ updateHasElidableModuleAccessor()
+
+ // Inlineable class
+ if (updateIsInlineable(classInfo)) {
+ for (method <- methods.values; if isConstructorName(method.encodedName))
+ myInterface.tagStaticCallersOf(method.encodedName)
+ }
+
+ // Recurse in subclasses
+ for (cls <- subclasses)
+ cls.walkForChanges(getClassInfo, getClassTreeIfChanged,
+ methodAttributeChanges)
+ }
+
+ /** UPDATE PASS ONLY. */
+ def walkForAdditions(
+ getNewChildren: String => GenIterable[Analyzer#ClassInfo],
+ getClassTreeIfChanged: GetClassTreeIfChanged): Unit = {
+
+ val subclassAcc = CollOps.prepAdd(subclasses)
+
+ for (classInfo <- getNewChildren(encodedName)) {
+ val cls = new Class(Some(this), classInfo.encodedName)
+ CollOps.add(subclassAcc, cls)
+ classes += classInfo.encodedName -> cls
+ cls.setupAfterCreation(classInfo, getClassTreeIfChanged)
+ cls.walkForAdditions(getNewChildren, getClassTreeIfChanged)
+ }
+
+ subclasses = CollOps.finishAdd(subclassAcc)
+ }
+
+ /** UPDATE PASS ONLY. */
+ def updateHasElidableModuleAccessor(): Unit = {
+ hasElidableModuleAccessor =
+ isAdHocElidableModuleAccessor(encodedName) ||
+ (isModuleClass && lookupMethod("init___").exists(isElidableModuleConstructor))
+ }
+
+ /** UPDATE PASS ONLY. */
+ def updateIsInlineable(classInfo: Analyzer#ClassInfo): Boolean = {
+ val oldTryNewInlineable = tryNewInlineable
+ isInlineable = classInfo.optimizerHints.hasInlineAnnot
+ if (!isInlineable) {
+ tryNewInlineable = None
+ } else {
+ val allFields = reverseParentChain.flatMap(_.fields)
+ val (fieldValues, fieldTypes) = (for {
+ VarDef(Ident(name, originalName), tpe, mutable, rhs) <- allFields
+ } yield {
+ (rhs, RecordType.Field(name, originalName, tpe, mutable))
+ }).unzip
+ tryNewInlineable = Some(
+ RecordValue(RecordType(fieldTypes), fieldValues)(Position.NoPosition))
+ }
+ tryNewInlineable != oldTryNewInlineable
+ }
+
+ /** UPDATE PASS ONLY. */
+ def setupAfterCreation(classInfo: Analyzer#ClassInfo,
+ getClassTreeIfChanged: GetClassTreeIfChanged): Unit = {
+
+ updateWith(classInfo, getClassTreeIfChanged)
+ interfaces =
+ classInfo.ancestors.map(info => getInterface(info.encodedName)).toSet
+
+ isInstantiated = classInfo.isInstantiated
+
+ if (batchMode) {
+ if (isInstantiated) {
+ /* Only add the class to all its ancestor interfaces */
+ for (intf <- interfaces)
+ intf.addInstantiatedSubclass(this)
+ }
+ } else {
+ val allMethodNames = allMethods().keys
+
+ if (isInstantiated) {
+ /* Add the class to all its ancestor interfaces + notify all callers
+ * of any of the methods.
+ * TODO: be more selective on methods that are notified: it is not
+ * necessary to modify callers of methods defined in a parent class
+ * that already existed in the previous run.
+ */
+ for (intf <- interfaces) {
+ intf.addInstantiatedSubclass(this)
+ for (methodName <- allMethodNames)
+ intf.tagDynamicCallersOf(methodName)
+ }
+ }
+
+ /* Tag static callers because the class could have been *moved*,
+ * not just added.
+ */
+ for (methodName <- allMethodNames)
+ myInterface.tagStaticCallersOf(methodName)
+ }
+
+ updateHasElidableModuleAccessor()
+ updateIsInlineable(classInfo)
+ }
+
+ /** UPDATE PASS ONLY. */
+ private def isElidableModuleConstructor(impl: MethodImpl): Boolean = {
+ def isTriviallySideEffectFree(tree: Tree): Boolean = tree match {
+ case _:VarRef | _:Literal | _:This => true
+ case _ => false
+ }
+ def isElidableStat(tree: Tree): Boolean = tree match {
+ case Block(stats) =>
+ stats.forall(isElidableStat)
+ case Assign(Select(This(), _, _), rhs) =>
+ isTriviallySideEffectFree(rhs)
+ case TraitImplApply(ClassType(traitImpl), methodName, List(This())) =>
+ traitImpls(traitImpl).methods(methodName.name).originalDef.body match {
+ case Skip() => true
+ case _ => false
+ }
+ case StaticApply(This(), ClassType(cls), methodName, args) =>
+ Definitions.isConstructorName(methodName.name) &&
+ args.forall(isTriviallySideEffectFree) &&
+ impl.owner.asInstanceOf[Class].superClass.exists { superCls =>
+ superCls.encodedName == cls &&
+ superCls.lookupMethod(methodName.name).exists(isElidableModuleConstructor)
+ }
+ case StoreModule(_, _) =>
+ true
+ case _ =>
+ isTriviallySideEffectFree(tree)
+ }
+ isElidableStat(impl.originalDef.body)
+ }
+
+ /** All the methods of this class, including inherited ones.
+ * It has () so we remember this is an expensive operation.
+ * UPDATE PASS ONLY.
+ */
+ def allMethods(): scala.collection.Map[String, MethodImpl] = {
+ val result = mutable.Map.empty[String, MethodImpl]
+ for (parent <- reverseParentChain)
+ result ++= parent.methods
+ result
+ }
+
+ /** BOTH PASSES. */
+ @tailrec
+ final def lookupMethod(methodName: String): Option[MethodImpl] = {
+ methods.get(methodName) match {
+ case Some(impl) => Some(impl)
+ case none =>
+ superClass match {
+ case Some(p) => p.lookupMethod(methodName)
+ case none => None
+ }
+ }
+ }
+ }
+
+ /** Trait impl. */
+ class TraitImpl(_encodedName: String) extends MethodContainer(_encodedName) {
+ def thisType: Type = NoType
+ }
+
+ /** Thing from which a [[MethodImpl]] can unregister itself from. */
+ trait Unregisterable {
+ /** UPDATE PASS ONLY. */
+ def unregisterDependee(dependee: MethodImpl): Unit
+ }
+
+ /** Type of a class or interface.
+ * Types are created on demand when a method is called on a given
+ * [[ClassType]].
+ *
+ * Fully concurrency safe unless otherwise noted.
+ */
+ abstract class InterfaceType(val encodedName: String) extends Unregisterable {
+
+ override def toString(): String =
+ s"intf $encodedName"
+
+ /** PROCESS PASS ONLY. Concurrency safe except with
+ * [[addInstantiatedSubclass]] and [[removeInstantiatedSubclass]]
+ */
+ def instantiatedSubclasses: Iterable[Class]
+
+ /** UPDATE PASS ONLY. Concurrency safe except with
+ * [[instantiatedSubclasses]]
+ */
+ def addInstantiatedSubclass(x: Class): Unit
+
+ /** UPDATE PASS ONLY. Concurrency safe except with
+ * [[instantiatedSubclasses]]
+ */
+ def removeInstantiatedSubclass(x: Class): Unit
+
+ /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]] */
+ def ancestors: List[String]
+
+ /** UPDATE PASS ONLY. Not concurrency safe. */
+ def ancestors_=(v: List[String]): Unit
+
+ /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]]. */
+ def registerAskAncestors(asker: MethodImpl): Unit
+
+ /** PROCESS PASS ONLY. */
+ def registerDynamicCaller(methodName: String, caller: MethodImpl): Unit
+
+ /** PROCESS PASS ONLY. */
+ def registerStaticCaller(methodName: String, caller: MethodImpl): Unit
+
+ /** UPDATE PASS ONLY. */
+ def tagDynamicCallersOf(methodName: String): Unit
+
+ /** UPDATE PASS ONLY. */
+ def tagStaticCallersOf(methodName: String): Unit
+ }
+
+ /** A method implementation.
+ * It must be concrete, and belong either to a [[Class]] or a [[TraitImpl]].
+ *
+ * A single instance is **not** concurrency safe (unless otherwise noted in
+ * a method comment). However, the global state modifications are
+ * concurrency safe.
+ */
+ abstract class MethodImpl(val owner: MethodContainer,
+ val encodedName: String) extends OptimizerCore.MethodImpl
+ with OptimizerCore.AbstractMethodID
+ with Unregisterable {
+ private[this] var _deleted: Boolean = false
+
+ var optimizerHints: OptimizerHints = OptimizerHints.empty
+ var originalDef: MethodDef = _
+ var desugaredDef: JSTree = _
+ var preciseInfo: Infos.MethodInfo = _
+
+ def thisType: Type = owner.thisType
+ def deleted: Boolean = _deleted
+
+ override def toString(): String =
+ s"$owner.$encodedName"
+
+ /** PROCESS PASS ONLY. */
+ def registerBodyAsker(asker: MethodImpl): Unit
+
+ /** UPDATE PASS ONLY. */
+ def tagBodyAskers(): Unit
+
+ /** PROCESS PASS ONLY. */
+ private def registerAskAncestors(intf: InterfaceType): Unit = {
+ intf.registerAskAncestors(this)
+ registeredTo(intf)
+ }
+
+ /** PROCESS PASS ONLY. */
+ private def registerDynamicCall(intf: InterfaceType,
+ methodName: String): Unit = {
+ intf.registerDynamicCaller(methodName, this)
+ registeredTo(intf)
+ }
+
+ /** PROCESS PASS ONLY. */
+ private def registerStaticCall(intf: InterfaceType,
+ methodName: String): Unit = {
+ intf.registerStaticCaller(methodName, this)
+ registeredTo(intf)
+ }
+
+ /** PROCESS PASS ONLY. */
+ def registerAskBody(target: MethodImpl): Unit = {
+ target.registerBodyAsker(this)
+ registeredTo(target)
+ }
+
+ /** PROCESS PASS ONLY. */
+ protected def registeredTo(intf: Unregisterable): Unit
+
+ /** UPDATE PASS ONLY. */
+ protected def unregisterFromEverywhere(): Unit
+
+ /** Return true iff this is the first time this method is called since the
+ * last reset (via [[resetTag]]).
+ * UPDATE PASS ONLY.
+ */
+ protected def protectTag(): Boolean
+
+ /** PROCESS PASS ONLY. */
+ protected def resetTag(): Unit
+
+ /** Returns true if the method's attributes changed.
+ * Attributes are whether it is inlineable, and whether it is a trait
+ * impl forwarder. Basically this is what is declared in
+ * [[OptimizerCore.AbstractMethodID]].
+ * In the process, tags all the body askers if the body changes.
+ * UPDATE PASS ONLY. Not concurrency safe on same instance.
+ */
+ def updateWith(methodInfo: Analyzer#MethodInfo,
+ methodDef: MethodDef): Boolean = {
+ assert(!_deleted, "updateWith() called on a deleted method")
+
+ val bodyChanged = {
+ originalDef == null ||
+ (methodDef.hash zip originalDef.hash).forall {
+ case (h1, h2) => !Hashers.hashesEqual(h1, h2, considerPositions)
+ }
+ }
+
+ if (bodyChanged)
+ tagBodyAskers()
+
+ val hints = methodInfo.optimizerHints
+ val changed = hints != optimizerHints || bodyChanged
+ if (changed) {
+ val oldAttributes = (inlineable, isTraitImplForwarder)
+
+ optimizerHints = hints
+ originalDef = methodDef
+ desugaredDef = null
+ preciseInfo = null
+ updateInlineable()
+ tag()
+
+ val newAttributes = (inlineable, isTraitImplForwarder)
+ newAttributes != oldAttributes
+ } else {
+ false
+ }
+ }
+
+ /** UPDATE PASS ONLY. Not concurrency safe on same instance. */
+ def delete(): Unit = {
+ assert(!_deleted, "delete() called twice")
+ _deleted = true
+ if (protectTag())
+ unregisterFromEverywhere()
+ }
+
+ /** Concurrency safe with itself and [[delete]] on the same instance
+ *
+ * [[tag]] can be called concurrently with [[delete]] when methods in
+ * traits/classes are updated.
+ *
+ * UPDATE PASS ONLY.
+ */
+ def tag(): Unit = if (protectTag()) {
+ scheduleMethod(this)
+ unregisterFromEverywhere()
+ }
+
+ /** PROCESS PASS ONLY. */
+ def process(): Unit = if (!_deleted) {
+ val (optimizedDef, info) = new Optimizer().optimize(thisType, originalDef)
+ desugaredDef =
+ if (owner.isInstanceOf[Class])
+ classEmitter.genMethod(owner.encodedName, optimizedDef)
+ else
+ classEmitter.genTraitImplMethod(owner.encodedName, optimizedDef)
+ preciseInfo = info
+ resetTag()
+ }
+
+ /** All methods are PROCESS PASS ONLY */
+ private class Optimizer extends OptimizerCore(semantics) {
+ type MethodID = MethodImpl
+
+ val myself: MethodImpl.this.type = MethodImpl.this
+
+ protected def getMethodBody(method: MethodID): MethodDef = {
+ MethodImpl.this.registerAskBody(method)
+ method.originalDef
+ }
+
+ protected def dynamicCall(intfName: String,
+ methodName: String): List[MethodID] = {
+ val intf = getInterface(intfName)
+ MethodImpl.this.registerDynamicCall(intf, methodName)
+ intf.instantiatedSubclasses.flatMap(_.lookupMethod(methodName)).toList
+ }
+
+ protected def staticCall(className: String,
+ methodName: String): Option[MethodID] = {
+ val clazz = classes(className)
+ MethodImpl.this.registerStaticCall(clazz.myInterface, methodName)
+ clazz.lookupMethod(methodName)
+ }
+
+ protected def traitImplCall(traitImplName: String,
+ methodName: String): Option[MethodID] = {
+ val traitImpl = traitImpls(traitImplName)
+ registerStaticCall(traitImpl.myInterface, methodName)
+ traitImpl.methods.get(methodName)
+ }
+
+ protected def getAncestorsOf(intfName: String): List[String] = {
+ val intf = getInterface(intfName)
+ registerAskAncestors(intf)
+ intf.ancestors
+ }
+
+ protected def hasElidableModuleAccessor(moduleClassName: String): Boolean =
+ classes(moduleClassName).hasElidableModuleAccessor
+
+ protected def tryNewInlineableClass(className: String): Option[RecordValue] =
+ classes(className).tryNewInlineable
+ }
+ }
+
+}
+
+object GenIncOptimizer {
+
+ private val isAdHocElidableModuleAccessor =
+ Set("s_Predef$")
+
+ private[optimizer] def logTime[A](logger: Logger,
+ title: String)(body: => A): A = {
+ val startTime = System.nanoTime()
+ val result = body
+ val endTime = System.nanoTime()
+ val elapsedTime = endTime - startTime
+ logger.time(title, elapsedTime)
+ result
+ }
+
+ private[optimizer] trait AbsCollOps {
+ type Map[K, V] <: mutable.Map[K, V]
+ type ParMap[K, V] <: GenMap[K, V]
+ type AccMap[K, V]
+ type ParIterable[V] <: GenIterableLike[V, ParIterable[V]]
+ type Addable[V]
+
+ def emptyAccMap[K, V]: AccMap[K, V]
+ def emptyMap[K, V]: Map[K, V]
+ def emptyParMap[K, V]: ParMap[K, V]
+ def emptyParIterable[V]: ParIterable[V]
+
+ // Operations on ParMap
+ def put[K, V](map: ParMap[K, V], k: K, v: V): Unit
+ def remove[K, V](map: ParMap[K, V], k: K): Option[V]
+ def retain[K, V](map: ParMap[K, V])(p: (K, V) => Boolean): Unit
+
+ // Operations on AccMap
+ def acc[K, V](map: AccMap[K, V], k: K, v: V): Unit
+ def getAcc[K, V](map: AccMap[K, V], k: K): GenIterable[V]
+ def parFlatMapKeys[A, B](map: AccMap[A, _])(
+ f: A => GenTraversableOnce[B]): GenIterable[B]
+
+ // Operations on ParIterable
+ def prepAdd[V](it: ParIterable[V]): Addable[V]
+ def add[V](addable: Addable[V], v: V): Unit
+ def finishAdd[V](addable: Addable[V]): ParIterable[V]
+
+ }
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala
new file mode 100644
index 0000000..6329826
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IRChecker.scala
@@ -0,0 +1,854 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import scala.language.implicitConversions
+
+import scala.annotation.switch
+
+import scala.collection.mutable
+
+import scala.scalajs.ir._
+import Definitions._
+import Trees._
+import Types._
+
+import scala.scalajs.tools.logging._
+
+/** Checker for the validity of the IR. */
+class IRChecker(analyzer: Analyzer, allClassDefs: Seq[ClassDef], logger: Logger) {
+ import IRChecker._
+
+ private var _errorCount: Int = 0
+ def errorCount: Int = _errorCount
+
+ private val classes: mutable.Map[String, CheckedClass] = {
+ mutable.Map.empty[String, CheckedClass] ++=
+ allClassDefs.map(new CheckedClass(_)).map(c => c.name -> c)
+ }
+
+ def check(): Boolean = {
+ for {
+ classDef <- allClassDefs
+ if analyzer.classInfos(classDef.name.name).isNeededAtAll
+ } {
+ classDef.kind match {
+ case ClassKind.Class | ClassKind.ModuleClass => checkClass(classDef)
+ case ClassKind.TraitImpl => checkTraitImpl(classDef)
+ case _ =>
+ }
+ }
+ errorCount == 0
+ }
+
+ def checkClass(classDef: ClassDef): Unit = {
+ if (!analyzer.classInfos(classDef.name.name).isAnySubclassInstantiated)
+ return
+
+ for (member <- classDef.defs) {
+ implicit val ctx = ErrorContext(member)
+ member match {
+ // Scala declarations
+ case v @ VarDef(_, _, _, _) =>
+ checkFieldDef(v, classDef)
+ case m: MethodDef if m.name.isInstanceOf[Ident] =>
+ checkMethodDef(m, classDef)
+
+ // Exports
+ case m: MethodDef if m.name.isInstanceOf[StringLiteral] =>
+ checkExportedMethodDef(m, classDef)
+ case member @ PropertyDef(_: StringLiteral, _, _, _) =>
+ checkExportedPropertyDef(member, classDef)
+ case member @ ConstructorExportDef(_, _, _) =>
+ checkConstructorExportDef(member, classDef)
+ case member @ ModuleExportDef(_) =>
+ checkModuleExportDef(member, classDef)
+
+ // Anything else is illegal
+ case _ =>
+ reportError(s"Illegal class member of type ${member.getClass.getName}")
+ }
+ }
+ }
+
+ def checkTraitImpl(classDef: ClassDef): Unit = {
+ for (member <- classDef.defs) {
+ implicit val ctx = ErrorContext(member)
+ member match {
+ case m: MethodDef =>
+ checkMethodDef(m, classDef)
+ case _ =>
+ reportError(s"Invalid member for a TraitImpl")
+ }
+ }
+ }
+
+ def checkFieldDef(fieldDef: VarDef, classDef: ClassDef): Unit = {
+ val VarDef(name, tpe, mutable, rhs) = fieldDef
+ implicit val ctx = ErrorContext(fieldDef)
+
+ if (tpe == NoType)
+ reportError(s"VarDef cannot have type NoType")
+ else
+ typecheckExpect(rhs, Env.empty, tpe)
+ }
+
+ def checkMethodDef(methodDef: MethodDef, classDef: ClassDef): Unit = {
+ val MethodDef(Ident(name, _), params, resultType, body) = methodDef
+ implicit val ctx = ErrorContext(methodDef)
+
+ if (!analyzer.classInfos(classDef.name.name).methodInfos(name).isReachable)
+ return
+
+ for (ParamDef(name, tpe, _) <- params)
+ if (tpe == NoType)
+ reportError(s"Parameter $name has type NoType")
+
+ val resultTypeForSig =
+ if (isConstructorName(name)) NoType
+ else resultType
+
+ val advertizedSig = (params.map(_.ptpe), resultTypeForSig)
+ val sigFromName = inferMethodType(name,
+ inTraitImpl = classDef.kind == ClassKind.TraitImpl)
+ if (advertizedSig != sigFromName) {
+ reportError(
+ s"The signature of ${classDef.name.name}.$name, which is "+
+ s"$advertizedSig, does not match its name (should be $sigFromName).")
+ }
+
+ val thisType =
+ if (!classDef.kind.isClass) NoType
+ else ClassType(classDef.name.name)
+ val bodyEnv = Env.fromSignature(thisType, params, resultType)
+ if (resultType == NoType)
+ typecheckStat(body, bodyEnv)
+ else
+ typecheckExpect(body, bodyEnv, resultType)
+ }
+
+ def checkExportedMethodDef(methodDef: MethodDef, classDef: ClassDef): Unit = {
+ val MethodDef(_, params, resultType, body) = methodDef
+ implicit val ctx = ErrorContext(methodDef)
+
+ if (!classDef.kind.isClass) {
+ reportError(s"Exported method def can only appear in a class")
+ return
+ }
+
+ for (ParamDef(name, tpe, _) <- params) {
+ if (tpe == NoType)
+ reportError(s"Parameter $name has type NoType")
+ else if (tpe != AnyType)
+ reportError(s"Parameter $name of exported method def has type $tpe, "+
+ "but must be Any")
+ }
+
+ if (resultType != AnyType) {
+ reportError(s"Result type of exported method def is $resultType, "+
+ "but must be Any")
+ }
+
+ val thisType = ClassType(classDef.name.name)
+ val bodyEnv = Env.fromSignature(thisType, params, resultType)
+ .withArgumentsVar(methodDef.pos)
+ typecheckExpect(body, bodyEnv, resultType)
+ }
+
+ def checkExportedPropertyDef(propDef: PropertyDef, classDef: ClassDef): Unit = {
+ val PropertyDef(_, getterBody, setterArg, setterBody) = propDef
+ implicit val ctx = ErrorContext(propDef)
+
+ if (!classDef.kind.isClass) {
+ reportError(s"Exported property def can only appear in a class")
+ return
+ }
+
+ val thisType = ClassType(classDef.name.name)
+
+ if (getterBody != EmptyTree) {
+ val getterBodyEnv = Env.fromSignature(thisType, Nil, AnyType)
+ typecheckExpect(getterBody, getterBodyEnv, AnyType)
+ }
+
+ if (setterBody != EmptyTree) {
+ if (setterArg.ptpe != AnyType)
+ reportError("Setter argument of exported property def has type "+
+ s"${setterArg.ptpe}, but must be Any")
+
+ val setterBodyEnv = Env.fromSignature(thisType, List(setterArg), NoType)
+ typecheckStat(setterBody, setterBodyEnv)
+ }
+ }
+
+ def checkConstructorExportDef(ctorDef: ConstructorExportDef,
+ classDef: ClassDef): Unit = {
+ val ConstructorExportDef(_, params, body) = ctorDef
+ implicit val ctx = ErrorContext(ctorDef)
+
+ if (!classDef.kind.isClass) {
+ reportError(s"Exported constructor def can only appear in a class")
+ return
+ }
+
+ for (ParamDef(name, tpe, _) <- params) {
+ if (tpe == NoType)
+ reportError(s"Parameter $name has type NoType")
+ else if (tpe != AnyType)
+ reportError(s"Parameter $name of exported constructor def has type "+
+ s"$tpe, but must be Any")
+ }
+
+ val thisType = ClassType(classDef.name.name)
+ val bodyEnv = Env.fromSignature(thisType, params, NoType)
+ .withArgumentsVar(ctorDef.pos)
+ typecheckStat(body, bodyEnv)
+ }
+
+ def checkModuleExportDef(moduleDef: ModuleExportDef,
+ classDef: ClassDef): Unit = {
+ implicit val ctx = ErrorContext(moduleDef)
+
+ if (classDef.kind != ClassKind.ModuleClass)
+ reportError(s"Exported module def can only appear in a module class")
+ }
+
+ def typecheckStat(tree: Tree, env: Env): Env = {
+ implicit val ctx = ErrorContext(tree)
+
+ tree match {
+ case VarDef(ident, vtpe, mutable, rhs) =>
+ typecheckExpect(rhs, env, vtpe)
+ env.withLocal(LocalDef(ident.name, vtpe, mutable)(tree.pos))
+
+ case Skip() =>
+ env
+
+ case Assign(select, rhs) =>
+ select match {
+ case Select(_, Ident(name, _), false) =>
+ /* TODO In theory this case would verify that we never assign to
+ * an immutable field. But we cannot do that because we *do* emit
+ * such assigns in constructors.
+ * In the future we might want to check that only these legal
+ * special cases happen, and nothing else. But it seems non-trivial
+ * to do so, so currently we trust scalac not to make us emit
+ * illegal assigns.
+ */
+ //reportError(s"Assignment to immutable field $name.")
+ case VarRef(Ident(name, _), false) =>
+ reportError(s"Assignment to immutable variable $name.")
+ case _ =>
+ }
+ val lhsTpe = typecheckExpr(select, env)
+ val expectedRhsTpe = select match {
+ case _:JSDotSelect | _:JSBracketSelect => AnyType
+ case _ => lhsTpe
+ }
+ typecheckExpect(rhs, env, expectedRhsTpe)
+ env
+
+ case StoreModule(cls, value) =>
+ if (!cls.className.endsWith("$"))
+ reportError("StoreModule of non-module class $cls")
+ typecheckExpect(value, env, ClassType(cls.className))
+ env
+
+ case Block(stats) =>
+ (env /: stats) { (prevEnv, stat) =>
+ typecheckStat(stat, prevEnv)
+ }
+ env
+
+ case Labeled(label, NoType, body) =>
+ typecheckStat(body, env.withLabeledReturnType(label.name, AnyType))
+ env
+
+ case If(cond, thenp, elsep) =>
+ typecheckExpect(cond, env, BooleanType)
+ typecheckStat(thenp, env)
+ typecheckStat(elsep, env)
+ env
+
+ case While(cond, body, label) =>
+ typecheckExpect(cond, env, BooleanType)
+ typecheckStat(body, env)
+ env
+
+ case DoWhile(body, cond, label) =>
+ typecheckStat(body, env)
+ typecheckExpect(cond, env, BooleanType)
+ env
+
+ case Try(block, errVar, handler, finalizer) =>
+ typecheckStat(block, env)
+ if (handler != EmptyTree) {
+ val handlerEnv =
+ env.withLocal(LocalDef(errVar.name, AnyType, false)(errVar.pos))
+ typecheckStat(handler, handlerEnv)
+ }
+ if (finalizer != EmptyTree) {
+ typecheckStat(finalizer, env)
+ }
+ env
+
+ case Match(selector, cases, default) =>
+ typecheckExpr(selector, env)
+ for ((alts, body) <- cases) {
+ alts.foreach(typecheckExpr(_, env))
+ typecheckStat(body, env)
+ }
+ typecheckStat(default, env)
+ env
+
+ case Debugger() =>
+ env
+
+ case JSDelete(JSDotSelect(obj, prop)) =>
+ typecheckExpr(obj, env)
+ env
+
+ case JSDelete(JSBracketSelect(obj, prop)) =>
+ typecheckExpr(obj, env)
+ typecheckExpr(prop, env)
+ env
+
+ case _ =>
+ typecheck(tree, env)
+ env
+ }
+ }
+
+ def typecheckExpect(tree: Tree, env: Env, expectedType: Type)(
+ implicit ctx: ErrorContext): Unit = {
+ val tpe = typecheckExpr(tree, env)
+ if (!isSubtype(tpe, expectedType))
+ reportError(s"$expectedType expected but $tpe found "+
+ s"for tree of type ${tree.getClass.getName}")
+ }
+
+ def typecheckExpr(tree: Tree, env: Env): Type = {
+ implicit val ctx = ErrorContext(tree)
+ if (tree.tpe == NoType)
+ reportError(s"Expression tree has type NoType")
+ typecheck(tree, env)
+ }
+
+ def typecheck(tree: Tree, env: Env): Type = {
+ implicit val ctx = ErrorContext(tree)
+
+ def checkApplyGeneric(methodName: String, methodFullName: String,
+ args: List[Tree], inTraitImpl: Boolean): Unit = {
+ val (methodParams, resultType) = inferMethodType(methodName, inTraitImpl)
+ if (args.size != methodParams.size)
+ reportError(s"Arity mismatch: ${methodParams.size} expected but "+
+ s"${args.size} found")
+ for ((actual, formal) <- args zip methodParams) {
+ typecheckExpect(actual, env, formal)
+ }
+ if (!isConstructorName(methodName) && tree.tpe != resultType)
+ reportError(s"Call to $methodFullName of type $resultType "+
+ s"typed as ${tree.tpe}")
+ }
+
+ tree match {
+ // Control flow constructs
+
+ case Block(statsAndExpr) =>
+ val stats :+ expr = statsAndExpr
+ val envAfterStats = (env /: stats) { (prevEnv, stat) =>
+ typecheckStat(stat, prevEnv)
+ }
+ typecheckExpr(expr, envAfterStats)
+
+ case Labeled(label, tpe, body) =>
+ typecheckExpect(body, env.withLabeledReturnType(label.name, tpe), tpe)
+
+ case Return(expr, label) =>
+ env.returnTypes.get(label.map(_.name)).fold[Unit] {
+ reportError(s"Cannot return to label $label.")
+ typecheckExpr(expr, env)
+ } { returnType =>
+ typecheckExpect(expr, env, returnType)
+ }
+
+ case If(cond, thenp, elsep) =>
+ val tpe = tree.tpe
+ typecheckExpect(cond, env, BooleanType)
+ typecheckExpect(thenp, env, tpe)
+ typecheckExpect(elsep, env, tpe)
+
+ case While(BooleanLiteral(true), body, label) if tree.tpe == NothingType =>
+ typecheckStat(body, env)
+
+ case Try(block, errVar, handler, finalizer) =>
+ val tpe = tree.tpe
+ typecheckExpect(block, env, tpe)
+ if (handler != EmptyTree) {
+ val handlerEnv =
+ env.withLocal(LocalDef(errVar.name, AnyType, false)(errVar.pos))
+ typecheckExpect(handler, handlerEnv, tpe)
+ }
+ if (finalizer != EmptyTree) {
+ typecheckStat(finalizer, env)
+ }
+
+ case Throw(expr) =>
+ typecheckExpr(expr, env)
+
+ case Continue(label) =>
+ /* Here we could check that it is indeed legal to break to the
+ * specified label. However, if we do anything illegal here, it will
+ * result in a SyntaxError in JavaScript anyway, so we do not really
+ * care.
+ */
+
+ case Match(selector, cases, default) =>
+ val tpe = tree.tpe
+ typecheckExpr(selector, env)
+ for ((alts, body) <- cases) {
+ alts.foreach(typecheckExpr(_, env))
+ typecheckExpect(body, env, tpe)
+ }
+ typecheckExpect(default, env, tpe)
+
+ // Scala expressions
+
+ case New(cls, ctor, args) =>
+ val clazz = lookupClass(cls)
+ if (!clazz.kind.isClass)
+ reportError(s"new $cls which is not a class")
+ checkApplyGeneric(ctor.name, s"$cls.$ctor", args,
+ inTraitImpl = false)
+
+ case LoadModule(cls) =>
+ if (!cls.className.endsWith("$"))
+ reportError("LoadModule of non-module class $cls")
+
+ case Select(qualifier, Ident(item, _), mutable) =>
+ val qualType = typecheckExpr(qualifier, env)
+ qualType match {
+ case ClassType(cls) =>
+ val clazz = lookupClass(cls)
+ if (!clazz.kind.isClass) {
+ reportError(s"Cannot select $item of non-class $cls")
+ } else {
+ clazz.lookupField(item).fold[Unit] {
+ reportError(s"Class $cls does not have a field $item")
+ } { fieldDef =>
+ if (fieldDef.tpe != tree.tpe)
+ reportError(s"Select $cls.$item of type "+
+ s"${fieldDef.tpe} typed as ${tree.tpe}")
+ if (fieldDef.mutable != mutable)
+ reportError(s"Select $cls.$item with "+
+ s"mutable=${fieldDef.mutable} marked as mutable=$mutable")
+ }
+ }
+ case NullType | NothingType =>
+ // always ok
+ case _ =>
+ reportError(s"Cannot select $item of non-class type $qualType")
+ }
+
+ case Apply(receiver, Ident(method, _), args) =>
+ val receiverType = typecheckExpr(receiver, env)
+ checkApplyGeneric(method, s"$receiverType.$method", args,
+ inTraitImpl = false)
+
+ case StaticApply(receiver, cls, Ident(method, _), args) =>
+ typecheckExpect(receiver, env, cls)
+ checkApplyGeneric(method, s"$cls.$method", args, inTraitImpl = false)
+
+ case TraitImplApply(impl, Ident(method, _), args) =>
+ val clazz = lookupClass(impl)
+ if (clazz.kind != ClassKind.TraitImpl)
+ reportError(s"Cannot trait-impl apply method of non-trait-impl $impl")
+ checkApplyGeneric(method, s"$impl.$method", args, inTraitImpl = true)
+
+ case UnaryOp(op, lhs) =>
+ import UnaryOp._
+ (op: @switch) match {
+ case `typeof` =>
+ typecheckExpr(lhs, env)
+ case IntToLong =>
+ typecheckExpect(lhs, env, IntType)
+ case LongToInt | LongToDouble =>
+ typecheckExpect(lhs, env, LongType)
+ case DoubleToInt | DoubleToFloat | DoubleToLong =>
+ typecheckExpect(lhs, env, DoubleType)
+ case Boolean_! =>
+ typecheckExpect(lhs, env, BooleanType)
+ }
+
+ case BinaryOp(op, lhs, rhs) =>
+ import BinaryOp._
+ (op: @switch) match {
+ case === | !== | String_+ =>
+ typecheckExpr(lhs, env)
+ typecheckExpr(rhs, env)
+ case `in` =>
+ typecheckExpect(lhs, env, ClassType(StringClass))
+ typecheckExpr(rhs, env)
+ case `instanceof` =>
+ typecheckExpr(lhs, env)
+ typecheckExpr(rhs, env)
+ case Int_+ | Int_- | Int_* | Int_/ | Int_% |
+ Int_| | Int_& | Int_^ | Int_<< | Int_>>> | Int_>> =>
+ typecheckExpect(lhs, env, IntType)
+ typecheckExpect(rhs, env, IntType)
+ case Float_+ | Float_- | Float_* | Float_/ | Float_% =>
+ typecheckExpect(lhs, env, FloatType)
+ typecheckExpect(lhs, env, FloatType)
+ case Long_+ | Long_- | Long_* | Long_/ | Long_% |
+ Long_| | Long_& | Long_^ |
+ Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= =>
+ typecheckExpect(lhs, env, LongType)
+ typecheckExpect(rhs, env, LongType)
+ case Long_<< | Long_>>> | Long_>> =>
+ typecheckExpect(lhs, env, LongType)
+ typecheckExpect(rhs, env, IntType)
+ case Double_+ | Double_- | Double_* | Double_/ | Double_% |
+ Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>= =>
+ typecheckExpect(lhs, env, DoubleType)
+ typecheckExpect(lhs, env, DoubleType)
+ case Boolean_== | Boolean_!= | Boolean_| | Boolean_& =>
+ typecheckExpect(lhs, env, BooleanType)
+ typecheckExpect(rhs, env, BooleanType)
+ }
+
+ case NewArray(tpe, lengths) =>
+ for (length <- lengths)
+ typecheckExpect(length, env, IntType)
+
+ case ArrayValue(tpe, elems) =>
+ val elemType = arrayElemType(tpe)
+ for (elem <- elems)
+ typecheckExpect(elem, env, elemType)
+
+ case ArrayLength(array) =>
+ val arrayType = typecheckExpr(array, env)
+ if (!arrayType.isInstanceOf[ArrayType])
+ reportError(s"Array type expected but $arrayType found")
+
+ case ArraySelect(array, index) =>
+ typecheckExpect(index, env, IntType)
+ typecheckExpr(array, env) match {
+ case arrayType: ArrayType =>
+ if (tree.tpe != arrayElemType(arrayType))
+ reportError(s"Array select of array type $arrayType typed as ${tree.tpe}")
+ case arrayType =>
+ reportError(s"Array type expected but $arrayType found")
+ }
+
+ case IsInstanceOf(expr, cls) =>
+ typecheckExpr(expr, env)
+
+ case AsInstanceOf(expr, cls) =>
+ typecheckExpr(expr, env)
+
+ case Unbox(expr, _) =>
+ typecheckExpr(expr, env)
+
+ case GetClass(expr) =>
+ typecheckExpr(expr, env)
+
+ // JavaScript expressions
+
+ case JSNew(ctor, args) =>
+ typecheckExpr(ctor, env)
+ for (arg <- args)
+ typecheckExpr(arg, env)
+
+ case JSDotSelect(qualifier, item) =>
+ typecheckExpr(qualifier, env)
+
+ case JSBracketSelect(qualifier, item) =>
+ typecheckExpr(qualifier, env)
+ typecheckExpr(item, env)
+
+ case JSFunctionApply(fun, args) =>
+ typecheckExpr(fun, env)
+ for (arg <- args)
+ typecheckExpr(arg, env)
+
+ case JSDotMethodApply(receiver, method, args) =>
+ typecheckExpr(receiver, env)
+ for (arg <- args)
+ typecheckExpr(arg, env)
+
+ case JSBracketMethodApply(receiver, method, args) =>
+ typecheckExpr(receiver, env)
+ typecheckExpr(method, env)
+ for (arg <- args)
+ typecheckExpr(arg, env)
+
+ case JSUnaryOp(op, lhs) =>
+ typecheckExpr(lhs, env)
+
+ case JSBinaryOp(op, lhs, rhs) =>
+ typecheckExpr(lhs, env)
+ typecheckExpr(rhs, env)
+
+ case JSArrayConstr(items) =>
+ for (item <- items)
+ typecheckExpr(item, env)
+
+ case JSObjectConstr(fields) =>
+ for ((_, value) <- fields)
+ typecheckExpr(value, env)
+
+ case JSEnvInfo() =>
+
+ // Literals
+
+ case _: Literal =>
+
+ // Atomic expressions
+
+ case VarRef(Ident(name, _), mutable) =>
+ env.locals.get(name).fold[Unit] {
+ reportError(s"Cannot find variable $name in scope")
+ } { localDef =>
+ if (tree.tpe != localDef.tpe)
+ reportError(s"Variable $name of type ${localDef.tpe} "+
+ s"typed as ${tree.tpe}")
+ if (mutable != localDef.mutable)
+ reportError(s"Variable $name with mutable=${localDef.mutable} "+
+ s"marked as mutable=$mutable")
+ }
+
+ case This() =>
+ if (!isSubtype(env.thisTpe, tree.tpe))
+ reportError(s"this of type ${env.thisTpe} typed as ${tree.tpe}")
+
+ case Closure(captureParams, params, body, captureValues) =>
+ if (captureParams.size != captureValues.size)
+ reportError("Mismatched size for captures: "+
+ s"${captureParams.size} params vs ${captureValues.size} values")
+
+ for ((ParamDef(name, ctpe, mutable), value) <- captureParams zip captureValues) {
+ if (mutable)
+ reportError(s"Capture parameter $name cannot be mutable")
+ if (ctpe == NoType)
+ reportError(s"Parameter $name has type NoType")
+ else
+ typecheckExpect(value, env, ctpe)
+ }
+
+ for (ParamDef(name, ptpe, mutable) <- params) {
+ if (ptpe == NoType)
+ reportError(s"Parameter $name has type NoType")
+ else if (ptpe != AnyType)
+ reportError(s"Closure parameter $name has type $ptpe instead of any")
+ }
+
+ val bodyEnv = Env.fromSignature(
+ AnyType, captureParams ++ params, AnyType)
+ typecheckExpect(body, bodyEnv, AnyType)
+
+ case _ =>
+ reportError(s"Invalid expression tree")
+ }
+
+ tree.tpe
+ }
+
+ def inferMethodType(encodedName: String, inTraitImpl: Boolean)(
+ implicit ctx: ErrorContext): (List[Type], Type) = {
+ def dropPrivateMarker(params: List[String]): List[String] =
+ if (params.nonEmpty && params.head.startsWith("p")) params.tail
+ else params
+
+ if (isConstructorName(encodedName)) {
+ assert(!inTraitImpl, "Trait impl should not have a constructor")
+ val params = dropPrivateMarker(
+ encodedName.stripPrefix("init___").split("__").toList)
+ if (params == List("")) (Nil, NoType)
+ else (params.map(decodeType), NoType)
+ } else if (isReflProxyName(encodedName)) {
+ assert(!inTraitImpl, "Trait impl should not have refl proxy methods")
+ val params = dropPrivateMarker(encodedName.split("__").toList.tail)
+ (params.map(decodeType), AnyType)
+ } else {
+ val paramsAndResult0 =
+ encodedName.split("__").toList.tail
+ val paramsAndResult1 =
+ if (inTraitImpl) paramsAndResult0.tail
+ else paramsAndResult0
+ val paramsAndResult =
+ dropPrivateMarker(paramsAndResult1)
+ (paramsAndResult.init.map(decodeType), decodeType(paramsAndResult.last))
+ }
+ }
+
+ def decodeType(encodedName: String)(implicit ctx: ErrorContext): Type = {
+ if (encodedName.isEmpty) NoType
+ else if (encodedName.charAt(0) == 'A') {
+ // array type
+ val dims = encodedName.indexWhere(_ != 'A')
+ val base = encodedName.substring(dims)
+ ArrayType(base, dims)
+ } else if (encodedName.length == 1) {
+ (encodedName.charAt(0): @switch) match {
+ case 'V' => NoType
+ case 'Z' => BooleanType
+ case 'C' | 'B' | 'S' | 'I' => IntType
+ case 'J' => LongType
+ case 'F' => FloatType
+ case 'D' => DoubleType
+ case 'O' => AnyType
+ case 'T' => ClassType(StringClass) // NOT StringType
+ }
+ } else if (encodedName == "sr_Nothing$") {
+ NothingType
+ } else if (encodedName == "sr_Null$") {
+ NullType
+ } else {
+ val clazz = lookupClass(encodedName)
+ if (clazz.kind == ClassKind.RawJSType) AnyType
+ else ClassType(encodedName)
+ }
+ }
+
+ def arrayElemType(arrayType: ArrayType)(implicit ctx: ErrorContext): Type = {
+ if (arrayType.dimensions == 1) decodeType(arrayType.baseClassName)
+ else ArrayType(arrayType.baseClassName, arrayType.dimensions-1)
+ }
+
+ def reportError(msg: String)(implicit ctx: ErrorContext): Unit = {
+ logger.error(s"$ctx: $msg")
+ _errorCount += 1
+ }
+
+ def lookupClass(className: String)(implicit ctx: ErrorContext): CheckedClass = {
+ classes.getOrElseUpdate(className, {
+ reportError(s"Cannot find class $className")
+ new CheckedClass(className, ClassKind.Class,
+ Some(ObjectClass), Set(ObjectClass))
+ })
+ }
+
+ def lookupClass(classType: ClassType)(implicit ctx: ErrorContext): CheckedClass =
+ lookupClass(classType.className)
+
+ def isSubclass(lhs: String, rhs: String)(implicit ctx: ErrorContext): Boolean = {
+ lookupClass(lhs).isSubclass(lookupClass(rhs))
+ }
+
+ def isSubtype(lhs: Type, rhs: Type)(implicit ctx: ErrorContext): Boolean = {
+ Types.isSubtype(lhs, rhs)(isSubclass)
+ }
+
+ class Env(
+ /** Type of `this`. Can be NoType. */
+ val thisTpe: Type,
+ /** Local variables in scope (including through closures). */
+ val locals: Map[String, LocalDef],
+ /** Return types by label. */
+ val returnTypes: Map[Option[String], Type]
+ ) {
+ def withThis(thisTpe: Type): Env =
+ new Env(thisTpe, this.locals, this.returnTypes)
+
+ def withLocal(localDef: LocalDef): Env =
+ new Env(thisTpe, locals + (localDef.name -> localDef), returnTypes)
+
+ def withLocals(localDefs: TraversableOnce[LocalDef]): Env =
+ new Env(thisTpe, locals ++ localDefs.map(d => d.name -> d), returnTypes)
+
+ def withReturnType(returnType: Type): Env =
+ new Env(this.thisTpe, this.locals, returnTypes + (None -> returnType))
+
+ def withLabeledReturnType(label: String, returnType: Type): Env =
+ new Env(this.thisTpe, this.locals, returnTypes + (Some(label) -> returnType))
+
+ def withArgumentsVar(pos: Position): Env =
+ withLocal(LocalDef("arguments", AnyType, mutable = false)(pos))
+ }
+
+ object Env {
+ val empty: Env = new Env(NoType, Map.empty, Map.empty)
+
+ def fromSignature(thisType: Type, params: List[ParamDef],
+ resultType: Type): Env = {
+ val paramLocalDefs =
+ for (p @ ParamDef(name, tpe, mutable) <- params) yield
+ name.name -> LocalDef(name.name, tpe, mutable)(p.pos)
+ new Env(thisType, paramLocalDefs.toMap,
+ Map(None -> (if (resultType == NoType) AnyType else resultType)))
+ }
+ }
+
+ class CheckedClass(
+ val name: String,
+ val kind: ClassKind,
+ val superClassName: Option[String],
+ val ancestors: Set[String],
+ _fields: TraversableOnce[CheckedField] = Nil) {
+
+ val fields = _fields.map(f => f.name -> f).toMap
+
+ lazy val superClass = superClassName.map(classes)
+
+ def this(classDef: ClassDef) = {
+ this(classDef.name.name, classDef.kind,
+ classDef.parent.map(_.name),
+ classDef.ancestors.map(_.name).toSet,
+ CheckedClass.collectFields(classDef))
+ }
+
+ def isSubclass(that: CheckedClass): Boolean =
+ this == that || ancestors.contains(that.name)
+
+ def isAncestorOfHijackedClass: Boolean =
+ AncestorsOfHijackedClasses.contains(name)
+
+ def lookupField(name: String): Option[CheckedField] =
+ fields.get(name).orElse(superClass.flatMap(_.lookupField(name)))
+ }
+
+ object CheckedClass {
+ private def collectFields(classDef: ClassDef) = {
+ classDef.defs collect {
+ case VarDef(Ident(name, _), tpe, mutable, _) =>
+ new CheckedField(name, tpe, mutable)
+ }
+ }
+ }
+
+ class CheckedField(val name: String, val tpe: Type, val mutable: Boolean)
+}
+
+object IRChecker {
+ private final class ErrorContext(val tree: Tree) extends AnyVal {
+ override def toString(): String = {
+ val pos = tree.pos
+ s"${pos.source}(${pos.line+1}:${pos.column+1}:${tree.getClass.getSimpleName})"
+ }
+
+ def pos: Position = tree.pos
+ }
+
+ private object ErrorContext {
+ implicit def tree2errorContext(tree: Tree): ErrorContext =
+ ErrorContext(tree)
+
+ def apply(tree: Tree): ErrorContext =
+ new ErrorContext(tree)
+ }
+
+ private def isConstructorName(name: String): Boolean =
+ name.startsWith("init___")
+
+ private def isReflProxyName(name: String): Boolean =
+ name.endsWith("__") && !isConstructorName(name)
+
+ case class LocalDef(name: String, tpe: Type, mutable: Boolean)(val pos: Position)
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IncOptimizer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IncOptimizer.scala
new file mode 100644
index 0000000..d115618
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/IncOptimizer.scala
@@ -0,0 +1,158 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import scala.collection.{GenTraversableOnce, GenIterable}
+import scala.collection.mutable
+
+import scala.scalajs.tools.sem.Semantics
+
+class IncOptimizer(semantics: Semantics) extends GenIncOptimizer(semantics) {
+
+ protected object CollOps extends GenIncOptimizer.AbsCollOps {
+ type Map[K, V] = mutable.Map[K, V]
+ type ParMap[K, V] = mutable.Map[K, V]
+ type AccMap[K, V] = mutable.Map[K, mutable.ListBuffer[V]]
+ type ParIterable[V] = mutable.ListBuffer[V]
+ type Addable[V] = mutable.ListBuffer[V]
+
+ def emptyAccMap[K, V]: AccMap[K, V] = mutable.Map.empty
+ def emptyMap[K, V]: Map[K, V] = mutable.Map.empty
+ def emptyParMap[K, V]: ParMap[K, V] = mutable.Map.empty
+ def emptyParIterable[V]: ParIterable[V] = mutable.ListBuffer.empty
+
+ // Operations on ParMap
+ def put[K, V](map: ParMap[K, V], k: K, v: V): Unit = map.put(k, v)
+ def remove[K, V](map: ParMap[K, V], k: K): Option[V] = map.remove(k)
+
+ def retain[K, V](map: ParMap[K, V])(p: (K, V) => Boolean): Unit =
+ map.retain(p)
+
+ // Operations on AccMap
+ def acc[K, V](map: AccMap[K, V], k: K, v: V): Unit =
+ map.getOrElseUpdate(k, mutable.ListBuffer.empty) += v
+
+ def getAcc[K, V](map: AccMap[K, V], k: K): GenIterable[V] =
+ map.getOrElse(k, Nil)
+
+ def parFlatMapKeys[A, B](map: AccMap[A, _])(
+ f: A => GenTraversableOnce[B]): GenIterable[B] =
+ map.keys.flatMap(f).toList
+
+ // Operations on ParIterable
+ def prepAdd[V](it: ParIterable[V]): Addable[V] = it
+ def add[V](addable: Addable[V], v: V): Unit = addable += v
+ def finishAdd[V](addable: Addable[V]): ParIterable[V] = addable
+ }
+
+ private val _interfaces = mutable.Map.empty[String, InterfaceType]
+ protected def getInterface(encodedName: String): InterfaceType =
+ _interfaces.getOrElseUpdate(encodedName, new SeqInterfaceType(encodedName))
+
+ private val methodsToProcess = mutable.ListBuffer.empty[MethodImpl]
+ protected def scheduleMethod(method: MethodImpl): Unit =
+ methodsToProcess += method
+
+ protected def newMethodImpl(owner: MethodContainer,
+ encodedName: String): MethodImpl = new SeqMethodImpl(owner, encodedName)
+
+ protected def processAllTaggedMethods(): Unit = {
+ logProcessingMethods(methodsToProcess.count(!_.deleted))
+ for (method <- methodsToProcess)
+ method.process()
+ methodsToProcess.clear()
+ }
+
+ private class SeqInterfaceType(encName: String) extends InterfaceType(encName) {
+ private val ancestorsAskers = mutable.Set.empty[MethodImpl]
+ private val dynamicCallers = mutable.Map.empty[String, mutable.Set[MethodImpl]]
+ private val staticCallers = mutable.Map.empty[String, mutable.Set[MethodImpl]]
+
+ private var _ancestors: List[String] = encodedName :: Nil
+
+ private var _instantiatedSubclasses: Set[Class] = Set.empty
+
+ def instantiatedSubclasses: Iterable[Class] = _instantiatedSubclasses
+
+ def addInstantiatedSubclass(x: Class): Unit =
+ _instantiatedSubclasses += x
+
+ def removeInstantiatedSubclass(x: Class): Unit =
+ _instantiatedSubclasses -= x
+
+ def ancestors: List[String] = _ancestors
+
+ def ancestors_=(v: List[String]): Unit = {
+ if (v != _ancestors) {
+ _ancestors = v
+ ancestorsAskers.foreach(_.tag())
+ ancestorsAskers.clear()
+ }
+ }
+
+ def registerAskAncestors(asker: MethodImpl): Unit =
+ ancestorsAskers += asker
+
+ def registerDynamicCaller(methodName: String, caller: MethodImpl): Unit =
+ dynamicCallers.getOrElseUpdate(methodName, mutable.Set.empty) += caller
+
+ def registerStaticCaller(methodName: String, caller: MethodImpl): Unit =
+ staticCallers.getOrElseUpdate(methodName, mutable.Set.empty) += caller
+
+ def unregisterDependee(dependee: MethodImpl): Unit = {
+ ancestorsAskers -= dependee
+ dynamicCallers.values.foreach(_ -= dependee)
+ staticCallers.values.foreach(_ -= dependee)
+ }
+
+ def tagDynamicCallersOf(methodName: String): Unit =
+ dynamicCallers.remove(methodName).foreach(_.foreach(_.tag()))
+
+ def tagStaticCallersOf(methodName: String): Unit =
+ staticCallers.remove(methodName).foreach(_.foreach(_.tag()))
+ }
+
+ private class SeqMethodImpl(owner: MethodContainer,
+ encodedName: String) extends MethodImpl(owner, encodedName) {
+
+ private val bodyAskers = mutable.Set.empty[MethodImpl]
+
+ def registerBodyAsker(asker: MethodImpl): Unit =
+ bodyAskers += asker
+
+ def unregisterDependee(dependee: MethodImpl): Unit =
+ bodyAskers -= dependee
+
+ def tagBodyAskers(): Unit = {
+ bodyAskers.foreach(_.tag())
+ bodyAskers.clear()
+ }
+
+ private var _registeredTo: List[Unregisterable] = Nil
+ private var tagged = false
+
+ protected def registeredTo(intf: Unregisterable): Unit =
+ _registeredTo ::= intf
+
+ protected def unregisterFromEverywhere(): Unit = {
+ _registeredTo.foreach(_.unregisterDependee(this))
+ _registeredTo = Nil
+ }
+
+ protected def protectTag(): Boolean = {
+ val res = !tagged
+ tagged = true
+ res
+ }
+ protected def resetTag(): Unit = tagged = false
+
+ }
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/JSTreeBuilder.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/JSTreeBuilder.scala
new file mode 100644
index 0000000..3d37a56
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/JSTreeBuilder.scala
@@ -0,0 +1,16 @@
+package scala.scalajs.tools.optimizer
+
+import scala.scalajs.ir
+import scala.scalajs.tools.javascript
+
+/** An abstract builder taking IR or JSTrees */
+trait JSTreeBuilder {
+ /** Add a JavaScript tree representing a statement.
+ * The tree must be a valid JavaScript tree (typically obtained by
+ * desugaring a full-fledged IR tree).
+ */
+ def addJSTree(tree: javascript.Trees.Tree): Unit
+
+ /** Completes the builder. */
+ def complete(): Unit = ()
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/OptimizerCore.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/OptimizerCore.scala
new file mode 100644
index 0000000..364038b
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/OptimizerCore.scala
@@ -0,0 +1,3572 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import scala.language.implicitConversions
+
+import scala.annotation.{switch, tailrec}
+
+import scala.collection.mutable
+
+import scala.util.control.{NonFatal, ControlThrowable, TailCalls}
+import scala.util.control.TailCalls.{done => _, _} // done is a too generic term
+
+import scala.scalajs.ir._
+import Definitions.{ObjectClass, isConstructorName, isReflProxyName}
+import Infos.OptimizerHints
+import Trees._
+import Types._
+
+import scala.scalajs.tools.sem.Semantics
+import scala.scalajs.tools.javascript.LongImpl
+import scala.scalajs.tools.logging._
+
+/** Optimizer core.
+ * Designed to be "mixed in" [[IncOptimizer#MethodImpl#Optimizer]].
+ * This is the core of the optimizer. It contains all the smart things the
+ * optimizer does. To perform inlining, it relies on abstract protected
+ * methods to identify the target of calls.
+ */
+private[optimizer] abstract class OptimizerCore(semantics: Semantics) {
+ import OptimizerCore._
+
+ type MethodID <: AbstractMethodID
+
+ val myself: MethodID
+
+ /** Returns the body of a method. */
+ protected def getMethodBody(method: MethodID): MethodDef
+
+ /** Returns the list of possible targets for a dynamically linked call. */
+ protected def dynamicCall(intfName: String,
+ methodName: String): List[MethodID]
+
+ /** Returns the target of a static call. */
+ protected def staticCall(className: String,
+ methodName: String): Option[MethodID]
+
+ /** Returns the target of a trait impl call. */
+ protected def traitImplCall(traitImplName: String,
+ methodName: String): Option[MethodID]
+
+ /** Returns the list of ancestors of a class or interface. */
+ protected def getAncestorsOf(encodedName: String): List[String]
+
+ /** Tests whether the given module class has an elidable accessor.
+ * In other words, whether it is safe to discard a LoadModule of that
+ * module class which is not used.
+ */
+ protected def hasElidableModuleAccessor(moduleClassName: String): Boolean
+
+ /** Tests whether the given class is inlineable.
+ * @return None if the class is not inlineable, Some(value) if it is, where
+ * value is a RecordValue with the initial value of its fields.
+ */
+ protected def tryNewInlineableClass(className: String): Option[RecordValue]
+
+ private val usedLocalNames = mutable.Set.empty[String]
+ private val usedLabelNames = mutable.Set.empty[String]
+ private var statesInUse: List[State[_]] = Nil
+
+ private var disableOptimisticOptimizations: Boolean = false
+ private var rollbacksCount: Int = 0
+
+ private val attemptedInlining = mutable.ListBuffer.empty[MethodID]
+
+ private var curTrampolineId = 0
+
+ def optimize(thisType: Type, originalDef: MethodDef): (MethodDef, Infos.MethodInfo) = {
+ try {
+ val MethodDef(name, params, resultType, body) = originalDef
+ val (newParams, newBody) = try {
+ transformIsolatedBody(Some(myself), thisType, params, resultType, body)
+ } catch {
+ case _: TooManyRollbacksException =>
+ usedLocalNames.clear()
+ usedLabelNames.clear()
+ statesInUse = Nil
+ disableOptimisticOptimizations = true
+ transformIsolatedBody(Some(myself), thisType, params, resultType, body)
+ }
+ val m = MethodDef(name, newParams, resultType, newBody)(None)(originalDef.pos)
+ val info = recreateInfo(m)
+ (m, info)
+ } catch {
+ case NonFatal(cause) =>
+ throw new OptimizeException(myself, attemptedInlining.distinct.toList, cause)
+ case e: Throwable =>
+ // This is a fatal exception. Don't wrap, just output debug info error
+ Console.err.println(exceptionMsg(myself, attemptedInlining.distinct.toList))
+ throw e
+ }
+ }
+
+ private def withState[A, B](state: State[A])(body: => B): B = {
+ statesInUse ::= state
+ try body
+ finally statesInUse = statesInUse.tail
+ }
+
+ private def freshLocalName(base: String): String =
+ freshNameGeneric(usedLocalNames, base)
+
+ private def freshLabelName(base: String): String =
+ freshNameGeneric(usedLabelNames, base)
+
+ private val isReserved = isKeyword ++ Seq("arguments", "eval", "ScalaJS")
+
+ private def freshNameGeneric(usedNames: mutable.Set[String], base: String): String = {
+ val result = if (!usedNames.contains(base) && !isReserved(base)) {
+ base
+ } else {
+ var i = 1
+ while (usedNames.contains(base + "$" + i))
+ i += 1
+ base + "$" + i
+ }
+ usedNames += result
+ result
+ }
+
+ private def tryOrRollback(body: CancelFun => TailRec[Tree])(
+ fallbackFun: () => TailRec[Tree]): TailRec[Tree] = {
+ if (disableOptimisticOptimizations) {
+ fallbackFun()
+ } else {
+ val trampolineId = curTrampolineId
+ val savedUsedLocalNames = usedLocalNames.toSet
+ val savedUsedLabelNames = usedLabelNames.toSet
+ val savedStates = statesInUse.map(_.makeBackup())
+
+ body { () =>
+ throw new RollbackException(trampolineId, savedUsedLocalNames,
+ savedUsedLabelNames, savedStates, fallbackFun)
+ }
+ }
+ }
+
+ private def isSubclass(lhs: String, rhs: String): Boolean =
+ getAncestorsOf(lhs).contains(rhs)
+
+ private val isSubclassFun = isSubclass _
+ private def isSubtype(lhs: Type, rhs: Type): Boolean =
+ Types.isSubtype(lhs, rhs)(isSubclassFun)
+
+ /** Transforms a statement.
+ *
+ * For valid expression trees, it is always the case that
+ * {{{
+ * transformStat(tree)
+ * ===
+ * pretransformExpr(tree)(finishTransformStat)
+ * }}}
+ */
+ private def transformStat(tree: Tree)(implicit scope: Scope): Tree =
+ transform(tree, isStat = true)
+
+ /** Transforms an expression.
+ *
+ * It is always the case that
+ * {{{
+ * transformExpr(tree)
+ * ===
+ * pretransformExpr(tree)(finishTransformExpr)
+ * }}}
+ */
+ private def transformExpr(tree: Tree)(implicit scope: Scope): Tree =
+ transform(tree, isStat = false)
+
+ /** Transforms a tree. */
+ private def transform(tree: Tree, isStat: Boolean)(
+ implicit scope: Scope): Tree = {
+
+ @inline implicit def pos = tree.pos
+ val result = tree match {
+ // Definitions
+
+ case VarDef(_, _, _, rhs) =>
+ /* A local var that is last (or alone) in its block is not terribly
+ * useful. Get rid of it.
+ * (Non-last VarDefs in blocks are handled in transformBlock.)
+ */
+ transformStat(rhs)
+
+ // Control flow constructs
+
+ case tree: Block =>
+ transformBlock(tree, isStat)
+
+ case Labeled(ident @ Ident(label, _), tpe, body) =>
+ trampoline {
+ returnable(label, if (isStat) NoType else tpe, body, isStat,
+ usePreTransform = false)(finishTransform(isStat))
+ }
+
+ case Assign(lhs, rhs) =>
+ val cont = { (preTransLhs: PreTransform) =>
+ resolveLocalDef(preTransLhs) match {
+ case PreTransRecordTree(lhsTree, lhsOrigType, lhsCancelFun) =>
+ val recordType = lhsTree.tpe.asInstanceOf[RecordType]
+ pretransformNoLocalDef(rhs) {
+ case PreTransRecordTree(rhsTree, rhsOrigType, rhsCancelFun) =>
+ if (rhsTree.tpe != recordType || rhsOrigType != lhsOrigType)
+ lhsCancelFun()
+ TailCalls.done(Assign(lhsTree, rhsTree))
+ case _ =>
+ lhsCancelFun()
+ }
+ case PreTransTree(lhsTree, _) =>
+ TailCalls.done(Assign(lhsTree, transformExpr(rhs)))
+ }
+ }
+ trampoline {
+ lhs match {
+ case lhs: Select =>
+ pretransformSelectCommon(lhs, isLhsOfAssign = true)(cont)
+ case _ =>
+ pretransformExpr(lhs)(cont)
+ }
+ }
+
+ case Return(expr, optLabel) =>
+ val optInfo = optLabel match {
+ case Some(Ident(label, _)) =>
+ Some(scope.env.labelInfos(label))
+ case None =>
+ scope.env.labelInfos.get("")
+ }
+ optInfo.fold[Tree] {
+ Return(transformExpr(expr), None)
+ } { info =>
+ val newOptLabel = Some(Ident(info.newName, None))
+ if (!info.acceptRecords) {
+ val newExpr = transformExpr(expr)
+ info.returnedTypes.value ::= (newExpr.tpe, RefinedType(newExpr.tpe))
+ Return(newExpr, newOptLabel)
+ } else trampoline {
+ pretransformNoLocalDef(expr) { texpr =>
+ texpr match {
+ case PreTransRecordTree(newExpr, origType, cancelFun) =>
+ info.returnedTypes.value ::= (newExpr.tpe, origType)
+ TailCalls.done(Return(newExpr, newOptLabel))
+ case PreTransTree(newExpr, tpe) =>
+ info.returnedTypes.value ::= (newExpr.tpe, tpe)
+ TailCalls.done(Return(newExpr, newOptLabel))
+ }
+ }
+ }
+ }
+
+ case If(cond, thenp, elsep) =>
+ val newCond = transformExpr(cond)
+ newCond match {
+ case BooleanLiteral(condValue) =>
+ if (condValue) transform(thenp, isStat)
+ else transform(elsep, isStat)
+ case _ =>
+ val newThenp = transform(thenp, isStat)
+ val newElsep = transform(elsep, isStat)
+ val refinedType =
+ constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe)
+ foldIf(newCond, newThenp, newElsep)(refinedType)
+ }
+
+ case While(cond, body, optLabel) =>
+ val newCond = transformExpr(cond)
+ newCond match {
+ case BooleanLiteral(false) => Skip()
+ case _ =>
+ optLabel match {
+ case None =>
+ While(newCond, transformStat(body), None)
+
+ case Some(labelIdent @ Ident(label, _)) =>
+ val newLabel = freshLabelName(label)
+ val info = new LabelInfo(newLabel, acceptRecords = false)
+ While(newCond, {
+ val bodyScope = scope.withEnv(
+ scope.env.withLabelInfo(label, info))
+ transformStat(body)(bodyScope)
+ }, Some(Ident(newLabel, None)(labelIdent.pos)))
+ }
+ }
+
+ case DoWhile(body, cond, None) =>
+ val newBody = transformStat(body)
+ val newCond = transformExpr(cond)
+ newCond match {
+ case BooleanLiteral(false) => newBody
+ case _ => DoWhile(newBody, newCond, None)
+ }
+
+ case Try(block, errVar, EmptyTree, finalizer) =>
+ val newBlock = transform(block, isStat)
+ val newFinalizer = transformStat(finalizer)
+ Try(newBlock, errVar, EmptyTree, newFinalizer)(newBlock.tpe)
+
+ case Try(block, errVar @ Ident(name, originalName), handler, finalizer) =>
+ val newBlock = transform(block, isStat)
+
+ val newName = freshLocalName(name)
+ val newOriginalName = originalName.orElse(Some(name))
+ val localDef = LocalDef(RefinedType(AnyType), true,
+ ReplaceWithVarRef(newName, newOriginalName, new SimpleState(true), None))
+ val newHandler = {
+ val handlerScope = scope.withEnv(scope.env.withLocalDef(name, localDef))
+ transform(handler, isStat)(handlerScope)
+ }
+
+ val newFinalizer = transformStat(finalizer)
+
+ val refinedType = constrainedLub(newBlock.tpe, newHandler.tpe, tree.tpe)
+ Try(newBlock, Ident(newName, newOriginalName)(errVar.pos),
+ newHandler, newFinalizer)(refinedType)
+
+ case Throw(expr) =>
+ Throw(transformExpr(expr))
+
+ case Continue(optLabel) =>
+ val newOptLabel = optLabel map { label =>
+ Ident(scope.env.labelInfos(label.name).newName, None)(label.pos)
+ }
+ Continue(newOptLabel)
+
+ case Match(selector, cases, default) =>
+ val newSelector = transformExpr(selector)
+ newSelector match {
+ case newSelector: Literal =>
+ val body = cases collectFirst {
+ case (alts, body) if alts.exists(literal_===(_, newSelector)) => body
+ } getOrElse default
+ transform(body, isStat)
+ case _ =>
+ Match(newSelector,
+ cases map (c => (c._1, transform(c._2, isStat))),
+ transform(default, isStat))(tree.tpe)
+ }
+
+ // Scala expressions
+
+ case New(cls, ctor, args) =>
+ New(cls, ctor, args map transformExpr)
+
+ case StoreModule(cls, value) =>
+ StoreModule(cls, transformExpr(value))
+
+ case tree: Select =>
+ trampoline {
+ pretransformSelectCommon(tree, isLhsOfAssign = false)(
+ finishTransform(isStat = false))
+ }
+
+ case tree: Apply =>
+ trampoline {
+ pretransformApply(tree, isStat, usePreTransform = false)(
+ finishTransform(isStat))
+ }
+
+ case tree: StaticApply =>
+ trampoline {
+ pretransformStaticApply(tree, isStat, usePreTransform = false)(
+ finishTransform(isStat))
+ }
+
+ case tree: TraitImplApply =>
+ trampoline {
+ pretransformTraitImplApply(tree, isStat, usePreTransform = false)(
+ finishTransform(isStat))
+ }
+
+ case tree @ UnaryOp(_, arg) =>
+ if (isStat) transformStat(arg)
+ else transformUnaryOp(tree)
+
+ case tree @ BinaryOp(op, lhs, rhs) =>
+ if (isStat) Block(transformStat(lhs), transformStat(rhs))
+ else transformBinaryOp(tree)
+
+ case NewArray(tpe, lengths) =>
+ NewArray(tpe, lengths map transformExpr)
+
+ case ArrayValue(tpe, elems) =>
+ ArrayValue(tpe, elems map transformExpr)
+
+ case ArrayLength(array) =>
+ ArrayLength(transformExpr(array))
+
+ case ArraySelect(array, index) =>
+ ArraySelect(transformExpr(array), transformExpr(index))(tree.tpe)
+
+ case RecordValue(tpe, elems) =>
+ RecordValue(tpe, elems map transformExpr)
+
+ case IsInstanceOf(expr, ClassType(ObjectClass)) =>
+ transformExpr(BinaryOp(BinaryOp.!==, expr, Null()))
+
+ case IsInstanceOf(expr, tpe) =>
+ trampoline {
+ pretransformExpr(expr) { texpr =>
+ val result = {
+ if (isSubtype(texpr.tpe.base, tpe)) {
+ if (texpr.tpe.isNullable)
+ BinaryOp(BinaryOp.!==, finishTransformExpr(texpr), Null())
+ else
+ Block(finishTransformStat(texpr), BooleanLiteral(true))
+ } else {
+ if (texpr.tpe.isExact)
+ Block(finishTransformStat(texpr), BooleanLiteral(false))
+ else
+ IsInstanceOf(finishTransformExpr(texpr), tpe)
+ }
+ }
+ TailCalls.done(result)
+ }
+ }
+
+ case AsInstanceOf(expr, ClassType(ObjectClass)) =>
+ transformExpr(expr)
+
+ case AsInstanceOf(expr, cls) =>
+ trampoline {
+ pretransformExpr(tree)(finishTransform(isStat))
+ }
+
+ case Unbox(arg, charCode) =>
+ trampoline {
+ pretransformExpr(arg) { targ =>
+ foldUnbox(targ, charCode)(finishTransform(isStat))
+ }
+ }
+
+ case GetClass(expr) =>
+ GetClass(transformExpr(expr))
+
+ // JavaScript expressions
+
+ case JSNew(ctor, args) =>
+ JSNew(transformExpr(ctor), args map transformExpr)
+
+ case JSDotSelect(qualifier, item) =>
+ JSDotSelect(transformExpr(qualifier), item)
+
+ case JSBracketSelect(qualifier, item) =>
+ JSBracketSelect(transformExpr(qualifier), transformExpr(item))
+
+ case tree: JSFunctionApply =>
+ trampoline {
+ pretransformJSFunctionApply(tree, isStat, usePreTransform = false)(
+ finishTransform(isStat))
+ }
+
+ case JSDotMethodApply(receiver, method, args) =>
+ JSDotMethodApply(transformExpr(receiver), method,
+ args map transformExpr)
+
+ case JSBracketMethodApply(receiver, method, args) =>
+ JSBracketMethodApply(transformExpr(receiver), transformExpr(method),
+ args map transformExpr)
+
+ case JSDelete(JSDotSelect(obj, prop)) =>
+ JSDelete(JSDotSelect(transformExpr(obj), prop))
+
+ case JSDelete(JSBracketSelect(obj, prop)) =>
+ JSDelete(JSBracketSelect(transformExpr(obj), transformExpr(prop)))
+
+ case JSUnaryOp(op, lhs) =>
+ JSUnaryOp(op, transformExpr(lhs))
+
+ case JSBinaryOp(op, lhs, rhs) =>
+ JSBinaryOp(op, transformExpr(lhs), transformExpr(rhs))
+
+ case JSArrayConstr(items) =>
+ JSArrayConstr(items map transformExpr)
+
+ case JSObjectConstr(fields) =>
+ JSObjectConstr(fields map {
+ case (name, value) => (name, transformExpr(value))
+ })
+
+ // Atomic expressions
+
+ case _:VarRef | _:This =>
+ trampoline {
+ pretransformExpr(tree)(finishTransform(isStat))
+ }
+
+ case Closure(captureParams, params, body, captureValues) =>
+ transformClosureCommon(captureParams, params, body,
+ captureValues.map(transformExpr))
+
+ // Trees that need not be transformed
+
+ case _:Skip | _:Debugger | _:LoadModule |
+ _:JSEnvInfo | _:Literal | EmptyTree =>
+ tree
+ }
+
+ if (isStat) keepOnlySideEffects(result)
+ else result
+ }
+
+ private def transformClosureCommon(captureParams: List[ParamDef],
+ params: List[ParamDef], body: Tree, newCaptureValues: List[Tree])(
+ implicit pos: Position): Closure = {
+
+ val (allNewParams, newBody) =
+ transformIsolatedBody(None, AnyType, captureParams ++ params, AnyType, body)
+ val (newCaptureParams, newParams) =
+ allNewParams.splitAt(captureParams.size)
+
+ Closure(newCaptureParams, newParams, newBody, newCaptureValues)
+ }
+
+ private def transformBlock(tree: Block, isStat: Boolean)(
+ implicit scope: Scope): Tree = {
+ def transformList(stats: List[Tree])(
+ implicit scope: Scope): Tree = stats match {
+ case last :: Nil =>
+ transform(last, isStat)
+
+ case (VarDef(Ident(name, originalName), vtpe, mutable, rhs)) :: rest =>
+ trampoline {
+ pretransformExpr(rhs) { trhs =>
+ withBinding(Binding(name, originalName, vtpe, mutable, trhs)) {
+ (restScope, cont1) =>
+ val newRest = transformList(rest)(restScope)
+ cont1(PreTransTree(newRest, RefinedType(newRest.tpe)))
+ } (finishTransform(isStat))
+ }
+ }
+
+ case stat :: rest =>
+ val transformedStat = transformStat(stat)
+ if (transformedStat.tpe == NothingType) transformedStat
+ else Block(transformedStat, transformList(rest))(stat.pos)
+
+ case Nil => // silence the exhaustivity warning in a sensible way
+ Skip()(tree.pos)
+ }
+ transformList(tree.stats)(scope)
+ }
+
+ /** Pretransforms a list of trees as a list of [[PreTransform]]s.
+ * This is a convenience method to use pretransformExpr on a list.
+ */
+ private def pretransformExprs(trees: List[Tree])(
+ cont: List[PreTransform] => TailRec[Tree])(
+ implicit scope: Scope): TailRec[Tree] = {
+ trees match {
+ case first :: rest =>
+ pretransformExpr(first) { tfirst =>
+ pretransformExprs(rest) { trest =>
+ cont(tfirst :: trest)
+ }
+ }
+
+ case Nil =>
+ cont(Nil)
+ }
+ }
+
+ /** Pretransforms two trees as a pair of [[PreTransform]]s.
+ * This is a convenience method to use pretransformExpr on two trees.
+ */
+ private def pretransformExprs(tree1: Tree, tree2: Tree)(
+ cont: (PreTransform, PreTransform) => TailRec[Tree])(
+ implicit scope: Scope): TailRec[Tree] = {
+ pretransformExpr(tree1) { ttree1 =>
+ pretransformExpr(tree2) { ttree2 =>
+ cont(ttree1, ttree2)
+ }
+ }
+ }
+
+ /** Pretransforms a tree and a list of trees as [[PreTransform]]s.
+ * This is a convenience method to use pretransformExpr.
+ */
+ private def pretransformExprs(first: Tree, rest: List[Tree])(
+ cont: (PreTransform, List[PreTransform]) => TailRec[Tree])(
+ implicit scope: Scope): TailRec[Tree] = {
+ pretransformExpr(first) { tfirst =>
+ pretransformExprs(rest) { trest =>
+ cont(tfirst, trest)
+ }
+ }
+ }
+
+ /** Pretransforms a tree to get a refined type while avoiding to force
+ * things we might be able to optimize by folding and aliasing.
+ */
+ private def pretransformExpr(tree: Tree)(cont: PreTransCont)(
+ implicit scope: Scope): TailRec[Tree] = tailcall {
+ @inline implicit def pos = tree.pos
+
+ tree match {
+ case tree: Block =>
+ pretransformBlock(tree)(cont)
+
+ case VarRef(Ident(name, _), _) =>
+ val localDef = scope.env.localDefs.getOrElse(name,
+ sys.error(s"Cannot find local def '$name' at $pos\n" +
+ s"While optimizing $myself\n" +
+ s"Env is ${scope.env}\nInlining ${scope.implsBeingInlined}"))
+ cont(PreTransLocalDef(localDef))
+
+ case This() =>
+ val localDef = scope.env.localDefs.getOrElse("this",
+ sys.error(s"Found invalid 'this' at $pos\n" +
+ s"While optimizing $myself\n" +
+ s"Env is ${scope.env}\nInlining ${scope.implsBeingInlined}"))
+ cont(PreTransLocalDef(localDef))
+
+ case If(cond, thenp, elsep) =>
+ val newCond = transformExpr(cond)
+ newCond match {
+ case BooleanLiteral(condValue) =>
+ if (condValue) pretransformExpr(thenp)(cont)
+ else pretransformExpr(elsep)(cont)
+ case _ =>
+ tryOrRollback { cancelFun =>
+ pretransformNoLocalDef(thenp) { tthenp =>
+ pretransformNoLocalDef(elsep) { telsep =>
+ (tthenp, telsep) match {
+ case (PreTransRecordTree(thenTree, thenOrigType, thenCancelFun),
+ PreTransRecordTree(elseTree, elseOrigType, elseCancelFun)) =>
+ val commonType =
+ if (thenTree.tpe == elseTree.tpe &&
+ thenOrigType == elseOrigType) thenTree.tpe
+ else cancelFun()
+ val refinedOrigType =
+ constrainedLub(thenOrigType, elseOrigType, tree.tpe)
+ cont(PreTransRecordTree(
+ If(newCond, thenTree, elseTree)(commonType),
+ refinedOrigType,
+ cancelFun))
+
+ case (PreTransRecordTree(thenTree, thenOrigType, thenCancelFun), _)
+ if telsep.tpe.isNothingType =>
+ cont(PreTransRecordTree(
+ If(newCond, thenTree, finishTransformExpr(telsep))(thenTree.tpe),
+ thenOrigType,
+ thenCancelFun))
+
+ case (_, PreTransRecordTree(elseTree, elseOrigType, elseCancelFun))
+ if tthenp.tpe.isNothingType =>
+ cont(PreTransRecordTree(
+ If(newCond, finishTransformExpr(tthenp), elseTree)(elseTree.tpe),
+ elseOrigType,
+ elseCancelFun))
+
+ case _ =>
+ val newThenp = finishTransformExpr(tthenp)
+ val newElsep = finishTransformExpr(telsep)
+ val refinedType =
+ constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe)
+ cont(PreTransTree(
+ foldIf(newCond, newThenp, newElsep)(refinedType)))
+ }
+ }
+ }
+ } { () =>
+ val newThenp = transformExpr(thenp)
+ val newElsep = transformExpr(elsep)
+ val refinedType =
+ constrainedLub(newThenp.tpe, newElsep.tpe, tree.tpe)
+ cont(PreTransTree(
+ foldIf(newCond, newThenp, newElsep)(refinedType)))
+ }
+ }
+
+ case Match(selector, cases, default) =>
+ val newSelector = transformExpr(selector)
+ newSelector match {
+ case newSelector: Literal =>
+ val body = cases collectFirst {
+ case (alts, body) if alts.exists(literal_===(_, newSelector)) => body
+ } getOrElse default
+ pretransformExpr(body)(cont)
+ case _ =>
+ cont(PreTransTree(Match(newSelector,
+ cases map (c => (c._1, transformExpr(c._2))),
+ transformExpr(default))(tree.tpe)))
+ }
+
+ case Labeled(ident @ Ident(label, _), tpe, body) =>
+ returnable(label, tpe, body, isStat = false, usePreTransform = true)(cont)
+
+ case New(cls @ ClassType(className), ctor, args) =>
+ tryNewInlineableClass(className) match {
+ case Some(initialValue) =>
+ pretransformExprs(args) { targs =>
+ tryOrRollback { cancelFun =>
+ inlineClassConstructor(
+ new AllocationSite(tree),
+ cls, initialValue, ctor, targs, cancelFun)(cont)
+ } { () =>
+ cont(PreTransTree(
+ New(cls, ctor, targs.map(finishTransformExpr)),
+ RefinedType(cls, isExact = true, isNullable = false)))
+ }
+ }
+ case None =>
+ cont(PreTransTree(
+ New(cls, ctor, args.map(transformExpr)),
+ RefinedType(cls, isExact = true, isNullable = false)))
+ }
+
+ case tree: Select =>
+ pretransformSelectCommon(tree, isLhsOfAssign = false)(cont)
+
+ case tree: Apply =>
+ pretransformApply(tree, isStat = false,
+ usePreTransform = true)(cont)
+
+ case tree: StaticApply =>
+ pretransformStaticApply(tree, isStat = false,
+ usePreTransform = true)(cont)
+
+ case tree: TraitImplApply =>
+ pretransformTraitImplApply(tree, isStat = false,
+ usePreTransform = true)(cont)
+
+ case tree: JSFunctionApply =>
+ pretransformJSFunctionApply(tree, isStat = false,
+ usePreTransform = true)(cont)
+
+ case AsInstanceOf(expr, tpe) =>
+ pretransformExpr(expr) { texpr =>
+ tpe match {
+ case ClassType(ObjectClass) =>
+ cont(texpr)
+ case _ =>
+ if (isSubtype(texpr.tpe.base, tpe)) {
+ cont(texpr)
+ } else {
+ cont(PreTransTree(
+ AsInstanceOf(finishTransformExpr(texpr), tpe)))
+ }
+ }
+ }
+
+ case Closure(captureParams, params, body, captureValues) =>
+ pretransformExprs(captureValues) { tcaptureValues =>
+ tryOrRollback { cancelFun =>
+ val captureBindings = for {
+ (ParamDef(Ident(name, origName), tpe, mutable), value) <-
+ captureParams zip tcaptureValues
+ } yield {
+ Binding(name, origName, tpe, mutable, value)
+ }
+ withNewLocalDefs(captureBindings) { (captureLocalDefs, cont1) =>
+ val alreadyUsedState = new SimpleState[Boolean](false)
+ withState(alreadyUsedState) {
+ val replacement = TentativeClosureReplacement(
+ captureParams, params, body, captureLocalDefs,
+ alreadyUsedState, cancelFun)
+ val localDef = LocalDef(
+ RefinedType(AnyType, isExact = false, isNullable = false),
+ mutable = false,
+ replacement)
+ cont1(PreTransLocalDef(localDef))
+ }
+ } (cont)
+ } { () =>
+ val newClosure = transformClosureCommon(captureParams, params, body,
+ tcaptureValues.map(finishTransformExpr))
+ cont(PreTransTree(
+ newClosure,
+ RefinedType(AnyType, isExact = false, isNullable = false)))
+ }
+ }
+
+ case _ =>
+ val result = transformExpr(tree)
+ cont(PreTransTree(result, RefinedType(result.tpe)))
+ }
+ }
+
+ private def pretransformBlock(tree: Block)(
+ cont: PreTransCont)(
+ implicit scope: Scope): TailRec[Tree] = {
+ def pretransformList(stats: List[Tree])(
+ cont: PreTransCont)(
+ implicit scope: Scope): TailRec[Tree] = stats match {
+ case last :: Nil =>
+ pretransformExpr(last)(cont)
+
+ case (VarDef(Ident(name, originalName), vtpe, mutable, rhs)) :: rest =>
+ pretransformExpr(rhs) { trhs =>
+ withBinding(Binding(name, originalName, vtpe, mutable, trhs)) {
+ (restScope, cont1) =>
+ pretransformList(rest)(cont1)(restScope)
+ } (cont)
+ }
+
+ case stat :: rest =>
+ implicit val pos = tree.pos
+ val transformedStat = transformStat(stat)
+ transformedStat match {
+ case Skip() =>
+ pretransformList(rest)(cont)
+ case _ =>
+ if (transformedStat.tpe == NothingType)
+ cont(PreTransTree(transformedStat, RefinedType.Nothing))
+ else {
+ pretransformList(rest) { trest =>
+ cont(PreTransBlock(transformedStat :: Nil, trest))
+ }
+ }
+ }
+
+ case Nil => // silence the exhaustivity warning in a sensible way
+ TailCalls.done(Skip()(tree.pos))
+ }
+ pretransformList(tree.stats)(cont)(scope)
+ }
+
+ private def pretransformSelectCommon(tree: Select, isLhsOfAssign: Boolean)(
+ cont: PreTransCont)(
+ implicit scope: Scope): TailRec[Tree] = {
+ val Select(qualifier, item, mutable) = tree
+ pretransformExpr(qualifier) { preTransQual =>
+ pretransformSelectCommon(tree.tpe, preTransQual, item, mutable,
+ isLhsOfAssign)(cont)(scope, tree.pos)
+ }
+ }
+
+ private def pretransformSelectCommon(expectedType: Type,
+ preTransQual: PreTransform, item: Ident, mutable: Boolean,
+ isLhsOfAssign: Boolean)(
+ cont: PreTransCont)(
+ implicit scope: Scope, pos: Position): TailRec[Tree] = {
+ preTransQual match {
+ case PreTransLocalDef(LocalDef(_, _,
+ InlineClassBeingConstructedReplacement(fieldLocalDefs, cancelFun))) =>
+ val fieldLocalDef = fieldLocalDefs(item.name)
+ if (!isLhsOfAssign || fieldLocalDef.mutable) {
+ cont(PreTransLocalDef(fieldLocalDef))
+ } else {
+ /* This is an assignment to an immutable field of a inlineable class
+ * being constructed, but that does not appear at the "top-level" of
+ * one of its constructors. We cannot handle those, so we cancel.
+ * (Assignments at the top-level are normal initializations of these
+ * fields, and are transformed as vals in inlineClassConstructor.)
+ */
+ cancelFun()
+ }
+ case PreTransLocalDef(LocalDef(_, _,
+ InlineClassInstanceReplacement(_, fieldLocalDefs, cancelFun))) =>
+ val fieldLocalDef = fieldLocalDefs(item.name)
+ if (!isLhsOfAssign || fieldLocalDef.mutable) {
+ cont(PreTransLocalDef(fieldLocalDef))
+ } else {
+ /* In an ideal world, this should not happen (assigning to an
+ * immutable field of an already constructed object). However, since
+ * we cannot IR-check that this does not happen (see #1021), this is
+ * effectively allowed by the IR spec. We are therefore not allowed
+ * to crash. We cancel instead. This will become an actual field
+ * (rather than an optimized local val) which is not considered pure
+ * (for that same reason).
+ */
+ cancelFun()
+ }
+ case _ =>
+ resolveLocalDef(preTransQual) match {
+ case PreTransRecordTree(newQual, origType, cancelFun) =>
+ val recordType = newQual.tpe.asInstanceOf[RecordType]
+ val field = recordType.findField(item.name)
+ val sel = Select(newQual, item, mutable)(field.tpe)
+ sel.tpe match {
+ case _: RecordType =>
+ cont(PreTransRecordTree(sel, RefinedType(expectedType), cancelFun))
+ case _ =>
+ cont(PreTransTree(sel, RefinedType(sel.tpe)))
+ }
+
+ case PreTransTree(newQual, _) =>
+ cont(PreTransTree(Select(newQual, item, mutable)(expectedType),
+ RefinedType(expectedType)))
+ }
+ }
+ }
+
+ /** Resolves any LocalDef in a [[PreTransform]]. */
+ private def resolveLocalDef(preTrans: PreTransform): PreTransGenTree = {
+ implicit val pos = preTrans.pos
+ preTrans match {
+ case PreTransBlock(stats, result) =>
+ resolveLocalDef(result) match {
+ case PreTransRecordTree(tree, tpe, cancelFun) =>
+ PreTransRecordTree(Block(stats :+ tree), tpe, cancelFun)
+ case PreTransTree(tree, tpe) =>
+ PreTransTree(Block(stats :+ tree), tpe)
+ }
+
+ case PreTransLocalDef(localDef @ LocalDef(tpe, mutable, replacement)) =>
+ replacement match {
+ case ReplaceWithRecordVarRef(name, originalName,
+ recordType, used, cancelFun) =>
+ used.value = true
+ PreTransRecordTree(
+ VarRef(Ident(name, originalName), mutable)(recordType),
+ tpe, cancelFun)
+
+ case InlineClassInstanceReplacement(recordType, fieldLocalDefs, cancelFun) =>
+ if (!isImmutableType(recordType))
+ cancelFun()
+ PreTransRecordTree(
+ RecordValue(recordType, recordType.fields.map(
+ f => fieldLocalDefs(f.name).newReplacement)),
+ tpe, cancelFun)
+
+ case _ =>
+ PreTransTree(localDef.newReplacement, localDef.tpe)
+ }
+
+ case preTrans: PreTransGenTree =>
+ preTrans
+ }
+ }
+
+ /** Combines pretransformExpr and resolveLocalDef in one convenience method. */
+ private def pretransformNoLocalDef(tree: Tree)(
+ cont: PreTransGenTree => TailRec[Tree])(
+ implicit scope: Scope): TailRec[Tree] = {
+ pretransformExpr(tree) { ttree =>
+ cont(resolveLocalDef(ttree))
+ }
+ }
+
+ /** Finishes a pretransform, either a statement or an expression. */
+ private def finishTransform(isStat: Boolean): PreTransCont = { preTrans =>
+ TailCalls.done {
+ if (isStat) finishTransformStat(preTrans)
+ else finishTransformExpr(preTrans)
+ }
+ }
+
+ /** Finishes an expression pretransform to get a normal [[Tree]].
+ * This method (together with finishTransformStat) must not be called more
+ * than once per pretransform and per translation.
+ * By "per translation", we mean in an alternative path through
+ * `tryOrRollback`. It could still be called several times as long as
+ * it is once in the 'try' part and once in the 'fallback' part.
+ */
+ private def finishTransformExpr(preTrans: PreTransform): Tree = {
+ implicit val pos = preTrans.pos
+ preTrans match {
+ case PreTransBlock(stats, result) =>
+ Block(stats :+ finishTransformExpr(result))
+ case PreTransLocalDef(localDef) =>
+ localDef.newReplacement
+ case PreTransRecordTree(_, _, cancelFun) =>
+ cancelFun()
+ case PreTransTree(tree, _) =>
+ tree
+ }
+ }
+
+ /** Finishes a statement pretransform to get a normal [[Tree]].
+ * This method (together with finishTransformExpr) must not be called more
+ * than once per pretransform and per translation.
+ * By "per translation", we mean in an alternative path through
+ * `tryOrRollback`. It could still be called several times as long as
+ * it is once in the 'try' part and once in the 'fallback' part.
+ */
+ private def finishTransformStat(stat: PreTransform): Tree = stat match {
+ case PreTransBlock(stats, result) =>
+ Block(stats :+ finishTransformStat(result))(stat.pos)
+ case PreTransLocalDef(_) =>
+ Skip()(stat.pos)
+ case PreTransRecordTree(tree, _, _) =>
+ keepOnlySideEffects(tree)
+ case PreTransTree(tree, _) =>
+ keepOnlySideEffects(tree)
+ }
+
+ /** Keeps only the side effects of a Tree (overapproximation). */
+ private def keepOnlySideEffects(stat: Tree): Tree = stat match {
+ case _:VarRef | _:This | _:Literal =>
+ Skip()(stat.pos)
+ case Block(init :+ last) =>
+ Block(init :+ keepOnlySideEffects(last))(stat.pos)
+ case LoadModule(ClassType(moduleClassName)) =>
+ if (hasElidableModuleAccessor(moduleClassName)) Skip()(stat.pos)
+ else stat
+ case Select(LoadModule(ClassType(moduleClassName)), _, _) =>
+ if (hasElidableModuleAccessor(moduleClassName)) Skip()(stat.pos)
+ else stat
+ case Closure(_, _, _, captureValues) =>
+ Block(captureValues.map(keepOnlySideEffects))(stat.pos)
+ case UnaryOp(_, arg) =>
+ keepOnlySideEffects(arg)
+ case If(cond, thenp, elsep) =>
+ (keepOnlySideEffects(thenp), keepOnlySideEffects(elsep)) match {
+ case (Skip(), Skip()) => keepOnlySideEffects(cond)
+ case (newThenp, newElsep) => If(cond, newThenp, newElsep)(NoType)(stat.pos)
+ }
+ case BinaryOp(_, lhs, rhs) =>
+ Block(keepOnlySideEffects(lhs), keepOnlySideEffects(rhs))(stat.pos)
+ case RecordValue(_, elems) =>
+ Block(elems.map(keepOnlySideEffects))(stat.pos)
+ case _ =>
+ stat
+ }
+
+ private def pretransformApply(tree: Apply, isStat: Boolean,
+ usePreTransform: Boolean)(
+ cont: PreTransCont)(
+ implicit scope: Scope): TailRec[Tree] = {
+ val Apply(receiver, methodIdent @ Ident(methodName, _), args) = tree
+ implicit val pos = tree.pos
+
+ pretransformExpr(receiver) { treceiver =>
+ def treeNotInlined0(transformedArgs: List[Tree]) =
+ cont(PreTransTree(Apply(finishTransformExpr(treceiver), methodIdent,
+ transformedArgs)(tree.tpe)(tree.pos), RefinedType(tree.tpe)))
+
+ def treeNotInlined = treeNotInlined0(args.map(transformExpr))
+
+ treceiver.tpe.base match {
+ case NothingType =>
+ cont(treceiver)
+ case NullType =>
+ cont(PreTransTree(Block(
+ finishTransformStat(treceiver),
+ CallHelper("throwNullPointerException")(NothingType))))
+ case _ =>
+ if (isReflProxyName(methodName)) {
+ // Never inline reflective proxies
+ treeNotInlined
+ } else {
+ val cls = boxedClassForType(treceiver.tpe.base)
+ val impls =
+ if (treceiver.tpe.isExact) staticCall(cls, methodName).toList
+ else dynamicCall(cls, methodName)
+ val allocationSite = treceiver.tpe.allocationSite
+ if (impls.isEmpty || impls.exists(impl =>
+ scope.implsBeingInlined((allocationSite, impl)))) {
+ // isEmpty could happen, have to leave it as is for the TypeError
+ treeNotInlined
+ } else if (impls.size == 1) {
+ val target = impls.head
+ pretransformExprs(args) { targs =>
+ val intrinsicCode = getIntrinsicCode(target)
+ if (intrinsicCode >= 0) {
+ callIntrinsic(intrinsicCode, Some(treceiver), targs,
+ isStat, usePreTransform)(cont)
+ } else if (target.inlineable || shouldInlineBecauseOfArgs(treceiver :: targs)) {
+ inline(allocationSite, Some(treceiver), targs, target,
+ isStat, usePreTransform)(cont)
+ } else {
+ treeNotInlined0(targs.map(finishTransformExpr))
+ }
+ }
+ } else {
+ if (impls.forall(_.isTraitImplForwarder)) {
+ val reference = impls.head
+ val TraitImplApply(ClassType(traitImpl), Ident(methodName, _), _) =
+ getMethodBody(reference).body
+ if (!impls.tail.forall(getMethodBody(_).body match {
+ case TraitImplApply(ClassType(`traitImpl`),
+ Ident(`methodName`, _), _) => true
+ case _ => false
+ })) {
+ // Not all calling the same method in the same trait impl
+ treeNotInlined
+ } else {
+ pretransformExprs(args) { targs =>
+ inline(allocationSite, Some(treceiver), targs, reference,
+ isStat, usePreTransform)(cont)
+ }
+ }
+ } else {
+ // TODO? Inline multiple non-trait-impl-forwarder with the exact same body?
+ treeNotInlined
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private def boxedClassForType(tpe: Type): String = (tpe: @unchecked) match {
+ case ClassType(cls) => cls
+ case AnyType => Definitions.ObjectClass
+ case UndefType => Definitions.BoxedUnitClass
+ case BooleanType => Definitions.BoxedBooleanClass
+ case IntType => Definitions.BoxedIntegerClass
+ case LongType => Definitions.BoxedLongClass
+ case FloatType => Definitions.BoxedFloatClass
+ case DoubleType => Definitions.BoxedDoubleClass
+ case StringType => Definitions.StringClass
+ case ArrayType(_, _) => Definitions.ObjectClass
+ }
+
+ private def pretransformStaticApply(tree: StaticApply, isStat: Boolean,
+ usePreTransform: Boolean)(
+ cont: PreTransCont)(
+ implicit scope: Scope): TailRec[Tree] = {
+ val StaticApply(receiver, clsType @ ClassType(cls),
+ methodIdent @ Ident(methodName, _), args) = tree
+ implicit val pos = tree.pos
+
+ def treeNotInlined0(transformedReceiver: Tree, transformedArgs: List[Tree]) =
+ cont(PreTransTree(StaticApply(transformedReceiver, clsType,
+ methodIdent, transformedArgs)(tree.tpe), RefinedType(tree.tpe)))
+
+ def treeNotInlined =
+ treeNotInlined0(transformExpr(receiver), args.map(transformExpr))
+
+ if (isReflProxyName(methodName)) {
+ // Never inline reflective proxies
+ treeNotInlined
+ } else {
+ val optTarget = staticCall(cls, methodName)
+ if (optTarget.isEmpty) {
+ // just in case
+ treeNotInlined
+ } else {
+ val target = optTarget.get
+ pretransformExprs(receiver, args) { (treceiver, targs) =>
+ val intrinsicCode = getIntrinsicCode(target)
+ if (intrinsicCode >= 0) {
+ callIntrinsic(intrinsicCode, Some(treceiver), targs,
+ isStat, usePreTransform)(cont)
+ } else {
+ val shouldInline =
+ target.inlineable || shouldInlineBecauseOfArgs(treceiver :: targs)
+ val allocationSite = treceiver.tpe.allocationSite
+ val beingInlined =
+ scope.implsBeingInlined((allocationSite, target))
+
+ if (shouldInline && !beingInlined) {
+ inline(allocationSite, Some(treceiver), targs, target,
+ isStat, usePreTransform)(cont)
+ } else {
+ treeNotInlined0(finishTransformExpr(treceiver),
+ targs.map(finishTransformExpr))
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private def pretransformTraitImplApply(tree: TraitImplApply, isStat: Boolean,
+ usePreTransform: Boolean)(
+ cont: PreTransCont)(
+ implicit scope: Scope): TailRec[Tree] = {
+ val TraitImplApply(implType @ ClassType(impl),
+ methodIdent @ Ident(methodName, _), args) = tree
+ implicit val pos = tree.pos
+
+ def treeNotInlined0(transformedArgs: List[Tree]) =
+ cont(PreTransTree(TraitImplApply(implType, methodIdent,
+ transformedArgs)(tree.tpe), RefinedType(tree.tpe)))
+
+ def treeNotInlined = treeNotInlined0(args.map(transformExpr))
+
+ val optTarget = traitImplCall(impl, methodName)
+ if (optTarget.isEmpty) {
+ // just in case
+ treeNotInlined
+ } else {
+ val target = optTarget.get
+ pretransformExprs(args) { targs =>
+ val intrinsicCode = getIntrinsicCode(target)
+ if (intrinsicCode >= 0) {
+ callIntrinsic(intrinsicCode, None, targs,
+ isStat, usePreTransform)(cont)
+ } else {
+ val shouldInline =
+ target.inlineable || shouldInlineBecauseOfArgs(targs)
+ val allocationSite = targs.headOption.flatMap(_.tpe.allocationSite)
+ val beingInlined =
+ scope.implsBeingInlined((allocationSite, target))
+
+ if (shouldInline && !beingInlined) {
+ inline(allocationSite, None, targs, target,
+ isStat, usePreTransform)(cont)
+ } else {
+ treeNotInlined0(targs.map(finishTransformExpr))
+ }
+ }
+ }
+ }
+ }
+
+ private def pretransformJSFunctionApply(tree: JSFunctionApply,
+ isStat: Boolean, usePreTransform: Boolean)(
+ cont: PreTransCont)(
+ implicit scope: Scope, pos: Position): TailRec[Tree] = {
+ val JSFunctionApply(fun, args) = tree
+ implicit val pos = tree.pos
+
+ pretransformExpr(fun) { tfun =>
+ tfun match {
+ case PreTransLocalDef(LocalDef(_, false,
+ closure @ TentativeClosureReplacement(
+ captureParams, params, body, captureLocalDefs,
+ alreadyUsed, cancelFun))) if !alreadyUsed.value =>
+ alreadyUsed.value = true
+ pretransformExprs(args) { targs =>
+ inlineBody(
+ Some(PreTransTree(Undefined())), // `this` is `undefined`
+ captureParams ++ params, AnyType, body,
+ captureLocalDefs.map(PreTransLocalDef(_)) ++ targs, isStat,
+ usePreTransform)(cont)
+ }
+
+ case _ =>
+ cont(PreTransTree(
+ JSFunctionApply(finishTransformExpr(tfun), args.map(transformExpr))))
+ }
+ }
+ }
+
+ private def shouldInlineBecauseOfArgs(
+ receiverAndArgs: List[PreTransform]): Boolean = {
+ def isLikelyOptimizable(arg: PreTransform): Boolean = arg match {
+ case PreTransBlock(_, result) =>
+ isLikelyOptimizable(result)
+
+ case PreTransLocalDef(localDef) =>
+ localDef.replacement match {
+ case TentativeClosureReplacement(_, _, _, _, _, _) => true
+ case ReplaceWithRecordVarRef(_, _, _, _, _) => true
+ case InlineClassBeingConstructedReplacement(_, _) => true
+ case InlineClassInstanceReplacement(_, _, _) => true
+ case _ => false
+ }
+
+ case PreTransRecordTree(_, _, _) =>
+ true
+
+ case _ =>
+ arg.tpe.base match {
+ case ClassType("s_Predef$$less$colon$less" | "s_Predef$$eq$colon$eq") =>
+ true
+ case _ =>
+ false
+ }
+ }
+ receiverAndArgs.exists(isLikelyOptimizable)
+ }
+
+ private def inline(allocationSite: Option[AllocationSite],
+ optReceiver: Option[PreTransform],
+ args: List[PreTransform], target: MethodID, isStat: Boolean,
+ usePreTransform: Boolean)(
+ cont: PreTransCont)(
+ implicit scope: Scope, pos: Position): TailRec[Tree] = {
+
+ attemptedInlining += target
+
+ val MethodDef(_, formals, resultType, body) = getMethodBody(target)
+
+ body match {
+ case Skip() =>
+ assert(isStat, "Found Skip() in expression position")
+ cont(PreTransTree(
+ Block((optReceiver ++: args).map(finishTransformStat)),
+ RefinedType.NoRefinedType))
+
+ case _: Literal =>
+ cont(PreTransTree(
+ Block((optReceiver ++: args).map(finishTransformStat) :+ body),
+ RefinedType(body.tpe)))
+
+ case This() if args.isEmpty =>
+ assert(optReceiver.isDefined,
+ "There was a This(), there should be a receiver")
+ cont(optReceiver.get)
+
+ case Select(This(), field, mutable) if formals.isEmpty =>
+ assert(optReceiver.isDefined,
+ "There was a This(), there should be a receiver")
+ pretransformSelectCommon(body.tpe, optReceiver.get, field, mutable,
+ isLhsOfAssign = false)(cont)
+
+ case Assign(lhs @ Select(This(), field, mutable), VarRef(Ident(rhsName, _), _))
+ if formals.size == 1 && formals.head.name.name == rhsName =>
+ assert(isStat, "Found Assign in expression position")
+ assert(optReceiver.isDefined,
+ "There was a This(), there should be a receiver")
+ pretransformSelectCommon(lhs.tpe, optReceiver.get, field, mutable,
+ isLhsOfAssign = true) { preTransLhs =>
+ // TODO Support assignment of record
+ cont(PreTransTree(
+ Assign(finishTransformExpr(preTransLhs),
+ finishTransformExpr(args.head)),
+ RefinedType.NoRefinedType))
+ }
+
+ case _ =>
+ val targetID = (allocationSite, target)
+ inlineBody(optReceiver, formals, resultType, body, args, isStat,
+ usePreTransform)(cont)(scope.inlining(targetID), pos)
+ }
+ }
+
+ private def inlineBody(optReceiver: Option[PreTransform],
+ formals: List[ParamDef], resultType: Type, body: Tree,
+ args: List[PreTransform], isStat: Boolean,
+ usePreTransform: Boolean)(
+ cont: PreTransCont)(
+ implicit scope: Scope, pos: Position): TailRec[Tree] = tailcall {
+
+ val optReceiverBinding = optReceiver map { receiver =>
+ Binding("this", None, receiver.tpe.base, false, receiver)
+ }
+
+ val argsBindings = for {
+ (ParamDef(Ident(name, originalName), tpe, mutable), arg) <- formals zip args
+ } yield {
+ Binding(name, originalName, tpe, mutable, arg)
+ }
+
+ withBindings(optReceiverBinding ++: argsBindings) { (bodyScope, cont1) =>
+ returnable("", resultType, body, isStat, usePreTransform)(
+ cont1)(bodyScope, pos)
+ } (cont) (scope.withEnv(OptEnv.Empty))
+ }
+
+ private def callIntrinsic(code: Int, optTReceiver: Option[PreTransform],
+ targs: List[PreTransform], isStat: Boolean, usePreTransform: Boolean)(
+ cont: PreTransCont)(
+ implicit pos: Position): TailRec[Tree] = {
+
+ import Intrinsics._
+
+ implicit def string2ident(s: String): Ident = Ident(s, None)
+
+ lazy val newArgs = targs.map(finishTransformExpr)
+
+ @inline def contTree(result: Tree) = cont(PreTransTree(result))
+
+ @inline def StringClassType = ClassType(Definitions.StringClass)
+
+ def asRTLong(arg: Tree): Tree =
+ AsInstanceOf(arg, ClassType(LongImpl.RuntimeLongClass))
+ def firstArgAsRTLong: Tree =
+ asRTLong(newArgs.head)
+
+ (code: @switch) match {
+ // java.lang.System
+
+ case ArrayCopy =>
+ assert(isStat, "System.arraycopy must be used in statement position")
+ contTree(CallHelper("systemArraycopy", newArgs)(NoType))
+ case IdentityHashCode =>
+ contTree(CallHelper("systemIdentityHashCode", newArgs)(IntType))
+
+ // scala.scalajs.runtime package object
+
+ case PropertiesOf =>
+ contTree(CallHelper("propertiesOf", newArgs)(AnyType))
+
+ // java.lang.Long
+
+ case LongToString =>
+ contTree(Apply(firstArgAsRTLong, "toString__T", Nil)(StringClassType))
+ case LongCompare =>
+ contTree(Apply(firstArgAsRTLong, "compareTo__sjsr_RuntimeLong__I",
+ List(asRTLong(newArgs(1))))(IntType))
+ case LongBitCount =>
+ contTree(Apply(firstArgAsRTLong, LongImpl.bitCount, Nil)(IntType))
+ case LongSignum =>
+ contTree(Apply(firstArgAsRTLong, LongImpl.signum, Nil)(LongType))
+ case LongLeading0s =>
+ contTree(Apply(firstArgAsRTLong, LongImpl.numberOfLeadingZeros, Nil)(IntType))
+ case LongTrailing0s =>
+ contTree(Apply(firstArgAsRTLong, LongImpl.numberOfTrailingZeros, Nil)(IntType))
+ case LongToBinStr =>
+ contTree(Apply(firstArgAsRTLong, LongImpl.toBinaryString, Nil)(StringClassType))
+ case LongToHexStr =>
+ contTree(Apply(firstArgAsRTLong, LongImpl.toHexString, Nil)(StringClassType))
+ case LongToOctalStr =>
+ contTree(Apply(firstArgAsRTLong, LongImpl.toOctalString, Nil)(StringClassType))
+
+ // TypedArray conversions
+
+ case ByteArrayToInt8Array =>
+ contTree(CallHelper("byteArray2TypedArray", newArgs)(AnyType))
+ case ShortArrayToInt16Array =>
+ contTree(CallHelper("shortArray2TypedArray", newArgs)(AnyType))
+ case CharArrayToUint16Array =>
+ contTree(CallHelper("charArray2TypedArray", newArgs)(AnyType))
+ case IntArrayToInt32Array =>
+ contTree(CallHelper("intArray2TypedArray", newArgs)(AnyType))
+ case FloatArrayToFloat32Array =>
+ contTree(CallHelper("floatArray2TypedArray", newArgs)(AnyType))
+ case DoubleArrayToFloat64Array =>
+ contTree(CallHelper("doubleArray2TypedArray", newArgs)(AnyType))
+
+ case Int8ArrayToByteArray =>
+ contTree(CallHelper("typedArray2ByteArray", newArgs)(AnyType))
+ case Int16ArrayToShortArray =>
+ contTree(CallHelper("typedArray2ShortArray", newArgs)(AnyType))
+ case Uint16ArrayToCharArray =>
+ contTree(CallHelper("typedArray2CharArray", newArgs)(AnyType))
+ case Int32ArrayToIntArray =>
+ contTree(CallHelper("typedArray2IntArray", newArgs)(AnyType))
+ case Float32ArrayToFloatArray =>
+ contTree(CallHelper("typedArray2FloatArray", newArgs)(AnyType))
+ case Float64ArrayToDoubleArray =>
+ contTree(CallHelper("typedArray2DoubleArray", newArgs)(AnyType))
+ }
+ }
+
+ private def inlineClassConstructor(allocationSite: AllocationSite,
+ cls: ClassType, initialValue: RecordValue,
+ ctor: Ident, args: List[PreTransform], cancelFun: CancelFun)(
+ cont: PreTransCont)(
+ implicit scope: Scope, pos: Position): TailRec[Tree] = {
+
+ val RecordValue(recordType, initialFieldValues) = initialValue
+
+ pretransformExprs(initialFieldValues) { tinitialFieldValues =>
+ val initialFieldBindings = for {
+ (RecordType.Field(name, originalName, tpe, mutable), value) <-
+ recordType.fields zip tinitialFieldValues
+ } yield {
+ Binding(name, originalName, tpe, mutable, value)
+ }
+
+ withNewLocalDefs(initialFieldBindings) { (initialFieldLocalDefList, cont1) =>
+ val fieldNames = initialValue.tpe.fields.map(_.name)
+ val initialFieldLocalDefs =
+ Map(fieldNames zip initialFieldLocalDefList: _*)
+
+ inlineClassConstructorBody(allocationSite, initialFieldLocalDefs,
+ cls, cls, ctor, args, cancelFun) { (finalFieldLocalDefs, cont2) =>
+ cont2(PreTransLocalDef(LocalDef(
+ RefinedType(cls, isExact = true, isNullable = false,
+ allocationSite = Some(allocationSite)),
+ mutable = false,
+ InlineClassInstanceReplacement(recordType, finalFieldLocalDefs, cancelFun))))
+ } (cont1)
+ } (cont)
+ }
+ }
+
+ private def inlineClassConstructorBody(
+ allocationSite: AllocationSite,
+ inputFieldsLocalDefs: Map[String, LocalDef], cls: ClassType,
+ ctorClass: ClassType, ctor: Ident, args: List[PreTransform],
+ cancelFun: CancelFun)(
+ buildInner: (Map[String, LocalDef], PreTransCont) => TailRec[Tree])(
+ cont: PreTransCont)(
+ implicit scope: Scope): TailRec[Tree] = tailcall {
+
+ val target = staticCall(ctorClass.className, ctor.name).getOrElse(cancelFun())
+ val targetID = (Some(allocationSite), target)
+ if (scope.implsBeingInlined.contains(targetID))
+ cancelFun()
+
+ val MethodDef(_, formals, _, BlockOrAlone(stats, This())) =
+ getMethodBody(target)
+
+ val argsBindings = for {
+ (ParamDef(Ident(name, originalName), tpe, mutable), arg) <- formals zip args
+ } yield {
+ Binding(name, originalName, tpe, mutable, arg)
+ }
+
+ withBindings(argsBindings) { (bodyScope, cont1) =>
+ val thisLocalDef = LocalDef(
+ RefinedType(cls, isExact = true, isNullable = false), false,
+ InlineClassBeingConstructedReplacement(inputFieldsLocalDefs, cancelFun))
+ val statsScope = bodyScope.inlining(targetID).withEnv(
+ bodyScope.env.withLocalDef("this", thisLocalDef))
+ inlineClassConstructorBodyList(allocationSite, thisLocalDef,
+ inputFieldsLocalDefs, cls, stats, cancelFun)(
+ buildInner)(cont1)(statsScope)
+ } (cont) (scope.withEnv(OptEnv.Empty))
+ }
+
+ private def inlineClassConstructorBodyList(
+ allocationSite: AllocationSite,
+ thisLocalDef: LocalDef, inputFieldsLocalDefs: Map[String, LocalDef],
+ cls: ClassType, stats: List[Tree], cancelFun: CancelFun)(
+ buildInner: (Map[String, LocalDef], PreTransCont) => TailRec[Tree])(
+ cont: PreTransCont)(
+ implicit scope: Scope): TailRec[Tree] = {
+ stats match {
+ case This() :: rest =>
+ inlineClassConstructorBodyList(allocationSite, thisLocalDef,
+ inputFieldsLocalDefs, cls, rest, cancelFun)(buildInner)(cont)
+
+ case Assign(s @ Select(ths: This,
+ Ident(fieldName, fieldOrigName), false), value) :: rest =>
+ pretransformExpr(value) { tvalue =>
+ withNewLocalDef(Binding(fieldName, fieldOrigName, s.tpe, false,
+ tvalue)) { (localDef, cont1) =>
+ if (localDef.contains(thisLocalDef)) {
+ /* Uh oh, there is a `val x = ...this...`. We can't keep it,
+ * because this field will not be updated with `newThisLocalDef`.
+ */
+ cancelFun()
+ }
+ val newFieldsLocalDefs =
+ inputFieldsLocalDefs.updated(fieldName, localDef)
+ val newThisLocalDef = LocalDef(
+ RefinedType(cls, isExact = true, isNullable = false), false,
+ InlineClassBeingConstructedReplacement(newFieldsLocalDefs, cancelFun))
+ val restScope = scope.withEnv(scope.env.withLocalDef(
+ "this", newThisLocalDef))
+ inlineClassConstructorBodyList(allocationSite,
+ newThisLocalDef, newFieldsLocalDefs, cls, rest, cancelFun)(
+ buildInner)(cont1)(restScope)
+ } (cont)
+ }
+
+ /* if (cond)
+ * throw e
+ * else
+ * this.outer = value
+ *
+ * becomes
+ *
+ * this.outer =
+ * if (cond) throw e
+ * else value
+ *
+ * Typical shape of initialization of outer pointer of inner classes.
+ */
+ case If(cond, th: Throw,
+ Assign(Select(This(), _, false), value)) :: rest =>
+ // work around a bug of the compiler (these should be @-bindings)
+ val stat = stats.head.asInstanceOf[If]
+ val ass = stat.elsep.asInstanceOf[Assign]
+ val lhs = ass.lhs
+ inlineClassConstructorBodyList(allocationSite, thisLocalDef,
+ inputFieldsLocalDefs, cls,
+ Assign(lhs, If(cond, th, value)(lhs.tpe)(stat.pos))(ass.pos) :: rest,
+ cancelFun)(buildInner)(cont)
+
+ case StaticApply(ths: This, superClass, superCtor, args) :: rest
+ if isConstructorName(superCtor.name) =>
+ pretransformExprs(args) { targs =>
+ inlineClassConstructorBody(allocationSite, inputFieldsLocalDefs,
+ cls, superClass, superCtor, targs,
+ cancelFun) { (outputFieldsLocalDefs, cont1) =>
+ val newThisLocalDef = LocalDef(
+ RefinedType(cls, isExact = true, isNullable = false), false,
+ InlineClassBeingConstructedReplacement(outputFieldsLocalDefs, cancelFun))
+ val restScope = scope.withEnv(scope.env.withLocalDef(
+ "this", newThisLocalDef))
+ inlineClassConstructorBodyList(allocationSite,
+ newThisLocalDef, outputFieldsLocalDefs,
+ cls, rest, cancelFun)(buildInner)(cont1)(restScope)
+ } (cont)
+ }
+
+ case VarDef(Ident(name, originalName), tpe, mutable, rhs) :: rest =>
+ pretransformExpr(rhs) { trhs =>
+ withBinding(Binding(name, originalName, tpe, mutable, trhs)) { (restScope, cont1) =>
+ inlineClassConstructorBodyList(allocationSite,
+ thisLocalDef, inputFieldsLocalDefs,
+ cls, rest, cancelFun)(buildInner)(cont1)(restScope)
+ } (cont)
+ }
+
+ case stat :: rest =>
+ val transformedStat = transformStat(stat)
+ transformedStat match {
+ case Skip() =>
+ inlineClassConstructorBodyList(allocationSite,
+ thisLocalDef, inputFieldsLocalDefs,
+ cls, rest, cancelFun)(buildInner)(cont)
+ case _ =>
+ if (transformedStat.tpe == NothingType)
+ cont(PreTransTree(transformedStat, RefinedType.Nothing))
+ else {
+ inlineClassConstructorBodyList(allocationSite,
+ thisLocalDef, inputFieldsLocalDefs,
+ cls, rest, cancelFun) { (outputFieldsLocalDefs, cont1) =>
+ buildInner(outputFieldsLocalDefs, { tinner =>
+ cont1(PreTransBlock(transformedStat :: Nil, tinner))
+ })
+ }(cont)
+ }
+ }
+
+ case Nil =>
+ buildInner(inputFieldsLocalDefs, cont)
+ }
+ }
+
+ private def foldIf(cond: Tree, thenp: Tree, elsep: Tree)(tpe: Type)(
+ implicit pos: Position): Tree = {
+ import BinaryOp._
+
+ @inline def default = If(cond, thenp, elsep)(tpe)
+ cond match {
+ case BooleanLiteral(v) =>
+ if (v) thenp
+ else elsep
+
+ case _ =>
+ @inline def negCond = foldUnaryOp(UnaryOp.Boolean_!, cond)
+ if (thenp.tpe == BooleanType && elsep.tpe == BooleanType) {
+ (cond, thenp, elsep) match {
+ case (_, BooleanLiteral(t), BooleanLiteral(e)) =>
+ if (t == e) Block(keepOnlySideEffects(cond), thenp)
+ else if (t) cond
+ else negCond
+
+ case (_, BooleanLiteral(false), _) =>
+ foldIf(negCond, elsep, BooleanLiteral(false))(tpe) // canonical && form
+ case (_, _, BooleanLiteral(true)) =>
+ foldIf(negCond, BooleanLiteral(true), thenp)(tpe) // canonical || form
+
+ /* if (lhs === null) rhs === null else lhs === rhs
+ * -> lhs === rhs
+ * This is the typical shape of a lhs == rhs test where
+ * the equals() method has been inlined as a reference
+ * equality test.
+ */
+ case (BinaryOp(BinaryOp.===, VarRef(lhsIdent, _), Null()),
+ BinaryOp(BinaryOp.===, VarRef(rhsIdent, _), Null()),
+ BinaryOp(BinaryOp.===, VarRef(lhsIdent2, _), VarRef(rhsIdent2, _)))
+ if lhsIdent2 == lhsIdent && rhsIdent2 == rhsIdent =>
+ elsep
+
+ // Example: (x > y) || (x == y) -> (x >= y)
+ case (BinaryOp(op1 @ (Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>=), l1, r1),
+ BooleanLiteral(true),
+ BinaryOp(op2 @ (Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>=), l2, r2))
+ if ((l1.isInstanceOf[Literal] || l1.isInstanceOf[VarRef]) &&
+ (r1.isInstanceOf[Literal] || r1.isInstanceOf[VarRef]) &&
+ (l1 == l2 && r1 == r2)) =>
+ val canBeEqual =
+ ((op1 == Num_==) || (op1 == Num_<=) || (op1 == Num_>=)) ||
+ ((op2 == Num_==) || (op2 == Num_<=) || (op2 == Num_>=))
+ val canBeLessThan =
+ ((op1 == Num_!=) || (op1 == Num_<) || (op1 == Num_<=)) ||
+ ((op2 == Num_!=) || (op2 == Num_<) || (op2 == Num_<=))
+ val canBeGreaterThan =
+ ((op1 == Num_!=) || (op1 == Num_>) || (op1 == Num_>=)) ||
+ ((op2 == Num_!=) || (op2 == Num_>) || (op2 == Num_>=))
+
+ fold3WayComparison(canBeEqual, canBeLessThan, canBeGreaterThan, l1, r1)
+
+ // Example: (x >= y) && (x <= y) -> (x == y)
+ case (BinaryOp(op1 @ (Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>=), l1, r1),
+ BinaryOp(op2 @ (Num_== | Num_!= | Num_< | Num_<= | Num_> | Num_>=), l2, r2),
+ BooleanLiteral(false))
+ if ((l1.isInstanceOf[Literal] || l1.isInstanceOf[VarRef]) &&
+ (r1.isInstanceOf[Literal] || r1.isInstanceOf[VarRef]) &&
+ (l1 == l2 && r1 == r2)) =>
+ val canBeEqual =
+ ((op1 == Num_==) || (op1 == Num_<=) || (op1 == Num_>=)) &&
+ ((op2 == Num_==) || (op2 == Num_<=) || (op2 == Num_>=))
+ val canBeLessThan =
+ ((op1 == Num_!=) || (op1 == Num_<) || (op1 == Num_<=)) &&
+ ((op2 == Num_!=) || (op2 == Num_<) || (op2 == Num_<=))
+ val canBeGreaterThan =
+ ((op1 == Num_!=) || (op1 == Num_>) || (op1 == Num_>=)) &&
+ ((op2 == Num_!=) || (op2 == Num_>) || (op2 == Num_>=))
+
+ fold3WayComparison(canBeEqual, canBeLessThan, canBeGreaterThan, l1, r1)
+
+ case _ => default
+ }
+ } else {
+ (thenp, elsep) match {
+ case (Skip(), Skip()) => keepOnlySideEffects(cond)
+ case (Skip(), _) => foldIf(negCond, elsep, thenp)(tpe)
+
+ case _ => default
+ }
+ }
+ }
+ }
+
+ private def transformUnaryOp(tree: UnaryOp)(implicit scope: Scope): Tree = {
+ import UnaryOp._
+
+ implicit val pos = tree.pos
+ val UnaryOp(op, arg) = tree
+
+ (op: @switch) match {
+ case LongToInt =>
+ trampoline {
+ pretransformExpr(arg) { (targ) =>
+ TailCalls.done {
+ foldUnaryOp(op, finishTransformOptLongExpr(targ))
+ }
+ }
+ }
+
+ case _ =>
+ foldUnaryOp(op, transformExpr(arg))
+ }
+ }
+
+ private def transformBinaryOp(tree: BinaryOp)(implicit scope: Scope): Tree = {
+ import BinaryOp._
+
+ implicit val pos = tree.pos
+ val BinaryOp(op, lhs, rhs) = tree
+
+ (op: @switch) match {
+ case === | !== =>
+ trampoline {
+ pretransformExprs(lhs, rhs) { (tlhs, trhs) =>
+ TailCalls.done(foldReferenceEquality(tlhs, trhs, op == ===))
+ }
+ }
+
+ case Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= =>
+ trampoline {
+ pretransformExprs(lhs, rhs) { (tlhs, trhs) =>
+ TailCalls.done {
+ if (isLiteralOrOptimizableLong(tlhs) &&
+ isLiteralOrOptimizableLong(trhs)) {
+ foldBinaryOp(op, finishTransformOptLongExpr(tlhs),
+ finishTransformOptLongExpr(trhs))
+ } else {
+ foldBinaryOp(op, finishTransformExpr(tlhs),
+ finishTransformExpr(trhs))
+ }
+ }
+ }
+ }
+
+ case _ =>
+ foldBinaryOp(op, transformExpr(lhs), transformExpr(rhs))
+ }
+ }
+
+ private def isLiteralOrOptimizableLong(texpr: PreTransform): Boolean = {
+ texpr match {
+ case PreTransTree(LongLiteral(_), _) =>
+ true
+ case PreTransLocalDef(LocalDef(_, _, replacement)) =>
+ replacement match {
+ case ReplaceWithVarRef(_, _, _, Some(_)) => true
+ case ReplaceWithConstant(LongLiteral(_)) => true
+ case _ => false
+ }
+ case _ =>
+ false
+ }
+ }
+
+ private def finishTransformOptLongExpr(targ: PreTransform): Tree = targ match {
+ case PreTransLocalDef(LocalDef(tpe, false,
+ ReplaceWithVarRef(_, _, _, Some(argValue)))) =>
+ argValue()
+ case _ =>
+ finishTransformExpr(targ)
+ }
+
+ private def foldUnaryOp(op: UnaryOp.Code, arg: Tree)(
+ implicit pos: Position): Tree = {
+ import UnaryOp._
+ @inline def default = UnaryOp(op, arg)
+ (op: @switch) match {
+ case Boolean_! =>
+ arg match {
+ case BooleanLiteral(v) => BooleanLiteral(!v)
+ case UnaryOp(Boolean_!, x) => x
+
+ case BinaryOp(innerOp, l, r) =>
+ val newOp = (innerOp: @switch) match {
+ case BinaryOp.=== => BinaryOp.!==
+ case BinaryOp.!== => BinaryOp.===
+
+ case BinaryOp.Num_== => BinaryOp.Num_!=
+ case BinaryOp.Num_!= => BinaryOp.Num_==
+ case BinaryOp.Num_< => BinaryOp.Num_>=
+ case BinaryOp.Num_<= => BinaryOp.Num_>
+ case BinaryOp.Num_> => BinaryOp.Num_<=
+ case BinaryOp.Num_>= => BinaryOp.Num_<
+
+ case BinaryOp.Long_== => BinaryOp.Long_!=
+ case BinaryOp.Long_!= => BinaryOp.Long_==
+ case BinaryOp.Long_< => BinaryOp.Long_>=
+ case BinaryOp.Long_<= => BinaryOp.Long_>
+ case BinaryOp.Long_> => BinaryOp.Long_<=
+ case BinaryOp.Long_>= => BinaryOp.Long_<
+
+ case BinaryOp.Boolean_== => BinaryOp.Boolean_!=
+ case BinaryOp.Boolean_!= => BinaryOp.Boolean_==
+
+ case _ => -1
+ }
+ if (newOp == -1) default
+ else BinaryOp(newOp, l, r)
+
+ case _ => default
+ }
+
+ case IntToLong =>
+ arg match {
+ case IntLiteral(v) => LongLiteral(v.toLong)
+ case _ => default
+ }
+
+ case LongToInt =>
+ arg match {
+ case LongLiteral(v) => IntLiteral(v.toInt)
+ case UnaryOp(IntToLong, x) => x
+
+ case BinaryOp(BinaryOp.Long_+, x, y) =>
+ foldBinaryOp(BinaryOp.Int_+,
+ foldUnaryOp(LongToInt, x),
+ foldUnaryOp(LongToInt, y))
+ case BinaryOp(BinaryOp.Long_-, x, y) =>
+ foldBinaryOp(BinaryOp.Int_-,
+ foldUnaryOp(LongToInt, x),
+ foldUnaryOp(LongToInt, y))
+
+ case _ => default
+ }
+
+ case LongToDouble =>
+ arg match {
+ case LongLiteral(v) => DoubleLiteral(v.toDouble)
+ case _ => default
+ }
+ case DoubleToInt =>
+ arg match {
+ case _ if arg.tpe == IntType => arg
+ case NumberLiteral(v) => IntLiteral(v.toInt)
+ case _ => default
+ }
+ case DoubleToFloat =>
+ arg match {
+ case _ if arg.tpe == FloatType => arg
+ case NumberLiteral(v) => FloatLiteral(v.toFloat)
+ case _ => default
+ }
+ case DoubleToLong =>
+ arg match {
+ case _ if arg.tpe == IntType => foldUnaryOp(IntToLong, arg)
+ case NumberLiteral(v) => LongLiteral(v.toLong)
+ case _ => default
+ }
+ case _ =>
+ default
+ }
+ }
+
+ /** Performs === for two literals.
+ * The result is always known statically.
+ */
+ private def literal_===(lhs: Literal, rhs: Literal): Boolean = {
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) => l == r
+ case (FloatLiteral(l), FloatLiteral(r)) => l == r
+ case (NumberLiteral(l), NumberLiteral(r)) => l == r
+ case (LongLiteral(l), LongLiteral(r)) => l == r
+ case (BooleanLiteral(l), BooleanLiteral(r)) => l == r
+ case (StringLiteral(l), StringLiteral(r)) => l == r
+ case (Undefined(), Undefined()) => true
+ case (Null(), Null()) => true
+ case _ => false
+ }
+ }
+
+ private def foldBinaryOp(op: BinaryOp.Code, lhs: Tree, rhs: Tree)(
+ implicit pos: Position): Tree = {
+ import BinaryOp._
+ @inline def default = BinaryOp(op, lhs, rhs)
+ (op: @switch) match {
+ case === | !== =>
+ val positive = (op == ===)
+ (lhs, rhs) match {
+ case (lhs: Literal, rhs: Literal) =>
+ BooleanLiteral(literal_===(lhs, rhs) == positive)
+
+ case (_: Literal, _) => foldBinaryOp(op, rhs, lhs)
+ case _ => default
+ }
+
+ case Int_+ =>
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l + r)
+ case (_, IntLiteral(_)) => foldBinaryOp(Int_+, rhs, lhs)
+ case (IntLiteral(0), _) => rhs
+
+ case (IntLiteral(x),
+ BinaryOp(innerOp @ (Int_+ | Int_-), IntLiteral(y), z)) =>
+ foldBinaryOp(innerOp, IntLiteral(x+y), z)
+
+ case _ => default
+ }
+
+ case Int_- =>
+ (lhs, rhs) match {
+ case (_, IntLiteral(r)) => foldBinaryOp(Int_+, lhs, IntLiteral(-r))
+
+ case (IntLiteral(x), BinaryOp(Int_+, IntLiteral(y), z)) =>
+ foldBinaryOp(Int_-, IntLiteral(x-y), z)
+ case (IntLiteral(x), BinaryOp(Int_-, IntLiteral(y), z)) =>
+ foldBinaryOp(Int_+, IntLiteral(x-y), z)
+
+ case (_, BinaryOp(Int_-, IntLiteral(0), x)) =>
+ foldBinaryOp(Int_+, lhs, x)
+
+ case _ => default
+ }
+
+ case Int_* =>
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l * r)
+ case (_, IntLiteral(_)) => foldBinaryOp(Int_*, rhs, lhs)
+
+ case (IntLiteral(1), _) => rhs
+ case (IntLiteral(-1), _) => foldBinaryOp(Int_-, IntLiteral(0), lhs)
+
+ case _ => default
+ }
+
+ case Int_/ =>
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) if r != 0 => IntLiteral(l / r)
+
+ case (_, IntLiteral(1)) => lhs
+ case (_, IntLiteral(-1)) => foldBinaryOp(Int_-, IntLiteral(0), lhs)
+
+ case _ => default
+ }
+
+ case Int_% =>
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) if r != 0 => IntLiteral(l % r)
+ case (_, IntLiteral(1 | -1)) =>
+ Block(keepOnlySideEffects(lhs), IntLiteral(0))
+ case _ => default
+ }
+
+ case Int_| =>
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l | r)
+ case (_, IntLiteral(_)) => foldBinaryOp(Int_|, rhs, lhs)
+ case (IntLiteral(0), _) => rhs
+
+ case (IntLiteral(x), BinaryOp(Int_|, IntLiteral(y), z)) =>
+ foldBinaryOp(Int_|, IntLiteral(x | y), z)
+
+ case _ => default
+ }
+
+ case Int_& =>
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l & r)
+ case (_, IntLiteral(_)) => foldBinaryOp(Int_&, rhs, lhs)
+ case (IntLiteral(-1), _) => rhs
+
+ case (IntLiteral(x), BinaryOp(Int_&, IntLiteral(y), z)) =>
+ foldBinaryOp(Int_&, IntLiteral(x & y), z)
+
+ case _ => default
+ }
+
+ case Int_^ =>
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l ^ r)
+ case (_, IntLiteral(_)) => foldBinaryOp(Int_^, rhs, lhs)
+ case (IntLiteral(0), _) => rhs
+
+ case (IntLiteral(x), BinaryOp(Int_^, IntLiteral(y), z)) =>
+ foldBinaryOp(Int_^, IntLiteral(x ^ y), z)
+
+ case _ => default
+ }
+
+ case Int_<< =>
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l << r)
+ case (_, IntLiteral(x)) if x % 32 == 0 => lhs
+ case _ => default
+ }
+
+ case Int_>>> =>
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l >>> r)
+ case (_, IntLiteral(x)) if x % 32 == 0 => lhs
+ case _ => default
+ }
+
+ case Int_>> =>
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) => IntLiteral(l >> r)
+ case (_, IntLiteral(x)) if x % 32 == 0 => lhs
+ case _ => default
+ }
+
+ case Long_+ =>
+ (lhs, rhs) match {
+ case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l + r)
+ case (_, LongLiteral(_)) => foldBinaryOp(Long_+, rhs, lhs)
+ case (LongLiteral(0), _) => rhs
+
+ case (LongLiteral(x),
+ BinaryOp(innerOp @ (Long_+ | Long_-), LongLiteral(y), z)) =>
+ foldBinaryOp(innerOp, LongLiteral(x+y), z)
+
+ case _ => default
+ }
+
+ case Long_- =>
+ (lhs, rhs) match {
+ case (_, LongLiteral(r)) => foldBinaryOp(Long_+, LongLiteral(-r), lhs)
+
+ case (LongLiteral(x), BinaryOp(Long_+, LongLiteral(y), z)) =>
+ foldBinaryOp(Long_-, LongLiteral(x-y), z)
+ case (LongLiteral(x), BinaryOp(Long_-, LongLiteral(y), z)) =>
+ foldBinaryOp(Long_+, LongLiteral(x-y), z)
+
+ case (_, BinaryOp(BinaryOp.Long_-, LongLiteral(0L), x)) =>
+ foldBinaryOp(Long_+, lhs, x)
+
+ case _ => default
+ }
+
+ case Long_* =>
+ (lhs, rhs) match {
+ case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l * r)
+ case (_, LongLiteral(_)) => foldBinaryOp(Long_*, rhs, lhs)
+
+ case (LongLiteral(1), _) => rhs
+ case (LongLiteral(-1), _) => foldBinaryOp(Long_-, LongLiteral(0), lhs)
+
+ case _ => default
+ }
+
+ case Long_/ =>
+ (lhs, rhs) match {
+ case (_, LongLiteral(0)) => default
+ case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l / r)
+
+ case (_, LongLiteral(1)) => lhs
+ case (_, LongLiteral(-1)) => foldBinaryOp(Long_-, LongLiteral(0), lhs)
+
+ case (LongFromInt(x), LongFromInt(y: IntLiteral)) if y.value != -1 =>
+ LongFromInt(foldBinaryOp(Int_/, x, y))
+
+ case _ => default
+ }
+
+ case Long_% =>
+ (lhs, rhs) match {
+ case (_, LongLiteral(0)) => default
+ case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l % r)
+
+ case (_, LongLiteral(1L | -1L)) =>
+ Block(keepOnlySideEffects(lhs), LongLiteral(0L))
+
+ case (LongFromInt(x), LongFromInt(y)) =>
+ LongFromInt(foldBinaryOp(Int_%, x, y))
+
+ case _ => default
+ }
+
+ case Long_| =>
+ (lhs, rhs) match {
+ case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l | r)
+ case (_, LongLiteral(_)) => foldBinaryOp(Long_|, rhs, lhs)
+ case (LongLiteral(0), _) => rhs
+
+ case (LongLiteral(x), BinaryOp(Long_|, LongLiteral(y), z)) =>
+ foldBinaryOp(Long_|, LongLiteral(x | y), z)
+
+ case _ => default
+ }
+
+ case Long_& =>
+ (lhs, rhs) match {
+ case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l & r)
+ case (_, LongLiteral(_)) => foldBinaryOp(Long_&, rhs, lhs)
+ case (LongLiteral(-1), _) => rhs
+
+ case (LongLiteral(x), BinaryOp(Long_&, LongLiteral(y), z)) =>
+ foldBinaryOp(Long_&, LongLiteral(x & y), z)
+
+ case _ => default
+ }
+
+ case Long_^ =>
+ (lhs, rhs) match {
+ case (LongLiteral(l), LongLiteral(r)) => LongLiteral(l ^ r)
+ case (_, LongLiteral(_)) => foldBinaryOp(Long_^, rhs, lhs)
+ case (LongLiteral(0), _) => rhs
+
+ case (LongLiteral(x), BinaryOp(Long_^, LongLiteral(y), z)) =>
+ foldBinaryOp(Long_^, LongLiteral(x ^ y), z)
+
+ case _ => default
+ }
+
+ case Long_<< =>
+ (lhs, rhs) match {
+ case (LongLiteral(l), IntLiteral(r)) => LongLiteral(l << r)
+ case (_, IntLiteral(x)) if x % 64 == 0 => lhs
+ case _ => default
+ }
+
+ case Long_>>> =>
+ (lhs, rhs) match {
+ case (LongLiteral(l), IntLiteral(r)) => LongLiteral(l >>> r)
+ case (_, IntLiteral(x)) if x % 64 == 0 => lhs
+ case _ => default
+ }
+
+ case Long_>> =>
+ (lhs, rhs) match {
+ case (LongLiteral(l), IntLiteral(r)) => LongLiteral(l >> r)
+ case (_, IntLiteral(x)) if x % 64 == 0 => lhs
+ case _ => default
+ }
+
+ case Long_== | Long_!= =>
+ val positive = (op == Long_==)
+ (lhs, rhs) match {
+ case (LongLiteral(l), LongLiteral(r)) =>
+ BooleanLiteral((l == r) == positive)
+
+ case (LongFromInt(x), LongFromInt(y)) =>
+ foldBinaryOp(if (positive) === else !==, x, y)
+ case (LongFromInt(x), LongLiteral(y)) =>
+ assert(y > Int.MaxValue || y < Int.MinValue)
+ Block(keepOnlySideEffects(x), BooleanLiteral(!positive))
+
+ case (BinaryOp(Long_+, LongLiteral(x), y), LongLiteral(z)) =>
+ foldBinaryOp(op, y, LongLiteral(z-x))
+ case (BinaryOp(Long_-, LongLiteral(x), y), LongLiteral(z)) =>
+ foldBinaryOp(op, y, LongLiteral(x-z))
+
+ case (LongLiteral(_), _) => foldBinaryOp(op, rhs, lhs)
+ case _ => default
+ }
+
+ case Long_< | Long_<= | Long_> | Long_>= =>
+ def flippedOp = (op: @switch) match {
+ case Long_< => Long_>
+ case Long_<= => Long_>=
+ case Long_> => Long_<
+ case Long_>= => Long_<=
+ }
+
+ def intOp = (op: @switch) match {
+ case Long_< => Num_<
+ case Long_<= => Num_<=
+ case Long_> => Num_>
+ case Long_>= => Num_>=
+ }
+
+ (lhs, rhs) match {
+ case (LongLiteral(l), LongLiteral(r)) =>
+ val result = (op: @switch) match {
+ case Long_< => l < r
+ case Long_<= => l <= r
+ case Long_> => l > r
+ case Long_>= => l >= r
+ }
+ BooleanLiteral(result)
+
+ case (_, LongLiteral(Long.MinValue)) =>
+ if (op == Long_< || op == Long_>=)
+ Block(keepOnlySideEffects(lhs), BooleanLiteral(op == Long_>=))
+ else
+ foldBinaryOp(if (op == Long_<=) Long_== else Long_!=, lhs, rhs)
+
+ case (_, LongLiteral(Long.MaxValue)) =>
+ if (op == Long_> || op == Long_<=)
+ Block(keepOnlySideEffects(lhs), BooleanLiteral(op == Long_<=))
+ else
+ foldBinaryOp(if (op == Long_>=) Long_== else Long_!=, lhs, rhs)
+
+ case (LongFromInt(x), LongFromInt(y)) =>
+ foldBinaryOp(intOp, x, y)
+ case (LongFromInt(x), LongLiteral(y)) =>
+ assert(y > Int.MaxValue || y < Int.MinValue)
+ val result =
+ if (y > Int.MaxValue) op == Long_< || op == Long_<=
+ else op == Long_> || op == Long_>=
+ Block(keepOnlySideEffects(x), BooleanLiteral(result))
+
+ /* x + y.toLong > z
+ * -x on both sides
+ * requires x + y.toLong not to overflow, and z - x likewise
+ * y.toLong > z - x
+ */
+ case (BinaryOp(Long_+, LongLiteral(x), y @ LongFromInt(_)), LongLiteral(z))
+ if canAddLongs(x, Int.MinValue) &&
+ canAddLongs(x, Int.MaxValue) &&
+ canSubtractLongs(z, x) =>
+ foldBinaryOp(op, y, LongLiteral(z-x))
+
+ /* x - y.toLong > z
+ * -x on both sides
+ * requires x - y.toLong not to overflow, and z - x likewise
+ * -(y.toLong) > z - x
+ */
+ case (BinaryOp(Long_-, LongLiteral(x), y @ LongFromInt(_)), LongLiteral(z))
+ if canSubtractLongs(x, Int.MinValue) &&
+ canSubtractLongs(x, Int.MaxValue) &&
+ canSubtractLongs(z, x) =>
+ if (z-x != Long.MinValue) {
+ // Since -(y.toLong) does not overflow, we can negate both sides
+ foldBinaryOp(flippedOp, y, LongLiteral(-(z-x)))
+ } else {
+ /* -(y.toLong) > Long.MinValue
+ * Depending on the operator, this is either always true or
+ * always false.
+ */
+ val result = (op == Long_>) || (op == Long_>=)
+ Block(keepOnlySideEffects(y), BooleanLiteral(result))
+ }
+
+ /* x.toLong + y.toLong > Int.MaxValue.toLong
+ *
+ * This is basically testing whether x+y overflows in positive.
+ * If x <= 0 or y <= 0, this cannot happen -> false.
+ * If x > 0 and y > 0, this can be detected with x+y < 0.
+ * Therefore, we rewrite as:
+ *
+ * x > 0 && y > 0 && x+y < 0.
+ *
+ * This requires to evaluate x and y once.
+ */
+ case (BinaryOp(Long_+, LongFromInt(x), LongFromInt(y)),
+ LongLiteral(Int.MaxValue)) =>
+ trampoline {
+ withNewLocalDefs(List(
+ Binding("x", None, IntType, false, PreTransTree(x)),
+ Binding("y", None, IntType, false, PreTransTree(y)))) {
+ (tempsLocalDefs, cont) =>
+ val List(tempXDef, tempYDef) = tempsLocalDefs
+ val tempX = tempXDef.newReplacement
+ val tempY = tempYDef.newReplacement
+ cont(PreTransTree(
+ AndThen(AndThen(
+ BinaryOp(Num_>, tempX, IntLiteral(0)),
+ BinaryOp(Num_>, tempY, IntLiteral(0))),
+ BinaryOp(Num_<, BinaryOp(Int_+, tempX, tempY), IntLiteral(0)))))
+ } (finishTransform(isStat = false))
+ }
+
+ case (LongLiteral(_), _) => foldBinaryOp(flippedOp, rhs, lhs)
+ case _ => default
+ }
+
+ case Float_+ =>
+ (lhs, rhs) match {
+ case (FloatLiteral(l), FloatLiteral(r)) => FloatLiteral(l + r)
+ case (FloatLiteral(0), _) => rhs
+ case (_, FloatLiteral(_)) => foldBinaryOp(Float_+, rhs, lhs)
+
+ case (FloatLiteral(x),
+ BinaryOp(innerOp @ (Float_+ | Float_-), FloatLiteral(y), z)) =>
+ foldBinaryOp(innerOp, FloatLiteral(x+y), z)
+
+ case _ => default
+ }
+
+ case Float_- =>
+ (lhs, rhs) match {
+ case (_, FloatLiteral(r)) => foldBinaryOp(Float_+, lhs, FloatLiteral(-r))
+
+ case (FloatLiteral(x), BinaryOp(Float_+, FloatLiteral(y), z)) =>
+ foldBinaryOp(Float_-, FloatLiteral(x-y), z)
+ case (FloatLiteral(x), BinaryOp(Float_-, FloatLiteral(y), z)) =>
+ foldBinaryOp(Float_+, FloatLiteral(x-y), z)
+
+ case (_, BinaryOp(BinaryOp.Float_-, FloatLiteral(0), x)) =>
+ foldBinaryOp(Float_+, lhs, x)
+
+ case _ => default
+ }
+
+ case Float_* =>
+ (lhs, rhs) match {
+ case (FloatLiteral(l), FloatLiteral(r)) => FloatLiteral(l * r)
+ case (_, FloatLiteral(_)) => foldBinaryOp(Float_*, rhs, lhs)
+
+ case (FloatLiteral(1), _) => rhs
+ case (FloatLiteral(-1), _) => foldBinaryOp(Float_-, FloatLiteral(0), lhs)
+
+ case _ => default
+ }
+
+ case Float_/ =>
+ (lhs, rhs) match {
+ case (FloatLiteral(l), FloatLiteral(r)) => FloatLiteral(l / r)
+
+ case (_, FloatLiteral(1)) => lhs
+ case (_, FloatLiteral(-1)) => foldBinaryOp(Float_-, FloatLiteral(0), lhs)
+
+ case _ => default
+ }
+
+ case Float_% =>
+ (lhs, rhs) match {
+ case (FloatLiteral(l), FloatLiteral(r)) => FloatLiteral(l % r)
+ case _ => default
+ }
+
+ case Double_+ =>
+ (lhs, rhs) match {
+ case (NumberLiteral(l), NumberLiteral(r)) => DoubleLiteral(l + r)
+ case (NumberLiteral(0), _) => rhs
+ case (_, NumberLiteral(_)) => foldBinaryOp(Double_+, rhs, lhs)
+
+ case (NumberLiteral(x),
+ BinaryOp(innerOp @ (Double_+ | Double_-), NumberLiteral(y), z)) =>
+ foldBinaryOp(innerOp, DoubleLiteral(x+y), z)
+
+ case _ => default
+ }
+
+ case Double_- =>
+ (lhs, rhs) match {
+ case (_, NumberLiteral(r)) => foldBinaryOp(Double_+, lhs, DoubleLiteral(-r))
+
+ case (NumberLiteral(x), BinaryOp(Double_+, NumberLiteral(y), z)) =>
+ foldBinaryOp(Double_-, DoubleLiteral(x-y), z)
+ case (NumberLiteral(x), BinaryOp(Double_-, NumberLiteral(y), z)) =>
+ foldBinaryOp(Double_+, DoubleLiteral(x-y), z)
+
+ case (_, BinaryOp(BinaryOp.Double_-, NumberLiteral(0), x)) =>
+ foldBinaryOp(Double_+, lhs, x)
+
+ case _ => default
+ }
+
+ case Double_* =>
+ (lhs, rhs) match {
+ case (NumberLiteral(l), NumberLiteral(r)) => DoubleLiteral(l * r)
+ case (_, NumberLiteral(_)) => foldBinaryOp(Double_*, rhs, lhs)
+
+ case (NumberLiteral(1), _) => rhs
+ case (NumberLiteral(-1), _) => foldBinaryOp(Double_-, DoubleLiteral(0), lhs)
+
+ case _ => default
+ }
+
+ case Double_/ =>
+ (lhs, rhs) match {
+ case (NumberLiteral(l), NumberLiteral(r)) => DoubleLiteral(l / r)
+
+ case (_, NumberLiteral(1)) => lhs
+ case (_, NumberLiteral(-1)) => foldBinaryOp(Double_-, DoubleLiteral(0), lhs)
+
+ case _ => default
+ }
+
+ case Double_% =>
+ (lhs, rhs) match {
+ case (NumberLiteral(l), NumberLiteral(r)) => DoubleLiteral(l % r)
+ case _ => default
+ }
+
+ case Boolean_== | Boolean_!= =>
+ val positive = (op == Boolean_==)
+ (lhs, rhs) match {
+ case (BooleanLiteral(l), _) =>
+ if (l == positive) rhs
+ else foldUnaryOp(UnaryOp.Boolean_!, rhs)
+ case (_, BooleanLiteral(r)) =>
+ if (r == positive) lhs
+ else foldUnaryOp(UnaryOp.Boolean_!, lhs)
+ case _ =>
+ default
+ }
+
+ case Boolean_| =>
+ (lhs, rhs) match {
+ case (_, BooleanLiteral(false)) => lhs
+ case (BooleanLiteral(false), _) => rhs
+ case _ => default
+ }
+
+ case Boolean_& =>
+ (lhs, rhs) match {
+ case (_, BooleanLiteral(true)) => lhs
+ case (BooleanLiteral(true), _) => rhs
+ case _ => default
+ }
+
+ case Num_== | Num_!= =>
+ val positive = (op == Num_==)
+ (lhs, rhs) match {
+ case (lhs: Literal, rhs: Literal) =>
+ BooleanLiteral(literal_===(lhs, rhs) == positive)
+
+ case (BinaryOp(Int_+, IntLiteral(x), y), IntLiteral(z)) =>
+ foldBinaryOp(op, y, IntLiteral(z-x))
+ case (BinaryOp(Int_-, IntLiteral(x), y), IntLiteral(z)) =>
+ foldBinaryOp(op, y, IntLiteral(x-z))
+
+ case (_: Literal, _) => foldBinaryOp(op, rhs, lhs)
+ case _ => default
+ }
+
+ case Num_< | Num_<= | Num_> | Num_>= =>
+ def flippedOp = (op: @switch) match {
+ case Num_< => Num_>
+ case Num_<= => Num_>=
+ case Num_> => Num_<
+ case Num_>= => Num_<=
+ }
+
+ if (lhs.tpe == IntType && rhs.tpe == IntType) {
+ (lhs, rhs) match {
+ case (IntLiteral(l), IntLiteral(r)) =>
+ val result = (op: @switch) match {
+ case Num_< => l < r
+ case Num_<= => l <= r
+ case Num_> => l > r
+ case Num_>= => l >= r
+ }
+ BooleanLiteral(result)
+
+ case (_, IntLiteral(Int.MinValue)) =>
+ if (op == Num_< || op == Num_>=)
+ Block(keepOnlySideEffects(lhs), BooleanLiteral(op == Num_>=))
+ else
+ foldBinaryOp(if (op == Num_<=) Num_== else Num_!=, lhs, rhs)
+
+ case (_, IntLiteral(Int.MaxValue)) =>
+ if (op == Num_> || op == Num_<=)
+ Block(keepOnlySideEffects(lhs), BooleanLiteral(op == Num_<=))
+ else
+ foldBinaryOp(if (op == Num_>=) Num_== else Num_!=, lhs, rhs)
+
+ case (IntLiteral(_), _) => foldBinaryOp(flippedOp, rhs, lhs)
+ case _ => default
+ }
+ } else {
+ (lhs, rhs) match {
+ case (NumberLiteral(l), NumberLiteral(r)) =>
+ val result = (op: @switch) match {
+ case Num_< => l < r
+ case Num_<= => l <= r
+ case Num_> => l > r
+ case Num_>= => l >= r
+ }
+ BooleanLiteral(result)
+
+ case _ => default
+ }
+ }
+
+ case _ =>
+ default
+ }
+ }
+
+ private def fold3WayComparison(canBeEqual: Boolean, canBeLessThan: Boolean,
+ canBeGreaterThan: Boolean, lhs: Tree, rhs: Tree)(
+ implicit pos: Position): Tree = {
+ import BinaryOp._
+ if (canBeEqual) {
+ if (canBeLessThan) {
+ if (canBeGreaterThan)
+ Block(keepOnlySideEffects(lhs), keepOnlySideEffects(rhs), BooleanLiteral(true))
+ else
+ foldBinaryOp(Num_<=, lhs, rhs)
+ } else {
+ if (canBeGreaterThan)
+ foldBinaryOp(Num_>=, lhs, rhs)
+ else
+ foldBinaryOp(Num_==, lhs, rhs)
+ }
+ } else {
+ if (canBeLessThan) {
+ if (canBeGreaterThan)
+ foldBinaryOp(Num_!=, lhs, rhs)
+ else
+ foldBinaryOp(Num_<, lhs, rhs)
+ } else {
+ if (canBeGreaterThan)
+ foldBinaryOp(Num_>, lhs, rhs)
+ else
+ Block(keepOnlySideEffects(lhs), keepOnlySideEffects(rhs), BooleanLiteral(false))
+ }
+ }
+ }
+
+ private def foldUnbox(arg: PreTransform, charCode: Char)(
+ cont: PreTransCont): TailRec[Tree] = {
+ (charCode: @switch) match {
+ case 'Z' if arg.tpe.base == BooleanType => cont(arg)
+ case 'I' if arg.tpe.base == IntType => cont(arg)
+ case 'F' if arg.tpe.base == FloatType => cont(arg)
+ case 'J' if arg.tpe.base == LongType => cont(arg)
+ case 'D' if arg.tpe.base == DoubleType ||
+ arg.tpe.base == IntType || arg.tpe.base == FloatType => cont(arg)
+ case _ =>
+ cont(PreTransTree(Unbox(finishTransformExpr(arg), charCode)(arg.pos)))
+ }
+ }
+
+ private def foldReferenceEquality(tlhs: PreTransform, trhs: PreTransform,
+ positive: Boolean = true)(implicit pos: Position): Tree = {
+ (tlhs, trhs) match {
+ case (_, PreTransTree(Null(), _)) if !tlhs.tpe.isNullable =>
+ Block(
+ finishTransformStat(tlhs),
+ BooleanLiteral(!positive))
+ case (PreTransTree(Null(), _), _) if !trhs.tpe.isNullable =>
+ Block(
+ finishTransformStat(trhs),
+ BooleanLiteral(!positive))
+ case _ =>
+ foldBinaryOp(if (positive) BinaryOp.=== else BinaryOp.!==,
+ finishTransformExpr(tlhs), finishTransformExpr(trhs))
+ }
+ }
+
+ private def finishTransformCheckNull(preTrans: PreTransform)(
+ implicit pos: Position): Tree = {
+ if (preTrans.tpe.isNullable) {
+ val transformed = finishTransformExpr(preTrans)
+ CallHelper("checkNonNull", transformed)(transformed.tpe)
+ } else {
+ finishTransformExpr(preTrans)
+ }
+ }
+
+ def transformIsolatedBody(optTarget: Option[MethodID],
+ thisType: Type, params: List[ParamDef], resultType: Type,
+ body: Tree): (List[ParamDef], Tree) = {
+ val (paramLocalDefs, newParamDefs) = (for {
+ p @ ParamDef(ident @ Ident(name, originalName), ptpe, mutable) <- params
+ } yield {
+ val newName = freshLocalName(name)
+ val newOriginalName = originalName.orElse(Some(newName))
+ val localDef = LocalDef(RefinedType(ptpe), mutable,
+ ReplaceWithVarRef(newName, newOriginalName, new SimpleState(true), None))
+ val newParamDef = ParamDef(
+ Ident(newName, newOriginalName)(ident.pos), ptpe, mutable)(p.pos)
+ ((name -> localDef), newParamDef)
+ }).unzip
+
+ val thisLocalDef =
+ if (thisType == NoType) None
+ else {
+ Some("this" -> LocalDef(
+ RefinedType(thisType, isExact = false, isNullable = false),
+ false, ReplaceWithThis()))
+ }
+
+ val allLocalDefs = thisLocalDef ++: paramLocalDefs
+
+ val scope0 = optTarget.fold(Scope.Empty)(
+ target => Scope.Empty.inlining((None, target)))
+ val scope = scope0.withEnv(OptEnv.Empty.withLocalDefs(allLocalDefs))
+ val newBody =
+ transform(body, resultType == NoType)(scope)
+
+ (newParamDefs, newBody)
+ }
+
+ private def returnable(oldLabelName: String, resultType: Type,
+ body: Tree, isStat: Boolean, usePreTransform: Boolean)(
+ cont: PreTransCont)(
+ implicit scope: Scope, pos: Position): TailRec[Tree] = tailcall {
+ val newLabel = freshLabelName(
+ if (oldLabelName.isEmpty) "inlinereturn" else oldLabelName)
+
+ def doMakeTree(newBody: Tree, returnedTypes: List[Type]): Tree = {
+ val refinedType =
+ returnedTypes.reduce(constrainedLub(_, _, resultType))
+ val returnCount = returnedTypes.size - 1
+
+ tryOptimizePatternMatch(oldLabelName, refinedType,
+ returnCount, newBody) getOrElse {
+ Labeled(Ident(newLabel, None), refinedType, newBody)
+ }
+ }
+
+ val info = new LabelInfo(newLabel, acceptRecords = usePreTransform)
+ withState(info.returnedTypes) {
+ val bodyScope = scope.withEnv(scope.env.withLabelInfo(oldLabelName, info))
+
+ if (usePreTransform) {
+ assert(!isStat, "Cannot use pretransform in statement position")
+ tryOrRollback { cancelFun =>
+ pretransformExpr(body) { tbody0 =>
+ val returnedTypes0 = info.returnedTypes.value
+ if (returnedTypes0.isEmpty) {
+ // no return to that label, we can eliminate it
+ cont(tbody0)
+ } else {
+ val tbody = resolveLocalDef(tbody0)
+ val (newBody, returnedTypes) = tbody match {
+ case PreTransRecordTree(bodyTree, origType, _) =>
+ (bodyTree, (bodyTree.tpe, origType) :: returnedTypes0)
+ case PreTransTree(bodyTree, tpe) =>
+ (bodyTree, (bodyTree.tpe, tpe) :: returnedTypes0)
+ }
+ val (actualTypes, origTypes) = returnedTypes.unzip
+ val refinedOrigType =
+ origTypes.reduce(constrainedLub(_, _, resultType))
+ actualTypes.collectFirst {
+ case actualType: RecordType => actualType
+ }.fold[TailRec[Tree]] {
+ // None of the returned types are records
+ cont(PreTransTree(
+ doMakeTree(newBody, actualTypes), refinedOrigType))
+ } { recordType =>
+ if (actualTypes.exists(t => t != recordType && t != NothingType))
+ cancelFun()
+
+ val resultTree = doMakeTree(newBody, actualTypes)
+
+ if (origTypes.exists(t => t != refinedOrigType && !t.isNothingType))
+ cancelFun()
+
+ cont(PreTransRecordTree(resultTree, refinedOrigType, cancelFun))
+ }
+ }
+ } (bodyScope)
+ } { () =>
+ returnable(oldLabelName, resultType, body, isStat,
+ usePreTransform = false)(cont)
+ }
+ } else {
+ val newBody = transform(body, isStat)(bodyScope)
+ val returnedTypes0 = info.returnedTypes.value.map(_._1)
+ if (returnedTypes0.isEmpty) {
+ // no return to that label, we can eliminate it
+ cont(PreTransTree(newBody, RefinedType(newBody.tpe)))
+ } else {
+ val returnedTypes = newBody.tpe :: returnedTypes0
+ val tree = doMakeTree(newBody, returnedTypes)
+ cont(PreTransTree(tree, RefinedType(tree.tpe)))
+ }
+ }
+ }
+ }
+
+ def tryOptimizePatternMatch(oldLabelName: String, refinedType: Type,
+ returnCount: Int, newBody: Tree): Option[Tree] = {
+ if (!oldLabelName.startsWith("matchEnd")) None
+ else {
+ newBody match {
+ case Block(stats) =>
+ @tailrec
+ def createRevAlts(xs: List[Tree], acc: List[(Tree, Tree)]): List[(Tree, Tree)] = xs match {
+ case If(cond, body, Skip()) :: xr =>
+ createRevAlts(xr, (cond, body) :: acc)
+ case remaining =>
+ (EmptyTree, Block(remaining)(remaining.head.pos)) :: acc
+ }
+ val revAlts = createRevAlts(stats, Nil)
+
+ if (revAlts.size == returnCount) {
+ @tailrec
+ def constructOptimized(revAlts: List[(Tree, Tree)], elsep: Tree): Option[Tree] = {
+ revAlts match {
+ case (cond, body) :: revAltsRest =>
+ body match {
+ case BlockOrAlone(prep,
+ Return(result, Some(Ident(newLabel, _)))) =>
+ val result1 =
+ if (refinedType == NoType) keepOnlySideEffects(result)
+ else result
+ val prepAndResult = Block(prep :+ result1)(body.pos)
+ if (cond == EmptyTree) {
+ assert(elsep == EmptyTree)
+ constructOptimized(revAltsRest, prepAndResult)
+ } else {
+ assert(elsep != EmptyTree)
+ constructOptimized(revAltsRest,
+ foldIf(cond, prepAndResult, elsep)(refinedType)(cond.pos))
+ }
+ case _ =>
+ None
+ }
+ case Nil =>
+ Some(elsep)
+ }
+ }
+ constructOptimized(revAlts, EmptyTree)
+ } else None
+ case _ =>
+ None
+ }
+ }
+ }
+
+ private def withBindings(bindings: List[Binding])(
+ buildInner: (Scope, PreTransCont) => TailRec[Tree])(
+ cont: PreTransCont)(
+ implicit scope: Scope): TailRec[Tree] = {
+ withNewLocalDefs(bindings) { (localDefs, cont1) =>
+ val newMappings = for {
+ (binding, localDef) <- bindings zip localDefs
+ } yield {
+ binding.name -> localDef
+ }
+ buildInner(scope.withEnv(scope.env.withLocalDefs(newMappings)), cont1)
+ } (cont)
+ }
+
+ private def withBinding(binding: Binding)(
+ buildInner: (Scope, PreTransCont) => TailRec[Tree])(
+ cont: PreTransCont)(
+ implicit scope: Scope): TailRec[Tree] = {
+ withNewLocalDef(binding) { (localDef, cont1) =>
+ buildInner(scope.withEnv(scope.env.withLocalDef(binding.name, localDef)),
+ cont1)
+ } (cont)
+ }
+
+ private def withNewLocalDefs(bindings: List[Binding])(
+ buildInner: (List[LocalDef], PreTransCont) => TailRec[Tree])(
+ cont: PreTransCont): TailRec[Tree] = {
+ bindings match {
+ case first :: rest =>
+ withNewLocalDef(first) { (firstLocalDef, cont1) =>
+ withNewLocalDefs(rest) { (restLocalDefs, cont2) =>
+ buildInner(firstLocalDef :: restLocalDefs, cont2)
+ } (cont1)
+ } (cont)
+
+ case Nil =>
+ buildInner(Nil, cont)
+ }
+ }
+
+ private def isImmutableType(tpe: Type): Boolean = tpe match {
+ case RecordType(fields) =>
+ fields.forall(f => !f.mutable && isImmutableType(f.tpe))
+ case _ =>
+ true
+ }
+
+ private def withNewLocalDef(binding: Binding)(
+ buildInner: (LocalDef, PreTransCont) => TailRec[Tree])(
+ cont: PreTransCont): TailRec[Tree] = tailcall {
+ val Binding(name, originalName, declaredType, mutable, value) = binding
+ implicit val pos = value.pos
+
+ def withDedicatedVar(tpe: RefinedType): TailRec[Tree] = {
+ val newName = freshLocalName(name)
+ val newOriginalName = originalName.orElse(Some(name))
+
+ val used = new SimpleState(false)
+ withState(used) {
+ def doBuildInner(localDef: LocalDef)(varDef: => VarDef)(
+ cont: PreTransCont): TailRec[Tree] = {
+ buildInner(localDef, { tinner =>
+ if (used.value) {
+ cont(PreTransBlock(varDef :: Nil, tinner))
+ } else {
+ tinner match {
+ case PreTransLocalDef(`localDef`) =>
+ cont(value)
+ case _ if tinner.contains(localDef) =>
+ cont(PreTransBlock(varDef :: Nil, tinner))
+ case _ =>
+ val rhsSideEffects = finishTransformStat(value)
+ rhsSideEffects match {
+ case Skip() =>
+ cont(tinner)
+ case _ =>
+ if (rhsSideEffects.tpe == NothingType)
+ cont(PreTransTree(rhsSideEffects, RefinedType.Nothing))
+ else
+ cont(PreTransBlock(rhsSideEffects :: Nil, tinner))
+ }
+ }
+ }
+ })
+ }
+
+ resolveLocalDef(value) match {
+ case PreTransRecordTree(valueTree, valueTpe, cancelFun) =>
+ val recordType = valueTree.tpe.asInstanceOf[RecordType]
+ if (!isImmutableType(recordType))
+ cancelFun()
+ val localDef = LocalDef(valueTpe, mutable,
+ ReplaceWithRecordVarRef(newName, newOriginalName, recordType,
+ used, cancelFun))
+ doBuildInner(localDef) {
+ VarDef(Ident(newName, newOriginalName), recordType, mutable,
+ valueTree)
+ } (cont)
+
+ case PreTransTree(valueTree, valueTpe) =>
+ def doDoBuildInner(optValueTree: Option[() => Tree])(
+ cont1: PreTransCont) = {
+ val localDef = LocalDef(tpe, mutable, ReplaceWithVarRef(
+ newName, newOriginalName, used, optValueTree))
+ doBuildInner(localDef) {
+ VarDef(Ident(newName, newOriginalName), tpe.base, mutable,
+ optValueTree.fold(valueTree)(_()))
+ } (cont1)
+ }
+ if (mutable) {
+ doDoBuildInner(None)(cont)
+ } else (valueTree match {
+ case LongFromInt(arg) =>
+ withNewLocalDef(
+ Binding("x", None, IntType, false, PreTransTree(arg))) {
+ (intLocalDef, cont1) =>
+ doDoBuildInner(Some(
+ () => LongFromInt(intLocalDef.newReplacement)))(
+ cont1)
+ } (cont)
+
+ case BinaryOp(op @ (BinaryOp.Long_+ | BinaryOp.Long_-),
+ LongFromInt(intLhs), LongFromInt(intRhs)) =>
+ withNewLocalDefs(List(
+ Binding("x", None, IntType, false, PreTransTree(intLhs)),
+ Binding("y", None, IntType, false, PreTransTree(intRhs)))) {
+ (intLocalDefs, cont1) =>
+ val List(lhsLocalDef, rhsLocalDef) = intLocalDefs
+ doDoBuildInner(Some(
+ () => BinaryOp(op,
+ LongFromInt(lhsLocalDef.newReplacement),
+ LongFromInt(rhsLocalDef.newReplacement))))(
+ cont1)
+ } (cont)
+
+ case _ =>
+ doDoBuildInner(None)(cont)
+ })
+ }
+ }
+ }
+
+ if (value.tpe.isNothingType) {
+ cont(value)
+ } else if (mutable) {
+ withDedicatedVar(RefinedType(declaredType))
+ } else {
+ val refinedType = value.tpe
+ value match {
+ case PreTransBlock(stats, result) =>
+ withNewLocalDef(binding.copy(value = result))(buildInner) { tresult =>
+ cont(PreTransBlock(stats, tresult))
+ }
+
+ case PreTransLocalDef(localDef) if !localDef.mutable =>
+ buildInner(localDef, cont)
+
+ case PreTransTree(literal: Literal, _) =>
+ buildInner(LocalDef(refinedType, false,
+ ReplaceWithConstant(literal)), cont)
+
+ case PreTransTree(VarRef(Ident(refName, refOriginalName), false), _) =>
+ buildInner(LocalDef(refinedType, false,
+ ReplaceWithVarRef(refName, refOriginalName,
+ new SimpleState(true), None)), cont)
+
+ case _ =>
+ withDedicatedVar(refinedType)
+ }
+ }
+ }
+
+ /** Finds a type as precise as possible which is a supertype of lhs and rhs
+ * but still a subtype of upperBound.
+ * Requires that lhs and rhs be subtypes of upperBound, obviously.
+ */
+ private def constrainedLub(lhs: RefinedType, rhs: RefinedType,
+ upperBound: Type): RefinedType = {
+ if (upperBound == NoType) RefinedType(upperBound)
+ else if (lhs == rhs) lhs
+ else if (lhs.isNothingType) rhs
+ else if (rhs.isNothingType) lhs
+ else {
+ RefinedType(constrainedLub(lhs.base, rhs.base, upperBound),
+ false, lhs.isNullable || rhs.isNullable)
+ }
+ }
+
+ /** Finds a type as precise as possible which is a supertype of lhs and rhs
+ * but still a subtype of upperBound.
+ * Requires that lhs and rhs be subtypes of upperBound, obviously.
+ */
+ private def constrainedLub(lhs: Type, rhs: Type, upperBound: Type): Type = {
+ // TODO Improve this
+ if (upperBound == NoType) upperBound
+ else if (lhs == rhs) lhs
+ else if (lhs == NothingType) rhs
+ else if (rhs == NothingType) lhs
+ else upperBound
+ }
+
+ /** Trampolines a pretransform */
+ private def trampoline(tailrec: => TailRec[Tree]): Tree = {
+ curTrampolineId += 1
+
+ val myTrampolineId = curTrampolineId
+
+ try {
+ var rec = () => tailrec
+
+ while (true) {
+ try {
+ return rec().result
+ } catch {
+ case e: RollbackException if e.trampolineId == myTrampolineId =>
+ rollbacksCount += 1
+ if (rollbacksCount > MaxRollbacksPerMethod)
+ throw new TooManyRollbacksException
+
+ usedLocalNames.clear()
+ usedLocalNames ++= e.savedUsedLocalNames
+ usedLabelNames.clear()
+ usedLabelNames ++= e.savedUsedLabelNames
+ for ((state, backup) <- statesInUse zip e.savedStates)
+ state.asInstanceOf[State[Any]].restore(backup)
+
+ rec = e.cont
+ }
+ }
+
+ sys.error("Reached end of infinite loop")
+ } finally {
+ curTrampolineId -= 1
+ }
+ }
+}
+
+private[optimizer] object OptimizerCore {
+
+ private final val MaxRollbacksPerMethod = 256
+
+ private final class TooManyRollbacksException
+ extends scala.util.control.ControlThrowable
+
+ private val AnonFunctionClassPrefix = "sjsr_AnonFunction"
+
+ private type CancelFun = () => Nothing
+ private type PreTransCont = PreTransform => TailRec[Tree]
+
+ private case class RefinedType private (base: Type, isExact: Boolean,
+ isNullable: Boolean)(
+ val allocationSite: Option[AllocationSite], dummy: Int = 0) {
+
+ def isNothingType: Boolean = base == NothingType
+ }
+
+ private object RefinedType {
+ def apply(base: Type, isExact: Boolean, isNullable: Boolean,
+ allocationSite: Option[AllocationSite]): RefinedType =
+ new RefinedType(base, isExact, isNullable)(allocationSite)
+
+ def apply(base: Type, isExact: Boolean, isNullable: Boolean): RefinedType =
+ RefinedType(base, isExact, isNullable, None)
+
+ def apply(tpe: Type): RefinedType = tpe match {
+ case BooleanType | IntType | FloatType | DoubleType | StringType |
+ UndefType | NothingType | _:RecordType | NoType =>
+ RefinedType(tpe, isExact = true, isNullable = false)
+ case NullType =>
+ RefinedType(tpe, isExact = true, isNullable = true)
+ case _ =>
+ RefinedType(tpe, isExact = false, isNullable = true)
+ }
+
+ val NoRefinedType = RefinedType(NoType)
+ val Nothing = RefinedType(NothingType)
+ }
+
+ private class AllocationSite(private val node: Tree) {
+ override def equals(that: Any): Boolean = that match {
+ case that: AllocationSite => this.node eq that.node
+ case _ => false
+ }
+
+ override def hashCode(): Int =
+ System.identityHashCode(node)
+
+ override def toString(): String =
+ s"AllocationSite($node)"
+ }
+
+ private case class LocalDef(
+ tpe: RefinedType,
+ mutable: Boolean,
+ replacement: LocalDefReplacement) {
+
+ def newReplacement(implicit pos: Position): Tree = replacement match {
+ case ReplaceWithVarRef(name, originalName, used, _) =>
+ used.value = true
+ VarRef(Ident(name, originalName), mutable)(tpe.base)
+
+ case ReplaceWithRecordVarRef(_, _, _, _, cancelFun) =>
+ cancelFun()
+
+ case ReplaceWithThis() =>
+ This()(tpe.base)
+
+ case ReplaceWithConstant(value) =>
+ value
+
+ case TentativeClosureReplacement(_, _, _, _, _, cancelFun) =>
+ cancelFun()
+
+ case InlineClassBeingConstructedReplacement(_, cancelFun) =>
+ cancelFun()
+
+ case InlineClassInstanceReplacement(_, _, cancelFun) =>
+ cancelFun()
+ }
+
+ def contains(that: LocalDef): Boolean = {
+ (this eq that) || (replacement match {
+ case TentativeClosureReplacement(_, _, _, captureLocalDefs, _, _) =>
+ captureLocalDefs.exists(_.contains(that))
+ case InlineClassInstanceReplacement(_, fieldLocalDefs, _) =>
+ fieldLocalDefs.valuesIterator.exists(_.contains(that))
+ case _ =>
+ false
+ })
+ }
+ }
+
+ private sealed abstract class LocalDefReplacement
+
+ private final case class ReplaceWithVarRef(name: String,
+ originalName: Option[String],
+ used: SimpleState[Boolean],
+ longOpTree: Option[() => Tree]) extends LocalDefReplacement
+
+ private final case class ReplaceWithRecordVarRef(name: String,
+ originalName: Option[String],
+ recordType: RecordType,
+ used: SimpleState[Boolean],
+ cancelFun: CancelFun) extends LocalDefReplacement
+
+ private final case class ReplaceWithThis() extends LocalDefReplacement
+
+ private final case class ReplaceWithConstant(
+ value: Tree) extends LocalDefReplacement
+
+ private final case class TentativeClosureReplacement(
+ captureParams: List[ParamDef], params: List[ParamDef], body: Tree,
+ captureValues: List[LocalDef],
+ alreadyUsed: SimpleState[Boolean],
+ cancelFun: CancelFun) extends LocalDefReplacement
+
+ private final case class InlineClassBeingConstructedReplacement(
+ fieldLocalDefs: Map[String, LocalDef],
+ cancelFun: CancelFun) extends LocalDefReplacement
+
+ private final case class InlineClassInstanceReplacement(
+ recordType: RecordType,
+ fieldLocalDefs: Map[String, LocalDef],
+ cancelFun: CancelFun) extends LocalDefReplacement
+
+ private final class LabelInfo(
+ val newName: String,
+ val acceptRecords: Boolean,
+ /** (actualType, originalType), actualType can be a RecordType. */
+ val returnedTypes: SimpleState[List[(Type, RefinedType)]] = new SimpleState(Nil))
+
+ private class OptEnv(
+ val localDefs: Map[String, LocalDef],
+ val labelInfos: Map[String, LabelInfo]) {
+
+ def withLocalDef(oldName: String, rep: LocalDef): OptEnv =
+ new OptEnv(localDefs + (oldName -> rep), labelInfos)
+
+ def withLocalDefs(reps: List[(String, LocalDef)]): OptEnv =
+ new OptEnv(localDefs ++ reps, labelInfos)
+
+ def withLabelInfo(oldName: String, info: LabelInfo): OptEnv =
+ new OptEnv(localDefs, labelInfos + (oldName -> info))
+
+ def withinFunction(paramLocalDefs: List[(String, LocalDef)]): OptEnv =
+ new OptEnv(localDefs ++ paramLocalDefs, Map.empty)
+
+ override def toString(): String = {
+ "localDefs:"+localDefs.mkString("\n ", "\n ", "\n") +
+ "labelInfos:"+labelInfos.mkString("\n ", "\n ", "")
+ }
+ }
+
+ private object OptEnv {
+ val Empty: OptEnv = new OptEnv(Map.empty, Map.empty)
+ }
+
+ private class Scope(val env: OptEnv,
+ val implsBeingInlined: Set[(Option[AllocationSite], AbstractMethodID)]) {
+ def withEnv(env: OptEnv): Scope =
+ new Scope(env, implsBeingInlined)
+
+ def inlining(impl: (Option[AllocationSite], AbstractMethodID)): Scope = {
+ assert(!implsBeingInlined(impl), s"Circular inlining of $impl")
+ new Scope(env, implsBeingInlined + impl)
+ }
+ }
+
+ private object Scope {
+ val Empty: Scope = new Scope(OptEnv.Empty, Set.empty)
+ }
+
+ /** The result of pretransformExpr().
+ * It has a `tpe` as precisely refined as if a full transformExpr() had
+ * been performed.
+ * It is also not dependent on the environment anymore. In some sense, it
+ * has "captured" its environment at definition site.
+ */
+ private sealed abstract class PreTransform {
+ def pos: Position
+ val tpe: RefinedType
+
+ def contains(localDef: LocalDef): Boolean = this match {
+ case PreTransBlock(_, result) =>
+ result.contains(localDef)
+ case PreTransLocalDef(thisLocalDef) =>
+ thisLocalDef.contains(localDef)
+ case _ =>
+ false
+ }
+ }
+
+ private final class PreTransBlock private (val stats: List[Tree],
+ val result: PreTransLocalDef) extends PreTransform {
+ def pos = result.pos
+ val tpe = result.tpe
+
+ assert(stats.nonEmpty)
+
+ override def toString(): String =
+ s"PreTransBlock($stats,$result)"
+ }
+
+ private object PreTransBlock {
+ def apply(stats: List[Tree], result: PreTransform): PreTransform = {
+ if (stats.isEmpty) result
+ else {
+ result match {
+ case PreTransBlock(innerStats, innerResult) =>
+ new PreTransBlock(stats ++ innerStats, innerResult)
+ case result: PreTransLocalDef =>
+ new PreTransBlock(stats, result)
+ case PreTransRecordTree(tree, tpe, cancelFun) =>
+ PreTransRecordTree(Block(stats :+ tree)(tree.pos), tpe, cancelFun)
+ case PreTransTree(tree, tpe) =>
+ PreTransTree(Block(stats :+ tree)(tree.pos), tpe)
+ }
+ }
+ }
+
+ def unapply(preTrans: PreTransBlock): Some[(List[Tree], PreTransLocalDef)] =
+ Some(preTrans.stats, preTrans.result)
+ }
+
+ private sealed abstract class PreTransNoBlock extends PreTransform
+
+ private final case class PreTransLocalDef(localDef: LocalDef)(
+ implicit val pos: Position) extends PreTransNoBlock {
+ val tpe: RefinedType = localDef.tpe
+ }
+
+ private sealed abstract class PreTransGenTree extends PreTransNoBlock
+
+ private final case class PreTransRecordTree(tree: Tree,
+ tpe: RefinedType, cancelFun: CancelFun) extends PreTransGenTree {
+ def pos = tree.pos
+
+ assert(tree.tpe.isInstanceOf[RecordType],
+ s"Cannot create a PreTransRecordTree with non-record type ${tree.tpe}")
+ }
+
+ private final case class PreTransTree(tree: Tree,
+ tpe: RefinedType) extends PreTransGenTree {
+ def pos: Position = tree.pos
+
+ assert(!tree.tpe.isInstanceOf[RecordType],
+ s"Cannot create a Tree with record type ${tree.tpe}")
+ }
+
+ private object PreTransTree {
+ def apply(tree: Tree): PreTransTree =
+ PreTransTree(tree, RefinedType(tree.tpe))
+ }
+
+ private final case class Binding(name: String, originalName: Option[String],
+ declaredType: Type, mutable: Boolean, value: PreTransform)
+
+ private object NumberLiteral {
+ def unapply(tree: Literal): Option[Double] = tree match {
+ case DoubleLiteral(v) => Some(v)
+ case IntLiteral(v) => Some(v.toDouble)
+ case FloatLiteral(v) => Some(v.toDouble)
+ case _ => None
+ }
+ }
+
+ private object LongFromInt {
+ def apply(x: Tree)(implicit pos: Position): Tree = x match {
+ case IntLiteral(v) => LongLiteral(v)
+ case _ => UnaryOp(UnaryOp.IntToLong, x)
+ }
+
+ def unapply(tree: Tree): Option[Tree] = tree match {
+ case LongLiteral(v) if v.toInt == v => Some(IntLiteral(v.toInt)(tree.pos))
+ case UnaryOp(UnaryOp.IntToLong, x) => Some(x)
+ case _ => None
+ }
+ }
+
+ private object AndThen {
+ def apply(lhs: Tree, rhs: Tree)(implicit pos: Position): Tree =
+ If(lhs, rhs, BooleanLiteral(false))(BooleanType)
+ }
+
+ /** Tests whether `x + y` is valid without falling out of range. */
+ private def canAddLongs(x: Long, y: Long): Boolean =
+ if (y >= 0) x+y >= x
+ else x+y < x
+
+ /** Tests whether `x - y` is valid without falling out of range. */
+ private def canSubtractLongs(x: Long, y: Long): Boolean =
+ if (y >= 0) x-y <= x
+ else x-y > x
+
+ /** Tests whether `-x` is valid without falling out of range. */
+ private def canNegateLong(x: Long): Boolean =
+ x != Long.MinValue
+
+ private object Intrinsics {
+ final val ArrayCopy = 1
+ final val IdentityHashCode = ArrayCopy + 1
+
+ final val PropertiesOf = IdentityHashCode + 1
+
+ final val LongToString = PropertiesOf + 1
+ final val LongCompare = LongToString + 1
+ final val LongBitCount = LongCompare + 1
+ final val LongSignum = LongBitCount + 1
+ final val LongLeading0s = LongSignum + 1
+ final val LongTrailing0s = LongLeading0s + 1
+ final val LongToBinStr = LongTrailing0s + 1
+ final val LongToHexStr = LongToBinStr + 1
+ final val LongToOctalStr = LongToHexStr + 1
+
+ final val ByteArrayToInt8Array = LongToOctalStr + 1
+ final val ShortArrayToInt16Array = ByteArrayToInt8Array + 1
+ final val CharArrayToUint16Array = ShortArrayToInt16Array + 1
+ final val IntArrayToInt32Array = CharArrayToUint16Array + 1
+ final val FloatArrayToFloat32Array = IntArrayToInt32Array + 1
+ final val DoubleArrayToFloat64Array = FloatArrayToFloat32Array + 1
+
+ final val Int8ArrayToByteArray = DoubleArrayToFloat64Array + 1
+ final val Int16ArrayToShortArray = Int8ArrayToByteArray + 1
+ final val Uint16ArrayToCharArray = Int16ArrayToShortArray + 1
+ final val Int32ArrayToIntArray = Uint16ArrayToCharArray + 1
+ final val Float32ArrayToFloatArray = Int32ArrayToIntArray + 1
+ final val Float64ArrayToDoubleArray = Float32ArrayToFloatArray + 1
+
+ val intrinsics: Map[String, Int] = Map(
+ "jl_System$.arraycopy__O__I__O__I__I__V" -> ArrayCopy,
+ "jl_System$.identityHashCode__O__I" -> IdentityHashCode,
+
+ "sjsr_package$.propertiesOf__sjs_js_Any__sjs_js_Array" -> PropertiesOf,
+
+ "jl_Long$.toString__J__T" -> LongToString,
+ "jl_Long$.compare__J__J__I" -> LongCompare,
+ "jl_Long$.bitCount__J__I" -> LongBitCount,
+ "jl_Long$.signum__J__J" -> LongSignum,
+ "jl_Long$.numberOfLeadingZeros__J__I" -> LongLeading0s,
+ "jl_Long$.numberOfTrailingZeros__J__I" -> LongTrailing0s,
+ "jl_long$.toBinaryString__J__T" -> LongToBinStr,
+ "jl_Long$.toHexString__J__T" -> LongToHexStr,
+ "jl_Long$.toOctalString__J__T" -> LongToOctalStr,
+
+ "sjs_js_typedarray_package$.byteArray2Int8Array__AB__sjs_js_typedarray_Int8Array" -> ByteArrayToInt8Array,
+ "sjs_js_typedarray_package$.shortArray2Int16Array__AS__sjs_js_typedarray_Int16Array" -> ShortArrayToInt16Array,
+ "sjs_js_typedarray_package$.charArray2Uint16Array__AC__sjs_js_typedarray_Uint16Array" -> CharArrayToUint16Array,
+ "sjs_js_typedarray_package$.intArray2Int32Array__AI__sjs_js_typedarray_Int32Array" -> IntArrayToInt32Array,
+ "sjs_js_typedarray_package$.floatArray2Float32Array__AF__sjs_js_typedarray_Float32Array" -> FloatArrayToFloat32Array,
+ "sjs_js_typedarray_package$.doubleArray2Float64Array__AD__sjs_js_typedarray_Float64Array" -> DoubleArrayToFloat64Array,
+
+ "sjs_js_typedarray_package$.int8Array2ByteArray__sjs_js_typedarray_Int8Array__AB" -> Int8ArrayToByteArray,
+ "sjs_js_typedarray_package$.int16Array2ShortArray__sjs_js_typedarray_Int16Array__AS" -> Int16ArrayToShortArray,
+ "sjs_js_typedarray_package$.uint16Array2CharArray__sjs_js_typedarray_Uint16Array__AC" -> Uint16ArrayToCharArray,
+ "sjs_js_typedarray_package$.int32Array2IntArray__sjs_js_typedarray_Int32Array__AI" -> Int32ArrayToIntArray,
+ "sjs_js_typedarray_package$.float32Array2FloatArray__sjs_js_typedarray_Float32Array__AF" -> Float32ArrayToFloatArray,
+ "sjs_js_typedarray_package$.float64Array2DoubleArray__sjs_js_typedarray_Float64Array__AD" -> Float64ArrayToDoubleArray
+ ).withDefaultValue(-1)
+ }
+
+ private def getIntrinsicCode(target: AbstractMethodID): Int =
+ Intrinsics.intrinsics(target.toString)
+
+ private trait State[A] {
+ def makeBackup(): A
+ def restore(backup: A): Unit
+ }
+
+ private class SimpleState[A](var value: A) extends State[A] {
+ def makeBackup(): A = value
+ def restore(backup: A): Unit = value = backup
+ }
+
+ trait AbstractMethodID {
+ def inlineable: Boolean
+ def isTraitImplForwarder: Boolean
+ }
+
+ /** Parts of [[GenIncOptimizer#MethodImpl]] with decisions about optimizations. */
+ abstract class MethodImpl {
+ def encodedName: String
+ def optimizerHints: OptimizerHints
+ def originalDef: MethodDef
+ def thisType: Type
+
+ var inlineable: Boolean = false
+ var isTraitImplForwarder: Boolean = false
+
+ protected def updateInlineable(): Unit = {
+ val MethodDef(Ident(methodName, _), params, _, body) = originalDef
+
+ isTraitImplForwarder = body match {
+ // Shape of forwarders to trait impls
+ case TraitImplApply(impl, method, args) =>
+ ((args.size == params.size + 1) &&
+ (args.head.isInstanceOf[This]) &&
+ (args.tail.zip(params).forall {
+ case (VarRef(Ident(aname, _), _),
+ ParamDef(Ident(pname, _), _, _)) => aname == pname
+ case _ => false
+ }))
+
+ case _ => false
+ }
+
+ inlineable = optimizerHints.hasInlineAnnot || isTraitImplForwarder || {
+ val MethodDef(_, params, _, body) = originalDef
+ body match {
+ case _:Skip | _:This | _:Literal => true
+
+ // Shape of accessors
+ case Select(This(), _, _) if params.isEmpty => true
+ case Assign(Select(This(), _, _), VarRef(_, _))
+ if params.size == 1 => true
+
+ // Shape of trivial call-super constructors
+ case Block(stats)
+ if params.isEmpty && isConstructorName(encodedName) &&
+ stats.forall(isTrivialConstructorStat) => true
+
+ // Simple method
+ case SimpleMethodBody() => true
+
+ case _ => false
+ }
+ }
+ }
+ }
+
+ private def isTrivialConstructorStat(stat: Tree): Boolean = stat match {
+ case This() =>
+ true
+ case StaticApply(This(), _, _, Nil) =>
+ true
+ case TraitImplApply(_, Ident(methodName, _), This() :: Nil) =>
+ methodName.contains("__$init$__")
+ case _ =>
+ false
+ }
+
+ private object SimpleMethodBody {
+ @tailrec
+ def unapply(body: Tree): Boolean = body match {
+ case New(_, _, args) => areSimpleArgs(args)
+ case Apply(receiver, _, args) => areSimpleArgs(receiver :: args)
+ case StaticApply(receiver, _, _, args) => areSimpleArgs(receiver :: args)
+ case TraitImplApply(_, _, args) => areSimpleArgs(args)
+ case Select(qual, _, _) => isSimpleArg(qual)
+ case IsInstanceOf(inner, _) => isSimpleArg(inner)
+
+ case Block(List(inner, Undefined())) =>
+ unapply(inner)
+
+ case Unbox(inner, _) => unapply(inner)
+ case AsInstanceOf(inner, _) => unapply(inner)
+
+ case _ => isSimpleArg(body)
+ }
+
+ private def areSimpleArgs(args: List[Tree]): Boolean =
+ args.forall(isSimpleArg)
+
+ @tailrec
+ private def isSimpleArg(arg: Tree): Boolean = arg match {
+ case New(_, _, Nil) => true
+ case Apply(receiver, _, Nil) => isTrivialArg(receiver)
+ case StaticApply(receiver, _, _, Nil) => isTrivialArg(receiver)
+ case TraitImplApply(_, _, Nil) => true
+
+ case ArrayLength(array) => isTrivialArg(array)
+ case ArraySelect(array, index) => isTrivialArg(array) && isTrivialArg(index)
+
+ case Unbox(inner, _) => isSimpleArg(inner)
+ case AsInstanceOf(inner, _) => isSimpleArg(inner)
+
+ case _ =>
+ isTrivialArg(arg)
+ }
+
+ private def isTrivialArg(arg: Tree): Boolean = arg match {
+ case _:VarRef | _:This | _:Literal | _:LoadModule =>
+ true
+ case _ =>
+ false
+ }
+ }
+
+ private object BlockOrAlone {
+ def unapply(tree: Tree): Some[(List[Tree], Tree)] = Some(tree match {
+ case Block(init :+ last) => (init, last)
+ case _ => (Nil, tree)
+ })
+ }
+
+ /** Recreates precise [[Infos.MethodInfo]] from the optimized [[MethodDef]]. */
+ private def recreateInfo(methodDef: MethodDef): Infos.MethodInfo = {
+ new RecreateInfoTraverser().recreateInfo(methodDef)
+ }
+
+ private final class RecreateInfoTraverser extends Traversers.Traverser {
+ import RecreateInfoTraverser._
+
+ private val calledMethods = mutable.Map.empty[String, mutable.Set[String]]
+ private val calledMethodsStatic = mutable.Map.empty[String, mutable.Set[String]]
+ private val instantiatedClasses = mutable.Set.empty[String]
+ private val accessedModules = mutable.Set.empty[String]
+ private val accessedClassData = mutable.Set.empty[String]
+
+ def recreateInfo(methodDef: MethodDef): Infos.MethodInfo = {
+ traverse(methodDef.body)
+ Infos.MethodInfo(
+ encodedName = methodDef.name.name,
+ calledMethods = calledMethods.toMap.mapValues(_.toList),
+ calledMethodsStatic = calledMethodsStatic.toMap.mapValues(_.toList),
+ instantiatedClasses = instantiatedClasses.toList,
+ accessedModules = accessedModules.toList,
+ accessedClassData = accessedClassData.toList)
+ }
+
+ private def addCalledMethod(container: String, methodName: String): Unit =
+ calledMethods.getOrElseUpdate(container, mutable.Set.empty) += methodName
+
+ private def addCalledMethodStatic(container: String, methodName: String): Unit =
+ calledMethodsStatic.getOrElseUpdate(container, mutable.Set.empty) += methodName
+
+ private def refTypeToClassData(tpe: ReferenceType): String = tpe match {
+ case ClassType(cls) => cls
+ case ArrayType(base, _) => base
+ }
+
+ def addAccessedClassData(encodedName: String): Unit = {
+ if (!AlwaysPresentClassData.contains(encodedName))
+ accessedClassData += encodedName
+ }
+
+ def addAccessedClassData(tpe: ReferenceType): Unit =
+ addAccessedClassData(refTypeToClassData(tpe))
+
+ override def traverse(tree: Tree): Unit = {
+ tree match {
+ case New(ClassType(cls), ctor, _) =>
+ instantiatedClasses += cls
+ addCalledMethodStatic(cls, ctor.name)
+
+ case Apply(receiver, method, _) =>
+ receiver.tpe match {
+ case ClassType(cls) if !Definitions.HijackedClasses.contains(cls) =>
+ addCalledMethod(cls, method.name)
+ case AnyType =>
+ addCalledMethod(Definitions.ObjectClass, method.name)
+ case ArrayType(_, _) if method.name != "clone__O" =>
+ /* clone__O is overridden in the pseudo Array classes and is
+ * always kept anyway, because it is in scalajsenv.js.
+ * Other methods delegate to Object, which we can model with
+ * a static call to Object.method.
+ */
+ addCalledMethodStatic(Definitions.ObjectClass, method.name)
+ case _ =>
+ // Nothing to do
+ }
+
+ case StaticApply(_, ClassType(cls), method, _) =>
+ addCalledMethodStatic(cls, method.name)
+ case TraitImplApply(ClassType(impl), method, _) =>
+ addCalledMethodStatic(impl, method.name)
+
+ case LoadModule(ClassType(cls)) =>
+ accessedModules += cls.stripSuffix("$")
+
+ case NewArray(tpe, _) =>
+ addAccessedClassData(tpe)
+ case ArrayValue(tpe, _) =>
+ addAccessedClassData(tpe)
+ case IsInstanceOf(_, cls) =>
+ addAccessedClassData(cls)
+ case AsInstanceOf(_, cls) =>
+ addAccessedClassData(cls)
+ case ClassOf(cls) =>
+ addAccessedClassData(cls)
+
+ case _ =>
+ }
+ super.traverse(tree)
+ }
+ }
+
+ private object RecreateInfoTraverser {
+ /** Class data that are never eliminated by dce, so we don't need to
+ * record them.
+ */
+ private val AlwaysPresentClassData = {
+ import Definitions._
+ Set("V", "Z", "C", "B", "S", "I", "J", "F", "D",
+ ObjectClass, StringClass)
+ }
+ }
+
+ private def exceptionMsg(myself: AbstractMethodID,
+ attemptedInlining: List[AbstractMethodID]) = {
+ val buf = new StringBuilder()
+
+ buf.append("The Scala.js optimizer crashed while optimizing " + myself)
+
+ buf.append("\nMethods attempted to inline:\n")
+
+ for (m <- attemptedInlining) {
+ buf.append("* ")
+ buf.append(m)
+ buf.append('\n')
+ }
+
+ buf.toString
+ }
+
+ private class RollbackException(val trampolineId: Int,
+ val savedUsedLocalNames: Set[String],
+ val savedUsedLabelNames: Set[String],
+ val savedStates: List[Any],
+ val cont: () => TailRec[Tree]) extends ControlThrowable
+
+ class OptimizeException(val myself: AbstractMethodID,
+ val attemptedInlining: List[AbstractMethodID], cause: Throwable
+ ) extends Exception(exceptionMsg(myself, attemptedInlining), cause)
+
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSOptimizer.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSOptimizer.scala
new file mode 100644
index 0000000..646484b
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSOptimizer.scala
@@ -0,0 +1,552 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import scala.annotation.{switch, tailrec}
+
+import scala.collection.mutable
+import scala.collection.immutable.{Seq, Traversable}
+
+import java.net.URI
+
+import scala.scalajs.ir
+import ir.Infos
+import ir.ClassKind
+
+import scala.scalajs.tools.logging._
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.classpath._
+import scala.scalajs.tools.sourcemap._
+import scala.scalajs.tools.corelib._
+
+import scala.scalajs.tools.sem.Semantics
+
+import scala.scalajs.tools.javascript
+import javascript.{Trees => js}
+
+/** Scala.js optimizer: does type-aware global dce. */
+class ScalaJSOptimizer(
+ semantics: Semantics,
+ optimizerFactory: (Semantics) => GenIncOptimizer) {
+ import ScalaJSOptimizer._
+
+ private val classEmitter = new javascript.ScalaJSClassEmitter(semantics)
+
+ private[this] var persistentState: PersistentState = new PersistentState
+ private[this] var optimizer: GenIncOptimizer = optimizerFactory(semantics)
+
+ def this(semantics: Semantics) = this(semantics, new IncOptimizer(_))
+
+ /** Applies Scala.js-specific optimizations to a CompleteIRClasspath.
+ * See [[ScalaJSOptimizer.Inputs]] for details about the required and
+ * optional inputs.
+ * See [[ScalaJSOptimizer.OutputConfig]] for details about the configuration
+ * for the output of this method.
+ * Returns a [[CompleteCIClasspath]] containing the result of the
+ * optimizations.
+ *
+ * analyzes, dead code eliminates and concatenates IR content
+ * - Maintains/establishes order
+ * - No IR in result
+ * - CoreJSLibs in result (since they are implicitly in the CompleteIRCP)
+ */
+ def optimizeCP(inputs: Inputs[IRClasspath], outCfg: OutputConfig,
+ logger: Logger): LinkedClasspath = {
+
+ val cp = inputs.input
+
+ CacheUtils.cached(cp.version, outCfg.output, outCfg.cache) {
+ logger.info(s"Fast optimizing ${outCfg.output.path}")
+ optimizeIR(inputs.copy(input = inputs.input.scalaJSIR), outCfg, logger)
+ }
+
+ new LinkedClasspath(cp.jsLibs, outCfg.output, cp.requiresDOM, cp.version)
+ }
+
+ def optimizeIR(inputs: Inputs[Traversable[VirtualScalaJSIRFile]],
+ outCfg: OutputConfig, logger: Logger): Unit = {
+
+ val builder = {
+ import outCfg._
+ if (wantSourceMap)
+ new JSFileBuilderWithSourceMap(output.name,
+ output.contentWriter,
+ output.sourceMapWriter,
+ relativizeSourceMapBase)
+ else
+ new JSFileBuilder(output.name, output.contentWriter)
+ }
+
+ builder.addLine("'use strict';")
+ CoreJSLibs.libs(semantics).foreach(builder.addFile _)
+
+ optimizeIR(inputs, outCfg, builder, logger)
+
+ builder.complete()
+ builder.closeWriters()
+ }
+
+ def optimizeIR(inputs: Inputs[Traversable[VirtualScalaJSIRFile]],
+ outCfg: OptimizerConfig, builder: JSTreeBuilder, logger: Logger): Unit = {
+
+ /* Handle tree equivalence: If we handled source maps so far, positions are
+ still up-to-date. Otherwise we need to flush the state if proper
+ positions are requested now.
+ */
+ if (outCfg.wantSourceMap && !persistentState.wasWithSourceMap)
+ clean()
+
+ persistentState.wasWithSourceMap = outCfg.wantSourceMap
+
+ persistentState.startRun()
+ try {
+ import inputs._
+ val allData =
+ GenIncOptimizer.logTime(logger, "Read info") {
+ readAllData(inputs.input, logger)
+ }
+ val (useOptimizer, refinedAnalyzer) = GenIncOptimizer.logTime(
+ logger, "Optimizations part") {
+ val analyzer =
+ GenIncOptimizer.logTime(logger, "Compute reachability") {
+ val analyzer = new Analyzer(logger, semantics, allData,
+ globalWarnEnabled = true,
+ isBeforeOptimizer = !outCfg.disableOptimizer)
+ analyzer.computeReachability(manuallyReachable, noWarnMissing)
+ analyzer
+ }
+ if (outCfg.checkIR) {
+ GenIncOptimizer.logTime(logger, "Check IR") {
+ if (analyzer.allAvailable)
+ checkIR(analyzer, logger)
+ else if (inputs.noWarnMissing.isEmpty)
+ sys.error("Could not check IR because there where linking errors.")
+ }
+ }
+ def getClassTreeIfChanged(encodedName: String,
+ lastVersion: Option[String]): Option[(ir.Trees.ClassDef, Option[String])] = {
+ val persistentFile = persistentState.encodedNameToPersistentFile(encodedName)
+ persistentFile.treeIfChanged(lastVersion)
+ }
+
+ val useOptimizer = analyzer.allAvailable && !outCfg.disableOptimizer
+
+ if (outCfg.batchMode)
+ optimizer = optimizerFactory(semantics)
+
+ val refinedAnalyzer = if (useOptimizer) {
+ GenIncOptimizer.logTime(logger, "Inliner") {
+ optimizer.update(analyzer, getClassTreeIfChanged,
+ outCfg.wantSourceMap, logger)
+ }
+ GenIncOptimizer.logTime(logger, "Refined reachability analysis") {
+ val refinedData = computeRefinedData(allData, optimizer)
+ val refinedAnalyzer = new Analyzer(logger, semantics, refinedData,
+ globalWarnEnabled = false,
+ isBeforeOptimizer = false)
+ refinedAnalyzer.computeReachability(manuallyReachable, noWarnMissing)
+ refinedAnalyzer
+ }
+ } else {
+ if (inputs.noWarnMissing.isEmpty && !outCfg.disableOptimizer)
+ logger.warn("Not running the inliner because there where linking errors.")
+ analyzer
+ }
+ (useOptimizer, refinedAnalyzer)
+ }
+ GenIncOptimizer.logTime(logger, "Write DCE'ed output") {
+ buildDCEedOutput(builder, refinedAnalyzer, useOptimizer)
+ }
+ } finally {
+ persistentState.endRun(outCfg.unCache)
+ logger.debug(
+ s"Inc. opt stats: reused: ${persistentState.statsReused} -- "+
+ s"invalidated: ${persistentState.statsInvalidated} -- "+
+ s"trees read: ${persistentState.statsTreesRead}")
+ }
+ }
+
+ /** Resets all persistent state of this optimizer */
+ def clean(): Unit = {
+ persistentState = new PersistentState
+ optimizer = optimizerFactory(semantics)
+ }
+
+ private def readAllData(ir: Traversable[VirtualScalaJSIRFile],
+ logger: Logger): scala.collection.Seq[Infos.ClassInfo] = {
+ ir.map(persistentState.getPersistentIRFile(_).info).toSeq
+ }
+
+ private def checkIR(analyzer: Analyzer, logger: Logger): Unit = {
+ val allClassDefs = for {
+ classInfo <- analyzer.classInfos.values
+ persistentIRFile <- persistentState.encodedNameToPersistentFile.get(
+ classInfo.encodedName)
+ } yield persistentIRFile.tree
+ val checker = new IRChecker(analyzer, allClassDefs.toSeq, logger)
+ if (!checker.check())
+ sys.error(s"There were ${checker.errorCount} IR checking errors.")
+ }
+
+ private def computeRefinedData(
+ allData: scala.collection.Seq[Infos.ClassInfo],
+ optimizer: GenIncOptimizer): scala.collection.Seq[Infos.ClassInfo] = {
+
+ def refineMethodInfo(container: optimizer.MethodContainer,
+ methodInfo: Infos.MethodInfo): Infos.MethodInfo = {
+ container.methods.get(methodInfo.encodedName).fold(methodInfo) {
+ methodImpl => methodImpl.preciseInfo
+ }
+ }
+
+ def refineMethodInfos(container: optimizer.MethodContainer,
+ methodInfos: List[Infos.MethodInfo]): List[Infos.MethodInfo] = {
+ methodInfos.map(m => refineMethodInfo(container, m))
+ }
+
+ def refineClassInfo(container: optimizer.MethodContainer,
+ info: Infos.ClassInfo): Infos.ClassInfo = {
+ val refinedMethods = refineMethodInfos(container, info.methods)
+ Infos.ClassInfo(info.name, info.encodedName, info.isExported,
+ info.ancestorCount, info.kind, info.superClass, info.ancestors,
+ Infos.OptimizerHints.empty, refinedMethods)
+ }
+
+ for {
+ info <- allData
+ } yield {
+ info.kind match {
+ case ClassKind.Class | ClassKind.ModuleClass =>
+ optimizer.getClass(info.encodedName).fold(info) {
+ cls => refineClassInfo(cls, info)
+ }
+
+ case ClassKind.TraitImpl =>
+ optimizer.getTraitImpl(info.encodedName).fold(info) {
+ impl => refineClassInfo(impl, info)
+ }
+
+ case _ =>
+ info
+ }
+ }
+ }
+
+ private def buildDCEedOutput(builder: JSTreeBuilder,
+ analyzer: Analyzer, useInliner: Boolean): Unit = {
+
+ def compareClassInfo(lhs: analyzer.ClassInfo, rhs: analyzer.ClassInfo) = {
+ if (lhs.ancestorCount != rhs.ancestorCount) lhs.ancestorCount < rhs.ancestorCount
+ else lhs.encodedName.compareTo(rhs.encodedName) < 0
+ }
+
+ def addPersistentFile(classInfo: analyzer.ClassInfo,
+ persistentFile: PersistentIRFile) = {
+ import ir.Trees._
+ import javascript.JSDesugaring.{desugarJavaScript => desugar}
+
+ val d = persistentFile.desugared
+ lazy val classDef = {
+ persistentState.statsTreesRead += 1
+ persistentFile.tree
+ }
+
+ def addTree(tree: js.Tree): Unit =
+ builder.addJSTree(tree)
+
+ def addReachableMethods(emitFun: (String, MethodDef) => js.Tree): Unit = {
+ /* This is a bit convoluted because we have to:
+ * * avoid to use classDef at all if we already know all the needed methods
+ * * if any new method is needed, better to go through the defs once
+ */
+ val methodNames = d.methodNames.getOrElseUpdate(
+ classDef.defs collect {
+ case MethodDef(Ident(encodedName, _), _, _, _) => encodedName
+ })
+ val reachableMethods = methodNames.filter(
+ name => classInfo.methodInfos(name).isReachable)
+ if (reachableMethods.forall(d.methods.contains(_))) {
+ for (encodedName <- reachableMethods) {
+ addTree(d.methods(encodedName))
+ }
+ } else {
+ classDef.defs.foreach {
+ case m: MethodDef if m.name.isInstanceOf[Ident] =>
+ if (classInfo.methodInfos(m.name.name).isReachable) {
+ addTree(d.methods.getOrElseUpdate(m.name.name,
+ emitFun(classInfo.encodedName, m)))
+ }
+ case _ =>
+ }
+ }
+ }
+
+ if (classInfo.isImplClass) {
+ if (useInliner) {
+ for {
+ method <- optimizer.findTraitImpl(classInfo.encodedName).methods.values
+ if (classInfo.methodInfos(method.encodedName).isReachable)
+ } {
+ addTree(method.desugaredDef)
+ }
+ } else {
+ addReachableMethods(classEmitter.genTraitImplMethod)
+ }
+ } else if (!classInfo.hasMoreThanData) {
+ // there is only the data anyway
+ addTree(d.wholeClass.getOrElseUpdate(
+ classEmitter.genClassDef(classDef)))
+ } else {
+ if (classInfo.isAnySubclassInstantiated) {
+ addTree(d.constructor.getOrElseUpdate(
+ classEmitter.genConstructor(classDef)))
+ if (useInliner) {
+ for {
+ method <- optimizer.findClass(classInfo.encodedName).methods.values
+ if (classInfo.methodInfos(method.encodedName).isReachable)
+ } {
+ addTree(method.desugaredDef)
+ }
+ } else {
+ addReachableMethods(classEmitter.genMethod)
+ }
+ addTree(d.exportedMembers.getOrElseUpdate(js.Block {
+ classDef.defs collect {
+ case m: MethodDef if m.name.isInstanceOf[StringLiteral] =>
+ classEmitter.genMethod(classInfo.encodedName, m)
+ case p: PropertyDef =>
+ classEmitter.genProperty(classInfo.encodedName, p)
+ }
+ }(classDef.pos)))
+ }
+ if (classInfo.isDataAccessed) {
+ addTree(d.typeData.getOrElseUpdate(js.Block(
+ classEmitter.genInstanceTests(classDef),
+ classEmitter.genArrayInstanceTests(classDef),
+ classEmitter.genTypeData(classDef)
+ )(classDef.pos)))
+ }
+ if (classInfo.isAnySubclassInstantiated)
+ addTree(d.setTypeData.getOrElseUpdate(
+ classEmitter.genSetTypeData(classDef)))
+ if (classInfo.isModuleAccessed)
+ addTree(d.moduleAccessor.getOrElseUpdate(
+ classEmitter.genModuleAccessor(classDef)))
+ addTree(d.classExports.getOrElseUpdate(
+ classEmitter.genClassExports(classDef)))
+ }
+ }
+
+
+ for {
+ classInfo <- analyzer.classInfos.values.toSeq.sortWith(compareClassInfo)
+ if classInfo.isNeededAtAll
+ } {
+ val optPersistentFile =
+ persistentState.encodedNameToPersistentFile.get(classInfo.encodedName)
+
+ // if we have a persistent file, this is not a dummy class
+ optPersistentFile.fold {
+ if (classInfo.isAnySubclassInstantiated) {
+ // Subclass will emit constructor that references this dummy class.
+ // Therefore, we need to emit a dummy parent.
+ builder.addJSTree(
+ classEmitter.genDummyParent(classInfo.encodedName))
+ }
+ } { pf => addPersistentFile(classInfo, pf) }
+ }
+ }
+}
+
+object ScalaJSOptimizer {
+ /** Inputs of the Scala.js optimizer. */
+ final case class Inputs[T](
+ /** The CompleteNCClasspath or the IR files to be packaged. */
+ input: T,
+ /** Manual additions to reachability */
+ manuallyReachable: Seq[ManualReachability] = Nil,
+ /** Elements we won't warn even if they don't exist */
+ noWarnMissing: Seq[NoWarnMissing] = Nil
+ )
+
+ sealed abstract class ManualReachability
+ final case class ReachObject(name: String) extends ManualReachability
+ final case class Instantiate(name: String) extends ManualReachability
+ final case class ReachMethod(className: String, methodName: String,
+ static: Boolean) extends ManualReachability
+
+ sealed abstract class NoWarnMissing
+ final case class NoWarnClass(className: String) extends NoWarnMissing
+ final case class NoWarnMethod(className: String, methodName: String)
+ extends NoWarnMissing
+
+ /** Configurations relevant to the optimizer */
+ trait OptimizerConfig {
+ /** Ask to produce source map for the output. Is used in the incremental
+ * optimizer to decide whether a position change should trigger re-inlining
+ */
+ val wantSourceMap: Boolean
+ /** If true, performs expensive checks of the IR for the used parts. */
+ val checkIR: Boolean
+ /** If true, the optimizer removes trees that have not been used in the
+ * last run from the cache. Otherwise, all trees that has been used once,
+ * are kept in memory. */
+ val unCache: Boolean
+ /** If true, no optimizations are performed */
+ val disableOptimizer: Boolean
+ /** If true, nothing is performed incrementally */
+ val batchMode: Boolean
+ }
+
+ /** Configuration for the output of the Scala.js optimizer. */
+ final case class OutputConfig(
+ /** Writer for the output. */
+ output: WritableVirtualJSFile,
+ /** Cache file */
+ cache: Option[WritableVirtualTextFile] = None,
+ /** Ask to produce source map for the output */
+ wantSourceMap: Boolean = false,
+ /** Base path to relativize paths in the source map. */
+ relativizeSourceMapBase: Option[URI] = None,
+ /** If true, performs expensive checks of the IR for the used parts. */
+ checkIR: Boolean = false,
+ /** If true, the optimizer removes trees that have not been used in the
+ * last run from the cache. Otherwise, all trees that has been used once,
+ * are kept in memory. */
+ unCache: Boolean = true,
+ /** If true, no optimizations are performed */
+ disableOptimizer: Boolean = false,
+ /** If true, nothing is performed incrementally */
+ batchMode: Boolean = false
+ ) extends OptimizerConfig
+
+ // Private helpers -----------------------------------------------------------
+
+ private final class PersistentState {
+ val files = mutable.Map.empty[String, PersistentIRFile]
+ val encodedNameToPersistentFile =
+ mutable.Map.empty[String, PersistentIRFile]
+
+ var statsReused: Int = 0
+ var statsInvalidated: Int = 0
+ var statsTreesRead: Int = 0
+
+ var wasWithSourceMap: Boolean = true
+
+ def startRun(): Unit = {
+ statsReused = 0
+ statsInvalidated = 0
+ statsTreesRead = 0
+ for (file <- files.values)
+ file.startRun()
+ }
+
+ def getPersistentIRFile(irFile: VirtualScalaJSIRFile): PersistentIRFile = {
+ val file = files.getOrElseUpdate(irFile.path,
+ new PersistentIRFile(irFile.path))
+ if (file.updateFile(irFile))
+ statsReused += 1
+ else
+ statsInvalidated += 1
+ encodedNameToPersistentFile += ((file.info.encodedName, file))
+ file
+ }
+
+ def endRun(unCache: Boolean): Unit = {
+ // "Garbage-collect" persisted versions of files that have disappeared
+ files.retain((_, f) => f.cleanAfterRun(unCache))
+ encodedNameToPersistentFile.clear()
+ }
+ }
+
+ private final class PersistentIRFile(val path: String) {
+ import ir.Trees._
+
+ private[this] var existedInThisRun: Boolean = false
+ private[this] var desugaredUsedInThisRun: Boolean = false
+
+ private[this] var irFile: VirtualScalaJSIRFile = null
+ private[this] var version: Option[String] = None
+ private[this] var _info: Infos.ClassInfo = null
+ private[this] var _tree: ClassDef = null
+ private[this] var _desugared: Desugared = null
+
+ def startRun(): Unit = {
+ existedInThisRun = false
+ desugaredUsedInThisRun = false
+ }
+
+ def updateFile(irFile: VirtualScalaJSIRFile): Boolean = {
+ existedInThisRun = true
+ this.irFile = irFile
+ if (version.isDefined && version == irFile.version) {
+ // yeepeeh, nothing to do
+ true
+ } else {
+ version = irFile.version
+ _info = irFile.info
+ _tree = null
+ _desugared = null
+ false
+ }
+ }
+
+ def info: Infos.ClassInfo = _info
+
+ def desugared: Desugared = {
+ desugaredUsedInThisRun = true
+ if (_desugared == null)
+ _desugared = new Desugared
+ _desugared
+ }
+
+ def tree: ClassDef = {
+ if (_tree == null)
+ _tree = irFile.tree
+ _tree
+ }
+
+ def treeIfChanged(lastVersion: Option[String]): Option[(ClassDef, Option[String])] = {
+ if (lastVersion.isDefined && lastVersion == version) None
+ else Some((tree, version))
+ }
+
+ /** Returns true if this file should be kept for the next run at all. */
+ def cleanAfterRun(unCache: Boolean): Boolean = {
+ irFile = null
+ if (unCache && !desugaredUsedInThisRun)
+ _desugared = null // free desugared if unused in this run
+ existedInThisRun
+ }
+ }
+
+ private final class Desugared {
+ // for class kinds that are not decomposed
+ val wholeClass = new OneTimeCache[js.Tree]
+
+ val constructor = new OneTimeCache[js.Tree]
+ val methodNames = new OneTimeCache[List[String]]
+ val methods = mutable.Map.empty[String, js.Tree]
+ val exportedMembers = new OneTimeCache[js.Tree]
+ val typeData = new OneTimeCache[js.Tree]
+ val setTypeData = new OneTimeCache[js.Tree]
+ val moduleAccessor = new OneTimeCache[js.Tree]
+ val classExports = new OneTimeCache[js.Tree]
+ }
+
+ private final class OneTimeCache[A >: Null] {
+ private[this] var value: A = null
+ def getOrElseUpdate(v: => A): A = {
+ if (value == null)
+ value = v
+ value
+ }
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/CheckedBehavior.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/CheckedBehavior.scala
new file mode 100644
index 0000000..4668b3c
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/CheckedBehavior.scala
@@ -0,0 +1,24 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.sem
+
+sealed abstract class CheckedBehavior {
+ import CheckedBehavior._
+ def optimized: CheckedBehavior = this match {
+ case Fatal => Unchecked
+ case _ => this
+ }
+}
+
+object CheckedBehavior {
+ case object Compliant extends CheckedBehavior
+ case object Fatal extends CheckedBehavior
+ case object Unchecked extends CheckedBehavior
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/Semantics.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/Semantics.scala
new file mode 100644
index 0000000..9d17b06
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sem/Semantics.scala
@@ -0,0 +1,97 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.sem
+
+import scala.collection.immutable.Traversable
+
+final class Semantics private (
+ val asInstanceOfs: CheckedBehavior,
+ val strictFloats: Boolean) {
+
+ import Semantics._
+
+ def withAsInstanceOfs(behavior: CheckedBehavior): Semantics =
+ copy(asInstanceOfs = behavior)
+
+ def withStrictFloats(strictFloats: Boolean): Semantics =
+ copy(strictFloats = strictFloats)
+
+ def optimized: Semantics =
+ copy(asInstanceOfs = this.asInstanceOfs.optimized)
+
+ override def equals(that: Any): Boolean = that match {
+ case that: Semantics =>
+ this.asInstanceOfs == that.asInstanceOfs &&
+ this.strictFloats == that.strictFloats
+ case _ =>
+ false
+ }
+
+ override def hashCode(): Int = {
+ import scala.util.hashing.MurmurHash3._
+ var acc = HashSeed
+ acc = mix(acc, asInstanceOfs.hashCode)
+ acc = mixLast(acc, strictFloats.##)
+ finalizeHash(acc, 1)
+ }
+
+ override def toString(): String = {
+ s"""Semantics(
+ | asInstanceOfs = $asInstanceOfs,
+ | strictFloats = $strictFloats
+ |)""".stripMargin
+ }
+
+ /** Checks whether the given semantics setting is Java compliant */
+ def isCompliant(name: String): Boolean = name match {
+ case "asInstanceOfs" => asInstanceOfs == CheckedBehavior.Compliant
+ case "strictFloats" => strictFloats
+ case _ => false
+ }
+
+ /** Retrieve a list of semantics which are set to compliant */
+ def compliants: List[String] = {
+ def cl(name: String, cond: Boolean) = if (cond) List(name) else Nil
+
+ cl("asInstanceOfs", asInstanceOfs == CheckedBehavior.Compliant) ++
+ cl("strictFloats", strictFloats)
+ }
+
+ private def copy(
+ asInstanceOfs: CheckedBehavior = this.asInstanceOfs,
+ strictFloats: Boolean = this.strictFloats): Semantics = {
+ new Semantics(
+ asInstanceOfs = asInstanceOfs,
+ strictFloats = strictFloats)
+ }
+}
+
+object Semantics {
+ private val HashSeed =
+ scala.util.hashing.MurmurHash3.stringHash(classOf[Semantics].getName)
+
+ val Defaults: Semantics = new Semantics(
+ asInstanceOfs = CheckedBehavior.Fatal,
+ strictFloats = false)
+
+ def compliantTo(semantics: Traversable[String]): Semantics = {
+ import Defaults._
+ import CheckedBehavior._
+
+ val semsSet = semantics.toSet
+
+ def sw[T](name: String, compliant: T, default: T): T =
+ if (semsSet.contains(name)) compliant else default
+
+ new Semantics(
+ asInstanceOfs = sw("asInstanceOfs", Compliant, asInstanceOfs),
+ strictFloats = sw("strictFloats", true, strictFloats))
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/JSFileBuilder.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/JSFileBuilder.scala
new file mode 100644
index 0000000..1bf2254
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/JSFileBuilder.scala
@@ -0,0 +1,144 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.sourcemap
+
+import scala.annotation.tailrec
+
+import scala.collection.mutable
+
+import java.io._
+import java.util.regex.Pattern
+import java.net.{ URI, URISyntaxException }
+
+import scala.scalajs.ir.Position
+import scala.scalajs.tools.{javascript => js}
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.optimizer.JSTreeBuilder
+
+class JSFileBuilder(val name: String,
+ protected val outputWriter: Writer) extends JSTreeBuilder {
+ def addLine(line: String): Unit = {
+ outputWriter.write(line)
+ outputWriter.write('\n')
+ }
+
+ def addLines(lines: Seq[String]): Unit =
+ lines.foreach(addLine)
+
+ def addFile(file: VirtualJSFile): Unit =
+ addPartsOfFile(file)(!_.startsWith("//# sourceMappingURL="))
+
+ def addPartsOfFile(file: VirtualJSFile)(selector: String => Boolean): Unit = {
+ for (line <- file.readLines() if selector(line))
+ addLine(line)
+ }
+
+ /** Add a JavaScript tree representing a statement.
+ * The tree must be a valid JavaScript tree (typically obtained by
+ * desugaring a full-fledged IR tree).
+ */
+ def addJSTree(tree: js.Trees.Tree): Unit = {
+ val printer = new js.Printers.JSTreePrinter(outputWriter)
+ printer.printTopLevelTree(tree)
+ // Do not close the printer: we do not have ownership of the writers
+ }
+
+ /** Closes the underlying writer(s).
+ */
+ def closeWriters(): Unit = {
+ outputWriter.close()
+ }
+}
+
+class JSFileBuilderWithSourceMapWriter(n: String, ow: Writer,
+ protected val sourceMapWriter: SourceMapWriter)
+ extends JSFileBuilder(n, ow) {
+
+ override def addLine(line: String): Unit = {
+ super.addLine(line)
+ sourceMapWriter.nextLine()
+ }
+
+ private final val NotSelected = -1
+
+ override def addPartsOfFile(file: VirtualJSFile)(
+ selector: String => Boolean): Unit = {
+ val br = new BufferedReader(file.reader)
+ try {
+ // Select lines, and remember offsets
+ val offsets = new mutable.ArrayBuffer[Int] // (maybe NotSelected)
+ val selectedLineLengths = new mutable.ArrayBuffer[Int]
+ var line: String = br.readLine()
+ var selectedCount = 0
+ while (line != null) {
+ if (selector(line)) {
+ super.addLine(line) // super call not to advance line in source map
+ offsets += selectedCount
+ selectedLineLengths += line.length
+ selectedCount += 1
+ } else {
+ offsets += NotSelected
+ }
+ line = br.readLine()
+ }
+
+ /* We ignore a potential source map.
+ * This happens typically for corejslib.js and other helper files
+ * written directly in JS.
+ * We generate a fake line-by-line source map for these on the fly
+ */
+ val sourceFile = file.toURI
+
+ for (lineNumber <- 0 until offsets.size) {
+ val offset = offsets(lineNumber)
+ if (offset != NotSelected) {
+ val originalPos = Position(sourceFile, lineNumber, 0)
+ sourceMapWriter.startNode(0, originalPos, None)
+ sourceMapWriter.endNode(selectedLineLengths(offset))
+ sourceMapWriter.nextLine()
+ }
+ }
+ } finally {
+ br.close()
+ }
+ }
+
+ override def addJSTree(tree: js.Trees.Tree): Unit = {
+ val printer = new js.Printers.JSTreePrinterWithSourceMap(
+ outputWriter, sourceMapWriter)
+ printer.printTopLevelTree(tree)
+ // Do not close the printer: we do not have ownership of the writers
+ }
+
+ override def complete(): Unit = {
+ super.complete()
+ sourceMapWriter.complete()
+ }
+
+}
+
+class JSFileBuilderWithSourceMap(n: String, ow: Writer,
+ sourceMapOutputWriter: Writer,
+ relativizeSourceMapBasePath: Option[URI] = None)
+ extends JSFileBuilderWithSourceMapWriter(
+ n, ow,
+ new SourceMapWriter(sourceMapOutputWriter, n,
+ relativizeSourceMapBasePath)) {
+
+ override def complete(): Unit = {
+ addLine("//# sourceMappingURL=" + name + ".map")
+ super.complete()
+ }
+
+ override def closeWriters(): Unit = {
+ super.closeWriters()
+ sourceMapOutputWriter.close()
+ }
+}
diff --git a/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala
new file mode 100644
index 0000000..5d8bdb1
--- /dev/null
+++ b/examples/scala-js/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala
@@ -0,0 +1,213 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.sourcemap
+
+import java.io.Writer
+import java.net.URI
+
+import scala.collection.mutable.{ ListBuffer, HashMap, Stack, StringBuilder }
+
+import scala.scalajs.ir
+import ir.Position
+import ir.Position._
+import ir.Utils
+
+object SourceMapWriter {
+ private val Base64Map =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+ "abcdefghijklmnopqrstuvwxyz" +
+ "0123456789+/"
+
+ // Some constants for writeBase64VLQ
+ // Each base-64 digit covers 6 bits, but 1 is used for the continuation
+ private final val VLQBaseShift = 5
+ private final val VLQBase = 1 << VLQBaseShift
+ private final val VLQBaseMask = VLQBase - 1
+ private final val VLQContinuationBit = VLQBase
+
+ private def jsonString(s: String) =
+ "\"" + Utils.escapeJS(s) + "\""
+}
+
+class SourceMapWriter(
+ val out: Writer,
+ val generatedFile: String,
+ val relativizeBaseURI: Option[URI] = None) {
+
+ import SourceMapWriter._
+
+ private val sources = new ListBuffer[String]
+ private val _srcToIndex = new HashMap[SourceFile, Int]
+
+ private val names = new ListBuffer[String]
+ private val _nameToIndex = new HashMap[String, Int]
+
+ private val nodePosStack = new Stack[(Position, Option[String])]
+ nodePosStack.push((NoPosition, None))
+
+ private var lineCountInGenerated = 0
+ private var lastColumnInGenerated = 0
+ private var firstSegmentOfLine = true
+ private var lastSource: SourceFile = null
+ private var lastSourceIndex = 0
+ private var lastLine: Int = 0
+ private var lastColumn: Int = 0
+ private var lastNameIndex: Int = 0
+
+ private var pendingColumnInGenerated: Int = -1
+ private var pendingPos: Position = NoPosition
+ private var pendingName: Option[String] = None
+
+ writeHeader()
+
+ private def sourceToIndex(source: SourceFile) =
+ _srcToIndex.getOrElseUpdate(source,
+ (sources += sourceToURI(source)).size-1)
+
+ /** Relatively hacky way to get a Web-friendly URI to the source file */
+ private def sourceToURI(source: SourceFile): String = {
+ val uri = source
+ val relURI = relativizeBaseURI.fold(uri)(Utils.relativize(_, uri))
+
+ Utils.fixFileURI(relURI).toASCIIString
+ }
+
+ private def nameToIndex(name: String) =
+ _nameToIndex.getOrElseUpdate(name, (names += name).size-1)
+
+ private def writeHeader(): Unit = {
+ out.write("{\n")
+ out.write("\"version\": 3,\n")
+ out.write("\"file\": " + jsonString(generatedFile) + ",\n")
+ out.write("\"mappings\": \"")
+ }
+
+ def nextLine(): Unit = {
+ writePendingSegment()
+ out.write(';')
+ lineCountInGenerated += 1
+ lastColumnInGenerated = 0
+ firstSegmentOfLine = true
+ pendingColumnInGenerated = -1
+ pendingPos = nodePosStack.top._1
+ pendingName = nodePosStack.top._2
+ }
+
+ def startNode(column: Int, originalPos: Position,
+ originalName: Option[String] = None): Unit = {
+ nodePosStack.push((originalPos, originalName))
+ startSegment(column, originalPos, originalName)
+ }
+
+ def endNode(column: Int): Unit = {
+ nodePosStack.pop()
+ startSegment(column, nodePosStack.top._1, nodePosStack.top._2)
+ }
+
+ private def startSegment(startColumn: Int, originalPos: Position,
+ originalName: Option[String]): Unit = {
+ // There is no point in outputting a segment with the same information
+ if ((originalPos == pendingPos) && (originalName == pendingName))
+ return
+
+ // Write pending segment if it covers a non-empty range
+ if (startColumn != pendingColumnInGenerated)
+ writePendingSegment()
+
+ // New pending
+ pendingColumnInGenerated = startColumn
+ pendingPos = originalPos
+ pendingName =
+ if (startColumn != pendingColumnInGenerated) originalName
+ else pendingName orElse originalName
+ }
+
+ private def writePendingSegment() {
+ if (pendingColumnInGenerated < 0)
+ return
+
+ // Segments of a line are separated by ','
+ if (firstSegmentOfLine) firstSegmentOfLine = false
+ else out.write(',')
+
+ // Generated column field
+ writeBase64VLQ(pendingColumnInGenerated-lastColumnInGenerated)
+ lastColumnInGenerated = pendingColumnInGenerated
+
+ // If the position is NoPosition, stop here
+ if (!pendingPos.isDefined)
+ return
+
+ // Extract relevant properties of pendingPos
+ val source = pendingPos.source
+ val line = pendingPos.line
+ val column = pendingPos.column
+
+ // Source index field
+ if (source == lastSource) { // highly likely
+ writeBase64VLQ0()
+ } else {
+ val sourceIndex = sourceToIndex(source)
+ writeBase64VLQ(sourceIndex-lastSourceIndex)
+ lastSource = source
+ lastSourceIndex = sourceIndex
+ }
+
+ // Line field
+ writeBase64VLQ(line - lastLine)
+ lastLine = line
+
+ // Column field
+ writeBase64VLQ(column - lastColumn)
+ lastColumn = column
+
+ // Name field
+ if (pendingName.isDefined) {
+ val nameIndex = nameToIndex(pendingName.get)
+ writeBase64VLQ(nameIndex-lastNameIndex)
+ lastNameIndex = nameIndex
+ }
+ }
+
+ def complete(): Unit = {
+ writePendingSegment()
+
+ out.write("\",\n")
+ out.write(
+ sources.map(jsonString(_)).mkString("\"sources\": [", ", ", "],\n"))
+ out.write(
+ names.map(jsonString(_)).mkString("\"names\": [", ", ", "],\n"))
+ out.write("\"lineCount\": "+lineCountInGenerated+"\n")
+ out.write("}\n")
+ }
+
+ /** Write the Base 64 VLQ of an integer to the mappings
+ * Inspired by the implementation in Closure Compiler:
+ * http://code.google.com/p/closure-compiler/source/browse/src/com/google/debugging/sourcemap/Base64VLQ.java
+ */
+ private def writeBase64VLQ(value0: Int): Unit = {
+ // Sign is encoded in the least significant bit
+ var value =
+ if (value0 < 0) ((-value0) << 1) + 1
+ else value0 << 1
+
+ // Write as many base-64 digits as necessary to encode value
+ do {
+ var digit = value & VLQBaseMask
+ value = value >>> VLQBaseShift
+ if (value != 0)
+ digit |= VLQContinuationBit
+ out.write(Base64Map.charAt(digit))
+ } while (value != 0)
+ }
+
+ private def writeBase64VLQ0(): Unit =
+ out.write('A')
+}