diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
new file mode 100644
index 0000000000..e2f42e5c54
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -0,0 +1,30 @@
+---
+name: "\U0001F41B Bug report"
+about: Report a bug to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+
diff --git a/.github/ISSUE_TEMPLATE/enhancement-request.md b/.github/ISSUE_TEMPLATE/enhancement-request.md
new file mode 100644
index 0000000000..4d10dc77d9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/enhancement-request.md
@@ -0,0 +1,30 @@
+---
+name: "\U0001F389 Enhancement request"
+about: Ask for something to be improved in this repo
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
new file mode 100644
index 0000000000..79c92c0e67
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,11 @@
+---
+name: "❓ Question"
+about: Please use Stack Overflow (https://stackoverflow.com/questions/tagged/scala.js)
+ or Gitter (https://gitter.im/scala-js/scala-js) for questions
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+Please ask questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/scala.js) or [Gitter](https://gitter.im/scala-js/scala-js) instead of using this issue tracker.
diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml
new file mode 100644
index 0000000000..718146327c
--- /dev/null
+++ b/.github/workflows/cla.yml
@@ -0,0 +1,10 @@
+name: CLA
+on: [pull_request]
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Verify CLA
+ uses: scala/cla-checker@v1
+ with:
+ author: ${{ github.event.pull_request.user.login }}
diff --git a/.gitignore b/.gitignore
index 3f6576ef59..5c978cc5cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,25 @@
+# Fundamental to the build
target/
+/.bsp/
+/scalalib/fetchedSources/
+/partest/fetchedSources/
+/linker-interface/**/scalajs-logging-src/
+/linker-interface/**/scalajs-logging-src-jars/
+/node_modules/
+
+# IDE specific
.cache
.classpath
.project
.settings/
-/scalalib/fetchedSources/
+/.idea/
+/.idea_modules/
+bin/
+/.bloop/
+/.metals/
+/project/**/metals.sbt
+/.vscode/
+
+# User specific
+/.jvmopts
+/.sbtopts
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 30d7e65967..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,68 +0,0 @@
-language: scala
-script:
- - |
- if [ $KIND == main ]; then
- sbt "${SBT_SETUP}" ++$TRAVIS_SCALA_VERSION package packageDoc
- sbt "${SBT_SETUP}" ++$TRAVIS_SCALA_VERSION testing/test testing/fastOptStage::test testing/fullOptStage::test
- sbt "${SBT_SETUP}" ++$TRAVIS_SCALA_VERSION scalajs-test/test scalajs-test/fastOptStage::test scalajs-test/fullOptStage::test scalajs-test/clean
- sbt "${SBT_SETUP}" 'set scalacOptions in ScalaJSBuild.test += "-Xexperimental"' ++$TRAVIS_SCALA_VERSION scalajs-test/test scalajs-test/fastOptStage::test scalajs-test/fullOptStage::test
- sbt "${SBT_SETUP}" ++$TRAVIS_SCALA_VERSION scalajs-compiler/test reversi/packageJS reversi/fullOptJS
- fi
- - if [ $KIND == main ]; then sh checksizes.sh $TRAVIS_SCALA_VERSION; fi
- - if [ $KIND == main ]; then sh check-partest-coverage.sh $TRAVIS_SCALA_VERSION; fi
- - if [ $KIND == tools ]; then sbt "${SBT_SETUP}" ++$TRAVIS_SCALA_VERSION scalajs-tools/package; fi
- - if [ $KIND == sbtplugin ]; then sbt "${SBT_SETUP}" ++$TRAVIS_SCALA_VERSION scalajs-tools/package scalajs-sbt-plugin/package scalajs-sbt-plugin/test; fi
- - if [ $KIND == partestc ]; then sbt "${SBT_SETUP}" ++$TRAVIS_SCALA_VERSION scalajs-partest/compile; fi
-after_success:
- - if [[ "${PUBLISH_ENABLED}" == "true" && "${TRAVIS_PULL_REQUEST}" == "false" && "${KIND}" == "main" && "${PUBLISH_USER}" != "" && "${PUBLISH_PASS}" != "" ]]; then sbt "${SBT_SETUP}" ++$TRAVIS_SCALA_VERSION publish; fi
- - if [[ "${PUBLISH_COMPILER}" == "true" && "${TRAVIS_PULL_REQUEST}" == "false" && "${KIND}" == "main" && "${PUBLISH_USER}" != "" && "${PUBLISH_PASS}" != "" ]]; then sbt "${SBT_SETUP}" ++$TRAVIS_SCALA_VERSION scalajs-compiler/publish; fi
- - if [[ "${PUBLISH_ENABLED}" == "true" && "${TRAVIS_PULL_REQUEST}" == "false" && "${KIND}" == "sbtplugin" && "${PUBLISH_USER}" != "" && "${PUBLISH_PASS}" != "" ]]; then sbt "${SBT_SETUP}" ++$TRAVIS_SCALA_VERSION scalajs-tools/publish scalajs-sbt-plugin/publish; fi
- - if [[ "${PUBLISH_ENABLED}" == "true" && "${TRAVIS_PULL_REQUEST}" == "false" && "${KIND}" == "tools" && "${PUBLISH_USER}" != "" && "${PUBLISH_PASS}" != "" ]]; then sbt "${SBT_SETUP}" ++$TRAVIS_SCALA_VERSION scalajs-tools/publish; fi
-env:
- global:
- - secure: "gIReAZ60hLtbYTlnNUM508LhhznImVPRO5fHQ/9SY7mqA/ql3EVJ65M43GxzvKjNkZ7thKj1ygYklCQaqXwdszR8xRRM7MNQIUImZhsjXJ0xqorpQf2fmo2sD54Gx/caAI4kx0x/ULm9ZA5QK6hprl8aVqb1o5bYAxfZdx719pE="
- - secure: "CiwH9VZ0Wl/fGsL6XUbXv92TWr+EnX94ICX2cbwwkvPDxmTrdWf8BZOq7JSrfITYvvW3l5U8YWLjCMpa/Cr2C2BD1QPvNXDoeL9cZXA0I2yquQNEfh12Zg5c5bHRtO+CeRHjq06jjEqojqTzp5MK10YXnfzzQFiZ1lt7c/rwmkU="
-matrix:
- include:
- - jdk: openjdk6
- scala: 2.10.2
- env:
- - KIND=main
- - PUBLISH_COMPILER=true
- - jdk: openjdk6
- scala: 2.10.3
- env:
- - KIND=main
- - PUBLISH_COMPILER=true
- - jdk: openjdk6
- scala: 2.10.4
- env:
- - KIND=main
- - PUBLISH_ENABLED=true
- - jdk: openjdk6
- scala: 2.10.4
- env:
- - KIND=sbtplugin
- - PUBLISH_ENABLED=true
- - jdk: openjdk6
- scala: 2.11.0
- env:
- - KIND=main
- - PUBLISH_ENABLED=true
- - jdk: openjdk6
- scala: 2.11.0
- env:
- - KIND=tools
- - PUBLISH_ENABLED=true
- - jdk: openjdk6
- scala: 2.11.0
- env:
- - KIND=partestc
- - jdk: oraclejdk7
- scala: 2.10.2
- env:
- - KIND=main
- - jdk: oraclejdk7
- scala: 2.11.0
- env:
- - KIND=main
diff --git a/CODINGSTYLE.md b/CODINGSTYLE.md
new file mode 100644
index 0000000000..eb79ec4986
--- /dev/null
+++ b/CODINGSTYLE.md
@@ -0,0 +1,628 @@
+# Coding Style Guide
+
+The Scala.js project has a strict policy regarding coding style.
+This is one of the cornerstones that has allowed Scala.js to maintain a clean, consistent and maintainable codebase.
+
+This document tries to document the style in use as much as possible to make it easier for everyone to contribute.
+
+A *few* of these rules are checked automatically using Scalastyle, but most of them are too complex to teach to an automated tool.
+
+The Scala.js core team has decided to designate a single developer to drive and "maintain" the project's coding style.
+This allows us to maintain a consistent, yet flexible style.
+Everyone in the core team follows the style maintainer's directions.
+Currently [@sjrd](https://github.com/sjrd) maintains the Scala.js coding style.
+Please follow his directions if they are in conflict with the present document.
+Feel free to point out the conflict, though: we are always looking to improve this document.
+
+
+## General style (whitespaces, braces, etc.)
+
+### Tabs, new lines, and eofs
+
+* Files must not contain tabs
+* New lines are UNIX style (\n)
+* A final new line must be present at the end of file
+* There must not be whitespace characters at the end of a line
+
+### Line length
+
+Lines should be limited at 80 characters.
+In some cases, if breaking a line makes it significantly less readable, it can go up to 120 characters.
+
+Rationale: when reviewing on GitHub, only 120 characters are visible; when reviewing on a mobile phone, only 80 characters are visible.
+And we do review on mobile phone quite a lot.
+
+#### Where to break a line
+
+A line can be broken after either a `,` or a `(`, or possibly after a binary operator in a long expression.
+
+### Indentation
+
+In general, indentation is 2 spaces, except continuation lines, which are indented 4 spaces.
+A continuation line is a line appearing because we broke something that should have been on one line for line length reasons.
+Typically, this means inside parentheses (formal or actual parameters of methods and constructors), and a long `extends` clause.
+
+Note that breaking a line right after the `=` sign of an initialization or assignment is *not* considered a continuation line, because it's not really breaking the line: instead, we just choose to put the rhs on its dedicated line, indented 2 spaces (similarly to the branches of an `if`).
+For example:
+
+```scala
+val x =
+ aLongFunctionCall()
+```
+
+Further, parenthesized lists that have a single element per line are not considered continuation lines.
+For example, the following two are allowed:
+
+```scala
+// "Binpacked style"
+f(arg1, arg2,
+ arg3, arg4)
+
+// "List style"
+f(
+ arg1,
+ arg2,
+ arg3,
+ arg4,
+)
+```
+
+Notes about the list style:
+* The parentheses must be on individual lines.
+* The trailing comma is mandatory.
+* This style is relatively new, so a lot of code does not comply to it; apply the boy scout rule where this does not cause unnecessary diffs.
+
+### Blank lines
+
+* Never put two blank lines in a row
+* (Almost) always put a blank line between two declarations in a class
+* Insert blank lines at will between logical blocks of statements in a method
+* Always put blank lines around a `case` whose body contains a blank line
+* In general, if some kind of block of code *contains* a blank line inside it, it should also be *surrounded* by blank lines (this prevents the brain from visually parsing blocks in the wrong way)
+
+The blank line between two consecutive declarations in a class can sometimes be omitted, if the declarations are single-line (which also means Scaladocless) and strongly related.
+This happens pretty rarely (mostly a series of private fields).
+The rule of thumb is to always put a blank line.
+
+### Braces
+
+Braces should not be used for one-liner methods.
+Similarly, you should not use braces for the body of a `while`/`if`/`for` that is a one-liner.
+
+```scala
+def plus(x: Int, y: Int): Int =
+ x + y
+
+def clamp(v: Int, min: Int, max: Int): Int = {
+ if (v < min) min
+ else if (v > max) max
+ else v
+}
+```
+
+Every expression that spans more than one line should be enclosed in braces.
+For example, this is wrong:
+
+```scala
+val iterator =
+ new Iterator[A] {
+ ...
+ }
+```
+
+It should be:
+
+```scala
+val iterator = {
+ new Iterator[A] {
+ ...
+ }
+}
+```
+
+#### Two-liner if/else
+
+As an exception to the above rule, when a two-liner `if/else` (see section about `if/else` in general) is used as the right-hand-side of a definition or assignment, i.e., after an `=` sign, the braces around it can and should be omitted.
+For example:
+
+```scala
+def abs(x: Int): Int =
+ if (x >= 0) x
+ else -x
+```
+
+Note that the following formatting is not considered a two-liner if/else, and is therefore not valid:
+
+```scala
+def abs(x: Int): Int =
+ if (x >= 0)
+ x
+ else
+ -x
+```
+
+#### Long expressions with binary operators
+
+Very long expressions consisting of binary operators at their "top-level" can be broken *without indentation* if they are alone in their brace-delimited block or their actual parameter.
+This happens mostly for long chains of `&&`s, `||`s, or string concatenations.
+Here is an example:
+
+```scala
+val isValidIdent = {
+ ident != "" &&
+ ident.charAt(0).isUnicodeIdentifierStart &&
+ ident.tail.forall(_.isUnicodeIdentifierPart)
+}
+
+if (!isValidIdent) {
+ reportError(
+ "This string is very long and will " +
+ "span several lines.")
+}
+```
+
+#### Braces in lambdas
+
+In lambdas (anonymous functions), the opening brace must be placed before the formal arguments, and not after the `=>`:
+
+```scala
+val f = { (x: Int) =>
+ body
+}
+```
+
+If the first line ends up being two long, the parameter list should go the next line, and the body indented with two more spaces:
+
+```scala
+val someLongIdentifierWithHighIdentation = {
+ (x: Int, ys: List[Traversable[String]]) =>
+ body
+}
+```
+
+If a lambda is a one-liner, we do not use braces at all:
+
+```scala
+val f = (x: Int) => body
+
+val ys = xs.map(x => x + 1)
+```
+
+### Spaces
+
+There must not be any space before the following tokens: `:` `,` `;` `)`
+
+There must be exactly one space after the following tokens: `:` `,` `;` `if` `for` `while`
+
+There must be exactly one space before the tokens `=` and `=>`, and either exactly one space or a new line after them.
+Exception: `=>` may be vertically aligned instead in some scenarios: see [the "Pattern matching" section](#pattern-matching).
+
+There must be exactly one space before and after `{` and `}`.
+With the exception of partial import, where there is no space on either side.
+
+Binary operators must have a single space on both sides.
+Unary operators must not be followed by a space.
+
+### Method call style
+
+Usually, parentheses should be used for actual parameters to a method call.
+Braces should be used instead if an argument list has only a lambda, and that lambda does not fit in an inline one-liner.
+
+In general, dot-notation should be used for non-symbolic methods, and infix notation should be used for symbolic methods.
+
+Examples:
+
+```scala
+// inline lambda, hence ()
+list.map(x => x * 2)
+
+// long lambda, hence braces
+list.map { x =>
+ if (x < 5) x
+ else x * 2
+}
+
+// symbolic operator, hence infix notation
+value :: list
+```
+
+When calling a method declared with an empty pair of parentheses, always use `()`.
+Not doing so causes (fatal) warnings when calling Scala-declared methods in Scala 2.13.x.
+For consistency, we also apply this rule to all Java-defined methods, including `toString()`.
+
+### Method definition
+
+All public and protected methods must have an explicit result type.
+Private methods are encouraged to have an explicit result type as well, as it helps reading the code.
+Local methods do not need an explicit result type.
+
+Procedure syntax must not be used.
+`: Unit =` must be used instead.
+
+Side-effect-free methods without formal parameters should be declared without `()`, unless either a) it overrides a method defined with `()` (such as `toString()`) or b) it implements a Java method in the Java libraries.
+
+The signature of a method is technically a single line, and hence, if it has to be broken due to line length reasons, subsequent lines should be indented 4 spaces.
+As a reminder, the line can be broken right after a `,` or a `(` (and not, for example, after `implicit` or `:`).
+You should avoid breaking the line between the last parameter and the result type; going over the 80 characters limit is preferred in that case.
+
+### `for` comprehensions
+
+`for` comprehensions may only use `()` if they have a single generator without `if`, such as:
+
+```scala
+for (i <- 0 until n)
+ doStuff(i)
+```
+
+Otherwise, it must use `{}`, and there must be one generator per line.
+Guards (`if`s) may be either on the same line as a generator, or on a dedicated line.
+The body of the `for` is then automatically surrounding by braces too, even if it is a one-liner:
+
+```scala
+for {
+ i <- 0 until n
+ j <- 0 until i
+} {
+ doStuff(i, j)
+}
+```
+
+The `yield` keyword should be placed at the end of the `for` line, followed by braces:
+
+```scala
+for (x <- xs) yield {
+ x * 2
+}
+```
+
+For short `yield` blocks, the following can be used instead:
+
+```scala
+for (x <- xs)
+ yield x * 2
+```
+
+With the multi-line brace for, the `yield` must be placed like this:
+
+```scala
+for {
+ i <- 0 until n
+ j <- 0 until i
+} yield {
+ thing(i, j)
+}
+```
+
+### Imports
+
+Imports must follow the following format:
+
+```scala
+import scala.language.implicitConversions
+
+import scala.collection.mutable
+
+import java.{util => ju}
+
+import org.scalajs.linker._
+import org.scalajs.linker.standard._
+```
+
+Language imports must always come first, and must always be at the top of the file (right after the `package` declaration).
+There must not be language imports in narrower scopes.
+
+If you import more than 3 or so items from a namespace, use a wildcard import.
+
+Avoid importing mutable collections directly; prefer importing `mutable` and then use `mutable.ListBuffer`.
+
+### Scaladoc
+
+Scaladoc comments that fit in one line must be written as
+
+```scala
+/** Returns the maximum of a and b. */
+def max(a: Int, b: Int): Int = ???
+```
+
+Multi-line Scaladoc comments must use the following style:
+
+```scala
+/** Returns the maximum of a and b.
+ *
+ * If a > b, returns a. Otherwise returns b.
+ */
+def max(a: Int, b: Int): Int = ???
+```
+
+### Non-Scaladoc comments
+
+Normal comments fitting on one-line should use `//`.
+A comment that does not fit on one line should use the multi-line comment syntax and follow this style:
+
+```scala
+/* This complicated algorithm computes the maximum of two integer values a
+ * and b. If a > b, it computes a, otherwise it computes b.
+ */
+```
+
+
+## Class declaration
+
+A class declaration, together with its constructor parameters, its `extends` clause, and its self type, is technically a single line.
+Example:
+
+```scala
+class Foo(val x: Int) extends Bar with Foobar { self =>
+```
+
+However, this tends to become too long in many cases.
+
+If the declaration does not fit on one line, the first thing to do is to put the self type on a dedicated line, indented 2 spaces only, and followed by a blank line:
+
+```scala
+class Foo(val x: Int) extends Bar with Foobar {
+ self =>
+
+ // declarations start here
+```
+
+The second thing to do is to break the line just before the `extends` keyword, indented 4 spaces:
+
+```scala
+class Foo(val x: Int)
+ extends Bar with Foobar {
+
+ // declarations start here
+```
+
+The `extends` clause can be further broken up before `with`s, if necessary.
+Additional lines are also indented 4 spaces wrt. the `class` keyword.
+
+```scala
+class Foo(val x: Int)
+ extends Bar with Foobar with AnotherTrait with YetAnotherTrait
+ with HowManyTraitsAreThere with TooManyTraits {
+
+ // declarations start here
+```
+
+If too long in itself, the list of constructor parameters should be broken similarly to formal parameters to a method, i.e., indented 4 spaces, and followed by a blank line:
+
+```scala
+class Foo(val x: Int, val y: Int,
+ val z: Int)
+ extends Bar with Foobar {
+
+ // declarations start here
+```
+
+If the constructor parameters are a (long) list of "configuration" parameters, the list style (as opposed to binpacking) should be used:
+
+```scala
+class Foo(
+ val width: Int = 1,
+ val height: Int = 1,
+ val depthOfField: Int = 3
+) extends Bar with Foobar {
+```
+
+Note that there is no vertical alignment, neither for the type nor the default value (if any).
+If there are several parameter lists (e.g., with an implicit parameter list), each parameter list follows its rules independently of the others, i.e., organizing one parameter list vertically does not mean another list should be organized vertically as well.
+For example:
+
+```scala
+class Foo[A](
+ val width: Int = 1,
+ val height: Int = 1,
+ val depthOfField: Int = 3
+)(implicit ct: ClassTag[A])
+ extends Bar with Foobar with AnotherTrait with YetAnotherTrait
+ with HowManyTraitsAreThere with TooManyTraits {
+```
+
+
+## Usages of higher-order methods
+
+### Option and js.UndefOr
+
+Use the higher-order methods in the APIs of `Option` and `js.UndefOr` rather than doing pattern matching.
+Note particularly the `fold` method, which should be used instead of the `map`+`getOrElse` combination.
+
+### Collections
+
+Higher-order methods should be favored over loops and tail-recursive methods wherever possible and readable.
+
+Do not reinvent the wheel: use the most appropriate method in the collection API (e.g., use `forall` instead of a custom-made `foldLeft`).
+
+Methods other than `foreach` should however be avoided if the lambda that is passed to them has side-effects.
+In other words, a `foldLeft` with a side-effecting function should be avoided, and a `while` loop or a `foreach` used instead.
+
+Use `xs.map(x => x * 2)` instead of `for (x <- xs) yield x * 2` for short, one-liner `map`s, `flatMap`s and `foreach`es.
+Otherwise, favor for comprehensions.
+
+### For comprehensions and ranges
+
+Do not fear for over ranges.
+The Scala.js optimizer inlines them away.
+
+
+## if/else
+
+An `if/else` pair in expression position can be written as
+
+```scala
+val x =
+ if (condition) someExpr
+ else anotherExpr
+```
+
+assuming both lines fit in the line length limit.
+We call this formatting a two-liner `if/else`.
+
+Otherwise, both expressions should be put on separate lines, and indented 2 spaces.
+In addition, the rhs of the `=` must then be surrounded in braces:
+
+```scala
+val x = {
+ if (condition)
+ someExpr
+ else
+ anotherExpr
+}
+```
+
+If one of the brances requires braces, then put braces on both branches (or *all* branches if it is a chain of `if/else`s):
+
+```scala
+val x = {
+ if (condition) {
+ val x = someExpr
+ x + 5
+ } else if (secondCondition) {
+ anotherExpr
+ } else {
+ aThirdExpr
+ }
+}
+```
+
+`if`s and `if/else`s in statement position must always have their branch(es) on dedicated lines.
+The following example is incorrect:
+
+```scala
+if (index >= size) throw new IndexOutOfBoundsException
+
+if (x > y) i += 1
+else i -= 1
+```
+
+and should instead be formatted as:
+
+```scala
+if (index >= size)
+ throw new IndexOutOfBoundsException
+
+if (x > y)
+ i += 1
+else
+ i -= 1
+```
+
+If the `condition` of an `if` (or `while`, for that matter) is too long, it can be broken *at most once* with 4 spaces of indentation.
+In that case, the if and else parts must be surrounded by braces, even if they are single-line.
+Obviously, the two-liner `if/else` formatting cannot be applied.
+
+If the condition is so long that two lines are not enough, then it should be extracted in a local `val` or `def` before it, such as:
+
+```scala
+val ident: String = ???
+
+def isValidIdent = {
+ ident != "" &&
+ ident.charAt(0).isUnicodeIdentifierStart &&
+ ident.tail.forall(_.isUnicodeIdentifierPart)
+}
+if (isValidIdent)
+ doSomething()
+else
+ doSomethingElse()
+```
+
+
+## Pattern matching
+
+One-liner cases should be written on one line, and the arrows aligned:
+
+```scala
+x match {
+ case Foo(a, b) => a + b
+ case Bar(y) => 2 * y
+}
+```
+
+If the body of a case does not fit on the same line, then put the body on the next line, indented 2 spaces, without braces around it.
+In that case, also put blank lines around that `case`, and do not align its arrow with the other cases:
+
+```scala
+x match {
+ case Foo(a, b) =>
+ val x = a + b
+ x * 2
+ case Bar(y) =>
+ if (y < 5) y
+ else y * 2
+}
+```
+
+A single pattern match can have *both* one-liners with aligned arrows and multi-line cases.
+In that case, there must be a blank line between every change of style:
+
+```scala
+x match {
+ case Foo(a, b) => a + b
+ case Bar(y) => 2 * y
+
+ case Foobar(y, z) =>
+ if (y < 5) z
+ else z * 2
+}
+```
+
+The arrows of multi-line cases must never be aligned with other arrows, either from neighboring multi-line cases or from blocks of one-liner cases.
+
+When pattern-matching based on specific subtypes of a value, reuse the same identifier for the refined binding, e.g.,
+
+```scala
+that match {
+ case that: Foo => that.bar == 5
+ case _ => false
+}
+```
+
+When using type-based pattern matching combined with alternatives (using `|`), remove the space after the `:` operator:
+
+```scala
+that match {
+ case _:Foo | _:Bar | _:Foobar => true
+ case _ => false
+}
+```
+
+This helps visually parsing the relative priority of `:` over `|`.
+
+As a reminder, avoid pattern-matching on `Option` types.
+Use `fold` instead.
+
+
+## Explicit types
+
+As already mentioned, public and protected `def`s must always have explicit types.
+Private `def`s are encouraged to have an explicit type as well.
+
+Public and protected `val`s and `var`s of public classes and traits should also have explicit types, as they are part of the binary API, and therefore must not be subject to the whims of type inference.
+
+Private `val`s and `var`s as well as local `val`s, `var`s and `def`s typically need not have an explicit type.
+They can have one if it helps readability or type inference.
+
+Sometimes, `var`s need an explicit type because their initial value has a more specific type than required (e.g., `None.type` even though we assign it later to a `List`).
+
+
+## Implementing the Java lib
+
+Special rules apply to implementing classes of the JDK (typically in `javalanglib/` or `javalib/`).
+
+### Order of declarations
+
+Fields and methods should be declared in the order in which they appear in the JavaDoc.
+Note that in the JavaDoc, you first see a *summary* of the members, which is always ordered alphabetically.
+This is *not* the order you should follow.
+Instead, you should follow the order in which the *full descriptions* of the members appear.
+
+### Parameterless methods
+
+Methods without parameters should *always* be declared with `()`, regardless of whether they have side-effects or not.
+
+### No public members in addition to those in the JavaDoc
+
+There should not be any public members besides those documented in the JavaDoc.
+Usually, there should also be no protected members not in the JavaDoc.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000..6d1f0aebea
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,95 @@
+# Contributing guidelines
+
+## Very important notice about the Javalib
+
+If you haven't read it, ***read the very important notice about the Javalib
+in the [Javalib documentation](./JAVALIB.md)*** .
+
+## Coding style
+
+The Scala.js project has a strict policy regarding coding style.
+This is one of the cornerstones that has allowed Scala.js to maintain a clean, consistent and maintainable codebase.
+
+Please consult and follow our [coding style guide](./CODINGSTYLE.md).
+
+## General workflow
+
+This the general workflow for contributing to Scala.js.
+
+1. Make sure you have signed the
+ [Scala CLA](https://www.lightbend.com/contribute/cla/scala).
+ If not, sign it.
+2. You should always perform your work in its own Git branch.
+ The branch should be given a descriptive name that explains its intent.
+3. When the feature or fix is completed you should open a
+ [Pull Request](https://help.github.com/articles/about-pull-requests/) on GitHub.
+4. The Pull Request should be reviewed by other maintainers (as many as feasible/practical),
+ among which at least one core developer.
+ Independent contributors can also participate in the review process,
+ and are encouraged to do so.
+5. After the review, you should resolve issues brought up by the reviewers as needed
+ (amending or adding commits to address reviewers' comments), iterating until
+ the reviewers give their thumbs up by approving the Pull Request.
+6. Once the code has passed review the Pull Request can be merged into the distribution.
+
+## Pull Request Requirements
+
+In order for a Pull Request to be considered, it has to meet these requirements:
+
+1. Live up to the current code standard:
+ - Follow the [coding style](./CODINGSTYLE.md)
+ - Not violate [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).
+ - The [Boy Scout Rule](https://medium.com/@biratkirat/step-8-the-boy-scout-rule-robert-c-martin-uncle-bob-9ac839778385) should be applied.
+2. Be accompanied by appropriate tests.
+3. Be issued from a branch *other than main or master* (PRs coming from `main` or `master` will not be accepted, as we've had trouble in the past with such PRs)
+
+If not *all* of these requirements are met then the code should **not** be
+merged into the distribution, and need not even be reviewed.
+
+## Documentation
+
+All code contributed to the user-facing standard library (the `library/`
+directory) should come accompanied with documentation.
+Pull requests containing undocumented code will not be accepted.
+
+Code contributed to the internals (compiler, IR, linker, JS environments, etc.)
+should come accompanied by internal documentation if the code is not
+self-explanatory, e.g., important design decisions that other maintainers
+should know about.
+
+## Creating Commits And Writing Commit Messages
+
+Follow these guidelines when creating public commits and writing commit messages.
+
+### Prepare meaningful commits
+
+If your work spans multiple local commits (for example; if you do safe point
+commits while working in a feature branch or work in a branch for long time
+doing merges/rebases etc.) then please do not commit it all but rewrite the
+history by squashing the commits into **one commit per useful unit of
+change**, each accompanied by a detailed commit message.
+For more info, see the article:
+[Git Workflow](https://sandofsky.com/blog/git-workflow.html).
+Additionally, every commit should be able to be used in isolation--that is,
+each commit must build and pass all tests.
+
+### First line of the commit message
+
+The first line should be a descriptive sentence about what the commit is
+doing, written using the imperative style, e.g., "Change this.", and should
+not exceed 70 characters.
+It should be possible to fully understand what the commit does by just
+reading this single line.
+It is **not ok** to only list the ticket number, type "minor fix" or similar.
+If the commit has a corresponding ticket, include a reference to the ticket
+number, with the format "Fix #xxx: Change that.", as the first line.
+Sometimes, there is no better message than "Fix #xxx: Fix that issue.",
+which is redundant.
+In that case, and assuming that it aptly and concisely summarizes the commit
+in a single line, the commit message should be "Fix #xxx: Title of the ticket.".
+
+### Body of the commit message
+
+If the commit is a small fix, the first line can be enough.
+Otherwise, following the single line description should be a blank line
+followed by details of the commit, in the form of free text, or bulleted list.
diff --git a/DEVELOPING.md b/DEVELOPING.md
new file mode 100644
index 0000000000..29dc5d18fa
--- /dev/null
+++ b/DEVELOPING.md
@@ -0,0 +1,177 @@
+# Developer documentation
+
+## Very important notice about the Javalib
+
+If you haven't read it, ***read the very important notice about the Javalib
+in the [Javalib documentation](./JAVALIB.md)*** .
+
+## Building
+
+Scala.js is entirely built with [sbt](https://www.scala-sbt.org/), and also
+requires [Node.js](https://nodejs.org/en/) to be installed. For complete
+support, Node.js >= 13.2.0 is required.
+
+The first time, or in the rare events where `package.json` changes
+([history](https://github.com/scala-js/scala-js/commits/main/package.json)),
+you need to run
+
+ $ npm install
+
+from your shell. If you *really* do not want to do this, you can avoid that
+step, but you will need to use
+`set MyScalaJSPlugin.wantSourceMaps in testSuite := false` within sbt to
+by-pass source-map-related tests. In addition, bootstrap tests will not pass.
+
+Otherwise, everything happens within sbt.
+
+Run the normal test suite using the entire Scala.js toolchain using
+
+ > testSuite2_12/test
+
+In order to test the tests themselves, run the cross-compiling tests on the JVM
+with:
+
+ > testSuiteJVM2_12/test
+
+If you have changed the IR or the compiler, you typically need to
+
+ > clean
+
+before testing anew.
+
+If you have changed the logging API, the linker interface, the JS environments,
+the test adapter or the sbt plugin, you typically need to
+
+ > reload
+
+To test in fullOpt stage:
+
+ > set scalaJSStage in Global := FullOptStage
+
+There are also a few additional tests in a separate testing project:
+
+ > testSuiteEx2_12/test
+
+The compiler tests (mostly verifying expected compile error messages) can be
+run with
+
+ > compiler2_12/test
+
+The full partest suite (tests of the Scala language, ported in Scala.js) are
+run with:
+
+ > partestSuite2_12/test
+
+or, more typically,
+
+ > partestSuite2_12/testOnly -- --fastOpt
+
+The JUnit tests from scala/scala can be run with
+
+ > scalaTestSuite2_12/test
+
+## Metals-based IDEs
+
+We recommend [Metals](https://scalameta.org/metals/)-based IDEs such as VS Code
+to develop Scala.js itself. It can import the Scala.js build out-of-the-box.
+
+After importing the build in Metals, you will need to run `clean` in sbt before
+normal sbt commands can correctly work. Metals will continue to provide all its
+features after cleaning.
+
+## Eclipse
+
+If you want to develop in Eclipse, use
+[sbteclipse](https://github.com/typesafehub/sbteclipse). Projects as created by
+the build by default are not suited for Eclipse. You can create *somewhat*
+appropriate projects with:
+
+ $ GENERATING_ECLIPSE=true sbt "eclipse with-source=true"
+
+You will still have to fix a few things:
+
+* Uncheck the "Allow output directories per source directory" in Build path
+* Add transitive project dependencies in Build path
+
+## Preparing a Pull Request
+
+One common build failure is code styling. Reproduce results locally with:
+
+ $ sbt scalastyleCheck
+
+## Organization of the repository
+
+The repository is organized as follows:
+
+### Compilation pipeline
+
+* `ir/` The Intermediate Representation, produced by the compiler and consumed by the linker
+* `compiler/` The scalac compiler plugin
+* `linker-private-library/` Some Scala.js files whose compiled .sjsir files are used as resources of the linker (2.12 only)
+* `linker-interface/` The linker interface, without its implementation
+* `linker/` The linker, optimizer, verifier, etc.: everything that happens at link time
+
+### Library
+
+* `library/` The Scala.js standard library (everything inside `scala.scalajs.*`)
+* `javalanglib/` Implementation in Scala.js of the classes in `java.lang.*`
+* `javalib/` Implementation in Scala.js of other classes in `java.*`
+* `scalalib/` Almost void project for recompiling the Scala library for Scala.js
+* `library-aux/` A few files of the Scala library that need to be compiled separately
+
+All of these are packaged in `scalajs-library.jar`.
+
+### Testing infrastructure
+
+There is a generic infrastructure that maps the sbt-testing-interface API
+across the JVM/JS boundary, so that Scala.js testing frameworks can be piloted
+from JVM processes such as sbt.
+
+* `test-interface/` the JS definition of the sbt-testing-interface API
+* `test-bridge/` JS side of the bridge
+* `test-adapter/` JVM side of the bridge
+* `test-common/` Code common between `test-bridge` and `test-adapter`
+
+This repository also contains a specific implementation of JUnit:
+
+* `junit-runtime/` The run-time library for JUnit
+* `junit-plugin/` The JUnit compiler plugin
+
+### sbt plugin
+
+* `sbt-plugin/` The sbt plugin itself (2.12 only)
+
+### Testing projects
+
+* `test-suite/` The main test suite of Scala.js
+* `test-suite-ex/` Additional tests
+* `partest-suite/` The partest suite of Scala
+* `scala-test-suite/` The JUnit suite of Scala
+
+### Example projects/sandboxes
+
+* `examples/helloworld/` A simple Hello World, typically used as sandbox for quick testing
+* `examples/reversi/` The historical Reversi demo - we use it to track the impact of changes on the emitted code size
+* `examples/testing/` A simple project with tests using the DOM, mostly used to test `testHtml` with DOM interaction
+
+The helloworld and reversi also have HTML pages to run them in real browsers.
+
+### The build itself
+
+The build itself contains the entire sbt plugin (and all its dependencies) as
+part of its sources.
+If you change any of the linker interface, linker,
+test adapter, or the sbt plugin itself, chances are you need to `reload` the
+build for your changes to take effect.
+
+## Publish locally
+
+To publish your changes locally to be used in a separate project, use the
+following incantations.
+`SCALA_VERSION` refers to the Scala version used by the separate project.
+
+ > ;ir2_12/publishLocal;linkerInterface2_12/publishLocal;linker2_12/publishLocal;testAdapter2_12/publishLocal;sbtPlugin/publishLocal;javalib/publishLocal;javalibintf/publishLocal
+ > ;library2_12/publishLocal;testInterface2_12/publishLocal;testBridge2_12/publishLocal;jUnitRuntime2_12/publishLocal;jUnitPlugin2_12/publishLocal;scalalib2_12/publishLocal
+ > ++SCALA_VERSION compiler2_12/publishLocal
+
+If using a non-2.12.x version for the Scala version, the `2_12` suffixes must be adapted in the second and third command (not in the first command).
diff --git a/JAVALIB.md b/JAVALIB.md
new file mode 100644
index 0000000000..44ff308367
--- /dev/null
+++ b/JAVALIB.md
@@ -0,0 +1,82 @@
+# Javalib documentation
+
+## Very important notice about the Javalib
+
+Scala.js contains a reimplementation of part of the JDK in Scala.js itself.
+
+***To contribute to this code, it is strictly forbidden to even look at the
+source code of the Oracle JDK or OpenJDK!***
+
+This is for license considerations: these JDKs are under a GPL-based license,
+which is not compatible with our Apache 2.0 license.
+
+It is also recommended *not to look at any other JDK implementation* (such as
+Apache Harmony), to minimize the chance of copyright debate.
+
+## What goes into the core Scala.js javalib
+
+Parts of the JDK are in Scala.js itself, parts are in separate projects
+(see below for examples). This section aims to provide some guidance
+on when an implementation should be included in the core repo as
+opposed to a separate repo. The guidance is (unfortunately) imprecise
+and the decision of the core maintainers applies in case of a
+disagreement.
+
+To determine whether a JDK API should be part of Scala.js itself,
+traverse the following criteria in order until a decision is reached.
+
+### Does Scala.js core itself depend on the API?
+
+If yes, include it in core.
+
+Examples:
+- `java.nio.charset._`
+- `java.io.DataOutputStream`
+
+### Does the API need to be implemented in core Scala.js?
+
+If yes, include it in core.
+
+Examples:
+- `java.nio.*Buffer` (for typedarray integration)
+- `java.lang.Object`
+
+### Can the API be implemented independent of the JS runtime?
+
+Does the implementation only rely on standardized ES 2015 or does it
+require some browser/Node.js/etc.-specific support?
+
+If no (i.e. it requires specific support), put it in a separate repo.
+
+Examples:
+- Removal of `javalib-ex` that depended on `jszip`.
+
+### Does the core team have the expertise to maintain the API?
+
+If no, put it in a separate repo.
+
+Examples:
+- `java.util.Locale` / `java.text._` (https://github.com/cquiroz/scala-java-locales)
+- `java.time._` (https://github.com/cquiroz/scala-java-time,
+ https://github.com/zoepepper/scalajs-jsjoda,
+ https://github.com/scala-js/scala-js-java-time)
+
+### Is the core team willing to take the maintenance burden?
+
+If no, put it in a separate repo.
+
+Examples:
+- `java.logging._` (https://github.com/scala-js/scala-js-java-logging)
+
+### Versioning / Release Frequency / Quality
+
+Is the versioning (i.e. pre-relese v.s. stable) and release frequency
+of the core artifacts appropriate for the API?
+
+Are the quality expectations of the core repo appropriate for the
+intended implementation?
+
+Is faster iteration than can be provided by the core repo needed?
+
+If yes, yes, no, put it in the core repo, otherwise, put it in a
+separate repo.
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000000..c1a4c70069
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,678 @@
+// If not a PR, this is a long-lived branch, which should have a nightly build
+def triggers = []
+if (!env.CHANGE_ID) {
+ // This is the 1.x series: run nightly from Sunday to Friday
+ triggers << cron('H H(0-2) * * 0-5')
+}
+
+// Setup properties of this job definition
+properties([
+ parameters([
+ string(name: 'matrix', defaultValue: 'auto', description: 'The matrix to build (auto, quick, full)')
+ ]),
+ pipelineTriggers(triggers)
+])
+
+// Check whether the job was started by a timer
+// See https://hopstorawpointers.blogspot.ch/2016/10/performing-nightly-build-steps-with.html
+@NonCPS
+def isJobStartedByTimer() {
+ def startedByTimer = false
+ def buildCauses = currentBuild.rawBuild.getCauses()
+ for (buildCause in buildCauses) {
+ if (buildCause != null) {
+ def causeDescription = buildCause.getShortDescription()
+ echo "shortDescription: ${causeDescription}"
+ if (causeDescription.contains("Started by timer")) {
+ startedByTimer = true
+ }
+ }
+ }
+ return startedByTimer
+}
+def startedByTimer = isJobStartedByTimer()
+
+// Auto-select a matrix if it was not explicitly specified
+def selectedMatrix = params.matrix
+if (selectedMatrix == 'auto') {
+ def reason = ''
+ if (env.CHANGE_ID) {
+ reason = "is a PR ${env.CHANGE_ID}"
+ selectedMatrix = 'quick'
+ } else {
+ reason = "is not a PR, startedByTimer = $startedByTimer"
+ if (startedByTimer) {
+ selectedMatrix = 'full'
+ } else {
+ selectedMatrix = 'quick'
+ }
+ }
+ echo("Auto-selected matrix: $selectedMatrix ($reason)")
+} else {
+ echo("Explicit matrix: $selectedMatrix")
+}
+
+def CIScriptPrelude = '''
+LOCAL_HOME="/localhome/jenkins"
+LOC_SBT_BASE="$LOCAL_HOME/scala-js-sbt-homes"
+LOC_SBT_BOOT="$LOC_SBT_BASE/sbt-boot"
+LOC_IVY_HOME="$LOC_SBT_BASE/sbt-home"
+LOC_CS_CACHE="$LOC_SBT_BASE/coursier/cache"
+TEST_LOCAL_IVY_HOME="$(pwd)/.ivy2-test-local"
+
+rm -rf $TEST_LOCAL_IVY_HOME
+mkdir $TEST_LOCAL_IVY_HOME
+ln -s "$LOC_IVY_HOME/cache" "$TEST_LOCAL_IVY_HOME/cache"
+
+export SBT_OPTS="-J-Xmx5G -Dsbt.boot.directory=$LOC_SBT_BOOT -Dsbt.ivy.home=$TEST_LOCAL_IVY_HOME -Divy.home=$TEST_LOCAL_IVY_HOME -Dsbt.global.base=$LOC_SBT_BASE"
+export COURSIER_CACHE="$LOC_CS_CACHE"
+
+export NODE_PATH="$HOME/node_modules/"
+
+# Define setJavaVersion
+
+setJavaVersion() {
+ export JAVA_HOME=$HOME/apps/java-$1
+ export PATH=$JAVA_HOME/bin:$PATH
+}
+
+# Define sbtretry and sbtnoretry
+
+sbtretry() {
+ local TIMEOUT=45m
+ echo "RUNNING timeout -k 5 $TIMEOUT sbt" "$@"
+ timeout -k 5 $TIMEOUT sbt $SBT_OPTS "$@"
+ local CODE=$?
+ if [ "$CODE" -eq 124 ]; then
+ echo "TIMEOUT after" $TIMEOUT
+ fi
+ if [ "$CODE" -ne 0 ]; then
+ echo "RETRYING timeout -k 5 $TIMEOUT sbt" "$@"
+ timeout -k 5 $TIMEOUT sbt $SBT_OPTS "$@"
+ CODE=$?
+ if [ "$CODE" -eq 124 ]; then
+ echo "TIMEOUT after" $TIMEOUT
+ fi
+ if [ "$CODE" -ne 0 ]; then
+ echo "FAILED TWICE"
+ echo "Command was: sbt" "$@"
+ return $CODE
+ fi
+ fi
+}
+
+sbtnoretry() {
+ echo "RUNNING sbt" "$@"
+ sbt $SBT_OPTS "$@"
+ CODE=$?
+ if [ "$CODE" -ne 0 ]; then
+ echo "FAILED"
+ echo "Command was: sbt" "$@"
+ return $CODE
+ fi
+}
+'''
+
+def Tasks = [
+ "main": '''
+ setJavaVersion $java
+ npm install &&
+ sbtretry ++$scala helloworld$v/run &&
+ sbtretry 'set scalaJSStage in Global := FullOptStage' \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withPrettyPrint(true))' \
+ ++$scala helloworld$v/run &&
+ sbtretry 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withOptimizer(false))' \
+ ++$scala helloworld$v/run &&
+ sbtretry 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withSemantics(_.withAsInstanceOfs(CheckedBehavior.Unchecked)))' \
+ ++$scala helloworld$v/run &&
+ sbtretry ++$scala \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)))' \
+ helloworld$v/run &&
+ sbtretry ++$scala \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
+ helloworld$v/run &&
+ sbtretry ++$scala \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
+ helloworld$v/run &&
+ sbtretry ++$scala \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ helloworld$v/run &&
+ sbtretry ++$scala \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ helloworld$v/run &&
+ sbtretry ++$scala \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("helloworld"))))' \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ helloworld$v/run &&
+ sbtretry ++$scala \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ helloworld$v/run &&
+ sbtretry ++$scala testingExample$v/testHtmlJSDom &&
+ sbtretry ++$scala \
+ 'set scalaJSLinkerConfig in testingExample.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
+ 'set scalaJSLinkerConfig in testingExample.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ testingExample$v/testHtml &&
+ sbtretry 'set scalaJSStage in Global := FullOptStage' \
+ ++$scala testingExample$v/testHtmlJSDom &&
+ sbtretry ++$scala testSuiteJVM$v/test testSuiteExJVM$v/test &&
+ sbtretry ++$scala testSuite$v/test &&
+ sbtretry ++$scala \
+ testSuite$v/saveForStabilityTest \
+ testSuite$v/checkStability \
+ testSuite$v/forceRelinkForStabilityTest \
+ testSuite$v/checkStability \
+ testSuite$v/clean \
+ testSuite$v/checkStability &&
+ sbtretry ++$scala testSuiteEx$v/test &&
+ sbtretry 'set scalaJSStage in Global := FullOptStage' \
+ ++$scala testSuiteEx$v/test &&
+ sbtretry ++$scala \
+ 'set scalaJSLinkerConfig in testSuiteEx.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
+ 'set scalaJSLinkerConfig in testSuiteEx.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ testSuiteEx$v/test &&
+ sbtretry ++$scala testSuite$v/test:doc library$v/test compiler$v/test &&
+ sbtretry ++$scala \
+ 'set scalaJSLinkerConfig in reversi.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
+ 'set scalaJSLinkerConfig in reversi.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ reversi$v/fastLinkJS \
+ reversi$v/fullLinkJS &&
+ sbtretry ++$scala \
+ 'set scalaJSLinkerConfig in reversi.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("reversi"))))' \
+ 'set scalaJSLinkerConfig in reversi.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ reversi$v/fastLinkJS \
+ reversi$v/fullLinkJS &&
+ sbtretry ++$scala \
+ reversi$v/fastLinkJS \
+ reversi$v/fullLinkJS \
+ reversi$v/checksizes &&
+ sbtretry ++$scala \
+ 'set Global/enableMinifyEverywhere := true' \
+ reversi$v/checksizes &&
+ sbtretry ++$scala javalibintf/compile:doc compiler$v/compile:doc library$v/compile:doc \
+ testInterface$v/compile:doc testBridge$v/compile:doc &&
+ sbtretry ++$scala headerCheck &&
+ sbtretry ++$scala partest$v/fetchScalaSource &&
+ sbtretry ++$scala \
+ javalibintf/mimaReportBinaryIssues \
+ library$v/mimaReportBinaryIssues \
+ testInterface$v/mimaReportBinaryIssues \
+ jUnitRuntime$v/mimaReportBinaryIssues
+ ''',
+
+ "test-suite-default-esversion": '''
+ setJavaVersion $java
+ npm install &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ jUnitTestOutputsJVM$v/test jUnitTestOutputsJS$v/test testBridge$v/test \
+ 'set scalaJSStage in Global := FullOptStage' jUnitTestOutputsJS$v/test testBridge$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ $testSuite$v/test $testSuite$v/testHtmlJSDom &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ $testSuite$v/test \
+ $testSuite$v/testHtmlJSDom &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)).withOptimizer(false))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAvoidLetsAndConsts(false).withAvoidClasses(false)))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAvoidLetsAndConsts(false).withAvoidClasses(false)))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("org.scalajs.testsuite"))))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ $testSuite$v/test &&
+ # The following tests the same thing whether testMinify is true or false; we also set it for regularity.
+ sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ $testSuite$v/test
+ ''',
+
+ "test-suite-custom-esversion-force-polyfills": '''
+ setJavaVersion $java
+ npm install &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ ++$scala $testSuite$v/test
+ ''',
+
+ "test-suite-custom-esversion": '''
+ setJavaVersion $java
+ npm install &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion).withAllowBigIntsForLongs(true)))' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion).withAllowBigIntsForLongs(true)).withOptimizer(false))' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
+ ++$scala $testSuite$v/test &&
+ sbtretry \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ ++$scala $testSuite$v/test &&
+ sbtretry \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ ++$scala $testSuite$v/test &&
+ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ ++$scala $testSuite$v/test
+ ''',
+
+ "test-suite-webassembly": '''
+ setJavaVersion $java
+ npm install &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ helloworld$v/run &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withPrettyPrint(true))' \
+ helloworld$v/run &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in reversi.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ reversi$v/fastLinkJS \
+ reversi$v/fullLinkJS &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in jUnitTestOutputsJS.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in testBridge.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ jUnitTestOutputsJS$v/test testBridge$v/test \
+ 'set scalaJSStage in Global := FullOptStage' \
+ jUnitTestOutputsJS$v/test testBridge$v/test &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ $testSuite$v/test &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ $testSuite$v/test &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ $testSuite$v/test &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ $testSuite$v/test &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \
+ $testSuite$v/test &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ testingExample$v/testHtml &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ 'set scalaJSStage in Global := FullOptStage' \
+ testingExample$v/testHtml &&
+ sbtretry ++$scala \
+ 'set Global/enableWasmEverywhere := true' \
+ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \
+ irJS$v/fastLinkJS
+ ''',
+
+ /* For the bootstrap tests to be able to call
+ * `testSuite/test:fastOptJS`, `scalaJSStage in testSuite` must be
+ * `FastOptStage`, even when `scalaJSStage in Global` is `FullOptStage`.
+ */
+ "bootstrap": '''
+ setJavaVersion $java
+ npm install &&
+ sbtnoretry ++$scala linker$v/test &&
+ sbtnoretry linkerPrivateLibrary/test &&
+ sbtnoretry ++$scala irJS$v/test &&
+ sbtnoretry ++$scala linkerInterfaceJS$v/test &&
+ sbtnoretry ++$scala linkerJS$v/test &&
+ sbtnoretry 'set scalaJSStage in Global := FullOptStage' \
+ 'set scalaJSStage in testSuite.v$v := FastOptStage' \
+ ++$scala irJS$v/test &&
+ sbtnoretry 'set scalaJSStage in Global := FullOptStage' \
+ 'set scalaJSStage in testSuite.v$v := FastOptStage' \
+ ++$scala linkerInterfaceJS$v/test &&
+ sbtnoretry 'set scalaJSStage in Global := FullOptStage' \
+ 'set scalaJSStage in testSuite.v$v := FastOptStage' \
+ ++$scala linkerJS$v/test &&
+ sbtnoretry ++$scala testSuite$v/bootstrap:test &&
+ sbtnoretry 'set scalaJSStage in Global := FullOptStage' \
+ 'set scalaJSStage in testSuite.v$v := FastOptStage' \
+ ++$scala testSuite$v/bootstrap:test &&
+ sbtnoretry ++$scala irJS$v/mimaReportBinaryIssues \
+ linkerInterfaceJS$v/mimaReportBinaryIssues linkerJS$v/mimaReportBinaryIssues
+ ''',
+
+ "tools": '''
+ setJavaVersion $java
+ npm install &&
+ sbtnoretry ++$scala ir$v/test linkerInterface$v/test \
+ linker$v/compile testAdapter$v/test \
+ ir$v/mimaReportBinaryIssues \
+ linkerInterface$v/mimaReportBinaryIssues linker$v/mimaReportBinaryIssues \
+ testAdapter$v/mimaReportBinaryIssues &&
+ sbtnoretry ++$scala ir$v/compile:doc \
+ linkerInterface$v/compile:doc linker$v/compile:doc \
+ testAdapter$v/compile:doc
+ ''',
+
+ // These are agnostic to the Scala version
+ "sbt-plugin-and-scalastyle": '''
+ setJavaVersion $java
+ npm install &&
+ sbtnoretry \
+ sbtPlugin/compile:doc \
+ sbtPlugin/mimaReportBinaryIssues \
+ scalastyleCheck &&
+ sbtnoretry sbtPlugin/scripted
+ ''',
+
+ "partest-noopt": '''
+ setJavaVersion $java
+ npm install &&
+ sbtnoretry ++$scala partestSuite$v/test:compile &&
+ sbtnoretry ++$scala "partestSuite$v/testOnly -- $partestopts --showDiff"
+ ''',
+
+ "partest-fastopt": '''
+ setJavaVersion $java
+ npm install &&
+ sbtnoretry ++$scala partestSuite$v/test:compile &&
+ sbtnoretry ++$scala "partestSuite$v/testOnly -- $partestopts --fastOpt --showDiff"
+ ''',
+
+ "partest-fullopt": '''
+ setJavaVersion $java
+ npm install &&
+ sbtnoretry ++$scala partestSuite$v/test:compile &&
+ sbtnoretry ++$scala "partestSuite$v/testOnly -- $partestopts --fullOpt --showDiff"
+ ''',
+
+ "scala3-compat": '''
+ setJavaVersion $java
+ npm install &&
+ sbtnoretry ++$scala! ir2_13/test
+ '''
+]
+
+def mainJavaVersion = "1.8"
+def otherJavaVersions = ["11", "17", "21"]
+def allJavaVersions = otherJavaVersions.clone()
+allJavaVersions << mainJavaVersion
+
+def mainScalaVersion = "2.12.20"
+def mainScalaVersions = ["2.12.20", "2.13.16"]
+def otherScalaVersions = [
+ "2.12.6",
+ "2.12.7",
+ "2.12.8",
+ "2.12.9",
+ "2.12.10",
+ "2.12.11",
+ "2.12.12",
+ "2.12.13",
+ "2.12.14",
+ "2.12.15",
+ "2.12.16",
+ "2.12.17",
+ "2.12.18",
+ "2.12.19",
+ "2.13.3",
+ "2.13.4",
+ "2.13.5",
+ "2.13.6",
+ "2.13.7",
+ "2.13.8",
+ "2.13.9",
+ "2.13.10",
+ "2.13.11",
+ "2.13.12",
+ "2.12.13",
+ "2.12.14",
+ "2.12.15"
+]
+
+def scala3Version = "3.6.3"
+
+def allESVersions = [
+ "ES5_1",
+ "ES2015",
+ // "ES2016", // Technically we have the '**' operator dependent on ES2016, but it's not enough to justify testing this version
+ "ES2017",
+ "ES2018",
+ // "ES2019", // We do not use anything specifically from ES2019
+ "ES2020",
+ "ES2021" // We do not use anything specifically from ES2021, but always test the latest to avoid #4675
+]
+def defaultESVersion = "ES2015"
+def latestESVersion = "ES2021"
+
+// The 'quick' matrix
+def quickMatrix = []
+mainScalaVersions.each { scalaVersion ->
+ allJavaVersions.each { javaVersion ->
+ quickMatrix.add([task: "main", scala: scalaVersion, java: javaVersion])
+ quickMatrix.add([task: "tools", scala: scalaVersion, java: javaVersion])
+ }
+ quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "false", testSuite: "testSuite"])
+ quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "true", testSuite: "testSuite"])
+ quickMatrix.add([task: "test-suite-custom-esversion", scala: scalaVersion, java: mainJavaVersion, esVersion: "ES5_1", testSuite: "testSuite"])
+ quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, esVersion: defaultESVersion, testMinify: "false", testSuite: "testSuite"])
+ quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, esVersion: latestESVersion, testMinify: "false", testSuite: "testSuite"])
+ quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, esVersion: defaultESVersion, testMinify: "false", testSuite: "testSuiteEx"])
+ quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "false", testSuite: "scalaTestSuite"])
+ quickMatrix.add([task: "test-suite-custom-esversion", scala: scalaVersion, java: mainJavaVersion, esVersion: "ES5_1", testSuite: "scalaTestSuite"])
+ quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, esVersion: defaultESVersion, testMinify: "false", testSuite: "scalaTestSuite"])
+ quickMatrix.add([task: "bootstrap", scala: scalaVersion, java: mainJavaVersion])
+ quickMatrix.add([task: "partest-fastopt", scala: scalaVersion, java: mainJavaVersion, partestopts: ""])
+ quickMatrix.add([task: "partest-fastopt", scala: scalaVersion, java: mainJavaVersion, partestopts: "--wasm"])
+}
+allESVersions.each { esVersion ->
+ quickMatrix.add([task: "test-suite-custom-esversion-force-polyfills", scala: mainScalaVersion, java: mainJavaVersion, esVersion: esVersion, testSuite: "testSuite"])
+}
+allJavaVersions.each { javaVersion ->
+ // the `scala` version is irrelevant here
+ // We exclude JDK 21 because our sbt scripted tests use old sbt versions (on purpose), which do not support JDK 21
+ if (javaVersion != '21') {
+ quickMatrix.add([task: "sbt-plugin-and-scalastyle", scala: mainScalaVersion, java: javaVersion])
+ }
+}
+quickMatrix.add([task: "scala3-compat", scala: scala3Version, java: mainJavaVersion])
+
+// The 'full' matrix
+def fullMatrix = quickMatrix.clone()
+otherScalaVersions.each { scalaVersion ->
+ fullMatrix.add([task: "main", scala: scalaVersion, java: mainJavaVersion])
+}
+mainScalaVersions.each { scalaVersion ->
+ otherJavaVersions.each { javaVersion ->
+ quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: javaVersion, testMinify: "false", testSuite: "testSuite"])
+ quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, esVersion: defaultESVersion, testMinify: "false", testSuite: "testSuite"])
+ }
+ fullMatrix.add([task: "partest-noopt", scala: scalaVersion, java: mainJavaVersion, partestopts: ""])
+ fullMatrix.add([task: "partest-noopt", scala: scalaVersion, java: mainJavaVersion, partestopts: "--wasm"])
+ fullMatrix.add([task: "partest-fullopt", scala: scalaVersion, java: mainJavaVersion, partestopts: ""])
+ fullMatrix.add([task: "partest-fullopt", scala: scalaVersion, java: mainJavaVersion, partestopts: "--wasm"])
+}
+otherJavaVersions.each { javaVersion ->
+ fullMatrix.add([task: "scala3-compat", scala: scala3Version, java: javaVersion])
+}
+
+def Matrices = [
+ quick: quickMatrix,
+ full: fullMatrix
+]
+
+if (!Matrices.containsKey(selectedMatrix)) {
+ error("Nonexistent matrix '$selectedMatrix'")
+}
+def matrix = Matrices[selectedMatrix]
+
+buildDefs = [:]
+matrix.each { taskDef ->
+ def taskName = taskDef.task
+ if (!Tasks.containsKey(taskName)) {
+ error("Nonexistent task '$taskName'")
+ }
+ def taskStr = Tasks[taskName]
+ def fullTaskName = taskName
+
+ taskDef.each { name, value ->
+ if (name != 'task') {
+ taskStr = taskStr.replace('$' + name, value)
+ fullTaskName += " $name=$value"
+ }
+ }
+
+ def suffix = taskDef.scala.split('\\.')[0..1].join('_')
+ taskStr = taskStr.replace('$v', suffix)
+
+ def ciScript = CIScriptPrelude + taskStr
+
+ buildDefs.put(fullTaskName, {
+ node('linuxworker') {
+ checkout scm
+ retry(2) {
+ sh "git clean -fdx && rm -rf partest/fetchedSources/"
+ writeFile file: 'ciscript.sh', text: ciScript, encoding: 'UTF-8'
+ timeout(time: 4, unit: 'HOURS') {
+ sh "echo '$fullTaskName' && cat ciscript.sh && sh ciscript.sh"
+ }
+ }
+ }
+ })
+}
+
+ansiColor('xterm') {
+ stage('Test') {
+ parallel(buildDefs)
+ }
+}
diff --git a/LICENSE b/LICENSE
index 2e6afc1729..9a57ed8100 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,27 +1,202 @@
-Copyright (c) 2013 EPFL
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
- * Neither the name of the EPFL nor the names of its contributors
- may be used to endorse or promote products derived from this software
- without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright (c) 2013-2018 EPFL
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000000..2edf2f73c7
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,26 @@
+Scala.js
+Copyright (c) 2013-2018 EPFL
+
+Scala.js includes software developed at EPFL (https://lamp.epfl.ch/ and
+https://scala.epfl.ch/).
+
+Licensed under the Apache License, Version 2.0 (the "License").
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+This project contains a translation of JUnit 4.12 to Scala.js in
+junit-runtime/src/main/scala/org/junit/. The original code can be found at
+https://github.com/junit-team/junit4/commit/64155f8a9babcfcf4263cf4d08253a1556e75481
+As a translation, it constitutes a derivative work and is therefore licensed
+under the Eclipse Public License v1.0, whose full text can be found in
+junit-runtime/src/main/scala/org/junit/LICENSE-junit.txt
+
+This project contains a translation of Hamcrest 1.3 to Scala.js in
+junit-runtime/src/main/scala/org/hamcrest/. The original code can be found at
+https://github.com/hamcrest/JavaHamcrest/tree/hamcrest-java-1.3
+As a translation, it constitutes a derivative work and is therefore licensed
+under the BSD License, whose full text can be found in
+junit-runtime/src/main/scala/org/hamcrest/LICENSE-hamcrest.txt
diff --git a/README.md b/README.md
index bbb1822edd..add1859b89 100644
--- a/README.md
+++ b/README.md
@@ -1,107 +1,20 @@
-# Scala.js, a Scala to JavaScript compiler
+
-Scala.js compiles Scala code to JavaScript, allowing you to write your
-Web application entirely in Scala!
+Chat: [#scala-js](https://discord.com/invite/scala) on Discord.
-Noteworthy features are:
+This is the repository for
+[Scala.js, the Scala to JavaScript compiler](https://www.scala-js.org/).
-* Support all of Scala (including macros!),
- modulo [a few semantic differences](http://www.scala-js.org/doc/semantics.html)
-* Very good [interoperability with JavaScript code](http://www.scala-js.org/doc/js-interoperability.html).
- For example, use jQuery and HTML5 from your Scala.js code, either in a
- typed or untyped way. Or create Scala.js objects and call their methods
- from JavaScript.
-* Integrated with [sbt](http://www.scala-sbt.org/)
- (including support for dependency management and incremental compilation)
-* Can be used with your favorite IDE for Scala
-* Generates [Source Maps](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/)
- for a smooth debugging experience (step through your Scala code from within
- your browser supporting source maps)
-* Integrates [Google Closure Compiler](https://developers.google.com/closure/compiler/)
- for producing minimal code for production.
-
-## Resources
-
-* [Website](http://www.scala-js.org/)
-* [Mailing list](https://groups.google.com/forum/?fromgroups#!forum/scala-js)
-
-## Get started
-
-We provide a
-[bootstrapping application](https://github.com/sjrd/scala-js-example-app)
-which you can fork to kick off your own project. Its readme provides further
-explanations on how to do so.
-
-## Contribute
-
-### Compile
-
-Scala.js uses [sbt](http://www.scala-sbt.org/) for its build process.
-To compile your fork, simply run:
-
- sbt> package
-
-By default the sbt environment uses Scala 2.10.2. You can switch to any of the
-supported versions with, e.g.,
-
- sbt> ++2.11.0-M7
-
-### Run the test suite
-
-Compile and run the Scala.js-specific test suite with
-
- sbt> scalajs-test/test
-
-(you must have run `package` before running the test suite)
-
-To run the Scala test suite (aka partest), you have to use a 2.11 version, e.g.,
-2.11.0-M7, and run:
-
- sbt> scalajs-partest-suite/test
-
-Beware, this takes a very long time.
-
-A complete test session from scratch on 2.11.0-M7 would then be
-
- sbt> ++2.11.0-M7
- sbt> package
- sbt> scalajs-test/test
- sbt> scalajs-partest-suite/test
-
-### Test the examples
-
-After having compiled Scala.js, you can compile the example applications with:
-
- sbt> examples/optimizeJS
-
-(you must have run `package` before compiling the examples)
-
-Then, you can "execute" them by opening their respective HTML files in your
-favorite browser. Since optimizing the JavaScript takes time (tens of seconds
-to several minutes, depending on your hardware), it is also possible not to
-optimize JS by doing instead:
-
- sbt> examples/packageJS
-
-In this case, you have to open the `-dev` version of the HTML files.
-
-Currently, two examples are provided:
-
-* `examples/helloworld/helloworld.html`, saying Hello World in four different
- ways (using DOM or jQuery, and using the untyped or typed interface to
- JavaScript).
-* `examples/reversi/reversi.html`, an implementation of a
- [Reversi](http://en.wikipedia.org/wiki/Reversi) game. Note that it uses the
- HTML5 Canvas element, so it won't work with Internet Explorer 8 or below.
-
-### Use your fork with your own projects
-
-Simply publish it locally with:
-
- sbt> publishLocal
- sbt> scalajs-sbt-plugin/publishLocal
+* [Report an issue](https://github.com/scala-js/scala-js/issues)
+* [Developer documentation](./DEVELOPING.md)
+* [Contributing guidelines](./CONTRIBUTING.md)
## License
Scala.js is distributed under the
-[Scala License](http://www.scala-lang.org/license.html).
+[Apache License Version 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 0000000000..b143e9a93e
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,42 @@
+# Scala.js Release Process
+
+1. Clean up bugs:
+ - Make sure all bugs assigned to the milestone are closed.
+ - Make sure all fixed bugs are assigned to the milestone ([this query][1]
+ should not return anything).
+1. Create a "Version x.y.z." commit ([example][2]) and push it to a branch on
+ your fork.
+ 1. Ping people on the commit for review.
+ 1. Once you have LGTM, push to `main` (do *not* create a merge commit).
+1. Testing (post results as comments to commit):
+ - Full build
+ - [Manual testing][3]
+1. If all tests pass, tag the commit with the release version.
+1. Perform [manual testing][3] that needs the tagging (source maps).
+1. Publish:
+ - Sonatype (`./script/publish.sh`)
+ - Docs to website: Use
+ `~/fetchapis.sh ` on the webserver
+ once artifacts are on maven central.
+1. Once artifacts are on maven central, create a "Towards x.y.z." commit
+ ([example][5]).
+ 1. Create an "FF ONLY" PR for CI and review.
+ 1. Once you have LGTM, push the commit (do *not* click the merge button)
+1. Prepare release announcement, taking the last one as model ([example][6]).
+1. When merging the release announcement PR (after proper review):
+ - Update the latest/ URLs (use `~/setlatestapi.sh ` on
+ webserver)
+ - Create a release on the core scala-js repository.
+ - Announce on Twitter using the @scala_js account
+ - Announce on [Gitter](https://gitter.im/scala-js/scala-js)
+ - Cross-post as an Announcement in Scala Users ([example][7])
+ - Send a PR to Scala Steward to "unleash" the release by updating
+ [these lines][8] with the next possible version numbers
+
+[1]: https://github.com/scala-js/scala-js/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20no%3Amilestone%20-label%3Ainvalid%20-label%3Aduplicate%20-label%3Aas-designed%20-label%3Aquestion%20-label%3Awontfix%20-label%3A%22can%27t%20reproduce%22%20-label%3A%22separate%20repo%22
+[2]: https://github.com/scala-js/scala-js/commit/c3520bb9dae46757a975cccd428a77b8d6e6a75e
+[3]: https://github.com/scala-js/scala-js/blob/main/TESTING.md
+[5]: https://github.com/scala-js/scala-js/commit/c6c82e80f56bd2008ff8273088bbbbbbbc30f777
+[6]: https://github.com/scala-js/scala-js-website/commit/057f743c3fb8abe6077fb4debeeec45cd5c53d5d
+[7]: https://users.scala-lang.org/t/announcing-scala-js-1-4-0/7013
+[8]: https://github.com/scala-steward-org/scala-steward/blob/30f3217ce11bbb0208d70070e7d5f49a3b1a25f0/modules/core/src/main/resources/default.scala-steward.conf#L19-L73
diff --git a/TESTING.md b/TESTING.md
new file mode 100644
index 0000000000..d26fafe4c3
--- /dev/null
+++ b/TESTING.md
@@ -0,0 +1,66 @@
+This file contains test cases that should be manually executed.
+
+## HTML-Runners
+
+The following HTML-runners must be manually tested:
+
+ examples/helloworld/helloworld-2.12{|-fastopt}.html
+ examples/reversi/reversi-2.12{|-fastopt}.html
+
+## HTML-Test Runner with Modules
+
+Still manual, because jsdom does not support modules yet
+[jsdom/jsdom#2475](https://github.com/jsdom/jsdom/issues/2475).
+
+```
+$ sbt
+> set scalaJSLinkerConfig in testingExample.v2_12 ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules).withModuleKind(ModuleKind.ESModule))
+> testingExample2_12/testHtml
+> set scalaJSLinkerConfig in testSuite.v2_12 ~= (_.withModuleKind(ModuleKind.ESModule))
+> testSuite2_12/testHtml
+> exit
+$ python3 -m http.server
+
+// Open http://localhost:8000/examples/testing/.2.12/target/scala-2.12/testing-fastopt-test-html/index.html
+// Open http://localhost:8000/test-suite/js/.2.12/target/scala-2.12/scalajs-test-suite-fastopt-test-html/index.html
+```
+
+## HTML-Test Runner with WebAssembly
+
+WebAssembly requires modules, so this is manual as well.
+
+This test currently requires Chrome (or another V8-based browser) with `--wasm-experimental-exnref` enabled.
+That option can be configured as "Experimental WebAssembly" at [chrome://flags/#enable-experimental-webassembly-features](chrome://flags/#enable-experimental-webassembly-features).
+
+```
+$ sbt
+> set Global/enableWasmEverywhere := true
+> testingExample2_12/testHtml
+> testSuite2_12/testHtml
+> exit
+$ python3 -m http.server
+
+// Open http://localhost:8000/examples/testing/.2.12/target/scala-2.12/testing-fastopt-test-html/index.html
+// Open http://localhost:8000/test-suite/js/.2.12/target/scala-2.12/scalajs-test-suite-fastopt-test-html/index.html
+```
+
+## Sourcemaps
+
+To test source maps, do the following on:
+
+ examples/reversi/reversi-2.12{|-fastopt}.html
+
+1. Open the respective file in Google Chrome
+2. Set a break-point in the HTML launcher on the `new Reversi` statement
+3. Step over calls to jQuery into constructor
+4. Step into the call to `Array.tabulate` and verify that source maps
+ to Scala standard library sources work (should point to GitHub)
+5. Single step through constructor, until you reach `buildUI()`
+6. Step into `buildUI()`
+
+
+## When releasing only
+
+Once all tests pass, tag the revision and verify that source maps to
+Scala.js sources work correctly (should point to GitHub), following
+the steps described in the section Sourcemaps.
diff --git a/VERSIONING.md b/VERSIONING.md
new file mode 100644
index 0000000000..941d33977f
--- /dev/null
+++ b/VERSIONING.md
@@ -0,0 +1,61 @@
+# Versioning
+
+This page describes how we version Scala.js core. Notably what compatibility
+guarantees we give with respect to the version numbering.
+
+# Major Changes
+
+The following changes must cause a major version bump.
+
+* Backward incompatible change in the IR
+* Backward binary incompatible change in the standard library
+* Backward incompatible change in the contract for calling JDK APIs
+
+# Severe Changes
+
+Severe changes can break the ecosystem of sbt plugins and other build tools, but
+not the ecosystem of libraries (which would be major). Severe changes should be
+done only if absolutely necessary. The following are considered severe changes:
+
+* Backward binary incompatible changes in `linker.*` or `linker.interface.*`
+* Backward binary incompatible changes in `sbtplugin.*`
+* Backward binary incompatible changes in `testadapter.*`
+
+Severe changes are difficult from a versioning point of view, since they require
+a careful tradeoff:
+
+* if a major bump is made, it forces libraries to re-publish unnecessarily
+ (because the IR is not actually affected).
+* if no major bump is made, the tooling API versioning breaks the SemVer
+ contract.
+
+As such, we leave the best course of action in case of severe changes to the
+maintainers. Possible courses of action are:
+
+* Major version bump
+* Minor version bump
+* Separating versioning of IR and tooling.
+
+# Minor Changes
+
+The following changes must cause a minor version bump.
+
+* Forward incompatible change in the IR
+* Backward source incompatible change at the language level or at the standard
+ library level (including any addition of public API in the stdlib)
+* Backward source incompatible change in `linker.*`, `linker.interface.*`
+ or `sbtplugin.*` (including any addition of public API)
+* Backward source incompatible changes in `testadapter.*`
+* Backward binary incompatible changes in `ir.*`, `linker.interface.unstable.*`
+ or `linker.standard.*`
+
+# Patch Changes
+
+All other changes cause a patch version bump only. Explicitly (but not
+exhaustively):
+
+* Backward source incompatible change in `ir.*`, `linker.interface.unstable.*`
+ or `linker.standard.*`
+* Backward source/binary incompatible changes elsewhere in `linker.**`
+* Fixes or additions in the JDK libs (since they are always backward source and
+ binary compatible)
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000000..4f0c93e14c
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,23 @@
+version: '{build}'
+image: Visual Studio 2015
+environment:
+ global:
+ NODEJS_VERSION: "16"
+ JAVA_HOME: C:\Program Files\Java\jdk1.8.0
+install:
+ - ps: Install-Product node $env:NODEJS_VERSION
+ - npm install
+ - cmd: choco install sbt --version 1.3.12 -ia "INSTALLDIR=""C:\sbt"""
+ - cmd: SET PATH=C:\sbt\bin;%JAVA_HOME%\bin;%PATH%
+ - cmd: SET "SBT_OPTS=-Xmx4g -Xms4m"
+build: off
+test_script:
+ # Very far from testing everything, but at least it is a good sanity check
+ # For slow things (partest and scripted), we execute only one test
+ - cmd: sbt ";clean;testSuite2_12/test;linker2_12/test;partestSuite2_12/testOnly -- --fastOpt run/option-fold.scala"
+ # Module splitting has some logic for case-insensitive filesystems, which we must test on Windows
+ - cmd: sbt ";setSmallESModulesForAppVeyorCI;testSuite2_12/test"
+cache:
+ - C:\sbt
+ - C:\Users\appveyor\.ivy2\cache
+ - C:\Users\appveyor\.sbt
diff --git a/assets/additional-doc-styles.css b/assets/additional-doc-styles.css
new file mode 100644
index 0000000000..c0d38e0efb
--- /dev/null
+++ b/assets/additional-doc-styles.css
@@ -0,0 +1,7 @@
+.badge-ecma6, .badge-ecma2015, .badge-ecma2016, .badge-ecma2017, .badge-ecma2018, .badge-ecma2019, .badge-ecma2020, .badge-ecma2021 {
+ background-color: #E68A00;
+}
+
+.badge-non-std {
+ background-color: #B94A48;
+}
diff --git a/build.sbt b/build.sbt
new file mode 100644
index 0000000000..e9bdde6b6b
--- /dev/null
+++ b/build.sbt
@@ -0,0 +1,42 @@
+import build.Build
+
+val scalajs = Build.root
+val ir = Build.irProject
+val irJS = Build.irProjectJS
+val compiler = Build.compiler
+val linkerInterface = Build.linkerInterface
+val linkerInterfaceJS = Build.linkerInterfaceJS
+val linkerPrivateLibrary = Build.linkerPrivateLibrary
+val linker = Build.linker
+val linkerJS = Build.linkerJS
+val testAdapter = Build.testAdapter
+val sbtPlugin = Build.plugin
+val javalibintf = Build.javalibintf
+val javalibInternal = Build.javalibInternal
+val javalib = Build.javalib
+val scalalibInternal = Build.scalalibInternal
+val libraryAux = Build.libraryAux
+val scalalib = Build.scalalib
+val library = Build.library
+val testInterface = Build.testInterface
+val testBridge = Build.testBridge
+val jUnitRuntime = Build.jUnitRuntime
+val jUnitTestOutputsJS = Build.jUnitTestOutputsJS
+val jUnitTestOutputsJVM = Build.jUnitTestOutputsJVM
+val jUnitPlugin = Build.jUnitPlugin
+val jUnitAsyncJS = Build.jUnitAsyncJS
+val jUnitAsyncJVM = Build.jUnitAsyncJVM
+val helloworld = Build.helloworld
+val reversi = Build.reversi
+val testingExample = Build.testingExample
+val testSuite = Build.testSuite
+val testSuiteJVM = Build.testSuiteJVM
+val javalibExtDummies = Build.javalibExtDummies
+val testSuiteEx = Build.testSuiteEx
+val testSuiteExJVM = Build.testSuiteExJVM
+val testSuiteLinker = Build.testSuiteLinker
+val partest = Build.partest
+val partestSuite = Build.partestSuite
+val scalaTestSuite = Build.scalaTestSuite
+
+inThisBuild(Build.thisBuildSettings)
diff --git a/check-partest-coverage.sh b/check-partest-coverage.sh
deleted file mode 100755
index bf111db044..0000000000
--- a/check-partest-coverage.sh
+++ /dev/null
@@ -1,58 +0,0 @@
-#! /bin/sh
-
-# This script tests if all Scala partests are classified. Since
-# Scala.js does not provide all the Scala functionality (see [1]), we
-# have to exclude some partests from testing. Therefore, every partest
-# in $TESTDIR has to be in exactly one of the following files located
-# in $KNOWDIR:
-# - WhitelistedTests.txt: Tests that succeed
-# - BlacklistedTests.txt: Tests that fail since they test for behavior
-# which is not supported in Scala.js
-# - BuglistedTests.txt: Tests that fail due to a bug in Scala.js
-#
-# [1] http://www.scala-js.org/doc/semantics.html
-
-# Arguments
-if [ $# -le 0 ]; then
- echo "Please give full scala version as argument" >&2
- exit 42
-fi
-
-FULLVER="$1"
-
-# Config
-BASEDIR="`dirname $0`"
-TESTDIR="$BASEDIR/scalalib/fetchedSources/$1/test/files"
-KNOWDIR="$BASEDIR/partest-suite/src/test/resources/scala/tools/partest/scalajs/$1/"
-
-# If the classification directory does not exist, this means (by
-# definition) that we do not want to or cannot partest this scala
-# version. Therefore, everything is OK.
-if [ ! -d $KNOWDIR ]; then
- exit 0
-fi
-
-# Temp files
-TMP_PREF=`basename $0`
-TMP_HAVE_FILE=`mktemp /tmp/${TMP_PREF}_have_XXXXX` || exit 2
-TMP_KNOW_FILE=`mktemp /tmp/${TMP_PREF}_know_XXXXX` || exit 2
-
-# Trap removal of tmp files on exit
-trap "rm \"$TMP_HAVE_FILE\" \"$TMP_KNOW_FILE\"" EXIT
-
-# Find all partests
-( # Subshell to protect cwd
-cd "$TESTDIR"
-find "run" "neg" "pos" \
- -mindepth 1 -maxdepth 1 \( -type d -or -name '*.scala' \) \
- | sort >> $TMP_HAVE_FILE
-)
-
-# Find classified partests
-( # Subshell to protect cwd
-cd "$KNOWDIR"
-cat BlacklistedTests.txt BuglistedTests.txt WhitelistedTests.txt \
- | grep -E -v '^#|^\s*$' | sort >> $TMP_KNOW_FILE
-)
-
-diff -U 0 --label 'Classified Tests' $TMP_KNOW_FILE --label 'Existing Tests' $TMP_HAVE_FILE
diff --git a/checksizes.sh b/checksizes.sh
deleted file mode 100755
index c38ce4d6ac..0000000000
--- a/checksizes.sh
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/bin/sh
-
-FULLVER="$1"
-
-case $FULLVER in
- 2.10.2)
- VER=2.10
- ;;
- 2.10.3)
- VER=2.10
- ;;
- 2.10.4)
- VER=2.10
- ;;
- 2.11.0)
- VER=2.11
- ;;
-esac
-
-REVERSI_EXTDEPS_SIZE=$(stat '-c%s' examples/reversi/target/scala-$VER/reversi-extdeps.js)
-REVERSI_SELF_SIZE=$(stat '-c%s' examples/reversi/target/scala-$VER/reversi.js)
-REVERSI_PREOPT_SIZE=$(stat '-c%s' examples/reversi/target/scala-$VER/reversi-fastopt.js)
-REVERSI_OPT_SIZE=$(stat '-c%s' examples/reversi/target/scala-$VER/reversi-opt.js)
-
-case $FULLVER in
- 2.10.2)
- REVERSI_EXTDEPS_EXPECTEDSIZE=15100000
- REVERSI_SELF_EXPECTEDSIZE=58000
- REVERSI_PREOPT_EXPECTEDSIZE=1200000
- REVERSI_OPT_EXPECTEDSIZE=182000
- ;;
- 2.10.3)
- REVERSI_EXTDEPS_EXPECTEDSIZE=15100000
- REVERSI_SELF_EXPECTEDSIZE=58000
- REVERSI_PREOPT_EXPECTEDSIZE=1200000
- REVERSI_OPT_EXPECTEDSIZE=181000
- ;;
- 2.10.4)
- REVERSI_EXTDEPS_EXPECTEDSIZE=15100000
- REVERSI_SELF_EXPECTEDSIZE=58000
- REVERSI_PREOPT_EXPECTEDSIZE=1200000
- REVERSI_OPT_EXPECTEDSIZE=181000
- ;;
- 2.11.0)
- REVERSI_EXTDEPS_EXPECTEDSIZE=12900000
- REVERSI_SELF_EXPECTEDSIZE=54000
- REVERSI_PREOPT_EXPECTEDSIZE=1190000
- REVERSI_OPT_EXPECTEDSIZE=181000
- ;;
-esac
-
-echo "Reversi extdeps size = $REVERSI_EXTDEPS_SIZE (expected $REVERSI_EXTDEPS_EXPECTEDSIZE)"
-echo "Reversi self size = $REVERSI_SELF_SIZE (expected $REVERSI_SELF_EXPECTEDSIZE)"
-echo "Reversi preopt size = $REVERSI_PREOPT_SIZE (expected $REVERSI_PREOPT_EXPECTEDSIZE)"
-echo "Reversi opt size = $REVERSI_OPT_SIZE (expected $REVERSI_OPT_EXPECTEDSIZE)"
-
-[ "$REVERSI_EXTDEPS_SIZE" -le "$REVERSI_EXTDEPS_EXPECTEDSIZE" ] && \
- [ "$REVERSI_SELF_SIZE" -le "$REVERSI_SELF_EXPECTEDSIZE" ] && \
- [ "$REVERSI_PREOPT_SIZE" -le "$REVERSI_PREOPT_EXPECTEDSIZE" ] && \
- [ "$REVERSI_OPT_SIZE" -le "$REVERSI_OPT_EXPECTEDSIZE" ]
diff --git a/compiler/src/main/resources/scalac-plugin.xml b/compiler/src/main/resources/scalac-plugin.xml
index 76ff1b7b9d..6f133869fa 100644
--- a/compiler/src/main/resources/scalac-plugin.xml
+++ b/compiler/src/main/resources/scalac-plugin.xml
@@ -1,4 +1,4 @@
scalajs
- scala.scalajs.compiler.ScalaJSPlugin
+ org.scalajs.nscplugin.ScalaJSPlugin
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala b/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala
new file mode 100644
index 0000000000..2c8951a67f
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala
@@ -0,0 +1,108 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.collection.mutable
+
+import scala.reflect.internal.Flags
+import scala.tools.nsc._
+
+/** Hacks to have our source code compatible with the compiler internals of all
+ * the versions of Scala that we support.
+ *
+ * In general, it tries to provide the newer APIs on top of older APIs.
+ *
+ * @author Sébastien Doeraene
+ */
+trait CompatComponent {
+ import CompatComponent.infiniteLoop
+
+ val global: Global
+
+ import global._
+
+ implicit final class SymbolCompat(self: Symbol) {
+ def isScala3Defined: Boolean = false
+ }
+
+ implicit final class GlobalCompat(
+ self: CompatComponent.this.global.type) {
+
+ // Added in Scala 2.13.2 for configurable warnings
+ object runReporting {
+ def warning(pos: Position, msg: String, cat: Any, site: Symbol): Unit =
+ reporter.warning(pos, msg)
+ }
+ }
+
+ implicit final class TyperCompat(self: analyzer.Typer) {
+ // Added in Scala 2.13.5 to make it clearer what is allowed since 2.13.4
+ def checkClassOrModuleType(tpt: Tree): Boolean =
+ self.checkClassType(tpt)
+
+ def checkClassType(tpt: Tree): Boolean =
+ infiniteLoop()
+ }
+
+ // DottyEnumSingleton was introduced in 2.13.6 to identify Scala 3 `enum` singleton cases.
+ object AttachmentsCompatDef {
+ object DottyEnumSingleton extends PlainAttachment
+ }
+
+ object AttachmentsCompat {
+ import AttachmentsCompatDef._
+
+ object Inner {
+ import global._
+
+ val DottyEnumSingletonAlias = DottyEnumSingleton
+ }
+ }
+
+ lazy val DottyEnumSingletonCompat = AttachmentsCompat.Inner.DottyEnumSingletonAlias
+
+ /* global.genBCode.bTypes.initializeCoreBTypes()
+ * Early 2.12.x versions require that this method be called from
+ * GenJSCode.run(), but it disappeared later in the 2.12.x series.
+ */
+
+ implicit class BTypesCompat(bTypes: genBCode.bTypes.type) {
+ def initializeCoreBTypes(): Unit = ()
+ }
+
+ // WarningCategory was added in Scala 2.13.2 for configurable warnings
+
+ object WarningCategoryCompat {
+ object Reporting {
+ object WarningCategory {
+ val Deprecation: Any = null
+ val Other: Any = null
+ }
+ }
+ }
+
+ // Of type Reporting.WarningCategory.type, but we cannot explicit write it
+ val WarningCategory = {
+ import WarningCategoryCompat._
+
+ {
+ import scala.tools.nsc._
+ Reporting.WarningCategory
+ }
+ }
+}
+
+object CompatComponent {
+ private def infiniteLoop(): Nothing =
+ throw new AssertionError("Infinite loop in Compat")
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitInnerJS.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitInnerJS.scala
new file mode 100644
index 0000000000..01ac4b8a85
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitInnerJS.scala
@@ -0,0 +1,311 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.reflect.internal.Flags
+
+import scala.tools.nsc
+import nsc._
+import nsc.transform.{InfoTransform, TypingTransformers}
+
+import scala.collection.immutable.ListMap
+import scala.collection.mutable
+
+/** Makes the references to inner JS class values explicit.
+ *
+ * Roughly, for every inner JS class of the form:
+ * {{{
+ * class Outer {
+ * class Inner extends ParentJSClass
+ * }
+ * }}}
+ * this phase creates a field `Inner\$jsclass` in `Outer` to hold the JS class
+ * value for `Inner`. The rhs of that field is a call to a magic method, used
+ * to retain information that the back-end will need.
+ * {{{
+ * class Outer {
+ * val Inner\$jsclass: AnyRef =
+ * createJSClass(classOf[Inner], js.constructorOf[ParentJSClass])
+ *
+ * class Inner extends ParentJSClass
+ * }
+ * }}}
+ *
+ * These fields will be read by code generated in `ExplicitLocalJS`.
+ *
+ * A `\$jsclass` field is also generated for classes declared inside *static
+ * JS objects*. Indeed, even though those classes have a unique, globally
+ * accessible class value, that class value needs to be *exposed* as a field
+ * of the enclosing object. In those cases, the rhs of the field is a direct
+ * call to `runtime.constructorOf[classOf[Inner]]`.
+ *
+ * Finally, for *modules* declared inside static JS objects, we generate an
+ * explicit exposed getter as well. For non-static objects, scalac already
+ * generates a getter with the `@ExposedJSMember` annotation, so we do not
+ * need to do anything. But for static objects, it doesn't, so we have to do
+ * it ourselves here.
+ *
+ * To illustrate the two above paragraphs, for the following input:
+ * {{{
+ * object Outer extends js.Object {
+ * class InnerClass extends ParentJSClass
+ * object InnerObject extends SomeOtherJSClass
+ * }
+ * }}}
+ * this phase will generate
+ * {{{
+ * object Outer extends js.Object {
+ * ...
+ *
+ * @ExposedJSMember @JSName("InnerClass")
+ * val InnerClass\$jsclass: AnyRef = runtime.constructorOf(classOf[InnerClass])
+ *
+ * @ExposedJSMember @JSName("InnerObject")
+ * def InnerObject\$jsobject: AnyRef = InnerObject
+ * }
+ * }}}
+ *
+ * Note that this field must also be added to outer classes and traits coming
+ * from separate compilation, therefore this phase is an `InfoTransform`.
+ * Since the `transformInfo` also applies to classes defined in the current
+ * compilation unit, the tree traversal must not create the field symbols a
+ * second time when synthesizing the `ValDef`. Instead, it must reuse the same
+ * symbols that `transformInfo` will create.
+ *
+ * It seems the easiest way to do that is to run the entire `transform` "in
+ * the future", with `exitingPhase(ExplicitInnerJS)`. This design is similar
+ * to how `explicitouter` works.
+ */
+abstract class ExplicitInnerJS[G <: Global with Singleton](val global: G)
+ extends plugins.PluginComponent with InfoTransform with TypingTransformers
+ with CompatComponent {
+
+ val jsAddons: JSGlobalAddons {
+ val global: ExplicitInnerJS.this.global.type
+ }
+
+ import global._
+ import jsAddons._
+ import jsInterop.jsclassAccessorFor
+ import definitions._
+ import rootMirror._
+ import jsDefinitions._
+
+ /* The missing 'e' is intentional so that the name of the phase is not longer
+ * than the longest standard phase (packageobjects/superaccessors). This
+ * avoids destroying the nice formatting of `-Xshow-phases`.
+ */
+ val phaseName: String = "xplicitinnerjs"
+
+ override def description: String =
+ "make references to inner JS classes explicit"
+
+ /** This class does not change linearization. */
+ override protected def changesBaseClasses: Boolean = false
+
+ protected def newTransformer(unit: CompilationUnit): Transformer =
+ new ExplicitInnerJSTransformer(unit)
+
+ /** Is the given symbol an owner for which this transformation applies?
+ *
+ * This applies if either or both of the following are true:
+ * - It is not a static owner, or
+ * - It is a non-native JS object.
+ *
+ * The latter is necessary for #4086.
+ */
+ private def isApplicableOwner(sym: Symbol): Boolean = {
+ !sym.isStaticOwner || (
+ sym.isModuleClass &&
+ sym.hasAnnotation(JSTypeAnnot) &&
+ !sym.hasAnnotation(JSNativeAnnotation)
+ )
+ }
+
+ /** Is the given symbol a JS class (that is not a trait nor an object)? */
+ private def isJSClass(sym: Symbol): Boolean = {
+ sym.isClass &&
+ !sym.hasFlag(Flags.TRAIT | Flags.MODULE) &&
+ sym.hasAnnotation(JSTypeAnnot)
+ }
+
+ /** Is the given symbol a Module that should be exposed? */
+ private def isExposedModule(sym: Symbol): Boolean =
+ sym.isModule && sym.hasAnnotation(ExposedJSMemberAnnot)
+
+ private def jsobjectGetterNameFor(moduleSym: Symbol): TermName =
+ moduleSym.name.append("$jsobject").toTermName
+
+ /** Transforms the info of types to add the `Inner\$jsclass` fields.
+ *
+ * This method was inspired by `ExplicitOuter.transformInfo`.
+ */
+ def transformInfo(sym: Symbol, tp: Type): Type = tp match {
+ case ClassInfoType(parents, decls, clazz) if !clazz.isJava && isApplicableOwner(clazz) =>
+ val innerJSClasses = decls.filter(isJSClass)
+
+ val innerObjectsForAdHocExposed =
+ if (!clazz.isStaticOwner) Nil // those receive a module accessor from scalac
+ else decls.filter(isExposedModule).toList
+
+ if (innerJSClasses.isEmpty && innerObjectsForAdHocExposed.isEmpty) {
+ tp
+ } else {
+ def addAnnots(sym: Symbol, symForName: Symbol): Unit = {
+ symForName.getAnnotation(JSNameAnnotation).fold {
+ sym.addAnnotation(JSNameAnnotation,
+ Literal(Constant(jsInterop.defaultJSNameOf(symForName))))
+ } { annot =>
+ sym.addAnnotation(annot)
+ }
+ sym.addAnnotation(ExposedJSMemberAnnot)
+ }
+
+ val clazzIsJSClass = clazz.hasAnnotation(JSTypeAnnot)
+
+ val decls1 = decls.cloneScope
+
+ for (innerJSClass <- innerJSClasses) {
+ def addAnnotsIfInJSClass(sym: Symbol): Unit = {
+ if (clazzIsJSClass)
+ addAnnots(sym, innerJSClass)
+ }
+
+ val accessorName: TermName =
+ innerJSClass.name.append("$jsclass").toTermName
+ val accessorFlags =
+ Flags.SYNTHETIC | Flags.ARTIFACT | Flags.STABLE | Flags.ACCESSOR
+ val accessor =
+ clazz.newMethod(accessorName, innerJSClass.pos, accessorFlags)
+ accessor.setInfo(NullaryMethodType(AnyRefTpe))
+ addAnnotsIfInJSClass(accessor)
+ decls1.enter(accessor)
+
+ if (!clazz.isTrait) {
+ val fieldName = accessorName.append(nme.LOCAL_SUFFIX_STRING)
+ val fieldFlags =
+ Flags.SYNTHETIC | Flags.ARTIFACT | Flags.PrivateLocal
+ val field = clazz
+ .newValue(fieldName, innerJSClass.pos, fieldFlags)
+ .setInfo(AnyRefTpe)
+ addAnnotsIfInJSClass(field)
+ decls1.enter(field)
+ }
+ }
+
+ // #4086 Create exposed getters for exposed objects in static JS objects
+ for (innerObject <- innerObjectsForAdHocExposed) {
+ assert(clazzIsJSClass && clazz.isModuleClass && clazz.isStatic,
+ s"trying to ad-hoc expose objects in non-JS static object")
+
+ val getterName = jsobjectGetterNameFor(innerObject)
+ val getterFlags = Flags.SYNTHETIC | Flags.ARTIFACT | Flags.STABLE
+ val getter = clazz.newMethod(getterName, innerObject.pos, getterFlags)
+ getter.setInfo(NullaryMethodType(AnyRefTpe))
+ addAnnots(getter, innerObject)
+ decls1.enter(getter)
+ }
+
+ ClassInfoType(parents, decls1, clazz)
+ }
+
+ case PolyType(tparams, restp) =>
+ val restp1 = transformInfo(sym, restp)
+ if (restp eq restp1) tp else PolyType(tparams, restp1)
+
+ case _ =>
+ tp
+ }
+
+ class ExplicitInnerJSTransformer(unit: CompilationUnit)
+ extends TypingTransformer(unit) {
+
+ /** Execute the whole transformation in the future, exiting this phase. */
+ override def transformUnit(unit: CompilationUnit): Unit = {
+ global.exitingPhase(currentRun.phaseNamed(phaseName)) {
+ super.transformUnit(unit)
+ }
+ }
+
+ /** The main transformation method. */
+ override def transform(tree: Tree): Tree = {
+ tree match {
+ // Add the ValDefs for inner JS class values
+ case Template(parents, self, decls) if isApplicableOwner(currentOwner) =>
+ val newDecls = mutable.ListBuffer.empty[Tree]
+ atOwner(tree, currentOwner) {
+ for (decl <- decls) {
+ val declSym = decl.symbol
+ if (declSym eq null) {
+ // not a member def, do nothing
+ } else if (isJSClass(declSym)) {
+ val jsclassAccessor = jsclassAccessorFor(declSym)
+
+ val rhs = if (currentOwner.hasAnnotation(JSNativeAnnotation)) {
+ gen.mkAttributedRef(JSPackage_native)
+ } else {
+ val clazzValue = gen.mkClassOf(declSym.tpe_*)
+ if (currentOwner.isStaticOwner) {
+ // #4086
+ gen.mkMethodCall(Runtime_constructorOf, List(clazzValue))
+ } else {
+ val parentTpe =
+ extractSuperTpeFromImpl(decl.asInstanceOf[ClassDef].impl)
+ val superClassCtor = gen.mkNullaryCall(
+ JSPackage_constructorOf, List(parentTpe))
+ gen.mkMethodCall(Runtime_createInnerJSClass,
+ List(clazzValue, superClassCtor))
+ }
+ }
+
+ if (!currentOwner.isTrait) {
+ val jsclassField = jsclassAccessor.accessed
+ assert(jsclassField != NoSymbol, jsclassAccessor.fullName)
+ newDecls += localTyper.typedValDef(ValDef(jsclassField, rhs))
+ newDecls += localTyper.typed {
+ val rhs = Select(This(currentClass), jsclassField)
+ DefDef(jsclassAccessor, rhs)
+ }
+ } else {
+ newDecls += localTyper.typedValDef(ValDef(jsclassAccessor, rhs))
+ }
+ } else if (currentOwner.isStaticOwner) {
+ // #4086
+ if (isExposedModule(declSym)) {
+ val getter =
+ currentOwner.info.member(jsobjectGetterNameFor(declSym))
+ newDecls += localTyper.typedDefDef {
+ DefDef(getter, gen.mkAttributedRef(declSym))
+ }
+ }
+ }
+
+ newDecls += decl
+ }
+ }
+
+ val newTemplate =
+ treeCopy.Template(tree, parents, self, newDecls.result())
+ super.transform(newTemplate)
+
+ case _ =>
+ // Taken from ExplicitOuter
+ val x = super.transform(tree)
+ if (x.tpe eq null) x
+ else x.setType(transformInfo(currentOwner, x.tpe))
+ }
+ }
+
+ }
+
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitLocalJS.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitLocalJS.scala
new file mode 100644
index 0000000000..42eff98571
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitLocalJS.scala
@@ -0,0 +1,450 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.reflect.internal.Flags
+
+import scala.tools.nsc
+import nsc._
+import nsc.transform.{Transform, TypingTransformers}
+
+import scala.collection.immutable.ListMap
+import scala.collection.mutable
+
+/** Makes the references to local JS classes explicit and desugars calls to
+ * `js.constructorOf`.
+ *
+ * It also makes explicit all references to inner JS classes, using the
+ * pointers created by `ExplicitInnerJS`, and otherwise makes sure the
+ * back-end will receive all the information it needs to translate inner- and
+ * local JS classes and objects.
+ *
+ * Note that in this comment, by "class" we mean *only* `class`es. `trait`s
+ * and `object`s are not implied.
+ *
+ * Similarly to how `ExplicitInnerJS` creates explicit fields in the enclosing
+ * templates of inner JS classes to hold the JS class values, this phase
+ * creates local vals for local JS classes in the enclosing statement list.
+ *
+ * For every local JS class of the form:
+ * {{{
+ * def outer() = {
+ * class Local extends ParentJSClass
+ * }
+ * }}}
+ * this phase creates a local `val Local\$jslass` in the body of `outer()` to
+ * hold the JS class value for `Local`. The rhs of that val is a call to a
+ * magic method, used to retain information that the back-end will need:
+ *
+ * - A reified reference to `class Local`, in the form of a `classOf`
+ * - An explicit reference to the super JS class value, i.e., the desugaring
+ * of `js.constructorOf[ParentJSClass]`
+ * - An array of fake `new` expressions for all overloaded constructors.
+ *
+ * The latter will be augmented by `lambdalift` with the appropriate actual
+ * parameters for the captures of `Local`, which will be needed by the
+ * back-end. In code, this looks like:
+ * {{{
+ * def outer() = {
+ * class Local extends ParentJSClass
+ * val Local\$jsclass: AnyRef = createLocalJSClass(
+ * classOf[Local],
+ * js.constructorOf[ParentJSClass],
+ * Array[AnyRef](new Local(), ...))
+ * }
+ * }}}
+ *
+ * Since we need to insert fake `new Inner()`s, this scheme does not work for
+ * abstract local classes. We therefore reject them as implementation
+ * restriction.
+ *
+ * If the body of `Local` references itself, then the `val Local\$jsclass` is
+ * instead declared as a `var` to work around the cyclic dependency:
+ * {{{
+ * def outer() = {
+ * var Local\$jsclass: AnyRef = null
+ * class Local extends ParentJSClass {
+ * ...
+ * }
+ * Local\$jsclass = createLocalJSClass(...)
+ * }
+ * }}}
+ *
+ * In addition to the above, `ExplicitLocalJS` transforms all *call sites* of
+ * local JS classes *and* inner JS classes, so that they refer to the
+ * synthesized local vals and fields.
+ *
+ * The primary transformation is the desugaring of `js.constructorOf[C]`,
+ * which depends on the nature of `C`:
+ *
+ * - If `C` is a statically accessible class, desugar to
+ * `runtime.constructorOf(classOf[C])` so that the reified symbol survives
+ * erasure and reaches the back-end.
+ * - If `C` is an inner JS class, it must be of the form `path.D` for some
+ * pair (`path`, `D`), and we desugar it to `path.D\$jsclass`, using the
+ * field created by `ExplicitInnerJS` (it is an error if `C` is of the form
+ * `Enclosing#D`).
+ * - If `C` is a local JS class, desugar to `C\$jsclass`, using the local val
+ * created by this phase.
+ *
+ * The other transformations build on top of the desugaring of
+ * `js.constructorOf[C]`, and apply only to inner JS classes and local JS
+ * classes (not for statically accessible classes):
+ *
+ * - `x.isInstanceOf[C]` desugars into
+ * `js.special.instanceof(x, js.constructorOf[C])`.
+ * - `new C(...args)` desugars into
+ * `withContextualJSClassValue(js.constructorOf[C], new C(...args))`, so
+ * that the back-end receives a reified reference to the JS class value.
+ * - In the same spirit, for `D extends C`, `D.super.m(...args)` desugars into
+ * `withContextualJSClassValue(js.constructorOf[C], D.super.m(...args))`.
+ *
+ * Finally, for inner- and local JS *objects*, their (only) instantiation
+ * point of the form `new O.type()` is rewritten as
+ * `withContextualJSClassValue(js.constructorOf[ParentClassOfO], new O.type())`,
+ * so that the back-end receives a reified reference to the parent class of
+ * `O`. A similar treatment is applied on anonymous JS classes, which
+ * basically define something very similar to an `object`, although without
+ * its own JS class.
+ */
+abstract class ExplicitLocalJS[G <: Global with Singleton](val global: G)
+ extends plugins.PluginComponent with Transform with TypingTransformers
+ with CompatComponent {
+
+ val jsAddons: JSGlobalAddons {
+ val global: ExplicitLocalJS.this.global.type
+ }
+
+ import global._
+ import jsAddons._
+ import jsInterop.{jsclassAccessorFor, JSCallingConvention}
+ import definitions._
+ import rootMirror._
+ import jsDefinitions._
+
+ /* The missing 'e' is intentional so that the name of the phase is not longer
+ * than the longest standard phase (packageobjects/superaccessors). This
+ * avoids destroying the nice formatting of `-Xshow-phases`.
+ */
+ val phaseName: String = "xplicitlocaljs"
+
+ override def description: String =
+ "make references to local JS classes explicit"
+
+ protected def newTransformer(unit: CompilationUnit): Transformer =
+ new ExplicitLocalJSTransformer(unit)
+
+ /** Is the gen clazz an inner or local JS class? */
+ private def isInnerOrLocalJSClass(sym: Symbol): Boolean =
+ isInnerJSClass(sym) || isLocalJSClass(sym)
+
+ /** Is the given clazz an inner JS class? */
+ private def isInnerJSClass(clazz: Symbol): Boolean =
+ isInnerJSClassOrObject(clazz) && !clazz.isModuleClass
+
+ /** Is the given clazz a local JS class? */
+ private def isLocalJSClass(clazz: Symbol): Boolean = {
+ isLocalJSClassOrObject(clazz) &&
+ !clazz.isModuleClass && !clazz.isAnonymousClass
+ }
+
+ /** Is the gen clazz an inner or local JS class or object? */
+ private def isInnerOrLocalJSClassOrObject(sym: Symbol): Boolean =
+ isInnerJSClassOrObject(sym) || isLocalJSClassOrObject(sym)
+
+ /** Is the given clazz an inner JS class or object? */
+ private def isInnerJSClassOrObject(clazz: Symbol): Boolean = {
+ clazz.hasAnnotation(JSTypeAnnot) &&
+ !clazz.isPackageClass && !clazz.outerClass.isStaticOwner &&
+ !clazz.isLocalToBlock && !clazz.isTrait
+ }
+
+ /** Is the given clazz a local JS class or object? */
+ private def isLocalJSClassOrObject(clazz: Symbol): Boolean = {
+ def isJSLambda: Boolean = {
+ // See GenJSCode.isJSFunctionDef
+ clazz.isAnonymousClass &&
+ clazz.superClass == JSFunctionClass &&
+ clazz.info.decl(nme.apply).filter(JSCallingConvention.isCall(_)).exists
+ }
+
+ clazz.isLocalToBlock &&
+ !clazz.isTrait && clazz.hasAnnotation(JSTypeAnnot) &&
+ !isJSLambda
+ }
+
+ class ExplicitLocalJSTransformer(unit: CompilationUnit)
+ extends TypingTransformer(unit) {
+
+ private val nestedObject2superClassTpe = mutable.Map.empty[Symbol, Type]
+ private val localClass2jsclassVal = mutable.Map.empty[Symbol, TermSymbol]
+ private val notYetSelfReferencingLocalClasses = mutable.Set.empty[Symbol]
+
+ override def transformUnit(unit: CompilationUnit): Unit = {
+ try {
+ super.transformUnit(unit)
+ } finally {
+ nestedObject2superClassTpe.clear()
+ localClass2jsclassVal.clear()
+ notYetSelfReferencingLocalClasses.clear()
+ }
+ }
+
+ /** The main transformation method. */
+ override def transform(tree: Tree): Tree = {
+ val sym = tree.symbol
+ tree match {
+ /* Populate `nestedObject2superClassTpe` for inner objects at the start
+ * of a `Template`, so that they are visible even before their
+ * definition (in their enclosing scope).
+ */
+ case Template(_, _, decls) =>
+ for (decl <- decls) {
+ decl match {
+ case ClassDef(_, _, _, impl)
+ if decl.symbol.isModuleClass && isInnerJSClassOrObject(decl.symbol) =>
+ nestedObject2superClassTpe(decl.symbol) =
+ extractSuperTpeFromImpl(impl)
+ case _ =>
+ }
+ }
+ super.transform(tree)
+
+ // Create local `val`s for local JS classes
+ case Block(stats, expr) =>
+ val newStats = mutable.ListBuffer.empty[Tree]
+ for (stat <- stats) {
+ stat match {
+ case ClassDef(mods, name, tparams, impl) if isLocalJSClass(stat.symbol) =>
+ val clazz = stat.symbol
+ val jsclassVal = currentOwner
+ .newValue(unit.freshTermName(name.toString() + "$jsname"), stat.pos)
+ .setInfo(AnyRefTpe)
+ localClass2jsclassVal(clazz) = jsclassVal
+ notYetSelfReferencingLocalClasses += clazz
+ val newClassDef = transform(stat)
+ val rhs = {
+ val clazzValue = gen.mkClassOf(clazz.tpe_*)
+ val superClassCtor =
+ genJSConstructorOf(tree, extractSuperTpeFromImpl(impl))
+ val fakeNewInstances = {
+ val elems = for {
+ ctor <- clazz.info.decl(nme.CONSTRUCTOR).alternatives
+ } yield {
+ assert(ctor.tpe.paramss.nonEmpty,
+ s"Constructor ${ctor.fullName} has no param list")
+ val argss = ctor.tpe.paramss.map { params =>
+ List.fill(params.size)(gen.mkAttributedRef(Predef_???))
+ }
+ argss.tail.foldLeft(
+ global.NewFromConstructor(ctor, argss.head: _*))(
+ Apply(_, _))
+ }
+ typer.typed(ArrayValue(TypeTree(AnyRefTpe), elems))
+ }
+ gen.mkMethodCall(Runtime_createLocalJSClass,
+ List(clazzValue, superClassCtor, fakeNewInstances))
+ }
+ if (notYetSelfReferencingLocalClasses.remove(clazz)) {
+ newStats += newClassDef
+ newStats += localTyper.typedValDef {
+ ValDef(jsclassVal, rhs)
+ }
+ } else {
+ /* We are using `jsclassVal` inside the definition of the
+ * class. We need to declare it as var before and initialize
+ * it after the class definition.
+ */
+ jsclassVal.setFlag(Flags.MUTABLE)
+ newStats += localTyper.typedValDef {
+ ValDef(jsclassVal, Literal(gen.mkConstantZero(AnyRefTpe)))
+ }
+ newStats += newClassDef
+ newStats += localTyper.typed {
+ Assign(Ident(jsclassVal), rhs)
+ }
+ }
+
+ case ClassDef(_, _, _, impl)
+ if isLocalJSClassOrObject(stat.symbol) =>
+ nestedObject2superClassTpe(stat.symbol) =
+ extractSuperTpeFromImpl(impl)
+ newStats += transform(stat)
+
+ case _ =>
+ newStats += transform(stat)
+ }
+ }
+ val newExpr = transform(expr)
+ treeCopy.Block(tree, newStats.toList, newExpr)
+
+ /* Wrap `new`s to inner and local JS classes and objects with
+ * `withContextualJSClassValue`, to preserve a reified reference to
+ * the necessary JS class value (the class itself for classes, or the
+ * super class for objects).
+ * Anonymous classes are considered as "objects" for this purpose.
+ */
+ case Apply(sel @ Select(New(tpt), nme.CONSTRUCTOR), args)
+ if isInnerOrLocalJSClassOrObject(sel.symbol.owner) =>
+ val newCall = super.transform(tree)
+ val newTpt = transform(tpt)
+ val classSym = sel.symbol.owner
+ if (!classSym.isModuleClass && !classSym.isAnonymousClass) {
+ val jsclassValue = genJSConstructorOf(newTpt, newTpt.tpe)
+ wrapWithContextualJSClassValue(jsclassValue) {
+ newCall
+ }
+ } else {
+ wrapWithContextualJSClassValue(nestedObject2superClassTpe(classSym)) {
+ newCall
+ }
+ }
+
+ /* Wrap `super` calls to inner and local JS classes with
+ * `withContextualJSClassValue`, to preserve a reified reference to the
+ * necessary JS class value (that of the super class).
+ */
+ case Apply(fun @ Select(sup: Super, _), _)
+ if !fun.symbol.isConstructor &&
+ isInnerOrLocalJSClass(sup.symbol.superClass) =>
+ wrapWithContextualSuperJSClassValue(sup.symbol.superClass) {
+ super.transform(tree)
+ }
+
+ // Same for a super call with type parameters
+ case Apply(TypeApply(fun @ Select(sup: Super, _), _), _)
+ if !fun.symbol.isConstructor &&
+ isInnerOrLocalJSClass(sup.symbol.superClass) =>
+ wrapWithContextualSuperJSClassValue(sup.symbol.superClass) {
+ super.transform(tree)
+ }
+
+ // Translate js.constructorOf[T]
+ case Apply(TypeApply(ctorOfTree, List(tpeArg)), Nil)
+ if ctorOfTree.symbol == JSPackage_constructorOf =>
+ val newTpeArg = transform(tpeArg)
+ gen.mkAttributedCast(genJSConstructorOf(tree, newTpeArg.tpe),
+ JSDynamicClass.tpe)
+
+ // Translate x.isInstanceOf[T] for inner and local JS classes
+ case Apply(TypeApply(fun @ Select(obj, _), List(tpeArg)), Nil)
+ if fun.symbol == Any_isInstanceOf &&
+ isInnerOrLocalJSClass(tpeArg.tpe.typeSymbol) =>
+ val newObj = transform(obj)
+ val newTpeArg = transform(tpeArg)
+ val jsCtorOf = genJSConstructorOf(tree, newTpeArg.tpe)
+ atPos(tree.pos) {
+ localTyper.typed {
+ gen.mkMethodCall(Special_instanceof, List(newObj, jsCtorOf))
+ }
+ }
+
+ case _ =>
+ super.transform(tree)
+ }
+ }
+
+ /** Generates the desugared version of `js.constructorOf[tpe]`.
+ */
+ private def genJSConstructorOf(tree: Tree, tpe: Type): Tree = {
+ val clazz = tpe.typeSymbol
+
+ // This should not have passed the checks in PrepJSInterop
+ assert(!clazz.isTrait && !clazz.isModuleClass,
+ s"non-trait class type required but $tpe found for " +
+ s"genJSConstructorOf at ${tree.pos}")
+
+ localTyper.typed {
+ atPos(tree.pos) {
+ if (isInnerJSClass(clazz)) {
+ // Use the $jsclass field in the outer instance
+ val prefix = tpe.prefix match {
+ case NoPrefix => clazz.outerClass.thisType
+ case x => x
+ }
+ if (prefix.isStable) {
+ val qual = gen.mkAttributedQualifier(prefix)
+ gen.mkAttributedSelect(qual, jsclassAccessorFor(clazz))
+ } else {
+ reporter.error(tree.pos,
+ s"stable reference to a JS class required but $tpe found")
+ gen.mkAttributedRef(Predef_???)
+ }
+ } else if (isLocalJSClass(clazz)) {
+ // Use the local `val` that stores the JS class value
+ val jsclassVal = localClass2jsclassVal(clazz)
+ notYetSelfReferencingLocalClasses.remove(clazz)
+ gen.mkAttributedIdent(jsclassVal)
+ } else {
+ // Defer translation to `LoadJSConstructor` to the back-end
+ val classValue = gen.mkClassOf(tpe)
+ gen.mkMethodCall(Runtime_constructorOf, List(classValue))
+ }
+ }
+ }
+ }
+
+ /** Wraps with the contextual super JS class value for super calls. */
+ private def wrapWithContextualSuperJSClassValue(superClass: Symbol)(
+ tree: Tree): Tree = {
+ /* #4801 We need to interpret the superClass type as seen from the
+ * current class' thisType.
+ *
+ * For example, in the test NestedJSClassTest.extendInnerJSClassInClass,
+ * the original `superClass.tpe_*` is
+ *
+ * OuterNativeClass_Issue4402.this.InnerClass
+ *
+ * because `InnerClass` is path-dependent. However, the path
+ * `OuterNativeClass.this` is only valid within `OuterNativeClass`
+ * itself. In the context of the current local class `Subclass`, this
+ * path must be replaced by the actual path `outer.`. This is precisely
+ * the role of `asSeenFrom`. We tell it to replace any `superClass.this`
+ * by `currentClass.this`, and it also transitively replaces paths for
+ * outer classes of `superClass`, matching them with the corresponding
+ * outer paths of `currentClass.thisType` if necessary. The result for
+ * that test case is
+ *
+ * outer.InnerClass
+ */
+ val jsClassTypeInSuperClass = superClass.tpe_*
+ val jsClassTypeAsSeenFromThis =
+ jsClassTypeInSuperClass.asSeenFrom(currentClass.thisType, superClass)
+
+ wrapWithContextualJSClassValue(jsClassTypeAsSeenFromThis) {
+ tree
+ }
+ }
+
+ private def wrapWithContextualJSClassValue(jsClassType: Type)(
+ tree: Tree): Tree = {
+ wrapWithContextualJSClassValue(genJSConstructorOf(tree, jsClassType)) {
+ tree
+ }
+ }
+
+ private def wrapWithContextualJSClassValue(jsClassValue: Tree)(
+ tree: Tree): Tree = {
+ atPos(tree.pos) {
+ localTyper.typed {
+ gen.mkMethodCall(
+ Runtime_withContextualJSClassValue,
+ List(tree.tpe),
+ List(jsClassValue, tree))
+ }
+ }
+ }
+
+ }
+
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala
new file mode 100644
index 0000000000..dc1348ea22
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala
@@ -0,0 +1,7428 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.language.implicitConversions
+
+import scala.annotation.switch
+
+import scala.collection.mutable
+import scala.collection.mutable.ListBuffer
+
+import scala.tools.nsc._
+
+import scala.annotation.tailrec
+
+import scala.reflect.internal.Flags
+
+import org.scalajs.ir
+import org.scalajs.ir.{
+ Trees => js,
+ Types => jstpe,
+ WellKnownNames => jswkn,
+ ClassKind,
+ Hashers,
+ OriginalName
+}
+import org.scalajs.ir.Names.{
+ LocalName,
+ LabelName,
+ SimpleFieldName,
+ FieldName,
+ SimpleMethodName,
+ MethodName,
+ ClassName
+}
+import org.scalajs.ir.OriginalName.NoOriginalName
+import org.scalajs.ir.Trees.OptimizerHints
+import org.scalajs.ir.Version.Unversioned
+
+import org.scalajs.nscplugin.util.{ScopedVar, VarBox}
+import ScopedVar.withScopedVars
+
+/** Generate JavaScript code and output it to disk
+ *
+ * @author Sébastien Doeraene
+ */
+abstract class GenJSCode[G <: Global with Singleton](val global: G)
+ extends plugins.PluginComponent with TypeConversions[G] with JSEncoding[G]
+ with GenJSExports[G] with GenJSFiles[G] with CompatComponent {
+
+ import GenJSCode._
+
+ /** Not for use in the constructor body: only initialized afterwards. */
+ val jsAddons: JSGlobalAddons {
+ val global: GenJSCode.this.global.type
+ }
+
+ /** Not for use in the constructor body: only initialized afterwards. */
+ val scalaJSOpts: ScalaJSOptions
+
+ import global._
+ import jsAddons._
+ import rootMirror._
+ import definitions._
+ import jsDefinitions._
+ import jsInterop.{jsNameOf, jsNativeLoadSpecOfOption, JSName, JSCallingConvention}
+
+ import treeInfo.{hasSynthCaseSymbol, StripCast}
+
+ import platform.isMaybeBoxed
+
+ val phaseName: String = "jscode"
+ override val description: String = "generate JavaScript code from ASTs"
+
+ /** testing: this will be called for each generated `ClassDef`. */
+ def generatedJSAST(clDef: js.ClassDef): Unit
+
+ /** Implicit conversion from nsc Position to ir.Position. */
+ implicit def pos2irPos(pos: Position): ir.Position = {
+ if (pos == NoPosition) ir.Position.NoPosition
+ else {
+ val source = pos2irPosCache.toIRSource(pos.source)
+ // nsc positions are 1-based but IR positions are 0-based
+ ir.Position(source, pos.line-1, pos.column-1)
+ }
+ }
+
+ private[this] object pos2irPosCache {
+ import scala.reflect.internal.util._
+
+ private[this] var lastNscSource: SourceFile = null
+ private[this] var lastIRSource: ir.Position.SourceFile = null
+
+ def toIRSource(nscSource: SourceFile): ir.Position.SourceFile = {
+ if (nscSource != lastNscSource) {
+ lastIRSource = convert(nscSource)
+ lastNscSource = nscSource
+ }
+ lastIRSource
+ }
+
+ private[this] def convert(nscSource: SourceFile): ir.Position.SourceFile = {
+ nscSource.file.file match {
+ case null =>
+ new java.net.URI(
+ "virtualfile", // Pseudo-Scheme
+ nscSource.file.path, // Scheme specific part
+ null // Fragment
+ )
+ case file =>
+ val srcURI = file.toURI
+ def matches(pat: java.net.URI) = pat.relativize(srcURI) != srcURI
+
+ scalaJSOpts.sourceURIMaps.collectFirst {
+ case ScalaJSOptions.URIMap(from, to) if matches(from) =>
+ val relURI = from.relativize(srcURI)
+ to.fold(relURI)(_.resolve(relURI))
+ } getOrElse srcURI
+ }
+ }
+
+ def clear(): Unit = {
+ lastNscSource = null
+ lastIRSource = null
+ }
+ }
+
+ /** Materialize implicitly an ir.Position from an implicit nsc Position. */
+ implicit def implicitPos2irPos(implicit pos: Position): ir.Position = pos
+
+ override def newPhase(p: Phase): StdPhase = new JSCodePhase(p)
+
+ object jsnme {
+ val anyHash = newTermName("anyHash")
+ val arg_outer = newTermName("arg$outer")
+ }
+
+ private sealed abstract class EnclosingLabelDefInfo {
+ var generatedReturns: Int = 0
+ }
+
+ private final class EnclosingLabelDefInfoWithResultAsReturn()
+ extends EnclosingLabelDefInfo
+
+ private final class EnclosingLabelDefInfoWithResultAsAssigns(
+ val paramSyms: List[Symbol])
+ extends EnclosingLabelDefInfo
+
+ class JSCodePhase(prev: Phase) extends StdPhase(prev) with JSExportsPhase {
+
+ override def name: String = phaseName
+ override def description: String = GenJSCode.this.description
+
+ // Scoped state ------------------------------------------------------------
+ // Per class body
+ val currentClassSym = new ScopedVar[Symbol]
+ private val fieldsMutatedInCurrentClass = new ScopedVar[mutable.Set[Name]]
+ private val generatedSAMWrapperCount = new ScopedVar[VarBox[Int]]
+ private val delambdafyTargetDefDefs = new ScopedVar[mutable.Map[Symbol, DefDef]]
+ private val methodsAllowingJSAwait = new ScopedVar[mutable.Set[Symbol]]
+
+ def currentThisTypeNullable: jstpe.Type =
+ encodeClassType(currentClassSym)
+
+ def currentThisType: jstpe.Type = {
+ currentThisTypeNullable match {
+ case tpe @ jstpe.ClassType(cls, _) =>
+ jswkn.BoxedClassToPrimType.getOrElse(cls, tpe.toNonNullable)
+ case tpe @ jstpe.AnyType =>
+ // We are in a JS class, in which even `this` is nullable
+ tpe
+ case tpe =>
+ throw new AssertionError(
+ s"Unexpected IR this type $tpe for class ${currentClassSym.get}")
+ }
+ }
+
+ // Per method body
+ private val currentMethodSym = new ScopedVar[Symbol]
+ private val thisLocalVarName = new ScopedVar[Option[LocalName]]
+ private val enclosingLabelDefInfos = new ScopedVar[Map[Symbol, EnclosingLabelDefInfo]]
+ private val isModuleInitialized = new ScopedVar[VarBox[Boolean]]
+ private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]]
+ private val mutableLocalVars = new ScopedVar[mutable.Set[Symbol]]
+ private val mutatedLocalVars = new ScopedVar[mutable.Set[Symbol]]
+
+ private def withPerMethodBodyState[A](methodSym: Symbol,
+ initThisLocalVarName: Option[LocalName] = None)(body: => A): A = {
+
+ withScopedVars(
+ currentMethodSym := methodSym,
+ thisLocalVarName := initThisLocalVarName,
+ enclosingLabelDefInfos := Map.empty,
+ isModuleInitialized := new VarBox(false),
+ undefinedDefaultParams := mutable.Set.empty,
+ mutableLocalVars := mutable.Set.empty,
+ mutatedLocalVars := mutable.Set.empty
+ ) {
+ body
+ }
+ }
+
+ // For anonymous methods
+ // It has a default, since we always read it.
+ private val paramAccessorLocals = new ScopedVar(Map.empty[Symbol, js.ParamDef])
+
+ /* Contextual JS class value for some operations of nested JS classes that
+ * need one.
+ */
+ private val contextualJSClassValue =
+ new ScopedVar[Option[js.Tree]](None)
+
+ private def acquireContextualJSClassValue[A](f: Option[js.Tree] => A): A = {
+ val jsClassValue = contextualJSClassValue.get
+ withScopedVars(
+ contextualJSClassValue := None
+ ) {
+ f(jsClassValue)
+ }
+ }
+
+ // Rewriting of anonymous function classes ---------------------------------
+
+ /** Start nested generation of a class.
+ *
+ * Fully resets the scoped state (including local name scope).
+ * Allows to generate an anonymous class as needed.
+ */
+ private def nestedGenerateClass[T](clsSym: Symbol)(body: => T): T = {
+ withScopedVars(
+ currentClassSym := clsSym,
+ fieldsMutatedInCurrentClass := mutable.Set.empty,
+ generatedSAMWrapperCount := new VarBox(0),
+ delambdafyTargetDefDefs := mutable.Map.empty,
+ methodsAllowingJSAwait := mutable.Set.empty,
+ currentMethodSym := null,
+ thisLocalVarName := null,
+ enclosingLabelDefInfos := null,
+ isModuleInitialized := null,
+ undefinedDefaultParams := null,
+ mutableLocalVars := null,
+ mutatedLocalVars := null,
+ paramAccessorLocals := Map.empty
+ )(withNewLocalNameScope(body))
+ }
+
+ // Global state for tracking methods that reference `this` -----------------
+
+ /* scalac generates private instance methods for
+ * a) local defs and
+ * b) anonymous functions.
+ *
+ * In many cases, these methods do not need access to the enclosing `this`
+ * value. For every other local variable, `lambdalift` only adds them as
+ * arguments when they are needed; but `this` is always implicitly added
+ * because all such methods are instance methods.
+ *
+ * We run a separate analysis to figure out which of those methods actually
+ * need their `this` value. We compile those that do not as static methods,
+ * as if they were `isStaticMember`.
+ *
+ * The `delambdafy` phase of scalac performs a similar analysis, although
+ * as it runs after our backend (for other reasons), we do not benefit from
+ * it. Our analysis is much simpler because it deals with local defs and
+ * anonymous functions in a unified way.
+ *
+ * Performing this analysis is particularly important for lifted methods
+ * that appear within arguments to a super constructor call. The lexical
+ * scope of Scala guarantees that they cannot use `this`, but if they are
+ * compiled as instance methods, they force the constructor to leak the
+ * `this` value before the super constructor call, which is invalid.
+ * While most of the time this analysis is only an optimization, in those
+ * (rare) situations it is required for correctness. See for example
+ * the partest `run/t8733.scala`.
+ */
+
+ private var statifyCandidateMethodsThatReferenceThis: scala.collection.Set[Symbol] = null
+
+ /** Is that given method a statify-candidate?
+ *
+ * If a method is a statify-candidate, we will analyze whether it actually
+ * needs its `this` value. If it does not, we will compile it as a static
+ * method in the IR.
+ *
+ * A method is a statify-candidate if it is a lifted method (a local def)
+ * or the target of an anonymous function.
+ *
+ * TODO Currently we also require that the method owner not be a JS type.
+ * We should relax that restriction in the future.
+ */
+ private def isStatifyCandidate(sym: Symbol): Boolean =
+ (sym.isLiftedMethod || sym.isDelambdafyTarget) && !isJSType(sym.owner)
+
+ /** Do we compile the given method as a static method in the IR?
+ *
+ * This is true if one of the following is true:
+ *
+ * - It is `isStaticMember`, or
+ * - It is a statify-candidate method and it does not reference `this`.
+ */
+ private def compileAsStaticMethod(sym: Symbol): Boolean = {
+ sym.isStaticMember || {
+ isStatifyCandidate(sym) &&
+ !statifyCandidateMethodsThatReferenceThis.contains(sym)
+ }
+ }
+
+ /** Finds all statify-candidate methods that reference `this`, inspired by Delambdafy. */
+ private final class ThisReferringMethodsTraverser extends Traverser {
+ // the set of statify-candidate methods that directly refer to `this`
+ private val roots = mutable.Set.empty[Symbol]
+
+ // for each statify-candidate method `m`, the set of statify-candidate methods that call `m`
+ private val methodReferences = mutable.Map.empty[Symbol, mutable.Set[Symbol]]
+
+ def methodReferencesThisIn(tree: Tree): collection.Set[Symbol] = {
+ traverse(tree)
+ propagateReferences()
+ }
+
+ private def addRoot(symbol: Symbol): Unit =
+ roots += symbol
+
+ private def addReference(from: Symbol, to: Symbol): Unit =
+ methodReferences.getOrElseUpdate(to, mutable.Set.empty) += from
+
+ private def propagateReferences(): collection.Set[Symbol] = {
+ val result = mutable.Set.empty[Symbol]
+
+ def rec(symbol: Symbol): Unit = {
+ // mark `symbol` as referring to `this`
+ if (result.add(symbol)) {
+ // `symbol` was not yet in the set; propagate further
+ methodReferences.getOrElse(symbol, Nil).foreach(rec(_))
+ }
+ }
+
+ roots.foreach(rec(_))
+
+ result
+ }
+
+ private var currentMethod: Symbol = NoSymbol
+
+ override def traverse(tree: Tree): Unit = tree match {
+ case _: DefDef =>
+ if (isStatifyCandidate(tree.symbol)) {
+ currentMethod = tree.symbol
+ super.traverse(tree)
+ currentMethod = NoSymbol
+ } else {
+ // No need to traverse other methods; we always assume they refer to `this`.
+ }
+ case Function(_, Apply(target, _)) =>
+ /* We don't drill into functions because at this phase they will always refer to `this`.
+ * They will be of the form {(args...) => this.anonfun(args...)}
+ * but we do need to make note of the lifted body method in case it refers to `this`.
+ */
+ if (currentMethod.exists)
+ addReference(from = currentMethod, to = target.symbol)
+ case Apply(sel @ Select(This(_), _), args) if isStatifyCandidate(sel.symbol) =>
+ if (currentMethod.exists)
+ addReference(from = currentMethod, to = sel.symbol)
+ super.traverseTrees(args)
+ case This(_) =>
+ // Note: tree.symbol != enclClass is possible if it is a module loaded with genLoadModule
+ if (currentMethod.exists && tree.symbol == currentMethod.enclClass)
+ addRoot(currentMethod)
+ case _ =>
+ super.traverse(tree)
+ }
+ }
+
+ // Global class generation state -------------------------------------------
+
+ private val lazilyGeneratedAnonClasses = mutable.Map.empty[Symbol, ClassDef]
+ private val generatedClasses = ListBuffer.empty[(js.ClassDef, Position)]
+ private val generatedStaticForwarderClasses = ListBuffer.empty[(Symbol, js.ClassDef)]
+
+ private def consumeLazilyGeneratedAnonClass(sym: Symbol): ClassDef = {
+ lazilyGeneratedAnonClasses.remove(sym).getOrElse {
+ abort("Couldn't find tree for lazily generated anonymous class " +
+ s"${sym.fullName} at ${sym.pos}")
+ }
+ }
+
+ // Top-level apply ---------------------------------------------------------
+
+ override def run(): Unit = {
+ scalaPrimitives.init()
+ genBCode.bTypes.initializeCoreBTypes()
+ jsPrimitives.init()
+ super.run()
+ }
+
+ /** Generates the Scala.js IR for a compilation unit
+ * This method iterates over all the class and interface definitions
+ * found in the compilation unit and emits their IR (.sjsir).
+ *
+ * Some classes are never actually emitted:
+ * - Classes representing primitive types
+ * - The scala.Array class
+ * - Implementation classes for JS traits
+ *
+ * Some classes representing anonymous functions are not actually emitted.
+ * Instead, a temporary representation of their `apply` method is built
+ * and recorded, so that it can be inlined as a JavaScript anonymous
+ * function in the method that instantiates it.
+ *
+ * Other ClassDefs are emitted according to their nature:
+ * * Non-native JS class -> `genNonNativeJSClass()`
+ * * Other JS type (<: js.Any) -> `genJSClassData()`
+ * * Interface -> `genInterface()`
+ * * Normal class -> `genClass()`
+ */
+ override def apply(cunit: CompilationUnit): Unit = {
+ try {
+ statifyCandidateMethodsThatReferenceThis =
+ new ThisReferringMethodsTraverser().methodReferencesThisIn(cunit.body)
+
+ def collectClassDefs(tree: Tree): List[ClassDef] = {
+ tree match {
+ case EmptyTree => Nil
+ case PackageDef(_, stats) => stats flatMap collectClassDefs
+ case cd: ClassDef => cd :: Nil
+ }
+ }
+ val allClassDefs = collectClassDefs(cunit.body)
+
+ /* There are two types of anonymous classes we want to generate only
+ * once we need them, so we can inline them at construction site:
+ *
+ * - Lambdas for `js.Function` SAMs, including `js.FunctionN`,
+ * `js.ThisFunctionN` and custom JS function SAMs. We must generate
+ * JS functions for these, instead of actual classes.
+ * - Anonymous (non-lambda) JS classes. These classes may not have
+ * their own prototype. Therefore, their constructor *must* be
+ * inlined.
+ *
+ * Since for all these, we don't know how they inter-depend, we just
+ * store them in a map at this point.
+ */
+ val (lazyAnons, fullClassDefs) = allClassDefs.partition { cd =>
+ val sym = cd.symbol
+ isAnonymousJSClass(sym) || isJSFunctionDef(sym)
+ }
+
+ lazilyGeneratedAnonClasses ++= lazyAnons.map(cd => cd.symbol -> cd)
+
+ /* Finally, we emit true code for the remaining class defs. */
+ for (cd <- fullClassDefs) {
+ val sym = cd.symbol
+ implicit val pos = sym.pos
+
+ /* Do not actually emit code for primitive types nor scala.Array. */
+ val isPrimitive =
+ isPrimitiveValueClass(sym) || (sym == ArrayClass)
+
+ if (!isPrimitive) {
+ withScopedVars(
+ currentClassSym := sym,
+ fieldsMutatedInCurrentClass := mutable.Set.empty,
+ generatedSAMWrapperCount := new VarBox(0),
+ delambdafyTargetDefDefs := mutable.Map.empty,
+ methodsAllowingJSAwait := mutable.Set.empty
+ ) {
+ val tree = if (isJSType(sym)) {
+ if (!sym.isTraitOrInterface && isNonNativeJSClass(sym) &&
+ !isJSFunctionDef(sym)) {
+ genNonNativeJSClass(cd)
+ } else {
+ genJSClassData(cd)
+ }
+ } else if (sym.isTraitOrInterface) {
+ genInterface(cd)
+ } else {
+ genClass(cd)
+ }
+
+ generatedClasses += tree -> sym.pos
+ }
+ }
+ }
+
+ val clDefs: List[(js.ClassDef, Position)] = if (generatedStaticForwarderClasses.isEmpty) {
+ /* Fast path, applicable under -Xno-forwarders, as well as when all
+ * the `object`s of a compilation unit have a companion class.
+ */
+ generatedClasses.toList
+ } else {
+ val regularClasses = generatedClasses.toList
+
+ /* #4148 Add generated static forwarder classes, except those that
+ * would collide with regular classes on case insensitive file
+ * systems.
+ */
+
+ /* I could not find any reference anywhere about what locale is used
+ * by case insensitive file systems to compare case-insensitively.
+ * In doubt, force the English locale, which is probably going to do
+ * the right thing in virtually all cases (especially if users stick
+ * to ASCII class names), and it has the merit of being deterministic,
+ * as opposed to using the OS' default locale.
+ * The JVM backend performs a similar test to emit a warning for
+ * conflicting top-level classes. However, it uses `toLowerCase()`
+ * without argument, which is not deterministic.
+ */
+ def caseInsensitiveNameOf(classDef: js.ClassDef): String =
+ classDef.name.name.nameString.toLowerCase(java.util.Locale.ENGLISH)
+
+ val generatedCaseInsensitiveNames =
+ regularClasses.map(pair => caseInsensitiveNameOf(pair._1)).toSet
+ val staticForwarderClasses = generatedStaticForwarderClasses.toList
+ .withFilter { case (site, classDef) =>
+ if (!generatedCaseInsensitiveNames.contains(caseInsensitiveNameOf(classDef))) {
+ true
+ } else {
+ global.runReporting.warning(
+ site.pos,
+ s"Not generating the static forwarders of ${classDef.name.name.nameString} " +
+ "because its name differs only in case from the name of another class or " +
+ "trait in this compilation unit.",
+ WarningCategory.Other,
+ site)
+ false
+ }
+ }
+ .map(pair => (pair._2, pair._1.pos))
+
+ regularClasses ::: staticForwarderClasses
+ }
+
+ for ((classDef, pos) <- clDefs) {
+ try {
+ val hashedClassDef = Hashers.hashClassDef(classDef)
+ generatedJSAST(hashedClassDef)
+ genIRFile(cunit, hashedClassDef)
+ } catch {
+ case e: ir.InvalidIRException =>
+ e.optTree match {
+ case Some(tree @ ir.Trees.Transient(UndefinedParam)) =>
+ reporter.error(pos,
+ "Found a dangling UndefinedParam at " +
+ s"${tree.pos}. This is likely due to a bad " +
+ "interaction between a macro or a compiler plugin " +
+ "and the Scala.js compiler plugin. If you hit " +
+ "this, please let us know.")
+
+ case Some(tree) =>
+ reporter.error(pos,
+ "The Scala.js compiler generated invalid IR for " +
+ "this class. Please report this as a bug. IR: " +
+ tree)
+
+ case None =>
+ reporter.error(pos,
+ "The Scala.js compiler generated invalid IR for this class. " +
+ "Please report this as a bug. " +
+ e.getMessage())
+ }
+ }
+ }
+ } catch {
+ // Handle exceptions in exactly the same way as the JVM backend
+ case ex: InterruptedException =>
+ throw ex
+ case ex: Throwable =>
+ if (settings.debug.value)
+ ex.printStackTrace()
+ globalError(s"Error while emitting ${cunit.source}\n${ex.getMessage}")
+ } finally {
+ lazilyGeneratedAnonClasses.clear()
+ generatedStaticForwarderClasses.clear()
+ generatedClasses.clear()
+ statifyCandidateMethodsThatReferenceThis = null
+ pos2irPosCache.clear()
+ }
+ }
+
+ private def collectDefDefs(impl: Template): List[DefDef] = {
+ val b = List.newBuilder[DefDef]
+
+ for (stat <- impl.body) {
+ stat match {
+ case stat: DefDef =>
+ if (stat.symbol.isDelambdafyTarget)
+ delambdafyTargetDefDefs += stat.symbol -> stat
+ else
+ b += stat
+
+ case EmptyTree | _:ValDef =>
+ ()
+
+ case _ =>
+ abort(s"Unexpected tree in template: $stat at ${stat.pos}")
+ }
+ }
+
+ b.result()
+ }
+
+ private def consumeDelambdafyTarget(sym: Symbol): DefDef = {
+ delambdafyTargetDefDefs.remove(sym).getOrElse {
+ abort(s"Cannot resolve delambdafy target $sym at ${sym.pos}")
+ }
+ }
+
+ // Generate a class --------------------------------------------------------
+
+ /** Gen the IR ClassDef for a class definition (maybe a module class).
+ */
+ def genClass(cd: ClassDef): js.ClassDef = {
+ val ClassDef(mods, name, _, impl) = cd
+ val sym = cd.symbol
+ implicit val pos = sym.pos
+
+ assert(!sym.isTraitOrInterface,
+ "genClass() must be called only for normal classes: "+sym)
+ assert(sym.superClass != NoSymbol, sym)
+
+ if (hasDefaultCtorArgsAndJSModule(sym)) {
+ reporter.error(pos,
+ "Implementation restriction: constructors of " +
+ "Scala classes cannot have default parameters " +
+ "if their companion module is JS native.")
+ }
+
+ val classIdent = encodeClassNameIdent(sym)
+ val originalName = originalNameOfClass(sym)
+ val isHijacked = isHijackedClass(sym)
+
+ // Optimizer hints
+
+ val isDynamicImportThunk = sym.isSubClass(DynamicImportThunkClass)
+
+ def isStdLibClassWithAdHocInlineAnnot(sym: Symbol): Boolean = {
+ val fullName = sym.fullName
+ (fullName.startsWith("scala.Tuple") && !fullName.endsWith("$")) ||
+ (fullName.startsWith("scala.collection.mutable.ArrayOps$of"))
+ }
+
+ val shouldMarkInline = (
+ isDynamicImportThunk ||
+ sym.hasAnnotation(InlineAnnotationClass) ||
+ (sym.isAnonymousFunction && !sym.isSubClass(PartialFunctionClass)) ||
+ isStdLibClassWithAdHocInlineAnnot(sym))
+
+ val optimizerHints =
+ OptimizerHints.empty.
+ withInline(shouldMarkInline).
+ withNoinline(sym.hasAnnotation(NoinlineAnnotationClass))
+
+ // Generate members (constructor + methods)
+
+ val methodsBuilder = List.newBuilder[js.MethodDef]
+ val jsNativeMembersBuilder = List.newBuilder[js.JSNativeMemberDef]
+
+ for (dd <- collectDefDefs(impl)) {
+ if (dd.symbol.hasAnnotation(JSNativeAnnotation))
+ jsNativeMembersBuilder += genJSNativeMemberDef(dd)
+ else
+ methodsBuilder ++= genMethod(dd)
+ }
+
+ val fields = if (!isHijacked) genClassFields(cd) else Nil
+
+ val jsNativeMembers = jsNativeMembersBuilder.result()
+ val generatedMethods = methodsBuilder.result()
+
+ val memberExports = genMemberExports(sym)
+
+ val topLevelExportDefs = genTopLevelExports(sym)
+
+ // Static initializer
+ val optStaticInitializer = {
+ // Initialization of reflection data, if required
+ val reflectInit = {
+ val enableReflectiveInstantiation = {
+ (sym :: sym.ancestors).exists { ancestor =>
+ ancestor.hasAnnotation(EnableReflectiveInstantiationAnnotation)
+ }
+ }
+ if (enableReflectiveInstantiation)
+ genRegisterReflectiveInstantiation(sym)
+ else
+ None
+ }
+
+ // Initialization of the module because of field exports
+ val needsStaticModuleInit =
+ topLevelExportDefs.exists(_.isInstanceOf[js.TopLevelFieldExportDef])
+ val staticModuleInit =
+ if (!needsStaticModuleInit) None
+ else Some(genLoadModule(sym))
+
+ val staticInitializerStats =
+ reflectInit.toList ::: staticModuleInit.toList
+ if (staticInitializerStats.nonEmpty) {
+ List(genStaticConstructorWithStats(
+ jswkn.StaticInitializerName,
+ js.Block(staticInitializerStats)))
+ } else {
+ Nil
+ }
+ }
+
+ val optDynamicImportForwarder =
+ if (isDynamicImportThunk) List(genDynamicImportForwarder(sym))
+ else Nil
+
+ val allMethodsExceptStaticForwarders: List[js.MethodDef] =
+ generatedMethods ::: optStaticInitializer ::: optDynamicImportForwarder
+
+ // Add static forwarders
+ val allMethods = if (!isCandidateForForwarders(sym)) {
+ allMethodsExceptStaticForwarders
+ } else {
+ if (sym.isModuleClass) {
+ /* If the module class has no linked class, we must create one to
+ * hold the static forwarders. Otherwise, this is going to be handled
+ * when generating the companion class.
+ */
+ if (!sym.linkedClassOfClass.exists) {
+ val forwarders = genStaticForwardersFromModuleClass(Nil, sym)
+ if (forwarders.nonEmpty) {
+ val forwardersClassDef = js.ClassDef(
+ js.ClassIdent(ClassName(classIdent.name.nameString.stripSuffix("$"))),
+ originalName,
+ ClassKind.Class,
+ None,
+ Some(js.ClassIdent(jswkn.ObjectClass)),
+ Nil,
+ None,
+ None,
+ fields = Nil,
+ methods = forwarders,
+ jsConstructor = None,
+ jsMethodProps = Nil,
+ jsNativeMembers = Nil,
+ topLevelExportDefs = Nil
+ )(js.OptimizerHints.empty)
+ generatedStaticForwarderClasses += sym -> forwardersClassDef
+ }
+ }
+ allMethodsExceptStaticForwarders
+ } else {
+ val forwarders = genStaticForwardersForClassOrInterface(
+ allMethodsExceptStaticForwarders, sym)
+ allMethodsExceptStaticForwarders ::: forwarders
+ }
+ }
+
+ // The complete class definition
+ val kind =
+ if (isStaticModule(sym)) ClassKind.ModuleClass
+ else if (isHijacked) ClassKind.HijackedClass
+ else ClassKind.Class
+
+ js.ClassDef(
+ classIdent,
+ originalName,
+ kind,
+ None,
+ Some(encodeClassNameIdent(sym.superClass)),
+ genClassInterfaces(sym, forJSClass = false),
+ None,
+ None,
+ fields,
+ allMethods,
+ jsConstructor = None,
+ memberExports,
+ jsNativeMembers,
+ topLevelExportDefs)(
+ optimizerHints)
+ }
+
+ /** Gen the IR ClassDef for a non-native JS class. */
+ def genNonNativeJSClass(cd: ClassDef): js.ClassDef = {
+ val sym = cd.symbol
+ implicit val pos = sym.pos
+
+ assert(isNonNativeJSClass(sym),
+ "genNonNativeJSClass() must be called only for " +
+ s"non-native JS classes: $sym")
+ assert(sym.superClass != NoSymbol, sym)
+
+ if (hasDefaultCtorArgsAndJSModule(sym)) {
+ reporter.error(pos,
+ "Implementation restriction: constructors of " +
+ "non-native JS classes cannot have default parameters " +
+ "if their companion module is JS native.")
+ }
+
+ val classIdent = encodeClassNameIdent(sym)
+
+ // Generate members (constructor + methods)
+
+ val constructorTrees = new ListBuffer[DefDef]
+ val generatedMethods = new ListBuffer[js.MethodDef]
+ val dispatchMethodNames = new ListBuffer[JSName]
+
+ for (dd <- collectDefDefs(cd.impl)) {
+ val sym = dd.symbol
+ val exposed = isExposed(sym)
+
+ if (sym.isClassConstructor) {
+ constructorTrees += dd
+ } else if (exposed && sym.isAccessor && !sym.isLazy) {
+ /* Exposed accessors must not be emitted, since the field they
+ * access is enough.
+ */
+ } else if (sym.hasAnnotation(JSOptionalAnnotation)) {
+ // Optional methods must not be emitted
+ } else {
+ generatedMethods ++= genMethod(dd)
+
+ // Collect the names of the dispatchers we have to create
+ if (exposed && !sym.isDeferred) {
+ /* We add symbols that we have to expose here. This way we also
+ * get inherited stuff that is implemented in this class.
+ */
+ dispatchMethodNames += jsNameOf(sym)
+ }
+ }
+ }
+
+ // Static members (exported from the companion object)
+ val (staticFields, staticExports) = {
+ /* Phase travel is necessary for non-top-level classes, because flatten
+ * breaks their companionModule. This is tracked upstream at
+ * https://github.com/scala/scala-dev/issues/403
+ */
+ val companionModuleClass =
+ exitingPhase(currentRun.picklerPhase)(sym.linkedClassOfClass)
+ if (companionModuleClass == NoSymbol) {
+ (Nil, Nil)
+ } else {
+ val (staticFields, staticExports) = {
+ withScopedVars(currentClassSym := companionModuleClass) {
+ genStaticExports(companionModuleClass)
+ }
+ }
+
+ if (staticFields.nonEmpty) {
+ generatedMethods += genStaticConstructorWithStats(
+ jswkn.ClassInitializerName, genLoadModule(companionModuleClass))
+ }
+
+ (staticFields, staticExports)
+ }
+ }
+
+ val topLevelExports = genTopLevelExports(sym)
+
+ val (generatedCtor, jsClassCaptures) = withNewLocalNameScope {
+ val isNested = isNestedJSClass(sym)
+
+ if (isNested)
+ reserveLocalName(JSSuperClassParamName)
+
+ val (captures, ctor) =
+ genJSClassCapturesAndConstructor(constructorTrees.toList)
+
+ val jsClassCaptures = {
+ if (isNested) {
+ val superParam = js.ParamDef(
+ js.LocalIdent(JSSuperClassParamName),
+ NoOriginalName, jstpe.AnyType, mutable = false)
+
+ Some(superParam :: captures)
+ } else {
+ assert(captures.isEmpty,
+ s"found non nested JS class with captures $captures at $pos")
+ None
+ }
+ }
+
+ (ctor, jsClassCaptures)
+ }
+
+ // Generate fields (and add to methods + ctors)
+ val fields = genClassFields(cd)
+
+ val jsMethodProps =
+ genJSClassDispatchers(sym, dispatchMethodNames.result().distinct) ::: staticExports
+
+ // The complete class definition
+ val kind =
+ if (isStaticModule(sym)) ClassKind.JSModuleClass
+ else ClassKind.JSClass
+
+ js.ClassDef(
+ classIdent,
+ originalNameOfClass(sym),
+ kind,
+ jsClassCaptures,
+ Some(encodeClassNameIdent(sym.superClass)),
+ genClassInterfaces(sym, forJSClass = true),
+ jsSuperClass = jsClassCaptures.map(_.head.ref),
+ None,
+ fields ::: staticFields,
+ generatedMethods.toList,
+ Some(generatedCtor),
+ jsMethodProps,
+ jsNativeMembers = Nil,
+ topLevelExports)(
+ OptimizerHints.empty)
+ }
+
+ /** Generate an instance of an anonymous (non-lambda) JS class inline
+ *
+ * @param sym Class to generate the instance of
+ * @param jsSuperClassValue JS class value of the super class
+ * @param args Arguments to the Scala constructor, which map to JS class captures
+ * @param pos Position of the original New tree
+ */
+ def genAnonJSClassNew(sym: Symbol, jsSuperClassValue: js.Tree,
+ args: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ assert(isAnonymousJSClass(sym),
+ "Generating AnonJSClassNew of non anonymous JS class")
+
+ // Find the ClassDef for this anonymous class
+ val classDef = consumeLazilyGeneratedAnonClass(sym)
+
+ // Generate a normal, non-native JS class
+ val origJsClass =
+ nestedGenerateClass(sym)(genNonNativeJSClass(classDef))
+
+ // Partition class members.
+ val privateFieldDefs = ListBuffer.empty[js.FieldDef]
+ val jsFieldDefs = ListBuffer.empty[js.JSFieldDef]
+
+ origJsClass.fields.foreach {
+ case fdef: js.FieldDef =>
+ privateFieldDefs += fdef
+
+ case fdef: js.JSFieldDef =>
+ jsFieldDefs += fdef
+ }
+
+ assert(origJsClass.jsNativeMembers.isEmpty,
+ "Found JS native members in anonymous JS class at " + pos)
+
+ assert(origJsClass.topLevelExportDefs.isEmpty,
+ "Found top-level exports in anonymous JS class at " + pos)
+
+ // Make new class def with static members
+ val newClassDef = {
+ implicit val pos = origJsClass.pos
+ val parent = js.ClassIdent(jswkn.ObjectClass)
+ js.ClassDef(origJsClass.name, origJsClass.originalName,
+ ClassKind.AbstractJSType, None, Some(parent), interfaces = Nil,
+ jsSuperClass = None, jsNativeLoadSpec = None, fields = Nil,
+ methods = origJsClass.methods, jsConstructor = None, jsMethodProps = Nil,
+ jsNativeMembers = Nil, topLevelExportDefs = Nil)(
+ origJsClass.optimizerHints)
+ }
+
+ generatedClasses += newClassDef -> pos
+
+ // Construct inline class definition
+
+ val jsClassCaptures = origJsClass.jsClassCaptures.getOrElse {
+ throw new AssertionError(
+ s"no class captures for anonymous JS class at $pos")
+ }
+ val js.JSConstructorDef(_, ctorParams, ctorRestParam, ctorBody) = origJsClass.jsConstructor.getOrElse {
+ throw new AssertionError("No ctor found")
+ }
+ assert(ctorParams.isEmpty && ctorRestParam.isEmpty,
+ s"non-empty constructor params for anonymous JS class at $pos")
+
+ /* The first class capture is always a reference to the super class.
+ * This is enforced by genJSClassCapturesAndConstructor.
+ */
+ def jsSuperClassRef(implicit pos: ir.Position): js.VarRef =
+ jsClassCaptures.head.ref
+
+ /* The `this` reference.
+ * FIXME This could clash with a local variable of the constructor or a JS
+ * class capture. How do we avoid this?
+ */
+ val selfIdent = freshLocalIdent("this")(pos)
+ def selfRef(implicit pos: ir.Position) =
+ js.VarRef(selfIdent.name)(jstpe.AnyType)
+
+ def memberLambda(params: List[js.ParamDef], restParam: Option[js.ParamDef],
+ body: js.Tree)(implicit pos: ir.Position) = {
+ js.Closure(js.ClosureFlags.function, captureParams = Nil, params,
+ restParam, jstpe.AnyType, body, captureValues = Nil)
+ }
+
+ val fieldDefinitions = jsFieldDefs.toList.map { fdef =>
+ implicit val pos = fdef.pos
+ js.Assign(js.JSSelect(selfRef, fdef.name), jstpe.zeroOf(fdef.ftpe))
+ }
+
+ val memberDefinitions0 = origJsClass.jsMethodProps.toList.map {
+ case mdef: js.JSMethodDef =>
+ implicit val pos = mdef.pos
+ val impl = memberLambda(mdef.args, mdef.restParam, mdef.body)
+ js.Assign(js.JSSelect(selfRef, mdef.name), impl)
+
+ case pdef: js.JSPropertyDef =>
+ implicit val pos = pdef.pos
+ val optGetter = pdef.getterBody.map { body =>
+ js.StringLiteral("get") -> memberLambda(params = Nil, restParam = None, body)
+ }
+ val optSetter = pdef.setterArgAndBody.map { case (arg, body) =>
+ js.StringLiteral("set") -> memberLambda(params = arg :: Nil, restParam = None, body)
+ }
+ val descriptor = js.JSObjectConstr(
+ optGetter.toList :::
+ optSetter.toList :::
+ List(js.StringLiteral("configurable") -> js.BooleanLiteral(true))
+ )
+ js.JSMethodApply(js.JSGlobalRef("Object"),
+ js.StringLiteral("defineProperty"),
+ List(selfRef, pdef.name, descriptor))
+ }
+
+ val memberDefinitions1 = fieldDefinitions ::: memberDefinitions0
+
+ val memberDefinitions = if (privateFieldDefs.isEmpty) {
+ memberDefinitions1
+ } else {
+ /* Private fields, declared in FieldDefs, are stored in a separate
+ * object, itself stored as a non-enumerable field of the `selfRef`.
+ * The name of that field is retrieved at
+ * `scala.scalajs.runtime.privateFieldsSymbol()`, and is a Symbol if
+ * supported, or a randomly generated string that has the same enthropy
+ * as a UUID (i.e., 128 random bits).
+ *
+ * This encoding solves two issues:
+ *
+ * - Hide private fields in anonymous JS classes from `JSON.stringify`
+ * and other cursory inspections in JS (#2748).
+ * - Get around the fact that abstract JS types cannot declare
+ * FieldDefs (#3777).
+ */
+ val fieldsObjValue = {
+ js.JSObjectConstr(privateFieldDefs.toList.map { fdef =>
+ implicit val pos = fdef.pos
+ js.StringLiteral(fdef.name.name.nameString) -> jstpe.zeroOf(fdef.ftpe)
+ })
+ }
+ val definePrivateFieldsObj = {
+ /* Object.defineProperty(selfRef, privateFieldsSymbol, {
+ * value: fieldsObjValue
+ * });
+ *
+ * `writable`, `configurable` and `enumerable` are false by default.
+ */
+ js.JSMethodApply(
+ js.JSGlobalRef("Object"),
+ js.StringLiteral("defineProperty"),
+ List(
+ selfRef,
+ genPrivateFieldsSymbol(),
+ js.JSObjectConstr(List(
+ js.StringLiteral("value") -> fieldsObjValue))
+ )
+ )
+ }
+ definePrivateFieldsObj :: memberDefinitions1
+ }
+
+ // Transform the constructor body.
+ val inlinedCtorStats = {
+ val beforeSuper = ctorBody.beforeSuper
+
+ val superCall = {
+ implicit val pos = ctorBody.superCall.pos
+ val js.JSSuperConstructorCall(args) = ctorBody.superCall
+
+ val newTree = {
+ val ident =
+ origJsClass.superClass.getOrElse(abort("No superclass"))
+ if (args.isEmpty && ident.name == JSObjectClassName)
+ js.JSObjectConstr(Nil)
+ else
+ js.JSNew(jsSuperClassRef, args)
+ }
+
+ val selfVarDef = js.VarDef(selfIdent.copy(), // copy for the correct `pos`
+ thisOriginalName, jstpe.AnyType, mutable = false, newTree)
+ selfVarDef :: memberDefinitions
+ }
+
+ // After the super call, substitute `selfRef` for `this`
+ val afterSuper = new ir.Transformers.LocalScopeTransformer {
+ override def transform(tree: js.Tree): js.Tree = tree match {
+ case js.This() =>
+ selfRef(tree.pos)
+ case tree =>
+ super.transform(tree)
+ }
+ }.transformTrees(ctorBody.afterSuper)
+
+ beforeSuper ::: superCall ::: afterSuper
+ }
+
+ // Wrap everything in a lambda, for namespacing
+ val closure = js.Closure(js.ClosureFlags.arrow, jsClassCaptures, Nil, None, jstpe.AnyType,
+ js.Block(inlinedCtorStats, selfRef), jsSuperClassValue :: args)
+ js.JSFunctionApply(closure, Nil)
+ }
+
+ // Generate the class data of a JS class -----------------------------------
+
+ /** Gen the IR ClassDef for a JS class or trait.
+ */
+ def genJSClassData(cd: ClassDef): js.ClassDef = {
+ val sym = cd.symbol
+ implicit val pos = sym.pos
+
+ val classIdent = encodeClassNameIdent(sym)
+ val kind = {
+ if (sym.isTraitOrInterface) ClassKind.AbstractJSType
+ else if (isJSFunctionDef(sym)) ClassKind.AbstractJSType
+ else if (sym.isModuleClass) ClassKind.NativeJSModuleClass
+ else ClassKind.NativeJSClass
+ }
+ val superClass =
+ if (sym.isTraitOrInterface) None
+ else Some(encodeClassNameIdent(sym.superClass))
+ val jsNativeLoadSpec = jsNativeLoadSpecOfOption(sym)
+
+ js.ClassDef(classIdent, originalNameOfClass(sym), kind, None, superClass,
+ genClassInterfaces(sym, forJSClass = true), None, jsNativeLoadSpec,
+ Nil, Nil, None, Nil, Nil, Nil)(
+ OptimizerHints.empty)
+ }
+
+ // Generate an interface ---------------------------------------------------
+
+ /** Gen the IR ClassDef for an interface definition.
+ */
+ def genInterface(cd: ClassDef): js.ClassDef = {
+ val sym = cd.symbol
+ implicit val pos = sym.pos
+
+ val classIdent = encodeClassNameIdent(sym)
+
+ val generatedMethods = collectDefDefs(cd.impl).flatMap(genMethod(_))
+ val interfaces = genClassInterfaces(sym, forJSClass = false)
+
+ val allMemberDefs =
+ if (!isCandidateForForwarders(sym)) generatedMethods
+ else generatedMethods ::: genStaticForwardersForClassOrInterface(generatedMethods, sym)
+
+ js.ClassDef(classIdent, originalNameOfClass(sym), ClassKind.Interface,
+ None, None, interfaces, None, None, fields = Nil, methods = allMemberDefs,
+ None, Nil, Nil, Nil)(
+ OptimizerHints.empty)
+ }
+
+ private lazy val jsTypeInterfacesBlacklist: Set[Symbol] =
+ Set(DynamicClass, SerializableClass) // #3118, #3252
+
+ private def genClassInterfaces(sym: Symbol, forJSClass: Boolean)(
+ implicit pos: Position): List[js.ClassIdent] = {
+
+ val blacklist =
+ if (forJSClass) jsTypeInterfacesBlacklist
+ else Set.empty[Symbol]
+
+ for {
+ parent <- sym.info.parents
+ typeSym = parent.typeSymbol
+ _ = assert(typeSym != NoSymbol, "parent needs symbol")
+ if typeSym.isTraitOrInterface && !blacklist.contains(typeSym)
+ } yield {
+ encodeClassNameIdent(typeSym)
+ }
+ }
+
+ // Static forwarders -------------------------------------------------------
+
+ /* This mimics the logic in BCodeHelpers.addForwarders and the code that
+ * calls it, except that we never have collisions with existing methods in
+ * the companion class. This is because in the IR, only methods with the
+ * same `MethodName` (including signature) and that are also
+ * `PublicStatic` would collide. Since we never emit any `PublicStatic`
+ * method otherwise, there can be no collision. If that assumption is broken,
+ * an error message is emitted asking the user to report a bug.
+ *
+ * It is important that we always emit forwarders, because some Java APIs
+ * actually have a public static method and a public instance method with
+ * the same name. For example the class `Integer` has a
+ * `def hashCode(): Int` and a `static def hashCode(Int): Int`. The JVM
+ * back-end considers them as colliding because they have the same name,
+ * but we must not.
+ *
+ * By default, we only emit forwarders for top-level objects, like scalac.
+ * However, if requested via a compiler option, we enable them for all
+ * static objects. This is important so we can implement static methods
+ * of nested static classes of JDK APIs (see #3950).
+ */
+
+ /** Is the given Scala class, interface or module class a candidate for
+ * static forwarders?
+ */
+ def isCandidateForForwarders(sym: Symbol): Boolean = {
+ !settings.noForwarders.value && sym.isStatic && {
+ // Reject non-top-level objects unless opted in via the appropriate option
+ scalaJSOpts.genStaticForwardersForNonTopLevelObjects ||
+ !sym.name.containsChar('$') // this is the same test that scalac performs
+ }
+ }
+
+ /** Gen the static forwarders to the members of a class or interface for
+ * methods of its companion object.
+ *
+ * This is only done if there exists a companion object and it is not a JS
+ * type.
+ *
+ * Precondition: `isCandidateForForwarders(sym)` is true
+ */
+ def genStaticForwardersForClassOrInterface(
+ existingMethods: List[js.MethodDef], sym: Symbol)(
+ implicit pos: Position): List[js.MethodDef] = {
+ /* Phase travel is necessary for non-top-level classes, because flatten
+ * breaks their companionModule. This is tracked upstream at
+ * https://github.com/scala/scala-dev/issues/403
+ */
+ val module = exitingPhase(currentRun.picklerPhase)(sym.companionModule)
+ if (module == NoSymbol) {
+ Nil
+ } else {
+ val moduleClass = module.moduleClass
+ if (!isJSType(moduleClass))
+ genStaticForwardersFromModuleClass(existingMethods, moduleClass)
+ else
+ Nil
+ }
+ }
+
+ /** Gen the static forwarders for the methods of a module class.
+ *
+ * Precondition: `isCandidateForForwarders(moduleClass)` is true
+ */
+ def genStaticForwardersFromModuleClass(existingMethods: List[js.MethodDef],
+ moduleClass: Symbol)(
+ implicit pos: Position): List[js.MethodDef] = {
+
+ assert(moduleClass.isModuleClass, moduleClass)
+
+ def listMembersBasedOnFlags = {
+ // Copy-pasted from BCodeHelpers.
+ val ExcludedForwarderFlags: Long = {
+ import scala.tools.nsc.symtab.Flags._
+ SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | PRIVATE | MACRO
+ }
+
+ moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, symtab.Flags.METHOD)
+ }
+
+ // See BCodeHelprs.addForwarders in 2.12+ for why we use exitingUncurry.
+ val members = exitingUncurry(listMembersBasedOnFlags)
+
+ def isExcluded(m: Symbol): Boolean = {
+ def isOfJLObject: Boolean = {
+ val o = m.owner
+ (o eq ObjectClass) || (o eq AnyRefClass) || (o eq AnyClass)
+ }
+
+ def isDefaultParamOfJSNativeDef: Boolean = {
+ DefaultParamInfo.isApplicable(m) && {
+ val info = new DefaultParamInfo(m)
+ !info.isForConstructor && info.attachedMethod.hasAnnotation(JSNativeAnnotation)
+ }
+ }
+
+ m.isDeferred || m.isConstructor || m.hasAccessBoundary ||
+ isOfJLObject ||
+ m.hasAnnotation(JSNativeAnnotation) || isDefaultParamOfJSNativeDef // #4557
+ }
+
+ val forwarders = for {
+ m <- members
+ if !isExcluded(m)
+ } yield {
+ withNewLocalNameScope {
+ val flags = js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic)
+ val methodIdent = encodeMethodSym(m)
+ val originalName = originalNameOfMethod(m)
+ val jsParams = m.tpe.params.map(genParamDef(_))
+ val resultType = toIRType(m.tpe.resultType)
+
+ js.MethodDef(flags, methodIdent, originalName, jsParams, resultType, Some {
+ genApplyMethod(genLoadModule(moduleClass), m, jsParams.map(_.ref))
+ })(OptimizerHints.empty, Unversioned)
+ }
+ }
+
+ forwarders.toList
+ }
+
+ // Generate the fields of a class ------------------------------------------
+
+ /** Gen definitions for the fields of a class.
+ * The fields are initialized with the zero of their types.
+ */
+ def genClassFields(cd: ClassDef): List[js.AnyFieldDef] = {
+ val classSym = cd.symbol
+ assert(currentClassSym.get == classSym,
+ "genClassFields called with a ClassDef other than the current one")
+
+ val isJSClass = isNonNativeJSClass(classSym)
+
+ // Non-method term members are fields, except for module members.
+ (for {
+ f <- classSym.info.decls
+ if !f.isMethod && f.isTerm && !f.isModule
+ if !f.hasAnnotation(JSOptionalAnnotation) && !f.hasAnnotation(JSNativeAnnotation)
+ if jsInterop.staticExportsOf(f).isEmpty
+ } yield {
+ implicit val pos = f.pos
+
+ val static = jsInterop.topLevelExportsOf(f).nonEmpty
+
+ val mutable = {
+ static || // static fields must always be mutable
+ f.isMutable || // mutable fields can be mutated from anywhere
+ fieldsMutatedInCurrentClass.contains(f.name) // the field is mutated in the current class
+ }
+
+ val namespace =
+ if (static) js.MemberNamespace.PublicStatic
+ else js.MemberNamespace.Public
+ val flags =
+ js.MemberFlags.empty.withNamespace(namespace).withMutable(mutable)
+
+ val irTpe0 = {
+ if (isJSClass) genExposedFieldIRType(f)
+ else if (static) jstpe.AnyType
+ else toIRType(f.tpe)
+ }
+
+ // #4370 Fields cannot have type NothingType
+ val irTpe =
+ if (irTpe0 == jstpe.NothingType) encodeClassType(RuntimeNothingClass)
+ else irTpe0
+
+ if (isJSClass && isExposed(f))
+ js.JSFieldDef(flags, genExpr(jsNameOf(f)), irTpe)
+ else
+ js.FieldDef(flags, encodeFieldSym(f), originalNameOfField(f), irTpe)
+ }).toList
+ }
+
+ def genExposedFieldIRType(f: Symbol): jstpe.Type = {
+ val tpeEnteringPosterasure =
+ enteringPhase(currentRun.posterasurePhase)(f.tpe)
+ tpeEnteringPosterasure match {
+ case tpe: ErasedValueType =>
+ /* Here, we must store the field as the boxed representation of
+ * the value class. The default value of that field, as
+ * initialized at the time the instance is created, will
+ * therefore be null. This will not match the behavior we would
+ * get in a Scala class. To match the behavior, we would need to
+ * initialized to an instance of the boxed representation, with
+ * an underlying value set to the zero of its type. However we
+ * cannot implement that, so we live with the discrepancy.
+ * Anyway, scalac also has problems with uninitialized value
+ * class values, if they come from a generic context.
+ */
+ jstpe.ClassType(encodeClassName(tpe.valueClazz), nullable = true)
+
+ case _ =>
+ /* Other types are not boxed, so we can initialize them to
+ * their true zero.
+ */
+ toIRType(f.tpe)
+ }
+ }
+
+ // Static initializers -----------------------------------------------------
+
+ private def genStaticConstructorWithStats(name: MethodName, stats: js.Tree)(
+ implicit pos: Position): js.MethodDef = {
+ js.MethodDef(
+ js.MemberFlags.empty.withNamespace(js.MemberNamespace.StaticConstructor),
+ js.MethodIdent(name),
+ NoOriginalName,
+ Nil,
+ jstpe.VoidType,
+ Some(stats))(
+ OptimizerHints.empty, Unversioned)
+ }
+
+ private def genRegisterReflectiveInstantiation(sym: Symbol)(
+ implicit pos: Position): Option[js.Tree] = {
+ if (isStaticModule(sym))
+ genRegisterReflectiveInstantiationForModuleClass(sym)
+ else if (sym.isModuleClass)
+ None // #3228
+ else if (sym.isLifted && !sym.originalOwner.isClass)
+ None // #3227
+ else
+ genRegisterReflectiveInstantiationForNormalClass(sym)
+ }
+
+ private def genRegisterReflectiveInstantiationForModuleClass(sym: Symbol)(
+ implicit pos: Position): Option[js.Tree] = {
+ val fqcnArg = js.StringLiteral(sym.fullName + "$")
+ val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
+ val loadModuleFunArg =
+ js.Closure(js.ClosureFlags.arrow, Nil, Nil, None, jstpe.AnyType, genLoadModule(sym), Nil)
+
+ val stat = genApplyMethod(
+ genLoadModule(ReflectModule),
+ Reflect_registerLoadableModuleClass,
+ List(fqcnArg, runtimeClassArg, loadModuleFunArg))
+
+ Some(stat)
+ }
+
+ private def genRegisterReflectiveInstantiationForNormalClass(sym: Symbol)(
+ implicit pos: Position): Option[js.Tree] = {
+ val ctors =
+ if (sym.isAbstractClass) Nil
+ else sym.info.member(nme.CONSTRUCTOR).alternatives.filter(_.isPublic)
+
+ if (ctors.isEmpty) {
+ None
+ } else {
+ val constructorsInfos = for {
+ ctor <- ctors
+ } yield {
+ withNewLocalNameScope {
+ val (parameterTypes, formalParams, actualParams) = (for {
+ param <- ctor.tpe.params
+ } yield {
+ /* Note that we do *not* use `param.tpe` entering posterasure
+ * (neither to compute `paramType` nor to give to `fromAny`).
+ * Logic would tell us that we should do so, but we intentionally
+ * do not to preserve the behavior on the JVM regarding value
+ * classes. If a constructor takes a value class as parameter, as
+ * in:
+ *
+ * class ValueClass(val underlying: Int) extends AnyVal
+ * class Foo(val vc: ValueClass)
+ *
+ * then, from a reflection point of view, on the JVM, the
+ * constructor of `Foo` takes an `Int`, not a `ValueClas`. It
+ * must therefore be identified as the constructor whose
+ * parameter types is `List(classOf[Int])`, and when invoked
+ * reflectively, it must be given an `Int` (or `Integer`).
+ */
+ val paramType = js.ClassOf(toTypeRef(param.tpe))
+ val paramDef = genParamDef(param, jstpe.AnyType)
+ val actualParam = fromAny(paramDef.ref, param.tpe)
+ (paramType, paramDef, actualParam)
+ }).unzip3
+
+ val paramTypesArray = js.JSArrayConstr(parameterTypes)
+
+ val newInstanceFun = js.Closure(js.ClosureFlags.arrow, Nil,
+ formalParams, None, jstpe.AnyType, genNew(sym, ctor, actualParams), Nil)
+
+ js.JSArrayConstr(List(paramTypesArray, newInstanceFun))
+ }
+ }
+
+ val fqcnArg = js.StringLiteral(sym.fullName)
+ val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
+ val ctorsInfosArg = js.JSArrayConstr(constructorsInfos)
+
+ val stat = genApplyMethod(
+ genLoadModule(ReflectModule),
+ Reflect_registerInstantiatableClass,
+ List(fqcnArg, runtimeClassArg, ctorsInfosArg))
+
+ Some(stat)
+ }
+ }
+
+ // Constructor of a non-native JS class ------------------------------
+
+ def genJSClassCapturesAndConstructor(constructorTrees: List[DefDef])(
+ implicit pos: Position): (List[js.ParamDef], js.JSConstructorDef) = {
+ /* We need to merge all Scala constructors into a single one because
+ * JavaScript only allows a single one.
+ *
+ * We do this by applying:
+ * 1. Applying runtime type based dispatch, just like exports.
+ * 2. Splitting secondary ctors into parts before and after the `this` call.
+ * 3. Topo-sorting all constructor statements and including/excluding
+ * them based on the overload that was chosen.
+ */
+
+ val (primaryTree :: Nil, secondaryTrees) =
+ constructorTrees.partition(_.symbol.isPrimaryConstructor)
+
+ val primaryCtor = genPrimaryJSClassCtor(primaryTree)
+ val secondaryCtors = secondaryTrees.map(genSecondaryJSClassCtor(_))
+
+ // VarDefs for the parameters of all constructors.
+ val paramVarDefs = for {
+ vparam <- constructorTrees.flatMap(_.vparamss.flatten)
+ } yield {
+ val sym = vparam.symbol
+ val tpe = toIRType(sym.tpe)
+ js.VarDef(encodeLocalSym(sym), originalNameOfLocal(sym), tpe, mutable = true,
+ jstpe.zeroOf(tpe))(vparam.pos)
+ }
+
+ /* organize constructors in a called-by tree
+ * (the implicit root is the primary constructor)
+ */
+ val ctorTree = {
+ val ctorToChildren = secondaryCtors
+ .groupBy(_.targetCtor)
+ .withDefaultValue(Nil)
+
+ /* when constructing the call-by tree, we use pre-order traversal to
+ * assign overload numbers.
+ * this puts all descendants of a ctor in a range of overloads numbers.
+ *
+ * this property is useful, later, when we need to make statements
+ * conditional based on the chosen overload.
+ */
+ var nextOverloadNum = 0
+ def subTree[T <: JSCtor](ctor: T): ConstructorTree[T] = {
+ val overloadNum = nextOverloadNum
+ nextOverloadNum += 1
+ val subtrees = ctorToChildren(ctor.sym).map(subTree(_))
+ new ConstructorTree(overloadNum, ctor, subtrees)
+ }
+
+ subTree(primaryCtor)
+ }
+
+ /* prepare overload dispatch for all constructors.
+ * as a side-product, we retrieve the capture parameters.
+ */
+ val (exports, jsClassCaptures) = {
+ val exports = List.newBuilder[Exported]
+ val jsClassCaptures = List.newBuilder[js.ParamDef]
+
+ def add(tree: ConstructorTree[_ <: JSCtor]): Unit = {
+ val (e, c) = genJSClassCtorDispatch(tree.ctor.sym,
+ tree.ctor.paramsAndInfo, tree.overloadNum)
+ exports += e
+ jsClassCaptures ++= c
+ tree.subCtors.foreach(add(_))
+ }
+
+ add(ctorTree)
+
+ (exports.result(), jsClassCaptures.result())
+ }
+
+ // The name 'constructor' is used for error reporting here
+ val (formalArgs, restParam, overloadDispatchBody) =
+ genOverloadDispatch(JSName.Literal("constructor"), exports, jstpe.IntType)
+
+ val overloadVar = js.VarDef(freshLocalIdent("overload"), NoOriginalName,
+ jstpe.IntType, mutable = false, overloadDispatchBody)
+
+ val constructorBody = wrapJSCtorBody(
+ paramVarDefs :+ overloadVar,
+ genJSClassCtorBody(overloadVar.ref, ctorTree),
+ js.Undefined() :: Nil
+ )
+
+ val constructorDef = js.JSConstructorDef(
+ js.MemberFlags.empty.withNamespace(js.MemberNamespace.Constructor),
+ formalArgs, restParam, constructorBody)(OptimizerHints.empty, Unversioned)
+
+ (jsClassCaptures, constructorDef)
+ }
+
+ private def genPrimaryJSClassCtor(dd: DefDef): PrimaryJSCtor = {
+ val DefDef(_, _, _, vparamss, _, Block(stats, _)) = dd
+ val sym = dd.symbol
+ assert(sym.isPrimaryConstructor, s"called with non-primary ctor: $sym")
+
+ var preSuperStats = List.newBuilder[js.Tree]
+ var jsSuperCall: Option[js.JSSuperConstructorCall] = None
+ val postSuperStats = mutable.ListBuffer.empty[js.Tree]
+
+ /* Move param accessor initializers and early initializers after the
+ * super constructor call since JS cannot access `this` before the super
+ * constructor call.
+ *
+ * scalac inserts statements before the super constructor call for early
+ * initializers and param accessor initializers (including val's and var's
+ * declared in the params). Those statements include temporary local `val`
+ * definitions (for true early initializers only) and the assignments,
+ * whose rhs'es are always simple Idents (either constructor params or the
+ * temporary local `val`s).
+ *
+ * There can also be local `val`s before the super constructor call for
+ * default arguments to the super constructor. These must remain before.
+ *
+ * Our strategy is therefore to move only the field assignments after the
+ * super constructor call. They are therefore executed later than for a
+ * Scala class (as specified for non-native JS classes semantics).
+ * However, side effects and evaluation order of all the other
+ * computations remains unchanged.
+ *
+ * For a somewhat extreme example of the shapes we can get here, consider
+ * the source code:
+ *
+ * class Parent(output: Any = "output", callbackObject: Any = "callbackObject") extends js.Object {
+ * println(s"Parent constructor; $output; $callbackObject")
+ * }
+ *
+ * class Child(val foo: Int, callbackObject: Any, val bar: Int) extends {
+ * val xyz = foo + bar
+ * val yz = { println(xyz); xyz + 2 }
+ * } with Parent(callbackObject = { println(foo); xyz + bar }) {
+ * println("Child constructor")
+ * println(xyz)
+ * }
+ *
+ * At this phase, for the constructor of `Child`, we receive the following
+ * scalac Tree:
+ *
+ * def (foo: Int, callbackObject: Object, bar: Int): helloworld.Child = {
+ * Child.this.foo = foo; // param accessor assignment, moved
+ * Child.this.bar = bar; // param accessor assignment, moved
+ * val xyz: Int = foo.+(bar); // note that these still use the ctor params, not the fields
+ * Child.this.xyz = xyz; // early initializer, moved
+ * val yz: Int = {
+ * scala.Predef.println(scala.Int.box(xyz)); // note that this uses the local val, not the field
+ * xyz.+(2)
+ * };
+ * Child.this.yz = yz; // early initializer, moved
+ * {
+ * val x$1: Int = {
+ * scala.Predef.println(scala.Int.box(foo));
+ * xyz.+(bar) // here as well, we use the local vals, not the fields
+ * };
+ * val x$2: Object = helloworld.this.Parent.$default$1();
+ * Child.super.(x$2, scala.Int.box(x$1))
+ * };
+ * scala.Predef.println("Child constructor");
+ * scala.Predef.println(scala.Int.box(Child.this.xyz()));
+ * ()
+ * }
+ *
+ */
+ withPerMethodBodyState(sym) {
+ def isThisFieldAssignment(tree: Tree): Boolean = tree match {
+ case Assign(Select(ths: This, _), Ident(_)) => ths.symbol == currentClassSym.get
+ case _ => false
+ }
+
+ flatStats(stats).foreach {
+ case tree @ Apply(fun @ Select(Super(This(_), _), _), args)
+ if fun.symbol.isClassConstructor =>
+ assert(jsSuperCall.isEmpty, s"Found 2 JS Super calls at ${dd.pos}")
+ implicit val pos = tree.pos
+ jsSuperCall = Some(js.JSSuperConstructorCall(genPrimitiveJSArgs(fun.symbol, args)))
+
+ case tree if jsSuperCall.isDefined =>
+ // Once we're past the super constructor call, everything goes after.
+ postSuperStats += genStat(tree)
+
+ case tree if isThisFieldAssignment(tree) =>
+ /* If that shape appears before the jsSuperCall, it is an early
+ * initializer or param accessor initializer. We move it.
+ */
+ postSuperStats += genStat(tree)
+
+ case tree @ OuterPointerNullCheck(outer, assign) if isThisFieldAssignment(assign) =>
+ /* Variant of the above with an outer pointer null check. The actual
+ * null check remains before the super call, while the associated
+ * assignment is moved after.
+ */
+ preSuperStats += js.UnaryOp(js.UnaryOp.CheckNotNull, genExpr(outer))(tree.pos)
+ postSuperStats += genStat(assign)
+
+ case stat =>
+ // Other statements are left before.
+ preSuperStats += genStat(stat)
+ }
+ }
+
+ assert(jsSuperCall.isDefined, "Did not find Super call in primary JS " +
+ s"construtor at ${dd.pos}")
+
+ /* Insert a StoreModule if required.
+ * Do this now so we have the pos of the super ctor call.
+ * +=: prepends to the ListBuffer in O(1) -- yes, it's a cryptic name.
+ */
+ if (isStaticModule(currentClassSym))
+ js.StoreModule()(jsSuperCall.get.pos) +=: postSuperStats
+
+ new PrimaryJSCtor(sym, genParamsAndInfo(sym, vparamss),
+ js.JSConstructorBody(preSuperStats.result(), jsSuperCall.get, postSuperStats.result())(dd.pos))
+ }
+
+ private def genSecondaryJSClassCtor(dd: DefDef): SplitSecondaryJSCtor = {
+ val DefDef(_, _, _, vparamss, _, Block(stats, _)) = dd
+ val sym = dd.symbol
+ assert(!sym.isPrimaryConstructor, s"called with primary ctor $sym")
+
+ val beforeThisCall = List.newBuilder[js.Tree]
+ var thisCall: Option[(Symbol, List[js.Tree])] = None
+ val afterThisCall = List.newBuilder[js.Tree]
+
+ withPerMethodBodyState(sym) {
+ flatStats(stats).foreach {
+ case tree @ Apply(fun @ Select(This(_), _), args)
+ if fun.symbol.isClassConstructor =>
+ assert(thisCall.isEmpty,
+ s"duplicate this() call in secondary JS constructor at ${dd.pos}")
+
+ implicit val pos = tree.pos
+ val sym = fun.symbol
+ thisCall = Some((sym, genActualArgs(sym, args)))
+
+ case stat =>
+ val jsStat = genStat(stat)
+ if (thisCall.isEmpty)
+ beforeThisCall += jsStat
+ else
+ afterThisCall += jsStat
+ }
+ }
+
+ val Some((targetCtor, ctorArgs)) = thisCall
+
+ new SplitSecondaryJSCtor(sym, genParamsAndInfo(sym, vparamss),
+ beforeThisCall.result(), targetCtor, ctorArgs, afterThisCall.result())
+ }
+
+ private def genParamsAndInfo(ctorSym: Symbol,
+ vparamss: List[List[ValDef]]): List[(js.VarRef, JSParamInfo)] = {
+ implicit val pos = ctorSym.pos
+
+ val paramSyms = if (vparamss.isEmpty) Nil else vparamss.head.map(_.symbol)
+
+ for {
+ (paramSym, info) <- paramSyms.zip(jsParamInfos(ctorSym))
+ } yield {
+ genVarRef(paramSym) -> info
+ }
+ }
+
+ private def genJSClassCtorDispatch(ctorSym: Symbol,
+ allParamsAndInfos: List[(js.VarRef, JSParamInfo)],
+ overloadNum: Int): (Exported, List[js.ParamDef]) = {
+ implicit val pos = ctorSym.pos
+
+ /* `allParams` are the parameters as seen from *inside* the constructor
+ * body. the symbols returned in jsParamInfos are the parameters as seen
+ * from *outside* (i.e. from a caller).
+ *
+ * we need to use the symbols from inside to generate the right
+ * identifiers (the ones generated by the trees in the constructor body).
+ */
+ val (captureParamsAndInfos, normalParamsAndInfos) =
+ allParamsAndInfos.partition(_._2.capture)
+
+ /* We use the *outer* param symbol to get different names than the *inner*
+ * symbols. This is necessary so that we can forward captures properly
+ * between constructor delegation calls.
+ */
+ val jsClassCaptures =
+ captureParamsAndInfos.map(x => genParamDef(x._2.sym))
+
+ val normalInfos = normalParamsAndInfos.map(_._2).toIndexedSeq
+
+ val jsExport = new Exported(ctorSym, normalInfos) {
+ def genBody(formalArgsRegistry: FormalArgsRegistry): js.Tree = {
+ val captureAssigns = for {
+ (param, info) <- captureParamsAndInfos
+ } yield {
+ js.Assign(param, genVarRef(info.sym))
+ }
+
+ val paramAssigns = for {
+ ((param, info), i) <- normalParamsAndInfos.zipWithIndex
+ } yield {
+ val rhs = genScalaArg(sym, i, formalArgsRegistry, info, static = true,
+ captures = captureParamsAndInfos.map(_._1))(
+ prevArgsCount => normalParamsAndInfos.take(prevArgsCount).map(_._1))
+
+ js.Assign(param, rhs)
+ }
+
+ js.Block(captureAssigns ::: paramAssigns, js.IntLiteral(overloadNum))
+ }
+ }
+
+ (jsExport, jsClassCaptures)
+ }
+
+ /** Generates a JS constructor body based on a constructor tree. */
+ private def genJSClassCtorBody(overloadVar: js.VarRef,
+ ctorTree: ConstructorTree[PrimaryJSCtor])(implicit pos: Position): js.JSConstructorBody = {
+
+ /* generates a statement that conditionally executes body iff the chosen
+ * overload is any of the descendants of `tree` (including itself).
+ *
+ * here we use the property from building the trees, that a set of
+ * descendants always has a range of overload numbers.
+ */
+ def ifOverload(tree: ConstructorTree[_], body: js.Tree): js.Tree = body match {
+ case js.Skip() => js.Skip()
+
+ case body =>
+ val x = overloadVar
+ val cond = {
+ import tree.{lo, hi}
+
+ if (lo == hi) {
+ js.BinaryOp(js.BinaryOp.Int_==, js.IntLiteral(lo), x)
+ } else {
+ val lhs = js.BinaryOp(js.BinaryOp.Int_<=, js.IntLiteral(lo), x)
+ val rhs = js.BinaryOp(js.BinaryOp.Int_<=, x, js.IntLiteral(hi))
+ js.If(lhs, rhs, js.BooleanLiteral(false))(jstpe.BooleanType)
+ }
+ }
+
+ js.If(cond, body, js.Skip())(jstpe.VoidType)
+ }
+
+ /* preStats / postStats use pre/post order traversal respectively to
+ * generate a topo-sorted sequence of statements.
+ */
+
+ def preStats(tree: ConstructorTree[SplitSecondaryJSCtor],
+ nextParamsAndInfo: List[(js.VarRef, JSParamInfo)]): js.Tree = {
+ val inner = tree.subCtors.map(preStats(_, tree.ctor.paramsAndInfo))
+
+ assert(tree.ctor.ctorArgs.size == nextParamsAndInfo.size, "param count mismatch")
+ val paramsInfosAndArgs = nextParamsAndInfo.zip(tree.ctor.ctorArgs)
+
+ val (captureParamsInfosAndArgs, normalParamsInfosAndArgs) =
+ paramsInfosAndArgs.partition(_._1._2.capture)
+
+ val captureAssigns = for {
+ ((param, _), arg) <- captureParamsInfosAndArgs
+ } yield {
+ js.Assign(param, arg)
+ }
+
+ val normalAssigns = for {
+ (((param, info), arg), i) <- normalParamsInfosAndArgs.zipWithIndex
+ } yield {
+ val newArg = arg match {
+ case js.Transient(UndefinedParam) =>
+ assert(info.hasDefault,
+ s"unexpected UndefinedParam for non default param: $param")
+
+ /* Go full circle: We have ignored the default param getter for
+ * this, we'll create it again.
+ *
+ * This seems not optimal: We could simply not ignore the calls to
+ * default param getters in the first place.
+ *
+ * However, this proves to be difficult: Because of translations in
+ * earlier phases, calls to default param getters may be assigned
+ * to temporary variables first (see the undefinedDefaultParams
+ * ScopedVar). If this happens, it becomes increasingly difficult
+ * to distinguish a default param getter call for a constructor
+ * call of *this* instance (in which case we would want to keep
+ * the default param getter call) from one for a *different*
+ * instance (in which case we would want to discard the default
+ * param getter call)
+ *
+ * Because of this, it ends up being easier to just re-create the
+ * default param getter call if necessary.
+ */
+ genCallDefaultGetter(tree.ctor.sym, i, tree.ctor.sym.pos, static = false,
+ captures = captureParamsInfosAndArgs.map(_._1._1))(
+ prevArgsCount => normalParamsInfosAndArgs.take(prevArgsCount).map(_._1._1))
+
+ case arg => arg
+ }
+
+ js.Assign(param, newArg)
+ }
+
+ ifOverload(tree, js.Block(
+ inner ++ tree.ctor.beforeCall ++ captureAssigns ++ normalAssigns))
+ }
+
+ def postStats(tree: ConstructorTree[SplitSecondaryJSCtor]): js.Tree = {
+ val inner = tree.subCtors.map(postStats(_))
+ ifOverload(tree, js.Block(tree.ctor.afterCall ++ inner))
+ }
+
+ val primaryCtor = ctorTree.ctor
+ val secondaryCtorTrees = ctorTree.subCtors
+
+ wrapJSCtorBody(
+ secondaryCtorTrees.map(preStats(_, primaryCtor.paramsAndInfo)),
+ primaryCtor.body,
+ secondaryCtorTrees.map(postStats(_))
+ )
+ }
+
+ private def wrapJSCtorBody(before: List[js.Tree], body: js.JSConstructorBody,
+ after: List[js.Tree]): js.JSConstructorBody = {
+ js.JSConstructorBody(before ::: body.beforeSuper, body.superCall,
+ body.afterSuper ::: after)(body.pos)
+ }
+
+ private sealed trait JSCtor {
+ val sym: Symbol
+ val paramsAndInfo: List[(js.VarRef, JSParamInfo)]
+ }
+
+ private class PrimaryJSCtor(val sym: Symbol,
+ val paramsAndInfo: List[(js.VarRef, JSParamInfo)],
+ val body: js.JSConstructorBody) extends JSCtor
+
+ private class SplitSecondaryJSCtor(val sym: Symbol,
+ val paramsAndInfo: List[(js.VarRef, JSParamInfo)],
+ val beforeCall: List[js.Tree],
+ val targetCtor: Symbol, val ctorArgs: List[js.Tree],
+ val afterCall: List[js.Tree]) extends JSCtor
+
+ private class ConstructorTree[Ctor <: JSCtor](
+ val overloadNum: Int, val ctor: Ctor,
+ val subCtors: List[ConstructorTree[SplitSecondaryJSCtor]]) {
+ val lo: Int = overloadNum
+ val hi: Int = subCtors.lastOption.fold(lo)(_.hi)
+
+ assert(lo <= hi, "bad overload range")
+ }
+
+ // Generate a method -------------------------------------------------------
+
+ /** Maybe gen JS code for a method definition.
+ *
+ * Some methods are not emitted at all:
+ *
+ * - Primitives, since they are never actually called (with exceptions)
+ * - Abstract methods in non-native JS classes
+ * - Default accessor of a native JS constructor
+ * - Constructors of hijacked classes
+ */
+ def genMethod(dd: DefDef): Option[js.MethodDef] = {
+ val sym = dd.symbol
+ val isAbstract = isAbstractMethod(dd)
+
+ /* Is this method a default accessor that should be ignored?
+ *
+ * This is the case iff one of the following applies:
+ * - It is a constructor default accessor and the linked class is a
+ * native JS class.
+ * - It is a default accessor for a native JS def, but with the caveat
+ * that its rhs must be `js.native` because of #4553.
+ *
+ * Both of those conditions can only happen if the default accessor is in
+ * a module class, so we use that as a fast way out. (But omitting that
+ * condition would not change the result.)
+ *
+ * This is different than `isJSDefaultParam` in `genApply`: we do not
+ * ignore default accessors of *non-native* JS types. Neither for
+ * constructor default accessor nor regular default accessors. We also
+ * do not need to worry about non-constructor members of native JS types,
+ * since for those, the entire member list is ignored in `genJSClassData`.
+ */
+ def isIgnorableDefaultParam: Boolean = {
+ DefaultParamInfo.isApplicable(sym) && sym.owner.isModuleClass && {
+ val info = new DefaultParamInfo(sym)
+ if (info.isForConstructor) {
+ /* This is a default accessor for a constructor parameter. Check
+ * whether the attached constructor is a native JS constructor,
+ * which is the case iff the linked class is a native JS type.
+ */
+ isJSNativeClass(info.constructorOwner)
+ } else {
+ /* #4553 We need to ignore default accessors for JS native defs.
+ * However, because Scala.js <= 1.7.0 actually emitted code calling
+ * those accessors, we must keep default accessors that would
+ * compile. The only accessors we can actually get rid of are those
+ * that are `= js.native`.
+ */
+ !isJSType(sym.owner) &&
+ info.attachedMethod.hasAnnotation(JSNativeAnnotation) && {
+ dd.rhs match {
+ case MaybeAsInstanceOf(Apply(fun, _)) =>
+ fun.symbol == JSPackage_native
+ case _ =>
+ false
+ }
+ }
+ }
+ }
+ }
+
+ if (scalaPrimitives.isPrimitive(sym)) {
+ None
+ } else if (isAbstract && isNonNativeJSClass(currentClassSym)) {
+ // #4409: Do not emit abstract methods in non-native JS classes
+ None
+ } else if (isIgnorableDefaultParam) {
+ None
+ } else if (sym.isClassConstructor && isHijackedClass(sym.owner)) {
+ None
+ } else {
+ withNewLocalNameScope {
+ Some(genMethodWithCurrentLocalNameScope(dd))
+ }
+ }
+ }
+
+ /** Gen JS code for a method definition in a class or in an impl class.
+ * On the JS side, method names are mangled to encode the full signature
+ * of the Scala method, as described in `JSEncoding`, to support
+ * overloading.
+ *
+ * Constructors are emitted by generating their body as a statement.
+ *
+ * Other (normal) methods are emitted with `genMethodDef()`.
+ */
+ def genMethodWithCurrentLocalNameScope(dd: DefDef,
+ initThisLocalVarName: Option[LocalName] = None): js.MethodDef = {
+
+ implicit val pos = dd.pos
+ val sym = dd.symbol
+
+ withPerMethodBodyState(sym, initThisLocalVarName) {
+ val methodName = encodeMethodSym(sym)
+ val originalName = originalNameOfMethod(sym)
+
+ val jsParams = {
+ val vparamss = dd.vparamss
+ assert(vparamss.isEmpty || vparamss.tail.isEmpty,
+ "Malformed parameter list: " + vparamss)
+ val params = if (vparamss.isEmpty) Nil else vparamss.head.map(_.symbol)
+ params.map(genParamDef(_))
+ }
+
+ val jsMethodDef = if (isAbstractMethod(dd)) {
+ js.MethodDef(js.MemberFlags.empty, methodName, originalName,
+ jsParams, toIRType(sym.tpe.resultType), None)(
+ OptimizerHints.empty, Unversioned)
+ } else {
+ val shouldMarkInline = {
+ sym.hasAnnotation(InlineAnnotationClass) ||
+ sym.name.containsName(nme.ANON_FUN_NAME) ||
+ adHocInlineMethods.contains(sym.fullName)
+ }
+
+ val shouldMarkNoinline = {
+ sym.hasAnnotation(NoinlineAnnotationClass) &&
+ !ignoreNoinlineAnnotation(sym)
+ }
+
+ val optimizerHints =
+ OptimizerHints.empty.
+ withInline(shouldMarkInline).
+ withNoinline(shouldMarkNoinline)
+
+ val methodDef = {
+ if (sym.isClassConstructor) {
+ val namespace = js.MemberNamespace.Constructor
+ js.MethodDef(
+ js.MemberFlags.empty.withNamespace(namespace), methodName,
+ originalName, jsParams, jstpe.VoidType, Some(genStat(dd.rhs)))(
+ optimizerHints, Unversioned)
+ } else {
+ val resultIRType = toIRType(sym.tpe.resultType)
+ val namespace = {
+ if (compileAsStaticMethod(sym)) {
+ if (sym.isPrivate) js.MemberNamespace.PrivateStatic
+ else js.MemberNamespace.PublicStatic
+ } else {
+ if (sym.isPrivate) js.MemberNamespace.Private
+ else js.MemberNamespace.Public
+ }
+ }
+ genMethodDef(namespace, methodName, originalName, jsParams,
+ resultIRType, dd.rhs, optimizerHints)
+ }
+ }
+
+ val methodDefWithoutUselessVars = {
+ val unmutatedMutableLocalVars =
+ (mutableLocalVars.diff(mutatedLocalVars)).toList
+ val mutatedImmutableLocalVals =
+ (mutatedLocalVars.diff(mutableLocalVars)).toList
+ if (unmutatedMutableLocalVars.isEmpty &&
+ mutatedImmutableLocalVals.isEmpty) {
+ // OK, we're good (common case)
+ methodDef
+ } else {
+ val patches = (
+ unmutatedMutableLocalVars.map(encodeLocalSymName(_) -> false) :::
+ mutatedImmutableLocalVals.map(encodeLocalSymName(_) -> true)
+ ).toMap
+ patchMutableFlagOfLocals(methodDef, patches)
+ }
+ }
+
+ methodDefWithoutUselessVars
+ }
+
+ /* #3953 Patch the param defs to have the type advertised by the method's type.
+ * This works around https://github.com/scala/bug/issues/11884, whose fix
+ * upstream is blocked because it is not binary compatible. The fix here
+ * only affects the inside of the js.MethodDef, so it is binary compat.
+ */
+ val paramTypeRewrites = jsParams.zip(sym.tpe.paramTypes.map(toIRType(_))).collect {
+ case (js.ParamDef(name, _, tpe, _), sigType) if tpe != sigType => name.name -> sigType
+ }
+ if (paramTypeRewrites.isEmpty) {
+ // Overwhelmingly common case: all the types match, so there is nothing to do
+ jsMethodDef
+ } else {
+ devWarning(
+ "Working around https://github.com/scala/bug/issues/11884 " +
+ s"for ${sym.fullName} at ${sym.pos}")
+ patchTypeOfParamDefs(jsMethodDef, paramTypeRewrites.toMap)
+ }
+ }
+ }
+
+ def isAbstractMethod(dd: DefDef): Boolean =
+ dd.rhs == EmptyTree
+
+ private val adHocInlineMethods = Set(
+ "scala.collection.mutable.ArrayOps$ofRef.newBuilder$extension",
+ "scala.runtime.ScalaRunTime.arrayClass",
+ "scala.runtime.ScalaRunTime.arrayElementClass"
+ )
+
+ /** Patches the mutable flags of selected locals in a [[js.MethodDef]].
+ *
+ * @param patches Map from local name to new value of the mutable flags.
+ * For locals not in the map, the flag is untouched.
+ */
+ private def patchMutableFlagOfLocals(methodDef: js.MethodDef,
+ patches: Map[LocalName, Boolean]): js.MethodDef = {
+
+ def newMutable(name: LocalName, oldMutable: Boolean): Boolean =
+ patches.getOrElse(name, oldMutable)
+
+ val js.MethodDef(flags, methodName, originalName, params, resultType, body) =
+ methodDef
+ val newParams = for {
+ p @ js.ParamDef(name, originalName, ptpe, mutable) <- params
+ } yield {
+ js.ParamDef(name, originalName, ptpe, newMutable(name.name, mutable))(p.pos)
+ }
+ val transformer = new ir.Transformers.LocalScopeTransformer {
+ override def transform(tree: js.Tree): js.Tree = tree match {
+ case js.VarDef(name, originalName, vtpe, mutable, rhs) =>
+ super.transform(js.VarDef(name, originalName, vtpe,
+ newMutable(name.name, mutable), rhs)(tree.pos))
+ case _ =>
+ super.transform(tree)
+ }
+ }
+ val newBody = transformer.transformTreeOpt(body)
+ js.MethodDef(flags, methodName, originalName, newParams, resultType,
+ newBody)(methodDef.optimizerHints, Unversioned)(methodDef.pos)
+ }
+
+ /** Patches the type of selected param defs in a [[js.MethodDef]].
+ *
+ * @param patches
+ * Map from local name to new type. For param defs not in the map, the
+ * type is untouched.
+ */
+ private def patchTypeOfParamDefs(methodDef: js.MethodDef,
+ patches: Map[LocalName, jstpe.Type]): js.MethodDef = {
+
+ def newType(name: LocalName, oldType: jstpe.Type): jstpe.Type =
+ patches.getOrElse(name, oldType)
+
+ val js.MethodDef(flags, methodName, originalName, params, resultType, body) =
+ methodDef
+ val newParams = for {
+ p @ js.ParamDef(name, originalName, ptpe, mutable) <- params
+ } yield {
+ js.ParamDef(name, originalName, newType(name.name, ptpe), mutable)(p.pos)
+ }
+ val transformer = new ir.Transformers.LocalScopeTransformer {
+ override def transform(tree: js.Tree): js.Tree = tree match {
+ case tree @ js.VarRef(name) =>
+ js.VarRef(name)(newType(name, tree.tpe))(tree.pos)
+ case _ =>
+ super.transform(tree)
+ }
+ }
+ val newBody = transformer.transformTreeOpt(body)
+ js.MethodDef(flags, methodName, originalName, newParams, resultType,
+ newBody)(methodDef.optimizerHints, Unversioned)(methodDef.pos)
+ }
+
+ /** Generates the JSNativeMemberDef of a JS native method. */
+ def genJSNativeMemberDef(tree: DefDef): js.JSNativeMemberDef = {
+ implicit val pos = tree.pos
+
+ val sym = tree.symbol
+ val flags = js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic)
+ val methodName = encodeMethodSym(sym)
+ val jsNativeLoadSpec = jsInterop.jsNativeLoadSpecOf(sym)
+ js.JSNativeMemberDef(flags, methodName, jsNativeLoadSpec)
+ }
+
+ /** Generates the MethodDef of a (non-constructor) method
+ *
+ * Most normal methods are emitted straightforwardly. If the result
+ * type is Unit, then the body is emitted as a statement. Otherwise, it is
+ * emitted as an expression.
+ *
+ * The additional complexity of this method handles the transformation of
+ * a peculiarity of recursive tail calls: the local ValDef that replaces
+ * `this`.
+ */
+ def genMethodDef(namespace: js.MemberNamespace, methodName: js.MethodIdent,
+ originalName: OriginalName, jsParams: List[js.ParamDef],
+ resultIRType: jstpe.Type, tree: Tree,
+ optimizerHints: OptimizerHints): js.MethodDef = {
+ implicit val pos = tree.pos
+
+ val bodyIsStat = resultIRType == jstpe.VoidType
+
+ def genBodyWithinReturnableScope(): js.Tree = tree match {
+ case Block(
+ (thisDef @ ValDef(_, nme.THIS, _, initialThis)) :: otherStats,
+ rhs) =>
+ // This method has tail jumps
+
+ val thisSym = thisDef.symbol
+ if (thisSym.isMutable)
+ mutableLocalVars += thisSym
+
+ /* The `thisLocalIdent` must be nullable. Even though we initially
+ * assign it to `this`, which is non-nullable, tail-recursive calls
+ * may reassign it to a different value, which in general will be
+ * nullable.
+ */
+ val thisLocalIdent = encodeLocalSym(thisSym)
+ val thisLocalType = currentThisTypeNullable
+
+ val genRhs = {
+ /* #3267 In default methods, scalac will type its _$this
+ * pseudo-variable as the *self-type* of the enclosing class,
+ * instead of the enclosing class type itself. However, it then
+ * considers *usages* of _$this as if its type were the
+ * enclosing class type. The latter makes sense, since it is
+ * compiled as `this` in the bytecode, which necessarily needs
+ * to be the enclosing class type. Only the declared type of
+ * _$this is wrong.
+ *
+ * In our case, since we generate an actual local variable for
+ * _$this, we must make sure to type it correctly, as the
+ * enclosing class type. However, this means the rhs of the
+ * ValDef does not match, which is why we have to adapt it
+ * here.
+ */
+ forceAdapt(genExpr(initialThis), thisLocalType)
+ }
+
+ val thisLocalVarDef = js.VarDef(thisLocalIdent, thisOriginalName,
+ thisLocalType, thisSym.isMutable, genRhs)
+
+ val innerBody = {
+ withScopedVars(
+ thisLocalVarName := Some(thisLocalIdent.name)
+ ) {
+ js.Block(otherStats.map(genStat) :+ (
+ if (bodyIsStat) genStat(rhs)
+ else genExpr(rhs)))
+ }
+ }
+
+ js.Block(thisLocalVarDef, innerBody)
+
+ case _ =>
+ if (bodyIsStat) genStat(tree)
+ else genExpr(tree)
+ }
+
+ def genBody(): js.Tree = {
+ withNewReturnableScope(resultIRType) {
+ genBodyWithinReturnableScope()
+ }
+ }
+
+ if (!isNonNativeJSClass(currentClassSym) ||
+ isJSFunctionDef(currentClassSym)) {
+ val flags = js.MemberFlags.empty.withNamespace(namespace)
+ val body = {
+ def genAsUnaryOp(op: js.UnaryOp.Code): js.Tree =
+ js.UnaryOp(op, genThis())
+ def genAsBinaryOp(op: js.BinaryOp.Code): js.Tree =
+ js.BinaryOp(op, genThis(), jsParams.head.ref)
+ def genAsBinaryOpRhsNotNull(op: js.BinaryOp.Code): js.Tree =
+ js.BinaryOp(op, genThis(), js.UnaryOp(js.UnaryOp.CheckNotNull, jsParams.head.ref))
+
+ if (currentClassSym.get == HackedStringClass) {
+ /* Hijack the bodies of String.length and String.charAt and replace
+ * them with String_length and String_charAt operations, respectively.
+ */
+ methodName.name match {
+ case `lengthMethodName` => genAsUnaryOp(js.UnaryOp.String_length)
+ case `charAtMethodName` => genAsBinaryOp(js.BinaryOp.String_charAt)
+ case _ => genBody()
+ }
+ } else if (currentClassSym.get == ClassClass) {
+ // Similar, for the Class_x operations
+ methodName.name match {
+ case `getNameMethodName` => genAsUnaryOp(js.UnaryOp.Class_name)
+ case `isPrimitiveMethodName` => genAsUnaryOp(js.UnaryOp.Class_isPrimitive)
+ case `isInterfaceMethodName` => genAsUnaryOp(js.UnaryOp.Class_isInterface)
+ case `isArrayMethodName` => genAsUnaryOp(js.UnaryOp.Class_isArray)
+ case `getComponentTypeMethodName` => genAsUnaryOp(js.UnaryOp.Class_componentType)
+ case `getSuperclassMethodName` => genAsUnaryOp(js.UnaryOp.Class_superClass)
+
+ case `isInstanceMethodName` => genAsBinaryOp(js.BinaryOp.Class_isInstance)
+ case `isAssignableFromMethodName` => genAsBinaryOpRhsNotNull(js.BinaryOp.Class_isAssignableFrom)
+ case `castMethodName` => genAsBinaryOp(js.BinaryOp.Class_cast)
+
+ case _ => genBody()
+ }
+ } else if (currentClassSym.get == JavaLangReflectArrayModClass) {
+ methodName.name match {
+ case `arrayNewInstanceMethodName` =>
+ val List(jlClassParam, lengthParam) = jsParams
+ js.BinaryOp(js.BinaryOp.Class_newArray,
+ js.UnaryOp(js.UnaryOp.CheckNotNull, jlClassParam.ref),
+ lengthParam.ref)
+ case _ =>
+ genBody()
+ }
+ } else {
+ genBody()
+ }
+ }
+ js.MethodDef(flags, methodName, originalName, jsParams, resultIRType,
+ Some(body))(
+ optimizerHints, Unversioned)
+ } else {
+ assert(!namespace.isStatic, tree.pos)
+
+ val thisLocalIdent = freshLocalIdent("this")
+ withScopedVars(
+ thisLocalVarName := Some(thisLocalIdent.name)
+ ) {
+ val staticNamespace =
+ if (namespace.isPrivate) js.MemberNamespace.PrivateStatic
+ else js.MemberNamespace.PublicStatic
+ val flags =
+ js.MemberFlags.empty.withNamespace(staticNamespace)
+ val thisParamDef = js.ParamDef(thisLocalIdent, thisOriginalName,
+ jstpe.AnyType, mutable = false)
+
+ js.MethodDef(flags, methodName, originalName,
+ thisParamDef :: jsParams, resultIRType, Some(genBody()))(
+ optimizerHints, Unversioned)
+ }
+ }
+ }
+
+ /** Forces the given `tree` to a given type by adapting it.
+ *
+ * @param tree
+ * The tree to adapt.
+ * @param tpe
+ * The target type, which must be either `AnyType` or `ClassType`.
+ */
+ private def forceAdapt(tree: js.Tree, tpe: jstpe.Type): js.Tree = {
+ if (tree.tpe == tpe || tpe == jstpe.AnyType) {
+ tree
+ } else {
+ /* Remove the useless cast that scalac's erasure had to introduce to
+ * work around their own ill-typed _$this. Note that the optimizer will
+ * not be able to do that, since it won't be able to prove that the
+ * underlying expression is indeed an instance of `tpe`.
+ */
+ tree match {
+ case js.AsInstanceOf(underlying, _) if underlying.tpe == tpe =>
+ underlying
+ case _ =>
+ js.AsInstanceOf(tree, tpe)(tree.pos)
+ }
+ }
+ }
+
+ /** Gen JS code for a tree in statement position (in the IR).
+ */
+ def genStat(tree: Tree): js.Tree = {
+ exprToStat(genStatOrExpr(tree, isStat = true))
+ }
+
+ /** Turn a JavaScript expression of type Unit into a statement */
+ def exprToStat(tree: js.Tree): js.Tree = {
+ /* Any JavaScript expression is also a statement, but at least we get rid
+ * of some pure expressions that come from our own codegen.
+ */
+ implicit val pos = tree.pos
+ tree match {
+ case js.Block(stats :+ expr) =>
+ js.Block(stats :+ exprToStat(expr))
+ case _:js.Literal | _:js.VarRef =>
+ js.Skip()
+ case _ =>
+ tree
+ }
+ }
+
+ /** Gen JS code for a tree in expression position (in the IR).
+ */
+ def genExpr(tree: Tree): js.Tree = {
+ val result = genStatOrExpr(tree, isStat = false)
+ assert(result.tpe != jstpe.VoidType,
+ s"genExpr($tree) returned a tree with type VoidType at pos ${tree.pos}")
+ result
+ }
+
+ /** Gen JS code for a tree in expression position (in the IR) or the
+ * global scope.
+ */
+ def genExprOrGlobalScope(tree: Tree): MaybeGlobalScope = {
+ implicit def pos: Position = tree.pos
+
+ tree match {
+ case _: This =>
+ val sym = tree.symbol
+ if (sym != currentClassSym.get && sym.isModule)
+ genLoadModuleOrGlobalScope(sym)
+ else
+ MaybeGlobalScope.NotGlobalScope(genExpr(tree))
+
+ case _:Ident | _:Select =>
+ val sym = tree.symbol
+ if (sym.isModule) {
+ assert(!sym.isPackageClass, "Cannot use package as value: " + tree)
+ genLoadModuleOrGlobalScope(sym)
+ } else {
+ MaybeGlobalScope.NotGlobalScope(genExpr(tree))
+ }
+
+ case _ =>
+ MaybeGlobalScope.NotGlobalScope(genExpr(tree))
+ }
+ }
+
+ /** Gen JS code for a tree in statement or expression position (in the IR).
+ *
+ * This is the main transformation method. Each node of the Scala AST
+ * is transformed into an equivalent portion of the JS AST.
+ */
+ def genStatOrExpr(tree: Tree, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+
+ tree match {
+ /** LabelDefs (for while and do..while loops) */
+ case lblDf: LabelDef =>
+ genLabelDef(lblDf, isStat)
+
+ /** Local val or var declaration */
+ case ValDef(_, name, _, rhs) =>
+ /* Must have been eliminated by the tail call transform performed
+ * by genMethodDef().
+ * If you ever change/remove this assertion, you need to update
+ * genEnclosingLabelApply() regarding `nme.THIS`.
+ */
+ assert(name != nme.THIS,
+ s"ValDef(_, nme.THIS, _, _) found at ${tree.pos}")
+
+ val sym = tree.symbol
+ val rhsTree =
+ if (rhs == EmptyTree) jstpe.zeroOf(toIRType(sym.tpe))
+ else genExpr(rhs)
+
+ rhsTree match {
+ case js.Transient(UndefinedParam) =>
+ // This is an intermediate assignment for default params on a
+ // js.Any. Add the symbol to the corresponding set to inform
+ // the Ident resolver how to replace it and don't emit the symbol
+ undefinedDefaultParams += sym
+ js.Skip()
+ case _ =>
+ if (sym.isMutable)
+ mutableLocalVars += sym
+ js.VarDef(encodeLocalSym(sym), originalNameOfLocal(sym),
+ toIRType(sym.tpe), sym.isMutable, rhsTree)
+ }
+
+ case tree @ If(cond, thenp, elsep) =>
+ def default: js.Tree = {
+ val tpe =
+ if (isStat) jstpe.VoidType
+ else toIRType(tree.tpe)
+
+ js.If(genExpr(cond), genStatOrExpr(thenp, isStat),
+ genStatOrExpr(elsep, isStat))(tpe)
+ }
+
+ if (isStat && currentMethodSym.isClassConstructor) {
+ /* Nested classes that need an outer pointer have a weird shape for
+ * assigning it, with an explicit null check. It looks like this:
+ *
+ * if ($outer.eq(null))
+ * throw null
+ * else
+ * this.$outer = $outer
+ *
+ * This is a bad shape for our optimizations, notably because the
+ * explicit null check cannot be considered UB at the IR level if
+ * we compile it as is, although in the original *language*, that
+ * would clearly fall into UB.
+ *
+ * Therefore, we intercept that shape and rewrite it as follows
+ * instead:
+ *
+ * ($outer) // null check subject to UB
+ * this.$outer = $outer // the `else` branch in general
+ */
+ tree match {
+ case OuterPointerNullCheck(outer, elsep) =>
+ js.Block(
+ js.UnaryOp(js.UnaryOp.CheckNotNull, genExpr(outer)),
+ genStat(elsep)
+ )
+ case _ =>
+ default
+ }
+ } else {
+ default
+ }
+
+ case Return(expr) =>
+ js.Return(
+ genStatOrExpr(expr, isStat = toIRType(expr.tpe) == jstpe.VoidType),
+ getEnclosingReturnLabel())
+
+ case t: Try =>
+ genTry(t, isStat)
+
+ case Throw(expr) =>
+ val ex = genExpr(expr)
+ ex match {
+ case js.New(cls, _, _) if cls != JavaScriptExceptionClassName =>
+ // Common case where ex is neither null nor a js.JavaScriptException
+ js.UnaryOp(js.UnaryOp.Throw, ex)
+ case _ =>
+ js.UnaryOp(js.UnaryOp.Throw,
+ js.UnaryOp(js.UnaryOp.UnwrapFromThrowable, js.UnaryOp(js.UnaryOp.CheckNotNull, ex)))
+ }
+
+ /* !!! Copy-pasted from `CleanUp.scala` upstream and simplified with
+ * our `WrapArray` extractor.
+ *
+ * Replaces `Array(Predef.wrapArray(ArrayValue(...).$asInstanceOf[...]), )`
+ * with just `ArrayValue(...)`
+ *
+ * See scala/bug#6611; we must *only* do this for literal vararg arrays.
+ *
+ * This is normally done by `cleanup` but it comes later than this phase.
+ */
+ case Apply(appMeth,
+ Apply(wrapRefArrayMeth, StripCast(arg @ ArrayValue(elemtpt, elems)) :: Nil) :: classTagEvidence :: Nil)
+ if WrapArray.isClassTagBasedWrapArrayMethod(wrapRefArrayMeth.symbol) &&
+ appMeth.symbol == ArrayModule_genericApply &&
+ !elemtpt.tpe.typeSymbol.isBottomClass &&
+ !elemtpt.tpe.typeSymbol.isPrimitiveValueClass /* can happen via specialization.*/ =>
+ classTagEvidence.attachments.get[analyzer.MacroExpansionAttachment] match {
+ case Some(att)
+ if att.expandee.symbol.name == nme.materializeClassTag && tree.isInstanceOf[ApplyToImplicitArgs] =>
+ genArrayValue(arg)
+ case _ =>
+ val arrValue = genApplyMethod(
+ genExpr(classTagEvidence),
+ ClassTagClass.info.decl(nme.newArray),
+ js.IntLiteral(elems.size) :: Nil)
+ val arrVarDef = js.VarDef(freshLocalIdent("arr"), NoOriginalName,
+ arrValue.tpe, mutable = false, arrValue)
+ val stats = List.newBuilder[js.Tree]
+ foreachWithIndex(elems) { (elem, i) =>
+ stats += genApplyMethod(
+ genLoadModule(ScalaRunTimeModule),
+ currentRun.runDefinitions.arrayUpdateMethod,
+ arrVarDef.ref :: js.IntLiteral(i) :: genExpr(elem) :: Nil)
+ }
+ js.Block(arrVarDef :: stats.result(), arrVarDef.ref)
+ }
+ case Apply(appMeth, elem0 :: WrapArray(rest @ ArrayValue(elemtpt, _)) :: Nil)
+ if appMeth.symbol == ArrayModule_apply(elemtpt.tpe) =>
+ genArrayValue(rest, elem0 :: rest.elems)
+ case Apply(appMeth, elem :: (nil: RefTree) :: Nil)
+ if nil.symbol == NilModule && appMeth.symbol == ArrayModule_apply(elem.tpe.widen) &&
+ treeInfo.isExprSafeToInline(nil) =>
+ genArrayValue(tree, elem :: Nil)
+
+ case app: Apply =>
+ genApply(app, isStat)
+
+ case app: ApplyDynamic =>
+ genApplyDynamic(app)
+
+ case This(qual) =>
+ if (tree.symbol == currentClassSym.get) {
+ genThis()
+ } else {
+ assert(tree.symbol.isModuleClass,
+ "Trying to access the this of another class: " +
+ "tree.symbol = " + tree.symbol +
+ ", class symbol = " + currentClassSym.get +
+ " compilation unit:" + currentUnit)
+ genLoadModule(tree.symbol)
+ }
+
+ case Select(qualifier, selector) =>
+ val sym = tree.symbol
+
+ def unboxFieldValue(boxed: js.Tree): js.Tree = {
+ fromAny(boxed,
+ enteringPhase(currentRun.posterasurePhase)(sym.tpe))
+ }
+
+ if (sym.isModule) {
+ assert(!sym.isPackageClass, "Cannot use package as value: " + tree)
+ genLoadModule(sym)
+ } else if (sym.isStaticMember) {
+ genStaticField(sym)
+ } else if (paramAccessorLocals contains sym) {
+ paramAccessorLocals(sym).ref
+ } else {
+ val (field, boxed) = genAssignableField(sym, qualifier)
+ if (boxed) unboxFieldValue(field)
+ else field
+ }
+
+ case Ident(name) =>
+ val sym = tree.symbol
+ if (!sym.hasPackageFlag) {
+ if (sym.isModule) {
+ assert(!sym.isPackageClass, "Cannot use package as value: " + tree)
+ genLoadModule(sym)
+ } else if (undefinedDefaultParams contains sym) {
+ // This is a default parameter whose assignment was moved to
+ // a local variable. Put a literal undefined param again
+ js.Transient(UndefinedParam)
+ } else {
+ genVarRef(sym)
+ }
+ } else {
+ abort("Cannot use package as value: " + tree)
+ }
+
+ case Literal(value) =>
+ value.tag match {
+ case UnitTag =>
+ js.Skip()
+ case BooleanTag =>
+ js.BooleanLiteral(value.booleanValue)
+ case ByteTag =>
+ js.ByteLiteral(value.byteValue)
+ case ShortTag =>
+ js.ShortLiteral(value.shortValue)
+ case CharTag =>
+ js.CharLiteral(value.charValue)
+ case IntTag =>
+ js.IntLiteral(value.intValue)
+ case LongTag =>
+ js.LongLiteral(value.longValue)
+ case FloatTag =>
+ js.FloatLiteral(value.floatValue)
+ case DoubleTag =>
+ js.DoubleLiteral(value.doubleValue)
+ case StringTag =>
+ js.StringLiteral(value.stringValue)
+ case NullTag =>
+ js.Null()
+ case ClazzTag =>
+ js.ClassOf(toTypeRef(value.typeValue))
+ case EnumTag =>
+ genStaticField(value.symbolValue)
+ }
+
+ case tree: Block =>
+ genBlock(tree, isStat)
+
+ case Typed(Super(_, _), _) =>
+ genThis()
+
+ case Typed(expr, _) =>
+ genExpr(expr)
+
+ case Assign(lhs, rhs) =>
+ val sym = lhs.symbol
+ if (sym.isStaticMember)
+ abort(s"Assignment to static member ${sym.fullName} not supported")
+ def genRhs = genExpr(rhs)
+ lhs match {
+ case Select(qualifier, _) =>
+ /* Record assignments to fields of the current class.
+ *
+ * We only do that for fields of the current class sym. For other
+ * fields, even if we recorded it, we would forget them when
+ * `fieldsMutatedInCurrentClass` is reset when going out of the
+ * class. If we assign to an immutable field in a different
+ * class, it will be reported as an IR checking error.
+ *
+ * Assignments to `this.` fields in the constructor are valid
+ * even for immutable fields, and are therefore not recorded.
+ *
+ * #3918 We record the *names* of the fields instead of their
+ * symbols because, in rare cases, scalac has different fields in
+ * the trees than in the class' decls. Since we only record fields
+ * from the current class, names are non ambiguous. For the same
+ * reason, we record assignments to *all* fields, not only the
+ * immutable ones, because in 2.13 the symbol here can be mutable
+ * but not the one in the class' decls.
+ */
+ if (sym.owner == currentClassSym.get) {
+ val ctorAssignment = (
+ currentMethodSym.isClassConstructor &&
+ currentMethodSym.owner == qualifier.symbol &&
+ qualifier.isInstanceOf[This]
+ )
+ if (!ctorAssignment)
+ fieldsMutatedInCurrentClass += sym.name
+ }
+
+ def genBoxedRhs: js.Tree = {
+ val tpeEnteringPosterasure =
+ enteringPhase(currentRun.posterasurePhase)(rhs.tpe)
+ if ((tpeEnteringPosterasure eq null) && genRhs.isInstanceOf[js.Null]) {
+ devWarning(
+ "Working around https://github.com/scala-js/scala-js/issues/3422 " +
+ s"for ${sym.fullName} at ${sym.pos}")
+ // Fortunately, a literal `null` never needs to be boxed
+ genRhs
+ } else {
+ ensureBoxed(genRhs, tpeEnteringPosterasure)
+ }
+ }
+
+ if (sym.hasAnnotation(JSNativeAnnotation)) {
+ /* This is an assignment to a @js.native field. Since we reject
+ * `@js.native var`s as compile errors, this can only happen in
+ * the constructor of the enclosing object.
+ * We simply ignore the assignment, since the field will not be
+ * emitted at all.
+ */
+ js.Skip()
+ } else {
+ val (field, boxed) = genAssignableField(sym, qualifier)
+
+ if (boxed) js.Assign(field, genBoxedRhs)
+ else js.Assign(field,genRhs)
+ }
+
+ case _ =>
+ mutatedLocalVars += sym
+ js.Assign(
+ js.VarRef(encodeLocalSymName(sym))(toIRType(sym.tpe)),
+ genRhs)
+ }
+
+ /** Array constructor */
+ case av: ArrayValue =>
+ genArrayValue(av)
+
+ /** A Match reaching the backend is supposed to be optimized as a switch */
+ case mtch: Match =>
+ genMatch(mtch, isStat)
+
+ /** Anonymous function */
+ case fun: Function =>
+ genAnonFunction(fun)
+
+ case EmptyTree =>
+ js.Skip()
+
+ case _ =>
+ abort("Unexpected tree in genExpr: " +
+ tree + "/" + tree.getClass + " at: " + tree.pos)
+ }
+ } // end of GenJSCode.genExpr()
+
+ /** Extractor for the shape of outer pointer null check.
+ *
+ * See the comment in `case If(...) =>` of `genExpr`.
+ *
+ * When successful, returns the pair `(outer, elsep)` where `outer` is the
+ * `Ident` of the outer constructor parameter, and `elsep` is the else
+ * branch of the condition.
+ */
+ private object OuterPointerNullCheck {
+ def unapply(tree: If): Option[(Ident, Tree)] = tree match {
+ case If(Apply(fun @ Select(outer: Ident, nme.eq), Literal(Constant(null)) :: Nil),
+ Throw(Literal(Constant(null))), elsep)
+ if outer.symbol.isOuterParam && fun.symbol == definitions.Object_eq =>
+ Some((outer, elsep))
+ case _ =>
+ None
+ }
+ }
+
+ /** Gen JS this of the current class.
+ * Normally encoded straightforwardly as a JS this.
+ * But must be replaced by the tail-jump-this local variable if there
+ * is one.
+ */
+ private def genThis()(implicit pos: Position): js.Tree = {
+ thisLocalVarName.fold[js.Tree] {
+ if (isJSFunctionDef(currentClassSym)) {
+ abort(
+ "Unexpected `this` reference inside the body of a JS function class: " +
+ currentClassSym.fullName)
+ }
+ js.This()(currentThisType)
+ } { thisLocalName =>
+ js.VarRef(thisLocalName)(currentThisTypeNullable)
+ }
+ }
+
+ /** Gen JS code for LabelDef.
+ *
+ * If a LabelDef reaches this method, then the only valid jumps are from
+ * within it, which means it basically represents a loop. Other kinds of
+ * LabelDefs, notably those for matches, are caught upstream and
+ * transformed in ad hoc ways.
+ *
+ * The general transformation for
+ * {{{
+ * labelName(...labelParams) {
+ * rhs
+ * }: T
+ * }}}
+ * is the following:
+ * {{{
+ * block[T]: {
+ * while (true) {
+ * labelName[void]: {
+ * return@block transformedRhs
+ * }
+ * }
+ * }
+ * }}}
+ * where all jumps to the label inside the rhs of the form
+ * {{{
+ * labelName(...args)
+ * }}}
+ * are transformed into
+ * {{{
+ * ...labelParams = ...args;
+ * return@labelName;
+ * }}}
+ *
+ * This is always correct, so it can handle arbitrary labels and jumps
+ * such as those produced by loops, tail-recursive calls and even some
+ * compiler plugins (see for example #1148). However, the result is
+ * unnecessarily ugly for simple `while` and `do while` loops, so we have
+ * some post-processing to simplify those.
+ */
+ def genLabelDef(tree: LabelDef, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val sym = tree.symbol
+
+ val labelParamSyms = tree.params.map(_.symbol)
+ val info = new EnclosingLabelDefInfoWithResultAsAssigns(labelParamSyms)
+
+ val labelName = encodeLabelSym(sym)
+
+ val transformedRhs = withScopedVars(
+ enclosingLabelDefInfos := enclosingLabelDefInfos.get + (sym -> info)
+ ) {
+ genStatOrExpr(tree.rhs, isStat)
+ }
+
+ /** Matches a `js.Return` to the current `labelName`, and returns the
+ * `exprToStat()` of the returned expression.
+ * We only keep the `exprToStat()` because this label has a `void` type,
+ * so the expression is always discarded except for its side effects.
+ */
+ object ReturnFromThisLabel {
+ def unapply(tree: js.Return): Option[js.Tree] = {
+ if (tree.label == labelName) Some(exprToStat(tree.expr))
+ else None
+ }
+ }
+
+ def genDefault(): js.Tree = {
+ if (transformedRhs.tpe == jstpe.NothingType) {
+ // In this case, we do not need the outer block label
+ js.While(js.BooleanLiteral(true), {
+ js.Labeled(labelName, jstpe.VoidType, {
+ transformedRhs match {
+ // Eliminate a trailing return@lab
+ case js.Block(stats :+ ReturnFromThisLabel(exprAsStat)) =>
+ js.Block(stats :+ exprAsStat)
+ case _ =>
+ transformedRhs
+ }
+ })
+ })
+ } else {
+ // When all else has failed, we need the full machinery
+ val blockLabelName = freshLabelName("block")
+ val bodyType =
+ if (isStat) jstpe.VoidType
+ else toIRType(tree.tpe)
+ js.Labeled(blockLabelName, bodyType, {
+ js.While(js.BooleanLiteral(true), {
+ js.Labeled(labelName, jstpe.VoidType, {
+ if (isStat)
+ js.Block(transformedRhs, js.Return(js.Skip(), blockLabelName))
+ else
+ js.Return(transformedRhs, blockLabelName)
+ })
+ })
+ })
+ }
+ }
+
+ info.generatedReturns match {
+ case 0 =>
+ /* There are no jumps to the loop label. Therefore we can remove
+ * the labeled block and and the loop altogether.
+ * This happens for `while (false)` and `do while (false)` loops.
+ */
+ transformedRhs
+
+ case 1 =>
+ /* There is exactly one jump. Let us see if we can isolate where it
+ * is to try and remove unnecessary labeled blocks and keep only
+ * the loop.
+ */
+ transformedRhs match {
+ /* { stats; return@lab expr }
+ * -> while (true) { stats; expr }
+ * This happens for `while (true)` and `do while (true)` loops.
+ */
+ case BlockOrAlone(stats, ReturnFromThisLabel(exprAsStat)) =>
+ js.While(js.BooleanLiteral(true), {
+ js.Block(stats, exprAsStat)
+ })
+
+ /* if (cond) { stats; return@lab expr } else elsep [; rest]
+ * -> while (cond) { stats; expr }; elsep; rest
+ * This happens for `while (cond)` loops with a non-constant `cond`.
+ * There is a `rest` if the while loop is on the rhs of a case in a
+ * patmat.
+ */
+ case FirstInBlockOrAlone(
+ js.If(cond, BlockOrAlone(stats, ReturnFromThisLabel(exprAsStat)), elsep),
+ rest) =>
+ js.Block(
+ js.While(cond, {
+ js.Block(stats, exprAsStat)
+ }) ::
+ elsep ::
+ rest
+ )
+
+ /* { stats; if (cond) { return@lab pureExpr } else { skip } }
+ *
+ * !! `cond` could refer to VarDefs declared in stats, and we have
+ * no way of telling (short of traversing `cond` again) so we
+ * generate a `while` loop anyway:
+ *
+ * -> while ({ stats; cond }) { skip }
+ *
+ * The `pureExpr` must be pure because we cannot add it after the
+ * `cond` above. It must be eliminated, which is only valid if it
+ * is pure.
+ *
+ * This happens for `do while (cond)` loops with a non-constant
+ * `cond`.
+ *
+ * There is no need for BlockOrAlone because the alone case would
+ * also be caught by the `case js.If` above.
+ */
+ case js.Block(stats :+ js.If(cond, ReturnFromThisLabel(js.Skip()), js.Skip())) =>
+ js.While(js.Block(stats, cond), js.Skip())
+
+ /* { stats; if (cond) { return@lab pureExpr } else { skip }; literal }
+ *
+ * Same as above, but there is an additional `literal` at the end.
+ *
+ * This happens for `do while (cond)` loops with a non-constant
+ * `cond` that are in the rhs of a case in a patmat.
+ */
+ case js.Block(stats :+ js.If(cond, ReturnFromThisLabel(js.Skip()), js.Skip()) :+ (res: js.Literal)) =>
+ js.Block(js.While(js.Block(stats, cond), js.Skip()), res)
+
+ case _ =>
+ genDefault()
+ }
+
+ case moreThan1 =>
+ genDefault()
+ }
+ }
+
+ /** Gen JS code for a try..catch or try..finally block
+ *
+ * try..finally blocks are compiled straightforwardly to try..finally
+ * blocks of JS.
+ *
+ * try..catch blocks are a bit more subtle, as JS does not have
+ * type-based selection of exceptions to catch. We thus encode explicitly
+ * the type tests, like in:
+ *
+ * try { ... }
+ * catch (e) {
+ * if (e.isInstanceOf[IOException]) { ... }
+ * else if (e.isInstanceOf[Exception]) { ... }
+ * else {
+ * throw e; // default, re-throw
+ * }
+ * }
+ */
+ def genTry(tree: Try, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Try(block, catches, finalizer) = tree
+
+ val blockAST = genStatOrExpr(block, isStat)
+
+ val resultType =
+ if (isStat) jstpe.VoidType
+ else toIRType(tree.tpe)
+
+ val handled =
+ if (catches.isEmpty) blockAST
+ else genTryCatch(blockAST, catches, resultType, isStat)
+
+ genStat(finalizer) match {
+ case js.Skip() => handled
+ case ast => js.TryFinally(handled, ast)
+ }
+ }
+
+ private def genTryCatch(body: js.Tree, catches: List[CaseDef],
+ resultType: jstpe.Type, isStat: Boolean)(
+ implicit pos: Position): js.Tree = {
+
+ catches match {
+ case CaseDef(Ident(nme.WILDCARD), _, catchAllBody) :: Nil =>
+ genTryCatchCatchIgnoreAll(body, catchAllBody, resultType, isStat)
+
+ case CaseDef(Typed(Ident(nme.WILDCARD), tpt), _, catchAllBody) :: Nil
+ if tpt.tpe.typeSymbol == ThrowableClass =>
+ genTryCatchCatchIgnoreAll(body, catchAllBody, resultType, isStat)
+
+ case _ =>
+ genTryCatchNotIgnoreAll(body, catches, resultType, isStat)
+ }
+ }
+
+ private def genTryCatchCatchIgnoreAll(body: js.Tree, catchAllBody: Tree,
+ resultType: jstpe.Type, isStat: Boolean)(
+ implicit pos: Position): js.Tree = {
+
+ js.TryCatch(body, freshLocalIdent("e"), NoOriginalName,
+ genStatOrExpr(catchAllBody, isStat))(
+ resultType)
+ }
+
+ private def genTryCatchNotIgnoreAll(body: js.Tree, catches: List[CaseDef],
+ resultType: jstpe.Type, isStat: Boolean)(
+ implicit pos: Position): js.Tree = {
+
+ val exceptIdent = freshLocalIdent("e")
+ val origExceptVar = js.VarRef(exceptIdent.name)(jstpe.AnyType)
+
+ val mightCatchJavaScriptException = catches.exists { caseDef =>
+ caseDef.pat match {
+ case Typed(Ident(nme.WILDCARD), tpt) =>
+ isMaybeJavaScriptException(tpt.tpe)
+ case Ident(nme.WILDCARD) =>
+ true
+ case pat @ Bind(_, _) =>
+ isMaybeJavaScriptException(pat.symbol.tpe)
+ }
+ }
+
+ val (exceptValDef, exceptVar) = if (mightCatchJavaScriptException) {
+ val valDef = js.VarDef(freshLocalIdent("e"), NoOriginalName,
+ encodeClassType(ThrowableClass), mutable = false,
+ js.UnaryOp(js.UnaryOp.WrapAsThrowable, origExceptVar))
+ (valDef, valDef.ref)
+ } else {
+ (js.Skip(), origExceptVar)
+ }
+
+ val elseHandler: js.Tree = js.UnaryOp(js.UnaryOp.Throw, origExceptVar)
+
+ val handler = catches.foldRight(elseHandler) { (caseDef, elsep) =>
+ implicit val pos = caseDef.pos
+ val CaseDef(pat, _, body) = caseDef
+
+ // Extract exception type and variable
+ val (tpe, boundVar) = (pat match {
+ case Typed(Ident(nme.WILDCARD), tpt) =>
+ (tpt.tpe, None)
+ case Ident(nme.WILDCARD) =>
+ (ThrowableClass.tpe, None)
+ case Bind(_, _) =>
+ val ident = encodeLocalSym(pat.symbol)
+ val origName = originalNameOfLocal(pat.symbol)
+ (pat.symbol.tpe, Some((ident, origName)))
+ })
+
+ // Generate the body that must be executed if the exception matches
+ val bodyWithBoundVar = (boundVar match {
+ case None =>
+ genStatOrExpr(body, isStat)
+ case Some((boundVarIdent, boundVarOriginalName)) =>
+ val castException = genAsInstanceOf(exceptVar, tpe)
+ js.Block(
+ js.VarDef(boundVarIdent, boundVarOriginalName, toIRType(tpe),
+ mutable = false, castException),
+ genStatOrExpr(body, isStat))
+ })
+
+ // Generate the test
+ if (tpe == ThrowableClass.tpe) {
+ bodyWithBoundVar
+ } else {
+ val cond = genIsInstanceOf(exceptVar, tpe)
+ js.If(cond, bodyWithBoundVar, elsep)(resultType)
+ }
+ }
+
+ js.TryCatch(body, exceptIdent, NoOriginalName,
+ js.Block(exceptValDef, handler))(resultType)
+ }
+
+ /** Gen JS code for an Apply node (method call)
+ *
+ * There's a whole bunch of varieties of Apply nodes: regular method
+ * calls, super calls, constructor calls, isInstanceOf/asInstanceOf,
+ * primitives, JS calls, etc. They are further dispatched in here.
+ */
+ def genApply(tree: Apply, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun, args) = tree
+ val sym = fun.symbol
+
+ /* Is the method a JS default accessor, which should become an
+ * `UndefinedParam` rather than being compiled normally.
+ *
+ * This is true iff one of the following conditions apply:
+ * - It is a constructor default param for the constructor of a JS class.
+ * - It is a default param of an instance method of a native JS type.
+ * - It is a default param of an instance method of a non-native JS type
+ * and the attached method is exposed.
+ * - It is a default param for a native JS def.
+ *
+ * This is different than `isIgnorableDefaultParam` in `genMethod`: we
+ * include here the default accessors of *non-native* JS types (unless
+ * the corresponding methods are not exposed). We also need to handle
+ * non-constructor members of native JS types.
+ */
+ def isJSDefaultParam: Boolean = {
+ DefaultParamInfo.isApplicable(sym) && {
+ val info = new DefaultParamInfo(sym)
+ if (info.isForConstructor) {
+ /* This is a default accessor for a constructor parameter. Check
+ * whether the attached constructor is a JS constructor, which is
+ * the case iff the linked class is a JS type.
+ */
+ isJSType(info.constructorOwner)
+ } else {
+ if (isJSType(sym.owner)) {
+ /* The default accessor is in a JS type. It is a JS default
+ * param iff the enclosing class is native or the attached method
+ * is exposed.
+ */
+ !isNonNativeJSClass(sym.owner) || isExposed(info.attachedMethod)
+ } else {
+ /* The default accessor is in a Scala type. It is a JS default
+ * param iff the attached method is a native JS def. This can
+ * only happen if the owner is a module class, which we test
+ * first as a fast way out.
+ */
+ sym.owner.isModuleClass && info.attachedMethod.hasAnnotation(JSNativeAnnotation)
+ }
+ }
+ }
+ }
+
+ fun match {
+ case TypeApply(_, _) =>
+ genApplyTypeApply(tree, isStat)
+
+ case _ if isJSDefaultParam =>
+ js.Transient(UndefinedParam)
+
+ case Select(Super(_, _), _) =>
+ genSuperCall(tree, isStat)
+
+ case Select(New(_), nme.CONSTRUCTOR) =>
+ genApplyNew(tree)
+
+ case _ =>
+ if (sym.isLabel) {
+ genLabelApply(tree)
+ } else if (scalaPrimitives.isPrimitive(sym)) {
+ genPrimitiveOp(tree, isStat)
+ } else if (currentRun.runDefinitions.isBox(sym)) {
+ // Box a primitive value (cannot be Unit)
+ val arg = args.head
+ makePrimitiveBox(genExpr(arg), sym.firstParam.tpe)
+ } else if (currentRun.runDefinitions.isUnbox(sym)) {
+ // Unbox a primitive value (cannot be Unit)
+ val arg = args.head
+ makePrimitiveUnbox(genExpr(arg), tree.tpe)
+ } else {
+ genNormalApply(tree, isStat)
+ }
+ }
+ }
+
+ /** Gen an Apply with a TypeApply method.
+ *
+ * Until 2.12.0-M5, only `isInstanceOf` and `asInstanceOf` kept their type
+ * argument until the backend. Since 2.12.0-RC1, `AnyRef.synchronized`
+ * does so too.
+ */
+ private def genApplyTypeApply(tree: Apply, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(TypeApply(fun @ Select(obj, _), targs), args) = tree
+ val sym = fun.symbol
+
+ sym match {
+ case Object_isInstanceOf =>
+ genIsAsInstanceOf(obj, targs, cast = false)
+ case Object_asInstanceOf =>
+ genIsAsInstanceOf(obj, targs, cast = true)
+ case Object_synchronized =>
+ genSynchronized(obj, args.head, isStat)
+ case _ =>
+ abort("Unexpected type application " + fun +
+ "[sym: " + sym.fullName + "]" + " in: " + tree)
+ }
+ }
+
+ /** Gen `isInstanceOf` or `asInstanceOf`. */
+ private def genIsAsInstanceOf(obj: Tree, targs: List[Tree], cast: Boolean)(
+ implicit pos: Position): js.Tree = {
+ genIsAsInstanceOf(genExpr(obj), obj.tpe, targs.head.tpe, cast)
+ }
+
+ /** Gen `isInstanceOf` or `asInstanceOf`. */
+ private def genIsAsInstanceOf(expr: js.Tree, from: Type, to: Type,
+ cast: Boolean)(
+ implicit pos: Position): js.Tree = {
+ val l = toIRType(from)
+ val r = toIRType(to)
+
+ def isValueType(tpe: jstpe.Type): Boolean = tpe match {
+ case jstpe.VoidType | jstpe.BooleanType | jstpe.CharType |
+ jstpe.ByteType | jstpe.ShortType | jstpe.IntType | jstpe.LongType |
+ jstpe.FloatType | jstpe.DoubleType =>
+ true
+ case _ =>
+ false
+ }
+
+ val lIsValueType = isValueType(l)
+ val rIsValueType = isValueType(r)
+
+ if (lIsValueType && rIsValueType) {
+ if (cast) {
+ /* It is unclear whether this case can be reached for all type
+ * conversions, but scalac handles all cases, so we do too.
+ * Three known user code patterns that become code handled by this
+ * case are `byte.##`, `short.##` and `char.##`, which become, e.g.,
+ * `char.toChar().$asInstanceOf[Int]`.
+ */
+ genConversion(l, r, expr)
+ } else {
+ js.BooleanLiteral(l == r)
+ }
+ } else if (lIsValueType) {
+ val result =
+ if (cast) genThrowClassCastException()
+ else js.BooleanLiteral(false)
+ js.Block(expr, result) // eval and discard source
+ } else if (rIsValueType) {
+ assert(!cast, s"Unexpected asInstanceOf from ref type to value type")
+ genIsInstanceOf(expr, boxedClass(to.typeSymbol).tpe)
+ } else {
+ if (cast)
+ genAsInstanceOf(expr, to)
+ else
+ genIsInstanceOf(expr, to)
+ }
+ }
+
+ private def genThrowClassCastException()(implicit pos: Position): js.Tree = {
+ val ctor = ClassCastExceptionClass.info.member(
+ nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty)
+ js.UnaryOp(js.UnaryOp.Throw, genNew(ClassCastExceptionClass, ctor, Nil))
+ }
+
+ /** Gen JS code for a super call, of the form Class.super[mix].fun(args).
+ *
+ * This does not include calls defined in mixin traits, as these are
+ * already desugared by the 'mixin' phase. Only calls to super classes
+ * remain.
+ * Since a class has exactly one direct superclass, and calling a method
+ * two classes above the current one is invalid, the `mix` item is
+ * irrelevant.
+ */
+ private def genSuperCall(tree: Apply, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun @ Select(sup @ Super(qual, _), _), args) = tree
+ val sym = fun.symbol
+
+ if (isJSType(qual.tpe)) {
+ if (sym.isMixinConstructor) {
+ /* Do not emit a call to the $init$ method of JS traits.
+ * This exception is necessary because @JSOptional fields cause the
+ * creation of a $init$ method, which we must not call.
+ */
+ js.Skip()
+ } else {
+ genJSSuperCall(tree, isStat)
+ }
+ } else {
+ /* #3013 `qual` can be `this.$outer()` in some cases since Scala 2.12,
+ * so we call `genExpr(qual)`, not just `genThis()`.
+ */
+ val superCall = genApplyMethodStatically(
+ genExpr(qual), sym, genActualArgs(sym, args))
+
+ // Initialize the module instance just after the super constructor call.
+ if (isStaticModule(currentClassSym) && !isModuleInitialized.value &&
+ currentMethodSym.isClassConstructor) {
+ isModuleInitialized.value = true
+ js.Block(superCall, js.StoreModule())
+ } else {
+ superCall
+ }
+ }
+ }
+
+ /** Gen JS code for a constructor call (new).
+ *
+ * Further refined into:
+ * * new of a hijacked boxed class
+ * * new of a JS function class
+ * * new of a JS class
+ * * new Array
+ * * regular new
+ */
+ private def genApplyNew(tree: Apply): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) = tree
+ val ctor = fun.symbol
+ val tpe = tpt.tpe
+ val clsSym = tpe.typeSymbol
+
+ assert(ctor.isClassConstructor,
+ "'new' call to non-constructor: " + ctor.name)
+
+ if (isHijackedClass(clsSym)) {
+ genNewHijackedClass(clsSym, ctor, args.map(genExpr))
+ } else if (isJSFunctionDef(clsSym)) {
+ val classDef = consumeLazilyGeneratedAnonClass(clsSym)
+ genJSFunction(classDef, args.map(genExpr))
+ } else if (isJSType(clsSym)) {
+ genPrimitiveJSNew(tree)
+ } else {
+ toTypeRef(tpe) match {
+ case jstpe.ClassRef(className) =>
+ genNew(className, ctor, genActualArgs(ctor, args))
+ case arr: jstpe.ArrayTypeRef =>
+ genNewArray(arr, args.map(genExpr))
+ case prim: jstpe.PrimRef =>
+ abort(s"unexpected primitive type $prim in New at $pos")
+ case typeRef: jstpe.TransientTypeRef =>
+ abort(s"unexpected special type ref $typeRef in New at $pos")
+ }
+ }
+ }
+
+ /** Gen jump to a label.
+ *
+ * Some label-applys are caught upstream (jumps to next case of a pattern
+ * match that are in tail-pos or their own case), but most are handled
+ * here, notably:
+ *
+ * - Jumps to the beginning label of loops, including tail-recursive calls
+ * - Jumps to the next case label that are not in tail position
+ * - Jumps to the end of a pattern match
+ */
+ private def genLabelApply(tree: Apply): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun, args) = tree
+ val sym = fun.symbol
+
+ val info = enclosingLabelDefInfos.getOrElse(sym, {
+ abort("Found unknown label apply at "+tree.pos+": "+tree)
+ })
+
+ val labelIdent = encodeLabelSym(sym)
+ info.generatedReturns += 1
+
+ def assertArgCountMatches(expected: Int): Unit = {
+ assert(args.size == expected,
+ s"argument count mismatch for label-apply at $pos: " +
+ s"expected $expected but got ${args.size}")
+ }
+
+ info match {
+ case info: EnclosingLabelDefInfoWithResultAsAssigns =>
+ val paramSyms = info.paramSyms
+ assertArgCountMatches(paramSyms.size)
+
+ val jump = js.Return(js.Skip(), labelIdent)
+
+ if (args.isEmpty) {
+ // fast path, applicable notably to loops and case labels
+ jump
+ } else {
+ js.Block(genMultiAssign(paramSyms, args), jump)
+ }
+
+ case _: EnclosingLabelDefInfoWithResultAsReturn =>
+ assertArgCountMatches(1)
+ js.Return(genExpr(args.head), labelIdent)
+ }
+ }
+
+ /** Gen multiple "parallel" assignments.
+ *
+ * This is used when assigning the new value of multiple parameters of a
+ * label-def, notably for the ones generated for tail-recursive methods.
+ *
+ * Since the rhs for the new value of an argument can depend on the value
+ * of another argument (and since deciding if it is indeed the case is
+ * impossible in general), new values are computed in temporary variables
+ * first, then copied to the actual variables representing the argument.
+ *
+ * Trivial assignments (arg1 = arg1) are eliminated.
+ *
+ * If, after elimination of trivial assignments, only one assignment
+ * remains, then we do not use a temporary variable for this one.
+ */
+ private def genMultiAssign(targetSyms: List[Symbol], values: List[Tree])(
+ implicit pos: Position): List[js.Tree] = {
+
+ // Prepare quadruplets of (formalArg, irType, tempVar, actualArg)
+ // Do not include trivial assignments (when actualArg == formalArg)
+ val quadruplets = {
+ val quadruplets =
+ List.newBuilder[(js.VarRef, jstpe.Type, js.LocalIdent, js.Tree)]
+
+ for ((formalArgSym, arg) <- targetSyms.zip(values)) {
+ val formalArgName = encodeLocalSymName(formalArgSym)
+ val actualArg = genExpr(arg)
+
+ /* #3267 The formal argument representing the special `this` of a
+ * tailrec method can have the wrong type in the scalac symbol table.
+ * We need to patch it up, along with the actual argument, to be the
+ * enclosing class type.
+ * See the longer comment in genMethodDef() for more details.
+ *
+ * Note that only testing the `name` against `nme.THIS` is safe,
+ * given that `genStatOrExpr()` for `ValDef` asserts that no local
+ * variable named `nme.THIS` can happen, other than the ones
+ * generated for tailrec methods.
+ */
+ val isTailJumpThisLocalVar = formalArgSym.name == nme.THIS
+
+ val tpe =
+ if (isTailJumpThisLocalVar) currentThisTypeNullable
+ else toIRType(formalArgSym.tpe)
+
+ val fixedActualArg =
+ if (isTailJumpThisLocalVar) forceAdapt(actualArg, tpe)
+ else actualArg
+
+ actualArg match {
+ case js.VarRef(`formalArgName`) =>
+ // This is trivial assignment, we don't need it
+
+ case _ =>
+ mutatedLocalVars += formalArgSym
+ quadruplets += ((js.VarRef(formalArgName)(tpe), tpe,
+ freshLocalIdent(formalArgName.withPrefix("temp$")),
+ fixedActualArg))
+ }
+ }
+
+ quadruplets.result()
+ }
+
+ quadruplets match {
+ case Nil =>
+ Nil
+
+ case (formalArg, _, _, actualArg) :: Nil =>
+ js.Assign(formalArg, actualArg) :: Nil
+
+ case _ =>
+ val tempAssignments =
+ for ((_, argType, tempArg, actualArg) <- quadruplets)
+ yield js.VarDef(tempArg, NoOriginalName, argType, mutable = false, actualArg)
+ val trueAssignments =
+ for ((formalArg, argType, tempArg, _) <- quadruplets)
+ yield js.Assign(formalArg, js.VarRef(tempArg.name)(argType))
+ tempAssignments ::: trueAssignments
+ }
+ }
+
+ /** Gen a "normal" apply (to a true method).
+ *
+ * But even these are further refined into:
+ *
+ * - Calls to methods of JS types.
+ * - Calls to methods in impl classes of traits.
+ * - Direct calls to constructors (from secondary constructor to another one).
+ * - Regular method calls.
+ */
+ private def genNormalApply(tree: Apply, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Apply(fun @ Select(receiver, _), args) = tree
+ val sym = fun.symbol
+
+ val inline = {
+ tree.hasAttachment[InlineCallsiteAttachment.type] ||
+ fun.hasAttachment[InlineCallsiteAttachment.type] // nullary methods
+ }
+ val noinline = {
+ tree.hasAttachment[NoInlineCallsiteAttachment.type] ||
+ fun.hasAttachment[NoInlineCallsiteAttachment.type] // nullary methods
+ }
+
+ if (isJSType(receiver.tpe) && sym.owner != ObjectClass) {
+ if (!isNonNativeJSClass(sym.owner) || isExposed(sym))
+ genPrimitiveJSCall(tree, isStat)
+ else
+ genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args))
+ } else if (sym.hasAnnotation(JSNativeAnnotation)) {
+ genJSNativeMemberCall(tree, isStat)
+ } else if (compileAsStaticMethod(sym)) {
+ if (sym.isMixinConstructor) {
+ /* Do not emit a call to the $init$ method of JS traits.
+ * This exception is necessary because optional JS fields cause the
+ * creation of a $init$ method, which we must not call.
+ */
+ js.Skip()
+ } else {
+ genApplyStatic(sym, args.map(genExpr), inline = inline, noinline = noinline)
+ }
+ } else {
+ genApplyMethodMaybeStatically(genExpr(receiver), sym,
+ genActualArgs(sym, args), inline = inline, noinline = noinline)
+ }
+ }
+
+ def genApplyMethodMaybeStatically(receiver: js.Tree,
+ method: Symbol, arguments: List[js.Tree],
+ inline: Boolean = false, noinline: Boolean = false)(
+ implicit pos: Position): js.Tree = {
+ if (method.isPrivate || method.isClassConstructor)
+ genApplyMethodStatically(receiver, method, arguments, inline = inline, noinline = noinline)
+ else
+ genApplyMethod(receiver, method, arguments, inline = inline, noinline = noinline)
+ }
+
+ /** Gen JS code for a call to a Scala method. */
+ def genApplyMethod(receiver: js.Tree,
+ method: Symbol, arguments: List[js.Tree],
+ inline: Boolean = false, noinline: Boolean = false)(
+ implicit pos: Position): js.Tree = {
+ assert(!method.isPrivate,
+ s"Cannot generate a dynamic call to private method $method at $pos")
+ val flags = js.ApplyFlags.empty
+ .withInline(inline)
+ .withNoinline(noinline)
+
+ js.Apply(flags, receiver, encodeMethodSym(method), arguments)(
+ toIRType(method.tpe.resultType))
+ }
+
+ def genApplyMethodStatically(receiver: js.Tree, method: Symbol,
+ arguments: List[js.Tree], inline: Boolean = false, noinline: Boolean = false)(
+ implicit pos: Position): js.Tree = {
+ val flags = js.ApplyFlags.empty
+ .withPrivate(method.isPrivate && !method.isClassConstructor)
+ .withConstructor(method.isClassConstructor)
+ .withInline(inline)
+ .withNoinline(noinline)
+ val methodIdent = encodeMethodSym(method)
+ val resultType =
+ if (method.isClassConstructor) jstpe.VoidType
+ else toIRType(method.tpe.resultType)
+ js.ApplyStatically(flags, receiver, encodeClassName(method.owner),
+ methodIdent, arguments)(resultType)
+ }
+
+ def genApplyJSClassMethod(receiver: js.Tree, method: Symbol,
+ arguments: List[js.Tree], inline: Boolean = false)(
+ implicit pos: Position): js.Tree = {
+ genApplyStatic(method, receiver :: arguments, inline = inline)
+ }
+
+ def genApplyStatic(method: Symbol, arguments: List[js.Tree],
+ inline: Boolean = false, noinline: Boolean = false)(
+ implicit pos: Position): js.Tree = {
+ val flags = js.ApplyFlags.empty
+ .withPrivate(method.isPrivate)
+ .withInline(inline)
+ .withNoinline(noinline)
+ js.ApplyStatic(flags, encodeClassName(method.owner),
+ encodeMethodSym(method), arguments)(toIRType(method.tpe.resultType))
+ }
+
+ private def adaptPrimitive(value: js.Tree, to: jstpe.Type)(
+ implicit pos: Position): js.Tree = {
+ genConversion(value.tpe, to, value)
+ }
+
+ /* This method corresponds to the method of the same name in
+ * BCodeBodyBuilder of the JVM back-end. It ends up calling the method
+ * BCodeIdiomatic.emitT2T, whose logic we replicate here.
+ */
+ private def genConversion(from: jstpe.Type, to: jstpe.Type, value: js.Tree)(
+ implicit pos: Position): js.Tree = {
+ import js.UnaryOp._
+
+ if (from == to || from == jstpe.NothingType) {
+ value
+ } else if (from == jstpe.BooleanType || to == jstpe.BooleanType) {
+ throw new AssertionError(s"Invalid genConversion from $from to $to")
+ } else {
+ def intValue = (from: @unchecked) match {
+ case jstpe.IntType => value
+ case jstpe.CharType => js.UnaryOp(CharToInt, value)
+ case jstpe.ByteType => js.UnaryOp(ByteToInt, value)
+ case jstpe.ShortType => js.UnaryOp(ShortToInt, value)
+ case jstpe.LongType => js.UnaryOp(LongToInt, value)
+ case jstpe.FloatType => js.UnaryOp(DoubleToInt, js.UnaryOp(FloatToDouble, value))
+ case jstpe.DoubleType => js.UnaryOp(DoubleToInt, value)
+ }
+
+ def doubleValue = from match {
+ case jstpe.DoubleType => value
+ case jstpe.FloatType => js.UnaryOp(FloatToDouble, value)
+ case jstpe.LongType => js.UnaryOp(LongToDouble, value)
+ case _ => js.UnaryOp(IntToDouble, intValue)
+ }
+
+ (to: @unchecked) match {
+ case jstpe.CharType =>
+ js.UnaryOp(IntToChar, intValue)
+ case jstpe.ByteType =>
+ js.UnaryOp(IntToByte, intValue)
+ case jstpe.ShortType =>
+ js.UnaryOp(IntToShort, intValue)
+ case jstpe.IntType =>
+ intValue
+ case jstpe.LongType =>
+ from match {
+ case jstpe.FloatType | jstpe.DoubleType =>
+ js.UnaryOp(DoubleToLong, doubleValue)
+ case _ =>
+ js.UnaryOp(IntToLong, intValue)
+ }
+ case jstpe.FloatType =>
+ if (from == jstpe.LongType)
+ js.UnaryOp(js.UnaryOp.LongToFloat, value)
+ else
+ js.UnaryOp(js.UnaryOp.DoubleToFloat, doubleValue)
+ case jstpe.DoubleType =>
+ doubleValue
+ }
+ }
+ }
+
+ /** Gen JS code for an isInstanceOf test (for reference types only) */
+ def genIsInstanceOf(value: js.Tree, to: Type)(
+ implicit pos: Position): js.Tree = {
+
+ val sym = to.typeSymbol
+
+ if (sym == ObjectClass) {
+ js.BinaryOp(js.BinaryOp.!==, value, js.Null())
+ } else if (isJSType(sym)) {
+ if (sym.isTrait) {
+ reporter.error(pos,
+ s"isInstanceOf[${sym.fullName}] not supported because it is a JS trait")
+ js.BooleanLiteral(true)
+ } else {
+ js.AsInstanceOf(
+ js.JSBinaryOp(js.JSBinaryOp.instanceof, value, genPrimitiveJSClass(sym)),
+ jstpe.BooleanType)
+ }
+ } else {
+ // The Scala type system prevents x.isInstanceOf[Null] and ...[Nothing]
+ assert(sym != NullClass && sym != NothingClass,
+ s"Found a .isInstanceOf[$sym] at $pos")
+ js.IsInstanceOf(value, toIRType(to).toNonNullable)
+ }
+ }
+
+ /** Gen JS code for an asInstanceOf cast (for reference types only) */
+ def genAsInstanceOf(value: js.Tree, to: Type)(
+ implicit pos: Position): js.Tree = {
+
+ def default: js.Tree =
+ js.AsInstanceOf(value, toIRType(to))
+
+ val sym = to.typeSymbol
+
+ if (sym == ObjectClass || isJSType(sym)) {
+ /* asInstanceOf[Object] always succeeds, and
+ * asInstanceOf to a JS type is completely erased.
+ */
+ value
+ } else if (sym == NullClass) {
+ js.If(
+ js.BinaryOp(js.BinaryOp.===, value, js.Null()),
+ js.Null(),
+ genThrowClassCastException())(
+ jstpe.NullType)
+ } else if (sym == NothingClass) {
+ js.Block(value, genThrowClassCastException())
+ } else {
+ default
+ }
+ }
+
+ /** Gen JS code for a call to a Scala class constructor. */
+ def genNew(clazz: Symbol, ctor: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ assert(!isJSFunctionDef(clazz),
+ s"Trying to instantiate a JS function def $clazz")
+ genNew(encodeClassName(clazz), ctor, arguments)
+ }
+
+ /** Gen JS code for a call to a Scala class constructor. */
+ def genNew(className: ClassName, ctor: Symbol, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ js.New(className, encodeMethodSym(ctor), arguments)
+ }
+
+ /** Gen JS code for a call to a constructor of a hijacked class.
+ * Reroute them to the `new` method with the same signature in the
+ * companion object.
+ */
+ private def genNewHijackedClass(clazz: Symbol, ctor: Symbol,
+ args: List[js.Tree])(implicit pos: Position): js.Tree = {
+
+ val flags = js.ApplyFlags.empty
+ val className = encodeClassName(clazz)
+
+ val initName = encodeMethodSym(ctor).name
+ val newName = MethodName(newSimpleMethodName, initName.paramTypeRefs,
+ jstpe.ClassRef(className))
+ val newMethodIdent = js.MethodIdent(newName)
+
+ js.ApplyStatic(flags, className, newMethodIdent, args)(
+ jstpe.ClassType(className, nullable = true))
+ }
+
+ /** Gen JS code for creating a new Array: new Array[T](length)
+ * For multidimensional arrays (dimensions > 1), the arguments can
+ * specify up to `dimensions` lengths for the first dimensions of the
+ * array.
+ */
+ def genNewArray(arrayTypeRef: jstpe.ArrayTypeRef, arguments: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ assert(arguments.size == 1,
+ "expected exactly 1 argument for array constructor: found " +
+ s"${arguments.length} at $pos")
+
+ js.NewArray(arrayTypeRef, arguments.head)
+ }
+
+ /** Gen JS code for an array literal. */
+ def genArrayValue(tree: ArrayValue): js.Tree = {
+ val ArrayValue(tpt @ TypeTree(), elems) = tree
+ genArrayValue(tree, elems)
+ }
+
+ /** Gen JS code for an array literal, in the context of `tree` (its `tpe`
+ * and `pos`) but with the elements `elems`.
+ */
+ def genArrayValue(tree: Tree, elems: List[Tree]): js.Tree = {
+ implicit val pos = tree.pos
+ val arrayTypeRef = toTypeRef(tree.tpe).asInstanceOf[jstpe.ArrayTypeRef]
+ js.ArrayValue(arrayTypeRef, elems.map(genExpr))
+ }
+
+ /** Gen JS code for a Match, i.e., a switch-able pattern match.
+ *
+ * In most cases, this straightforwardly translates to a Match in the IR,
+ * which will eventually become a `switch` in JavaScript.
+ *
+ * However, sometimes there is a guard in here, despite the fact that
+ * matches cannot have guards (in the JVM nor in the IR). The JVM backend
+ * emits a jump to the default clause when a guard is not fulfilled. We
+ * cannot do that, since we do not have arbitrary jumps. We therefore use
+ * a funny encoding with two nested `Labeled` blocks. For example,
+ * {{{
+ * x match {
+ * case 1 if y > 0 => a
+ * case 2 => b
+ * case _ => c
+ * }
+ * }}}
+ * arrives at the back-end as
+ * {{{
+ * x match {
+ * case 1 =>
+ * if (y > 0)
+ * a
+ * else
+ * default()
+ * case 2 =>
+ * b
+ * case _ =>
+ * default() {
+ * c
+ * }
+ * }
+ * }}}
+ * which we then translate into the following IR:
+ * {{{
+ * matchResult[I]: {
+ * default[V]: {
+ * x match {
+ * case 1 =>
+ * return(matchResult) if (y > 0)
+ * a
+ * else
+ * return(default) (void 0)
+ * case 2 =>
+ * return(matchResult) b
+ * case _ =>
+ * ()
+ * }
+ * }
+ * c
+ * }
+ * }}}
+ */
+ def genMatch(tree: Tree, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Match(selector, cases) = tree
+
+ /* Although GenBCode adapts the scrutinee and the cases to `int`, only
+ * true `int`s can reach the back-end, as asserted by the String-switch
+ * transformation in `cleanup`. Therefore, we do not adapt, preserving
+ * the `string`s and `null`s that come out of the pattern matching in
+ * Scala 2.13.x.
+ */
+ val genSelector = genExpr(selector)
+
+ val resultType =
+ if (isStat) jstpe.VoidType
+ else toIRType(tree.tpe)
+
+ val optDefaultLabelSymAndInfo = cases.collectFirst {
+ case CaseDef(Ident(nme.WILDCARD), EmptyTree,
+ body @ LabelDef(_, Nil, rhs)) if hasSynthCaseSymbol(body) =>
+ body.symbol -> new EnclosingLabelDefInfoWithResultAsAssigns(Nil)
+ }
+
+ var clauses: List[(List[js.MatchableLiteral], js.Tree)] = Nil
+ var optElseClause: Option[js.Tree] = None
+
+ withScopedVars(
+ enclosingLabelDefInfos := enclosingLabelDefInfos.get ++ optDefaultLabelSymAndInfo.toList
+ ) {
+ for (caze @ CaseDef(pat, guard, body) <- cases) {
+ assert(guard == EmptyTree, s"found a case guard at ${caze.pos}")
+
+ def genBody(body: Tree): js.Tree =
+ genStatOrExpr(body, isStat)
+
+ def invalidCase(tree: Tree): Nothing =
+ abort(s"Invalid case in alternative in switch-like pattern match: $tree at: ${tree.pos}")
+
+ def genMatchableLiteral(tree: Literal): js.MatchableLiteral = {
+ genExpr(tree) match {
+ case matchableLiteral: js.MatchableLiteral => matchableLiteral
+ case otherExpr => invalidCase(tree)
+ }
+ }
+
+ pat match {
+ case lit: Literal =>
+ clauses = (List(genMatchableLiteral(lit)), genBody(body)) :: clauses
+ case Ident(nme.WILDCARD) =>
+ optElseClause = Some(body match {
+ case LabelDef(_, Nil, rhs) if hasSynthCaseSymbol(body) =>
+ genBody(rhs)
+ case _ =>
+ genBody(body)
+ })
+ case Alternative(alts) =>
+ val genAlts = {
+ alts map {
+ case lit: Literal => genMatchableLiteral(lit)
+ case _ => invalidCase(tree)
+ }
+ }
+ clauses = (genAlts, genBody(body)) :: clauses
+ case _ =>
+ invalidCase(tree)
+ }
+ }
+ }
+
+ val elseClause = optElseClause.getOrElse(
+ throw new AssertionError("No elseClause in pattern match"))
+
+ /* Builds a `js.Match`, but simplifies it to a `js.If` if there is only
+ * one case with one alternative, and to a `js.Block` if there is no case
+ * at all. This happens in practice in the standard library. Having no
+ * case is a typical product of `match`es that are full of
+ * `case n if ... =>`, which are used instead of `if` chains for
+ * convenience and/or readability.
+ */
+ def buildMatch(cases: List[(List[js.MatchableLiteral], js.Tree)],
+ default: js.Tree, tpe: jstpe.Type): js.Tree = {
+
+ def isInt(tree: js.Tree): Boolean = tree.tpe == jstpe.IntType
+
+ cases match {
+ case Nil =>
+ /* Completely remove the Match. Preserve the side-effects of
+ * `genSelector`.
+ */
+ js.Block(exprToStat(genSelector), default)
+
+ case (uniqueAlt :: Nil, caseRhs) :: Nil =>
+ /* Simplify the `match` as an `if`, so that the optimizer has less
+ * work to do, and we emit less code at the end of the day.
+ * Use `Int_==` instead of `===` if possible, since it is a common
+ * case.
+ */
+ val op =
+ if (isInt(genSelector) && isInt(uniqueAlt)) js.BinaryOp.Int_==
+ else js.BinaryOp.===
+ js.If(js.BinaryOp(op, genSelector, uniqueAlt), caseRhs, default)(tpe)
+
+ case _ =>
+ // We have more than one case: use a js.Match
+ js.Match(genSelector, cases, default)(tpe)
+ }
+ }
+
+ optDefaultLabelSymAndInfo match {
+ case Some((defaultLabelSym, defaultLabelInfo)) if defaultLabelInfo.generatedReturns > 0 =>
+ val matchResultLabel = freshLabelName("matchResult")
+ val patchedClauses = for ((alts, body) <- clauses) yield {
+ implicit val pos = body.pos
+ val newBody = js.Return(body, matchResultLabel)
+ (alts, newBody)
+ }
+ js.Labeled(matchResultLabel, resultType, js.Block(List(
+ js.Labeled(encodeLabelSym(defaultLabelSym), jstpe.VoidType, {
+ buildMatch(patchedClauses.reverse, js.Skip(), jstpe.VoidType)
+ }),
+ elseClause
+ )))
+
+ case _ =>
+ buildMatch(clauses.reverse, elseClause, resultType)
+ }
+ }
+
+ /** Flatten nested Blocks that can be flattened without compromising the
+ * identification of pattern matches.
+ */
+ private def flatStats(stats: List[Tree]): Iterator[Tree] = {
+ /* #4581 Never decompose a Block with LabelDef's, as they need to
+ * be processed by genBlockWithCaseLabelDefs.
+ */
+ stats.iterator.flatMap {
+ case Block(stats, expr) if !stats.exists(isCaseLabelDef(_)) =>
+ stats.iterator ++ Iterator.single(expr)
+ case tree =>
+ Iterator.single(tree)
+ }
+ }
+
+ /** Predicate satisfied by LabelDefs produced by the pattern matcher,
+ * except matchEnd's.
+ */
+ private def isCaseLabelDef(tree: Tree): Boolean = {
+ tree.isInstanceOf[LabelDef] && hasSynthCaseSymbol(tree) &&
+ !tree.symbol.name.startsWith("matchEnd")
+ }
+
+ /** Predicate satisfied by matchEnd LabelDefs produced by the pattern
+ * matcher.
+ */
+ private def isMatchEndLabelDef(tree: LabelDef): Boolean =
+ hasSynthCaseSymbol(tree) && tree.symbol.name.startsWith("matchEnd")
+
+ private def genBlock(tree: Block, isStat: Boolean): js.Tree = {
+ implicit val pos = tree.pos
+ val Block(stats, expr) = tree
+
+ val genStatsAndExpr = if (!stats.exists(isCaseLabelDef(_))) {
+ // #4684 Collapse { ; BoxedUnit } to
+ val genStatsAndExpr0 = stats.map(genStat(_)) :+ genStatOrExpr(expr, isStat)
+ genStatsAndExpr0 match {
+ case (undefParam @ js.Transient(UndefinedParam)) :: js.Undefined() :: Nil =>
+ undefParam :: Nil
+ case _ =>
+ genStatsAndExpr0
+ }
+ } else {
+ genBlockWithCaseLabelDefs(stats :+ expr, isStat)
+ }
+
+ /* A bit of dead code elimination: we drop all statements and
+ * expressions after the first statement of type `NothingType`.
+ * This helps other optimizations.
+ */
+ val (nonNothing, rest) = genStatsAndExpr.span(_.tpe != jstpe.NothingType)
+ if (rest.isEmpty || rest.tail.isEmpty)
+ js.Block(genStatsAndExpr)
+ else
+ js.Block(nonNothing, rest.head)
+ }
+
+ private def genBlockWithCaseLabelDefs(trees: List[Tree], isStat: Boolean)(
+ implicit pos: Position): List[js.Tree] = {
+
+ val (prologue, casesAndRest) = trees.span(!isCaseLabelDef(_))
+
+ if (casesAndRest.isEmpty) {
+ if (prologue.isEmpty) Nil
+ else if (isStat) prologue.map(genStat(_))
+ else prologue.init.map(genStat(_)) :+ genExpr(prologue.last)
+ } else {
+ val genPrologue = prologue.map(genStat(_))
+
+ val (cases0, rest) = casesAndRest.span(isCaseLabelDef(_))
+ val cases = cases0.asInstanceOf[List[LabelDef]]
+
+ val genCasesAndRest = rest match {
+ case (matchEnd: LabelDef) :: more if isMatchEndLabelDef(matchEnd) =>
+ val translatedMatch = genTranslatedMatch(cases, matchEnd)
+ translatedMatch :: genBlockWithCaseLabelDefs(more, isStat)
+
+ // Sometimes the pattern matcher casts its final result
+ case Apply(TypeApply(Select(matchEnd: LabelDef, nme.asInstanceOf_Ob),
+ List(targ)), Nil) :: more
+ if isMatchEndLabelDef(matchEnd) =>
+ val translatedMatch = genTranslatedMatch(cases, matchEnd)
+ genIsAsInstanceOf(translatedMatch, matchEnd.tpe, targ.tpe,
+ cast = true) :: genBlockWithCaseLabelDefs(more, isStat)
+
+ // Peculiar shape generated by `return x match {...}` - #2928
+ case Return(matchEnd: LabelDef) :: more if isMatchEndLabelDef(matchEnd) =>
+ val translatedMatch = genTranslatedMatch(cases, matchEnd)
+ val genMore = genBlockWithCaseLabelDefs(more, isStat)
+ val label = getEnclosingReturnLabel()
+ js.Return(translatedMatch, label) :: genMore
+
+ // Otherwise, there is no matchEnd, only consecutive cases
+ case Nil =>
+ genTranslatedCases(cases, isStat)
+ case _ =>
+ genTranslatedCases(cases, isStat = false) ::: genBlockWithCaseLabelDefs(rest, isStat)
+ }
+
+ genPrologue ::: genCasesAndRest
+ }
+ }
+
+ /** Gen JS code for a translated match.
+ *
+ * A translated match consists of consecutive `case` LabelDefs directly
+ * followed by a `matchEnd` LabelDef.
+ */
+ private def genTranslatedMatch(cases: List[LabelDef], matchEnd: LabelDef)(
+ implicit pos: Position): js.Tree = {
+ genMatchEnd(matchEnd) {
+ genTranslatedCases(cases, isStat = true)
+ }
+ }
+
+ /** Gen JS code for the cases of a patmat-transformed match.
+ *
+ * This implementation relies heavily on the patterns of trees emitted
+ * by the pattern match phase, including its variants across versions of
+ * scalac that we support.
+ *
+ * The trees output by the pattern matcher are assumed to follow these
+ * rules:
+ *
+ * - Each case LabelDef (in `cases`) must not take any argument.
+ * - Jumps to case label-defs are restricted to jumping to the very next
+ * case.
+ *
+ * There is an optimization to avoid generating jumps that are in tail
+ * position of a case, if they are in positions denoted by in:
+ * {{{
+ * ::=
+ * If(_, , )
+ * | Block(_, )
+ * |
+ * | _
+ * }}}
+ * Since all but the last case (which cannot have jumps) are in statement
+ * position, those jumps in tail position can be replaced by `skip`.
+ */
+ private def genTranslatedCases(cases: List[LabelDef], isStat: Boolean)(
+ implicit pos: Position): List[js.Tree] = {
+
+ assert(!cases.isEmpty,
+ s"genTranslatedCases called with no cases at $pos")
+
+ val translatedCasesInit = for {
+ (caseLabelDef, nextCaseSym) <- cases.zip(cases.tail.map(_.symbol))
+ } yield {
+ implicit val pos = caseLabelDef.pos
+ assert(caseLabelDef.params.isEmpty,
+ s"found case LabelDef with parameters at $pos")
+
+ val info = new EnclosingLabelDefInfoWithResultAsAssigns(Nil)
+
+ val translatedBody = withScopedVars(
+ enclosingLabelDefInfos :=
+ enclosingLabelDefInfos.get + (nextCaseSym -> info)
+ ) {
+ /* Eager optimization of jumps in tail position, following the shapes
+ * produced by scala until 2.12.8. 2.12.9 introduced flat patmat
+ * translation, which does not trigger those optimizations.
+ * These shapes are also often produced by the async transformation.
+ */
+ def genCaseBody(tree: Tree): js.Tree = {
+ implicit val pos = tree.pos
+ tree match {
+ case If(cond, thenp, elsep) =>
+ js.If(genExpr(cond), genCaseBody(thenp), genCaseBody(elsep))(
+ jstpe.VoidType)
+
+ case Block(stats, Literal(Constant(()))) =>
+ // Generated a lot by the async transform
+ if (stats.isEmpty) js.Skip()
+ else js.Block(stats.init.map(genStat(_)), genCaseBody(stats.last))
+
+ case Block(stats, expr) =>
+ js.Block((stats map genStat) :+ genCaseBody(expr))
+
+ case Apply(_, Nil) if tree.symbol == nextCaseSym =>
+ js.Skip()
+
+ case _ =>
+ genStat(tree)
+ }
+ }
+
+ genCaseBody(caseLabelDef.rhs)
+ }
+
+ genOptimizedCaseLabeled(encodeLabelSym(nextCaseSym), translatedBody,
+ info.generatedReturns)
+ }
+
+ val translatedLastCase = genStatOrExpr(cases.last.rhs, isStat)
+
+ translatedCasesInit :+ translatedLastCase
+ }
+
+ /** Gen JS code for a match-end label def following match-cases.
+ *
+ * The preceding cases, which are allowed to jump to this match-end, must
+ * be generated in the `genTranslatedCases` callback. During the execution
+ * of this callback, the enclosing label infos contain appropriate info
+ * for this match-end.
+ *
+ * The translation of the match-end itself is straightforward, but is
+ * augmented with several optimizations to remove as many labeled blocks
+ * as possible.
+ *
+ * Most of the time, a match-end label has exactly one parameter. However,
+ * with the async transform, it can sometimes have no parameter instead.
+ * We handle those cases very differently.
+ */
+ private def genMatchEnd(matchEnd: LabelDef)(
+ genTranslatedCases: => List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+
+ val sym = matchEnd.symbol
+ val labelIdent = encodeLabelSym(sym)
+ val matchEndBody = matchEnd.rhs
+
+ def genMatchEndBody(): js.Tree = {
+ genStatOrExpr(matchEndBody,
+ isStat = toIRType(matchEndBody.tpe) == jstpe.VoidType)
+ }
+
+ matchEnd.params match {
+ // Optimizable common case produced by the regular pattern matcher
+ case List(matchEndParam) =>
+ val info = new EnclosingLabelDefInfoWithResultAsReturn()
+
+ val translatedCases = withScopedVars(
+ enclosingLabelDefInfos := enclosingLabelDefInfos.get + (sym -> info)
+ ) {
+ genTranslatedCases
+ }
+
+ val innerResultType = toIRType(matchEndParam.tpe)
+ val optimized = genOptimizedMatchEndLabeled(encodeLabelSym(sym),
+ innerResultType, translatedCases, info.generatedReturns)
+
+ matchEndBody match {
+ case Ident(_) if matchEndParam.symbol == matchEndBody.symbol =>
+ // matchEnd is identity.
+ optimized
+
+ case Literal(Constant(())) =>
+ // Unit return type.
+ optimized
+
+ case _ =>
+ // matchEnd does something.
+ js.Block(
+ js.VarDef(encodeLocalSym(matchEndParam.symbol),
+ originalNameOfLocal(matchEndParam.symbol),
+ innerResultType, mutable = false, optimized),
+ genMatchEndBody())
+ }
+
+ /* Other cases, notably the matchEnd's produced by the async transform,
+ * which have no parameters. The case of more than one parameter is
+ * hypothetical, but it costs virtually nothing to handle it here.
+ */
+ case params =>
+ val paramSyms = params.map(_.symbol)
+ val varDefs = for (s <- paramSyms) yield {
+ implicit val pos = s.pos
+ val irType = toIRType(s.tpe)
+ js.VarDef(encodeLocalSym(s), originalNameOfLocal(s), irType,
+ mutable = true, jstpe.zeroOf(irType))
+ }
+ val info = new EnclosingLabelDefInfoWithResultAsAssigns(paramSyms)
+ val translatedCases = withScopedVars(
+ enclosingLabelDefInfos := enclosingLabelDefInfos.get + (sym -> info)
+ ) {
+ genTranslatedCases
+ }
+ val optimized = genOptimizedMatchEndLabeled(labelIdent, jstpe.VoidType,
+ translatedCases, info.generatedReturns)
+ js.Block(varDefs ::: optimized :: genMatchEndBody() :: Nil)
+ }
+ }
+
+ /** Gen JS code for a Labeled block from a pattern match'es case, while
+ * trying to optimize it away as a reversed If.
+ *
+ * If there was no `return` to the label at all, simply avoid generating
+ * the `Labeled` block altogether.
+ *
+ * If there was more than one `return`, do not optimize anything, as
+ * nothing could be good enough for `genOptimizedMatchEndLabeled` to do
+ * anything useful down the line.
+ *
+ * If however there was a single `return`, we try and get rid of it by
+ * identifying the following shape:
+ *
+ * {{{
+ * {
+ * ...stats1
+ * if (test)
+ * return(nextCaseSym)
+ * ...stats2
+ * }
+ * }}}
+ *
+ * which we then rewrite as
+ *
+ * {{{
+ * {
+ * ...stats1
+ * if (!test) {
+ * ...stats2
+ * }
+ * }
+ * }}}
+ *
+ * The above rewrite is important for `genOptimizedMatchEndLabeled` below
+ * to be able to do its job, which in turn is important for the IR
+ * optimizer to perform a better analysis.
+ *
+ * This whole thing is only necessary in Scala 2.12.9+, with the new flat
+ * patmat ASTs. In previous versions, `returnCount` is always 0 because
+ * all jumps to case labels are already caught upstream by `genCaseBody()`
+ * inside `genTranslatedMatch()`.
+ */
+ private def genOptimizedCaseLabeled(label: LabelName,
+ translatedBody: js.Tree, returnCount: Int)(
+ implicit pos: Position): js.Tree = {
+
+ def default: js.Tree =
+ js.Labeled(label, jstpe.VoidType, translatedBody)
+
+ if (returnCount == 0) {
+ translatedBody
+ } else if (returnCount > 1) {
+ default
+ } else {
+ translatedBody match {
+ case js.Block(stats) =>
+ val (stats1, testAndStats2) = stats.span {
+ case js.If(_, js.Return(_, `label`), js.Skip()) =>
+ false
+ case _ =>
+ true
+ }
+
+ testAndStats2 match {
+ case js.If(cond, js.Return(returnedValue, _), _) :: stats2 =>
+ val notCond = cond match {
+ case js.UnaryOp(js.UnaryOp.Boolean_!, notCond) =>
+ notCond
+ case _ =>
+ js.UnaryOp(js.UnaryOp.Boolean_!, cond)
+ }
+ js.Block(stats1 :+ js.If(notCond, js.Block(stats2), returnedValue)(jstpe.VoidType))
+
+ case _ :: _ =>
+ throw new AssertionError("unreachable code")
+
+ case Nil =>
+ default
+ }
+
+ case _ =>
+ default
+ }
+ }
+ }
+
+ /** Gen JS code for a Labeled block from a pattern match'es match-end,
+ * while trying to optimize it away as an If chain.
+ *
+ * It is important to do so at compile-time because, when successful, the
+ * resulting IR can be much better optimized by the optimizer.
+ *
+ * The optimizer also does something similar, but *after* it has processed
+ * the body of the Labeled block, at which point it has already lost any
+ * information about stack-allocated values.
+ *
+ * !!! There is quite of bit of code duplication with
+ * OptimizerCore.tryOptimizePatternMatch.
+ */
+ def genOptimizedMatchEndLabeled(label: LabelName, tpe: jstpe.Type,
+ translatedCases: List[js.Tree], returnCount: Int)(
+ implicit pos: Position): js.Tree = {
+ def default: js.Tree =
+ js.Labeled(label, tpe, js.Block(translatedCases))
+
+ @tailrec
+ def createRevAlts(xs: List[js.Tree],
+ acc: List[(js.Tree, js.Tree)]): (List[(js.Tree, js.Tree)], js.Tree) = xs match {
+ case js.If(cond, body, js.Skip()) :: xr =>
+ createRevAlts(xr, (cond, body) :: acc)
+ case remaining =>
+ (acc, js.Block(remaining)(remaining.head.pos))
+ }
+ val (revAlts, elsep) = createRevAlts(translatedCases, Nil)
+
+ if (revAlts.size == returnCount - 1) {
+ def tryDropReturn(body: js.Tree): Option[js.Tree] = body match {
+ case js.Return(result, `label`) =>
+ Some(result)
+
+ case js.Block(prep :+ js.Return(result, `label`)) =>
+ Some(js.Block(prep :+ result)(body.pos))
+
+ case _ =>
+ None
+ }
+
+ @tailrec
+ def constructOptimized(revAlts: List[(js.Tree, js.Tree)],
+ elsep: js.Tree): js.Tree = {
+ revAlts match {
+ case (cond, body) :: revAltsRest =>
+ // cannot use flatMap due to tailrec
+ tryDropReturn(body) match {
+ case Some(newBody) =>
+ constructOptimized(revAltsRest,
+ js.If(cond, newBody, elsep)(tpe)(cond.pos))
+
+ case None =>
+ default
+ }
+ case Nil =>
+ elsep
+ }
+ }
+
+ tryDropReturn(elsep).fold(default)(constructOptimized(revAlts, _))
+ } else {
+ default
+ }
+ }
+
+ /** Gen JS code for a primitive method call */
+ private def genPrimitiveOp(tree: Apply, isStat: Boolean): js.Tree = {
+ import scalaPrimitives._
+
+ implicit val pos = tree.pos
+
+ val sym = tree.symbol
+ val Apply(fun @ Select(receiver, _), args) = tree
+
+ val code = scalaPrimitives.getPrimitive(sym, receiver.tpe)
+
+ if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code))
+ genSimpleOp(tree, receiver :: args, code)
+ else if (code == scalaPrimitives.CONCAT)
+ genStringConcat(tree, receiver, args)
+ else if (code == HASH)
+ genScalaHash(tree, receiver)
+ else if (isArrayOp(code))
+ genArrayOp(tree, code)
+ else if (code == SYNCHRONIZED)
+ genSynchronized(receiver, args.head, isStat)
+ else if (isCoercion(code))
+ genCoercion(tree, receiver, code)
+ else if (jsPrimitives.isJavaScriptPrimitive(code))
+ genJSPrimitive(tree, args, code, isStat)
+ else
+ abort("Unknown primitive operation: " + sym.fullName + "(" +
+ fun.symbol.simpleName + ") " + " at: " + (tree.pos))
+ }
+
+ private def genPrimitiveOpForReflectiveCall(sym: Symbol, receiver: js.Tree,
+ args: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+
+ import scalaPrimitives._
+
+ if (!isPrimitive(sym)) {
+ abort(
+ "Trying to reflectively call a method of a primitive type that " +
+ "is not itself a primitive method: " + sym.fullName + " at " + pos)
+ }
+ val code = getPrimitive(sym)
+
+ if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code)) {
+ genSimpleOp(sym.owner.tpe :: sym.tpe.paramTypes, sym.tpe.resultType,
+ receiver :: args, code)
+ } else if (code == CONCAT) {
+ js.BinaryOp(js.BinaryOp.String_+, receiver, args.head)
+ } else if (isCoercion(code)) {
+ adaptPrimitive(receiver, toIRType(sym.tpe.resultType))
+ } else {
+ abort(
+ "Unknown primitive operation for reflective call: " + sym.fullName +
+ " at " + pos)
+ }
+ }
+
+ /** Gen JS code for a simple operation (arithmetic, logical, or comparison) */
+ private def genSimpleOp(tree: Apply, args: List[Tree], code: Int): js.Tree = {
+ implicit val pos = tree.pos
+
+ genSimpleOp(args.map(_.tpe), tree.tpe, args.map(genExpr), code)
+ }
+
+ /** Gen JS code for a simple operation (arithmetic, logical, or comparison) */
+ private def genSimpleOp(argTpes: List[Type], resultTpe: Type,
+ sources: List[js.Tree], code: Int)(
+ implicit pos: Position): js.Tree = {
+
+ import scalaPrimitives._
+
+ sources match {
+ // Unary operation
+ case List(src_in) =>
+ val opType = toIRType(resultTpe)
+ val src = adaptPrimitive(src_in, opType)
+
+ (code match {
+ case POS =>
+ src
+ case NEG =>
+ (opType: @unchecked) match {
+ case jstpe.IntType =>
+ js.BinaryOp(js.BinaryOp.Int_-, js.IntLiteral(0), src)
+ case jstpe.LongType =>
+ js.BinaryOp(js.BinaryOp.Long_-, js.LongLiteral(0), src)
+ case jstpe.FloatType =>
+ js.BinaryOp(js.BinaryOp.Float_*, js.FloatLiteral(-1.0f), src)
+ case jstpe.DoubleType =>
+ js.BinaryOp(js.BinaryOp.Double_*, js.DoubleLiteral(-1.0), src)
+ }
+ case NOT =>
+ (opType: @unchecked) match {
+ case jstpe.IntType =>
+ js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(-1), src)
+ case jstpe.LongType =>
+ js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(-1), src)
+ }
+ case ZNOT =>
+ js.UnaryOp(js.UnaryOp.Boolean_!, src)
+ case _ =>
+ abort("Unknown unary operation code: " + code)
+ })
+
+ // Binary operation
+ case List(lsrc_in, rsrc_in) =>
+ import js.BinaryOp._
+
+ val isShift = isShiftOp(code)
+ val leftIRType = toIRType(argTpes(0))
+ val rightIRType = toIRType(argTpes(1))
+
+ val opType = {
+ if (isShift) {
+ if (leftIRType == jstpe.LongType) jstpe.LongType
+ else jstpe.IntType
+ } else {
+ (leftIRType, rightIRType) match {
+ case (jstpe.DoubleType, _) | (_, jstpe.DoubleType) =>
+ jstpe.DoubleType
+ case (jstpe.FloatType, _) | (_, jstpe.FloatType) =>
+ jstpe.FloatType
+ case (jstpe.LongType, _) | (_, jstpe.LongType) =>
+ jstpe.LongType
+ case (jstpe.IntType | jstpe.CharType | jstpe.ByteType | jstpe.ShortType, _) |
+ (_, jstpe.IntType | jstpe.CharType | jstpe.ByteType | jstpe.ShortType) =>
+ jstpe.IntType
+ case (jstpe.BooleanType, _) | (_, jstpe.BooleanType) =>
+ jstpe.BooleanType
+ case _ =>
+ jstpe.AnyType
+ }
+ }
+ }
+
+ val lsrc =
+ if (opType == jstpe.AnyType) lsrc_in
+ else adaptPrimitive(lsrc_in, opType)
+ val rsrc =
+ if (opType == jstpe.AnyType) rsrc_in
+ else adaptPrimitive(rsrc_in, if (isShift) jstpe.IntType else opType)
+
+ (opType: @unchecked) match {
+ case jstpe.IntType =>
+ val op = (code: @switch) match {
+ case ADD => Int_+
+ case SUB => Int_-
+ case MUL => Int_*
+ case DIV => Int_/
+ case MOD => Int_%
+ case OR => Int_|
+ case AND => Int_&
+ case XOR => Int_^
+ case LSL => Int_<<
+ case LSR => Int_>>>
+ case ASR => Int_>>
+ case EQ => Int_==
+ case NE => Int_!=
+ case LT => Int_<
+ case LE => Int_<=
+ case GT => Int_>
+ case GE => Int_>=
+ }
+ js.BinaryOp(op, lsrc, rsrc)
+
+ case jstpe.LongType =>
+ val op = (code: @switch) match {
+ case ADD => Long_+
+ case SUB => Long_-
+ case MUL => Long_*
+ case DIV => Long_/
+ case MOD => Long_%
+ case OR => Long_|
+ case XOR => Long_^
+ case AND => Long_&
+ case LSL => Long_<<
+ case LSR => Long_>>>
+ case ASR => Long_>>
+ case EQ => Long_==
+ case NE => Long_!=
+ case LT => Long_<
+ case LE => Long_<=
+ case GT => Long_>
+ case GE => Long_>=
+ }
+ js.BinaryOp(op, lsrc, rsrc)
+
+ case jstpe.FloatType =>
+ def withFloats(op: Int): js.Tree =
+ js.BinaryOp(op, lsrc, rsrc)
+
+ def toDouble(value: js.Tree): js.Tree =
+ js.UnaryOp(js.UnaryOp.FloatToDouble, value)
+
+ def withDoubles(op: Int): js.Tree =
+ js.BinaryOp(op, toDouble(lsrc), toDouble(rsrc))
+
+ (code: @switch) match {
+ case ADD => withFloats(Float_+)
+ case SUB => withFloats(Float_-)
+ case MUL => withFloats(Float_*)
+ case DIV => withFloats(Float_/)
+ case MOD => withFloats(Float_%)
+
+ case EQ => withDoubles(Double_==)
+ case NE => withDoubles(Double_!=)
+ case LT => withDoubles(Double_<)
+ case LE => withDoubles(Double_<=)
+ case GT => withDoubles(Double_>)
+ case GE => withDoubles(Double_>=)
+ }
+
+ case jstpe.DoubleType =>
+ val op = (code: @switch) match {
+ case ADD => Double_+
+ case SUB => Double_-
+ case MUL => Double_*
+ case DIV => Double_/
+ case MOD => Double_%
+ case EQ => Double_==
+ case NE => Double_!=
+ case LT => Double_<
+ case LE => Double_<=
+ case GT => Double_>
+ case GE => Double_>=
+ }
+ js.BinaryOp(op, lsrc, rsrc)
+
+ case jstpe.BooleanType =>
+ (code: @switch) match {
+ case OR =>
+ js.BinaryOp(Boolean_|, lsrc, rsrc)
+ case AND =>
+ js.BinaryOp(Boolean_&, lsrc, rsrc)
+ case EQ =>
+ js.BinaryOp(Boolean_==, lsrc, rsrc)
+ case XOR | NE =>
+ js.BinaryOp(Boolean_!=, lsrc, rsrc)
+ case ZOR =>
+ js.If(lsrc, js.BooleanLiteral(true), rsrc)(jstpe.BooleanType)
+ case ZAND =>
+ js.If(lsrc, rsrc, js.BooleanLiteral(false))(jstpe.BooleanType)
+ }
+
+ case jstpe.AnyType =>
+ def genAnyEquality(eqeq: Boolean, not: Boolean): js.Tree = {
+ // Arrays, Null, Nothing never have a custom equals() method
+ def canHaveCustomEquals(tpe: jstpe.Type): Boolean = tpe match {
+ case jstpe.AnyType | _:jstpe.ClassType => true
+ case _ => false
+ }
+ if (eqeq &&
+ // don't call equals if we have a literal null at either side
+ !lsrc.isInstanceOf[js.Null] &&
+ !rsrc.isInstanceOf[js.Null] &&
+ canHaveCustomEquals(leftIRType)) {
+ val body = genEqEqPrimitive(argTpes(0), argTpes(1), lsrc, rsrc)
+ if (not) js.UnaryOp(js.UnaryOp.Boolean_!, body) else body
+ } else {
+ js.BinaryOp(
+ if (not) js.BinaryOp.!== else js.BinaryOp.===,
+ lsrc, rsrc)
+ }
+ }
+
+ (code: @switch) match {
+ case EQ => genAnyEquality(eqeq = true, not = false)
+ case NE => genAnyEquality(eqeq = true, not = true)
+ case ID => genAnyEquality(eqeq = false, not = false)
+ case NI => genAnyEquality(eqeq = false, not = true)
+ }
+ }
+
+ case _ =>
+ abort("Too many arguments for primitive function at " + pos)
+ }
+ }
+
+ /** Gen JS code for a call to Any.== */
+ def genEqEqPrimitive(ltpe: Type, rtpe: Type, lsrc: js.Tree, rsrc: js.Tree)(
+ implicit pos: Position): js.Tree = {
+ /* True if the equality comparison is between values that require the
+ * use of the rich equality comparator
+ * (scala.runtime.BoxesRunTime.equals).
+ * This is the case when either side of the comparison might have a
+ * run-time type subtype of java.lang.Number or java.lang.Character,
+ * **which includes when either is a JS type**.
+ *
+ * When it is statically known that both sides are equal and subtypes of
+ * Number or Character, not using the rich equality is possible (their
+ * own equals method will do ok), except for java.lang.Float and
+ * java.lang.Double: their `equals` have different behavior around `NaN`
+ * and `-0.0`, see Javadoc (scala-dev#329, #2799).
+ */
+ val mustUseAnyComparator: Boolean = {
+ val lsym = ltpe.typeSymbol
+ val rsym = rtpe.typeSymbol
+ isJSType(lsym) || isJSType(rsym) || {
+ isMaybeBoxed(lsym) && isMaybeBoxed(rsym) && {
+ val areSameFinals =
+ ltpe.isFinalType && rtpe.isFinalType && lsym == rsym
+ !areSameFinals || (lsym == BoxedFloatClass || lsym == BoxedDoubleClass)
+ }
+ }
+ }
+
+ if (mustUseAnyComparator) {
+ val equalsMethod: Symbol = {
+ // scalastyle:off line.size.limit
+ if (ltpe <:< BoxedNumberClass.tpe) {
+ if (rtpe <:< BoxedNumberClass.tpe) platform.externalEqualsNumNum
+ else if (rtpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030
+ else platform.externalEqualsNumObject
+ } else platform.externalEquals
+ // scalastyle:on line.size.limit
+ }
+ if (BoxesRunTimeClass.isJavaDefined)
+ genApplyStatic(equalsMethod, List(lsrc, rsrc))
+ else // this happens when in the same compilation run as BoxesRunTime
+ genApplyMethod(genLoadModule(BoxesRunTimeClass), equalsMethod, List(lsrc, rsrc))
+ } else {
+ // if (lsrc eq null) rsrc eq null else lsrc.equals(rsrc)
+ if (isStringType(ltpe)) {
+ // String.equals(that) === (this eq that)
+ js.BinaryOp(js.BinaryOp.===, lsrc, rsrc)
+ } else {
+ /* This requires to evaluate both operands in local values first.
+ * The optimizer will eliminate them if possible.
+ */
+ val ltemp = js.VarDef(freshLocalIdent(), NoOriginalName, lsrc.tpe,
+ mutable = false, lsrc)
+ val rtemp = js.VarDef(freshLocalIdent(), NoOriginalName, rsrc.tpe,
+ mutable = false, rsrc)
+ js.Block(
+ ltemp,
+ rtemp,
+ js.If(js.BinaryOp(js.BinaryOp.===, ltemp.ref, js.Null()),
+ js.BinaryOp(js.BinaryOp.===, rtemp.ref, js.Null()),
+ genApplyMethod(ltemp.ref, Object_equals, List(rtemp.ref)))(
+ jstpe.BooleanType))
+ }
+ }
+ }
+
+ /** Gen JS code for string concatenation.
+ */
+ private def genStringConcat(tree: Apply, receiver: Tree,
+ args: List[Tree]): js.Tree = {
+ implicit val pos = tree.pos
+
+ /* Primitive number types such as scala.Int have a
+ * def +(s: String): String
+ * method, which is why we have to box the lhs sometimes.
+ * Otherwise, both lhs and rhs are already reference types (Any of String)
+ * so boxing is not necessary (in particular, rhs is never a primitive).
+ */
+ assert(!isPrimitiveValueType(receiver.tpe) || isStringType(args.head.tpe),
+ s"unexpected signature for string-concat call at $pos")
+ assert(!isPrimitiveValueType(args.head.tpe),
+ s"unexpected signature for string-concat call at $pos")
+
+ val rhs = genExpr(args.head)
+
+ val lhs = {
+ val lhs0 = genExpr(receiver)
+ // Box the receiver if it is a primitive value
+ if (!isPrimitiveValueType(receiver.tpe)) lhs0
+ else makePrimitiveBox(lhs0, receiver.tpe)
+ }
+
+ js.BinaryOp(js.BinaryOp.String_+, lhs, rhs)
+ }
+
+ /** Gen JS code for a call to `Any.##`.
+ *
+ * This method unconditionally generates a call to `Statics.anyHash`.
+ * On the JVM, `anyHash` is only called as of 2.12.0-M5. Previous versions
+ * emitted a call to `ScalaRunTime.hash`. However, since our `anyHash`
+ * is always consistent with `ScalaRunTime.hash`, we always use it.
+ */
+ private def genScalaHash(tree: Apply, receiver: Tree): js.Tree = {
+ implicit val pos = tree.pos
+
+ val instance = genLoadModule(RuntimeStaticsModule)
+ val arguments = List(genExpr(receiver))
+ val sym = getMember(RuntimeStaticsModule, jsnme.anyHash)
+
+ genApplyMethod(instance, sym, arguments)
+ }
+
+ /** Gen JS code for an array operation (get, set or length) */
+ private def genArrayOp(tree: Tree, code: Int): js.Tree = {
+ import scalaPrimitives._
+
+ implicit val pos = tree.pos
+
+ val Apply(fun @ Select(arrayObj, _), args) = tree
+ val arrayValue = genExpr(arrayObj)
+ val arguments = args map genExpr
+
+ def genSelect(elemType: Type) =
+ js.ArraySelect(arrayValue, arguments(0))(toIRType(elemType))
+
+ if (scalaPrimitives.isArrayGet(code)) {
+ // get an item of the array
+ assert(args.length == 1,
+ s"Array get requires 1 argument, found ${args.length} in $tree")
+ genSelect(fun.tpe.resultType)
+ } else if (scalaPrimitives.isArraySet(code)) {
+ // set an item of the array
+ assert(args.length == 2,
+ s"Array set requires 2 arguments, found ${args.length} in $tree")
+ js.Assign(genSelect(fun.tpe.paramTypes(1)), arguments(1))
+ } else {
+ // length of the array
+ js.UnaryOp(js.UnaryOp.Array_length,
+ js.UnaryOp(js.UnaryOp.CheckNotNull, arrayValue))
+ }
+ }
+
+ /** Gen JS code for a call to AnyRef.synchronized */
+ private def genSynchronized(receiver: Tree, arg: Tree, isStat: Boolean)(
+ implicit pos: Position): js.Tree = {
+ /* JavaScript is single-threaded, so we can drop the
+ * synchronization altogether.
+ */
+ val newReceiver = genExpr(receiver)
+ val newArg = genStatOrExpr(arg, isStat)
+ newReceiver match {
+ case newReceiver: js.VarRef if !newReceiver.tpe.isNullable =>
+ // common case (notably for `this`) for which there is no side-effect nor NPE
+ newArg
+ case _ =>
+ js.Block(
+ js.UnaryOp(js.UnaryOp.CheckNotNull, newReceiver),
+ newArg)
+ }
+ }
+
+ /** Gen JS code for a coercion */
+ private def genCoercion(tree: Apply, receiver: Tree, code: Int): js.Tree = {
+ implicit val pos = tree.pos
+
+ val source = genExpr(receiver)
+ val resultType = toIRType(tree.tpe)
+ adaptPrimitive(source, resultType)
+ }
+
+ /** Gen JS code for an ApplyDynamic
+ * ApplyDynamic nodes appear as the result of calls to methods of a
+ * structural type.
+ *
+ * Most unfortunately, earlier phases of the compiler assume too much
+ * about the backend, namely, they believe arguments and the result must
+ * be boxed, and do the boxing themselves. This decision should be left
+ * to the backend, but it's not, so we have to undo these boxes.
+ * Note that this applies to parameter types only. The return type is boxed
+ * anyway since we do not know it's exact type.
+ *
+ * This then generates a call to the reflective call proxy for the given
+ * arguments.
+ */
+ private def genApplyDynamic(tree: ApplyDynamic): js.Tree = {
+ implicit val pos = tree.pos
+
+ val sym = tree.symbol
+ val name = sym.name
+ val params = sym.tpe.params
+
+ /* Is this a primitive method introduced in AnyRef?
+ * The concerned methods are `eq`, `ne` and `synchronized`.
+ *
+ * If it is, it can be defined in a custom value class. Calling it
+ * reflectively works on the JVM in that case. However, it does not work
+ * if the reflective call should in fact resolve to the method in
+ * `AnyRef` (it causes a `NoSuchMethodError`). We maintain bug
+ * compatibility for these methods: they work if redefined in a custom
+ * AnyVal, and fail at run-time (with a `TypeError`) otherwise.
+ */
+ val isAnyRefPrimitive = {
+ (name == nme.eq || name == nme.ne || name == nme.synchronized_) &&
+ params.size == 1 && params.head.tpe.typeSymbol == ObjectClass
+ }
+
+ /** check if the method we are invoking conforms to a method on
+ * scala.Array. If this is the case, we check that case specially at
+ * runtime to avoid having reflective call proxies on scala.Array.
+ * (Also, note that the element type of Array#update is not erased and
+ * therefore the method name mangling would turn out wrong)
+ *
+ * Note that we cannot check if the expected return type is correct,
+ * since this type information is already erased.
+ */
+ def isArrayLikeOp = name match {
+ case nme.update =>
+ params.size == 2 && params.head.tpe.typeSymbol == IntClass
+ case nme.apply =>
+ params.size == 1 && params.head.tpe.typeSymbol == IntClass
+ case nme.length =>
+ params.size == 0
+ case nme.clone_ =>
+ params.size == 0
+ case _ =>
+ false
+ }
+
+ /**
+ * Tests whether one of our reflective "boxes" for primitive types
+ * implements the particular method. If this is the case
+ * (result != NoSymbol), we generate a runtime instance check if we are
+ * dealing with the appropriate primitive type.
+ */
+ def matchingSymIn(clazz: Symbol) = clazz.tpe.member(name).suchThat { s =>
+ val sParams = s.tpe.params
+ !s.isBridge &&
+ params.size == sParams.size &&
+ (params zip sParams).forall { case (s1,s2) =>
+ s1.tpe =:= s2.tpe
+ }
+ }
+
+ val ApplyDynamic(receiver, args) = tree
+
+ val receiverType = toIRType(receiver.tpe)
+ val callTrgIdent = freshLocalIdent()
+ val callTrgVarDef = js.VarDef(callTrgIdent, NoOriginalName, receiverType,
+ mutable = false, genExpr(receiver))
+ val callTrg = js.VarRef(callTrgIdent.name)(receiverType)
+
+ val arguments = args zip sym.tpe.params map { case (arg, param) =>
+ /* No need for enteringPosterasure, because value classes are not
+ * supported as parameters of methods in structural types.
+ * We could do it for safety and future-proofing anyway, except that
+ * I am weary of calling enteringPosterasure for a reflective method
+ * symbol.
+ *
+ * Note also that this will typically unbox a primitive value that
+ * has just been boxed, or will .asInstanceOf[T] an expression which
+ * is already of type T. But the optimizer will get rid of that, and
+ * reflective calls are not numerous, so we don't complicate the
+ * compiler to eliminate them early.
+ */
+ fromAny(genExpr(arg), param.tpe)
+ }
+
+ var callStatement: js.Tree = js.Apply(js.ApplyFlags.empty, callTrg,
+ encodeMethodSym(sym, reflProxy = true), arguments)(jstpe.AnyType)
+
+ if (!isAnyRefPrimitive) {
+ def boxIfNeeded(call: js.Tree, returnType: Type): js.Tree = {
+ if (isPrimitiveValueType(returnType))
+ makePrimitiveBox(call, returnType)
+ else
+ call
+ }
+
+ if (isArrayLikeOp) {
+ def genRTCall(method: Symbol, args: js.Tree*) =
+ genApplyMethod(genLoadModule(ScalaRunTimeModule),
+ method, args.toList)
+ val isArrayTree =
+ genRTCall(ScalaRunTime_isArray, callTrg, js.IntLiteral(1))
+ callStatement = js.If(isArrayTree, {
+ name match {
+ case nme.update =>
+ js.Block(
+ genRTCall(currentRun.runDefinitions.arrayUpdateMethod,
+ callTrg, arguments(0), arguments(1)),
+ js.Undefined()) // Boxed Unit
+ case nme.apply =>
+ genRTCall(currentRun.runDefinitions.arrayApplyMethod, callTrg,
+ arguments(0))
+ case nme.length =>
+ genRTCall(currentRun.runDefinitions.arrayLengthMethod, callTrg)
+ case nme.clone_ =>
+ genApplyMethod(callTrg, Object_clone, arguments)
+ }
+ }, {
+ callStatement
+ })(jstpe.AnyType)
+ }
+
+ /* Add an explicit type test for a hijacked class with a call to a
+ * hijacked method, if applicable (i.e., if there is a matching method
+ * in the given hijacked class). This is necessary because hijacked
+ * classes of the IR do not support reflective proxy calls.
+ *
+ * Returns true if this treatment was applicable.
+ */
+ def addCallToHijackedMethodIfApplicable(hijackedClass: Symbol): Boolean = {
+ val hijackedMethod = matchingSymIn(hijackedClass)
+ val isApplicable =
+ hijackedMethod != NoSymbol && hijackedMethod.isPublic
+ if (isApplicable) {
+ val hijackedClassTpe = hijackedClass.tpe
+ callStatement = js.If(genIsInstanceOf(callTrg, hijackedClassTpe), {
+ boxIfNeeded(
+ genApplyMethod(genAsInstanceOf(callTrg, hijackedClassTpe),
+ hijackedMethod, arguments),
+ hijackedMethod.tpe.resultType)
+ }, { // else
+ callStatement
+ })(jstpe.AnyType)
+ }
+ isApplicable
+ }
+
+ // String is a hijacked class
+ addCallToHijackedMethodIfApplicable(StringClass)
+
+ /* For primitive types, we need to handle two cases. The method could
+ * either be defined in the boxed class of the primitive type (which is
+ * hijacked), or it could be defined in the primitive class itself.
+ * If the hijacked class treatment is not applicable, we also try the
+ * primitive treatment, in which case we directly generate the
+ * primitive operation.
+ */
+
+ def addCallForPrimitive(primitiveClass: Symbol): Boolean = {
+ val boxedClass = definitions.boxedClass(primitiveClass)
+ if (addCallToHijackedMethodIfApplicable(boxedClass)) {
+ true
+ } else {
+ val methodInPrimClass = matchingSymIn(primitiveClass)
+ if (methodInPrimClass != NoSymbol && methodInPrimClass.isPublic) {
+ def isIntOrLong(tpe: jstpe.Type): Boolean = tpe match {
+ case jstpe.ByteType | jstpe.ShortType | jstpe.IntType | jstpe.LongType =>
+ true
+ case _ =>
+ false
+ }
+ val ignoreBecauseItMustBeAnInt = {
+ primitiveClass == DoubleClass &&
+ toIRType(methodInPrimClass.tpe.resultType) == jstpe.DoubleType &&
+ isIntOrLong(toIRType(sym.tpe.resultType))
+ }
+ if (ignoreBecauseItMustBeAnInt) {
+ // Fall through to the Int case that will come next
+ false
+ } else {
+ val boxedTpe = boxedClass.tpe
+ callStatement = js.If(genIsInstanceOf(callTrg, boxedTpe), {
+ val castCallTrg =
+ makePrimitiveUnbox(callTrg, primitiveClass.tpe)
+ val call = genPrimitiveOpForReflectiveCall(methodInPrimClass,
+ castCallTrg, arguments)
+ boxIfNeeded(call, methodInPrimClass.tpe.resultType)
+ }, { // else
+ callStatement
+ })(jstpe.AnyType)
+ true
+ }
+ } else {
+ false
+ }
+ }
+ }
+
+ addCallForPrimitive(BooleanClass)
+ addCallForPrimitive(LongClass)
+ addCallForPrimitive(CharClass)
+
+ /* For primitive numeric types that box as JS numbers, find the first
+ * one that matches. It will be able to handle the subsequent cases.
+ */
+ Seq(DoubleClass, IntClass, FloatClass, ShortClass, ByteClass).find(
+ addCallForPrimitive)
+ }
+
+ js.Block(callTrgVarDef, callStatement)
+ }
+
+ /** Ensures that the value of the given tree is boxed when used as a method result value.
+ * @param expr Tree to be boxed if needed.
+ * @param sym Method symbol this is the result of.
+ */
+ def ensureResultBoxed(expr: js.Tree, methodSym: Symbol)(
+ implicit pos: Position): js.Tree = {
+ val tpeEnteringPosterasure =
+ enteringPhase(currentRun.posterasurePhase)(methodSym.tpe.resultType)
+ ensureBoxed(expr, tpeEnteringPosterasure)
+ }
+
+ /** Ensures that the value of the given tree is boxed.
+ * @param expr Tree to be boxed if needed.
+ * @param tpeEnteringPosterasure The type of `expr` as it was entering
+ * the posterasure phase.
+ */
+ def ensureBoxed(expr: js.Tree, tpeEnteringPosterasure: Type)(
+ implicit pos: Position): js.Tree = {
+
+ tpeEnteringPosterasure match {
+ case tpe if isPrimitiveValueType(tpe) =>
+ makePrimitiveBox(expr, tpe)
+
+ case tpe: ErasedValueType =>
+ val boxedClass = tpe.valueClazz
+ val ctor = boxedClass.primaryConstructor
+ genNew(boxedClass, ctor, List(expr))
+
+ case _ =>
+ expr
+ }
+ }
+
+ /** Extracts a value typed as Any to the given type after posterasure.
+ * @param expr Tree to be extracted.
+ * @param tpeEnteringPosterasure The type of `expr` as it was entering
+ * the posterasure phase.
+ */
+ def fromAny(expr: js.Tree, tpeEnteringPosterasure: Type)(
+ implicit pos: Position): js.Tree = {
+
+ tpeEnteringPosterasure match {
+ case tpe if isPrimitiveValueType(tpe) =>
+ makePrimitiveUnbox(expr, tpe)
+
+ case tpe: ErasedValueType =>
+ val boxedClass = tpe.valueClazz
+ val unboxMethod = boxedClass.derivedValueClassUnbox
+ val content = genApplyMethod(
+ genAsInstanceOf(expr, tpe), unboxMethod, Nil)
+ if (unboxMethod.tpe.resultType <:< tpe.erasedUnderlying)
+ content
+ else
+ fromAny(content, tpe.erasedUnderlying)
+
+ case tpe =>
+ genAsInstanceOf(expr, tpe)
+ }
+ }
+
+ /** Adapt boxes on a tree from and to the given types after posterasure.
+ *
+ * @param expr
+ * Tree to be adapted.
+ * @param fromTpeEnteringPosterasure
+ * The type of `expr` as it was entering the posterasure phase.
+ * @param toTpeEnteringPosterausre
+ * The type of the adapted tree as it would be entering the posterasure phase.
+ */
+ def adaptBoxes(expr: js.Tree, fromTpeEnteringPosterasure: Type,
+ toTpeEnteringPosterasure: Type)(
+ implicit pos: Position): js.Tree = {
+ if (fromTpeEnteringPosterasure =:= toTpeEnteringPosterasure) {
+ expr
+ } else {
+ /* Upcast to `Any` then downcast to `toTpe`. This is not very smart.
+ * We rely on the optimizer to get rid of unnecessary casts.
+ */
+ fromAny(ensureBoxed(expr, fromTpeEnteringPosterasure), toTpeEnteringPosterasure)
+ }
+ }
+
+ /** Gen a boxing operation (tpe is the primitive type) */
+ def makePrimitiveBox(expr: js.Tree, tpe: Type)(
+ implicit pos: Position): js.Tree = {
+ toIRType(tpe) match {
+ case jstpe.VoidType => // for JS interop cases
+ js.Block(expr, js.Undefined())
+ case jstpe.BooleanType | jstpe.CharType | jstpe.ByteType |
+ jstpe.ShortType | jstpe.IntType | jstpe.LongType | jstpe.FloatType |
+ jstpe.DoubleType =>
+ expr // box is identity for all those primitive types
+ case _ =>
+ abort(s"makePrimitiveBox requires a primitive type, found $tpe at $pos")
+ }
+ }
+
+ /** Gen an unboxing operation (tpe is the primitive type) */
+ def makePrimitiveUnbox(expr: js.Tree, tpe: Type)(
+ implicit pos: Position): js.Tree = {
+ toIRType(tpe) match {
+ case jstpe.VoidType => expr // for JS interop cases
+ case irTpe => js.AsInstanceOf(expr, irTpe)
+ }
+ }
+
+ /** Gen JS code for a Scala.js-specific primitive method */
+ private def genJSPrimitive(tree: Apply, args: List[Tree], code: Int,
+ isStat: Boolean): js.Tree = {
+ import jsPrimitives._
+
+ implicit val pos = tree.pos
+
+ def genArgs1: js.Tree = {
+ assert(args.size == 1,
+ s"Expected exactly 1 argument for JS primitive $code but got " +
+ s"${args.size} at $pos")
+ genExpr(args.head)
+ }
+
+ def genArgs2: (js.Tree, js.Tree) = {
+ assert(args.size == 2,
+ s"Expected exactly 2 arguments for JS primitive $code but got " +
+ s"${args.size} at $pos")
+ (genExpr(args.head), genExpr(args.tail.head))
+ }
+
+ def genArgsVarLength: List[js.TreeOrJSSpread] =
+ genPrimitiveJSArgs(tree.symbol, args)
+
+ def resolveReifiedJSClassSym(arg: Tree): Symbol = {
+ def fail(): Symbol = {
+ reporter.error(pos,
+ tree.symbol.nameString + " must be called with a constant " +
+ "classOf[T] representing a class extending js.Any " +
+ "(not a trait nor an object)")
+ NoSymbol
+ }
+ arg match {
+ case Literal(value) if value.tag == ClazzTag =>
+ val classSym = value.typeValue.typeSymbol
+ if (isJSType(classSym) && !classSym.isTrait && !classSym.isModuleClass)
+ classSym
+ else
+ fail()
+ case _ =>
+ fail()
+ }
+ }
+
+ (code: @switch) match {
+ case DYNNEW =>
+ // js.Dynamic.newInstance(clazz)(actualArgs: _*)
+ val (jsClass, actualArgs) = extractFirstArg(genArgsVarLength)
+ js.JSNew(jsClass, actualArgs)
+
+ case ARR_CREATE =>
+ // js.Array(elements: _*)
+ js.JSArrayConstr(genArgsVarLength)
+
+ case CONSTRUCTOROF =>
+ // runtime.constructorOf(clazz)
+ val classSym = resolveReifiedJSClassSym(args.head)
+ if (classSym == NoSymbol)
+ js.Undefined() // compile error emitted by resolveReifiedJSClassSym
+ else
+ genPrimitiveJSClass(classSym)
+
+ case CREATE_INNER_JS_CLASS | CREATE_LOCAL_JS_CLASS =>
+ // runtime.createInnerJSClass(clazz, superClass)
+ // runtime.createLocalJSClass(clazz, superClass, fakeNewInstances)
+ val classSym = resolveReifiedJSClassSym(args(0))
+ val superClassValue = genExpr(args(1))
+ if (classSym == NoSymbol) {
+ js.Undefined() // compile error emitted by resolveReifiedJSClassSym
+ } else {
+ val captureValues = {
+ if (code == CREATE_INNER_JS_CLASS) {
+ val outer = genThis()
+ List.fill(classSym.info.decls.count(_.isClassConstructor))(outer)
+ } else {
+ val ArrayValue(_, fakeNewInstances) = args(2)
+ fakeNewInstances.flatMap(genCaptureValuesFromFakeNewInstance(_))
+ }
+ }
+ js.CreateJSClass(encodeClassName(classSym),
+ superClassValue :: captureValues)
+ }
+
+ case WITH_CONTEXTUAL_JS_CLASS_VALUE =>
+ // withContextualJSClassValue(jsclass, inner)
+ val jsClassValue = genExpr(args(0))
+ withScopedVars(
+ contextualJSClassValue := Some(jsClassValue)
+ ) {
+ genStatOrExpr(args(1), isStat)
+ }
+
+ case IDENTITY_HASH_CODE =>
+ // runtime.identityHashCode(arg)
+ val arg = genArgs1
+ js.UnaryOp(js.UnaryOp.IdentityHashCode, arg)
+
+ case DEBUGGER =>
+ // js.special.debugger()
+ js.Debugger()
+
+ case UNITVAL =>
+ // BoxedUnit.UNIT, which is the boxed version of ()
+ js.Undefined()
+
+ case JS_NATIVE =>
+ // js.native
+ reporter.error(pos,
+ "js.native may only be used as stub implementation in facade types")
+ js.Undefined()
+
+ case TYPEOF =>
+ // js.typeOf(arg)
+ val arg = genArgs1
+ val typeofExpr = arg match {
+ case arg: js.JSGlobalRef => js.JSTypeOfGlobalRef(arg)
+ case _ => js.JSUnaryOp(js.JSUnaryOp.typeof, arg)
+ }
+ genAsInstanceOf(typeofExpr, StringClass.tpe)
+
+ case JS_NEW_TARGET =>
+ // js.new.target
+ val valid = currentMethodSym.isClassConstructor && isNonNativeJSClass(currentClassSym)
+ if (!valid) {
+ reporter.error(pos,
+ "Illegal use of js.`new`.target.\n" +
+ "It can only be used in the constructor of a JS class, " +
+ "as a statement or in the rhs of a val or var.\n" +
+ "It cannot be used inside a lambda or by-name parameter, nor in any other location.")
+ }
+ js.JSNewTarget()
+
+ case JS_IMPORT =>
+ // js.import(arg)
+ val arg = genArgs1
+ js.JSImportCall(arg)
+
+ case JS_IMPORT_META =>
+ // js.import.meta
+ js.JSImportMeta()
+
+ case JS_ASYNC =>
+ // js.async(arg)
+ assert(args.size == 1,
+ s"Expected exactly 1 argument for JS primitive $code but got " +
+ s"${args.size} at $pos")
+ val Block(stats, fun @ Function(_, Apply(target, _))) = args.head
+ methodsAllowingJSAwait += target.symbol
+ val genStats = stats.map(genStat(_))
+ val asyncExpr = genAnonFunction(fun) match {
+ case js.NewLambda(_, closure: js.Closure)
+ if closure.params.isEmpty && closure.resultType == jstpe.AnyType =>
+ val newFlags = closure.flags.withTyped(false).withAsync(true)
+ js.JSFunctionApply(closure.copy(flags = newFlags), Nil)
+ case other =>
+ abort(s"Unexpected tree generated for the Function0 argument to js.async at $pos: $other")
+ }
+ js.Block(genStats, asyncExpr)
+
+ case JS_AWAIT =>
+ // js.await(arg)(permit)
+ val (arg, permitValue) = genArgs2
+ if (!methodsAllowingJSAwait.contains(currentMethodSym)) {
+ // This is an orphan await
+ if (!(args(1).tpe <:< WasmJSPI_allowOrphanJSAwaitModuleClass.toTypeConstructor)) {
+ reporter.error(pos,
+ "Illegal use of js.await().\n" +
+ "It can only be used inside a js.async {...} block, without any lambda,\n" +
+ "by-name argument or nested method in-between.\n" +
+ "If you compile for WebAssembly, you can allow arbitrary js.await()\n" +
+ "calls by adding the following import:\n" +
+ "import scala.scalajs.js.wasm.JSPI.allowOrphanJSAwait")
+ }
+ }
+ /* In theory we should evaluate `permit` after `arg` but before the `JSAwait`.
+ * It *should* always be side-effect-free, though, so we just discard it.
+ */
+ js.JSAwait(arg)
+
+ case DYNAMIC_IMPORT =>
+ assert(args.size == 1,
+ s"Expected exactly 1 argument for JS primitive $code but got " +
+ s"${args.size} at $pos")
+
+ args.head match {
+ case Block(stats, expr @ Apply(fun @ Select(New(tpt), _), args)) =>
+ /* stats is always empty if no other compiler plugin is present.
+ * However, code instrumentation (notably scoverage) might add
+ * statements here. If this is the case, the thunk anonymous class
+ * has already been created when the other plugin runs (i.e. the
+ * plugin ran after jsinterop).
+ *
+ * Therefore, it is OK to leave the statements on our side of the
+ * dynamic loading boundary.
+ */
+
+ val clsSym = tpt.symbol
+ val ctor = fun.symbol
+
+ assert(clsSym.isSubClass(DynamicImportThunkClass),
+ s"expected subclass of DynamicImportThunk, got: $clsSym at: ${expr.pos}")
+ assert(ctor.isPrimaryConstructor,
+ s"expected primary constructor, got: $ctor at: ${expr.pos}")
+
+ js.Block(
+ stats.map(genStat(_)),
+ js.ApplyDynamicImport(
+ js.ApplyFlags.empty,
+ encodeClassName(clsSym),
+ encodeDynamicImportForwarderIdent(ctor.tpe.params),
+ genActualArgs(ctor, args))
+ )
+
+ case tree =>
+ abort("Unexpected argument tree in dynamicImport: " +
+ tree + "/" + tree.getClass + " at: " + tree.pos)
+ }
+
+ case STRICT_EQ =>
+ // js.special.strictEquals(arg1, arg2)
+ val (arg1, arg2) = genArgs2
+ js.JSBinaryOp(js.JSBinaryOp.===, arg1, arg2)
+
+ case IN =>
+ // js.special.in(arg1, arg2)
+ val (arg1, arg2) = genArgs2
+ js.AsInstanceOf(js.JSBinaryOp(js.JSBinaryOp.in, arg1, arg2),
+ jstpe.BooleanType)
+
+ case INSTANCEOF =>
+ // js.special.instanceof(arg1, arg2)
+ val (arg1, arg2) = genArgs2
+ js.AsInstanceOf(js.JSBinaryOp(js.JSBinaryOp.instanceof, arg1, arg2),
+ jstpe.BooleanType)
+
+ case DELETE =>
+ // js.special.delete(arg1, arg2)
+ val (arg1, arg2) = genArgs2
+ js.JSDelete(arg1, arg2)
+
+ case FORIN =>
+ /* js.special.forin(arg1, arg2)
+ *
+ * We must generate:
+ *
+ * val obj = arg1
+ * val f = arg2
+ * for (val key in obj) {
+ * f(key)
+ * }
+ *
+ * with temporary vals, because `arg2` must be evaluated only
+ * once, and after `arg1`.
+ */
+ val (arg1, arg2) = genArgs2
+ val objVarDef = js.VarDef(freshLocalIdent("obj"), NoOriginalName,
+ jstpe.AnyType, mutable = false, arg1)
+ val fVarDef = js.VarDef(freshLocalIdent("f"), NoOriginalName,
+ jstpe.AnyType, mutable = false, arg2)
+ val keyVarIdent = freshLocalIdent("key")
+ val keyVarRef = js.VarRef(keyVarIdent.name)(jstpe.AnyType)
+ js.Block(
+ objVarDef,
+ fVarDef,
+ js.ForIn(objVarDef.ref, keyVarIdent, NoOriginalName, {
+ js.JSFunctionApply(fVarDef.ref, List(keyVarRef))
+ }))
+
+ case JS_THROW =>
+ // js.special.throw(arg)
+ js.UnaryOp(js.UnaryOp.Throw, genArgs1)
+
+ case JS_TRY_CATCH =>
+ /* js.special.tryCatch(arg1, arg2)
+ *
+ * We must generate:
+ *
+ * val body = arg1
+ * val handler = arg2
+ * try {
+ * body()
+ * } catch (e) {
+ * handler(e)
+ * }
+ *
+ * with temporary vals, because `arg2` must be evaluated before
+ * `body` executes. Moreover, exceptions thrown while evaluating
+ * the function values `arg1` and `arg2` must not be caught.
+ */
+ val (arg1, arg2) = genArgs2
+ val bodyVarDef = js.VarDef(freshLocalIdent("body"), NoOriginalName,
+ jstpe.AnyType, mutable = false, arg1)
+ val handlerVarDef = js.VarDef(freshLocalIdent("handler"), NoOriginalName,
+ jstpe.AnyType, mutable = false, arg2)
+ val exceptionVarIdent = freshLocalIdent("e")
+ val exceptionVarRef = js.VarRef(exceptionVarIdent.name)(jstpe.AnyType)
+ js.Block(
+ bodyVarDef,
+ handlerVarDef,
+ js.TryCatch(
+ js.JSFunctionApply(bodyVarDef.ref, Nil),
+ exceptionVarIdent,
+ NoOriginalName,
+ js.JSFunctionApply(handlerVarDef.ref, List(exceptionVarRef))
+ )(jstpe.AnyType)
+ )
+
+ case WRAP_AS_THROWABLE =>
+ // js.special.wrapAsThrowable(arg)
+ js.UnaryOp(js.UnaryOp.WrapAsThrowable, genArgs1)
+
+ case UNWRAP_FROM_THROWABLE =>
+ // js.special.unwrapFromThrowable(arg)
+ js.UnaryOp(js.UnaryOp.UnwrapFromThrowable,
+ js.UnaryOp(js.UnaryOp.CheckNotNull, genArgs1))
+
+ case LINKTIME_IF =>
+ // LinkingInfo.linkTimeIf(cond, thenp, elsep)
+ val cond = genLinkTimeExpr(args(0))
+ val thenp = genExpr(args(1))
+ val elsep = genExpr(args(2))
+ val tpe =
+ if (isStat) jstpe.VoidType
+ else toIRType(tree.tpe)
+ js.LinkTimeIf(cond, thenp, elsep)(tpe)
+
+ case LINKTIME_PROPERTY =>
+ // LinkingInfo.linkTimePropertyXXX("...")
+ val arg = genArgs1
+ val tpe: jstpe.Type = toIRType(tree.tpe) match {
+ case jstpe.ClassType(jswkn.BoxedStringClass, _) => jstpe.StringType
+ case irType => irType
+ }
+ arg match {
+ case js.StringLiteral(name) =>
+ js.LinkTimeProperty(name)(tpe)
+ case _ =>
+ reporter.error(args.head.pos,
+ "The argument of linkTimePropertyXXX must be a String literal: \"...\"")
+ js.LinkTimeProperty("erroneous")(tpe)
+ }
+ }
+ }
+
+ private def genLinkTimeExpr(tree: Tree): js.Tree = {
+ import scalaPrimitives._
+
+ implicit val pos = tree.pos
+
+ def invalid(): js.Tree = {
+ reporter.error(tree.pos,
+ "Illegal expression in the condition of a linkTimeIf. " +
+ "Valid expressions are: boolean and int primitives; " +
+ "references to link-time properties; " +
+ "primitive operations on booleans; " +
+ "and comparisons on ints.")
+ js.BooleanLiteral(false)
+ }
+
+ tree match {
+ case Literal(c) =>
+ c.tag match {
+ case BooleanTag => js.BooleanLiteral(c.booleanValue)
+ case IntTag => js.IntLiteral(c.intValue)
+ case _ => invalid()
+ }
+
+ case Apply(fun @ Select(receiver, _), args) =>
+ fun.symbol.getAnnotation(LinkTimePropertyAnnotation) match {
+ case Some(annotation) =>
+ val propName = annotation.constantAtIndex(0).get.stringValue
+ js.LinkTimeProperty(propName)(toIRType(tree.tpe))
+
+ case None if isPrimitive(fun.symbol) =>
+ val code = getPrimitive(fun.symbol)
+
+ def genLhs: js.Tree = genLinkTimeExpr(receiver)
+ def genRhs: js.Tree = genLinkTimeExpr(args.head)
+
+ def unaryOp(op: js.UnaryOp.Code): js.Tree =
+ js.UnaryOp(op, genLhs)
+ def binaryOp(op: js.BinaryOp.Code): js.Tree =
+ js.BinaryOp(op, genLhs, genRhs)
+
+ toIRType(receiver.tpe) match {
+ case jstpe.BooleanType =>
+ (code: @switch) match {
+ case ZNOT => unaryOp(js.UnaryOp.Boolean_!)
+ case EQ => binaryOp(js.BinaryOp.Boolean_==)
+ case NE | XOR => binaryOp(js.BinaryOp.Boolean_!=)
+ case OR => binaryOp(js.BinaryOp.Boolean_|)
+ case AND => binaryOp(js.BinaryOp.Boolean_&)
+ case ZOR => js.LinkTimeIf(genLhs, js.BooleanLiteral(true), genRhs)(jstpe.BooleanType)
+ case ZAND => js.LinkTimeIf(genLhs, genRhs, js.BooleanLiteral(false))(jstpe.BooleanType)
+ case _ => invalid()
+ }
+
+ case jstpe.IntType =>
+ (code: @switch) match {
+ case EQ => binaryOp(js.BinaryOp.Int_==)
+ case NE => binaryOp(js.BinaryOp.Int_!=)
+ case LT => binaryOp(js.BinaryOp.Int_<)
+ case LE => binaryOp(js.BinaryOp.Int_<=)
+ case GT => binaryOp(js.BinaryOp.Int_>)
+ case GE => binaryOp(js.BinaryOp.Int_>=)
+ case _ => invalid()
+ }
+
+ case _ =>
+ invalid()
+ }
+
+ case None => // if !isPrimitive
+ invalid()
+ }
+
+ case _ =>
+ invalid()
+ }
+ }
+
+ /** Gen JS code for a primitive JS call (to a method of a subclass of js.Any)
+ * This is the typed Scala.js to JS bridge feature. Basically it boils
+ * down to calling the method without name mangling. But other aspects
+ * come into play:
+ * * Operator methods are translated to JS operators (not method calls)
+ * * apply is translated as a function call, i.e. o() instead of o.apply()
+ * * Scala varargs are turned into JS varargs (see genPrimitiveJSArgs())
+ * * Getters and parameterless methods are translated as Selects
+ * * Setters are translated to Assigns of Selects
+ */
+ private def genPrimitiveJSCall(tree: Apply, isStat: Boolean): js.Tree = {
+ val sym = tree.symbol
+ val Apply(fun @ Select(receiver0, _), args0) = tree
+
+ implicit val pos = tree.pos
+
+ val receiver = genExprOrGlobalScope(receiver0)
+ val args = genPrimitiveJSArgs(sym, args0)
+
+ genJSCallGeneric(sym, receiver, args, isStat)
+ }
+
+ /** Gen JS code for a call to a native JS def or val. */
+ private def genJSNativeMemberCall(tree: Apply, isStat: Boolean): js.Tree = {
+ val sym = tree.symbol
+ val Apply(_, args0) = tree
+
+ implicit val pos = tree.pos
+
+ val jsNativeMemberValue =
+ js.SelectJSNativeMember(encodeClassName(sym.owner), encodeMethodSym(sym))
+
+ val boxedResult =
+ if (jsInterop.isJSGetter(sym)) jsNativeMemberValue
+ else js.JSFunctionApply(jsNativeMemberValue, genPrimitiveJSArgs(sym, args0))
+
+ fromAny(boxedResult, enteringPhase(currentRun.posterasurePhase) {
+ sym.tpe.resultType
+ })
+ }
+
+ private def genJSSuperCall(tree: Apply, isStat: Boolean): js.Tree = {
+ acquireContextualJSClassValue { explicitJSSuperClassValue =>
+ implicit val pos = tree.pos
+ val Apply(fun @ Select(sup @ Super(qual, _), _), args) = tree
+ val sym = fun.symbol
+
+ /* #3013 `qual` can be `this.$outer()` in some cases since Scala 2.12,
+ * so we call `genExpr(qual)`, not just `genThis()`.
+ */
+ val genReceiver = genExpr(qual)
+ lazy val genScalaArgs = genActualArgs(sym, args)
+ lazy val genJSArgs = genPrimitiveJSArgs(sym, args)
+
+ if (sym.owner == ObjectClass) {
+ // Normal call anyway
+ assert(!sym.isClassConstructor,
+ "Trying to call the super constructor of Object in a " +
+ s"non-native JS class at $pos")
+ genApplyMethod(genReceiver, sym, genScalaArgs)
+ } else if (sym.isClassConstructor) {
+ throw new AssertionError("calling a JS super constructor should " +
+ s"have happened in genPrimaryJSClassCtor at $pos")
+ } else if (isNonNativeJSClass(sym.owner) && !isExposed(sym)) {
+ // Reroute to the static method
+ genApplyJSClassMethod(genReceiver, sym, genScalaArgs)
+ } else {
+ val jsSuperClassValue = explicitJSSuperClassValue.orElse {
+ Some(genPrimitiveJSClass(currentClassSym.superClass))
+ }
+ genJSCallGeneric(sym, MaybeGlobalScope.NotGlobalScope(genReceiver),
+ genJSArgs, isStat, jsSuperClassValue)
+ }
+ }
+ }
+
+ private def genJSCallGeneric(sym: Symbol, receiver: MaybeGlobalScope,
+ args: List[js.TreeOrJSSpread], isStat: Boolean,
+ jsSuperClassValue: Option[js.Tree] = None)(
+ implicit pos: Position): js.Tree = {
+
+ def argsNoSpread: List[js.Tree] = {
+ assert(!args.exists(_.isInstanceOf[js.JSSpread]),
+ s"Unexpected spread at $pos")
+ args.asInstanceOf[List[js.Tree]]
+ }
+
+ val argc = args.size // meaningful only for methods that don't have varargs
+
+ def requireNotSuper(): Unit = {
+ if (jsSuperClassValue.isDefined) {
+ reporter.error(pos,
+ "Illegal super call in non-native JS class")
+ }
+ }
+
+ def genSuperReference(propName: js.Tree): js.AssignLhs = {
+ jsSuperClassValue.fold[js.AssignLhs] {
+ genJSBracketSelectOrGlobalRef(receiver, propName)
+ } { superClassValue =>
+ js.JSSuperSelect(superClassValue,
+ ruleOutGlobalScope(receiver), propName)
+ }
+ }
+
+ def genSelectGet(propName: js.Tree): js.Tree =
+ genSuperReference(propName)
+
+ def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree = {
+ val lhs = genSuperReference(propName)
+ lhs match {
+ case js.JSGlobalRef(js.JSGlobalRef.FileLevelThis) =>
+ reporter.error(pos,
+ "Illegal assignment to global this.")
+ case _ =>
+ }
+ js.Assign(lhs, value)
+ }
+
+ def genCall(methodName: js.Tree,
+ args: List[js.TreeOrJSSpread]): js.Tree = {
+ jsSuperClassValue.fold[js.Tree] {
+ genJSBracketMethodApplyOrGlobalRefApply(
+ receiver, methodName, args)
+ } { superClassValue =>
+ js.JSSuperMethodCall(superClassValue,
+ ruleOutGlobalScope(receiver), methodName, args)
+ }
+ }
+
+ val boxedResult = JSCallingConvention.of(sym) match {
+ case JSCallingConvention.UnaryOp(code) =>
+ requireNotSuper()
+ assert(argc == 0, s"bad argument count ($argc) for unary op at $pos")
+ js.JSUnaryOp(code, ruleOutGlobalScope(receiver))
+
+ case JSCallingConvention.BinaryOp(code) =>
+ requireNotSuper()
+ assert(argc == 1, s"bad argument count ($argc) for binary op at $pos")
+ js.JSBinaryOp(code, ruleOutGlobalScope(receiver), argsNoSpread.head)
+
+ case JSCallingConvention.Call =>
+ requireNotSuper()
+
+ if (sym.owner.isSubClass(JSThisFunctionClass)) {
+ genJSBracketMethodApplyOrGlobalRefApply(receiver,
+ js.StringLiteral("call"), args)
+ } else {
+ js.JSFunctionApply(ruleOutGlobalScope(receiver), args)
+ }
+
+ case JSCallingConvention.Property(jsName) =>
+ argsNoSpread match {
+ case Nil => genSelectGet(genExpr(jsName))
+ case value :: Nil => genSelectSet(genExpr(jsName), value)
+
+ case _ =>
+ throw new AssertionError(
+ s"property methods should have 0 or 1 non-varargs arguments at $pos")
+ }
+
+ case JSCallingConvention.BracketAccess =>
+ argsNoSpread match {
+ case keyArg :: Nil =>
+ genSelectGet(keyArg)
+ case keyArg :: valueArg :: Nil =>
+ genSelectSet(keyArg, valueArg)
+ case _ =>
+ throw new AssertionError(
+ s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments at $pos")
+ }
+
+ case JSCallingConvention.BracketCall =>
+ val (methodName, actualArgs) = extractFirstArg(args)
+ genCall(methodName, actualArgs)
+
+ case JSCallingConvention.Method(jsName) =>
+ genCall(genExpr(jsName), args)
+ }
+
+ boxedResult match {
+ case js.Assign(_, _) =>
+ boxedResult
+ case _ if isStat =>
+ boxedResult
+ case _ =>
+ fromAny(boxedResult,
+ enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType))
+ }
+ }
+
+ /** Extract the first argument to a primitive JS call.
+ * This is nothing else than decomposing into head and tail, except that
+ * we assert that the first element is not a JSSpread.
+ */
+ private def extractFirstArg(
+ args: List[js.TreeOrJSSpread]): (js.Tree, List[js.TreeOrJSSpread]) = {
+ assert(args.nonEmpty,
+ "Trying to extract the first argument of an empty argument list")
+ val firstArg = args.head match {
+ case firstArg: js.Tree =>
+ firstArg
+ case firstArg: js.JSSpread =>
+ throw new AssertionError(
+ "Trying to extract the first argument of an argument list starting " +
+ "with a Spread argument: " + firstArg)
+ }
+ (firstArg, args.tail)
+ }
+
+ /** Gen JS code for a new of a JS class (subclass of js.Any) */
+ private def genPrimitiveJSNew(tree: Apply): js.Tree = {
+ acquireContextualJSClassValue { jsClassValue =>
+ implicit val pos = tree.pos
+
+ val Apply(fun @ Select(New(tpt), _), args0) = tree
+ val cls = tpt.tpe.typeSymbol
+ val ctor = fun.symbol
+
+ val nestedJSClass = isNestedJSClass(cls)
+ assert(jsClassValue.isDefined == nestedJSClass,
+ s"$cls at $pos: jsClassValue.isDefined = ${jsClassValue.isDefined} " +
+ s"but isInnerNonNativeJSClass = $nestedJSClass")
+
+ def args = genPrimitiveJSArgs(ctor, args0)
+
+ if (cls == JSObjectClass && args0.isEmpty)
+ js.JSObjectConstr(Nil)
+ else if (cls == JSArrayClass && args0.isEmpty)
+ js.JSArrayConstr(Nil)
+ else if (isAnonymousJSClass(cls))
+ genAnonJSClassNew(cls, jsClassValue.get, args0.map(genExpr))(fun.pos)
+ else if (!nestedJSClass)
+ js.JSNew(genPrimitiveJSClass(cls), args)
+ else if (!cls.isModuleClass)
+ js.JSNew(jsClassValue.get, args)
+ else
+ genCreateInnerJSModule(cls, jsClassValue.get, args0.map(genExpr))
+ }
+ }
+
+ /** Gen JS code representing a JS class (subclass of js.Any) */
+ private def genPrimitiveJSClass(sym: Symbol)(
+ implicit pos: Position): js.Tree = {
+ assert(!isStaticModule(sym) && !sym.isTraitOrInterface,
+ s"genPrimitiveJSClass called with non-class $sym")
+ js.LoadJSConstructor(encodeClassName(sym))
+ }
+
+ /** Gen JS code to create the JS class of an inner JS module class. */
+ private def genCreateInnerJSModule(sym: Symbol,
+ jsSuperClassValue: js.Tree, args: List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+ js.JSNew(js.CreateJSClass(encodeClassName(sym),
+ jsSuperClassValue :: args), Nil)
+ }
+
+ /** Gen actual actual arguments to Scala method call.
+ *
+ * Returns a list of the transformed arguments.
+ *
+ * This tries to optimize repeated arguments (varargs) by turning them
+ * into JS arrays wrapped in the appropriate Seq, rather than Scala
+ * arrays.
+ */
+ private def genActualArgs(sym: Symbol, args: List[Tree])(
+ implicit pos: Position): List[js.Tree] = {
+ val wereRepeated = exitingPhase(currentRun.typerPhase) {
+ /* Do NOT use `params` instead of `paramss.flatten` here! Exiting
+ * typer, `params` only contains the *first* parameter list.
+ * This was causing #2265 and #2741.
+ */
+ sym.tpe.paramss.flatten.map(p => isScalaRepeatedParamType(p.tpe))
+ }
+
+ if (wereRepeated.size > args.size) {
+ // Should not happen, but let's not crash
+ args.map(genExpr)
+ } else {
+ /* Arguments that are in excess compared to the type signature after
+ * typer are lambda-lifted arguments. They cannot be repeated, hence
+ * the extension to `false`.
+ */
+ for ((arg, wasRepeated) <- args.zipAll(wereRepeated, EmptyTree, false)) yield {
+ if (wasRepeated) {
+ tryGenRepeatedParamAsJSArray(arg, handleNil = false).fold {
+ genExpr(arg)
+ } { genArgs =>
+ genJSArrayToVarArgs(js.JSArrayConstr(genArgs))
+ }
+ } else {
+ genExpr(arg)
+ }
+ }
+ }
+ }
+
+ /** Info about a Scala method param when called as JS method.
+ *
+ * @param sym Parameter symbol as seen now.
+ * @param tpe Parameter type (type of a single element if repeated)
+ * @param repeated Whether the parameter is repeated.
+ * @param capture Whether the parameter is a capture.
+ */
+ final class JSParamInfo(val sym: Symbol, val tpe: Type,
+ val repeated: Boolean = false, val capture: Boolean = false) {
+ assert(!repeated || !capture, "capture cannot be repeated")
+ def hasDefault: Boolean = sym.hasFlag(Flags.DEFAULTPARAM)
+ }
+
+ def jsParamInfos(sym: Symbol): List[JSParamInfo] = {
+ assert(sym.isMethod, s"trying to take JS param info of non-method: $sym")
+
+ /* For constructors of nested JS classes (*), explicitouter and
+ * lambdalift have introduced some parameters for the outer parameter and
+ * captures. We must ignore those, as captures and outer pointers are
+ * taken care of by `explicitinerjs` for such classes.
+ *
+ * Unfortunately, for some reason lambdalift creates new symbol *even for
+ * parameters originally in the signature* when doing so! That is why we
+ * use the *names* of the parameters as a link through time, rather than
+ * the symbols, to identify which ones already existed at the time of
+ * explicitinerjs.
+ *
+ * This is pretty fragile, but fortunately we have a huge test suite to
+ * back us up should scalac alter its behavior.
+ *
+ * In addition, for actual parameters that we keep, we have to look back
+ * in time to see whether they were repeated and what was their type.
+ *
+ * (*) Note that we are not supposed to receive in genPrimitiveJSArgs a
+ * method symbol that would have such captures *and* would not be a
+ * class constructors. Indeed, such methods would have started their
+ * life as local defs, which are not exposed.
+ */
+
+ val uncurryParams = enteringPhase(currentRun.uncurryPhase) {
+ for {
+ paramUncurry <- sym.tpe.paramss.flatten
+ } yield {
+ val v = {
+ if (isRepeated(paramUncurry))
+ Some(repeatedToSingle(paramUncurry.tpe))
+ else
+ None
+ }
+
+ paramUncurry.name -> v
+ }
+ }.toMap
+
+ val paramTpes = enteringPhase(currentRun.posterasurePhase) {
+ for (param <- sym.tpe.params)
+ yield param.name -> param.tpe
+ }.toMap
+
+ for {
+ paramSym <- sym.tpe.params
+ } yield {
+ uncurryParams.get(paramSym.name) match {
+ case None =>
+ // This is a capture parameter introduced by explicitouter or lambdalift
+ new JSParamInfo(paramSym, paramSym.tpe, capture = true)
+
+ case Some(Some(tpe)) =>
+ new JSParamInfo(paramSym, tpe, repeated = true)
+
+ case Some(None) =>
+ val tpe = paramTpes.getOrElse(paramSym.name, paramSym.tpe)
+ new JSParamInfo(paramSym, tpe)
+ }
+ }
+ }
+
+ /** Gen actual actual arguments to a primitive JS call.
+ *
+ * * Repeated arguments (varargs) are expanded
+ * * Default arguments are omitted or replaced by undefined
+ * * All arguments are boxed
+ *
+ * Repeated arguments that cannot be expanded at compile time (i.e., if a
+ * Seq is passed to a varargs parameter with the syntax `seq: _*`) will be
+ * wrapped in a [[js.JSSpread]] node to be expanded at runtime.
+ */
+ private def genPrimitiveJSArgs(sym: Symbol, args: List[Tree])(
+ implicit pos: Position): List[js.TreeOrJSSpread] = {
+
+ var reversedArgs: List[js.TreeOrJSSpread] = Nil
+
+ for ((arg, info) <- args.zip(jsParamInfos(sym))) {
+ if (info.repeated) {
+ reversedArgs =
+ genPrimitiveJSRepeatedParam(arg) reverse_::: reversedArgs
+ } else if (info.capture) {
+ // Ignore captures
+ assert(sym.isClassConstructor,
+ s"Found an unknown param ${info.sym.name} in method " +
+ s"${sym.fullName}, which is not a class constructor, at $pos")
+ } else {
+ val unboxedArg = genExpr(arg)
+ val boxedArg = unboxedArg match {
+ case js.Transient(UndefinedParam) =>
+ unboxedArg
+ case _ =>
+ ensureBoxed(unboxedArg, info.tpe)
+ }
+
+ reversedArgs ::= boxedArg
+ }
+ }
+
+ /* Remove all consecutive UndefinedParam's at the end of the argument
+ * list. No check is performed whether they may be there, since they will
+ * only be placed where default arguments can be anyway.
+ */
+ reversedArgs = reversedArgs.dropWhile {
+ case js.Transient(UndefinedParam) => true
+ case _ => false
+ }
+
+ // Find remaining UndefinedParam's and replace by js.Undefined. This can
+ // happen with named arguments or when multiple argument lists are present
+ reversedArgs = reversedArgs map {
+ case js.Transient(UndefinedParam) => js.Undefined()
+ case arg => arg
+ }
+
+ reversedArgs.reverse
+ }
+
+ /** Gen JS code for a repeated param of a primitive JS method
+ * In this case `arg` has type Seq[T] for some T, but the result should
+ * be an expanded list of the elements in the sequence. So this method
+ * takes care of the conversion.
+ * It is specialized for the shapes of tree generated by the desugaring
+ * of repeated params in Scala, so that these are actually expanded at
+ * compile-time.
+ * Otherwise, it returns a JSSpread with the Seq converted to a js.Array.
+ */
+ private def genPrimitiveJSRepeatedParam(arg: Tree): List[js.TreeOrJSSpread] = {
+ tryGenRepeatedParamAsJSArray(arg, handleNil = true) getOrElse {
+ /* Fall back to calling runtime.toJSVarArgs to perform the conversion
+ * to js.Array, then wrap in a Spread operator.
+ */
+ implicit val pos = arg.pos
+ val jsArrayArg = genApplyMethod(
+ genLoadModule(RuntimePackageModule),
+ Runtime_toJSVarArgs,
+ List(genExpr(arg)))
+ List(js.JSSpread(jsArrayArg))
+ }
+ }
+
+ /** Try and expand a repeated param (xs: T*) at compile-time.
+ * This method recognizes the shapes of tree generated by the desugaring
+ * of repeated params in Scala, and expands them.
+ * If `arg` does not have the shape of a generated repeated param, this
+ * method returns `None`.
+ */
+ private def tryGenRepeatedParamAsJSArray(arg: Tree,
+ handleNil: Boolean): Option[List[js.Tree]] = {
+ implicit val pos = arg.pos
+
+ // Given a method `def foo(args: T*)`
+ arg match {
+ // foo(arg1, arg2, ..., argN) where N > 0
+ case MaybeAsInstanceOf(WrapArray(
+ MaybeAsInstanceOf(ArrayValue(tpt, elems)))) =>
+ /* Value classes in arrays are already boxed, so no need to use
+ * the type before erasure.
+ */
+ val elemTpe = tpt.tpe
+ Some(elems.map(e => ensureBoxed(genExpr(e), elemTpe)))
+
+ // foo()
+ case Select(_, _) if handleNil && arg.symbol == NilModule =>
+ Some(Nil)
+
+ // foo(argSeq:_*) - cannot be optimized
+ case _ =>
+ None
+ }
+ }
+
+ object MaybeAsInstanceOf {
+ def unapply(tree: Tree): Some[Tree] = tree match {
+ case Apply(TypeApply(asInstanceOf_? @ Select(base, _), _), _)
+ if asInstanceOf_?.symbol == Object_asInstanceOf =>
+ Some(base)
+ case _ =>
+ Some(tree)
+ }
+ }
+
+ object WrapArray {
+ private val wrapArrayModule =
+ if (hasNewCollections) ScalaRunTimeModule
+ else PredefModule
+
+ val wrapRefArrayMethod: Symbol =
+ getMemberMethod(wrapArrayModule, nme.wrapRefArray)
+
+ val genericWrapArrayMethod: Symbol =
+ getMemberMethod(wrapArrayModule, nme.genericWrapArray)
+
+ def isClassTagBasedWrapArrayMethod(sym: Symbol): Boolean =
+ sym == wrapRefArrayMethod || sym == genericWrapArrayMethod
+
+ private val isWrapArray: Set[Symbol] = {
+ Seq(
+ nme.wrapRefArray,
+ nme.wrapByteArray,
+ nme.wrapShortArray,
+ nme.wrapCharArray,
+ nme.wrapIntArray,
+ nme.wrapLongArray,
+ nme.wrapFloatArray,
+ nme.wrapDoubleArray,
+ nme.wrapBooleanArray,
+ nme.wrapUnitArray,
+ nme.genericWrapArray
+ ).map(getMemberMethod(wrapArrayModule, _)).toSet
+ }
+
+ def unapply(tree: Apply): Option[Tree] = tree match {
+ case Apply(wrapArray_?, List(wrapped))
+ if isWrapArray(wrapArray_?.symbol) =>
+ Some(wrapped)
+ case _ =>
+ None
+ }
+ }
+
+ /** Wraps a `js.Array` to use as varargs. */
+ def genJSArrayToVarArgs(arrayRef: js.Tree)(
+ implicit pos: Position): js.Tree = {
+ genApplyMethod(genLoadModule(RuntimePackageModule),
+ Runtime_toScalaVarArgs, List(arrayRef))
+ }
+
+ /** Gen the actual capture values for a JS constructor based on its fake
+ * `new` invocation.
+ */
+ private def genCaptureValuesFromFakeNewInstance(
+ tree: Tree): List[js.Tree] = {
+
+ implicit val pos = tree.pos
+
+ val Apply(fun @ Select(New(_), _), args) = tree
+ val sym = fun.symbol
+
+ /* We use the same strategy as genPrimitiveJSArgs to detect which
+ * parameters were introduced by explicitouter or lambdalift (but
+ * reversed, of course).
+ */
+
+ val existedBeforeUncurry = enteringPhase(currentRun.uncurryPhase) {
+ for {
+ params <- sym.tpe.paramss
+ param <- params
+ } yield {
+ param.name
+ }
+ }.toSet
+
+ for {
+ (arg, paramSym) <- args.zip(sym.tpe.params)
+ if !existedBeforeUncurry(paramSym.name)
+ } yield {
+ genExpr(arg)
+ }
+ }
+
+ // Synthesizers for JS functions -------------------------------------------
+
+ /** Gen JS code for a JS function class.
+ *
+ * This is called when emitting a ClassDef that represents an anonymous
+ * class extending `js.FunctionN`. These are generated by the SAM
+ * synthesizer when the target type is a `js.FunctionN`. Since JS
+ * functions are not classes, we deconstruct the ClassDef, then
+ * reconstruct it to be a genuine Closure.
+ *
+ * We can always do so, because the body of SAM lambdas is hoisted in the
+ * enclosing class. Hence, the apply() method is just a forwarder to
+ * calling that hoisted method.
+ *
+ * From a class looking like this:
+ *
+ * final class (outer, capture1, ..., captureM) extends js.FunctionN[...] {
+ * def apply(param1, ..., paramN) = {
+ * outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM)
+ * }
+ * }
+ * new (o, c1, ..., cM)
+ *
+ * we generate a function:
+ *
+ * arrow-lambda(param1, ..., paramN) {
+ * outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM)
+ * }
+ */
+ def genJSFunction(cd: ClassDef, captures: List[js.Tree]): js.Tree = {
+ val sym = cd.symbol
+ assert(isJSFunctionDef(sym),
+ s"genAndRecordJSFunctionClass called with non-JS function $cd")
+
+ nestedGenerateClass(sym) {
+ genJSFunctionInner(cd, captures)
+ }
+ }
+
+ /** The code of `genJSFunction` that is inside the `nestedGenerateClass` wrapper. */
+ private def genJSFunctionInner(cd: ClassDef,
+ initialCapturedArgs: List[js.Tree]): js.Closure = {
+ implicit val pos = cd.pos
+ val sym = cd.symbol
+
+ def fail(reason: String): Nothing =
+ abort(s"Could not generate function for JS function: $reason")
+
+ // First step: find the apply method def, and collect param accessors
+
+ var paramAccessors: List[Symbol] = Nil
+ var applyDef: DefDef = null
+
+ def gen(tree: Tree): Unit = {
+ tree match {
+ case EmptyTree => ()
+ case Template(_, _, body) => body foreach gen
+ case vd @ ValDef(mods, name, tpt, rhs) =>
+ val fsym = vd.symbol
+ if (!fsym.isParamAccessor)
+ fail(s"Found field $fsym which is not a param accessor in anon function $cd")
+
+ if (fsym.isPrivate) {
+ paramAccessors ::= fsym
+ } else {
+ // Uh oh ... an inner something will try to access my fields
+ fail(s"Found a non-private field $fsym in $cd")
+ }
+ case dd: DefDef =>
+ val ddsym = dd.symbol
+ if (ddsym.isClassConstructor) {
+ if (!ddsym.isPrimaryConstructor)
+ fail(s"Non-primary constructor $ddsym in anon function $cd")
+ } else {
+ if (dd.name == nme.apply) {
+ if (!ddsym.isBridge) {
+ if (applyDef ne null)
+ fail(s"Found duplicate apply method $ddsym in $cd")
+ applyDef = dd
+ }
+ } else if (ddsym.hasAnnotation(JSOptionalAnnotation)) {
+ // Ignore (this is useful for default parameters in custom JS function types)
+ } else {
+ // Found a method we cannot encode in the rewriting
+ fail(s"Found a non-apply method $ddsym in $cd")
+ }
+ }
+ case _ =>
+ fail("Illegal tree in gen of genAndRecordAnonFunctionClass(): " + tree)
+ }
+ }
+ gen(cd.impl)
+ paramAccessors = paramAccessors.reverse // preserve definition order
+
+ if (applyDef eq null)
+ fail(s"Did not find any apply method in anon function $cd")
+
+ withNewLocalNameScope {
+ // Second step: build the list of useful constructor parameters
+
+ val ctorParams = sym.primaryConstructor.tpe.params
+
+ if (paramAccessors.size != ctorParams.size &&
+ !(paramAccessors.size == ctorParams.size-1 &&
+ ctorParams.head.unexpandedName == jsnme.arg_outer)) {
+ fail(
+ s"Have param accessors $paramAccessors but "+
+ s"ctor params $ctorParams in anon function $cd")
+ }
+
+ val hasUnusedOuterCtorParam = paramAccessors.size != ctorParams.size
+ val usedCtorParams =
+ if (hasUnusedOuterCtorParam) ctorParams.tail
+ else ctorParams
+ val ctorParamDefs = usedCtorParams.map(genParamDef(_))
+
+ // Third step: emit the body of the apply method def
+
+ val applyMethod = withScopedVars(
+ paramAccessorLocals := (paramAccessors zip ctorParamDefs).toMap
+ ) {
+ genMethodWithCurrentLocalNameScope(applyDef)
+ }
+
+ // Fourth step: patch the body to unbox parameters and box result
+
+ val hasRepeatedParam = enteringUncurry {
+ applyDef.symbol.paramss.flatten.lastOption.exists(isRepeated(_))
+ }
+
+ val js.MethodDef(_, _, _, params, _, body) = applyMethod
+ val (patchedParams, paramsLocals) = {
+ val nonRepeatedParams =
+ if (hasRepeatedParam) params.init
+ else params
+ patchFunParamsWithBoxes(applyDef.symbol, nonRepeatedParams,
+ useParamsBeforeLambdaLift = false,
+ fromParamTypes = nonRepeatedParams.map(_ => ObjectTpe))
+ }
+
+ val (patchedRepeatedParam, repeatedParamLocal) = {
+ /* Instead of this circus, simply `unzip` would be nice.
+ * But that lowers the type to iterable.
+ */
+ if (hasRepeatedParam) {
+ val (p, l) = genPatchedParam(params.last, genJSArrayToVarArgs(_), jstpe.AnyType)
+ (Some(p), Some(l))
+ } else {
+ (None, None)
+ }
+ }
+
+ val patchedBody =
+ js.Block(paramsLocals ++ repeatedParamLocal :+ ensureResultBoxed(body.get, applyDef.symbol))
+
+ // Fifth step: build the js.Closure
+
+ val isThisFunction = sym.isSubClass(JSThisFunctionClass) && {
+ val ok = patchedParams.nonEmpty
+ if (!ok) {
+ reporter.error(pos,
+ "The apply method for a js.ThisFunction must have a leading non-varargs parameter")
+ }
+ ok
+ }
+
+ val capturedArgs =
+ if (hasUnusedOuterCtorParam) initialCapturedArgs.tail
+ else initialCapturedArgs
+ assert(capturedArgs.size == ctorParamDefs.size,
+ s"$capturedArgs does not match $ctorParamDefs")
+
+ val closure = {
+ if (isThisFunction) {
+ val thisParam :: actualParams = patchedParams
+ js.Closure(
+ js.ClosureFlags.function,
+ ctorParamDefs,
+ actualParams,
+ patchedRepeatedParam,
+ jstpe.AnyType,
+ js.Block(
+ js.VarDef(thisParam.name, thisParam.originalName,
+ thisParam.ptpe, mutable = false,
+ js.This()(thisParam.ptpe)(thisParam.pos))(thisParam.pos),
+ patchedBody),
+ capturedArgs)
+ } else {
+ js.Closure(js.ClosureFlags.arrow, ctorParamDefs, patchedParams,
+ patchedRepeatedParam, jstpe.AnyType, patchedBody, capturedArgs)
+ }
+ }
+
+ closure
+ }
+ }
+
+ /** Generate JS code for an anonymous function
+ *
+ * Anonymous functions survive until the backend for any
+ * LambdaMetaFactory-capable type.
+ *
+ * When they do, their body is always of the form
+ * {{{
+ * EnclosingClass.this.someMethod(args)
+ * }}}
+ * where the args are either formal parameters of the lambda, or local
+ * variables or the enclosing def. The latter must be captured.
+ *
+ * We identify the captures using the same method as the `delambdafy`
+ * phase. We have an additional hack for `this`.
+ *
+ * To translate them, we first construct a typed closure for the body:
+ * {{{
+ * typed-lambda<_this = this, capture1: U1 = capture1, ..., captureM: UM = captureM>(
+ * arg1: T1, ..., argN: TN): TR = {
+ * val arg1Unboxed: S1 = arg1.asInstanceOf[S1];
+ * ...
+ * val argNUnboxed: SN = argN.asInstanceOf[SN];
+ * // inlined body of `someMethod`, boxed
+ * }
+ * }}}
+ * In the closure, input params are unboxed before use, and the result of
+ * the body of `someMethod` is boxed back. The Si and SR are the types
+ * found in the target `someMethod`; the Ti and TR are the types found in
+ * the SAM method to be implemented. It is common for `Si` to be different
+ * from `Ti`. For example, in a Scala function `(x: Int) => x`,
+ * `S1 = SR = int`, but `T1 = TR = any`, because `scala.Function1` defines
+ * an `apply` method that erases to using `any`'s.
+ *
+ * Then, we wrap that closure in a class satisfying the expected type.
+ * For SAM types that do not need any bridges (including all Scala
+ * function types), we use a `NewLambda` node.
+ *
+ * When bridges are required (which is rare), we generate a class on the
+ * fly. In that case, we "inline" the captures of the typed closure as
+ * fields of the class, and its body as the body of the main SAM method
+ * implementation. Overall, it looks like this:
+ * {{{
+ * class AnonFun extends Object with FunctionalInterface {
+ * val ...captureI: UI
+ * def (...captureI: UI) {
+ * super();
+ * ...this.captureI = captureI;
+ * }
+ * // main SAM method implementation
+ * def theSAMMethod(params: Ti...): TR = {
+ * val ...captureI = this.captureI;
+ * // inline body of the typed-lambda
+ * }
+ * // a bridge
+ * def theSAMMethod(params: Vi...): VR = {
+ * this.theSAMMethod(...params.asInstanceOf[Ti]).asInstanceOf[VR]
+ * }
+ * }
+ * }}}
+ */
+ private def genAnonFunction(originalFunction: Function): js.Tree = {
+ implicit val pos = originalFunction.pos
+ val Function(paramTrees, Apply(
+ targetTree @ Select(receiver, _), allArgs0)) = originalFunction
+
+ // Extract information about the SAM type we are implementing
+ val samClassSym = originalFunction.tpe.typeSymbolDirect
+ val (superClass, interfaces, sam, samBridges) = if (isFunctionSymbol(samClassSym)) {
+ // This is a scala.FunctionN SAM; extend the corresponding AbstractFunctionN class
+ val arity = paramTrees.size
+ val superClass = AbstractFunctionClass(arity)
+ val sam = superClass.info.member(nme.apply)
+ (superClass, Nil, sam, Nil)
+ } else {
+ // This is an arbitrary SAM interface
+ val samInfo = originalFunction.attachments.get[SAMFunction].getOrElse {
+ abort(s"Cannot find the SAMFunction attachment on $originalFunction at $pos")
+ }
+ (ObjectClass, samClassSym :: Nil, samInfo.sam, samBridgesFor(samInfo))
+ }
+
+ val captureSyms =
+ global.delambdafy.FreeVarTraverser.freeVarsOf(originalFunction).toList
+ val target = targetTree.symbol
+
+ val isTargetStatic = compileAsStaticMethod(target)
+
+ // Gen actual captures in the local name scope of the enclosing method
+ val actualCaptures: List[js.Tree] = {
+ val base = captureSyms.map(genVarRef(_))
+ if (isTargetStatic)
+ base
+ else
+ genExpr(receiver) :: base
+ }
+
+ val closure: js.Closure = withNewLocalNameScope {
+ // Gen the formal capture params for the closure
+ val thisFormalCapture: Option[js.ParamDef] = if (isTargetStatic) {
+ None
+ } else {
+ Some(js.ParamDef(
+ freshLocalIdent("this")(receiver.pos), thisOriginalName,
+ toIRType(receiver.tpe), mutable = false)(receiver.pos))
+ }
+ val formalCaptures: List[js.ParamDef] =
+ thisFormalCapture.toList ::: captureSyms.map(genParamDef(_, pos))
+
+ // Gen the inlined target method body
+ val genMethodDef = {
+ genMethodWithCurrentLocalNameScope(consumeDelambdafyTarget(target),
+ initThisLocalVarName = thisFormalCapture.map(_.name.name))
+ }
+ val js.MethodDef(methodFlags, _, _, methodParams, _, methodBody) = genMethodDef
+
+ /* If the target method was not supposed to be static, but genMethodDef
+ * turns out to be static, it means it is a non-exposed method of a JS
+ * class. The `this` param was turned into a regular param, for which
+ * we need a `js.VarDef`.
+ */
+ val (maybeThisParamAsVarDef, remainingMethodParams) = {
+ if (methodFlags.namespace.isStatic && !isTargetStatic) {
+ val thisParamDef :: remainingMethodParams = methodParams: @unchecked
+ val thisParamAsVarDef = js.VarDef(thisParamDef.name, thisParamDef.originalName,
+ thisParamDef.ptpe, thisParamDef.mutable, thisFormalCapture.get.ref)
+ (thisParamAsVarDef :: Nil, remainingMethodParams)
+ } else {
+ (Nil, methodParams)
+ }
+ }
+
+ // After that, the args found in the `Function` node had better match the remaining method params
+ assert(remainingMethodParams.size == allArgs0.size,
+ s"Arity mismatch: $remainingMethodParams <-> $allArgs0 at $pos")
+
+ /* Declare each method param as a VarDef, initialized to the corresponding arg.
+ * In practice, all the args are `This` nodes or `VarRef` nodes, so the
+ * optimizer will alias those VarDefs away.
+ * We do this because we have different Symbols, hence different
+ * encoded LocalIdents.
+ */
+ val methodParamsAsVarDefs = for ((methodParam, arg) <- remainingMethodParams.zip(allArgs0)) yield {
+ js.VarDef(methodParam.name, methodParam.originalName, methodParam.ptpe,
+ methodParam.mutable, genExpr(arg))
+ }
+
+ val (samParamTypes, samResultType, targetResultType) = enteringPhase(currentRun.posterasurePhase) {
+ val methodType = sam.tpe.asInstanceOf[MethodType]
+ (methodType.params.map(_.info), methodType.resultType, target.tpe.finalResultType)
+ }
+
+ /* Adapt the params and result so that they are boxed from the outside.
+ *
+ * TODO In total we generate *3* locals for each original param: the
+ * patched ParamDef, the VarDef for the unboxed value, and a VarDef for
+ * the original parameter of the delambdafy target. In theory we only
+ * need 2: can we make it so?
+ */
+ val formalArgs = paramTrees.map(p => genParamDef(p.symbol))
+ val (patchedFormalArgs, paramsLocals) =
+ patchFunParamsWithBoxes(target, formalArgs, useParamsBeforeLambdaLift = true, fromParamTypes = samParamTypes)
+ val patchedBodyWithBox =
+ adaptBoxes(methodBody.get, targetResultType, samResultType)
+
+ // Finally, assemble all the pieces
+ val fullClosureBody = js.Block(
+ paramsLocals :::
+ maybeThisParamAsVarDef :::
+ methodParamsAsVarDefs :::
+ patchedBodyWithBox ::
+ Nil
+ )
+ js.Closure(
+ js.ClosureFlags.typed,
+ formalCaptures,
+ patchedFormalArgs,
+ restParam = None,
+ resultType = toIRType(underlyingOfEVT(samResultType)),
+ fullClosureBody,
+ actualCaptures
+ )
+ }
+
+ // Build the descriptor
+ val closureType = closure.tpe.asInstanceOf[jstpe.ClosureType]
+ val descriptor = js.NewLambda.Descriptor(
+ encodeClassName(superClass), interfaces.map(encodeClassName(_)),
+ encodeMethodSym(sam).name, closureType.paramTypes,
+ closureType.resultType)
+
+ /* Wrap the closure in the appropriate box for the SAM type.
+ * Use a `NewLambda` if we do not need any bridges; otherwise synthesize
+ * a SAM wrapper class.
+ */
+ if (samBridges.isEmpty) {
+ // No bridges are needed; we can directly use a NewLambda
+ js.NewLambda(descriptor, closure)(encodeClassType(samClassSym).toNonNullable)
+ } else {
+ /* We need bridges; expand the `NewLambda` into a synthesized class.
+ * Captures of the closure are turned into fields of the wrapper class.
+ */
+ val formalCaptureTypeRefs = captureSyms.map(sym => toTypeRef(sym.info))
+ val allFormalCaptureTypeRefs =
+ if (isTargetStatic) formalCaptureTypeRefs
+ else toTypeRef(receiver.tpe) :: formalCaptureTypeRefs
+
+ val ctorName = ir.Names.MethodName.constructor(allFormalCaptureTypeRefs)
+ val samWrapperClassName = synthesizeSAMWrapper(descriptor, sam, samBridges, closure, ctorName)
+ js.New(samWrapperClassName, js.MethodIdent(ctorName), closure.captureValues)
+ }
+ }
+
+ private def samBridgesFor(samInfo: SAMFunction)(implicit pos: Position): List[Symbol] = {
+ /* scala/bug#10512: any methods which `samInfo.sam` overrides need
+ * bridges made for them.
+ */
+ val samBridges = {
+ import scala.reflect.internal.Flags.BRIDGE
+ samInfo.synthCls.info.findMembers(excludedFlags = 0L, requiredFlags = BRIDGE).toList
+ }
+
+ if (samBridges.isEmpty) {
+ // fast path
+ Nil
+ } else {
+ /* Remove duplicates, e.g., if we override the same method declared
+ * in two super traits.
+ */
+ val builder = List.newBuilder[Symbol]
+ val seenMethodNames = mutable.Set.empty[MethodName]
+
+ seenMethodNames.add(encodeMethodSym(samInfo.sam).name)
+
+ for (samBridge <- samBridges) {
+ if (seenMethodNames.add(encodeMethodSym(samBridge).name))
+ builder += samBridge
+ }
+
+ builder.result()
+ }
+ }
+
+ private def synthesizeSAMWrapper(descriptor: js.NewLambda.Descriptor,
+ sam: Symbol, samBridges: List[Symbol], closure: js.Closure,
+ ctorName: ir.Names.MethodName)(
+ implicit pos: Position): ClassName = {
+
+ val suffix = {
+ generatedSAMWrapperCount.value += 1
+ // LambdaMetaFactory names classes like this
+ "$$Lambda$" + generatedSAMWrapperCount.value
+ }
+ val className = encodeClassName(currentClassSym).withSuffix(suffix)
+
+ val thisType = jstpe.ClassType(className, nullable = false)
+
+ // val captureI: CaptureTypeI
+ val captureFieldDefs = for (captureParam <- closure.captureParams) yield {
+ val simpleFieldName = SimpleFieldName(captureParam.name.name.encoded)
+ val ident = js.FieldIdent(FieldName(className, simpleFieldName))
+ js.FieldDef(js.MemberFlags.empty, ident, captureParam.originalName, captureParam.ptpe)
+ }
+
+ // def this(f: Any) = { ...this.captureI = captureI; super() }
+ val ctorDef = {
+ val captureFieldAssignments = for {
+ (captureFieldDef, captureParam) <- captureFieldDefs.zip(closure.captureParams)
+ } yield {
+ js.Assign(
+ js.Select(js.This()(thisType), captureFieldDef.name)(captureFieldDef.ftpe),
+ captureParam.ref)
+ }
+ js.MethodDef(
+ js.MemberFlags.empty.withNamespace(js.MemberNamespace.Constructor),
+ js.MethodIdent(ctorName),
+ NoOriginalName,
+ closure.captureParams,
+ jstpe.VoidType,
+ Some(js.Block(List(
+ js.Block(captureFieldAssignments),
+ js.ApplyStatically(js.ApplyFlags.empty.withConstructor(true),
+ js.This()(thisType),
+ jswkn.ObjectClass,
+ js.MethodIdent(jswkn.NoArgConstructorName),
+ Nil)(jstpe.VoidType)))))(
+ js.OptimizerHints.empty, Unversioned)
+ }
+
+ /* def samMethod(...closure.params): closure.resultType = {
+ * val captureI: CaptureTypeI = this.captureI;
+ * ...
+ * closure.body
+ * }
+ */
+ val samMethodDef: js.MethodDef = {
+ val localCaptureVarDefs = for {
+ (captureParam, captureFieldDef) <- closure.captureParams.zip(captureFieldDefs)
+ } yield {
+ js.VarDef(captureParam.name, captureParam.originalName, captureParam.ptpe, mutable = false,
+ js.Select(js.This()(thisType), captureFieldDef.name)(captureFieldDef.ftpe))
+ }
+
+ val body = js.Block(localCaptureVarDefs, closure.body)
+
+ js.MethodDef(js.MemberFlags.empty, encodeMethodSym(sam),
+ originalNameOfMethod(sam), closure.params, closure.resultType,
+ Some(body))(
+ js.OptimizerHints.empty, Unversioned)
+ }
+
+ val adaptBoxesTupled = (adaptBoxes(_, _, _)).tupled
+
+ // def samBridgeMethod(...params): resultType = this.samMethod(...params) // (with adaptBoxes)
+ val samBridgeMethodDefs = for (samBridge <- samBridges) yield {
+ val jsParams = samBridge.tpe.params.map(genParamDef(_, pos))
+ val resultType = toIRType(samBridge.tpe.finalResultType)
+
+ val actualParams = enteringPhase(currentRun.posterasurePhase) {
+ for (((formal, bridgeParam), samParam) <- jsParams.zip(samBridge.tpe.params).zip(sam.tpe.params))
+ yield (formal.ref, bridgeParam.tpe, samParam.tpe)
+ }.map(adaptBoxesTupled)
+
+ val call = js.Apply(js.ApplyFlags.empty, js.This()(thisType),
+ samMethodDef.name, actualParams)(samMethodDef.resultType)
+
+ val body = adaptBoxesTupled(enteringPhase(currentRun.posterasurePhase) {
+ (call, sam.tpe.finalResultType, samBridge.tpe.finalResultType)
+ })
+
+ js.MethodDef(js.MemberFlags.empty, encodeMethodSym(samBridge),
+ originalNameOfMethod(samBridge), jsParams, resultType,
+ Some(body))(
+ js.OptimizerHints.empty, Unversioned)
+ }
+
+ // The class definition
+ val classDef = js.ClassDef(
+ js.ClassIdent(className),
+ NoOriginalName,
+ ClassKind.Class,
+ None,
+ Some(js.ClassIdent(descriptor.superClass)),
+ descriptor.interfaces.map(js.ClassIdent(_)),
+ None,
+ None,
+ fields = captureFieldDefs,
+ methods = ctorDef :: samMethodDef :: samBridgeMethodDefs,
+ jsConstructor = None,
+ Nil,
+ Nil,
+ Nil)(
+ js.OptimizerHints.empty.withInline(true))
+
+ generatedClasses += classDef -> pos
+
+ className
+ }
+
+ private def patchFunParamsWithBoxes(methodSym: Symbol,
+ params: List[js.ParamDef], useParamsBeforeLambdaLift: Boolean,
+ fromParamTypes: List[Type])(
+ implicit pos: Position): (List[js.ParamDef], List[js.VarDef]) = {
+ // See the comment in genPrimitiveJSArgs for a rationale about this
+ val paramTpes = enteringPhase(currentRun.posterasurePhase) {
+ for (param <- methodSym.tpe.params)
+ yield param.name -> param.tpe
+ }.toMap
+
+ /* Normally, we should work with the list of parameters as seen right
+ * now. But when generating an anonymous function from a Function node,
+ * the `methodSym` we use is the *target* of the inner call, not the
+ * enclosing method for which we're patching the params and body. This
+ * is a hack which we have to do because there is no such enclosing
+ * method in that case. When we use the target, the list of symbols for
+ * formal parameters that we want to see is that before lambdalift, not
+ * the one we see right now.
+ */
+ val paramSyms = {
+ if (useParamsBeforeLambdaLift)
+ enteringPhase(currentRun.phaseNamed("lambdalift"))(methodSym.tpe.params)
+ else
+ methodSym.tpe.params
+ }
+
+ (for {
+ ((param, paramSym), fromParamType) <- params.zip(paramSyms).zip(fromParamTypes)
+ } yield {
+ val paramTpe = paramTpes.getOrElse(paramSym.name, paramSym.tpe)
+ genPatchedParam(param, adaptBoxes(_, fromParamType, paramTpe),
+ toIRType(underlyingOfEVT(fromParamType)))
+ }).unzip
+ }
+
+ private def genPatchedParam(param: js.ParamDef, rhs: js.VarRef => js.Tree,
+ fromParamType: jstpe.Type)(
+ implicit pos: Position): (js.ParamDef, js.VarDef) = {
+ val paramNameIdent = param.name
+ val origName = param.originalName
+ val newNameIdent = freshLocalIdent(paramNameIdent.name)(paramNameIdent.pos)
+ val newOrigName = origName.orElse(paramNameIdent.name)
+ val patchedParam = js.ParamDef(newNameIdent, newOrigName, fromParamType,
+ mutable = false)(param.pos)
+ val paramLocal = js.VarDef(paramNameIdent, origName, param.ptpe,
+ mutable = false, rhs(patchedParam.ref))
+ (patchedParam, paramLocal)
+ }
+
+ private def underlyingOfEVT(tpe: Type): Type = tpe match {
+ case tpe: ErasedValueType => tpe.erasedUnderlying
+ case _ => tpe
+ }
+
+ /** Generates a static method instantiating and calling this
+ * DynamicImportThunk's `apply`:
+ *
+ * {{{
+ * static def dynamicImport$;;Ljava.lang.Object(): any = {
+ * new .;:V().apply;Ljava.lang.Object()
+ * }
+ * }}}
+ */
+ private def genDynamicImportForwarder(clsSym: Symbol)(
+ implicit pos: Position): js.MethodDef = {
+ withNewLocalNameScope {
+ val ctor = clsSym.primaryConstructor
+ val paramSyms = ctor.tpe.params
+ val paramDefs = paramSyms.map(genParamDef(_))
+
+ val body = {
+ val inst = genNew(clsSym, ctor, paramDefs.map(_.ref))
+ genApplyMethod(inst, DynamicImportThunkClass_apply, Nil)
+ }
+
+ js.MethodDef(
+ js.MemberFlags.empty.withNamespace(js.MemberNamespace.PublicStatic),
+ encodeDynamicImportForwarderIdent(paramSyms),
+ NoOriginalName,
+ paramDefs,
+ jstpe.AnyType,
+ Some(body))(OptimizerHints.empty, Unversioned)
+ }
+ }
+
+ // Methods to deal with JSName ---------------------------------------------
+
+ def genExpr(name: JSName)(implicit pos: Position): js.Tree = name match {
+ case JSName.Literal(name) => js.StringLiteral(name)
+ case JSName.Computed(sym) => genComputedJSName(sym)
+ }
+
+ private def genComputedJSName(sym: Symbol)(implicit pos: Position): js.Tree = {
+ /* By construction (i.e. restriction in PrepJSInterop), we know that sym
+ * must be a static method.
+ * Therefore, at this point, we can invoke it by loading its owner and
+ * calling it.
+ */
+ def moduleOrGlobalScope = genLoadModuleOrGlobalScope(sym.owner)
+ def module = genLoadModule(sym.owner)
+
+ if (isJSType(sym.owner)) {
+ if (!isNonNativeJSClass(sym.owner) || isExposed(sym))
+ genJSCallGeneric(sym, moduleOrGlobalScope, args = Nil, isStat = false)
+ else
+ genApplyJSClassMethod(module, sym, arguments = Nil)
+ } else {
+ genApplyMethod(module, sym, arguments = Nil)
+ }
+ }
+
+ // Utilities ---------------------------------------------------------------
+
+ def genVarRef(sym: Symbol)(implicit pos: Position): js.VarRef =
+ js.VarRef(encodeLocalSymName(sym))(toIRType(sym.tpe))
+
+ def genParamDef(sym: Symbol): js.ParamDef =
+ genParamDef(sym, toIRType(sym.tpe))
+
+ private def genParamDef(sym: Symbol, ptpe: jstpe.Type): js.ParamDef =
+ genParamDef(sym, ptpe, sym.pos)
+
+ private def genParamDef(sym: Symbol, pos: Position): js.ParamDef =
+ genParamDef(sym, toIRType(sym.tpe), pos)
+
+ private def genParamDef(sym: Symbol, ptpe: jstpe.Type,
+ pos: Position): js.ParamDef = {
+ js.ParamDef(encodeLocalSym(sym)(pos), originalNameOfLocal(sym), ptpe,
+ mutable = false)(pos)
+ }
+
+ /** Generates a call to `runtime.privateFieldsSymbol()` */
+ private def genPrivateFieldsSymbol()(implicit pos: Position): js.Tree = {
+ genApplyMethod(genLoadModule(RuntimePackageModule),
+ Runtime_privateFieldsSymbol, Nil)
+ }
+
+ /** Generate loading of a module value.
+ *
+ * Can be given either the module symbol or its module class symbol.
+ *
+ * If the module we load refers to the global scope (i.e., it is
+ * annotated with `@JSGlobalScope`), report a compile error specifying
+ * that a global scope object should only be used as the qualifier of a
+ * `.`-selection.
+ */
+ def genLoadModule(sym0: Symbol)(implicit pos: Position): js.Tree =
+ ruleOutGlobalScope(genLoadModuleOrGlobalScope(sym0))
+
+ /** Generate loading of a module value or the global scope.
+ *
+ * Can be given either the module symbol of its module class symbol.
+ *
+ * Unlike `genLoadModule`, this method does not fail if the module we load
+ * refers to the global scope.
+ */
+ def genLoadModuleOrGlobalScope(sym0: Symbol)(
+ implicit pos: Position): MaybeGlobalScope = {
+
+ require(sym0.isModuleOrModuleClass,
+ "genLoadModule called with non-module symbol: " + sym0)
+
+ if (sym0.isModule && sym0.isScala3Defined && sym0.hasAttachment[DottyEnumSingletonCompat.type]) {
+ /* #4739 This is a reference to a singleton `case` from a Scala 3 `enum`.
+ * It is not a module. Instead, it is a static field (accessed through
+ * a static getter) in the `enum` class.
+ * We use `originalOwner` and `rawname` because that's what the JVM back-end uses.
+ */
+ val className = encodeClassName(sym0.originalOwner)
+ val getterSimpleName = sym0.rawname.toString()
+ val getterMethodName = MethodName(getterSimpleName, Nil, toTypeRef(sym0.tpe))
+ val tree = js.ApplyStatic(js.ApplyFlags.empty, className, js.MethodIdent(getterMethodName), Nil)(
+ toIRType(sym0.tpe))
+ MaybeGlobalScope.NotGlobalScope(tree)
+ } else {
+ val sym = if (sym0.isModule) sym0.moduleClass else sym0
+
+ // Does that module refer to the global scope?
+ if (sym.hasAnnotation(JSGlobalScopeAnnotation)) {
+ MaybeGlobalScope.GlobalScope(pos)
+ } else {
+ if (sym == currentClassSym.get && isModuleInitialized.get != null && isModuleInitialized.value) {
+ /* This is a LoadModule(myClass) after the StoreModule(). It is
+ * guaranteed to always return the `this` value. We eagerly replace
+ * it by a `This()` node to help the elidable constructors analysis
+ * of the linker. If we don't do this, then the analysis must
+ * tolerate `LoadModule(myClass)` after `StoreModule()` to be
+ * side-effect-free, but that would weaken the guarantees resulting
+ * from the analysis. In particular, it cannot guarantee that the
+ * result of a `LoadModule()` of a module with elidable constructors
+ * is always fully initialized.
+ */
+ MaybeGlobalScope.NotGlobalScope(genThis())
+ } else {
+ val className = encodeClassName(sym)
+ val tree =
+ if (isJSType(sym)) js.LoadJSModule(className)
+ else js.LoadModule(className)
+ MaybeGlobalScope.NotGlobalScope(tree)
+ }
+ }
+ }
+ }
+
+ private final val GenericGlobalObjectInformationMsg = {
+ "\n " +
+ "See https://www.scala-js.org/doc/interoperability/global-scope.html " +
+ "for further information."
+ }
+
+ /** Rule out the `GlobalScope` case of a `MaybeGlobalScope` and extract the
+ * value tree.
+ *
+ * If `tree` represents the global scope, report a compile error.
+ */
+ private def ruleOutGlobalScope(tree: MaybeGlobalScope): js.Tree = {
+ tree match {
+ case MaybeGlobalScope.NotGlobalScope(t) =>
+ t
+ case MaybeGlobalScope.GlobalScope(pos) =>
+ reportErrorLoadGlobalScope()(pos)
+ }
+ }
+
+ /** Report a compile error specifying that the global scope cannot be
+ * loaded as a value.
+ */
+ private def reportErrorLoadGlobalScope()(implicit pos: Position): js.Tree = {
+ reporter.error(pos,
+ "Loading the global scope as a value (anywhere but as the " +
+ "left-hand-side of a `.`-selection) is not allowed." +
+ GenericGlobalObjectInformationMsg)
+ js.Undefined()(pos)
+ }
+
+ /** Gen a JS bracket select or a `JSGlobalRef`.
+ *
+ * If the receiver is a normal value, i.e., not the global scope, then
+ * emit a `JSBracketSelect`.
+ *
+ * Otherwise, if the `item` is a constant string that is a valid
+ * JavaScript identifier, emit a `JSGlobalRef`.
+ *
+ * Otherwise, report a compile error.
+ */
+ private def genJSBracketSelectOrGlobalRef(qual: MaybeGlobalScope,
+ item: js.Tree)(implicit pos: Position): js.AssignLhs = {
+ qual match {
+ case MaybeGlobalScope.NotGlobalScope(qualTree) =>
+ js.JSSelect(qualTree, item)
+
+ case MaybeGlobalScope.GlobalScope(_) =>
+ genJSGlobalRef(item, "Selecting a field", "selection")
+ }
+ }
+
+ /** Gen a JS bracket method apply or an apply of a `GlobalRef`.
+ *
+ * If the receiver is a normal value, i.e., not the global scope, then
+ * emit a `JSBracketMethodApply`.
+ *
+ * Otherwise, if the `method` is a constant string that is a valid
+ * JavaScript identifier, emit a `JSFunctionApply(JSGlobalRef(...), ...)`.
+ *
+ * Otherwise, report a compile error.
+ */
+ private def genJSBracketMethodApplyOrGlobalRefApply(
+ receiver: MaybeGlobalScope, method: js.Tree,
+ args: List[js.TreeOrJSSpread])(
+ implicit pos: Position): js.Tree = {
+ receiver match {
+ case MaybeGlobalScope.NotGlobalScope(receiverTree) =>
+ js.JSMethodApply(receiverTree, method, args)
+
+ case MaybeGlobalScope.GlobalScope(_) =>
+ val globalRef = genJSGlobalRef(method, "Calling a method", "call")
+ js.JSFunctionApply(globalRef, args)
+ }
+ }
+
+ private def genJSGlobalRef(propName: js.Tree,
+ actionFull: String, actionSimpleNoun: String)(
+ implicit pos: Position): js.JSGlobalRef = {
+ propName match {
+ case js.StringLiteral(value) =>
+ if (js.JSGlobalRef.isValidJSGlobalRefName(value)) {
+ if (value == "await") {
+ global.runReporting.warning(pos,
+ s"$actionFull of the global scope with the name '$value' is deprecated.\n" +
+ " It may produce invalid JavaScript code causing a SyntaxError in some environments." +
+ GenericGlobalObjectInformationMsg,
+ WarningCategory.Deprecation,
+ currentMethodSym.get)
+ }
+ js.JSGlobalRef(value)
+ } else if (js.JSGlobalRef.ReservedJSIdentifierNames.contains(value)) {
+ reporter.error(pos,
+ s"Invalid $actionSimpleNoun in the global scope of the reserved identifier name `$value`." +
+ GenericGlobalObjectInformationMsg)
+ js.JSGlobalRef("erroneous")
+ } else {
+ reporter.error(pos,
+ s"$actionFull of the global scope whose name is not a valid JavaScript identifier is not allowed." +
+ GenericGlobalObjectInformationMsg)
+ js.JSGlobalRef("erroneous")
+ }
+
+ case _ =>
+ reporter.error(pos,
+ s"$actionFull of the global scope with a dynamic name is not allowed." +
+ GenericGlobalObjectInformationMsg)
+ js.JSGlobalRef("erroneous")
+ }
+ }
+
+ private def genAssignableField(sym: Symbol, qualifier: Tree)(
+ implicit pos: Position): (js.AssignLhs, Boolean) = {
+ def qual = genExpr(qualifier)
+
+ if (isNonNativeJSClass(sym.owner)) {
+ val f = if (isExposed(sym)) {
+ js.JSSelect(qual, genExpr(jsNameOf(sym)))
+ } else if (isAnonymousJSClass(sym.owner)) {
+ js.JSSelect(
+ js.JSSelect(qual, genPrivateFieldsSymbol()),
+ encodeFieldSymAsStringLiteral(sym))
+ } else {
+ js.JSPrivateSelect(qual, encodeFieldSym(sym))
+ }
+
+ (f, true)
+ } else if (jsInterop.topLevelExportsOf(sym).nonEmpty) {
+ val f = js.SelectStatic(encodeFieldSym(sym))(jstpe.AnyType)
+ (f, true)
+ } else if (jsInterop.staticExportsOf(sym).nonEmpty) {
+ val exportInfo = jsInterop.staticExportsOf(sym).head
+ val companionClass = patchedLinkedClassOfClass(sym.owner)
+ val f = js.JSSelect(
+ genPrimitiveJSClass(companionClass),
+ js.StringLiteral(exportInfo.jsName))
+
+ (f, true)
+ } else {
+ val fieldIdent = encodeFieldSym(sym)
+
+ /* #4370 Fields cannot have type NothingType, so we box them as
+ * scala.runtime.Nothing$ instead. They will be initialized with
+ * `null`, and any attempt to access them will throw a
+ * `ClassCastException` (generated in the unboxing code).
+ */
+ toIRType(sym.tpe) match {
+ case jstpe.NothingType =>
+ val f = js.Select(qual, fieldIdent)(
+ encodeClassType(RuntimeNothingClass))
+ (f, true)
+ case ftpe =>
+ val f = js.Select(qual, fieldIdent)(ftpe)
+ (f, false)
+ }
+ }
+ }
+
+ /** Generate access to a static field. */
+ private def genStaticField(sym: Symbol)(implicit pos: Position): js.Tree = {
+ /* Static fields are not accessed directly at the IR level, because there
+ * is no lazily-executed static initializer to make sure they are
+ * initialized. Instead, reading a static field must always go through a
+ * static getter with the same name as the field, 0 argument, and with
+ * the field's type as result type. The static getter is responsible for
+ * proper initialization, if required.
+ */
+ import scalaPrimitives._
+ import jsPrimitives._
+ if (isPrimitive(sym)) {
+ getPrimitive(sym) match {
+ case UNITVAL => js.Undefined()
+ }
+ } else {
+ val className = encodeClassName(sym.owner)
+ val method = encodeStaticFieldGetterSym(sym)
+ js.ApplyStatic(js.ApplyFlags.empty, className, method, Nil)(toIRType(sym.tpe))
+ }
+ }
+ }
+
+ private lazy val hasNewCollections =
+ !scala.util.Properties.versionNumberString.startsWith("2.12.")
+
+ /** Tests whether the given type represents a JavaScript type,
+ * i.e., whether it extends scala.scalajs.js.Any.
+ */
+ def isJSType(tpe: Type): Boolean =
+ isJSType(tpe.typeSymbol)
+
+ /** Tests whether the given type symbol represents a JavaScript type,
+ * i.e., whether it extends scala.scalajs.js.Any.
+ */
+ def isJSType(sym: Symbol): Boolean =
+ sym.hasAnnotation(JSTypeAnnot)
+
+ /** Tests whether the given class is a non-native JS class. */
+ def isNonNativeJSClass(sym: Symbol): Boolean =
+ !sym.isTrait && isJSType(sym) && !sym.hasAnnotation(JSNativeAnnotation)
+
+ def isNestedJSClass(sym: Symbol): Boolean =
+ sym.isLifted && !isStaticModule(sym.originalOwner) && isJSType(sym)
+
+ /** Tests whether the given class is a JS native class. */
+ private def isJSNativeClass(sym: Symbol): Boolean =
+ sym.hasAnnotation(JSNativeAnnotation)
+
+ /** Tests whether the given member is exposed, i.e., whether it was
+ * originally a public or protected member of a non-native JS class.
+ */
+ private def isExposed(sym: Symbol): Boolean = {
+ !sym.isBridge && {
+ if (sym.isLazy)
+ sym.isAccessor && sym.accessed.hasAnnotation(ExposedJSMemberAnnot)
+ else
+ sym.hasAnnotation(ExposedJSMemberAnnot)
+ }
+ }
+
+ /** Test whether `sym` is the symbol of a JS function definition */
+ private def isJSFunctionDef(sym: Symbol): Boolean = {
+ /* A JS function may only reach the backend if it originally was a lambda.
+ * This is difficult to check in the backend, so we use the fact that a
+ * non-lambda, concrete, non-native JS type, cannot implement a method named
+ * `apply`.
+ *
+ * Therefore, a class is a JS lambda iff it is anonymous, its direct
+ * super class is `js.Function`, and it contains an implementation of an
+ * `apply` method.
+ *
+ * Note that being anonymous implies being concrete and non-native, so we
+ * do not have to test that.
+ */
+ sym.isAnonymousClass &&
+ sym.superClass == JSFunctionClass &&
+ sym.info.decl(nme.apply).filter(JSCallingConvention.isCall(_)).exists
+ }
+
+ private def hasDefaultCtorArgsAndJSModule(classSym: Symbol): Boolean = {
+ /* Get the companion module class.
+ * For inner classes the sym.owner.companionModule can be broken,
+ * therefore companionModule is fetched at uncurryPhase.
+ */
+ val companionClass = enteringPhase(currentRun.uncurryPhase) {
+ classSym.companionModule
+ }.moduleClass
+
+ def hasDefaultParameters = {
+ val syms = classSym.info.members.filter(_.isClassConstructor)
+ enteringPhase(currentRun.uncurryPhase) {
+ syms.exists(_.paramss.iterator.flatten.exists(_.hasDefault))
+ }
+ }
+
+ isJSNativeClass(companionClass) && hasDefaultParameters
+ }
+
+ private def patchedLinkedClassOfClass(sym: Symbol): Symbol = {
+ /* Work around a bug of scalac with linkedClassOfClass where package
+ * objects are involved (the companion class would somehow exist twice
+ * in the scope, making an assertion fail in Symbol.suchThat).
+ * Basically this inlines linkedClassOfClass up to companionClass,
+ * then replaces the `suchThat` by a `filter` and `head`.
+ */
+ val flatOwnerInfo = {
+ // inline Symbol.flatOwnerInfo because it is protected
+ if (sym.needsFlatClasses)
+ sym.info
+ sym.owner.rawInfo
+ }
+ val result = flatOwnerInfo.decl(sym.name).filter(_ isCoDefinedWith sym)
+ if (!result.isOverloaded) result
+ else result.alternatives.head
+ }
+
+ private object DefaultParamInfo {
+ /** Is the symbol applicable to `DefaultParamInfo`?
+ *
+ * This is true iff it is a default accessor and it is not an value class
+ * `$extension` method. The latter condition is for #4583.
+ *
+ * Excluding all `$extension` methods is fine because `DefaultParamInfo`
+ * is used for JS default accessors, i.e., default accessors of
+ * `@js.native def`s or of `def`s in JS types. Those can never appear in
+ * an `AnyVal` class (as a class, it cannot contain `@js.native def`s, and
+ * as `AnyVal` it cannot also extend `js.Any`).
+ */
+ def isApplicable(sym: Symbol): Boolean =
+ sym.hasFlag(Flags.DEFAULTPARAM) && !sym.name.endsWith("$extension")
+ }
+
+ /** Info about a default param accessor.
+ *
+ * `DefaultParamInfo.isApplicable(sym)` must be true for this class to make
+ * sense.
+ */
+ private class DefaultParamInfo(sym: Symbol) {
+ private val methodName = nme.defaultGetterToMethod(sym.name)
+
+ def isForConstructor: Boolean = methodName == nme.CONSTRUCTOR
+
+ /** When `isForConstructor` is true, returns the owner of the attached
+ * constructor.
+ */
+ def constructorOwner: Symbol = patchedLinkedClassOfClass(sym.owner)
+
+ /** When `isForConstructor` is false, returns the method attached to the
+ * specified default accessor.
+ */
+ def attachedMethod: Symbol = {
+ // If there are overloads, we need to find the one that has default params.
+ val overloads = sym.owner.info.decl(methodName)
+ if (!overloads.isOverloaded) {
+ overloads
+ } else {
+ /* We should use `suchThat` here instead of `filter`+`head`. Normally,
+ * it can never happen that two overloads of a method both have default
+ * params. However, there is a loophole until Scala 2.12, with the
+ * `-Xsource:2.10` flag, which disables a check and allows that to
+ * happen in some circumstances. This is still tested as part of the
+ * partest test `pos/t8157-2.10.scala`. The use of `filter` instead of
+ * `suchThat` allows those situations not to crash, although that is
+ * mostly for (intense) backward compatibility purposes.
+ *
+ * This loophole can be use to construct a case of miscompilation where
+ * one of the overloads would be `@js.native` but the other not. We
+ * don't really care, though, as it needs some deep hackery to produce
+ * it.
+ */
+ overloads
+ .filter(_.paramss.exists(_.exists(_.hasFlag(Flags.DEFAULTPARAM))))
+ .alternatives
+ .head
+ }
+ }
+ }
+
+ private def isStringType(tpe: Type): Boolean =
+ tpe.typeSymbol == StringClass
+
+ protected lazy val isHijackedClass: Set[Symbol] = {
+ /* This list is a duplicate of ir.Definitions.HijackedClasses, but
+ * with global.Symbol's instead of IR encoded names as Strings.
+ * We also add java.lang.Void, which BoxedUnit "erases" to, and
+ * HackedStringClass if it is defined.
+ */
+ val s = Set[Symbol](
+ JavaLangVoidClass, BoxedUnitClass, BoxedBooleanClass,
+ BoxedCharacterClass, BoxedByteClass, BoxedShortClass, BoxedIntClass,
+ BoxedLongClass, BoxedFloatClass, BoxedDoubleClass, StringClass
+ )
+ if (HackedStringClass == NoSymbol) s
+ else s + HackedStringClass
+ }
+
+ private lazy val InlineAnnotationClass = requiredClass[scala.inline]
+ private lazy val NoinlineAnnotationClass = requiredClass[scala.noinline]
+
+ private lazy val ignoreNoinlineAnnotation: Set[Symbol] = {
+ val ccClass = getClassIfDefined("scala.util.continuations.ControlContext")
+
+ Set(
+ getMemberIfDefined(ListClass, nme.map),
+ getMemberIfDefined(ListClass, nme.flatMap),
+ getMemberIfDefined(ListClass, newTermName("collect")),
+ getMemberIfDefined(ccClass, nme.map),
+ getMemberIfDefined(ccClass, nme.flatMap)
+ ) - NoSymbol
+ }
+
+ private def isMaybeJavaScriptException(tpe: Type) =
+ JavaScriptExceptionClass isSubClass tpe.typeSymbol
+
+ def isStaticModule(sym: Symbol): Boolean =
+ sym.isModuleClass && !sym.isLifted
+
+ def isAnonymousJSClass(sym: Symbol): Boolean = {
+ /* sym.isAnonymousClass simply checks if
+ * `name containsName tpnme.ANON_CLASS_NAME`
+ * so after flatten (which we are) it identifies classes nested inside
+ * anonymous classes as anonymous (notably local classes, see #4278).
+ *
+ * Instead we recognize names generated for anonymous classes:
+ * tpnme.ANON_CLASS_NAME followed by $ where `n` is an integer.
+ */
+ isJSType(sym) && {
+ val name = sym.name
+ val i = name.lastIndexOf('$')
+
+ i > 0 &&
+ name.endsWith(tpnme.ANON_CLASS_NAME, i) &&
+ (i + 1 until name.length).forall(j => name.charAt(j).isDigit)
+ }
+ }
+
+ sealed abstract class MaybeGlobalScope
+
+ object MaybeGlobalScope {
+ case class NotGlobalScope(tree: js.Tree) extends MaybeGlobalScope
+
+ case class GlobalScope(pos: Position) extends MaybeGlobalScope
+ }
+
+ /** Marker object for undefined parameters in JavaScript semantic calls.
+ *
+ * To be used inside a `js.Transient` node.
+ */
+ case object UndefinedParam extends js.Transient.Value {
+ val tpe: jstpe.Type = jstpe.UndefType
+
+ def traverse(traverser: ir.Traversers.Traverser): Unit = ()
+
+ def transform(transformer: ir.Transformers.Transformer)(
+ implicit pos: ir.Position): js.Tree = {
+ js.Transient(this)
+ }
+
+ def printIR(out: ir.Printers.IRTreePrinter): Unit =
+ out.print("")
+ }
+}
+
+private object GenJSCode {
+ private val JSObjectClassName = ClassName("scala.scalajs.js.Object")
+ private val JavaScriptExceptionClassName = ClassName("scala.scalajs.js.JavaScriptException")
+
+ private val newSimpleMethodName = SimpleMethodName("new")
+
+ private val ObjectArgConstructorName =
+ MethodName.constructor(List(jswkn.ObjectRef))
+
+ private val lengthMethodName =
+ MethodName("length", Nil, jstpe.IntRef)
+ private val charAtMethodName =
+ MethodName("charAt", List(jstpe.IntRef), jstpe.CharRef)
+
+ private val getNameMethodName =
+ MethodName("getName", Nil, jstpe.ClassRef(jswkn.BoxedStringClass))
+ private val isPrimitiveMethodName =
+ MethodName("isPrimitive", Nil, jstpe.BooleanRef)
+ private val isInterfaceMethodName =
+ MethodName("isInterface", Nil, jstpe.BooleanRef)
+ private val isArrayMethodName =
+ MethodName("isArray", Nil, jstpe.BooleanRef)
+ private val getComponentTypeMethodName =
+ MethodName("getComponentType", Nil, jstpe.ClassRef(jswkn.ClassClass))
+ private val getSuperclassMethodName =
+ MethodName("getSuperclass", Nil, jstpe.ClassRef(jswkn.ClassClass))
+
+ private val isInstanceMethodName =
+ MethodName("isInstance", List(jstpe.ClassRef(jswkn.ObjectClass)), jstpe.BooleanRef)
+ private val isAssignableFromMethodName =
+ MethodName("isAssignableFrom", List(jstpe.ClassRef(jswkn.ClassClass)), jstpe.BooleanRef)
+ private val castMethodName =
+ MethodName("cast", List(jstpe.ClassRef(jswkn.ObjectClass)), jstpe.ClassRef(jswkn.ObjectClass))
+
+ private val arrayNewInstanceMethodName = {
+ MethodName("newInstance",
+ List(jstpe.ClassRef(jswkn.ClassClass), jstpe.IntRef),
+ jstpe.ClassRef(jswkn.ObjectClass))
+ }
+
+ private val thisOriginalName = OriginalName("this")
+
+ private object BlockOrAlone {
+ def unapply(tree: js.Tree): Some[(List[js.Tree], js.Tree)] = tree match {
+ case js.Block(trees) => Some((trees.init, trees.last))
+ case _ => Some((Nil, tree))
+ }
+ }
+
+ private object FirstInBlockOrAlone {
+ def unapply(tree: js.Tree): Some[(js.Tree, List[js.Tree])] = tree match {
+ case js.Block(trees) => Some((trees.head, trees.tail))
+ case _ => Some((tree, Nil))
+ }
+ }
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala
new file mode 100644
index 0000000000..bcac2098ea
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala
@@ -0,0 +1,1090 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.collection.{immutable, mutable}
+
+import scala.tools.nsc._
+import scala.math.PartialOrdering
+import scala.reflect.{ClassTag, classTag}
+import scala.reflect.internal.Flags
+
+import org.scalajs.ir
+import org.scalajs.ir.{Trees => js, Types => jstpe, WellKnownNames => jswkn}
+import org.scalajs.ir.Names.LocalName
+import org.scalajs.ir.OriginalName.NoOriginalName
+import org.scalajs.ir.Trees.OptimizerHints
+import org.scalajs.ir.Version.Unversioned
+
+import org.scalajs.nscplugin.util.ScopedVar
+import ScopedVar.withScopedVars
+
+/** Generation of exports for JavaScript
+ *
+ * @author Sébastien Doeraene
+ */
+trait GenJSExports[G <: Global with Singleton] extends SubComponent {
+ self: GenJSCode[G] =>
+
+ import global._
+ import jsAddons._
+ import definitions._
+ import jsDefinitions._
+ import jsInterop.{jsNameOf, JSName}
+
+ trait JSExportsPhase { this: JSCodePhase =>
+
+ /** Generates exported methods and properties for a class.
+ *
+ * @param classSym symbol of the class we export for
+ */
+ def genMemberExports(classSym: Symbol): List[js.JSMethodPropDef] = {
+ val allExports = classSym.info.members.filter(jsInterop.isExport(_))
+
+ val newlyDecldExports = if (classSym.superClass == NoSymbol) {
+ allExports
+ } else {
+ allExports.filterNot { sym =>
+ classSym.superClass.info.member(sym.name)
+ .filter(_.tpe =:= sym.tpe).exists
+ }
+ }
+
+ val newlyDecldExportNames =
+ newlyDecldExports.map(_.name.toTermName).toList.distinct
+
+ newlyDecldExportNames map { genMemberExport(classSym, _) }
+ }
+
+ def genJSClassDispatchers(classSym: Symbol,
+ dispatchMethodsNames: List[JSName]): List[js.JSMethodPropDef] = {
+ dispatchMethodsNames
+ .map(genJSClassDispatcher(classSym, _))
+ }
+
+ private sealed trait ExportKind
+
+ private object ExportKind {
+ case object Module extends ExportKind
+ case object JSClass extends ExportKind
+ case object Constructor extends ExportKind
+ case object Method extends ExportKind
+ case object Property extends ExportKind
+ case object Field extends ExportKind
+
+ def apply(sym: Symbol): ExportKind = {
+ if (isStaticModule(sym)) Module
+ else if (sym.isClass) JSClass
+ else if (sym.isConstructor) Constructor
+ else if (!sym.isMethod) Field
+ else if (jsInterop.isJSProperty(sym)) Property
+ else Method
+ }
+ }
+
+ private def checkSameKind(tups: List[(jsInterop.ExportInfo, Symbol)]): Option[ExportKind] = {
+ assert(tups.nonEmpty, "must have at least one export")
+
+ val firstSym = tups.head._2
+ val overallKind = ExportKind(firstSym)
+ var bad = false
+
+ for ((info, sym) <- tups.tail) {
+ val kind = ExportKind(sym)
+
+ if (kind != overallKind) {
+ bad = true
+ reporter.error(info.pos, "export overload conflicts with export of " +
+ s"$firstSym: they are of different types ($kind / $overallKind)")
+ }
+ }
+
+ if (bad) None
+ else Some(overallKind)
+ }
+
+ private def checkSingleField(tups: List[(jsInterop.ExportInfo, Symbol)]): Symbol = {
+ assert(tups.nonEmpty, "must have at least one export")
+
+ val firstSym = tups.head._2
+
+ for ((info, _) <- tups.tail) {
+ reporter.error(info.pos, "export overload conflicts with export of " +
+ s"$firstSym: a field may not share its exported name with another export")
+ }
+
+ firstSym
+ }
+
+ def genTopLevelExports(classSym: Symbol): List[js.TopLevelExportDef] = {
+ val exports = for {
+ sym <- List(classSym) ++ classSym.info.members
+ info <- jsInterop.topLevelExportsOf(sym)
+ } yield {
+ (info, sym)
+ }
+
+ for {
+ (info, tups) <- stableGroupByWithoutHashCode(exports)(_._1)
+ kind <- checkSameKind(tups)
+ } yield {
+ import ExportKind._
+
+ implicit val pos = info.pos
+
+ kind match {
+ case Module =>
+ js.TopLevelModuleExportDef(info.moduleID, info.jsName)
+
+ case JSClass =>
+ assert(isNonNativeJSClass(classSym), "found export on non-JS class")
+ js.TopLevelJSClassExportDef(info.moduleID, info.jsName)
+
+ case Constructor | Method =>
+ val methodDef = withNewLocalNameScope {
+ genExportMethod(tups.map(_._2), JSName.Literal(info.jsName), static = true,
+ allowCallsiteInlineSingle = false)
+ }
+
+ js.TopLevelMethodExportDef(info.moduleID, methodDef)
+
+ case Property =>
+ throw new AssertionError("found top-level exported property")
+
+ case Field =>
+ val sym = checkSingleField(tups)
+ js.TopLevelFieldExportDef(info.moduleID, info.jsName, encodeFieldSym(sym))
+ }
+ }
+ }
+
+ def genStaticExports(classSym: Symbol): (List[js.JSFieldDef], List[js.JSMethodPropDef]) = {
+ val exports = (for {
+ sym <- classSym.info.members
+ info <- jsInterop.staticExportsOf(sym)
+ } yield {
+ (info, sym)
+ }).toList
+
+ val fields = List.newBuilder[js.JSFieldDef]
+ val methodProps = List.newBuilder[js.JSMethodPropDef]
+
+ for {
+ (info, tups) <- stableGroupByWithoutHashCode(exports)(_._1)
+ kind <- checkSameKind(tups)
+ } {
+ def alts = tups.map(_._2)
+
+ implicit val pos = info.pos
+
+ import ExportKind._
+
+ kind match {
+ case Method =>
+ methodProps += genMemberExportOrDispatcher(
+ JSName.Literal(info.jsName), isProp = false, alts, static = true,
+ allowCallsiteInlineSingle = false)
+
+ case Property =>
+ methodProps += genMemberExportOrDispatcher(
+ JSName.Literal(info.jsName), isProp = true, alts, static = true,
+ allowCallsiteInlineSingle = false)
+
+ case Field =>
+ val sym = checkSingleField(tups)
+
+ // static fields must always be mutable
+ val flags = js.MemberFlags.empty
+ .withNamespace(js.MemberNamespace.PublicStatic)
+ .withMutable(true)
+ val name = js.StringLiteral(info.jsName)
+ val irTpe = genExposedFieldIRType(sym)
+ fields += js.JSFieldDef(flags, name, irTpe)
+
+ case kind =>
+ throw new AssertionError(s"unexpected static export kind: $kind")
+ }
+ }
+
+ (fields.result(), methodProps.result())
+ }
+
+ private def genMemberExport(classSym: Symbol, name: TermName): js.JSMethodPropDef = {
+ /* This used to be `.member(name)`, but it caused #3538, since we were
+ * sometimes selecting mixin forwarders, whose type history does not go
+ * far enough back in time to see varargs. We now explicitly exclude
+ * mixed-in members in addition to bridge methods (the latter are always
+ * excluded by `.member(name)`).
+ */
+ val alts = classSym.info.memberBasedOnName(name,
+ excludedFlags = Flags.BRIDGE | Flags.MIXEDIN).alternatives
+
+ assert(!alts.isEmpty,
+ s"Ended up with no alternatives for ${classSym.fullName}::$name. " +
+ s"Original set was ${alts} with types ${alts.map(_.tpe)}")
+
+ val (jsName, isProp) = jsInterop.jsExportInfo(name)
+
+ // Check if we have a conflicting export of the other kind
+ val conflicting =
+ classSym.info.member(jsInterop.scalaExportName(jsName, !isProp))
+
+ if (conflicting != NoSymbol) {
+ val kind = if (isProp) "property" else "method"
+ val alts = conflicting.alternatives
+
+ reporter.error(alts.head.pos,
+ s"Exported $kind $jsName conflicts with ${alts.head.fullName}")
+ }
+
+ genMemberExportOrDispatcher(JSName.Literal(jsName), isProp, alts,
+ static = false, allowCallsiteInlineSingle = false)
+ }
+
+ private def genJSClassDispatcher(classSym: Symbol, name: JSName): js.JSMethodPropDef = {
+ val alts = classSym.info.members.toList.filter { sym =>
+ sym.isMethod &&
+ !sym.isBridge &&
+ /* #3939: Object is not a "real" superclass of JS types.
+ * as such, its methods do not participate in overload resolution.
+ * An exception is toString, which is handled specially in
+ * genExportMethod.
+ */
+ sym.owner != ObjectClass &&
+ jsNameOf(sym) == name
+ }
+
+ assert(!alts.isEmpty,
+ s"Ended up with no alternatives for ${classSym.fullName}::$name.")
+
+ val (propSyms, methodSyms) = alts.partition(jsInterop.isJSProperty(_))
+ val isProp = propSyms.nonEmpty
+
+ if (isProp && methodSyms.nonEmpty) {
+ reporter.error(alts.head.pos,
+ s"Conflicting properties and methods for ${classSym.fullName}::$name.")
+ implicit val pos = alts.head.pos
+ js.JSPropertyDef(js.MemberFlags.empty, genExpr(name), None, None)(Unversioned)
+ } else {
+ genMemberExportOrDispatcher(name, isProp, alts, static = false,
+ allowCallsiteInlineSingle = true)
+ }
+ }
+
+ def genMemberExportOrDispatcher(jsName: JSName, isProp: Boolean,
+ alts: List[Symbol], static: Boolean,
+ allowCallsiteInlineSingle: Boolean): js.JSMethodPropDef = {
+ withNewLocalNameScope {
+ if (isProp)
+ genExportProperty(alts, jsName, static, allowCallsiteInlineSingle)
+ else
+ genExportMethod(alts, jsName, static, allowCallsiteInlineSingle)
+ }
+ }
+
+ private def genExportProperty(alts: List[Symbol], jsName: JSName,
+ static: Boolean, allowCallsiteInlineSingle: Boolean): js.JSPropertyDef = {
+ assert(!alts.isEmpty,
+ s"genExportProperty with empty alternatives for $jsName")
+
+ implicit val pos = alts.head.pos
+
+ val namespace =
+ if (static) js.MemberNamespace.PublicStatic
+ else js.MemberNamespace.Public
+ val flags = js.MemberFlags.empty.withNamespace(namespace)
+
+ // Separate getters and setters. Somehow isJSGetter doesn't work here. Hence
+ // we just check the parameter list length.
+ val (getter, setters) = alts.partition(_.tpe.params.isEmpty)
+
+ // We can have at most one getter
+ if (getter.size > 1)
+ reportCannotDisambiguateError(jsName, alts)
+
+ val getterBody = getter.headOption.map { getterSym =>
+ genApplyForSym(new FormalArgsRegistry(0, false), getterSym, static,
+ inline = allowCallsiteInlineSingle)
+ }
+
+ val setterArgAndBody = {
+ if (setters.isEmpty) {
+ None
+ } else {
+ val formalArgsRegistry = new FormalArgsRegistry(1, false)
+ val (List(arg), None) = formalArgsRegistry.genFormalArgs()
+
+ val body = {
+ if (setters.size == 1) {
+ genApplyForSym(formalArgsRegistry, setters.head, static,
+ inline = allowCallsiteInlineSingle)
+ } else {
+ genOverloadDispatchSameArgc(jsName, formalArgsRegistry,
+ alts = setters.map(new ExportedSymbol(_, static)), jstpe.AnyType,
+ paramIndex = 0)
+ }
+ }
+
+ Some((arg, body))
+ }
+ }
+
+ js.JSPropertyDef(flags, genExpr(jsName), getterBody, setterArgAndBody)(Unversioned)
+ }
+
+ /** generates the exporter function (i.e. exporter for non-properties) for
+ * a given name */
+ private def genExportMethod(alts0: List[Symbol], jsName: JSName,
+ static: Boolean, allowCallsiteInlineSingle: Boolean): js.JSMethodDef = {
+ assert(alts0.nonEmpty,
+ "need at least one alternative to generate exporter method")
+
+ implicit val pos = alts0.head.pos
+
+ val namespace =
+ if (static) js.MemberNamespace.PublicStatic
+ else js.MemberNamespace.Public
+ val flags = js.MemberFlags.empty.withNamespace(namespace)
+
+ val alts = {
+ // toString() is always exported. We might need to add it here
+ // to get correct overloading.
+ val needsToString =
+ jsName == JSName.Literal("toString") && alts0.forall(_.tpe.params.nonEmpty)
+
+ if (needsToString)
+ Object_toString :: alts0
+ else
+ alts0
+ }
+
+ val overloads = alts.map(new ExportedSymbol(_, static))
+
+ val (formalArgs, restParam, body) = {
+ if (overloads.size == 1) {
+ val trg = overloads.head
+ val minArgc = trg.params.lastIndexWhere(p => !p.hasDefault && !p.repeated) + 1
+ val formalArgsRegistry = new FormalArgsRegistry(minArgc,
+ needsRestParam = trg.params.size > minArgc)
+ val body = genApplyForSym(formalArgsRegistry, trg.sym, static,
+ inline = allowCallsiteInlineSingle)
+ val (formalArgs, restParam) = formalArgsRegistry.genFormalArgs()
+ (formalArgs, restParam, body)
+ } else {
+ genOverloadDispatch(jsName, overloads, jstpe.AnyType)
+ }
+ }
+
+ js.JSMethodDef(flags, genExpr(jsName), formalArgs, restParam, body)(
+ OptimizerHints.empty, Unversioned)
+ }
+
+ def genOverloadDispatch(jsName: JSName, alts: List[Exported], tpe: jstpe.Type)(
+ implicit pos: Position): (List[js.ParamDef], Option[js.ParamDef], js.Tree) = {
+ // Factor out methods with variable argument lists. Note that they can
+ // only be at the end of the lists as enforced by PrepJSExports
+ val (varArgMeths, normalMeths) = alts.partition(_.hasRepeatedParam)
+
+ // Highest non-repeated argument count
+ val maxArgc = (
+ // We have argc - 1, since a repeated parameter list may also be empty
+ // (unlike a normal parameter)
+ varArgMeths.map(_.params.size - 1) ++
+ normalMeths.map(_.params.size)
+ ).max
+
+ // Calculates possible arg counts for normal method
+ def argCounts(ex: Exported) = {
+ val params = ex.params
+ // Find default param
+ val dParam = params.indexWhere(_.hasDefault)
+ if (dParam == -1) Seq(params.size)
+ else dParam to params.size
+ }
+
+ // Generate tuples (argc, method)
+ val methodArgCounts = {
+ // Normal methods
+ for {
+ method <- normalMeths
+ argc <- argCounts(method)
+ } yield (argc, method)
+ } ++ {
+ // Repeated parameter methods
+ for {
+ method <- varArgMeths
+ argc <- method.params.size - 1 to maxArgc
+ } yield (argc, method)
+ }
+
+ /** Like groupBy, but returns a sorted List instead of an unordered Map. */
+ def sortedGroupBy[A, K, O](xs: List[A])(grouper: A => K)(
+ sorter: ((K, List[A])) => O)(implicit ord: Ordering[O]): List[(K, List[A])] = {
+ xs.groupBy(grouper).toList.sortBy(sorter)
+ }
+
+ /* Create tuples: (argCount, methods).
+ * Methods may appear multiple times.
+ *
+ * The method lists preserve the order out of `methodArgCounts`, so if
+ * two such lists contain the same set of methods, they are equal.
+ *
+ * The resulting list is sorted by argCount. This is both for stability
+ * and because we then rely on the fact that the head is the minimum.
+ */
+ val methodByArgCount: List[(Int, List[Exported])] = {
+ sortedGroupBy(methodArgCounts)(_._1)(_._1) // sort by the Int
+ .map(kv => kv._1 -> kv._2.map(_._2)) // preserves the relative order of the Exported's
+ }
+
+ // Create the formal args registry
+ val minArgc = methodByArgCount.head._1 // it is sorted by argCount, so the head is the minimum
+ val hasVarArg = varArgMeths.nonEmpty
+ val needsRestParam = maxArgc != minArgc || hasVarArg
+ val formalArgsRegistry = new FormalArgsRegistry(minArgc, needsRestParam)
+
+ // List of formal parameters
+ val (formalArgs, restParam) = formalArgsRegistry.genFormalArgs()
+
+ /* Create tuples: (methods, argCounts). These will be the cases we generate.
+ *
+ * Within each element, the argCounts are sorted. (This sort order is
+ * inherited from the sort order of `methodByArgCount`.)
+ *
+ * For stability, the list as a whole is sorted by the minimum (head) of
+ * argCounts.
+ */
+ val caseDefinitions: List[(List[Exported], List[Int])] = {
+ sortedGroupBy(methodByArgCount)(_._2)(_._2.head._1) // sort by the minimum of the Ints
+ .map(kv => kv._1 -> kv._2.map(_._1)) // the Ints are still sorted from `methodByArgCount`
+ }
+
+ // Verify stuff about caseDefinitions
+ assert({
+ val argcs = caseDefinitions.map(_._2).flatten
+ argcs == argcs.distinct &&
+ argcs.forall(_ <= maxArgc)
+ }, "every argc should appear only once and be lower than max")
+
+ /* We will avoid generating cases where the set of methods is exactly the
+ * the set of varargs methods. Since all the `Exported` in `alts`, and
+ * hence in `varArgMeths` and `methods`, are distinct, we can do
+ * something faster than converting both sides to sets.
+ */
+ def isSameAsVarArgMethods(methods: List[Exported]): Boolean =
+ methods.size == varArgMeths.size && methods.forall(varArgMeths.contains(_))
+
+ // Generate a case block for each (methods, argCounts) tuple
+ val cases: List[(List[js.IntLiteral], js.Tree)] = for {
+ (methods, argcs) <- caseDefinitions
+ if methods.nonEmpty && argcs.nonEmpty && !isSameAsVarArgMethods(methods)
+ } yield {
+ val argcAlternatives = argcs.map(argc => js.IntLiteral(argc - minArgc))
+
+ // body of case to disambiguate methods with current count
+ val maxUsableArgc = argcs.head // i.e., the *minimum* of the argcs here
+ val caseBody = genOverloadDispatchSameArgc(jsName, formalArgsRegistry,
+ methods, tpe, paramIndex = 0, Some(maxUsableArgc))
+
+ (argcAlternatives, caseBody)
+ }
+
+ def defaultCase = {
+ if (!hasVarArg) {
+ genThrowTypeError()
+ } else {
+ genOverloadDispatchSameArgc(jsName, formalArgsRegistry, varArgMeths,
+ tpe, paramIndex = 0)
+ }
+ }
+
+ val body = {
+ if (cases.isEmpty)
+ defaultCase
+ else if (cases.size == 1 && !hasVarArg)
+ cases.head._2
+ else {
+ assert(needsRestParam,
+ "Trying to read rest param length but needsRestParam is false")
+ val restArgRef = formalArgsRegistry.genRestArgRef()
+ js.Match(
+ js.AsInstanceOf(js.JSSelect(restArgRef, js.StringLiteral("length")), jstpe.IntType),
+ cases, defaultCase)(tpe)
+ }
+ }
+
+ (formalArgs, restParam, body)
+ }
+
+ /**
+ * Resolve method calls to [[alts]] while assuming they have the same
+ * parameter count.
+ * @param minArgc The minimum number of arguments that must be given
+ * @param alts Alternative methods
+ * @param paramIndex Index where to start disambiguation
+ * @param maxArgc only use that many arguments
+ */
+ private def genOverloadDispatchSameArgc(jsName: JSName,
+ formalArgsRegistry: FormalArgsRegistry, alts: List[Exported],
+ tpe: jstpe.Type, paramIndex: Int, maxArgc: Option[Int] = None): js.Tree = {
+
+ implicit val pos = alts.head.sym.pos
+
+ if (alts.size == 1) {
+ alts.head.genBody(formalArgsRegistry)
+ } else if (maxArgc.exists(_ <= paramIndex) ||
+ !alts.exists(_.params.size > paramIndex)) {
+ // We reach here in three cases:
+ // 1. The parameter list has been exhausted
+ // 2. The optional argument count restriction has triggered
+ // 3. We only have (more than once) repeated parameters left
+ // Therefore, we should fail
+ reportCannotDisambiguateError(jsName, alts.map(_.sym))
+ js.Undefined()
+ } else {
+ val altsByTypeTest = stableGroupByWithoutHashCode(alts) { exported =>
+ typeTestForTpe(exported.exportArgTypeAt(paramIndex))
+ }
+
+ if (altsByTypeTest.size == 1) {
+ // Testing this parameter is not doing any us good
+ genOverloadDispatchSameArgc(jsName, formalArgsRegistry, alts, tpe,
+ paramIndex + 1, maxArgc)
+ } else {
+ // Sort them so that, e.g., isInstanceOf[String]
+ // comes before isInstanceOf[Object]
+ val sortedAltsByTypeTest = topoSortDistinctsWith(altsByTypeTest) { (lhs, rhs) =>
+ (lhs._1, rhs._1) match {
+ // NoTypeTest is always last
+ case (_, NoTypeTest) => true
+ case (NoTypeTest, _) => false
+
+ case (PrimitiveTypeTest(_, rank1), PrimitiveTypeTest(_, rank2)) =>
+ rank1 <= rank2
+
+ case (InstanceOfTypeTest(t1), InstanceOfTypeTest(t2)) =>
+ t1 <:< t2
+
+ case (_: PrimitiveTypeTest, _: InstanceOfTypeTest) => true
+ case (_: InstanceOfTypeTest, _: PrimitiveTypeTest) => false
+ }
+ }
+
+ val defaultCase = genThrowTypeError()
+
+ sortedAltsByTypeTest.foldRight[js.Tree](defaultCase) { (elem, elsep) =>
+ val (typeTest, subAlts) = elem
+ implicit val pos = subAlts.head.sym.pos
+
+ val paramRef = formalArgsRegistry.genArgRef(paramIndex)
+ val genSubAlts = genOverloadDispatchSameArgc(jsName, formalArgsRegistry,
+ subAlts, tpe, paramIndex + 1, maxArgc)
+
+ def hasDefaultParam = subAlts.exists { exported =>
+ val params = exported.params
+ params.size > paramIndex &&
+ params(paramIndex).hasDefault
+ }
+
+ val optCond = typeTest match {
+ case PrimitiveTypeTest(tpe, _) =>
+ Some(js.IsInstanceOf(paramRef, tpe))
+
+ case InstanceOfTypeTest(tpe) =>
+ Some(genIsInstanceOf(paramRef, tpe))
+
+ case NoTypeTest =>
+ None
+ }
+
+ optCond.fold[js.Tree] {
+ genSubAlts // note: elsep is discarded, obviously
+ } { cond =>
+ val condOrUndef = if (!hasDefaultParam) cond else {
+ js.If(cond, js.BooleanLiteral(true),
+ js.BinaryOp(js.BinaryOp.===, paramRef, js.Undefined()))(
+ jstpe.BooleanType)
+ }
+ js.If(condOrUndef, genSubAlts, elsep)(tpe)
+ }
+ }
+ }
+ }
+ }
+
+ private def reportCannotDisambiguateError(jsName: JSName,
+ alts: List[Symbol]): Unit = {
+ val currentClass = currentClassSym.get
+
+ /* Find a position that is in the current class for decent error reporting.
+ * If there are more than one, always use the "highest" one (i.e., the
+ * one coming last in the source text) so that we reliably display the
+ * same error in all compilers.
+ */
+ val validPositions = alts.collect {
+ case alt if alt.owner == currentClass => alt.pos
+ }
+ val pos =
+ if (validPositions.isEmpty) currentClass.pos
+ else validPositions.maxBy(_.point)
+
+ val kind =
+ if (jsInterop.isJSGetter(alts.head)) "getter"
+ else if (jsInterop.isJSSetter(alts.head)) "setter"
+ else "method"
+
+ val fullKind =
+ if (isNonNativeJSClass(currentClass)) kind
+ else "exported " + kind
+
+ val displayName = jsName.displayName
+ val altsTypesInfo = alts.map(_.tpe.toString).sorted.mkString("\n ")
+
+ reporter.error(pos,
+ s"Cannot disambiguate overloads for $fullKind $displayName with types\n" +
+ s" $altsTypesInfo")
+ }
+
+ /**
+ * Generate a call to the method [[sym]] while using the formalArguments
+ * and potentially the argument array. Also inserts default parameters if
+ * required.
+ */
+ private def genApplyForSym(formalArgsRegistry: FormalArgsRegistry,
+ sym: Symbol, static: Boolean, inline: Boolean): js.Tree = {
+ if (isNonNativeJSClass(currentClassSym) &&
+ sym.owner != currentClassSym.get) {
+ assert(!static, s"nonsensical JS super call in static export of $sym")
+ genApplyForSymJSSuperCall(formalArgsRegistry, sym)
+ } else {
+ genApplyForSymNonJSSuperCall(formalArgsRegistry, sym, static, inline)
+ }
+ }
+
+ private def genApplyForSymJSSuperCall(
+ formalArgsRegistry: FormalArgsRegistry, sym: Symbol): js.Tree = {
+ implicit val pos = sym.pos
+
+ assert(!sym.isClassConstructor,
+ "Trying to genApplyForSymJSSuperCall for the constructor " +
+ sym.fullName)
+
+ val allArgs = formalArgsRegistry.genAllArgsRefsForForwarder()
+
+ val superClass = {
+ val superClassSym = currentClassSym.superClass
+ if (isNestedJSClass(superClassSym)) {
+ js.VarRef(JSSuperClassParamName)(jstpe.AnyType)
+ } else {
+ js.LoadJSConstructor(encodeClassName(superClassSym))
+ }
+ }
+
+ val receiver = js.This()(currentThisType)
+ val nameString = genExpr(jsNameOf(sym))
+
+ if (jsInterop.isJSGetter(sym)) {
+ assert(allArgs.isEmpty,
+ s"getter symbol $sym does not have a getter signature")
+ js.JSSuperSelect(superClass, receiver, nameString)
+ } else if (jsInterop.isJSSetter(sym)) {
+ assert(allArgs.size == 1 && allArgs.head.isInstanceOf[js.Tree],
+ s"setter symbol $sym does not have a setter signature")
+ js.Assign(js.JSSuperSelect(superClass, receiver, nameString),
+ allArgs.head.asInstanceOf[js.Tree])
+ } else {
+ js.JSSuperMethodCall(superClass, receiver, nameString, allArgs)
+ }
+ }
+
+ private def genApplyForSymNonJSSuperCall(
+ formalArgsRegistry: FormalArgsRegistry, sym: Symbol,
+ static: Boolean, inline: Boolean): js.Tree = {
+ implicit val pos = sym.pos
+
+ val varDefs = new mutable.ListBuffer[js.VarDef]
+
+ for ((param, i) <- jsParamInfos(sym).zipWithIndex) {
+ val rhs = genScalaArg(sym, i, formalArgsRegistry, param, static, captures = Nil)(
+ prevArgsCount => varDefs.take(prevArgsCount).toList.map(_.ref))
+
+ varDefs += js.VarDef(freshLocalIdent("prep" + i), NoOriginalName,
+ rhs.tpe, mutable = false, rhs)
+ }
+
+ val builtVarDefs = varDefs.result()
+
+ val jsResult = genResult(sym, builtVarDefs.map(_.ref), static, inline)
+
+ js.Block(builtVarDefs :+ jsResult)
+ }
+
+ /** Generates a Scala argument from dispatched JavaScript arguments
+ * (unboxing and default parameter handling).
+ */
+ def genScalaArg(methodSym: Symbol, paramIndex: Int,
+ formalArgsRegistry: FormalArgsRegistry, param: JSParamInfo,
+ static: Boolean, captures: List[js.Tree])(
+ previousArgsValues: Int => List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+
+ if (param.repeated) {
+ genJSArrayToVarArgs(formalArgsRegistry.genVarargRef(paramIndex))
+ } else {
+ val jsArg = formalArgsRegistry.genArgRef(paramIndex)
+ // Unboxed argument (if it is defined)
+ val unboxedArg = fromAny(jsArg, param.tpe)
+
+ if (param.hasDefault) {
+ // If argument is undefined and there is a default getter, call it
+ val default = genCallDefaultGetter(methodSym, paramIndex,
+ param.sym.pos, static, captures)(previousArgsValues)
+ js.If(js.BinaryOp(js.BinaryOp.===, jsArg, js.Undefined()),
+ default, unboxedArg)(unboxedArg.tpe)
+ } else {
+ // Otherwise, it is always the unboxed argument
+ unboxedArg
+ }
+ }
+ }
+
+ def genCallDefaultGetter(sym: Symbol, paramIndex: Int,
+ paramPos: Position, static: Boolean, captures: List[js.Tree])(
+ previousArgsValues: Int => List[js.Tree])(
+ implicit pos: Position): js.Tree = {
+
+ val owner = sym.owner
+ val isNested = owner.isLifted && !isStaticModule(owner.originalOwner)
+
+ val (trgSym, trgTree) = {
+ if (!sym.isClassConstructor && !static) {
+ /* Default getter is on the receiver.
+ *
+ * Since we only use this method for class internal exports
+ * dispatchers, we know the default getter is on `this`. This applies
+ * to both top-level and nested classes.
+ */
+ (owner, js.This()(currentThisType))
+ } else if (isNested) {
+ assert(captures.size == 1,
+ s"expected exactly one capture got $captures ($sym at $pos)")
+
+ /* Find the module accessor.
+ *
+ * #4465: If owner is a nested class, the linked class is *not* a
+ * module value, but another class. In this case we need to call the
+ * module accessor on the enclosing class to retrieve this.
+ *
+ * #4526: If the companion module is private, linkedClassOfClass
+ * does not work (because the module name is prefixed with the full
+ * path). So we find the module accessor first and take its result
+ * type to be the companion module type.
+ */
+ val outer = owner.originalOwner
+
+ val modAccessor = {
+ val name = enteringPhase(currentRun.typerPhase) {
+ owner.unexpandedName.toTermName
+ }
+
+ outer.info.members.find { sym =>
+ sym.isModule && sym.unexpandedName == name
+ }.getOrElse {
+ throw new AssertionError(
+ s"couldn't find module accessor for ${owner.fullName} at $pos")
+ }
+ }
+
+ val receiver = captures.head
+
+ val trgSym = modAccessor.tpe.resultType.typeSymbol
+
+ val trgTree = if (isJSType(outer)) {
+ genApplyJSClassMethod(receiver, modAccessor, Nil)
+ } else {
+ genApplyMethodMaybeStatically(receiver, modAccessor, Nil)
+ }
+
+ (trgSym, trgTree)
+ } else if (sym.isClassConstructor) {
+ assert(captures.isEmpty, "expected empty captures")
+
+ /* Get the companion module class.
+ * For classes nested inside modules the sym.owner.companionModule
+ * can be broken, therefore companionModule is fetched at
+ * uncurryPhase.
+ */
+ val trgSym = enteringPhase(currentRun.uncurryPhase) {
+ owner.linkedClassOfClass
+ }
+ (trgSym, genLoadModule(trgSym))
+ } else {
+ assert(static, "expected static")
+ assert(captures.isEmpty, "expected empty captures")
+ (owner, genLoadModule(owner))
+ }
+ }
+
+ val defaultGetter = trgSym.tpe.member(
+ nme.defaultGetterName(sym.name, paramIndex + 1))
+
+ assert(defaultGetter.exists,
+ s"need default getter for method ${sym.fullName}")
+ assert(!defaultGetter.isOverloaded,
+ s"found overloaded default getter $defaultGetter")
+
+ // Pass previous arguments to defaultGetter
+ val defaultGetterArgs = previousArgsValues(defaultGetter.tpe.params.size)
+
+ val callGetter = if (isJSType(trgSym)) {
+ if (isNonNativeJSClass(defaultGetter.owner)) {
+ if (defaultGetter.hasAnnotation(JSOptionalAnnotation))
+ js.Undefined()
+ else
+ genApplyJSClassMethod(trgTree, defaultGetter, defaultGetterArgs)
+ } else if (defaultGetter.owner == trgSym) {
+ /* We get here if a non-native constructor has a native companion.
+ * This is reported on a per-class level.
+ */
+ assert(sym.isClassConstructor,
+ s"got non-constructor method $sym with default method in JS native companion")
+ js.Undefined()
+ } else {
+ reporter.error(paramPos, "When overriding a native method " +
+ "with default arguments, the overriding method must " +
+ "explicitly repeat the default arguments.")
+ js.Undefined()
+ }
+ } else {
+ genApplyMethod(trgTree, defaultGetter, defaultGetterArgs)
+ }
+
+ // #4684 If the getter returns void, we must "box" it by returning undefined
+ if (callGetter.tpe == jstpe.VoidType)
+ js.Block(callGetter, js.Undefined())
+ else
+ callGetter
+ }
+
+ /** Generate the final forwarding call to the exported method. */
+ private def genResult(sym: Symbol, args: List[js.Tree],
+ static: Boolean, inline: Boolean)(implicit pos: Position): js.Tree = {
+ def receiver = {
+ if (static)
+ genLoadModule(sym.owner)
+ else
+ js.This()(currentThisType)
+ }
+
+ if (isNonNativeJSClass(currentClassSym)) {
+ assert(sym.owner == currentClassSym.get, sym.fullName)
+ ensureResultBoxed(genApplyJSClassMethod(receiver, sym, args, inline = inline), sym)
+ } else {
+ if (sym.isClassConstructor)
+ genNew(currentClassSym, sym, args)
+ else if (sym.isPrivate)
+ ensureResultBoxed(genApplyMethodStatically(receiver, sym, args, inline = inline), sym)
+ else
+ ensureResultBoxed(genApplyMethod(receiver, sym, args, inline = inline), sym)
+ }
+ }
+
+ // Note: GenJSCode creates an anonymous subclass of Exported for JS class constructors.
+ abstract class Exported(val sym: Symbol,
+ // Parameters participating in overload resolution.
+ val params: immutable.IndexedSeq[JSParamInfo]) {
+
+ assert(!params.exists(_.capture), "illegal capture params in Exported")
+
+ final def exportArgTypeAt(paramIndex: Int): Type = {
+ if (paramIndex < params.length) {
+ params(paramIndex).tpe
+ } else {
+ assert(hasRepeatedParam,
+ s"$sym does not have varargs nor enough params for $paramIndex")
+ params.last.tpe
+ }
+ }
+
+ def genBody(formalArgsRegistry: FormalArgsRegistry): js.Tree
+
+ lazy val hasRepeatedParam = params.lastOption.exists(_.repeated)
+ }
+
+ private class ExportedSymbol(sym: Symbol, static: Boolean)
+ extends Exported(sym, jsParamInfos(sym).toIndexedSeq) {
+ def genBody(formalArgsRegistry: FormalArgsRegistry): js.Tree =
+ genApplyForSym(formalArgsRegistry, sym, static, inline = false)
+ }
+ }
+
+ private sealed abstract class RTTypeTest
+
+ private case class PrimitiveTypeTest(tpe: jstpe.PrimType, rank: Int)
+ extends RTTypeTest
+
+ // scalastyle:off equals.hash.code
+ private case class InstanceOfTypeTest(tpe: Type) extends RTTypeTest {
+ override def equals(that: Any): Boolean = {
+ that match {
+ case InstanceOfTypeTest(thatTpe) => tpe =:= thatTpe
+ case _ => false
+ }
+ }
+ }
+ // scalastyle:on equals.hash.code
+
+ private case object NoTypeTest extends RTTypeTest
+
+ // Very simple O(n²) topological sort for elements assumed to be distinct
+ private def topoSortDistinctsWith[A <: AnyRef](coll: List[A])(
+ lteq: (A, A) => Boolean): List[A] = {
+ @scala.annotation.tailrec
+ def loop(coll: List[A], acc: List[A]): List[A] = {
+ if (coll.isEmpty) acc
+ else if (coll.tail.isEmpty) coll.head :: acc
+ else {
+ val (lhs, rhs) = coll.span(x => !coll.forall(
+ y => (x eq y) || !lteq(x, y)))
+ assert(!rhs.isEmpty, s"cycle while ordering $coll")
+ loop(lhs ::: rhs.tail, rhs.head :: acc)
+ }
+ }
+
+ loop(coll, Nil)
+ }
+
+ private def typeTestForTpe(tpe: Type): RTTypeTest = {
+ tpe match {
+ case tpe: ErasedValueType =>
+ InstanceOfTypeTest(tpe.valueClazz.typeConstructor)
+
+ case _ =>
+ (toIRType(tpe): @unchecked) match {
+ case jstpe.AnyType | jstpe.AnyNotNullType => NoTypeTest
+
+ case jstpe.VoidType => PrimitiveTypeTest(jstpe.UndefType, 0)
+ case jstpe.BooleanType => PrimitiveTypeTest(jstpe.BooleanType, 1)
+ case jstpe.CharType => PrimitiveTypeTest(jstpe.CharType, 2)
+ case jstpe.ByteType => PrimitiveTypeTest(jstpe.ByteType, 3)
+ case jstpe.ShortType => PrimitiveTypeTest(jstpe.ShortType, 4)
+ case jstpe.IntType => PrimitiveTypeTest(jstpe.IntType, 5)
+ case jstpe.LongType => PrimitiveTypeTest(jstpe.LongType, 6)
+ case jstpe.FloatType => PrimitiveTypeTest(jstpe.FloatType, 7)
+ case jstpe.DoubleType => PrimitiveTypeTest(jstpe.DoubleType, 8)
+
+ case jstpe.ClassType(jswkn.BoxedUnitClass, _) => PrimitiveTypeTest(jstpe.UndefType, 0)
+ case jstpe.ClassType(jswkn.BoxedStringClass, _) => PrimitiveTypeTest(jstpe.StringType, 9)
+ case jstpe.ClassType(_, _) => InstanceOfTypeTest(tpe)
+
+ case jstpe.ArrayType(_, _) => InstanceOfTypeTest(tpe)
+ }
+ }
+ }
+
+ /** Stable group-by that does not rely on hashCode(), only equals() - O(n²).
+ *
+ * In addition to preserving the relative order of elements in the value
+ * lists (like `groupBy`), this stable group-by also preserves the relative
+ * order of they keys, by their first appearance in the collection.
+ */
+ private def stableGroupByWithoutHashCode[A, B](
+ coll: List[A])(f: A => B): List[(B, List[A])] = {
+
+ import scala.collection.mutable.{ArrayBuffer, Builder}
+
+ val m = new ArrayBuffer[(B, Builder[A, List[A]])]
+ m.sizeHint(coll.length)
+
+ for (elem <- coll) {
+ val key = f(elem)
+ val index = m.indexWhere(_._1 == key)
+ if (index < 0)
+ m += ((key, List.newBuilder[A] += elem))
+ else
+ m(index)._2 += elem
+ }
+
+ m.toList.map(kv => kv._1 -> kv._2.result())
+ }
+
+ private def genThrowTypeError(msg: String = "No matching overload")(
+ implicit pos: Position): js.Tree = {
+ js.UnaryOp(js.UnaryOp.Throw, js.StringLiteral(msg))
+ }
+
+ class FormalArgsRegistry(minArgc: Int, needsRestParam: Boolean) {
+ private val fixedParamNames: scala.collection.immutable.IndexedSeq[LocalName] =
+ (0 until minArgc).toIndexedSeq.map(_ => freshLocalIdent("arg")(NoPosition).name)
+
+ private val restParamName: LocalName =
+ if (needsRestParam) freshLocalIdent("rest")(NoPosition).name
+ else null
+
+ def genFormalArgs()(implicit pos: Position): (List[js.ParamDef], Option[js.ParamDef]) = {
+ val fixedParamDefs = fixedParamNames.toList.map { paramName =>
+ js.ParamDef(js.LocalIdent(paramName), NoOriginalName, jstpe.AnyType,
+ mutable = false)
+ }
+
+ val restParam = {
+ if (needsRestParam) {
+ Some(js.ParamDef(js.LocalIdent(restParamName),
+ NoOriginalName, jstpe.AnyType, mutable = false))
+ } else {
+ None
+ }
+ }
+
+ (fixedParamDefs, restParam)
+ }
+
+ def genArgRef(index: Int)(implicit pos: Position): js.Tree = {
+ if (index < minArgc)
+ js.VarRef(fixedParamNames(index))(jstpe.AnyType)
+ else
+ js.JSSelect(genRestArgRef(), js.IntLiteral(index - minArgc))
+ }
+
+ def genVarargRef(fixedParamCount: Int)(implicit pos: Position): js.Tree = {
+ val restParam = genRestArgRef()
+ assert(fixedParamCount >= minArgc,
+ s"genVarargRef($fixedParamCount) with minArgc = $minArgc at $pos")
+ if (fixedParamCount == minArgc) {
+ restParam
+ } else {
+ js.JSMethodApply(restParam, js.StringLiteral("slice"),
+ List(js.IntLiteral(fixedParamCount - minArgc)))
+ }
+ }
+
+ def genRestArgRef()(implicit pos: Position): js.Tree = {
+ assert(needsRestParam,
+ s"trying to generate a reference to non-existent rest param at $pos")
+ js.VarRef(restParamName)(jstpe.AnyType)
+ }
+
+ def genAllArgsRefsForForwarder()(implicit pos: Position): List[js.TreeOrJSSpread] = {
+ val fixedArgRefs = fixedParamNames.toList.map { paramName =>
+ js.VarRef(paramName)(jstpe.AnyType)
+ }
+
+ if (needsRestParam) {
+ val restArgRef = js.VarRef(restParamName)(jstpe.AnyType)
+ fixedArgRefs :+ js.JSSpread(restArgRef)
+ } else {
+ fixedArgRefs
+ }
+ }
+ }
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSFiles.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSFiles.scala
new file mode 100644
index 0000000000..10d2ac4f21
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSFiles.scala
@@ -0,0 +1,50 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.tools.nsc._
+import scala.tools.nsc.io.AbstractFile
+import scala.reflect.internal.pickling.PickleBuffer
+
+import java.io._
+
+import org.scalajs.ir
+
+/** Send JS ASTs to files
+ *
+ * @author Sébastien Doeraene
+ */
+trait GenJSFiles[G <: Global with Singleton] extends SubComponent {
+ self: GenJSCode[G] =>
+
+ import global._
+ import jsAddons._
+
+ def genIRFile(cunit: CompilationUnit, tree: ir.Trees.ClassDef): Unit = {
+ val outfile = getFileFor(cunit, tree.name.name, ".sjsir")
+ val output = outfile.bufferedOutput
+ try ir.Serializers.serialize(output, tree)
+ finally output.close()
+ }
+
+ private def getFileFor(cunit: CompilationUnit, className: ir.Names.ClassName,
+ suffix: String): AbstractFile = {
+ val baseDir: AbstractFile =
+ settings.outputDirs.outputDirFor(cunit.source.file)
+
+ val pathParts = className.nameString.split('.')
+ val dir = pathParts.init.foldLeft(baseDir)(_.subdirectoryNamed(_))
+ val filename = pathParts.last
+ dir.fileNamed(filename + suffix)
+ }
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala
new file mode 100644
index 0000000000..58c4910233
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala
@@ -0,0 +1,168 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.tools.nsc._
+
+/** Core definitions for Scala.js
+ *
+ * @author Sébastien Doeraene
+ */
+trait JSDefinitions {
+ val global: Global
+
+ import global._
+
+ // scalastyle:off line.size.limit
+
+ object jsDefinitions extends JSDefinitionsClass
+
+ import definitions._
+ import rootMirror._
+
+ class JSDefinitionsClass {
+
+ lazy val HackedStringClass = getClassIfDefined("java.lang._String")
+ lazy val HackedStringModClass = getModuleIfDefined("java.lang._String").moduleClass
+
+ lazy val JavaLangVoidClass = getRequiredClass("java.lang.Void")
+
+ lazy val JavaLangReflectArrayModClass = getModuleIfDefined("java.lang.reflect.Array").moduleClass
+
+ lazy val BoxedUnitModClass = BoxedUnitModule.moduleClass
+
+ lazy val ScalaJSJSPackageModule = getPackageObject("scala.scalajs.js")
+ lazy val JSPackage_typeOf = getMemberMethod(ScalaJSJSPackageModule, newTermName("typeOf"))
+ lazy val JSPackage_constructorOf = getMemberMethod(ScalaJSJSPackageModule, newTermName("constructorOf"))
+ lazy val JSPackage_native = getMemberMethod(ScalaJSJSPackageModule, newTermName("native"))
+ lazy val JSPackage_undefined = getMemberMethod(ScalaJSJSPackageModule, newTermName("undefined"))
+ lazy val JSPackage_dynamicImport = getMemberMethod(ScalaJSJSPackageModule, newTermName("dynamicImport"))
+ lazy val JSPackage_async = getMemberMethod(ScalaJSJSPackageModule, newTermName("async"))
+ lazy val JSPackage_await = getMemberMethod(ScalaJSJSPackageModule, newTermName("await"))
+
+ lazy val JSNativeAnnotation = getRequiredClass("scala.scalajs.js.native")
+
+ lazy val JSAnyClass = getRequiredClass("scala.scalajs.js.Any")
+ lazy val JSDynamicClass = getRequiredClass("scala.scalajs.js.Dynamic")
+ lazy val JSObjectClass = getRequiredClass("scala.scalajs.js.Object")
+ lazy val JSFunctionClass = getRequiredClass("scala.scalajs.js.Function")
+ lazy val JSThisFunctionClass = getRequiredClass("scala.scalajs.js.ThisFunction")
+
+ lazy val UnionClass = getRequiredClass("scala.scalajs.js.$bar")
+
+ lazy val JSArrayClass = getRequiredClass("scala.scalajs.js.Array")
+
+ lazy val JavaScriptExceptionClass = getClassIfDefined("scala.scalajs.js.JavaScriptException")
+
+ lazy val JSNameAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSName")
+ lazy val JSBracketAccessAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSBracketAccess")
+ lazy val JSBracketCallAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSBracketCall")
+ lazy val JSExportAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExport")
+ lazy val JSExportAllAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportAll")
+ lazy val JSExportStaticAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportStatic")
+ lazy val JSExportTopLevelAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportTopLevel")
+ lazy val JSImportAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSImport")
+ lazy val JSGlobalAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSGlobal")
+ lazy val JSGlobalScopeAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSGlobalScope")
+ lazy val JSOperatorAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSOperator")
+
+ lazy val JSImportNamespaceObject = getRequiredModule("scala.scalajs.js.annotation.JSImport.Namespace")
+
+ lazy val ExposedJSMemberAnnot = getRequiredClass("scala.scalajs.js.annotation.internal.ExposedJSMember")
+ lazy val JSOptionalAnnotation = getRequiredClass("scala.scalajs.js.annotation.internal.JSOptional")
+ lazy val JSTypeAnnot = getRequiredClass("scala.scalajs.js.annotation.internal.JSType")
+ lazy val WasPublicBeforeTyperClass = getRequiredClass("scala.scalajs.js.annotation.internal.WasPublicBeforeTyper")
+
+ lazy val JSDynamicModule = JSDynamicClass.companionModule
+ lazy val JSDynamic_newInstance = getMemberMethod(JSDynamicModule, newTermName("newInstance"))
+ lazy val JSDynamicLiteral = getMemberModule(JSDynamicModule, newTermName("literal"))
+ lazy val JSDynamicLiteral_applyDynamicNamed = getMemberMethod(JSDynamicLiteral, newTermName("applyDynamicNamed"))
+ lazy val JSDynamicLiteral_applyDynamic = getMemberMethod(JSDynamicLiteral, newTermName("applyDynamic"))
+
+ lazy val JSArrayModule = JSArrayClass.companionModule
+ lazy val JSArray_create = getMemberMethod(JSArrayModule, newTermName("apply"))
+
+ lazy val JSConstructorTagModule = getRequiredModule("scala.scalajs.js.ConstructorTag")
+ lazy val JSConstructorTag_materialize = getMemberMethod(JSConstructorTagModule, newTermName("materialize"))
+
+ lazy val JSNewModule = getRequiredModule("scala.scalajs.js.new")
+ lazy val JSNewModuleClass = JSNewModule.moduleClass
+ lazy val JSNew_target = getMemberMethod(JSNewModuleClass, newTermName("target"))
+
+ lazy val JSImportModule = getRequiredModule("scala.scalajs.js.import")
+ lazy val JSImportModuleClass = JSImportModule.moduleClass
+ lazy val JSImport_apply = getMemberMethod(JSImportModuleClass, nme.apply)
+ lazy val JSImport_meta = getMemberMethod(JSImportModuleClass, newTermName("meta"))
+
+ lazy val SpecialPackageModule = getPackageObject("scala.scalajs.js.special")
+ lazy val Special_strictEquals = getMemberMethod(SpecialPackageModule, newTermName("strictEquals"))
+ lazy val Special_in = getMemberMethod(SpecialPackageModule, newTermName("in"))
+ lazy val Special_instanceof = getMemberMethod(SpecialPackageModule, newTermName("instanceof"))
+ lazy val Special_delete = getMemberMethod(SpecialPackageModule, newTermName("delete"))
+ lazy val Special_forin = getMemberMethod(SpecialPackageModule, newTermName("forin"))
+ lazy val Special_throw = getMemberMethod(SpecialPackageModule, newTermName("throw"))
+ lazy val Special_tryCatch = getMemberMethod(SpecialPackageModule, newTermName("tryCatch"))
+ lazy val Special_wrapAsThrowable = getMemberMethod(SpecialPackageModule, newTermName("wrapAsThrowable"))
+ lazy val Special_unwrapFromThrowable = getMemberMethod(SpecialPackageModule, newTermName("unwrapFromThrowable"))
+ lazy val Special_debugger = getMemberMethod(SpecialPackageModule, newTermName("debugger"))
+
+ lazy val WasmJSPIModule = getRequiredModule("scala.scalajs.js.wasm.JSPI")
+ lazy val WasmJSPIModuleClass = WasmJSPIModule.moduleClass
+ lazy val WasmJSPI_allowOrphanJSAwaitModule = getMemberModule(WasmJSPIModuleClass, newTermName("allowOrphanJSAwait"))
+ lazy val WasmJSPI_allowOrphanJSAwaitModuleClass = WasmJSPI_allowOrphanJSAwaitModule.moduleClass
+
+ lazy val RuntimePackageModule = getPackageObject("scala.scalajs.runtime")
+ lazy val Runtime_toScalaVarArgs = getMemberMethod(RuntimePackageModule, newTermName("toScalaVarArgs"))
+ lazy val Runtime_toJSVarArgs = getMemberMethod(RuntimePackageModule, newTermName("toJSVarArgs"))
+ lazy val Runtime_constructorOf = getMemberMethod(RuntimePackageModule, newTermName("constructorOf"))
+ lazy val Runtime_newConstructorTag = getMemberMethod(RuntimePackageModule, newTermName("newConstructorTag"))
+ lazy val Runtime_createInnerJSClass = getMemberMethod(RuntimePackageModule, newTermName("createInnerJSClass"))
+ lazy val Runtime_createLocalJSClass = getMemberMethod(RuntimePackageModule, newTermName("createLocalJSClass"))
+ lazy val Runtime_withContextualJSClassValue = getMemberMethod(RuntimePackageModule, newTermName("withContextualJSClassValue"))
+ lazy val Runtime_privateFieldsSymbol = getMemberMethod(RuntimePackageModule, newTermName("privateFieldsSymbol"))
+ lazy val Runtime_linkingInfo = getMemberMethod(RuntimePackageModule, newTermName("linkingInfo"))
+ lazy val Runtime_identityHashCode = getMemberMethod(RuntimePackageModule, newTermName("identityHashCode"))
+ lazy val Runtime_dynamicImport = getMemberMethod(RuntimePackageModule, newTermName("dynamicImport"))
+
+ lazy val LinkingInfoModule = getRequiredModule("scala.scalajs.LinkingInfo")
+ lazy val LinkingInfo_linkTimeIf = getMemberMethod(LinkingInfoModule, newTermName("linkTimeIf"))
+ lazy val LinkingInfo_linkTimePropertyBoolean = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyBoolean"))
+ lazy val LinkingInfo_linkTimePropertyInt = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyInt"))
+ lazy val LinkingInfo_linkTimePropertyString = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyString"))
+
+ lazy val LinkTimePropertyAnnotation = getRequiredClass("scala.scalajs.annotation.linkTimeProperty")
+
+ lazy val DynamicImportThunkClass = getRequiredClass("scala.scalajs.runtime.DynamicImportThunk")
+ lazy val DynamicImportThunkClass_apply = getMemberMethod(DynamicImportThunkClass, nme.apply)
+
+ lazy val Tuple2_apply = getMemberMethod(TupleClass(2).companionModule, nme.apply)
+
+ // This is a def, since similar symbols (arrayUpdateMethod, etc.) are in runDefinitions
+ // (rather than definitions) and we weren't sure if it is safe to make this a lazy val
+ def ScalaRunTime_isArray: Symbol = getMemberMethod(ScalaRunTimeModule, newTermName("isArray")).suchThat(_.tpe.params.size == 2)
+
+ lazy val ReflectModule = getRequiredModule("scala.scalajs.reflect.Reflect")
+ lazy val Reflect_registerLoadableModuleClass = getMemberMethod(ReflectModule, newTermName("registerLoadableModuleClass"))
+ lazy val Reflect_registerInstantiatableClass = getMemberMethod(ReflectModule, newTermName("registerInstantiatableClass"))
+
+ lazy val EnableReflectiveInstantiationAnnotation = getRequiredClass("scala.scalajs.reflect.annotation.EnableReflectiveInstantiation")
+
+ lazy val ExecutionContextModule = getRequiredModule("scala.concurrent.ExecutionContext")
+ lazy val ExecutionContext_global = getMemberMethod(ExecutionContextModule, newTermName("global"))
+
+ lazy val ExecutionContextImplicitsModule = getRequiredModule("scala.concurrent.ExecutionContext.Implicits")
+ lazy val ExecutionContextImplicits_global = getMemberMethod(ExecutionContextImplicitsModule, newTermName("global"))
+ }
+
+ // scalastyle:on line.size.limit
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala
new file mode 100644
index 0000000000..263f1def30
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala
@@ -0,0 +1,327 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.collection.mutable
+
+import scala.tools.nsc._
+
+import org.scalajs.ir
+import org.scalajs.ir.{Trees => js, Types => jstpe, WellKnownNames => jswkn}
+import org.scalajs.ir.Names.{LocalName, LabelName, SimpleFieldName, FieldName, SimpleMethodName, MethodName, ClassName}
+import org.scalajs.ir.OriginalName
+import org.scalajs.ir.OriginalName.NoOriginalName
+import org.scalajs.ir.UTF8String
+
+import org.scalajs.nscplugin.util.{ScopedVar, VarBox}
+import ScopedVar.withScopedVars
+
+/** Encoding of symbol names for the IR. */
+trait JSEncoding[G <: Global with Singleton] extends SubComponent {
+ self: GenJSCode[G] =>
+
+ import global._
+ import jsAddons._
+
+ /** Name of the capture param storing the JS super class.
+ *
+ * This is used by the dispatchers of exposed JS methods and properties of
+ * nested JS classes when they need to perform a super call. Other super
+ * calls (in the actual bodies of the methods, not in the dispatchers) do
+ * not use this value, since they are implemented as static methods that do
+ * not have access to it. Instead, they get the JS super class value through
+ * the magic method inserted by `ExplicitLocalJS`, leveraging `lambdalift`
+ * to ensure that it is properly captured.
+ *
+ * Using this identifier is only allowed if it was reserved in the current
+ * local name scope using [[reserveLocalName]]. Otherwise, this name can
+ * clash with another local identifier.
+ */
+ final val JSSuperClassParamName = LocalName("superClass$")
+
+ private val xLocalName = LocalName("x")
+
+ private val ScalaRuntimeNullClass = ClassName("scala.runtime.Null$")
+ private val ScalaRuntimeNothingClass = ClassName("scala.runtime.Nothing$")
+
+ private val dynamicImportForwarderSimpleName = SimpleMethodName("dynamicImport$")
+
+ // Fresh local name generator ----------------------------------------------
+
+ private val usedLocalNames = new ScopedVar[mutable.Set[LocalName]]
+ private val localSymbolNames = new ScopedVar[mutable.Map[Symbol, LocalName]]
+ private val usedLabelNames = new ScopedVar[mutable.Set[LabelName]]
+ private val labelSymbolNames = new ScopedVar[mutable.Map[Symbol, LabelName]]
+ private val returnLabelName = new ScopedVar[VarBox[Option[LabelName]]]
+
+ def withNewLocalNameScope[A](body: => A): A = {
+ withScopedVars(
+ usedLocalNames := mutable.Set.empty,
+ localSymbolNames := mutable.Map.empty,
+ usedLabelNames := mutable.Set.empty,
+ labelSymbolNames := mutable.Map.empty,
+ returnLabelName := null
+ )(body)
+ }
+
+ def reserveLocalName(name: LocalName): Unit = {
+ require(usedLocalNames.isEmpty,
+ s"Trying to reserve the name '$name' but names have already been " +
+ "allocated")
+ usedLocalNames += name
+ }
+
+ def withNewReturnableScope(tpe: jstpe.Type)(body: => js.Tree)(
+ implicit pos: ir.Position): js.Tree = {
+ withScopedVars(
+ returnLabelName := new VarBox(None)
+ ) {
+ val inner = body
+ returnLabelName.get.value match {
+ case None =>
+ inner
+ case Some(labelName) =>
+ js.Labeled(labelName, tpe, inner)
+ }
+ }
+ }
+
+ private def freshNameGeneric[N <: ir.Names.Name](base: N,
+ usedNamesSet: mutable.Set[N])(
+ withSuffix: (N, String) => N): N = {
+
+ var suffix = 1
+ var result = base
+ while (usedNamesSet(result)) {
+ suffix += 1
+ result = withSuffix(base, "$" + suffix)
+ }
+ usedNamesSet += result
+ result
+ }
+
+ private def freshName(base: LocalName): LocalName =
+ freshNameGeneric(base, usedLocalNames)(_.withSuffix(_))
+
+ private def freshName(base: String): LocalName =
+ freshName(LocalName(base))
+
+ def freshLocalIdent()(implicit pos: ir.Position): js.LocalIdent =
+ js.LocalIdent(freshName(xLocalName))
+
+ def freshLocalIdent(base: LocalName)(implicit pos: ir.Position): js.LocalIdent =
+ js.LocalIdent(freshName(base))
+
+ def freshLocalIdent(base: String)(implicit pos: ir.Position): js.LocalIdent =
+ freshLocalIdent(LocalName(base))
+
+ private def localSymbolName(sym: Symbol): LocalName = {
+ localSymbolNames.getOrElseUpdate(sym, {
+ /* The emitter does not like local variables that start with a '$',
+ * because it needs to encode them not to clash with emitter-generated
+ * names. There are two common cases, caused by scalac-generated names:
+ * - the `$this` parameter of tailrec methods and "extension" methods of
+ * AnyVals, which scalac knows as `nme.SELF`, and
+ * - the `$outer` parameter of inner class constructors, which scalac
+ * knows as `nme.OUTER`.
+ * We choose different base names for those two cases instead, so that
+ * the avoidance mechanism of the emitter doesn't happen as a common
+ * case. It can still happen for user-defined variables, but in that case
+ * the emitter will deal with it.
+ */
+ val base = sym.name match {
+ case nme.SELF => "this$" // instead of $this
+ case nme.OUTER => "outer" // instead of $outer
+ case name => name.toString()
+ }
+ freshName(base)
+ })
+ }
+
+ private def freshLabelName(base: LabelName): LabelName =
+ freshNameGeneric(base, usedLabelNames)(_.withSuffix(_))
+
+ def freshLabelName(base: String): LabelName =
+ freshLabelName(LabelName(base))
+
+ private def labelSymbolName(sym: Symbol): LabelName =
+ labelSymbolNames.getOrElseUpdate(sym, freshLabelName(sym.name.toString))
+
+ def getEnclosingReturnLabel()(implicit pos: Position): LabelName = {
+ val box = returnLabelName.get
+ if (box == null)
+ throw new IllegalStateException(s"No enclosing returnable scope at $pos")
+ if (box.value.isEmpty)
+ box.value = Some(freshLabelName("_return"))
+ box.value.get
+ }
+
+ // Encoding methods ----------------------------------------------------------
+
+ def encodeLabelSym(sym: Symbol): LabelName = {
+ require(sym.isLabel, "encodeLabelSym called with non-label symbol: " + sym)
+ labelSymbolName(sym)
+ }
+
+ def encodeFieldSym(sym: Symbol)(implicit pos: Position): js.FieldIdent = {
+ requireSymIsField(sym)
+ val className = encodeClassName(sym.owner)
+ val simpleName = SimpleFieldName(sym.name.dropLocal.toString())
+ js.FieldIdent(FieldName(className, simpleName))
+ }
+
+ def encodeFieldSymAsStringLiteral(sym: Symbol)(
+ implicit pos: Position): js.StringLiteral = {
+
+ requireSymIsField(sym)
+ js.StringLiteral(sym.name.dropLocal.toString())
+ }
+
+ private def requireSymIsField(sym: Symbol): Unit = {
+ require(sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule,
+ "encodeFieldSym called with non-field symbol: " + sym)
+ }
+
+ def encodeMethodSym(sym: Symbol, reflProxy: Boolean = false)(
+ implicit pos: Position): js.MethodIdent = {
+
+ require(sym.isMethod,
+ "encodeMethodSym called with non-method symbol: " + sym)
+
+ val tpe = sym.tpe
+
+ val paramTypeRefs0 = tpe.params.map(p => paramOrResultTypeRef(p.tpe))
+
+ val hasExplicitThisParameter = isNonNativeJSClass(sym.owner)
+ val paramTypeRefs =
+ if (!hasExplicitThisParameter) paramTypeRefs0
+ else paramOrResultTypeRef(sym.owner.toTypeConstructor) :: paramTypeRefs0
+
+ val name = sym.name
+ val simpleName = SimpleMethodName(name.toString())
+
+ val methodName = {
+ if (sym.isClassConstructor)
+ MethodName.constructor(paramTypeRefs)
+ else if (reflProxy)
+ MethodName.reflectiveProxy(simpleName, paramTypeRefs)
+ else
+ MethodName(simpleName, paramTypeRefs, paramOrResultTypeRef(tpe.resultType))
+ }
+
+ js.MethodIdent(methodName)
+ }
+
+ def encodeStaticFieldGetterSym(sym: Symbol)(
+ implicit pos: Position): js.MethodIdent = {
+
+ require(sym.isStaticMember,
+ "encodeStaticFieldGetterSym called with non-static symbol: " + sym)
+
+ val name = sym.name
+ val resultTypeRef = paramOrResultTypeRef(sym.tpe)
+ val methodName = MethodName(name.toString(), Nil, resultTypeRef)
+ js.MethodIdent(methodName)
+ }
+
+ def encodeDynamicImportForwarderIdent(params: List[Symbol])(
+ implicit pos: Position): js.MethodIdent = {
+ val paramTypeRefs = params.map(sym => paramOrResultTypeRef(sym.tpe))
+ val resultTypeRef = jstpe.ClassRef(jswkn.ObjectClass)
+ val methodName =
+ MethodName(dynamicImportForwarderSimpleName, paramTypeRefs, resultTypeRef)
+
+ js.MethodIdent(methodName)
+ }
+
+ /** Computes the internal name for a type. */
+ private def paramOrResultTypeRef(tpe: Type): jstpe.TypeRef = {
+ toTypeRef(tpe) match {
+ case jstpe.ClassRef(ScalaRuntimeNothingClass) => jstpe.NothingRef
+ case jstpe.ClassRef(ScalaRuntimeNullClass) => jstpe.NullRef
+ case typeRef => typeRef
+ }
+ }
+
+ def encodeLocalSym(sym: Symbol)(implicit pos: Position): js.LocalIdent =
+ js.LocalIdent(encodeLocalSymName(sym))
+
+ def encodeLocalSymName(sym: Symbol): LocalName = {
+ /* The isValueParameter case is necessary to work around an internal bug
+ * of scalac: for some @varargs methods, the owner of some parameters is
+ * the enclosing class rather the method, so !sym.owner.isClass fails.
+ * Go figure ...
+ * See #1440
+ */
+ require(sym.isValueParameter ||
+ (!sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule),
+ "encodeLocalSym called with non-local symbol: " + sym)
+ localSymbolName(sym)
+ }
+
+ def encodeClassType(sym: Symbol): jstpe.Type = {
+ if (sym == definitions.ObjectClass) jstpe.AnyType
+ else if (isJSType(sym)) jstpe.AnyType
+ else {
+ assert(sym != definitions.ArrayClass,
+ "encodeClassType() cannot be called with ArrayClass")
+ jstpe.ClassType(encodeClassName(sym), nullable = true)
+ }
+ }
+
+ def encodeClassNameIdent(sym: Symbol)(implicit pos: Position): js.ClassIdent =
+ js.ClassIdent(encodeClassName(sym))
+
+ private val BoxedStringModuleClassName = ClassName("java.lang.String$")
+
+ def encodeClassName(sym: Symbol): ClassName = {
+ assert(!sym.isPrimitiveValueClass,
+ s"Illegal encodeClassName(${sym.fullName}")
+ if (sym == jsDefinitions.HackedStringClass) {
+ jswkn.BoxedStringClass
+ } else if (sym == jsDefinitions.HackedStringModClass) {
+ BoxedStringModuleClassName
+ } else if (sym == definitions.BoxedUnitClass || sym == jsDefinitions.BoxedUnitModClass) {
+ // Rewire scala.runtime.BoxedUnit to java.lang.Void, as the IR expects
+ // BoxedUnit$ is a JVM artifact
+ jswkn.BoxedUnitClass
+ } else {
+ ClassName(sym.fullName + (if (needsModuleClassSuffix(sym)) "$" else ""))
+ }
+ }
+
+ def needsModuleClassSuffix(sym: Symbol): Boolean =
+ sym.isModuleClass && !sym.isJavaDefined
+
+ def originalNameOfLocal(sym: Symbol): OriginalName = {
+ val irName = localSymbolName(sym)
+ val originalName = UTF8String(nme.unexpandedName(sym.name).decoded)
+ if (UTF8String.equals(originalName, irName.encoded)) NoOriginalName
+ else OriginalName(originalName)
+ }
+
+ def originalNameOfField(sym: Symbol): OriginalName =
+ originalNameOf(sym.name.dropLocal)
+
+ def originalNameOfMethod(sym: Symbol): OriginalName =
+ originalNameOf(sym.name)
+
+ def originalNameOfClass(sym: Symbol): OriginalName =
+ originalNameOf(sym.fullNameAsName('.'))
+
+ private def originalNameOf(name: Name): OriginalName = {
+ val originalName = nme.unexpandedName(name).decoded
+ if (originalName == name.toString) NoOriginalName
+ else OriginalName(originalName)
+ }
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala
new file mode 100644
index 0000000000..dea4d5529d
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala
@@ -0,0 +1,397 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.tools.nsc._
+
+import scala.collection.mutable
+
+import org.scalajs.ir.Trees.JSNativeLoadSpec
+import org.scalajs.ir.{Trees => js}
+
+/** Additions to Global meaningful for the JavaScript backend
+ *
+ * @author Sébastien Doeraene
+ */
+trait JSGlobalAddons extends JSDefinitions
+ with CompatComponent {
+ val global: Global
+
+ import global._
+ import jsDefinitions._
+ import definitions._
+
+ /** JavaScript primitives, used in jscode */
+ object jsPrimitives extends JSPrimitives {
+ val global: JSGlobalAddons.this.global.type = JSGlobalAddons.this.global
+ val jsAddons: ThisJSGlobalAddons =
+ JSGlobalAddons.this.asInstanceOf[ThisJSGlobalAddons]
+ }
+
+ /** Extracts the super type of a `Template`, with type parameters reinvented
+ * so that the type is well-formed outside of the `Template`, i.e., at the
+ * same level where the corresponding `ImplDef` is defined.
+ */
+ def extractSuperTpeFromImpl(impl: Template): Type =
+ reinventTypeParams(impl.parents.head.tpe)
+
+ /** Reinvents all the type parameters of a `TypeRef`.
+ *
+ * This is done by existentially quantifying over all type parameters of
+ * the class type referenced by the `TypeRef`.
+ *
+ * As a simple example, given the definition
+ * {{{
+ * class C[A, B <: AnyVal]
+ * }}}
+ * this transforms
+ * {{{
+ * path.C[A, Int]
+ * }}}
+ * into
+ * {{{
+ * path.C[_, _ <: AnyVal]
+ * }}}
+ *
+ * As a complex example, given the definition
+ * {{{
+ * class D[A, B <: List[Seq[A]]]
+ * }}}
+ * this method transforms
+ * {{{
+ * path.D[?0, ?1] forSome { type ?0; type ?1 <: List[Seq[?0]] }
+ * }}}
+ */
+ private def reinventTypeParams(tp: Type): Type = {
+ tp match {
+ case TypeRef(pre, sym, _) if sym.isClass && sym.typeParams.nonEmpty =>
+ val eparams = typeParamsToExistentials(sym)
+ existentialAbstraction(eparams, typeRef(pre, sym, eparams.map(_.tpe)))
+ case _ =>
+ tp
+ }
+ }
+
+ /** global javascript interop related helpers */
+ object jsInterop {
+ import scala.reflect.NameTransformer
+ import scala.reflect.internal.Flags
+
+ /** TopLevel exports, by owner. */
+ private val topLevelExports =
+ mutable.Map.empty[Symbol, List[TopLevelExportInfo]]
+
+ /** Static exports, by owner. */
+ private val staticExports =
+ mutable.Map.empty[Symbol, List[StaticExportInfo]]
+
+ /** JS native load specs of the symbols in the current compilation run. */
+ private val jsNativeLoadSpecs =
+ mutable.Map.empty[Symbol, JSNativeLoadSpec]
+
+ private val exportPrefix = "$js$exported$"
+ private val methodExportPrefix = exportPrefix + "meth$"
+ private val propExportPrefix = exportPrefix + "prop$"
+
+ /** Info for a non-member export. */
+ sealed trait ExportInfo {
+ val pos: Position
+ }
+
+ /* Not final because it causes the following compile warning:
+ * "The outer reference in this type test cannot be checked at run time."
+ */
+ case class TopLevelExportInfo(moduleID: String, jsName: String)(
+ val pos: Position) extends ExportInfo
+ case class StaticExportInfo(jsName: String)(val pos: Position)
+ extends ExportInfo
+
+ sealed abstract class JSName {
+ def displayName: String
+ }
+
+ object JSName {
+ // Not final because it causes annoying compile warnings
+ case class Literal(name: String) extends JSName {
+ def displayName: String = name
+ }
+
+ // Not final because it causes annoying compile warnings
+ case class Computed(sym: Symbol) extends JSName {
+ def displayName: String = sym.fullName
+ }
+ }
+
+ sealed abstract class JSCallingConvention {
+ def displayName: String
+ }
+
+ object JSCallingConvention {
+ case object Call extends JSCallingConvention {
+ def displayName: String = "function application"
+ }
+
+ case object BracketAccess extends JSCallingConvention {
+ def displayName: String = "bracket access"
+ }
+
+ case object BracketCall extends JSCallingConvention {
+ def displayName: String = "bracket call"
+ }
+
+ case class Method(name: JSName) extends JSCallingConvention {
+ def displayName: String = "method '" + name.displayName + "'"
+ }
+
+ case class Property(name: JSName) extends JSCallingConvention {
+ def displayName: String = "property '" + name.displayName + "'"
+ }
+
+ case class UnaryOp(code: js.JSUnaryOp.Code) extends JSCallingConvention {
+ def displayName: String = "unary operator"
+ }
+
+ case class BinaryOp(code: js.JSBinaryOp.Code) extends JSCallingConvention {
+ def displayName: String = "binary operator"
+ }
+
+ def of(sym: Symbol): JSCallingConvention = {
+ assert(sym.isTerm, s"got non-term symbol: $sym")
+
+ if (isJSBracketAccess(sym)) {
+ BracketAccess
+ } else if (isJSBracketCall(sym)) {
+ BracketCall
+ } else {
+ def default = {
+ val jsName = jsNameOf(sym)
+ if (isJSProperty(sym)) Property(jsName)
+ else Method(jsName)
+ }
+
+ if (!sym.hasAnnotation(JSNameAnnotation)) {
+ lazy val pc = sym.paramss.map(_.size).sum
+
+ sym.name match {
+ case nme.apply => Call
+
+ case JSUnaryOpMethodName(code, defaultsToOp)
+ if (defaultsToOp || sym.hasAnnotation(JSOperatorAnnotation)) && pc == 0 =>
+ UnaryOp(code)
+
+ case JSBinaryOpMethodName(code, defaultsToOp)
+ if (defaultsToOp || sym.hasAnnotation(JSOperatorAnnotation)) && pc == 1 =>
+ BinaryOp(code)
+
+ case _ =>
+ default
+ }
+ } else {
+ default
+ }
+ }
+ }
+
+ /** Tests whether the calling convention of the specified symbol is `Call`.
+ *
+ * This helper is provided because we use this test in a few places.
+ */
+ def isCall(sym: Symbol): Boolean =
+ of(sym) == Call
+ }
+
+ object JSUnaryOpMethodName {
+ private val map = Map[Name, (js.JSUnaryOp.Code, Boolean)](
+ nme.UNARY_+ -> (js.JSUnaryOp.+, true),
+ nme.UNARY_- -> (js.JSUnaryOp.-, true),
+ nme.UNARY_~ -> (js.JSUnaryOp.~, true),
+ nme.UNARY_! -> (js.JSUnaryOp.!, true)
+ )
+
+ /* We use Name instead of TermName to work around
+ * https://github.com/scala/bug/issues/11534
+ */
+ def unapply(name: Name): Option[(js.JSUnaryOp.Code, Boolean)] =
+ map.get(name)
+ }
+
+ object JSBinaryOpMethodName {
+ private val map = Map[Name, (js.JSBinaryOp.Code, Boolean)](
+ nme.ADD -> (js.JSBinaryOp.+, true),
+ nme.SUB -> (js.JSBinaryOp.-, true),
+ nme.MUL -> (js.JSBinaryOp.*, true),
+ nme.DIV -> (js.JSBinaryOp./, true),
+ nme.MOD -> (js.JSBinaryOp.%, true),
+
+ nme.LSL -> (js.JSBinaryOp.<<, true),
+ nme.ASR -> (js.JSBinaryOp.>>, true),
+ nme.LSR -> (js.JSBinaryOp.>>>, true),
+ nme.OR -> (js.JSBinaryOp.|, true),
+ nme.AND -> (js.JSBinaryOp.&, true),
+ nme.XOR -> (js.JSBinaryOp.^, true),
+
+ nme.LT -> (js.JSBinaryOp.<, true),
+ nme.LE -> (js.JSBinaryOp.<=, true),
+ nme.GT -> (js.JSBinaryOp.>, true),
+ nme.GE -> (js.JSBinaryOp.>=, true),
+
+ nme.ZAND -> (js.JSBinaryOp.&&, true),
+ nme.ZOR -> (js.JSBinaryOp.||, true),
+
+ global.encode("**") -> (js.JSBinaryOp.**, false)
+ )
+
+ /* We use Name instead of TermName to work around
+ * https://github.com/scala/bug/issues/11534
+ */
+ def unapply(name: Name): Option[(js.JSBinaryOp.Code, Boolean)] =
+ map.get(name)
+ }
+
+ def clearGlobalState(): Unit = {
+ topLevelExports.clear()
+ staticExports.clear()
+ jsNativeLoadSpecs.clear()
+ }
+
+ def registerTopLevelExports(sym: Symbol, infos: List[TopLevelExportInfo]): Unit = {
+ assert(!topLevelExports.contains(sym), s"symbol exported twice: $sym")
+ topLevelExports.put(sym, infos)
+ }
+
+ def registerStaticExports(sym: Symbol, infos: List[StaticExportInfo]): Unit = {
+ assert(!staticExports.contains(sym), s"symbol exported twice: $sym")
+ staticExports.put(sym, infos)
+ }
+
+ def topLevelExportsOf(sym: Symbol): List[TopLevelExportInfo] =
+ topLevelExports.getOrElse(sym, Nil)
+
+ def staticExportsOf(sym: Symbol): List[StaticExportInfo] =
+ staticExports.getOrElse(sym, Nil)
+
+ /** creates a name for an export specification */
+ def scalaExportName(jsName: String, isProp: Boolean): TermName = {
+ val pref = if (isProp) propExportPrefix else methodExportPrefix
+ val encname = NameTransformer.encode(jsName)
+ newTermName(pref + encname)
+ }
+
+ /** checks if the given symbol is a JSExport */
+ def isExport(sym: Symbol): Boolean =
+ sym.name.startsWith(exportPrefix) && !sym.hasFlag(Flags.DEFAULTPARAM)
+
+ /** retrieves the originally assigned jsName of this export and whether it
+ * is a property
+ */
+ def jsExportInfo(name: Name): (String, Boolean) = {
+ def dropPrefix(prefix: String) ={
+ if (name.startsWith(prefix)) {
+ // We can't decode right away due to $ separators
+ val enc = name.toString.substring(prefix.length)
+ Some(NameTransformer.decode(enc))
+ } else None
+ }
+
+ dropPrefix(methodExportPrefix).map((_,false)).orElse {
+ dropPrefix(propExportPrefix).map((_,true))
+ }.getOrElse {
+ throw new IllegalArgumentException(
+ "non-exported name passed to jsExportInfo")
+ }
+ }
+
+ def jsclassAccessorFor(clazz: Symbol): Symbol =
+ clazz.owner.info.member(clazz.name.append("$jsclass").toTermName)
+
+ def isJSProperty(sym: Symbol): Boolean = isJSGetter(sym) || isJSSetter(sym)
+
+ @inline private def enteringUncurryIfAtPhaseAfter[A](op: => A): A = {
+ if (currentRun.uncurryPhase != NoPhase &&
+ isAtPhaseAfter(currentRun.uncurryPhase)) {
+ enteringPhase(currentRun.uncurryPhase)(op)
+ } else {
+ op
+ }
+ }
+
+ /** has this symbol to be translated into a JS getter (both directions)? */
+ def isJSGetter(sym: Symbol): Boolean = {
+ /* `sym.isModule` implies that `sym` is the module's accessor. In 2.12,
+ * module accessors are synthesized
+ * after uncurry, thus their first info is a MethodType at phase fields.
+ */
+ sym.isModule || (sym.tpe.params.isEmpty && enteringUncurryIfAtPhaseAfter {
+ sym.tpe match {
+ case _: NullaryMethodType => true
+ case PolyType(_, _: NullaryMethodType) => true
+ case _ => false
+ }
+ })
+ }
+
+ /** has this symbol to be translated into a JS setter (both directions)? */
+ def isJSSetter(sym: Symbol): Boolean =
+ nme.isSetterName(sym.name) && sym.isMethod && !sym.isConstructor
+
+ /** has this symbol to be translated into a JS bracket access (JS to Scala) */
+ def isJSBracketAccess(sym: Symbol): Boolean =
+ sym.hasAnnotation(JSBracketAccessAnnotation)
+
+ /** has this symbol to be translated into a JS bracket call (JS to Scala) */
+ def isJSBracketCall(sym: Symbol): Boolean =
+ sym.hasAnnotation(JSBracketCallAnnotation)
+
+ /** Gets the unqualified JS name of a symbol.
+ *
+ * If it is not explicitly specified with an `@JSName` annotation, the
+ * JS name is inferred from the Scala name.
+ */
+ def jsNameOf(sym: Symbol): JSName = {
+ sym.getAnnotation(JSNameAnnotation).fold[JSName] {
+ JSName.Literal(defaultJSNameOf(sym))
+ } { annotation =>
+ annotation.constantAtIndex(0).collect {
+ case Constant(name: String) => JSName.Literal(name)
+ }.getOrElse {
+ JSName.Computed(annotation.args.head.symbol)
+ }
+ }
+ }
+
+ def defaultJSNameOf(sym: Symbol): String = {
+ val base = sym.unexpandedName.decoded.stripSuffix("_=")
+ if (!sym.isMethod) base.stripSuffix(" ")
+ else base
+ }
+
+ /** Stores the JS native load spec of a symbol for the current compilation
+ * run.
+ */
+ def storeJSNativeLoadSpec(sym: Symbol, spec: JSNativeLoadSpec): Unit =
+ jsNativeLoadSpecs(sym) = spec
+
+ /** Gets the JS native load spec of a symbol in the current compilation run.
+ */
+ def jsNativeLoadSpecOf(sym: Symbol): JSNativeLoadSpec =
+ jsNativeLoadSpecs(sym)
+
+ /** Gets the JS native load spec of a symbol in the current compilation run,
+ * if it has one.
+ */
+ def jsNativeLoadSpecOfOption(sym: Symbol): Option[JSNativeLoadSpec] =
+ jsNativeLoadSpecs.get(sym)
+
+ }
+
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala
new file mode 100644
index 0000000000..cf6f896453
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala
@@ -0,0 +1,140 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.tools.nsc._
+
+import scala.collection.mutable
+
+/** Extension of ScalaPrimitives for primitives only relevant to the JS backend
+ *
+ * @author Sébastie Doeraene
+ */
+abstract class JSPrimitives {
+ val global: Global
+
+ type ThisJSGlobalAddons = JSGlobalAddons {
+ val global: JSPrimitives.this.global.type
+ }
+
+ val jsAddons: ThisJSGlobalAddons
+
+ import global._
+ import jsAddons._
+ import definitions._
+ import jsDefinitions._
+ import scalaPrimitives._
+
+ final val FirstJSPrimitiveCode = 300
+
+ final val DYNNEW = FirstJSPrimitiveCode + 1 // Instantiate a new JavaScript object
+
+ final val ARR_CREATE = DYNNEW + 1 // js.Array.apply (array literal syntax)
+
+ final val TYPEOF = ARR_CREATE + 1 // typeof x
+ final val JS_NATIVE = TYPEOF + 1 // js.native. Marker method. Fails if tried to be emitted.
+
+ final val UNITVAL = JS_NATIVE + 1 // () value, which is undefined
+
+ final val JS_NEW_TARGET = UNITVAL + 1 // js.new.target
+
+ final val JS_IMPORT = JS_NEW_TARGET + 1 // js.import.apply(specifier)
+ final val JS_IMPORT_META = JS_IMPORT + 1 // js.import.meta
+
+ final val JS_ASYNC = JS_IMPORT_META + 1 // js.async
+ final val JS_AWAIT = JS_ASYNC + 1 // js.await
+
+ final val CONSTRUCTOROF = JS_AWAIT + 1 // runtime.constructorOf(clazz)
+ final val CREATE_INNER_JS_CLASS = CONSTRUCTOROF + 1 // runtime.createInnerJSClass
+ final val CREATE_LOCAL_JS_CLASS = CREATE_INNER_JS_CLASS + 1 // runtime.createLocalJSClass
+ final val WITH_CONTEXTUAL_JS_CLASS_VALUE = CREATE_LOCAL_JS_CLASS + 1 // runtime.withContextualJSClassValue
+ final val IDENTITY_HASH_CODE = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.identityHashCode
+ final val DYNAMIC_IMPORT = IDENTITY_HASH_CODE + 1 // runtime.dynamicImport
+
+ final val STRICT_EQ = DYNAMIC_IMPORT + 1 // js.special.strictEquals
+ final val IN = STRICT_EQ + 1 // js.special.in
+ final val INSTANCEOF = IN + 1 // js.special.instanceof
+ final val DELETE = INSTANCEOF + 1 // js.special.delete
+ final val FORIN = DELETE + 1 // js.special.forin
+ final val JS_THROW = FORIN + 1 // js.special.throw
+ final val JS_TRY_CATCH = JS_THROW + 1 // js.special.tryCatch
+ final val WRAP_AS_THROWABLE = JS_TRY_CATCH + 1 // js.special.wrapAsThrowable
+ final val UNWRAP_FROM_THROWABLE = WRAP_AS_THROWABLE + 1 // js.special.unwrapFromThrowable
+ final val DEBUGGER = UNWRAP_FROM_THROWABLE + 1 // js.special.debugger
+ final val LINKTIME_IF = DEBUGGER + 1 // LinkingInfo.linkTimeIf
+ final val LINKTIME_PROPERTY = LINKTIME_IF + 1 // LinkingInfo.linkTimePropertyXXX
+
+ final val LastJSPrimitiveCode = LINKTIME_PROPERTY
+
+ /** Initialize the map of primitive methods (for GenJSCode) */
+ def init(): Unit = initWithPrimitives(addPrimitive)
+
+ /** Init the map of primitive methods for Scala.js (for PrepJSInterop) */
+ def initPrepJSPrimitives(): Unit = {
+ scalaJSPrimitives.clear()
+ initWithPrimitives(scalaJSPrimitives.put)
+ }
+
+ /** Only call from PrepJSInterop. In GenJSCode, use
+ * scalaPrimitives.isPrimitive instead
+ */
+ def isJavaScriptPrimitive(sym: Symbol): Boolean =
+ scalaJSPrimitives.contains(sym)
+
+ private val scalaJSPrimitives = mutable.Map.empty[Symbol, Int]
+
+ private def initWithPrimitives(addPrimitive: (Symbol, Int) => Unit): Unit = {
+ addPrimitive(JSDynamic_newInstance, DYNNEW)
+
+ addPrimitive(JSArray_create, ARR_CREATE)
+
+ addPrimitive(JSPackage_typeOf, TYPEOF)
+ addPrimitive(JSPackage_native, JS_NATIVE)
+ addPrimitive(JSPackage_async, JS_ASYNC)
+ addPrimitive(JSPackage_await, JS_AWAIT)
+
+ addPrimitive(BoxedUnit_UNIT, UNITVAL)
+
+ addPrimitive(JSNew_target, JS_NEW_TARGET)
+
+ addPrimitive(JSImport_apply, JS_IMPORT)
+ addPrimitive(JSImport_meta, JS_IMPORT_META)
+
+ addPrimitive(Runtime_constructorOf, CONSTRUCTOROF)
+ addPrimitive(Runtime_createInnerJSClass, CREATE_INNER_JS_CLASS)
+ addPrimitive(Runtime_createLocalJSClass, CREATE_LOCAL_JS_CLASS)
+ addPrimitive(Runtime_withContextualJSClassValue,
+ WITH_CONTEXTUAL_JS_CLASS_VALUE)
+ addPrimitive(Runtime_identityHashCode, IDENTITY_HASH_CODE)
+ addPrimitive(Runtime_dynamicImport, DYNAMIC_IMPORT)
+
+ addPrimitive(Special_strictEquals, STRICT_EQ)
+ addPrimitive(Special_in, IN)
+ addPrimitive(Special_instanceof, INSTANCEOF)
+ addPrimitive(Special_delete, DELETE)
+ addPrimitive(Special_forin, FORIN)
+ addPrimitive(Special_throw, JS_THROW)
+ addPrimitive(Special_tryCatch, JS_TRY_CATCH)
+ addPrimitive(Special_wrapAsThrowable, WRAP_AS_THROWABLE)
+ addPrimitive(Special_unwrapFromThrowable, UNWRAP_FROM_THROWABLE)
+ addPrimitive(Special_debugger, DEBUGGER)
+
+ addPrimitive(LinkingInfo_linkTimeIf, LINKTIME_IF)
+ addPrimitive(LinkingInfo_linkTimePropertyBoolean, LINKTIME_PROPERTY)
+ addPrimitive(LinkingInfo_linkTimePropertyInt, LINKTIME_PROPERTY)
+ addPrimitive(LinkingInfo_linkTimePropertyString, LINKTIME_PROPERTY)
+ }
+
+ def isJavaScriptPrimitive(code: Int): Boolean =
+ code >= FirstJSPrimitiveCode && code <= LastJSPrimitiveCode
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/PreTyperComponent.scala b/compiler/src/main/scala/org/scalajs/nscplugin/PreTyperComponent.scala
new file mode 100644
index 0000000000..2983735355
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/PreTyperComponent.scala
@@ -0,0 +1,143 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.tools.nsc
+import nsc._
+
+/** This `jspretyper` phase prepares a fix for issue SI-9487 in the case of
+ * anonymous classes that extend js.Any.
+ *
+ * During `typer`, due to a bug (SI-9487), Scalac transfroms some public method
+ * definitions of a quite specific syntactic form of anonymous classes into
+ * private methods. This affects both methods and field accessors. This phase
+ * identifies any anonymous class and adds a `@WasPublicBeforeTyper` annotation
+ * on its public methods and fields. After the `typer` in `jsinterop` the
+ * anonymous classes are fixed if they extend js.Any using the annotations as
+ * reference.
+ *
+ * As an example:
+ * {{{
+ * class \$anon extends ... {
+ * val foo = ???
+ * var bar = ???
+ * def baz = ???
+ * private val foo2 = ???
+ * private var bar2 = ???
+ * private def baz2 = ???
+ * }
+ * }}}
+ *
+ * Would become:
+ * {{{
+ * class \$anon extends ... {
+ * @WasPublicBeforeTyper val foo = ???
+ * @WasPublicBeforeTyper var bar = ???
+ * @WasPublicBeforeTyper def baz = ???
+ * private val foo2 = ???
+ * private var bar2 = ???
+ * private def baz2 = ???
+ * }
+ * }}}
+ *
+ * And after `typer` (if has SI-9487) will be:
+ * {{{
+ * class \$anon extends ... {
+ * @WasPublicBeforeTyper private[this] var foo = ???
+ * private def foo = ??? // Needs fix
+ *
+ * @WasPublicBeforeTyper private[this] var bar = ???
+ * private def bar = ??? // Needs fix
+ * private def bar_=(...) = ??? // Needs fix
+ *
+ * @WasPublicBeforeTyper private def baz = ??? // Needs fix
+ * ...
+ * }
+ * }}}
+ *
+ * @author Nicolas Stucki
+ */
+abstract class PreTyperComponent(val global: Global)
+ extends plugins.PluginComponent
+ with transform.Transform with CompatComponent {
+
+ import global._
+
+ val phaseName: String = "jspretyper"
+ override def description: String =
+ "capture pre-typer only tree info (for Scala.js)"
+
+ override protected def newTransformer(unit: CompilationUnit): Transformer =
+ new PreTyperTransformer(unit)
+
+ class PreTyperTransformer(unit: CompilationUnit) extends Transformer {
+ override def transform(tree: Tree): Tree = tree match {
+ case tree: ClassDef if needsAnnotations(tree) =>
+ val newBody = tree.impl.body.map {
+ case vdef: ValDef if needsAnnotations(vdef) =>
+ treeCopy.ValDef(vdef, withWasPublic(vdef.mods), vdef.name,
+ vdef.tpt, transform(vdef.rhs))
+
+ case ddef: DefDef if needsAnnotations(ddef) =>
+ treeCopy.DefDef(ddef, withWasPublic(ddef.mods), ddef.name,
+ ddef.tparams, ddef.vparamss, ddef.tpt, transform(ddef.rhs))
+
+ case member => transform(member)
+ }
+ val newImpl =
+ treeCopy.Template(tree.impl, tree.impl.parents, tree.impl.self, newBody)
+ treeCopy.ClassDef(tree, tree.mods, tree.name, tree.tparams, newImpl)
+
+ case tree: Template =>
+ /* Avoid filtering out members that are EmptyTree during this transform.
+ *
+ * run/macro-term-declared-in-trait is an example of code where they
+ * should not be cleaned.
+ */
+ val newBody = tree.body.map(transform)
+ treeCopy.Template(tree, tree.parents, tree.self, newBody)
+
+ case _ => super.transform(tree)
+ }
+ }
+
+ private def needsAnnotations(classDef: ClassDef): Boolean = {
+ classDef.name == nme.ANON_CLASS_NAME.toTypeName &&
+ classDef.impl.body.exists {
+ case vdef: ValDef => needsAnnotations(vdef)
+ case ddef: DefDef => needsAnnotations(ddef)
+ case _ => false
+ }
+ }
+
+ private def needsAnnotations(vdef: ValDef): Boolean =
+ vdef.mods.isPublic
+
+ private def needsAnnotations(ddef: DefDef): Boolean =
+ ddef.mods.isPublic && ddef.name != nme.CONSTRUCTOR
+
+ private def withWasPublic(mods: Modifiers): Modifiers =
+ mods.withAnnotations(List(anonymousClassMethodWasPublicAnnotation))
+
+ private val scalajs = newTermName("scalajs")
+ private val js = newTermName("js")
+ private val internal_ = newTermName("internal")
+ private val wasPublicBeforeTyper = newTypeName("WasPublicBeforeTyper")
+
+ private def anonymousClassMethodWasPublicAnnotation: Tree = {
+ val cls = Select(Select(Select(Select(Select(Select(Ident(nme.ROOTPKG),
+ nme.scala_), scalajs), js), nme.annotation), internal_),
+ wasPublicBeforeTyper)
+ Apply(Select(New(cls), nme.CONSTRUCTOR), Nil)
+ }
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala
new file mode 100644
index 0000000000..e9217c04a7
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala
@@ -0,0 +1,572 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.collection.mutable
+
+import scala.tools.nsc.Global
+
+import org.scalajs.ir.Trees.TopLevelExportDef.isValidTopLevelExportName
+import org.scalajs.ir.WellKnownNames.DefaultModuleID
+
+/**
+ * Prepare export generation
+ *
+ * Helpers for transformation of @JSExport annotations
+ */
+trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
+
+ import global._
+ import jsAddons._
+ import definitions._
+ import jsDefinitions._
+
+ import scala.reflect.internal.Flags
+
+ private sealed abstract class ExportDestination
+
+ private object ExportDestination {
+ /** Export in the "normal" way: as an instance member, or at the top-level
+ * for naturally top-level things (classes and modules).
+ */
+ case object Normal extends ExportDestination
+
+ /** Export at the top-level. */
+ case class TopLevel(moduleID: String) extends ExportDestination
+
+ /** Export as a static member of the companion class. */
+ case object Static extends ExportDestination
+ }
+
+ /* Not final because it causes the following compile warning:
+ * "The outer reference in this type test cannot be checked at run time."
+ */
+ private case class ExportInfo(jsName: String,
+ destination: ExportDestination)(val pos: Position)
+
+ /** Generate exports for the given Symbol.
+ *
+ * * Registers top-level and static exports.
+ * * Returns (non-static) exporters for this symbol.
+ */
+ def genExport(sym: Symbol): List[Tree] = {
+ // Scala classes are never exported: Their constructors are.
+ val isScalaClass = sym.isClass && !sym.isTrait && !sym.isModuleClass && !isJSAny(sym)
+
+ /* Filter case class apply (and unapply) to work around
+ * https://github.com/scala/bug/issues/8826
+ */
+ val isCaseApplyOrUnapplyParam = sym.isLocalToBlock && sym.owner.isCaseApplyOrUnapply
+
+ /* Filter constructors of module classes: The module classes themselves will
+ * be exported.
+ */
+ val isModuleClassCtor = sym.isConstructor && sym.owner.isModuleClass
+
+ val exports =
+ if (isScalaClass || isCaseApplyOrUnapplyParam || isModuleClassCtor) Nil
+ else exportsOf(sym)
+
+ assert(exports.isEmpty || !sym.isBridge,
+ s"found exports for bridge symbol $sym. exports: $exports")
+
+ val (normalExports, topLevelAndStaticExports) =
+ exports.partition(_.destination == ExportDestination.Normal)
+
+ /* We can handle top level exports and static exports entirely in the
+ * backend. So just register them here.
+ *
+ * For accessors, we need to apply some special logic to static and
+ * top-level exports: They actually apply to the *fields*, not to the
+ * accessors.
+ */
+ if (sym.isAccessor && sym.accessed != NoSymbol) {
+ /* Only forward registration from the getter (not the setter) to avoid
+ * duplicate registration.
+ */
+ if (sym.isGetter)
+ registerStaticAndTopLevelExports(sym.accessed, topLevelAndStaticExports)
+ } else {
+ registerStaticAndTopLevelExports(sym, topLevelAndStaticExports)
+ }
+
+ // For normal exports, generate exporter methods.
+ normalExports.flatMap(exp => genExportDefs(sym, exp.jsName, exp.pos))
+ }
+
+ private def registerStaticAndTopLevelExports(sym: Symbol,
+ exports: List[ExportInfo]): Unit = {
+ val topLevel = exports.collect {
+ case info @ ExportInfo(jsName, ExportDestination.TopLevel(moduleID)) =>
+ jsInterop.TopLevelExportInfo(moduleID, jsName)(info.pos)
+ }
+
+ if (topLevel.nonEmpty)
+ jsInterop.registerTopLevelExports(sym, topLevel)
+
+ val static = exports.collect {
+ case info @ ExportInfo(jsName, ExportDestination.Static) =>
+ jsInterop.StaticExportInfo(jsName)(info.pos)
+ }
+
+ if (static.nonEmpty)
+ jsInterop.registerStaticExports(sym, static)
+ }
+
+ /** retrieves the names a sym should be exported to from its annotations
+ *
+ * Note that for accessor symbols, the annotations of the accessed symbol
+ * are used, rather than the annotations of the accessor itself.
+ */
+ private def exportsOf(sym: Symbol): List[ExportInfo] = {
+ val trgSym = {
+ def isOwnerScalaClass = !sym.owner.isModuleClass && !isJSAny(sym.owner)
+
+ // For primary Scala class constructors, look on the class itself
+ if (sym.isPrimaryConstructor && isOwnerScalaClass) sym.owner
+ else sym
+ }
+
+ val symOwner =
+ if (sym.isConstructor) sym.owner.owner
+ else sym.owner
+
+ // Annotations that are directly on the member
+ val directAnnots = trgSym.annotations.filter(
+ annot => isDirectMemberAnnot(annot.symbol))
+
+ /* Annotations for this member on the whole unit
+ *
+ * Note that for top-level classes / modules this is always empty, because
+ * packages cannot have annotations.
+ */
+ val unitAnnots = {
+ val useExportAll = {
+ sym.isPublic &&
+ !sym.isSynthetic &&
+ !sym.isConstructor &&
+ !sym.isTrait &&
+ (!sym.isClass || sym.isModuleClass)
+ }
+
+ if (useExportAll)
+ symOwner.annotations.filter(_.symbol == JSExportAllAnnotation)
+ else
+ Nil
+ }
+
+ val allAnnots = {
+ val allAnnots0 = directAnnots ++ unitAnnots
+
+ if (allAnnots0.nonEmpty) {
+ if (checkExportTarget(sym, allAnnots0.head.pos)) allAnnots0
+ else Nil // prevent code generation from running to avoid crashes.
+ } else {
+ Nil
+ }
+ }
+
+ val allExportInfos = for {
+ annot <- allAnnots
+ } yield {
+ val isExportAll = annot.symbol == JSExportAllAnnotation
+ val isTopLevelExport = annot.symbol == JSExportTopLevelAnnotation
+ val isStaticExport = annot.symbol == JSExportStaticAnnotation
+ val hasExplicitName = annot.args.nonEmpty
+
+ assert(!isTopLevelExport || hasExplicitName,
+ "Found a top-level export without an explicit name at " + annot.pos)
+
+ val name = {
+ if (hasExplicitName) {
+ annot.stringArg(0).getOrElse {
+ reporter.error(annot.args(0).pos,
+ s"The argument to ${annot.symbol.name} must be a literal string")
+ "dummy"
+ }
+ } else {
+ val nameBase =
+ (if (sym.isConstructor) sym.owner else sym).unexpandedName
+
+ if (nme.isSetterName(nameBase) && !jsInterop.isJSSetter(sym)) {
+ reporter.error(annot.pos, "You must set an explicit name when " +
+ "exporting a non-setter with a name ending in _=")
+ }
+
+ nameBase.decoded.stripSuffix("_=")
+ }
+ }
+
+ val destination = {
+ if (isTopLevelExport) {
+ val moduleID = if (annot.args.size == 1) {
+ DefaultModuleID
+ } else {
+ annot.stringArg(1).getOrElse {
+ reporter.error(annot.args(1).pos,
+ "moduleID must be a literal string")
+ DefaultModuleID
+ }
+ }
+
+ ExportDestination.TopLevel(moduleID)
+ } else if (isStaticExport) {
+ ExportDestination.Static
+ } else {
+ ExportDestination.Normal
+ }
+ }
+
+ // Enforce no __ in name
+ if (!isTopLevelExport && name.contains("__")) {
+ // Get position for error message
+ val pos = if (hasExplicitName) annot.args.head.pos else trgSym.pos
+
+ reporter.error(pos,
+ "An exported name may not contain a double underscore (`__`)")
+ }
+
+ // Destination-specific restrictions
+ destination match {
+ case ExportDestination.Normal =>
+ if (symOwner.hasPackageFlag) {
+ // Disallow @JSExport on top-level definitions.
+ reporter.error(annot.pos,
+ "@JSExport is forbidden on top-level objects and classes. " +
+ "Use @JSExportTopLevel instead.")
+ }
+
+ // Make sure we do not override the default export of toString
+ def isIllegalToString = {
+ name == "toString" && sym.name != nme.toString_ &&
+ sym.tpe.params.isEmpty && !jsInterop.isJSGetter(sym)
+ }
+ if (isIllegalToString) {
+ reporter.error(annot.pos, "You may not export a zero-argument " +
+ "method named other than 'toString' under the name 'toString'")
+ }
+
+ /* Illegal function application exports, i.e., method named 'apply'
+ * without an explicit export name.
+ */
+ if (!hasExplicitName && sym.name == nme.apply) {
+ def shouldBeTolerated = {
+ isExportAll && directAnnots.exists { annot =>
+ annot.symbol == JSExportAnnotation &&
+ annot.args.nonEmpty &&
+ annot.stringArg(0) == Some("apply")
+ }
+ }
+
+ // Don't allow apply without explicit name
+ if (!shouldBeTolerated) {
+ // Get position for error message
+ val pos = if (isExportAll) trgSym.pos else annot.pos
+
+ reporter.error(pos, "A member cannot be exported to function " +
+ "application. Add @JSExport(\"apply\") to export under the " +
+ "name apply.")
+ }
+ }
+
+ case _: ExportDestination.TopLevel =>
+ if (sym.isLazy) {
+ reporter.error(annot.pos,
+ "You may not export a lazy val to the top level")
+ } else if (!sym.isAccessor && jsInterop.isJSProperty(sym)) {
+ reporter.error(annot.pos,
+ "You may not export a getter or a setter to the top level")
+ }
+
+ // Disallow non-static definitions.
+ if (!symOwner.isStatic || !symOwner.isModuleClass) {
+ reporter.error(annot.pos,
+ "Only static objects may export their members to the top level")
+ }
+
+ // The top-level name must be a valid JS identifier
+ if (!isValidTopLevelExportName(name)) {
+ reporter.error(annot.pos,
+ "The top-level export name must be a valid JavaScript " +
+ "identifier name")
+ }
+
+ case ExportDestination.Static =>
+ def companionIsNonNativeJSClass: Boolean = {
+ val companion = symOwner.companionClass
+ companion != NoSymbol &&
+ !companion.isTrait &&
+ isJSAny(companion) &&
+ !companion.hasAnnotation(JSNativeAnnotation)
+ }
+
+ if (!symOwner.isStatic || !symOwner.isModuleClass ||
+ !companionIsNonNativeJSClass) {
+ reporter.error(annot.pos,
+ "Only a static object whose companion class is a " +
+ "non-native JS class may export its members as static.")
+ }
+
+ if (sym.isLazy) {
+ reporter.error(annot.pos,
+ "You may not export a lazy val as static")
+ }
+
+ // Illegal function application export
+ if (!hasExplicitName && sym.name == nme.apply) {
+ reporter.error(annot.pos,
+ "A member cannot be exported to function application as " +
+ "static. Use @JSExportStatic(\"apply\") to export it under " +
+ "the name 'apply'.")
+ }
+
+ if (sym.isClass || sym.isConstructor) {
+ reporter.error(annot.pos,
+ "Implementation restriction: cannot export a class or " +
+ "object as static")
+ }
+ }
+
+ ExportInfo(name, destination)(annot.pos)
+ }
+
+ allExportInfos.filter(_.destination == ExportDestination.Normal)
+ .groupBy(_.jsName)
+ .filter { case (jsName, group) =>
+ if (jsName == "apply" && group.size == 2)
+ // @JSExportAll and single @JSExport("apply") should not be warned.
+ !unitAnnots.exists(_.symbol == JSExportAllAnnotation)
+ else
+ group.size > 1
+ }
+ .foreach(_ => reporter.warning(sym.pos, s"Found duplicate @JSExport"))
+
+ /* Check that no field is exported *twice* as static, nor both as static
+ * and as top-level (it is possible to export a field several times as
+ * top-level, though).
+ */
+ if (sym.isGetter) {
+ for {
+ firstStatic <- allExportInfos.find(_.destination == ExportDestination.Static).toList
+ duplicate <- allExportInfos
+ if duplicate ne firstStatic
+ } {
+ duplicate.destination match {
+ case ExportDestination.Normal => // OK
+
+ case ExportDestination.Static =>
+ reporter.error(duplicate.pos,
+ "Fields (val or var) cannot be exported as static more " +
+ "than once")
+
+ case _: ExportDestination.TopLevel =>
+ reporter.error(duplicate.pos,
+ "Fields (val or var) cannot be exported both as static " +
+ "and at the top-level")
+ }
+ }
+ }
+
+ allExportInfos.distinct
+ }
+
+ /** Checks whether the given target is suitable for export and exporting
+ * should be performed.
+ *
+ * Reports any errors for unsuitable targets.
+ * @returns a boolean indicating whether exporting should be performed. Note:
+ * a result of true is not a guarantee that no error was emitted. But it is
+ * a guarantee that the target is not "too broken" to run the rest of
+ * the generation. This approximation is done to avoid having to complicate
+ * shared code verifying conditions.
+ */
+ private def checkExportTarget(sym: Symbol, errPos: Position): Boolean = {
+ def err(msg: String) = {
+ reporter.error(errPos, msg)
+ false
+ }
+
+ def hasLegalExportVisibility(sym: Symbol) =
+ sym.isPublic || sym.isProtected && !sym.isProtectedLocal
+
+ lazy val params = sym.paramss.flatten
+
+ def hasIllegalDefaultParam = {
+ val isDefParam = (_: Symbol).hasFlag(Flags.DEFAULTPARAM)
+ params.reverse.dropWhile(isDefParam).exists(isDefParam)
+ }
+
+ def hasAnyNonPrivateCtor: Boolean =
+ sym.info.member(nme.CONSTRUCTOR).filter(!isPrivateMaybeWithin(_)).exists
+
+ if (sym.isTrait) {
+ err("You may not export a trait")
+ } else if (sym.hasAnnotation(JSNativeAnnotation)) {
+ err("You may not export a native JS definition")
+ } else if (!hasLegalExportVisibility(sym)) {
+ err("You may only export public and protected definitions")
+ } else if (sym.isConstructor && !hasLegalExportVisibility(sym.owner)) {
+ err("You may only export constructors of public and protected classes")
+ } else if (sym.isMacro) {
+ err("You may not export a macro")
+ } else if (isJSAny(sym.owner)) {
+ err("You may not export a member of a subclass of js.Any")
+ } else if (scalaPrimitives.isPrimitive(sym)) {
+ err("You may not export a primitive")
+ } else if (sym.isLocalToBlock) {
+ err("You may not export a local definition")
+ } else if (sym.isConstructor && sym.owner.isLocalToBlock) {
+ err("You may not export constructors of local classes")
+ } else if (params.nonEmpty && params.init.exists(isRepeated _)) {
+ err("In an exported method or constructor, a *-parameter must come last " +
+ "(through all parameter lists)")
+ } else if (hasIllegalDefaultParam) {
+ err("In an exported method or constructor, all parameters with " +
+ "defaults must be at the end")
+ } else if (sym.isConstructor && sym.owner.isAbstractClass && !isJSAny(sym)) {
+ err("You may not export an abstract class")
+ } else if (sym.isClass && !sym.isModuleClass && isJSAny(sym) && !hasAnyNonPrivateCtor) {
+ /* This test is only relevant for JS classes: We'll complain on the
+ * individual exported constructors in case of a Scala class.
+ */
+ err("You may not export a class that has only private constructors")
+ } else {
+ if (jsInterop.isJSSetter(sym))
+ checkSetterSignature(sym, errPos, exported = true)
+
+ true // ok even if a setter has the wrong signature.
+ }
+ }
+
+ /** generate an exporter for a target including default parameter methods */
+ private def genExportDefs(sym: Symbol, jsName: String, pos: Position) = {
+ val siblingSym =
+ if (sym.isConstructor) sym.owner
+ else sym
+
+ val clsSym = siblingSym.owner
+
+ val isProperty = sym.isModuleClass || isJSAny(sym) || jsInterop.isJSProperty(sym)
+ val scalaName = jsInterop.scalaExportName(jsName, isProperty)
+
+ val copiedFlags = siblingSym.flags & (Flags.PROTECTED | Flags.FINAL)
+
+ // Create symbol for new method
+ val expSym = clsSym.newMethod(scalaName, pos, Flags.SYNTHETIC | copiedFlags)
+ expSym.privateWithin = siblingSym.privateWithin
+
+ val expSymTpe = {
+ /* Alter type for new method (lift return type to Any)
+ * The return type is lifted, in order to avoid bridge
+ * construction and to detect methods whose signature only differs
+ * in the return type.
+ */
+ if (sym.isClass) NullaryMethodType(AnyClass.tpe)
+ else retToAny(sym.tpe.cloneInfo(expSym))
+ }
+
+ expSym.setInfoAndEnter(expSymTpe)
+
+ // Construct exporter DefDef tree
+ val exporter = genProxyDefDef(sym, expSym, pos)
+
+ // Construct exporters for default getters
+ val defaultGetters = for {
+ (param, i) <- expSym.paramss.flatten.zipWithIndex
+ if param.hasFlag(Flags.DEFAULTPARAM)
+ } yield genExportDefaultGetter(clsSym, sym, expSym, i + 1, pos)
+
+ exporter :: defaultGetters
+ }
+
+ private def genExportDefaultGetter(clsSym: Symbol, trgMethod: Symbol,
+ exporter: Symbol, paramPos: Int, pos: Position) = {
+
+ // Get default getter method we'll copy
+ val trgGetter =
+ clsSym.tpe.member(nme.defaultGetterName(trgMethod.name, paramPos))
+
+ assert(trgGetter.exists,
+ s"Cannot find default getter for param $paramPos of $trgMethod")
+
+ // Although the following must be true in a correct program, we cannot
+ // assert, since a graceful failure message is only generated later
+ if (!trgGetter.isOverloaded) {
+ val expGetter = trgGetter.cloneSymbol
+
+ expGetter.name = nme.defaultGetterName(exporter.name, paramPos)
+ expGetter.pos = pos
+
+ clsSym.info.decls.enter(expGetter)
+
+ genProxyDefDef(trgGetter, expGetter, pos)
+
+ } else EmptyTree
+ }
+
+ /** generate a DefDef tree (from [[proxySym]]) that calls [[trgSym]] */
+ private def genProxyDefDef(trgSym: Symbol, proxySym: Symbol, pos: Position) = atPos(pos) {
+ val tpeParams = proxySym.typeParams.map(gen.mkAttributedIdent(_))
+
+ // Construct proxied function call
+ val nonPolyFun = {
+ if (trgSym.isConstructor) {
+ val clsTpe = trgSym.owner.tpe
+ val tpe = gen.mkTypeApply(TypeTree(clsTpe), tpeParams)
+ Select(New(tpe), trgSym)
+ } else if (trgSym.isModuleClass) {
+ assert(proxySym.paramss.isEmpty,
+ s"got a module export with non-empty paramss. target: $trgSym, proxy: $proxySym at $pos")
+ gen.mkAttributedRef(trgSym.sourceModule)
+ } else if (trgSym.isClass) {
+ assert(isJSAny(trgSym), s"got a class export for a non-JS class ($trgSym) at $pos")
+ assert(proxySym.paramss.isEmpty,
+ s"got a class export with non-empty paramss. target: $trgSym, proxy: $proxySym at $pos")
+ val tpe = gen.mkTypeApply(TypeTree(trgSym.tpe), tpeParams)
+ gen.mkTypeApply(gen.mkAttributedRef(JSPackage_constructorOf), List(tpe))
+ } else {
+ val fun = gen.mkAttributedRef(trgSym)
+ gen.mkTypeApply(fun, tpeParams)
+ }
+ }
+
+ val rhs = proxySym.paramss.foldLeft(nonPolyFun) { (fun, params) =>
+ val args = params.map { param =>
+ val ident = gen.mkAttributedIdent(param)
+
+ if (isRepeated(param)) Typed(ident, Ident(tpnme.WILDCARD_STAR))
+ else ident
+ }
+
+ Apply(fun, args)
+ }
+
+ typer.typedDefDef(DefDef(proxySym, rhs))
+ }
+
+ /** changes the return type of the method type tpe to Any. returns new type */
+ private def retToAny(tpe: Type): Type = tpe match {
+ case MethodType(params, result) => MethodType(params, retToAny(result))
+ case NullaryMethodType(result) => NullaryMethodType(AnyClass.tpe)
+ case PolyType(tparams, result) => PolyType(tparams, retToAny(result))
+ case _ => AnyClass.tpe
+ }
+
+ /** Whether a symbol is an annotation that goes directly on a member */
+ private lazy val isDirectMemberAnnot = Set[Symbol](
+ JSExportAnnotation,
+ JSExportTopLevelAnnotation,
+ JSExportStaticAnnotation
+ )
+
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala
new file mode 100644
index 0000000000..592b9aa381
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala
@@ -0,0 +1,1748 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.tools.nsc
+import nsc._
+
+import scala.collection.immutable.ListMap
+import scala.collection.mutable
+
+import org.scalajs.ir.Trees.{JSGlobalRef, JSNativeLoadSpec}
+
+/** Prepares classes extending js.Any for JavaScript interop
+ *
+ * This phase does:
+ * - Sanity checks for js.Any hierarchy
+ * - Annotate subclasses of js.Any to be treated specially
+ * - Rewrite calls to scala.Enumeration.Value (include name string)
+ * - Create JSExport methods: Dummy methods that are propagated
+ * through the whole compiler chain to mark exports. This allows
+ * exports to have the same semantics than methods.
+ *
+ * @author Tobias Schlatter
+ */
+abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
+ extends plugins.PluginComponent with PrepJSExports[G]
+ with transform.Transform with CompatComponent {
+
+ import PrepJSInterop._
+
+ /** Not for use in the constructor body: only initialized afterwards. */
+ val jsAddons: JSGlobalAddons {
+ val global: PrepJSInterop.this.global.type
+ }
+
+ /** Not for use in the constructor body: only initialized afterwards. */
+ val scalaJSOpts: ScalaJSOptions
+
+ import global._
+ import jsAddons._
+ import definitions._
+ import rootMirror._
+ import jsDefinitions._
+ import jsInterop.{JSCallingConvention, JSName}
+
+ import scala.reflect.internal.Flags
+
+ val phaseName: String = "jsinterop"
+ override def description: String = "prepare ASTs for JavaScript interop"
+
+ override def newPhase(p: nsc.Phase): StdPhase = new JSInteropPhase(p)
+
+ class JSInteropPhase(prev: nsc.Phase) extends Phase(prev) {
+ override def name: String = phaseName
+ override def description: String = PrepJSInterop.this.description
+ override def run(): Unit = {
+ jsPrimitives.initPrepJSPrimitives()
+ jsInterop.clearGlobalState()
+ super.run()
+ }
+ }
+
+ override protected def newTransformer(unit: CompilationUnit): Transformer =
+ new JSInteropTransformer(unit)
+
+ private object jsnme {
+ val hasNext = newTermName("hasNext")
+ val next = newTermName("next")
+ val nextName = newTermName("nextName")
+ val Value = newTermName("Value")
+ val Val = newTermName("Val")
+
+ val ArrowAssoc = newTermName("ArrowAssoc")
+ }
+
+ class JSInteropTransformer(unit: CompilationUnit) extends Transformer {
+
+ /** Kind of the directly enclosing (most nested) owner. */
+ private var enclosingOwner: OwnerKind = OwnerKind.None
+
+ /** Cumulative kinds of all enclosing owners. */
+ private var allEnclosingOwners: OwnerKind = OwnerKind.None
+
+ /** Nicer syntax for `allEnclosingOwners is kind`. */
+ private def anyEnclosingOwner: OwnerKind = allEnclosingOwners
+
+ /** Nicer syntax for `allEnclosingOwners isnt kind`. */
+ private object noEnclosingOwner {
+ @inline def is(kind: OwnerKind): Boolean =
+ allEnclosingOwners isnt kind
+ }
+
+ private def enterOwner[A](kind: OwnerKind)(body: => A): A = {
+ require(kind.isBaseKind, kind)
+ val oldEnclosingOwner = enclosingOwner
+ val oldAllEnclosingOwners = allEnclosingOwners
+ enclosingOwner = kind
+ allEnclosingOwners |= kind
+ try {
+ body
+ } finally {
+ enclosingOwner = oldEnclosingOwner
+ allEnclosingOwners = oldAllEnclosingOwners
+ }
+ }
+
+ /** Tests whether this is a ScalaDoc run.
+ *
+ * There are some things we must not do in ScalaDoc runs because, because
+ * ScalaDoc runs don't do everything we need, for example constant-folding
+ * 'final val's.
+ *
+ * At the same time, it's no big deal to skip these things, because we
+ * won't reach the backend.
+ *
+ * We don't completely disable this phase under ScalaDoc mostly because
+ * we want to keep the addition of `JSType` annotations, so that they
+ * appear in the doc.
+ *
+ * Preparing exports, however, is a pure waste of time, which we cannot
+ * do properly anyway because of the aforementioned limitation.
+ */
+ private def forScaladoc = global.isInstanceOf[doc.ScaladocGlobal]
+
+ /** Whether to check that we have proper literals in some crucial places. */
+ private def shouldCheckLiterals = !forScaladoc
+
+ /** Whether to check and prepare exports. */
+ private def shouldPrepareExports = !forScaladoc
+
+ /** DefDefs in class templates that export methods to JavaScript */
+ private val exporters = mutable.Map.empty[Symbol, mutable.ListBuffer[Tree]]
+
+ override def transform(tree: Tree): Tree = {
+ tree match {
+ case tree: MemberDef => transformMemberDef(tree)
+ case tree: Template => transformTemplateTree(tree)
+ case _ => transformStatOrExpr(tree)
+ }
+ }
+
+ private def transformMemberDef(tree: MemberDef): Tree = {
+ val sym = moduleToModuleClass(tree.symbol)
+
+ checkInternalAnnotations(sym)
+
+ /* Checks related to @js.native:
+ * - if @js.native, verify that it is allowed in this context, and if
+ * yes, compute and store the JS native load spec
+ * - if not @js.native, verify that we do not use any other annotation
+ * reserved for @js.native members (namely, JS native load spec annots)
+ */
+ val isJSNative = sym.hasAnnotation(JSNativeAnnotation)
+ if (isJSNative)
+ checkJSNativeDefinition(tree.pos, sym)
+ else
+ checkJSNativeSpecificAnnotsOnNonJSNative(tree)
+
+ checkJSCallingConventionAnnots(sym)
+
+ // @unchecked needed because MemberDef is not marked `sealed`
+ val transformedTree: Tree = (tree: @unchecked) match {
+ case tree: ImplDef =>
+ if (shouldPrepareExports) {
+ val exports = genExport(sym)
+ if (exports.nonEmpty)
+ exporters.getOrElseUpdate(sym.owner, mutable.ListBuffer.empty) ++= exports
+ }
+
+ if ((enclosingOwner is OwnerKind.JSNonNative) && sym.owner.isTrait && !sym.isTrait) {
+ reporter.error(tree.pos,
+ "Non-native JS traits cannot contain inner classes or objects")
+ }
+
+ if (isJSAny(sym))
+ transformJSImplDef(tree)
+ else
+ transformScalaImplDef(tree)
+
+ case tree: ValOrDefDef =>
+ /* Prepare exports for methods, local defs and local variables.
+ * Avoid *fields* (non-local non-method) because they all have a
+ * corresponding getter, which is the one that handles exports.
+ * (Note that local-to-block can never have exports, but the error
+ * messages for that are handled by genExportMember).
+ */
+ if (shouldPrepareExports && (sym.isMethod || sym.isLocalToBlock)) {
+ val exports = genExport(sym)
+ if (exports.nonEmpty) {
+ val target =
+ if (sym.isConstructor) sym.owner.owner
+ else sym.owner
+
+ exporters.getOrElseUpdate(target, mutable.ListBuffer.empty) ++= exports
+ }
+ }
+
+ if (sym.isLocalToBlock) {
+ super.transform(tree)
+ } else if (isJSNative) {
+ transformJSNativeValOrDefDef(tree)
+ } else if (enclosingOwner is OwnerKind.JSType) {
+ val fixedTree = tree match {
+ case tree: DefDef => fixPublicBeforeTyper(tree)
+ case _ => tree
+ }
+ transformValOrDefDefInJSType(fixedTree)
+ } else {
+ transformScalaValOrDefDef(tree)
+ }
+
+ case _:TypeDef | _:PackageDef =>
+ super.transform(tree)
+ }
+
+ /* Give tree.symbol, not sym, so that for modules it is the module
+ * symbol, not the module class symbol.
+ *
+ * #1899 This must be done *after* transforming the member def tree,
+ * because fixPublicBeforeTyper must have run.
+ */
+ markExposedIfRequired(tree.symbol)
+
+ transformedTree
+ }
+
+ private def transformScalaImplDef(tree: ImplDef): Tree = {
+ val sym = moduleToModuleClass(tree.symbol)
+ val isModuleDef = tree.isInstanceOf[ModuleDef]
+
+ // In native JS things, only js.Any stuff is allowed
+ if (enclosingOwner is OwnerKind.JSNative) {
+ /* We have to allow synthetic companion objects here, as they get
+ * generated when a nested native JS class has default arguments in
+ * its constructor (see #1891).
+ */
+ if (!sym.isSynthetic) {
+ reporter.error(tree.pos,
+ "Native JS traits, classes and objects cannot contain inner " +
+ "Scala traits, classes or objects (i.e., not extending js.Any)")
+ }
+ }
+
+ if (sym == UnionClass)
+ sym.addAnnotation(JSTypeAnnot)
+
+ val kind = if (sym.isSubClass(ScalaEnumClass)) {
+ if (isModuleDef) OwnerKind.EnumMod
+ else if (sym == ScalaEnumClass) OwnerKind.EnumImpl
+ else OwnerKind.EnumClass
+ } else {
+ if (isModuleDef) OwnerKind.NonEnumScalaMod
+ else OwnerKind.NonEnumScalaClass
+ }
+ enterOwner(kind) {
+ super.transform(tree)
+ }
+ }
+
+ private def transformScalaValOrDefDef(tree: ValOrDefDef): Tree = {
+ tree match {
+ // Catch ValDefs in enumerations with simple calls to Value
+ case ValDef(mods, name, tpt, ScalaEnumValue.NoName(optPar))
+ if anyEnclosingOwner is OwnerKind.Enum =>
+ val nrhs = ScalaEnumValName(tree.symbol.owner, tree.symbol, optPar)
+ treeCopy.ValDef(tree, mods, name, transform(tpt), nrhs)
+
+ // Exporter generation
+ case _ =>
+ super.transform(tree)
+ }
+ }
+
+ private def transformTemplateTree(tree: Template): Template = {
+ val Template(parents, self, body) = tree
+
+ /* Do not transform `self`. We do not need to perform any checks on
+ * it (#3998).
+ */
+ val transformedParents = parents.map(transform(_))
+ val nonTransformedSelf = self
+ val transformedBody = body.map(transform(_))
+
+ val clsSym = tree.symbol.owner
+
+ // Check that @JSExportStatic fields come first
+ if (clsSym.isModuleClass) { // quick check to avoid useless work
+ var foundStatOrNonStaticVal: Boolean = false
+ for (tree <- transformedBody) {
+ tree match {
+ case vd: ValDef if vd.symbol.hasAnnotation(JSExportStaticAnnotation) =>
+ if (foundStatOrNonStaticVal) {
+ reporter.error(vd.pos,
+ "@JSExportStatic vals and vars must be defined before " +
+ "any other val/var, and before any constructor " +
+ "statement.")
+ }
+ case vd: ValDef if !vd.symbol.isLazy =>
+ foundStatOrNonStaticVal = true
+ case _: MemberDef =>
+ case _ =>
+ foundStatOrNonStaticVal = true
+ }
+ }
+ }
+
+ // Add exports to the template, if there are any
+ val transformedBodyWithExports = exporters.get(clsSym).fold {
+ transformedBody
+ } { exports =>
+ assert(exports.nonEmpty, s"found empty exporters for $clsSym" )
+
+ // Reset interface flag: We're adding non-empty methods.
+ clsSym.resetFlag(Flags.INTERFACE)
+
+ transformedBody ::: exports.toList
+ }
+
+ treeCopy.Template(tree, transformedParents, nonTransformedSelf,
+ transformedBodyWithExports)
+ }
+
+ private def transformStatOrExpr(tree: Tree): Tree = {
+ tree match {
+ /* Anonymous function, need to check that it is not used as a SAM for a
+ * JS type, unless it is a JS function type.
+ * See #2921.
+ */
+ case tree: Function =>
+ // tpeSym is the type of the target SAM type (not the to-be-generated anonymous class)
+ val tpeSym = tree.tpe.typeSymbol
+ if (isJSAny(tpeSym)) {
+ def reportError(reasonAndExplanation: String): Unit = {
+ reporter.error(tree.pos,
+ "Using an anonymous function as a SAM for the JavaScript " +
+ s"type ${tpeSym.fullNameString} is not allowed because " +
+ reasonAndExplanation)
+ }
+ if (!tpeSym.isTrait || tpeSym.superClass != JSFunctionClass) {
+ reportError(
+ "it is not a trait extending js.Function. " +
+ "Use an anonymous class instead.")
+ } else if (tpeSym.hasAnnotation(JSNativeAnnotation)) {
+ reportError(
+ "it is a native JS type. " +
+ "It is not possible to directly implement it.")
+ } else if (!JSCallingConvention.isCall(samOf(tree.tpe))) {
+ reportError(
+ "its single abstract method is not named `apply`. " +
+ "Use an anonymous class instead.")
+ }
+ }
+ super.transform(tree)
+
+ // Catch Select on Enumeration.Value we couldn't transform but need to
+ // we ignore the implementation of scala.Enumeration itself
+ case ScalaEnumValue.NoName(_) if noEnclosingOwner is OwnerKind.EnumImpl =>
+ reporter.warning(tree.pos,
+ """Couldn't transform call to Enumeration.Value.
+ |The resulting program is unlikely to function properly as this
+ |operation requires reflection.""".stripMargin)
+ super.transform(tree)
+
+ case ScalaEnumValue.NullName() if noEnclosingOwner is OwnerKind.EnumImpl =>
+ reporter.warning(tree.pos,
+ """Passing null as name to Enumeration.Value
+ |requires reflection at runtime. The resulting
+ |program is unlikely to function properly.""".stripMargin)
+ super.transform(tree)
+
+ case ScalaEnumVal.NoName(_) if noEnclosingOwner is OwnerKind.EnumImpl =>
+ reporter.warning(tree.pos,
+ """Calls to the non-string constructors of Enumeration.Val
+ |require reflection at runtime. The resulting
+ |program is unlikely to function properly.""".stripMargin)
+ super.transform(tree)
+
+ case ScalaEnumVal.NullName() if noEnclosingOwner is OwnerKind.EnumImpl =>
+ reporter.warning(tree.pos,
+ """Passing null as name to a constructor of Enumeration.Val
+ |requires reflection at runtime. The resulting
+ |program is unlikely to function properly.""".stripMargin)
+ super.transform(tree)
+
+ case tree if tree.symbol == ExecutionContext_global ||
+ tree.symbol == ExecutionContextImplicits_global =>
+ if (scalaJSOpts.warnGlobalExecutionContext) {
+ global.runReporting.warning(tree.pos,
+ """The global execution context in Scala.js is based on JS Promises (microtasks).
+ |Using it may prevent macrotasks (I/O, timers, UI rendering) from running reliably.
+ |
+ |Unfortunately, there is no way with ECMAScript only to implement a performant
+ |macrotask execution context (and hence Scala.js core does not contain one).
+ |
+ |We recommend you use: https://github.com/scala-js/scala-js-macrotask-executor
+ |Please refer to the README.md of that project for more details regarding
+ |microtask vs. macrotask execution contexts.
+ |
+ |If you do not care about macrotask fairness, you can silence this warning by:
+ |- Adding @nowarn("cat=other") (Scala >= 2.13.x only)
+ |- Setting the -P:scalajs:nowarnGlobalExecutionContext compiler option (Scala < 3.x.y only)
+ |- Using scala.scalajs.concurrent.JSExecutionContext.queue
+ | (the implementation of ExecutionContext.global in Scala.js) directly.
+ |
+ |If you do not care about performance, you can use
+ |scala.scalajs.concurrent.QueueExecutionContext.timeouts().
+ |It is based on setTimeout which makes it fair but slow (due to clamping).
+ """.stripMargin,
+ WarningCategory.Other, tree.symbol)
+ }
+ super.transform(tree)
+
+ // Validate js.constructorOf[T]
+ case TypeApply(ctorOfTree, List(tpeArg))
+ if ctorOfTree.symbol == JSPackage_constructorOf =>
+ validateJSConstructorOf(tree, tpeArg)
+ super.transform(tree)
+
+ /* Rewrite js.ConstructorTag.materialize[T] into
+ * runtime.newConstructorTag[T](js.constructorOf[T])
+ */
+ case TypeApply(ctorOfTree, List(tpeArg))
+ if ctorOfTree.symbol == JSConstructorTag_materialize =>
+ validateJSConstructorOf(tree, tpeArg)
+ typer.typed {
+ atPos(tree.pos) {
+ val ctorOf = gen.mkTypeApply(
+ gen.mkAttributedRef(JSPackage_constructorOf), List(tpeArg))
+ gen.mkMethodCall(Runtime_newConstructorTag,
+ List(tpeArg.tpe), List(ctorOf))
+ }
+ }
+
+ /* Rewrite js.dynamicImport[T](body) into
+ *
+ * runtime.dynamicImport[T](
+ * new DynamicImportThunk { def apply(): Any = body }
+ * )
+ */
+ case Apply(TypeApply(fun, List(tpeArg)), List(body))
+ if fun.symbol == JSPackage_dynamicImport =>
+ val pos = tree.pos
+
+ assert(currentOwner.isTerm, s"unexpected owner: $currentOwner at $pos")
+
+ val clsSym = currentOwner.newClass(tpnme.ANON_CLASS_NAME, pos)
+ clsSym.setInfo( // do not enter the symbol, owner is a term.
+ ClassInfoType(List(DynamicImportThunkClass.tpe), newScope, clsSym))
+
+ val ctorSym = clsSym.newClassConstructor(pos)
+ ctorSym.setInfoAndEnter(MethodType(Nil, clsSym.tpe))
+
+ val applySym = clsSym.newMethod(nme.apply)
+ applySym.setInfoAndEnter(MethodType(Nil, AnyTpe))
+
+ body.changeOwner(currentOwner -> applySym)
+ val newBody = atOwner(applySym)(transform(body))
+
+ typer.typed {
+ atPos(tree.pos) {
+ /* gen.mkSuperInitCall would be nicer, but that doesn't get past the typer:
+ *
+ * scala.reflect.internal.Types$TypeError:
+ * stable identifier required, but $anon.super. found.
+ */
+ val superCtorCall = gen.mkMethodCall(
+ Super(clsSym, tpnme.EMPTY),
+ DynamicImportThunkClass.primaryConstructor, Nil, Nil)
+
+ // class $anon extends DynamicImportThunk
+ val clsDef = ClassDef(clsSym, List(
+ // def () = { super.(); () }
+ DefDef(ctorSym, gen.mkUnitBlock(superCtorCall)),
+ // def apply(): Any = body
+ DefDef(applySym, newBody)))
+
+ /* runtime.DynamicImport[A]({
+ * class $anon ...
+ * new $anon
+ * })
+ */
+ Apply(TypeApply(gen.mkAttributedRef(Runtime_dynamicImport),
+ List(tpeArg)), List(Block(clsDef, New(clsSym))))
+ }
+ }
+
+ /* Catch calls to Predef.classOf[T]. These should NEVER reach this phase
+ * but unfortunately do. In normal cases, the typer phase replaces these
+ * calls by a literal constant of the given type. However, when we compile
+ * the scala library itself and Predef.scala is in the sources, this does
+ * not happen.
+ *
+ * The trees reach this phase under the form:
+ *
+ * scala.this.Predef.classOf[T]
+ *
+ * or, as of Scala 2.12.0, as:
+ *
+ * scala.Predef.classOf[T]
+ *
+ * or so it seems, at least.
+ *
+ * If we encounter such a tree, depending on the plugin options, we fail
+ * here or silently fix those calls.
+ */
+ case TypeApply(classOfTree @ Select(predef, nme.classOf), List(tpeArg))
+ if predef.symbol == PredefModule =>
+ if (scalaJSOpts.fixClassOf) {
+ // Replace call by literal constant containing type
+ if (typer.checkClassOrModuleType(tpeArg)) {
+ typer.typed { Literal(Constant(tpeArg.tpe.dealias.widen)) }
+ } else {
+ reporter.error(tpeArg.pos, s"Type ${tpeArg} is not a class type")
+ EmptyTree
+ }
+ } else {
+ reporter.error(classOfTree.pos,
+ """This classOf resulted in an unresolved classOf in the jscode
+ |phase. This is most likely a bug in the Scala compiler. ScalaJS
+ |is probably able to work around this bug. Enable the workaround
+ |by passing the fixClassOf option to the plugin.""".stripMargin)
+ EmptyTree
+ }
+
+ // Compile-time errors and warnings for js.Dynamic.literal
+ case Apply(Apply(fun, nameArgs), args)
+ if fun.symbol == JSDynamicLiteral_applyDynamic ||
+ fun.symbol == JSDynamicLiteral_applyDynamicNamed =>
+ // Check that the first argument list is a constant string "apply"
+ nameArgs match {
+ case List(Literal(Constant(s: String))) =>
+ if (s != "apply") {
+ reporter.error(tree.pos,
+ s"js.Dynamic.literal does not have a method named $s")
+ }
+ case _ =>
+ reporter.error(tree.pos,
+ s"js.Dynamic.literal.${tree.symbol.name} may not be " +
+ "called directly")
+ }
+
+ // Warn for known duplicate property names
+ val knownPropNames = mutable.Set.empty[String]
+ for (arg <- args) {
+ def processPropName(propNameTree: Tree): Unit = {
+ propNameTree match {
+ case Literal(Constant(propName: String)) =>
+ if (!knownPropNames.add(propName)) {
+ reporter.warning(propNameTree.pos,
+ s"""Duplicate property "$propName" shadows a """ +
+ "previously defined one")
+ }
+ case _ =>
+ // ignore
+ }
+ }
+ arg match {
+ case Apply(fun, List(propNameTree, _))
+ if fun.symbol == Tuple2_apply =>
+ processPropName(propNameTree)
+ case Apply(fun @ TypeApply(Select(receiver, nme.MINGT), _), _)
+ if currentRun.runDefinitions.isArrowAssoc(fun.symbol) =>
+ receiver match {
+ case Apply(TypeApply(Select(predef, jsnme.ArrowAssoc), _),
+ List(propNameTree))
+ if predef.symbol == PredefModule =>
+ processPropName(propNameTree)
+ case _ =>
+ // ignore
+ }
+ case _ =>
+ // ignore
+ }
+ }
+
+ super.transform(tree)
+
+ case _ => super.transform(tree)
+ }
+ }
+
+ private def validateJSConstructorOf(tree: Tree, tpeArg: Tree): Unit = {
+ val classValue = try {
+ typer.typedClassOf(tree, tpeArg)
+ } catch {
+ case typeError: TypeError =>
+ reporter.error(typeError.pos, typeError.msg)
+ EmptyTree
+ }
+
+ if (classValue != EmptyTree) {
+ val Literal(classConstant) = classValue
+ val tpe = classConstant.typeValue.dealiasWiden
+ val typeSym = tpe.typeSymbol
+ if (typeSym.isTrait || typeSym.isModuleClass) {
+ reporter.error(tpeArg.pos,
+ s"non-trait class type required but $tpe found")
+ }
+ }
+ }
+
+ /** Performs checks and rewrites specific to classes / objects extending
+ * js.Any.
+ */
+ private def transformJSImplDef(implDef: ImplDef): Tree = {
+ val sym = moduleToModuleClass(implDef.symbol)
+
+ sym.addAnnotation(JSTypeAnnot)
+
+ val isJSNative = sym.hasAnnotation(JSNativeAnnotation)
+
+ // Forbid @EnableReflectiveInstantiation on JS types
+ sym.getAnnotation(EnableReflectiveInstantiationAnnotation).foreach {
+ annot =>
+ reporter.error(annot.pos,
+ "@EnableReflectiveInstantiation cannot be used on types " +
+ "extending js.Any.")
+ }
+
+ // Forbid package objects that extends js.Any
+ if (sym.isPackageObjectClass)
+ reporter.error(implDef.pos, "Package objects may not extend js.Any.")
+
+ // Check that we do not have a case modifier
+ if (implDef.mods.hasFlag(Flag.CASE)) {
+ reporter.error(implDef.pos, "Classes and objects extending " +
+ "js.Any may not have a case modifier")
+ }
+
+ // Check the parents
+ for (parent <- sym.info.parents) {
+ parent.typeSymbol match {
+ case AnyRefClass | ObjectClass =>
+ // AnyRef is valid, except for non-native JS traits
+ if (!isJSNative && !sym.isTrait) {
+ reporter.error(implDef.pos,
+ "Non-native JS classes and objects cannot directly extend " +
+ "AnyRef. They must extend a JS class (native or not).")
+ }
+ case parentSym if isJSAny(parentSym) =>
+ // A non-native JS type cannot extend a native JS trait
+ // Otherwise, extending a JS type is valid
+ if (!isJSNative && parentSym.isTrait &&
+ parentSym.hasAnnotation(JSNativeAnnotation)) {
+ reporter.error(implDef.pos,
+ "Non-native JS types cannot directly extend native JS " +
+ "traits.")
+ }
+ case DynamicClass =>
+ /* We have to allow scala.Dynamic to be able to define js.Dynamic
+ * and similar constructs.
+ * This causes the unsoundness filed as #1385.
+ */
+ ()
+ case parentSym =>
+ /* This is a Scala class or trait other than AnyRef and Dynamic,
+ * which is never valid.
+ */
+ reporter.error(implDef.pos,
+ s"${sym.nameString} extends ${parentSym.fullName} " +
+ "which does not extend js.Any.")
+ }
+ }
+
+ // Checks for non-native JS stuff
+ if (!isJSNative) {
+ // It cannot be in a native JS class or trait
+ if (enclosingOwner is OwnerKind.JSNativeClass) {
+ reporter.error(implDef.pos,
+ "Native JS classes and traits cannot contain non-native JS " +
+ "classes, traits or objects")
+ }
+
+ // Unless it is a trait, it cannot be in a native JS object
+ if (!sym.isTrait && (enclosingOwner is OwnerKind.JSNativeMod)) {
+ reporter.error(implDef.pos,
+ "Native JS objects cannot contain inner non-native JS " +
+ "classes or objects")
+ }
+
+ // Local JS classes cannot be abstract (implementation restriction)
+ if (!sym.isTrait && sym.isAbstractClass && sym.isLocalToBlock) {
+ reporter.error(implDef.pos,
+ "Implementation restriction: local JS classes cannot be abstract")
+ }
+ }
+
+ // Check for consistency of JS semantics across overriding
+ for (overridingPair <- new overridingPairs.Cursor(sym).iterator) {
+ val low = overridingPair.low
+ val high = overridingPair.high
+
+ if (low.isType || high.isType) {
+ /* #4375 Do nothing if either is a type, and let refchecks take care
+ * of it.
+ * The case where one is a type and the other is not should never
+ * happen, because they would live in different namespaces and
+ * therefore not override each other. However, if that should still
+ * happen for some reason, rechecks should take care of it as well.
+ */
+ } else {
+ def errorPos = {
+ if (sym == low.owner) low.pos
+ else if (sym == high.owner) high.pos
+ else sym.pos
+ }
+
+ def memberDefString(membSym: Symbol): String =
+ membSym.defStringSeenAs(sym.thisType.memberType(membSym))
+
+ // Check for overrides with different JS names - issue #1983
+ if (jsInterop.JSCallingConvention.of(low) != jsInterop.JSCallingConvention.of(high)) {
+ val msg = {
+ def memberDefStringWithCallingConvention(membSym: Symbol) = {
+ memberDefString(membSym) +
+ membSym.locationString + " called from JS as " +
+ JSCallingConvention.of(membSym).displayName
+ }
+ "A member of a JS class is overriding another member with a different JS calling convention.\n\n" +
+ memberDefStringWithCallingConvention(low) + "\n" +
+ " is conflicting with\n" +
+ memberDefStringWithCallingConvention(high) + "\n"
+ }
+
+ reporter.error(errorPos, msg)
+ }
+
+ /* Cannot override a non-@JSOptional with an @JSOptional. Unfortunately
+ * at this point the symbols do not have @JSOptional yet, so we need
+ * to detect whether it would be applied.
+ */
+ if (!isJSNative) {
+ def isJSOptional(sym: Symbol): Boolean = {
+ sym.owner.isTrait && !sym.isDeferred && !sym.isConstructor &&
+ !sym.owner.hasAnnotation(JSNativeAnnotation)
+ }
+
+ if (isJSOptional(low) && !(high.isDeferred || isJSOptional(high))) {
+ reporter.error(errorPos,
+ s"Cannot override concrete ${memberDefString(high)} from " +
+ s"${high.owner.fullName} in a non-native JS trait.")
+ }
+ }
+ }
+ }
+
+ val kind = {
+ if (!isJSNative) {
+ if (sym.isModuleClass) OwnerKind.JSMod
+ else OwnerKind.JSClass
+ } else {
+ if (sym.isModuleClass) OwnerKind.JSNativeMod
+ else OwnerKind.JSNativeClass
+ }
+ }
+ enterOwner(kind) {
+ super.transform(implDef)
+ }
+ }
+
+ private def checkJSNativeDefinition(pos: Position, sym: Symbol): Unit = {
+ // Check if we may have a JS native here
+ if (sym.isLocalToBlock) {
+ reporter.error(pos,
+ "@js.native is not allowed on local definitions")
+ } else if (!sym.isClass && (anyEnclosingOwner is (OwnerKind.ScalaClass | OwnerKind.JSType))) {
+ reporter.error(pos,
+ "@js.native vals and defs can only appear in static Scala objects")
+ } else if (sym.isClass && !isJSAny(sym)) {
+ reporter.error(pos,
+ "Classes, traits and objects not extending js.Any may not have " +
+ "an @js.native annotation")
+ } else if (anyEnclosingOwner is OwnerKind.ScalaClass) {
+ reporter.error(pos,
+ "Scala traits and classes may not have native JS members")
+ } else if (enclosingOwner is OwnerKind.JSNonNative) {
+ reporter.error(pos,
+ "non-native JS classes, traits and objects may not have " +
+ "native JS members")
+ } else if (!sym.isTrait) {
+ /* Compute the loading spec now, before `flatten` destroys the name.
+ * We store it in a global map.
+ */
+ val optLoadSpec = checkAndComputeJSNativeLoadSpecOf(pos, sym)
+ for (loadSpec <- optLoadSpec)
+ jsInterop.storeJSNativeLoadSpec(sym, loadSpec)
+ } else {
+ assert(sym.isTrait, sym) // just tested in the previous `if`
+ for (annot <- sym.annotations) {
+ val annotSym = annot.symbol
+ if (JSNativeLoadingSpecAnnots.contains(annotSym)) {
+ reporter.error(annot.pos,
+ s"Traits may not have an @${annotSym.nameString} annotation.")
+ }
+ }
+ }
+ }
+
+ private def checkAndComputeJSNativeLoadSpecOf(pos: Position,
+ sym: Symbol): Option[JSNativeLoadSpec] = {
+ import JSNativeLoadSpec._
+
+ def makeGlobalRefNativeLoadSpec(globalRef: String,
+ path: List[String]): Global = {
+ val validatedGlobalRef = if (!JSGlobalRef.isValidJSGlobalRefName(globalRef)) {
+ reporter.error(pos,
+ "The name of a JS global variable must be a valid JS " +
+ s"identifier (got '$globalRef')")
+ "erroneous"
+ } else {
+ globalRef
+ }
+ Global(validatedGlobalRef, path)
+ }
+
+ if (enclosingOwner is OwnerKind.JSNative) {
+ /* We cannot get here for @js.native vals and defs. That would mean we
+ * have an @js.native val/def inside a JavaScript type, which is not
+ * allowed and already caught in checkJSNativeDefinition().
+ */
+ assert(sym.isClass,
+ s"undetected @js.native val or def ${sym.fullName} inside JS type at $pos")
+
+ for (annot <- sym.annotations) {
+ val annotSym = annot.symbol
+
+ if (JSNativeLoadingSpecAnnots.contains(annotSym)) {
+ reporter.error(annot.pos,
+ "Nested JS classes and objects cannot " +
+ s"have an @${annotSym.nameString} annotation.")
+ }
+ }
+
+ if (sym.owner.isStaticOwner) {
+ for (annot <- sym.annotations) {
+ if (annot.symbol == JSNameAnnotation &&
+ annot.args.head.tpe.typeSymbol != StringClass) {
+ reporter.error(annot.pos,
+ "Implementation restriction: @JSName with a js.Symbol is " +
+ "not supported on nested native classes and objects")
+ }
+ }
+
+ val jsName = jsInterop.jsNameOf(sym) match {
+ case JSName.Literal(jsName) => jsName
+ case JSName.Computed(_) => "" // compile error above
+ }
+
+ val ownerLoadSpec = jsInterop.jsNativeLoadSpecOfOption(sym.owner)
+ val loadSpec = ownerLoadSpec match {
+ case None =>
+ // The owner is a JSGlobalScope
+ makeGlobalRefNativeLoadSpec(jsName, Nil)
+ case Some(Global(globalRef, path)) =>
+ Global(globalRef, path :+ jsName)
+ case Some(Import(module, path)) =>
+ Import(module, path :+ jsName)
+ case Some(ImportWithGlobalFallback(
+ Import(module, modulePath), Global(globalRef, globalPath))) =>
+ ImportWithGlobalFallback(
+ Import(module, modulePath :+ jsName),
+ Global(globalRef, globalPath :+ jsName))
+ }
+ Some(loadSpec)
+ } else {
+ None
+ }
+ } else {
+ def parsePath(pathName: String): List[String] =
+ pathName.split('.').toList
+
+ def parseGlobalPath(pathName: String): Global = {
+ val globalRef :: path = parsePath(pathName)
+ makeGlobalRefNativeLoadSpec(globalRef, path)
+ }
+
+ checkAndGetJSNativeLoadingSpecAnnotOf(pos, sym) match {
+ case Some(annot) if annot.symbol == JSGlobalScopeAnnotation =>
+ if (!sym.isModuleClass) {
+ reporter.error(annot.pos,
+ "@JSGlobalScope can only be used on native JS objects (with @js.native).")
+ }
+ None
+
+ case Some(annot) if annot.symbol == JSGlobalAnnotation =>
+ if (shouldCheckLiterals)
+ checkJSGlobalLiteral(annot)
+ val pathName = annot.stringArg(0).getOrElse {
+ val symTermName = sym.name.dropModule.toTermName.dropLocal
+ if (symTermName == nme.apply) {
+ reporter.error(annot.pos,
+ "Native JS definitions named 'apply' must have an explicit name in @JSGlobal")
+ } else if (symTermName.endsWith(nme.SETTER_SUFFIX)) {
+ reporter.error(annot.pos,
+ "Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal")
+ }
+ jsInterop.defaultJSNameOf(sym)
+ }
+ Some(parseGlobalPath(pathName))
+
+ case Some(annot) if annot.symbol == JSImportAnnotation =>
+ if (shouldCheckLiterals)
+ checkJSImportLiteral(annot)
+ val module = annot.stringArg(0).getOrElse {
+ "" // an error is reported by checkJSImportLiteral in this case
+ }
+ val path = annot.stringArg(1).fold {
+ if (annot.args.size < 2) {
+ val symTermName = sym.name.dropModule.toTermName.dropLocal
+ if (symTermName == nme.apply) {
+ reporter.error(annot.pos,
+ "Native JS definitions named 'apply' must have an explicit name in @JSImport")
+ } else if (symTermName.endsWith(nme.SETTER_SUFFIX)) {
+ reporter.error(annot.pos,
+ "Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport")
+ }
+ parsePath(jsInterop.defaultJSNameOf(sym))
+ } else {
+ Nil
+ }
+ } { pathName =>
+ parsePath(pathName)
+ }
+ val importSpec = Import(module, path)
+ val loadSpec = annot.stringArg(2).fold[JSNativeLoadSpec] {
+ importSpec
+ } { globalPathName =>
+ ImportWithGlobalFallback(importSpec,
+ parseGlobalPath(globalPathName))
+ }
+ Some(loadSpec)
+
+ case Some(annot) =>
+ abort(s"checkAndGetJSNativeLoadingSpecAnnotOf returned unexpected annotation $annot")
+
+ case None =>
+ /* We already emitted an error. Invent something not to cause
+ * cascading errors.
+ */
+ Some(JSNativeLoadSpec.Global("erroneous", Nil))
+ }
+ }
+ }
+
+ /** Verify a ValOrDefDef that is annotated with `@js.native`. */
+ private def transformJSNativeValOrDefDef(tree: ValOrDefDef): ValOrDefDef = {
+ val sym = tree.symbol
+
+ if (sym.isLazy || jsInterop.isJSSetter(sym)) {
+ reporter.error(tree.pos,
+ "@js.native is not allowed on vars, lazy vals and setter defs")
+ }
+
+ if (!sym.isAccessor)
+ checkRHSCallsJSNative(tree, "@js.native members")
+
+ if (sym.isMethod) { // i.e., it is not a field
+ for (overridden <- sym.allOverriddenSymbols.headOption) {
+ val verb = if (overridden.isDeferred) "implement" else "override"
+ reporter.error(tree.pos,
+ s"An @js.native member cannot $verb the inherited member " +
+ overridden.fullName)
+ }
+ }
+
+ tree
+ }
+
+ /** Verify a ValOrDefDef inside a js.Any */
+ private def transformValOrDefDefInJSType(tree: ValOrDefDef): Tree = {
+ val sym = tree.symbol
+
+ assert(!sym.isLocalToBlock, s"$tree at ${tree.pos}")
+
+ sym.name match {
+ case nme.apply if !sym.hasAnnotation(JSNameAnnotation) && jsInterop.isJSGetter(sym) =>
+ reporter.error(sym.pos, "A member named apply represents function " +
+ "application in JavaScript. A parameterless member should be " +
+ "exported as a property. You must add @JSName(\"apply\")")
+
+ case jsInterop.JSUnaryOpMethodName(_, _) =>
+ if (sym.hasAnnotation(JSOperatorAnnotation)) {
+ if (sym.paramss.map(_.size).sum != 0) {
+ reporter.error(tree.pos,
+ s"@JSOperator methods with the name '${sym.nameString}' may not have any parameters")
+ }
+ } else if (!sym.annotations.exists(annot => JSCallingConventionAnnots.contains(annot.symbol))) {
+ reporter.warning(tree.pos,
+ s"Method '${sym.nameString}' should have an explicit @JSName or @JSOperator annotation " +
+ "because its name is one of the JavaScript operators")
+ }
+
+ case jsInterop.JSBinaryOpMethodName(_, _) =>
+ if (sym.hasAnnotation(JSOperatorAnnotation)) {
+ if (sym.paramss.map(_.size).sum != 1) {
+ reporter.error(tree.pos,
+ s"@JSOperator methods with the name '${sym.nameString}' must have exactly one parameter")
+ }
+ } else if (!sym.annotations.exists(annot => JSCallingConventionAnnots.contains(annot.symbol))) {
+ reporter.warning(tree.pos,
+ s"Method '${sym.nameString}' should have an explicit @JSName or @JSOperator annotation " +
+ "because its name is one of the JavaScript operators")
+ }
+
+ case _ if sym.hasAnnotation(JSOperatorAnnotation) =>
+ reporter.error(tree.pos,
+ s"@JSOperator cannot be used on a method with the name '${sym.nameString}' " +
+ "because it is not one of the JavaScript operators")
+
+ case nme.equals_ if sym.tpe.matches(Any_equals.tpe) =>
+ reporter.warning(sym.pos, "Overriding equals in a JS class does " +
+ "not change how it is compared. To silence this warning, change " +
+ "the name of the method and optionally add @JSName(\"equals\").")
+
+ case nme.hashCode_ if sym.tpe.matches(Any_hashCode.tpe) =>
+ reporter.warning(sym.pos, "Overriding hashCode in a JS class does " +
+ "not change its hash code. To silence this warning, change " +
+ "the name of the method and optionally add @JSName(\"hashCode\").")
+
+ case _ =>
+ }
+
+ if (jsInterop.isJSSetter(sym))
+ checkSetterSignature(sym, tree.pos, exported = false)
+
+ if (enclosingOwner is OwnerKind.JSNonNative) {
+ JSCallingConvention.of(sym) match {
+ case JSCallingConvention.Property(_) => // checked above
+ case JSCallingConvention.Method(_) => // no checks needed
+
+ case JSCallingConvention.Call if !sym.isDeferred =>
+ /* Concrete `def apply` methods are normally rejected because we
+ * cannot implement them in JavaScript. However, we do allow a
+ * synthetic `apply` method if it is in a SAM for a JS function
+ * type.
+ */
+ val isJSFunctionSAM =
+ sym.isSynthetic && sym.owner.isAnonymousFunction
+ if (!isJSFunctionSAM) {
+ reporter.error(sym.pos,
+ "A non-native JS class cannot declare a concrete method " +
+ "named `apply` without `@JSName`")
+ }
+
+ case JSCallingConvention.Call => // if sym.isDeferred
+ /* Allow an abstract `def apply` only if the owner is a plausible
+ * JS function SAM trait.
+ */
+ val owner = sym.owner
+ val isPlausibleJSFunctionType = {
+ owner.isTrait &&
+ owner.superClass == JSFunctionClass &&
+ samOf(owner.toTypeConstructor) == sym
+ }
+ if (!isPlausibleJSFunctionType) {
+ reporter.error(sym.pos,
+ "A non-native JS type can only declare an abstract " +
+ "method named `apply` without `@JSName` if it is the " +
+ "SAM of a trait that extends js.Function")
+ }
+
+ case JSCallingConvention.BracketAccess =>
+ reporter.error(tree.pos,
+ "@JSBracketAccess is not allowed in non-native JS classes")
+
+ case JSCallingConvention.BracketCall =>
+ reporter.error(tree.pos,
+ "@JSBracketCall is not allowed in non-native JS classes")
+
+ case JSCallingConvention.UnaryOp(_) =>
+ reporter.error(sym.pos,
+ "A non-native JS class cannot declare a method " +
+ "named like a unary operation without `@JSName`")
+
+ case JSCallingConvention.BinaryOp(_) =>
+ reporter.error(sym.pos,
+ "A non-native JS class cannot declare a method " +
+ "named like a binary operation without `@JSName`")
+ }
+ } else {
+ def checkNoDefaultOrRepeated(subject: String) = {
+ for (param <- sym.paramss.flatten) {
+ if (isScalaRepeatedParamType(param.tpe)) {
+ reporter.error(param.pos, s"$subject may not have repeated parameters")
+ } else if (param.isParamWithDefault) {
+ reporter.error(param.pos, s"$subject may not have default parameters")
+ }
+ }
+ }
+
+ JSCallingConvention.of(sym) match {
+ case JSCallingConvention.Property(_) => // checked above
+ case JSCallingConvention.Method(_) => // no checks needed
+ case JSCallingConvention.Call => // no checks needed
+ case JSCallingConvention.UnaryOp(_) => // no checks needed
+
+ case JSCallingConvention.BinaryOp(_) =>
+ checkNoDefaultOrRepeated("methods representing binary operations")
+
+ case JSCallingConvention.BracketAccess =>
+ val paramCount = sym.paramss.map(_.size).sum
+ if (paramCount != 1 && paramCount != 2) {
+ reporter.error(tree.pos,
+ "@JSBracketAccess methods must have one or two parameters")
+ } else if (paramCount == 2 &&
+ sym.tpe.finalResultType.typeSymbol != UnitClass) {
+ reporter.error(tree.pos,
+ "@JSBracketAccess methods with two parameters must return Unit")
+ }
+
+ checkNoDefaultOrRepeated("@JSBracketAccess methods")
+
+ case JSCallingConvention.BracketCall =>
+ // JS bracket calls must have at least one non-repeated parameter
+ sym.tpe.paramss match {
+ case (param :: _) :: _ if !isScalaRepeatedParamType(param.tpe) =>
+ // ok
+ case _ =>
+ reporter.error(tree.pos, "@JSBracketCall methods must have at " +
+ "least one non-repeated parameter")
+ }
+ }
+ }
+
+ if (sym.hasAnnotation(NativeAttr)) {
+ // Native methods are not allowed
+ reporter.error(tree.pos, "Methods in a js.Any may not be @native")
+ }
+
+ /* In native JS types, there should not be any private member, except
+ * private[this] constructors.
+ */
+ if ((enclosingOwner is OwnerKind.JSNative) && isPrivateMaybeWithin(sym)) {
+ // Necessary for `private[this] val/var
+ def isFieldPrivateThis: Boolean = {
+ sym.isPrivateThis &&
+ !sym.isParamAccessor &&
+ !sym.owner.info.decls.exists(s => s.isGetter && s.accessed == sym)
+ }
+
+ if (sym.isClassConstructor) {
+ if (!sym.isPrivateThis) {
+ reporter.error(sym.pos,
+ "Native JS classes may not have private constructors. " +
+ "Use `private[this]` to declare an internal constructor.")
+ }
+ } else if (sym.isMethod || isFieldPrivateThis) {
+ reporter.error(tree.pos,
+ "Native JS classes may not have private members. " +
+ "Use a public member in a private facade instead.")
+ }
+ }
+
+ if (enclosingOwner is OwnerKind.JSNonNative) {
+ // Private methods cannot be overloaded
+ if (sym.isMethod && isPrivateMaybeWithin(sym)) {
+ val alts = sym.owner.info.member(sym.name).filter(_.isMethod)
+ if (alts.isOverloaded) {
+ reporter.error(tree.pos,
+ "Private methods in non-native JS classes cannot be " +
+ "overloaded. Use different names instead.")
+ }
+ }
+
+ // private[Scope] methods must be final
+ if (sym.isMethod && (sym.hasAccessBoundary && !sym.isProtected) &&
+ !sym.isFinal && !sym.isClassConstructor) {
+ reporter.error(tree.pos,
+ "Qualified private members in non-native JS classes " +
+ "must be final")
+ }
+
+ // Traits must be pure interfaces, except for js.undefined members
+ if (sym.owner.isTrait && sym.isTerm && !sym.isConstructor) {
+ if (sym.isMethod && isPrivateMaybeWithin(sym)) {
+ reporter.error(tree.pos,
+ "A non-native JS trait cannot contain private members")
+ } else if (sym.isLazy) {
+ reporter.error(tree.pos,
+ "A non-native JS trait cannot contain lazy vals")
+ } else if (!sym.isDeferred) {
+ /* Tell the back-end not emit this thing. In fact, this only
+ * matters for mixed-in members created from this member.
+ */
+ sym.addAnnotation(JSOptionalAnnotation)
+
+ // For non-accessor methods, check that they do not have parens
+ if (sym.isMethod && !sym.isAccessor) {
+ sym.tpe match {
+ case _: NullaryMethodType =>
+ // ok
+ case PolyType(_, _: NullaryMethodType) =>
+ // ok
+ case _ =>
+ reporter.error(tree.rhs.pos,
+ "In non-native JS traits, defs with parentheses " +
+ "must be abstract.")
+ }
+ }
+
+ // Check that the right-hand-side is `js.undefined`.
+ if (!sym.isSetter) {
+ // Check that the tree's body is `js.undefined`
+ tree.rhs match {
+ case sel: Select if sel.symbol == JSPackage_undefined =>
+ // ok
+ case _ =>
+ if (sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM)) {
+ reporter.error(tree.rhs.pos,
+ "Members of non-native JS traits may not have default " +
+ "parameters unless their default is `js.undefined`.")
+ } else {
+ reporter.error(tree.rhs.pos,
+ "Members of non-native JS traits must either be " +
+ "abstract, or their right-hand-side must be " +
+ "`js.undefined`.")
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (sym.isPrimaryConstructor || sym.isValueParameter ||
+ sym.isParamWithDefault || sym.isAccessor ||
+ sym.isParamAccessor || sym.isDeferred || sym.isSynthetic ||
+ (enclosingOwner is OwnerKind.JSNonNative)) {
+ /* Ignore (i.e. allow) primary ctor, parameters, default parameter
+ * getters, accessors, param accessors, abstract members, synthetic
+ * methods (to avoid double errors with case classes, e.g. generated
+ * copy method), and any member of a non-native JS class/trait.
+ */
+ } else if (jsPrimitives.isJavaScriptPrimitive(sym)) {
+ // No check for primitives. We trust our own standard library.
+ } else if (sym.isConstructor) {
+ // Force secondary ctor to have only a call to the primary ctor inside
+ tree.rhs match {
+ case Block(List(Apply(trg, _)), Literal(Constant(())))
+ if trg.symbol.isPrimaryConstructor &&
+ trg.symbol.owner == sym.owner =>
+ // everything is fine here
+ case _ =>
+ reporter.error(tree.pos, "A secondary constructor of a class " +
+ "extending js.Any may only call the primary constructor")
+ }
+ } else {
+ // Check that the tree's rhs is exactly `= js.native`
+ checkRHSCallsJSNative(tree, "Concrete members of JS native types")
+ }
+
+ super.transform(tree)
+ }
+
+ private def checkRHSCallsJSNative(tree: ValOrDefDef,
+ longKindStr: String): Unit = {
+ // Check that the rhs is exactly `= js.native`
+ tree.rhs match {
+ case sel: Select if sel.symbol == JSPackage_native =>
+ // ok
+ case _ =>
+ val pos = if (tree.rhs != EmptyTree) tree.rhs.pos else tree.pos
+ reporter.error(pos, s"$longKindStr may only call js.native.")
+ }
+
+ // Warn if resultType is Nothing and not ascribed
+ val sym = tree.symbol
+ if (sym.tpe.resultType.typeSymbol == NothingClass &&
+ tree.tpt.asInstanceOf[TypeTree].original == null) {
+ val name = sym.name.decoded.trim
+ reporter.warning(tree.pos,
+ s"The type of $name got inferred as Nothing. " +
+ "To suppress this warning, explicitly ascribe the type.")
+ }
+ }
+
+ private def checkJSNativeSpecificAnnotsOnNonJSNative(
+ memberDef: MemberDef): Unit = {
+ val sym = memberDef.symbol
+
+ for (annot <- sym.annotations) {
+ annot.symbol match {
+ case JSGlobalAnnotation =>
+ reporter.error(annot.pos,
+ "@JSGlobal can only be used on native JS definitions (with @js.native).")
+ case JSImportAnnotation =>
+ reporter.error(annot.pos,
+ "@JSImport can only be used on native JS definitions (with @js.native).")
+ case JSGlobalScopeAnnotation =>
+ reporter.error(annot.pos,
+ "@JSGlobalScope can only be used on native JS objects (with @js.native).")
+ case _ =>
+ // ok
+ }
+ }
+ }
+
+ private def checkJSCallingConventionAnnots(sym: Symbol): Unit = {
+ val callingConvAnnots = sym.annotations.filter(annot => JSCallingConventionAnnots.contains(annot.symbol))
+
+ callingConvAnnots match {
+ case Nil =>
+ () // OK
+
+ case annot :: rest =>
+ def annotName: String = annot.symbol.nameString
+
+ if (sym.isLocalToBlock || (enclosingOwner isnt OwnerKind.JSType)) {
+ reporter.error(annot.pos,
+ s"@$annotName can only be used on members of JS types.")
+ } else if (sym.isTrait) {
+ reporter.error(annot.pos,
+ s"@$annotName cannot be used on traits.")
+ } else if ((sym.isMethod || sym.isClass) && isPrivateMaybeWithin(sym)) {
+ reporter.error(annot.pos,
+ s"@$annotName cannot be used on private members.")
+ } else {
+ annot.symbol match {
+ case JSNameAnnotation =>
+ if (shouldCheckLiterals)
+ checkJSNameArgument(sym, annot)
+ case JSOperatorAnnotation | JSBracketAccessAnnotation | JSBracketCallAnnotation =>
+ if (!sym.isMethod) {
+ reporter.error(annot.pos,
+ s"@$annotName can only be used on methods.")
+ }
+ case _ =>
+ throw new AssertionError(
+ s"Found unexpected annotation ${annot.symbol} " +
+ s"in calling convention annots at ${annot.pos}")
+ }
+ }
+
+ for (duplicateAnnot <- rest) {
+ reporter.error(duplicateAnnot.pos,
+ "A member can have at most one annotation among " +
+ "@JSName, @JSOperator, @JSBracketAccess and @JSBracketCall.")
+ }
+ }
+ }
+
+ private lazy val JSCallingConventionAnnots: Set[Symbol] =
+ Set(JSNameAnnotation, JSOperatorAnnotation, JSBracketAccessAnnotation, JSBracketCallAnnotation)
+
+ /** Checks that argument to @JSName on [[member]] is a literal.
+ * Reports an error on each annotation where this is not the case.
+ */
+ private def checkJSNameArgument(memberSym: Symbol, annot: AnnotationInfo): Unit = {
+ val argTree = annot.args.head
+ if (argTree.tpe.typeSymbol == StringClass) {
+ if (annot.stringArg(0).isEmpty) {
+ reporter.error(argTree.pos,
+ "A string argument to JSName must be a literal string")
+ }
+ } else {
+ // We have a js.Symbol
+ val sym = argTree.symbol
+ if (!sym.isStatic || !sym.isStable) {
+ reporter.error(argTree.pos,
+ "A js.Symbol argument to JSName must be a static, stable identifier")
+ } else if ((enclosingOwner is OwnerKind.JSNonNative) &&
+ sym.owner == memberSym.owner) {
+ reporter.warning(argTree.pos,
+ "This symbol is defined in the same object as the annotation's " +
+ "target. This will cause a stackoverflow at runtime")
+ }
+ }
+ }
+
+ /** Mark the symbol as exposed if it is a non-private term member of a
+ * non-native JS class.
+ *
+ * @param sym
+ * The symbol, which must be the module symbol for a module, not its
+ * module class symbol.
+ */
+ private def markExposedIfRequired(sym: Symbol): Unit = {
+ def shouldBeExposed: Boolean = {
+ // it is a member of a non-native JS class
+ (enclosingOwner is OwnerKind.JSNonNative) && !sym.isLocalToBlock &&
+ // it is a term member
+ (sym.isModule || sym.isMethod) &&
+ // it is not private
+ !isPrivateMaybeWithin(sym) &&
+ // it is not a kind of term member that we never expose
+ !sym.isConstructor && !sym.isValueParameter && !sym.isParamWithDefault &&
+ // it is not synthetic
+ !sym.isSynthetic
+ }
+
+ if (shouldPrepareExports && shouldBeExposed) {
+ sym.addAnnotation(ExposedJSMemberAnnot)
+ /* For accessors, the field being accessed must also be exposed,
+ * although it is private.
+ *
+ * #4089 Don't do this if `sym.accessed == NoSymbol`. This happens in
+ * 2.12+, where fields are created later than this phase.
+ */
+ if (sym.isAccessor && sym.accessed != NoSymbol)
+ sym.accessed.addAnnotation(ExposedJSMemberAnnot)
+ }
+ }
+
+ }
+
+ def isJSAny(sym: Symbol): Boolean =
+ sym.isSubClass(JSAnyClass)
+
+ /** Checks that a setter has the right signature.
+ *
+ * Reports error messages otherwise.
+ */
+ def checkSetterSignature(sym: Symbol, pos: Position, exported: Boolean): Unit = {
+ val typeStr = if (exported) "Exported" else "JS"
+
+ // Forbid setters with non-unit return type
+ if (sym.tpe.resultType.typeSymbol != UnitClass) {
+ reporter.error(pos, s"$typeStr setters must return Unit")
+ }
+
+ // Forbid setters with more than one argument
+ sym.tpe.paramss match {
+ case List(List(arg)) =>
+ // Arg list is OK. Do additional checks.
+ if (isScalaRepeatedParamType(arg.tpe))
+ reporter.error(pos, s"$typeStr setters may not have repeated params")
+
+ if (arg.hasFlag(reflect.internal.Flags.DEFAULTPARAM))
+ reporter.error(pos, s"$typeStr setters may not have default params")
+
+ case _ =>
+ reporter.error(pos, s"$typeStr setters must have exactly one argument")
+ }
+ }
+
+ /** Tests whether the symbol has `private` in any form, either `private`,
+ * `private[this]` or `private[Enclosing]`.
+ */
+ def isPrivateMaybeWithin(sym: Symbol): Boolean =
+ sym.isPrivate || (sym.hasAccessBoundary && !sym.isProtected)
+
+ /** Checks that the optional argument to an `@JSGlobal` annotation is a
+ * literal.
+ *
+ * Reports an error on the annotation if it is not the case.
+ */
+ private def checkJSGlobalLiteral(annot: AnnotationInfo): Unit = {
+ if (annot.args.nonEmpty) {
+ assert(annot.args.size == 1,
+ s"@JSGlobal annotation $annot has more than 1 argument")
+
+ val argIsValid = annot.stringArg(0).isDefined
+ if (!argIsValid) {
+ reporter.error(annot.args.head.pos,
+ "The argument to @JSGlobal must be a literal string.")
+ }
+ }
+ }
+
+ /** Checks that arguments to an `@JSImport` annotation are literals.
+ *
+ * The second argument can also be the singleton `JSImport.Namespace`
+ * object.
+ *
+ * Reports an error on the annotation if it is not the case.
+ */
+ private def checkJSImportLiteral(annot: AnnotationInfo): Unit = {
+ val argCount = annot.args.size
+ assert(argCount >= 1 && argCount <= 3,
+ s"@JSImport annotation $annot does not have between 1 and 3 arguments")
+
+ val firstArgIsValid = annot.stringArg(0).isDefined
+ if (!firstArgIsValid) {
+ reporter.error(annot.args.head.pos,
+ "The first argument to @JSImport must be a literal string.")
+ }
+
+ val secondArgIsValid = {
+ argCount < 2 ||
+ annot.stringArg(1).isDefined ||
+ annot.args(1).symbol == JSImportNamespaceObject
+ }
+ if (!secondArgIsValid) {
+ reporter.error(annot.args(1).pos,
+ "The second argument to @JSImport must be literal string or the " +
+ "JSImport.Namespace object.")
+ }
+
+ val thirdArgIsValid = argCount < 3 || annot.stringArg(2).isDefined
+ if (!thirdArgIsValid) {
+ reporter.error(annot.args(2).pos,
+ "The third argument to @JSImport, when present, must be a " +
+ "literal string.")
+ }
+ }
+
+ private abstract class ScalaEnumFctExtractors(methSym: Symbol) {
+ private def resolve(ptpes: Symbol*) = {
+ val res = methSym suchThat {
+ _.tpe.params.map(_.tpe.typeSymbol) == ptpes.toList
+ }
+ assert(res != NoSymbol, s"no overload of $methSym for param types $ptpes")
+ res
+ }
+
+ private val noArg = resolve()
+ private val nameArg = resolve(StringClass)
+ private val intArg = resolve(IntClass)
+ private val fullMeth = resolve(IntClass, StringClass)
+
+ /**
+ * Extractor object for calls to the targeted symbol that do not have an
+ * explicit name in the parameters
+ *
+ * Extracts:
+ * - `sel: Select` where sel.symbol is targeted symbol (no arg)
+ * - Apply(meth, List(param)) where meth.symbol is targeted symbol (i: Int)
+ */
+ object NoName {
+ def unapply(t: Tree): Option[Option[Tree]] = t match {
+ case sel: Select if sel.symbol == noArg =>
+ Some(None)
+ case Apply(meth, List(param)) if meth.symbol == intArg =>
+ Some(Some(param))
+ case _ =>
+ None
+ }
+ }
+
+ object NullName {
+ def unapply(tree: Tree): Boolean = tree match {
+ case Apply(meth, List(Literal(Constant(null)))) =>
+ meth.symbol == nameArg
+ case Apply(meth, List(_, Literal(Constant(null)))) =>
+ meth.symbol == fullMeth
+ case _ => false
+ }
+ }
+
+ }
+
+ private object ScalaEnumValue
+ extends ScalaEnumFctExtractors(getMemberMethod(ScalaEnumClass, jsnme.Value))
+
+ private object ScalaEnumVal
+ extends ScalaEnumFctExtractors(getMemberClass(ScalaEnumClass, jsnme.Val).tpe.member(nme.CONSTRUCTOR))
+
+ /**
+ * Construct a call to Enumeration.Value
+ * @param thisSym ClassSymbol of enclosing class
+ * @param nameOrig Symbol of ValDef where this call will be placed
+ * (determines the string passed to Value)
+ * @param intParam Optional tree with Int passed to Value
+ * @return Typed tree with appropriate call to Value
+ */
+ private def ScalaEnumValName(
+ thisSym: Symbol,
+ nameOrig: Symbol,
+ intParam: Option[Tree]) = {
+
+ val defaultName = nameOrig.asTerm.getterName.encoded
+
+
+ // Construct the following tree
+ //
+ // if (nextName != null && nextName.hasNext)
+ // nextName.next()
+ // else
+ //
+ //
+ val nextNameTree = Select(This(thisSym), jsnme.nextName)
+ val nullCompTree =
+ Apply(Select(nextNameTree, nme.NE), Literal(Constant(null)) :: Nil)
+ val hasNextTree = Select(nextNameTree, jsnme.hasNext)
+ val condTree = Apply(Select(nullCompTree, nme.ZAND), hasNextTree :: Nil)
+ val nameTree = If(condTree,
+ Apply(Select(nextNameTree, jsnme.next), Nil),
+ Literal(Constant(defaultName)))
+ val params = intParam.toList :+ nameTree
+
+ typer.typed {
+ Apply(Select(This(thisSym), jsnme.Value), params)
+ }
+ }
+
+ private def checkAndGetJSNativeLoadingSpecAnnotOf(
+ pos: Position, sym: Symbol): Option[Annotation] = {
+
+ for (annot <- sym.getAnnotation(JSNameAnnotation)) {
+ reporter.error(annot.pos,
+ "@JSName can only be used on members of JS types.")
+ }
+
+ val annots = sym.annotations.filter { annot =>
+ JSNativeLoadingSpecAnnots.contains(annot.symbol)
+ }
+
+ val badAnnotCountMsg = if (sym.isModuleClass) {
+ "Native JS objects must have exactly one annotation among " +
+ "@JSGlobal, @JSImport and @JSGlobalScope."
+ } else {
+ "Native JS classes, vals and defs must have exactly one annotation " +
+ "among @JSGlobal and @JSImport."
+ }
+
+ annots match {
+ case Nil =>
+ reporter.error(pos, badAnnotCountMsg)
+ None
+
+ case result :: duplicates =>
+ for (annot <- duplicates)
+ reporter.error(annot.pos, badAnnotCountMsg)
+
+ Some(result)
+ }
+ }
+
+ /* Note that we consider @JSGlobalScope as a JS native loading spec because
+ * it's convenient for the purposes of PrepJSInterop. Actually @JSGlobalScope
+ * objects do not receive a JS loading spec in their IR.
+ */
+ private lazy val JSNativeLoadingSpecAnnots: Set[Symbol] = {
+ Set(JSGlobalAnnotation, JSImportAnnotation, JSGlobalScopeAnnotation)
+ }
+
+ private lazy val ScalaEnumClass = getRequiredClass("scala.Enumeration")
+
+ private def wasPublicBeforeTyper(sym: Symbol): Boolean =
+ sym.hasAnnotation(WasPublicBeforeTyperClass)
+
+ private def fixPublicBeforeTyper(ddef: DefDef): DefDef = {
+ // This method assumes that isJSAny(ddef.symbol.owner) is true
+ val sym = ddef.symbol
+ val needsFix = {
+ sym.isPrivate &&
+ (wasPublicBeforeTyper(sym) ||
+ (sym.isAccessor && wasPublicBeforeTyper(sym.accessed)))
+ }
+ if (needsFix) {
+ sym.resetFlag(Flag.PRIVATE)
+ treeCopy.DefDef(ddef, ddef.mods &~ Flag.PRIVATE, ddef.name, ddef.tparams,
+ ddef.vparamss, ddef.tpt, ddef.rhs)
+ } else {
+ ddef
+ }
+ }
+
+ private def checkInternalAnnotations(sym: Symbol): Unit = {
+ /** Returns true iff it is a compiler annotations. This does not include
+ * annotations inserted before the typer (such as `@WasPublicBeforeTyper`).
+ */
+ def isCompilerAnnotation(annotation: AnnotationInfo): Boolean = {
+ annotation.symbol == ExposedJSMemberAnnot ||
+ annotation.symbol == JSTypeAnnot ||
+ annotation.symbol == JSOptionalAnnotation
+ }
+
+ for (annotation <- sym.annotations) {
+ if (isCompilerAnnotation(annotation)) {
+ reporter.error(annotation.pos,
+ s"$annotation is for compiler internal use only. " +
+ "Do not use it yourself.")
+ }
+ }
+ }
+
+ private def moduleToModuleClass(sym: Symbol): Symbol =
+ if (sym.isModule) sym.moduleClass
+ else sym
+}
+
+object PrepJSInterop {
+ private final class OwnerKind private (private val baseKinds: Int)
+ extends AnyVal {
+
+ import OwnerKind._
+
+ @inline def isBaseKind: Boolean =
+ Integer.lowestOneBit(baseKinds) == baseKinds && baseKinds != 0 // exactly 1 bit on
+
+ @inline def |(that: OwnerKind): OwnerKind =
+ new OwnerKind(this.baseKinds | that.baseKinds)
+
+ @inline def is(that: OwnerKind): Boolean =
+ (this.baseKinds & that.baseKinds) != 0
+
+ @inline def isnt(that: OwnerKind): Boolean =
+ !this.is(that)
+ }
+
+ private object OwnerKind {
+ /** No owner, i.e., we are at the top-level. */
+ val None = new OwnerKind(0x00)
+
+ // Base kinds - those form a partition of all possible enclosing owners
+
+ /** A Scala class/trait that does not extend Enumeration. */
+ val NonEnumScalaClass = new OwnerKind(0x01)
+ /** A Scala object that does not extend Enumeration. */
+ val NonEnumScalaMod = new OwnerKind(0x02)
+ /** A native JS class/trait, which extends js.Any. */
+ val JSNativeClass = new OwnerKind(0x04)
+ /** A native JS object, which extends js.Any. */
+ val JSNativeMod = new OwnerKind(0x08)
+ /** A non-native JS class/trait. */
+ val JSClass = new OwnerKind(0x10)
+ /** A non-native JS object. */
+ val JSMod = new OwnerKind(0x20)
+ /** A Scala class/trait that extends Enumeration. */
+ val EnumClass = new OwnerKind(0x40)
+ /** A Scala object that extends Enumeration. */
+ val EnumMod = new OwnerKind(0x80)
+ /** The Enumeration class itself. */
+ val EnumImpl = new OwnerKind(0x100)
+
+ // Compound kinds
+
+ /** A Scala class/trait, possibly Enumeration-related. */
+ val ScalaClass = NonEnumScalaClass | EnumClass | EnumImpl
+ /** A Scala object, possibly Enumeration-related. */
+ val ScalaMod = NonEnumScalaMod | EnumMod
+ /** A Scala class, trait or object, i.e., anything not extending js.Any. */
+ val ScalaThing = ScalaClass | ScalaMod
+
+ /** A Scala class/trait/object extending Enumeration, but not Enumeration itself. */
+ val Enum = EnumClass | EnumMod
+
+ /** A native JS class/trait/object. */
+ val JSNative = JSNativeClass | JSNativeMod
+ /** A non-native JS class/trait/object. */
+ val JSNonNative = JSClass | JSMod
+ /** A JS type, i.e., something extending js.Any. */
+ val JSType = JSNative | JSNonNative
+
+ /** Any kind of class/trait, i.e., a Scala or JS class/trait. */
+ val AnyClass = ScalaClass | JSNativeClass | JSClass
+ }
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSOptions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSOptions.scala
new file mode 100644
index 0000000000..50cc0bf1c8
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSOptions.scala
@@ -0,0 +1,50 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import java.net.URI
+
+/** This trait allows to query all options to the ScalaJS plugin
+ *
+ * Also see the help text in ScalaJSPlugin for information about particular
+ * options.
+ */
+trait ScalaJSOptions {
+ import ScalaJSOptions.URIMap
+
+ /** should calls to Predef.classOf[T] be fixed in the jsinterop phase.
+ * If false, bad calls to classOf will cause an error. */
+ def fixClassOf: Boolean
+
+ /** Should static forwarders be emitted for non-top-level objects.
+ *
+ * Scala/JVM does not do that. Since Scala.js 1.2.0, we do not do it by
+ * default either, but this option can be used to opt in. This is necessary
+ * for implementations of JDK classes.
+ */
+ def genStaticForwardersForNonTopLevelObjects: Boolean
+
+ /** which source locations in source maps should be relativized (or where
+ * should they be mapped to)? */
+ def sourceURIMaps: List[URIMap]
+
+ /** Whether to warn if the global execution context is used.
+ *
+ * See the warning itself or #4129 for context.
+ */
+ def warnGlobalExecutionContext: Boolean
+}
+
+object ScalaJSOptions {
+ case class URIMap(from: URI, to: Option[URI])
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala
new file mode 100644
index 0000000000..a67adbb948
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala
@@ -0,0 +1,183 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.tools.nsc._
+import scala.tools.nsc.plugins.{
+ Plugin => NscPlugin, PluginComponent => NscPluginComponent
+}
+import scala.collection.{ mutable, immutable }
+
+import java.net.{ URI, URISyntaxException }
+
+import org.scalajs.ir.Trees
+
+/** Main entry point for the Scala.js compiler plugin
+ *
+ * @author Sébastien Doeraene
+ */
+class ScalaJSPlugin(val global: Global) extends NscPlugin {
+ import global._
+
+ val name = "scalajs"
+ val description = "Compile to JavaScript"
+ val components = {
+ if (global.isInstanceOf[doc.ScaladocGlobal]) {
+ List[NscPluginComponent](PrepInteropComponent)
+ } else {
+ List[NscPluginComponent](PreTyperComponentComponent, PrepInteropComponent,
+ ExplicitInnerJSComponent, ExplicitLocalJSComponent, GenCodeComponent)
+ }
+ }
+
+ /** Called for each generated `ClassDef`. Override for testing. */
+ def generatedJSAST(clDef: Trees.ClassDef): Unit = {}
+
+ /** A trick to avoid early initializers while still enforcing that `global`
+ * is initialized early.
+ */
+ abstract class JSGlobalAddonsEarlyInit[G <: Global with Singleton](val global: G)
+ extends JSGlobalAddons
+
+ /** Addons for the JavaScript platform. */
+ object jsAddons extends JSGlobalAddonsEarlyInit[global.type](global)
+
+ object scalaJSOpts extends ScalaJSOptions {
+ import ScalaJSOptions.URIMap
+ var fixClassOf: Boolean = false
+ var genStaticForwardersForNonTopLevelObjects: Boolean = false
+ lazy val sourceURIMaps: List[URIMap] = {
+ if (_sourceURIMaps.nonEmpty)
+ _sourceURIMaps.reverse
+ else
+ relSourceMap.toList.map(URIMap(_, absSourceMap))
+ }
+ var warnGlobalExecutionContext: Boolean = true
+ var _sourceURIMaps: List[URIMap] = Nil
+ var relSourceMap: Option[URI] = None
+ var absSourceMap: Option[URI] = None
+ }
+
+ object PreTyperComponentComponent extends PreTyperComponent(global) {
+ val runsAfter = List("parser")
+ override val runsBefore = List("namer")
+ }
+
+ object PrepInteropComponent extends PrepJSInterop[global.type](global) {
+ val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons
+ val scalaJSOpts = ScalaJSPlugin.this.scalaJSOpts
+ override val runsAfter = List("typer")
+ override val runsBefore = List("pickler")
+ }
+
+ object ExplicitInnerJSComponent extends ExplicitInnerJS[global.type](global) {
+ val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons
+ override val runsAfter = List("refchecks")
+ override val runsBefore = List("uncurry")
+ }
+
+ object ExplicitLocalJSComponent extends ExplicitLocalJS[global.type](global) {
+ val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons
+ override val runsAfter = List("specialize")
+ override val runsBefore = List("explicitouter")
+ }
+
+ object GenCodeComponent extends GenJSCode[global.type](global) {
+ val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons
+ val scalaJSOpts = ScalaJSPlugin.this.scalaJSOpts
+ override val runsAfter = List("mixin")
+ override val runsBefore = List("delambdafy", "cleanup", "terminal")
+
+ def generatedJSAST(clDef: Trees.ClassDef): Unit =
+ ScalaJSPlugin.this.generatedJSAST(clDef)
+ }
+
+ override def init(options: List[String], error: String => Unit): Boolean = {
+ import ScalaJSOptions.URIMap
+ import scalaJSOpts._
+
+ for (option <- options) {
+ if (option == "fixClassOf") {
+ fixClassOf = true
+ } else if (option == "genStaticForwardersForNonTopLevelObjects") {
+ genStaticForwardersForNonTopLevelObjects = true
+ } else if (option == "nowarnGlobalExecutionContext") {
+ warnGlobalExecutionContext = false
+ } else if (option.startsWith("mapSourceURI:")) {
+ val uris = option.stripPrefix("mapSourceURI:").split("->")
+
+ if (uris.length != 1 && uris.length != 2) {
+ error("relocateSourceMap needs one or two URIs as argument.")
+ } else {
+ try {
+ val from = new URI(uris.head)
+ val to = uris.lift(1).map(str => new URI(str))
+ _sourceURIMaps ::= URIMap(from, to)
+ } catch {
+ case e: URISyntaxException =>
+ error(s"${e.getInput} is not a valid URI")
+ }
+ }
+ // The following options are deprecated (how do we show this to the user?)
+ } else if (option.startsWith("relSourceMap:")) {
+ val uriStr = option.stripPrefix("relSourceMap:")
+ try { relSourceMap = Some(new URI(uriStr)) }
+ catch {
+ case e: URISyntaxException => error(s"$uriStr is not a valid URI")
+ }
+ } else if (option.startsWith("absSourceMap:")) {
+ val uriStr = option.stripPrefix("absSourceMap:")
+ try { absSourceMap = Some(new URI(uriStr)) }
+ catch {
+ case e: URISyntaxException => error(s"$uriStr is not a valid URI")
+ }
+ } else {
+ error("Option not understood: " + option)
+ }
+ }
+
+ // Verify constraints
+ if (_sourceURIMaps.nonEmpty && relSourceMap.isDefined)
+ error("You may not use mapSourceURI and relSourceMap together. " +
+ "Use another mapSourceURI option without second URI.")
+ else if (_sourceURIMaps.nonEmpty && absSourceMap.isDefined)
+ error("You may not use mapSourceURI and absSourceMap together. " +
+ "Use another mapSourceURI option.")
+ else if (absSourceMap.isDefined && relSourceMap.isEmpty)
+ error("absSourceMap requires the use of relSourceMap")
+
+ true // this plugin is always enabled
+ }
+
+ override val optionsHelp: Option[String] = Some(s"""
+ | -P:$name:mapSourceURI:FROM_URI[->TO_URI]
+ | Change the location the source URIs in the emitted IR point to
+ | - strips away the prefix FROM_URI (if it matches)
+ | - optionally prefixes the TO_URI, where stripping has been performed
+ | - any number of occurrences are allowed. Processing is done on a first match basis.
+ | -P:$name:genStaticForwardersForNonTopLevelObjects
+ | Generate static forwarders for non-top-level objects.
+ | This option should be used by codebases that implement JDK classes.
+ | When used together with -Xno-forwarders, this option has no effect.
+ | -P:$name:fixClassOf
+ | Repair calls to Predef.classOf that reach Scala.js.
+ | WARNING: This is a tremendous hack! Expect ugly errors if you use this option.
+ |Deprecated options
+ | -P:$name:relSourceMap:
+ | Relativize emitted source maps with
+ | -P:$name:absSourceMap:
+ | Absolutize emitted source maps with
+ | This option requires the use of relSourceMap
+ """.stripMargin)
+
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala
new file mode 100644
index 0000000000..eae31fdb14
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala
@@ -0,0 +1,141 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin
+
+import scala.tools.nsc._
+
+import org.scalajs.ir.Types
+
+/** Conversions from scalac `Type`s to the IR `Type`s and `TypeRef`s. */
+trait TypeConversions[G <: Global with Singleton] extends SubComponent {
+ this: GenJSCode[G] =>
+
+ import global._
+ import definitions._
+
+ private lazy val primitiveIRTypeMap: Map[Symbol, Types.Type] = {
+ Map(
+ UnitClass -> Types.VoidType,
+ BooleanClass -> Types.BooleanType,
+ CharClass -> Types.CharType,
+ ByteClass -> Types.ByteType,
+ ShortClass -> Types.ShortType,
+ IntClass -> Types.IntType,
+ LongClass -> Types.LongType,
+ FloatClass -> Types.FloatType,
+ DoubleClass -> Types.DoubleType,
+ NothingClass -> Types.NothingType,
+ NullClass -> Types.NullType
+ )
+ }
+
+ private lazy val primitiveRefMap: Map[Symbol, Types.NonArrayTypeRef] = {
+ Map(
+ UnitClass -> Types.VoidRef,
+ BooleanClass -> Types.BooleanRef,
+ CharClass -> Types.CharRef,
+ ByteClass -> Types.ByteRef,
+ ShortClass -> Types.ShortRef,
+ IntClass -> Types.IntRef,
+ LongClass -> Types.LongRef,
+ FloatClass -> Types.FloatRef,
+ DoubleClass -> Types.DoubleRef,
+ NothingClass -> Types.ClassRef(encodeClassName(RuntimeNothingClass)),
+ NullClass -> Types.ClassRef(encodeClassName(RuntimeNullClass))
+ )
+ }
+
+ def toIRType(t: Type): Types.Type = {
+ val (base, arrayDepth) = convert(t)
+ if (arrayDepth == 0)
+ primitiveIRTypeMap.getOrElse(base, encodeClassType(base))
+ else
+ Types.ArrayType(makeArrayTypeRef(base, arrayDepth), nullable = true)
+ }
+
+ def toTypeRef(t: Type): Types.TypeRef = {
+ val (base, arrayDepth) = convert(t)
+ if (arrayDepth == 0)
+ makeNonArrayTypeRef(base)
+ else
+ makeArrayTypeRef(base, arrayDepth)
+ }
+
+ private def makeNonArrayTypeRef(sym: Symbol): Types.NonArrayTypeRef =
+ primitiveRefMap.getOrElse(sym, Types.ClassRef(encodeClassName(sym)))
+
+ private def makeArrayTypeRef(base: Symbol, depth: Int): Types.ArrayTypeRef =
+ Types.ArrayTypeRef(makeNonArrayTypeRef(base), depth)
+
+ // The following code was modeled after backend.icode.TypeKinds.toTypeKind
+
+ /** Converts the given `Type` to a Scala.js `Type` or `TypeRef`, according to
+ * the given `ConversionFinisher`.
+ *
+ * @param t
+ * The `Type` to convert
+ * @return
+ * The base symbol type, and the array depth. If the array depth is 0, it
+ * means that the base symbol itself is the result.
+ */
+ /* The call to .normalize fixes #3003 (follow type aliases). Otherwise,
+ * convertMaybeArray below would return ObjectReference.
+ */
+ private def convert(t: Type): (Symbol, Int) = t.normalize match {
+ case ThisType(ArrayClass) => (ObjectClass, 0)
+ case ThisType(sym) => (sym, 0)
+ case SingleType(_, sym) => (sym, 0)
+ case ConstantType(_) => convert(t.underlying)
+ case TypeRef(_, sym, args) => convertMaybeArray(sym, args)
+ case ClassInfoType(_, _, ArrayClass) => abort("ClassInfoType to ArrayClass!")
+ case ClassInfoType(_, _, sym) => (sym, 0)
+
+ // !!! Iulian says types which make no sense after erasure should not reach here,
+ // which includes the ExistentialType, AnnotatedType, RefinedType. I don't know
+ // if the first two cases exist because they do or as a defensive measure, but
+ // at the time I added it, RefinedTypes were indeed reaching here.
+ // !!! Removed in JavaScript backend because I do not know what to do with lub
+ //case ExistentialType(_, t) => toTypeKind(t)
+ // Apparently, this case does occur (see pos/CustomGlobal.scala)
+ case t: AnnotatedType => convert(t.underlying)
+ //case RefinedType(parents, _) => parents map toTypeKind reduceLeft lub
+
+ /* This case is not in scalac. We need it for the test
+ * run/valueclasses-classtag-existential. I have no idea how icode does
+ * not fail this test: we do everything the same as icode up to here.
+ */
+ case tpe: ErasedValueType => (tpe.valueClazz, 0)
+
+ // For sure WildcardTypes shouldn't reach here either, but when
+ // debugging such situations this may come in handy.
+ // case WildcardType => (ObjectClass, 0)
+ case norm => abort(
+ "Unknown type: %s, %s [%s, %s] TypeRef? %s".format(
+ t, norm, t.getClass, norm.getClass, t.isInstanceOf[TypeRef]
+ )
+ )
+ }
+
+ /** Convert a type ref, possibly an array type. */
+ private def convertMaybeArray(sym: Symbol,
+ targs: List[Type]): (Symbol, Int) = sym match {
+ case ArrayClass =>
+ val convertedArg = convert(targs.head)
+ (convertedArg._1, convertedArg._2 + 1)
+ case _ if sym.isClass =>
+ (sym, 0)
+ case _ =>
+ assert(sym.isType, sym) // it must be compiling Array[a]
+ (ObjectClass, 0)
+ }
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/util/ScopedVar.scala b/compiler/src/main/scala/org/scalajs/nscplugin/util/ScopedVar.scala
new file mode 100644
index 0000000000..c9781465af
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/util/ScopedVar.scala
@@ -0,0 +1,50 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.util
+
+import language.implicitConversions
+
+class ScopedVar[A](init: A) {
+ import ScopedVar.Assignment
+
+ private var value = init
+
+ def this()(implicit ev: Null <:< A) = this(ev(null))
+
+ def get: A = value
+ def :=(newValue: A): Assignment[A] = new Assignment(this, newValue)
+}
+
+object ScopedVar {
+ class Assignment[T](scVar: ScopedVar[T], value: T) {
+ private[ScopedVar] def push(): AssignmentStackElement[T] = {
+ val stack = new AssignmentStackElement(scVar, scVar.value)
+ scVar.value = value
+ stack
+ }
+ }
+
+ private class AssignmentStackElement[T](scVar: ScopedVar[T], oldValue: T) {
+ private[ScopedVar] def pop(): Unit = {
+ scVar.value = oldValue
+ }
+ }
+
+ implicit def toValue[T](scVar: ScopedVar[T]): T = scVar.get
+
+ def withScopedVars[T](ass: Assignment[_]*)(body: => T): T = {
+ val stack = ass.map(_.push())
+ try body
+ finally stack.reverse.foreach(_.pop())
+ }
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/util/VarBox.scala b/compiler/src/main/scala/org/scalajs/nscplugin/util/VarBox.scala
new file mode 100644
index 0000000000..e1d47515a0
--- /dev/null
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/util/VarBox.scala
@@ -0,0 +1,17 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.util
+
+import language.implicitConversions
+
+class VarBox[T](var value: T)
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/ClassInfos.scala b/compiler/src/main/scala/scala/scalajs/compiler/ClassInfos.scala
deleted file mode 100644
index cd76e217a0..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/ClassInfos.scala
+++ /dev/null
@@ -1,126 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.compiler
-
-import scala.language.implicitConversions
-
-import scala.collection.mutable
-import scala.tools.nsc._
-
-import java.io.{ File, PrintWriter, BufferedOutputStream, FileOutputStream }
-
-import scala.scalajs.ir
-import ir.{Trees => js, ClassKind}
-import ir.Infos._
-
-trait ClassInfos extends SubComponent { self: GenJSCode =>
- import global._
- import jsAddons._
-
- class ClassInfoBuilder(val symbol: ClassSymbol) {
- val name = classNameOf(symbol)
- val encodedName = encodeClassFullName(symbol)
- var isExported: Boolean = false
- val ancestorCount = symbol.ancestors.count(!_.isInterface)
- val kind = {
- if (isStaticModule(symbol)) ClassKind.ModuleClass
- else if (symbol.isInterface) ClassKind.Interface
- else if (isRawJSType(symbol.tpe)) ClassKind.RawJSType
- else if (isHijackedBoxedClass(symbol)) ClassKind.HijackedClass
- else if (symbol.isImplClass) ClassKind.TraitImpl
- else ClassKind.Class
- }
- val superClass =
- if (kind.isClass || kind == ClassKind.HijackedClass)
- encodeClassFullName(symbol.superClass)
- else
- ""
- val ancestors = (symbol :: symbol.ancestors) map encodeClassFullName
-
- val methodInfos = mutable.ListBuffer.empty[MethodInfoBuilder]
-
- def addMethod(encodedName: String, isAbstract: Boolean = false,
- isExported: Boolean = false): MethodInfoBuilder = {
- val b = new MethodInfoBuilder(encodedName, isAbstract, isExported)
- methodInfos += b
- b
- }
-
- def result(): ClassInfo = {
- ClassInfo(name, encodedName, isExported, ancestorCount, kind,
- superClass, ancestors, methodInfos.map(_.result()).result())
- }
- }
-
- class MethodInfoBuilder(val encodedName: String,
- val isAbstract: Boolean = false,
- val isExported: Boolean = false) {
-
- val calledMethods = mutable.Set.empty[(String, String)] // (tpe, method)
- val calledMethodsStatic = mutable.Set.empty[(String, String)] // (class, method)
- val instantiatedClasses = mutable.Set.empty[String]
- val accessedModules = mutable.Set.empty[String]
- val accessedClassData = mutable.Set.empty[String]
- var optimizerHints: OptimizerHints = OptimizerHints.empty
-
- def callsMethod(ownerIdent: js.Ident, method: js.Ident): Unit =
- calledMethods += ((patchClassName(ownerIdent.name), method.name))
-
- def callsMethod(owner: Symbol, method: js.Ident): Unit =
- calledMethods += ((patchClassName(encodeClassFullName(owner)), method.name))
-
- def callsMethodStatic(ownerIdent: js.Ident, method: js.Ident): Unit =
- calledMethodsStatic += ((patchClassName(ownerIdent.name), method.name))
-
- def instantiatesClass(classSym: Symbol): Unit =
- instantiatedClasses += patchClassName(encodeClassFullName(classSym))
-
- def accessesModule(moduleClassSym: Symbol): Unit =
- accessedModules += patchModuleName(encodeModuleFullName(moduleClassSym))
-
- def accessesClassData(classSym: Symbol): Unit =
- if (!classSym.isPrimitiveValueClass)
- accessedClassData += encodeClassFullName(classSym)
-
- def createsAnonFunction(funInfo: ClassInfoBuilder): Unit = {
- for (methodInfo <- funInfo.methodInfos) {
- calledMethods ++= methodInfo.calledMethods
- calledMethodsStatic ++= methodInfo.calledMethodsStatic
- instantiatedClasses ++= methodInfo.instantiatedClasses
- accessedModules ++= methodInfo.accessedModules
- accessedClassData ++= methodInfo.accessedClassData
- }
- }
-
- private def patchClassName(name: String): String = name match {
- case "jl_String$" => "sjsr_RuntimeString$"
- case _ => name
- }
-
- private def patchModuleName(name: String): String = name match {
- case "jl_String" => "sjsr_RuntimeString"
- case _ => name
- }
-
- def result(): MethodInfo = {
- MethodInfo(
- encodedName,
- isAbstract,
- isExported,
- calledMethods.toList.groupBy(_._1).mapValues(_.map(_._2)),
- calledMethodsStatic.toList.groupBy(_._1).mapValues(_.map(_._2)),
- instantiatedClasses.toList,
- accessedModules.result.toList,
- accessedClassData.result.toList,
- optimizerHints
- )
- }
- }
-
- private def classNameOf(sym: Symbol): String =
- if (needsModuleClassSuffix(sym)) sym.fullName + nme.MODULE_SUFFIX_STRING
- else sym.fullName
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/Compat210Component.scala b/compiler/src/main/scala/scala/scalajs/compiler/Compat210Component.scala
deleted file mode 100644
index 0df72c6316..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/Compat210Component.scala
+++ /dev/null
@@ -1,103 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.compiler
-
-import scala.tools.nsc._
-
-/** Hacks to have our source code compatible with 2.10 and 2.11.
- * It exposes 2.11 API in a 2.10 compiler.
- *
- * @author Sébastien Doeraene
- */
-trait Compat210Component {
-
- val global: Global
-
- import global._
-
- // unexpandedName replaces originalName
-
- implicit final class SymbolCompat(self: Symbol) {
- def unexpandedName: Name = self.originalName
- def originalName: Name = sys.error("infinite loop in Compat")
- }
-
- // enteringPhase/exitingPhase replace beforePhase/afterPhase
-
- @inline final def enteringPhase[T](ph: Phase)(op: => T): T = {
- global.enteringPhase(ph)(op)
- }
-
- @inline final def exitingPhase[T](ph: Phase)(op: => T): T = {
- global.exitingPhase(ph)(op)
- }
-
- private implicit final class GlobalCompat(
- self: Compat210Component.this.global.type) {
-
- def enteringPhase[T](ph: Phase)(op: => T): T = self.beforePhase(ph)(op)
- def beforePhase[T](ph: Phase)(op: => T): T = sys.error("infinite loop in Compat")
-
- def exitingPhase[T](ph: Phase)(op: => T): T = self.afterPhase(ph)(op)
- def afterPhase[T](ph: Phase)(op: => T): T = sys.error("infinite loop in Compat")
- }
-
- // ErasedValueType has a different encoding
-
- implicit final class ErasedValueTypeCompat(self: global.ErasedValueType) {
- def valueClazz: Symbol = self.original.typeSymbol
- def original: TypeRef = sys.error("infinite loop in Compat")
- }
-
- // repeatedToSingle
-
- @inline final def repeatedToSingle(t: Type) =
- global.definitions.repeatedToSingle(t)
-
- private implicit final class DefinitionsCompat(
- self: Compat210Component.this.global.definitions.type) {
-
- def repeatedToSingle(t: Type) = t match {
- case TypeRef(_, self.RepeatedParamClass, arg :: Nil) => arg
- case _ => t
- }
-
- }
-
- // run.runDefinitions bundles methods and state related to the run
- // that were previously in definitions itself
-
- implicit final class RunCompat(self: Run) {
- val runDefinitions: Compat210Component.this.global.definitions.type =
- global.definitions
- }
-
- // Mode.FUNmode replaces analyzer.FUNmode
-
- object Mode {
- import Compat210Component.AnalyzerCompat
- // No type ascription! Type is different in 2.10 / 2.11
- val FUNmode = analyzer.FUNmode
- }
-}
-
-object Compat210Component {
- private object LowPriorityMode {
- object Mode {
- def FUNmode = sys.error("infinite loop in Compat")
- }
- }
-
- private implicit final class AnalyzerCompat(self: scala.tools.nsc.typechecker.Analyzer) {
- def FUNmode = {
- import Compat210Component.LowPriorityMode._
- {
- import scala.reflect.internal._
- Mode.FUNmode
- }
- }
- }
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala b/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala
deleted file mode 100644
index 1efe847db0..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/GenJSCode.scala
+++ /dev/null
@@ -1,3729 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.compiler
-
-import scala.language.implicitConversions
-
-import scala.annotation.switch
-
-import scala.collection.mutable
-import scala.collection.mutable.ListBuffer
-
-import scala.tools.nsc._
-
-import scala.annotation.tailrec
-
-import scala.scalajs.ir
-import ir.{Trees => js, Types => jstpe, ClassKind}
-
-import util.ScopedVar
-import ScopedVar.withScopedVars
-
-/** Generate JavaScript code and output it to disk
- *
- * @author Sébastien Doeraene
- */
-abstract class GenJSCode extends plugins.PluginComponent
- with TypeKinds
- with JSEncoding
- with GenJSExports
- with ClassInfos
- with GenJSFiles
- with Compat210Component {
-
- val jsAddons: JSGlobalAddons {
- val global: GenJSCode.this.global.type
- }
-
- val scalaJSOpts: ScalaJSOptions
-
- import global._
- import jsAddons._
- import rootMirror._
- import definitions._
- import jsDefinitions._
- import JSTreeExtractors._
-
- import treeInfo.hasSynthCaseSymbol
-
- import platform.isMaybeBoxed
-
- val phaseName = "jscode"
-
- /** testing: this will be called when ASTs are generated */
- def generatedJSAST(clDefs: List[js.Tree]): Unit
-
- /** Implicit conversion from nsc Position to ir.Position. */
- implicit def pos2irPos(pos: Position): ir.Position = {
- if (pos == NoPosition) ir.Position.NoPosition
- else {
- val source = pos2irPosCache.toIRSource(pos.source)
- // nsc positions are 1-based but IR positions are 0-based
- ir.Position(source, pos.line-1, pos.column-1)
- }
- }
-
- private[this] object pos2irPosCache {
- import scala.reflect.internal.util._
-
- private[this] var lastNscSource: SourceFile = null
- private[this] var lastIRSource: ir.Position.SourceFile = null
-
- def toIRSource(nscSource: SourceFile): ir.Position.SourceFile = {
- if (nscSource != lastNscSource) {
- lastIRSource = convert(nscSource)
- lastNscSource = nscSource
- }
- lastIRSource
- }
-
- private[this] def convert(nscSource: SourceFile): ir.Position.SourceFile = {
- nscSource.file.file match {
- case null =>
- new java.net.URI(
- "virtualfile", // Pseudo-Scheme
- nscSource.file.path, // Scheme specific part
- null // Fragment
- )
- case file =>
- val relURI = scalaJSOpts.relSourceMap.fold(file.toURI)(_.relativize(file.toURI))
- val absURI = scalaJSOpts.absSourceMap.fold(relURI)(_.resolve(relURI))
- absURI
- }
- }
-
- def clear(): Unit = {
- lastNscSource = null
- lastIRSource = null
- }
- }
-
- /** Materialize implicitly an ir.Position from an implicit nsc Position. */
- implicit def implicitPos2irPos(implicit pos: Position): ir.Position = pos
-
- override def newPhase(p: Phase) = new JSCodePhase(p)
-
- class JSCodePhase(prev: Phase) extends StdPhase(prev) with JSExportsPhase {
-
- override def name = phaseName
- override def description = "Generate JavaScript code from ASTs"
- override def erasedTypes = true
-
- // Some state --------------------------------------------------------------
-
- val currentClassSym = new ScopedVar[Symbol]
- val currentClassInfoBuilder = new ScopedVar[ClassInfoBuilder]
- val currentMethodSym = new ScopedVar[Symbol]
- val currentMethodInfoBuilder = new ScopedVar[MethodInfoBuilder]
- val methodTailJumpThisSym = new ScopedVar[Symbol]
- val methodTailJumpLabelSym = new ScopedVar[Symbol]
- val methodTailJumpFormalArgs = new ScopedVar[List[Symbol]]
- val paramAccessorLocals = new ScopedVar(Map.empty[Symbol, js.ParamDef])
-
- var isModuleInitialized: Boolean = false // see genApply for super calls
-
- def currentClassType = encodeClassType(currentClassSym)
-
- // Fresh local name generator ----------------------------------------------
-
- val usedLocalNames = mutable.Set.empty[String]
- val localSymbolNames = mutable.Map.empty[Symbol, String]
- private val isKeywordOrReserved =
- js.isKeyword ++ Seq("arguments", ScalaJSEnvironmentName)
-
- def freshName(base: String = "x"): String = {
- var suffix = 1
- var longName = base
- while (usedLocalNames(longName) || isKeywordOrReserved(longName)) {
- suffix += 1
- longName = base+"$"+suffix
- }
- usedLocalNames += longName
- longName
- }
-
- def freshName(sym: Symbol): String =
- localSymbolNames.getOrElseUpdate(sym, freshName(sym.name.toString))
-
- // Rewriting of anonymous function classes ---------------------------------
-
- private val translatedAnonFunctions =
- mutable.Map.empty[Symbol,
- (/*ctor args:*/ List[js.Tree] => /*instance:*/ js.Tree, ClassInfoBuilder)]
- private val instantiatedAnonFunctions =
- mutable.Set.empty[Symbol]
- private val undefinedDefaultParams =
- mutable.Set.empty[Symbol]
-
- // Top-level apply ---------------------------------------------------------
-
- override def run() {
- scalaPrimitives.init()
- jsPrimitives.init()
- super.run()
- }
-
- /** Generate JS code for a compilation unit
- * This method iterates over all the class and interface definitions
- * found in the compilation unit and emits their code (.js) and type
- * definitions (.jstype).
- *
- * Classes representing primitive types, as well as the scala.Array
- * class, are not actually emitted.
- *
- * Other ClassDefs are emitted according to their nature:
- * * Interface -> `genInterface()`
- * * Implementation class -> `genImplClass()`
- * * Raw JS type (<: js.Any) -> `genRawJSClassData()`
- * * Normal class -> `genClass()`
- * + `genModuleAccessor()` if module class
- *
- * The resulting tree is desugared with `JSDesugaring`, and then sent to
- * disc with `GenJSFiles`.
- *
- * Type definitions (i.e., pickles) for top-level representatives are also
- * emitted.
- */
- override def apply(cunit: CompilationUnit) {
- try {
- val generatedClasses = ListBuffer.empty[(Symbol, js.Tree, ClassInfoBuilder)]
-
- def collectClassDefs(tree: Tree): List[ClassDef] = {
- tree match {
- case EmptyTree => Nil
- case PackageDef(_, stats) => stats flatMap collectClassDefs
- case cd: ClassDef => cd :: Nil
- }
- }
- val allClassDefs = collectClassDefs(cunit.body)
-
- /* First gen and record lambdas for js.FunctionN and js.ThisFunctionN.
- * Since they are SAMs, there cannot be dependencies within this set,
- * and hence we are sure we can record them before they are used,
- * which is critical for these.
- */
- val nonRawJSFunctionDefs = allClassDefs filterNot { cd =>
- if (isRawJSFunctionDef(cd.symbol)) {
- genAndRecordRawJSFunctionClass(cd)
- true
- } else {
- false
- }
- }
-
- /* Then try to gen and record lambdas for scala.FunctionN.
- * These may fail, and sometimes because of dependencies. Since there
- * appears to be more forward dependencies than backward dependencies
- * (at least for non-nested lambdas, which we cannot translate anyway),
- * we process class defs in reverse order here.
- */
- val fullClassDefs = (nonRawJSFunctionDefs.reverse filterNot { cd =>
- cd.symbol.isAnonymousFunction && tryGenAndRecordAnonFunctionClass(cd)
- }).reverse
-
- /* Finally, we emit true code for the remaining class defs. */
- for (cd <- fullClassDefs) {
- val sym = cd.symbol
- implicit val pos = sym.pos
-
- /* Do not actually emit code for primitive types nor scala.Array. */
- val isPrimitive =
- isPrimitiveValueClass(sym) || (sym == ArrayClass)
-
- /* Similarly, do not emit code for impl classes of raw JS traits. */
- val isRawJSImplClass =
- sym.isImplClass && isRawJSType(
- sym.owner.info.decl(sym.name.dropRight(nme.IMPL_CLASS_SUFFIX.length)).tpe)
-
- if (!isPrimitive && !isRawJSImplClass) {
- withScopedVars(
- currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass),
- currentClassSym := sym
- ) {
- val tree = if (isRawJSType(sym.tpe)) {
- assert(!isRawJSFunctionDef(sym),
- s"Raw JS function def should have been recorded: $cd")
- genRawJSClassData(cd)
- } else if (sym.isInterface) {
- genInterface(cd)
- } else if (sym.isImplClass) {
- genImplClass(cd)
- } else if (isHijackedBoxedClass(sym)) {
- genHijackedBoxedClassData(cd)
- } else {
- genClass(cd)
- }
- generatedClasses += ((sym, tree, currentClassInfoBuilder.get))
- }
- }
- }
-
- val clDefs = generatedClasses.map(_._2).toList
- generatedJSAST(clDefs)
-
- for ((sym, tree, infoBuilder) <- generatedClasses) {
- genIRFile(cunit, sym, tree, infoBuilder.result())
- }
- } finally {
- translatedAnonFunctions.clear()
- instantiatedAnonFunctions.clear()
- undefinedDefaultParams.clear()
- pos2irPosCache.clear()
- }
- }
-
- // Generate a class --------------------------------------------------------
-
- /** Gen JS code for a class definition (maybe a module class)
- * It emits:
- * * An ES6 class declaration with:
- * - A constructor creating all the fields (ValDefs)
- * - Methods (DefDefs), including the Scala constructor
- * - JS-friendly bridges for all public methods (with non-mangled names)
- * * An inheritable constructor, used to create the prototype of subclasses
- * * A JS-friendly constructor bridge, if there is a public constructor
- * * Functions for instance tests
- * * The class data record
- */
- def genClass(cd: ClassDef): js.Tree = {
- val ClassDef(mods, name, _, impl) = cd
- val sym = cd.symbol
- implicit val pos = sym.pos
-
- assert(!sym.isInterface && !sym.isImplClass,
- "genClass() must be called only for normal classes: "+sym)
- assert(sym.superClass != NoSymbol, sym)
-
- val classIdent = encodeClassFullNameIdent(sym)
-
- // Generate members (constructor + methods)
-
- val generatedMembers = new ListBuffer[js.Tree]
- val exportedSymbols = new ListBuffer[Symbol]
-
- generatedMembers ++= genClassFields(cd)
-
- def gen(tree: Tree): Unit = {
- tree match {
- case EmptyTree => ()
- case Template(_, _, body) => body foreach gen
-
- case ValDef(mods, name, tpt, rhs) =>
- () // fields are added via genClassFields()
-
- case dd: DefDef =>
- val sym = dd.symbol
- generatedMembers ++= genMethod(dd)
-
- if (jsInterop.isExport(sym)) {
- // We add symbols that we have to export here. This way we also
- // get inherited stuff that is implemented in this class.
- exportedSymbols += sym
- }
-
- case _ => abort("Illegal tree in gen of genClass(): " + tree)
- }
- }
-
- gen(impl)
-
- // Create method info builder for exported stuff
- val exports = withScopedVars(
- currentMethodInfoBuilder := currentClassInfoBuilder.addMethod(
- dceExportName + classIdent.name, isExported = true)
- ) {
- // Generate the exported members
- val memberExports = genMemberExports(sym, exportedSymbols.toList)
-
- // Generate exported constructors or accessors
- val exportedConstructorsOrAccessors =
- if (isStaticModule(sym)) genModuleAccessorExports(sym)
- else genConstructorExports(sym)
- if (exportedConstructorsOrAccessors.nonEmpty)
- currentClassInfoBuilder.isExported = true
-
- memberExports ++ exportedConstructorsOrAccessors
- }
-
- // Generate the reflective call proxies (where required)
- val reflProxies = genReflCallProxies(sym)
-
- // The complete class definition
- val classDefinition = js.ClassDef(
- classIdent,
- if (sym.isModuleClass) ClassKind.ModuleClass else ClassKind.Class,
- Some(encodeClassFullNameIdent(sym.superClass)),
- sym.ancestors.map(encodeClassFullNameIdent),
- generatedMembers.toList ++ exports ++ reflProxies)
-
- classDefinition
- }
-
- // Generate the class data of a raw JS class -------------------------------
-
- /** Gen JS code creating the class data of a raw JS class
- */
- def genRawJSClassData(cd: ClassDef): js.Tree = {
- val sym = cd.symbol
- implicit val pos = sym.pos
-
- // Check that RawJS type is not exported
- for ( (_, pos) <- jsInterop.exportsOf(sym) ) {
- currentUnit.error(pos, "You may not export a class extending js.Any")
- }
-
- val classIdent = encodeClassFullNameIdent(sym)
- js.ClassDef(classIdent, ClassKind.RawJSType, None, Nil, Nil)
- }
-
- // Generate the class data of a hijacked boxed class -----------------------
-
- /** Gen JS code creating the class data of a hijacked boxed class
- */
- def genHijackedBoxedClassData(cd: ClassDef): js.Tree = {
- val sym = cd.symbol
- implicit val pos = sym.pos
- val classIdent = encodeClassFullNameIdent(sym)
- js.ClassDef(classIdent, ClassKind.HijackedClass, None, Nil, Nil)
- }
-
- // Generate an interface ---------------------------------------------------
-
- /** Gen JS code for an interface definition
- * This is very simple, as interfaces have virtually no existence at
- * runtime. They exist solely for reflection purposes.
- */
- def genInterface(cd: ClassDef): js.Tree = {
- val sym = cd.symbol
- implicit val pos = sym.pos
-
- val classIdent = encodeClassFullNameIdent(sym)
-
- // fill in class info builder
- def gen(tree: Tree) {
- tree match {
- case EmptyTree => ()
- case Template(_, _, body) => body foreach gen
- case dd: DefDef =>
- currentClassInfoBuilder.addMethod(
- encodeMethodName(dd.symbol), isAbstract = true)
- case _ => abort("Illegal tree in gen of genInterface(): " + tree)
- }
- }
- gen(cd.impl)
-
- // Check that interface/trait is not exported
- for ( (_, pos) <- jsInterop.exportsOf(sym) ) {
- currentUnit.error(pos, "You may not export a trait")
- }
-
- js.ClassDef(classIdent, ClassKind.Interface, None,
- sym.ancestors.map(encodeClassFullNameIdent), Nil)
- }
-
- // Generate an implementation class of a trait -----------------------------
-
- /** Gen JS code for an implementation class (of a trait)
- */
- def genImplClass(cd: ClassDef): js.Tree = {
- val ClassDef(mods, name, _, impl) = cd
- val sym = cd.symbol
- implicit val pos = sym.pos
-
- def gen(tree: Tree): List[js.MethodDef] = {
- tree match {
- case EmptyTree => Nil
- case Template(_, _, body) => body.flatMap(gen)
-
- case dd: DefDef =>
- val m = genMethod(dd)
- m.toList
-
- case _ => abort("Illegal tree in gen of genImplClass(): " + tree)
- }
- }
- val generatedMethods = gen(impl)
-
- js.ClassDef(encodeClassFullNameIdent(sym), ClassKind.TraitImpl,
- None, Nil, generatedMethods)
- }
-
- // Generate the fields of a class ------------------------------------------
-
- /** Gen definitions for the fields of a class.
- * The fields are initialized with the zero of their types.
- */
- def genClassFields(cd: ClassDef): List[js.VarDef] = withScopedVars(
- currentMethodInfoBuilder :=
- currentClassInfoBuilder.addMethod("__init__")
- ) {
- // Non-method term members are fields, except for module members.
- (for {
- f <- currentClassSym.info.decls
- if !f.isMethod && f.isTerm && !f.isModule
- } yield {
- implicit val pos = f.pos
- js.VarDef(encodeFieldSym(f), toTypeKind(f.tpe).toIRType,
- mutable = f.isMutable, genZeroOf(f.tpe))
- }).toList
- }
-
- // Generate a method -------------------------------------------------------
-
- def genMethod(dd: DefDef): Option[js.MethodDef] =
- genMethodWithInfoBuilder(dd).map(_._1)
-
- /** Gen JS code for a method definition in a class.
- * On the JS side, method names are mangled to encode the full signature
- * of the Scala method, as described in `JSEncoding`, to support
- * overloading.
- *
- * Some methods are not emitted at all:
- * * Primitives, since they are never actually called
- * * Abstract methods (alternative: throw a java.lang.AbstractMethodError)
- * * Trivial constructors, which only call their super constructor, with
- * the same signature, and the same arguments. The JVM needs these
- * constructors, but not JavaScript. Since there are lots of them, we
- * take the trouble of recognizing and removing them.
- *
- * Constructors are emitted by generating their body as a statement, then
- * return `this`.
- *
- * Other (normal) methods are emitted with `genMethodBody()`.
- */
- def genMethodWithInfoBuilder(
- dd: DefDef): Option[(js.MethodDef, MethodInfoBuilder)] = {
-
- implicit val pos = dd.pos
- val DefDef(mods, name, _, vparamss, _, rhs) = dd
- val sym = dd.symbol
-
- isModuleInitialized = false
-
- val result = withScopedVars(
- currentMethodSym := sym,
- methodTailJumpThisSym := NoSymbol,
- methodTailJumpLabelSym := NoSymbol,
- methodTailJumpFormalArgs := Nil
- ) {
-
- /* Do NOT clear usedLocalNames and localSymbolNames!
- * genAndRecordAnonFunctionClass() starts populating them before
- * genMethod() starts.
- */
-
- assert(vparamss.isEmpty || vparamss.tail.isEmpty,
- "Malformed parameter list: " + vparamss)
- val params = if (vparamss.isEmpty) Nil else vparamss.head map (_.symbol)
-
- assert(!sym.owner.isInterface,
- "genMethod() must not be called for methods in interfaces: "+sym)
-
- val methodIdent = encodeMethodSym(sym)
-
- def createInfoBuilder(isAbstract: Boolean = false) = {
- currentClassInfoBuilder.addMethod(methodIdent.name,
- isAbstract = isAbstract,
- isExported = sym.isClassConstructor &&
- jsInterop.exportsOf(sym).nonEmpty)
- }
-
- if (scalaPrimitives.isPrimitive(sym)) {
- None
- } else if (sym.isDeferred) {
- createInfoBuilder(isAbstract = true)
- None
- } else if (isTrivialConstructor(sym, params, rhs)) {
- createInfoBuilder().callsMethod(sym.owner.superClass, methodIdent)
- None
- } else {
- withScopedVars(
- currentMethodInfoBuilder := createInfoBuilder()
- ) {
- currentMethodInfoBuilder.optimizerHints =
- currentMethodInfoBuilder.optimizerHints.copy(
- isAccessor = sym.isAccessor,
- hasInlineAnnot = sym.hasAnnotation(InlineAnnotationClass))
-
- val jsParams = for (param <- params) yield {
- implicit val pos = param.pos
- js.ParamDef(encodeLocalSym(param, freshName),
- toTypeKind(param.tpe).toIRType)
- }
-
- val (resultType, body) = {
- if (sym.isClassConstructor) {
- (currentClassType, js.Block(
- genStat(rhs), js.Return(js.This()(currentClassType))))
- } else {
- val resultKind = toTypeKind(sym.tpe.resultType)
- (resultKind.toIRType, genMethodBody(rhs, params, resultKind))
- }
- }
-
- val methodDef =
- js.MethodDef(methodIdent, jsParams, resultType, body)
-
- Some((methodDef, currentMethodInfoBuilder.get))
- }
- }
- }
-
- usedLocalNames.clear()
- localSymbolNames.clear()
-
- result
- }
-
- private def isTrivialConstructor(sym: Symbol, params: List[Symbol],
- rhs: Tree): Boolean = {
- if (!sym.isClassConstructor) {
- false
- } else {
- rhs match {
- // Shape of a constructor that only calls super
- case Block(List(Apply(fun @ Select(_:Super, _), args)), Literal(_)) =>
- val callee = fun.symbol
- implicit val dummyPos = NoPosition
-
- // Does the callee have the same signature as sym
- if (encodeMethodSym(sym) == encodeMethodSym(callee)) {
- // Test whether args are trivial forwarders
- assert(args.size == params.size, "Argument count mismatch")
- params.zip(args) forall { case (param, arg) =>
- arg.symbol == param
- }
- } else {
- false
- }
-
- case _ => false
- }
- }
- }
-
- /**
- * Generates reflective proxy methods for methods in sym
- *
- * Reflective calls don't depend on the return type, so it's hard to
- * generate calls without using runtime reflection to list the methods. We
- * generate a method to be used for reflective calls (without return
- * type in the name).
- *
- * There are cases where non-trivial overloads cause ambiguous situations:
- *
- * {{{
- * object A {
- * def foo(x: Option[Int]): String
- * def foo(x: Option[String]): Int
- * }
- * }}}
- *
- * This is completely legal code, but due to the same erased parameter
- * type of the {{{foo}}} overloads, they cannot be disambiguated in a
- * reflective call, as the exact return type is unknown at the call site.
- *
- * Cases like the upper currently fail on the JVM backend at runtime. The
- * Scala.js backend uses the following rules for selection (which will
- * also cause runtime failures):
- *
- * - If a proxy with the same signature (method name and parameters)
- * exists in the superclass, no proxy is generated (proxy is inherited)
- * - If no proxy exists in the superclass, a proxy is generated for the
- * first method with matching signatures.
- */
- def genReflCallProxies(sym: Symbol): List[js.Tree] = {
- import scala.reflect.internal.Flags
-
- // Flags of members we do not want to consider for reflective call proxys
- val excludedFlags = (
- Flags.BRIDGE |
- Flags.PRIVATE |
- Flags.MACRO
- )
-
- /** Check if two method symbols conform in name and parameter types */
- def weakMatch(s1: Symbol)(s2: Symbol) = {
- val p1 = s1.tpe.params
- val p2 = s2.tpe.params
- s1 == s2 || // Shortcut
- s1.name == s2.name &&
- p1.size == p2.size &&
- (p1 zip p2).forall { case (s1,s2) =>
- s1.tpe =:= s2.tpe
- }
- }
-
- /** Check if the symbol's owner's superclass has a matching member (and
- * therefore an existing proxy).
- */
- def superHasProxy(s: Symbol) = {
- val alts = sym.superClass.tpe.findMember(
- name = s.name,
- excludedFlags = excludedFlags,
- requiredFlags = Flags.METHOD,
- stableOnly = false).alternatives
- alts.exists(weakMatch(s) _)
- }
-
- // Query candidate methods
- val methods = sym.tpe.findMembers(
- excludedFlags = excludedFlags,
- requiredFlags = Flags.METHOD)
-
- val candidates = methods filterNot { s =>
- s.isConstructor ||
- superHasProxy(s) ||
- jsInterop.isExport(s)
- }
-
- val proxies = candidates filter {
- c => candidates.find(weakMatch(c) _).get == c
- }
-
- proxies.map(genReflCallProxy _).toList
- }
-
- /** actually generates reflective call proxy for the given method symbol */
- private def genReflCallProxy(sym: Symbol): js.Tree = {
- implicit val pos = sym.pos
-
- val proxyIdent = encodeMethodSym(sym, reflProxy = true)
-
- withScopedVars(
- currentMethodInfoBuilder :=
- currentClassInfoBuilder.addMethod(proxyIdent.name)
- ) {
- val jsParams = for (param <- sym.tpe.params) yield {
- implicit val pos = param.pos
- val name = encodeLocalSym(param, freshName)
- val tpe = toTypeKind(param.tpe).toIRType
- (js.ParamDef(name, tpe), js.VarRef(name, false)(tpe))
- }
-
- val call = genApplyMethod(js.This()(currentClassType), sym.owner, sym,
- jsParams.map(_._2))
- val value = ensureBoxed(call,
- enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType))
-
- val body = js.Return(value)
-
- js.MethodDef(proxyIdent, jsParams.map(_._1), jstpe.AnyType, body)
- }
- }
-
- /** Generate the body of a (non-constructor) method
- *
- * Most normal methods are emitted straightforwardly. If the result
- * type is Unit, then the body is emitted as a statement. Otherwise, it is
- * emitted as an expression and wrapped in a `js.Return()` statement.
- *
- * The additional complexity of this method handles the transformation of
- * recursive tail calls. The `tailcalls` phase unhelpfully transforms
- * them as one big LabelDef surrounding the body of the method, and
- * label-Apply's for recursive tail calls.
- * Here, we transform the outer LabelDef into a labelled `while (true)`
- * loop. Label-Apply's to the LabelDef are turned into a `continue` of
- * that loop. The body of the loop is a `js.Return()` of the body of the
- * LabelDef (even if the return type is Unit), which will break out of
- * the loop as necessary.
- */
- def genMethodBody(tree: Tree, paramsSyms: List[Symbol],
- resultTypeKind: TypeKind): js.Tree = {
- implicit val pos = tree.pos
-
- tree match {
- case Block(
- List(thisDef @ ValDef(_, nme.THIS, _, initialThis)),
- ld @ LabelDef(labelName, _, rhs)) =>
- // This method has tail jumps
- withScopedVars(
- (methodTailJumpLabelSym := ld.symbol) +:
- (initialThis match {
- case This(_) => Seq(
- methodTailJumpThisSym := thisDef.symbol,
- methodTailJumpFormalArgs := thisDef.symbol :: paramsSyms)
- case Ident(_) => Seq(
- methodTailJumpThisSym := NoSymbol,
- methodTailJumpFormalArgs := paramsSyms)
- }) :_*
- ) {
- val theLoop =
- js.While(js.BooleanLiteral(true), js.Return(genExpr(rhs)),
- Some(js.Ident("tailCallLoop")))
-
- if (methodTailJumpThisSym.get == NoSymbol) {
- theLoop
- } else {
- js.Block(
- js.VarDef(encodeLocalSym(methodTailJumpThisSym, freshName),
- currentClassType, mutable = true,
- js.This()(currentClassType)),
- theLoop)
- }
-
- }
-
- case _ =>
- val bodyIsStat = resultTypeKind == UNDEFINED
- if (bodyIsStat) genStat(tree)
- else js.Return(genExpr(tree))
- }
- }
-
- /** Gen JS code for a tree in statement position (from JS's perspective)
- *
- * Here we handle Assign trees directly. All other types of nodes are
- * redirected `genExpr()`.
- */
- def genStat(tree: Tree): js.Tree = {
- implicit val pos = tree.pos
-
- tree match {
- /** qualifier.field = rhs */
- case Assign(lhs @ Select(qualifier, _), rhs) =>
- val sym = lhs.symbol
-
- val member =
- if (sym.isStaticMember) {
- genStaticMember(sym)
- } else {
- js.Select(genExpr(qualifier), encodeFieldSym(sym),
- mutable = true)(toIRType(sym.tpe))
- }
-
- js.Assign(member, genExpr(rhs))
-
- /** lhs = rhs */
- case Assign(lhs, rhs) =>
- val sym = lhs.symbol
- js.Assign(
- js.VarRef(encodeLocalSym(sym, freshName), mutable = true)(
- (toIRType(sym.tpe))),
- genExpr(rhs))
-
- case _ =>
- exprToStat(genExpr(tree))
- }
- }
-
- /** Turn a JavaScript statement into an expression of type Unit */
- def statToExpr(tree: js.Tree): js.Tree = {
- implicit val pos = tree.pos
- js.Block(tree, js.Undefined())
- }
-
- /** Turn a JavaScript expression of type Unit into a statement */
- def exprToStat(tree: js.Tree): js.Tree = {
- /* Any JavaScript expression is also a statement, but at least we get rid
- * of the stupid js.Block(..., js.Undefined()) that we create ourselves
- * in statToExpr().
- * We also remove any uV() wrapper.
- */
- implicit val pos = tree.pos
- tree match {
- case js.Block(stats :+ js.Undefined()) => js.Block(stats)
- case js.Undefined() => js.Skip()
- case js.CallHelper("uV", List(expr)) => exprToStat(expr)
- case _ => tree
- }
- }
-
- /** Gen JS code for a tree in expression position (from JS's perspective)
- *
- * This is the main transformation method. Each node of the Scala AST
- * is transformed into an equivalent portion of the JS AST.
- */
- def genExpr(tree: Tree): js.Tree = {
- implicit val pos = tree.pos
-
- /** Predicate satisfied by LabelDefs produced by the pattern matcher */
- def isCaseLabelDef(tree: Tree) =
- tree.isInstanceOf[LabelDef] && hasSynthCaseSymbol(tree)
-
- tree match {
- /** LabelDefs (for while and do..while loops) */
- case lblDf: LabelDef =>
- genLabelDef(lblDf)
-
- /** val nme.THIS = this
- * Must have been eliminated by the tail call transform performed
- * by `genMethodBody()`.
- */
- case ValDef(_, nme.THIS, _, _) =>
- abort("ValDef(_, nme.THIS, _, _) found at: " + tree.pos)
-
- /** Local val or var declaration */
- case ValDef(_, name, _, rhs) =>
- val sym = tree.symbol
- val rhsTree =
- if (rhs == EmptyTree) genZeroOf(sym.tpe)
- else genExpr(rhs)
-
- rhsTree match {
- case js.UndefinedParam() =>
- // This is an intermediate assignment for default params on a
- // js.Any. Add the symbol to the corresponding set to inform
- // the Ident resolver how to replace it and don't emit the symbol
- undefinedDefaultParams += sym
- statToExpr(js.Skip())
- case _ =>
- statToExpr(js.VarDef(encodeLocalSym(sym, freshName),
- toIRType(sym.tpe), sym.isMutable, rhsTree))
- }
-
- case If(cond, thenp, elsep) =>
- js.If(genExpr(cond), genExpr(thenp), genExpr(elsep))(
- toIRType(tree.tpe))
-
- case Return(expr) =>
- js.Return(genExpr(expr))
-
- case t: Try =>
- genTry(t)
-
- case Throw(expr) =>
- val ex = genExpr(expr)
- if (isMaybeJavaScriptException(expr.tpe))
- js.Throw(js.CallHelper("unwrapJavaScriptException", ex)(jstpe.AnyType))
- else
- js.Throw(ex)
-
- case app: Apply =>
- genApply(app)
-
- case app: ApplyDynamic =>
- genApplyDynamic(app)
-
- /** this
- * Normally encoded straightforwardly as a JS this.
- * But must be replaced by the tail-jump-this local variable if there
- * is one.
- */
- case This(qual) =>
- val symIsModuleClass = tree.symbol.isModuleClass
- assert(tree.symbol == currentClassSym.get || symIsModuleClass,
- "Trying to access the this of another class: " +
- "tree.symbol = " + tree.symbol +
- ", class symbol = " + currentClassSym.get +
- " compilation unit:" + currentUnit)
- if (symIsModuleClass && tree.symbol != currentClassSym.get) {
- genLoadModule(tree.symbol)
- } else if (methodTailJumpThisSym.get != NoSymbol) {
- js.VarRef(
- encodeLocalSym(methodTailJumpThisSym, freshName),
- mutable = true)(currentClassType)
- } else {
- js.This()(currentClassType)
- }
-
- case Select(Ident(nme.EMPTY_PACKAGE_NAME), module) =>
- assert(tree.symbol.isModule,
- "Selection of non-module from empty package: " + tree +
- " sym: " + tree.symbol + " at: " + (tree.pos))
- genLoadModule(tree.symbol)
-
- case Select(qualifier, selector) =>
- val sym = tree.symbol
-
- if (sym.isModule) {
- if (settings.debug.value)
- log("LOAD_MODULE from Select(qualifier, selector): " + sym)
- assert(!tree.symbol.isPackageClass, "Cannot use package as value: " + tree)
- genLoadModule(sym)
- } else if (sym.isStaticMember) {
- genStaticMember(sym)
- } else if (paramAccessorLocals contains sym) {
- paramAccessorLocals(sym).ref
- } else {
- js.Select(genExpr(qualifier), encodeFieldSym(sym),
- mutable = sym.isMutable)(toIRType(sym.tpe))
- }
-
- case Ident(name) =>
- val sym = tree.symbol
- if (!sym.isPackage) {
- if (sym.isModule) {
- assert(!sym.isPackageClass, "Cannot use package as value: " + tree)
- genLoadModule(sym)
- } else if (undefinedDefaultParams contains sym) {
- // This is a default parameter whose assignment was moved to
- // a local variable. Put a literal undefined param again
- js.UndefinedParam()(toIRType(sym.tpe))
- } else {
- js.VarRef(encodeLocalSym(sym, freshName),
- mutable = sym.isMutable)(toIRType(sym.tpe))
- }
- } else {
- sys.error("Cannot use package as value: " + tree)
- }
-
- case Literal(value) =>
- value.tag match {
- case UnitTag =>
- js.Undefined()
- case BooleanTag =>
- js.BooleanLiteral(value.booleanValue)
- case ByteTag | ShortTag | CharTag | IntTag =>
- js.IntLiteral(value.intValue)
- case LongTag =>
- // Convert literal to triplet (at compile time!)
- val (l,m,h) = JSConversions.scalaLongToTriplet(value.longValue)
- genLongModuleCall("apply", js.IntLiteral(l), js.IntLiteral(m), js.IntLiteral(h))
- case FloatTag | DoubleTag =>
- js.DoubleLiteral(value.doubleValue)
- case StringTag =>
- js.StringLiteral(value.stringValue)
- case NullTag =>
- js.Null()
- case ClazzTag =>
- genClassConstant(value.typeValue)
- case EnumTag =>
- genStaticMember(value.symbolValue)
- }
-
- /** Block that appeared as the result of a translated match
- * Such blocks are recognized by having at least one element that is
- * a so-called case-label-def.
- * The method `genTranslatedMatch()` takes care of compiling the
- * actual match.
- */
- case Block(stats, expr) if (expr +: stats) exists isCaseLabelDef =>
- /* The assumption is once we encounter a case, the remainder of the
- * block will consist of cases.
- * The prologue may be empty, usually it is the valdef that stores
- * the scrut.
- */
- val (prologue, cases) = stats span (s => !isCaseLabelDef(s))
- assert((expr +: cases) forall isCaseLabelDef,
- "Assumption on the form of translated matches broken: " + tree)
-
- val translatedMatch =
- genTranslatedMatch(cases map (_.asInstanceOf[LabelDef]),
- expr.asInstanceOf[LabelDef])
-
- js.Block((prologue map genStat) :+ translatedMatch)
-
- /** Normal block */
- case Block(stats, expr) =>
- val statements = stats map genStat
- val expression = genExpr(expr)
- js.Block(statements :+ expression)
-
- case Typed(Super(_, _), _) =>
- genExpr(This(currentClassSym))
-
- case Typed(expr, _) =>
- genExpr(expr)
-
- case Assign(_, _) =>
- statToExpr(genStat(tree))
-
- /** Array constructor */
- case av: ArrayValue =>
- genArrayValue(av)
-
- /** A Match reaching the backend is supposed to be optimized as a switch */
- case mtch: Match =>
- genMatch(mtch)
-
- /** Anonymous function (only with -Ydelambdafy:method) */
- case fun: Function =>
- genAnonFunction(fun)
-
- case EmptyTree =>
- // TODO Hum, I do not think this is OK
- js.Undefined()
-
- case _ =>
- abort("Unexpected tree in genExpr: " +
- tree + "/" + tree.getClass + " at: " + tree.pos)
- }
- } // end of GenJSCode.genExpr()
-
- /** Gen JS code for LabelDef
- * The only LabelDefs that can reach here are the desugaring of
- * while and do..while loops. All other LabelDefs (for tail calls or
- * matches) are caught upstream and transformed in ad hoc ways.
- *
- * So here we recognize all the possible forms of trees that can result
- * of while or do..while loops, and we reconstruct the loop for emission
- * to JS.
- */
- def genLabelDef(tree: LabelDef): js.Tree = {
- implicit val pos = tree.pos
- val sym = tree.symbol
-
- tree match {
- // while (cond) { body }
- case LabelDef(lname, Nil,
- If(cond,
- Block(bodyStats, Apply(target @ Ident(lname2), Nil)),
- Literal(_))) if (target.symbol == sym) =>
- statToExpr(js.While(genExpr(cond), js.Block(bodyStats map genStat)))
-
- // while (cond) { body }; result
- case LabelDef(lname, Nil,
- Block(List(
- If(cond,
- Block(bodyStats, Apply(target @ Ident(lname2), Nil)),
- Literal(_))),
- result)) if (target.symbol == sym) =>
- js.Block(
- js.While(genExpr(cond), js.Block(bodyStats map genStat)),
- genExpr(result))
-
- // while (true) { body }
- case LabelDef(lname, Nil,
- Block(bodyStats,
- Apply(target @ Ident(lname2), Nil))) if (target.symbol == sym) =>
- statToExpr(js.While(js.BooleanLiteral(true),
- js.Block(bodyStats map genStat)))
-
- // while (false) { body }
- case LabelDef(lname, Nil, Literal(Constant(()))) =>
- js.Skip()
-
- // do { body } while (cond)
- case LabelDef(lname, Nil,
- Block(bodyStats,
- If(cond,
- Apply(target @ Ident(lname2), Nil),
- Literal(_)))) if (target.symbol == sym) =>
- statToExpr(js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond)))
-
- // do { body } while (cond); result
- case LabelDef(lname, Nil,
- Block(
- bodyStats :+
- If(cond,
- Apply(target @ Ident(lname2), Nil),
- Literal(_)),
- result)) if (target.symbol == sym) =>
- js.Block(
- js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond)),
- genExpr(result))
-
- case _ =>
- abort("Found unknown label def at "+tree.pos+": "+tree)
- }
- }
-
- /** Gen JS code for a try..catch or try..finally block
- *
- * try..finally blocks are compiled straightforwardly to try..finally
- * blocks of JS.
- *
- * try..catch blocks are a little more subtle, as JS does not have
- * type-based selection of exceptions to catch. We thus encode explicitly
- * the type tests, like in:
- *
- * try { ... }
- * catch (e) {
- * if (e.isInstanceOf[IOException]) { ... }
- * else if (e.isInstanceOf[Exception]) { ... }
- * else {
- * throw e; // default, re-throw
- * }
- * }
- */
- def genTry(tree: Try): js.Tree = {
- implicit val jspos = tree.pos
- val Try(block, catches, finalizer) = tree
-
- val blockAST = genExpr(block)
- val exceptIdent = js.Ident(freshName("ex"))
- val exceptVar = js.VarRef(exceptIdent, mutable = true)(jstpe.AnyType)
- val resultType = toIRType(tree.tpe)
-
- val handlerAST = {
- if (catches.isEmpty) {
- js.EmptyTree
- } else {
- val mightCatchJavaScriptException = catches.exists { caseDef =>
- caseDef.pat match {
- case Typed(Ident(nme.WILDCARD), tpt) =>
- isMaybeJavaScriptException(tpt.tpe)
- case Ident(nme.WILDCARD) =>
- true
- case pat @ Bind(_, _) =>
- isMaybeJavaScriptException(pat.symbol.tpe)
- }
- }
-
- val elseHandler: js.Tree =
- if (mightCatchJavaScriptException)
- js.Throw(js.CallHelper("unwrapJavaScriptException", exceptVar)(jstpe.AnyType))
- else
- js.Throw(exceptVar)
-
- val handler0 = catches.foldRight(elseHandler) { (caseDef, elsep) =>
- implicit val jspos = caseDef.pos
- val CaseDef(pat, _, body) = caseDef
-
- // Extract exception type and variable
- val (tpe, boundVar) = (pat match {
- case Typed(Ident(nme.WILDCARD), tpt) =>
- (tpt.tpe, None)
- case Ident(nme.WILDCARD) =>
- (ThrowableClass.tpe, None)
- case Bind(_, _) =>
- (pat.symbol.tpe, Some(encodeLocalSym(pat.symbol, freshName)))
- })
-
- // Generate the body that must be executed if the exception matches
- val bodyWithBoundVar = (boundVar match {
- case None => genExpr(body)
- case Some(bv) =>
- js.Block(
- js.VarDef(bv, toIRType(tpe), mutable = false, exceptVar),
- genExpr(body))
- })
-
- // Generate the test
- if (tpe == ThrowableClass.tpe) {
- bodyWithBoundVar
- } else {
- val cond = genIsInstanceOf(tpe, exceptVar)
- js.If(cond, bodyWithBoundVar, elsep)(resultType)
- }
- }
-
- if (mightCatchJavaScriptException) {
- js.Block(
- js.Assign(exceptVar,
- js.CallHelper("wrapJavaScriptException", exceptVar)(
- encodeClassType(ThrowableClass))),
- handler0)
- } else {
- handler0
- }
- }
- }
-
- val finalizerAST = genStat(finalizer) match {
- case js.Skip() => js.EmptyTree
- case ast => ast
- }
-
- if (handlerAST == js.EmptyTree && finalizerAST == js.EmptyTree) blockAST
- else js.Try(blockAST, exceptIdent, handlerAST, finalizerAST)(resultType)
- }
-
- /** Gen JS code for an Apply node (method call)
- *
- * There's a whole bunch of varieties of Apply nodes: regular method
- * calls, super calls, constructor calls, isInstanceOf/asInstanceOf,
- * primitives, JS calls, etc. They are further dispatched in here.
- */
- def genApply(tree: Tree): js.Tree = {
- implicit val pos = tree.pos
-
- tree match {
- /** isInstanceOf and asInstanceOf
- * The two only methods that keep their type argument until the
- * backend.
- */
- case Apply(TypeApply(fun, targs), _) =>
- val sym = fun.symbol
- val cast = sym match {
- case Object_isInstanceOf => false
- case Object_asInstanceOf => true
- case _ =>
- abort("Unexpected type application " + fun +
- "[sym: " + sym.fullName + "]" + " in: " + tree)
- }
-
- val Select(obj, _) = fun
- val to = targs.head.tpe
- val l = toTypeKind(obj.tpe)
- val r = toTypeKind(to)
- val source = genExpr(obj)
-
- if (l.isValueType && r.isValueType) {
- if (cast)
- genConversion(l, r, source)
- else
- js.BooleanLiteral(l == r)
- }
- else if (l.isValueType) {
- val result = if (cast) {
- val ctor = ClassCastExceptionClass.info.member(
- nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty)
- js.Throw(genNew(ClassCastExceptionClass, ctor, Nil))
- } else {
- js.BooleanLiteral(false)
- }
- js.Block(source, result) // eval and discard source
- }
- else if (r.isValueType && cast) {
- // Erasure should have added an unboxing operation to prevent that.
- assert(false, tree)
- source
- }
- else if (r.isValueType)
- genIsInstanceOf(boxedClass(to.typeSymbol).tpe, source)
- else if (cast)
- genAsInstanceOf(to, source)
- else
- genIsInstanceOf(to, source)
-
- /** Super call of the form Class.super[mix].fun(args)
- * This does not include calls defined in mixin traits, as these are
- * already desugared by the 'mixin' phase. Only calls to super
- * classes remain.
- * Since a class has exactly one direct superclass, and calling a
- * method two classes above the current one is invalid, I believe
- * the `mix` item is irrelevant.
- */
- case Apply(fun @ Select(sup @ Super(_, mix), _), args) =>
- if (settings.debug.value)
- log("Call to super: " + tree)
-
- /* We produce a desugared JavaScript super call immediately,
- * because we might have to use the special `methodTailJumpThisSym`
- * instead of the js.This() that would be output by the JavaScript
- * desugaring.
- */
- val superCall = {
- val thisArg = {
- implicit val pos = sup.pos
- if (methodTailJumpThisSym.get == NoSymbol)
- js.This()(currentClassType)
- else
- js.VarRef(encodeLocalSym(methodTailJumpThisSym, freshName),
- mutable = false)(currentClassType)
- }
- genStaticApplyMethod(thisArg, fun.symbol, args map genExpr)
- }
-
- // We initialize the module instance just after the super constructor
- // call.
- if (isStaticModule(currentClassSym) && !isModuleInitialized &&
- currentMethodSym.isClassConstructor) {
- isModuleInitialized = true
- val thisType = jstpe.ClassType(encodeClassFullName(currentClassSym))
- val initModule = js.StoreModule(thisType, js.This()(thisType))
- js.Block(superCall, initModule, js.This()(thisType))
- } else {
- superCall
- }
-
- /** Constructor call (new)
- * Further refined into:
- * * new String(...)
- * * new of a hijacked boxed class
- * * new of a primitive JS type
- * * new Array
- * * regular new
- */
- case app @ Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) =>
- val ctor = fun.symbol
- if (settings.debug.value)
- assert(ctor.isClassConstructor,
- "'new' call to non-constructor: " + ctor.name)
-
- val tpe = tpt.tpe
- if (isStringType(tpe)) {
- genNewString(app)
- } else if (isHijackedBoxedClass(tpe.typeSymbol)) {
- genNewHijackedBoxedClass(tpe.typeSymbol, ctor, args map genExpr)
- } else if (translatedAnonFunctions contains tpe.typeSymbol) {
- val (functionMaker, funInfo) = translatedAnonFunctions(tpe.typeSymbol)
- currentMethodInfoBuilder.createsAnonFunction(funInfo)
- functionMaker(args map genExpr)
- } else if (isRawJSType(tpe)) {
- genPrimitiveJSNew(app)
- } else {
- val arguments = args map genExpr
-
- val generatedType = toTypeKind(tpt.tpe)
- if (settings.debug.value)
- assert(generatedType.isReferenceType || generatedType.isArrayType,
- "Non reference type cannot be instantiated: " + generatedType)
-
- (generatedType: @unchecked) match {
- case arr @ ARRAY(elem) =>
- genNewArray(tpt.tpe, arr.dimensions, arguments)
-
- case rt @ REFERENCE(cls) =>
- genNew(cls, ctor, arguments)
- }
- }
-
- /** All other Applys, which cannot be refined by pattern matching
- * They are further refined by properties of the method symbol.
- */
- case app @ Apply(fun, args) =>
- val sym = fun.symbol
-
- /** Jump to a label
- * Most label-applys are catched upstream (while and do..while
- * loops, jumps to next case of a pattern match), but some are
- * still handled here:
- * * Recursive tail call
- * * Jump to the end of a pattern match
- */
- if (sym.isLabel) {
- /** Recursive tail call
- * Basically this compiled into
- * continue tailCallLoop;
- * but arguments need to be updated beforehand.
- *
- * Since the rhs for the new value of an argument can depend on
- * the value of another argument (and since deciding if it is
- * indeed the case is impossible in general), new values are
- * computed in temporary variables first, then copied to the
- * actual variables representing the argument.
- *
- * Trivial assignments (arg1 = arg1) are eliminated.
- *
- * If, after elimination of trivial assignments, only one
- * assignment remains, then we do not use a temporary variable
- * for this one.
- */
- if (sym == methodTailJumpLabelSym.get) {
- // Prepare triplets of (formalArg, tempVar, actualArg)
- // Do not include trivial assignments (when actualArg == formalArg)
- val formalArgs = methodTailJumpFormalArgs.get
- val actualArgs = args map genExpr
- val quadruplets = {
- for {
- (formalArgSym, actualArg) <- formalArgs zip actualArgs
- formalArg = encodeLocalSym(formalArgSym, freshName)
- if (actualArg match {
- case js.VarRef(`formalArg`, _) => false
- case _ => true
- })
- } yield {
- (formalArg, toIRType(formalArgSym.tpe),
- js.Ident(freshName("temp$" + formalArg.name), None),
- actualArg)
- }
- }
-
- // The actual jump (continue tailCallLoop;)
- val tailJump = js.Continue(Some(js.Ident("tailCallLoop")))
-
- quadruplets match {
- case Nil => tailJump
-
- case (formalArg, argType, _, actualArg) :: Nil =>
- js.Block(js.Assign(
- js.VarRef(formalArg, mutable = true)(argType), actualArg),
- tailJump)
-
- case _ =>
- val tempAssignments =
- for ((_, argType, tempArg, actualArg) <- quadruplets)
- yield js.VarDef(tempArg, argType, mutable = false, actualArg)
- val trueAssignments =
- for ((formalArg, argType, tempArg, _) <- quadruplets)
- yield js.Assign(
- js.VarRef(formalArg, mutable = true)(argType),
- js.VarRef(tempArg, mutable = false)(argType))
- js.Block(tempAssignments ++ trueAssignments :+ tailJump)
- }
- } else // continues after the comment
- /** Jump the to the end-label of a pattern match
- * Such labels have exactly one argument, which is the result of
- * the pattern match (of type Unit if the match is in statement
- * position). We simply `return` the argument as the result of the
- * labeled block surrounding the match.
- */
- if (sym.name.toString() startsWith "matchEnd") {
- val labelIdent = encodeLabelSym(sym, freshName)
- js.Return(genExpr(args.head), Some(labelIdent))
- } else {
- /* No other label apply should ever happen. If it does, then we
- * have missed a pattern of LabelDef/LabelApply and some new
- * translation must be found for it.
- */
- abort("Found unknown label apply at "+tree.pos+": "+tree)
- }
- } else // continues after the comment
- /** Primitive method whose code is generated by the codegen */
- if (scalaPrimitives.isPrimitive(sym)) {
- // primitive operation
- genPrimitiveOp(app)
- } else if (currentRun.runDefinitions.isBox(sym)) {
- /** Box a primitive value */
- val arg = args.head
- makePrimitiveBox(genExpr(arg), arg.tpe)
- } else if (currentRun.runDefinitions.isUnbox(sym)) {
- /** Unbox a primitive value */
- val arg = args.head
- makePrimitiveUnbox(genExpr(arg), tree.tpe)
- } else {
- /** Actual method call
- * But even these are further refined into:
- * * Methods of java.lang.Object (because things typed as such
- * at compile-time are sometimes raw JS values at runtime).
- * * Methods of ancestors of java.lang.String (because they could
- * be a primitive string at runtime).
- * * Likewise, methods of ancestors of hijacked boxed classes
- * * Calls to primitive JS methods (Scala.js -> JS bridge)
- * * Regular method call
- */
- if (settings.debug.value)
- log("Gen CALL_METHOD with sym: " + sym + " isStaticSymbol: " + sym.isStaticMember);
-
- val Select(receiver, _) = fun
-
- if (MethodWithHelperInEnv contains fun.symbol) {
- if (!isRawJSType(receiver.tpe)) {
- currentMethodInfoBuilder.callsMethod(receiver.tpe.typeSymbol,
- encodeMethodSym(fun.symbol))
- }
- val helper = MethodWithHelperInEnv(fun.symbol)
- val arguments = (receiver :: args) map genExpr
- js.CallHelper(helper, arguments:_*)(toIRType(tree.tpe))
- } else if (ToStringMaybeOnHijackedClass contains fun.symbol) {
- js.Cast(js.JSApply(js.JSDotSelect(
- js.Cast(genExpr(receiver), jstpe.DynType),
- js.Ident("toString")), Nil), toIRType(tree.tpe))
- } else if (isStringType(receiver.tpe)) {
- genStringCall(app)
- } else if (isRawJSType(receiver.tpe)) {
- genPrimitiveJSCall(app)
- } else if (foreignIsImplClass(sym.owner)) {
- genTraitImplApply(sym, args map genExpr)
- } else {
- val instance = genExpr(receiver)
- val arguments = args map genExpr
-
- if (sym.isClassConstructor) {
- /* See #66: we have to emit a static call to avoid calling a
- * constructor with the same signature in a subclass */
- genStaticApplyMethod(instance, sym, arguments)
- } else {
- genApplyMethod(instance, receiver.tpe, sym, arguments)
- }
- }
- }
- }
- }
-
- def genStaticApplyMethod(receiver: js.Tree, method: Symbol,
- arguments: List[js.Tree])(implicit pos: Position): js.Tree = {
- val classIdent = encodeClassFullNameIdent(method.owner)
- val methodIdent = encodeMethodSym(method)
- currentMethodInfoBuilder.callsMethodStatic(classIdent, methodIdent)
- js.StaticApply(receiver, jstpe.ClassType(classIdent.name), methodIdent,
- arguments)(toIRType(method.tpe.resultType))
- }
-
- def genTraitImplApply(method: Symbol, arguments: List[js.Tree])(
- implicit pos: Position): js.Tree = {
- val implIdent = encodeClassFullNameIdent(method.owner)
- val methodIdent = encodeMethodSym(method)
- genTraitImplApply(implIdent, methodIdent, arguments,
- toIRType(method.tpe.resultType))
- }
-
- def genTraitImplApply(implIdent: js.Ident, methodIdent: js.Ident,
- arguments: List[js.Tree], resultType: jstpe.Type)(
- implicit pos: Position): js.Tree = {
- currentMethodInfoBuilder.callsMethod(implIdent, methodIdent)
- js.TraitImplApply(jstpe.ClassType(implIdent.name), methodIdent,
- arguments)(resultType)
- }
-
- private lazy val ToStringMaybeOnHijackedClass: Set[Symbol] =
- (Set(CharSequenceClass, StringClass, NumberClass) ++ HijackedBoxedClasses)
- .map(cls => getMemberMethod(cls, nme.toString_))
-
- private lazy val MethodWithHelperInEnv: Map[Symbol, String] = {
- val m = mutable.Map[Symbol, String](
- Object_toString -> "objectToString",
- Object_getClass -> "objectGetClass",
- Object_clone -> "objectClone",
- Object_finalize -> "objectFinalize",
- Object_notify -> "objectNotify",
- Object_notifyAll -> "objectNotifyAll",
- Object_equals -> "objectEquals",
- Object_hashCode -> "objectHashCode"
- )
-
- def addN(clazz: Symbol, meth: TermName, helperName: String): Unit = {
- for (sym <- getMemberMethod(clazz, meth).alternatives)
- m += sym -> helperName
- }
- def addS(clazz: Symbol, meth: String, helperName: String): Unit =
- addN(clazz, newTermName(meth), helperName)
-
- addS(CharSequenceClass, "length", "charSequenceLength")
- addS(CharSequenceClass, "charAt", "charSequenceCharAt")
- addS(CharSequenceClass, "subSequence", "charSequenceSubSequence")
-
- addS(ComparableClass, "compareTo", "comparableCompareTo")
-
- for (clazz <- StringClass +: HijackedBoxedClasses) {
- addN(clazz, nme.equals_, "objectEquals")
- addN(clazz, nme.hashCode_, "objectHashCode")
- if (clazz != BoxedUnitClass)
- addS(clazz, "compareTo", "comparableCompareTo")
- }
-
- for (clazz <- NumberClass +: HijackedNumberClasses) {
- for (pref <- Seq("byte", "short", "int", "long", "float", "double")) {
- val meth = pref+"Value"
- addS(clazz, meth, "number"+meth.capitalize)
- // example: "intValue" -> "numberIntValue"
- }
- }
-
- addS(BoxedFloatClass, "isNaN", "isNaN")
- addS(BoxedDoubleClass, "isNaN", "isNaN")
-
- addS(BoxedFloatClass, "isInfinite", "isInfinite")
- addS(BoxedDoubleClass, "isInfinite", "isInfinite")
-
- m.toMap
- }
-
- private lazy val CharSequenceClass = requiredClass[java.lang.CharSequence]
-
- /** Gen JS code for a conversion between primitive value types */
- def genConversion(from: TypeKind, to: TypeKind, value: js.Tree)(
- implicit pos: Position): js.Tree = {
- def int0 = js.IntLiteral(0)
- def int1 = js.IntLiteral(1)
- def float0 = js.DoubleLiteral(0.0)
- def float1 = js.DoubleLiteral(1.0)
-
- (from, to) match {
- case (LongKind, BOOL) =>
- genLongCall(value, "notEquals", genLongModuleCall("zero"))
- case (_:INT, BOOL) => js.BinaryOp("!==", value, int0, jstpe.BooleanType)
- case (_:FLOAT, BOOL) => js.BinaryOp("!==", value, float0, jstpe.BooleanType)
-
- case (BOOL, LongKind) =>
- js.If(value, genLongModuleCall("one"), genLongModuleCall("zero"))(
- jstpe.ClassType(ir.Definitions.RuntimeLongClass))
- case (BOOL, _:INT) => js.If(value, int1, int0 )(jstpe.IntType)
- case (BOOL, _:FLOAT) => js.If(value, float1, float0)(jstpe.DoubleType)
-
- // TODO Isn't float-to-int missing?
-
- case _ => value
- }
- }
-
- /** Gen JS code for an isInstanceOf test (for reference types only) */
- def genIsInstanceOf(to: Type, value: js.Tree)(
- implicit pos: Position): js.Tree = {
-
- def genTypeOfTest(typeString: String) = {
- js.BinaryOp("===",
- js.UnaryOp("typeof", value, jstpe.StringType),
- js.StringLiteral(typeString),
- jstpe.BooleanType)
- }
-
- if (isRawJSType(to)) {
- to.typeSymbol match {
- case JSNumberClass => genTypeOfTest("number")
- case JSStringClass => genTypeOfTest("string")
- case JSBooleanClass => genTypeOfTest("boolean")
- case JSUndefinedClass => genTypeOfTest("undefined")
- case sym if sym.isTrait =>
- currentUnit.error(pos,
- s"isInstanceOf[${sym.fullName}] not supported because it is a raw JS trait")
- js.BooleanLiteral(true)
- case sym =>
- js.BinaryOp("instanceof", value, genGlobalJSObject(sym),
- jstpe.BooleanType)
- }
- } else {
- val (irType, sym) = encodeReferenceType(to)
- if (sym != RuntimeLongClass)
- currentMethodInfoBuilder.accessesClassData(sym)
- js.IsInstanceOf(value, irType)
- }
- }
-
- /** Gen JS code for an asInstanceOf cast (for reference types only) */
- def genAsInstanceOf(to: Type, value: js.Tree)(
- implicit pos: Position): js.Tree = {
-
- def default: js.Tree = {
- val (irType, sym) = encodeReferenceType(to)
- currentMethodInfoBuilder.accessesClassData(sym)
- js.AsInstanceOf(value, irType)
- }
-
- if (isRawJSType(to)) {
- // asInstanceOf on JavaScript is completely erased
- if (value.tpe == jstpe.DynType) value
- else js.Cast(value, jstpe.DynType)
- } else if (FunctionClass.seq contains to.typeSymbol) {
- /* Don't hide a JSFunctionToScala inside a useless cast, otherwise
- * the optimization avoiding double-wrapping in genApply() will not
- * be able to kick in.
- */
- value match {
- case JSFunctionToScala(fun, _) => value
- case _ => default
- }
- } else {
- default
- }
- }
-
- /** Gen JS code for a call to a Scala method.
- * This also registers that the given method is called by the current
- * method in the method info builder.
- */
- def genApplyMethod(receiver: js.Tree, receiverType: Type,
- methodSym: Symbol, arguments: List[js.Tree])(
- implicit pos: Position): js.Tree = {
- genApplyMethod(receiver, receiverType.typeSymbol, methodSym, arguments)
- }
-
- /** Gen JS code for a call to a Scala method.
- * This also registers that the given method is called by the current
- * method in the method info builder.
- */
- def genApplyMethod(receiver: js.Tree, receiverTypeSym: Symbol,
- methodSym: Symbol, arguments: List[js.Tree])(
- implicit pos: Position): js.Tree = {
- genApplyMethod(receiver, receiverTypeSym,
- encodeMethodSym(methodSym), arguments,
- toIRType(methodSym.tpe.resultType))
- }
-
- /** Gen JS code for a call to a Scala method.
- * This also registers that the given method is called by the current
- * method in the method info builder.
- */
- def genApplyMethod(receiver: js.Tree, receiverType: Type,
- methodIdent: js.Ident, arguments: List[js.Tree], resultType: jstpe.Type)(
- implicit pos: Position): js.Tree = {
- genApplyMethod(receiver, receiverType.typeSymbol, methodIdent,
- arguments, resultType)
- }
-
- /** Gen JS code for a call to a Scala method.
- * This also registers that the given method is called by the current
- * method in the method info builder.
- */
- def genApplyMethod(receiver: js.Tree, receiverTypeSym: Symbol,
- methodIdent: js.Ident, arguments: List[js.Tree], resultType: jstpe.Type)(
- implicit pos: Position): js.Tree = {
- currentMethodInfoBuilder.callsMethod(receiverTypeSym, methodIdent)
- js.Apply(receiver, methodIdent, arguments)(resultType)
- }
-
- /** Gen JS code for a call to a Scala class constructor
- * This first calls the only JS constructor for the class, which creates
- * the fields of the instance, initialized to the zero of their respective
- * types.
- * Then we call the method containing the code of the particular
- * overload of the Scala constructors. Since this method returns `this`,
- * we simply chain the calls.
- *
- * This also registers that the given class is instantiated by the current
- * method, and that the given constructor is called, in the method info
- * builder.
- */
- def genNew(clazz: Symbol, ctor: Symbol, arguments: List[js.Tree])(
- implicit pos: Position): js.Tree = {
- if (clazz.isAnonymousFunction)
- instantiatedAnonFunctions += clazz
- assert(!isRawJSFunctionDef(clazz),
- s"Trying to instantiate a raw JS function def $clazz")
- val ctorIdent = encodeMethodSym(ctor)
- currentMethodInfoBuilder.instantiatesClass(clazz)
- currentMethodInfoBuilder.callsMethod(clazz, ctorIdent)
- js.New(jstpe.ClassType(encodeClassFullName(clazz)),
- ctorIdent, arguments)
- }
-
- /** Gen JS code for a call to a constructor of a hijacked boxed class.
- * All of these have 2 constructors: one with the primitive
- * value, which is erased, and one with a String, which is
- * equivalent to BoxedClass.valueOf(arg).
- */
- private def genNewHijackedBoxedClass(clazz: Symbol, ctor: Symbol,
- arguments: List[js.Tree])(implicit pos: Position): js.Tree = {
- assert(arguments.size == 1)
- if (isStringType(ctor.tpe.params.head.tpe)) {
- // BoxedClass.valueOf(arg)
- val companion = clazz.companionModule.moduleClass
- val valueOf = getMemberMethod(companion, nme.valueOf) suchThat { s =>
- s.tpe.params.size == 1 && isStringType(s.tpe.params.head.tpe)
- }
- genApplyMethod(genLoadModule(companion), companion, valueOf, arguments)
- } else {
- // erased
- arguments.head
- }
- }
-
- /** Gen JS code for creating a new Array: new Array[T](length)
- * For multidimensional arrays (dimensions > 1), the arguments can
- * specify up to `dimensions` lengths for the first dimensions of the
- * array.
- */
- def genNewArray(arrayType: Type, dimensions: Int,
- arguments: List[js.Tree])(implicit pos: Position): js.Tree = {
- val argsLength = arguments.length
-
- if (argsLength > dimensions)
- abort("too many arguments for array constructor: found " + argsLength +
- " but array has only " + dimensions + " dimension(s)")
-
- val (irType, sym) = encodeArrayType(arrayType)
- currentMethodInfoBuilder.accessesClassData(sym)
- js.NewArray(irType, arguments)
- }
-
- /** Gen JS code for an array literal
- * We generate a JS array construction that we wrap in a native array
- * wrapper.
- */
- def genArrayValue(tree: Tree): js.Tree = {
- implicit val pos = tree.pos
- val ArrayValue(tpt @ TypeTree(), elems) = tree
-
- val (irType, sym) = encodeArrayType(tree.tpe)
- currentMethodInfoBuilder.accessesClassData(sym)
- js.ArrayValue(irType, elems map genExpr)
- }
-
- /** Gen JS code for a Match, i.e., a switch-able pattern match
- * Eventually, this is compiled into a JS switch construct. But because
- * we can be in expression position, and a JS switch cannot be given a
- * meaning in expression position, we emit a JS "match" construct (which
- * does not need the `break`s in each case. `JSDesugaring` will transform
- * that in a switch.
- *
- * Some caveat here. It may happen that there is a guard in here, despite
- * the fact that switches cannot have guards (in the JVM nor in JS).
- * The JVM backend emits a jump to the default clause when a guard is not
- * fulfilled. We cannot do that. Instead, currently we duplicate the body
- * of the default case in the else branch of the guard test.
- */
- def genMatch(tree: Tree): js.Tree = {
- implicit val pos = tree.pos
- val Match(selector, cases) = tree
-
- val expr = genExpr(selector)
- val resultType = toIRType(tree.tpe)
-
- val List(defaultBody0) = for {
- CaseDef(Ident(nme.WILDCARD), EmptyTree, body) <- cases
- } yield body
-
- val (defaultBody, defaultLabelSym) = defaultBody0 match {
- case LabelDef(_, Nil, rhs) if hasSynthCaseSymbol(defaultBody0) =>
- (rhs, defaultBody0.symbol)
- case _ =>
- (defaultBody0, NoSymbol)
- }
-
- var clauses: List[(List[js.Tree], js.Tree)] = Nil
- var elseClause: js.Tree = js.EmptyTree
-
- for (caze @ CaseDef(pat, guard, body) <- cases) {
- assert(guard == EmptyTree)
-
- def genBody() = body match {
- // Yes, this will duplicate the default body in the output
- case If(cond, thenp, app @ Apply(_, Nil)) if app.symbol == defaultLabelSym =>
- js.If(genExpr(cond), genExpr(thenp), genExpr(defaultBody))(resultType)(body.pos)
- case If(cond, thenp, Block(List(app @ Apply(_, Nil)), _)) if app.symbol == defaultLabelSym =>
- js.If(genExpr(cond), genExpr(thenp), genExpr(defaultBody))(resultType)(body.pos)
-
- case _ =>
- genExpr(body)
- }
-
- pat match {
- case lit: Literal =>
- clauses = (List(genExpr(lit)), genBody()) :: clauses
- case Ident(nme.WILDCARD) =>
- elseClause = genExpr(defaultBody)
- case Alternative(alts) =>
- val genAlts = {
- alts map {
- case lit: Literal => genExpr(lit)
- case _ =>
- abort("Invalid case in alternative in switch-like pattern match: " +
- tree + " at: " + tree.pos)
- }
- }
- clauses = (genAlts, genBody()) :: clauses
- case _ =>
- abort("Invalid case statement in switch-like pattern match: " +
- tree + " at: " + (tree.pos))
- }
- }
-
- js.Match(expr, clauses.reverse, elseClause)(resultType)
- }
-
- /** Gen JS code for a translated match
- *
- * This implementation relies heavily on the patterns of trees emitted
- * by the current pattern match phase (as of Scala 2.10).
- *
- * The trees output by the pattern matcher are assumed to follow these
- * rules:
- * * Each case LabelDef (in `cases`) must not take any argument.
- * * The last one must be a catch-all (case _ =>) that never falls through.
- * * Jumps to the `matchEnd` are allowed anywhere in the body of the
- * corresponding case label-defs, but not outside.
- * * Jumps to case label-defs are restricted to jumping to the very next
- * case, and only in positions denoted by in:
- * ::=
- * If(_, , )
- * | Block(_, )
- * |
- * | _
- * These restrictions, together with the fact that we are in statement
- * position (thanks to the above transformation), mean that they can be
- * simply replaced by `skip`.
- *
- * To implement jumps to `matchEnd`, which have one argument which is the
- * result of the match, we enclose all the cases in one big labeled block.
- * Jumps are then compiled as `break`s out of that block if the result has
- * type Unit, or `return`s out of the block otherwise.
- */
- def genTranslatedMatch(cases: List[LabelDef],
- matchEnd: LabelDef)(implicit pos: Position): js.Tree = {
-
- val nextCaseSyms = (cases.tail map (_.symbol)) :+ NoSymbol
-
- val translatedCases = for {
- (LabelDef(_, Nil, rhs), nextCaseSym) <- cases zip nextCaseSyms
- } yield {
- def genCaseBody(tree: Tree): js.Tree = {
- implicit val pos = tree.pos
- tree match {
- case If(cond, thenp, elsep) =>
- js.If(genExpr(cond), genCaseBody(thenp), genCaseBody(elsep))(
- jstpe.UndefType)
-
- case Block(stats, expr) =>
- js.Block((stats map genStat) :+ genCaseBody(expr))
-
- case Apply(_, Nil) if tree.symbol == nextCaseSym =>
- js.Skip()
-
- case _ =>
- genStat(tree)
- }
- }
-
- genCaseBody(rhs)
- }
-
- js.Labeled(encodeLabelSym(matchEnd.symbol, freshName),
- toIRType(matchEnd.tpe), js.Block(translatedCases))
- }
-
- /** Gen JS code for a primitive method call */
- private def genPrimitiveOp(tree: Apply): js.Tree = {
- import scalaPrimitives._
-
- implicit val jspos = tree.pos
-
- val sym = tree.symbol
- val Apply(fun @ Select(receiver, _), args) = tree
-
- val code = scalaPrimitives.getPrimitive(sym, receiver.tpe)
-
- if (isArithmeticOp(code) || isLogicalOp(code) || isComparisonOp(code))
- genSimpleOp(tree, receiver :: args, code)
- else if (code == scalaPrimitives.CONCAT)
- genStringConcat(tree, receiver, args)
- else if (code == HASH)
- genScalaHash(tree, receiver)
- else if (isArrayOp(code))
- genArrayOp(tree, code)
- else if (code == SYNCHRONIZED)
- genSynchronized(tree)
- else if (isCoercion(code))
- genCoercion(tree, receiver, code)
- else if (jsPrimitives.isJavaScriptPrimitive(code))
- genJSPrimitive(tree, receiver, args, code)
- else
- abort("Primitive operation not handled yet: " + sym.fullName + "(" +
- fun.symbol.simpleName + ") " + " at: " + (tree.pos))
- }
-
- /** Gen JS code for a simple operation (arithmetic, logical, or comparison) */
- private def genSimpleOp(tree: Apply, args: List[Tree], code: Int): js.Tree = {
- import scalaPrimitives._
-
- implicit val jspos = tree.pos
-
- def needLongConv(ltpe: Type, rtpe: Type) =
- (isLongType(ltpe) || isLongType(rtpe)) &&
- !(toTypeKind(ltpe).isInstanceOf[FLOAT] ||
- toTypeKind(rtpe).isInstanceOf[FLOAT] ||
- isStringType(ltpe) || isStringType(rtpe))
-
- val sources = args map genExpr
-
- def resultType = toIRType(tree.tpe)
-
- sources match {
- // Unary op on long
- case List(source) if isLongType(args.head.tpe) => code match {
- case POS => genLongCall(source, "unary_+")
- case NEG => genLongCall(source, "unary_-")
- case NOT => genLongCall(source, "unary_~")
- case _ => abort("Unknown or invalid op code on Long: " + code)
- }
-
- // Unary operation
- case List(source) =>
- (code match {
- case POS =>
- js.UnaryOp("+", source, resultType)
- case NEG =>
- js.UnaryOp("-", source, resultType)
- case NOT =>
- js.UnaryOp("~", source, resultType)
- case ZNOT =>
- js.UnaryOp("!", source, resultType)
- case _ =>
- abort("Unknown unary operation code: " + code)
- })
-
- // Binary operation requiring conversion to Long of both sides
- case List(lsrc, rsrc) if needLongConv(args(0).tpe, args(1).tpe) =>
- def toLong(tree: js.Tree, tpe: Type) = tpe.typeSymbol match {
- case ByteClass => genLongModuleCall("fromByte", tree)
- case ShortClass => genLongModuleCall("fromShort", tree)
- case CharClass => genLongModuleCall("fromChar", tree)
- case IntClass => genLongModuleCall("fromInt", tree)
- case LongClass => tree
- }
-
- val ltree = toLong(lsrc, args(0).tpe)
- val rtree = toLong(rsrc, args(1).tpe)
- val rtLongTpe = RuntimeLongClass.tpe
-
- code match {
- case ADD => genOlLongCall(ltree, "+", rtree)(rtLongTpe)
- case SUB => genOlLongCall(ltree, "-", rtree)(rtLongTpe)
- case MUL => genOlLongCall(ltree, "*", rtree)(rtLongTpe)
- case DIV => genOlLongCall(ltree, "/", rtree)(rtLongTpe)
- case MOD => genOlLongCall(ltree, "%", rtree)(rtLongTpe)
- case OR => genOlLongCall(ltree, "|", rtree)(rtLongTpe)
- case XOR => genOlLongCall(ltree, "^", rtree)(rtLongTpe)
- case AND => genOlLongCall(ltree, "&", rtree)(rtLongTpe)
- case LSL => genOlLongCall(ltree, "<<", rtree)(IntTpe)
- case LSR => genOlLongCall(ltree, ">>>", rtree)(IntTpe)
- case ASR => genOlLongCall(ltree, ">>", rtree)(IntTpe)
- case LT => genOlLongCall(ltree, "<", rtree)(rtLongTpe)
- case LE => genOlLongCall(ltree, "<=", rtree)(rtLongTpe)
- case GT => genOlLongCall(ltree, ">", rtree)(rtLongTpe)
- case GE => genOlLongCall(ltree, ">=", rtree)(rtLongTpe)
- case EQ => genLongCall (ltree, "equals", rtree)
- case NE => genLongCall (ltree, "notEquals", rtree)
- case _ =>
- abort("Unknown binary operation code: " + code)
- }
-
- // Binary operation
- case List(lsrc_in, rsrc_in) =>
- def fromLong(tree: js.Tree, tpe: Type) = tpe.typeSymbol match {
- // If we end up with a long, target must be float
- case LongClass => genLongCall(tree, "toDouble")
- case _ => tree
- }
-
- lazy val leftKind = toTypeKind(args(0).tpe)
- lazy val rightKind = toTypeKind(args(1).tpe)
- lazy val resultKind = toTypeKind(tree.tpe)
-
- val lsrc = fromLong(lsrc_in, args(0).tpe)
- val rsrc = fromLong(rsrc_in, args(1).tpe)
-
- def genEquality(eqeq: Boolean, not: Boolean) = {
- if (eqeq &&
- leftKind.isReferenceType &&
- !isRawJSType(args(0).tpe) &&
- !isRawJSType(args(1).tpe) &&
- // don't call equals if we have a literal null at rhs
- !rsrc.isInstanceOf[js.Null]
- ) {
- val body = genEqEqPrimitive(args(0), args(1), lsrc, rsrc)
- if (not) js.UnaryOp("!", body, resultType) else body
- } else
- js.BinaryOp(if (not) "!==" else "===", lsrc, rsrc, resultType)
- }
-
- (code: @switch) match {
- case EQ => genEquality(eqeq = true, not = false)
- case NE => genEquality(eqeq = true, not = true)
- case ID => genEquality(eqeq = false, not = false)
- case NI => genEquality(eqeq = false, not = true)
- case _ =>
- js.BinaryOp(primCodeToBinaryOp(code), lsrc, rsrc, resultType)
- }
-
- case _ =>
- abort("Too many arguments for primitive function: " + tree)
- }
- }
-
- private val primCodeToBinaryOp: Map[Int, String] = {
- import scalaPrimitives._
- Map(
- ADD -> "+" , SUB -> "-", MUL -> "*" , DIV -> "/" , MOD -> "%",
- OR -> "|" , XOR -> "^", AND -> "&" , LSL -> "<<", LSR -> ">>>",
- ASR -> ">>", LT -> "<", LE -> "<=", GT -> ">" , GE -> ">=",
- ZOR -> "||", ZAND -> "&&")
- }
-
- /** Gen JS code for a call to Any.== */
- def genEqEqPrimitive(l: Tree, r: Tree, lsrc: js.Tree, rsrc: js.Tree)(
- implicit pos: Position): js.Tree = {
- /** True if the equality comparison is between values that require the use of the rich equality
- * comparator (scala.runtime.Comparator.equals). This is the case when either side of the
- * comparison might have a run-time type subtype of java.lang.Number or java.lang.Character.
- * When it is statically known that both sides are equal and subtypes of Number of Character,
- * not using the rich equality is possible (their own equals method will do ok.)*/
- def mustUseAnyComparator: Boolean = {
- def areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe)
- !areSameFinals && isMaybeBoxed(l.tpe.typeSymbol) && isMaybeBoxed(r.tpe.typeSymbol)
- }
-
- val function = if (mustUseAnyComparator) "anyEqEq" else "anyRefEqEq"
- js.CallHelper(function, lsrc, rsrc)(jstpe.BooleanType)
- }
-
- /** Gen JS code for string concatenation
- * We explicitly call the JS toString() on any non-String argument to
- * avoid the weird things happening when adding "things" in JS.
- * Because any argument can potentially be `null` or `undefined`, we
- * cannot really call toString() directly. The helper
- * `anyToStringForConcat` handles these cases properly.
- */
- private def genStringConcat(tree: Apply, receiver: Tree,
- args: List[Tree]): js.Tree = {
- implicit val pos = tree.pos
-
- /* Primitive number types such as scala.Int have a
- * def +(s: String): String
- * method, which is why we have to box the lhs sometimes.
- * Otherwise, both lhs and rhs are already reference types (Any of String)
- * so boxing is not necessary (in particular, rhs is never a primitive).
- */
- assert(!isPrimitiveValueType(receiver.tpe) || isStringType(args.head.tpe))
- assert(!isPrimitiveValueType(args.head.tpe))
-
- val rhs = genExpr(args.head)
-
- val lhs = {
- val lhs0 = genExpr(receiver)
- // Box the receiver if it is a primitive value
- if (!isPrimitiveValueType(receiver.tpe)) lhs0
- else makePrimitiveBox(lhs0, receiver.tpe)
- }
-
- js.BinaryOp("+", lhs, rhs, jstpe.StringType)
- }
-
- /** Gen JS code for a call to Any.## */
- private def genScalaHash(tree: Apply, receiver: Tree): js.Tree = {
- implicit val jspos = tree.pos
-
- val instance = genLoadModule(ScalaRunTimeModule)
- val arguments = List(genExpr(receiver))
- val sym = getMember(ScalaRunTimeModule, nme.hash_)
-
- genApplyMethod(instance, ScalaRunTimeModule.moduleClass, sym, arguments)
- }
-
- /** Gen JS code for an array operation (get, set or length) */
- private def genArrayOp(tree: Tree, code: Int): js.Tree = {
- import scalaPrimitives._
-
- implicit val pos = tree.pos
-
- val Apply(Select(arrayObj, _), args) = tree
- val arrayValue = genExpr(arrayObj)
- val arguments = args map genExpr
-
- if (scalaPrimitives.isArrayGet(code)) {
- // get an item of the array
- if (settings.debug.value)
- assert(args.length == 1,
- s"Array get requires 1 argument, found ${args.length} in $tree")
- js.ArraySelect(arrayValue, arguments(0))(toIRType(tree.tpe))
- } else if (scalaPrimitives.isArraySet(code)) {
- // set an item of the array
- if (settings.debug.value)
- assert(args.length == 2,
- s"Array set requires 2 arguments, found ${args.length} in $tree")
- statToExpr {
- js.Assign(
- js.ArraySelect(arrayValue, arguments(0))(toIRType(tree.tpe)),
- arguments(1))
- }
- } else {
- // length of the array
- js.ArrayLength(arrayValue)
- }
- }
-
- /** Gen JS code for a call to AnyRef.synchronized */
- private def genSynchronized(tree: Apply): js.Tree = {
- /* JavaScript is single-threaded. I believe we can drop the
- * synchronization altogether.
- */
- genExpr(tree.args.head)
- }
-
- /** Gen JS code for a coercion */
- private def genCoercion(tree: Apply, receiver: Tree, code: Int): js.Tree = {
- import scalaPrimitives._
-
- implicit val jspos = tree.pos
-
- val source = genExpr(receiver)
-
- (code: @scala.annotation.switch) match {
- // From Long to something
- case L2B =>
- genLongCall(source, "toByte")
- case L2S =>
- genLongCall(source, "toShort")
- case L2C =>
- genLongCall(source, "toChar")
- case L2I =>
- genLongCall(source, "toInt")
- case L2F =>
- genLongCall(source, "toFloat")
- case L2D =>
- genLongCall(source, "toDouble")
-
- // From something to long
- case B2L =>
- genLongModuleCall("fromByte", source)
- case S2L =>
- genLongModuleCall("fromShort", source)
- case C2L =>
- genLongModuleCall("fromChar", source)
- case I2L =>
- genLongModuleCall("fromInt", source)
- case F2L =>
- genLongModuleCall("fromFloat", source)
- case D2L =>
- genLongModuleCall("fromDouble", source)
-
- // Conversions to chars (except for Long)
- case B2C | S2C | I2C | F2C | D2C =>
- js.BinaryOp("&", source, js.IntLiteral(0xffff), jstpe.IntType)
-
- // To Byte, need to crop at signed 8-bit
- case C2B | S2B | I2B | F2B | D2B =>
- // note: & 0xff would not work because of negative values
- js.BinaryOp(">>",
- js.BinaryOp("<<", source, js.IntLiteral(24), jstpe.IntType),
- js.IntLiteral(24), jstpe.IntType)
-
- // To Short, need to crop at signed 16-bit
- case C2S | I2S | F2S | D2S =>
- // note: & 0xffff would not work because of negative values
- js.BinaryOp(">>",
- js.BinaryOp("<<", source, js.IntLiteral(16), jstpe.IntType),
- js.IntLiteral(16), jstpe.IntType)
-
- // To Int, need to crop at signed 32-bit
- case F2I | D2I =>
- js.BinaryOp("|", source, js.IntLiteral(0), jstpe.IntType)
-
- case _ => source
- }
- }
-
- /** Gen JS code for an ApplyDynamic
- * ApplyDynamic nodes appear as the result of calls to methods of a
- * structural type.
- *
- * Most unfortunately, earlier phases of the compiler assume too much
- * about the backend, namely, they believe arguments and the result must
- * be boxed, and do the boxing themselves. This decision should be left
- * to the backend, but it's not, so we have to undo these boxes.
- * Note that this applies to parameter types only. The return type is boxed
- * anyway since we do not know it's exact type.
- *
- * This then generates a call to the reflective call proxy for the given
- * arguments.
- */
- private def genApplyDynamic(tree: ApplyDynamic): js.Tree = {
- implicit val pos = tree.pos
-
- val sym = tree.symbol
- val params = sym.tpe.params
-
- /** check if the method we are invoking is eq or ne. they cannot be
- * overridden since they are final. If this is true, we only emit a
- * `===` or `!==`.
- */
- val isEqOrNeq = (sym.name.decoded == "eq" || sym.name.decoded == "ne") &&
- params.size == 1 && params.head.tpe.typeSymbol == ObjectClass
-
- /** check if the method we are invoking conforms to the update
- * method on scala.Array. If this is the case, we have to check
- * that case specially at runtime, since the arrays element type is not
- * erased and therefore the method name mangling turns out wrong.
- *
- * Note that we cannot check if Unit conforms to the expected return
- * type, since this type information is already erased.
- */
- def isArrayLikeUpdate = sym.name.decoded == "update" && {
- params.size == 2 && params.head.tpe.typeSymbol == IntClass
- }
-
- /**
- * Tests whether one of our reflective "boxes" for primitive types
- * implements the particular method. If this is the case
- * (result != NoSymbol), we generate a runtime instance check if we are
- * dealing with the appropriate primitive type.
- */
- def matchingSymIn(clazz: Symbol) = clazz.tpe.member(sym.name).suchThat { s =>
- val sParams = s.tpe.params
- !s.isBridge &&
- params.size == sParams.size &&
- (params zip sParams).forall { case (s1,s2) =>
- s1.tpe =:= s2.tpe
- }
- }
-
- val ApplyDynamic(receiver, args) = tree
-
- if (isEqOrNeq) {
- // Just emit a boxed equiality check
- val jsThis = genExpr(receiver)
- val jsThat = genExpr(args.head)
- val op = if (sym.name.decoded == "eq") "===" else "!=="
-
- ensureBoxed(js.BinaryOp(op, jsThis, jsThat, jstpe.BooleanType),
- BooleanClass.tpe)
- } else {
- // Create a fully-fledged reflective call
- val receiverType = toIRType(receiver.tpe)
- val callTrgIdent = js.Ident(freshName("dynCallTrg"))
- val callTrgVarDef =
- js.VarDef(callTrgIdent, receiverType, mutable = false, genExpr(receiver))
- val callTrg = js.VarRef(callTrgIdent, mutable = false)(receiverType)
-
- val arguments = args zip sym.tpe.params map { case (arg, param) =>
- if (isPrimitiveValueType(param.tpe)) {
- arg match {
- case Apply(_, List(result)) if currentRun.runDefinitions.isBox(arg.symbol) =>
- genExpr(result)
- case _ =>
- makePrimitiveUnbox(genExpr(arg), param.tpe)
- }
- } else {
- genExpr(arg)
- }
- }
-
- val proxyIdent = encodeMethodSym(sym, reflProxy = true)
- var callStatement: js.Tree =
- genApplyMethod(callTrg, receiver.tpe, proxyIdent, arguments,
- jstpe.AnyType)
-
- if (isArrayLikeUpdate) {
- val elemIRTpe = toTypeKind(sym.tpe.params(1).tpe).toReferenceType
- val arrIRTpe = jstpe.ArrayType(elemIRTpe)
- callStatement = js.If(js.IsInstanceOf(callTrg, arrIRTpe), statToExpr {
- val castCallTrg = js.AsInstanceOf(callTrg, arrIRTpe)
- js.Assign(
- js.ArraySelect(castCallTrg, arguments(0))(elemIRTpe),
- arguments(1))
- }, {
- callStatement
- })(jstpe.AnyType)
- }
-
- for {
- (primTypeOf, reflBoxClass) <- Seq(
- ("string", RuntimeStringClass),
- ("number", NumberReflectiveCallClass),
- ("boolean", BooleanReflectiveCallClass)
- )
- implMethodSym = matchingSymIn(reflBoxClass)
- if implMethodSym != NoSymbol && implMethodSym.isPublic
- } {
- callStatement = js.If(
- js.BinaryOp("===",
- js.UnaryOp("typeof", callTrg, jstpe.StringType),
- js.StringLiteral(primTypeOf),
- jstpe.BooleanType), {
- val helper = MethodWithHelperInEnv.get(implMethodSym)
- if (helper.isDefined) {
- // This method has a helper, call it
- js.CallHelper(helper.get, callTrg :: arguments:_*)(
- toIRType(implMethodSym.tpe.resultType))
- } else if (implMethodSym.owner == ObjectClass) {
- /* If we end up here, we have a call to a method in
- * java.lang.Object we cannot support (such as wait).
- * Calls like this only fail reflectively at compile time because
- * some of them exist in the Scala stdlib. DCE will issue a
- * warning in any case.
- */
- currentUnit.error(pos,
- s"""You tried to call ${implMethodSym.name} on AnyRef reflectively, but this
- |method does not make sense in Scala.js. You may not call it""".stripMargin)
- statToExpr(js.Skip())
- } else {
- if (primTypeOf == "string") {
- val (implClass, methodIdent) =
- encodeImplClassMethodSym(implMethodSym)
- val retTpe = implMethodSym.tpe.resultType
- val rawApply = genTraitImplApply(
- encodeClassFullNameIdent(implClass),
- methodIdent,
- callTrg :: arguments,
- toIRType(retTpe))
- // Box the result of the implementing method if required
- if (isPrimitiveValueType(retTpe))
- makePrimitiveBox(rawApply, retTpe)
- else
- rawApply
- } else {
- val reflBoxClassPatched = {
- if (primTypeOf == "number" &&
- toTypeKind(implMethodSym.tpe.resultType) == DoubleKind &&
- toTypeKind(sym.tpe.resultType).isInstanceOf[INT]) {
- // This must be an Int, and not a Double
- IntegerReflectiveCallClass
- } else {
- reflBoxClass
- }
- }
- val reflBox = genNew(reflBoxClassPatched,
- reflBoxClassPatched.primaryConstructor, List(callTrg))
- genApplyMethod(
- reflBox,
- reflBoxClassPatched,
- proxyIdent,
- arguments,
- jstpe.AnyType)
- }
- }
- }, { // else
- callStatement
- })(jstpe.AnyType)
- }
-
- js.Block(callTrgVarDef, callStatement)
- }
- }
-
- /** Ensures that the value of the given tree is boxed.
- * @param expr Tree to be boxed if needed.
- * @param tpeEnteringPosterasure The type of `expr` as it was entering
- * the posterasure phase.
- */
- def ensureBoxed(expr: js.Tree, tpeEnteringPosterasure: Type)(
- implicit pos: Position): js.Tree = {
-
- tpeEnteringPosterasure match {
- case tpe if isPrimitiveValueType(tpe) =>
- makePrimitiveBox(expr, tpe)
-
- case tpe: ErasedValueType =>
- val boxedClass = tpe.valueClazz
- val ctor = boxedClass.primaryConstructor
- genNew(boxedClass, ctor, List(expr))
-
- case _ =>
- expr
- }
- }
-
- /** Ensures that the value of the given tree is unboxed.
- * @param expr Tree to be unboxed if needed.
- * @param tpeEnteringPosterasure The type of `expr` as it was entering
- * the posterasure phase.
- */
- def ensureUnboxed(expr: js.Tree, tpeEnteringPosterasure: Type)(
- implicit pos: Position): js.Tree = {
-
- tpeEnteringPosterasure match {
- case tpe if isPrimitiveValueType(tpe) =>
- makePrimitiveUnbox(expr, tpe)
-
- case tpe: ErasedValueType =>
- val boxedClass = tpe.valueClazz
- val unboxMethod = boxedClass.derivedValueClassUnbox
- genApplyMethod(expr, boxedClass, unboxMethod, Nil)
-
- case _ =>
- expr
- }
- }
-
- /** Gen a boxing operation (tpe is the primitive type) */
- private def makePrimitiveBox(expr: js.Tree, tpe: Type)(
- implicit pos: Position): js.Tree =
- makePrimitiveBoxUnbox(expr, tpe, unbox = false)
-
- /** Gen an unboxing operation (tpe is the primitive type) */
- private def makePrimitiveUnbox(expr: js.Tree, tpe: Type)(
- implicit pos: Position): js.Tree =
- makePrimitiveBoxUnbox(expr, tpe, unbox = true)
-
- /** Common implementation for `makeBox()` and `makeUnbox()` */
- private def makePrimitiveBoxUnbox(expr: js.Tree, tpe: Type, unbox: Boolean)(
- implicit pos: Position): js.Tree = {
-
- toTypeKind(tpe) match {
- case kind: ValueTypeKind =>
- if (unbox) {
- js.CallHelper("u" + kind.primitiveCharCode, expr)(toIRType(tpe))
- } else if (kind == CharKind) {
- js.CallHelper("bC", expr)(encodeClassType(BoxedCharacterClass))
- } else {
- expr // box is identity for all non-Char types
- }
- case _ =>
- abort(s"makePrimitiveBoxUnbox requires a primitive type, found $tpe at $pos")
- }
- }
-
- private def lookupModuleClass(name: String) = {
- val module = getModuleIfDefined(name)
- if (module == NoSymbol) NoSymbol
- else module.moduleClass
- }
-
- lazy val ReflectArrayModuleClass = lookupModuleClass("java.lang.reflect.Array")
- lazy val UtilArraysModuleClass = lookupModuleClass("java.util.Arrays")
-
- /** Gen JS code for a Scala.js-specific primitive method */
- private def genJSPrimitive(tree: Apply, receiver0: Tree,
- args: List[Tree], code: Int): js.Tree = {
- import jsPrimitives._
-
- implicit val pos = tree.pos
-
- def receiver = genExpr(receiver0)
- val genArgArray = genPrimitiveJSArgs(tree.symbol, args)
-
- lazy val js.JSArrayConstr(genArgs) = genArgArray
-
- def extractFirstArg() = {
- (genArgArray: @unchecked) match {
- case js.JSArrayConstr(firstArg :: otherArgs) =>
- (firstArg, js.JSArrayConstr(otherArgs))
- case js.JSApply(js.JSDotSelect(
- js.JSArrayConstr(firstArg :: firstPart), concat), otherParts) =>
- (firstArg, js.JSApply(js.JSDotSelect(
- js.JSArrayConstr(firstPart), concat), otherParts))
- }
- }
-
- if (code == DYNNEW) {
- // js.Dynamic.newInstance(clazz)(actualArgs:_*)
- val (jsClass, actualArgArray) = extractFirstArg()
- actualArgArray match {
- case js.JSArrayConstr(actualArgs) =>
- js.JSNew(jsClass, actualArgs)
- case _ =>
- js.CallHelper("newInstanceWithVarargs",
- jsClass, actualArgArray)(jstpe.DynType)
- }
- } else if (code == DYNAPPLY) {
- // js.Dynamic.applyDynamic(methodName)(actualArgs:_*)
- val (methodName, actualArgArray) = extractFirstArg()
- actualArgArray match {
- case js.JSArrayConstr(actualArgs) =>
- js.JSApply(js.JSBracketSelect(receiver, methodName), actualArgs)
- case _ =>
- js.CallHelper("applyMethodWithVarargs",
- receiver, methodName, actualArgArray)(jstpe.DynType)
- }
- } else if (code == DYNLITN) {
- // We have a call of the form:
- // js.Dynamic.literal(name1 = ..., name2 = ...)
- // Translate to:
- // {"name1": ..., "name2": ... }
- extractFirstArg() match {
- case (js.StringLiteral("apply", _),
- js.JSArrayConstr(jse.LitNamed(pairs))) =>
- js.JSObjectConstr(pairs)
- case (js.StringLiteral(name, _), _) if name != "apply" =>
- currentUnit.error(pos,
- s"js.Dynamic.literal does not have a method named $name")
- statToExpr(js.Skip())
- case _ =>
- currentUnit.error(pos,
- "js.Dynamic.literal.applyDynamicNamed may not be called directly")
- statToExpr(js.Skip())
- }
- } else if (code == DYNLIT) {
- // We have a call of some other form
- // js.Dynamic.literal(...)
- // Translate to:
- // var obj = {};
- // obj[...] = ...;
- // obj
-
- // Extract first arg to future proof against varargs
- extractFirstArg() match {
- // case js.Dynamic.literal("name1" -> ..., "name2" -> ...)
- case (js.StringLiteral("apply", _),
- js.JSArrayConstr(jse.LitNamed(pairs))) =>
- js.JSObjectConstr(pairs)
- // case js.Dynamic.literal(x, y)
- case (js.StringLiteral("apply", _),
- js.JSArrayConstr(tups)) =>
-
- // Create tmp variable
- val resIdent = js.Ident(freshName("obj"))
- val resVarDef = js.VarDef(resIdent, jstpe.DynType, mutable = false,
- js.JSObjectConstr(Nil))
- val res = js.VarRef(resIdent, mutable = false)(jstpe.DynType)
-
- // Assign fields
- val tuple2Type = encodeClassType(TupleClass(2))
- val assigns = tups flatMap {
- // special case for literals
- case jse.Tuple2(name, value) =>
- js.Assign(js.JSBracketSelect(res, name), value) :: Nil
- case tupExpr =>
- val tupIdent = js.Ident(freshName("tup"))
- val tup = js.VarRef(tupIdent, mutable = false)(tuple2Type)
- js.VarDef(tupIdent, tuple2Type, mutable = false, tupExpr) ::
- js.Assign(js.JSBracketSelect(res,
- genApplyMethod(tup, TupleClass(2), js.Ident("$$und1__O"), Nil, jstpe.AnyType)),
- genApplyMethod(tup, TupleClass(2), js.Ident("$$und2__O"), Nil, jstpe.AnyType)) :: Nil
- }
-
- js.Block(resVarDef +: assigns :+ res :_*)
-
- // Here we would need the case where the varargs are passed in
- // as non-literal list:
- // js.Dynamic.literal(x :_*)
- // However, Scala does currently not support this
-
- // case where another method is called
- case (js.StringLiteral(name, _), _) if name != "apply" =>
- currentUnit.error(pos,
- s"js.Dynamic.literal does not have a method named $name")
- statToExpr(js.Skip())
- case _ =>
- currentUnit.error(pos,
- "js.Dynamic.literal.applyDynamic may not be called directly")
- statToExpr(js.Skip())
- }
- } else if (code == ARR_CREATE) {
- // js.Array.create(elements:_*)
- genArgArray
- } else if (code == ARRAYCOPY) {
- // System.arraycopy - not a helper because receiver is dropped
- js.CallHelper("systemArraycopy", genArgs)(toIRType(tree.tpe))
- } else (genArgs match {
- case Nil =>
- code match {
- case GETGLOBAL => js.JSGlobal()
- case NTR_MOD_SUFF =>
- js.StringLiteral(scala.reflect.NameTransformer.MODULE_SUFFIX_STRING)
- case NTR_NAME_JOIN =>
- js.StringLiteral(scala.reflect.NameTransformer.NAME_JOIN_STRING)
- case DEBUGGER =>
- statToExpr(js.Debugger())
- case RETURNRECEIVER =>
- receiver
- case UNITVAL =>
- js.Undefined()
- case UNITTYPE =>
- genClassConstant(UnitTpe)
- }
-
- case List(arg) =>
-
- /** get the apply method of a class extending FunctionN
- *
- * only use when implementing a fromFunctionN primitive
- * as it uses the tree
- */
- def getFunApply(clSym: Symbol) = {
- // Fetch symbol and resolve overload if necessary
- val sym = getMemberMethod(clSym, newTermName("apply"))
-
- if (sym.isOverloaded) {
- // The symbol is overloaded. Figure out the arity
- // from the name of the primitive function we are
- // implementing. Then chose the overload with the right
- // number of Object arguments
- val funName = tree.fun.symbol.name.encoded
- assert(funName.startsWith("fromFunction"))
- val arity = funName.substring(12).toInt
-
- sym.alternatives.find { s =>
- val ps = s.paramss
- ps.size == 1 &&
- ps.head.size == arity &&
- ps.head.forall(_.tpe.typeSymbol == ObjectClass)
- }.get
- } else sym
- }
-
- def captureWithin(ident: js.Ident, tpe: jstpe.Type, value: js.Tree)(
- within: js.Tree): js.Tree = {
- js.Cast(js.JSApply(
- js.Function(List(js.ParamDef(ident, tpe)), within.tpe,
- js.Return(within)),
- List(value)), within.tpe)
- }
-
- code match {
- case V2JS => statToExpr(exprToStat(arg))
- case Z2JS | N2JS | S2JS => js.Cast(arg, jstpe.DynType)
-
- /** Convert a scala.FunctionN f to a js.FunctionN
- * Basically it binds the appropriate `apply` method of f to f.
- * (function($this) {
- * return function(args...) {
- * return $this.apply__something(args...);
- * }
- * })(f);
- *
- * TODO Use the JS function Function.prototype.bind()?
- */
- case F2JS =>
- arg match {
- /* This case will happend every time we have a Scala lambda
- * in js.FunctionN position. We remove the JS function to
- * Scala function wrapper, instead of adding a Scala function
- * to JS function wrapper.
- */
- case JSFunctionToScala(fun, arity) =>
- fun
-
- case _ =>
- val inputTpe = args.head.tpe
- val inputIRType = toIRType(inputTpe)
- val applyMeth = getFunApply(inputTpe.typeSymbol)
- val arity = applyMeth.tpe.params.size
- val theFunction = js.Ident("$this")
- val arguments = (1 to arity).toList map (x => js.Ident("arg"+x))
- captureWithin(theFunction, inputIRType, arg) {
- js.Function(arguments.map(js.ParamDef(_, jstpe.AnyType)), jstpe.AnyType, {
- js.Return(genApplyMethod(
- js.VarRef(theFunction, mutable = false)(inputIRType),
- inputTpe, applyMeth,
- arguments.map(js.VarRef(_, mutable = false)(jstpe.AnyType))))
- })
- }
- }
-
- /** Convert a scala.FunctionN f to a js.ThisFunction{N-1}
- * Generates:
- * (function(f) {
- * return function(args...) {
- * return f.apply__something(this, args...);
- * };
- * })(f);
- */
- case F2JSTHIS =>
- val inputTpe = args.head.tpe
- val inputIRType = toIRType(inputTpe)
- val applyMeth = getFunApply(inputTpe.typeSymbol)
- val arity = applyMeth.tpe.params.size
- val theFunction = js.Ident("f")
- val arguments = (1 until arity).toList map (x => js.Ident("arg"+x))
- captureWithin(theFunction, inputIRType, arg) {
- js.Function(arguments.map(js.ParamDef(_, jstpe.AnyType)), jstpe.AnyType, {
- js.Return(genApplyMethod(
- js.VarRef(theFunction, mutable = false)(inputIRType),
- inputTpe, applyMeth,
- js.This()(jstpe.AnyType) ::
- arguments.map(js.VarRef(_, mutable = false)(jstpe.AnyType))))
- })
- }
-
- case JS2Z | JS2N =>
- makePrimitiveUnbox(arg, tree.tpe)
-
- case JS2S =>
- genAsInstanceOf(tree.tpe, arg)
-
- case RTJ2J | J2RTJ =>
- arg // TODO? What if (arg === null) for RTJ2J?
-
- case DYNSELECT =>
- // js.Dynamic.selectDynamic(arg)
- js.JSBracketSelect(receiver, arg)
-
- case DICT_DEL =>
- // js.Dictionary.delete(arg)
- js.JSDelete(receiver, arg)
-
- case ISUNDEF =>
- // js.isUndefined(arg)
- js.BinaryOp("===", arg, js.Undefined(), jstpe.BooleanType)
- case TYPEOF =>
- // js.typeOf(arg)
- js.UnaryOp("typeof", arg, jstpe.StringType)
-
- case OBJPROPS =>
- // js.Object.properties(arg)
- js.CallHelper("propertiesOf", arg)(jstpe.DynType)
- }
-
- case List(arg1, arg2) =>
- code match {
- case DYNUPDATE =>
- // js.Dynamic.updateDynamic(arg1)(arg2)
- statToExpr(js.Assign(js.JSBracketSelect(receiver, arg1), arg2))
-
- case HASPROP =>
- // js.Object.hasProperty(arg1, arg2)
- /* Here we have an issue with evaluation order of arg1 and arg2,
- * since the obvious translation is `arg2 in arg1`, but then
- * arg2 is evaluated before arg1. Since this is not a commonly
- * used operator, we don't try to avoid unnessary temp vars, and
- * simply always evaluate arg1 in a temp before doing the `in`.
- */
- val temp = js.Ident(freshName())
- js.Block(
- js.VarDef(temp, jstpe.DynType, mutable = false, arg1),
- js.BinaryOp("in", arg2,
- js.VarRef(temp, mutable = false)(jstpe.DynType),
- jstpe.BooleanType))
- }
- })
- }
-
- /** Gen JS code for a primitive JS call (to a method of a subclass of js.Any)
- * This is the typed Scala.js to JS bridge feature. Basically it boils
- * down to calling the method without name mangling. But other aspects
- * come into play:
- * * Operator methods are translated to JS operators (not method calls)
- * * apply is translated as a function call, i.e. o() instead of o.apply()
- * * Scala varargs are turned into JS varargs (see genPrimitiveJSArgs())
- * * Getters and parameterless methods are translated as Selects
- * * Setters are translated to Assigns of Selects
- */
- private def genPrimitiveJSCall(tree: Apply): js.Tree = {
- implicit val pos = tree.pos
-
- val sym = tree.symbol
- val Apply(fun @ Select(receiver0, _), args0) = tree
-
- val funName = sym.unexpandedName.decoded
- val receiver = genExpr(receiver0)
- val argArray = genPrimitiveJSArgs(sym, args0)
-
- // valid only for methods that don't have any varargs
- lazy val js.JSArrayConstr(args) = argArray
- lazy val argc = args.length
-
- def hasExplicitJSEncoding =
- sym.hasAnnotation(JSNameAnnotation) ||
- sym.hasAnnotation(JSBracketAccessAnnotation)
-
- val boxedResult = funName match {
- case "unary_+" | "unary_-" | "unary_~" | "unary_!" =>
- assert(argc == 0)
- js.JSUnaryOp(funName.substring(funName.length-1), receiver)
-
- case "+" | "-" | "*" | "/" | "%" | "<<" | ">>" | ">>>" |
- "&" | "|" | "^" | "&&" | "||" | "<" | ">" | "<=" | ">=" =>
- assert(argc == 1)
- js.JSBinaryOp(funName, receiver, args.head)
-
- case "apply" if receiver0.tpe.typeSymbol.isSubClass(JSThisFunctionClass) =>
- js.JSApply(js.JSBracketSelect(receiver, js.StringLiteral("call")), args)
-
- case "apply" if !hasExplicitJSEncoding =>
- /* Protect the receiver so that if the receiver is, e.g.,
- * path.f
- * we emit
- * ScalaJS.protect(path.f)(args...)
- * instead of
- * path.f(args...)
- * where
- * ScalaJS.protect = function(x) { return x; }
- * 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`).
- */
- def protectedReceiver = receiver match {
- case js.JSDotSelect(_, _) | js.JSBracketSelect(_, _) =>
- js.CallHelper("protect", receiver)(receiver.tpe)
- case _ =>
- receiver
- }
- argArray match {
- case js.JSArrayConstr(args) =>
- js.JSApply(protectedReceiver, args)
- case _ =>
- js.JSApply(js.JSBracketSelect(
- receiver, js.StringLiteral("apply")), List(js.Null(), argArray))
- }
-
- case _ =>
- def jsFunName = jsNameOf(sym)
-
- if (sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM)) {
- js.UndefinedParam()(toIRType(sym.tpe.resultType))
- } else if (jsInterop.isJSGetter(sym)) {
- assert(argc == 0)
- js.JSBracketSelect(receiver, js.StringLiteral(jsFunName))
- } else if (jsInterop.isJSSetter(sym)) {
- assert(argc == 1)
- statToExpr(js.Assign(
- js.JSBracketSelect(receiver,
- js.StringLiteral(jsFunName.stripSuffix("_="))),
- args.head))
- } else if (jsInterop.isJSBracketAccess(sym)) {
- assert(argArray.isInstanceOf[js.JSArrayConstr] && (argc == 1 || argc == 2),
- s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments")
- args match {
- case List(keyArg) =>
- js.JSBracketSelect(receiver, keyArg)
- case List(keyArg, valueArg) =>
- statToExpr(js.Assign(
- js.JSBracketSelect(receiver, keyArg),
- valueArg))
- }
- } else {
- argArray match {
- case js.JSArrayConstr(args) =>
- js.JSApply(js.JSBracketSelect(
- receiver, js.StringLiteral(jsFunName)), args)
- case _ =>
- js.CallHelper("applyMethodWithVarargs", receiver,
- js.StringLiteral(jsFunName), argArray)(jstpe.DynType)
- }
- }
- }
-
- boxedResult match {
- case js.UndefinedParam() => boxedResult
- case _ =>
- ensureUnboxed(boxedResult,
- enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType))
- }
- }
-
- /** Gen JS code for new java.lang.String(...)
- * Proxies calls to method newString on object
- * scala.scalajs.runtime.RuntimeString with proper arguments
- */
- private def genNewString(tree: Apply): js.Tree = {
- implicit val pos = tree.pos
- val Apply(fun @ Select(_, _), args0) = tree
-
- val ctor = fun.symbol
- val args = args0 map genExpr
-
- // Filter members of target module for matching member
- val compMembers = for {
- mem <- RuntimeStringModule.tpe.members
- if mem.name.decoded == "newString"
- // Deconstruct method type.
- MethodType(params, returnType) = mem.tpe
- if returnType.typeSymbol == JSStringClass
- // Construct fake type returning java.lang.String
- fakeType = MethodType(params, StringClass.tpe)
- if ctor.tpe.matches(fakeType)
- } yield mem
-
- if (compMembers.isEmpty) {
- currentUnit.error(pos,
- s"""Could not find implementation for constructor of java.lang.String
- |with type ${ctor.tpe}. Constructors on java.lang.String
- |are forwarded to the companion object of
- |scala.scalajs.runtime.RuntimeString""".stripMargin)
- js.Undefined()
- } else {
- assert(compMembers.size == 1,
- s"""For constructor with type ${ctor.tpe} on java.lang.String,
- |found multiple companion module members.""".stripMargin)
-
- // Emit call to companion object
- genApplyMethod(
- genLoadModule(RuntimeStringModule),
- RuntimeStringModule.moduleClass,
- compMembers.head,
- args)
- }
- }
-
- /**
- * Forwards call on java.lang.String to the implementation class of
- * scala.scalajs.runtime.RuntimeString
- */
- private def genStringCall(tree: Apply): js.Tree = {
- implicit val pos = tree.pos
-
- val sym = tree.symbol
-
- // Deconstruct tree and create receiver and argument JS expressions
- val Apply(Select(receiver0, _), args0) = tree
- val receiver = js.Cast(genExpr(receiver0), jstpe.DynType)
- val args = args0 map genExpr
-
- // Get implementation from RuntimeString trait
- val rtStrSym = sym.overridingSymbol(RuntimeStringClass)
-
- // Check that we found a member
- if (rtStrSym == NoSymbol) {
- currentUnit.error(pos,
- s"""Could not find implementation for method ${sym.name}
- |on java.lang.String with type ${sym.tpe}
- |Methods on java.lang.String are forwarded to the implementation class
- |of scala.scalajs.runtime.RuntimeString""".stripMargin)
- js.Undefined()
- } else {
- assert(!rtStrSym.isOverloaded,
- s"""For method ${sym.name} on java.lang.String with type ${sym.tpe},
- |found multiple implementation class members.""".stripMargin)
-
- // Emit call to implementation class
- val (implClass, methodIdent) = encodeImplClassMethodSym(rtStrSym)
- genTraitImplApply(
- encodeClassFullNameIdent(implClass),
- methodIdent,
- receiver :: args,
- toIRType(tree.tpe))
- }
- }
-
- /** Gen JS code for a new of a JS class (subclass of js.Any) */
- private def genPrimitiveJSNew(tree: Apply): js.Tree = {
- implicit val pos = tree.pos
-
- val Apply(fun @ Select(New(tpt), _), args0) = tree
- val cls = tpt.tpe.typeSymbol
- val ctor = fun.symbol
-
- genPrimitiveJSArgs(ctor, args0) match {
- case js.JSArrayConstr(args) =>
- if (cls == JSObjectClass && args.isEmpty) js.JSObjectConstr(Nil)
- else js.JSNew(genPrimitiveJSClass(cls), args)
- case argArray =>
- js.CallHelper("newInstanceWithVarargs",
- genPrimitiveJSClass(cls), argArray)(jstpe.DynType)
- }
- }
-
- /** Gen JS code representing a JS class (subclass of js.Any) */
- private def genPrimitiveJSClass(sym: Symbol)(
- implicit pos: Position): js.Tree = {
- genGlobalJSObject(sym)
- }
-
- /** Gen JS code representing a JS module (var of the global scope) */
- private def genPrimitiveJSModule(sym: Symbol)(
- implicit pos: Position): js.Tree = {
- genGlobalJSObject(sym)
- }
-
- /** Gen JS code representing a JS object (class or module) in global scope
- */
- private def genGlobalJSObject(sym: Symbol)(
- implicit pos: Position): js.Tree = {
- jsNameOf(sym).split('.').foldLeft[js.Tree](js.JSGlobal()) { (memo, chunk) =>
- js.JSBracketSelect(memo, js.StringLiteral(chunk, Some(chunk)))
- }
- }
-
- /** Gen actual actual arguments to a primitive JS call
- * This handles repeated arguments (varargs) by turning them into
- * JS varargs, i.e., by expanding them into normal arguments.
- *
- * Returns an only tree which is a JS array of the arguments. In most
- * cases, it will be a js.JSArrayConstr with the expanded arguments. It will
- * not if a Seq is passed to a varargs argument with the syntax seq:_*.
- */
- private def genPrimitiveJSArgs(sym: Symbol, args: List[Tree])(
- implicit pos: Position): js.Tree = {
- val wereRepeated = exitingPhase(currentRun.typerPhase) {
- for {
- params <- sym.tpe.paramss
- param <- params
- } yield isScalaRepeatedParamType(param.tpe)
- }
-
- var reversedParts: List[js.Tree] = Nil
- var reversedPartUnderConstruction: List[js.Tree] = Nil
-
- def closeReversedPartUnderConstruction() = {
- if (!reversedPartUnderConstruction.isEmpty) {
- val part = reversedPartUnderConstruction.reverse
- reversedParts ::= js.JSArrayConstr(part)
- reversedPartUnderConstruction = Nil
- }
- }
-
- val paramTpes = enteringPhase(currentRun.posterasurePhase) {
- for (param <- sym.tpe.params)
- yield param.tpe
- }
-
- for (((arg, wasRepeated), tpe) <- (args zip wereRepeated) zip paramTpes) {
- if (wasRepeated) {
- genPrimitiveJSRepeatedParam(arg) match {
- case js.JSArrayConstr(jsArgs) =>
- reversedPartUnderConstruction =
- jsArgs reverse_::: reversedPartUnderConstruction
- case jsArgArray =>
- closeReversedPartUnderConstruction()
- reversedParts ::= jsArgArray
- }
- } else {
- val unboxedArg = genExpr(arg)
- val boxedArg = unboxedArg match {
- case js.UndefinedParam() => unboxedArg
- case _ => ensureBoxed(unboxedArg, tpe)
- }
- reversedPartUnderConstruction ::= boxedArg
- }
- }
- closeReversedPartUnderConstruction()
-
- // Find js.UndefinedParam at the end of the argument list. No check is
- // performed whether they may be there, since they will only be placed
- // where default arguments can be anyway
- reversedParts = reversedParts match {
- case Nil => Nil
- case js.JSArrayConstr(params) :: others =>
- val nparams =
- params.reverse.dropWhile(_.isInstanceOf[js.UndefinedParam]).reverse
- js.JSArrayConstr(nparams) :: others
- case parts => parts
- }
-
- // Find remaining js.UndefinedParam and replace by js.Undefined. This can
- // happen with named arguments or when multiple argument lists are present
- reversedParts = reversedParts map {
- case js.JSArrayConstr(params) =>
- val nparams = params map {
- case js.UndefinedParam() => js.Undefined()
- case param => param
- }
- js.JSArrayConstr(nparams)
- case part => part
- }
-
- reversedParts match {
- case Nil => js.JSArrayConstr(Nil)
- case List(part) => part
- case _ =>
- val partHead :: partTail = reversedParts.reverse
- js.JSApply(js.JSBracketSelect(
- partHead, js.StringLiteral("concat")), partTail)
- }
- }
-
- /** Gen JS code for a repeated param of a primitive JS method
- * In this case `arg` has type Seq[T] for some T, but the result should
- * have type js.Array[T]. So this method takes care of the conversion.
- * It is specialized for the shapes of tree generated by the desugaring
- * of repeated params in Scala, so that these produce a js.JSArrayConstr.
- */
- private def genPrimitiveJSRepeatedParam(arg: Tree): js.Tree = {
- implicit val pos = arg.pos
-
- // Given a method `def foo(args: T*)`
- arg match {
- // foo(arg1, arg2, ..., argN) where N > 0
- case MaybeAsInstanceOf(WrapArray(
- MaybeAsInstanceOf(ArrayValue(tpt, elems))))
- if elems.forall(e => !isPrimitiveValueType(e.tpe)) => // non-optimal fix to #39
- js.JSArrayConstr(elems map genExpr)
-
- // foo()
- case Select(_, _) if arg.symbol == NilModule =>
- js.JSArrayConstr(Nil)
-
- // foo(argSeq:_*)
- case _ =>
- /* Here we fall back to calling js.Any.fromTraversableOnce(seqExpr)
- * to perform the conversion.
- */
- genApplyMethod(
- genLoadModule(JSAnyModule),
- JSAnyModule.moduleClass,
- JSAny_fromTraversableOnce,
- List(genExpr(arg)))
- }
- }
-
- object MaybeAsInstanceOf {
- def unapply(tree: Tree): Some[Tree] = tree match {
- case Apply(TypeApply(asInstanceOf_? @ Select(base, _), _), _)
- if asInstanceOf_?.symbol == Object_asInstanceOf =>
- Some(base)
- case _ =>
- Some(tree)
- }
- }
-
- object WrapArray {
- lazy val isWrapArray: Set[Symbol] = Seq(
- nme.wrapRefArray,
- nme.wrapByteArray,
- nme.wrapShortArray,
- nme.wrapCharArray,
- nme.wrapIntArray,
- nme.wrapLongArray,
- nme.wrapFloatArray,
- nme.wrapDoubleArray,
- nme.wrapBooleanArray,
- nme.wrapUnitArray,
- nme.genericWrapArray).map(getMemberMethod(PredefModule, _)).toSet
-
- def unapply(tree: Apply): Option[Tree] = tree match {
- case Apply(wrapArray_?, List(wrapped))
- if isWrapArray(wrapArray_?.symbol) =>
- Some(wrapped)
- case _ =>
- None
- }
- }
-
- // Synthesizers for raw JS functions ---------------------------------------
-
- /** Try and gen and record JS code for an anonymous function class.
- *
- * Returns true if the class could be rewritten that way, false otherwise.
- *
- * We make the following assumptions on the form of such classes:
- * - It is an anonymous function
- * - Includes being anonymous, final, and having exactly one constructor
- * - It is not a PartialFunction
- * - It has no field other than param accessors
- * - It has exactly one constructor
- * - It has exactly one non-bridge method apply if it is not specialized,
- * or a method apply$...$sp and a forwarder apply if it is specialized.
- * - As a precaution: it is synthetic
- *
- * From a class looking like this:
- *
- * final class (outer, capture1, ..., captureM) extends AbstractionFunctionN[...] {
- * def apply(param1, ..., paramN) = {
- *
- * }
- * }
- *
- * we generate:
- *
- * function(outer, capture1, ..., captureM) {
- * return function(param1, ..., paramN) {
- *
- * }
- * }
- *
- * so that, at instantiation point, we can write:
- *
- * new AnonFunctionN(functionMaker(captured1, ..., capturedM))
- *
- * Trickier things apply when the function is specialized.
- */
- private def tryGenAndRecordAnonFunctionClass(cd: ClassDef): Boolean = {
- implicit val pos = cd.pos
- val sym = cd.symbol
- assert(sym.isAnonymousFunction,
- s"tryGenAndRecordAnonFunctionClass called with non-anonymous function $cd")
-
- withScopedVars(
- currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass),
- currentClassSym := sym
- ) {
-
- val (functionMakerBase, arity) =
- tryGenAndRecordAnonFunctionClassGeneric(cd) { msg =>
- return false
- }
-
- val functionMaker = { capturedArgs: List[js.Tree] =>
- JSFunctionToScala(functionMakerBase(capturedArgs), arity)
- }
-
- translatedAnonFunctions +=
- sym -> (functionMaker, currentClassInfoBuilder.get)
-
- }
- true
- }
-
- /** Constructor and extractor object for a tree that converts a JavaScript
- * function into a Scala function.
- */
- private object JSFunctionToScala {
- private val AnonFunPrefScala =
- "scala.scalajs.runtime.AnonFunction"
- private val AnonFunPrefJS =
- "sjsr_AnonFunction"
-
- def apply(jsFunction: js.Tree, arity: Int)(
- implicit pos: Position): js.Tree = {
- val clsSym = getRequiredClass(AnonFunPrefScala + arity)
- val ctor = clsSym.tpe.member(nme.CONSTRUCTOR)
- genNew(clsSym, ctor, List(jsFunction))
- }
-
- def unapply(tree: js.New): Option[(js.Tree, Int)] = tree match {
- case js.New(jstpe.ClassType(wrapperName), _, List(fun))
- if wrapperName.startsWith(AnonFunPrefJS) =>
- val arityStr = wrapperName.substring(AnonFunPrefJS.length)
- try {
- Some((fun, arityStr.toInt))
- } catch {
- case e: NumberFormatException => None
- }
-
- case _ =>
- None
- }
- }
-
- /** Gen and record JS code for a raw JS function class.
- *
- * This is called when emitting a ClassDef that represents an anonymous
- * class extending `js.FunctionN`. These are generated by the SAM
- * synthesizer when the target type is a `js.FunctionN`. Since JS
- * functions are not classes, we deconstruct the ClassDef, then
- * reconstruct it to be a genuine raw JS function maker.
- *
- * Compared to `tryGenAndRecordAnonFunctionClass()`, this function must
- * always succeed, because we really cannot afford keeping them as
- * anonymous classes. The good news is that it can do so, because the
- * body of SAM lambdas is hoisted in the enclosing class. Hence, the
- * apply() method is just a forwarder to calling that hoisted method.
- *
- * From a class looking like this:
- *
- * final class (outer, capture1, ..., captureM) extends js.FunctionN[...] {
- * def apply(param1, ..., paramN) = {
- * outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM)
- * }
- * }
- *
- * we generate:
- *
- * function(outer, capture1, ..., captureM) {
- * return function(param1, ..., paramN) {
- * return outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM);
- * }
- * }
- *
- * The function maker is recorded in `translatedAnonFunctions` to be
- * fetched later by the translation for New.
- */
- def genAndRecordRawJSFunctionClass(cd: ClassDef): Unit = {
- val sym = cd.symbol
- assert(isRawJSFunctionDef(sym),
- s"genAndRecordRawJSFunctionClass called with non-JS function $cd")
-
- withScopedVars(
- currentClassInfoBuilder := new ClassInfoBuilder(sym.asClass),
- currentClassSym := sym
- ) {
-
- val (functionMaker, _) =
- tryGenAndRecordAnonFunctionClassGeneric(cd) { msg =>
- abort(s"Could not generate raw function maker for JS function: $msg")
- }
-
- translatedAnonFunctions +=
- sym -> (functionMaker, currentClassInfoBuilder.get)
-
- }
- }
-
- /** Code common to tryGenAndRecordAnonFunctionClass and
- * genAndRecordRawJSFunctionClass.
- */
- private def tryGenAndRecordAnonFunctionClassGeneric(cd: ClassDef)(
- onFailure: (=> String) => Unit): (List[js.Tree] => js.Tree, Int) = {
- implicit val pos = cd.pos
- val sym = cd.symbol
-
- // First checks
-
- if (sym.isSubClass(PartialFunctionClass))
- onFailure(s"Cannot rewrite PartialFunction $cd")
- if (instantiatedAnonFunctions contains sym) {
- // when the ordering we're given is evil (it happens!)
- onFailure(s"Abort function rewrite because it was already instantiated: $cd")
- }
-
- // First step: find the apply method def, and collect param accessors
-
- var paramAccessors: List[Symbol] = Nil
- var applyDef: DefDef = null
-
- def gen(tree: Tree): Unit = {
- tree match {
- case EmptyTree => ()
- case Template(_, _, body) => body foreach gen
- case vd @ ValDef(mods, name, tpt, rhs) =>
- val fsym = vd.symbol
- assert(fsym.isParamAccessor,
- s"Found field $fsym which is not a param accessor in anon function $cd")
- if (fsym.isPrivate) {
- paramAccessors ::= fsym
- } else {
- // Uh oh ... an inner something will try to access my fields
- onFailure(s"Found a non-private field $fsym in $cd")
- }
- case dd: DefDef =>
- val ddsym = dd.symbol
- if (ddsym.isClassConstructor) {
- assert(ddsym.isPrimaryConstructor,
- s"Non-primary constructor $ddsym in anon function $cd")
- } else {
- val name = dd.name.toString
- if (name == "apply" || (ddsym.isSpecialized && name.startsWith("apply$"))) {
- if ((applyDef eq null) || ddsym.isSpecialized)
- applyDef = dd
- } else {
- // Found a method we cannot encode in the rewriting
- onFailure(s"Found a non-apply method $ddsym in $cd")
- }
- }
- case _ =>
- abort("Illegal tree in gen of genAndRecordAnonFunctionClass(): " + tree)
- }
- }
- gen(cd.impl)
- paramAccessors = paramAccessors.reverse // preserve definition order
- assert(applyDef ne null,
- s"Did not find any apply method in anon function $cd")
-
- // Second step: build the list of useful constructor parameters
-
- val ctorParams = sym.primaryConstructor.tpe.params
- assert(
- paramAccessors.size == ctorParams.size ||
- (paramAccessors.size == ctorParams.size-1 &&
- ctorParams.head.unexpandedName == newTermName("arg$outer")),
- s"Have param accessors $paramAccessors but "+
- s"ctor params $ctorParams in anon function $cd")
- val hasUnusedOuterCtorParam = paramAccessors.size != ctorParams.size
- val usedCtorParams =
- if (hasUnusedOuterCtorParam) ctorParams.tail
- else ctorParams
- val ctorParamDefs = usedCtorParams map { p =>
- // in the apply method's context
- js.ParamDef(encodeLocalSym(p, freshName)(p.pos), toIRType(p.tpe))(p.pos)
- }
-
- // Third step: emit the body of the apply method def
-
- val (applyMethod, methodInfoBuilder) = withScopedVars(
- paramAccessorLocals := (paramAccessors zip ctorParamDefs).toMap
- ) {
- genMethodWithInfoBuilder(applyDef).getOrElse(
- abort(s"Oops, $applyDef did not produce a method"))
- }
-
- withScopedVars(
- currentMethodInfoBuilder := methodInfoBuilder
- ) {
- // Fourth step: patch the body to unbox parameters and box result
-
- val js.MethodDef(_, params, _, body) = applyMethod
- val patchedBody = patchFunBodyWithBoxes(applyDef.symbol, params, body)
-
- // Fifth step: build the function maker
-
- val functionMakerFun =
- js.Function(ctorParamDefs, jstpe.DynType, {
- js.Return {
- if (JSThisFunctionClasses.exists(sym isSubClass _)) {
- assert(params.nonEmpty, s"Empty param list in ThisFunction: $cd")
- js.Function(params.tail, jstpe.AnyType, js.Block(
- js.VarDef(params.head.name, params.head.ptpe,
- mutable = false, js.This()(params.head.ptpe)),
- patchedBody
- ))
- } else {
- js.Function(params, jstpe.AnyType, patchedBody)
- }
- }
- })
-
- val functionMaker = { capturedArgs0: List[js.Tree] =>
- val capturedArgs =
- if (hasUnusedOuterCtorParam) capturedArgs0.tail
- else capturedArgs0
- assert(capturedArgs.size == ctorParamDefs.size)
- js.JSApply(functionMakerFun, capturedArgs)
- }
-
- val arity = params.size
-
- (functionMaker, arity)
- }
- }
-
- /** Generate JS code for an anonymous function
- *
- * Anonymous functions survive until the backend only under
- * -Ydelambdafy:method
- * and when they do, their body is always of the form
- * EnclosingClass.this.someMethod(arg1, ..., argN, capture1, ..., captureM)
- * where argI are the formal arguments of the lambda, and captureI are
- * local variables or the enclosing def.
- *
- * We translate them by instantiating scala.scalajs.runtime.AnonFunctionN
- * with a JS anonymous function:
- *
- * new ScalaJS.c.scala_scalajs_runtime_AnonFunctionN().init___xyz(
- * (function(arg1, ..., argN) {
- * return this.someMethod(arg1, ..., argN, capture1, ..., captureM)
- * }).bind(this)
- * )
- *
- * (with additional considerations for protecting captures against
- * mutations)
- *
- * In addition, input params are unboxed before use, and the result of
- * someMethod() is boxed back.
- *
- * Currently, this translation does not take advantage of specialization.
- */
- private def genAnonFunction(originalFunction: Function): js.Tree = {
- implicit val pos = originalFunction.pos
- val Function(paramTrees, Apply(
- targetTree @ Select(receiver, _), actualArgs)) = originalFunction
-
- val target = targetTree.symbol
- val params = paramTrees.map(_.symbol)
-
- val isInImplClass = target.owner.isImplClass
-
- val jsFunction = {
- val jsParams = params map { p =>
- js.ParamDef(encodeLocalSym(p, freshName)(p.pos), toIRType(p.tpe))(p.pos)
- }
- val jsBody = js.Return {
- if (isInImplClass)
- genTraitImplApply(target, actualArgs map genExpr)
- else
- genApplyMethod(js.This()(toIRType(receiver.tpe)),
- receiver.tpe, target, actualArgs map genExpr)
- }
- val patchedBody = patchFunBodyWithBoxes(target, jsParams, jsBody)
- js.Function(jsParams, jstpe.AnyType, patchedBody)
- }
-
- val boundFunction = {
- if (isInImplClass) {
- jsFunction
- } else {
- js.JSApply(js.JSBracketSelect(
- jsFunction, js.StringLiteral("bind")), List(genExpr(receiver)))
- }
- }
-
- JSFunctionToScala(boundFunction, params.size)
- }
-
- private def patchFunBodyWithBoxes(methodSym: Symbol,
- params: List[js.ParamDef], body: js.Tree)(implicit pos: Position): js.Tree = {
- val methodType = enteringPhase(currentRun.posterasurePhase)(methodSym.tpe)
-
- val unboxParams = for {
- (paramIdent, paramSym) <- params zip methodType.params
- paramTpe = enteringPhase(currentRun.posterasurePhase)(paramSym.tpe)
- paramRef = paramIdent.ref
- unboxedParam = ensureUnboxed(paramRef, paramTpe)
- if unboxedParam ne paramRef
- } yield {
- js.Assign(paramRef, unboxedParam)
- }
-
- val returnStat = {
- val resultType = methodType.resultType
- body match {
- case js.Return(expr, None) =>
- js.Return(ensureBoxed(expr, resultType))
- case _ =>
- assert(resultType.typeSymbol == UnitClass)
- /* In theory we should return a boxed () value, but that is the
- * undefined value, which is returned automatically in
- * JavaScript when there is no return statement. */
- body
- }
- }
-
- js.Block(unboxParams :+ returnStat)
- }
-
- // Utilities ---------------------------------------------------------------
-
- /** Generate a literal "zero" for the requested type */
- def genZeroOf(tpe: Type)(implicit pos: Position): js.Tree = toTypeKind(tpe) match {
- case UNDEFINED => js.Undefined()
- case BOOL => js.BooleanLiteral(false)
- case LongKind => genLongModuleCall("zero")
- case INT(_) => js.IntLiteral(0)
- case FLOAT(_) => js.DoubleLiteral(0.0)
- case REFERENCE(_) => js.Null()
- case ARRAY(_) => js.Null()
- }
-
- /** Generate loading of a module value
- * Can be given either the module symbol, or its module class symbol.
- */
- def genLoadModule(sym0: Symbol)(implicit pos: Position): js.Tree = {
- require(sym0.isModuleOrModuleClass,
- "genLoadModule called with non-module symbol: " + sym0)
- val sym1 = if (sym0.isModule) sym0.moduleClass else sym0
- val sym = // redirect all static methods of String to RuntimeString
- if (sym1 == StringModule) RuntimeStringModule.moduleClass
- else sym1
-
- val isGlobalScope = sym.tpe.typeSymbol isSubClass JSGlobalScopeClass
-
- if (isGlobalScope) js.JSGlobal()
- else if (isRawJSType(sym.tpe)) genPrimitiveJSModule(sym)
- else {
- if (!foreignIsImplClass(sym))
- currentMethodInfoBuilder.accessesModule(sym)
- js.LoadModule(jstpe.ClassType(encodeClassFullName(sym)))
- }
- }
-
- /** Generate a call to scala.scalajs.runtime.RuntimeLong companion */
- private def genLongModuleCall(methodName: String, args: js.Tree*)(implicit pos: Position) = {
- val LongModule = genLoadModule(RuntimeLongModule)
- val encName = scala.reflect.NameTransformer.encode(methodName)
- val method = getMemberMethod(RuntimeLongModule, newTermName(encName))
- genApplyMethod(LongModule, RuntimeLongModule.moduleClass,
- method, args.toList)
- }
-
- private def genOlLongCall(
- receiver: js.Tree,
- methodName: String,
- args: js.Tree*)(argTypes: Type*)
- (implicit pos: Position): js.Tree = {
-
- val encName = scala.reflect.NameTransformer.encode(methodName)
- val method = getMemberMethod(
- jsDefinitions.RuntimeLongClass, newTermName(encName))
- assert(method.isOverloaded)
-
- def checkParams(types: List[Type]) = types.size == argTypes.size &&
- (argTypes zip types).forall { case (t1,t2) => t1 =:= t2 }
-
- val opt = method.alternatives find { m =>
- checkParams(m.paramss.head.map(_.typeSignature))
- }
-
- genLongCall(receiver, opt.get, args :_*)
- }
-
- private def genLongCall(
- receiver: js.Tree,
- methodName: String,
- args: js.Tree*)(implicit pos: Position): js.Tree = {
- val encName = scala.reflect.NameTransformer.encode(methodName)
- val method = getMemberMethod(
- jsDefinitions.RuntimeLongClass, newTermName(encName))
- genLongCall(receiver, method, args :_*)
- }
-
- private def genLongCall(receiver: js.Tree, method: Symbol, args: js.Tree*)(
- implicit pos: Position): js.Tree =
- genApplyMethod(receiver, RuntimeLongClass, method, args.toList)
-
- /** Generate access to a static member */
- private def genStaticMember(sym: Symbol)(implicit pos: Position) = {
- /* Actually, there is no static member in Scala.js. If we come here, that
- * is because we found the symbol in a Java-emitted .class in the
- * classpath. But the corresponding implementation in Scala.js will
- * actually be a val in the companion module.
- * So we cheat here. This is a workaround for not having separate
- * compilation yet.
- */
- import scalaPrimitives._
- import jsPrimitives._
- if (isPrimitive(sym)) {
- getPrimitive(sym) match {
- case UNITVAL => js.Undefined()
- case UNITTYPE => genClassConstant(UnitTpe)
- }
- } else {
- val instance = genLoadModule(sym.owner)
- val method = encodeStaticMemberSym(sym)
- currentMethodInfoBuilder.callsMethod(sym.owner, method)
- js.Apply(instance, method, Nil)(toIRType(sym.tpe))
- }
- }
-
- /** Generate a Class[_] value (e.g. coming from classOf[T]) */
- private def genClassConstant(tpe: Type)(implicit pos: Position): js.Tree = {
- val (irType, sym) = encodeReferenceType(tpe)
- currentMethodInfoBuilder.accessesClassData(sym)
- js.ClassOf(irType)
- }
- }
-
- /** Test whether the given type represents a raw JavaScript type
- *
- * I.e., test whether the type extends scala.js.Any
- */
- def isRawJSType(tpe: Type): Boolean =
- tpe.typeSymbol.annotations.find(_.tpe =:= RawJSTypeAnnot.tpe).isDefined ||
- tpe.typeSymbol == UndefOrClass
-
- /** Test whether `sym` is the symbol of a raw JS function definition */
- private def isRawJSFunctionDef(sym: Symbol): Boolean =
- sym.isAnonymousClass && AllJSFunctionClasses.exists(sym isSubClass _)
-
- private def isStringType(tpe: Type): Boolean =
- tpe.typeSymbol == StringClass
-
- private def isLongType(tpe: Type): Boolean =
- tpe.typeSymbol == LongClass
-
- private lazy val BoxedBooleanClass = boxedClass(BooleanClass)
- private lazy val BoxedByteClass = boxedClass(ByteClass)
- private lazy val BoxedShortClass = boxedClass(ShortClass)
- private lazy val BoxedIntClass = boxedClass(IntClass)
- private lazy val BoxedLongClass = boxedClass(LongClass)
- private lazy val BoxedFloatClass = boxedClass(FloatClass)
- private lazy val BoxedDoubleClass = boxedClass(DoubleClass)
-
- private lazy val NumberClass = requiredClass[java.lang.Number]
-
- private lazy val HijackedNumberClasses =
- Seq(BoxedByteClass, BoxedShortClass, BoxedIntClass, BoxedLongClass,
- BoxedFloatClass, BoxedDoubleClass)
- private lazy val HijackedBoxedClasses =
- Seq(BoxedUnitClass, BoxedBooleanClass) ++ HijackedNumberClasses
-
- protected lazy val isHijackedBoxedClass: Set[Symbol] =
- HijackedBoxedClasses.toSet
-
- private lazy val InlineAnnotationClass = requiredClass[scala.inline]
-
- private def isMaybeJavaScriptException(tpe: Type) =
- JavaScriptExceptionClass isSubClass tpe.typeSymbol
-
- /** Get JS name of Symbol if it was specified with JSName annotation */
- def jsNameOf(sym: Symbol): String =
- sym.getAnnotation(JSNameAnnotation).flatMap(_.stringArg(0)).getOrElse(
- sym.unexpandedName.decoded)
-
- def isStaticModule(sym: Symbol): Boolean =
- sym.isModuleClass && !sym.isImplClass && !sym.isLifted
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/GenJSExports.scala b/compiler/src/main/scala/scala/scalajs/compiler/GenJSExports.scala
deleted file mode 100644
index 33ec979fb4..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/GenJSExports.scala
+++ /dev/null
@@ -1,590 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.compiler
-
-import scala.collection.mutable
-
-import scala.tools.nsc._
-import scala.math.PartialOrdering
-import scala.reflect.internal.Flags
-
-import scala.scalajs.ir.{Trees => js, Types => jstpe}
-
-/** Generation of exports for JavaScript
- *
- * @author Sébastien Doeraene
- */
-trait GenJSExports extends SubComponent { self: GenJSCode =>
- import global._
- import jsAddons._
- import definitions._
- import jsDefinitions._
-
- trait JSExportsPhase { this: JSCodePhase =>
-
- /**
- * Generate exporter methods for a class
- * @param classSym symbol of class we export for
- * @param decldExports symbols exporter methods that have been encountered in
- * the class' tree. This is not the same as classSym.info.delcs since
- * inherited concrete methods from traits should be in this param, too
- */
- def genMemberExports(
- classSym: Symbol,
- decldExports: List[Symbol]): List[js.Tree] = {
-
- val newlyDecldExports = decldExports.filterNot { isOverridingExport _ }
- val newlyDecldExportNames =
- newlyDecldExports.map(_.name.toTermName).toList.distinct
-
- newlyDecldExportNames map { genMemberExport(classSym, _) }
- }
-
- def genConstructorExports(classSym: Symbol): List[js.ConstructorExportDef] = {
- val constructors = classSym.tpe.member(nme.CONSTRUCTOR).alternatives
-
- // Generate exports from constructors and their annotations
- val ctorExports = for {
- ctor <- constructors
- (jsName, pos) <- jsInterop.exportsOf(ctor)
- } yield (jsName, ctor)
-
- val exports = for {
- (jsName, specs) <- ctorExports.groupBy(_._1) // group by exported name
- } yield {
- val ctors = specs.map(_._2)
- implicit val pos = ctors.head.pos
-
- val js.MethodDef(_, args, _, body) = genExportMethod(ctors, jsName)
- js.ConstructorExportDef(jsName, args, body)
- }
-
- exports.toList
- }
-
- def genModuleAccessorExports(classSym: Symbol): List[js.ModuleExportDef] = {
- for {
- (jsName, p) <- jsInterop.exportsOf(classSym)
- } yield {
- implicit val pos = p
- js.ModuleExportDef(jsName)
- }
- }
-
- private def genMemberExport(classSym: Symbol, name: TermName): js.Tree = {
- val alts = classSym.info.member(name).alternatives
-
- assert(!alts.isEmpty,
- s"Ended up with no alternatives for ${classSym.fullName}::$name. " +
- s"Original set was ${alts} with types ${alts.map(_.tpe)}")
-
- val (jsName, isProp) = jsInterop.jsExportInfo(name)
-
- if (isProp)
- genExportProperty(alts, jsName)
- else
- genExportMethod(alts, jsName)
-
- }
-
- private def genExportProperty(alts: List[Symbol], jsName: String) = {
- assert(!alts.isEmpty)
- implicit val pos = alts.head.pos
-
- // Separate getters and setters. Somehow isJSGetter doesn't work here. Hence
- // we just check the parameter list length.
- val (getter, setters) = alts.partition(_.tpe.params.isEmpty)
-
- // if we have more than one getter, something went horribly wrong
- assert(getter.size <= 1,
- s"Found more than one getter to export for name ${jsName}.")
-
- val getTree =
- if (getter.isEmpty) js.EmptyTree
- else genApplyForSym(getter.head)
-
- val setTree =
- if (setters.isEmpty) js.EmptyTree
- else genExportSameArgc(setters, 0) // we only have 1 argument
-
- js.PropertyDef(js.StringLiteral(jsName), getTree, genFormalArg(1), setTree)
- }
-
- /** generates the exporter function (i.e. exporter for non-properties) for
- * a given name */
- private def genExportMethod(alts: List[Symbol], jsName: String) = {
- implicit val pos = alts.head.pos
-
- assert(alts.nonEmpty,
- "need at least one alternative to generate exporter method")
-
- // Factor out methods with variable argument lists. Note that they can
- // only be at the end of the lists as enforced by PrepJSExports
- val (varArgMeths, normalMeths) = alts.partition(hasRepeatedParam _)
-
- // Highest non-repeated argument count
- val maxArgc = (
- // We have argc - 1, since a repeated parameter list may also be empty
- // (unlike a normal parameter)
- varArgMeths.map(_.tpe.params.size - 1) ++
- normalMeths.map(_.tpe.params.size)
- ).max
-
- val formalArgs = genFormalArgs(maxArgc)
-
- // Calculates possible arg counts for normal method
- def argCounts(sym: Symbol) = {
- val params = sym.tpe.params
- // Find default param
- val dParam = params.indexWhere { _.hasFlag(Flags.DEFAULTPARAM) }
- if (dParam == -1) Seq(params.size)
- else dParam to params.size
- }
-
- // Generate tuples (argc, method)
- val methodArgCounts = {
- // Normal methods
- for {
- method <- normalMeths
- argc <- argCounts(method)
- } yield (argc, method)
- } ++ {
- // Repeated parameter methods
- for {
- method <- varArgMeths
- argc <- method.tpe.params.size - 1 to maxArgc
- } yield (argc, method)
- }
-
- // Create a map: argCount -> methods (methods may appear multiple times)
- val methodByArgCount =
- methodArgCounts.groupBy(_._1).mapValues(_.map(_._2).toSet)
-
- // Create tuples: (methods, argCounts). This will be the cases we generate
- val caseDefinitions =
- methodByArgCount.groupBy(_._2).mapValues(_.keySet)
-
- // Verify stuff about caseDefinitions
- assert({
- val argcs = caseDefinitions.values.flatten.toList
- argcs == argcs.distinct &&
- argcs.forall(_ <= maxArgc)
- }, "every argc should appear only once and be lower than max")
-
- // Generate a case block for each (methods, argCounts) tuple
- val cases = for {
- (methods, argcs) <- caseDefinitions
- if methods.nonEmpty
-
- // exclude default case we're generating anyways for varargs
- if methods != varArgMeths.toSet
-
- // body of case to disambiguates methods with current count
- caseBody =
- genExportSameArgc(methods.toList, 0, Some(argcs.min))
-
- // argc in reverse order
- argcList = argcs.toList.sortBy(- _)
-
- // A case statement for each argc. Last one contains body
- caseStmt <- genMultiValCase(argcList, caseBody)
- } yield caseStmt
-
- val hasVarArg = varArgMeths.nonEmpty
-
- def defaultCase = {
- if (!hasVarArg)
- genThrowTypeError()
- else
- genExportSameArgc(varArgMeths, 0)
- }
-
- val body = {
- if (cases.isEmpty)
- defaultCase
- else if (cases.size == 1 && !hasVarArg)
- cases.head._2
- else {
- js.Switch(js.JSDotSelect(
- js.VarRef(js.Ident("arguments"), false)(jstpe.DynType), js.Ident("length")),
- cases.toList, defaultCase)
- }
- }
-
- js.MethodDef(js.StringLiteral(jsName), formalArgs, jstpe.DynType, body)
- }
-
- /**
- * Resolve method calls to [[alts]] while assuming they have the same
- * parameter count.
- * @param alts Alternative methods
- * @param paramIndex Index where to start disambiguation
- * @param maxArgc only use that many arguments
- */
- private def genExportSameArgc(alts: List[Symbol],
- paramIndex: Int, maxArgc: Option[Int] = None): js.Tree = {
-
- implicit val pos = alts.head.pos
-
- if (alts.size == 1)
- genApplyForSym(alts.head)
- else if (maxArgc.exists(_ <= paramIndex) ||
- !alts.exists(_.tpe.params.size > paramIndex)) {
- // We reach here in three cases:
- // 1. The parameter list has been exhausted
- // 2. The optional argument count restriction has triggered
- // 3. We only have (more than once) repeated parameters left
- // Therefore, we should fail
- currentUnit.error(pos,
- s"""Cannot disambiguate overloads for exported method ${alts.head.name} with types
- | ${alts.map(_.tpe).mkString("\n ")}""".stripMargin)
- js.Return(js.Undefined())
- } else {
-
- val altsByTypeTest = groupByWithoutHashCode(alts) { alt =>
- // get parameter type while resolving repeated params
- val tpe = enteringPhase(currentRun.uncurryPhase) {
- val ps = alt.paramss.flatten
- if (ps.size <= paramIndex || isRepeated(ps(paramIndex))) {
- assert(isRepeated(ps.last))
- repeatedToSingle(ps.last.tpe)
- } else {
- enteringPhase(currentRun.posterasurePhase) {
- ps(paramIndex).tpe
- }
- }
- }
-
- typeTestForTpe(tpe)
- }
-
- if (altsByTypeTest.size == 1) {
- // Testing this parameter is not doing any us good
- genExportSameArgc(alts, paramIndex+1, maxArgc)
- } else {
- // Sort them so that, e.g., isInstanceOf[String]
- // comes before isInstanceOf[Object]
- val sortedAltsByTypeTest = topoSortDistinctsBy(
- altsByTypeTest)(_._1)(RTTypeTest.Ordering)
-
- val defaultCase = genThrowTypeError()
-
- sortedAltsByTypeTest.foldRight[js.Tree](defaultCase) { (elem, elsep) =>
- val (typeTest, subAlts) = elem
- implicit val pos = subAlts.head.pos
-
- val param = genFormalArg(paramIndex+1)
- val genSubAlts = genExportSameArgc(subAlts, paramIndex+1, maxArgc)
-
- def hasDefaultParam = subAlts.exists { p =>
- val params = p.tpe.params
- params.size > paramIndex &&
- params(paramIndex).hasFlag(Flags.DEFAULTPARAM)
- }
-
- def orUndef(cond: js.Tree) = if (!hasDefaultParam) cond else {
- js.JSBinaryOp("||", cond, js.JSBinaryOp("===", param.ref, js.Undefined()))
- }
-
- typeTest match {
- case HelperTypeTest(helperName, _) =>
- js.If(orUndef(js.CallHelper(helperName, param.ref)(jstpe.BooleanType)),
- genSubAlts, elsep)(jstpe.UndefType)
-
- case TypeOfTypeTest(typeString) =>
- js.If(orUndef {
- js.JSBinaryOp("===", js.JSUnaryOp("typeof", param.ref),
- js.StringLiteral(typeString))
- }, genSubAlts, elsep)(jstpe.UndefType)
-
- case InstanceOfTypeTest(tpe) =>
- js.If(orUndef(genIsInstanceOf(tpe, param.ref)),
- genSubAlts, elsep)(jstpe.UndefType)
-
- case NoTypeTest =>
- genSubAlts // note: elsep is discarded, obviously
- }
- }
- }
- }
- }
-
- /**
- * Generate a call to the method [[sym]] while using the formalArguments
- * and potentially the argument array. Also inserts default parameters if
- * required.
- */
- private def genApplyForSym(sym: Symbol): js.Tree = {
- implicit val pos = sym.pos
-
- // the (single) type of the repeated parameter if any
- val repeatedTpe = enteringPhase(currentRun.uncurryPhase) {
- for {
- param <- sym.paramss.flatten.lastOption
- if isRepeated(param)
- } yield repeatedToSingle(param.tpe)
- }
-
- val normalArgc = sym.tpe.params.size -
- (if (repeatedTpe.isDefined) 1 else 0)
-
- // optional repeated parameter list
- val jsVarArg = repeatedTpe map { tpe =>
- // Construct a new JSArraySeq
- genNew(JSArraySeqClass, JSArraySeq_ctor,
- List(js.VarRef(js.Ident("arguments"), false)(jstpe.DynType),
- js.IntLiteral(normalArgc)))
- }
-
- // normal arguments
- val jsArgs = genFormalArgs(normalArgc)
-
- // Generate JS code to prepare arguments (default getters and unboxes)
- val funTpe = enteringPhase(currentRun.posterasurePhase)(sym.tpe)
- val jsArgPrep = for {
- (jsArg, (param, i)) <- jsArgs zip funTpe.params.zipWithIndex
- } yield {
-
- // Code to verify the type of the argument (if it is defined)
- val jsVerifyArg = {
- val tpePosterasure =
- enteringPhase(currentRun.posterasurePhase)(param.tpe)
- val argRef = jsArg.ref
- val unboxed = ensureUnboxed(argRef, tpePosterasure)
-
- val verifiedArg = {
- if (isPrimitiveValueType(param.tpe))
- // Ensure we don't convert null to a primitive value type
- js.If(js.BinaryOp("===", argRef, js.Null(), jstpe.BooleanType),
- genThrowTypeError(s"Found null, expected ${param.tpe}"),
- unboxed)(unboxed.tpe)
- else if (argRef ne unboxed)
- // This is the value class case
- unboxed
- else
- genAsInstanceOf(param.tpe, argRef)
- }
-
- js.Assign(argRef, verifiedArg)
- }
-
- // If argument is undefined and there is a default getter, call it
- if (param.hasFlag(Flags.DEFAULTPARAM)) {
- js.If(js.BinaryOp("===", jsArg.ref, js.Undefined(), jstpe.BooleanType), {
- val trgSym = {
- if (sym.isClassConstructor) sym.owner.companionModule.moduleClass
- else sym.owner
- }
- val defaultGetter = trgSym.tpe.member(
- nme.defaultGetterName(sym.name, i+1))
-
- assert(defaultGetter.exists,
- s"need default getter for method ${sym.fullName}")
- assert(!defaultGetter.isOverloaded)
-
- val trgTree = {
- if (sym.isClassConstructor) genLoadModule(trgSym)
- else js.This()(encodeClassType(trgSym))
- }
-
- // Pass previous arguments to defaultGetter
- js.Assign(jsArg.ref, genApplyMethod(trgTree, trgSym, defaultGetter,
- jsArgs.take(defaultGetter.tpe.params.size).map(_.ref)))
- }, {
- // Otherwise, unbox the argument
- jsVerifyArg
- })(jstpe.UndefType)
- } else {
- // Otherwise, it is always the unboxed argument
- jsVerifyArg
- }
- }
-
- val jsReturn = js.Return {
- val call = genApplyMethod(js.This()(encodeClassType(sym.owner)),
- sym.owner, sym, jsArgs.map(_.ref) ++ jsVarArg)
- ensureBoxed(call,
- enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType))
- }
-
- js.Block(jsArgPrep :+ jsReturn)
- }
-
- }
-
- private def isOverridingExport(sym: Symbol): Boolean = {
- lazy val osym = sym.nextOverriddenSymbol
- sym.isOverridingSymbol && !osym.owner.isInterface
- }
-
- private sealed abstract class RTTypeTest
-
- private final case class HelperTypeTest(helperName: String,
- rank: Int) extends RTTypeTest
-
- private final case class TypeOfTypeTest(typeString: String) extends RTTypeTest
-
- private final case class InstanceOfTypeTest(tpe: Type) extends RTTypeTest {
- override def equals(that: Any): Boolean = {
- that match {
- case InstanceOfTypeTest(thatTpe) => tpe =:= thatTpe
- case _ => false
- }
- }
- }
-
- private case object NoTypeTest extends RTTypeTest
-
- private object RTTypeTest {
- implicit object Ordering extends PartialOrdering[RTTypeTest] {
- override def tryCompare(lhs: RTTypeTest, rhs: RTTypeTest): Option[Int] = {
- if (lteq(lhs, rhs)) if (lteq(rhs, lhs)) Some(0) else Some(-1)
- else if (lteq(rhs, lhs)) Some(1) else None
- }
-
- override def lteq(lhs: RTTypeTest, rhs: RTTypeTest): Boolean = {
- (lhs, rhs) match {
- // NoTypeTest is always last
- case (_, NoTypeTest) => true
- case (NoTypeTest, _) => false
-
- // undefined test is always first
- case (TypeOfTypeTest("undefined"), _) => true
- case (_, TypeOfTypeTest("undefined")) => false
-
- case (HelperTypeTest(_, rank1), HelperTypeTest(_, rank2)) =>
- rank1 <= rank2
- case (_:HelperTypeTest, _) => true
- case (_, _:HelperTypeTest) => false
-
- case (TypeOfTypeTest(s1), TypeOfTypeTest(s2)) =>
- s1 <= s2
-
- case (InstanceOfTypeTest(t1), InstanceOfTypeTest(t2)) =>
- t1 <:< t2
-
- case (_:TypeOfTypeTest, _:InstanceOfTypeTest) => true
- case (_:InstanceOfTypeTest, _:TypeOfTypeTest) => false
- }
- }
-
- override def equiv(lhs: RTTypeTest, rhs: RTTypeTest): Boolean = {
- lhs == rhs
- }
- }
- }
-
- // Very simple O(n²) topological sort for elements assumed to be distinct
- private def topoSortDistinctsBy[A <: AnyRef, B](coll: List[A])(f: A => B)(
- implicit ord: PartialOrdering[B]): List[A] = {
-
- @scala.annotation.tailrec
- def loop(coll: List[A], acc: List[A]): List[A] = {
- if (coll.isEmpty) acc
- else if (coll.tail.isEmpty) coll.head :: acc
- else {
- val (lhs, rhs) = coll.span(x => !coll.forall(
- y => (x eq y) || !ord.lteq(f(x), f(y))))
- assert(!rhs.isEmpty, s"cycle while ordering $coll")
- loop(lhs ::: rhs.tail, rhs.head :: acc)
- }
- }
-
- loop(coll, Nil)
- }
-
- private def typeTestForTpe(tpe: Type): RTTypeTest = {
- tpe match {
- case tpe: ErasedValueType =>
- InstanceOfTypeTest(tpe.valueClazz.typeConstructor)
-
- case _ =>
- (toTypeKind(tpe): @unchecked) match {
- case UndefinedKind => TypeOfTypeTest("undefined")
- case BooleanKind => TypeOfTypeTest("boolean")
- case CharKind => InstanceOfTypeTest(boxedClass(CharClass).tpe)
- case ByteKind => HelperTypeTest("isByte", 0)
- case ShortKind => HelperTypeTest("isShort", 1)
- case IntKind => HelperTypeTest("isInt", 2)
- case LongKind => InstanceOfTypeTest(RuntimeLongClass.tpe)
- case _:FLOAT => TypeOfTypeTest("number")
-
- case REFERENCE(cls) =>
- if (cls == StringClass) TypeOfTypeTest("string")
- else if (isRawJSType(tpe)) {
- cls match {
- case JSNumberClass => TypeOfTypeTest("number")
- case JSBooleanClass => TypeOfTypeTest("boolean")
- case JSStringClass => TypeOfTypeTest("string")
- case JSUndefinedClass => TypeOfTypeTest("undefined")
- case _ => NoTypeTest
- }
- } else InstanceOfTypeTest(tpe)
-
- case ARRAY(_) => InstanceOfTypeTest(tpe)
- }
- }
- }
-
- // Group-by that does not rely on hashCode(), only equals() - O(n²)
- private def groupByWithoutHashCode[A, B](
- coll: List[A])(f: A => B): List[(B, List[A])] = {
-
- import scala.collection.mutable.ArrayBuffer
- val m = new ArrayBuffer[(B, List[A])]
- m.sizeHint(coll.length)
-
- for (elem <- coll) {
- val key = f(elem)
- val index = m.indexWhere(_._1 == key)
- if (index < 0) m += ((key, List(elem)))
- else m(index) = (key, elem :: m(index)._2)
- }
-
- m.toList
- }
-
- private def genThrowTypeError(msg: String = "No matching overload")(
- implicit pos: Position): js.Tree = {
- js.Throw(js.StringLiteral(msg))
- }
-
- private def genFormalArgs(count: Int)(implicit pos: Position): List[js.ParamDef] =
- (1 to count map genFormalArg).toList
-
- private def genFormalArg(index: Int)(implicit pos: Position): js.ParamDef =
- js.ParamDef(js.Ident("arg$" + index), jstpe.AnyType)
-
-
- /**
- * Generate a JS tree like:
- *
- * case x1:
- * case x2:
- * case x3:
- *
- *
- * @param ris literals on cases in reverse order
- * @param body the body to put in the last statement
- */
- private def genMultiValCase(ris: Seq[Int], body: => js.Tree)(
- implicit pos: Position): List[(js.Tree,js.Tree)] = {
-
- if (ris.isEmpty) Nil
- else {
- val bodyCase = (js.IntLiteral(ris.head), body)
- val emptyCases = ris.tail.map(i => (js.IntLiteral(i), js.Skip()))
-
- (emptyCases.reverse :+ bodyCase).toList
- }
- }
-
- private def hasRepeatedParam(sym: Symbol) =
- enteringPhase(currentRun.uncurryPhase) {
- sym.paramss.flatten.lastOption.exists(isRepeated _)
- }
-
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/GenJSFiles.scala b/compiler/src/main/scala/scala/scalajs/compiler/GenJSFiles.scala
deleted file mode 100644
index 80cf2ffa79..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/GenJSFiles.scala
+++ /dev/null
@@ -1,72 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.compiler
-
-import scala.tools.nsc._
-import scala.tools.nsc.io.AbstractFile
-import scala.reflect.internal.pickling.PickleBuffer
-
-import java.io._
-
-import scala.scalajs.ir
-import ir.{Trees => js, Printers, SourceMapWriter}
-import ir.Infos._
-
-/** Send JS ASTs to files
- *
- * @author Sébastien Doeraene
- */
-trait GenJSFiles extends SubComponent { self: GenJSCode =>
- import global._
- import jsAddons._
-
- def genIRFile(cunit: CompilationUnit, sym: Symbol, tree: js.Tree,
- classInfo: ClassInfo): Unit = {
- val outfile = getFileFor(cunit, sym, ".sjsir")
- val output = outfile.bufferedOutput
- try {
- ir.InfoSerializers.serialize(output, classInfo)
- ir.Serializers.serialize(output, tree)
- } finally {
- output.close()
- }
- }
-
- def genIRFileText(cunit: CompilationUnit, sym: Symbol, tree: js.Tree,
- classInfo: ClassInfo): Unit = {
- val outfile = getFileFor(cunit, sym, ".ir.js")
- val output = bufferedWriter(outfile)
- try {
- val printer = new Printers.IRTreePrinter(output)
- printer.printTopLevelTree(tree)
- printer.close()
-
- val infoPrinter = new Printers.InfoPrinter(output)
- infoPrinter.printClassInfo(classInfo)
- infoPrinter.close()
- } finally {
- output.close()
- }
- }
-
- private def bufferedWriter(file: AbstractFile): Writer =
- new OutputStreamWriter(file.bufferedOutput, "UTF-8")
-
- private def getFileFor(cunit: CompilationUnit, sym: Symbol,
- suffix: String) = {
- val baseDir: AbstractFile =
- settings.outputDirs.outputDirFor(cunit.source.file)
-
- val pathParts = sym.fullName.split("[./]")
- val dir = (baseDir /: pathParts.init)(_.subdirectoryNamed(_))
-
- var filename = pathParts.last
- if (sym.isModuleClass && !sym.isImplClass)
- filename = filename + nme.MODULE_SUFFIX_STRING
-
- dir fileNamed (filename + suffix)
- }
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/JSConversions.scala b/compiler/src/main/scala/scala/scalajs/compiler/JSConversions.scala
deleted file mode 100644
index f8cc348617..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/JSConversions.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-package scala.scalajs.compiler
-
-object JSConversions {
- // Boldly copied from library/ scala.scalajs.runtime.Long
-
- /** number of relevant bits in each Long.l and Long.m */
- private val BITS: Int = 22
- /** number of relevant bits in Long.l and Long.m together */
- private val BITS01: Int = 2 * BITS
- /** number of relevant bits in Long.h */
- private val BITS2: Int = 64 - BITS01
- /** bitmask for Long.l and Long.m */
- private val MASK: Int = (1 << BITS) - 1
- /** bitmask for Long.h */
- private val MASK_2: Int = (1 << BITS2) - 1
-
- private[scalajs] def scalaLongToTriplet(value: scala.Long) =
- (value.toInt & MASK, (value >> BITS).toInt & MASK, (value >> BITS01).toInt & MASK_2)
-}
\ No newline at end of file
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/JSDefinitions.scala b/compiler/src/main/scala/scala/scalajs/compiler/JSDefinitions.scala
deleted file mode 100644
index ef15785c76..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/JSDefinitions.scala
+++ /dev/null
@@ -1,136 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.compiler
-
-import scala.tools.nsc._
-
-/** Core definitions for Scala.js
- *
- * @author Sébastien Doeraene
- */
-trait JSDefinitions { self: JSGlobalAddons =>
- import global._
-
- object jsDefinitions extends JSDefinitionsClass
-
- import definitions._
- import rootMirror._
-
- class JSDefinitionsClass {
-
- lazy val ScalaJSJSPackage = getPackage(newTermNameCached("scala.scalajs.js")) // compat 2.10/2.11
- lazy val JSPackage_undefined = getMemberMethod(ScalaJSJSPackage, newTermName("undefined"))
- lazy val JSPackage_isUndefined = getMemberMethod(ScalaJSJSPackage, newTermName("isUndefined"))
- lazy val JSPackage_typeOf = getMemberMethod(ScalaJSJSPackage, newTermName("typeOf"))
- lazy val JSPackage_debugger = getMemberMethod(ScalaJSJSPackage, newTermName("debugger"))
-
- lazy val ScalaJSJSPrimPackage = getPackage(newTermNameCached("scala.scalajs.js.prim")) // compat 2.10/2.11
-
- lazy val JSAnyClass = getRequiredClass("scala.scalajs.js.Any")
- lazy val JSDynamicClass = getRequiredClass("scala.scalajs.js.Dynamic")
- lazy val JSDynamic_selectDynamic = getMemberMethod(JSDynamicClass, newTermName("selectDynamic"))
- lazy val JSDynamic_updateDynamic = getMemberMethod(JSDynamicClass, newTermName("updateDynamic"))
- lazy val JSDynamic_applyDynamic = getMemberMethod(JSDynamicClass, newTermName("applyDynamic"))
- lazy val JSDictionaryClass = getRequiredClass("scala.scalajs.js.Dictionary")
- lazy val JSDictionary_delete = getMemberMethod(JSDictionaryClass, newTermName("delete"))
- lazy val JSNumberClass = getRequiredClass("scala.scalajs.js.prim.Number")
- lazy val JSBooleanClass = getRequiredClass("scala.scalajs.js.prim.Boolean")
- lazy val JSStringClass = getRequiredClass("scala.scalajs.js.prim.String")
- lazy val JSUndefinedClass = getRequiredClass("scala.scalajs.js.prim.Undefined")
- lazy val JSObjectClass = getRequiredClass("scala.scalajs.js.Object")
- lazy val JSThisFunctionClass = getRequiredClass("scala.scalajs.js.ThisFunction")
-
- lazy val JSGlobalScopeClass = getRequiredClass("scala.scalajs.js.GlobalScope")
-
- lazy val UndefOrClass = getRequiredClass("scala.scalajs.js.UndefOr")
-
- lazy val JSArrayClass = getRequiredClass("scala.scalajs.js.Array")
- lazy val JSArray_apply = getMemberMethod(JSArrayClass, newTermName("apply"))
- lazy val JSArray_update = getMemberMethod(JSArrayClass, newTermName("update"))
-
- lazy val JSFunctionClasses = (0 to 22) map (n => getRequiredClass("scala.scalajs.js.Function"+n))
- lazy val JSThisFunctionClasses = (0 to 21) map (n => getRequiredClass("scala.scalajs.js.ThisFunction"+n))
- lazy val AllJSFunctionClasses = JSFunctionClasses ++ JSThisFunctionClasses
-
- lazy val RuntimeExceptionClass = requiredClass[RuntimeException]
- lazy val JavaScriptExceptionClass = getClassIfDefined("scala.scalajs.js.JavaScriptException")
-
- lazy val JSNameAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSName")
- lazy val JSBracketAccessAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSBracketAccess")
- lazy val JSExportAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExport")
- lazy val JSExportDescendentObjectsAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSExportDescendentObjects")
-
- lazy val JSAnyTpe = JSAnyClass.toTypeConstructor
- lazy val JSDynamicTpe = JSDynamicClass.toTypeConstructor
- lazy val JSNumberTpe = JSNumberClass.toTypeConstructor
- lazy val JSBooleanTpe = JSBooleanClass.toTypeConstructor
- lazy val JSStringTpe = JSStringClass.toTypeConstructor
- lazy val JSUndefinedTpe = JSUndefinedClass.toTypeConstructor
- lazy val JSObjectTpe = JSObjectClass.toTypeConstructor
-
- lazy val JSGlobalScopeTpe = JSGlobalScopeClass.toTypeConstructor
-
- lazy val JSFunctionTpes = JSFunctionClasses.map(_.toTypeConstructor)
-
- lazy val JSAnyModule = JSAnyClass.companionModule
- lazy val JSAny_fromUnit = getMemberMethod(JSAnyModule, newTermName("fromUnit"))
- lazy val JSAny_fromBoolean = getMemberMethod(JSAnyModule, newTermName("fromBoolean"))
- lazy val JSAny_fromByte = getMemberMethod(JSAnyModule, newTermName("fromByte"))
- lazy val JSAny_fromShort = getMemberMethod(JSAnyModule, newTermName("fromShort"))
- lazy val JSAny_fromInt = getMemberMethod(JSAnyModule, newTermName("fromInt"))
- lazy val JSAny_fromFloat = getMemberMethod(JSAnyModule, newTermName("fromFloat"))
- lazy val JSAny_fromDouble = getMemberMethod(JSAnyModule, newTermName("fromDouble"))
- lazy val JSAny_fromString = getMemberMethod(JSAnyModule, newTermName("fromString"))
-
- lazy val JSAny_fromTraversableOnce = getMemberMethod(JSAnyModule, newTermName("fromTraversableOnce"))
-
- def JSAny_fromFunction(arity: Int) = getMemberMethod(JSAnyModule, newTermName("fromFunction"+arity))
-
- lazy val JSDynamicModule = JSDynamicClass.companionModule
- lazy val JSDynamic_global = getMemberMethod(JSDynamicModule, newTermName("global"))
- lazy val JSDynamic_newInstance = getMemberMethod(JSDynamicModule, newTermName("newInstance"))
- lazy val JSDynamicLiteral = getMemberModule(JSDynamicModule, newTermName("literal"))
- lazy val JSDynamicLiteral_applyDynamicNamed = getMemberMethod(JSDynamicLiteral, newTermName("applyDynamicNamed"))
- lazy val JSDynamicLiteral_applyDynamic = getMemberMethod(JSDynamicLiteral, newTermName("applyDynamic"))
-
- lazy val JSNumberModule = JSNumberClass.companionModule
- lazy val JSNumber_toDouble = getMemberMethod(JSNumberModule, newTermName("toDouble"))
-
- lazy val JSBooleanModule = JSBooleanClass.companionModule
- lazy val JSBoolean_toBoolean = getMemberMethod(JSBooleanModule, newTermName("toBoolean"))
-
- lazy val JSStringModule = JSStringClass.companionModule
- lazy val JSString_toScalaString = getMemberMethod(JSStringModule, newTermName("toScalaString"))
-
- lazy val JSObjectModule = JSObjectClass.companionModule
- lazy val JSObject_hasProperty = getMemberMethod(JSObjectModule, newTermName("hasProperty"))
- lazy val JSObject_properties = getMemberMethod(JSObjectModule, newTermName("properties"))
-
- lazy val JSArrayModule = JSArrayClass.companionModule
- lazy val JSArray_create = getMemberMethod(JSArrayModule, newTermName("apply"))
-
- lazy val JSThisFunctionModule = JSThisFunctionClass.companionModule
- def JSThisFunction_fromFunction(arity: Int) = getMemberMethod(JSThisFunctionModule, newTermName("fromFunction"+arity))
-
- lazy val RawJSTypeAnnot = getClassIfDefined("scala.scalajs.js.annotation.RawJSType")
-
- lazy val RuntimeLongClass = getRequiredClass("scala.scalajs.runtime.RuntimeLong")
- lazy val RuntimeLongModule = RuntimeLongClass.companionModule
- lazy val RuntimeLong_from = getMemberMethod(RuntimeLongModule, newTermName("fromRuntimeLong"))
- lazy val RuntimeLong_to = getMemberMethod(RuntimeLongModule, newTermName("toRuntimeLong"))
-
- lazy val RuntimeStringClass = getRequiredClass("scala.scalajs.runtime.RuntimeString")
- lazy val RuntimeStringModule = RuntimeStringClass.companionModule
-
- lazy val BooleanReflectiveCallClass = getRequiredClass("scala.scalajs.runtime.BooleanReflectiveCall")
- lazy val NumberReflectiveCallClass = getRequiredClass("scala.scalajs.runtime.NumberReflectiveCall")
- lazy val IntegerReflectiveCallClass = getRequiredClass("scala.scalajs.runtime.IntegerReflectiveCall")
-
- lazy val JSArraySeqClass = getRequiredClass("scala.scalajs.runtime.JSArraySeq")
- lazy val JSArraySeq_ctor = JSArraySeqClass.primaryConstructor
-
- }
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala b/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala
deleted file mode 100644
index cab692fcfa..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala
+++ /dev/null
@@ -1,235 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.compiler
-
-import scala.tools.nsc._
-
-import scala.scalajs.ir
-import ir.{Trees => js, Types => jstpe}
-
-/** Encoding of symbol names for JavaScript
- *
- * Some issues that this encoding solves:
- * * Overloading: encode the full signature in the JS name
- * * Same scope for fields and methods of a class
- * * Global access to classes and modules (by their full name)
- *
- * @author Sébastien Doeraene
- */
-trait JSEncoding extends SubComponent { self: GenJSCode =>
- import global._
- import jsAddons._
-
- /** Outer separator string (between parameter types) */
- final val OuterSep = "__"
-
- /** Inner separator character (replace dots in full names) */
- final val InnerSep = "_"
-
- /** Name given to the local Scala.js environment variable */
- final val ScalaJSEnvironmentName = "ScalaJS"
-
- /** Name given to all exported stuff of a class for DCE */
- final val dceExportName = ""
-
- def encodeLabelSym(sym: Symbol, freshName: Symbol => String)(
- implicit pos: Position): js.Ident = {
- require(sym.isLabel, "encodeLabelSym called with non-label symbol: " + sym)
- js.Ident(freshName(sym), Some(sym.unexpandedName.decoded))
- }
-
- private lazy val allRefClasses: Set[Symbol] = {
- import definitions._
- (Set(ObjectRefClass, VolatileObjectRefClass) ++
- refClass.values ++ volatileRefClass.values)
- }
-
- def encodeFieldSym(sym: Symbol)(implicit pos: Position): js.Ident = {
- require(sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule,
- "encodeFieldSym called with non-field symbol: " + sym)
-
- val name0 = encodeMemberNameInternal(sym)
- val name =
- if (name0.charAt(name0.length()-1) != ' ') name0
- else name0.substring(0, name0.length()-1)
-
- /* We have to special-case fields of Ref types (IntRef, ObjectRef, etc.)
- * because they are emitted as private by our .scala source files, but
- * they are considered public at use site since their symbols come from
- * Java-emitted .class files.
- */
- val idSuffix =
- if (sym.isPrivate || allRefClasses.contains(sym.owner))
- sym.owner.ancestors.count(!_.isInterface).toString
- else
- "f"
-
- val encodedName = name + "$" + idSuffix
- js.Ident(mangleJSName(encodedName), Some(sym.unexpandedName.decoded))
- }
-
- def encodeMethodSym(sym: Symbol, reflProxy: Boolean = false)
- (implicit pos: Position): js.Ident = {
- val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy)
- js.Ident(encodedName + paramsString,
- Some(sym.unexpandedName.decoded + paramsString))
- }
-
- def encodeMethodName(sym: Symbol, reflProxy: Boolean = false): String = {
- val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy)
- encodedName + paramsString
- }
-
- private def encodeMethodNameInternal(sym: Symbol,
- reflProxy: Boolean = false): (String, String) = {
- require(sym.isMethod, "encodeMethodSym called with non-method symbol: " + sym)
-
- def name = encodeMemberNameInternal(sym)
-
- val encodedName = {
- if (sym.isClassConstructor)
- "init" + InnerSep
- else if (foreignIsImplClass(sym.owner))
- encodeClassFullName(sym.owner) + OuterSep + name
- else if (sym.isPrivate)
- mangleJSName(name) + OuterSep + "p" +
- sym.owner.ancestors.count(!_.isInterface).toString
- else
- mangleJSName(name)
- }
-
- val paramsString = makeParamsString(sym, reflProxy)
-
- (encodedName, paramsString)
- }
-
- /** encode a method symbol on a trait so it refers to its corresponding
- * symbol in the implementation class. This works around a limitation in
- * Scalac 2.11 that doesn't return members for the implementation class.
- */
- def encodeImplClassMethodSym(sym: Symbol)(
- implicit pos: Position): (Symbol, js.Ident) = {
- require(sym.owner.isInterface)
- // Unfortunately we cannot verify here, whether this symbol is actually
- // implemented in the trait.
-
- // Prepare special parameter string
- val tpe = sym.tpe
- val paramTpes = sym.owner.tpe +: tpe.params.map(_.tpe) :+ tpe.resultType
- val paramsString = makeParamsString(paramTpes.map(internalName _))
-
- // Encode prefix
- val implClass = sym.owner.implClass orElse erasure.implClass(sym.owner)
- val encodedName =
- encodeClassFullName(implClass) + OuterSep + encodeMemberNameInternal(sym)
-
- // Create ident
- (implClass, js.Ident(encodedName + paramsString,
- Some(sym.unexpandedName.decoded + paramsString)))
- }
-
- def encodeStaticMemberSym(sym: Symbol)(implicit pos: Position): js.Ident = {
- require(sym.isStaticMember,
- "encodeStaticMemberSym called with non-static symbol: " + sym)
- js.Ident(
- mangleJSName(encodeMemberNameInternal(sym)) +
- makeParamsString(List(internalName(sym.tpe))),
- Some(sym.unexpandedName.decoded))
- }
-
- def encodeLocalSym(sym: Symbol, freshName: Symbol => String)(
- implicit pos: Position): js.Ident = {
- require(!sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule,
- "encodeLocalSym called with non-local symbol: " + sym)
- js.Ident(mangleJSName(freshName(sym)), Some(sym.unexpandedName.decoded))
- }
-
- def foreignIsImplClass(sym: Symbol): Boolean =
- sym.isModuleClass && nme.isImplClassName(sym.name)
-
- def encodeReferenceType(tpe: Type)(implicit pos: Position): (jstpe.ReferenceType, Symbol) = {
- toTypeKind(tpe) match {
- case kind: ARRAY =>
- val sym = mapRuntimeClass(kind.elementKind.toType.typeSymbol)
- (jstpe.ArrayType(encodeClassFullName(sym), kind.dimensions), sym)
-
- case kind =>
- val sym = mapRuntimeClass(kind.toType.typeSymbol)
- (jstpe.ClassType(encodeClassFullName(sym)), sym)
- }
- }
-
- def encodeArrayType(tpe: Type)(implicit pos: Position): (jstpe.ArrayType, Symbol) = {
- (encodeReferenceType(tpe): @unchecked) match {
- case (arrayType: jstpe.ArrayType, sym) => (arrayType, sym)
- }
- }
-
- def encodeClassType(sym: Symbol): jstpe.Type = {
- if (isRawJSType(sym.toTypeConstructor)) jstpe.DynType
- else {
- assert(sym != definitions.ArrayClass,
- "encodeClassType() cannot be called with ArrayClass")
- jstpe.ClassType(encodeClassFullName(sym))
- }
- }
-
- def encodeClassFullNameIdent(sym: Symbol)(implicit pos: Position): js.Ident = {
- js.Ident(encodeClassFullName(sym), Some(sym.fullName))
- }
-
- def encodeModuleFullNameIdent(sym: Symbol)(implicit pos: Position): js.Ident = {
- js.Ident(encodeModuleFullName(sym), Some(sym.fullName))
- }
-
- def encodeClassFullName(sym: Symbol): String = {
- ir.Definitions.encodeClassName(
- sym.fullName + (if (needsModuleClassSuffix(sym)) "$" else ""))
- }
-
- def needsModuleClassSuffix(sym: Symbol): Boolean =
- sym.isModuleClass && !foreignIsImplClass(sym)
-
- def encodeModuleFullName(sym: Symbol): String =
- ir.Definitions.encodeClassName(sym.fullName + "$").dropRight(1)
-
- private def encodeMemberNameInternal(sym: Symbol): String =
- sym.name.toString.replace("_", "$und")
-
- // Encoding of method signatures
-
- private def makeParamsString(sym: Symbol, reflProxy: Boolean): String = {
- val tpe = sym.tpe
- val paramTypeNames = tpe.params map (p => internalName(p.tpe))
- makeParamsString(
- if (sym.isClassConstructor)
- paramTypeNames
- else if (reflProxy)
- paramTypeNames :+ ""
- else
- paramTypeNames :+ internalName(tpe.resultType))
- }
-
- private def makeParamsString(paramAndResultTypeNames: List[String]) =
- paramAndResultTypeNames.mkString(OuterSep, OuterSep, "")
-
- /** Computes the internal name for a type. */
- private def internalName(tpe: Type): String = internalName(toTypeKind(tpe))
-
- private def internalName(kind: TypeKind): String = kind match {
- case kind: ValueTypeKind => kind.primitiveCharCode
- case REFERENCE(cls) => encodeClassFullName(cls)
- case ARRAY(elem) => "A"+internalName(elem)
- }
-
- /** mangles names that are illegal in JavaScript by prepending a $
- * also mangles names that would collide with these mangled names
- */
- private def mangleJSName(name: String) =
- if (js.isKeyword(name) || name(0).isDigit || name(0) == '$')
- "$" + name
- else name
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala b/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala
deleted file mode 100644
index d4d8b26c1b..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/JSGlobalAddons.scala
+++ /dev/null
@@ -1,163 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.compiler
-
-import scala.tools.nsc._
-
-/** Additions to Global meaningful for the JavaScript backend
- *
- * @author Sébastien Doeraene
- */
-trait JSGlobalAddons extends JSDefinitions
- with Compat210Component {
- val global: Global
-
- import global._
- import jsDefinitions._
- import definitions._
-
- /** JavaScript primitives, used in jscode */
- object jsPrimitives extends JSPrimitives {
- val global: JSGlobalAddons.this.global.type = JSGlobalAddons.this.global
- val jsAddons: ThisJSGlobalAddons =
- JSGlobalAddons.this.asInstanceOf[ThisJSGlobalAddons]
- }
-
- /** global javascript interop related helpers */
- object jsInterop {
- import scala.reflect.NameTransformer
- import scala.reflect.internal.Flags
-
- private val exportPrefix = "$js$exported$"
- private val methodExportPrefix = exportPrefix + "meth$"
- private val propExportPrefix = exportPrefix + "prop$"
-
- /** retrieves the names a sym should be exported to from its annotations
- *
- * Note that for accessor symbols, the annotations of the accessed symbol
- * are used, rather than the annotations of the accessor itself.
- */
- def exportsOf(sym: Symbol): List[(String, Position)] = {
- val trgSym = {
- // For accessors, look on the val/var def
- if (sym.isAccessor) sym.accessed
- // For primary class constructors, look on the class itself
- else if (sym.isPrimaryConstructor && !sym.owner.isModuleClass) sym.owner
- else sym
- }
-
- val directExports = for {
- annot <- trgSym.annotations
- if annot.symbol == JSExportAnnotation
- } yield {
- // Symbol we use to get name from (constructors take name of class)
- val nmeSym = if (sym.isConstructor) sym.owner else sym
- // The actual name of the symbol
- val symNme = nmeSym.unexpandedName.decoded
-
- // Enforce that methods ending with _= are exported as setters
- if (symNme.endsWith("_=") && !isJSSetter(sym)) {
- currentUnit.error(annot.pos, "A method ending in _= will be " +
- s"exported as setter. But $symNme does not have the right " +
- "signature to do so (single argument, unit return type).")
- }
-
- val name = annot.stringArg(0).getOrElse(symNme.stripSuffix("_="))
-
- // Enforce no __ in name
- if (name.contains("__")) {
- // Get position for error message
- val pos = if (annot.stringArg(0).isDefined)
- annot.args.head.pos
- else trgSym.pos
-
- currentUnit.error(pos,
- "An exported name may not contain a double underscore (`__`)")
- }
-
- (name, annot.pos)
- }
-
- val inheritedExports = if (sym.isModuleClass) {
- val forcingSym = sym.ancestors.find(_.annotations.exists(
- _.symbol == JSExportDescendentObjectsAnnotation))
-
- forcingSym map { fs =>
- val name = sym.fullName
-
- // Enfore no __ in name
- if (name.contains("__")) {
- // Get all annotation positions for error message
- currentUnit.error(sym.pos,
- s"""${sym.name} may not have a double underscore (`__`) in its fully qualified
- |name, since it is forced to be exported by a @JSExportDescendentObjects on ${fs}""".stripMargin)
- }
-
- List((sym.fullName, sym.pos))
-
- } getOrElse Nil
- } else Nil
-
- directExports ::: inheritedExports
- }
-
- /** creates a name for an export specification */
- def scalaExportName(jsName: String, isProp: Boolean): Name = {
- val pref = if (isProp) propExportPrefix else methodExportPrefix
- val encname = NameTransformer.encode(jsName)
- newTermName(pref + encname)
- }
-
- /** checks if the given symbol is a JSExport */
- def isExport(sym: Symbol): Boolean =
- sym.unexpandedName.startsWith(exportPrefix) &&
- !sym.hasFlag(Flags.DEFAULTPARAM)
-
- /** retrieves the originally assigned jsName of this export and whether it
- * is a property
- */
- def jsExportInfo(name: Name): (String, Boolean) = {
- def dropPrefix(prefix: String) ={
- if (name.startsWith(prefix)) {
- // We can't decode right away due to $ separators
- val enc = name.encoded.substring(prefix.length)
- Some(NameTransformer.decode(enc))
- } else None
- }
-
- dropPrefix(methodExportPrefix).map((_,false)) orElse
- dropPrefix(propExportPrefix).map((_,true)) getOrElse
- sys.error("non-exported name passed to jsInfoSpec")
- }
-
- def isJSProperty(sym: Symbol): Boolean = isJSGetter(sym) || isJSSetter(sym)
-
- /** has this symbol to be translated into a JS getter (both directions)? */
- def isJSGetter(sym: Symbol): Boolean = {
- sym.tpe.params.isEmpty && enteringPhase(currentRun.uncurryPhase) {
- sym.tpe.isInstanceOf[NullaryMethodType]
- }
- }
-
- /** has this symbol to be translated into a JS setter (both directions)? */
- def isJSSetter(sym: Symbol) = {
- sym.unexpandedName.decoded.endsWith("_=") &&
- sym.tpe.resultType.typeSymbol == UnitClass &&
- enteringPhase(currentRun.uncurryPhase) {
- sym.tpe.paramss match {
- case List(List(arg)) => !isScalaRepeatedParamType(arg.tpe)
- case _ => false
- }
- }
- }
-
- /** has this symbol to be translated into a JS bracket access (JS to Scala) */
- def isJSBracketAccess(sym: Symbol) =
- sym.hasAnnotation(JSBracketAccessAnnotation)
-
- }
-
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/JSPrimitives.scala b/compiler/src/main/scala/scala/scalajs/compiler/JSPrimitives.scala
deleted file mode 100644
index f0dcd6f715..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/JSPrimitives.scala
+++ /dev/null
@@ -1,146 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.compiler
-
-import scala.tools.nsc._
-
-/** Extension of ScalaPrimitives for primitives only relevant to the JS backend
- *
- * @author Sébastie Doeraene
- */
-abstract class JSPrimitives {
- val global: Global
-
- type ThisJSGlobalAddons = JSGlobalAddons {
- val global: JSPrimitives.this.global.type
- }
-
- val jsAddons: ThisJSGlobalAddons
-
- import global._
- import jsAddons._
- import definitions._
- import rootMirror._
- import jsDefinitions._
- import scalaPrimitives._
-
- // Conversions from Scala types to JS types
- val V2JS = 300 // Unit
- val Z2JS = 301 // Boolean
- //val C2JS = 302 // Char
- val N2JS = 303 // Number (any numeric type except for Long)
- val S2JS = 304 // String
- val F2JS = 305 // FunctionN
- val F2JSTHIS = 306 // ThisFunctionN
-
- // Conversions from JS types to Scala types
- val JS2Z = 311 // Boolean
- //val JS2C = 312 // Char
- val JS2N = 313 // Number (any numeric type)
- val JS2S = 314 // String
-
- val GETGLOBAL = 320 // Get the top-level object (`window` in browsers)
- val DYNNEW = 321 // Instantiate a new JavaScript object
-
- val DYNSELECT = 330 // js.Dynamic.selectDynamic
- val DYNUPDATE = 331 // js.Dynamic.updateDynamic
- val DYNAPPLY = 332 // js.Dynamic.applyDynamic
- val DYNLITN = 333 // js.Dynamic.literal.applyDynamicNamed
- val DYNLIT = 334 // js.Dynamic.literal.applyDynamic
-
- val DICT_DEL = 335 // js.Dictionary.delete
-
- val ARR_CREATE = 337 // js.Array.apply (array literal syntax)
-
- val RTJ2J = 338 // Runtime Long to Long
- val J2RTJ = 339 // Long to Runtime Long
-
- val NTR_MOD_SUFF = 340 // scala.reflect.NameTransformer.MODULE_SUFFIX_STRING
- val NTR_NAME_JOIN = 341 // scala.relfect.NameTransformer.NAME_JOIN_STRING
-
- val ISUNDEF = 342 // js.isUndefined
- val TYPEOF = 343 // typeof x
- val DEBUGGER = 344 // js.debugger()
- val HASPROP = 345 // js.Object.hasProperty(o, p), equiv to `p in o` in JS
- val OBJPROPS = 346 // js.Object.properties(o), equiv to `for (p in o)` in JS
-
- val RETURNRECEIVER = 347 // anything "return this;", e.g., Boolean.booleanValue()
- val UNITVAL = 348 // () value, which is undefined
- val UNITTYPE = 349 // BoxedUnit.TYPE (== classOf[Unit])
-
- val ARRAYCOPY = 350 // System.arraycopy
-
- /** Initialize the map of primitive methods */
- def init() {
-
- addPrimitive(JSAny_fromUnit, V2JS)
- addPrimitive(JSAny_fromBoolean, Z2JS)
- addPrimitive(JSAny_fromByte, N2JS)
- addPrimitive(JSAny_fromShort, N2JS)
- addPrimitive(JSAny_fromInt, N2JS)
- addPrimitive(JSAny_fromFloat, N2JS)
- addPrimitive(JSAny_fromDouble, N2JS)
- addPrimitive(JSAny_fromString, S2JS)
-
- for (i <- 0 to 22)
- addPrimitive(JSAny_fromFunction(i), F2JS)
- for (i <- 1 to 22)
- addPrimitive(JSThisFunction_fromFunction(i), F2JSTHIS)
-
- addPrimitive(JSBoolean_toBoolean, JS2Z)
- addPrimitive(JSNumber_toDouble, JS2N)
- addPrimitive(JSString_toScalaString, JS2S)
-
- addPrimitive(JSDynamic_global, GETGLOBAL)
- addPrimitive(JSDynamic_newInstance, DYNNEW)
-
- addPrimitive(JSDynamic_selectDynamic, DYNSELECT)
- addPrimitive(JSDynamic_updateDynamic, DYNUPDATE)
- addPrimitive(JSDynamic_applyDynamic, DYNAPPLY)
- addPrimitive(JSDynamicLiteral_applyDynamicNamed, DYNLITN)
- addPrimitive(JSDynamicLiteral_applyDynamic, DYNLIT)
-
- addPrimitive(JSDictionary_delete, DICT_DEL)
-
- addPrimitive(JSArray_create, ARR_CREATE)
-
- addPrimitive(RuntimeLong_from, RTJ2J)
- addPrimitive(RuntimeLong_to, J2RTJ)
-
- val ntModule = getRequiredModule("scala.reflect.NameTransformer")
-
- addPrimitive(getMember(ntModule, newTermName("MODULE_SUFFIX_STRING")), NTR_MOD_SUFF)
- addPrimitive(getMember(ntModule, newTermName("NAME_JOIN_STRING")), NTR_NAME_JOIN)
-
- addPrimitive(JSPackage_typeOf, TYPEOF)
- addPrimitive(JSPackage_debugger, DEBUGGER)
- addPrimitive(JSPackage_undefined, UNITVAL)
- addPrimitive(JSPackage_isUndefined, ISUNDEF)
-
- addPrimitive(JSObject_hasProperty, HASPROP)
- addPrimitive(JSObject_properties, OBJPROPS)
-
- addPrimitive(getMember(requiredClass[java.lang.Boolean],
- newTermName("booleanValue")), RETURNRECEIVER)
- /* We could add a bunch of other such methods, like Double.doubleValue()
- * or Long.longValue(). However,
- * * These methods are already covered as methods with helpers in the env,
- * * They are seldom called (they are not called by generated code), and
- * * We should do it for the cross-product of all Number subclasses with
- * all the xValue() methods.
- * Conclusion: we do not bother.
- */
-
- addPrimitive(BoxedUnit_UNIT, UNITVAL)
- addPrimitive(BoxedUnit_TYPE, UNITTYPE)
-
- addPrimitive(getMember(getRequiredModule("java.lang.System"),
- newTermName("arraycopy")), ARRAYCOPY)
- }
-
- def isJavaScriptPrimitive(code: Int) =
- code >= 300 && code < 360
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/JSTreeExtractors.scala b/compiler/src/main/scala/scala/scalajs/compiler/JSTreeExtractors.scala
deleted file mode 100644
index a18ad88741..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/JSTreeExtractors.scala
+++ /dev/null
@@ -1,66 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Tobias Schlatter
- */
-
-package scala.scalajs.compiler
-
-import scala.annotation.tailrec
-
-import scala.scalajs.ir.Trees._
-import scala.scalajs.ir.Types._
-
-/** Useful extractors for JavaScript trees */
-object JSTreeExtractors {
-
- object jse {
- /**
- * A literally named sequence (like in a call to applyDynamicNamed)
- *
- * Example (Scala): method(("name1", x), ("name2", y))
- */
- object LitNamed {
- def unapply(exprs: List[Tree]) = unapply0(exprs, Nil)
-
- @tailrec
- private def unapply0(
- exprs: List[Tree],
- acc: List[(StringLiteral, Tree)]
- ): Option[List[(StringLiteral, Tree)]] = exprs match {
- case Tuple2(name: StringLiteral, value) :: xs =>
- unapply0(xs, (name, value) :: acc)
- case Nil => Some(acc.reverse)
- case _ => None
- }
- }
-
- /**
- * A literal Tuple2
- *
- * Example (Scala): (x, y)
- * But also (Scala): x -> y
- */
- object Tuple2 {
- def unapply(tree: Tree): Option[(Tree, Tree)] = tree match {
- // case (x, y)
- case New(ClassType("T2"), Ident("init___O__O", _),
- List(_1, _2)) =>
- Some((_1, _2))
- // case x -> y
- case Apply(
- LoadModule(ClassType("s_Predef$ArrowAssoc$")),
- Ident("$$minus$greater$extension__O__O__T2", _),
- List(
- Apply(
- LoadModule(ClassType("s_Predef$")),
- Ident("any2ArrowAssoc__O__O", _),
- List(_1)),
- _2)) =>
- Some((_1, _2))
- case _ =>
- None
- }
- }
- }
-
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala b/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala
deleted file mode 100644
index 6c08bf8181..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/PrepJSExports.scala
+++ /dev/null
@@ -1,229 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Tobias Schlatter
- */
-
-package scala.scalajs.compiler
-
-import scala.annotation.tailrec
-
-/**
- * Prepare export generation
- *
- * Helpers for transformation of @JSExport annotations
- */
-trait PrepJSExports { this: PrepJSInterop =>
-
- import global._
- import jsAddons._
- import definitions._
- import jsDefinitions._
-
- import scala.reflect.internal.Flags
-
- def genExportMember(ddef: DefDef): List[Tree] = {
- val baseSym = ddef.symbol
- val clsSym = baseSym.owner
-
- val exportNames = jsInterop.exportsOf(baseSym)
-
- // Helper function for errors
- def err(msg: String) = { currentUnit.error(exportNames.head._2, msg); Nil }
- def memType = if (baseSym.isConstructor) "constructor" else "method"
-
- if (exportNames.isEmpty)
- Nil
- else if (isJSAny(clsSym))
- err(s"You may not export a $memType of a subclass of js.Any")
- else if (!baseSym.isPublic)
- err(s"You may not export a non-public $memType")
- else if (baseSym.isMacro)
- err("You may not export a macro")
- else if (scalaPrimitives.isPrimitive(baseSym))
- err("You may not export a primitive")
- else if (!hasAllowedRetType(baseSym.tpe))
- err("""You may not export a method whose return type is neither a subtype of
- |AnyRef nor a concrete subtype of AnyVal (i.e. a value class or a
- |primitive value type).""".stripMargin)
- else if (hasIllegalRepeatedParam(baseSym))
- err(s"In an exported $memType, a *-parameter must come last " +
- "(through all parameter lists)")
- else if (hasIllegalDefaultParam(baseSym))
- err(s"In an exported $memType, all parameters with defaults " +
- "must be at the end")
- else if (forScaladoc) {
- /* Don't do anything under scaladoc because the uncurry phase does not
- * exist in that setting (see bug #323). It's no big deal because we do
- * not need exports for scaladoc
- */
- Nil
- } else if (baseSym.isConstructor) {
- // we can generate constructors entirely in the backend, since they
- // do not need inheritance and such. But we want to check their sanity
- // here by previous tests and the following ones.
-
- if (!clsSym.isPublic)
- err("You may not export a non-public class")
- else if (clsSym.isLocal)
- err("You may not export a local class")
- else if (clsSym.isNestedClass)
- err("You may not export a nested class. Create an exported factory " +
- "method in the outer class to work around this limitation.")
- else Nil
-
- } else {
- assert(!baseSym.isBridge)
-
- // Reset interface flag: Any trait will contain non-empty methods
- clsSym.resetFlag(Flags.INTERFACE)
-
- // Actually generate exporter methods
- for {
- (jsName, pos) <- exportNames
- tree <- genExportDefs(baseSym, jsName, pos)
- } yield tree
- }
- }
-
- /** generate an exporter for a DefDef including default parameter methods */
- private def genExportDefs(defSym: Symbol, jsName: String, pos: Position) = {
- val clsSym = defSym.owner
- val scalaName =
- jsInterop.scalaExportName(jsName, jsInterop.isJSProperty(defSym))
-
- // Create symbol for new method
- val expSym = defSym.cloneSymbol
-
- // Set position of symbol
- expSym.pos = pos
-
- // Alter type for new method (lift return type to Any)
- // The return type is lifted, in order to avoid bridge
- // construction and to detect methods whose signature only differs
- // in the return type.
- // Attention: This will cause boxes for primitive value types and value
- // classes. However, since we have restricted the return types, we can
- // always safely remove these boxes again in the back-end.
- if (!defSym.isConstructor)
- expSym.setInfo(retToAny(expSym.tpe))
-
- // Change name for new method
- expSym.name = scalaName
-
- // Update flags
- expSym.setFlag(Flags.SYNTHETIC)
- expSym.resetFlag(
- Flags.DEFERRED | // We always have a body now
- Flags.OVERRIDE // Synthetic methods need not bother with this
- )
-
- // Remove JSExport annotations
- expSym.removeAnnotation(JSExportAnnotation)
-
- // Add symbol to class
- clsSym.info.decls.enter(expSym)
-
- // Construct exporter DefDef tree
- val exporter = genProxyDefDef(clsSym, defSym, expSym, pos)
-
- // Construct exporters for default getters
- val defaultGetters = for {
- (param, i) <- expSym.paramss.flatten.zipWithIndex
- if param.hasFlag(Flags.DEFAULTPARAM)
- } yield genExportDefaultGetter(clsSym, defSym, expSym, i + 1, pos)
-
- exporter :: defaultGetters
- }
-
- private def genExportDefaultGetter(clsSym: Symbol, trgMethod: Symbol,
- exporter: Symbol, paramPos: Int, pos: Position) = {
-
- // Get default getter method we'll copy
- val trgGetter =
- clsSym.tpe.member(nme.defaultGetterName(trgMethod.name, paramPos))
-
- assert(trgGetter.exists)
-
- // Although the following must be true in a correct program, we cannot
- // assert, since a graceful failure message is only generated later
- if (!trgGetter.isOverloaded) {
- val expGetter = trgGetter.cloneSymbol
-
- expGetter.name = nme.defaultGetterName(exporter.name, paramPos)
- expGetter.pos = pos
-
- clsSym.info.decls.enter(expGetter)
-
- genProxyDefDef(clsSym, trgGetter, expGetter, pos)
-
- } else EmptyTree
- }
-
- /** generate a DefDef tree (from [[proxySym]]) that calls [[trgSym]] */
- private def genProxyDefDef(clsSym: Symbol, trgSym: Symbol,
- proxySym: Symbol, pos: Position) = atPos(pos) {
-
- // Helper to ascribe repeated argument lists when calling
- def spliceParam(sym: Symbol) = {
- if (isRepeated(sym))
- Typed(Ident(sym), Ident(tpnme.WILDCARD_STAR))
- else
- Ident(sym)
- }
-
- // Construct proxied function call
- val sel: Tree = Select(This(clsSym), trgSym)
- val rhs = (sel /: proxySym.paramss) {
- (fun,params) => Apply(fun, params map spliceParam)
- }
-
- typer.typedDefDef(DefDef(proxySym, rhs))
- }
-
- /** changes the return type of the method type tpe to Any. returns new type */
- private def retToAny(tpe: Type): Type = tpe match {
- case MethodType(params, result) => MethodType(params, retToAny(result))
- case NullaryMethodType(result) => NullaryMethodType(AnyClass.tpe)
- case PolyType(tparams, result) => PolyType(tparams, retToAny(result))
- case _: TypeRef => AnyClass.tpe
- case _ => abort(s"Type of method is not method type, but ${tpe}")
- }
-
- /** checks whether the type is subtype of AnyRef (or generic with bounds),
- * a primitive value type, or a value class */
- @tailrec
- private def hasAllowedRetType(tpe: Type): Boolean = {
- val sym = tpe.typeSymbol // this may be NoSymbol
-
- (tpe <:< AnyRefClass.tpe) ||
- sym.isPrimitiveValueClass ||
- sym.isDerivedValueClass || {
- tpe match {
- case MethodType(_, retTpe) => hasAllowedRetType(retTpe)
- case NullaryMethodType(retTpe) => hasAllowedRetType(retTpe)
- // Note that in the PolyType case, the return type may be polymorphic,
- // but the conformance test correctly works with bounds.
- // Therefore, `T <: AnyRef` is a valid return type.
- case PolyType(_, retTpe) => hasAllowedRetType(retTpe)
- case _ => false
- }
- }
- }
-
- /** checks whether this type has a repeated parameter elsewhere than at the end
- * of all the params
- */
- private def hasIllegalRepeatedParam(sym: Symbol): Boolean = {
- val params = sym.paramss.flatten
- params.nonEmpty && params.init.exists(isRepeated _)
- }
-
- /** checks whether there are default parameters not at the end of
- * the flattened parameter list
- */
- private def hasIllegalDefaultParam(sym: Symbol): Boolean = {
- val isDefParam = (_: Symbol).hasFlag(Flags.DEFAULTPARAM)
- sym.paramss.flatten.reverse.dropWhile(isDefParam).exists(isDefParam)
- }
-
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala b/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala
deleted file mode 100644
index 1fb9aa4855..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/PrepJSInterop.scala
+++ /dev/null
@@ -1,524 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Tobias Schlatter
- */
-
-package scala.scalajs.compiler
-
-import scala.tools.nsc
-import nsc._
-
-import scala.collection.immutable.ListMap
-import scala.collection.mutable
-
-/** Prepares classes extending js.Any for JavaScript interop
- *
- * This phase does:
- * - Sanity checks for js.Any hierarchy
- * - Annotate subclasses of js.Any to be treated specially
- * - Rewrite calls to scala.Enumeration.Value (include name string)
- * - Create JSExport methods: Dummy methods that are propagated
- * through the whole compiler chain to mark exports. This allows
- * exports to have the same semantics than methods.
- *
- * @author Tobias Schlatter
- */
-abstract class PrepJSInterop extends plugins.PluginComponent
- with PrepJSExports
- with transform.Transform {
- val jsAddons: JSGlobalAddons {
- val global: PrepJSInterop.this.global.type
- }
-
- val scalaJSOpts: ScalaJSOptions
-
- import global._
- import jsAddons._
- import definitions._
- import rootMirror._
- import jsDefinitions._
-
- val phaseName = "jsinterop"
-
- override def newPhase(p: nsc.Phase) = new JSInteropPhase(p)
- class JSInteropPhase(prev: nsc.Phase) extends Phase(prev) {
- override def name = phaseName
- override def description = "Prepare ASTs for JavaScript interop"
- }
-
- override protected def newTransformer(unit: CompilationUnit) =
- new JSInteropTransformer(unit)
-
- class JSInteropTransformer(unit: CompilationUnit) extends Transformer {
-
- // Force evaluation of JSDynamicLiteral: Strangely, we are unable to find
- // nested objects in the JSCode phase (probably after flatten).
- // Therefore we force the symbol of js.Dynamic.literal here in order to
- // have access to it in JSCode.
- JSDynamicLiteral
-
- var inJSAnyMod = false
- var inJSAnyCls = false
- var inScalaCls = false
- /** are we inside a subclass of scala.Enumeration */
- var inScalaEnum = false
- /** are we inside the implementation of scala.Enumeration? */
- var inEnumImpl = false
-
- def jsAnyClassOnly = !inJSAnyCls && allowJSAny
- def allowImplDef = !inJSAnyCls && !inJSAnyMod
- def allowJSAny = !inScalaCls
- def inJSAny = inJSAnyMod || inJSAnyCls
-
- /** DefDefs in class templates that export methods to JavaScript */
- val exporters = mutable.Map.empty[Symbol, mutable.ListBuffer[Tree]]
-
- override def transform(tree: Tree): Tree = postTransform { tree match {
- // Catch special case of ClassDef in ModuleDef
- case cldef: ClassDef if jsAnyClassOnly && isJSAny(cldef) =>
- transformJSAny(cldef)
-
- // Catch forbidden implDefs
- case idef: ImplDef if !allowImplDef =>
- unit.error(idef.pos, "Traits, classes and objects extending js.Any " +
- "may not have inner traits, classes or objects")
- super.transform(tree)
-
- // Handle js.Anys
- case idef: ImplDef if isJSAny(idef) =>
- transformJSAny(idef)
-
- // Catch the definition of scala.Enumeration itself
- case cldef: ClassDef if cldef.symbol == ScalaEnumClass =>
- enterEnumImpl { super.transform(cldef) }
-
- // Catch Scala Enumerations to transform calls to scala.Enumeration.Value
- case cldef: ClassDef if isScalaEnum(cldef) =>
- enterScalaCls {
- enterScalaEnum {
- super.transform(cldef)
- }
- }
- case idef: ImplDef if isScalaEnum(idef) =>
- enterScalaEnum { super.transform(idef) }
-
- // Catch (Scala) ClassDefs to forbid js.Anys
- case cldef: ClassDef =>
- enterScalaCls { super.transform(cldef) }
-
- // Catch DefDefs in JSAny to forbid setters with non-unit return type
- case ddef: DefDef if inJSAny && isNonJSScalaSetter(ddef.symbol) =>
- unit.error(tree.pos, "Setters that do not return Unit are " +
- "not allowed in types extending js.Any")
- super.transform(ddef)
-
- // Catch ValDefs in enumerations with simple calls to Value
- case ValDef(mods, name, tpt, ScalaEnumValue.NoName(optPar)) if inScalaEnum =>
- val nrhs = ScalaEnumValName(tree.symbol.owner, tree.symbol, optPar)
- treeCopy.ValDef(tree, mods, name, transform(tpt), nrhs)
-
- // Catch Select on Enumeration.Value we couldn't transform but need to
- // we ignore the implementation of scala.Enumeration itself
- case ScalaEnumValue.NoName(_) if !inEnumImpl =>
- unit.warning(tree.pos,
- """Couldn't transform call to Enumeration.Value.
- |The resulting program is unlikely to function properly as this
- |operation requires reflection.""".stripMargin)
- super.transform(tree)
-
- case ScalaEnumValue.NullName() if !inEnumImpl =>
- unit.warning(tree.pos,
- """Passing null as name to Enumeration.Value
- |requires reflection at runtime. The resulting
- |program is unlikely to function properly.""".stripMargin)
- super.transform(tree)
-
- case ScalaEnumVal.NoName(_) if !inEnumImpl =>
- unit.warning(tree.pos,
- """Calls to the non-string constructors of Enumeration.Val
- |require reflection at runtime. The resulting
- |program is unlikely to function properly.""".stripMargin)
- super.transform(tree)
-
- case ScalaEnumVal.NullName() if !inEnumImpl =>
- unit.warning(tree.pos,
- """Passing null as name to a constructor of Enumeration.Val
- |requires reflection at runtime. The resulting
- |program is unlikely to function properly.""".stripMargin)
- super.transform(tree)
-
- // Catch calls to Predef.classOf[T]. These should NEVER reach this phase
- // but unfortunately do. In normal cases, the typer phase replaces these
- // calls by a literal constant of the given type. However, when we compile
- // the scala library itself and Predef.scala is in the sources, this does
- // not happen.
- //
- // The trees reach this phase under the form:
- //
- // scala.this.Predef.classOf[T]
- //
- // If we encounter such a tree, depending on the plugin options, we fail
- // here or silently fix those calls.
- case TypeApply(
- classOfTree @ Select(Select(This(scala_?), predef_?), classOf_?),
- List(tpeArg))
- if (scala_?.decoded == "scala" &&
- predef_?.decoded == "Predef" &&
- classOf_?.decoded == "classOf") =>
-
- if (scalaJSOpts.fixClassOf) {
- // Replace call by literal constant containing type
- if (typer.checkClassType(tpeArg)) {
- typer.typed { Literal(Constant(tpeArg.tpe.dealias.widen)) }
- } else {
- unit.error(tpeArg.pos, s"Type ${tpeArg} is not a class type")
- EmptyTree
- }
- } else {
- unit.error(classOfTree.pos,
- """This classOf resulted in an unresolved classOf in the jscode
- |phase. This is most likely a bug in the Scala compiler. ScalaJS
- |is probably able to work around this bug. Enable the workaround
- |by passing the fixClassOf option to the plugin.""".stripMargin)
- EmptyTree
- }
-
- // Exporter generation
- case ddef: DefDef =>
- // Generate exporters for this ddef if required
- exporters.getOrElseUpdate(ddef.symbol.owner,
- mutable.ListBuffer.empty) ++= genExportMember(ddef)
-
- super.transform(tree)
-
- // Module export sanity check (export generated in JSCode phase)
- case modDef: ModuleDef =>
- val sym = modDef.symbol
-
- def condErr(msg: String) = {
- for ((_, pos) <- jsInterop.exportsOf(sym)) {
- currentUnit.error(pos, msg)
- }
- }
-
- if (!sym.isPublic)
- condErr("You may not export an non-public object")
- else if (sym.isLocal)
- condErr("You may not export a local object")
- else if (!sym.owner.isPackage)
- condErr("You may not export a nested object")
-
- super.transform(modDef)
-
- // Fix for issue with calls to js.Dynamic.x()
- // Rewrite (obj: js.Dynamic).x(...) to obj.applyDynamic("x")(...)
- case Select(Select(trg, x_?), nme.apply) if (isJSDynamic(trg) &&
- x_?.decoded == "x") =>
-
- val newTree = atPos(tree.pos) {
- Apply(
- Select(super.transform(trg), newTermName("applyDynamic")),
- List(Literal(Constant("x")))
- )
- }
-
- typer.typed(newTree, Mode.FUNmode, tree.tpe)
-
-
- // Fix for issue with calls to js.Dynamic.x()
- // Rewrite (obj: js.Dynamic).x to obj.selectDynamic("x")
- case Select(trg, x_?) if isJSDynamic(trg) && x_?.decoded == "x" =>
-
- val newTree = atPos(tree.pos) {
- Apply(
- Select(super.transform(trg), newTermName("selectDynamic")),
- List(Literal(Constant("x")))
- )
- }
-
- typer.typed(newTree, Mode.FUNmode, tree.tpe)
-
- case _ => super.transform(tree)
- } }
-
- private def postTransform(tree: Tree) = tree match {
- case Template(parents, self, body) =>
- val clsSym = tree.symbol.owner
- val exports = exporters.get(clsSym).toIterable.flatten
-
- // Add exports to the template
- treeCopy.Template(tree, parents, self, body ++ exports)
-
- case memDef: MemberDef =>
- val sym = memDef.symbol
- if (sym.isLocal) {
- for ((_, pos) <- jsInterop.exportsOf(sym)) {
- currentUnit.error(pos, "You may not export a local definition")
- }
- }
-
- memDef
- case _ => tree
- }
-
- /**
- * Performs checks and rewrites specific to classes / objects extending
- * js.Any
- */
- private def transformJSAny(implDef: ImplDef) = {
- val sym = implDef.symbol
-
- lazy val badParent = sym.info.parents.find(t => !(t <:< JSAnyClass.tpe))
- def inScalaJSJSPackage =
- sym.enclosingPackage == ScalaJSJSPackage ||
- sym.enclosingPackage == ScalaJSJSPrimPackage
-
- implDef match {
- // Check that we do not have a case modifier
- case _ if implDef.mods.hasFlag(Flag.CASE) =>
- unit.error(implDef.pos, "Classes and objects extending " +
- "js.Any may not have a case modifier")
-
- // Check that we do not extends a trait that does not extends js.Any
- case _ if !inScalaJSJSPackage && !badParent.isEmpty &&
- !isJSLambda(sym) =>
- val badName = {
- val names = (badParent.get.typeSymbol.fullName, sym.fullName).zipped
- names.dropWhile(scala.Function.tupled(_ == _)).unzip._1.mkString
- }
- unit.error(implDef.pos, s"${sym.nameString} extends ${badName} " +
- "which does not extend js.Any.")
-
- // Check that we are not an anonymous class
- case cldef: ClassDef
- if cldef.symbol.isAnonymousClass && !isJSLambda(sym) =>
- unit.error(implDef.pos, "Anonymous classes may not " +
- "extend js.Any")
-
- // Check if we may have a js.Any here
- case cldef: ClassDef if !allowJSAny && !jsAnyClassOnly &&
- !isJSLambda(sym) =>
- unit.error(implDef.pos, "Classes extending js.Any may not be " +
- "defined inside a class or trait")
-
- case _: ModuleDef if !allowJSAny =>
- unit.error(implDef.pos, "Objects extending js.Any may not be " +
- "defined inside a class or trait")
-
- // Check that this is not a class extending js.GlobalScope
- case _: ClassDef if isJSGlobalScope(implDef) &&
- implDef.symbol != JSGlobalScopeClass =>
- unit.error(implDef.pos, "Only objects may extend js.GlobalScope")
-
- // Check that primary ctor of a ClassDef is no-arg
- // FIXME temporarily disabled until we have better handling.
- //case cldef: ClassDef if !primCtorNoArg(cldef) =>
- // unit.error(cldef.pos, "The primary constructor of a class extending "+
- // "js.Any may only have a single, empty argument list")
-
- case _ =>
- // We cannot use sym directly, since the symbol
- // of a module is not its type's symbol but the value it declares
- val tSym = sym.tpe.typeSymbol
-
- tSym.setAnnotations(rawJSAnnot :: sym.annotations)
-
- }
-
- if (implDef.isInstanceOf[ModuleDef])
- enterJSAnyMod { super.transform(implDef) }
- else
- enterJSAnyCls { super.transform(implDef) }
- }
-
- private def enterJSAnyCls[T](body: =>T) = {
- val old = inJSAnyCls
- inJSAnyCls = true
- val res = body
- inJSAnyCls = old
- res
- }
-
- private def enterJSAnyMod[T](body: =>T) = {
- val old = inJSAnyMod
- inJSAnyMod = true
- val res = body
- inJSAnyMod = old
- res
- }
-
- private def enterScalaCls[T](body: =>T) = {
- val old = inScalaCls
- inScalaCls = true
- val res = body
- inScalaCls = old
- res
- }
-
- private def enterScalaEnum[T](body: =>T) = {
- val old = inScalaEnum
- inScalaEnum = true
- val res = body
- inScalaEnum = old
- res
- }
-
- private def enterEnumImpl[T](body: =>T) = {
- val old = inEnumImpl
- inEnumImpl = true
- val res = body
- inEnumImpl = old
- res
- }
-
- }
-
- def isJSAny(sym: Symbol): Boolean =
- sym.tpe.typeSymbol isSubClass JSAnyClass
-
- private def isJSAny(implDef: ImplDef): Boolean = isJSAny(implDef.symbol)
-
- private def isJSGlobalScope(implDef: ImplDef) =
- implDef.symbol.tpe.typeSymbol isSubClass JSGlobalScopeClass
-
- private def isJSLambda(sym: Symbol) = sym.isAnonymousClass &&
- AllJSFunctionClasses.exists(sym.tpe.typeSymbol isSubClass _)
-
- private def isScalaEnum(implDef: ImplDef) =
- implDef.symbol.tpe.typeSymbol isSubClass ScalaEnumClass
-
- private def isJSDynamic(tree: Tree) = tree.tpe.typeSymbol == JSDynamicClass
-
- /**
- * is this symbol a setter that has a non-unit return type
- *
- * these setters don't make sense in JS (in JS, assignment returns
- * the assigned value) and are therefore not allowed in facade types
- */
- private def isNonJSScalaSetter(sym: Symbol) = sym.name.decoded.endsWith("_=") && {
- sym.tpe.paramss match {
- case List(List(arg)) =>
- !isScalaRepeatedParamType(arg.tpe) &&
- sym.tpe.resultType.typeSymbol != UnitClass
- case _ => false
- }
- }
-
- trait ScalaEnumFctExtractors {
- protected val methSym: Symbol
-
- protected def resolve(ptpes: Symbol*) = {
- val res = methSym suchThat {
- _.tpe.params.map(_.tpe.typeSymbol) == ptpes.toList
- }
- assert(res != NoSymbol)
- res
- }
-
- protected val noArg = resolve()
- protected val nameArg = resolve(StringClass)
- protected val intArg = resolve(IntClass)
- protected val fullMeth = resolve(IntClass, StringClass)
-
- /**
- * Extractor object for calls to the targeted symbol that do not have an
- * explicit name in the parameters
- *
- * Extracts:
- * - `sel: Select` where sel.symbol is targeted symbol (no arg)
- * - Apply(meth, List(param)) where meth.symbol is targeted symbol (i: Int)
- */
- object NoName {
- def unapply(t: Tree) = t match {
- case sel: Select if sel.symbol == noArg =>
- Some(None)
- case Apply(meth, List(param)) if meth.symbol == intArg =>
- Some(Some(param))
- case _ =>
- None
- }
- }
-
- object NullName {
- def unapply(tree: Tree) = tree match {
- case Apply(meth, List(Literal(Constant(null)))) =>
- meth.symbol == nameArg
- case Apply(meth, List(_, Literal(Constant(null)))) =>
- meth.symbol == fullMeth
- case _ => false
- }
- }
-
- }
-
- private object ScalaEnumValue extends {
- protected val methSym =
- getMemberMethod(ScalaEnumClass, newTermName("Value"))
- } with ScalaEnumFctExtractors
-
- private object ScalaEnumVal extends {
- private val valSym =
- getMemberClass(ScalaEnumClass, newTermName("Val"))
- protected val methSym = valSym.tpe.member(nme.CONSTRUCTOR)
- } with ScalaEnumFctExtractors
-
- /**
- * Construct a call to Enumeration.Value
- * @param thisSym ClassSymbol of enclosing class
- * @param nameOrig Symbol of ValDef where this call will be placed
- * (determines the string passed to Value)
- * @param intParam Optional tree with Int passed to Value
- * @return Typed tree with appropriate call to Value
- */
- private def ScalaEnumValName(
- thisSym: Symbol,
- nameOrig: Symbol,
- intParam: Option[Tree]) = {
-
- def enc(name: TermName) = name.encodedName
-
- val defaultName = nameOrig.asTerm.getterName.encoded
-
-
- // Construct the following tree
- //
- // if (nextName != null && nextName.hasNext)
- // nextName.next()
- // else
- //
- //
- val nextNameTree = Select(This(thisSym), "nextName")
- val nullCompTree =
- Apply(Select(nextNameTree, enc("!=")), Literal(Constant(null)) :: Nil)
- val hasNextTree = Select(nextNameTree, "hasNext")
- val condTree = Apply(Select(nullCompTree, enc("&&")), hasNextTree :: Nil)
- val nameTree = If(condTree,
- Apply(Select(nextNameTree, "next"), Nil),
- Literal(Constant(defaultName)))
- val params = intParam.toList :+ nameTree
-
- typer.typed {
- Apply(Select(This(thisSym),newTermName("Value")), params)
- }
- }
-
- private def rawJSAnnot =
- Annotation(RawJSTypeAnnot.tpe, List.empty, ListMap.empty)
-
- private val ScalaEnumClass = getRequiredClass("scala.Enumeration")
-
- /** checks if the primary constructor of the ClassDef `cldef` does not
- * take any arguments
- */
- private def primCtorNoArg(cldef: ClassDef) =
- getPrimCtor(cldef.symbol.tpe).map(_.paramss == List(List())).getOrElse(true)
-
- /** return the MethodSymbol of the primary constructor of the given type
- * if it exists
- */
- private def getPrimCtor(tpe: Type) =
- tpe.declaration(nme.CONSTRUCTOR).alternatives.collectFirst {
- case ctor: MethodSymbol if ctor.isPrimaryConstructor => ctor
- }
-
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSOptions.scala b/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSOptions.scala
deleted file mode 100644
index 2d57542d5c..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSOptions.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Tobias Schlatter
- */
-
-package scala.scalajs.compiler
-
-import java.net.URI
-
-/** This trait allows to query all options to the ScalaJS plugin
- *
- * Also see the help text in ScalaJSPlugin for information about particular
- * options.
- */
-trait ScalaJSOptions {
- /** should calls to Predef.classOf[T] be fixed in the jsinterop phase.
- * If false, bad calls to classOf will cause an error. */
- def fixClassOf: Boolean
-
- /** can be used to turn off source map generation */
- def noSourceMap: Boolean
-
- /** URI to relativize referenced file in source maps with */
- def relSourceMap: Option[URI]
-
- /** URI to make referenced file absolute again (requires relSourceMap) */
- def absSourceMap: Option[URI]
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSPlugin.scala b/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSPlugin.scala
deleted file mode 100644
index bc7bacc911..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/ScalaJSPlugin.scala
+++ /dev/null
@@ -1,104 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.compiler
-
-import scala.tools.nsc._
-import scala.tools.nsc.plugins.{
- Plugin => NscPlugin, PluginComponent => NscPluginComponent
-}
-import scala.collection.{ mutable, immutable }
-
-import java.net.{ URI, URISyntaxException }
-
-import scala.scalajs.ir.Trees
-
-/** Main entry point for the Scala.js compiler plugin
- *
- * @author Sébastien Doeraene
- */
-class ScalaJSPlugin(val global: Global) extends NscPlugin {
- import global._
-
- val name = "scalajs"
- val description = "Compile to JavaScript"
- val components = List[NscPluginComponent](PrepInteropComponent, GenCodeComponent)
-
- /** Called when the JS ASTs are generated. Override for testing */
- def generatedJSAST(clDefs: List[Trees.Tree]): Unit = {}
-
- /** Addons for JavaScript platform */
- object jsAddons extends {
- val global: ScalaJSPlugin.this.global.type = ScalaJSPlugin.this.global
- } with JSGlobalAddons with Compat210Component
-
- object scalaJSOpts extends ScalaJSOptions {
- var fixClassOf: Boolean = false
- var noSourceMap: Boolean = false
- var relSourceMap: Option[URI] = None
- var absSourceMap: Option[URI] = None
- }
-
- object PrepInteropComponent extends {
- val global: ScalaJSPlugin.this.global.type = ScalaJSPlugin.this.global
- val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons
- val scalaJSOpts = ScalaJSPlugin.this.scalaJSOpts
- override val runsAfter = List("typer")
- override val runsBefore = List("pickle")
- } with PrepJSInterop
-
- object GenCodeComponent extends {
- val global: ScalaJSPlugin.this.global.type = ScalaJSPlugin.this.global
- val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons
- val scalaJSOpts = ScalaJSPlugin.this.scalaJSOpts
- override val runsAfter = List("mixin")
- override val runsBefore = List("delambdafy", "cleanup", "terminal")
- } with GenJSCode {
- def generatedJSAST(clDefs: List[Trees.Tree]) =
- ScalaJSPlugin.this.generatedJSAST(clDefs)
- }
-
- override def processOptions(options: List[String],
- error: String => Unit): Unit = {
- import scalaJSOpts._
-
- for (option <- options) {
- if (option == "fixClassOf") {
- fixClassOf = true
-
- } else if (option == "noSourceMap") {
- noSourceMap = true
- } else if (option.startsWith("relSourceMap:")) {
- val uriStr = option.stripPrefix("relSourceMap:")
- try { relSourceMap = Some(new URI(uriStr)) }
- catch {
- case e: URISyntaxException => error(s"$uriStr is not a valid URI")
- }
- } else if (option.startsWith("absSourceMap:")) {
- val uriStr = option.stripPrefix("absSourceMap:")
- try { absSourceMap = Some(new URI(uriStr)) }
- catch {
- case e: URISyntaxException => error(s"$uriStr is not a valid URI")
- }
- } else {
- error("Option not understood: " + option)
- }
- }
-
- // Verfiy constraits on flags that require others
- if (absSourceMap.isDefined && relSourceMap.isEmpty)
- error("absSourceMap requires the use of relSourceMap")
- }
-
- override val optionsHelp: Option[String] = Some(s"""
- | -P:$name:noSourceMap turn off source map generation
- | -P:$name:relSourceMap: relativize emitted source maps with
- | -P:$name:absSourceMap: absolutize emitted source maps with
- This option requires the use of relSourceMap
- | -P:$name:fixClassOf repair calls to Predef.classOf that reach ScalaJS
- | WARNING: This is a tremendous hack! Expect ugly errors if you use this option.
- """.stripMargin)
-
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/TypeKinds.scala b/compiler/src/main/scala/scala/scalajs/compiler/TypeKinds.scala
deleted file mode 100644
index cb81e44903..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/TypeKinds.scala
+++ /dev/null
@@ -1,266 +0,0 @@
-/* Scala.js compiler
- * Copyright 2013 LAMP/EPFL
- * @author Sébastien Doeraene
- */
-
-package scala.scalajs.compiler
-
-import scala.tools.nsc._
-
-import scala.scalajs.ir
-import ir.Types
-
-/** Types as their are viewed by JavaScript
- *
- * @author Sébastien Doeraene
- */
-trait TypeKinds extends SubComponent { this: GenJSCode =>
- import global._
- import jsAddons._
-
- import definitions.{ UnitClass, BooleanClass, CharClass, ByteClass,
- ShortClass, IntClass, LongClass, FloatClass, DoubleClass, ArrayClass,
- AnyRefClass, ObjectClass, NullClass, NothingClass, arrayType,
- RuntimeNullClass, RuntimeNothingClass }
-
- lazy val ObjectReference = REFERENCE(definitions.ObjectClass)
-
- lazy val UndefinedKind = UNDEFINED
- //lazy val NullKind = NULL
- lazy val BooleanKind = BOOL
- lazy val CharKind = INT(CharClass)
- lazy val ByteKind = INT(ByteClass)
- lazy val ShortKind = INT(ShortClass)
- lazy val IntKind = INT(IntClass)
- lazy val LongKind = INT(LongClass)
- lazy val FloatKind = FLOAT(FloatClass)
- lazy val DoubleKind = FLOAT(DoubleClass)
- lazy val RefKind = ObjectReference
-
- lazy val primitiveKinds = List(UndefinedKind, /*NullKind,*/ BooleanKind,
- CharKind, ByteKind, ShortKind, IntKind, LongKind, FloatKind, DoubleKind)
-
- /** A map from scala primitive Types to OzCode TypeKinds */
- lazy val primitiveTypeMap: Map[Symbol, TypeKind] = {
- import definitions._
- Map(
- UnitClass -> UndefinedKind,
- BooleanClass -> BooleanKind,
- CharClass -> CharKind,
- ByteClass -> ByteKind,
- ShortClass -> ShortKind,
- IntClass -> IntKind,
- LongClass -> LongKind,
- FloatClass -> FloatKind,
- DoubleClass -> DoubleKind
- )
- }
-
- /**
- * This class represents a type kind. Type kinds represent the types that
- * JavaScript knows.
- */
- sealed abstract class TypeKind {
- def isReferenceType = false
- def isArrayType = false
- def isValueType = false
-
- def dimensions: Int = 0
-
- override def toString = {
- this.getClass.getName stripSuffix "$" dropWhile (_ != '$') drop 1
- }
-
- def toType: Type
-
- def toIRType: Types.Type
- def toReferenceType: Types.ReferenceType
- }
-
- sealed abstract class TypeKindButArray extends TypeKind {
- override def toReferenceType: Types.ClassType
- }
-
- sealed abstract class ValueTypeKind(cls: Symbol) extends TypeKindButArray {
- override def isValueType = true
-
- def toType = cls.tpe
-
- override def toReferenceType: Types.ClassType =
- Types.ClassType(encodeClassFullName(cls))
-
- val primitiveCharCode = cls match {
- case UnitClass => "V"
- case BooleanClass => "Z"
- case CharClass => "C"
- case ByteClass => "B"
- case ShortClass => "S"
- case IntClass => "I"
- case LongClass => "J"
- case FloatClass => "F"
- case DoubleClass => "D"
- case x => abort("Unknown primitive type: " + x.fullName)
- }
- }
-
- /** The undefined value */
- case object UNDEFINED extends ValueTypeKind(definitions.UnitClass) {
- def toIRType = Types.UndefType
- }
-
- /** The null value */
- //case object NULL extends ValueTypeKind(definitions.NullClass) {}
-
- /** Int */
- case class INT(cls: Symbol) extends ValueTypeKind(cls) {
- def toIRType =
- if (cls == LongClass) Types.ClassType(ir.Definitions.RuntimeLongClass)
- else Types.IntType
- }
-
- /** Float */
- case class FLOAT(cls: Symbol) extends ValueTypeKind(cls) {
- def toIRType = Types.DoubleType
- }
-
- /** Boolean */
- case object BOOL extends ValueTypeKind(definitions.BooleanClass) {
- def toIRType = Types.BooleanType
- }
-
- /** An object */
- case class REFERENCE(cls: Symbol) extends TypeKindButArray {
- override def toString = "REFERENCE(" + cls.fullName + ")"
- override def isReferenceType = true
-
- def toType = cls.tpe
-
- def toIRType =
- if (cls == ObjectClass) Types.AnyType
- else Types.ClassType(encodeClassFullName(cls))
-
- override def toReferenceType: Types.ClassType =
- Types.ClassType(encodeClassFullName(mapRuntimeClass(cls)))
- }
-
- /** An array */
- case class ARRAY(elem: TypeKind) extends TypeKind {
- override def toString = "ARRAY[" + elem + "]"
- override def isArrayType = true
- override def dimensions = elem.dimensions + 1
-
- def toType = arrayType(elem.toType)
-
- def toIRType = toReferenceType
-
- override def toReferenceType: Types.ArrayType = {
- Types.ArrayType(
- elementKind.toReferenceType.className,
- dimensions)
- }
-
- /** The ultimate element type of this array. */
- def elementKind: TypeKindButArray = elem match {
- case a: ARRAY => a.elementKind
- case k: TypeKindButArray => k
- }
- }
-
- ////////////////// Conversions //////////////////////////////
-
- def toIRType(t: Type): Types.Type =
- toTypeKind(t).toIRType
-
- // The following code is a hard copy-and-paste from backend.icode.TypeKinds
-
- /** Return the TypeKind of the given type
- *
- * Call to .normalize fixes #3003 (follow type aliases). Otherwise,
- * arrayOrClassType below would return ObjectReference.
- */
- def toTypeKind(t: Type): TypeKind = t.normalize match {
- case ThisType(ArrayClass) => ObjectReference
- case ThisType(sym) => REFERENCE(sym)
- case SingleType(_, sym) => primitiveOrRefType(sym)
- case ConstantType(_) => toTypeKind(t.underlying)
- case TypeRef(_, sym, args) => primitiveOrClassType(sym, args)
- case ClassInfoType(_, _, ArrayClass) => abort("ClassInfoType to ArrayClass!")
- case ClassInfoType(_, _, sym) => primitiveOrRefType(sym)
-
- // !!! Iulian says types which make no sense after erasure should not reach here,
- // which includes the ExistentialType, AnnotatedType, RefinedType. I don't know
- // if the first two cases exist because they do or as a defensive measure, but
- // at the time I added it, RefinedTypes were indeed reaching here.
- // !!! Removed in JavaScript backend because I do not know what to do with lub
- //case ExistentialType(_, t) => toTypeKind(t)
- // Apparently, this case does occur (see pos/CustomGlobal.scala)
- case t: AnnotatedType => toTypeKind(t.underlying)
- //case RefinedType(parents, _) => parents map toTypeKind reduceLeft lub
-
- /* This case is not in scalac. We need it for the test
- * run/valueclasses-classtag-existential. I have no idea how icode does
- * not fail this test: we do everything the same as icode up to here.
- */
- case tpe: ErasedValueType => newReference(tpe.valueClazz)
-
- // For sure WildcardTypes shouldn't reach here either, but when
- // debugging such situations this may come in handy.
- // case WildcardType => REFERENCE(ObjectClass)
- case norm => abort(
- "Unknown type: %s, %s [%s, %s] TypeRef? %s".format(
- t, norm, t.getClass, norm.getClass, t.isInstanceOf[TypeRef]
- )
- )
- }
-
- /** Return the type kind of a class, possibly an array type.
- */
- private def arrayOrClassType(sym: Symbol, targs: List[Type]) = sym match {
- case ArrayClass => ARRAY(toTypeKind(targs.head))
- case _ if sym.isClass => newReference(sym)
- case _ =>
- assert(sym.isType, sym) // it must be compiling Array[a]
- ObjectReference
- }
-
- /** Interfaces have to be handled delicately to avoid introducing
- * spurious errors, but if we treat them all as AnyRef we lose too
- * much information.
- */
- private def newReference(sym: Symbol): TypeKind = {
- // Can't call .toInterface (at this phase) or we trip an assertion.
- // See PackratParser#grow for a method which fails with an apparent mismatch
- // between "object PackratParsers$class" and "trait PackratParsers"
- if (sym.isImplClass) {
- // pos/spec-List.scala is the sole failure if we don't check for NoSymbol
- val traitSym = sym.owner.info.decl(tpnme.interfaceName(sym.name))
- if (traitSym != NoSymbol)
- return REFERENCE(traitSym)
- }
- REFERENCE(sym)
- }
-
- private def primitiveOrRefType(sym: Symbol) =
- primitiveTypeMap.getOrElse(sym, newReference(sym))
- private def primitiveOrClassType(sym: Symbol, targs: List[Type]) =
- primitiveTypeMap.getOrElse(sym, arrayOrClassType(sym, targs))
-
- /**
- * Extractor object for Scala runtime mapped types
- *
- * These are types that are mapped to a different type at runtime.
- * Currently scala.Nothing and scala.Null
- */
- object ScalaRTMapped {
- def unapply(cls: Symbol) = cls match {
- case NullClass => Some(RuntimeNullClass)
- case NothingClass => Some(RuntimeNothingClass)
- case _ => None
- }
- }
-
- def mapRuntimeClass(cls: Symbol): Symbol = cls match {
- case ScalaRTMapped(rtCls) => rtCls
- case _ => cls
- }
-}
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/util/ScopedVar.scala b/compiler/src/main/scala/scala/scalajs/compiler/util/ScopedVar.scala
deleted file mode 100644
index 3924955852..0000000000
--- a/compiler/src/main/scala/scala/scalajs/compiler/util/ScopedVar.scala
+++ /dev/null
@@ -1,38 +0,0 @@
-package scala.scalajs.compiler.util
-
-import language.implicitConversions
-
-class ScopedVar[A](init: A) {
- import ScopedVar.Assignment
-
- private var value = init
-
- def this()(implicit ev: Null <:< A) = this(ev(null))
-
- def get: A = value
- def :=(newValue: A): Assignment[A] = new Assignment(this, newValue)
-}
-
-object ScopedVar {
- class Assignment[T](scVar: ScopedVar[T], value: T) {
- private[ScopedVar] def push(): AssignmentStackElement[T] = {
- val stack = new AssignmentStackElement(scVar, scVar.value)
- scVar.value = value
- stack
- }
- }
-
- private class AssignmentStackElement[T](scVar: ScopedVar[T], oldValue: T) {
- private[ScopedVar] def pop(): Unit = {
- scVar.value = oldValue
- }
- }
-
- implicit def toValue[T](scVar: ScopedVar[T]): T = scVar.get
-
- def withScopedVars[T](ass: Assignment[_]*)(body: => T): T = {
- val stack = ass.map(_.push())
- try body
- finally stack.reverse.foreach(_.pop())
- }
-}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/BinaryCompatTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/BinaryCompatTest.scala
new file mode 100644
index 0000000000..9b8e8f4e33
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/BinaryCompatTest.scala
@@ -0,0 +1,65 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+
+import org.scalajs.ir.Names._
+import org.scalajs.ir.Trees._
+import org.scalajs.ir.Types._
+
+import org.junit.Test
+
+// scalastyle:off line.size.limit
+
+class BinaryCompatTest extends JSASTTest {
+
+ @Test
+ def emitDefaultAccessorsOfJSNativeDefs(): Unit = {
+ val XDefaultAccessorName = MethodName("foo$default$1", Nil, IntRef)
+
+ /* Check that, even with the fix to #4553, we still emit default accessors
+ * for JS native defs, unless they are `= js.native`.
+ */
+ """
+ import scala.scalajs.js, js.annotation._
+
+ object Container {
+ @js.native
+ @JSGlobal("foo")
+ def foo(x: Int = 5): Int = js.native
+
+ def bar(x: Int): Int = x
+ }
+ """.hasExactly(1, "default accessor for x in foo") {
+ case MethodDef(flags, MethodIdent(XDefaultAccessorName), _, _, _, _) =>
+ }
+
+ // Check that it is not emitted for `= js.native`.
+ """
+ import scala.scalajs.js, js.annotation._
+
+ object Container {
+ @js.native
+ @JSGlobal("foo")
+ def foo(x: Int = js.native): Int = js.native
+
+ def bar(x: Int): Int = x
+ }
+ """.hasNot("default accessor for x in foo") {
+ case MethodDef(flags, MethodIdent(XDefaultAccessorName), _, _, _, _) =>
+ }
+
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/CallSiteInlineTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/CallSiteInlineTest.scala
new file mode 100644
index 0000000000..b4c33256b6
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/CallSiteInlineTest.scala
@@ -0,0 +1,115 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import util._
+
+import org.junit.Test
+import org.junit.Assert._
+
+import org.scalajs.ir.{Trees => js, Types => jstpe}
+import org.scalajs.ir.Names
+import org.scalajs.ir.Names._
+
+class CallSiteInlineTest extends JSASTTest {
+
+ @Test
+ def testInline: Unit = {
+ val flags = {
+ """
+ object A {
+ println("F"): @inline
+ }
+ """.extractOne("println call") {
+ case js.Apply(flags, _, SMN("println"), _) => flags
+ }
+ }
+
+ assertTrue(flags.inline)
+ }
+
+ @Test
+ def testNoinline: Unit = {
+ val flags = {
+ """
+ object A {
+ println("F"): @noinline
+ }
+ """.extractOne("println call") {
+ case js.Apply(flags, _, SMN("println"), _) => flags
+ }
+ }
+
+ assertTrue(flags.noinline)
+ }
+
+ @Test
+ def testInlineNullary: Unit = {
+ val flags = {
+ """
+ object A {
+ Map.empty: @inline
+ }
+ """.extractOne("Map.empty") {
+ case js.Apply(flags, _, SMN("empty"), _) => flags
+ }
+ }
+
+ assertTrue(flags.inline)
+ }
+
+ @Test
+ def testNoinlineNullary: Unit = {
+ val flags = {
+ """
+ object A {
+ Map.empty: @noinline
+ }
+ """.extractOne("Map.empty") {
+ case js.Apply(flags, _, SMN("empty"), _) => flags
+ }
+ }
+
+ assertTrue(flags.noinline)
+ }
+
+ @Test
+ def testInlineStatic: Unit = {
+ val flags = {
+ """
+ object A {
+ Integer.compare(1, 2): @inline
+ }
+ """.extractOne("compare call") {
+ case js.ApplyStatic(flags, _, SMN("compare"), _) => flags
+ }
+ }
+
+ assertTrue(flags.inline)
+ }
+
+ @Test
+ def testNoinlineStatic: Unit = {
+ val flags = {
+ """
+ object A {
+ Integer.compare(1, 2): @noinline
+ }
+ """.extractOne("compare call") {
+ case js.ApplyStatic(flags, _, SMN("compare"), _) => flags
+ }
+ }
+
+ assertTrue(flags.noinline)
+ }
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala
new file mode 100644
index 0000000000..9ed869cbcc
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala
@@ -0,0 +1,373 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.junit.Test
+import org.junit.Assume._
+
+// scalastyle:off line.size.limit
+
+class DiverseErrorsTest extends DirectTest with TestHelpers {
+
+ override def preamble: String =
+ """import scala.scalajs.js, js.annotation._
+ """
+
+ private def version = scala.util.Properties.versionNumberString
+
+ private val allowsSingletonClassOf =
+ !version.startsWith("2.12.") && version != "2.13.3"
+
+ @Test
+ def noIsInstanceOnJS(): Unit = {
+
+ """
+ @js.native
+ trait JSTrait extends js.Object
+
+ class A {
+ val a: AnyRef = "asdf"
+ def x = a.isInstanceOf[JSTrait]
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: isInstanceOf[JSTrait] not supported because it is a JS trait
+ | def x = a.isInstanceOf[JSTrait]
+ | ^
+ """
+
+ }
+
+ @Test
+ def jsConstructorOfErrors(): Unit = {
+
+ """
+ class ScalaClass
+ trait ScalaTrait
+ object ScalaObject
+
+ object A {
+ val a = js.constructorOf[ScalaClass]
+ val b = js.constructorOf[ScalaTrait]
+ val c = js.constructorOf[ScalaObject.type]
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: type arguments [ScalaClass] do not conform to method constructorOf's type parameter bounds [T <: scala.scalajs.js.Any]
+ | val a = js.constructorOf[ScalaClass]
+ | ^
+ |newSource1.scala:9: error: type arguments [ScalaTrait] do not conform to method constructorOf's type parameter bounds [T <: scala.scalajs.js.Any]
+ | val b = js.constructorOf[ScalaTrait]
+ | ^
+ |newSource1.scala:10: error: type arguments [ScalaObject.type] do not conform to method constructorOf's type parameter bounds [T <: scala.scalajs.js.Any]
+ | val c = js.constructorOf[ScalaObject.type]
+ | ^
+ """
+
+ val singletonPrefix =
+ if (allowsSingletonClassOf) "non-trait "
+ else ""
+
+ """
+ @js.native @JSGlobal class NativeJSClass extends js.Object
+ @js.native trait NativeJSTrait extends js.Object
+ @js.native @JSGlobal object NativeJSObject extends js.Object
+
+ class JSClass extends js.Object
+ trait JSTrait extends js.Object
+ object JSObject extends js.Object
+
+ object A {
+ val a = js.constructorOf[NativeJSTrait]
+ val b = js.constructorOf[NativeJSObject.type]
+
+ val c = js.constructorOf[NativeJSClass with NativeJSTrait]
+ val d = js.constructorOf[NativeJSClass { def bar: Int }]
+
+ val e = js.constructorOf[JSTrait]
+ val f = js.constructorOf[JSObject.type]
+
+ val g = js.constructorOf[JSClass with JSTrait]
+ val h = js.constructorOf[JSClass { def bar: Int }]
+
+ def foo[A <: js.Any] = js.constructorOf[A]
+ def bar[A <: js.Any: scala.reflect.ClassTag] = js.constructorOf[A]
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:12: error: non-trait class type required but NativeJSTrait found
+ | val a = js.constructorOf[NativeJSTrait]
+ | ^
+ |newSource1.scala:13: error: ${singletonPrefix}class type required but NativeJSObject.type found
+ | val b = js.constructorOf[NativeJSObject.type]
+ | ^
+ |newSource1.scala:15: error: class type required but NativeJSClass with NativeJSTrait found
+ | val c = js.constructorOf[NativeJSClass with NativeJSTrait]
+ | ^
+ |newSource1.scala:16: error: class type required but NativeJSClass{def bar: Int} found
+ | val d = js.constructorOf[NativeJSClass { def bar: Int }]
+ | ^
+ |newSource1.scala:18: error: non-trait class type required but JSTrait found
+ | val e = js.constructorOf[JSTrait]
+ | ^
+ |newSource1.scala:19: error: ${singletonPrefix}class type required but JSObject.type found
+ | val f = js.constructorOf[JSObject.type]
+ | ^
+ |newSource1.scala:21: error: class type required but JSClass with JSTrait found
+ | val g = js.constructorOf[JSClass with JSTrait]
+ | ^
+ |newSource1.scala:22: error: class type required but JSClass{def bar: Int} found
+ | val h = js.constructorOf[JSClass { def bar: Int }]
+ | ^
+ |newSource1.scala:24: error: class type required but A found
+ | def foo[A <: js.Any] = js.constructorOf[A]
+ | ^
+ |newSource1.scala:25: error: class type required but A found
+ | def bar[A <: js.Any: scala.reflect.ClassTag] = js.constructorOf[A]
+ | ^
+ """
+
+ }
+
+ @Test
+ def jsConstructorTagErrors(): Unit = {
+
+ """
+ class ScalaClass
+ trait ScalaTrait
+ object ScalaObject
+
+ object A {
+ val a = js.constructorTag[ScalaClass]
+ val b = js.constructorTag[ScalaTrait]
+ val c = js.constructorTag[ScalaObject.type]
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: type arguments [ScalaClass] do not conform to method constructorTag's type parameter bounds [T <: scala.scalajs.js.Any]
+ | val a = js.constructorTag[ScalaClass]
+ | ^
+ |newSource1.scala:9: error: type arguments [ScalaTrait] do not conform to method constructorTag's type parameter bounds [T <: scala.scalajs.js.Any]
+ | val b = js.constructorTag[ScalaTrait]
+ | ^
+ |newSource1.scala:10: error: type arguments [ScalaObject.type] do not conform to method constructorTag's type parameter bounds [T <: scala.scalajs.js.Any]
+ | val c = js.constructorTag[ScalaObject.type]
+ | ^
+ """
+
+ val singletonPrefix =
+ if (allowsSingletonClassOf) "non-trait "
+ else ""
+
+ """
+ @js.native @JSGlobal class NativeJSClass extends js.Object
+ @js.native trait NativeJSTrait extends js.Object
+ @js.native @JSGlobal object NativeJSObject extends js.Object
+
+ class JSClass extends js.Object
+ trait JSTrait extends js.Object
+ object JSObject extends js.Object
+
+ object A {
+ val a = js.constructorTag[NativeJSTrait]
+ val b = js.constructorTag[NativeJSObject.type]
+
+ val c = js.constructorTag[NativeJSClass with NativeJSTrait]
+ val d = js.constructorTag[NativeJSClass { def bar: Int }]
+
+ val e = js.constructorTag[JSTrait]
+ val f = js.constructorTag[JSObject.type]
+
+ val g = js.constructorTag[JSClass with JSTrait]
+ val h = js.constructorTag[JSClass { def bar: Int }]
+
+ def foo[A <: js.Any] = js.constructorTag[A]
+ def bar[A <: js.Any: scala.reflect.ClassTag] = js.constructorTag[A]
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:12: error: non-trait class type required but NativeJSTrait found
+ | val a = js.constructorTag[NativeJSTrait]
+ | ^
+ |newSource1.scala:13: error: ${singletonPrefix}class type required but NativeJSObject.type found
+ | val b = js.constructorTag[NativeJSObject.type]
+ | ^
+ |newSource1.scala:15: error: class type required but NativeJSClass with NativeJSTrait found
+ | val c = js.constructorTag[NativeJSClass with NativeJSTrait]
+ | ^
+ |newSource1.scala:16: error: class type required but NativeJSClass{def bar: Int} found
+ | val d = js.constructorTag[NativeJSClass { def bar: Int }]
+ | ^
+ |newSource1.scala:18: error: non-trait class type required but JSTrait found
+ | val e = js.constructorTag[JSTrait]
+ | ^
+ |newSource1.scala:19: error: ${singletonPrefix}class type required but JSObject.type found
+ | val f = js.constructorTag[JSObject.type]
+ | ^
+ |newSource1.scala:21: error: class type required but JSClass with JSTrait found
+ | val g = js.constructorTag[JSClass with JSTrait]
+ | ^
+ |newSource1.scala:22: error: class type required but JSClass{def bar: Int} found
+ | val h = js.constructorTag[JSClass { def bar: Int }]
+ | ^
+ |newSource1.scala:24: error: class type required but A found
+ | def foo[A <: js.Any] = js.constructorTag[A]
+ | ^
+ |newSource1.scala:25: error: class type required but A found
+ | def bar[A <: js.Any: scala.reflect.ClassTag] = js.constructorTag[A]
+ | ^
+ """
+
+ }
+
+ @Test
+ def runtimeConstructorOfErrorsDisallowedSingletonTypes(): Unit = {
+ assumeTrue(!allowsSingletonClassOf)
+
+ """
+ import scala.scalajs.runtime
+
+ object ScalaObject
+ @js.native @JSGlobal object NativeJSObject extends js.Object
+ object JSObject extends js.Object
+
+ object A {
+ val a = runtime.constructorOf(classOf[ScalaObject.type].asInstanceOf[Class[_ <: js.Any]])
+ val b = runtime.constructorOf(classOf[NativeJSObject.type])
+ val c = runtime.constructorOf(classOf[JSObject.type])
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: class type required but ScalaObject.type found
+ | val a = runtime.constructorOf(classOf[ScalaObject.type].asInstanceOf[Class[_ <: js.Any]])
+ | ^
+ |newSource1.scala:11: error: class type required but NativeJSObject.type found
+ | val b = runtime.constructorOf(classOf[NativeJSObject.type])
+ | ^
+ |newSource1.scala:12: error: class type required but JSObject.type found
+ | val c = runtime.constructorOf(classOf[JSObject.type])
+ | ^
+ """
+
+ }
+
+ @Test
+ def runtimeConstructorOfErrorsAllowedSingletonTypes(): Unit = {
+ assumeTrue(allowsSingletonClassOf)
+
+ """
+ import scala.scalajs.runtime
+
+ object ScalaObject
+ @js.native @JSGlobal object NativeJSObject extends js.Object
+ object JSObject extends js.Object
+
+ object A {
+ val a = runtime.constructorOf(classOf[ScalaObject.type].asInstanceOf[Class[_ <: js.Any]])
+ val b = runtime.constructorOf(classOf[NativeJSObject.type])
+ val c = runtime.constructorOf(classOf[JSObject.type])
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: constructorOf must be called with a constant classOf[T] representing a class extending js.Any (not a trait nor an object)
+ | val a = runtime.constructorOf(classOf[ScalaObject.type].asInstanceOf[Class[_ <: js.Any]])
+ | ^
+ |newSource1.scala:11: error: constructorOf must be called with a constant classOf[T] representing a class extending js.Any (not a trait nor an object)
+ | val b = runtime.constructorOf(classOf[NativeJSObject.type])
+ | ^
+ |newSource1.scala:12: error: constructorOf must be called with a constant classOf[T] representing a class extending js.Any (not a trait nor an object)
+ | val c = runtime.constructorOf(classOf[JSObject.type])
+ | ^
+ """
+
+ }
+
+ @Test
+ def runtimeConstructorOfErrors(): Unit = {
+
+ """
+ import scala.scalajs.runtime
+
+ class ScalaClass
+ trait ScalaTrait
+
+ @js.native @JSGlobal class NativeJSClass extends js.Object
+ @js.native trait NativeJSTrait extends js.Object
+ @js.native @JSGlobal object NativeJSObject extends js.Object
+
+ class JSClass extends js.Object
+ trait JSTrait extends js.Object
+ object JSObject extends js.Object
+
+ object A {
+ val a = runtime.constructorOf(classOf[ScalaClass].asInstanceOf[Class[_ <: js.Any]])
+ val b = runtime.constructorOf(classOf[ScalaTrait].asInstanceOf[Class[_ <: js.Any]])
+
+ val c = runtime.constructorOf(classOf[NativeJSTrait])
+ val d = runtime.constructorOf(classOf[JSTrait])
+
+ def jsClassClass = classOf[JSClass]
+ val e = runtime.constructorOf(jsClassClass)
+
+ val f = runtime.constructorOf(NativeJSObject.getClass)
+ val g = runtime.constructorOf(JSObject.getClass)
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:17: error: constructorOf must be called with a constant classOf[T] representing a class extending js.Any (not a trait nor an object)
+ | val a = runtime.constructorOf(classOf[ScalaClass].asInstanceOf[Class[_ <: js.Any]])
+ | ^
+ |newSource1.scala:18: error: constructorOf must be called with a constant classOf[T] representing a class extending js.Any (not a trait nor an object)
+ | val b = runtime.constructorOf(classOf[ScalaTrait].asInstanceOf[Class[_ <: js.Any]])
+ | ^
+ |newSource1.scala:20: error: constructorOf must be called with a constant classOf[T] representing a class extending js.Any (not a trait nor an object)
+ | val c = runtime.constructorOf(classOf[NativeJSTrait])
+ | ^
+ |newSource1.scala:21: error: constructorOf must be called with a constant classOf[T] representing a class extending js.Any (not a trait nor an object)
+ | val d = runtime.constructorOf(classOf[JSTrait])
+ | ^
+ |newSource1.scala:24: error: constructorOf must be called with a constant classOf[T] representing a class extending js.Any (not a trait nor an object)
+ | val e = runtime.constructorOf(jsClassClass)
+ | ^
+ |newSource1.scala:26: error: constructorOf must be called with a constant classOf[T] representing a class extending js.Any (not a trait nor an object)
+ | val f = runtime.constructorOf(NativeJSObject.getClass)
+ | ^
+ |newSource1.scala:27: error: constructorOf must be called with a constant classOf[T] representing a class extending js.Any (not a trait nor an object)
+ | val g = runtime.constructorOf(JSObject.getClass)
+ | ^
+ """
+
+ }
+
+ @Test
+ def veryLongStringLiteral(): Unit = {
+ // Create a string whose length is greater than 65,635 bytes
+ val len = 70000
+ val charArray = new Array[Char](len)
+ java.util.Arrays.fill(charArray, 'A')
+ val veryLongString = new String(charArray)
+
+ s"""
+ object Foo {
+ val bar: String = "$veryLongString"
+ }
+ """ containsErrors
+ """
+ |error: Error while emitting newSource1.scala
+ |encoded string
+ """
+ // optionally followed by the string, then by " too long: 70000 bytes"
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/EnumerationInteropTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/EnumerationInteropTest.scala
new file mode 100644
index 0000000000..f930632797
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/EnumerationInteropTest.scala
@@ -0,0 +1,171 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+
+import org.junit.Test
+
+class EnumerationInteropTest extends DirectTest with TestHelpers {
+
+ @Test
+ def warnIfUnableToTransformValue(): Unit = {
+
+ """
+ class A extends Enumeration {
+ val a = {
+ println("oh, oh!")
+ Value
+ }
+ val b = {
+ println("oh, oh!")
+ Value(4)
+ }
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:5: warning: Couldn't transform call to Enumeration.Value.
+ |The resulting program is unlikely to function properly as this
+ |operation requires reflection.
+ | Value
+ | ^
+ |newSource1.scala:9: warning: Couldn't transform call to Enumeration.Value.
+ |The resulting program is unlikely to function properly as this
+ |operation requires reflection.
+ | Value(4)
+ | ^
+ """
+
+ }
+
+ @Test
+ def warnIfNoNameVal(): Unit = {
+
+ """
+ class A extends Enumeration {
+ val a = new Val
+ val b = new Val(10)
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Calls to the non-string constructors of Enumeration.Val
+ |require reflection at runtime. The resulting
+ |program is unlikely to function properly.
+ | val a = new Val
+ | ^
+ |newSource1.scala:4: warning: Calls to the non-string constructors of Enumeration.Val
+ |require reflection at runtime. The resulting
+ |program is unlikely to function properly.
+ | val b = new Val(10)
+ | ^
+ """
+
+ }
+
+ @Test
+ def warnIfNullValue(): Unit = {
+
+ """
+ class A extends Enumeration {
+ val a = Value(null)
+ val b = Value(10, null)
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Passing null as name to Enumeration.Value
+ |requires reflection at runtime. The resulting
+ |program is unlikely to function properly.
+ | val a = Value(null)
+ | ^
+ |newSource1.scala:4: warning: Passing null as name to Enumeration.Value
+ |requires reflection at runtime. The resulting
+ |program is unlikely to function properly.
+ | val b = Value(10, null)
+ | ^
+ """
+
+ }
+
+ @Test
+ def warnIfNullNewVal(): Unit = {
+
+ """
+ class A extends Enumeration {
+ val a = new Val(null)
+ val b = new Val(10, null)
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Passing null as name to a constructor of Enumeration.Val
+ |requires reflection at runtime. The resulting
+ |program is unlikely to function properly.
+ | val a = new Val(null)
+ | ^
+ |newSource1.scala:4: warning: Passing null as name to a constructor of Enumeration.Val
+ |requires reflection at runtime. The resulting
+ |program is unlikely to function properly.
+ | val b = new Val(10, null)
+ | ^
+ """
+
+ }
+
+ @Test
+ def warnIfExtNoNameVal(): Unit = {
+
+ """
+ class A extends Enumeration {
+ protected class Val1 extends Val
+ protected class Val2 extends Val(1)
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Calls to the non-string constructors of Enumeration.Val
+ |require reflection at runtime. The resulting
+ |program is unlikely to function properly.
+ | protected class Val1 extends Val
+ | ^
+ |newSource1.scala:4: warning: Calls to the non-string constructors of Enumeration.Val
+ |require reflection at runtime. The resulting
+ |program is unlikely to function properly.
+ | protected class Val2 extends Val(1)
+ | ^
+ """
+
+ }
+
+ @Test
+ def warnIfExtNullNameVal(): Unit = {
+
+ """
+ class A extends Enumeration {
+ protected class Val1 extends Val(null)
+ protected class Val2 extends Val(1,null)
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Passing null as name to a constructor of Enumeration.Val
+ |requires reflection at runtime. The resulting
+ |program is unlikely to function properly.
+ | protected class Val1 extends Val(null)
+ | ^
+ |newSource1.scala:4: warning: Passing null as name to a constructor of Enumeration.Val
+ |requires reflection at runtime. The resulting
+ |program is unlikely to function properly.
+ | protected class Val2 extends Val(1,null)
+ | ^
+ """
+
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextNoWarnTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextNoWarnTest.scala
new file mode 100644
index 0000000000..94decfd65a
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextNoWarnTest.scala
@@ -0,0 +1,47 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.scalajs.nscplugin.test.util.VersionDependentUtils.scalaSupportsNoWarn
+
+import org.junit.Assume._
+import org.junit.Test
+
+class GlobalExecutionContextNoWarnTest extends DirectTest with TestHelpers {
+
+ override def extraArgs: List[String] =
+ super.extraArgs ::: List("-P:scalajs:nowarnGlobalExecutionContext")
+
+ @Test
+ def noWarnOnUsage: Unit = {
+ """
+ import scala.concurrent.ExecutionContext.global
+
+ object Enclosing {
+ global
+ }
+ """.hasNoWarns()
+ }
+
+ @Test
+ def noWarnOnImplicitUsage: Unit = {
+ """
+ import scala.concurrent.ExecutionContext.Implicits.global
+
+ object Enclosing {
+ scala.concurrent.Future { }
+ }
+ """.hasNoWarns()
+ }
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextWarnTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextWarnTest.scala
new file mode 100644
index 0000000000..1fd1333eb1
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/GlobalExecutionContextWarnTest.scala
@@ -0,0 +1,122 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.scalajs.nscplugin.test.util.VersionDependentUtils.scalaSupportsNoWarn
+
+import org.junit.Assume._
+import org.junit.Test
+
+class GlobalExecutionContextWarnTest extends DirectTest with TestHelpers {
+
+ @Test
+ def warnOnUsage: Unit = {
+ """
+ import scala.concurrent.ExecutionContext.global
+
+ object Enclosing {
+ global
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:5: warning: The global execution context in Scala.js is based on JS Promises (microtasks).
+ |Using it may prevent macrotasks (I/O, timers, UI rendering) from running reliably.
+ |
+ |Unfortunately, there is no way with ECMAScript only to implement a performant
+ |macrotask execution context (and hence Scala.js core does not contain one).
+ |
+ |We recommend you use: https://github.com/scala-js/scala-js-macrotask-executor
+ |Please refer to the README.md of that project for more details regarding
+ |microtask vs. macrotask execution contexts.
+ |
+ |If you do not care about macrotask fairness, you can silence this warning by:
+ |- Adding @nowarn("cat=other") (Scala >= 2.13.x only)
+ |- Setting the -P:scalajs:nowarnGlobalExecutionContext compiler option (Scala < 3.x.y only)
+ |- Using scala.scalajs.concurrent.JSExecutionContext.queue
+ | (the implementation of ExecutionContext.global in Scala.js) directly.
+ |
+ |If you do not care about performance, you can use
+ |scala.scalajs.concurrent.QueueExecutionContext.timeouts().
+ |It is based on setTimeout which makes it fair but slow (due to clamping).
+ |
+ | global
+ | ^
+ """
+ }
+
+ @Test
+ def warnOnImplicitUsage: Unit = {
+ """
+ import scala.concurrent.ExecutionContext.Implicits.global
+
+ object Enclosing {
+ scala.concurrent.Future { }
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:5: warning: The global execution context in Scala.js is based on JS Promises (microtasks).
+ |Using it may prevent macrotasks (I/O, timers, UI rendering) from running reliably.
+ |
+ |Unfortunately, there is no way with ECMAScript only to implement a performant
+ |macrotask execution context (and hence Scala.js core does not contain one).
+ |
+ |We recommend you use: https://github.com/scala-js/scala-js-macrotask-executor
+ |Please refer to the README.md of that project for more details regarding
+ |microtask vs. macrotask execution contexts.
+ |
+ |If you do not care about macrotask fairness, you can silence this warning by:
+ |- Adding @nowarn("cat=other") (Scala >= 2.13.x only)
+ |- Setting the -P:scalajs:nowarnGlobalExecutionContext compiler option (Scala < 3.x.y only)
+ |- Using scala.scalajs.concurrent.JSExecutionContext.queue
+ | (the implementation of ExecutionContext.global in Scala.js) directly.
+ |
+ |If you do not care about performance, you can use
+ |scala.scalajs.concurrent.QueueExecutionContext.timeouts().
+ |It is based on setTimeout which makes it fair but slow (due to clamping).
+ |
+ | scala.concurrent.Future { }
+ | ^
+ """
+ }
+
+ @Test
+ def noWarnIfSelectivelyDisabled: Unit = {
+ assumeTrue(scalaSupportsNoWarn)
+
+ """
+ import scala.annotation.nowarn
+ import scala.concurrent.ExecutionContext.global
+
+ object Enclosing {
+ global: @nowarn("cat=other")
+ }
+ """.hasNoWarns()
+ }
+
+ @Test
+ def noWarnQueue: Unit = {
+ /* Test that JSExecutionContext.queue does not warn for good measure.
+ * We explicitly say it doesn't so we want to notice if it does.
+ */
+
+ """
+ import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
+
+ object Enclosing {
+ scala.concurrent.Future { }
+ }
+ """.hasNoWarns()
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/InternalAnnotationsTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/InternalAnnotationsTest.scala
new file mode 100644
index 0000000000..7e553d378f
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/InternalAnnotationsTest.scala
@@ -0,0 +1,83 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+
+import org.junit._
+
+// scalastyle:off line.size.limit
+
+class InternalAnnotationsTest extends DirectTest with TestHelpers {
+
+ override def preamble: String =
+ "import scala.scalajs.js, js.annotation._, js.annotation.internal._"
+
+ @Test
+ def exposedJSMember(): Unit = {
+ test("ExposedJSMember")
+ }
+
+ @Test
+ def jsType(): Unit = {
+ test("JSType")
+ }
+
+ @Test
+ def jsOptional(): Unit = {
+ test("JSOptional")
+ }
+
+ private def test(annotation: String): Unit =
+ test(annotation, s"scala.scalajs.js.annotation.internal.$annotation")
+
+ private def test(annotation: String, annotFullName: String): Unit = {
+ s"""
+ @$annotation trait A
+ @$annotation class B {
+ @$annotation val a = ???
+ @$annotation var b = ???
+ @$annotation def c = ???
+ def d(@$annotation i: Int) = ???
+ @$annotation class X
+ @$annotation trait Y
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:2: error: $annotFullName is for compiler internal use only. Do not use it yourself.
+ | @$annotation trait A
+ | ^
+ |newSource1.scala:3: error: $annotFullName is for compiler internal use only. Do not use it yourself.
+ | @$annotation class B {
+ | ^
+ |newSource1.scala:4: error: $annotFullName is for compiler internal use only. Do not use it yourself.
+ | @$annotation val a = ???
+ | ^
+ |newSource1.scala:5: error: $annotFullName is for compiler internal use only. Do not use it yourself.
+ | @$annotation var b = ???
+ | ^
+ |newSource1.scala:6: error: $annotFullName is for compiler internal use only. Do not use it yourself.
+ | @$annotation def c = ???
+ | ^
+ |newSource1.scala:7: error: $annotFullName is for compiler internal use only. Do not use it yourself.
+ | def d(@$annotation i: Int) = ???
+ | ^
+ |newSource1.scala:8: error: $annotFullName is for compiler internal use only. Do not use it yourself.
+ | @$annotation class X
+ | ^
+ |newSource1.scala:9: error: $annotFullName is for compiler internal use only. Do not use it yourself.
+ | @$annotation trait Y
+ | ^
+ """
+ }
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSAsyncAwaitTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSAsyncAwaitTest.scala
new file mode 100644
index 0000000000..d8147fad0a
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSAsyncAwaitTest.scala
@@ -0,0 +1,83 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.junit.Test
+
+// scalastyle:off line.size.limit
+
+class JSAsyncAwaitTest extends DirectTest with TestHelpers {
+
+ override def preamble: String =
+ """import scala.scalajs.js
+ """
+
+ @Test
+ def orphanAwait(): Unit = {
+ """
+ class A {
+ def foo(x: js.Promise[Int]): Int =
+ js.await(x)
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Illegal use of js.await().
+ |It can only be used inside a js.async {...} block, without any lambda,
+ |by-name argument or nested method in-between.
+ |If you compile for WebAssembly, you can allow arbitrary js.await()
+ |calls by adding the following import:
+ |import scala.scalajs.js.wasm.JSPI.allowOrphanJSAwait
+ | js.await(x)
+ | ^
+ """
+
+ """
+ class A {
+ def foo(x: js.Promise[Int]): js.Promise[Int] = js.async {
+ val f: () => Int = () => js.await(x)
+ f()
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Illegal use of js.await().
+ |It can only be used inside a js.async {...} block, without any lambda,
+ |by-name argument or nested method in-between.
+ |If you compile for WebAssembly, you can allow arbitrary js.await()
+ |calls by adding the following import:
+ |import scala.scalajs.js.wasm.JSPI.allowOrphanJSAwait
+ | val f: () => Int = () => js.await(x)
+ | ^
+ """
+
+ """
+ class A {
+ def foo(x: js.Promise[Int]): js.Promise[Int] = js.async {
+ def f(): Int = js.await(x)
+ f()
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Illegal use of js.await().
+ |It can only be used inside a js.async {...} block, without any lambda,
+ |by-name argument or nested method in-between.
+ |If you compile for WebAssembly, you can allow arbitrary js.await()
+ |calls by adding the following import:
+ |import scala.scalajs.js.wasm.JSPI.allowOrphanJSAwait
+ | def f(): Int = js.await(x)
+ | ^
+ """
+ }
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSDynamicLiteralTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSDynamicLiteralTest.scala
new file mode 100644
index 0000000000..df493ce0ff
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSDynamicLiteralTest.scala
@@ -0,0 +1,284 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.junit.Test
+
+// scalastyle:off line.size.limit
+
+class JSDynamicLiteralTest extends DirectTest with TestHelpers {
+
+ override def preamble: String =
+ """import scala.scalajs.js.Dynamic.{ literal => lit }
+ """
+
+ @Test
+ def callApplyOnly(): Unit = {
+
+ // selectDynamic (with any name)
+ expr"""
+ lit.helloWorld
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: value selectDynamic is not a member of object scalajs.js.Dynamic.literal
+ |error after rewriting to scala.scalajs.js.Dynamic.literal.("helloWorld")
+ |possible cause: maybe a wrong Dynamic method signature?
+ | lit.helloWorld
+ | ^
+ """
+
+ // applyDynamicNamed with wrong method name
+ expr"""
+ lit.helloWorld(a = "a")
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: js.Dynamic.literal does not have a method named helloWorld
+ | lit.helloWorld(a = "a")
+ | ^
+ """
+
+ // applyDynamic with wrong method name
+ expr"""
+ lit.helloWorld("a" -> "a")
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: js.Dynamic.literal does not have a method named helloWorld
+ | lit.helloWorld("a" -> "a")
+ | ^
+ """
+
+ }
+
+ @Test
+ def goodTypesOnly(): Unit = {
+
+ // Bad value type (applyDynamic)
+ """
+ class A {
+ val x = new Object()
+ def foo = lit("a" -> x)
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: type mismatch;
+ | found : Object
+ | required: scala.scalajs.js.Any
+ | def foo = lit("a" -> x)
+ | ^
+ """
+
+ // Bad key type (applyDynamic)
+ """
+ class A {
+ val x = Seq()
+ def foo = lit(x -> "a")
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: type mismatch;
+ | found : (Seq[Nothing], String)
+ | required: (String, scala.scalajs.js.Any)
+ | def foo = lit(x -> "a")
+ | ^
+ """
+
+ // Bad value type (applyDynamicNamed)
+ """
+ class A {
+ val x = new Object()
+ def foo = lit(a = x)
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: type mismatch;
+ | found : Object
+ | required: scala.scalajs.js.Any
+ |error after rewriting to scala.scalajs.js.Dynamic.literal.applyDynamicNamed("apply")(scala.Tuple2("a", x))
+ |possible cause: maybe a wrong Dynamic method signature?
+ | def foo = lit(a = x)
+ | ^
+ """
+
+ }
+
+ @Test
+ def noNonLiteralMethodName(): Unit = {
+
+ // applyDynamicNamed
+ """
+ class A {
+ val x = "string"
+ def foo = lit.applyDynamicNamed(x)()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: js.Dynamic.literal.applyDynamicNamed may not be called directly
+ | def foo = lit.applyDynamicNamed(x)()
+ | ^
+ """
+
+ // applyDynamic
+ """
+ class A {
+ val x = "string"
+ def foo = lit.applyDynamic(x)()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: js.Dynamic.literal.applyDynamic may not be called directly
+ | def foo = lit.applyDynamic(x)()
+ | ^
+ """
+
+ }
+
+ @Test
+ def keyDuplicationWarning(): Unit = {
+ // detects duplicate named keys
+ expr"""
+ lit(a = "1", b = "2", a = "3")
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Duplicate property "a" shadows a previously defined one
+ | lit(a = "1", b = "2", a = "3")
+ | ^
+ """
+
+ // detects duplicate named keys
+ expr"""
+ lit(aaa = "1", b = "2", aaa = "3")
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Duplicate property "aaa" shadows a previously defined one
+ | lit(aaa = "1", b = "2", aaa = "3")
+ | ^
+ """
+
+ // detects duplicate named keys
+ expr"""
+ lit(aaa = "1",
+ bb = "2",
+ bb = "3")
+ """ hasWarns
+ """
+ |newSource1.scala:5: warning: Duplicate property "bb" shadows a previously defined one
+ | bb = "3")
+ | ^
+ """
+
+ // detects duplicate named keys
+ expr"""
+ lit(aaa = "1",
+ b = "2",
+ aaa = "3")
+ """ hasWarns
+ """
+ |newSource1.scala:5: warning: Duplicate property "aaa" shadows a previously defined one
+ | aaa = "3")
+ | ^
+ """
+
+ // detects triplicated named keys
+ expr"""
+ lit(a = "1", a = "2", a = "3")
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Duplicate property "a" shadows a previously defined one
+ | lit(a = "1", a = "2", a = "3")
+ | ^
+ |newSource1.scala:3: warning: Duplicate property "a" shadows a previously defined one
+ | lit(a = "1", a = "2", a = "3")
+ | ^
+ """
+
+ // detects two different duplicates named keys
+ expr"""
+ lit(a = "1", b = "2", a = "3", b = "4", c = "5", c = "6", c = "7")
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Duplicate property "a" shadows a previously defined one
+ | lit(a = "1", b = "2", a = "3", b = "4", c = "5", c = "6", c = "7")
+ | ^
+ |newSource1.scala:3: warning: Duplicate property "b" shadows a previously defined one
+ | lit(a = "1", b = "2", a = "3", b = "4", c = "5", c = "6", c = "7")
+ | ^
+ |newSource1.scala:3: warning: Duplicate property "c" shadows a previously defined one
+ | lit(a = "1", b = "2", a = "3", b = "4", c = "5", c = "6", c = "7")
+ | ^
+ |newSource1.scala:3: warning: Duplicate property "c" shadows a previously defined one
+ | lit(a = "1", b = "2", a = "3", b = "4", c = "5", c = "6", c = "7")
+ | ^
+ """
+
+ // detects duplicate keys when represented with arrows
+ expr"""
+ lit("a" -> "1", "b" -> "2", "a" -> "3")
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Duplicate property "a" shadows a previously defined one
+ | lit("a" -> "1", "b" -> "2", "a" -> "3")
+ | ^
+ """
+
+ // detects duplicate keys when represented with tuples
+ expr"""
+ lit(("a", "1"), ("b", "2"), ("a", "3"))
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Duplicate property "a" shadows a previously defined one
+ | lit(("a", "1"), ("b", "2"), ("a", "3"))
+ | ^
+ """
+
+ // detects duplicate keys when represented with mixed tuples and arrows
+ expr"""
+ lit("a" -> "1", ("b", "2"), ("a", "3"))
+ """ hasWarns
+ """
+ |newSource1.scala:3: warning: Duplicate property "a" shadows a previously defined one
+ | lit("a" -> "1", ("b", "2"), ("a", "3"))
+ | ^
+ """
+
+ // should not warn if the key is not literal
+ expr"""
+ val a = "x"
+ lit("a" -> "1", a -> "2", a -> "3")
+ """.hasNoWarns()
+
+ // should not warn if the key/value pairs are not literal
+ """
+ class A {
+ val tup = "x" -> lit()
+ def foo = lit(tup, tup)
+ }
+ """.hasNoWarns()
+
+ // should warn only for the literal keys when in
+ // the presence of non literal keys
+ """
+ class A {
+ val b = "b"
+ val tup = b -> lit()
+ lit("a" -> "2", tup, ("a", "3"), b -> "5", tup, b -> "6")
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:6: warning: Duplicate property "a" shadows a previously defined one
+ | lit("a" -> "2", tup, ("a", "3"), b -> "5", tup, b -> "6")
+ | ^
+ """
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportASTTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportASTTest.scala
new file mode 100644
index 0000000000..01fe141a4a
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportASTTest.scala
@@ -0,0 +1,43 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import util._
+
+import org.junit.Test
+import org.junit.Assert._
+
+import org.scalajs.ir.{Trees => js}
+
+class JSExportASTTest extends JSASTTest {
+
+ @Test
+ def inheritExportMethods(): Unit = {
+ """
+ import scala.scalajs.js.annotation.JSExport
+
+ class A {
+ @JSExport
+ def foo = 1
+ }
+
+ class B extends A {
+ @JSExport
+ override def foo = 2
+ }
+ """.hasExactly(1, "definitions of property `foo`") {
+ case js.JSPropertyDef(_, js.StringLiteral("foo"), _, _) =>
+ }
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala
new file mode 100644
index 0000000000..234d3a1bb6
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala
@@ -0,0 +1,1967 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.scalajs.nscplugin.test.util.VersionDependentUtils.methodSig
+
+import org.junit.Assume._
+import org.junit.Test
+
+// scalastyle:off line.size.limit
+
+class JSExportTest extends DirectTest with TestHelpers {
+
+ override def extraArgs: List[String] =
+ super.extraArgs ::: List("-deprecation")
+
+ override def preamble: String =
+ """import scala.scalajs.js, js.annotation._
+ """
+
+ @Test
+ def warnOnDuplicateExport(): Unit = {
+ """
+ class A {
+ @JSExport
+ @JSExport
+ def a = 1
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:6: warning: Found duplicate @JSExport
+ | def a = 1
+ | ^
+ """
+
+ """
+ class A {
+ @JSExport
+ @JSExport("a")
+ def a = 1
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:6: warning: Found duplicate @JSExport
+ | def a = 1
+ | ^
+ """
+
+ """
+ class A {
+ @JSExport("a")
+ @JSExport("a")
+ def a = 1
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:6: warning: Found duplicate @JSExport
+ | def a = 1
+ | ^
+ """
+
+ // special case for @JSExportAll and 2 or more @JSExport("apply")
+ // since @JSExportAll and single @JSExport("apply") should not be warned (see other tests)
+ """
+ @JSExportAll
+ class A {
+ @JSExport("apply")
+ @JSExport("apply")
+ def apply(): Int = 1
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:7: warning: Found duplicate @JSExport
+ | def apply(): Int = 1
+ | ^
+ """
+
+ """
+ @JSExportAll
+ class A {
+ @JSExport
+ def a = 1
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:6: warning: Found duplicate @JSExport
+ | def a = 1
+ | ^
+ """
+ }
+
+ @Test
+ def noWarnOnUniqueExplicitName(): Unit = {
+ """
+ class A {
+ @JSExport("a")
+ @JSExport("b")
+ def c = 1
+ }
+ """.hasNoWarns()
+ }
+
+ @Test
+ def noJSExportClass(): Unit = {
+ """
+ @JSExport
+ class A
+
+ @JSExport("Foo")
+ class B
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: @JSExport is forbidden on top-level objects and classes. Use @JSExportTopLevel instead.
+ | @JSExport
+ | ^
+ |newSource1.scala:6: error: @JSExport is forbidden on top-level objects and classes. Use @JSExportTopLevel instead.
+ | @JSExport("Foo")
+ | ^
+ """
+ }
+
+ @Test
+ def noJSExportObject(): Unit = {
+ """
+ @JSExport
+ object A
+
+ @JSExport("Foo")
+ object B
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: @JSExport is forbidden on top-level objects and classes. Use @JSExportTopLevel instead.
+ | @JSExport
+ | ^
+ |newSource1.scala:6: error: @JSExport is forbidden on top-level objects and classes. Use @JSExportTopLevel instead.
+ | @JSExport("Foo")
+ | ^
+ """
+ }
+
+ @Test
+ def noDoubleUnderscoreExport(): Unit = {
+ """
+ class A {
+ @JSExport(name = "__")
+ def foo = 1
+
+ @JSExport
+ def bar__(x: Int) = x
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: An exported name may not contain a double underscore (`__`)
+ | @JSExport(name = "__")
+ | ^
+ |newSource1.scala:8: error: An exported name may not contain a double underscore (`__`)
+ | def bar__(x: Int) = x
+ | ^
+ """
+ }
+
+ @Test
+ def doubleUnderscoreOKInTopLevelExport(): Unit = {
+ """
+ @JSExportTopLevel("__A")
+ class A
+
+ @JSExportTopLevel("__B")
+ object B
+
+ object Container {
+ @JSExportTopLevel("__c")
+ def c(): Int = 4
+
+ @JSExportTopLevel("__d")
+ val d: Boolean = true
+ }
+ """.hasNoWarns()
+ }
+
+ @Test
+ def noConflictingExport(): Unit = {
+ """
+ class Confl {
+ @JSExport("value")
+ def hello = "foo"
+
+ @JSExport("value")
+ def world = "bar"
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: double definition:
+ |def $js$exported$prop$value: Any at line 4 and
+ |def $js$exported$prop$value: Any at line 7
+ |have same type
+ | @JSExport("value")
+ | ^
+ """
+
+ """
+ class Confl {
+ class Box[T](val x: T)
+
+ @JSExport
+ def ub(x: Box[String]): String = x.x
+ @JSExport
+ def ub(x: Box[Int]): Int = x.x
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:8: error: double definition:
+ |def ${"$js$exported$meth$ub"}(x: Confl.this.Box[String]): Any at line 6 and
+ |def ${"$js$exported$meth$ub"}(x: Confl.this.Box[Int]): Any at line 8
+ |have same type after erasure: ${methodSig("(x: Confl#Box)", "Object")}
+ | @JSExport
+ | ^
+ """
+
+ """
+ class Confl {
+ @JSExport
+ def rtType(x: js.Any) = x
+
+ @JSExport
+ def rtType(x: js.Dynamic) = x
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:7: error: Cannot disambiguate overloads for exported method rtType with types
+ | ${methodSig("(x: scala.scalajs.js.Any)", "Object")}
+ | ${methodSig("(x: scala.scalajs.js.Dynamic)", "Object")}
+ | @JSExport
+ | ^
+ """
+
+ """
+ class Confl {
+ @JSExport
+ def foo(x: Int)(ys: Int*) = x
+
+ @JSExport
+ def foo(x: Int*) = x
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:7: error: Cannot disambiguate overloads for exported method foo with types
+ | ${methodSig("(x: Int, ys: Seq)", "Object")}
+ | ${methodSig("(x: Seq)", "Object")}
+ | @JSExport
+ | ^
+ """
+
+ """
+ class Confl {
+ @JSExport
+ def foo(x: Int = 1) = x
+ @JSExport
+ def foo(x: String*) = x
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:6: error: Cannot disambiguate overloads for exported method foo with types
+ | ${methodSig("(x: Int)", "Object")}
+ | ${methodSig("(x: Seq)", "Object")}
+ | @JSExport
+ | ^
+ """
+
+ """
+ class Confl {
+ @JSExport
+ def foo(x: Double, y: String)(z: Int = 1) = x
+ @JSExport
+ def foo(x: Double, y: String)(z: String*) = x
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:6: error: Cannot disambiguate overloads for exported method foo with types
+ | ${methodSig("(x: Double, y: String, z: Int)", "Object")}
+ | ${methodSig("(x: Double, y: String, z: Seq)", "Object")}
+ | @JSExport
+ | ^
+ """
+
+ """
+ class A {
+ @JSExport
+ def a(x: scala.scalajs.js.Any) = 1
+
+ @JSExport
+ def a(x: Any) = 2
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:7: error: Cannot disambiguate overloads for exported method a with types
+ | ${methodSig("(x: Object)", "Object")}
+ | ${methodSig("(x: scala.scalajs.js.Any)", "Object")}
+ | @JSExport
+ | ^
+ """
+
+ }
+
+ @Test
+ def noExportLocal(): Unit = {
+ // Local class
+ """
+ class A {
+ def method = {
+ @JSExport
+ class A
+
+ @JSExport
+ class B extends js.Object
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export constructors of local classes
+ | @JSExport
+ | ^
+ |newSource1.scala:8: error: You may not export a local definition
+ | @JSExport
+ | ^
+ """
+
+ // Local object
+ """
+ class A {
+ def method = {
+ @JSExport
+ object A
+
+ @JSExport
+ object B extends js.Object
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export a local definition
+ | @JSExport
+ | ^
+ |newSource1.scala:8: error: You may not export a local definition
+ | @JSExport
+ | ^
+ """
+
+ // Local method
+ """
+ class A {
+ def method = {
+ @JSExport
+ def foo = 1
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export a local definition
+ | @JSExport
+ | ^
+ """
+
+ // Local val
+ """
+ class A {
+ def method = {
+ @JSExport
+ val x = 1
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export a local definition
+ | @JSExport
+ | ^
+ """
+
+ // Local var
+ """
+ class A {
+ def method = {
+ @JSExport
+ var x = 1
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export a local definition
+ | @JSExport
+ | ^
+ """
+
+ }
+
+ @Test
+ def noMiddleVarArg(): Unit = {
+
+ """
+ class A {
+ @JSExport
+ def method(xs: Int*)(ys: String) = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: In an exported method or constructor, a *-parameter must come last (through all parameter lists)
+ | @JSExport
+ | ^
+ """
+
+ }
+
+ @Test
+ def noMiddleDefaultParam(): Unit = {
+
+ """
+ class A {
+ @JSExport
+ def method(x: Int = 1)(y: String) = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: In an exported method or constructor, all parameters with defaults must be at the end
+ | @JSExport
+ | ^
+ """
+
+ }
+
+ @Test
+ def noExportAbstractClass(): Unit = {
+
+ """
+ object Foo {
+ @JSExport
+ abstract class A
+
+ abstract class B(x: Int) {
+ @JSExport
+ def this() = this(5)
+ }
+
+ @JSExport // ok!
+ abstract class C extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: You may not export an abstract class
+ | @JSExport
+ | ^
+ |newSource1.scala:8: error: You may not export an abstract class
+ | @JSExport
+ | ^
+ """
+
+ """
+ @JSExportTopLevel("A")
+ abstract class A
+
+ abstract class B(x: Int) {
+ @JSExportTopLevel("B")
+ def this() = this(5)
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: You may not export an abstract class
+ | @JSExportTopLevel("A")
+ | ^
+ |newSource1.scala:7: error: You may not export an abstract class
+ | @JSExportTopLevel("B")
+ | ^
+ """
+
+ }
+
+ @Test
+ def noJSExportOnTrait(): Unit = {
+
+ """
+ @JSExport
+ trait Test
+
+ @JSExport
+ trait Test2 extends js.Object
+
+ @JSExport
+ @js.native
+ trait Test3 extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: You may not export a trait
+ | @JSExport
+ | ^
+ |newSource1.scala:6: error: You may not export a trait
+ | @JSExport
+ | ^
+ |newSource1.scala:9: error: You may not export a trait
+ | @JSExport
+ | ^
+ """
+
+ """
+ object A {
+ @JSExport
+ trait Test
+
+ @JSExport
+ trait Test2 extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: You may not export a trait
+ | @JSExport
+ | ^
+ |newSource1.scala:7: error: You may not export a trait
+ | @JSExport
+ | ^
+ """
+
+ }
+
+ @Test
+ def noExportNonPublicClassOrObject(): Unit = {
+
+ """
+ @JSExportTopLevel("A")
+ private class A
+
+ @JSExportTopLevel("B")
+ protected[this] class B
+
+ @JSExportTopLevel("C")
+ private class C extends js.Object
+
+ @JSExportTopLevel("D")
+ protected[this] class D extends js.Object
+
+ private class E(x: Int) {
+ @JSExportTopLevel("E")
+ def this() = this(1)
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: You may only export constructors of public and protected classes
+ | @JSExportTopLevel("A")
+ | ^
+ |newSource1.scala:6: error: You may only export constructors of public and protected classes
+ | @JSExportTopLevel("B")
+ | ^
+ |newSource1.scala:9: error: You may only export public and protected definitions
+ | @JSExportTopLevel("C")
+ | ^
+ |newSource1.scala:12: error: You may only export public and protected definitions
+ | @JSExportTopLevel("D")
+ | ^
+ |newSource1.scala:16: error: You may only export constructors of public and protected classes
+ | @JSExportTopLevel("E")
+ | ^
+ """
+
+ """
+ @JSExportTopLevel("A")
+ private object A
+
+ @JSExportTopLevel("B")
+ protected[this] object B
+
+ @JSExportTopLevel("C")
+ private object C extends js.Object
+
+ @JSExportTopLevel("D")
+ protected[this] object D extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: You may only export public and protected definitions
+ | @JSExportTopLevel("A")
+ | ^
+ |newSource1.scala:6: error: You may only export public and protected definitions
+ | @JSExportTopLevel("B")
+ | ^
+ |newSource1.scala:9: error: You may only export public and protected definitions
+ | @JSExportTopLevel("C")
+ | ^
+ |newSource1.scala:12: error: You may only export public and protected definitions
+ | @JSExportTopLevel("D")
+ | ^
+ """
+
+ }
+
+ @Test
+ def noExportNonPublicMember(): Unit = {
+
+ """
+ class A {
+ @JSExport
+ private def foo = 1
+
+ @JSExport
+ protected[this] def bar = 2
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: You may only export public and protected definitions
+ | @JSExport
+ | ^
+ |newSource1.scala:7: error: You may only export public and protected definitions
+ | @JSExport
+ | ^
+ """
+
+ }
+
+ @Test
+ def noExportTopLevelNestedObject(): Unit = {
+
+ """
+ class A {
+ @JSExportTopLevel("Nested")
+ object Nested
+
+ @JSExportTopLevel("Nested2")
+ object Nested2 extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Only static objects may export their members to the top level
+ | @JSExportTopLevel("Nested")
+ | ^
+ |newSource1.scala:7: error: Only static objects may export their members to the top level
+ | @JSExportTopLevel("Nested2")
+ | ^
+ """
+
+ }
+
+ @Test
+ def noExportJSNative(): Unit = {
+
+ """
+ import scala.scalajs.js
+
+ @JSExportTopLevel("A")
+ @js.native
+ @JSGlobal("Dummy")
+ object A extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export a native JS definition
+ | @JSExportTopLevel("A")
+ | ^
+ """
+
+ """
+ import scala.scalajs.js
+
+ @JSExportTopLevel("A")
+ @js.native
+ trait A extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export a trait
+ | @JSExportTopLevel("A")
+ | ^
+ """
+
+ """
+ import scala.scalajs.js
+
+ @JSExportTopLevel("A")
+ @js.native
+ @JSGlobal("Dummy")
+ class A extends js.Object {
+ @JSExportTopLevel("A")
+ def this(x: Int) = this()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export a native JS definition
+ | @JSExportTopLevel("A")
+ | ^
+ |newSource1.scala:9: error: You may not export a member of a subclass of js.Any
+ | @JSExportTopLevel("A")
+ | ^
+ """
+
+ """
+ import scala.scalajs.js
+
+ object A {
+ @JSExport("A")
+ @js.native
+ @JSGlobal("a")
+ def a(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: You may not export a native JS definition
+ | @JSExport("A")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportJSMember(): Unit = {
+
+ """
+ import scala.scalajs.js
+
+ @js.native
+ @JSGlobal("Dummy")
+ class A extends js.Object {
+ @JSExport
+ def foo: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: You may not export a member of a subclass of js.Any
+ | @JSExport
+ | ^
+ """
+
+ """
+ import scala.scalajs.js
+
+ class A extends js.Object {
+ @JSExport
+ def foo: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: You may not export a member of a subclass of js.Any
+ | @JSExport
+ | ^
+ """
+
+ }
+
+ @Test
+ def noBadSetterType(): Unit = {
+
+ // Bad param list
+ """
+ class A {
+ @JSExport
+ def foo_=(x: Int, y: Int) = ()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Exported setters must have exactly one argument
+ | @JSExport
+ | ^
+ """
+
+ // Bad return type
+ """
+ class A {
+ @JSExport
+ def foo_=(x: Int) = "string"
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Exported setters must return Unit
+ | @JSExport
+ | ^
+ """
+
+ // Varargs
+ """
+ class A {
+ @JSExport
+ def foo_=(x: Int*) = ()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Exported setters may not have repeated params
+ | @JSExport
+ | ^
+ """
+
+ // Default arguments
+ """
+ class A {
+ @JSExport
+ def foo_=(x: Int = 1) = ()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Exported setters may not have default params
+ | @JSExport
+ | ^
+ """
+
+ }
+
+ @Test
+ def noNonSetterNameForNonSetter(): Unit = {
+
+ """
+ class A {
+ @JSExport
+ class A_=
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: You must set an explicit name when exporting a non-setter with a name ending in _=
+ | @JSExport
+ | ^
+ """
+
+ """
+ class A {
+ @JSExport
+ object A_=
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: You must set an explicit name when exporting a non-setter with a name ending in _=
+ | @JSExport
+ | ^
+ """
+
+ // Not a Scala.js error, but we check it anyways to complete the test suite.
+ """
+ class A {
+ @JSExport
+ val A_= = 1
+ }
+ """.fails() // error is different on 2.12 / 2.13
+
+ }
+
+ @Test
+ def noBadToStringExport(): Unit = {
+
+ """
+ class A {
+ @JSExport("toString")
+ def a(): Int = 5
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: You may not export a zero-argument method named other than 'toString' under the name 'toString'
+ | @JSExport("toString")
+ | ^
+ """
+
+ }
+
+ @Test
+ def noBadNameExportAll(): Unit = {
+
+ """
+ @JSExportAll
+ class A {
+ val __f = 1
+ def a_= = 2
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: An exported name may not contain a double underscore (`__`)
+ | val __f = 1
+ | ^
+ |newSource1.scala:3: error: Exported setters must return Unit
+ | @JSExportAll
+ | ^
+ """
+
+ }
+
+ @Test
+ def noConflictingMethodAndProperty(): Unit = {
+
+ // Basic case
+ """
+ class A {
+ @JSExport("a")
+ def bar() = 2
+
+ @JSExport("a")
+ val foo = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Exported property a conflicts with A.$js$exported$meth$a
+ | @JSExport("a")
+ | ^
+ |newSource1.scala:7: error: Exported method a conflicts with A.$js$exported$prop$a
+ | @JSExport("a")
+ | ^
+ """
+
+ // Inherited case
+ """
+ class A {
+ @JSExport("a")
+ def bar() = 2
+ }
+
+ class B extends A {
+ @JSExport("a")
+ def foo_=(x: Int): Unit = ()
+
+ @JSExport("a")
+ val foo = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Exported property a conflicts with A.$js$exported$meth$a
+ | @JSExport("a")
+ | ^
+ """
+
+ }
+
+ @Test
+ def gracefulDoubleDefaultFail(): Unit = {
+ // This used to blow up (i.e. not just fail), because PrepJSExports asked
+ // for the symbol of the default parameter getter of [[y]], and asserted its
+ // not overloaded. Since the Scala compiler only fails later on this, the
+ // assert got triggered and made the compiler crash
+ """
+ class A {
+ @JSExport
+ def foo(x: String, y: String = "hello") = x
+ def foo(x: Int, y: String = "bar") = x
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: in class A, multiple overloaded alternatives of method foo define default arguments.
+ | class A {
+ | ^
+ """
+ }
+
+ @Test
+ def noNonLiteralExportNames(): Unit = {
+
+ """
+ object A {
+ val a = "Hello"
+ final val b = "World"
+ }
+
+ class B {
+ @JSExport(A.a)
+ def foo = 1
+ @JSExport(A.b)
+ def bar = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: The argument to JSExport must be a literal string
+ | @JSExport(A.a)
+ | ^
+ """
+
+ }
+
+ @Test
+ def noNonLiteralModuleID(): Unit = {
+
+ """
+ object A {
+ val a = "Hello"
+ final val b = "World"
+ }
+
+ object B {
+ @JSExportTopLevel("foo", A.a)
+ def foo() = 1
+ @JSExportTopLevel("foo", A.b)
+ def bar() = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: moduleID must be a literal string
+ | @JSExportTopLevel("foo", A.a)
+ | ^
+ """
+
+ }
+
+ @Test
+ def noExportImplicitApply(): Unit = {
+
+ """
+ class A {
+ @JSExport
+ def apply(): Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: A member cannot be exported to function application. Add @JSExport("apply") to export under the name apply.
+ | @JSExport
+ | ^
+ """
+
+ """
+ @JSExportAll
+ class A {
+ def apply(): Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: A member cannot be exported to function application. Add @JSExport("apply") to export under the name apply.
+ | def apply(): Int = 1
+ | ^
+ """
+
+ """
+ @JSExportAll
+ class A {
+ @JSExport("foo")
+ def apply(): Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: A member cannot be exported to function application. Add @JSExport("apply") to export under the name apply.
+ | def apply(): Int = 1
+ | ^
+ """
+
+ """
+ @JSExportAll
+ class A {
+ @JSExport("apply")
+ def apply(): Int = 1
+ }
+ """.hasNoWarns()
+
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def apply(): Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: A member cannot be exported to function application as static. Use @JSExportStatic("apply") to export it under the name 'apply'.
+ | @JSExportStatic
+ | ^
+ """
+
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic("apply")
+ def apply(): Int = 1
+ }
+ """.hasNoWarns()
+ }
+
+ @Test
+ def exportObjectAsToString(): Unit = {
+
+ """
+ @JSExportTopLevel("toString")
+ object ExportAsToString
+ """.succeeds()
+
+ }
+
+ @Test
+ def noExportTopLevelTrait(): Unit = {
+ """
+ @JSExportTopLevel("foo")
+ trait A
+
+ @JSExportTopLevel("bar")
+ trait B extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: You may not export a trait
+ | @JSExportTopLevel("foo")
+ | ^
+ |newSource1.scala:6: error: You may not export a trait
+ | @JSExportTopLevel("bar")
+ | ^
+ """
+
+ """
+ object Container {
+ @JSExportTopLevel("foo")
+ trait A
+
+ @JSExportTopLevel("bar")
+ trait B extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: You may not export a trait
+ | @JSExportTopLevel("foo")
+ | ^
+ |newSource1.scala:7: error: You may not export a trait
+ | @JSExportTopLevel("bar")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportTopLevelLazyVal(): Unit = {
+ """
+ object A {
+ @JSExportTopLevel("foo")
+ lazy val a: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: You may not export a lazy val to the top level
+ | @JSExportTopLevel("foo")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportTopLevelInvalidJSIdentifier(): Unit = {
+ """
+ @JSExportTopLevel("not-a-valid-JS-identifier-1")
+ object A
+
+ @JSExportTopLevel("not-a-valid-JS-identifier-2")
+ class B
+
+ object C {
+ @JSExportTopLevel("not-a-valid-JS-identifier-3")
+ val a: Int = 1
+
+ @JSExportTopLevel("not-a-valid-JS-identifier-4")
+ var b: Int = 1
+
+ @JSExportTopLevel("not-a-valid-JS-identifier-5")
+ def c(): Int = 1
+ }
+
+ @JSExportTopLevel("")
+ object D
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: The top-level export name must be a valid JavaScript identifier name
+ | @JSExportTopLevel("not-a-valid-JS-identifier-1")
+ | ^
+ |newSource1.scala:6: error: The top-level export name must be a valid JavaScript identifier name
+ | @JSExportTopLevel("not-a-valid-JS-identifier-2")
+ | ^
+ |newSource1.scala:10: error: The top-level export name must be a valid JavaScript identifier name
+ | @JSExportTopLevel("not-a-valid-JS-identifier-3")
+ | ^
+ |newSource1.scala:13: error: The top-level export name must be a valid JavaScript identifier name
+ | @JSExportTopLevel("not-a-valid-JS-identifier-4")
+ | ^
+ |newSource1.scala:16: error: The top-level export name must be a valid JavaScript identifier name
+ | @JSExportTopLevel("not-a-valid-JS-identifier-5")
+ | ^
+ |newSource1.scala:20: error: The top-level export name must be a valid JavaScript identifier name
+ | @JSExportTopLevel("")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportTopLevelNamespaced(): Unit = {
+ """
+ @JSExportTopLevel("namespaced.export1")
+ object A
+ @JSExportTopLevel("namespaced.export2")
+ class B
+ object C {
+ @JSExportTopLevel("namespaced.export3")
+ val a: Int = 1
+ @JSExportTopLevel("namespaced.export4")
+ var b: Int = 1
+ @JSExportTopLevel("namespaced.export5")
+ def c(): Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:3: error: The top-level export name must be a valid JavaScript identifier name
+ | @JSExportTopLevel("namespaced.export1")
+ | ^
+ |newSource1.scala:5: error: The top-level export name must be a valid JavaScript identifier name
+ | @JSExportTopLevel("namespaced.export2")
+ | ^
+ |newSource1.scala:8: error: The top-level export name must be a valid JavaScript identifier name
+ | @JSExportTopLevel("namespaced.export3")
+ | ^
+ |newSource1.scala:10: error: The top-level export name must be a valid JavaScript identifier name
+ | @JSExportTopLevel("namespaced.export4")
+ | ^
+ |newSource1.scala:12: error: The top-level export name must be a valid JavaScript identifier name
+ | @JSExportTopLevel("namespaced.export5")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportTopLevelGetter(): Unit = {
+ """
+ object A {
+ @JSExportTopLevel("foo")
+ def a: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: You may not export a getter or a setter to the top level
+ | @JSExportTopLevel("foo")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportTopLevelSetter(): Unit = {
+ """
+ object A {
+ @JSExportTopLevel("foo")
+ def a_=(x: Int): Unit = ()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: You may not export a getter or a setter to the top level
+ | @JSExportTopLevel("foo")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportTopLevelFieldsWithSameName(): Unit = {
+ """
+ object A {
+ @JSExportTopLevel("foo")
+ val a: Int = 1
+
+ @JSExportTopLevel("foo")
+ var b: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: export overload conflicts with export of variable b: a field may not share its exported name with another export
+ | @JSExportTopLevel("foo")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportTopLevelFieldsAndMethodsWithSameName(): Unit = {
+ """
+ object A {
+ @JSExportTopLevel("foo")
+ val a: Int = 1
+
+ @JSExportTopLevel("foo")
+ def b(x: Int): Int = x + 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: export overload conflicts with export of method b: they are of different types (Field / Method)
+ | @JSExportTopLevel("foo")
+ | ^
+ """
+
+ """
+ object A {
+ @JSExportTopLevel("foo")
+ def a(x: Int): Int = x + 1
+
+ @JSExportTopLevel("foo")
+ val b: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: export overload conflicts with export of value b: they are of different types (Method / Field)
+ | @JSExportTopLevel("foo")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportTopLevelNonStatic(): Unit = {
+ """
+ class A {
+ @JSExportTopLevel("foo")
+ def a(): Unit = ()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Only static objects may export their members to the top level
+ | @JSExportTopLevel("foo")
+ | ^
+ """
+
+ """
+ class A {
+ object B {
+ @JSExportTopLevel("foo")
+ def a(): Unit = ()
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Only static objects may export their members to the top level
+ | @JSExportTopLevel("foo")
+ | ^
+ """
+
+ """
+ class A {
+ @JSExportTopLevel("Foo")
+ object B
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Only static objects may export their members to the top level
+ | @JSExportTopLevel("Foo")
+ | ^
+ """
+
+ """
+ class A {
+ @JSExportTopLevel("Foo")
+ object B extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Only static objects may export their members to the top level
+ | @JSExportTopLevel("Foo")
+ | ^
+ """
+
+ """
+ class A {
+ @JSExportTopLevel("Foo")
+ class B extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Only static objects may export their members to the top level
+ | @JSExportTopLevel("Foo")
+ | ^
+ """
+
+ """
+ class A {
+ @JSExportTopLevel("Foo")
+ class B
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Only static objects may export their members to the top level
+ | @JSExportTopLevel("Foo")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportTopLevelLocal(): Unit = {
+ // Local class
+ """
+ class A {
+ def method = {
+ @JSExportTopLevel("A")
+ class A
+
+ @JSExportTopLevel("B")
+ class B extends js.Object
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export constructors of local classes
+ | @JSExportTopLevel("A")
+ | ^
+ |newSource1.scala:8: error: You may not export a local definition
+ | @JSExportTopLevel("B")
+ | ^
+ """
+
+ // Local object
+ """
+ class A {
+ def method = {
+ @JSExportTopLevel("A")
+ object A
+
+ @JSExportTopLevel("B")
+ object B extends js.Object
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export a local definition
+ | @JSExportTopLevel("A")
+ | ^
+ |newSource1.scala:8: error: You may not export a local definition
+ | @JSExportTopLevel("B")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportTopLevelJSModule(): Unit = {
+ """
+ object A extends js.Object {
+ @JSExportTopLevel("foo")
+ def a(): Unit = ()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: You may not export a member of a subclass of js.Any
+ | @JSExportTopLevel("foo")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticModule(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ object A
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Implementation restriction: cannot export a class or object as static
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticTrait(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ trait A
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: You may not export a trait
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticClass(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ class A
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Implementation restriction: cannot export a class or object as static
+ | @JSExportStatic
+ | ^
+ """
+
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ class A {
+ @JSExportStatic
+ def this(x: Int) = this()
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Implementation restriction: cannot export a class or object as static
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticValTwice(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ @JSExportStatic("b")
+ val a: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Fields (val or var) cannot be exported as static more than once
+ | @JSExportStatic("b")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticVarTwice(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ @JSExportStatic("b")
+ var a: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Fields (val or var) cannot be exported as static more than once
+ | @JSExportStatic("b")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticLazyVal(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ lazy val a: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: You may not export a lazy val as static
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportValAsStaticAndTopLevel(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ @JSExportTopLevel("foo")
+ val a: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Fields (val or var) cannot be exported both as static and at the top-level
+ | @JSExportTopLevel("foo")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportVarAsStaticAndTopLevel(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ @JSExportTopLevel("foo")
+ var a: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Fields (val or var) cannot be exported both as static and at the top-level
+ | @JSExportTopLevel("foo")
+ | ^
+ """
+ }
+
+ @Test
+ def noExportSetterWithBadSetterType(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def a_=(x: Int, y: Int): Unit = ()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Exported setters must have exactly one argument
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticCollapsingMethods(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def foo(x: Int): Int = x
+
+ @JSExportStatic("foo")
+ def bar(x: Int): Int = x + 1
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:10: error: Cannot disambiguate overloads for exported method foo with types
+ | ${methodSig("(x: Int)", "Int")}
+ | ${methodSig("(x: Int)", "Int")}
+ | def bar(x: Int): Int = x + 1
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticCollapsingGetters(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def foo: Int = 1
+
+ @JSExportStatic("foo")
+ def bar: Int = 2
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:10: error: Cannot disambiguate overloads for exported getter foo with types
+ | ${methodSig("()", "Int")}
+ | ${methodSig("()", "Int")}
+ | def bar: Int = 2
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticCollapsingSetters(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def foo_=(v: Int): Unit = ()
+
+ @JSExportStatic("foo")
+ def bar_=(v: Int): Unit = ()
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:10: error: Cannot disambiguate overloads for exported setter foo with types
+ | ${methodSig("(v: Int)", "Unit")}
+ | ${methodSig("(v: Int)", "Unit")}
+ | def bar_=(v: Int): Unit = ()
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticFieldsWithSameName(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ val a: Int = 1
+
+ @JSExportStatic("a")
+ var b: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: export overload conflicts with export of variable b: a field may not share its exported name with another export
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticFieldsAndMethodsWithSameName(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ val a: Int = 1
+
+ @JSExportStatic("a")
+ def b(x: Int): Int = x + 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: export overload conflicts with export of method b: they are of different types (Field / Method)
+ | @JSExportStatic
+ | ^
+ """
+
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def a(x: Int): Int = x + 1
+
+ @JSExportStatic("a")
+ val b: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: export overload conflicts with export of value b: they are of different types (Method / Field)
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticFieldsAndPropertiesWithSameName(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ val a: Int = 1
+
+ @JSExportStatic("a")
+ def b: Int = 2
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: export overload conflicts with export of method b: they are of different types (Field / Property)
+ | @JSExportStatic
+ | ^
+ """
+
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def a: Int = 1
+
+ @JSExportStatic("a")
+ val b: Int = 2
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: export overload conflicts with export of value b: they are of different types (Property / Field)
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticPropertiesAndMethodsWithSameName(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def a: Int = 1
+
+ @JSExportStatic("a")
+ def b(x: Int): Int = x + 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: export overload conflicts with export of method b: they are of different types (Property / Method)
+ | @JSExportStatic
+ | ^
+ """
+
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def a(x: Int): Int = x + 1
+
+ @JSExportStatic("a")
+ def b: Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: export overload conflicts with export of method b: they are of different types (Method / Property)
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticNonStatic(): Unit = {
+ """
+ class A {
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def a(): Unit = ()
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Only a static object whose companion class is a non-native JS class may export its members as static.
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticInJSModule(): Unit = {
+ """
+ class StaticContainer extends js.Object
+
+ object StaticContainer extends js.Object {
+ @JSExportStatic
+ def a(): Unit = ()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: You may not export a member of a subclass of js.Any
+ | @JSExportStatic
+ | ^
+ """
+
+ """
+ class StaticContainer extends js.Object
+
+ @js.native
+ @JSGlobal("Dummy")
+ object StaticContainer extends js.Object {
+ @JSExportStatic
+ def a(): Unit = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: You may not export a member of a subclass of js.Any
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticIfWrongCompanionType(): Unit = {
+ """
+ class StaticContainer
+
+ object StaticContainer {
+ @JSExportStatic
+ def a(): Unit = ()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Only a static object whose companion class is a non-native JS class may export its members as static.
+ | @JSExportStatic
+ | ^
+ """
+
+ """
+ trait StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def a(): Unit = ()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Only a static object whose companion class is a non-native JS class may export its members as static.
+ | @JSExportStatic
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal("Dummy")
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ @JSExportStatic
+ def a(): Unit = ()
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: Only a static object whose companion class is a non-native JS class may export its members as static.
+ | @JSExportStatic
+ | ^
+ """
+ }
+
+ @Test
+ def noExportStaticFieldAfterStatOrNonStaticField(): Unit = {
+ for {
+ offendingDecl <- Seq(
+ "val a: Int = 1",
+ "var a: Int = 1",
+ """println("foo")"""
+ )
+ }
+ s"""
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ $offendingDecl
+
+ @JSExportStatic
+ val b: Int = 1
+
+ @JSExportStatic
+ var c: Int = 1
+
+ @JSExportStatic
+ def d: Int = 1
+
+ @JSExportStatic
+ def d_=(v: Int): Unit = ()
+
+ @JSExportStatic
+ def e(): Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: @JSExportStatic vals and vars must be defined before any other val/var, and before any constructor statement.
+ | val b: Int = 1
+ | ^
+ |newSource1.scala:12: error: @JSExportStatic vals and vars must be defined before any other val/var, and before any constructor statement.
+ | var c: Int = 1
+ | ^
+ """
+
+ for {
+ validDecl <- Seq(
+ "@JSExportStatic val a: Int = 1",
+ "@JSExportStatic var a: Int = 1",
+ "lazy val a: Int = 1",
+ "def a: Int = 1",
+ "def a_=(v: Int): Unit = ()",
+ "def a(): Int = 1",
+ "@JSExportStatic def a: Int = 1",
+ "@JSExportStatic def a_=(v: Int): Unit = ()",
+ "@JSExportStatic def a(): Int = 1",
+ "class A",
+ "object A",
+ "trait A",
+ "type A = Int"
+ )
+ }
+ s"""
+ class StaticContainer extends js.Object
+
+ object StaticContainer {
+ $validDecl
+
+ @JSExportStatic
+ val b: Int = 1
+
+ @JSExportStatic
+ var c: Int = 1
+ }
+ """.succeeds()
+ }
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala
new file mode 100644
index 0000000000..19d3f0f8d3
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala
@@ -0,0 +1,530 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.scalajs.nscplugin.test.util.VersionDependentUtils.scalaSupportsNoWarn
+
+import org.junit.Test
+import org.junit.Ignore
+import org.junit.Assume._
+
+// scalastyle:off line.size.limit
+
+class JSGlobalScopeTest extends DirectTest with TestHelpers {
+
+ override def extraArgs: List[String] =
+ super.extraArgs :+ "-deprecation"
+
+ override def preamble: String = {
+ """
+ import scala.scalajs.js
+ import scala.scalajs.js.annotation._
+
+ object Symbols {
+ val sym: js.Symbol = js.Symbol()
+ }
+
+ @js.native
+ @JSGlobalScope
+ object SomeGlobalScope extends js.Any {
+ var validVar: Int = js.native
+ def validDef(): Int = js.native
+
+ var `not-a-valid-identifier-var`: Int = js.native
+ def `not-a-valid-identifier-def`(): Int = js.native
+
+ @JSOperator def +(that: Int): Int = js.native
+
+ def apply(x: Int): Int = js.native
+
+ @JSBracketAccess
+ def bracketSelect(name: String): Int = js.native
+ @JSBracketAccess
+ def bracketUpdate(name: String, v: Int): Unit = js.native
+
+ @JSBracketCall
+ def bracketCall(name: String)(arg: Int): Int = js.native
+
+ @JSName(Symbols.sym)
+ var symbolVar: Int = js.native
+ @JSName(Symbols.sym)
+ def symbolDef(): Int = js.native
+
+ var arguments: js.Array[Any] = js.native
+ @JSName("arguments") def arguments2(x: Int): Int = js.native
+ }
+ """
+ }
+
+ @Test
+ def canAccessLegitMembers(): Unit = {
+ s"""
+ object Main {
+ def main(): Unit = {
+ val a = js.Dynamic.global.validVar
+ js.Dynamic.global.validVar = 3
+ val b = js.Dynamic.global.validDef()
+
+ val c = SomeGlobalScope.validVar
+ SomeGlobalScope.validVar = 3
+ val d = SomeGlobalScope.validDef()
+
+ val e = SomeGlobalScope.bracketSelect("validVar")
+ SomeGlobalScope.bracketUpdate("validVar", 3)
+ val f = SomeGlobalScope.bracketCall("validDef")(4)
+ }
+ }
+ """.hasNoWarns()
+ }
+
+ @Test
+ def noLoadGlobalValue(): Unit = {
+ s"""
+ object Main {
+ def main(): Unit = {
+ val g1 = js.Dynamic.global
+ val g2 = SomeGlobalScope
+ }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:41: error: Loading the global scope as a value (anywhere but as the left-hand-side of a `.`-selection) is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val g1 = js.Dynamic.global
+ | ^
+ |newSource1.scala:42: error: Loading the global scope as a value (anywhere but as the left-hand-side of a `.`-selection) is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val g2 = SomeGlobalScope
+ | ^
+ """
+ }
+
+ @Test
+ def rejectInvalidJSIdentifiers(): Unit = {
+ s"""
+ object Main {
+ def main(): Unit = {
+ val a = js.Dynamic.global.`not-a-valid-identifier-var`
+ js.Dynamic.global.`not-a-valid-identifier-var` = 3
+ val b = js.Dynamic.global.`not-a-valid-identifier-def`()
+
+ val c = SomeGlobalScope.`not-a-valid-identifier-var`
+ SomeGlobalScope.`not-a-valid-identifier-var` = 3
+ val d = SomeGlobalScope.`not-a-valid-identifier-def`()
+
+ val e = SomeGlobalScope.bracketSelect("not-a-valid-identifier-var")
+ SomeGlobalScope.bracketUpdate("not-a-valid-identifier-var", 3)
+ val f = SomeGlobalScope.bracketCall("not-a-valid-identifier-def")(4)
+ }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:41: error: Selecting a field of the global scope whose name is not a valid JavaScript identifier is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val a = js.Dynamic.global.`not-a-valid-identifier-var`
+ | ^
+ |newSource1.scala:42: error: Selecting a field of the global scope whose name is not a valid JavaScript identifier is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | js.Dynamic.global.`not-a-valid-identifier-var` = 3
+ | ^
+ |newSource1.scala:43: error: Calling a method of the global scope whose name is not a valid JavaScript identifier is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val b = js.Dynamic.global.`not-a-valid-identifier-def`()
+ | ^
+ |newSource1.scala:45: error: Selecting a field of the global scope whose name is not a valid JavaScript identifier is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val c = SomeGlobalScope.`not-a-valid-identifier-var`
+ | ^
+ |newSource1.scala:46: error: Selecting a field of the global scope whose name is not a valid JavaScript identifier is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | SomeGlobalScope.`not-a-valid-identifier-var` = 3
+ | ^
+ |newSource1.scala:47: error: Calling a method of the global scope whose name is not a valid JavaScript identifier is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val d = SomeGlobalScope.`not-a-valid-identifier-def`()
+ | ^
+ |newSource1.scala:49: error: Selecting a field of the global scope whose name is not a valid JavaScript identifier is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val e = SomeGlobalScope.bracketSelect("not-a-valid-identifier-var")
+ | ^
+ |newSource1.scala:50: error: Selecting a field of the global scope whose name is not a valid JavaScript identifier is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | SomeGlobalScope.bracketUpdate("not-a-valid-identifier-var", 3)
+ | ^
+ |newSource1.scala:51: error: Calling a method of the global scope whose name is not a valid JavaScript identifier is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val f = SomeGlobalScope.bracketCall("not-a-valid-identifier-def")(4)
+ | ^
+ """
+ }
+
+ @Test
+ def rejectInvalidJSIdentifiersInNestedObjectClass(): Unit = {
+ """
+ @js.native
+ @JSGlobalScope
+ object EnclosingGlobalScope extends js.Any {
+ @js.native
+ class `not-a-valid-JS-identifier` extends js.Object
+
+ @js.native
+ @JSName("not-a-valid-JS-identifier")
+ object A extends js.Object
+
+ @js.native
+ @JSName("foo.bar")
+ object B extends js.Object
+
+ @js.native
+ @JSName("")
+ object C extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:43: error: The name of a JS global variable must be a valid JS identifier (got 'not-a-valid-JS-identifier')
+ | class `not-a-valid-JS-identifier` extends js.Object
+ | ^
+ |newSource1.scala:47: error: The name of a JS global variable must be a valid JS identifier (got 'not-a-valid-JS-identifier')
+ | object A extends js.Object
+ | ^
+ |newSource1.scala:51: error: The name of a JS global variable must be a valid JS identifier (got 'foo.bar')
+ | object B extends js.Object
+ | ^
+ |newSource1.scala:55: error: The name of a JS global variable must be a valid JS identifier (got '')
+ | object C extends js.Object
+ | ^
+ """
+ }
+
+ @Test
+ def rejectJSOperators(): Unit = {
+ """
+ object Main {
+ def main(): Unit = {
+ val a = js.Dynamic.global + 3.asInstanceOf[js.Dynamic]
+ }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:41: error: type mismatch;
+ | found : scala.scalajs.js.Dynamic
+ | required: String
+ | val a = js.Dynamic.global + 3.asInstanceOf[js.Dynamic]
+ | ^
+ """
+
+ """
+ object Main {
+ def main(): Unit = {
+ val a = SomeGlobalScope + 3
+ }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:41: error: Loading the global scope as a value (anywhere but as the left-hand-side of a `.`-selection) is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val a = SomeGlobalScope + 3
+ | ^
+ """
+ }
+
+ @Test
+ def rejectApply(): Unit = {
+ """
+ object Main {
+ def main(): Unit = {
+ val a = js.Dynamic.global(3)
+ }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:41: warning: method apply in object global is deprecated (since forever): The global scope cannot be called as function.
+ | val a = js.Dynamic.global(3)
+ | ^
+ |newSource1.scala:41: error: Loading the global scope as a value (anywhere but as the left-hand-side of a `.`-selection) is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val a = js.Dynamic.global(3)
+ | ^
+ """
+
+ """
+ object Main {
+ def main(): Unit = {
+ val a = SomeGlobalScope(3)
+ }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:41: error: Loading the global scope as a value (anywhere but as the left-hand-side of a `.`-selection) is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val a = SomeGlobalScope(3)
+ | ^
+ """
+ }
+
+ @Test
+ def rejectDynamicNames(): Unit = {
+ s"""
+ object Main {
+ def dynName: String = "foo"
+
+ def main(): Unit = {
+ val a = js.Dynamic.global.selectDynamic(dynName)
+ js.Dynamic.global.updateDynamic(dynName)(3)
+ val b = js.Dynamic.global.applyDynamic(dynName)(3)
+
+ val e = SomeGlobalScope.bracketSelect(dynName)
+ SomeGlobalScope.bracketUpdate(dynName, 3)
+ val f = SomeGlobalScope.bracketCall(dynName)(4)
+
+ val i = SomeGlobalScope.symbolVar
+ SomeGlobalScope.symbolVar = 3
+ val k = SomeGlobalScope.symbolDef()
+ }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:43: error: Selecting a field of the global scope with a dynamic name is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val a = js.Dynamic.global.selectDynamic(dynName)
+ | ^
+ |newSource1.scala:44: error: Selecting a field of the global scope with a dynamic name is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | js.Dynamic.global.updateDynamic(dynName)(3)
+ | ^
+ |newSource1.scala:45: error: Calling a method of the global scope with a dynamic name is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val b = js.Dynamic.global.applyDynamic(dynName)(3)
+ | ^
+ |newSource1.scala:47: error: Selecting a field of the global scope with a dynamic name is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val e = SomeGlobalScope.bracketSelect(dynName)
+ | ^
+ |newSource1.scala:48: error: Selecting a field of the global scope with a dynamic name is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | SomeGlobalScope.bracketUpdate(dynName, 3)
+ | ^
+ |newSource1.scala:49: error: Calling a method of the global scope with a dynamic name is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val f = SomeGlobalScope.bracketCall(dynName)(4)
+ | ^
+ |newSource1.scala:51: error: Selecting a field of the global scope with a dynamic name is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val i = SomeGlobalScope.symbolVar
+ | ^
+ |newSource1.scala:52: error: Selecting a field of the global scope with a dynamic name is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | SomeGlobalScope.symbolVar = 3
+ | ^
+ |newSource1.scala:53: error: Calling a method of the global scope with a dynamic name is not allowed.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val k = SomeGlobalScope.symbolDef()
+ | ^
+ """
+ }
+
+ @Test
+ def rejectAllReservedIdentifiers(): Unit = {
+ val reservedIdentifiers = List(
+ "arguments", "break", "case", "catch", "class", "const", "continue",
+ "debugger", "default", "delete", "do", "else", "enum", "export",
+ "extends", "false", "finally", "for", "function", "if", "implements",
+ "import", "in", "instanceof", "interface", "let", "new", "null",
+ "package", "private", "protected", "public", "return", "static",
+ "super", "switch", "throw", "true", "try", "typeof", "var",
+ "void", "while", "with", "yield")
+
+ for (reservedIdentifier <- reservedIdentifiers) {
+ val spaces = " " * reservedIdentifier.length()
+
+ s"""
+ @js.native
+ @JSGlobalScope
+ object CustomGlobalScope extends js.Any {
+ var `$reservedIdentifier`: Int = js.native
+ @JSName("$reservedIdentifier")
+ def `${reservedIdentifier}2`(x: Int): Int = js.native
+ }
+
+ object Main {
+ def main(): Unit = {
+ val a = js.Dynamic.global.`$reservedIdentifier`
+ js.Dynamic.global.`$reservedIdentifier` = 5
+ val b = js.Dynamic.global.`$reservedIdentifier`(5)
+
+ val c = CustomGlobalScope.`$reservedIdentifier`
+ CustomGlobalScope.`$reservedIdentifier` = 5
+ val d = CustomGlobalScope.`${reservedIdentifier}2`(5)
+ }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:49: error: Invalid selection in the global scope of the reserved identifier name `$reservedIdentifier`.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val a = js.Dynamic.global.`$reservedIdentifier`
+ | ^
+ |newSource1.scala:50: error: Invalid selection in the global scope of the reserved identifier name `$reservedIdentifier`.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | js.Dynamic.global.`$reservedIdentifier` = 5
+ | ^
+ |newSource1.scala:51: error: Invalid call in the global scope of the reserved identifier name `$reservedIdentifier`.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val b = js.Dynamic.global.`$reservedIdentifier`(5)
+ | $spaces^
+ |newSource1.scala:53: error: Invalid selection in the global scope of the reserved identifier name `$reservedIdentifier`.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val c = CustomGlobalScope.`$reservedIdentifier`
+ | ^
+ |newSource1.scala:54: error: Invalid selection in the global scope of the reserved identifier name `$reservedIdentifier`.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | CustomGlobalScope.`$reservedIdentifier` = 5
+ | $spaces^
+ |newSource1.scala:55: error: Invalid call in the global scope of the reserved identifier name `$reservedIdentifier`.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val d = CustomGlobalScope.`${reservedIdentifier}2`(5)
+ | $spaces^
+ """
+ }
+ }
+
+ @Test
+ def warnAboutAwaitReservedWord_Issue4705(): Unit = {
+ val reservedIdentifiers = List("await")
+
+ for (reservedIdentifier <- reservedIdentifiers) {
+ val spaces = " " * reservedIdentifier.length()
+
+ s"""
+ @js.native
+ @JSGlobalScope
+ object CustomGlobalScope extends js.Any {
+ var `$reservedIdentifier`: Int = js.native
+ @JSName("$reservedIdentifier")
+ def `${reservedIdentifier}2`(x: Int): Int = js.native
+ }
+
+ object Main {
+ def main(): Unit = {
+ val a = js.Dynamic.global.`$reservedIdentifier`
+ js.Dynamic.global.`$reservedIdentifier` = 5
+ val b = js.Dynamic.global.`$reservedIdentifier`(5)
+
+ val c = CustomGlobalScope.`$reservedIdentifier`
+ CustomGlobalScope.`$reservedIdentifier` = 5
+ val d = CustomGlobalScope.`${reservedIdentifier}2`(5)
+ }
+ }
+ """ hasWarns
+ s"""
+ |newSource1.scala:49: warning: Selecting a field of the global scope with the name '$reservedIdentifier' is deprecated.
+ | It may produce invalid JavaScript code causing a SyntaxError in some environments.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val a = js.Dynamic.global.`$reservedIdentifier`
+ | ^
+ |newSource1.scala:50: warning: Selecting a field of the global scope with the name '$reservedIdentifier' is deprecated.
+ | It may produce invalid JavaScript code causing a SyntaxError in some environments.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | js.Dynamic.global.`$reservedIdentifier` = 5
+ | ^
+ |newSource1.scala:51: warning: Calling a method of the global scope with the name '$reservedIdentifier' is deprecated.
+ | It may produce invalid JavaScript code causing a SyntaxError in some environments.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val b = js.Dynamic.global.`$reservedIdentifier`(5)
+ | $spaces^
+ |newSource1.scala:53: warning: Selecting a field of the global scope with the name '$reservedIdentifier' is deprecated.
+ | It may produce invalid JavaScript code causing a SyntaxError in some environments.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val c = CustomGlobalScope.`$reservedIdentifier`
+ | ^
+ |newSource1.scala:54: warning: Selecting a field of the global scope with the name '$reservedIdentifier' is deprecated.
+ | It may produce invalid JavaScript code causing a SyntaxError in some environments.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | CustomGlobalScope.`$reservedIdentifier` = 5
+ | $spaces^
+ |newSource1.scala:55: warning: Calling a method of the global scope with the name '$reservedIdentifier' is deprecated.
+ | It may produce invalid JavaScript code causing a SyntaxError in some environments.
+ | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
+ | val d = CustomGlobalScope.`${reservedIdentifier}2`(5)
+ | $spaces^
+ """
+ }
+ }
+
+ @Test
+ def noWarnAboutAwaitReservedWordIfSelectivelyDisabled(): Unit = {
+ assumeTrue(scalaSupportsNoWarn)
+
+ val reservedIdentifiers = List("await")
+
+ for (reservedIdentifier <- reservedIdentifiers) {
+ val spaces = " " * reservedIdentifier.length()
+
+ s"""
+ import scala.annotation.nowarn
+
+ @js.native
+ @JSGlobalScope
+ object CustomGlobalScope extends js.Any {
+ var `$reservedIdentifier`: Int = js.native
+ @JSName("$reservedIdentifier")
+ def `${reservedIdentifier}2`(x: Int): Int = js.native
+ }
+
+ object Main {
+ @nowarn("cat=deprecation")
+ def main(): Unit = {
+ val a = js.Dynamic.global.`$reservedIdentifier`
+ js.Dynamic.global.`$reservedIdentifier` = 5
+ val b = js.Dynamic.global.`$reservedIdentifier`(5)
+
+ val c = CustomGlobalScope.`$reservedIdentifier`
+ CustomGlobalScope.`$reservedIdentifier` = 5
+ val d = CustomGlobalScope.`${reservedIdentifier}2`(5)
+ }
+ }
+ """.hasNoWarns()
+ }
+ }
+
+ @Test
+ def rejectAssignmentToGlobalThis(): Unit = {
+ """
+ import scala.scalajs.js
+ import scala.scalajs.js.annotation._
+
+ object Main {
+ def main(): Unit = {
+ js.Dynamic.global.`this` = 0
+ GlobalScope.globalThis = 0
+ }
+ }
+
+ @js.native
+ @JSGlobalScope
+ object GlobalScope extends js.Any {
+ @JSName("this")
+ var globalThis: Any = js.native
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:44: error: Illegal assignment to global this.
+ | js.Dynamic.global.`this` = 0
+ | ^
+ |newSource1.scala:45: error: Illegal assignment to global this.
+ | GlobalScope.globalThis = 0
+ | ^
+ """
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala
new file mode 100644
index 0000000000..8d79f251a3
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala
@@ -0,0 +1,4670 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+
+import org.junit.Test
+import org.junit.Ignore
+
+// scalastyle:off line.size.limit
+
+class JSInteropTest extends DirectTest with TestHelpers {
+
+ override def preamble: String =
+ """
+ import scala.scalajs.js
+ import scala.scalajs.js.annotation._
+ """
+
+ private val JSNativeLoadSpecAnnots = Seq(
+ "JSGlobal" -> "@JSGlobal",
+ "JSGlobal" -> "@JSGlobal(\"foo\")",
+ "JSImport" -> "@JSImport(\"foo\", \"bar\")",
+ "JSImport" -> "@JSImport(\"foo\", \"bar\", globalFallback = \"baz\")",
+ "JSGlobalScope" -> "@JSGlobalScope"
+ )
+
+ private def version = scala.util.Properties.versionNumberString
+
+ private def ifHasNewRefChecks(msg: String): String = {
+ if (version.startsWith("2.12.")) {
+ ""
+ } else {
+ msg.stripMargin.trim()
+ }
+ }
+
+ @Test def warnJSPackageObjectDeprecated: Unit = {
+
+ s"""
+ package object jspackage extends js.Object
+ """ hasErrors
+ s"""
+ |newSource1.scala:5: error: Package objects may not extend js.Any.
+ | package object jspackage extends js.Object
+ | ^
+ """
+
+ }
+
+ @Test def noJSNameAnnotOnNonJSNative: Unit = {
+
+ for {
+ obj <- Seq("class", "trait", "object")
+ } yield {
+ s"""
+ @JSName("foo")
+ $obj A extends js.Object
+
+ object Sym {
+ val sym = js.Symbol()
+ }
+
+ @JSName(Sym.sym)
+ $obj B extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSName can only be used on members of JS types.
+ | @JSName("foo")
+ | ^
+ |newSource1.scala:12: error: @JSName can only be used on members of JS types.
+ | @JSName(Sym.sym)
+ | ^
+ """
+ }
+
+ for {
+ obj <- Seq("class", "trait", "object")
+ } yield {
+ s"""
+ @JSName("foo")
+ $obj A
+
+ object Sym {
+ val sym = js.Symbol()
+ }
+
+ @JSName(Sym.sym)
+ $obj B
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSName can only be used on members of JS types.
+ | @JSName("foo")
+ | ^
+ |newSource1.scala:12: error: @JSName can only be used on members of JS types.
+ | @JSName(Sym.sym)
+ | ^
+ """
+ }
+
+ """
+ object Container {
+ @JSName("foo")
+ val a: Int = 1
+
+ @JSName("foo")
+ var b: Int = 2
+
+ @JSName("foo")
+ def c: Int = 3
+
+ @JSName("foo")
+ def d_=(v: Int): Unit = ()
+
+ @JSName("foo")
+ def e(x: Int): Int = x + 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: @JSName can only be used on members of JS types.
+ | @JSName("foo")
+ | ^
+ |newSource1.scala:9: error: @JSName can only be used on members of JS types.
+ | @JSName("foo")
+ | ^
+ |newSource1.scala:12: error: @JSName can only be used on members of JS types.
+ | @JSName("foo")
+ | ^
+ |newSource1.scala:15: error: @JSName can only be used on members of JS types.
+ | @JSName("foo")
+ | ^
+ |newSource1.scala:18: error: @JSName can only be used on members of JS types.
+ | @JSName("foo")
+ | ^
+ """
+
+ }
+
+ @Test def okJSNameOnNestedObjects: Unit = {
+
+ """
+ class A extends js.Object {
+ @JSName("foo")
+ object toto
+
+ @JSName("bar")
+ object tata extends js.Object
+ }
+ """.hasNoWarns()
+
+ """
+ class A extends js.Object {
+ @JSName("foo")
+ private object toto
+
+ @JSName("bar")
+ private object tata extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: @JSName cannot be used on private members.
+ | @JSName("foo")
+ | ^
+ |newSource1.scala:9: error: @JSName cannot be used on private members.
+ | @JSName("bar")
+ | ^
+ """
+
+ }
+
+ @Test def noJSGlobalAnnotOnNonJSNative: Unit = {
+
+ for {
+ obj <- Seq("class", "trait", "object")
+ } yield {
+ s"""
+ @JSGlobal
+ $obj A extends js.Object
+
+ @JSGlobal("Foo")
+ $obj B extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal
+ | ^
+ |newSource1.scala:8: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal("Foo")
+ | ^
+ """
+ }
+
+ for {
+ obj <- Seq("class", "trait", "object")
+ } yield {
+ s"""
+ @JSGlobal
+ $obj A
+
+ @JSGlobal("Foo")
+ $obj B
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal
+ | ^
+ |newSource1.scala:8: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal("Foo")
+ | ^
+ """
+ }
+
+ """
+ object Container {
+ @JSGlobal
+ val a: Int = 1
+
+ @JSGlobal
+ var b: Int = 2
+
+ @JSGlobal
+ def c: Int = 3
+
+ @JSGlobal
+ def d_=(v: Int): Unit = ()
+
+ @JSGlobal
+ def e(x: Int): Int = x + 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal
+ | ^
+ |newSource1.scala:9: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal
+ | ^
+ |newSource1.scala:12: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal
+ | ^
+ |newSource1.scala:15: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal
+ | ^
+ |newSource1.scala:18: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal
+ | ^
+ """
+
+ }
+
+ @Test def noJSImportAnnotOnNonJSNative: Unit = {
+
+ for {
+ obj <- Seq("class", "trait", "object")
+ } yield {
+ s"""
+ @JSImport("foo", JSImport.Namespace)
+ $obj A extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", JSImport.Namespace)
+ | ^
+ """
+ }
+
+ for {
+ obj <- Seq("class", "trait", "object")
+ } yield {
+ s"""
+ @JSImport("foo", JSImport.Namespace)
+ $obj A
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", JSImport.Namespace)
+ | ^
+ """
+ }
+
+ """
+ object Container {
+ @JSImport("foo", "bar")
+ val a: Int = 1
+
+ @JSImport("foo", "bar")
+ var b: Int = 2
+
+ @JSImport("foo", "bar")
+ def c: Int = 3
+
+ @JSImport("foo", "bar")
+ def d_=(v: Int): Unit = ()
+
+ @JSImport("foo", "bar")
+ def e(x: Int): Int = x + 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", "bar")
+ | ^
+ |newSource1.scala:9: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", "bar")
+ | ^
+ |newSource1.scala:12: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", "bar")
+ | ^
+ |newSource1.scala:15: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", "bar")
+ | ^
+ |newSource1.scala:18: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", "bar")
+ | ^
+ """
+
+ for {
+ obj <- Seq("class", "trait", "object")
+ } yield {
+ s"""
+ @JSImport("foo", JSImport.Namespace, globalFallback = "Foo")
+ $obj A extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", JSImport.Namespace, globalFallback = "Foo")
+ | ^
+ """
+ }
+
+ for {
+ obj <- Seq("class", "trait", "object")
+ } yield {
+ s"""
+ @JSImport("foo", JSImport.Namespace, globalFallback = "Foo")
+ $obj A
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", JSImport.Namespace, globalFallback = "Foo")
+ | ^
+ """
+ }
+
+ """
+ object Container {
+ @JSImport("foo", "bar", globalFallback = "Foo")
+ val a: Int = 1
+
+ @JSImport("foo", "bar", globalFallback = "Foo")
+ var b: Int = 2
+
+ @JSImport("foo", "bar", globalFallback = "Foo")
+ def c: Int = 3
+
+ @JSImport("foo", "bar", globalFallback = "Foo")
+ def d_=(v: Int): Unit = ()
+
+ @JSImport("foo", "bar", globalFallback = "Foo")
+ def e(x: Int): Int = x + 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", "bar", globalFallback = "Foo")
+ | ^
+ |newSource1.scala:9: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", "bar", globalFallback = "Foo")
+ | ^
+ |newSource1.scala:12: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", "bar", globalFallback = "Foo")
+ | ^
+ |newSource1.scala:15: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", "bar", globalFallback = "Foo")
+ | ^
+ |newSource1.scala:18: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("foo", "bar", globalFallback = "Foo")
+ | ^
+ """
+
+ }
+
+ @Test def noJSGlobalScopeAnnotOnNonJSNative: Unit = {
+
+ """
+ @JSGlobalScope
+ object A extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSGlobalScope can only be used on native JS objects (with @js.native).
+ | @JSGlobalScope
+ | ^
+ """
+
+ """
+ @JSGlobalScope
+ object A
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSGlobalScope can only be used on native JS objects (with @js.native).
+ | @JSGlobalScope
+ | ^
+ """
+
+ }
+ @Test def noJSNameAnnotOnClass: Unit = {
+ """
+ @js.native
+ @JSName("Foo")
+ class A extends js.Object
+
+ @js.native
+ @JSName("Foo")
+ abstract class B extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: @JSName can only be used on members of JS types.
+ | @JSName("Foo")
+ | ^
+ |newSource1.scala:7: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | class A extends js.Object
+ | ^
+ |newSource1.scala:10: error: @JSName can only be used on members of JS types.
+ | @JSName("Foo")
+ | ^
+ |newSource1.scala:11: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | abstract class B extends js.Object
+ | ^
+ """
+ }
+
+ @Test def noJSNameAnnotOnObject: Unit = {
+ """
+ @js.native
+ @JSName("Foo")
+ object A extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: @JSName can only be used on members of JS types.
+ | @JSName("Foo")
+ | ^
+ |newSource1.scala:7: error: Native JS objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ | object A extends js.Object
+ | ^
+ """
+ }
+
+ @Test def noJSNameAnnotOnTrait: Unit = {
+
+ s"""
+ object Sym {
+ val sym = js.Symbol()
+ }
+
+ @js.native @JSGlobal
+ object Container extends js.Object {
+ @js.native
+ @JSName("foo")
+ trait A extends js.Object
+
+ @js.native
+ @JSName(Sym.sym)
+ trait B extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:12: error: @JSName cannot be used on traits.
+ | @JSName("foo")
+ | ^
+ |newSource1.scala:16: error: @JSName cannot be used on traits.
+ | @JSName(Sym.sym)
+ | ^
+ """
+
+ }
+
+ @Test def noJSNameAnnotOnNativeValDef: Unit = {
+
+ s"""
+ object Sym {
+ val sym = js.Symbol()
+ }
+
+ object Container {
+ @js.native
+ @JSName("foo")
+ val a: Int = js.native
+
+ @js.native
+ @JSName("foo")
+ def b: Int = js.native
+
+ @js.native
+ @JSName("foo")
+ def c(x: Int): Int = js.native
+
+ @js.native
+ @JSName(Sym.sym)
+ val d: Int = js.native
+
+ @js.native
+ @JSName(Sym.sym)
+ def e: Int = js.native
+
+ @js.native
+ @JSName(Sym.sym)
+ def f(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:11: error: @JSName can only be used on members of JS types.
+ | @JSName("foo")
+ | ^
+ |newSource1.scala:12: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | val a: Int = js.native
+ | ^
+ |newSource1.scala:15: error: @JSName can only be used on members of JS types.
+ | @JSName("foo")
+ | ^
+ |newSource1.scala:16: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | def b: Int = js.native
+ | ^
+ |newSource1.scala:19: error: @JSName can only be used on members of JS types.
+ | @JSName("foo")
+ | ^
+ |newSource1.scala:20: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | def c(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:23: error: @JSName can only be used on members of JS types.
+ | @JSName(Sym.sym)
+ | ^
+ |newSource1.scala:24: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | val d: Int = js.native
+ | ^
+ |newSource1.scala:27: error: @JSName can only be used on members of JS types.
+ | @JSName(Sym.sym)
+ | ^
+ |newSource1.scala:28: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | def e: Int = js.native
+ | ^
+ |newSource1.scala:31: error: @JSName can only be used on members of JS types.
+ | @JSName(Sym.sym)
+ | ^
+ |newSource1.scala:32: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | def f(x: Int): Int = js.native
+ | ^
+ """
+
+ }
+
+ @Test def noJSGlobalAnnotOnTrait: Unit = {
+
+ s"""
+ @js.native
+ @JSGlobal
+ trait A extends js.Object
+ """ hasErrors
+ s"""
+ |newSource1.scala:6: error: Traits may not have an @JSGlobal annotation.
+ | @JSGlobal
+ | ^
+ """
+
+ s"""
+ @js.native
+ @JSGlobal("Foo")
+ trait A extends js.Object
+ """ hasErrors
+ s"""
+ |newSource1.scala:6: error: Traits may not have an @JSGlobal annotation.
+ | @JSGlobal("Foo")
+ | ^
+ """
+
+ }
+
+ @Test def noJSImportAnnotOnTrait: Unit = {
+
+ s"""
+ @js.native
+ @JSImport("foo", JSImport.Namespace)
+ trait A extends js.Object
+ """ hasErrors
+ s"""
+ |newSource1.scala:6: error: Traits may not have an @JSImport annotation.
+ | @JSImport("foo", JSImport.Namespace)
+ | ^
+ """
+
+ s"""
+ @js.native
+ @JSImport("foo", JSImport.Namespace, globalFallback = "Foo")
+ trait A extends js.Object
+ """ hasErrors
+ s"""
+ |newSource1.scala:6: error: Traits may not have an @JSImport annotation.
+ | @JSImport("foo", JSImport.Namespace, globalFallback = "Foo")
+ | ^
+ """
+
+ }
+
+ @Test def noJSGlobalScopeExceptOnObjects: Unit = {
+ """
+ @js.native @JSGlobalScope
+ class A extends js.Any
+
+ @js.native @JSGlobalScope
+ trait B extends js.Any
+
+ object Container {
+ @js.native @JSGlobalScope
+ class C extends js.Any
+
+ @js.native @JSGlobalScope
+ trait D extends js.Any
+
+ @js.native @JSGlobalScope
+ val a: Int = js.native
+
+ @js.native @JSGlobalScope
+ def b: Int = js.native
+
+ @js.native @JSGlobalScope
+ def c(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSGlobalScope can only be used on native JS objects (with @js.native).
+ | @js.native @JSGlobalScope
+ | ^
+ |newSource1.scala:8: error: Traits may not have an @JSGlobalScope annotation.
+ | @js.native @JSGlobalScope
+ | ^
+ |newSource1.scala:12: error: @JSGlobalScope can only be used on native JS objects (with @js.native).
+ | @js.native @JSGlobalScope
+ | ^
+ |newSource1.scala:15: error: Traits may not have an @JSGlobalScope annotation.
+ | @js.native @JSGlobalScope
+ | ^
+ |newSource1.scala:18: error: @JSGlobalScope can only be used on native JS objects (with @js.native).
+ | @js.native @JSGlobalScope
+ | ^
+ |newSource1.scala:19: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | val a: Int = js.native
+ | ^
+ |newSource1.scala:21: error: @JSGlobalScope can only be used on native JS objects (with @js.native).
+ | @js.native @JSGlobalScope
+ | ^
+ |newSource1.scala:24: error: @JSGlobalScope can only be used on native JS objects (with @js.native).
+ | @js.native @JSGlobalScope
+ | ^
+ """
+ }
+
+ @Test def noTwoJSNativeLoadSpecAnnots: Unit = {
+ for {
+ (firstAnnotName, firstAnnot) <- JSNativeLoadSpecAnnots
+ (secondAnnotName, secondAnnot) <- JSNativeLoadSpecAnnots
+ } {
+ if (firstAnnotName == "JSGlobalScope" || secondAnnotName == "JSGlobalScope") {
+ s"""
+ |@js.native
+ |$firstAnnot
+ |$secondAnnot
+ |object A extends js.Object
+ """.stripMargin hasErrors s"""
+ |newSource1.scala:7: error: Native JS objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ |$secondAnnot
+ | ^
+ """
+ } else {
+ s"""
+ |@js.native
+ |$firstAnnot
+ |$secondAnnot
+ |object A extends js.Object
+ |
+ |@js.native
+ |$firstAnnot
+ |$secondAnnot
+ |class A extends js.Object
+ """.stripMargin hasErrors s"""
+ |newSource1.scala:7: error: Native JS objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ |$secondAnnot
+ | ^
+ |newSource1.scala:12: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ |$secondAnnot
+ | ^
+ """
+
+ if (firstAnnot != "@JSGlobal" && secondAnnot != "@JSGlobal") {
+ s"""
+ |object Container {
+ | @js.native
+ | $firstAnnot
+ | $secondAnnot
+ | val a: Int = js.native
+ |
+ | @js.native
+ | $firstAnnot
+ | $secondAnnot
+ | def b: Int = js.native
+ |
+ | @js.native
+ | $firstAnnot
+ | $secondAnnot
+ | def c(x: Int): Int = js.native
+ |}
+ """.stripMargin hasErrors s"""
+ |newSource1.scala:8: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | $secondAnnot
+ | ^
+ |newSource1.scala:13: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | $secondAnnot
+ | ^
+ |newSource1.scala:18: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | $secondAnnot
+ | ^
+ """
+ }
+ }
+ }
+ }
+
+ @Test def noJSNativeAnnotWithoutJSAny: Unit = {
+
+ // With the correct amount of native load spec annotations
+ """
+ @js.native @JSGlobal
+ class A
+
+ @js.native
+ trait B
+
+ @js.native @JSGlobal
+ object C
+
+ @js.native @JSGlobal
+ class D extends Enumeration
+
+ @js.native @JSGlobal
+ object E extends Enumeration
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
+ | class A
+ | ^
+ |newSource1.scala:9: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
+ | trait B
+ | ^
+ |newSource1.scala:12: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
+ | object C
+ | ^
+ |newSource1.scala:15: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
+ | class D extends Enumeration
+ | ^
+ |newSource1.scala:18: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
+ | object E extends Enumeration
+ | ^
+ """
+
+ // With an incorrect amount of native load spec annotations
+ """
+ @js.native
+ class A
+
+ @js.native @JSGlobal
+ trait B
+
+ @js.native
+ object C
+
+ @js.native
+ class D extends Enumeration
+
+ @js.native
+ object E extends Enumeration
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
+ | class A
+ | ^
+ |newSource1.scala:9: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
+ | trait B
+ | ^
+ |newSource1.scala:12: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
+ | object C
+ | ^
+ |newSource1.scala:15: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
+ | class D extends Enumeration
+ | ^
+ |newSource1.scala:18: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
+ | object E extends Enumeration
+ | ^
+ """
+
+ }
+
+ @Test def noInnerScalaClassTraitObjectInJSNative: Unit = {
+
+ for {
+ outer <- Seq("class", "trait")
+ inner <- Seq("class", "trait", "object")
+ } yield {
+ val jsGlobalAnnot =
+ if (outer == "trait") ""
+ else "@JSGlobal"
+ s"""
+ @js.native $jsGlobalAnnot
+ $outer A extends js.Object {
+ $inner A
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:7: error: Native JS traits, classes and objects cannot contain inner Scala traits, classes or objects (i.e., not extending js.Any)
+ | $inner A
+ | ${" " * inner.length} ^
+ """
+ }
+
+ }
+
+ @Test def noInnerNonNativeJSClassTraitObjectInJSNative: Unit = {
+
+ for {
+ outer <- Seq("class", "trait")
+ inner <- Seq("class", "trait", "object")
+ } yield {
+ val jsGlobalAnnot =
+ if (outer == "trait") ""
+ else "@JSGlobal"
+ s"""
+ @js.native $jsGlobalAnnot
+ $outer A extends js.Object {
+ $inner A extends js.Object
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:7: error: Native JS classes and traits cannot contain non-native JS classes, traits or objects
+ | $inner A extends js.Object
+ | ${" " * inner.length} ^
+ """
+ }
+
+ }
+
+ @Test def noScalaStuffInsideNativeJSObject: Unit = {
+
+ for {
+ inner <- Seq("class", "trait", "object")
+ } yield {
+ s"""
+ @js.native
+ @JSGlobal
+ object A extends js.Object {
+ $inner A
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:8: error: Native JS traits, classes and objects cannot contain inner Scala traits, classes or objects (i.e., not extending js.Any)
+ | $inner A
+ | ${" " * inner.length} ^
+ """
+ }
+
+ }
+
+ @Test def noNonSyntheticCompanionInsideNativeJSObject: Unit = {
+
+ // See #1891: The default parameter generates a synthetic companion object
+ // The synthetic companion should be allowed, but it may not be explicit
+
+ """
+ @js.native @JSGlobal object A extends js.Object {
+ @js.native class B(x: Int = ???) extends js.Object
+ object B
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS traits, classes and objects cannot contain inner Scala traits, classes or objects (i.e., not extending js.Any)
+ | object B
+ | ^
+ """
+
+ """
+ @js.native @JSGlobal object A extends js.Object {
+ @js.native class B(x: Int = ???) extends js.Object
+ }
+ """.succeeds()
+
+ }
+
+ @Test def noNonNativeJSTypesInsideNativeJSObject: Unit = {
+
+ for {
+ inner <- Seq("class", "object")
+ } yield {
+ s"""
+ @js.native
+ @JSGlobal
+ object A extends js.Object {
+ $inner A extends js.Object
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:8: error: Native JS objects cannot contain inner non-native JS classes or objects
+ | $inner A extends js.Object
+ | ${" " * inner.length} ^
+ """
+ }
+
+ }
+
+ @Test def jsNativeValDefsHaveJSNativeRHS: Unit = {
+ """
+ object Container {
+ @js.native @JSGlobal("a")
+ val a: Int = 1
+
+ @js.native @JSGlobal("b")
+ def b: Int = 3
+
+ @js.native @JSGlobal("c")
+ def c(x: Int): Int = x + 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: @js.native members may only call js.native.
+ | val a: Int = 1
+ | ^
+ |newSource1.scala:10: error: @js.native members may only call js.native.
+ | def b: Int = 3
+ | ^
+ |newSource1.scala:13: error: @js.native members may only call js.native.
+ | def c(x: Int): Int = x + 1
+ | ^
+ """
+ }
+
+ @Test def noJSBracketAccessOnJSNativeValDefs: Unit = {
+ """
+ object Container {
+ @js.native @JSGlobal("a")
+ @JSBracketAccess
+ val a: Int = js.native
+
+ @js.native @JSGlobal("b")
+ @JSBracketAccess
+ def b: Int = js.native
+
+ @js.native @JSGlobal("c")
+ @JSBracketAccess
+ def c(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: @JSBracketAccess can only be used on members of JS types.
+ | @JSBracketAccess
+ | ^
+ |newSource1.scala:11: error: @JSBracketAccess can only be used on members of JS types.
+ | @JSBracketAccess
+ | ^
+ |newSource1.scala:15: error: @JSBracketAccess can only be used on members of JS types.
+ | @JSBracketAccess
+ | ^
+ """
+ }
+
+ @Test def noJSBracketCallOnJSNativeValDefs: Unit = {
+ """
+ object Container {
+ @js.native @JSGlobal("a")
+ @JSBracketCall
+ val a: Int = js.native
+
+ @js.native @JSGlobal("b")
+ @JSBracketCall
+ def b: Int = js.native
+
+ @js.native @JSGlobal("c")
+ @JSBracketCall
+ def c(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: @JSBracketCall can only be used on members of JS types.
+ | @JSBracketCall
+ | ^
+ |newSource1.scala:11: error: @JSBracketCall can only be used on members of JS types.
+ | @JSBracketCall
+ | ^
+ |newSource1.scala:15: error: @JSBracketCall can only be used on members of JS types.
+ | @JSBracketCall
+ | ^
+ """
+ }
+
+ @Test def noJSNativeValDefsInJSObjects: Unit = {
+ """
+ object A {
+ val sym = js.Symbol("foo")
+ }
+
+ object NonNativeContainer extends js.Object {
+ @js.native @JSGlobal("a")
+ val a: Int = js.native
+
+ @js.native @JSGlobal("b")
+ def b: Int = js.native
+
+ @js.native @JSGlobal("c")
+ def c(x: Int): Int = js.native
+
+ @js.native @JSName("foo")
+ val d: Int = js.native
+
+ @js.native @JSName("bar")
+ def e(x: Int): Int = js.native
+
+ @js.native @JSName(A.sym)
+ val f: Int = js.native
+
+ @js.native @JSName(A.sym)
+ def g(x: Int): Int = js.native
+ }
+
+ @js.native @JSGlobal
+ object NativeContainer extends js.Object {
+ @js.native @JSGlobal("a")
+ val a: Int = js.native
+
+ @js.native @JSGlobal("b")
+ def b: Int = js.native
+
+ @js.native @JSGlobal("c")
+ def c(x: Int): Int = js.native
+
+ @js.native @JSName("foo")
+ val d: Int = js.native
+
+ @js.native @JSName("bar")
+ def e(x: Int): Int = js.native
+
+ @js.native @JSName(A.sym)
+ val f: Int = js.native
+
+ @js.native @JSName(A.sym)
+ def g(x: Int): Int = js.native
+ }
+
+ @js.native @JSGlobal
+ object NativeContainer2 extends js.Object {
+ @js.native
+ val a: Int = js.native
+
+ @js.native
+ def b: Int = js.native
+
+ @js.native
+ def c(x: Int): Int = js.native
+
+ @js.native
+ val d: Int = js.native
+
+ @js.native
+ def e(x: Int): Int = js.native
+
+ @js.native @JSName(A.sym)
+ val f: Int = js.native
+
+ @js.native @JSName(A.sym)
+ def g(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:11: error: @js.native vals and defs can only appear in static Scala objects
+ | val a: Int = js.native
+ | ^
+ |newSource1.scala:14: error: @js.native vals and defs can only appear in static Scala objects
+ | def b: Int = js.native
+ | ^
+ |newSource1.scala:17: error: @js.native vals and defs can only appear in static Scala objects
+ | def c(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:20: error: @js.native vals and defs can only appear in static Scala objects
+ | val d: Int = js.native
+ | ^
+ |newSource1.scala:23: error: @js.native vals and defs can only appear in static Scala objects
+ | def e(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:26: error: @js.native vals and defs can only appear in static Scala objects
+ | val f: Int = js.native
+ | ^
+ |newSource1.scala:29: error: @js.native vals and defs can only appear in static Scala objects
+ | def g(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:35: error: @js.native vals and defs can only appear in static Scala objects
+ | val a: Int = js.native
+ | ^
+ |newSource1.scala:38: error: @js.native vals and defs can only appear in static Scala objects
+ | def b: Int = js.native
+ | ^
+ |newSource1.scala:41: error: @js.native vals and defs can only appear in static Scala objects
+ | def c(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:44: error: @js.native vals and defs can only appear in static Scala objects
+ | val d: Int = js.native
+ | ^
+ |newSource1.scala:47: error: @js.native vals and defs can only appear in static Scala objects
+ | def e(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:50: error: @js.native vals and defs can only appear in static Scala objects
+ | val f: Int = js.native
+ | ^
+ |newSource1.scala:53: error: @js.native vals and defs can only appear in static Scala objects
+ | def g(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:59: error: @js.native vals and defs can only appear in static Scala objects
+ | val a: Int = js.native
+ | ^
+ |newSource1.scala:62: error: @js.native vals and defs can only appear in static Scala objects
+ | def b: Int = js.native
+ | ^
+ |newSource1.scala:65: error: @js.native vals and defs can only appear in static Scala objects
+ | def c(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:68: error: @js.native vals and defs can only appear in static Scala objects
+ | val d: Int = js.native
+ | ^
+ |newSource1.scala:71: error: @js.native vals and defs can only appear in static Scala objects
+ | def e(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:74: error: @js.native vals and defs can only appear in static Scala objects
+ | val f: Int = js.native
+ | ^
+ |newSource1.scala:77: error: @js.native vals and defs can only appear in static Scala objects
+ | def g(x: Int): Int = js.native
+ | ^
+ """
+ }
+
+ @Test def noJSNativeSetters: Unit = {
+ """
+ object Container {
+ @js.native @JSGlobal("foo")
+ def foo_=(x: Int): Int = js.native
+ @js.native @JSGlobal("bar")
+ def bar_=(x: Int, y: Int): Unit = js.native
+ @js.native @JSGlobal("goo")
+ def goo_=(x: Int*): Unit = js.native
+ @js.native @JSGlobal("hoo")
+ def hoo_=(x: Int = 1): Unit = js.native
+
+ @js.native @JSImport("module.js", "foo")
+ def foo2_=(x: Int): Int = js.native
+ @js.native @JSImport("module.js", "bar")
+ def bar2_=(x: Int, y: Int): Unit = js.native
+ @js.native @JSImport("module.js", "goo")
+ def goo2_=(x: Int*): Unit = js.native
+ @js.native @JSImport("module.js", "hoo")
+ def hoo2_=(x: Int = 1): Unit = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def foo_=(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:9: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def bar_=(x: Int, y: Int): Unit = js.native
+ | ^
+ |newSource1.scala:11: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def goo_=(x: Int*): Unit = js.native
+ | ^
+ |newSource1.scala:13: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def hoo_=(x: Int = 1): Unit = js.native
+ | ^
+ |newSource1.scala:16: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def foo2_=(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:18: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def bar2_=(x: Int, y: Int): Unit = js.native
+ | ^
+ |newSource1.scala:20: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def goo2_=(x: Int*): Unit = js.native
+ | ^
+ |newSource1.scala:22: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def hoo2_=(x: Int = 1): Unit = js.native
+ | ^
+ """
+
+ // containsErrors because some versions of the compiler use `_=` and some use `_=' (notice the quotes)
+ """
+ object Container {
+ @js.native @JSGlobal("foo")
+ val foo_= : Int = js.native
+ }
+ """ containsErrors
+ """
+ |newSource1.scala:7: error: Names of vals or vars may not end in `_=
+ """
+
+ // containsErrors because some versions of the compiler use `_=` and some use `_=' (notice the quotes)
+ """
+ object Container {
+ @js.native @JSImport("module.js")
+ val foo_= : Int = js.native
+ }
+ """ containsErrors
+ """
+ |newSource1.scala:7: error: Names of vals or vars may not end in `_=
+ """
+ }
+
+ @Test def noJSNativeVars: Unit = {
+ """
+ object Container {
+ @js.native @JSGlobal("foo")
+ var foo: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | var foo: Int = js.native
+ | ^
+ """
+ }
+
+ @Test def noJSNativeLazyVals: Unit = {
+ """
+ object Container {
+ @js.native @JSGlobal("foo")
+ lazy val foo: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | lazy val foo: Int = js.native
+ | ^
+ """
+ }
+
+ @Test def jsNativeValDefsCannotImplementAbstractMethod: Unit = {
+ """
+ abstract class Parent {
+ val a: Int
+ def b: Int
+ def c(x: Int): Int
+ }
+
+ object Container extends Parent {
+ @js.native @JSGlobal("a")
+ val a: Int = js.native
+
+ @js.native @JSGlobal("b")
+ def b: Int = js.native
+
+ @js.native @JSGlobal("c")
+ def c(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:13: error: An @js.native member cannot implement the inherited member Parent.a
+ | val a: Int = js.native
+ | ^
+ |newSource1.scala:16: error: An @js.native member cannot implement the inherited member Parent.b
+ | def b: Int = js.native
+ | ^
+ |newSource1.scala:19: error: An @js.native member cannot implement the inherited member Parent.c
+ | def c(x: Int): Int = js.native
+ | ^
+ """
+ }
+
+ @Test def jsNativeValDefsCannotOverrideConcreteMethod: Unit = {
+ """
+ class Parent {
+ val a: Int = 1
+ def b: Int = 2
+ def c(x: Int): Int = x + 1
+ }
+
+ object Container extends Parent {
+ @js.native @JSGlobal("a")
+ override val a: Int = js.native
+
+ @js.native @JSGlobal("b")
+ override def b: Int = js.native
+
+ @js.native @JSGlobal("c")
+ override def c(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:13: error: An @js.native member cannot override the inherited member Parent.a
+ | override val a: Int = js.native
+ | ^
+ |newSource1.scala:16: error: An @js.native member cannot override the inherited member Parent.b
+ | override def b: Int = js.native
+ | ^
+ |newSource1.scala:19: error: An @js.native member cannot override the inherited member Parent.c
+ | override def c(x: Int): Int = js.native
+ | ^
+ """
+ }
+
+ @Test def noBadSetters: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ def foo_=(x: Int): Int = js.native
+ def bar_=(x: Int, y: Int): Unit = js.native
+ def goo_=(x: Int*): Unit = js.native
+ def hoo_=(x: Int = 1): Unit = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: JS setters must return Unit
+ | def foo_=(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:9: error: JS setters must have exactly one argument
+ | def bar_=(x: Int, y: Int): Unit = js.native
+ | ^
+ |newSource1.scala:10: error: JS setters may not have repeated params
+ | def goo_=(x: Int*): Unit = js.native
+ | ^
+ |newSource1.scala:11: error: JS setters may not have default params
+ | def hoo_=(x: Int = 1): Unit = js.native
+ | ^
+ """
+
+ }
+
+ @Test def noBadBracketAccess: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @js.annotation.JSBracketAccess
+ def foo(): Int = js.native
+
+ @js.annotation.JSBracketAccess
+ def bar(x: Int, y: Int, z: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: @JSBracketAccess methods must have one or two parameters
+ | def foo(): Int = js.native
+ | ^
+ |newSource1.scala:12: error: @JSBracketAccess methods must have one or two parameters
+ | def bar(x: Int, y: Int, z: Int): Int = js.native
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @js.annotation.JSBracketAccess
+ def foo(x: Int, y: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: @JSBracketAccess methods with two parameters must return Unit
+ | def foo(x: Int, y: Int): Int = js.native
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @js.annotation.JSBracketAccess
+ def bar(x: Int*): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: @JSBracketAccess methods may not have repeated parameters
+ | def bar(x: Int*): Int = js.native
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @js.annotation.JSBracketAccess
+ def bar(x: Int = 1): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: @JSBracketAccess methods may not have default parameters
+ | def bar(x: Int = 1): Int = js.native
+ | ^
+ """
+
+ }
+
+ @Test def noBadBracketCall: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @js.annotation.JSBracketCall
+ def foo(): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: @JSBracketCall methods must have at least one non-repeated parameter
+ | def foo(): Int = js.native
+ | ^
+ """
+
+ }
+
+ @Test
+ def noJSOperatorAndJSName: Unit = {
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSOperator
+ @JSName("bar")
+ def +(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall.
+ | @JSName("bar")
+ | ^
+ """
+ }
+
+ @Test // #4284
+ def noBracketAccessAndJSName: Unit = {
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSBracketAccess
+ @JSName("bar")
+ def bar(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall.
+ | @JSName("bar")
+ | ^
+ """
+ }
+
+ // #4284
+ @Test def noBracketCallAndJSName: Unit = {
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSBracketCall
+ @JSName("bar")
+ def bar(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall.
+ | @JSName("bar")
+ | ^
+ """
+ }
+
+ // #4284
+ @Test def noBracketAccessAndBracketCall: Unit = {
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSBracketAccess
+ @JSBracketCall
+ def bar(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall.
+ | @JSBracketCall
+ | ^
+ """
+ }
+
+ @Test def noBadUnaryOp: Unit = {
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSOperator
+ def unary_!(x: Int*): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: @JSOperator methods with the name 'unary_!' may not have any parameters
+ | def unary_!(x: Int*): Int = js.native
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSOperator
+ def unary_-(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: @JSOperator methods with the name 'unary_-' may not have any parameters
+ | def unary_-(x: Int): Int = js.native
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSOperator
+ def unary_%(): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: @JSOperator cannot be used on a method with the name 'unary_%' because it is not one of the JavaScript operators
+ | def unary_%(): Int = js.native
+ | ^
+ """
+ }
+
+ @Test def noBadBinaryOp: Unit = {
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ def +(x: Int*): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: warning: Method '+' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators
+ | def +(x: Int*): Int = js.native
+ | ^
+ |newSource1.scala:8: error: methods representing binary operations may not have repeated parameters
+ | def +(x: Int*): Int = js.native
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSOperator
+ def +(x: Int*): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: methods representing binary operations may not have repeated parameters
+ | def +(x: Int*): Int = js.native
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSOperator
+ def +(x: Int, y: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: @JSOperator methods with the name '+' must have exactly one parameter
+ | def +(x: Int, y: Int): Int = js.native
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSOperator
+ def %%(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: @JSOperator cannot be used on a method with the name '%%' because it is not one of the JavaScript operators
+ | def %%(x: Int): Int = js.native
+ | ^
+ """
+ }
+
+ @Test def onlyJSTraits: Unit = {
+
+ """
+ trait A
+
+ @js.native
+ @JSGlobal
+ class B extends js.Object with A
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: B extends A which does not extend js.Any.
+ | class B extends js.Object with A
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class B extends js.Object with java.io.Serializable
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: B extends java.io.Serializable which does not extend js.Any.
+ | class B extends js.Object with java.io.Serializable
+ | ^
+ """
+
+ }
+
+ @Test def noCaseClassObject: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ case class A(x: Int) extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Classes and objects extending js.Any may not have a case modifier
+ | case class A(x: Int) extends js.Object
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ case object B extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Classes and objects extending js.Any may not have a case modifier
+ | case object B extends js.Object
+ | ^
+ """
+
+ """
+ case class A(x: Int) extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Classes and objects extending js.Any may not have a case modifier
+ | case class A(x: Int) extends js.Object
+ | ^
+ """
+
+ """
+ case object B extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Classes and objects extending js.Any may not have a case modifier
+ | case object B extends js.Object
+ | ^
+ """
+
+ }
+
+ @Test def noNativeJSNestedInScalaClassTrait: Unit = {
+
+ val outers = List("class", "trait")
+ val inners = List("trait", "class", "object")
+
+ for {
+ outer <- outers
+ inner <- inners
+ } yield {
+ val jsGlobalAnnot =
+ if (inner == "trait") ""
+ else "@JSGlobal"
+
+ val errTrg = if (inner == "object") "objects" else "classes"
+
+ s"""
+ $outer A {
+ @js.native $jsGlobalAnnot
+ $inner Inner extends js.Object
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:7: error: Scala traits and classes may not have native JS members
+ | $inner Inner extends js.Object
+ | ${" " * inner.length}^
+ """
+ }
+
+ }
+
+ @Test def noNativeJSNestedInNonNativeJS: Unit = {
+
+ val outers = List("class", "trait", "object")
+ val inners = List("class", "trait", "object")
+
+ for {
+ outer <- outers
+ inner <- inners
+ } yield {
+ val jsGlobalAnnot =
+ if (inner == "trait") ""
+ else "@JSGlobal"
+
+ val errTrg = if (inner == "object") "objects" else "classes"
+
+ s"""
+ $outer A extends js.Object {
+ @js.native $jsGlobalAnnot
+ $inner Inner extends js.Object
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:7: error: non-native JS classes, traits and objects may not have native JS members
+ | $inner Inner extends js.Object
+ | ${" " * inner.length}^
+ """
+ }
+
+ }
+
+ @Test def noLocalJSNative: Unit = {
+ """
+ object A {
+ def a = {
+ @js.native @JSGlobal
+ class B extends js.Object
+
+ @js.native @JSGlobal
+ object C extends js.Object
+
+ @js.native @JSGlobal
+ val d: Int = js.native
+
+ @js.native @JSGlobal
+ var e: Int = js.native
+
+ @js.native @JSGlobal
+ def f: Int = js.native
+
+ @js.native @JSGlobal
+ def f_=(v: Int): Unit = js.native
+
+ @js.native @JSGlobal
+ def g(x: Int): Int = js.native
+
+ @js.native @JSGlobal
+ lazy val h: Int = js.native
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: @js.native is not allowed on local definitions
+ | class B extends js.Object
+ | ^
+ |newSource1.scala:11: error: @js.native is not allowed on local definitions
+ | object C extends js.Object
+ | ^
+ |newSource1.scala:14: error: @js.native is not allowed on local definitions
+ | val d: Int = js.native
+ | ^
+ |newSource1.scala:17: error: @js.native is not allowed on local definitions
+ | var e: Int = js.native
+ | ^
+ |newSource1.scala:20: error: @js.native is not allowed on local definitions
+ | def f: Int = js.native
+ | ^
+ |newSource1.scala:23: error: @js.native is not allowed on local definitions
+ | def f_=(v: Int): Unit = js.native
+ | ^
+ |newSource1.scala:26: error: @js.native is not allowed on local definitions
+ | def g(x: Int): Int = js.native
+ | ^
+ |newSource1.scala:29: error: @js.native is not allowed on local definitions
+ | lazy val h: Int = js.native
+ | ^
+ """
+ }
+
+ @Test def noNativeInJSAny: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @native
+ def value: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: Methods in a js.Any may not be @native
+ | def value: Int = js.native
+ | ^
+ """
+
+ }
+
+ @Test def checkJSAnyBody: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ def value: Int = ???
+ val x: Int = ???
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: Concrete members of JS native types may only call js.native.
+ | def value: Int = ???
+ | ^
+ |newSource1.scala:9: error: Concrete members of JS native types may only call js.native.
+ | val x: Int = ???
+ | ^
+ """
+
+ }
+
+ @Test def noWarnJSAnyDeferred: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ abstract class A extends js.Object {
+ def value: Int
+ val x: Int
+ }
+ """.hasNoWarns()
+
+ """
+ @js.native
+ trait A extends js.Object {
+ def value: Int
+ val x: Int
+ }
+ """.hasNoWarns()
+
+ }
+
+ @Test def noCallSecondaryCtor: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class A(x: Int, y: Int) extends js.Object {
+ def this(x: Int) = this(x, 5)
+ def this() = this(7)
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: A secondary constructor of a class extending js.Any may only call the primary constructor
+ | def this() = this(7)
+ | ^
+ """
+
+ }
+
+ @Test def noPrivateMemberInNative: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ private[this] val a: Int = js.native
+ private val b: Int = js.native
+ private[A] val c: Int = js.native
+
+ private[this] var d: Int = js.native
+ private var e: Int = js.native
+ private[A] var f: Int = js.native
+
+ private[this] def g(): Int = js.native
+ private def h(): Int = js.native
+ private[A] def i(): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: Native JS classes may not have private members. Use a public member in a private facade instead.
+ | private[this] val a: Int = js.native
+ | ^
+ |newSource1.scala:9: error: Native JS classes may not have private members. Use a public member in a private facade instead.
+ | private val b: Int = js.native
+ | ^
+ |newSource1.scala:10: error: Native JS classes may not have private members. Use a public member in a private facade instead.
+ | private[A] val c: Int = js.native
+ | ^
+ |newSource1.scala:12: error: Native JS classes may not have private members. Use a public member in a private facade instead.
+ | private[this] var d: Int = js.native
+ | ^
+ |newSource1.scala:13: error: Native JS classes may not have private members. Use a public member in a private facade instead.
+ | private var e: Int = js.native
+ | ^
+ |newSource1.scala:14: error: Native JS classes may not have private members. Use a public member in a private facade instead.
+ | private[A] var f: Int = js.native
+ | ^
+ |newSource1.scala:16: error: Native JS classes may not have private members. Use a public member in a private facade instead.
+ | private[this] def g(): Int = js.native
+ | ^
+ |newSource1.scala:17: error: Native JS classes may not have private members. Use a public member in a private facade instead.
+ | private def h(): Int = js.native
+ | ^
+ |newSource1.scala:18: error: Native JS classes may not have private members. Use a public member in a private facade instead.
+ | private[A] def i(): Int = js.native
+ | ^
+ """
+
+ }
+
+ @Test def noPrivateConstructorInNative: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class A private () extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS classes may not have private constructors. Use `private[this]` to declare an internal constructor.
+ | class A private () extends js.Object
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class A private[A] () extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS classes may not have private constructors. Use `private[this]` to declare an internal constructor.
+ | class A private[A] () extends js.Object
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class A private[this] () extends js.Object
+ """.hasNoWarns()
+
+ }
+
+ @Test def noUseJsNative: Unit = {
+
+ """
+ class A {
+ def foo = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: js.native may only be used as stub implementation in facade types
+ | def foo = js.native
+ | ^
+ """
+
+ }
+
+ @Test def warnNothingInNativeJS: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ def foo = js.native
+ val bar = js.native
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:8: warning: The type of foo got inferred as Nothing. To suppress this warning, explicitly ascribe the type.
+ | def foo = js.native
+ | ^
+ |newSource1.scala:9: warning: The type of bar got inferred as Nothing. To suppress this warning, explicitly ascribe the type.
+ | val bar = js.native
+ | ^
+ """
+
+ }
+
+ @Test def nativeClassHasLoadingSpec: Unit = {
+ """
+ @js.native
+ class A extends js.Object
+
+ @js.native
+ abstract class B extends js.Object
+
+ object Container {
+ @js.native
+ class C extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | class A extends js.Object
+ | ^
+ |newSource1.scala:9: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | abstract class B extends js.Object
+ | ^
+ |newSource1.scala:13: error: Native JS classes, vals and defs must have exactly one annotation among @JSGlobal and @JSImport.
+ | class C extends js.Object
+ | ^
+ """
+ }
+
+ @Test def nativeObjectHasLoadingSpec: Unit = {
+ """
+ @js.native
+ object A extends js.Object
+
+ object Container {
+ @js.native
+ object B extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Native JS objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ | object A extends js.Object
+ | ^
+ |newSource1.scala:10: error: Native JS objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ | object B extends js.Object
+ | ^
+ """
+ }
+
+ @Test def noNativeDefinitionNamedApplyWithoutExplicitName: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class apply extends js.Object
+
+ @js.native
+ @JSGlobal
+ object apply extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ |newSource1.scala:10: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ """
+
+ """
+ @js.native
+ @JSImport("foo.js")
+ class apply extends js.Object
+
+ @js.native
+ @JSImport("foo.js")
+ object apply extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Native JS definitions named 'apply' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ |newSource1.scala:10: error: Native JS definitions named 'apply' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSGlobal
+ class apply extends js.Object
+
+ @js.native
+ @JSGlobal
+ object apply extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ |newSource1.scala:11: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSImport("foo.js")
+ class apply extends js.Object
+
+ @js.native
+ @JSImport("foo.js")
+ object apply extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ |newSource1.scala:11: error: Native JS definitions named 'apply' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ """
+
+ """
+ package object A {
+ @js.native
+ @JSGlobal
+ class apply extends js.Object
+
+ @js.native
+ @JSGlobal
+ object apply extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ |newSource1.scala:11: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ """
+
+ """
+ package object A {
+ @js.native
+ @JSImport("foo.js")
+ class apply extends js.Object
+
+ @js.native
+ @JSImport("foo.js")
+ object apply extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ |newSource1.scala:11: error: Native JS definitions named 'apply' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSGlobal
+ val apply: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSImport("foo.js")
+ val apply: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSGlobal
+ def apply: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSImport("foo.js")
+ def apply: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSGlobal
+ def apply(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSImport("foo.js")
+ def apply(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions named 'apply' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ """
+
+ """
+ @JSGlobal("apply")
+ @js.native
+ class apply extends js.Object
+
+ @JSGlobal("apply")
+ @js.native
+ object apply extends js.Object
+
+ object A {
+ @JSGlobal("apply")
+ @js.native
+ class apply extends js.Object
+
+ @JSGlobal("apply")
+ @js.native
+ object apply extends js.Object
+ }
+
+ object B {
+ @JSGlobal("apply")
+ @js.native
+ val apply: Int = js.native
+ }
+
+ object C {
+ @JSGlobal("apply")
+ @js.native
+ def apply: Int = js.native
+ }
+
+ object D {
+ @JSGlobal("apply")
+ @js.native
+ def apply(x: Int): Int = js.native
+ }
+ """.hasNoWarns()
+
+ """
+ @JSImport("foo.js", "apply")
+ @js.native
+ class apply extends js.Object
+
+ @JSImport("foo.js", "apply")
+ @js.native
+ object apply extends js.Object
+
+ object A {
+ @JSImport("foo.js", "apply")
+ @js.native
+ class apply extends js.Object
+
+ @JSImport("foo.js", "apply")
+ @js.native
+ object apply extends js.Object
+ }
+
+ object B {
+ @JSImport("foo.js", "apply")
+ @js.native
+ val apply: Int = js.native
+ }
+
+ object C {
+ @JSImport("foo.js", "apply")
+ @js.native
+ def apply: Int = js.native
+ }
+
+ object D {
+ @JSImport("foo.js", "apply")
+ @js.native
+ def apply(x: Int): Int = js.native
+ }
+ """.hasNoWarns()
+
+ """
+ @JSImport("foo.js", "apply", globalFallback = "apply")
+ @js.native
+ class apply extends js.Object
+
+ @JSImport("foo.js", "apply", globalFallback = "apply")
+ @js.native
+ object apply extends js.Object
+
+ object A {
+ @JSImport("foo.js", "apply", globalFallback = "apply")
+ @js.native
+ class apply extends js.Object
+
+ @JSImport("foo.js", "apply", globalFallback = "apply")
+ @js.native
+ object apply extends js.Object
+ }
+ """.hasNoWarns()
+
+ }
+
+ @Test def noNativeDefinitionWithSetterNameWithoutExplicitName: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class foo_= extends js.Object
+
+ @js.native
+ @JSGlobal
+ object foo_= extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ |newSource1.scala:10: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ """
+
+ """
+ @js.native
+ @JSImport("foo.js")
+ class foo_= extends js.Object
+
+ @js.native
+ @JSImport("foo.js")
+ object foo_= extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ |newSource1.scala:10: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSGlobal
+ class foo_= extends js.Object
+
+ @js.native
+ @JSGlobal
+ object foo_= extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ |newSource1.scala:11: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSImport("foo.js")
+ class foo_= extends js.Object
+
+ @js.native
+ @JSImport("foo.js")
+ object foo_= extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ |newSource1.scala:11: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ """
+
+ """
+ package object A {
+ @js.native
+ @JSGlobal
+ class foo_= extends js.Object
+
+ @js.native
+ @JSGlobal
+ object foo_= extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ |newSource1.scala:11: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ """
+
+ """
+ package object A {
+ @js.native
+ @JSImport("foo.js")
+ class foo_= extends js.Object
+
+ @js.native
+ @JSImport("foo.js")
+ object foo_= extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ |newSource1.scala:11: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ """
+
+ // containsErrors because some versions of the compiler use `_=` and some use `_=' (notice the quotes)
+ """
+ object A {
+ @js.native
+ @JSGlobal
+ val foo_= : Int = js.native
+ }
+ """ containsErrors
+ """
+ |newSource1.scala:8: error: Names of vals or vars may not end in `_=
+ """
+
+ // containsErrors because some versions of the compiler use `_=` and some use `_=' (notice the quotes)
+ """
+ object A {
+ @js.native
+ @JSImport("foo.js")
+ val foo_= : Int = js.native
+ }
+ """ containsErrors
+ """
+ |newSource1.scala:8: error: Names of vals or vars may not end in `_=
+ """
+
+ // containsErrors because some versions of the compiler use `_=` and some use `_=' (notice the quotes)
+ """
+ object A {
+ @js.native
+ @JSGlobal
+ var foo_= : Int = js.native
+ }
+ """ containsErrors
+ """
+ |newSource1.scala:8: error: Names of vals or vars may not end in `_=
+ """
+
+ """
+ object A {
+ @js.native
+ @JSGlobal
+ def foo_= : Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def foo_= : Int = js.native
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSGlobal("foo")
+ def foo_= : Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def foo_= : Int = js.native
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSImport("foo.js")
+ def foo_= : Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def foo_= : Int = js.native
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSImport("foo.js", "foo")
+ def foo_= : Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def foo_= : Int = js.native
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSGlobal
+ def foo_=(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal
+ | @JSGlobal
+ | ^
+ |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def foo_=(x: Int): Int = js.native
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSGlobal("foo")
+ def foo_=(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def foo_=(x: Int): Int = js.native
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSImport("foo.js")
+ def foo_=(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
+ | ^
+ |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def foo_=(x: Int): Int = js.native
+ | ^
+ """
+
+ """
+ object A {
+ @js.native
+ @JSImport("foo.js", "foo")
+ def foo_=(x: Int): Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: @js.native is not allowed on vars, lazy vals and setter defs
+ | def foo_=(x: Int): Int = js.native
+ | ^
+ """
+
+ """
+ @JSGlobal("foo")
+ @js.native
+ class foo_= extends js.Object
+
+ @JSGlobal("foo")
+ @js.native
+ object foo_= extends js.Object
+
+ object A {
+ @JSGlobal("foo")
+ @js.native
+ class foo_= extends js.Object
+
+ @JSGlobal("foo")
+ @js.native
+ object foo_= extends js.Object
+ }
+ """.hasNoWarns()
+
+ """
+ @JSImport("foo.js", "foo_=")
+ @js.native
+ class foo_= extends js.Object
+
+ @JSImport("foo.js", "foo_=")
+ @js.native
+ object foo_= extends js.Object
+
+ object A {
+ @JSImport("foo.js", "foo_=")
+ @js.native
+ class foo_= extends js.Object
+
+ @JSImport("foo.js", "foo_=")
+ @js.native
+ object foo_= extends js.Object
+ }
+ """.hasNoWarns()
+
+ """
+ @JSImport("foo.js", "foo_=", globalFallback = "foo")
+ @js.native
+ class foo_= extends js.Object
+
+ @JSImport("foo.js", "foo_=", globalFallback = "foo")
+ @js.native
+ object foo_= extends js.Object
+
+ object A {
+ @JSImport("foo.js", "foo_=", globalFallback = "foo")
+ @js.native
+ class foo_= extends js.Object
+
+ @JSImport("foo.js", "foo_=", globalFallback = "foo")
+ @js.native
+ object foo_= extends js.Object
+ }
+ """.hasNoWarns()
+
+ }
+
+ @Test def noNonLiteralJSName: Unit = {
+
+ """
+ import js.annotation.JSName
+
+ object A {
+ val a = "Hello"
+ final val b = "World"
+ }
+
+ @js.native
+ @JSGlobal
+ class B extends js.Object {
+ @JSName(A.a)
+ def foo: Int = js.native
+ @JSName(A.b)
+ def bar: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:15: error: A string argument to JSName must be a literal string
+ | @JSName(A.a)
+ | ^
+ """
+
+ }
+
+ @Test def noNonStaticStableJSNameSymbol: Unit = {
+
+ """
+ import js.annotation.JSName
+
+ class A {
+ val a = js.Symbol("foo")
+ }
+
+ @js.native
+ @JSGlobal
+ class B extends js.Object {
+ @JSName(js.Symbol())
+ def foo: Int = js.native
+ @JSName(new A().a)
+ def bar: Int = js.native
+ }
+
+ class C extends js.Object {
+ @JSName(js.Symbol())
+ def foo: Int = js.native
+ @JSName(new A().a)
+ def bar: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:14: error: A js.Symbol argument to JSName must be a static, stable identifier
+ | @JSName(js.Symbol())
+ | ^
+ |newSource1.scala:16: error: A js.Symbol argument to JSName must be a static, stable identifier
+ | @JSName(new A().a)
+ | ^
+ |newSource1.scala:21: error: A js.Symbol argument to JSName must be a static, stable identifier
+ | @JSName(js.Symbol())
+ | ^
+ |newSource1.scala:23: error: A js.Symbol argument to JSName must be a static, stable identifier
+ | @JSName(new A().a)
+ | ^
+ """
+
+ }
+
+ @Test def noSelfReferenceJSNameSymbol: Unit = {
+
+ """
+ object A extends js.Object {
+ val a = js.Symbol("foo")
+
+ @JSName(a)
+ def foo: Int = 1
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:8: warning: This symbol is defined in the same object as the annotation's target. This will cause a stackoverflow at runtime
+ | @JSName(a)
+ | ^
+ """
+
+ // Native objects are OK, since we do not control definition order.
+ """
+ @JSGlobal
+ @js.native
+ object A extends js.Object {
+ val a: js.Symbol = js.native
+
+ @JSName(a)
+ def foo: Int = js.native
+ }
+ """.succeeds()
+
+ }
+
+ @Test def noJSGlobalOnMembersOfClassesAndTraits: Unit = {
+
+ for (outer <- Seq("class", "trait")) {
+ s"""
+ @js.native ${if (outer == "trait") "" else "@JSGlobal"}
+ $outer Foo extends js.Object {
+ @JSGlobal("bar1")
+ val bar1: Int = js.native
+ @JSGlobal("bar2")
+ var bar2: Int = js.native
+ @JSGlobal("bar3")
+ def bar3: Int = js.native
+
+ @js.native
+ @JSGlobal("Inner")
+ class Inner extends js.Object
+
+ @js.native
+ @JSGlobal("Inner")
+ object Inner extends js.Object
+
+ @js.native
+ @JSGlobal
+ class InnerImplied extends js.Object
+
+ @js.native
+ @JSGlobal
+ object InnerImplied extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal("bar1")
+ | ^
+ |newSource1.scala:9: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal("bar2")
+ | ^
+ |newSource1.scala:11: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal("bar3")
+ | ^
+ |newSource1.scala:15: error: Nested JS classes and objects cannot have an @JSGlobal annotation.
+ | @JSGlobal("Inner")
+ | ^
+ |newSource1.scala:19: error: Nested JS classes and objects cannot have an @JSGlobal annotation.
+ | @JSGlobal("Inner")
+ | ^
+ |newSource1.scala:23: error: Nested JS classes and objects cannot have an @JSGlobal annotation.
+ | @JSGlobal
+ | ^
+ |newSource1.scala:27: error: Nested JS classes and objects cannot have an @JSGlobal annotation.
+ | @JSGlobal
+ | ^
+ """
+ }
+
+ }
+
+ @Test def noJSGlobalOnMembersOfObjects: Unit = {
+
+ s"""
+ @js.native @JSGlobal
+ object Foo extends js.Object {
+ @JSGlobal("bar1")
+ val bar1: Int = js.native
+ @JSGlobal("bar2")
+ var bar2: Int = js.native
+ @JSGlobal("bar3")
+ def bar3: Int = js.native
+
+ @js.native
+ @JSGlobal("Inner")
+ class Inner extends js.Object
+
+ @js.native
+ @JSGlobal("Inner")
+ object Inner extends js.Object
+
+ @js.native
+ @JSGlobal
+ class InnerImplied extends js.Object
+
+ @js.native
+ @JSGlobal
+ object InnerImplied extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal("bar1")
+ | ^
+ |newSource1.scala:9: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal("bar2")
+ | ^
+ |newSource1.scala:11: error: @JSGlobal can only be used on native JS definitions (with @js.native).
+ | @JSGlobal("bar3")
+ | ^
+ |newSource1.scala:15: error: Nested JS classes and objects cannot have an @JSGlobal annotation.
+ | @JSGlobal("Inner")
+ | ^
+ |newSource1.scala:19: error: Nested JS classes and objects cannot have an @JSGlobal annotation.
+ | @JSGlobal("Inner")
+ | ^
+ |newSource1.scala:23: error: Nested JS classes and objects cannot have an @JSGlobal annotation.
+ | @JSGlobal
+ | ^
+ |newSource1.scala:27: error: Nested JS classes and objects cannot have an @JSGlobal annotation.
+ | @JSGlobal
+ | ^
+ """
+
+ }
+
+ @Test def noJSImportOnMembersOfClassesAndTraits: Unit = {
+
+ for {
+ outer <- Seq("class", "trait")
+ fallbackStr <- Seq("", ", globalFallback = \"Foo\"")
+ } {
+ s"""
+ @js.native ${if (outer == "trait") "" else "@JSGlobal"}
+ $outer Foo extends js.Object {
+ @JSImport("bar1", JSImport.Namespace$fallbackStr)
+ val bar1: Int = js.native
+ @JSImport("bar2", JSImport.Namespace$fallbackStr)
+ var bar2: Int = js.native
+ @JSImport("bar3", JSImport.Namespace$fallbackStr)
+ def bar3: Int = js.native
+
+ @js.native
+ @JSImport("Inner", JSImport.Namespace$fallbackStr)
+ class Inner extends js.Object
+
+ @js.native
+ @JSImport("Inner", JSImport.Namespace$fallbackStr)
+ object Inner extends js.Object
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:7: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("bar1", JSImport.Namespace$fallbackStr)
+ | ^
+ |newSource1.scala:9: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("bar2", JSImport.Namespace$fallbackStr)
+ | ^
+ |newSource1.scala:11: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("bar3", JSImport.Namespace$fallbackStr)
+ | ^
+ |newSource1.scala:15: error: Nested JS classes and objects cannot have an @JSImport annotation.
+ | @JSImport("Inner", JSImport.Namespace$fallbackStr)
+ | ^
+ |newSource1.scala:19: error: Nested JS classes and objects cannot have an @JSImport annotation.
+ | @JSImport("Inner", JSImport.Namespace$fallbackStr)
+ | ^
+ """
+ }
+
+ }
+
+ @Test def noJSImportOnMembersOfObjects: Unit = {
+
+ for {
+ fallbackStr <- Seq("", ", globalFallback = \"Foo\"")
+ } {
+ s"""
+ @js.native @JSGlobal
+ object Foo extends js.Object {
+ @JSImport("bar1", JSImport.Namespace$fallbackStr)
+ val bar1: Int = js.native
+ @JSImport("bar2", JSImport.Namespace$fallbackStr)
+ var bar2: Int = js.native
+ @JSImport("bar3", JSImport.Namespace$fallbackStr)
+ def bar3: Int = js.native
+
+ @js.native
+ @JSImport("Inner", JSImport.Namespace$fallbackStr)
+ class Inner extends js.Object
+
+ @js.native
+ @JSImport("Inner", JSImport.Namespace$fallbackStr)
+ object Inner extends js.Object
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:7: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("bar1", JSImport.Namespace$fallbackStr)
+ | ^
+ |newSource1.scala:9: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("bar2", JSImport.Namespace$fallbackStr)
+ | ^
+ |newSource1.scala:11: error: @JSImport can only be used on native JS definitions (with @js.native).
+ | @JSImport("bar3", JSImport.Namespace$fallbackStr)
+ | ^
+ |newSource1.scala:15: error: Nested JS classes and objects cannot have an @JSImport annotation.
+ | @JSImport("Inner", JSImport.Namespace$fallbackStr)
+ | ^
+ |newSource1.scala:19: error: Nested JS classes and objects cannot have an @JSImport annotation.
+ | @JSImport("Inner", JSImport.Namespace$fallbackStr)
+ | ^
+ """
+ }
+
+ }
+
+ @Test def noNonLiteralJSGlobal: Unit = {
+
+ """
+ object A {
+ val a = "Hello"
+ }
+
+ @JSGlobal(A.a)
+ @js.native
+ object B extends js.Object
+
+ @JSGlobal(A.a)
+ @js.native
+ class C extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: The argument to @JSGlobal must be a literal string.
+ | @JSGlobal(A.a)
+ | ^
+ |newSource1.scala:13: error: The argument to @JSGlobal must be a literal string.
+ | @JSGlobal(A.a)
+ | ^
+ """
+
+ }
+
+ @Test def noNonJSIdentifierJSGlobal: Unit = {
+
+ """
+ @js.native
+ @JSGlobal
+ class `not-a-valid-JS-identifier` extends js.Object
+
+ @js.native
+ @JSGlobal("not-a-valid-JS-identifier")
+ object A extends js.Object
+
+ @js.native
+ @JSGlobal("not-a-valid-JS-identifier.further")
+ object B extends js.Object
+
+ @js.native
+ @JSGlobal("TopLevel.not-a-valid-JS-identifier") // valid
+ object C extends js.Object
+
+ @js.native
+ @JSGlobal("")
+ object D extends js.Object
+
+ @js.native
+ @JSGlobal(".tricky")
+ object E extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: The name of a JS global variable must be a valid JS identifier (got 'not-a-valid-JS-identifier')
+ | class `not-a-valid-JS-identifier` extends js.Object
+ | ^
+ |newSource1.scala:11: error: The name of a JS global variable must be a valid JS identifier (got 'not-a-valid-JS-identifier')
+ | object A extends js.Object
+ | ^
+ |newSource1.scala:15: error: The name of a JS global variable must be a valid JS identifier (got 'not-a-valid-JS-identifier')
+ | object B extends js.Object
+ | ^
+ |newSource1.scala:23: error: The name of a JS global variable must be a valid JS identifier (got '')
+ | object D extends js.Object
+ | ^
+ |newSource1.scala:27: error: The name of a JS global variable must be a valid JS identifier (got '')
+ | object E extends js.Object
+ | ^
+ """
+
+ """
+ @js.native
+ @JSImport("foo.js", "foo", globalFallback = "not-a-valid-JS-identifier")
+ object A extends js.Object
+
+ @js.native
+ @JSImport("foo.js", "foo", globalFallback = "not-a-valid-JS-identifier.further")
+ object B extends js.Object
+
+ @js.native
+ @JSImport("foo.js", "foo", globalFallback = "TopLevel.not-a-valid-JS-identifier") // valid
+ object C extends js.Object
+
+ @js.native
+ @JSImport("foo.js", "foo", globalFallback = "")
+ object D extends js.Object
+
+ @js.native
+ @JSImport("foo.js", "foo", globalFallback = ".tricky")
+ object E extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: The name of a JS global variable must be a valid JS identifier (got 'not-a-valid-JS-identifier')
+ | object A extends js.Object
+ | ^
+ |newSource1.scala:11: error: The name of a JS global variable must be a valid JS identifier (got 'not-a-valid-JS-identifier')
+ | object B extends js.Object
+ | ^
+ |newSource1.scala:19: error: The name of a JS global variable must be a valid JS identifier (got '')
+ | object D extends js.Object
+ | ^
+ |newSource1.scala:23: error: The name of a JS global variable must be a valid JS identifier (got '')
+ | object E extends js.Object
+ | ^
+ """
+
+ }
+
+ @Test def noNonLiteralJSImport: Unit = {
+
+ // Without global fallback
+
+ """
+ object A {
+ val a = "Hello"
+ }
+
+ @JSImport(A.a, JSImport.Namespace)
+ @js.native
+ object B1 extends js.Object
+
+ @JSImport(A.a, "B2")
+ @js.native
+ object B2 extends js.Object
+
+ @JSImport("B3", A.a)
+ @js.native
+ object B3 extends js.Object
+
+ @JSImport(A.a, JSImport.Namespace)
+ @js.native
+ object C1 extends js.Object
+
+ @JSImport(A.a, "C2")
+ @js.native
+ object C2 extends js.Object
+
+ @JSImport("C3", A.a)
+ @js.native
+ object C3 extends js.Object
+
+ @JSImport(A.a, A.a)
+ @js.native
+ object D extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, JSImport.Namespace)
+ | ^
+ |newSource1.scala:13: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, "B2")
+ | ^
+ |newSource1.scala:17: error: The second argument to @JSImport must be literal string or the JSImport.Namespace object.
+ | @JSImport("B3", A.a)
+ | ^
+ |newSource1.scala:21: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, JSImport.Namespace)
+ | ^
+ |newSource1.scala:25: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, "C2")
+ | ^
+ |newSource1.scala:29: error: The second argument to @JSImport must be literal string or the JSImport.Namespace object.
+ | @JSImport("C3", A.a)
+ | ^
+ |newSource1.scala:33: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, A.a)
+ | ^
+ |newSource1.scala:33: error: The second argument to @JSImport must be literal string or the JSImport.Namespace object.
+ | @JSImport(A.a, A.a)
+ | ^
+ """
+
+ // With constant (valid) global fallback
+
+ """
+ object A {
+ val a = "Hello"
+ }
+
+ @JSImport(A.a, JSImport.Namespace, globalFallback = "GlobB1")
+ @js.native
+ object B1 extends js.Object
+
+ @JSImport(A.a, "B2", globalFallback = "GlobB2")
+ @js.native
+ object B2 extends js.Object
+
+ @JSImport("B3", A.a, globalFallback = "GlobB3")
+ @js.native
+ object B3 extends js.Object
+
+ @JSImport(A.a, JSImport.Namespace, globalFallback = "GlobC1")
+ @js.native
+ object C1 extends js.Object
+
+ @JSImport(A.a, "C2", globalFallback = "GlobC2")
+ @js.native
+ object C2 extends js.Object
+
+ @JSImport("C3", A.a, globalFallback = "GlobC3")
+ @js.native
+ object C3 extends js.Object
+
+ @JSImport(A.a, A.a, globalFallback = "GlobD")
+ @js.native
+ object D extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, JSImport.Namespace, globalFallback = "GlobB1")
+ | ^
+ |newSource1.scala:13: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, "B2", globalFallback = "GlobB2")
+ | ^
+ |newSource1.scala:17: error: The second argument to @JSImport must be literal string or the JSImport.Namespace object.
+ | @JSImport("B3", A.a, globalFallback = "GlobB3")
+ | ^
+ |newSource1.scala:21: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, JSImport.Namespace, globalFallback = "GlobC1")
+ | ^
+ |newSource1.scala:25: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, "C2", globalFallback = "GlobC2")
+ | ^
+ |newSource1.scala:29: error: The second argument to @JSImport must be literal string or the JSImport.Namespace object.
+ | @JSImport("C3", A.a, globalFallback = "GlobC3")
+ | ^
+ |newSource1.scala:33: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, A.a, globalFallback = "GlobD")
+ | ^
+ |newSource1.scala:33: error: The second argument to @JSImport must be literal string or the JSImport.Namespace object.
+ | @JSImport(A.a, A.a, globalFallback = "GlobD")
+ | ^
+ """
+
+ // With variable (invalid) global fallback
+
+ """
+ object A {
+ val a = "Hello"
+ }
+
+ @JSImport(A.a, JSImport.Namespace, globalFallback = A.a)
+ @js.native
+ object B1 extends js.Object
+
+ @JSImport(A.a, "B2", globalFallback = A.a)
+ @js.native
+ object B2 extends js.Object
+
+ @JSImport("B3", A.a, globalFallback = A.a)
+ @js.native
+ object B3 extends js.Object
+
+ @JSImport(A.a, JSImport.Namespace, globalFallback = A.a)
+ @js.native
+ object C1 extends js.Object
+
+ @JSImport(A.a, "C2", globalFallback = A.a)
+ @js.native
+ object C2 extends js.Object
+
+ @JSImport("C3", A.a, globalFallback = A.a)
+ @js.native
+ object C3 extends js.Object
+
+ @JSImport(A.a, A.a, globalFallback = A.a)
+ @js.native
+ object D extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:9: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, JSImport.Namespace, globalFallback = A.a)
+ | ^
+ |newSource1.scala:9: error: The third argument to @JSImport, when present, must be a literal string.
+ | @JSImport(A.a, JSImport.Namespace, globalFallback = A.a)
+ | ^
+ |newSource1.scala:13: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, "B2", globalFallback = A.a)
+ | ^
+ |newSource1.scala:13: error: The third argument to @JSImport, when present, must be a literal string.
+ | @JSImport(A.a, "B2", globalFallback = A.a)
+ | ^
+ |newSource1.scala:17: error: The second argument to @JSImport must be literal string or the JSImport.Namespace object.
+ | @JSImport("B3", A.a, globalFallback = A.a)
+ | ^
+ |newSource1.scala:17: error: The third argument to @JSImport, when present, must be a literal string.
+ | @JSImport("B3", A.a, globalFallback = A.a)
+ | ^
+ |newSource1.scala:21: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, JSImport.Namespace, globalFallback = A.a)
+ | ^
+ |newSource1.scala:21: error: The third argument to @JSImport, when present, must be a literal string.
+ | @JSImport(A.a, JSImport.Namespace, globalFallback = A.a)
+ | ^
+ |newSource1.scala:25: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, "C2", globalFallback = A.a)
+ | ^
+ |newSource1.scala:25: error: The third argument to @JSImport, when present, must be a literal string.
+ | @JSImport(A.a, "C2", globalFallback = A.a)
+ | ^
+ |newSource1.scala:29: error: The second argument to @JSImport must be literal string or the JSImport.Namespace object.
+ | @JSImport("C3", A.a, globalFallback = A.a)
+ | ^
+ |newSource1.scala:29: error: The third argument to @JSImport, when present, must be a literal string.
+ | @JSImport("C3", A.a, globalFallback = A.a)
+ | ^
+ |newSource1.scala:33: error: The first argument to @JSImport must be a literal string.
+ | @JSImport(A.a, A.a, globalFallback = A.a)
+ | ^
+ |newSource1.scala:33: error: The second argument to @JSImport must be literal string or the JSImport.Namespace object.
+ | @JSImport(A.a, A.a, globalFallback = A.a)
+ | ^
+ |newSource1.scala:33: error: The third argument to @JSImport, when present, must be a literal string.
+ | @JSImport(A.a, A.a, globalFallback = A.a)
+ | ^
+ """
+
+ }
+
+ @Test def noApplyProperty: Unit = {
+
+ // def apply
+
+ """
+ @js.native
+ trait A extends js.Object {
+ def apply: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: A member named apply represents function application in JavaScript. A parameterless member should be exported as a property. You must add @JSName("apply")
+ | def apply: Int = js.native
+ | ^
+ """
+
+ """
+ import js.annotation.JSName
+
+ @js.native
+ trait A extends js.Object {
+ @JSName("apply")
+ def apply: Int = js.native
+ }
+ """.succeeds()
+
+ // val apply
+
+ """
+ @js.native
+ trait A extends js.Object {
+ val apply: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: A member named apply represents function application in JavaScript. A parameterless member should be exported as a property. You must add @JSName("apply")
+ | val apply: Int = js.native
+ | ^
+ """
+
+ """
+ import js.annotation.JSName
+
+ @js.native
+ trait A extends js.Object {
+ @JSName("apply")
+ val apply: Int = js.native
+ }
+ """.succeeds()
+
+ // var apply
+
+ """
+ @js.native
+ trait A extends js.Object {
+ var apply: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: A member named apply represents function application in JavaScript. A parameterless member should be exported as a property. You must add @JSName("apply")
+ | var apply: Int = js.native
+ | ^
+ """
+
+ """
+ import js.annotation.JSName
+
+ @js.native
+ trait A extends js.Object {
+ @JSName("apply")
+ var apply: Int = js.native
+ }
+ """.succeeds()
+
+ }
+
+ @Test def noAbstractLocalJSClass: Unit = {
+ """
+ object Enclosing {
+ def method(): Unit = {
+ abstract class AbstractLocalJSClass extends js.Object
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Implementation restriction: local JS classes cannot be abstract
+ | abstract class AbstractLocalJSClass extends js.Object
+ | ^
+ """
+ }
+
+ @Test def noLoadJSConstructorOfUnstableRef: Unit = {
+ """
+ class Enclosing {
+ class InnerJSClass extends js.Object
+ }
+
+ object A {
+ def method(): Any =
+ js.constructorOf[Enclosing#InnerJSClass]
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:11: error: stable reference to a JS class required but Enclosing#InnerJSClass found
+ | js.constructorOf[Enclosing#InnerJSClass]
+ | ^
+ """
+
+ // version-dependent error message due to https://github.com/scala/bug/issues/10619
+ """
+ class Enclosing {
+ class InnerJSClass extends js.Object
+ }
+
+ object A {
+ def newEnclosing: Enclosing = new Enclosing
+
+ def method(): Any =
+ js.constructorOf[newEnclosing.InnerJSClass]
+ }
+ """.fails()
+
+ """
+ class Enclosing {
+ class InnerJSClass extends js.Object
+ }
+
+ object A {
+ def method(a: Any): Boolean =
+ a.isInstanceOf[Enclosing#InnerJSClass]
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:11: error: stable reference to a JS class required but Enclosing#InnerJSClass found
+ | a.isInstanceOf[Enclosing#InnerJSClass]
+ | ^
+ """
+
+ // version-dependent error message due to https://github.com/scala/bug/issues/10619
+ """
+ class Enclosing {
+ class InnerJSClass extends js.Object
+ }
+
+ object A {
+ def newEnclosing: Enclosing = new Enclosing
+
+ def method(a: Any): Boolean =
+ a.isInstanceOf[newEnclosing.InnerJSClass]
+ }
+ """.fails()
+ }
+
+ @Test def noJSSymbolNameOnNestedNativeClassesAndObjects: Unit = {
+ for {
+ kind <- Seq("class", "object")
+ } {
+ s"""
+ object Sym {
+ val sym = js.Symbol()
+ }
+
+ @js.native
+ @JSGlobal
+ object Enclosing extends js.Object {
+ @JSName(Sym.sym)
+ @js.native
+ $kind A extends js.Object
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:12: error: Implementation restriction: @JSName with a js.Symbol is not supported on nested native classes and objects
+ | @JSName(Sym.sym)
+ | ^
+ """
+ }
+ }
+
+ @Test def noBracketCallOrBracketAccessOnJSClasses: Unit = {
+ // native
+ """
+ @js.native
+ @JSGlobal
+ @JSBracketCall
+ class A extends js.Object
+
+ @js.native
+ @JSGlobal
+ @JSBracketAccess
+ object B extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: @JSBracketCall can only be used on members of JS types.
+ | @JSBracketCall
+ | ^
+ |newSource1.scala:12: error: @JSBracketAccess can only be used on members of JS types.
+ | @JSBracketAccess
+ | ^
+ """
+
+ // Non-native
+ """
+ @JSBracketCall
+ class A extends js.Object
+
+ @JSBracketAccess
+ object B extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: @JSBracketCall can only be used on members of JS types.
+ | @JSBracketCall
+ | ^
+ |newSource1.scala:8: error: @JSBracketAccess can only be used on members of JS types.
+ | @JSBracketAccess
+ | ^
+ """
+
+ // Nested native
+ """
+ @js.native
+ @JSGlobal
+ object Enclosing extends js.Object {
+ @JSBracketCall
+ @js.native
+ class A extends js.Object
+
+ @JSBracketAccess
+ @js.native
+ object B extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: @JSBracketCall can only be used on methods.
+ | @JSBracketCall
+ | ^
+ |newSource1.scala:12: error: @JSBracketAccess can only be used on methods.
+ | @JSBracketAccess
+ | ^
+ """
+
+ // Nested non-native
+ """
+ object Enclosing extends js.Object {
+ @JSBracketCall
+ object A extends js.Object
+
+ @JSBracketAccess
+ class B extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: @JSBracketCall can only be used on methods.
+ | @JSBracketCall
+ | ^
+ |newSource1.scala:9: error: @JSBracketAccess can only be used on methods.
+ | @JSBracketAccess
+ | ^
+ """
+ }
+
+ @Test def noDuplicateJSNameAnnotOnMember: Unit = {
+ for {
+ kind <- Seq("class", "object", "trait")
+ } {
+ """
+ object A {
+ val a = js.Symbol()
+ }
+
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSName(A.a)
+ @JSName("foo")
+ def a: Int = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:13: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall.
+ | @JSName("foo")
+ | ^
+ """
+ }
+ }
+
+ @Test def nonNativeJSTypesNameOverrideErrors: Unit = {
+ """
+ abstract class A extends js.Object {
+ def bar(): Int
+ }
+ class B extends A {
+ override def bar() = 1
+ }
+ """.hasNoWarns()
+
+ """
+ trait A extends js.Object {
+ @JSName("foo")
+ def bar(): Int
+ }
+ class B extends A {
+ @JSName("foo")
+ override def bar() = 1
+ }
+ """.hasNoWarns()
+
+ """
+ abstract class A extends js.Object {
+ @JSName("foo")
+ def bar(): Int
+ }
+ class B extends A {
+ @JSName("foo")
+ override def bar() = 1
+ }
+ """.hasNoWarns()
+
+ // #4375
+ """
+ abstract class Parent extends js.Object {
+ type TypeMember <: CharSequence
+ type JSTypeMember <: js.Object
+
+ type Foo = Int
+ @JSName("Babar") def Bar: Int = 5
+ }
+
+ class Child extends Parent {
+ type TypeMember = String
+ override type JSTypeMember = js.Date // the override keyword makes no difference
+
+ @JSName("Foobar") def Foo: Int = 5
+ type Bar = Int
+ }
+ """.hasNoWarns()
+
+ """
+ abstract class A extends js.Object {
+ @JSName("foo")
+ def bar(): Int
+ }
+ class B extends A {
+ @JSName("baz")
+ override def bar() = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:11: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): Int in class B called from JS as method 'baz'
+ | is conflicting with
+ |def bar(): Int in class A called from JS as method 'foo'
+ |
+ | override def bar() = 1
+ | ^
+ """
+
+ """
+ abstract class A extends js.Object {
+ @JSName("foo")
+ def bar(): Int
+ }
+ class B extends A {
+ override def bar() = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): Int in class B called from JS as method 'bar'
+ | is conflicting with
+ |def bar(): Int in class A called from JS as method 'foo'
+ |
+ | override def bar() = 1
+ | ^
+ """
+
+ """
+ abstract class A extends js.Object {
+ @JSName("foo")
+ def bar(): Object
+ }
+ abstract class B extends A {
+ override def bar(): String
+ }
+ class C extends B {
+ override def bar() = "1"
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): String in class B called from JS as method 'bar'
+ | is conflicting with
+ |def bar(): Object in class A called from JS as method 'foo'
+ |
+ | override def bar(): String
+ | ^
+ |newSource1.scala:13: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): String in class C called from JS as method 'bar'
+ | is conflicting with
+ |def bar(): Object in class A called from JS as method 'foo'
+ |
+ | override def bar() = "1"
+ | ^
+ """
+
+ """
+ abstract class A extends js.Object {
+ def bar(): Object
+ }
+ abstract class B extends A {
+ @JSName("foo")
+ override def bar(): String
+ }
+ class C extends B {
+ override def bar() = "1"
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): String in class B called from JS as method 'foo'
+ | is conflicting with
+ |def bar(): Object in class A called from JS as method 'bar'
+ |
+ | override def bar(): String
+ | ^
+ |newSource1.scala:13: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): String in class C called from JS as method 'bar'
+ | is conflicting with
+ |override def bar(): String in class B called from JS as method 'foo'
+ |
+ | override def bar() = "1"
+ | ^
+ """
+
+ """
+ class A extends js.Object {
+ def foo: Int = 5
+ }
+ trait B extends A {
+ @JSName("bar")
+ def foo: Int
+ }
+ class C extends B
+ """ hasErrors
+ s"""
+ |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in class A called from JS as property 'foo'
+ | is conflicting with
+ |def foo: Int in trait B called from JS as property 'bar'
+ |
+ | def foo: Int
+ | ^
+ |${ifHasNewRefChecks("""
+ |newSource1.scala:12: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in class A called from JS as property 'foo'
+ | is conflicting with
+ |def foo: Int in trait B called from JS as property 'bar'
+ |
+ | class C extends B
+ | ^
+ """)}
+ """
+
+ """
+ class A extends js.Object {
+ @JSName("bar")
+ def foo: Int = 5
+ }
+ trait B extends A {
+ def foo: Int
+ }
+ class C extends B
+ """ hasErrors
+ s"""
+ |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in class A called from JS as property 'bar'
+ | is conflicting with
+ |def foo: Int in trait B called from JS as property 'foo'
+ |
+ | def foo: Int
+ | ^
+ |${ifHasNewRefChecks("""
+ |newSource1.scala:12: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in class A called from JS as property 'bar'
+ | is conflicting with
+ |def foo: Int in trait B called from JS as property 'foo'
+ |
+ | class C extends B
+ | ^
+ """)}
+ """
+
+ """
+ class A[T] extends js.Object {
+ @JSName("bar")
+ def foo(x: T): T = x
+ }
+ class B extends A[Int] {
+ override def foo(x: Int): Int = x
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def foo(x: Int): Int in class B called from JS as method 'foo'
+ | is conflicting with
+ |def foo(x: Int): Int in class A called from JS as method 'bar'
+ |
+ | override def foo(x: Int): Int = x
+ | ^
+ """
+
+ """
+ trait A[T] extends js.Object {
+ @JSName("bar")
+ def foo(x: T): T
+ }
+ class B extends A[Int] {
+ override def foo(x: Int): Int = x
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def foo(x: Int): Int in class B called from JS as method 'foo'
+ | is conflicting with
+ |def foo(x: Int): Int in trait A called from JS as method 'bar'
+ |
+ | override def foo(x: Int): Int = x
+ | ^
+ """
+
+ """
+ class A[T] extends js.Object {
+ @JSName("bar")
+ def foo(x: T): T = x
+ }
+ trait B extends A[Int] {
+ def foo(x: Int): Int
+ }
+ class C extends B {
+ override def foo(x: Int): Int = x
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo(x: Int): Int in class A called from JS as method 'bar'
+ | is conflicting with
+ |def foo(x: Int): Int in trait B called from JS as method 'foo'
+ |
+ | def foo(x: Int): Int
+ | ^
+ |newSource1.scala:13: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def foo(x: Int): Int in class C called from JS as method 'foo'
+ | is conflicting with
+ |def foo(x: Int): Int in class A called from JS as method 'bar'
+ |
+ | override def foo(x: Int): Int = x
+ | ^
+ """
+
+ """
+ class A[T] extends js.Object {
+ def foo(x: T): T = x
+ }
+ trait B extends A[Int] {
+ @JSName("bar")
+ def foo(x: Int): Int
+ }
+ class C extends B {
+ override def foo(x: Int): Int = x
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo(x: Int): Int in class A called from JS as method 'foo'
+ | is conflicting with
+ |def foo(x: Int): Int in trait B called from JS as method 'bar'
+ |
+ | def foo(x: Int): Int
+ | ^
+ |newSource1.scala:13: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def foo(x: Int): Int in class C called from JS as method 'foo'
+ | is conflicting with
+ |def foo(x: Int): Int in trait B called from JS as method 'bar'
+ |
+ | override def foo(x: Int): Int = x
+ | ^
+ """
+
+ """
+ trait A extends js.Object {
+ def foo: Int
+ }
+ trait B extends js.Object {
+ @JSName("bar")
+ def foo: Int
+ }
+ trait C extends A with B
+ """ hasErrors
+ """
+ |newSource1.scala:12: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in trait B called from JS as property 'bar'
+ | is conflicting with
+ |def foo: Int in trait A called from JS as property 'foo'
+ |
+ | trait C extends A with B
+ | ^
+ """
+
+ """
+ trait A extends js.Object {
+ def foo: Int
+ }
+ trait B extends js.Object {
+ @JSName("bar")
+ def foo: Int
+ }
+ abstract class C extends A with B
+ """ hasErrors
+ """
+ |newSource1.scala:12: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in trait B called from JS as property 'bar'
+ | is conflicting with
+ |def foo: Int in trait A called from JS as property 'foo'
+ |
+ | abstract class C extends A with B
+ | ^
+ """
+ }
+
+ @Test def nonNativeJSTypesJSNameWithSymbolOverrideErrors: Unit = {
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ trait A extends js.Object {
+ @JSName(Syms.sym1)
+ def bar(): Int
+ }
+ class B extends A {
+ @JSName(Syms.sym1)
+ override def bar() = 1
+ }
+ """.hasNoWarns()
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ abstract class A extends js.Object {
+ @JSName(Syms.sym1)
+ def bar(): Int
+ }
+ class B extends A {
+ @JSName(Syms.sym1)
+ override def bar() = 1
+ }
+ """.hasNoWarns()
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ val sym2 = js.Symbol()
+ }
+
+ abstract class A extends js.Object {
+ @JSName(Syms.sym1)
+ def bar(): Int
+ }
+ class B extends A {
+ @JSName(Syms.sym2)
+ override def bar() = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:16: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): Int in class B called from JS as method 'Syms.sym2'
+ | is conflicting with
+ |def bar(): Int in class A called from JS as method 'Syms.sym1'
+ |
+ | override def bar() = 1
+ | ^
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ abstract class A extends js.Object {
+ @JSName(Syms.sym1)
+ def bar(): Int
+ }
+ class B extends A {
+ @JSName("baz")
+ override def bar() = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:15: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): Int in class B called from JS as method 'baz'
+ | is conflicting with
+ |def bar(): Int in class A called from JS as method 'Syms.sym1'
+ |
+ | override def bar() = 1
+ | ^
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ abstract class A extends js.Object {
+ @JSName("foo")
+ def bar(): Int
+ }
+ class B extends A {
+ @JSName(Syms.sym1)
+ override def bar() = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:15: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): Int in class B called from JS as method 'Syms.sym1'
+ | is conflicting with
+ |def bar(): Int in class A called from JS as method 'foo'
+ |
+ | override def bar() = 1
+ | ^
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ abstract class A extends js.Object {
+ @JSName(Syms.sym1)
+ def bar(): Int
+ }
+ class B extends A {
+ override def bar() = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): Int in class B called from JS as method 'bar'
+ | is conflicting with
+ |def bar(): Int in class A called from JS as method 'Syms.sym1'
+ |
+ | override def bar() = 1
+ | ^
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ abstract class A extends js.Object {
+ @JSName(Syms.sym1)
+ def bar(): Object
+ }
+ abstract class B extends A {
+ override def bar(): String
+ }
+ class C extends B {
+ override def bar() = "1"
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): String in class B called from JS as method 'bar'
+ | is conflicting with
+ |def bar(): Object in class A called from JS as method 'Syms.sym1'
+ |
+ | override def bar(): String
+ | ^
+ |newSource1.scala:17: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): String in class C called from JS as method 'bar'
+ | is conflicting with
+ |def bar(): Object in class A called from JS as method 'Syms.sym1'
+ |
+ | override def bar() = "1"
+ | ^
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ abstract class A extends js.Object {
+ def bar(): Object
+ }
+ abstract class B extends A {
+ @JSName(Syms.sym1)
+ override def bar(): String
+ }
+ class C extends B {
+ override def bar() = "1"
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): String in class B called from JS as method 'Syms.sym1'
+ | is conflicting with
+ |def bar(): Object in class A called from JS as method 'bar'
+ |
+ | override def bar(): String
+ | ^
+ |newSource1.scala:17: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def bar(): String in class C called from JS as method 'bar'
+ | is conflicting with
+ |override def bar(): String in class B called from JS as method 'Syms.sym1'
+ |
+ | override def bar() = "1"
+ | ^
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ class A extends js.Object {
+ def foo: Int = 5
+ }
+ trait B extends A {
+ @JSName(Syms.sym1)
+ def foo: Int
+ }
+ class C extends B
+ """ hasErrors
+ s"""
+ |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in class A called from JS as property 'foo'
+ | is conflicting with
+ |def foo: Int in trait B called from JS as property 'Syms.sym1'
+ |
+ | def foo: Int
+ | ^
+ |${ifHasNewRefChecks("""
+ |newSource1.scala:16: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in class A called from JS as property 'foo'
+ | is conflicting with
+ |def foo: Int in trait B called from JS as property 'Syms.sym1'
+ |
+ | class C extends B
+ | ^
+ """)}
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ class A extends js.Object {
+ @JSName(Syms.sym1)
+ def foo: Int = 5
+ }
+ trait B extends A {
+ def foo: Int
+ }
+ class C extends B
+ """ hasErrors
+ s"""
+ |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in class A called from JS as property 'Syms.sym1'
+ | is conflicting with
+ |def foo: Int in trait B called from JS as property 'foo'
+ |
+ | def foo: Int
+ | ^
+ |${ifHasNewRefChecks("""
+ |newSource1.scala:16: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in class A called from JS as property 'Syms.sym1'
+ | is conflicting with
+ |def foo: Int in trait B called from JS as property 'foo'
+ |
+ | class C extends B
+ | ^
+ """)}
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ class A[T] extends js.Object {
+ @JSName(Syms.sym1)
+ def foo(x: T): T = x
+ }
+ class B extends A[Int] {
+ override def foo(x: Int): Int = x
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def foo(x: Int): Int in class B called from JS as method 'foo'
+ | is conflicting with
+ |def foo(x: Int): Int in class A called from JS as method 'Syms.sym1'
+ |
+ | override def foo(x: Int): Int = x
+ | ^
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ trait A[T] extends js.Object {
+ @JSName(Syms.sym1)
+ def foo(x: T): T
+ }
+ class B extends A[Int] {
+ override def foo(x: Int): Int = x
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def foo(x: Int): Int in class B called from JS as method 'foo'
+ | is conflicting with
+ |def foo(x: Int): Int in trait A called from JS as method 'Syms.sym1'
+ |
+ | override def foo(x: Int): Int = x
+ | ^
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ class A[T] extends js.Object {
+ @JSName(Syms.sym1)
+ def foo(x: T): T = x
+ }
+ trait B extends A[Int] {
+ def foo(x: Int): Int
+ }
+ class C extends B {
+ override def foo(x: Int): Int = x
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo(x: Int): Int in class A called from JS as method 'Syms.sym1'
+ | is conflicting with
+ |def foo(x: Int): Int in trait B called from JS as method 'foo'
+ |
+ | def foo(x: Int): Int
+ | ^
+ |newSource1.scala:17: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def foo(x: Int): Int in class C called from JS as method 'foo'
+ | is conflicting with
+ |def foo(x: Int): Int in class A called from JS as method 'Syms.sym1'
+ |
+ | override def foo(x: Int): Int = x
+ | ^
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ class A[T] extends js.Object {
+ def foo(x: T): T = x
+ }
+ trait B extends A[Int] {
+ @JSName(Syms.sym1)
+ def foo(x: Int): Int
+ }
+ class C extends B {
+ override def foo(x: Int): Int = x
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo(x: Int): Int in class A called from JS as method 'foo'
+ | is conflicting with
+ |def foo(x: Int): Int in trait B called from JS as method 'Syms.sym1'
+ |
+ | def foo(x: Int): Int
+ | ^
+ |newSource1.scala:17: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |override def foo(x: Int): Int in class C called from JS as method 'foo'
+ | is conflicting with
+ |def foo(x: Int): Int in trait B called from JS as method 'Syms.sym1'
+ |
+ | override def foo(x: Int): Int = x
+ | ^
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ trait A extends js.Object {
+ def foo: Int
+ }
+ trait B extends js.Object {
+ @JSName(Syms.sym1)
+ def foo: Int
+ }
+ trait C extends A with B
+ """ hasErrors
+ """
+ |newSource1.scala:16: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in trait B called from JS as property 'Syms.sym1'
+ | is conflicting with
+ |def foo: Int in trait A called from JS as property 'foo'
+ |
+ | trait C extends A with B
+ | ^
+ """
+
+ """
+ object Syms {
+ val sym1 = js.Symbol()
+ }
+
+ trait A extends js.Object {
+ def foo: Int
+ }
+ trait B extends js.Object {
+ @JSName(Syms.sym1)
+ def foo: Int
+ }
+ abstract class C extends A with B
+ """ hasErrors
+ """
+ |newSource1.scala:16: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def foo: Int in trait B called from JS as property 'Syms.sym1'
+ | is conflicting with
+ |def foo: Int in trait A called from JS as property 'foo'
+ |
+ | abstract class C extends A with B
+ | ^
+ """
+ }
+
+ // #4282
+ @Test def jsTypesSpecialCallingConventionOverrideErrors: Unit = {
+ // name "apply" vs function application
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ def apply(): Int
+ }
+
+ class B extends A {
+ @JSName("apply")
+ def apply(): Int
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:13: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def apply(): Int in class B called from JS as method 'apply'
+ | is conflicting with
+ |def apply(): Int in class A called from JS as function application
+ |
+ | def apply(): Int
+ | ^
+ """
+
+ // property vs method
+ """
+ class A extends js.Object {
+ def a: Int
+ }
+
+ class B extends A {
+ def a(): Int
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def a(): Int in class B called from JS as method 'a'
+ | is conflicting with
+ |def a: Int in class A called from JS as property 'a'
+ |
+ | def a(): Int
+ | ^
+ """
+
+ val postUnarySpace = {
+ val hasNoSpace = {
+ version == "2.12.6" ||
+ version == "2.12.7" ||
+ version == "2.12.8" ||
+ version == "2.12.9" ||
+ version == "2.12.10"
+ }
+ if (hasNoSpace) ""
+ else " "
+ }
+
+ // unary op vs thing named like it
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSOperator
+ def unary_+ : Int
+ }
+
+ class B extends A {
+ @JSName("unary_+")
+ def unary_+ : Int
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def unary_+$postUnarySpace: Int in class B called from JS as property 'unary_+'
+ | is conflicting with
+ |def unary_+$postUnarySpace: Int in class A called from JS as unary operator
+ |
+ | def unary_+ : Int
+ | ^
+ """
+
+ // non-zero arg is OK
+ """
+ class A extends js.Object {
+ def unary_+(x: String): Int = 1
+ }
+
+ class B extends A {
+ @JSName("unary_+")
+ override def unary_+(x: String): Int = 2
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:6: warning: Method 'unary_+' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators
+ | def unary_+(x: String): Int = 1
+ | ^
+ """
+
+ // binary op vs thing named like it
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSOperator
+ def ||(x: Int): Int
+ }
+
+ class B extends A {
+ @JSName("||")
+ def ||(x: Int): Int
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention.
+ |
+ |def ||(x: Int): Int in class B called from JS as method '||'
+ | is conflicting with
+ |def ||(x: Int): Int in class A called from JS as binary operator
+ |
+ | def ||(x: Int): Int
+ | ^
+ """
+
+ // non-single arg is OK
+ """
+ class A extends js.Object {
+ def ||(): Int = 1
+ }
+
+ class B extends A {
+ @JSName("||")
+ override def ||(): Int = 2
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:6: warning: Method '||' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators
+ | def ||(): Int = 1
+ | ^
+ """
+ }
+
+ @Test def noDefaultConstructorArgsIfModuleIsJSNative: Unit = {
+ """
+ class A(x: Int = 1) extends js.Object
+
+ @js.native
+ @JSGlobal
+ object A extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Implementation restriction: constructors of non-native JS classes cannot have default parameters if their companion module is JS native.
+ | class A(x: Int = 1) extends js.Object
+ | ^
+ """
+
+ """
+ class A(x: Int = 1)
+
+ @js.native
+ @JSGlobal
+ object A extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Implementation restriction: constructors of Scala classes cannot have default parameters if their companion module is JS native.
+ | class A(x: Int = 1)
+ | ^
+ """
+ }
+
+ // #2547
+ @Test def noDefaultOverrideCrash: Unit = {
+ """
+ @js.native
+ @JSGlobal
+ class NativeBase extends js.Object {
+ def add(option: js.Any = js.native): js.Any = js.native
+ }
+ class Derived extends NativeBase {
+ override def add(option: js.Any): js.Any = super.add(option)
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:11: error: When overriding a native method with default arguments, the overriding method must explicitly repeat the default arguments.
+ | override def add(option: js.Any): js.Any = super.add(option)
+ | ^
+ """
+
+ """
+ @js.native
+ trait NativeTrait extends js.Object {
+ def add(option: js.Any = js.native): js.Any = js.native
+ }
+
+ @js.native
+ @JSGlobal
+ class NativeBase extends NativeTrait
+
+ class Derived extends NativeBase {
+ override def add(option: js.Any): js.Any = super.add(option)
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:15: error: When overriding a native method with default arguments, the overriding method must explicitly repeat the default arguments.
+ | override def add(option: js.Any): js.Any = super.add(option)
+ | ^
+ """
+ }
+
+ // # 3969
+ @Test def overrideEqualsHashCode: Unit = {
+ for {
+ obj <- List("class", "object")
+ } {
+ s"""
+ $obj A extends js.Object {
+ override def hashCode(): Int = 1
+ override def equals(obj: Any): Boolean = false
+
+ // this one works as expected (so allowed)
+ override def toString(): String = "frobber"
+
+ /* these are allowed, since they are protected in jl.Object.
+ * as a result, only the overrides can be called. So the fact that they
+ * do not truly override the methods in jl.Object is not observable.
+ */
+ override def clone(): Object = null
+ override def finalize(): Unit = ()
+
+ // other methods in jl.Object are final.
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:6: warning: Overriding hashCode in a JS class does not change its hash code. To silence this warning, change the name of the method and optionally add @JSName("hashCode").
+ | override def hashCode(): Int = 1
+ | ^
+ |newSource1.scala:7: warning: Overriding equals in a JS class does not change how it is compared. To silence this warning, change the name of the method and optionally add @JSName("equals").
+ | override def equals(obj: Any): Boolean = false
+ | ^
+ """
+ }
+
+ for {
+ obj <- List("class", "object")
+ } {
+ s"""
+ @js.native
+ @JSGlobal
+ $obj A extends js.Object {
+ override def hashCode(): Int = js.native
+ override def equals(obj: Any): Boolean = js.native
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:8: warning: Overriding hashCode in a JS class does not change its hash code. To silence this warning, change the name of the method and optionally add @JSName("hashCode").
+ | override def hashCode(): Int = js.native
+ | ^
+ |newSource1.scala:9: warning: Overriding equals in a JS class does not change how it is compared. To silence this warning, change the name of the method and optionally add @JSName("equals").
+ | override def equals(obj: Any): Boolean = js.native
+ | ^
+ """
+ }
+
+ """
+ @js.native
+ trait A extends js.Any {
+ override def hashCode(): Int = js.native
+ override def equals(obj: Any): Boolean = js.native
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:7: warning: Overriding hashCode in a JS class does not change its hash code. To silence this warning, change the name of the method and optionally add @JSName("hashCode").
+ | override def hashCode(): Int = js.native
+ | ^
+ |newSource1.scala:8: warning: Overriding equals in a JS class does not change how it is compared. To silence this warning, change the name of the method and optionally add @JSName("equals").
+ | override def equals(obj: Any): Boolean = js.native
+ | ^
+ """
+
+ """
+ trait A extends js.Any {
+ override def hashCode(): Int
+ override def equals(obj: Any): Boolean
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:6: warning: Overriding hashCode in a JS class does not change its hash code. To silence this warning, change the name of the method and optionally add @JSName("hashCode").
+ | override def hashCode(): Int
+ | ^
+ |newSource1.scala:7: warning: Overriding equals in a JS class does not change how it is compared. To silence this warning, change the name of the method and optionally add @JSName("equals").
+ | override def equals(obj: Any): Boolean
+ | ^
+ """
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala
new file mode 100644
index 0000000000..2e2c1664f2
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala
@@ -0,0 +1,149 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.junit.Test
+
+// scalastyle:off line.size.limit
+
+class JSNewTargetTest extends DirectTest with TestHelpers {
+
+ override def preamble: String =
+ """import scala.scalajs.js
+ """
+
+ @Test
+ def illegalInScalaClass(): Unit = {
+
+ """
+ class A {
+ js.`new`.target
+
+ def this(x: Int) = {
+ this()
+ js.`new`.target
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Illegal use of js.`new`.target.
+ |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var.
+ |It cannot be used inside a lambda or by-name parameter, nor in any other location.
+ | js.`new`.target
+ | ^
+ |newSource1.scala:8: error: Illegal use of js.`new`.target.
+ |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var.
+ |It cannot be used inside a lambda or by-name parameter, nor in any other location.
+ | js.`new`.target
+ | ^
+ """
+
+ """
+ class A {
+ def foo(x: Int): Unit =
+ js.`new`.target
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Illegal use of js.`new`.target.
+ |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var.
+ |It cannot be used inside a lambda or by-name parameter, nor in any other location.
+ | js.`new`.target
+ | ^
+ """
+
+ """
+ class A extends js.Object {
+ class B {
+ js.`new`.target
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Illegal use of js.`new`.target.
+ |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var.
+ |It cannot be used inside a lambda or by-name parameter, nor in any other location.
+ | js.`new`.target
+ | ^
+ """
+
+ }
+
+ @Test
+ def illegalInDefOrLazyVal(): Unit = {
+
+ """
+ class A extends js.Object {
+ lazy val x = js.`new`.target
+ def y: js.Dynamic = js.`new`.target
+ def z(x: Int): Any = js.`new`.target
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: Illegal use of js.`new`.target.
+ |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var.
+ |It cannot be used inside a lambda or by-name parameter, nor in any other location.
+ | lazy val x = js.`new`.target
+ | ^
+ |newSource1.scala:5: error: Illegal use of js.`new`.target.
+ |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var.
+ |It cannot be used inside a lambda or by-name parameter, nor in any other location.
+ | def y: js.Dynamic = js.`new`.target
+ | ^
+ |newSource1.scala:6: error: Illegal use of js.`new`.target.
+ |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var.
+ |It cannot be used inside a lambda or by-name parameter, nor in any other location.
+ | def z(x: Int): Any = js.`new`.target
+ | ^
+ """
+
+ }
+
+ @Test
+ def illegalInLambdaOrByName(): Unit = {
+
+ """
+ class A extends js.Object {
+ val x = () => js.`new`.target
+ val y = Option(null).getOrElse(js.`new`.target)
+ val z: js.Function1[Int, Any] = (x: Int) => js.`new`.target
+ val w: js.ThisFunction0[Any, Any] = (x: Any) => js.`new`.target
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Illegal use of js.`new`.target.
+ |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var.
+ |It cannot be used inside a lambda or by-name parameter, nor in any other location.
+ | val z: js.Function1[Int, Any] = (x: Int) => js.`new`.target
+ | ^
+ |newSource1.scala:7: error: Illegal use of js.`new`.target.
+ |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var.
+ |It cannot be used inside a lambda or by-name parameter, nor in any other location.
+ | val w: js.ThisFunction0[Any, Any] = (x: Any) => js.`new`.target
+ | ^
+ |newSource1.scala:4: error: Illegal use of js.`new`.target.
+ |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var.
+ |It cannot be used inside a lambda or by-name parameter, nor in any other location.
+ | val x = () => js.`new`.target
+ | ^
+ |newSource1.scala:5: error: Illegal use of js.`new`.target.
+ |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var.
+ |It cannot be used inside a lambda or by-name parameter, nor in any other location.
+ | val y = Option(null).getOrElse(js.`new`.target)
+ | ^
+ """
+
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSOptionalTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSOptionalTest.scala
new file mode 100644
index 0000000000..c275ea0d59
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSOptionalTest.scala
@@ -0,0 +1,213 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+
+import org.junit.Test
+import org.junit.Ignore
+
+// scalastyle:off line.size.limit
+
+class JSOptionalTest extends DirectTest with TestHelpers {
+
+ override def preamble: String = {
+ """
+ import scala.scalajs.js
+ import scala.scalajs.js.annotation._
+ """
+ }
+
+ @Test
+ def optionalRequiresUndefinedRHS: Unit = {
+ s"""
+ trait A extends js.Object {
+ val a1: js.UndefOr[Int] = 5
+ val a2: Int = 5
+
+ def b1: js.UndefOr[Int] = 5
+ def b2: Int = 5
+
+ var c1: js.UndefOr[Int] = 5
+ var c2: Int = 5
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:6: error: Members of non-native JS traits must either be abstract, or their right-hand-side must be `js.undefined`.
+ | val a1: js.UndefOr[Int] = 5
+ | ^
+ |newSource1.scala:7: error: Members of non-native JS traits must either be abstract, or their right-hand-side must be `js.undefined`.
+ | val a2: Int = 5
+ | ^
+ |newSource1.scala:9: error: Members of non-native JS traits must either be abstract, or their right-hand-side must be `js.undefined`.
+ | def b1: js.UndefOr[Int] = 5
+ | ^
+ |newSource1.scala:10: error: Members of non-native JS traits must either be abstract, or their right-hand-side must be `js.undefined`.
+ | def b2: Int = 5
+ | ^
+ |newSource1.scala:12: error: Members of non-native JS traits must either be abstract, or their right-hand-side must be `js.undefined`.
+ | var c1: js.UndefOr[Int] = 5
+ | ^
+ |newSource1.scala:13: error: Members of non-native JS traits must either be abstract, or their right-hand-side must be `js.undefined`.
+ | var c2: Int = 5
+ | ^
+ """
+ }
+
+ @Test // #4319
+ def optionalDefaultParamRequiresUndefinedRHS: Unit = {
+ s"""
+ trait A extends js.Object {
+ def a(x: js.UndefOr[Int] = 1): Int
+ def b(x: String = "foo"): Unit
+ def c(x: js.UndefOr[Int] = js.undefined): Int // ok
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Members of non-native JS traits may not have default parameters unless their default is `js.undefined`.
+ | def a(x: js.UndefOr[Int] = 1): Int
+ | ^
+ |newSource1.scala:7: error: Members of non-native JS traits may not have default parameters unless their default is `js.undefined`.
+ | def b(x: String = "foo"): Unit
+ | ^
+ """
+
+ // Also for custom JS function types
+ s"""
+ trait A extends js.Function {
+ def apply(x: js.UndefOr[Int] = 1): Int
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Members of non-native JS traits may not have default parameters unless their default is `js.undefined`.
+ | def apply(x: js.UndefOr[Int] = 1): Int
+ | ^
+ """
+ }
+
+ @Test
+ def noOptionalLazyVal: Unit = {
+ s"""
+ trait A extends js.Object {
+ lazy val a1: js.UndefOr[Int] = js.undefined
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:6: error: A non-native JS trait cannot contain lazy vals
+ | lazy val a1: js.UndefOr[Int] = js.undefined
+ | ^
+ """
+ }
+
+ @Test
+ def noOverrideConcreteNonOptionalWithOptional: Unit = {
+ s"""
+ abstract class A extends js.Object {
+ val a1: js.UndefOr[Int] = 5
+ val a2: js.UndefOr[Int]
+
+ def b1: js.UndefOr[Int] = 5
+ def b2: js.UndefOr[Int]
+ }
+
+ trait B extends A {
+ override val a1: js.UndefOr[Int] = js.undefined
+ override val a2: js.UndefOr[Int] = js.undefined
+
+ override def b1: js.UndefOr[Int] = js.undefined
+ override def b2: js.UndefOr[Int] = js.undefined
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:14: error: Cannot override concrete val a1: scala.scalajs.js.UndefOr[Int] from A in a non-native JS trait.
+ | override val a1: js.UndefOr[Int] = js.undefined
+ | ^
+ |newSource1.scala:17: error: Cannot override concrete def b1: scala.scalajs.js.UndefOr[Int] from A in a non-native JS trait.
+ | override def b1: js.UndefOr[Int] = js.undefined
+ | ^
+ """
+
+ s"""
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ val a: js.UndefOr[Int] = js.native
+ def b: js.UndefOr[Int] = js.native
+ }
+
+ trait B extends A {
+ override val a: js.UndefOr[Int] = js.undefined
+ override def b: js.UndefOr[Int] = js.undefined
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:13: error: Cannot override concrete val a: scala.scalajs.js.UndefOr[Int] from A in a non-native JS trait.
+ | override val a: js.UndefOr[Int] = js.undefined
+ | ^
+ |newSource1.scala:14: error: Cannot override concrete def b: scala.scalajs.js.UndefOr[Int] from A in a non-native JS trait.
+ | override def b: js.UndefOr[Int] = js.undefined
+ | ^
+ """
+
+ s"""
+ @js.native
+ trait A extends js.Object {
+ val a: js.UndefOr[Int] = js.native
+ def b: js.UndefOr[Int] = js.native
+ }
+
+ @js.native
+ @JSGlobal
+ class B extends A
+
+ trait C extends B {
+ override val a: js.UndefOr[Int] = js.undefined
+ override def b: js.UndefOr[Int] = js.undefined
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:16: error: Cannot override concrete val a: scala.scalajs.js.UndefOr[Int] from A in a non-native JS trait.
+ | override val a: js.UndefOr[Int] = js.undefined
+ | ^
+ |newSource1.scala:17: error: Cannot override concrete def b: scala.scalajs.js.UndefOr[Int] from A in a non-native JS trait.
+ | override def b: js.UndefOr[Int] = js.undefined
+ | ^
+ """
+ }
+
+ @Test
+ def noOptionalDefWithParens: Unit = {
+ s"""
+ trait A extends js.Object {
+ def a(): js.UndefOr[Int] = js.undefined
+ def b(x: Int): js.UndefOr[Int] = js.undefined
+ def c_=(v: Int): js.UndefOr[Int] = js.undefined
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:6: error: In non-native JS traits, defs with parentheses must be abstract.
+ | def a(): js.UndefOr[Int] = js.undefined
+ | ^
+ |newSource1.scala:7: error: In non-native JS traits, defs with parentheses must be abstract.
+ | def b(x: Int): js.UndefOr[Int] = js.undefined
+ | ^
+ |newSource1.scala:8: error: JS setters must return Unit
+ | def c_=(v: Int): js.UndefOr[Int] = js.undefined
+ | ^
+ |newSource1.scala:8: error: In non-native JS traits, defs with parentheses must be abstract.
+ | def c_=(v: Int): js.UndefOr[Int] = js.undefined
+ | ^
+ """
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala
new file mode 100644
index 0000000000..4eedcb3447
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala
@@ -0,0 +1,194 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.scalajs.nscplugin.test.util.VersionDependentUtils.scalaVersion
+
+import org.junit.Assume._
+import org.junit.Test
+
+// scalastyle:off line.size.limit
+
+class JSSAMTest extends DirectTest with TestHelpers {
+
+ override def preamble: String =
+ """
+ import scala.scalajs.js
+ import scala.scalajs.js.annotation._
+ """
+
+ @Test
+ def noSAMAsJSType: Unit = {
+ """
+ @js.native
+ trait Foo extends js.Object {
+ def foo(x: Int): Int
+ }
+
+ trait Bar extends js.Object {
+ def bar(x: Int): Int
+ }
+
+ class Foobar extends js.Function {
+ def foobar(x: Int): Int
+ }
+
+ class A {
+ val foo: Foo = x => x + 1
+ val bar: Bar = x => x + 1
+ val foobar: Foobar = x => x + 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:19: error: Using an anonymous function as a SAM for the JavaScript type Foo is not allowed because it is not a trait extending js.Function. Use an anonymous class instead.
+ | val foo: Foo = x => x + 1
+ | ^
+ |newSource1.scala:20: error: Using an anonymous function as a SAM for the JavaScript type Bar is not allowed because it is not a trait extending js.Function. Use an anonymous class instead.
+ | val bar: Bar = x => x + 1
+ | ^
+ |newSource1.scala:21: error: Using an anonymous function as a SAM for the JavaScript type Foobar is not allowed because it is not a trait extending js.Function. Use an anonymous class instead.
+ | val foobar: Foobar = x => x + 1
+ | ^
+ """
+ }
+
+ @Test
+ def noSAMOfNativeJSFunctionType: Unit = {
+ """
+ @js.native
+ trait Foo extends js.Function {
+ def apply(x: Int): Int
+ }
+
+ @js.native
+ trait Bar extends js.Function {
+ def bar(x: Int = 5): Int
+ }
+
+ class A {
+ val foo: Foo = x => x + 1
+ val bar: Bar = x => x + 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:16: error: Using an anonymous function as a SAM for the JavaScript type Foo is not allowed because it is a native JS type. It is not possible to directly implement it.
+ | val foo: Foo = x => x + 1
+ | ^
+ |newSource1.scala:17: error: Using an anonymous function as a SAM for the JavaScript type Bar is not allowed because it is a native JS type. It is not possible to directly implement it.
+ | val bar: Bar = x => x + 1
+ | ^
+ """
+ }
+
+ @Test
+ def noSAMOfNonApplyJSType: Unit = {
+ """
+ trait Foo extends js.Function {
+ def foo(x: Int): Int
+ }
+
+ class A {
+ val foo: Foo = x => x + 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: Using an anonymous function as a SAM for the JavaScript type Foo is not allowed because its single abstract method is not named `apply`. Use an anonymous class instead.
+ | val foo: Foo = x => x + 1
+ | ^
+ """
+ }
+
+ @Test
+ def missingThisArgForJSThisFunction: Unit = {
+ """
+ trait BadThisFunction1 extends js.ThisFunction {
+ def apply(): Int
+ }
+
+ trait BadThisFunction2 extends js.ThisFunction {
+ def apply(args: Int*): Int
+ }
+
+ class A {
+ val badThisFunction1: BadThisFunction1 = () => 42
+ val badThisFunction2: BadThisFunction2 = args => args.size
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:14: error: The apply method for a js.ThisFunction must have a leading non-varargs parameter
+ | val badThisFunction1: BadThisFunction1 = () => 42
+ | ^
+ |newSource1.scala:15: error: The apply method for a js.ThisFunction must have a leading non-varargs parameter
+ | val badThisFunction2: BadThisFunction2 = args => args.size
+ | ^
+ """
+ }
+
+ @Test
+ def noNonsensicalJSFunctionTypes: Unit = {
+ """
+ class BadFunctionIsClass extends js.Function {
+ def apply(x: Int): Int
+ }
+
+ trait BadFunctionExtendsNonFunction extends js.Object {
+ def apply(x: Int): Int
+ }
+
+ class SubclassOfFunction extends js.Function
+
+ trait BadFunctionExtendsSubclassOfFunction extends SubclassOfFunction {
+ def apply(x: Int): Int
+ }
+
+ trait BadFunctionParametricMethod extends js.Function {
+ def apply[A](x: A): A
+ }
+
+ trait BadFunctionOverloaded extends js.Function {
+ def apply(x: Int): Int
+ def apply(x: String): String
+ }
+
+ trait BadFunctionMultipleAbstract extends js.Function {
+ def apply(x: Int): Int
+ def foo(x: Int): Int
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: A non-native JS type can only declare an abstract method named `apply` without `@JSName` if it is the SAM of a trait that extends js.Function
+ | def apply(x: Int): Int
+ | ^
+ |newSource1.scala:10: error: A non-native JS type can only declare an abstract method named `apply` without `@JSName` if it is the SAM of a trait that extends js.Function
+ | def apply(x: Int): Int
+ | ^
+ |newSource1.scala:16: error: A non-native JS type can only declare an abstract method named `apply` without `@JSName` if it is the SAM of a trait that extends js.Function
+ | def apply(x: Int): Int
+ | ^
+ |newSource1.scala:20: error: A non-native JS type can only declare an abstract method named `apply` without `@JSName` if it is the SAM of a trait that extends js.Function
+ | def apply[A](x: A): A
+ | ^
+ |newSource1.scala:24: error: A non-native JS type can only declare an abstract method named `apply` without `@JSName` if it is the SAM of a trait that extends js.Function
+ | def apply(x: Int): Int
+ | ^
+ |newSource1.scala:25: error: A non-native JS type can only declare an abstract method named `apply` without `@JSName` if it is the SAM of a trait that extends js.Function
+ | def apply(x: String): String
+ | ^
+ |newSource1.scala:29: error: A non-native JS type can only declare an abstract method named `apply` without `@JSName` if it is the SAM of a trait that extends js.Function
+ | def apply(x: Int): Int
+ | ^
+ """
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSUndefinedParamTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSUndefinedParamTest.scala
new file mode 100644
index 0000000000..a4eb2d9850
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSUndefinedParamTest.scala
@@ -0,0 +1,101 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.junit.Test
+
+// scalastyle:off line.size.limit
+
+/** This tests the UndefinedParam tracker in the compiler backend.
+ *
+ * In order to properly implement removal of trailing default parameters, the
+ * compiler backend may generate a UndefinedParam tree and catch it later.
+ * However, some macros and compiler plugins may generate trees the backend
+ * doesn't expect. As a result the backend used to generate invalid IR.
+ * Instead, we now track these helper trees and emit a more helpful error
+ * message if one of them sticks around.
+ *
+ * This test contains a macro that generates a tree we cannot handle and
+ * verifies that the compiler bails out.
+ */
+class JSUndefinedParamTest extends DirectTest with TestHelpers {
+
+ /* We need a macro in the test. Therefore, we add scala-reflect and the
+ * compiler's output path itself to the classpath.
+ */
+ override def classpath: List[String] =
+ super.classpath ++ List(scalaReflectPath, testOutputPath)
+
+ @Test def noDanglingUndefinedParam: Unit = {
+
+ // Define macro that extracts method parameter.
+ """
+ import language.experimental.macros
+
+ /** Dummy object to get the right shadowing for cross compilation */
+ private object Compat210 {
+ object blackbox {
+ type Context = scala.reflect.macros.Context
+ }
+ }
+
+ import Compat210._
+
+ object JSUndefinedParamTest {
+ import scala.reflect.macros._ // shadows blackbox from above
+ import blackbox.Context
+
+ def extractArg(call: Any): Any = macro extractArg_impl
+
+ def extractArg_impl(c: Context)(call: c.Expr[Any]): c.Expr[Any] = {
+ import c.universe._
+
+ call.tree match {
+ case Apply(fun, List(arg)) => c.Expr[Any](arg)
+
+ case tree =>
+ c.abort(tree.pos, "Bad tree. Need function call with single argument.")
+ }
+ }
+ }
+ """.succeeds()
+
+ // Use the macro to trigger UndefinedParam catcher.
+ """
+ import scala.scalajs.js
+ import scala.scalajs.js.annotation._
+
+ @js.native
+ trait MyTrait extends js.Any {
+ def foo(x: Int = js.native): Int = js.native
+ }
+
+ object A {
+ val myTrait: MyTrait = ???
+
+ /* We assign the default parameter value for foo to b.
+ * This should fail.
+ */
+ val b = JSUndefinedParamTest.extractArg(myTrait.foo())
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:10: error: Found a dangling UndefinedParam at Position(virtualfile:newSource1.scala,15,54). This is likely due to a bad interaction between a macro or a compiler plugin and the Scala.js compiler plugin. If you hit this, please let us know.
+ | object A {
+ | ^
+ """
+
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/LinkTimeIfTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/LinkTimeIfTest.scala
new file mode 100644
index 0000000000..881c0e9a2f
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/LinkTimeIfTest.scala
@@ -0,0 +1,109 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import util._
+
+import org.junit.Test
+import org.junit.Assert._
+
+// scalastyle:off line.size.limit
+
+class LinkTimeIfTest extends TestHelpers {
+ override def preamble: String = "import scala.scalajs.LinkingInfo._"
+
+ private final val IllegalLinkTimeIfArgMessage = {
+ "Illegal expression in the condition of a linkTimeIf. " +
+ "Valid expressions are: boolean and int primitives; " +
+ "references to link-time properties; " +
+ "primitive operations on booleans; " +
+ "and comparisons on ints."
+ }
+
+ @Test
+ def linkTimeErrorInvalidOp(): Unit = {
+ """
+ object A {
+ def foo =
+ linkTimeIf((esVersion + 1) < ESVersion.ES2015) { } { }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:4: error: $IllegalLinkTimeIfArgMessage
+ | linkTimeIf((esVersion + 1) < ESVersion.ES2015) { } { }
+ | ^
+ """
+ }
+
+ @Test
+ def linkTimeErrorInvalidEntities(): Unit = {
+ """
+ object A {
+ def foo(x: String) = {
+ val bar = 1
+ linkTimeIf(bar == 0) { } { }
+ }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage
+ | linkTimeIf(bar == 0) { } { }
+ | ^
+ """
+
+ // String comparison is a `BinaryOp.===`, which is not allowed
+ """
+ object A {
+ def foo(x: String) =
+ linkTimeIf("foo" == x) { } { }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:4: error: $IllegalLinkTimeIfArgMessage
+ | linkTimeIf("foo" == x) { } { }
+ | ^
+ """
+
+ """
+ object A {
+ def bar = true
+ def foo(x: String) =
+ linkTimeIf(bar || !bar) { } { }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage
+ | linkTimeIf(bar || !bar) { } { }
+ | ^
+ |newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage
+ | linkTimeIf(bar || !bar) { } { }
+ | ^
+ """
+ }
+
+ @Test
+ def linkTimeCondInvalidTree(): Unit = {
+ """
+ object A {
+ def bar = true
+ def foo(x: String) =
+ linkTimeIf(if (bar) true else false) { } { }
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage
+ | linkTimeIf(if (bar) true else false) { } { }
+ | ^
+ """
+ }
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/MatchASTTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/MatchASTTest.scala
new file mode 100644
index 0000000000..fa8faf6071
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/MatchASTTest.scala
@@ -0,0 +1,83 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import util._
+
+import org.junit.Test
+import org.junit.Assert._
+
+import org.scalajs.ir.{Trees => js}
+
+class MatchASTTest extends JSASTTest {
+
+ @Test
+ def stripIdentityMatchEndNonUnitResult: Unit = {
+ """
+ object A {
+ def aString: String = "a"
+ def foo = aString match {
+ case "a" => true
+ case "b" => false
+ }
+ }
+ """.hasExactly(1, "local variable") {
+ case js.VarDef(_, _, _, _, _) =>
+ }
+ }
+
+ @Test
+ def stripIdentityMatchEndUnitResult: Unit = {
+ """
+ object A {
+ def aString: String = "a"
+ def foo = aString match {
+ case "a" =>
+ case "b" =>
+ }
+ }
+ """.hasExactly(1, "local variable") {
+ case js.VarDef(_, _, _, _, _) =>
+ }
+ }
+
+ @Test
+ def matchWithZeroAlternativeInSwitch: Unit = {
+ """
+ object A {
+ def foo(x: Int): Int = (x: @scala.annotation.switch) match {
+ case n if n > 5 => n
+ case n if n >= 0 => 0
+ case n => -n
+ }
+ }
+ """.hasNot("any match") {
+ case js.Match(_, _, _) =>
+ }
+ }
+
+ @Test
+ def matchWithOneAlternativeInSwitch: Unit = {
+ """
+ object A {
+ def foo(x: Int): Int = (x: @scala.annotation.switch) match {
+ case -1 => 10
+ case n => n
+ }
+ }
+ """.hasNot("any match") {
+ case js.Match(_, _, _) =>
+ }
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala
new file mode 100644
index 0000000000..a6d37efc1f
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala
@@ -0,0 +1,901 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.scalajs.nscplugin.test.util.VersionDependentUtils.methodSig
+
+import org.junit.Test
+import org.junit.Ignore
+
+// scalastyle:off line.size.limit
+
+class NonNativeJSTypeTest extends DirectTest with TestHelpers {
+
+ override def preamble: String =
+ """
+ import scala.scalajs.js
+ import scala.scalajs.js.annotation._
+ """
+
+ @Test
+ def noExtendAnyRef: Unit = {
+ """
+ class A extends js.Any
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Non-native JS classes and objects cannot directly extend AnyRef. They must extend a JS class (native or not).
+ | class A extends js.Any
+ | ^
+ """
+
+ """
+ object A extends js.Any
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: Non-native JS classes and objects cannot directly extend AnyRef. They must extend a JS class (native or not).
+ | object A extends js.Any
+ | ^
+ """
+ }
+
+ @Test
+ def noExtendNativeTrait: Unit = {
+ """
+ @js.native
+ trait NativeTrait extends js.Object
+
+ class A extends NativeTrait
+
+ trait B extends NativeTrait
+
+ object C extends NativeTrait
+
+ object Container {
+ val x = new NativeTrait {}
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:8: error: Non-native JS types cannot directly extend native JS traits.
+ | class A extends NativeTrait
+ | ^
+ |newSource1.scala:10: error: Non-native JS types cannot directly extend native JS traits.
+ | trait B extends NativeTrait
+ | ^
+ |newSource1.scala:12: error: Non-native JS types cannot directly extend native JS traits.
+ | object C extends NativeTrait
+ | ^
+ |newSource1.scala:15: error: Non-native JS types cannot directly extend native JS traits.
+ | val x = new NativeTrait {}
+ | ^
+ """
+ }
+
+ @Test
+ def noConcreteApplyMethod: Unit = {
+ """
+ class A extends js.Object {
+ def apply(arg: Int): Int = arg
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: A non-native JS class cannot declare a concrete method named `apply` without `@JSName`
+ | def apply(arg: Int): Int = arg
+ | ^
+ """
+
+ """
+ trait B extends js.Object {
+ def apply(arg: Int): Int
+ }
+
+ class A extends B {
+ def apply(arg: Int): Int = arg
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: A non-native JS type can only declare an abstract method named `apply` without `@JSName` if it is the SAM of a trait that extends js.Function
+ | def apply(arg: Int): Int
+ | ^
+ |newSource1.scala:10: error: A non-native JS class cannot declare a concrete method named `apply` without `@JSName`
+ | def apply(arg: Int): Int = arg
+ | ^
+ """
+
+ """
+ abstract class B extends js.Object {
+ def apply(arg: Int): Int
+ }
+
+ class A extends B {
+ def apply(arg: Int): Int = arg
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: A non-native JS type can only declare an abstract method named `apply` without `@JSName` if it is the SAM of a trait that extends js.Function
+ | def apply(arg: Int): Int
+ | ^
+ |newSource1.scala:10: error: A non-native JS class cannot declare a concrete method named `apply` without `@JSName`
+ | def apply(arg: Int): Int = arg
+ | ^
+ """
+
+ """
+ object Enclosing {
+ val f = new js.Object {
+ def apply(arg: Int): Int = arg
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: A non-native JS class cannot declare a concrete method named `apply` without `@JSName`
+ | def apply(arg: Int): Int = arg
+ | ^
+ """
+
+ """
+ object Enclosing {
+ val f = new js.Function {
+ def apply(arg: Int): Int = arg
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: A non-native JS class cannot declare a concrete method named `apply` without `@JSName`
+ | def apply(arg: Int): Int = arg
+ | ^
+ """
+
+ """
+ object Enclosing {
+ val f = new js.Function1[Int, Int] {
+ def apply(arg: Int): Int = arg
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: A non-native JS class cannot declare a concrete method named `apply` without `@JSName`
+ | def apply(arg: Int): Int = arg
+ | ^
+ """
+ }
+
+ @Test
+ def noUnaryOp: Unit = {
+ """
+ class A extends js.Object {
+ def unary_+ : Int = 1
+ def unary_-() : Int = 1
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: warning: Method 'unary_+' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators
+ | def unary_+ : Int = 1
+ | ^
+ |newSource1.scala:6: error: A non-native JS class cannot declare a method named like a unary operation without `@JSName`
+ | def unary_+ : Int = 1
+ | ^
+ |newSource1.scala:7: warning: Method 'unary_-' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators
+ | def unary_-() : Int = 1
+ | ^
+ |newSource1.scala:7: error: A non-native JS class cannot declare a method named like a unary operation without `@JSName`
+ | def unary_-() : Int = 1
+ | ^
+ """
+
+ """
+ class A extends js.Object {
+ def unary_+(x: Int): Int = 2
+
+ @JSName("unary_-")
+ def unary_-() : Int = 1
+ }
+ """.succeeds()
+ }
+
+ @Test
+ def noBinaryOp: Unit = {
+ """
+ class A extends js.Object {
+ def +(x: Int): Int = x
+ def &&(x: String): String = x
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: warning: Method '+' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators
+ | def +(x: Int): Int = x
+ | ^
+ |newSource1.scala:6: error: A non-native JS class cannot declare a method named like a binary operation without `@JSName`
+ | def +(x: Int): Int = x
+ | ^
+ |newSource1.scala:7: warning: Method '&&' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators
+ | def &&(x: String): String = x
+ | ^
+ |newSource1.scala:7: error: A non-native JS class cannot declare a method named like a binary operation without `@JSName`
+ | def &&(x: String): String = x
+ | ^
+ """
+
+ """
+ class A extends js.Object {
+ def + : Int = 2
+
+ def -(x: Int, y: Int): Int = 7
+
+ @JSName("&&")
+ def &&(x: String): String = x
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:6: warning: Method '+' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators
+ | def + : Int = 2
+ | ^
+ |newSource1.scala:8: warning: Method '-' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators
+ | def -(x: Int, y: Int): Int = 7
+ | ^
+ """
+ }
+
+ @Test // #4281
+ def noExtendJSFunctionAnon: Unit = {
+ """
+ @js.native
+ @JSGlobal("bad")
+ abstract class BadFunction extends js.Function1[Int, String]
+
+ object Test {
+ new BadFunction {
+ def apply(x: Int): String = "f"
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:11: error: A non-native JS class cannot declare a concrete method named `apply` without `@JSName`
+ | def apply(x: Int): String = "f"
+ | ^
+ """
+
+ """
+ class $anonfun extends js.Function1[Int, String] {
+ def apply(x: Int): String = "f"
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: A non-native JS class cannot declare a concrete method named `apply` without `@JSName`
+ | def apply(x: Int): String = "f"
+ | ^
+ """
+ }
+
+ @Test
+ def noBracketAccess: Unit = {
+ """
+ class A extends js.Object {
+ @JSBracketAccess
+ def foo(index: Int, arg: Int): Int = arg
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: @JSBracketAccess is not allowed in non-native JS classes
+ | def foo(index: Int, arg: Int): Int = arg
+ | ^
+ """
+ }
+
+ @Test
+ def noBracketCall: Unit = {
+ """
+ class A extends js.Object {
+ @JSBracketCall
+ def foo(m: String, arg: Int): Int = arg
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: @JSBracketCall is not allowed in non-native JS classes
+ | def foo(m: String, arg: Int): Int = arg
+ | ^
+ """
+ }
+
+ @Test
+ def noCollapseOverloadsOnJSName: Unit = {
+ """
+ class A extends js.Object {
+ @JSName("bar")
+ def foo(): Int = 42
+
+ def bar(): Int = 24
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:9: error: Cannot disambiguate overloads for method bar with types
+ | ${methodSig("()", "Int")}
+ | ${methodSig("()", "Int")}
+ | def bar(): Int = 24
+ | ^
+ """
+
+ """
+ class A extends js.Object {
+ def bar(): Int = 24
+
+ @JSName("bar")
+ def foo(): Int = 42
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:9: error: Cannot disambiguate overloads for method bar with types
+ | ${methodSig("()", "Int")}
+ | ${methodSig("()", "Int")}
+ | def foo(): Int = 42
+ | ^
+ """
+
+ """
+ class A extends js.Object {
+ @JSName("bar")
+ def foo(): Int = 42
+ }
+
+ class B extends A {
+ def bar(): Int = 24
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:11: error: Cannot disambiguate overloads for method bar with types
+ | ${methodSig("()", "Int")}
+ | ${methodSig("()", "Int")}
+ | def bar(): Int = 24
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSName("bar")
+ def foo(): Int = js.native
+ }
+
+ class B extends A {
+ def bar(): Int = 24
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:13: error: Cannot disambiguate overloads for method bar with types
+ | ${methodSig("()", "Int")}
+ | ${methodSig("()", "Int")}
+ | def bar(): Int = 24
+ | ^
+ """
+
+ """
+ @js.native
+ @JSGlobal
+ class Foo extends js.Object {
+ def foo(x: Int): Int = js.native
+
+ @JSName("foo")
+ def bar(x: Int): Int = js.native
+ }
+
+ class Bar extends Foo {
+ def foo(): Int = 42
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:14: error: Cannot disambiguate overloads for method foo with types
+ | ${methodSig("(x: Int)", "Int")}
+ | ${methodSig("(x: Int)", "Int")}
+ | class Bar extends Foo {
+ | ^
+ """
+ }
+
+ @Test
+ def noOverloadedPrivate: Unit = {
+ """
+ class A extends js.Object {
+ private def foo(i: Int): Int = i
+ private def foo(s: String): String = s
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Private methods in non-native JS classes cannot be overloaded. Use different names instead.
+ | private def foo(i: Int): Int = i
+ | ^
+ |newSource1.scala:7: error: Private methods in non-native JS classes cannot be overloaded. Use different names instead.
+ | private def foo(s: String): String = s
+ | ^
+ """
+
+ """
+ object A extends js.Object {
+ private def foo(i: Int): Int = i
+ private def foo(s: String): String = s
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Private methods in non-native JS classes cannot be overloaded. Use different names instead.
+ | private def foo(i: Int): Int = i
+ | ^
+ |newSource1.scala:7: error: Private methods in non-native JS classes cannot be overloaded. Use different names instead.
+ | private def foo(s: String): String = s
+ | ^
+ """
+
+ """
+ object Enclosing {
+ class A extends js.Object {
+ private[Enclosing] def foo(i: Int): Int = i
+ private def foo(s: String): String = s
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Private methods in non-native JS classes cannot be overloaded. Use different names instead.
+ | private[Enclosing] def foo(i: Int): Int = i
+ | ^
+ |newSource1.scala:8: error: Private methods in non-native JS classes cannot be overloaded. Use different names instead.
+ | private def foo(s: String): String = s
+ | ^
+ """
+
+ """
+ class A extends js.Object {
+ private def foo(i: Int): Int = i
+ def foo(s: String): String = s
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: Private methods in non-native JS classes cannot be overloaded. Use different names instead.
+ | private def foo(i: Int): Int = i
+ | ^
+ """
+
+ """
+ object Enclosing {
+ class A extends js.Object {
+ private[Enclosing] def foo(i: Int): Int = i
+ def foo(s: String): String = s
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Private methods in non-native JS classes cannot be overloaded. Use different names instead.
+ | private[Enclosing] def foo(i: Int): Int = i
+ | ^
+ """
+ }
+
+ @Test
+ def noVirtualQualifiedPrivate: Unit = {
+ """
+ object Enclosing {
+ class A extends js.Object {
+ private[Enclosing] def foo(i: Int): Int = i
+ private[Enclosing] val x: Int = 3
+ private[Enclosing] var y: Int = 5
+ }
+
+ class B extends A {
+ override private[Enclosing] final def foo(i: Int): Int = i + 1
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] def foo(i: Int): Int = i
+ | ^
+ |newSource1.scala:8: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] val x: Int = 3
+ | ^
+ |newSource1.scala:9: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] var y: Int = 5
+ | ^
+ """
+
+ """
+ object Enclosing {
+ object A extends js.Object {
+ private[Enclosing] def foo(i: Int): Int = i
+ private[Enclosing] val x: Int = 3
+ private[Enclosing] var y: Int = 5
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] def foo(i: Int): Int = i
+ | ^
+ |newSource1.scala:8: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] val x: Int = 3
+ | ^
+ |newSource1.scala:9: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] var y: Int = 5
+ | ^
+ """
+
+ """
+ object Enclosing {
+ abstract class A extends js.Object {
+ private[Enclosing] def foo(i: Int): Int
+ private[Enclosing] val x: Int
+ private[Enclosing] var y: Int
+ }
+
+ class B extends A {
+ override private[Enclosing] final def foo(i: Int): Int = i + 1
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] def foo(i: Int): Int
+ | ^
+ |newSource1.scala:8: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] val x: Int
+ | ^
+ |newSource1.scala:9: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] var y: Int
+ | ^
+ """
+
+ """
+ object Enclosing {
+ trait A extends js.Object {
+ private[Enclosing] def foo(i: Int): Int
+ private[Enclosing] val x: Int
+ private[Enclosing] var y: Int
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] def foo(i: Int): Int
+ | ^
+ |newSource1.scala:8: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] val x: Int
+ | ^
+ |newSource1.scala:9: error: Qualified private members in non-native JS classes must be final
+ | private[Enclosing] var y: Int
+ | ^
+ """
+
+ """
+ object Enclosing {
+ class A private () extends js.Object
+
+ class B private[this] () extends js.Object
+
+ class C private[Enclosing] () extends js.Object
+ }
+ """.succeeds()
+
+ """
+ object Enclosing {
+ class A extends js.Object {
+ final private[Enclosing] def foo(i: Int): Int = i
+ }
+ }
+ """.succeeds()
+
+ """
+ object Enclosing {
+ class A extends js.Object {
+ private def foo(i: Int): Int = i
+ private[this] def bar(i: Int): Int = i + 1
+ }
+ }
+ """.succeeds()
+
+ """
+ object Enclosing {
+ object A extends js.Object {
+ final private[Enclosing] def foo(i: Int): Int = i
+ }
+ }
+ """.succeeds()
+
+ """
+ object Enclosing {
+ object A extends js.Object {
+ private def foo(i: Int): Int = i
+ private[this] def bar(i: Int): Int = i + 1
+ }
+ }
+ """.succeeds()
+
+ """
+ object Enclosing {
+ abstract class A extends js.Object {
+ final private[Enclosing] def foo(i: Int): Int
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: abstract member may not have final modifier
+ | final private[Enclosing] def foo(i: Int): Int
+ | ^
+ """
+
+ """
+ object Enclosing {
+ trait A extends js.Object {
+ final private[Enclosing] def foo(i: Int): Int
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: abstract member may not have final modifier
+ | final private[Enclosing] def foo(i: Int): Int
+ | ^
+ """
+ }
+
+ @Test
+ def noUseJsNative: Unit = {
+ """
+ class A extends js.Object {
+ def foo = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: js.native may only be used as stub implementation in facade types
+ | def foo = js.native
+ | ^
+ """
+
+ """
+ object A extends js.Object {
+ def foo = js.native
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: js.native may only be used as stub implementation in facade types
+ | def foo = js.native
+ | ^
+ """
+
+ """
+ class A {
+ val x = new js.Object {
+ def a: Int = js.native
+ }
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:7: error: js.native may only be used as stub implementation in facade types
+ | def a: Int = js.native
+ | ^
+ """
+ }
+
+ @Test
+ def noNonLiteralJSName: Unit = {
+ """
+ object A {
+ val a = "Hello"
+ final val b = "World"
+ }
+
+ class B extends js.Object {
+ @JSName(A.a)
+ def foo: Int = 5
+ @JSName(A.b)
+ def bar: Int = 5
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:11: error: A string argument to JSName must be a literal string
+ | @JSName(A.a)
+ | ^
+ """
+
+ """
+ object A {
+ val a = "Hello"
+ final val b = "World"
+ }
+
+ object B extends js.Object {
+ @JSName(A.a)
+ def foo: Int = 5
+ @JSName(A.b)
+ def bar: Int = 5
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:11: error: A string argument to JSName must be a literal string
+ | @JSName(A.a)
+ | ^
+ """
+ }
+
+ @Test
+ def noApplyProperty: Unit = {
+ // def apply
+
+ """
+ class A extends js.Object {
+ def apply: Int = 42
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: A member named apply represents function application in JavaScript. A parameterless member should be exported as a property. You must add @JSName("apply")
+ | def apply: Int = 42
+ | ^
+ """
+
+ """
+ class A extends js.Object {
+ @JSName("apply")
+ def apply: Int = 42
+ }
+ """.succeeds()
+
+ // val apply
+
+ """
+ class A extends js.Object {
+ val apply: Int = 42
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: A member named apply represents function application in JavaScript. A parameterless member should be exported as a property. You must add @JSName("apply")
+ | val apply: Int = 42
+ | ^
+ """
+
+ """
+ class A extends js.Object {
+ @JSName("apply")
+ val apply: Int = 42
+ }
+ """.succeeds()
+
+ // var apply
+
+ """
+ class A extends js.Object {
+ var apply: Int = 42
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: A member named apply represents function application in JavaScript. A parameterless member should be exported as a property. You must add @JSName("apply")
+ | var apply: Int = 42
+ | ^
+ """
+
+ """
+ class A extends js.Object {
+ @JSName("apply")
+ var apply: Int = 42
+ }
+ """.succeeds()
+ }
+
+ @Test
+ def noExportClassWithOnlyPrivateCtors: Unit = {
+ """
+ @JSExportTopLevel("A")
+ class A private () extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export a class that has only private constructors
+ | @JSExportTopLevel("A")
+ | ^
+ """
+
+ """
+ @JSExportTopLevel("A")
+ class A private[this] () extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export a class that has only private constructors
+ | @JSExportTopLevel("A")
+ | ^
+ """
+
+ """
+ @JSExportTopLevel("A")
+ class A private[A] () extends js.Object
+
+ object A
+ """ hasErrors
+ """
+ |newSource1.scala:5: error: You may not export a class that has only private constructors
+ | @JSExportTopLevel("A")
+ | ^
+ """
+ }
+
+ @Test
+ def noConcreteMemberInTrait: Unit = {
+ """
+ trait A extends js.Object {
+ def foo(x: Int): Int = x + 1
+ def bar[A](x: A): A = x
+
+ object InnerScalaObject
+ object InnerJSObject extends js.Object
+ @js.native object InnerNativeJSObject extends js.Object
+
+ class InnerScalaClass
+ class InnerJSClass extends js.Object
+ @js.native class InnerNativeJSClass extends js.Object
+ }
+ """ hasErrors
+ """
+ |newSource1.scala:6: error: In non-native JS traits, defs with parentheses must be abstract.
+ | def foo(x: Int): Int = x + 1
+ | ^
+ |newSource1.scala:7: error: In non-native JS traits, defs with parentheses must be abstract.
+ | def bar[A](x: A): A = x
+ | ^
+ |newSource1.scala:9: error: Non-native JS traits cannot contain inner classes or objects
+ | object InnerScalaObject
+ | ^
+ |newSource1.scala:10: error: Non-native JS traits cannot contain inner classes or objects
+ | object InnerJSObject extends js.Object
+ | ^
+ |newSource1.scala:11: error: non-native JS classes, traits and objects may not have native JS members
+ | @js.native object InnerNativeJSObject extends js.Object
+ | ^
+ |newSource1.scala:13: error: Non-native JS traits cannot contain inner classes or objects
+ | class InnerScalaClass
+ | ^
+ |newSource1.scala:14: error: Non-native JS traits cannot contain inner classes or objects
+ | class InnerJSClass extends js.Object
+ | ^
+ |newSource1.scala:15: error: non-native JS classes, traits and objects may not have native JS members
+ | @js.native class InnerNativeJSClass extends js.Object
+ | ^
+ """
+ }
+
+ @Test // #4511
+ def noConflictingProperties: Unit = {
+ """
+ class A extends js.Object {
+ def a: Unit = ()
+
+ @JSName("a")
+ def b: Unit = ()
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:9: error: Cannot disambiguate overloads for getter a with types
+ | ${methodSig("()", "Unit")}
+ | ${methodSig("()", "Unit")}
+ | def b: Unit = ()
+ | ^
+ """
+
+ """
+ class A extends js.Object {
+ class B extends js.Object
+
+ object B
+ }
+ """ hasErrors
+ s"""
+ |newSource1.scala:8: error: Cannot disambiguate overloads for getter B with types
+ | ${methodSig("()", "A$B.type")}
+ | ${methodSig("()", "Object")}
+ | object B
+ | ^
+ """
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala
new file mode 100644
index 0000000000..b10bef4b95
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala
@@ -0,0 +1,611 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import util._
+
+import org.junit.Test
+import org.junit.Assert._
+
+import org.scalajs.ir.{Trees => js, Types => jstpe}
+import org.scalajs.ir.Names._
+
+class OptimizationTest extends JSASTTest {
+ import OptimizationTest._
+
+ @Test
+ def testArrayApplyOptimization: Unit = {
+ /* Make sure Array(...) is optimized away completely for several kinds
+ * of data types, with both the generic overload and the ones specialized
+ * for primitives.
+ */
+ """
+ class A {
+ val a = Array(5, 7, 9, -3)
+ val b = Array("hello", "world")
+ val c = Array('a', 'b')
+ val d = Array(Nil)
+ val e = Array(5.toByte, 7.toByte, 9.toByte, -3.toByte)
+
+ // Also with exactly 1 element of a primitive type (#3938)
+ val f = Array('a')
+ val g = Array(5.toByte)
+ }
+ """.
+ hasNot("any LoadModule of the scala.Array companion") {
+ case js.LoadModule(ArrayModuleClass) =>
+ }
+
+ /* Using [] with primitives produces suboptimal trees, which cannot be
+ * optimized. We should improve this in the future, if possible. This is
+ * particularly annoying for Byte and Short, as it means that we need to
+ * write `.toByte` for every single element if we want the optimization to
+ * kick in.
+ *
+ * Scala/JVM has the same limitation.
+ */
+ """
+ class A {
+ val a = Array[Int](5, 7, 9, -3)
+ val b = Array[Byte](5, 7, 9, -3)
+ val c = Array[Int](5)
+ val d = Array[Byte](5)
+ }
+ """.
+ hasExactly(4, "calls to Array.apply methods") {
+ case js.Apply(_, js.LoadModule(ArrayModuleClass), js.MethodIdent(methodName), _)
+ if methodName.simpleName == applySimpleMethodName =>
+ }
+ }
+
+ @Test
+ def testJSArrayApplyOptimization: Unit = {
+ /* Make sure js.Array(...) is optimized away completely for several kinds
+ * of data types.
+ */
+ """
+ import scala.scalajs.js
+
+ class VC(val x: Int) extends AnyVal
+
+ class A {
+ val a = js.Array(5, 7, 9, -3)
+ val b = js.Array("hello", "world")
+ val c = js.Array('a', 'b')
+ val d = js.Array(Nil)
+ val e = js.Array(new VC(151189))
+ }
+ """.
+ hasNot("any of the wrapArray methods") {
+ case WrapArrayCall() =>
+ }
+ }
+
+ @Test
+ def testVarArgsOptimization: Unit = {
+ /* Make sure varargs are optimized to use js.WrappedArray instead of
+ * scm.WrappedArray, for various data types.
+ */
+ """
+ import scala.scalajs.js
+
+ class VC(val x: Int) extends AnyVal
+
+ class A {
+ val a = List(5, 7, 9, -3)
+ val b = List("hello", "world")
+ val c = List('a', 'b')
+ val d = List(Nil)
+ val e = List(new VC(151189))
+ }
+ """.
+ hasNot("any of the wrapArray methods") {
+ case WrapArrayCall() =>
+ }
+
+ /* #2265 and #2741:
+ * Make sure varargs are optimized to use js.WrappedArray instead of
+ * scm.WrappedArray, for different species of target method (single arg
+ * list, multiple arg list, in value class).
+ */
+ """
+ import scala.scalajs.js
+
+ class VC(val x: Int) extends AnyVal {
+ def singleInVC(ys: Int*): Int = x + ys.size
+ }
+
+ class A {
+ def test(): Int = {
+ val a = single(5, 7, 9, -3)
+ val b = multiple(5)(7, 9, -3)
+ val c = new VC(5).singleInVC(7, 9, -3)
+ a + b + c
+ }
+
+ def single(x: Int, ys: Int*): Int = x + ys.size
+ def multiple(x: Int)(ys: Int*): Int = x + ys.size
+ }
+ """.
+ hasNot("any of the wrapArray methods") {
+ case WrapArrayCall() =>
+ }
+
+ /* Make sure our wrapper matcher has the right name.
+ * With the new collections, only actual varargs will produce a call to the
+ * methods we optimize, and we would always be able to optimize them in
+ * that case. So we need to explicitly call the method that the codegen
+ * would use.
+ */
+ val sanityCheckCode = if (hasOldCollections) {
+ """
+ class A {
+ val a: Seq[Int] = new Array[Int](5)
+ }
+ """
+ } else {
+ """
+ class A {
+ runtime.ScalaRunTime.wrapIntArray(new Array[Int](5))
+ }
+ """
+ }
+ sanityCheckCode.has("one of the wrapArray methods") {
+ case WrapArrayCall() =>
+ }
+ }
+
+ @Test
+ def testNewJSObjectAndJSArray: Unit = {
+ // Verify the optimized emitted code for 'new js.Object' and 'new js.Array'
+ """
+ import scala.scalajs.js
+ class A {
+ val o = new js.Object
+ val a = new js.Array
+ }
+ """.
+ hasNot("any reference to the global scope nor loading JS constructor") {
+ case js.JSGlobalRef(_) =>
+ case js.LoadJSConstructor(_) =>
+ }
+ }
+
+ @Test
+ def noLabeledBlockForWhileLoops: Unit = {
+ """
+ class Test {
+ def testWhileStatWithCond(): Unit = {
+ var x: Int = 5
+ while (x != 0) {
+ x -= 1
+ }
+ println(x)
+ }
+
+ def testWhileExprWithCond(s: Any): Unit = {
+ var x: Int = 5
+ s match {
+ case s: String =>
+ while (x != 0) {
+ x -= 1
+ }
+ }
+ }
+
+ def testWhileTrueStat(): Unit = {
+ var x: Int = 5
+ while (true) {
+ x -= 1
+ if (x == 0)
+ return
+ println(x)
+ }
+ }
+
+ def testWhileTrueExpr(s: Any): Unit = {
+ var x: Int = 5
+ s match {
+ case s: String =>
+ while (true) {
+ x -= 1
+ if (x == 0)
+ return
+ println(x)
+ }
+ }
+ }
+
+ def testWhileFalseStat(): Unit = {
+ var x: Int = 5
+ while (false) {
+ x -= 1
+ if (x == 0)
+ return
+ println(x)
+ }
+ }
+
+ def testWhileFalseExpr(s: Any): Unit = {
+ var x: Int = 5
+ s match {
+ case s: String =>
+ while (false) {
+ x -= 1
+ if (x == 0)
+ return
+ println(x)
+ }
+ }
+ }
+ }
+ """.hasNot("non-return labeled block") {
+ case js.Labeled(name, _, _) if !name.nameString.startsWith("_return") =>
+ }
+ }
+
+ @Test
+ def noLabeledBlockForDoWhileLoops: Unit = {
+ """
+ class Test {
+ def testDoWhileStatWithCond(): Unit = {
+ var x: Int = 5
+ do {
+ x -= 1
+ } while (x != 0)
+ println(x)
+ }
+
+ def testDoWhileExprWithCond(s: Any): Unit = {
+ var x: Int = 5
+ s match {
+ case s: String =>
+ do {
+ x -= 1
+ } while (x != 0)
+ }
+ }
+
+ def testDoWhileTrueStat(): Unit = {
+ var x: Int = 5
+ do {
+ x -= 1
+ if (x == 0)
+ return
+ println(x)
+ } while (true)
+ }
+
+ def testDoWhileTrueExpr(s: Any): Unit = {
+ var x: Int = 5
+ s match {
+ case s: String =>
+ do {
+ x -= 1
+ if (x == 0)
+ return
+ println(x)
+ } while (true)
+ }
+ }
+
+ def testDoWhileFalseStat(): Unit = {
+ var x: Int = 5
+ do {
+ x -= 1
+ if (x == 0)
+ return
+ println(x)
+ } while (false)
+ }
+
+ def testDoWhileFalseExpr(s: Any): Unit = {
+ var x: Int = 5
+ s match {
+ case s: String =>
+ do {
+ x -= 1
+ if (x == 0)
+ return
+ println(x)
+ } while (false)
+ }
+ }
+ }
+ """.hasNot("non-return labeled block") {
+ case js.Labeled(name, _, _) if !name.nameString.startsWith("_return") =>
+ }
+ }
+
+ @Test
+ def noLabeledBlockForPatmatWithToplevelCaseClassesOnlyAndNoGuards: Unit = {
+ """
+ sealed abstract class Foo
+ final case class Foobar(x: Int) extends Foo
+ final case class Foobaz(y: String) extends Foo
+
+ class Test {
+ def testWithListsStat(xs: List[Int]): Unit = {
+ xs match {
+ case head :: tail => println(head + " " + tail)
+ case Nil => println("nil")
+ }
+ }
+
+ def testWithListsExpr(xs: List[Int]): Int = {
+ xs match {
+ case head :: tail => head + tail.size
+ case Nil => 0
+ }
+ }
+
+ def testWithFooStat(foo: Foo): Unit = {
+ foo match {
+ case Foobar(x) => println("foobar: " + x)
+ case Foobaz(y) => println(y)
+ }
+ }
+
+ def testWithFooExpr(foo: Foo): String = {
+ foo match {
+ case Foobar(x) => "foobar: " + x
+ case Foobaz(y) => "foobaz: " + y
+ }
+ }
+ }
+ """.hasNot("Labeled block") {
+ case js.Labeled(_, _, _) =>
+ }
+ }
+
+ @Test
+ def switchWithoutGuards: Unit = {
+ """
+ class Test {
+ def switchWithGuardsStat(x: Int, y: Int): Unit = {
+ x match {
+ case 1 => println("one")
+ case 2 => println("two")
+ case z if y > 100 => println("big " + z)
+ case _ => println("None of those")
+ }
+ }
+ }
+ """.hasNot("Labeled block") {
+ case js.Labeled(_, _, _) =>
+ }.has("Match node") {
+ case js.Match(_, _, _) =>
+ }
+ }
+
+ @Test
+ def switchWithGuards: Unit = {
+ // Statement position
+ """
+ class Test {
+ def switchWithGuardsStat(x: Int, y: Int): Unit = {
+ x match {
+ case 1 => println("one")
+ case 2 if y < 10 => println("two special")
+ case 2 => println("two")
+ case 3 if y < 10 => println("three special")
+ case 3 if y > 100 => println("three big special")
+ case z if y > 100 => println("big " + z)
+ case _ => println("None of those")
+ }
+ }
+ }
+ """.hasExactly(1, "default case (\"None of those\")") {
+ case js.StringLiteral("None of those") =>
+ }.has("Match node") {
+ case js.Match(_, _, _) =>
+ }
+
+ // Expression position
+ """
+ class Test {
+ def switchWithGuardsExpr(x: Int, y: Int): Unit = {
+ val message = x match {
+ case 1 => "one"
+ case 2 if y < 10 => "two special"
+ case 2 => "two"
+ case 3 if y < 10 => "three special"
+ case 3 if y > 100 => "three big special"
+ case z if y > 100 => "big " + z
+ case _ => "None of those"
+ }
+ println(message)
+ }
+ }
+ """.hasExactly(1, "default case (\"None of those\")") {
+ case js.StringLiteral("None of those") =>
+ }.has("Match node") {
+ case js.Match(_, _, _) =>
+ }
+ }
+
+ @Test
+ def newSJSDefinedTraitProducesObjectConstr: Unit = {
+ """
+ import scala.scalajs.js
+ import scala.scalajs.js.annotation._
+
+ trait Point extends js.Object {
+ val x: Double
+ val y: Double
+ }
+
+ class Test {
+ def newSJSDefinedTraitProducesObjectConstr(): Any = {
+ new Point {
+ val x = 5.0
+ val y = 6.5
+ }
+ }
+ }
+ """.hasNot("`new Object`") {
+ case js.JSNew(_, _) =>
+ }.has("object literal") {
+ case js.JSObjectConstr(Nil) =>
+ }
+
+ """
+ import scala.scalajs.js
+ import scala.scalajs.js.annotation._
+
+ trait Point extends js.Object {
+ var x: js.UndefOr[Double] = js.undefined
+ var y: js.UndefOr[Double] = js.undefined
+ }
+
+ class Test {
+ def newSJSDefinedTraitProducesObjectConstr(): Any = {
+ new Point {
+ x = 5.0
+ y = 6.5
+ }
+ }
+ }
+ """.hasNot("`new Object`") {
+ case js.JSNew(_, _) =>
+ }.has("object literal") {
+ case js.JSObjectConstr(Nil) =>
+ }
+ }
+
+ @Test
+ def optimizeScalaLambda: Unit = {
+ val allowedNames = Set(ClassName("A$"), ClassName("A"))
+
+ """
+ object A {
+ val x: Int => String = _.toString
+ }
+ """.hasNot("auxiliary/anonymous class") {
+ case cl: js.ClassDef if !allowedNames.contains(cl.name.name) =>
+ }
+ }
+
+ @Test
+ def noWrapJavaScriptExceptionWhenCatchingWildcardThrowable: Unit = {
+ """
+ class Test {
+ def foo(): Int = throw new IllegalArgumentException("foo")
+
+ def testCatchFullWildcard(): Int = {
+ try {
+ foo()
+ } catch {
+ case _ => -1 // causes an expected Scala compile warning
+ }
+ }
+
+ def testCatchWildcardOfTypeThrowable(): Int = {
+ try {
+ foo()
+ } catch {
+ case _: Throwable => -1
+ }
+ }
+ }
+ """.hasNot("WrapAsThrowable") {
+ case js.UnaryOp(js.UnaryOp.WrapAsThrowable, _) =>
+ }
+
+ // Confidence check
+ """
+ class Test {
+ def foo(): Int = throw new IllegalArgumentException("foo")
+
+ def testCatchWildcardOfTypeRuntimeException(): Int = {
+ try {
+ foo()
+ } catch {
+ case _: RuntimeException => -1
+ }
+ }
+ }
+ """.hasExactly(1, "WrapAsThrowable") {
+ case js.UnaryOp(js.UnaryOp.WrapAsThrowable, _) =>
+ }
+ }
+
+ @Test
+ def callSiteInlineSingleDispatchJSMethods: Unit = {
+ val fooName = SimpleMethodName("foo")
+ val aName = ClassName("A")
+
+ val flags = {
+ """
+ import scala.scalajs.js
+
+ class A extends js.Object {
+ def foo(x: Int, y: Int = 2): Int = x + y
+ }
+ """.extractOne("foo dispatch call") {
+ case js.ApplyStatic(flags, `aName`, SMN("foo"), _) =>
+ flags
+ }
+ }
+
+ assertTrue(flags.inline)
+ }
+
+ @Test
+ def loadModuleAfterStoreModuleIsThis: Unit = {
+ val testName = ClassName("Test$")
+
+ """
+ object Test {
+ private val selfPair = (Test, Test)
+ }
+ """.hasNot("LoadModule") {
+ case js.LoadModule(_) =>
+ }
+
+ // Confidence check
+ """
+ object Test {
+ private def selfPair = (Test, Test)
+ }
+ """.hasExactly(2, "LoadModule") {
+ case js.LoadModule(`testName`) =>
+ }
+ }
+}
+
+object OptimizationTest {
+
+ private val ArrayModuleClass = ClassName("scala.Array$")
+
+ private val applySimpleMethodName = SimpleMethodName("apply")
+
+ private val hasOldCollections =
+ scala.util.Properties.versionNumberString.startsWith("2.12.")
+
+ private object WrapArrayCall {
+ private val WrappedArrayTypeRef = {
+ val name =
+ if (hasOldCollections) "scala.collection.mutable.WrappedArray"
+ else "scala.collection.immutable.ArraySeq"
+ jstpe.ClassRef(ClassName(name))
+ }
+
+ def unapply(tree: js.Apply): Boolean = {
+ val methodName = tree.method.name
+ methodName.simpleName.nameString.startsWith("wrap") &&
+ methodName.resultTypeRef == WrappedArrayTypeRef
+ }
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/PositionTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/PositionTest.scala
new file mode 100644
index 0000000000..846560dbab
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/PositionTest.scala
@@ -0,0 +1,49 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import util.JSASTTest
+
+import org.junit.Test
+import org.junit.Assert._
+
+import scala.reflect.internal.util.BatchSourceFile
+
+import org.scalajs.ir.{Trees => js}
+
+class PositionTest extends JSASTTest {
+
+ @Test
+ def virtualFilePosition: Unit = {
+
+ val name = ""
+ val source = new BatchSourceFile(name,
+ """class A { def x = 1 }""")
+
+ var found = false
+ sourceAST(source) traverse {
+ case lit: js.IntLiteral =>
+ found = true
+ assertEquals(
+ "Scheme of virtual file URI should be `virtualfile'",
+ "virtualfile", lit.pos.source.getScheme)
+ assertEquals(
+ "Scheme specific part of virtual file URI should be its path",
+ name, lit.pos.source.getSchemeSpecificPart)
+ }
+
+ assertTrue("Should have IntLiteral tree", found)
+
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/ReflectTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/ReflectTest.scala
new file mode 100644
index 0000000000..f6937e2475
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/ReflectTest.scala
@@ -0,0 +1,75 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.junit.Test
+
+// scalastyle:off line.size.limit
+
+class ReflectTest extends DirectTest with TestHelpers {
+
+ override def preamble: String =
+ """import scala.scalajs.js, js.annotation._
+ import scala.scalajs.reflect.annotation._
+ """
+
+ @Test
+ def noEnableReflectiveInstantiationOnJSType: Unit = {
+ """
+ @EnableReflectiveInstantiation
+ class A extends js.Object
+
+ @EnableReflectiveInstantiation
+ trait B extends js.Object
+
+ @EnableReflectiveInstantiation
+ object C extends js.Object
+
+ @EnableReflectiveInstantiation
+ @js.native
+ @JSGlobal
+ class D extends js.Object
+
+ @EnableReflectiveInstantiation
+ @js.native
+ trait E extends js.Object
+
+ @EnableReflectiveInstantiation
+ @js.native
+ @JSGlobal
+ object F extends js.Object
+ """ hasErrors
+ """
+ |newSource1.scala:4: error: @EnableReflectiveInstantiation cannot be used on types extending js.Any.
+ | @EnableReflectiveInstantiation
+ | ^
+ |newSource1.scala:7: error: @EnableReflectiveInstantiation cannot be used on types extending js.Any.
+ | @EnableReflectiveInstantiation
+ | ^
+ |newSource1.scala:10: error: @EnableReflectiveInstantiation cannot be used on types extending js.Any.
+ | @EnableReflectiveInstantiation
+ | ^
+ |newSource1.scala:13: error: @EnableReflectiveInstantiation cannot be used on types extending js.Any.
+ | @EnableReflectiveInstantiation
+ | ^
+ |newSource1.scala:18: error: @EnableReflectiveInstantiation cannot be used on types extending js.Any.
+ | @EnableReflectiveInstantiation
+ | ^
+ |newSource1.scala:22: error: @EnableReflectiveInstantiation cannot be used on types extending js.Any.
+ | @EnableReflectiveInstantiation
+ | ^
+ """
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersASTTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersASTTest.scala
new file mode 100644
index 0000000000..3505a06eaf
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersASTTest.scala
@@ -0,0 +1,91 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+
+import org.scalajs.ir.Names._
+import org.scalajs.ir.Trees._
+import org.scalajs.ir.Types._
+
+import org.junit.Assert._
+import org.junit.Test
+
+// scalastyle:off line.size.limit
+
+class StaticForwardersASTTest extends JSASTTest {
+
+ @Test
+ def emitStaticForwardersInExistingClass(): Unit = {
+ val classDef = """
+ import scala.scalajs.js, js.annotation._
+
+ class Foo(val y: Int = 10)
+
+ object Foo {
+ def bar(x: Int = 5): Int = x + 1
+
+ @js.native
+ @JSGlobal("foobar")
+ def foobar(x: Int = 5): Int = js.native
+ }
+ """.extractOne("class Foo") {
+ case cd: ClassDef if cd.name.name == ClassName("Foo") => cd
+ }
+
+ val staticMethodNames = classDef.methods
+ .withFilter(_.flags.namespace.isStatic)
+ .map(_.name.name)
+ .sortBy(_.simpleName)
+
+ assertEquals(
+ List(
+ MethodName("$lessinit$greater$default$1", Nil, IntRef),
+ MethodName("bar", List(IntRef), IntRef),
+ MethodName("bar$default$1", Nil, IntRef)
+ ),
+ staticMethodNames
+ )
+ }
+
+ @Test
+ def emitStaticForwardersInSyntheticClass(): Unit = {
+ val classDef = """
+ import scala.scalajs.js, js.annotation._
+
+ object Foo {
+ def bar(x: Int = 5): Int = x + 1
+
+ @js.native
+ @JSGlobal("foobar")
+ def foobar(x: Int = 5): Int = js.native
+ }
+ """.extractOne("class Foo") {
+ case cd: ClassDef if cd.name.name == ClassName("Foo") => cd
+ }
+
+ val staticMethodNames = classDef.methods
+ .withFilter(_.flags.namespace.isStatic)
+ .map(_.name.name)
+ .sortBy(_.simpleName)
+
+ assertEquals(
+ List(
+ MethodName("bar", List(IntRef), IntRef),
+ MethodName("bar$default$1", Nil, IntRef)
+ ),
+ staticMethodNames
+ )
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsAllObjectsTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsAllObjectsTest.scala
new file mode 100644
index 0000000000..6294d7c7ba
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsAllObjectsTest.scala
@@ -0,0 +1,64 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.scalajs.nscplugin.test.util.VersionDependentUtils.scalaSupportsNoWarn
+
+import org.junit.Assume._
+import org.junit.Test
+
+// scalastyle:off line.size.limit
+
+class StaticForwardersWarningsAllObjectsTest extends DirectTest with TestHelpers {
+
+ override def extraArgs: List[String] =
+ super.extraArgs ::: List("-P:scalajs:genStaticForwardersForNonTopLevelObjects")
+
+ @Test
+ def warnWhenAvoidingStaticForwardersForNonTopLevelObject: Unit = {
+ """
+ object Enclosing {
+ class A
+
+ object a {
+ def foo(x: Int): Int = x + 1
+ }
+ }
+ """ hasWarns
+ """
+ |newSource1.scala:5: warning: Not generating the static forwarders of Enclosing$a because its name differs only in case from the name of another class or trait in this compilation unit.
+ | object a {
+ | ^
+ """
+ }
+
+ @Test
+ def noWarnIfSelectivelyDisabled: Unit = {
+ assumeTrue(scalaSupportsNoWarn)
+
+ """
+ import scala.annotation.nowarn
+
+ object Enclosing {
+ class A
+
+ @nowarn("cat=other")
+ object a {
+ def foo(x: Int): Int = x + 1
+ }
+ }
+ """.hasNoWarns()
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsTopLevelOnlyTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsTopLevelOnlyTest.scala
new file mode 100644
index 0000000000..1b2438655e
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsTopLevelOnlyTest.scala
@@ -0,0 +1,74 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test
+
+import org.scalajs.nscplugin.test.util._
+import org.scalajs.nscplugin.test.util.VersionDependentUtils._
+
+import org.junit.Assume._
+import org.junit.Test
+
+// scalastyle:off line.size.limit
+
+class StaticForwardersWarningsTopLevelOnlyTest extends DirectTest with TestHelpers {
+
+ @Test
+ def warnWhenAvoidingStaticForwardersForTopLevelObject: Unit = {
+ """
+ class A
+
+ object a {
+ def foo(x: Int): Int = x + 1
+ }
+ """ hasWarns
+ s"""
+ |newSource1.scala:4: warning: Not generating the static forwarders of a because its name differs only in case from the name of another class or trait in this compilation unit.
+ | object a {
+ | ^
+ |newSource1.scala:4: warning: Generated class a differs only in case from A.
+ | Such classes will overwrite one another on case-insensitive filesystems.
+ | object a {
+ | ^
+ """
+ }
+
+ @Test
+ def noWarnIfSelectivelyDisabled: Unit = {
+ assumeTrue(scalaSupportsNoWarn)
+
+ """
+ import scala.annotation.nowarn
+
+ class A
+
+ @nowarn("cat=other")
+ object a {
+ def foo(x: Int): Int = x + 1
+ }
+ """.hasNoWarns()
+ }
+
+ @Test
+ def noWarnForNonTopLevelObject: Unit = {
+ """
+ object Enclosing {
+ class A
+
+ object a {
+ def foo(x: Int): Int = x + 1
+ }
+ }
+ """.hasNoWarns()
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/DirectTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/DirectTest.scala
new file mode 100644
index 0000000000..5fd603c286
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/DirectTest.scala
@@ -0,0 +1,121 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test.util
+
+import scala.tools.nsc._
+import scala.tools.nsc.plugins.Plugin
+import scala.tools.nsc.reporters.ConsoleReporter
+
+import scala.reflect.internal.util.{SourceFile, BatchSourceFile}
+
+import org.scalajs.nscplugin.ScalaJSPlugin
+
+import scala.collection.mutable
+
+import java.io.File
+
+/** This is heavily inspired by scala's partest suite's DirectTest */
+abstract class DirectTest {
+
+ /** these arguments are always added to the args passed to newSettings */
+ def extraArgs: List[String] = Nil
+
+ /** create settings objects for test from arg string */
+ def newSettings(args: List[String]): Settings = {
+ val s = new Settings
+ s.processArguments(args, true)
+ s
+ }
+
+ def newScalaJSCompiler(args: String*): Global = {
+ val settings0 = newSettings(
+ List(
+ "-d", testOutputPath,
+ "-bootclasspath", scalaLibPath,
+ "-classpath", classpath.mkString(File.pathSeparator)) ++
+ extraArgs ++ args.toList)
+
+ lazy val global: Global = new Global(settings0, newReporter(settings0)) {
+ private implicit class PluginCompat(val plugin: Plugin) {
+ def options: List[String] = {
+ val prefix = plugin.name + ":"
+ for {
+ option <- settings.pluginOptions.value
+ if option.startsWith(prefix)
+ } yield {
+ option.stripPrefix(prefix)
+ }
+ }
+ }
+
+ override lazy val plugins = {
+ val scalaJSPlugin = newScalaJSPlugin(global)
+ scalaJSPlugin.init(scalaJSPlugin.options,
+ msg => throw new IllegalArgumentException(msg))
+ scalaJSPlugin :: Nil
+ }
+ }
+
+ global
+ }
+
+ def newScalaJSPlugin(global: Global): ScalaJSPlugin =
+ new ScalaJSPlugin(global)
+
+ def newReporter(settings: Settings): ConsoleReporter =
+ new ConsoleReporter(settings)
+
+ private def newSources(codes: String*) = codes.toList.zipWithIndex map {
+ case (src, idx) => new BatchSourceFile(s"newSource${idx + 1}.scala", src)
+ }
+
+ def withRun[T](global: Global)(f: global.Run => T): T = {
+ global.reporter.reset()
+ f(new global.Run)
+ }
+
+ def compileSources(global: Global)(sources: SourceFile*): Boolean = {
+ withRun(global)(_ compileSources sources.toList)
+ !global.reporter.hasErrors
+ }
+
+ def compileString(global: Global)(sourceCode: String): Boolean =
+ compileSources(global)(newSources(sourceCode): _*)
+
+ def compileString(sourceCode: String): Boolean =
+ compileString(defaultGlobal)(sourceCode)
+
+ // Cannot reuse global, otherwise the compiler crashes on the following tests:
+ // - org.scalajs.nscplugin.test.JSExportTest
+ // - org.scalajs.nscplugin.test.JSDynamicLiteralTest
+ // Filed as #1443
+ def defaultGlobal: Global = newScalaJSCompiler()
+
+ def testOutputPath: String = {
+ val baseDir = System.getProperty("scala.scalajs.compiler.test.output")
+ val outDir = new File(baseDir, getClass.getName)
+ outDir.mkdirs()
+ outDir.getAbsolutePath
+ }
+
+ def scalaJSLibPath: String =
+ System.getProperty("scala.scalajs.compiler.test.scalajslib")
+
+ def scalaLibPath: String =
+ System.getProperty("scala.scalajs.compiler.test.scalalib")
+
+ def scalaReflectPath: String =
+ System.getProperty("scala.scalajs.compiler.test.scalareflect")
+
+ def classpath: List[String] = List(scalaJSLibPath)
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/JSASTTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/JSASTTest.scala
new file mode 100644
index 0000000000..5a74a397fb
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/JSASTTest.scala
@@ -0,0 +1,196 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test.util
+
+import language.implicitConversions
+
+import scala.tools.nsc._
+import scala.reflect.internal.util.SourceFile
+
+import scala.collection.mutable
+import scala.util.control.ControlThrowable
+
+import org.junit.Assert._
+
+import org.scalajs.nscplugin.ScalaJSPlugin
+import org.scalajs.ir
+import ir.{Trees => js}
+
+abstract class JSASTTest extends DirectTest {
+
+ object SMN {
+ def unapply(ident: js.MethodIdent): Some[String] =
+ Some(ident.name.simpleName.nameString)
+ }
+
+ class JSAST(val clDefs: List[js.ClassDef]) {
+ type Pat = PartialFunction[js.IRNode, Unit]
+
+ class PFTraverser(pf: Pat) extends ir.Traversers.Traverser {
+ private case object Found extends ControlThrowable
+
+ private[this] var finding = false
+
+ def find: Boolean = {
+ finding = true
+ try {
+ clDefs.map(traverseClassDef)
+ false
+ } catch {
+ case Found => true
+ }
+ }
+
+ def traverse(): Unit = {
+ finding = false
+ clDefs.map(traverseClassDef)
+ }
+
+ override def traverse(tree: js.Tree): Unit = {
+ handle(tree)
+ super.traverse(tree)
+ }
+
+ override def traverseClassDef(classDef: js.ClassDef): Unit = {
+ handle(classDef)
+ super.traverseClassDef(classDef)
+ }
+
+ override def traverseAnyFieldDef(fieldDef: js.AnyFieldDef): Unit = {
+ handle(fieldDef)
+ super.traverseAnyFieldDef(fieldDef)
+ }
+
+ override def traverseMethodDef(methodDef: js.MethodDef): Unit = {
+ handle(methodDef)
+ super.traverseMethodDef(methodDef)
+ }
+
+ override def traverseJSConstructorDef(jsConstructor: js.JSConstructorDef): Unit = {
+ handle(jsConstructor)
+ super.traverseJSConstructorDef(jsConstructor)
+ }
+
+ override def traverseJSMethodPropDef(jsMethodPropDef: js.JSMethodPropDef): Unit = {
+ handle(jsMethodPropDef)
+ super.traverseJSMethodPropDef(jsMethodPropDef)
+ }
+
+ override def traverseTopLevelExportDef(
+ exportDef: js.TopLevelExportDef): Unit = {
+ handle(exportDef)
+ super.traverseTopLevelExportDef(exportDef)
+ }
+
+ private def handle(node: js.IRNode): Unit = {
+ if (finding) {
+ if (pf.isDefinedAt(node))
+ throw Found
+ } else {
+ pf.lift(node)
+ }
+ }
+ }
+
+ def has(trgName: String)(pf: Pat): this.type = {
+ val tr = new PFTraverser(pf)
+ if (!tr.find)
+ fail(s"AST should have $trgName but was\n$show")
+ this
+ }
+
+ def hasNot(trgName: String)(pf: Pat): this.type = {
+ val tr = new PFTraverser(pf)
+ if (tr.find)
+ fail(s"AST should not have $trgName but was\n$show")
+ this
+ }
+
+ def hasExactly(count: Int, trgName: String)(pf: Pat): this.type = {
+ var actualCount = 0
+ val tr = new PFTraverser(pf.andThen(_ => actualCount += 1))
+ tr.traverse()
+ if (actualCount != count)
+ fail(s"AST has $actualCount $trgName but expected $count; it was\n$show")
+ this
+ }
+
+ def extractOne[A](trgName: String)(pf: PartialFunction[js.IRNode, A]): A = {
+ var result: Option[A] = None
+ val tr = new PFTraverser(pf.andThen { r =>
+ if (result.isDefined)
+ fail(s"AST has more than one $trgName")
+ result = Some(r)
+ })
+ tr.traverse()
+ result.getOrElse {
+ fail(s"AST should have a $trgName")
+ throw new AssertionError("unreachable")
+ }
+ }
+
+ def traverse(pf: Pat): this.type = {
+ val tr = new PFTraverser(pf)
+ tr.traverse()
+ this
+ }
+
+ def show: String =
+ clDefs.map(_.show).mkString("\n")
+
+ }
+
+ implicit def string2ast(str: String): JSAST = stringAST(str)
+
+ private var generatedClassDefs: Option[mutable.ListBuffer[js.ClassDef]] = None
+
+ private def captureGeneratedClassDefs(body: => Unit): JSAST = {
+ if (generatedClassDefs.isDefined)
+ throw new IllegalStateException(s"Nested or concurrent calls to captureGeneratedClassDefs")
+
+ val buffer = new mutable.ListBuffer[js.ClassDef]
+ generatedClassDefs = Some(buffer)
+ try {
+ body
+ new JSAST(buffer.toList)
+ } finally {
+ generatedClassDefs = None
+ }
+ }
+
+ override def newScalaJSPlugin(global: Global): ScalaJSPlugin = {
+ new ScalaJSPlugin(global) {
+ override def generatedJSAST(cld: js.ClassDef): Unit = {
+ for (buffer <- generatedClassDefs)
+ buffer += cld
+ }
+ }
+ }
+
+ def stringAST(code: String): JSAST = stringAST(defaultGlobal)(code)
+ def stringAST(global: Global)(code: String): JSAST = {
+ captureGeneratedClassDefs {
+ if (!compileString(global)(code))
+ throw new IllegalArgumentException("snippet did not compile")
+ }
+ }
+
+ def sourceAST(source: SourceFile): JSAST = sourceAST(defaultGlobal)(source)
+ def sourceAST(global: Global)(source: SourceFile): JSAST = {
+ captureGeneratedClassDefs {
+ if (!compileSources(global)(source))
+ throw new IllegalArgumentException("snippet did not compile")
+ }
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/TestHelpers.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/TestHelpers.scala
new file mode 100644
index 0000000000..112b9aad99
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/TestHelpers.scala
@@ -0,0 +1,88 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test.util
+
+import java.io._
+
+import scala.tools.nsc._
+import scala.tools.nsc.reporters.ConsoleReporter
+
+import org.junit.Assert._
+
+trait TestHelpers extends DirectTest {
+
+ private[this] val errBuffer = new CharArrayWriter
+
+ override def newReporter(settings: Settings): ConsoleReporter = {
+ val in = new BufferedReader(new StringReader(""))
+ val out = new PrintWriter(errBuffer)
+ new ConsoleReporter(settings, in, out)
+ }
+
+ /** will be prefixed to every code that is compiled. use for imports */
+ def preamble: String = ""
+
+ /** pimps a string to compile it and apply the specified test */
+ implicit class CompileTests(val code: String) {
+ private lazy val (success, output) = {
+ errBuffer.reset()
+ val success = compileString(preamble + code)
+ val output = errBuffer.toString.replaceAll("\r\n?", "\n").trim
+ (success, output)
+ }
+
+ def hasErrors(expected: String): Unit = {
+ assertFalse("snippet shouldn't compile", success)
+ assertEquals("should have right errors", expected.stripMargin.trim, output)
+ }
+
+ def containsErrors(expected: String): Unit = {
+ assertFalse("snippet shouldn't compile", success)
+ assertTrue("should have right errors",
+ output.contains(expected.stripMargin.trim))
+ }
+
+ def hasWarns(expected: String): Unit = {
+ assertTrue("snippet should compile\n" + output, success)
+ assertEquals("should have right warnings", expected.stripMargin.trim, output)
+ }
+
+ def containsWarns(expected: String): Unit = {
+ assertTrue("snippet should compile\n" + output, success)
+ assertTrue("should contain the right warnings",
+ output.contains(expected.stripMargin.trim))
+ }
+
+ def hasNoWarns(): Unit = {
+ assertTrue("snippet should compile\n" + output, success)
+ assertTrue("should not have warnings\n" + output, output.isEmpty)
+ }
+
+ def fails(): Unit =
+ assertFalse("snippet shouldn't compile", success)
+
+ def warns(): Unit = {
+ assertTrue("snippet should compile\n" + output, success)
+ assertFalse("should have warnings", output.isEmpty)
+ }
+
+ def succeeds(): Unit =
+ assertTrue("snippet should compile\n" + output, success)
+ }
+
+ implicit class CodeWrappers(sc: StringContext) {
+ def expr(): CompileTests =
+ new CompileTests(s"class A { ${sc.parts.mkString} }")
+ }
+
+}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/VersionDependentUtils.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/VersionDependentUtils.scala
new file mode 100644
index 0000000000..7e52a1edf8
--- /dev/null
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/VersionDependentUtils.scala
@@ -0,0 +1,26 @@
+/*
+ * Scala.js (https://www.scala-js.org/)
+ *
+ * Copyright EPFL.
+ *
+ * Licensed under Apache License 2.0
+ * (https://www.apache.org/licenses/LICENSE-2.0).
+ *
+ * See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ */
+
+package org.scalajs.nscplugin.test.util
+
+object VersionDependentUtils {
+ val scalaVersion = scala.util.Properties.versionNumberString
+
+ private val isScala212 = scalaVersion.startsWith("2.12.")
+
+ /** Does the current Scala version support the `@nowarn` annotation? */
+ val scalaSupportsNoWarn = !isScala212
+
+ def methodSig(params: String, resultType: String): String =
+ if (!isScala212) params + ": " + resultType
+ else params + resultType
+}
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/DiverseErrorsTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/DiverseErrorsTest.scala
deleted file mode 100644
index d4c7416f23..0000000000
--- a/compiler/src/test/scala/scala/scalajs/compiler/test/DiverseErrorsTest.scala
+++ /dev/null
@@ -1,49 +0,0 @@
-package scala.scalajs.compiler.test
-
-import scala.scalajs.compiler.test.util._
-import org.junit.Test
-
-class DiverseErrorsTest extends DirectTest with TestHelpers {
-
- override def preamble =
- """import scala.scalajs.js
- """
-
- @Test
- def noIsInstanceOnJSRaw = {
-
- """
- trait JSRaw extends js.Object
-
- class A {
- val a: AnyRef = "asdf"
- def x = a.isInstanceOf[JSRaw]
- }
- """ hasErrors
- """
- |newSource1.scala:7: error: isInstanceOf[JSRaw] not supported because it is a raw JS trait
- | def x = a.isInstanceOf[JSRaw]
- | ^
- """
-
- }
-
- @Test
- def noReflCallOnUndefinedObjectFun = {
-
- """
- class A {
- type Waitable = Any { def wait(): Unit }
- def foo(obj: Waitable) = obj.wait()
- }
- """ hasErrors
- """
- |newSource1.scala:5: error: You tried to call wait on AnyRef reflectively, but this
- |method does not make sense in Scala.js. You may not call it
- | def foo(obj: Waitable) = obj.wait()
- | ^
- """
-
- }
-
-}
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/EnumerationInteropTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/EnumerationInteropTest.scala
deleted file mode 100644
index e186cf4553..0000000000
--- a/compiler/src/test/scala/scala/scalajs/compiler/test/EnumerationInteropTest.scala
+++ /dev/null
@@ -1,135 +0,0 @@
-package scala.scalajs.compiler.test
-
-import scala.scalajs.compiler.test.util._
-
-import org.junit.Test
-
-class EnumerationInteropTest extends DirectTest with TestHelpers {
-
- @Test
- def warnIfUnableToTransformValue = {
-
- """
- class A extends Enumeration {
- val a = {
- println("oh, oh!")
- Value
- }
- val b = {
- println("oh, oh!")
- Value(4)
- }
- }
- """ hasWarns
- """
- |newSource1.scala:5: warning: Couldn't transform call to Enumeration.Value.
- |The resulting program is unlikely to function properly as this
- |operation requires reflection.
- | Value
- | ^
- |newSource1.scala:9: warning: Couldn't transform call to Enumeration.Value.
- |The resulting program is unlikely to function properly as this
- |operation requires reflection.
- | Value(4)
- | ^
- """
-
- }
-
- @Test
- def warnIfNoNameVal = {
-
- """
- class A extends Enumeration {
- val a = new Val
- val b = new Val(10)
- }
- """ hasWarns
- """
- |newSource1.scala:3: warning: Calls to the non-string constructors of Enumeration.Val
- |require reflection at runtime. The resulting
- |program is unlikely to function properly.
- | val a = new Val
- | ^
- |newSource1.scala:4: warning: Calls to the non-string constructors of Enumeration.Val
- |require reflection at runtime. The resulting
- |program is unlikely to function properly.
- | val b = new Val(10)
- | ^
- """
-
- }
-
- @Test
- def warnIfNullValue = {
-
- """
- class A extends Enumeration {
- val a = Value(null)
- val b = Value(10, null)
- }
- """ hasWarns
- """
- |newSource1.scala:3: warning: Passing null as name to Enumeration.Value
- |requires reflection at runtime. The resulting
- |program is unlikely to function properly.
- | val a = Value(null)
- | ^
- |newSource1.scala:4: warning: Passing null as name to Enumeration.Value
- |requires reflection at runtime. The resulting
- |program is unlikely to function properly.
- | val b = Value(10, null)
- | ^
- """
-
- }
-
- @Test
- def warnIfNullNewVal = {
-
- """
- class A extends Enumeration {
- val a = new Val(null)
- val b = new Val(10, null)
- }
- """ hasWarns
- """
- |newSource1.scala:3: warning: Passing null as name to a constructor of Enumeration.Val
- |requires reflection at runtime. The resulting
- |program is unlikely to function properly.
- | val a = new Val(null)
- | ^
- |newSource1.scala:4: warning: Passing null as name to a constructor of Enumeration.Val
- |requires reflection at runtime. The resulting
- |program is unlikely to function properly.
- | val b = new Val(10, null)
- | ^
- """
-
- }
-
- @Test
- def warnIfExtNoNameVal = {
-
- """
- class A extends Enumeration {
- protected class Val1 extends Val
- protected class Val2 extends Val(1)
- }
- """ warns() // no message checking: position differs in 2.10 and 2.11
-
- }
-
- @Test
- def warnIfExtNullNameVal = {
-
- """
- class A extends Enumeration {
- protected class Val1 extends Val(null)
- protected class Val2 extends Val(1,null)
- }
- """ warns() // no message checking: position differs in 2.10 and 2.11
-
- }
-
-}
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/JSDynamicLiteralTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/JSDynamicLiteralTest.scala
deleted file mode 100644
index bc1a1b48dc..0000000000
--- a/compiler/src/test/scala/scala/scalajs/compiler/test/JSDynamicLiteralTest.scala
+++ /dev/null
@@ -1,102 +0,0 @@
-package scala.scalajs.compiler.test
-
-import scala.scalajs.compiler.test.util._
-import org.junit.Test
-
-class JSDynamicLiteralTest extends DirectTest with TestHelpers {
-
- override def preamble =
- """import scala.scalajs.js.Dynamic.{ literal => lit }
- """
-
- @Test
- def callApplyOnly = {
-
- // selectDynamic (with any name)
- expr"""
- lit.helloWorld
- """.fails() // Scala error, no string checking due to versions
-
- // applyDynamicNamed with wrong method name
- expr"""
- lit.helloWorld(a = "a")
- """ hasErrors
- """
- |newSource1.scala:3: error: js.Dynamic.literal does not have a method named helloWorld
- | lit.helloWorld(a = "a")
- | ^
- """
-
- // applyDynamic with wrong method name
- expr"""
- lit.helloWorld("a" -> "a")
- """ hasErrors
- """
- |newSource1.scala:3: error: js.Dynamic.literal does not have a method named helloWorld
- | lit.helloWorld("a" -> "a")
- | ^
- """
-
- }
-
- @Test
- def goodTypesOnly = {
-
- // Bad value type (applyDynamic)
- """
- class A {
- val x = new Object()
- def foo = lit("a" -> x)
- }
- """.fails()
-
- // Bad key type (applyDynamic)
- """
- class A {
- val x = Seq()
- def foo = lit(x -> "a")
- }
- """.fails()
-
- // Bad value type (applyDynamicNamed)
- """
- class A {
- val x = new Object()
- def foo = lit(a = x)
- }
- """.fails()
-
- }
-
- @Test
- def noNonLiteralMethodName = {
-
- // applyDynamicNamed
- """
- class A {
- val x = "string"
- def foo = lit.applyDynamicNamed(x)()
- }
- """ hasErrors
- """
- |newSource1.scala:5: error: js.Dynamic.literal.applyDynamicNamed may not be called directly
- | def foo = lit.applyDynamicNamed(x)()
- | ^
- """
-
- // applyDynamic
- """
- class A {
- val x = "string"
- def foo = lit.applyDynamic(x)()
- }
- """ hasErrors
- """
- |newSource1.scala:5: error: js.Dynamic.literal.applyDynamic may not be called directly
- | def foo = lit.applyDynamic(x)()
- | ^
- """
-
- }
-
-}
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportASTTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportASTTest.scala
deleted file mode 100644
index 36041c655a..0000000000
--- a/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportASTTest.scala
+++ /dev/null
@@ -1,38 +0,0 @@
-package scala.scalajs.compiler.test
-
-import util._
-
-import org.junit.Test
-import org.junit.Assert._
-
-import scala.scalajs.ir.{Trees => js}
-
-class JSExportASTTest extends JSASTTest {
-
- @Test
- def inheritExportMethods: Unit = {
-
- var props = 0
-
- """
- import scala.scalajs.js.annotation.JSExport
-
- class A {
- @JSExport
- def foo = 1
- }
-
- class B extends A {
- @JSExport
- override def foo = 2
- }
- """.traverse {
- case js.PropertyDef(js.StringLiteral("foo", _), _, _, _) =>
- props += 1
- }
-
- assertEquals("Only define the property `foo` once", props, 1)
-
- }
-
-}
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportTest.scala
deleted file mode 100644
index 648945d369..0000000000
--- a/compiler/src/test/scala/scala/scalajs/compiler/test/JSExportTest.scala
+++ /dev/null
@@ -1,513 +0,0 @@
-package scala.scalajs.compiler.test
-
-import scala.scalajs.compiler.test.util._
-import org.junit.Test
-import org.junit.Ignore
-
-class JSExportTest extends DirectTest with TestHelpers {
-
- override def preamble =
- """import scala.scalajs.js.annotation.{JSExport, JSExportDescendentObjects}
- """
-
- @Test
- def noDoubleUnderscoreExport = {
- // Normal exports
- """
- class A {
- @JSExport(name = "__")
- def foo = 1
-
- @JSExport
- def bar__(x: Int) = x
- }
-
- @JSExport
- class B__
- """ hasErrors
- """
- |newSource1.scala:4: error: An exported name may not contain a double underscore (`__`)
- | @JSExport(name = "__")
- | ^
- |newSource1.scala:8: error: An exported name may not contain a double underscore (`__`)
- | def bar__(x: Int) = x
- | ^
- |newSource1.scala:12: error: An exported name may not contain a double underscore (`__`)
- | class B__
- | ^
- """
-
- // Inherited exports
- """
- @JSExportDescendentObjects
- trait A
-
- package fo__o {
- object B extends A
- }
- """ hasErrors
- """
- |newSource1.scala:7: error: B may not have a double underscore (`__`) in its fully qualified
- |name, since it is forced to be exported by a @JSExportDescendentObjects on trait A
- | object B extends A
- | ^
- """
- }
-
- @Test
- def noConflictingExport = {
- """
- class Confl {
- @JSExport("value")
- def hello = "foo"
-
- @JSExport("value")
- def world = "bar"
- }
- """ fails() // No error test, Scala version dependent error messages
-
- """
- class Confl {
- class Box[T](val x: T)
-
- @JSExport
- def ub(x: Box[String]): String = x.x
- @JSExport
- def ub(x: Box[Int]): Int = x.x
- }
- """ fails() // No error test, Scala version dependent error messages
-
- """
- class Confl {
- @JSExport
- def rtType(x: Float) = x
-
- @JSExport
- def rtType(x: Double) = x
- }
- """ hasErrors
- """
- |newSource1.scala:7: error: Cannot disambiguate overloads for exported method $js$exported$meth$rtType with types
- | (x: Double)Object
- | (x: Float)Object
- | @JSExport
- | ^
- """
-
- """
- class Confl {
- @JSExport
- def foo(x: Int)(ys: Int*) = x
-
- @JSExport
- def foo(x: Int*) = x
- }
- """ hasErrors
- """
- |newSource1.scala:7: error: Cannot disambiguate overloads for exported method $js$exported$meth$foo with types
- | (x: Seq)Object
- | (x: Int, ys: Seq)Object
- | @JSExport
- | ^
- """
-
- """
- class Confl {
- @JSExport
- def foo(x: Int = 1) = x
- @JSExport
- def foo(x: String*) = x
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: Cannot disambiguate overloads for exported method $js$exported$meth$foo with types
- | (x: Int)Object
- | (x: Seq)Object
- | @JSExport
- | ^
- """
-
- """
- class Confl {
- @JSExport
- def foo(x: Float, y: String)(z: Int = 1) = x
- @JSExport
- def foo(x: Double, y: String)(z: String*) = x
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: Cannot disambiguate overloads for exported method $js$exported$meth$foo with types
- | (x: Float, y: String, z: Int)Object
- | (x: Double, y: String, z: Seq)Object
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noAnyValReturn = {
- """
- class AnyValRet {
- @JSExport
- def anyVal: AnyVal = 1
-
- @JSExport
- def badGen[T](x: T) = x
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: You may not export a method whose return type is neither a subtype of
- |AnyRef nor a concrete subtype of AnyVal (i.e. a value class or a
- |primitive value type).
- | @JSExport
- | ^
- |newSource1.scala:7: error: You may not export a method whose return type is neither a subtype of
- |AnyRef nor a concrete subtype of AnyVal (i.e. a value class or a
- |primitive value type).
- | @JSExport
- | ^
- """
- }
-
- @Test
- def noExportLocal = {
- // Local class
- """
- class A {
- def method = {
- @JSExport
- class A
- }
- }
- """ hasErrors
- """
- |newSource1.scala:5: error: You may not export a local class
- | @JSExport
- | ^
- """
-
- // Local object
- """
- class A {
- def method = {
- @JSExport
- object A
- }
- }
- """ hasErrors
- """
- |newSource1.scala:5: error: You may not export a local object
- | @JSExport
- | ^
- """
-
- // Local method
- """
- class A {
- def method = {
- @JSExport
- def foo = 1
- }
- }
- """ hasErrors
- """
- |newSource1.scala:5: error: You may not export a local definition
- | @JSExport
- | ^
- """
-
- // Local val
- """
- class A {
- def method = {
- @JSExport
- val x = 1
- }
- }
- """ hasErrors
- """
- |newSource1.scala:5: error: You may not export a local definition
- | @JSExport
- | ^
- """
-
- // Local var
- """
- class A {
- def method = {
- @JSExport
- var x = 1
- }
- }
- """ hasErrors
- """
- |newSource1.scala:5: error: You may not export a local definition
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noMiddleVarArg = {
-
- """
- class A {
- @JSExport
- def method(xs: Int*)(ys: String) = 1
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: In an exported method, a *-parameter must come last (through all parameter lists)
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noMiddleDefaultParam = {
-
- """
- class A {
- @JSExport
- def method(x: Int = 1)(y: String) = 1
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: In an exported method, all parameters with defaults must be at the end
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noExportTrait = {
-
- """
- @JSExport
- trait Test
- """ hasErrors
- """
- |newSource1.scala:3: error: You may not export a trait
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noExportNonPublicClassOrObject = {
-
- """
- @JSExport
- private class A
-
- @JSExport
- protected class B
- """ hasErrors
- """
- |newSource1.scala:3: error: You may not export a non-public class
- | @JSExport
- | ^
- |newSource1.scala:6: error: You may not export a non-public class
- | @JSExport
- | ^
- """
-
- """
- @JSExport
- private object A
-
- @JSExport
- protected object B
- """ hasErrors
- """
- |newSource1.scala:3: error: You may not export an non-public object
- | @JSExport
- | ^
- |newSource1.scala:6: error: You may not export an non-public object
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noExportNonPublicMember = {
-
- """
- class A {
- @JSExport
- private def foo = 1
-
- @JSExport
- protected def bar = 2
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: You may not export a non-public method
- | @JSExport
- | ^
- |newSource1.scala:7: error: You may not export a non-public method
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noExportNestedClass = {
-
- """
- class A {
- @JSExport
- class Nested
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: You may not export a nested class. Create an exported factory method in the outer class to work around this limitation.
- | @JSExport
- | ^
- """
-
- """
- object A {
- @JSExport
- class Nested
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: You may not export a nested class. Create an exported factory method in the outer class to work around this limitation.
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noExportNestedObject = {
-
- """
- class A {
- @JSExport
- object Nested
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: You may not export a nested object
- | @JSExport
- | ^
- """
-
- """
- object A {
- @JSExport
- object Nested
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: You may not export a nested object
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noExportJSRaw = {
-
- """
- import scala.scalajs.js
-
- @JSExport
- object A extends js.Object
- """ hasErrors
- """
- |newSource1.scala:5: error: You may not export a class extending js.Any
- | @JSExport
- | ^
- """
-
- """
- import scala.scalajs.js
-
- @JSExport
- class A extends js.Object
- """ hasErrors
- """
- |newSource1.scala:5: error: You may not export a constructor of a subclass of js.Any
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noExportJSRawMember = {
-
- """
- import scala.scalajs.js
-
- class A extends js.Object {
- @JSExport
- def foo = 1
- }
- """ hasErrors
- """
- |newSource1.scala:6: error: You may not export a method of a subclass of js.Any
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noBadSetterType = {
-
- // Bad param list
- """
- class A {
- @JSExport
- def foo_=(x: Int, y: Int) = ()
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: A method ending in _= will be exported as setter. But foo_= does not have the right signature to do so (single argument, unit return type).
- | @JSExport
- | ^
- """
-
- // Bad return type
- """
- class A {
- @JSExport
- def foo_=(x: Int) = "string"
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: A method ending in _= will be exported as setter. But foo_= does not have the right signature to do so (single argument, unit return type).
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def gracefulDoubleDefaultFail = {
- // This used to blow up (i.e. not just fail), because PrepJSExports asked
- // for the symbol of the default parameter getter of [[y]], and asserted its
- // not overloaded. Since the Scala compiler only fails later on this, the
- // assert got triggered and made the compiler crash
- """
- class A {
- @JSExport
- def foo(x: String, y: String = "hello") = x
- def foo(x: Int, y: String = "bar") = x
- }
- """ fails()
- }
-
-}
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/JSInteropTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/JSInteropTest.scala
deleted file mode 100644
index 1a53322b39..0000000000
--- a/compiler/src/test/scala/scala/scalajs/compiler/test/JSInteropTest.scala
+++ /dev/null
@@ -1,159 +0,0 @@
-package scala.scalajs.compiler.test
-
-import scala.scalajs.compiler.test.util._
-
-import org.junit.Test
-import org.junit.Ignore
-
-class JSInteropTest extends DirectTest with TestHelpers {
-
- override def preamble =
- """import scala.scalajs.js
- """
-
- @Test
- def noInnerClassTraitObject: Unit = {
-
- val objs = List("class", "trait", "object")
-
- for {
- outer <- objs
- inner <- objs
- } yield {
- s"""
- $outer A extends js.Object {
- $inner A
- }
- """ hasErrors
- s"""
- |newSource1.scala:4: error: Traits, classes and objects extending js.Any may not have inner traits, classes or objects
- | $inner A
- | ${" " * inner.length}^
- """
- }
-
- }
-
- @Test
- def noBadSetters = {
-
- """
- class A extends js.Object {
- def foo_=(x: Int) = x
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: Setters that do not return Unit are not allowed in types extending js.Any
- | def foo_=(x: Int) = x
- | ^
- """
-
- }
-
- @Test
- def onlyJSRawTraits = {
-
- """
- trait A
- class B extends js.Object with A
- """ hasErrors
- """
- |newSource1.scala:4: error: B extends A which does not extend js.Any.
- | class B extends js.Object with A
- | ^
- """
-
- }
-
- @Test
- def noAnonymousClass = {
-
- """
- class A {
- val x = new js.Object {
- def a: Int = ???
- }
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: Anonymous classes may not extend js.Any
- | val x = new js.Object {
- | ^
- """
-
- }
-
- @Test
- def noCaseClassObject = {
-
- """
- case class A(x: Int) extends js.Object
- """ hasErrors
- """
- |newSource1.scala:3: error: Classes and objects extending js.Any may not have a case modifier
- | case class A(x: Int) extends js.Object
- | ^
- """
-
- """
- case object B extends js.Object
- """ hasErrors
- """
- |newSource1.scala:3: error: Classes and objects extending js.Any may not have a case modifier
- | case object B extends js.Object
- | ^
- """
-
- }
-
- @Test
- def notNested: Unit = {
-
- val outers = List("class", "trait")
- val inners = List("trait", "class", "object")
-
- for {
- outer <- outers
- inner <- inners
- } yield {
-
- val errTrg = if (inner == "object") "Objects" else "Classes"
-
- s"""
- $outer A {
- $inner Inner extends js.Object
- }
- """ hasErrors
- s"""
- |newSource1.scala:4: error: $errTrg extending js.Any may not be defined inside a class or trait
- | $inner Inner extends js.Object
- | ${" " * inner.length}^
- """
- }
-
- }
-
- @Test
- def noGlobalScopeClass = {
-
- """
- class A extends js.GlobalScope
- """ hasErrors
- """
- |newSource1.scala:3: error: Only objects may extend js.GlobalScope
- | class A extends js.GlobalScope
- | ^
- """
-
- """
- trait A extends js.GlobalScope
- """ hasErrors
- """
- |newSource1.scala:3: error: Only objects may extend js.GlobalScope
- | trait A extends js.GlobalScope
- | ^
- """
-
- }
-
-}
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/OptimizationTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/OptimizationTest.scala
deleted file mode 100644
index 8e43472709..0000000000
--- a/compiler/src/test/scala/scala/scalajs/compiler/test/OptimizationTest.scala
+++ /dev/null
@@ -1,41 +0,0 @@
-package scala.scalajs.compiler.test
-
-import util._
-
-import org.junit.Test
-
-import scala.scalajs.ir.{Trees => js, Types => jstpe}
-
-class OptimizationTest extends JSASTTest {
-
- @Test
- def unwrapScalaFunWrapper: Unit = {
-
- // Make sure we do not wrap and unwrap right away
- """
- import scala.scalajs.js
-
- class A {
- val jsFun: js.Function = (x: Int) => x * 2
- }
- """.
- hasNot("runtime.AnonFunction ctor") {
- case js.New(jstpe.ClassType("sjsr_AnonFunction1"), _, _) =>
- }
-
- // Make sure our wrapper matcher has the right name
- """
- import scala.scalajs.js
-
- class A {
- val scalaFun = (x: Int) => x * 2
- val jsFun: js.Function = scalaFun
- }
- """.
- has("runtime.AnonFunction ctor") {
- case js.New(jstpe.ClassType("sjsr_AnonFunction1"), _, _) =>
- }
-
- }
-
-}
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/PositionTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/PositionTest.scala
deleted file mode 100644
index e25399b16b..0000000000
--- a/compiler/src/test/scala/scala/scalajs/compiler/test/PositionTest.scala
+++ /dev/null
@@ -1,37 +0,0 @@
-package scala.scalajs.compiler.test
-
-import util.JSASTTest
-
-import org.junit.Test
-import org.junit.Assert._
-
-import scala.reflect.internal.util.BatchSourceFile
-
-import scala.scalajs.ir.{Trees => js}
-
-class PositionTest extends JSASTTest {
-
- @Test
- def virtualFilePosition = {
-
- val name = ""
- val source = new BatchSourceFile(name,
- """class A { def x = 1 }""")
-
- var found = false
- sourceAST(source) traverse {
- case lit: js.IntLiteral =>
- found = true
- assertEquals(
- "Scheme of virtual file URI should be `virtualfile'",
- "virtualfile", lit.pos.source.getScheme)
- assertEquals(
- "Scheme specific part of virtual file URI should be its path",
- name, lit.pos.source.getSchemeSpecificPart)
- }
-
- assertTrue("Should have IntLiteral tree", found)
-
- }
-
-}
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/util/DirectTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/util/DirectTest.scala
deleted file mode 100644
index 8289129838..0000000000
--- a/compiler/src/test/scala/scala/scalajs/compiler/test/util/DirectTest.scala
+++ /dev/null
@@ -1,70 +0,0 @@
-package scala.scalajs.compiler.test.util
-
-import scala.tools.nsc._
-import reporters.{Reporter, ConsoleReporter}
-import scala.reflect.internal.util.{ SourceFile, BatchSourceFile }
-
-import scala.scalajs.compiler.ScalaJSPlugin
-
-import scala.collection.mutable
-
-/** This is heavily inspired by scala's partest suite's DirectTest */
-abstract class DirectTest {
-
- /** these arguments are always added to the args passed to newSettings */
- def extraArgs: List[String] = Nil
-
- /** create settings objects for test from arg string */
- def newSettings(args: List[String]) = {
- val s = new Settings
- s processArguments (args, true)
- s
- }
-
- def newScalaJSCompiler(args: String*): Global = {
- val settings = newSettings(
- List(
- "-d", testOutputPath,
- "-bootclasspath", scalaLibPath,
- "-classpath", scalaJSLibPath) ++
- extraArgs ++ args.toList)
-
- lazy val global: Global = new Global(settings, newReporter(settings)) {
- override lazy val plugins = newScalaJSPlugin(global) :: Nil
- }
-
- global
- }
-
- def newScalaJSPlugin(global: Global): ScalaJSPlugin =
- new ScalaJSPlugin(global)
-
- def newReporter(settings: Settings) = new ConsoleReporter(settings)
-
- def newSources(codes: String*) = codes.toList.zipWithIndex map {
- case (src, idx) => new BatchSourceFile(s"newSource${idx + 1}.scala", src)
- }
-
- def withRun[T](global: Global)(f: global.Run => T): T = {
- global.reporter.reset()
- f(new global.Run)
- }
-
- def compileSources(global: Global)(sources: SourceFile*): Boolean = {
- withRun(global)(_ compileSources sources.toList)
- !global.reporter.hasErrors
- }
-
- def compileString(global: Global)(sourceCode: String): Boolean =
- compileSources(global)(newSources(sourceCode): _*)
-
- def compileString(sourceCode: String): Boolean =
- compileString(defaultGlobal)(sourceCode)
-
- lazy val defaultGlobal = newScalaJSCompiler()
-
- def testOutputPath = sys.props("scala.scalajs.compiler.test.output")
- def scalaJSLibPath = sys.props("scala.scalajs.compiler.test.scalajslib")
- def scalaLibPath = sys.props("scala.scalajs.compiler.test.scalalib")
-
-}
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/util/JSASTTest.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/util/JSASTTest.scala
deleted file mode 100644
index 9355623086..0000000000
--- a/compiler/src/test/scala/scala/scalajs/compiler/test/util/JSASTTest.scala
+++ /dev/null
@@ -1,112 +0,0 @@
-package scala.scalajs.compiler.test.util
-
-import language.implicitConversions
-
-import scala.tools.nsc._
-import scala.reflect.internal.util.SourceFile
-
-import scala.util.control.ControlThrowable
-
-import org.junit.Assert._
-
-import scala.scalajs.compiler.{ScalaJSPlugin, JSTreeExtractors}
-import JSTreeExtractors.jse
-import scala.scalajs.ir
-import ir.{Trees => js}
-
-abstract class JSASTTest extends DirectTest {
-
- private var lastAST: JSAST = _
-
- class JSAST(val clDefs: List[js.Tree]) {
- type Pat = PartialFunction[js.Tree, Unit]
-
- class PFTraverser(pf: Pat) extends ir.Transformers.Transformer {
- private case object Found extends ControlThrowable
-
- private[this] var finding = false
-
- def find: Boolean = {
- finding = true
- try {
- clDefs map transformStat _
- false
- } catch {
- case Found => true
- }
- }
-
- def traverse: Unit = {
- finding = false
- clDefs map transformStat _
- }
-
- private def visit(tr: js.Tree)(after: => js.Tree) = {
- if (finding && pf.isDefinedAt(tr))
- throw Found
-
- if (!finding)
- pf.lift(tr)
-
- after
- }
-
- override def transformStat(tr: js.Tree) = visit(tr) {
- super.transformStat(tr)
- }
-
- override def transformExpr(tr: js.Tree) = visit(tr) {
- super.transformExpr(tr)
- }
-
- override def transformDef(tr: js.Tree) = visit(tr) {
- super.transformDef(tr)
- }
- }
-
- def has(trgName: String)(pf: Pat): this.type = {
- val tr = new PFTraverser(pf)
- assertTrue(s"AST should have $trgName", tr.find)
- this
- }
-
- def hasNot(trgName: String)(pf: Pat): this.type = {
- val tr = new PFTraverser(pf)
- assertFalse(s"AST should not have $trgName", tr.find)
- this
- }
-
- def traverse(pf: Pat): this.type = {
- val tr = new PFTraverser(pf)
- tr.traverse
- this
- }
-
- def show: this.type = {
- clDefs foreach println _
- this
- }
-
- }
-
- implicit def string2ast(str: String): JSAST = stringAST(str)
-
- override def newScalaJSPlugin(global: Global) = new ScalaJSPlugin(global) {
- override def generatedJSAST(cld: List[js.Tree]): Unit = {
- lastAST = new JSAST(cld)
- }
- }
-
- def stringAST(code: String): JSAST = stringAST(defaultGlobal)(code)
- def stringAST(global: Global)(code: String): JSAST = {
- compileString(global)(code)
- lastAST
- }
-
- def sourceAST(source: SourceFile): JSAST = sourceAST(defaultGlobal)(source)
- def sourceAST(global: Global)(source: SourceFile): JSAST = {
- compileSources(global)(source)
- lastAST
- }
-
-}
diff --git a/compiler/src/test/scala/scala/scalajs/compiler/test/util/TestHelpers.scala b/compiler/src/test/scala/scala/scalajs/compiler/test/util/TestHelpers.scala
deleted file mode 100644
index adad89cf67..0000000000
--- a/compiler/src/test/scala/scala/scalajs/compiler/test/util/TestHelpers.scala
+++ /dev/null
@@ -1,68 +0,0 @@
-package scala.scalajs.compiler.test.util
-
-import java.io._
-import scala.tools.nsc._
-
-import reporters.ConsoleReporter
-
-import org.junit.Assert._
-
-import scala.util.matching.Regex
-
-trait TestHelpers extends DirectTest {
-
- private[this] val errBuffer = new CharArrayWriter
-
- override def newReporter(settings: Settings) = {
- val in = new BufferedReader(new StringReader(""))
- val out = new PrintWriter(errBuffer)
- new ConsoleReporter(settings, in, out)
- }
-
- /** will be prefixed to every code that is compiled. use for imports */
- def preamble = ""
-
- /** pimps a string to compile it and apply the specified test */
- implicit class CompileTests(val code: String) {
-
- def hasErrors(expected: String) = {
- val reps = repResult {
- assertFalse("snippet shouldn't compile", compileString(preamble + code))
- }
- assertEquals("should have right errors",
- expected.stripMargin.trim, reps.trim)
- }
-
- def hasWarns(expected: String) = {
- val reps = repResult {
- assertTrue("snippet should compile", compileString(preamble + code))
- }
- assertEquals("should have right warnings",
- expected.stripMargin.trim, reps.trim)
- }
-
- def fails() =
- assertFalse("snippet shouldn't compile", compileString(preamble + code))
-
- def warns() = {
- val reps = repResult {
- assertTrue("snippet should compile", compileString(preamble + code))
- }
- assertFalse("should have warnings", reps.isEmpty)
- }
-
- def succeeds() =
- assertTrue("snippet should compile", compileString(preamble + code))
-
- private def repResult(body: => Unit) = {
- errBuffer.reset()
- body
- errBuffer.toString
- }
- }
-
- implicit class CodeWrappers(sc: StringContext) {
- def expr() = new CompileTests(s"class A { ${sc.parts.mkString} }")
- }
-
-}
diff --git a/examples/helloworld/HelloWorld.scala b/examples/helloworld/HelloWorld.scala
deleted file mode 100644
index 58107e661a..0000000000
--- a/examples/helloworld/HelloWorld.scala
+++ /dev/null
@@ -1,85 +0,0 @@
-/* Scala.js example code
- * Public domain
- * @author Sébastien Doeraene
- */
-
-package helloworld
-
-import scala.scalajs.js
-import js.annotation.{ JSName, JSExport }
-
-object HelloWorld extends js.JSApp {
- def main() {
- if (!(!js.Dynamic.global.document)) {
- sayHelloFromDOM()
- sayHelloFromTypedDOM()
- sayHelloFromJQuery()
- sayHelloFromTypedJQuery()
- } else {
- println("Hello world!")
- }
- }
-
- def sayHelloFromDOM() {
- val document = js.Dynamic.global.document
- val playground = document.getElementById("playground")
-
- val newP = document.createElement("p")
- newP.innerHTML = "Hello world! -- DOM"
- playground.appendChild(newP)
- }
-
- def sayHelloFromTypedDOM() {
- val document = window.document
- val playground = document.getElementById("playground")
-
- val newP = document.createElement("p")
- newP.innerHTML = "Hello world! -- typed DOM"
- playground.appendChild(newP)
- }
-
- def sayHelloFromJQuery() {
- // val $ is fine too, but not very recommended in Scala code
- val jQuery = js.Dynamic.global.jQuery
- val newP = jQuery("