diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
index 1a789cd380..e2f42e5c54 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.md
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -11,6 +11,7 @@ assignees: ''
Please follow these steps to help us fixing the bug:
1. Make sure it is a bug for this repo
+ * Does it only happen with Scala 3.x? If yes, report it to https://github.com/lampepfl/dotty/issues instead
* Can you reproduce the bug with Scala/JVM? If yes, report it to https://github.com/scala/bug instead
* Make sure it is not one of the intended semantic differences: https://www.scala-js.org/doc/semantics.html
* Does the bug involve macros? If yes, make sure to reproduce *without* macros, or file an issue to the relevant macro library instead or ask on Gitter if it's your own macro
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/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml
new file mode 100644
index 0000000000..9b8170126d
--- /dev/null
+++ b/.github/workflows/windows-ci.yml
@@ -0,0 +1,51 @@
+name: Windows CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+env:
+ SBT_OPTS: '-Xmx6g -Xms1g -Xss4m'
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ java: [ '8' ]
+
+ # Use the latest supported version. We will be less affected by ambient changes
+ # due to the lack of pinning than by breakages because of changing version support.
+ runs-on: windows-latest
+
+ steps:
+ - name: Set up git to use LF
+ run: |
+ git config --global core.autocrlf false
+ git config --global core.eol lf
+ - uses: actions/checkout@v3
+ - name: Set up JDK ${{ matrix.java }}
+ uses: coursier/setup-action@v1
+ with:
+ jvm: temurin:1.${{ matrix.java }}
+ apps: sbt
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '24.x'
+ cache: 'npm'
+ - name: npm install
+ run: npm install
+
+ # Very far from testing everything, but at least it is a good sanity check
+ - name: Test suite
+ run: sbt testSuite2_12/test
+ - name: Linker test suite
+ run: sbt linker2_12/test
+ # partest is slow; only execute one test as a smoke test
+ - name: partest smoke test
+ run: sbt "partestSuite2_12/testOnly -- --fastOpt run/option-fold.scala"
+ # Module splitting has some logic for case-insensitive filesystems, which we must test on Windows
+ - name: Test suite with module splitting
+ run: sbt 'set testSuite.v2_12/scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.ESModule).withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("org.scalajs.testsuite"))))' testSuite2_12/test
+ shell: bash # for the special characters in the command
diff --git a/.gitignore b/.gitignore
index ab33f36db9..5c978cc5cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +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/
-/partest/fetchedSources/
-/cli/pack/
/.idea/
/.idea_modules/
bin/
+/.bloop/
+/.metals/
+/project/**/metals.sbt
+/.vscode/
+
+# User specific
+/.jvmopts
+/.sbtopts
diff --git a/CODINGSTYLE.md b/CODINGSTYLE.md
index a3f8567203..eb79ec4986 100644
--- a/CODINGSTYLE.md
+++ b/CODINGSTYLE.md
@@ -43,15 +43,44 @@ A continuation line is a line appearing because we broke something that should h
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
-* Always put blank lines around a `case` whose body spans several lines
* 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.
+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.
@@ -114,7 +143,7 @@ def abs(x: Int): Int =
#### 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.
+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:
@@ -124,6 +153,12 @@ val isValidIdent = {
ident.charAt(0).isUnicodeIdentifierStart &&
ident.tail.forall(_.isUnicodeIdentifierPart)
}
+
+if (!isValidIdent) {
+ reportError(
+ "This string is very long and will " +
+ "span several lines.")
+}
```
#### Braces in lambdas
@@ -145,20 +180,27 @@ val someLongIdentifierWithHighIdentation = {
}
```
+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`
-Sometimes, it is acceptable to have several spaces, for vertical alignment reasons.
+
+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, including `=>`, must have a single space on both sides.
-Sometimes, spaces can be removed to highlight the relatively higher priority wrt. to a neighboring operator, for easier visual parsing.
-For example, instead of `x < len - 1`, it is better to write `x < len-1`, highlighting that `-` has a higher priority than `<`.
-
+Binary operators must have a single space on both sides.
Unary operators must not be followed by a space.
### Method call style
@@ -167,16 +209,15 @@ 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.
-Infix notation is also used if the only argument is a brace lambda.
Examples:
```scala
-// inline lambda, hence (), hence dot-notation
+// inline lambda, hence ()
list.map(x => x * 2)
-// long lambda, hence braces, hence infix notation
-list map { x =>
+// long lambda, hence braces
+list.map { x =>
if (x < 5) x
else x * 2
}
@@ -185,20 +226,15 @@ list map { x =>
value :: list
```
-Using dot-notation with a brace lambda is possible to force priorities.
-This is typically the case if the call is chained to a parameterless method call, as in
-
-```scala
-list.map { x =>
- // complicated stuff
-}.toMap
-```
-
-When calling a method declared with an empty pair of parentheses, use `()`, except if the method is Java-defined *and* does not have side-effects.
+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.
@@ -279,16 +315,16 @@ 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
-ScalaDoc comments that fit in one line must be written as
+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:
+Multi-line Scaladoc comments must use the following style:
```scala
/** Returns the maximum of a and b.
@@ -298,7 +334,7 @@ Multi-line ScalaDoc comments must use the following style:
def max(a: Int, b: Int): Int = ???
```
-### Non-ScalaDoc comments
+### 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:
@@ -321,7 +357,7 @@ 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 self type must be on dedicated line, indented 2 spaces only, and followed by a blank line:
+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 {
@@ -330,55 +366,58 @@ class Foo(val x: Int) extends Bar with Foobar {
// 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:
+The second thing to do is to break the line just before the `extends` keyword, indented 4 spaces:
```scala
-class Foo(val x: Int, val y: Int,
- val z: Int) extends Bar with Foobar {
+class Foo(val x: Int)
+ extends Bar with Foobar {
// declarations start here
```
-As an exception, if the constructor parameters are a (long) list of "configuration" parameters, the following format should be used instead:
+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 width: Int = 1,
- val height: Int = 1,
- val depthOfField: Int = 3
-) extends Bar with Foobar {
+class Foo(val x: Int)
+ extends Bar with Foobar with AnotherTrait with YetAnotherTrait
+ with HowManyTraitsAreThere with TooManyTraits {
+
+ // declarations start here
```
-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:
+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[A](
- val width: Int = 1,
- val height: Int = 1,
- val depthOfField: Int = 3
-)(implicit ct: ClassTag[A]) extends Bar with Foobar {
+class Foo(val x: Int, val y: Int,
+ val z: Int)
+ extends Bar with Foobar {
+
+ // declarations start here
```
-If too long, the `extends` clause itself should go to the next line, indented 4 spaces, and followed by a blank line:
+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 x: Int)
- extends Bar with Foobar with AnotherTrait {
-
- // declarations start here
+class Foo(
+ val width: Int = 1,
+ val height: Int = 1,
+ val depthOfField: Int = 3
+) extends Bar with Foobar {
```
-The `extends` clause can be broken further before `with`s, if necessary.
-Additional lines are also indented 4 spaces wrt. the `class` keyword.
+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(val x: Int)
+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 {
-
- // declarations start here
```
@@ -396,7 +435,7 @@ Higher-order methods should be favored over loops and tail-recursive methods whe
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 order words, a `foldLeft` with a side-effecting function should be avoided, and a `while` loop or a `foreach` used instead.
+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.
@@ -432,31 +471,48 @@ val x = {
}
```
-If one of the brances requires braces, then put braces on both branches:
+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 {
+ } else if (secondCondition) {
anotherExpr
+ } else {
+ aThirdExpr
}
}
```
-`if`s and `if/else`s in statement position should always have their branch(es) on dedicated lines:
+`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 surrounded by braces, even if they are single-line.
+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 `def` before it, such as:
+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 = ???
@@ -466,7 +522,6 @@ def isValidIdent = {
ident.charAt(0).isUnicodeIdentifierStart &&
ident.tail.forall(_.isUnicodeIdentifierPart)
}
-
if (isValidIdent)
doSomething()
else
@@ -486,18 +541,35 @@ x match {
```
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 groups:
+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) => a + b
-
+ 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
@@ -516,12 +588,25 @@ that match {
}
```
-This is an instantiation of the rule saying that spaces can be removed around a binary operator to highlight its higher priority wrt. its neighbors.
+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/`).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 71aa7c094d..6d1f0aebea 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,7 +3,7 @@
## Very important notice about the Javalib
If you haven't read it, ***read the very important notice about the Javalib
-in the [Developer documentation](./DEVELOPING.md)*** .
+in the [Javalib documentation](./JAVALIB.md)*** .
## Coding style
@@ -41,7 +41,7 @@ In order for a Pull Request to be considered, it has to meet these requirements:
- 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 master* (PRs coming from master will not be accepted, as we've had trouble in the past with such PRs)
+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.
diff --git a/DEVELOPING.md b/DEVELOPING.md
index 395a6d6e1f..29dc5d18fa 100644
--- a/DEVELOPING.md
+++ b/DEVELOPING.md
@@ -2,25 +2,17 @@
## 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 BSD 3-clause license.
-
-It is also recommended *not to look at any other JDK implementation* (such as
-Apache Harmony), to minimize the chance of copyright debate.
+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 >= 10.0.0 is required.
+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/master/package.json)),
+([history](https://github.com/scala-js/scala-js/commits/main/package.json)),
you need to run
$ npm install
@@ -34,12 +26,12 @@ Otherwise, everything happens within sbt.
Run the normal test suite using the entire Scala.js toolchain using
- > testSuite/test
+ > testSuite2_12/test
In order to test the tests themselves, run the cross-compiling tests on the JVM
with:
- > testSuiteJVM/test
+ > testSuiteJVM2_12/test
If you have changed the IR or the compiler, you typically need to
@@ -47,8 +39,8 @@ If you have changed the IR or the compiler, you typically need to
before testing anew.
-If you have changed the IR, the linker, the JS environments, the test adapter
-or the sbt plugin, you typically need to
+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
@@ -58,25 +50,34 @@ To test in fullOpt stage:
There are also a few additional tests in a separate testing project:
- > testSuiteEx/test
+ > testSuiteEx2_12/test
The compiler tests (mostly verifying expected compile error messages) can be
run with
- > compiler/test
+ > compiler2_12/test
The full partest suite (tests of the Scala language, ported in Scala.js) are
run with:
- > partestSuite/test
+ > partestSuite2_12/test
or, more typically,
- > partestSuite/testOnly -- --fastOpt
+ > partestSuite2_12/testOnly -- --fastOpt
The JUnit tests from scala/scala can be run with
- > scalaTestSuite/test
+ > 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
@@ -92,6 +93,12 @@ 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:
@@ -100,8 +107,8 @@ The repository is organized as follows:
* `ir/` The Intermediate Representation, produced by the compiler and consumed by the linker
* `compiler/` The scalac compiler plugin
-* `io/` Virtual I/O abstractions
-* `logging/` A tiny logging API
+* `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
@@ -114,26 +121,16 @@ The repository is organized as follows:
All of these are packaged in `scalajs-library.jar`.
-### JS environments
-
-The JS environments are JVM libraries that abstract the details of using a
-JavaScript engine to run JS code.
-
-* `js-envs/` The generic definitions of JavaScript environments and runners
-* `nodejs-env/` The Node.js environment
-
-Other JS environments are developed in separate repositories under the
-`scala-js` organization.
-
### 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/` JS side of the bridge, as well as the JS definition of the
- sbt-testing-interface API
+* `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:
@@ -142,7 +139,7 @@ This repository also contains a specific implementation of JUnit:
### sbt plugin
-* `sbt-plugin/` The sbt plugin itself
+* `sbt-plugin/` The sbt plugin itself (2.12 only)
### Testing projects
@@ -163,7 +160,7 @@ The helloworld and reversi also have HTML pages to run them in real browsers.
The build itself contains the entire sbt plugin (and all its dependencies) as
part of its sources.
-If you change any of the IR, virtual IO, logging API, linker, JS environments,
+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.
@@ -173,7 +170,8 @@ 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.
- > ++SCALA_VERSION
- > ;compiler/publishLocal;library/publishLocal;testInterface/publishLocal;testBridge/publishLocal;jUnitRuntime/publishLocal;jUnitPlugin/publishLocal
- > ++2.10.7
- > ;ir/publishLocal;io/publishLocal;logging/publishLocal;linker/publishLocal;jsEnvs/publishLocal;jsEnvsTestKit/publishLocal;nodeJSEnv/publishLocal;testAdapter/publishLocal;sbtPlugin/publishLocal
+ > ;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
index 0c16bff949..c1a4c70069 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -56,9 +56,16 @@ def CIScriptPrelude = '''
LOCAL_HOME="/localhome/jenkins"
LOC_SBT_BASE="$LOCAL_HOME/scala-js-sbt-homes"
LOC_SBT_BOOT="$LOC_SBT_BASE/sbt-boot"
-LOC_SBT_HOME="$LOC_SBT_BASE/sbt-home"
+LOC_IVY_HOME="$LOC_SBT_BASE/sbt-home"
+LOC_CS_CACHE="$LOC_SBT_BASE/coursier/cache"
+TEST_LOCAL_IVY_HOME="$(pwd)/.ivy2-test-local"
-export SBT_OPTS="-J-Xmx5G -J-XX:MaxPermSize=512M -Dsbt.boot.directory=$LOC_SBT_BOOT -Dsbt.ivy.home=$LOC_SBT_HOME -Divy.home=$LOC_SBT_HOME -Dsbt.global.base=$LOC_SBT_BASE"
+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/"
@@ -69,7 +76,7 @@ setJavaVersion() {
export PATH=$JAVA_HOME/bin:$PATH
}
-# Define sbtretry
+# Define sbtretry and sbtnoretry
sbtretry() {
local TIMEOUT=45m
@@ -88,207 +95,351 @@ sbtretry() {
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/run &&
+ sbtretry ++$scala helloworld$v/run &&
sbtretry 'set scalaJSStage in Global := FullOptStage' \
- ++$scala helloworld/run \
- helloworld/clean &&
- sbtretry 'set scalaJSLinkerConfig in helloworld ~= (_.withOptimizer(false))' \
- ++$scala helloworld/run \
- helloworld/clean &&
- sbtretry 'set scalaJSLinkerConfig in helloworld ~= (_.withSemantics(_.withAsInstanceOfs(CheckedBehavior.Unchecked)))' \
- ++$scala helloworld/run \
- helloworld/clean &&
+ '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 ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
- helloworld/run \
- helloworld/clean &&
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ helloworld$v/run &&
sbtretry ++$scala \
- 'set artifactPath in (helloworld, Compile, fastOptJS) := (crossTarget in helloworld).value / "helloworld-fastopt.mjs"' \
- 'set jsEnv in helloworld := new org.scalajs.jsenv.nodejs.NodeJSEnv(org.scalajs.jsenv.nodejs.NodeJSEnv.Config().withArgs(List("--experimental-modules")))' \
- 'set scalaJSLinkerConfig in helloworld ~= (_.withModuleKind(ModuleKind.ESModule))' \
- helloworld/run &&
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \
+ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ helloworld$v/run &&
sbtretry ++$scala \
- 'set artifactPath in (helloworld, Compile, fullOptJS) := (crossTarget in helloworld).value / "helloworld-opt.mjs"' \
- 'set jsEnv in helloworld := new org.scalajs.jsenv.nodejs.NodeJSEnv(org.scalajs.jsenv.nodejs.NodeJSEnv.Config().withArgs(List("--experimental-modules")))' \
- 'set scalaJSLinkerConfig in helloworld ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ '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/run \
- helloworld/clean &&
- sbtretry ++$scala testingExample/testHtml &&
+ 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/testHtml \
- testingExample/clean &&
- sbtretry ++$scala testSuiteJVM/test testSuiteJVM/clean &&
- sbtretry ++$scala testSuite/test &&
- sbtretry ++$scala testSuiteEx/test &&
+ ++$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/test &&
- sbtretry ++$scala testSuite/test:doc library/test compiler/test reversi/fastOptJS reversi/fullOptJS &&
- sbtretry ++$scala compiler/compile:doc library/compile:doc \
- testInterface/compile:doc testBridge/compile:doc &&
+ ++$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/fetchScalaSource &&
- sbtretry ++$scala library/mimaReportBinaryIssues testInterface/mimaReportBinaryIssues &&
- sh ci/checksizes.sh $scala &&
- sh ci/check-partest-coverage.sh $scala
+ sbtretry ++$scala partest$v/fetchScalaSource &&
+ sbtretry ++$scala \
+ javalibintf/mimaReportBinaryIssues \
+ library$v/mimaReportBinaryIssues \
+ testInterface$v/mimaReportBinaryIssues \
+ jUnitRuntime$v/mimaReportBinaryIssues
''',
- "test-suite-ecma-script2015": '''
+ "test-suite-default-esversion": '''
setJavaVersion $java
npm install &&
- sbtretry ++$scala jUnitTestOutputsJVM/test jUnitTestOutputsJS/test testBridge/test \
- 'set scalaJSStage in Global := FullOptStage' jUnitTestOutputsJS/test testBridge/test &&
- sbtretry ++$scala $testSuite/test &&
- sbtretry 'set scalaJSStage in Global := FullOptStage' \
- ++$scala $testSuite/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withOptimizer(false))' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withOptimizer(false))' \
+ 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' \
- ++$scala $testSuite/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= makeCompliant' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= makeCompliant' \
+ $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' \
- ++$scala $testSuite/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= makeCompliant' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.withOptimizer(false))' \
- ++$scala $testSuite/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)))' \
- 'set jsEnv in $testSuite := new org.scalajs.jsenv.nodejs.NodeJSEnv(org.scalajs.jsenv.nodejs.NodeJSEnv.Config().withArgs(List("--harmony-bigint")))' \
- ++$scala $testSuite/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)).withOptimizer(false))' \
- 'set jsEnv in $testSuite := new org.scalajs.jsenv.nodejs.NodeJSEnv(org.scalajs.jsenv.nodejs.NodeJSEnv.Config().withArgs(List("--harmony-bigint")))' \
- ++$scala $testSuite/test \
- $testSuite/clean &&
- sbtretry 'set scalacOptions in $testSuite += "-Xexperimental"' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalacOptions in $testSuite += "-Xexperimental"' \
+ $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' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
+ $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' \
- ++$scala $testSuite/test \
- $testSuite/clean &&
- sbtretry 'set artifactPath in ($testSuite, Test, fastOptJS) := (crossTarget in $testSuite).value / "testsuite-fastopt.mjs"' \
- 'set jsEnv in $testSuite := new org.scalajs.jsenv.nodejs.NodeJSEnv(org.scalajs.jsenv.nodejs.NodeJSEnv.Config().withArgs(List("--experimental-modules")))' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.withModuleKind(ModuleKind.ESModule))' \
- ++$scala $testSuite/test &&
- sbtretry 'set artifactPath in ($testSuite, Test, fullOptJS) := (crossTarget in $testSuite).value / "testsuite-opt.mjs"' \
- 'set jsEnv in $testSuite := new org.scalajs.jsenv.nodejs.NodeJSEnv(org.scalajs.jsenv.nodejs.NodeJSEnv.Config().withArgs(List("--experimental-modules")))' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.withModuleKind(ModuleKind.ESModule))' \
+ $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' \
- ++$scala $testSuite/test
+ $testSuite$v/test
''',
- "test-suite-ecma-script5-force-polyfills": '''
+ "test-suite-custom-esversion-force-polyfills": '''
setJavaVersion $java
npm install &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set jsEnv in $testSuite := new NodeJSEnvForcePolyfills()' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set jsEnv in $testSuite := new NodeJSEnvForcePolyfills()' \
+ 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/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set jsEnv in $testSuite := new NodeJSEnvForcePolyfills()' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.withOptimizer(false))' \
- ++$scala $testSuite/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set jsEnv in $testSuite := new NodeJSEnvForcePolyfills()' \
- 'set scalaJSLinkerConfig in $testSuite ~= makeCompliant' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set jsEnv in $testSuite := new NodeJSEnvForcePolyfills()' \
- 'set scalaJSLinkerConfig in $testSuite ~= 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 ~= (_.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/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set jsEnv in $testSuite := new NodeJSEnvForcePolyfills()' \
- 'set scalaJSLinkerConfig in $testSuite ~= makeCompliant' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.withOptimizer(false))' \
- ++$scala $testSuite/test \
- $testSuite/clean
+ ++$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-ecma-script5": '''
+ "test-suite-custom-esversion": '''
setJavaVersion $java
npm install &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
+ 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/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.withOptimizer(false))' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.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))' \
+ ++$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/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set scalaJSLinkerConfig in $testSuite ~= makeCompliant' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set scalaJSLinkerConfig in $testSuite ~= makeCompliant' \
+ ++$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/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set scalaJSLinkerConfig in $testSuite ~= makeCompliant' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.withOptimizer(false))' \
- ++$scala $testSuite/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false).withAllowBigIntsForLongs(true)))' \
- 'set jsEnv in $testSuite := new org.scalajs.jsenv.nodejs.NodeJSEnv(org.scalajs.jsenv.nodejs.NodeJSEnv.Config().withArgs(List("--harmony-bigint")))' \
- ++$scala $testSuite/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false).withAllowBigIntsForLongs(true)).withOptimizer(false))' \
- 'set jsEnv in $testSuite := new org.scalajs.jsenv.nodejs.NodeJSEnv(org.scalajs.jsenv.nodejs.NodeJSEnv.Config().withArgs(List("--harmony-bigint")))' \
- ++$scala $testSuite/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \
+ ++$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/test \
- $testSuite/clean &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set artifactPath in ($testSuite, Test, fastOptJS) := (crossTarget in $testSuite).value / "testsuite-fastopt.mjs"' \
- 'set jsEnv in $testSuite := new org.scalajs.jsenv.nodejs.NodeJSEnv(org.scalajs.jsenv.nodejs.NodeJSEnv.Config().withArgs(List("--experimental-modules")))' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.withModuleKind(ModuleKind.ESModule))' \
- ++$scala $testSuite/test &&
- sbtretry 'set scalaJSLinkerConfig in $testSuite ~= (_.withESFeatures(_.withUseECMAScript2015(false)))' \
- 'set artifactPath in ($testSuite, Test, fullOptJS) := (crossTarget in $testSuite).value / "testsuite-opt.mjs"' \
- 'set jsEnv in $testSuite := new org.scalajs.jsenv.nodejs.NodeJSEnv(org.scalajs.jsenv.nodejs.NodeJSEnv.Config().withArgs(List("--experimental-modules")))' \
- 'set scalaJSLinkerConfig in $testSuite ~= (_.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))' \
+ ++$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/test
+ ++$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
@@ -298,171 +449,163 @@ def Tasks = [
"bootstrap": '''
setJavaVersion $java
npm install &&
- sbt ++$scala linker/test &&
- sbt ++$scala irJS/test linkerJS/test &&
- sbt 'set scalaJSStage in Global := FullOptStage' \
- 'set scalaJSStage in testSuite := FastOptStage' \
- ++$scala irJS/test linkerJS/test &&
- sbt ++$scala testSuite/bootstrap:test &&
- sbt 'set scalaJSStage in Global := FullOptStage' \
- 'set scalaJSStage in testSuite := FastOptStage' \
- ++$scala testSuite/bootstrap:test &&
- sbt ++$scala irJS/mimaReportBinaryIssues \
- loggingJS/mimaReportBinaryIssues linkerJS/mimaReportBinaryIssues
+ 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 &&
- sbt ++$scala ir/test logging/compile linker/compile \
- jsEnvs/test nodeJSEnv/test testAdapter/test \
- ir/mimaReportBinaryIssues \
- logging/mimaReportBinaryIssues linker/mimaReportBinaryIssues \
- jsEnvs/mimaReportBinaryIssues jsEnvsTestKit/mimaReportBinaryIssues \
- nodeJSEnv/mimaReportBinaryIssues \
- testAdapter/mimaReportBinaryIssues &&
- sbt ++$scala ir/compile:doc logging/compile:doc \
- linker/compile:doc jsEnvs/compile:doc \
- jsEnvsTestKit/compile:doc nodeJSEnv/compile:doc \
- testAdapter/compile:doc
+ 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
''',
- "tools-sbtplugin": '''
+ // These are agnostic to the Scala version
+ "sbt-plugin-and-scalastyle": '''
setJavaVersion $java
npm install &&
- sbt ++$scala ir/test logging/compile linker/compile \
- jsEnvs/test nodeJSEnv/test testAdapter/test \
- sbtPlugin/package \
- ir/mimaReportBinaryIssues \
- logging/mimaReportBinaryIssues linker/mimaReportBinaryIssues \
- jsEnvs/mimaReportBinaryIssues jsEnvsTestKit/mimaReportBinaryIssues \
- nodeJSEnv/mimaReportBinaryIssues \
- testAdapter/mimaReportBinaryIssues \
- sbtPlugin/mimaReportBinaryIssues &&
- sbt ++$scala library/scalastyle javalanglib/scalastyle javalib/scalastyle \
- ir/scalastyle compiler/scalastyle \
- compiler/test:scalastyle \
- logging/scalastyle logging/test:scalastyle \
- linker/scalastyle linker/test:scalastyle \
- jsEnvs/scalastyle jsEnvsTestKit/scalastyle nodeJSEnv/scalastyle \
- jsEnvs/test:scalastyle nodeJSEnv/test:scalastyle testAdapter/scalastyle \
- sbtPlugin/scalastyle testInterface/scalastyle testBridge/scalastyle \
- testSuite/scalastyle testSuite/test:scalastyle \
- testSuiteJVM/test:scalastyle \
- testSuiteEx/test:scalastyle helloworld/scalastyle \
- reversi/scalastyle testingExample/scalastyle \
- testingExample/test:scalastyle \
- jUnitPlugin/scalastyle jUnitRuntime/scalastyle \
- jUnitTestOutputsJVM/scalastyle jUnitTestOutputsJVM/test:scalastyle \
- jUnitTestOutputsJS/scalastyle jUnitTestOutputsJS/test:scalastyle &&
- sbt ++$scala ir/compile:doc logging/compile:doc \
- linker/compile:doc jsEnvs/compile:doc \
- jsEnvsTestKit/compile:doc nodeJSEnv/compile:doc \
- testAdapter/compile:doc \
- sbtPlugin/compile:doc
+ sbtnoretry \
+ sbtPlugin/compile:doc \
+ sbtPlugin/mimaReportBinaryIssues \
+ scalastyleCheck &&
+ sbtnoretry sbtPlugin/scripted
''',
- "partestc": '''
+ "partest-noopt": '''
setJavaVersion $java
npm install &&
- sbt ++$scala partest/compile
+ sbtnoretry ++$scala partestSuite$v/test:compile &&
+ sbtnoretry ++$scala "partestSuite$v/testOnly -- $partestopts --showDiff"
''',
- "sbtplugin-test": '''
- setJavaVersion 1.8
- SBT_VER_OVERRIDE=$sbt_version_override
- # Publish Scala.js artifacts locally
- # Then go into standalone project and test
- npm install &&
- sbt ++2.11.12 compiler/publishLocal library/publishLocal \
- testInterface/publishLocal testBridge/publishLocal \
- jUnitPlugin/publishLocal jUnitRuntime/publishLocal &&
- sbt ++$toolsscala ${SBT_VER_OVERRIDE:+^^$SBT_VER_OVERRIDE} \
- ir/publishLocal logging/publishLocal \
- linker/publishLocal jsEnvs/publishLocal \
- nodeJSEnv/publishLocal testAdapter/publishLocal \
- sbtPlugin/publishLocal &&
- cd sbt-plugin-test &&
- setJavaVersion $java &&
- if [ -n "$SBT_VER_OVERRIDE" ]; then echo "sbt.version=$SBT_VER_OVERRIDE" > ./project/build.properties; fi &&
- sbt noDOM/run \
- noDOM/testHtml multiTestJS/testHtml \
- test \
- noDOM/testScalaJSModuleInitializers \
- noDOM/clean noDOM/concurrentUseOfLinkerTest \
- multiTestJS/test:testScalaJSSourceMapAttribute &&
- sbt 'set scalaJSStage in Global := FullOptStage' \
- noDOM/testHtml multiTestJS/testHtml
- ''',
-
- "partest-noopt": '''
+ "partest-fastopt": '''
setJavaVersion $java
npm install &&
- sbt ++$scala package "partestSuite/testOnly -- --showDiff"
+ sbtnoretry ++$scala partestSuite$v/test:compile &&
+ sbtnoretry ++$scala "partestSuite$v/testOnly -- $partestopts --fastOpt --showDiff"
''',
- "partest-fastopt": '''
+ "partest-fullopt": '''
setJavaVersion $java
npm install &&
- sbt ++$scala package "partestSuite/testOnly -- --fastOpt --showDiff"
+ sbtnoretry ++$scala partestSuite$v/test:compile &&
+ sbtnoretry ++$scala "partestSuite$v/testOnly -- $partestopts --fullOpt --showDiff"
''',
- "partest-fullopt": '''
+ "scala3-compat": '''
setJavaVersion $java
npm install &&
- sbt ++$scala package "partestSuite/testOnly -- --fullOpt --showDiff"
+ sbtnoretry ++$scala! ir2_13/test
'''
]
def mainJavaVersion = "1.8"
-def otherJavaVersions = []
+def otherJavaVersions = ["11", "17", "21"]
def allJavaVersions = otherJavaVersions.clone()
allJavaVersions << mainJavaVersion
-def mainScalaVersion = "2.12.8"
-def mainScalaVersions = ["2.11.12", "2.12.8", "2.13.0"]
+def mainScalaVersion = "2.12.20"
+def mainScalaVersions = ["2.12.20", "2.13.16"]
def otherScalaVersions = [
- "2.11.0",
- "2.11.1",
- "2.11.2",
- "2.11.4",
- "2.11.5",
- "2.11.6",
- "2.11.7",
- "2.11.8",
- "2.11.11",
- "2.11.12",
- "2.12.1",
- "2.12.2",
- "2.12.3",
- "2.12.4",
- "2.12.5",
"2.12.6",
- "2.12.7"
+ "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-ecma-script2015", scala: scalaVersion, java: mainJavaVersion, testSuite: "testSuite"])
- quickMatrix.add([task: "test-suite-ecma-script5", scala: scalaVersion, java: mainJavaVersion, testSuite: "testSuite"])
- quickMatrix.add([task: "test-suite-ecma-script2015", scala: scalaVersion, java: mainJavaVersion, testSuite: "scalaTestSuite"])
- quickMatrix.add([task: "test-suite-ecma-script5", scala: scalaVersion, java: mainJavaVersion, testSuite: "scalaTestSuite"])
+ 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])
+ 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"])
}
-quickMatrix.add([task: "test-suite-ecma-script5-force-polyfills", scala: mainScalaVersion, java: mainJavaVersion, testSuite: "testSuite"])
allJavaVersions.each { javaVersion ->
- quickMatrix.add([task: "tools-sbtplugin", scala: "2.12.8", sbt_version_override: "", java: javaVersion])
- quickMatrix.add([task: "tools", scala: "2.10.7", java: javaVersion])
- quickMatrix.add([task: "tools", scala: "2.11.12", java: 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: "partestc", scala: "2.11.0", java: mainJavaVersion])
-quickMatrix.add([task: "sbtplugin-test", toolsscala: "2.10.7", sbt_version_override: "0.13.17", java: mainJavaVersion])
-quickMatrix.add([task: "sbtplugin-test", toolsscala: "2.12.8", sbt_version_override: "", java: mainJavaVersion])
+quickMatrix.add([task: "scala3-compat", scala: scala3Version, java: mainJavaVersion])
// The 'full' matrix
def fullMatrix = quickMatrix.clone()
@@ -471,17 +614,16 @@ otherScalaVersions.each { scalaVersion ->
}
mainScalaVersions.each { scalaVersion ->
otherJavaVersions.each { javaVersion ->
- quickMatrix.add([task: "test-suite-ecma-script2015", scala: scalaVersion, java: javaVersion, testSuite: "testSuite"])
- quickMatrix.add([task: "test-suite-ecma-script5", scala: scalaVersion, java: javaVersion, testSuite: "testSuite"])
+ 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])
- fullMatrix.add([task: "partest-fullopt", scala: scalaVersion, java: mainJavaVersion])
+ 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"])
}
-otherScalaVersions.each { scalaVersion ->
- // Partest does not compile on Scala 2.11.4 (see #1215).
- if (scalaVersion != "2.11.4") {
- fullMatrix.add([task: "partest-fastopt", scala: scalaVersion, java: mainJavaVersion])
- }
+otherJavaVersions.each { javaVersion ->
+ fullMatrix.add([task: "scala3-compat", scala: scala3Version, java: javaVersion])
}
def Matrices = [
@@ -510,14 +652,17 @@ matrix.each { taskDef ->
}
}
+ def suffix = taskDef.scala.split('\\.')[0..1].join('_')
+ taskStr = taskStr.replace('$v', suffix)
+
def ciScript = CIScriptPrelude + taskStr
buildDefs.put(fullTaskName, {
node('linuxworker') {
checkout scm
- sh "git clean -fdx && rm -rf partest/fetchedSources/"
- writeFile file: 'ciscript.sh', text: ciScript, encoding: 'UTF-8'
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"
}
diff --git a/README.md b/README.md
index 00b31c917b..add1859b89 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,12 @@
+
+
+
+ Scala.js
+
+
+
+Chat: [#scala-js](https://discord.com/invite/scala) on Discord.
+
This is the repository for
[Scala.js, the Scala to JavaScript compiler](https://www.scala-js.org/).
diff --git a/RELEASING.md b/RELEASING.md
index f964da787b..b143e9a93e 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -7,16 +7,14 @@
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 master (do *not* create a merge commit).
+ 1. Once you have LGTM, push to `main` (do *not* create a merge commit).
1. Testing (post results as comments to commit):
- - Nightly
- - Weekly
+ - 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, bintray (`./script/publish.sh`)
- - [Publish the CLI][4]
+ - Sonatype (`./script/publish.sh`)
- Docs to website: Use
`~/fetchapis.sh ` on the webserver
once artifacts are on maven central.
@@ -28,14 +26,17 @@
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)
- - Announce on the mailing list (scala-js@googlegroups.com)
- - Cross-post to scala-announce (scala-announce@googlegroups.com)
+ - 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/a09e8cdd92b962e90c83ec124b9764970a4889ff
-[3]: https://github.com/scala-js/scala-js/blob/master/TESTING
-[4]: https://github.com/scala-js/scala-js-cli/blob/master/RELEASING.md
-[5]: https://github.com/scala-js/scala-js/commit/c51f8b65d3eca45de84397f7167058c91d6b6aa1
-[6]: https://github.com/scala-js/scala-js-website/commit/8dc9e9d3ee63ec47e6eb154fa7bd5a2ae8d1d42d
+[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 b/TESTING
deleted file mode 100644
index 14d5d892dd..0000000000
--- a/TESTING
+++ /dev/null
@@ -1,35 +0,0 @@
-This file contains test cases that should be manually executed.
-
-## HTML-Runners
-
-The following HTML-runners must be manually tested:
-
- examples/helloworld/helloworld-{2.11|2.12}{|-fastopt}.html
- examples/reversi/reversi-{2.11|2.12}{|-fastopt}.html
-
-The following sbt-plugin generated test runners must be manually tested (with
-Scala 2.11 and 2.12, and in `FastOptStage` and `FullOptStage`):
-
- testingExample/testHtml
- testSuite/testHtml
-
-## Sourcemaps
-
-To test source maps, do the following on:
-
- examples/reversi/reversi-{2.11|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/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
deleted file mode 100644
index d3b7eaf4d8..0000000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-version: '{build}'
-image: Visual Studio 2015
-environment:
- global:
- NODEJS_VERSION: "8"
- JAVA_HOME: C:\Program Files\Java\jdk1.8.0
- SCALA_VERSION: 2.12.4
-install:
- - ps: Install-Product node $env:NODEJS_VERSION
- - npm install
- - cmd: choco install sbt --version 1.0.2 -ia "INSTALLDIR=""C:\sbt"""
- - cmd: SET PATH=C:\sbt\bin;%JAVA_HOME%\bin;%PATH%
- - cmd: SET SBT_OPTS=-Xmx4g
-build: off
-test_script:
- # Very far from testing everything, but at least it is a good sanity check
- - cmd: sbt ";clean;++%SCALA_VERSION%;testSuite/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
index 75da65ce31..c0d38e0efb 100644
--- a/assets/additional-doc-styles.css
+++ b/assets/additional-doc-styles.css
@@ -1,24 +1,7 @@
-.badge-ecma6 {
- background-color: #E68A00;
-}
-
-.badge-ecma2019 {
+.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;
}
-
-.badge-external {
- background-color: #710071;
-}
-
-a.badge-external {
- color: #FFFFFF;
- text-decoration: none;
-}
-
-a[href].badge-external:hover {
- text-decoration: underline;
-}
diff --git a/build.sbt b/build.sbt
index e4b80cf2f7..e9bdde6b6b 100644
--- a/build.sbt
+++ b/build.sbt
@@ -4,21 +4,20 @@ val scalajs = Build.root
val ir = Build.irProject
val irJS = Build.irProjectJS
val compiler = Build.compiler
-val logging = Build.logging
-val loggingJS = Build.loggingJS
+val linkerInterface = Build.linkerInterface
+val linkerInterfaceJS = Build.linkerInterfaceJS
+val linkerPrivateLibrary = Build.linkerPrivateLibrary
val linker = Build.linker
val linkerJS = Build.linkerJS
-val jsEnvs = Build.jsEnvs
-val jsEnvsTestKit = Build.jsEnvsTestKit
-val nodeJSEnv = Build.nodeJSEnv
val testAdapter = Build.testAdapter
val sbtPlugin = Build.plugin
-val javalanglib = Build.javalanglib
+val javalibintf = Build.javalibintf
+val javalibInternal = Build.javalibInternal
val javalib = Build.javalib
-val scalalib = Build.scalalib
+val scalalibInternal = Build.scalalibInternal
val libraryAux = Build.libraryAux
+val scalalib = Build.scalalib
val library = Build.library
-val minilib = Build.minilib
val testInterface = Build.testInterface
val testBridge = Build.testBridge
val jUnitRuntime = Build.jUnitRuntime
@@ -27,13 +26,14 @@ val jUnitTestOutputsJVM = Build.jUnitTestOutputsJVM
val jUnitPlugin = Build.jUnitPlugin
val jUnitAsyncJS = Build.jUnitAsyncJS
val jUnitAsyncJVM = Build.jUnitAsyncJVM
-val examples = Build.examples
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
diff --git a/ci/check-partest-coverage.sh b/ci/check-partest-coverage.sh
deleted file mode 100755
index ca35f3711f..0000000000
--- a/ci/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/partest/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/ci/checksizes.sh b/ci/checksizes.sh
deleted file mode 100755
index 5838408622..0000000000
--- a/ci/checksizes.sh
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/bin/sh
-
-BASEDIR="`dirname $0`/.."
-
-FULLVER="$1"
-
-case $FULLVER in
- 2.11.12)
- VER=2.11
- ;;
- 2.12.8)
- VER=2.12
- ;;
- 2.13.0)
- VER=2.13
- ;;
- 2.11.0|2.11.1|2.11.2|2.11.4|2.11.5|2.11.6|2.11.7|2.11.8|2.11.11|2.12.1|2.12.2|2.12.3|2.12.4|2.12.5|2.12.6|2.12.7)
- echo "Ignoring checksizes for Scala $FULLVER"
- exit 0
- ;;
-esac
-
-REVERSI_PREOPT="$BASEDIR/examples/reversi/target/scala-$VER/reversi-fastopt.js"
-REVERSI_OPT="$BASEDIR/examples/reversi/target/scala-$VER/reversi-opt.js"
-
-REVERSI_PREOPT_SIZE=$(stat '-c%s' "$REVERSI_PREOPT")
-REVERSI_OPT_SIZE=$(stat '-c%s' "$REVERSI_OPT")
-
-gzip -c "$REVERSI_PREOPT" > "$REVERSI_PREOPT.gz"
-gzip -c "$REVERSI_OPT" > "$REVERSI_OPT.gz"
-
-REVERSI_PREOPT_GZ_SIZE=$(stat '-c%s' "$REVERSI_PREOPT.gz")
-REVERSI_OPT_GZ_SIZE=$(stat '-c%s' "$REVERSI_OPT.gz")
-
-case $FULLVER in
- 2.11.12)
- REVERSI_PREOPT_EXPECTEDSIZE=415000
- REVERSI_OPT_EXPECTEDSIZE=95000
- REVERSI_PREOPT_GZ_EXPECTEDSIZE=58000
- REVERSI_OPT_GZ_EXPECTEDSIZE=27000
- ;;
- 2.12.8)
- REVERSI_PREOPT_EXPECTEDSIZE=485000
- REVERSI_OPT_EXPECTEDSIZE=111000
- REVERSI_PREOPT_GZ_EXPECTEDSIZE=59000
- REVERSI_OPT_GZ_EXPECTEDSIZE=27000
- ;;
- 2.13.0)
- REVERSI_PREOPT_EXPECTEDSIZE=526000
- REVERSI_OPT_EXPECTEDSIZE=123000
- REVERSI_PREOPT_GZ_EXPECTEDSIZE=74000
- REVERSI_OPT_GZ_EXPECTEDSIZE=34000
- ;;
-esac
-
-echo "Checksizes: Scala version: $FULLVER"
-echo "Reversi preopt size = $REVERSI_PREOPT_SIZE (expected $REVERSI_PREOPT_EXPECTEDSIZE)"
-echo "Reversi opt size = $REVERSI_OPT_SIZE (expected $REVERSI_OPT_EXPECTEDSIZE)"
-echo "Reversi preopt gzip size = $REVERSI_PREOPT_GZ_SIZE (expected $REVERSI_PREOPT_GZ_EXPECTEDSIZE)"
-echo "Reversi opt gzip size = $REVERSI_OPT_GZ_SIZE (expected $REVERSI_OPT_GZ_EXPECTEDSIZE)"
-
-[ "$REVERSI_PREOPT_SIZE" -le "$REVERSI_PREOPT_EXPECTEDSIZE" ] && \
- [ "$REVERSI_OPT_SIZE" -le "$REVERSI_OPT_EXPECTEDSIZE" ] && \
- [ "$REVERSI_PREOPT_GZ_SIZE" -le "$REVERSI_PREOPT_GZ_EXPECTEDSIZE" ] && \
- [ "$REVERSI_OPT_GZ_SIZE" -le "$REVERSI_OPT_GZ_EXPECTEDSIZE" ]
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala b/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala
index 634e993787..2c8951a67f 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala
@@ -25,109 +25,79 @@ import scala.tools.nsc._
* @author Sébastien Doeraene
*/
trait CompatComponent {
- import CompatComponent.{infiniteLoop, noImplClasses}
+ import CompatComponent.infiniteLoop
val global: Global
import global._
implicit final class SymbolCompat(self: Symbol) {
- def originalOwner: Symbol =
- global.originalOwner.getOrElse(self, self.rawowner)
-
- def implClass: Symbol = NoSymbol
-
- def isTraitOrInterface: Boolean = self.isTrait || self.isInterface
+ def isScala3Defined: Boolean = false
}
implicit final class GlobalCompat(
self: CompatComponent.this.global.type) {
- object originalOwner {
- def getOrElse(sym: Symbol, orElse: => Symbol): Symbol = infiniteLoop()
+ // 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)
}
}
- private implicit final class FlagsCompat(self: Flags.type) {
- def IMPLCLASS: Long = infiniteLoop()
- }
-
- lazy val scalaUsesImplClasses: Boolean =
- definitions.SeqClass.implClass != NoSymbol // a trait we know has an impl class
-
- def isImplClass(sym: Symbol): Boolean =
- scalaUsesImplClasses && sym.hasFlag(Flags.IMPLCLASS)
-
- implicit final class StdTermNamesCompat(self: global.nme.type) {
- def IMPL_CLASS_SUFFIX: String = noImplClasses()
-
- def isImplClassName(name: Name): Boolean = false
- }
-
- implicit final class StdTypeNamesCompat(self: global.tpnme.type) {
- def IMPL_CLASS_SUFFIX: String = noImplClasses()
+ 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 interfaceName(implname: Name): TypeName = noImplClasses()
+ def checkClassType(tpt: Tree): Boolean =
+ infiniteLoop()
}
- // SAMFunction was introduced in 2.12 for LMF-capable SAM types
-
- object SAMFunctionAttachCompatDef {
- case class SAMFunction(samTp: Type, sam: Symbol, synthCls: Symbol)
- extends PlainAttachment
+ // DottyEnumSingleton was introduced in 2.13.6 to identify Scala 3 `enum` singleton cases.
+ object AttachmentsCompatDef {
+ object DottyEnumSingleton extends PlainAttachment
}
- object SAMFunctionAttachCompat {
- import SAMFunctionAttachCompatDef._
+ object AttachmentsCompat {
+ import AttachmentsCompatDef._
object Inner {
import global._
- type SAMFunctionAlias = SAMFunction
- val SAMFunctionAlias = SAMFunction
+ val DottyEnumSingletonAlias = DottyEnumSingleton
}
}
- type SAMFunctionCompat = SAMFunctionAttachCompat.Inner.SAMFunctionAlias
- lazy val SAMFunctionCompat = SAMFunctionAttachCompat.Inner.SAMFunctionAlias
-
- implicit final class SAMFunctionCompatOps(self: SAMFunctionCompat) {
- // Introduced in 2.12.5 to synthesize bridges in LMF classes
- def synthCls: Symbol = NoSymbol
- }
+ lazy val DottyEnumSingletonCompat = AttachmentsCompat.Inner.DottyEnumSingletonAlias
/* global.genBCode.bTypes.initializeCoreBTypes()
- *
- * This one has a very particular history:
- * - in 2.11.{0-1}, genBCode does not have a bTypes member
- * - In 2.11.{2-5}, there is genBCode.bTypes, but it has no
- * initializeCoreBTypes (it was actually typo'ed as intializeCoreBTypes!)
- * - In 2.11.6+, including 2.12, we finally have
- * genBCode.bTypes.initializeCoreBTypes
- * - Since 2.12, it is mandatory to call that method from GenJSCode.run()
+ * Early 2.12.x versions require that this method be called from
+ * GenJSCode.run(), but it disappeared later in the 2.12.x series.
*/
- object LowPrioGenBCodeCompat {
- object genBCode {
- object bTypes {
- def initializeCoreBTypes(): Unit = ()
+ 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
}
}
}
- def initializeCoreBTypesCompat(): Unit = {
- import LowPrioGenBCodeCompat.genBCode._
+ // Of type Reporting.WarningCategory.type, but we cannot explicit write it
+ val WarningCategory = {
+ import WarningCategoryCompat._
{
- import genBCode._
-
- import LowPrioGenBCodeCompat.genBCode.bTypes._
-
- {
- import bTypes._
-
- initializeCoreBTypes()
- }
+ import scala.tools.nsc._
+ Reporting.WarningCategory
}
}
}
@@ -135,7 +105,4 @@ trait CompatComponent {
object CompatComponent {
private def infiniteLoop(): Nothing =
throw new AssertionError("Infinite loop in Compat")
-
- private def noImplClasses(): Nothing =
- throw new AssertionError("No impl classes in this version")
}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitInnerJS.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitInnerJS.scala
index 35e55c1d49..01ac4b8a85 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitInnerJS.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitInnerJS.scala
@@ -43,6 +43,38 @@ import scala.collection.mutable
*
* 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
@@ -81,47 +113,72 @@ abstract class ExplicitInnerJS[G <: Global with Singleton](val global: G)
/** This class does not change linearization. */
override protected def changesBaseClasses: Boolean = false
- /** Whether vals in traits are represented by their getter.
- * This is true in 2.12+, since the addition of the `fields` phase.
- * @see https://github.com/scala/scala/pull/5141
- */
- private lazy val traitValsHoldTheirGetterSymbol =
- !scala.util.Properties.versionNumberString.startsWith("2.11.")
-
protected def newTransformer(unit: CompilationUnit): Transformer =
new ExplicitInnerJSTransformer(unit)
- /** Is the given clazz an inner JS class? */
- private def isInnerJSClass(clazz: Symbol): Boolean = {
- clazz.hasAnnotation(JSTypeAnnot) &&
- !clazz.isPackageClass && !clazz.outerClass.isStaticOwner &&
- !clazz.isLocalToBlock && !clazz.isModuleClass && !clazz.isTrait
+ /** 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 =>
- val innerJSClasses = decls.filter(isInnerJSClass)
- if (innerJSClasses.isEmpty) {
+ 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) {
- innerJSClass.getAnnotation(JSNameAnnotation).fold {
- sym.addAnnotation(JSNameAnnotation,
- Literal(Constant(jsInterop.defaultJSNameOf(innerJSClass))))
- } { annot =>
- sym.addAnnotation(annot)
- }
- sym.addAnnotation(ExposedJSMemberAnnot)
- }
+ if (clazzIsJSClass)
+ addAnnots(sym, innerJSClass)
}
val accessorName: TermName =
@@ -134,7 +191,7 @@ abstract class ExplicitInnerJS[G <: Global with Singleton](val global: G)
addAnnotsIfInJSClass(accessor)
decls1.enter(accessor)
- if (!clazz.isTrait || !traitValsHoldTheirGetterSymbol) {
+ if (!clazz.isTrait) {
val fieldName = accessorName.append(nme.LOCAL_SUFFIX_STRING)
val fieldFlags =
Flags.SYNTHETIC | Flags.ARTIFACT | Flags.PrivateLocal
@@ -145,6 +202,20 @@ abstract class ExplicitInnerJS[G <: Global with Singleton](val global: G)
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)
}
@@ -168,30 +239,36 @@ abstract class ExplicitInnerJS[G <: Global with Singleton](val global: G)
/** The main transformation method. */
override def transform(tree: Tree): Tree = {
- val sym = tree.symbol
tree match {
// Add the ValDefs for inner JS class values
- case Template(parents, self, decls) =>
+ case Template(parents, self, decls) if isApplicableOwner(currentOwner) =>
val newDecls = mutable.ListBuffer.empty[Tree]
atOwner(tree, currentOwner) {
for (decl <- decls) {
- if ((decl.symbol ne null) && isInnerJSClass(decl.symbol)) {
- val clazz = decl.symbol
- val jsclassAccessor = jsclassAccessorFor(clazz)
+ 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 (sym.hasAnnotation(JSNativeAnnotation)) {
+ val rhs = if (currentOwner.hasAnnotation(JSNativeAnnotation)) {
gen.mkAttributedRef(JSPackage_native)
} else {
- val clazzValue = gen.mkClassOf(clazz.tpe_*)
- val parentTpe =
- extractSuperTpeFromImpl(decl.asInstanceOf[ClassDef].impl)
- val superClassCtor = gen.mkNullaryCall(
- JSPackage_constructorOf, List(parentTpe))
- gen.mkMethodCall(Runtime_createInnerJSClass,
- List(clazzValue, superClassCtor))
+ 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 || !traitValsHoldTheirGetterSymbol) {
+ if (!currentOwner.isTrait) {
val jsclassField = jsclassAccessor.accessed
assert(jsclassField != NoSymbol, jsclassAccessor.fullName)
newDecls += localTyper.typedValDef(ValDef(jsclassField, rhs))
@@ -202,6 +279,15 @@ abstract class ExplicitInnerJS[G <: Global with Singleton](val global: G)
} 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
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitLocalJS.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitLocalJS.scala
index deb0fe9ee6..42eff98571 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitLocalJS.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitLocalJS.scala
@@ -127,7 +127,7 @@ abstract class ExplicitLocalJS[G <: Global with Singleton](val global: G)
import global._
import jsAddons._
- import jsInterop.jsclassAccessorFor
+ import jsInterop.{jsclassAccessorFor, JSCallingConvention}
import definitions._
import rootMirror._
import jsDefinitions._
@@ -171,8 +171,12 @@ abstract class ExplicitLocalJS[G <: Global with Singleton](val global: G)
/** Is the given clazz a local JS class or object? */
private def isLocalJSClassOrObject(clazz: Symbol): Boolean = {
- def isJSLambda =
- clazz.isAnonymousClass && AllJSFunctionClasses.exists(clazz.isSubClass(_))
+ 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) &&
@@ -313,7 +317,7 @@ abstract class ExplicitLocalJS[G <: Global with Singleton](val global: G)
case Apply(fun @ Select(sup: Super, _), _)
if !fun.symbol.isConstructor &&
isInnerOrLocalJSClass(sup.symbol.superClass) =>
- wrapWithContextualJSClassValue(sup.symbol.superClass.tpe_*) {
+ wrapWithContextualSuperJSClassValue(sup.symbol.superClass) {
super.transform(tree)
}
@@ -321,7 +325,7 @@ abstract class ExplicitLocalJS[G <: Global with Singleton](val global: G)
case Apply(TypeApply(fun @ Select(sup: Super, _), _), _)
if !fun.symbol.isConstructor &&
isInnerOrLocalJSClass(sup.symbol.superClass) =>
- wrapWithContextualJSClassValue(sup.symbol.superClass.tpe_*) {
+ wrapWithContextualSuperJSClassValue(sup.symbol.superClass) {
super.transform(tree)
}
@@ -390,6 +394,38 @@ abstract class ExplicitLocalJS[G <: Global with Singleton](val global: G)
}
}
+ /** 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)) {
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala
index 45ad02344c..3839c61e8c 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala
@@ -23,9 +23,29 @@ import scala.tools.nsc._
import scala.annotation.tailrec
+import scala.reflect.internal.Flags
+
import org.scalajs.ir
-import ir.{Trees => js, Types => jstpe, ClassKind, Hashers}
-import ir.Trees.OptimizerHints
+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
@@ -38,6 +58,8 @@ 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
@@ -51,17 +73,17 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
import rootMirror._
import definitions._
import jsDefinitions._
- import jsInterop.{jsNameOf, jsNativeLoadSpecOfOption, JSName}
+ import jsInterop.{jsNameOf, jsNativeLoadSpecOfOption, JSName, JSCallingConvention}
- import treeInfo.hasSynthCaseSymbol
+ 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 when ASTs are generated */
- def generatedJSAST(clDefs: List[js.ClassDef]): Unit
+ /** 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 = {
@@ -123,6 +145,17 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
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
@@ -131,28 +164,54 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
// Scoped state ------------------------------------------------------------
// Per class body
val currentClassSym = new ScopedVar[Symbol]
- private val unexpectedMutatedFields = new ScopedVar[mutable.Set[Symbol]]
+ private val fieldsMutatedInCurrentClass = new ScopedVar[mutable.Set[Name]]
private val generatedSAMWrapperCount = new ScopedVar[VarBox[Int]]
-
- private def currentClassType = encodeClassType(currentClassSym)
+ 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 thisLocalVarIdent = new ScopedVar[Option[js.Ident]]
- private val fakeTailJumpParamRepl = new ScopedVar[(Symbol, Symbol)]
- private val enclosingLabelDefParams = new ScopedVar[Map[Symbol, List[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 countsOfReturnsToMatchCase = new ScopedVar[mutable.Map[Symbol, Int]]
- private val countsOfReturnsToMatchEnd = new ScopedVar[mutable.Map[Symbol, Int]]
private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]]
-
- // For some method bodies
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
- // These have a default, since we always read them.
- private val tryingToGenMethodAsJSFunction = new ScopedVar[Boolean](false)
+ // 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
@@ -170,11 +229,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
}
- private class CancelGenMethodAsJSFunction(message: String)
- extends scala.util.control.ControlThrowable {
- override def getMessage(): String = message
- }
-
// Rewriting of anonymous function classes ---------------------------------
/** Start nested generation of a class.
@@ -185,45 +239,156 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
private def nestedGenerateClass[T](clsSym: Symbol)(body: => T): T = {
withScopedVars(
currentClassSym := clsSym,
- unexpectedMutatedFields := mutable.Set.empty,
+ fieldsMutatedInCurrentClass := mutable.Set.empty,
generatedSAMWrapperCount := new VarBox(0),
+ delambdafyTargetDefDefs := mutable.Map.empty,
+ methodsAllowingJSAwait := mutable.Set.empty,
currentMethodSym := null,
- thisLocalVarIdent := null,
- fakeTailJumpParamRepl := null,
- enclosingLabelDefParams := null,
+ thisLocalVarName := null,
+ enclosingLabelDefInfos := null,
isModuleInitialized := null,
- countsOfReturnsToMatchCase := null,
- countsOfReturnsToMatchEnd := null,
undefinedDefaultParams := null,
mutableLocalVars := null,
mutatedLocalVars := null,
- tryingToGenMethodAsJSFunction := false,
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[(Symbol, Option[String], js.ClassDef)]
+ private val generatedClasses = ListBuffer.empty[(js.ClassDef, Position)]
+ private val generatedStaticForwarderClasses = ListBuffer.empty[(Symbol, js.ClassDef)]
private def consumeLazilyGeneratedAnonClass(sym: Symbol): ClassDef = {
- /* If we are trying to generate an method as JSFunction, we cannot
- * actually consume the symbol, since we might fail trying and retry.
- * We will then see the same tree again and not find the symbol anymore.
- *
- * If we are sure this is the only generation, we remove the symbol to
- * make sure we don't generate the same class twice.
- */
- val optDef = {
- if (tryingToGenMethodAsJSFunction)
- lazilyGeneratedAnonClasses.get(sym)
- else
- lazilyGeneratedAnonClasses.remove(sym)
- }
-
- optDef.getOrElse {
+ lazilyGeneratedAnonClasses.remove(sym).getOrElse {
abort("Couldn't find tree for lazily generated anonymous class " +
s"${sym.fullName} at ${sym.pos}")
}
@@ -233,7 +398,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
override def run(): Unit = {
scalaPrimitives.init()
- initializeCoreBTypesCompat()
+ genBCode.bTypes.initializeCoreBTypes()
jsPrimitives.init()
super.run()
}
@@ -256,11 +421,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* * Non-native JS class -> `genNonNativeJSClass()`
* * Other JS type (<: js.Any) -> `genJSClassData()`
* * Interface -> `genInterface()`
- * * Implementation class -> `genImplClass()`
* * 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
@@ -270,39 +437,26 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
val allClassDefs = collectClassDefs(cunit.body)
- /* There are three types of anonymous classes we want to generate
- * only once we need them so we can inline them at construction site:
+ /* There are two types of anonymous classes we want to generate only
+ * once we need them, so we can inline them at construction site:
*
- * - anonymous class that are JS types, which includes:
- * - lambdas for js.FunctionN and js.ThisFunctionN (SAMs). (We may
- * not generate actual Scala classes for these).
- * - anonymous (non-lambda) JS classes. These classes may not have
- * their own prototype. Therefore, their constructor *must* be
- * inlined.
- * - lambdas for scala.FunctionN. This is only an optimization and may
- * fail. In the case of failure, we fall back to generating a
- * fully-fledged Scala class.
+ * - 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, fullClassDefs0) = allClassDefs.partition { cd =>
+ val (lazyAnons, fullClassDefs) = allClassDefs.partition { cd =>
val sym = cd.symbol
- (sym.isAnonymousClass && isJSType(sym)) || sym.isAnonymousFunction
+ isAnonymousJSClass(sym) || isJSFunctionDef(sym)
}
lazilyGeneratedAnonClasses ++= lazyAnons.map(cd => cd.symbol -> cd)
- /* Under Scala 2.11 with -Xexperimental, anonymous JS function classes
- * can be referred to in private method signatures, which means they
- * must exist at the IR level, as `AbstractJSType`s.
- */
- val fullClassDefs = if (isScala211WithXexperimental) {
- lazyAnons.filter(cd => isJSFunctionDef(cd.symbol)) ::: fullClassDefs0
- } else {
- fullClassDefs0
- }
-
/* Finally, we emit true code for the remaining class defs. */
for (cd <- fullClassDefs) {
val sym = cd.symbol
@@ -312,64 +466,155 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val isPrimitive =
isPrimitiveValueClass(sym) || (sym == ArrayClass)
- if (!isPrimitive && !isJSImplClass(sym)) {
+ if (!isPrimitive) {
withScopedVars(
- currentClassSym := sym,
- unexpectedMutatedFields := mutable.Set.empty,
- generatedSAMWrapperCount := new VarBox(0)
+ currentClassSym := sym,
+ fieldsMutatedInCurrentClass := mutable.Set.empty,
+ generatedSAMWrapperCount := new VarBox(0),
+ delambdafyTargetDefDefs := mutable.Map.empty,
+ methodsAllowingJSAwait := mutable.Set.empty
) {
- try {
- val tree = if (isJSType(sym)) {
- if (!sym.isTraitOrInterface && isNonNativeJSClass(sym) &&
- !isJSFunctionDef(sym)) {
- genNonNativeJSClass(cd)
- } else {
- genJSClassData(cd)
- }
- } else if (sym.isTraitOrInterface) {
- genInterface(cd)
- } else if (isImplClass(sym)) {
- genImplClass(cd)
+ val tree = if (isJSType(sym)) {
+ if (!sym.isTraitOrInterface && isNonNativeJSClass(sym) &&
+ !isJSFunctionDef(sym)) {
+ genNonNativeJSClass(cd)
} else {
- genClass(cd)
+ genJSClassData(cd)
}
+ } else if (sym.isTraitOrInterface) {
+ genInterface(cd)
+ } else {
+ genClass(cd)
+ }
- generatedClasses += ((sym, None, tree))
- } catch {
- case e: ir.InvalidIRException =>
- e.tree match {
- case ir.Trees.Transient(UndefinedParam) =>
- reporter.error(sym.pos,
- "Found a dangling UndefinedParam at " +
- s"${e.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.")
+ generatedClasses += tree -> sym.pos
+ }
+ }
+ }
- case _ =>
- reporter.error(sym.pos,
- "The Scala.js compiler generated invalid IR for " +
- "this class. Please report this as a bug. IR: " +
- e.tree)
- }
+ 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
}
- val clDefs = generatedClasses.map(_._3).toList
- generatedJSAST(clDefs)
+ 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)
- for ((sym, suffix, tree) <- generatedClasses) {
- genIRFile(cunit, sym, suffix, 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).
@@ -379,7 +624,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val sym = cd.symbol
implicit val pos = sym.pos
- assert(!sym.isTraitOrInterface && !isImplClass(sym),
+ assert(!sym.isTraitOrInterface,
"genClass() must be called only for normal classes: "+sym)
assert(sym.superClass != NoSymbol, sym)
@@ -390,11 +635,14 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
"if their companion module is JS native.")
}
- val classIdent = encodeClassFullNameIdent(sym)
+ 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("$")) ||
@@ -402,6 +650,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
val shouldMarkInline = (
+ isDynamicImportThunk ||
sym.hasAnnotation(InlineAnnotationClass) ||
(sym.isAnonymousFunction && !sym.isSubClass(PartialFunctionClass)) ||
isStdLibClassWithAdHocInlineAnnot(sym))
@@ -413,44 +662,24 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
// Generate members (constructor + methods)
- val generatedMethods = new ListBuffer[js.MethodDef]
-
- 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 =>
- generatedMethods ++= genMethod(dd)
+ val methodsBuilder = List.newBuilder[js.MethodDef]
+ val jsNativeMembersBuilder = List.newBuilder[js.JSNativeMemberDef]
- case _ => abort("Illegal tree in gen of genClass(): " + tree)
- }
+ for (dd <- collectDefDefs(impl)) {
+ if (dd.symbol.hasAnnotation(JSNativeAnnotation))
+ jsNativeMembersBuilder += genJSNativeMemberDef(dd)
+ else
+ methodsBuilder ++= genMethod(dd)
}
- gen(impl)
+ val fields = if (!isHijacked) genClassFields(cd) else Nil
- // Generate fields if necessary (and add to methods + ctors)
- val generatedMembers =
- if (!isHijacked) genClassFields(cd) ++ generatedMethods.toList
- else generatedMethods.toList // No fields needed
+ val jsNativeMembers = jsNativeMembersBuilder.result()
+ val generatedMethods = methodsBuilder.result()
- // Generate member exports
val memberExports = genMemberExports(sym)
- // Generate the exported members, constructors and accessors
- val topLevelExportDefs = {
- // Generate exported constructors or accessors
- val exportedConstructorsOrAccessors =
- if (isStaticModule(sym)) genModuleAccessorExports(sym)
- else genConstructorExports(sym)
-
- val topLevelExports = genTopLevelExports(sym)
-
- exportedConstructorsOrAccessors ++ topLevelExports
- }
+ val topLevelExportDefs = genTopLevelExports(sym)
// Static initializer
val optStaticInitializer = {
@@ -476,15 +705,60 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val staticInitializerStats =
reflectInit.toList ::: staticModuleInit.toList
- if (staticInitializerStats.nonEmpty)
- Some(genStaticInitializerWithStats(js.Block(staticInitializerStats)))
- else
- None
+ if (staticInitializerStats.nonEmpty) {
+ List(genStaticConstructorWithStats(
+ jswkn.StaticInitializerName,
+ js.Block(staticInitializerStats)))
+ } else {
+ Nil
+ }
}
- // Hashed definitions of the class
- val hashedMemberDefs =
- Hashers.hashMemberDefs(generatedMembers ++ memberExports ++ optStaticInitializer)
+ 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 =
@@ -492,19 +766,22 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
else if (isHijacked) ClassKind.HijackedClass
else ClassKind.Class
- val classDefinition = js.ClassDef(
+ js.ClassDef(
classIdent,
+ originalName,
kind,
None,
- Some(encodeClassFullNameIdent(sym.superClass)),
+ Some(encodeClassNameIdent(sym.superClass)),
genClassInterfaces(sym, forJSClass = false),
None,
None,
- hashedMemberDefs,
+ fields,
+ allMethods,
+ jsConstructor = None,
+ memberExports,
+ jsNativeMembers,
topLevelExportDefs)(
optimizerHints)
-
- classDefinition
}
/** Gen the IR ClassDef for a non-native JS class. */
@@ -517,7 +794,14 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
s"non-native JS classes: $sym")
assert(sym.superClass != NoSymbol, sym)
- val classIdent = encodeClassFullNameIdent(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)
@@ -525,46 +809,33 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val generatedMethods = new ListBuffer[js.MethodDef]
val dispatchMethodNames = new ListBuffer[JSName]
- 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()
+ for (dd <- collectDefDefs(cd.impl)) {
+ val sym = dd.symbol
+ val exposed = isExposed(sym)
- case dd: DefDef =>
- 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)
- }
- }
+ 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)
- case _ => abort("Illegal tree in gen of genClass(): " + tree)
+ // 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)
+ }
}
}
- gen(cd.impl)
-
// Static members (exported from the companion object)
- val staticMembers = {
+ 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
@@ -572,78 +843,91 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val companionModuleClass =
exitingPhase(currentRun.picklerPhase)(sym.linkedClassOfClass)
if (companionModuleClass == NoSymbol) {
- Nil
+ (Nil, Nil)
} else {
- val exports = withScopedVars(currentClassSym := companionModuleClass) {
- genStaticExports(companionModuleClass)
+ val (staticFields, staticExports) = {
+ withScopedVars(currentClassSym := companionModuleClass) {
+ genStaticExports(companionModuleClass)
+ }
}
- if (exports.exists(_.isInstanceOf[js.FieldDef])) {
- val staticInitializer =
- genStaticInitializerWithStats(genLoadModule(companionModuleClass))
- exports :+ staticInitializer
- } else {
- exports
+
+ if (staticFields.nonEmpty) {
+ generatedMethods += genStaticConstructorWithStats(
+ jswkn.ClassInitializerName, genLoadModule(companionModuleClass))
}
+
+ (staticFields, staticExports)
}
}
- // Generate top-level exporters
- val topLevelExports =
- if (isStaticModule(sym)) genModuleAccessorExports(sym)
- else genJSClassExports(sym)
+ val topLevelExports = genTopLevelExports(sym)
- val (jsClassCaptures, generatedConstructor) =
- genJSClassCapturesAndConstructor(sym, constructorTrees.toList)
+ val (generatedCtor, jsClassCaptures) = withNewLocalNameScope {
+ val isNested = isNestedJSClass(sym)
- /* If there is one, the JS super class value is always the first JS class
- * capture. This is a GenJSCode-specific invariant (the IR does not rely
- * on this) enforced in genJSClassCapturesAndConstructor.
- */
- val jsSuperClass = jsClassCaptures.map(_.head.ref)
+ if (isNested)
+ reserveLocalName(JSSuperClassParamName)
- // Generate fields (and add to methods + ctors)
- val generatedMembers = {
- genClassFields(cd) :::
- generatedConstructor ::
- genJSClassDispatchers(sym, dispatchMethodNames.result().distinct) :::
- generatedMethods.toList :::
- staticMembers
+ 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)
}
- // Hashed definitions of the class
- val hashedMemberDefs =
- Hashers.hashMemberDefs(generatedMembers)
+ // 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
- val classDefinition = js.ClassDef(
+ js.ClassDef(
classIdent,
+ originalNameOfClass(sym),
kind,
jsClassCaptures,
- Some(encodeClassFullNameIdent(sym.superClass)),
+ Some(encodeClassNameIdent(sym.superClass)),
genClassInterfaces(sym, forJSClass = true),
- jsSuperClass,
+ jsSuperClass = jsClassCaptures.map(_.head.ref),
None,
- hashedMemberDefs,
+ fields ::: staticFields,
+ generatedMethods.toList,
+ Some(generatedCtor),
+ jsMethodProps,
+ jsNativeMembers = Nil,
topLevelExports)(
OptimizerHints.empty)
-
- classDefinition
}
/** 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 constructor
+ * @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.TreeOrJSSpread], pos: Position): js.Tree = {
- assert(sym.isAnonymousClass && !isJSFunctionDef(sym),
+ 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
@@ -654,158 +938,183 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
nestedGenerateClass(sym)(genNonNativeJSClass(classDef))
// Partition class members.
- val staticMembers = ListBuffer.empty[js.MemberDef]
- val classMembers = ListBuffer.empty[js.MemberDef]
- var constructor: Option[js.MethodDef] = None
+ val privateFieldDefs = ListBuffer.empty[js.FieldDef]
+ val jsFieldDefs = ListBuffer.empty[js.JSFieldDef]
- origJsClass.memberDefs.foreach {
+ origJsClass.fields.foreach {
case fdef: js.FieldDef =>
- classMembers += fdef
-
- case mdef: js.MethodDef =>
- mdef.name match {
- case _: js.Ident =>
- assert(mdef.flags.namespace.isStatic,
- "Non-static, unexported method in non-native JS class")
- staticMembers += mdef
-
- case js.StringLiteral("constructor") =>
- assert(!mdef.flags.namespace.isStatic, "Exported static method")
- assert(constructor.isEmpty, "two ctors in class")
- constructor = Some(mdef)
-
- case _ =>
- assert(!mdef.flags.namespace.isStatic, "Exported static method")
- classMembers += mdef
- }
+ privateFieldDefs += fdef
- case property: js.PropertyDef =>
- classMembers += property
+ 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 only
+ // Make new class def with static members
val newClassDef = {
implicit val pos = origJsClass.pos
- val parent = js.Ident(ir.Definitions.ObjectClass)
- js.ClassDef(origJsClass.name, ClassKind.AbstractJSType, None,
- Some(parent), interfaces = Nil, jsSuperClass = None,
- jsNativeLoadSpec = None, staticMembers.toList, Nil)(
+ 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 += ((sym, None, newClassDef))
+ generatedClasses += newClassDef -> pos
// Construct inline class definition
- val js.MethodDef(_, _, ctorParams, _, Some(ctorBody)) =
- constructor.getOrElse(throw new AssertionError("No ctor found"))
- val jsSuperClassParam = js.ParamDef(freshLocalIdent("super")(pos),
- jstpe.AnyType, mutable = false, rest = false)(pos)
- def jsSuperClassRef(implicit pos: ir.Position) =
- jsSuperClassParam.ref
+ 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
- val selfName = freshLocalIdent("this")(pos)
+ /* 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(selfName)(jstpe.AnyType)
+ js.VarRef(selfIdent.name)(jstpe.AnyType)
- def memberLambda(params: List[js.ParamDef], body: js.Tree)(
- implicit pos: ir.Position) = {
- js.Closure(arrow = false, captureParams = Nil, params, body,
- captureValues = Nil)
+ 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 memberDefinitions = classMembers.toList.map {
- case fdef: js.FieldDef =>
- implicit val pos = fdef.pos
- val select = fdef.name match {
- case ident: js.Ident => js.JSDotSelect(selfRef, ident)
- case lit: js.StringLiteral => js.JSBracketSelect(selfRef, lit)
- case js.ComputedName(tree, _) => js.JSBracketSelect(selfRef, tree)
- }
- js.Assign(select, jstpe.zeroOf(fdef.ftpe))
+ val fieldDefinitions = jsFieldDefs.toList.map { fdef =>
+ implicit val pos = fdef.pos
+ js.Assign(js.JSSelect(selfRef, fdef.name), jstpe.zeroOf(fdef.ftpe))
+ }
- case mdef: js.MethodDef =>
+ val memberDefinitions0 = origJsClass.jsMethodProps.toList.map {
+ case mdef: js.JSMethodDef =>
implicit val pos = mdef.pos
- val name = mdef.name.asInstanceOf[js.StringLiteral]
- val impl = memberLambda(mdef.args, mdef.body.getOrElse(
- throw new AssertionError("Got anon SJS class with abstract method")))
- js.Assign(js.JSBracketSelect(selfRef, name), impl)
+ val impl = memberLambda(mdef.args, mdef.restParam, mdef.body)
+ js.Assign(js.JSSelect(selfRef, mdef.name), impl)
- case pdef: js.PropertyDef =>
+ case pdef: js.JSPropertyDef =>
implicit val pos = pdef.pos
- val name = pdef.name.asInstanceOf[js.StringLiteral]
- val jsObject = js.JSGlobalRef(js.Ident("Object"))
-
- def field(name: String, value: js.Tree) =
- List(js.StringLiteral(name) -> value)
-
- val optGetter = pdef.getterBody map { body =>
- js.StringLiteral("get") -> memberLambda(params = Nil, body)
+ 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, 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 ++
+ optGetter.toList :::
+ optSetter.toList :::
List(js.StringLiteral("configurable") -> js.BooleanLiteral(true))
)
+ js.JSMethodApply(js.JSGlobalRef("Object"),
+ js.StringLiteral("defineProperty"),
+ List(selfRef, pdef.name, descriptor))
+ }
- js.JSBracketMethodApply(jsObject, js.StringLiteral("defineProperty"),
- List(selfRef, name, descriptor))
+ val memberDefinitions1 = fieldDefinitions ::: memberDefinitions0
- case tree =>
- abort("Unexpected tree: " + tree)
+ 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 = new ir.Transformers.Transformer {
- override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match {
- // The super constructor call. Transform this into a simple new call.
- case js.JSSuperConstructorCall(args) =>
- implicit val pos = tree.pos
-
- val newTree = {
- val ident =
- origJsClass.superClass.getOrElse(abort("No superclass"))
- if (args.isEmpty && ident.name == "sjs_js_Object")
- js.JSObjectConstr(Nil)
- else
- js.JSNew(jsSuperClassRef, args)
- }
-
- js.Block(
- js.VarDef(selfName, jstpe.AnyType, mutable = false, newTree) ::
- memberDefinitions)(NoPosition)
-
- case js.This() => selfRef(tree.pos)
-
- // Don't traverse closure boundaries
- case closure: js.Closure =>
- val newCaptureValues = closure.captureValues.map(transformExpr)
- closure.copy(captureValues = newCaptureValues)(closure.pos)
+ 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)
+ }
- case tree =>
- super.transform(tree, isStat)
+ val selfVarDef = js.VarDef(selfIdent.copy(), // copy for the correct `pos`
+ thisOriginalName, jstpe.AnyType, mutable = false, newTree)
+ selfVarDef :: memberDefinitions
}
- }.transform(ctorBody, isStat = true)
-
- val invocation = {
- implicit val invocationPosition = pos
- val closure = js.Closure(arrow = true, Nil,
- jsSuperClassParam :: ctorParams,
- js.Block(inlinedCtorStats, selfRef), Nil)
+ // 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)
- js.JSFunctionApply(closure, jsSuperClassValue :: args)
+ beforeSuper ::: superCall ::: afterSuper
}
- invocation
+ // 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 -----------------------------------
@@ -816,7 +1125,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val sym = cd.symbol
implicit val pos = sym.pos
- val classIdent = encodeClassFullNameIdent(sym)
+ val classIdent = encodeClassNameIdent(sym)
val kind = {
if (sym.isTraitOrInterface) ClassKind.AbstractJSType
else if (isJSFunctionDef(sym)) ClassKind.AbstractJSType
@@ -825,12 +1134,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
val superClass =
if (sym.isTraitOrInterface) None
- else Some(encodeClassFullNameIdent(sym.superClass))
+ else Some(encodeClassNameIdent(sym.superClass))
val jsNativeLoadSpec = jsNativeLoadSpecOfOption(sym)
- js.ClassDef(classIdent, kind, None, superClass,
+ js.ClassDef(classIdent, originalNameOfClass(sym), kind, None, superClass,
genClassInterfaces(sym, forJSClass = true), None, jsNativeLoadSpec,
- Nil, Nil)(
+ Nil, Nil, None, Nil, Nil, Nil)(
OptimizerHints.empty)
}
@@ -842,74 +1151,26 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val sym = cd.symbol
implicit val pos = sym.pos
- val classIdent = encodeClassFullNameIdent(sym)
-
- // fill in class info builder
- def gen(tree: Tree): List[js.MethodDef] = {
- tree match {
- case EmptyTree => Nil
- case Template(_, _, body) => body.flatMap(gen)
-
- case dd: DefDef =>
- genMethod(dd).toList
+ val classIdent = encodeClassNameIdent(sym)
- case _ =>
- abort("Illegal tree in gen of genInterface(): " + tree)
- }
- }
- val generatedMethods = gen(cd.impl)
+ val generatedMethods = collectDefDefs(cd.impl).flatMap(genMethod(_))
val interfaces = genClassInterfaces(sym, forJSClass = false)
- // Hashed definitions of the interface
- val hashedMemberDefs =
- Hashers.hashMemberDefs(generatedMethods)
-
- js.ClassDef(classIdent, ClassKind.Interface, None, None, interfaces, None,
- None, hashedMemberDefs, Nil)(OptimizerHints.empty)
- }
-
- // Generate an implementation class of a trait -----------------------------
-
- /** Gen the IR ClassDef for an implementation class (of a trait).
- */
- def genImplClass(cd: ClassDef): js.ClassDef = {
- val ClassDef(mods, name, _, impl) = cd
- val sym = cd.symbol
- implicit val pos = sym.pos
-
- def gen(tree: Tree): List[js.MethodDef] = {
- tree match {
- case EmptyTree => Nil
- case Template(_, _, body) => body.flatMap(gen)
-
- case dd: DefDef =>
- assert(!dd.symbol.isDeferred,
- s"Found an abstract method in an impl class at $pos: ${dd.symbol.fullName}")
- val m = genMethod(dd)
- m.toList
+ val allMemberDefs =
+ if (!isCandidateForForwarders(sym)) generatedMethods
+ else generatedMethods ::: genStaticForwardersForClassOrInterface(generatedMethods, sym)
- case _ => abort("Illegal tree in gen of genImplClass(): " + tree)
- }
- }
- val generatedMethods = gen(impl)
-
- val classIdent = encodeClassFullNameIdent(sym)
- val objectClassIdent = encodeClassFullNameIdent(ObjectClass)
-
- // Hashed definitions of the impl class
- val hashedMemberDefs =
- Hashers.hashMemberDefs(generatedMethods)
-
- js.ClassDef(classIdent, ClassKind.Class, None,
- Some(objectClassIdent), Nil, None, None,
- hashedMemberDefs, Nil)(OptimizerHints.empty)
+ 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.Ident] = {
+ implicit pos: Position): List[js.ClassIdent] = {
val blacklist =
if (forJSClass) jsTypeInterfacesBlacklist
@@ -921,38 +1182,159 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
_ = assert(typeSym != NoSymbol, "parent needs symbol")
if typeSym.isTraitOrInterface && !blacklist.contains(typeSym)
} yield {
- encodeClassFullNameIdent(typeSym)
+ encodeClassNameIdent(typeSym)
}
}
- // Generate the fields of a class ------------------------------------------
+ // Static forwarders -------------------------------------------------------
- /** Gen definitions for the fields of a class.
+ /* 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.FieldDef] = {
+ 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)
- def isStaticBecauseOfTopLevelExport(f: Symbol): Boolean =
- jsInterop.registeredExportsOf(f).head.destination == ExportDestination.TopLevel
-
// 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)
- static = jsInterop.isFieldStatic(f)
- if !static || isStaticBecauseOfTopLevelExport(f)
+ 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
- suspectFieldMutable(f) || unexpectedMutatedFields.contains(f)
+ f.isMutable || // mutable fields can be mutated from anywhere
+ fieldsMutatedInCurrentClass.contains(f.name) // the field is mutated in the current class
}
val namespace =
@@ -961,17 +1343,21 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val flags =
js.MemberFlags.empty.withNamespace(namespace).withMutable(mutable)
- val name =
- if (isJSClass && isExposed(f)) genPropertyName(jsNameOf(f))
- else encodeFieldSym(f)
-
- val irTpe = {
+ val irTpe0 = {
if (isJSClass) genExposedFieldIRType(f)
else if (static) jstpe.AnyType
else toIRType(f.tpe)
}
- js.FieldDef(flags, name, irTpe)
+ // #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
}
@@ -991,7 +1377,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* Anyway, scalac also has problems with uninitialized value
* class values, if they come from a generic context.
*/
- jstpe.ClassType(encodeClassFullName(tpe.valueClazz))
+ jstpe.ClassType(encodeClassName(tpe.valueClazz), nullable = true)
case _ =>
/* Other types are not boxed, so we can initialize them to
@@ -1003,15 +1389,16 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
// Static initializers -----------------------------------------------------
- private def genStaticInitializerWithStats(stats: js.Tree)(
+ private def genStaticConstructorWithStats(name: MethodName, stats: js.Tree)(
implicit pos: Position): js.MethodDef = {
js.MethodDef(
js.MemberFlags.empty.withNamespace(js.MemberNamespace.StaticConstructor),
- js.Ident(ir.Definitions.StaticInitializerName),
+ js.MethodIdent(name),
+ NoOriginalName,
Nil,
- jstpe.NoType,
+ jstpe.VoidType,
Some(stats))(
- OptimizerHints.empty, None)
+ OptimizerHints.empty, Unversioned)
}
private def genRegisterReflectiveInstantiation(sym: Symbol)(
@@ -1031,7 +1418,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val fqcnArg = js.StringLiteral(sym.fullName + "$")
val runtimeClassArg = js.ClassOf(toTypeRef(sym.info))
val loadModuleFunArg =
- js.Closure(arrow = true, Nil, Nil, genLoadModule(sym), Nil)
+ js.Closure(js.ClosureFlags.arrow, Nil, Nil, None, jstpe.AnyType, genLoadModule(sym), Nil)
val stat = genApplyMethod(
genLoadModule(ReflectModule),
@@ -1074,17 +1461,15 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* reflectively, it must be given an `Int` (or `Integer`).
*/
val paramType = js.ClassOf(toTypeRef(param.tpe))
- val paramDef = js.ParamDef(encodeLocalSym(param), jstpe.AnyType,
- mutable = false, rest = false)
+ 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(arrow = true, Nil, formalParams, {
- genNew(sym, ctor, actualParams)
- }, Nil)
+ val newInstanceFun = js.Closure(js.ClosureFlags.arrow, Nil,
+ formalParams, None, jstpe.AnyType, genNew(sym, ctor, actualParams), Nil)
js.JSArrayConstr(List(paramTypesArray, newInstanceFun))
}
@@ -1105,412 +1490,530 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
// Constructor of a non-native JS class ------------------------------
- def genJSClassCapturesAndConstructor(classSym: Symbol,
- constructorTrees: List[DefDef]): (Option[List[js.ParamDef]], js.MethodDef) = {
- implicit val pos = classSym.pos
+ 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.
+ */
- if (hasDefaultCtorArgsAndJSModule(classSym)) {
- reporter.error(pos,
- "Implementation restriction: constructors of " +
- "non-native JS classes cannot have default parameters " +
- "if their companion module is JS native.")
- val ctorDef = js.MethodDef(js.MemberFlags.empty,
- js.StringLiteral("constructor"), Nil, jstpe.AnyType,
- Some(js.Skip()))(
- OptimizerHints.empty, None)
- (None, ctorDef)
- } else {
- withNewLocalNameScope {
- reserveLocalName(JSSuperClassParamName)
+ val (primaryTree :: Nil, secondaryTrees) =
+ constructorTrees.partition(_.symbol.isPrimaryConstructor)
- val ctors: List[js.MethodDef] = constructorTrees.flatMap { tree =>
- genMethodWithCurrentLocalNameScope(tree)
- }
+ val primaryCtor = genPrimaryJSClassCtor(primaryTree)
+ val secondaryCtors = secondaryTrees.map(genSecondaryJSClassCtor(_))
- val (captureParams, dispatch) =
- genJSConstructorExport(constructorTrees.map(_.symbol))
+ // 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)
+ }
- /* Ensure that the first JS class capture is a reference to the JS
- * super class value. genNonNativeJSClass relies on this.
- */
- val captureParamsWithJSSuperClass = captureParams.map { params =>
- val jsSuperClassParam = js.ParamDef(js.Ident(JSSuperClassParamName),
- jstpe.AnyType, mutable = false, rest = false)
- jsSuperClassParam :: params
- }
+ /* 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)
+ }
- val ctorDef = buildJSConstructorDef(dispatch, ctors)
+ subTree(primaryCtor)
+ }
- (captureParamsWithJSSuperClass, ctorDef)
+ /* 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(_))
}
- }
- }
- private def buildJSConstructorDef(dispatch: js.MethodDef,
- ctors: List[js.MethodDef])(
- implicit pos: Position): js.MethodDef = {
+ add(ctorTree)
- val js.MethodDef(_, dispatchName, dispatchArgs, dispatchResultType,
- Some(dispatchResolution)) = dispatch
+ (exports.result(), jsClassCaptures.result())
+ }
- val jsConstructorBuilder = mkJSConstructorBuilder(ctors)
+ // The name 'constructor' is used for error reporting here
+ val (formalArgs, restParam, overloadDispatchBody) =
+ genOverloadDispatch(JSName.Literal("constructor"), exports, jstpe.IntType)
- val overloadIdent = freshLocalIdent("overload")
+ val overloadVar = js.VarDef(freshLocalIdent("overload"), NoOriginalName,
+ jstpe.IntType, mutable = false, overloadDispatchBody)
- // Section containing the overload resolution and casts of parameters
- val overloadSelection = mkOverloadSelection(jsConstructorBuilder,
- overloadIdent, dispatchResolution)
+ val constructorBody = wrapJSCtorBody(
+ paramVarDefs :+ overloadVar,
+ genJSClassCtorBody(overloadVar.ref, ctorTree),
+ js.Undefined() :: Nil
+ )
- /* Section containing all the code executed before the call to `this`
- * for every secondary constructor.
- */
- val prePrimaryCtorBody =
- jsConstructorBuilder.mkPrePrimaryCtorBody(overloadIdent)
+ val constructorDef = js.JSConstructorDef(
+ js.MemberFlags.empty.withNamespace(js.MemberNamespace.Constructor),
+ formalArgs, restParam, constructorBody)(OptimizerHints.empty, Unversioned)
- val primaryCtorBody = jsConstructorBuilder.primaryCtorBody
+ (jsClassCaptures, constructorDef)
+ }
- /* Section containing all the code executed after the call to this for
- * every secondary constructor.
- */
- val postPrimaryCtorBody =
- jsConstructorBuilder.mkPostPrimaryCtorBody(overloadIdent)
+ 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")
- val newBody = js.Block(overloadSelection ::: prePrimaryCtorBody ::
- primaryCtorBody :: postPrimaryCtorBody :: Nil)
+ var preSuperStats = List.newBuilder[js.Tree]
+ var jsSuperCall: Option[js.JSSuperConstructorCall] = None
+ val postSuperStats = mutable.ListBuffer.empty[js.Tree]
- js.MethodDef(js.MemberFlags.empty, dispatchName, dispatchArgs,
- jstpe.NoType, Some(newBody))(
- dispatch.optimizerHints, None)
- }
+ /* 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
+ }
- private class ConstructorTree(val overrideNum: Int, val method: js.MethodDef,
- val subConstructors: List[ConstructorTree]) {
+ 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)))
- lazy val overrideNumBounds: (Int, Int) =
- if (subConstructors.isEmpty) (overrideNum, overrideNum)
- else (subConstructors.head.overrideNumBounds._1, overrideNum)
+ case tree if jsSuperCall.isDefined =>
+ // Once we're past the super constructor call, everything goes after.
+ postSuperStats += genStat(tree)
- def get(methodName: String): Option[ConstructorTree] = {
- if (methodName == this.method.name.encodedName) {
- Some(this)
- } else {
- subConstructors.iterator.map(_.get(methodName)).collectFirst {
- case Some(node) => node
- }
- }
- }
+ 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)
- def getParamRefs(implicit pos: Position): List[js.VarRef] =
- method.args.map(_.ref)
+ 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)
- def getAllParamDefsAsVars(implicit pos: Position): List[js.VarDef] = {
- val localDefs = method.args.map { pDef =>
- js.VarDef(pDef.name, pDef.ptpe, mutable = true, jstpe.zeroOf(pDef.ptpe))
+ case stat =>
+ // Other statements are left before.
+ preSuperStats += genStat(stat)
}
- localDefs ++ subConstructors.flatMap(_.getAllParamDefsAsVars)
}
- }
-
- private class JSConstructorBuilder(root: ConstructorTree) {
- def primaryCtorBody: js.Tree = root.method.body.getOrElse(
- throw new AssertionError("Found abstract constructor"))
+ assert(jsSuperCall.isDefined, "Did not find Super call in primary JS " +
+ s"construtor at ${dd.pos}")
- def hasSubConstructors: Boolean = root.subConstructors.nonEmpty
+ /* 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
- def getOverrideNum(methodName: String): Int =
- root.get(methodName).fold(-1)(_.overrideNum)
+ new PrimaryJSCtor(sym, genParamsAndInfo(sym, vparamss),
+ js.JSConstructorBody(preSuperStats.result(), jsSuperCall.get, postSuperStats.result())(dd.pos))
+ }
- def getParamRefsFor(methodName: String)(implicit pos: Position): List[js.VarRef] =
- root.get(methodName).fold(List.empty[js.VarRef])(_.getParamRefs)
+ 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")
- def getAllParamDefsAsVars(implicit pos: Position): List[js.VarDef] =
- root.getAllParamDefsAsVars
+ val beforeThisCall = List.newBuilder[js.Tree]
+ var thisCall: Option[(Symbol, List[js.Tree])] = None
+ val afterThisCall = List.newBuilder[js.Tree]
- def mkPrePrimaryCtorBody(overrideNumIdent: js.Ident)(
- implicit pos: Position): js.Tree = {
- val overrideNumRef = js.VarRef(overrideNumIdent)(jstpe.IntType)
- mkSubPreCalls(root, overrideNumRef)
- }
+ 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}")
- def mkPostPrimaryCtorBody(overrideNumIdent: js.Ident)(
- implicit pos: Position): js.Tree = {
- val overrideNumRef = js.VarRef(overrideNumIdent)(jstpe.IntType)
- js.Block(mkSubPostCalls(root, overrideNumRef))
- }
+ implicit val pos = tree.pos
+ val sym = fun.symbol
+ thisCall = Some((sym, genActualArgs(sym, args)))
- private def mkSubPreCalls(constructorTree: ConstructorTree,
- overrideNumRef: js.VarRef)(implicit pos: Position): js.Tree = {
- val overrideNumss = constructorTree.subConstructors.map(_.overrideNumBounds)
- val paramRefs = constructorTree.getParamRefs
- val bodies = constructorTree.subConstructors.map { constructorTree =>
- mkPrePrimaryCtorBodyOnSndCtr(constructorTree, overrideNumRef, paramRefs)
- }
- overrideNumss.zip(bodies).foldRight[js.Tree](js.Skip()) {
- case ((numBounds, body), acc) =>
- val cond = mkOverrideNumsCond(overrideNumRef, numBounds)
- js.If(cond, body, acc)(jstpe.BooleanType)
+ case stat =>
+ val jsStat = genStat(stat)
+ if (thisCall.isEmpty)
+ beforeThisCall += jsStat
+ else
+ afterThisCall += jsStat
}
}
- private def mkPrePrimaryCtorBodyOnSndCtr(constructorTree: ConstructorTree,
- overrideNumRef: js.VarRef, outputParams: List[js.VarRef])(
- implicit pos: Position): js.Tree = {
- val subCalls =
- mkSubPreCalls(constructorTree, overrideNumRef)
+ val Some((targetCtor, ctorArgs)) = thisCall
- val preSuperCall = {
- def checkForUndefinedParams(args: List[js.Tree]): List[js.Tree] = {
- def isUndefinedParam(tree: js.Tree): Boolean = tree match {
- case js.Transient(UndefinedParam) => true
- case _ => false
- }
+ new SplitSecondaryJSCtor(sym, genParamsAndInfo(sym, vparamss),
+ beforeThisCall.result(), targetCtor, ctorArgs, afterThisCall.result())
+ }
- if (!args.exists(isUndefinedParam)) {
- args
- } else {
- /* If we find an undefined param here, we're in trouble, because
- * the handling of a default param for the target constructor has
- * already been done during overload resolution. If we store an
- * `undefined` now, it will fall through without being properly
- * processed.
- *
- * Since this seems very tricky to deal with, and a pretty rare
- * use case (with a workaround), we emit an "implementation
- * restriction" error.
- */
- reporter.error(pos,
- "Implementation restriction: in a JS class, a secondary " +
- "constructor calling another constructor with default " +
- "parameters must provide the values of all parameters.")
+ private def genParamsAndInfo(ctorSym: Symbol,
+ vparamss: List[List[ValDef]]): List[(js.VarRef, JSParamInfo)] = {
+ implicit val pos = ctorSym.pos
- /* Replace undefined params by undefined to prevent subsequent
- * compiler crashes.
- */
- args.map { arg =>
- if (isUndefinedParam(arg))
- js.Undefined()(arg.pos)
- else
- arg
- }
- }
- }
-
- constructorTree.method.body.get match {
- case js.Block(stats) =>
- val beforeSuperCall = stats.takeWhile {
- case js.ApplyStatic(_, _, mtd, _) =>
- !ir.Definitions.isConstructorName(mtd.name)
- case _ =>
- true
- }
- val superCallParams = stats.collectFirst {
- case js.ApplyStatic(_, _, mtd, js.This() :: args)
- if ir.Definitions.isConstructorName(mtd.name) =>
- val checkedArgs = checkForUndefinedParams(args)
- zipMap(outputParams, checkedArgs)(js.Assign(_, _))
- }.getOrElse(Nil)
+ val paramSyms = if (vparamss.isEmpty) Nil else vparamss.head.map(_.symbol)
- beforeSuperCall ::: superCallParams
+ for {
+ (paramSym, info) <- paramSyms.zip(jsParamInfos(ctorSym))
+ } yield {
+ genVarRef(paramSym) -> info
+ }
+ }
- case js.ApplyStatic(_, _, mtd, js.This() :: args)
- if ir.Definitions.isConstructorName(mtd.name) =>
- val checkedArgs = checkForUndefinedParams(args)
- zipMap(outputParams, checkedArgs)(js.Assign(_, _))
+ private def genJSClassCtorDispatch(ctorSym: Symbol,
+ allParamsAndInfos: List[(js.VarRef, JSParamInfo)],
+ overloadNum: Int): (Exported, List[js.ParamDef]) = {
+ implicit val pos = ctorSym.pos
- case _ => Nil
- }
- }
+ /* `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)
- js.Block(subCalls :: preSuperCall)
- }
+ /* 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))
- private def mkSubPostCalls(constructorTree: ConstructorTree,
- overrideNumRef: js.VarRef)(implicit pos: Position): js.Tree = {
- val overrideNumss = constructorTree.subConstructors.map(_.overrideNumBounds)
- val bodies = constructorTree.subConstructors.map { ct =>
- mkPostPrimaryCtorBodyOnSndCtr(ct, overrideNumRef)
- }
- overrideNumss.zip(bodies).foldRight[js.Tree](js.Skip()) {
- case ((numBounds, js.Skip()), acc) => acc
+ val normalInfos = normalParamsAndInfos.map(_._2).toIndexedSeq
- case ((numBounds, body), acc) =>
- val cond = mkOverrideNumsCond(overrideNumRef, numBounds)
- js.If(cond, body, acc)(jstpe.BooleanType)
- }
- }
+ 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))
+ }
- private def mkPostPrimaryCtorBodyOnSndCtr(constructorTree: ConstructorTree,
- overrideNumRef: js.VarRef)(implicit pos: Position): js.Tree = {
- val postSuperCall = {
- constructorTree.method.body.get match {
- case js.Block(stats) =>
- stats.dropWhile {
- case js.ApplyStatic(_, _, mtd, _) =>
- !ir.Definitions.isConstructorName(mtd.name)
- case _ =>
- true
- }.tail
+ 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))
- case _ => Nil
+ js.Assign(param, rhs)
}
+
+ js.Block(captureAssigns ::: paramAssigns, js.IntLiteral(overloadNum))
}
- js.Block(postSuperCall :+ mkSubPostCalls(constructorTree, overrideNumRef))
}
- private def mkOverrideNumsCond(numRef: js.VarRef,
- numBounds: (Int, Int))(implicit pos: Position) = numBounds match {
- case (lo, hi) if lo == hi =>
- js.BinaryOp(js.BinaryOp.===, js.IntLiteral(lo), numRef)
+ (jsExport, jsClassCaptures)
+ }
- case (lo, hi) if lo == hi - 1 =>
- val lhs = js.BinaryOp(js.BinaryOp.Int_==, numRef, js.IntLiteral(lo))
- val rhs = js.BinaryOp(js.BinaryOp.Int_==, numRef, js.IntLiteral(hi))
- js.If(lhs, js.BooleanLiteral(true), rhs)(jstpe.BooleanType)
+ /** Generates a JS constructor body based on a constructor tree. */
+ private def genJSClassCtorBody(overloadVar: js.VarRef,
+ ctorTree: ConstructorTree[PrimaryJSCtor])(implicit pos: Position): js.JSConstructorBody = {
- case (lo, hi) =>
- val lhs = js.BinaryOp(js.BinaryOp.Int_<=, js.IntLiteral(lo), numRef)
- val rhs = js.BinaryOp(js.BinaryOp.Int_<=, numRef, js.IntLiteral(hi))
- js.BinaryOp(js.BinaryOp.Boolean_&, lhs, rhs)
- js.If(lhs, rhs, js.BooleanLiteral(false))(jstpe.BooleanType)
- }
- }
+ /* 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()
- private def zipMap[T, U, V](xs: List[T], ys: List[U])(
- f: (T, U) => V): List[V] = {
- for ((x, y) <- xs zip ys) yield f(x, y)
- }
+ case body =>
+ val x = overloadVar
+ val cond = {
+ import tree.{lo, hi}
- /** mkOverloadSelection return a list of `stats` with that starts with:
- * 1) The definition for the local variable that will hold the overload
- * resolution number.
- * 2) The definitions of all local variables that are used as parameters
- * in all the constructors.
- * 3) The overload resolution match/if statements. For each overload the
- * overload number is assigned and the parameters are cast and assigned
- * to their corresponding variables.
- */
- private def mkOverloadSelection(jsConstructorBuilder: JSConstructorBuilder,
- overloadIdent: js.Ident, dispatchResolution: js.Tree)(
- implicit pos: Position): List[js.Tree] = {
+ 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)
+ }
+ }
- def deconstructApplyCtor(body: js.Tree): (List[js.Tree], String, List[js.Tree]) = {
- val (prepStats, applyCtor) = body match {
- case applyCtor: js.ApplyStatic =>
- (Nil, applyCtor)
- case js.Block(prepStats :+ (applyCtor: js.ApplyStatic)) =>
- (prepStats, applyCtor)
- }
- val js.ApplyStatic(_, _, js.Ident(ctorName, _), js.This() :: ctorArgs) =
- applyCtor
- assert(ir.Definitions.isConstructorName(ctorName),
- s"unexpected super constructor call to non-constructor $ctorName at ${applyCtor.pos}")
- (prepStats, ctorName, ctorArgs)
+ js.If(cond, body, js.Skip())(jstpe.VoidType)
}
- if (!jsConstructorBuilder.hasSubConstructors) {
- val (prepStats, ctorName, ctorArgs) =
- deconstructApplyCtor(dispatchResolution)
-
- val refs = jsConstructorBuilder.getParamRefsFor(ctorName)
- assert(refs.size == ctorArgs.size, currentClassSym.fullName)
- val assignCtorParams = zipMap(refs, ctorArgs) { (ref, ctorArg) =>
- js.VarDef(ref.ident, ref.tpe, mutable = false, ctorArg)
- }
+ /* preStats / postStats use pre/post order traversal respectively to
+ * generate a topo-sorted sequence of statements.
+ */
- prepStats ::: assignCtorParams
- } else {
- val overloadRef = js.VarRef(overloadIdent)(jstpe.IntType)
+ def preStats(tree: ConstructorTree[SplitSecondaryJSCtor],
+ nextParamsAndInfo: List[(js.VarRef, JSParamInfo)]): js.Tree = {
+ val inner = tree.subCtors.map(preStats(_, tree.ctor.paramsAndInfo))
- /* transformDispatch takes the body of the method generated by
- * `genJSConstructorExport` and transform it recursively.
- */
- def transformDispatch(tree: js.Tree): js.Tree = tree match {
- // Parameter count resolution
- case js.Match(selector, cases, default) =>
- val newCases = cases.map {
- case (literals, body) => (literals, transformDispatch(body))
- }
- val newDefault = transformDispatch(default)
- js.Match(selector, newCases, newDefault)(tree.tpe)
+ assert(tree.ctor.ctorArgs.size == nextParamsAndInfo.size, "param count mismatch")
+ val paramsInfosAndArgs = nextParamsAndInfo.zip(tree.ctor.ctorArgs)
- // Parameter type resolution
- case js.If(cond, thenp, elsep) =>
- js.If(cond, transformDispatch(thenp),
- transformDispatch(elsep))(tree.tpe)
+ val (captureParamsInfosAndArgs, normalParamsInfosAndArgs) =
+ paramsInfosAndArgs.partition(_._1._2.capture)
- // Throw(StringLiteral(No matching overload))
- case tree: js.Throw =>
- tree
+ val captureAssigns = for {
+ ((param, _), arg) <- captureParamsInfosAndArgs
+ } yield {
+ js.Assign(param, arg)
+ }
- // Overload resolution done, apply the constructor
- case _ =>
- val (prepStats, ctorName, ctorArgs) = deconstructApplyCtor(tree)
+ 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")
- val num = jsConstructorBuilder.getOverrideNum(ctorName)
- val overloadAssign = js.Assign(overloadRef, js.IntLiteral(num))
+ /* 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))
- val refs = jsConstructorBuilder.getParamRefsFor(ctorName)
- assert(refs.size == ctorArgs.size, currentClassSym.fullName)
- val assignCtorParams = zipMap(refs, ctorArgs)(js.Assign(_, _))
+ case arg => arg
+ }
- js.Block(overloadAssign :: prepStats ::: assignCtorParams)
+ js.Assign(param, newArg)
}
- val newDispatchResolution = transformDispatch(dispatchResolution)
- val allParamDefsAsVars = jsConstructorBuilder.getAllParamDefsAsVars
- val overrideNumDef =
- js.VarDef(overloadIdent, jstpe.IntType, mutable = true, js.IntLiteral(0))
+ ifOverload(tree, js.Block(
+ inner ++ tree.ctor.beforeCall ++ captureAssigns ++ normalAssigns))
+ }
- overrideNumDef :: allParamDefsAsVars ::: newDispatchResolution :: Nil
+ def postStats(tree: ConstructorTree[SplitSecondaryJSCtor]): js.Tree = {
+ val inner = tree.subCtors.map(postStats(_))
+ ifOverload(tree, js.Block(tree.ctor.afterCall ++ inner))
}
- }
- private def mkJSConstructorBuilder(ctors: List[js.MethodDef])(
- implicit pos: Position): JSConstructorBuilder = {
- def findCtorForwarderCall(tree: js.Tree): String = tree match {
- case js.ApplyStatic(_, _, method, js.This() :: _)
- if ir.Definitions.isConstructorName(method.name) =>
- method.name
+ val primaryCtor = ctorTree.ctor
+ val secondaryCtorTrees = ctorTree.subCtors
- case js.Block(stats) =>
- stats.collectFirst {
- case js.ApplyStatic(_, _, method, js.This() :: _)
- if ir.Definitions.isConstructorName(method.name) =>
- method.name
- }.get
- }
+ wrapJSCtorBody(
+ secondaryCtorTrees.map(preStats(_, primaryCtor.paramsAndInfo)),
+ primaryCtor.body,
+ secondaryCtorTrees.map(postStats(_))
+ )
+ }
- val (primaryCtor :: Nil, secondaryCtors) = ctors.partition {
- _.body.get match {
- case js.Block(stats) =>
- stats.exists(_.isInstanceOf[js.JSSuperConstructorCall])
+ 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)
+ }
- case _: js.JSSuperConstructorCall => true
- case _ => false
- }
- }
+ private sealed trait JSCtor {
+ val sym: Symbol
+ val paramsAndInfo: List[(js.VarRef, JSParamInfo)]
+ }
- val ctorToChildren = secondaryCtors.map { ctor =>
- findCtorForwarderCall(ctor.body.get) -> ctor
- }.groupBy(_._1).map(kv => kv._1 -> kv._2.map(_._2)).withDefaultValue(Nil)
+ private class PrimaryJSCtor(val sym: Symbol,
+ val paramsAndInfo: List[(js.VarRef, JSParamInfo)],
+ val body: js.JSConstructorBody) extends JSCtor
- var overrideNum = -1
- def mkConstructorTree(method: js.MethodDef): ConstructorTree = {
- val methodName = method.name.encodedName
- val subCtrTrees = ctorToChildren(methodName).map(mkConstructorTree)
- overrideNum += 1
- new ConstructorTree(overrideNum, method, subCtrTrees)
- }
+ 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)
- new JSConstructorBuilder(mkConstructorTree(primaryCtor))
+ 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] = {
- withNewLocalNameScope {
- genMethodWithCurrentLocalNameScope(dd)
+ 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))
+ }
}
}
@@ -1519,176 +2022,115 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* 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 (with exceptions)
- * * Abstract methods
- * * Constructors of hijacked classes
- * * Methods with the {{{@JavaDefaultMethod}}} annotation mixed in classes.
- *
* Constructors are emitted by generating their body as a statement.
*
- * Interface methods with the {{{@JavaDefaultMethod}}} annotation produce
- * default methods forwarding to the trait impl class method.
- *
* Other (normal) methods are emitted with `genMethodDef()`.
*/
- def genMethodWithCurrentLocalNameScope(dd: DefDef): Option[js.MethodDef] = {
+ def genMethodWithCurrentLocalNameScope(dd: DefDef,
+ initThisLocalVarName: Option[LocalName] = None): js.MethodDef = {
+
implicit val pos = dd.pos
- val DefDef(mods, name, _, vparamss, _, rhs) = dd
val sym = dd.symbol
- withScopedVars(
- currentMethodSym := sym,
- thisLocalVarIdent := None,
- fakeTailJumpParamRepl := (NoSymbol, NoSymbol),
- enclosingLabelDefParams := Map.empty,
- isModuleInitialized := new VarBox(false),
- countsOfReturnsToMatchCase := mutable.Map.empty,
- countsOfReturnsToMatchEnd := mutable.Map.empty,
- undefinedDefaultParams := mutable.Set.empty
- ) {
- assert(vparamss.isEmpty || vparamss.tail.isEmpty,
- "Malformed parameter list: " + vparamss)
- val params = if (vparamss.isEmpty) Nil else vparamss.head map (_.symbol)
-
- val isJSClassConstructor =
- sym.isClassConstructor && isNonNativeJSClass(currentClassSym)
-
- val methodName: js.PropertyName = encodeMethodSym(sym)
+ withPerMethodBodyState(sym, initThisLocalVarName) {
+ val methodName = encodeMethodSym(sym)
+ val originalName = originalNameOfMethod(sym)
- def jsParams = for (param <- params) yield {
- implicit val pos = param.pos
- js.ParamDef(encodeLocalSym(param), toIRType(param.tpe),
- mutable = false, rest = false)
+ 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(_))
}
- if (scalaPrimitives.isPrimitive(sym)) {
- None
- } else if (isAbstractMethod(dd)) {
- val body = if (scalaUsesImplClasses &&
- sym.hasAnnotation(JavaDefaultMethodAnnotation)) {
- /* For an interface method with @JavaDefaultMethod, make it a
- * default method calling the impl class method.
- */
- val implClassSym = sym.owner.implClass
- val implMethodSym = implClassSym.info.member(sym.name).suchThat { s =>
- s.isMethod &&
- s.tpe.params.size == sym.tpe.params.size + 1 &&
- s.tpe.params.head.tpe =:= sym.owner.toTypeConstructor &&
- s.tpe.params.tail.zip(sym.tpe.params).forall {
- case (sParam, symParam) =>
- sParam.tpe =:= symParam.tpe
- }
- }
- Some(genTraitImplApply(implMethodSym,
- js.This()(currentClassType) :: jsParams.map(_.ref)))
- } else {
- None
- }
- Some(js.MethodDef(js.MemberFlags.empty, methodName,
- jsParams, toIRType(sym.tpe.resultType), body)(
- OptimizerHints.empty, None))
- } else if (isJSNativeCtorDefaultParam(sym)) {
- None
- } else if (sym.isClassConstructor && isHijackedClass(sym.owner)) {
- None
- } else if (scalaUsesImplClasses && !isImplClass(sym.owner) &&
- sym.hasAnnotation(JavaDefaultMethodAnnotation)) {
- // Do not emit trait impl forwarders with @JavaDefaultMethod
- None
+ val jsMethodDef = if (isAbstractMethod(dd)) {
+ js.MethodDef(js.MemberFlags.empty, methodName, originalName,
+ jsParams, toIRType(sym.tpe.resultType), None)(
+ OptimizerHints.empty, Unversioned)
} else {
- withScopedVars(
- mutableLocalVars := mutable.Set.empty,
- mutatedLocalVars := mutable.Set.empty
- ) {
- def isTraitImplForwarder = dd.rhs match {
- case app: Apply => foreignIsImplClass(app.symbol.owner)
- case _ => false
- }
+ val shouldMarkInline = {
+ sym.hasAnnotation(InlineAnnotationClass) ||
+ sym.name.containsName(nme.ANON_FUN_NAME) ||
+ adHocInlineMethods.contains(sym.fullName)
+ }
- val shouldMarkInline = {
- sym.hasAnnotation(InlineAnnotationClass) ||
- sym.name.startsWith(nme.ANON_FUN_NAME) ||
- adHocInlineMethods.contains(sym.fullName)
- }
+ val shouldMarkNoinline = {
+ sym.hasAnnotation(NoinlineAnnotationClass) &&
+ !ignoreNoinlineAnnotation(sym)
+ }
- val shouldMarkNoinline = {
- sym.hasAnnotation(NoinlineAnnotationClass) &&
- !isTraitImplForwarder &&
- !ignoreNoinlineAnnotation(sym)
- }
+ val optimizerHints =
+ OptimizerHints.empty.
+ withInline(shouldMarkInline).
+ withNoinline(shouldMarkNoinline)
- val optimizerHints =
- OptimizerHints.empty.
- withInline(shouldMarkInline).
- withNoinline(shouldMarkNoinline)
-
- val methodDef = {
- if (isJSClassConstructor) {
- val body0 = genStat(rhs)
- val body1 =
- if (!sym.isPrimaryConstructor) body0
- else moveAllStatementsAfterSuperConstructorCall(body0)
- js.MethodDef(js.MemberFlags.empty, methodName,
- jsParams, jstpe.NoType, Some(body1))(optimizerHints, None)
- } else if (sym.isClassConstructor) {
- val namespace = js.MemberNamespace.Constructor
- js.MethodDef(
- js.MemberFlags.empty.withNamespace(namespace),
- methodName, jsParams, jstpe.NoType, Some(genStat(rhs)))(
- optimizerHints, None)
- } else {
- val resultIRType = toIRType(sym.tpe.resultType)
- val namespace = {
- if (isImplClass(sym.owner)) {
- if (sym.isPrivate) js.MemberNamespace.PrivateStatic
- else js.MemberNamespace.PublicStatic
- } else {
- if (sym.isPrivate) js.MemberNamespace.Private
- else js.MemberNamespace.Public
- }
+ 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, params, resultIRType, rhs,
- optimizerHints)
}
+ 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(encodeLocalSym(_).name -> false) :::
- mutatedImmutableLocalVals.map(encodeLocalSym(_).name -> true)
- ).toMap
- patchMutableFlagOfLocals(methodDef, patches)
- }
+ 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)
}
-
- Some(methodDefWithoutUselessVars)
}
+
+ 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 = {
- /* When scalac uses impl classes, we cannot trust `rhs` to be
- * `EmptyTree` for deferred methods (probably due to an internal bug
- * of scalac), as can be seen in run/t6443.scala.
- * However, when it does not use impl class anymore, we have to use
- * `rhs == EmptyTree` as predicate, just like the JVM back-end does.
- */
- if (scalaUsesImplClasses)
- dd.symbol.isDeferred || dd.symbol.owner.isInterface
- else
- dd.rhs == EmptyTree
- }
+ def isAbstractMethod(dd: DefDef): Boolean =
+ dd.rhs == EmptyTree
private val adHocInlineMethods = Set(
"scala.collection.mutable.ArrayOps$ofRef.newBuilder$extension",
@@ -1702,65 +2144,73 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* For locals not in the map, the flag is untouched.
*/
private def patchMutableFlagOfLocals(methodDef: js.MethodDef,
- patches: Map[String, Boolean]): js.MethodDef = {
+ patches: Map[LocalName, Boolean]): js.MethodDef = {
- def newMutable(name: String, oldMutable: Boolean): Boolean =
+ def newMutable(name: LocalName, oldMutable: Boolean): Boolean =
patches.getOrElse(name, oldMutable)
- val js.MethodDef(flags, methodName, params, resultType, body) = methodDef
+ val js.MethodDef(flags, methodName, originalName, params, resultType, body) =
+ methodDef
val newParams = for {
- p @ js.ParamDef(name, ptpe, mutable, rest) <- params
+ p @ js.ParamDef(name, originalName, ptpe, mutable) <- params
} yield {
- js.ParamDef(name, ptpe, newMutable(name.name, mutable), rest)(p.pos)
- }
- val transformer = new ir.Transformers.Transformer {
- override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match {
- case js.VarDef(name, vtpe, mutable, rhs) =>
- assert(isStat, s"found a VarDef in expression position at ${tree.pos}")
- super.transform(js.VarDef(
- name, vtpe, newMutable(name.name, mutable), rhs)(tree.pos), isStat)
- case js.Closure(arrow, captureParams, params, body, captureValues) =>
- js.Closure(arrow, captureParams, params, body,
- captureValues.map(transformExpr))(tree.pos)
+ 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, isStat)
+ super.transform(tree)
}
}
- val newBody = body.map(
- b => transformer.transform(b, isStat = resultType == jstpe.NoType))
- js.MethodDef(flags, methodName, newParams, resultType,
- newBody)(methodDef.optimizerHints, None)(methodDef.pos)
+ val newBody = transformer.transformTreeOpt(body)
+ js.MethodDef(flags, methodName, originalName, newParams, resultType,
+ newBody)(methodDef.optimizerHints, Unversioned)(methodDef.pos)
}
- /** Moves all statements after the super constructor call.
+ /** Patches the type of selected param defs in a [[js.MethodDef]].
*
- * This is used for the primary constructor of a non-native JS
- * class, because those 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). We move those after the super constructor
- * call, and are therefore executed later than for a Scala class.
+ * @param patches
+ * Map from local name to new type. For param defs not in the map, the
+ * type is untouched.
*/
- private def moveAllStatementsAfterSuperConstructorCall(
- body: js.Tree): js.Tree = {
- val bodyStats = body match {
- case js.Block(stats) => stats
- case _ => body :: Nil
- }
+ private def patchTypeOfParamDefs(methodDef: js.MethodDef,
+ patches: Map[LocalName, jstpe.Type]): js.MethodDef = {
- val (beforeSuper, superCall :: afterSuper) =
- bodyStats.span(!_.isInstanceOf[js.JSSuperConstructorCall])
+ 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)
+ }
- assert(!beforeSuper.exists(_.isInstanceOf[js.VarDef]),
- "Trying to move a local VarDef after the super constructor call " +
- "of a non-native JS class at ${body.pos}")
+ /** Generates the JSNativeMemberDef of a JS native method. */
+ def genJSNativeMemberDef(tree: DefDef): js.JSNativeMemberDef = {
+ implicit val pos = tree.pos
- js.Block(
- superCall ::
- beforeSuper :::
- afterSuper)(body.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
@@ -1773,91 +2223,66 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* a peculiarity of recursive tail calls: the local ValDef that replaces
* `this`.
*/
- def genMethodDef(namespace: js.MemberNamespace, methodName: js.PropertyName,
- paramsSyms: List[Symbol], resultIRType: jstpe.Type,
- tree: Tree, optimizerHints: OptimizerHints): js.MethodDef = {
+ 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 jsParams = for (param <- paramsSyms) yield {
- implicit val pos = param.pos
- js.ParamDef(encodeLocalSym(param), toIRType(param.tpe),
- mutable = false, rest = false)
- }
-
- val bodyIsStat = resultIRType == jstpe.NoType
+ 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
-
- // To be called from within withScopedVars
- def genInnerBody() = {
- js.Block(otherStats.map(genStat) :+ (
- if (bodyIsStat) genStat(rhs)
- else genExpr(rhs)))
- }
-
- initialThis match {
- case Ident(_) =>
- /* This case happens in trait implementation classes, until
- * Scala 2.11. In the method, all usages of `this` have been
- * replaced by the method's formal parameter `$this`. However,
- * there is still a declaration of the pseudo local variable
- * `_$this`, which is used in the param list of the label. We
- * need to remember it now, so that when we build the JS version
- * of the formal params for the label, we can redirect the
- * assignment to `$this` instead of the otherwise unused
- * `_$this`.
- */
- withScopedVars(
- fakeTailJumpParamRepl := (thisDef.symbol, initialThis.symbol)
- ) {
- genInnerBody()
- }
+ rhs) =>
+ // This method has tail jumps
- case _ =>
- val thisSym = thisDef.symbol
- if (thisSym.isMutable)
- mutableLocalVars += thisSym
-
- val thisLocalIdent = encodeLocalSym(thisSym)
- val thisLocalType = currentClassType
-
- 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 thisSym = thisDef.symbol
+ if (thisSym.isMutable)
+ mutableLocalVars += thisSym
- val thisLocalVarDef = js.VarDef(thisLocalIdent,
- thisLocalType, thisSym.isMutable, genRhs)
+ /* 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 innerBody = {
- withScopedVars(
- thisLocalVarIdent := Some(thisLocalIdent)
- ) {
- genInnerBody()
- }
- }
+ val thisLocalVarDef = js.VarDef(thisLocalIdent, thisOriginalName,
+ thisLocalType, thisSym.isMutable, genRhs)
- js.Block(thisLocalVarDef, innerBody)
+ 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)
@@ -1873,37 +2298,40 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
isJSFunctionDef(currentClassSym)) {
val flags = js.MemberFlags.empty.withNamespace(namespace)
val body = {
- if (isImplClass(currentClassSym)) {
- val thisParam = jsParams.head
- withScopedVars(
- thisLocalVarIdent := Some(thisParam.name)
- ) {
- genBody()
- }
- } else {
+ val classOwner = currentClassSym.owner
+ if (classOwner != JavaLangPackageClass && classOwner.owner != JavaLangPackageClass) {
+ // Fast path; it cannot be any of the special methods of the javalib
genBody()
+ } else {
+ JavalibMethodsWithOpBody.get((encodeClassName(currentClassSym), methodName.name)) match {
+ case None =>
+ genBody()
+ case Some(javalibOpBody) =>
+ javalibOpBody.generate(genThis(), jsParams.map(_.ref))
+ }
}
}
- js.MethodDef(flags, methodName, jsParams, resultIRType, Some(body))(
- optimizerHints, None)
+ js.MethodDef(flags, methodName, originalName, jsParams, resultIRType,
+ Some(body))(
+ optimizerHints, Unversioned)
} else {
assert(!namespace.isStatic, tree.pos)
val thisLocalIdent = freshLocalIdent("this")
withScopedVars(
- thisLocalVarIdent := Some(thisLocalIdent)
+ 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,
- jstpe.AnyType, mutable = false, rest = false)
+ val thisParamDef = js.ParamDef(thisLocalIdent, thisOriginalName,
+ jstpe.AnyType, mutable = false)
- js.MethodDef(flags, methodName, thisParamDef :: jsParams,
- resultIRType, Some(genBody()))(
- optimizerHints, None)
+ js.MethodDef(flags, methodName, originalName,
+ thisParamDef :: jsParams, resultIRType, Some(genBody()))(
+ optimizerHints, Unversioned)
}
}
}
@@ -1913,7 +2341,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* @param tree
* The tree to adapt.
* @param tpe
- * The target type, which must be either `AnyType` or `ClassType(_)`.
+ * 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) {
@@ -1928,8 +2356,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
case js.AsInstanceOf(underlying, _) if underlying.tpe == tpe =>
underlying
case _ =>
- val cls = tpe.asInstanceOf[jstpe.ClassType].className
- js.AsInstanceOf(tree, jstpe.ClassRef(cls))(tree.pos)
+ js.AsInstanceOf(tree, tpe)(tree.pos)
}
}
}
@@ -1949,7 +2376,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
tree match {
case js.Block(stats :+ expr) =>
js.Block(stats :+ exprToStat(expr))
- case _:js.Literal | _:js.This | _:js.VarRef =>
+ case _:js.Literal | _:js.VarRef =>
js.Skip()
case _ =>
tree
@@ -1960,8 +2387,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
*/
def genExpr(tree: Tree): js.Tree = {
val result = genStatOrExpr(tree, isStat = false)
- assert(result.tpe != jstpe.NoType,
- s"genExpr($tree) returned a tree with type NoType at pos ${tree.pos}")
+ assert(result.tpe != jstpe.VoidType,
+ s"genExpr($tree) returned a tree with type VoidType at pos ${tree.pos}")
result
}
@@ -2004,7 +2431,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
tree match {
/** LabelDefs (for while and do..while loops) */
case lblDf: LabelDef =>
- genLabelDef(lblDf)
+ genLabelDef(lblDf, isStat)
/** Local val or var declaration */
case ValDef(_, name, _, rhs) =>
@@ -2031,35 +2458,115 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
case _ =>
if (sym.isMutable)
mutableLocalVars += sym
- js.VarDef(encodeLocalSym(sym),
+ js.VarDef(encodeLocalSym(sym), originalNameOfLocal(sym),
toIRType(sym.tpe), sym.isMutable, rhsTree)
}
- case If(cond, thenp, elsep) =>
- js.If(genExpr(cond), genStatOrExpr(thenp, isStat),
- genStatOrExpr(elsep, isStat))(toIRType(tree.tpe))
+ case 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(toIRType(expr.tpe) match {
- case jstpe.NoType => js.Block(genStat(expr), js.Undefined())
- case _ => genExpr(expr)
- }, getEnclosingReturnLabel())
+ js.Return(
+ genStatOrExpr(expr, isStat = toIRType(expr.tpe) == jstpe.VoidType),
+ getEnclosingReturnLabel())
case t: Try =>
genTry(t, isStat)
case Throw(expr) =>
val ex = genExpr(expr)
- js.Throw {
- if (isMaybeJavaScriptException(expr.tpe)) {
- genApplyMethod(
- genLoadModule(RuntimePackageModule),
- Runtime_unwrapJavaScriptException,
- List(ex))
- } else {
- ex
- }
+ 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)
@@ -2091,21 +2598,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
assert(!sym.isPackageClass, "Cannot use package as value: " + tree)
genLoadModule(sym)
} else if (sym.isStaticMember) {
- genStaticMember(sym)
+ genStaticField(sym)
} else if (paramAccessorLocals contains sym) {
paramAccessorLocals(sym).ref
- } else if (isNonNativeJSClass(sym.owner)) {
- val genQual = genExpr(qualifier)
- val boxed = if (isExposed(sym))
- js.JSBracketSelect(genQual, genExpr(jsNameOf(sym)))
- else
- js.JSDotSelect(genQual, encodeFieldSym(sym))
- unboxFieldValue(boxed)
- } else if (jsInterop.isFieldStatic(sym)) {
- unboxFieldValue(genSelectStaticFieldAsBoxed(sym))
} else {
- js.Select(genExpr(qualifier),
- encodeFieldSym(sym))(toIRType(sym.tpe))
+ val (field, boxed) = genAssignableField(sym, qualifier)
+ if (boxed) unboxFieldValue(field)
+ else field
}
case Ident(name) =>
@@ -2117,9 +2616,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
} 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)(toIRType(sym.tpe))
+ js.Transient(UndefinedParam)
} else {
- js.VarRef(encodeLocalSym(sym))(toIRType(sym.tpe))
+ genVarRef(sym)
}
} else {
abort("Cannot use package as value: " + tree)
@@ -2152,7 +2651,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
case ClazzTag =>
js.ClassOf(toTypeRef(value.typeValue))
case EnumTag =>
- genStaticMember(value.symbolValue)
+ genStaticField(value.symbolValue)
}
case tree: Block =>
@@ -2168,18 +2667,37 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val sym = lhs.symbol
if (sym.isStaticMember)
abort(s"Assignment to static member ${sym.fullName} not supported")
- val genRhs = genExpr(rhs)
+ def genRhs = genExpr(rhs)
lhs match {
case Select(qualifier, _) =>
- val ctorAssignment = (
- currentMethodSym.isClassConstructor &&
- currentMethodSym.owner == qualifier.symbol &&
- qualifier.isInstanceOf[This]
- )
- if (!ctorAssignment && !suspectFieldMutable(sym))
- unexpectedMutatedFields += sym
-
- val genQual = genExpr(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 =
@@ -2195,23 +2713,25 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
}
- if (isNonNativeJSClass(sym.owner)) {
- val genLhs = if (isExposed(sym))
- js.JSBracketSelect(genQual, genExpr(jsNameOf(sym)))
- else
- js.JSDotSelect(genQual, encodeFieldSym(sym))
- js.Assign(genLhs, genBoxedRhs)
- } else if (jsInterop.isFieldStatic(sym)) {
- js.Assign(genSelectStaticFieldAsBoxed(sym), genBoxedRhs)
+ 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 {
- js.Assign(
- js.Select(genQual, encodeFieldSym(sym))(toIRType(sym.tpe)),
- genRhs)
+ val (field, boxed) = genAssignableField(sym, qualifier)
+
+ if (boxed) js.Assign(field, genBoxedRhs)
+ else js.Assign(field,genRhs)
}
+
case _ =>
mutatedLocalVars += sym
js.Assign(
- js.VarRef(encodeLocalSym(sym))(toIRType(sym.tpe)),
+ js.VarRef(encodeLocalSymName(sym))(toIRType(sym.tpe)),
genRhs)
}
@@ -2223,7 +2743,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
case mtch: Match =>
genMatch(mtch, isStat)
- /** Anonymous function (in 2.12, or with -Ydelambdafy:method in 2.11) */
+ /** Anonymous function */
case fun: Function =>
genAnonFunction(fun)
@@ -2236,138 +2756,219 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
} // 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 = {
- thisLocalVarIdent.fold[js.Tree] {
- if (tryingToGenMethodAsJSFunction) {
- throw new CancelGenMethodAsJSFunction(
- "Trying to generate `this` inside the body")
+ thisLocalVarName.fold[js.Tree] {
+ if (isJSFunctionDef(currentClassSym)) {
+ abort(
+ "Unexpected `this` reference inside the body of a JS function class: " +
+ currentClassSym.fullName)
}
- js.This()(currentClassType)
- } { thisLocalIdent =>
- // .copy() to get the correct position
- val tpe = {
- if (isImplClass(currentClassSym))
- encodeClassType(traitOfImplClass(currentClassSym))
- else
- currentClassType
- }
- js.VarRef(thisLocalIdent.copy())(tpe)
- }
- }
-
- private def genSelectStaticFieldAsBoxed(sym: Symbol)(
- implicit pos: Position): js.Tree = {
- val exportInfos = jsInterop.staticFieldInfoOf(sym)
- (exportInfos.head.destination: @unchecked) match {
- case ExportDestination.TopLevel =>
- js.SelectStatic(encodeClassRef(sym.owner), encodeFieldSym(sym))(
- jstpe.AnyType)
-
- case ExportDestination.Static =>
- val exportInfo = exportInfos.head
- val companionClass = patchedLinkedClassOfClass(sym.owner)
- js.JSBracketSelect(
- genPrimitiveJSClass(companionClass),
- js.StringLiteral(exportInfo.jsName))
+ js.This()(currentThisType)
+ } { thisLocalName =>
+ js.VarRef(thisLocalName)(currentThisTypeNullable)
}
}
- /** 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.
+ /** 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;
+ * }}}
*
- * 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.
+ * 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): js.Tree = {
+ def genLabelDef(tree: LabelDef, isStat: Boolean): js.Tree = {
implicit val pos = tree.pos
val sym = tree.symbol
- tree match {
- // while (cond) { body }
- case LabelDef(lname, Nil,
- If(cond,
- Block(bodyStats, Apply(target @ Ident(lname2), Nil)),
- Literal(_))) if (target.symbol == sym) =>
- js.While(genExpr(cond), js.Block(bodyStats map genStat))
-
- // while (cond) { body }; result
- case LabelDef(lname, Nil,
- Block(List(
- If(cond,
- Block(bodyStats, Apply(target @ Ident(lname2), Nil)),
- Literal(_))),
- result)) if (target.symbol == sym) =>
- js.Block(
- js.While(genExpr(cond), js.Block(bodyStats map genStat)),
- genExpr(result))
+ val labelParamSyms = tree.params.map(_.symbol)
+ val info = new EnclosingLabelDefInfoWithResultAsAssigns(labelParamSyms)
- // while (true) { body }
- case LabelDef(lname, Nil,
- Block(bodyStats,
- Apply(target @ Ident(lname2), Nil))) if (target.symbol == sym) =>
- js.While(js.BooleanLiteral(true), js.Block(bodyStats map genStat))
+ val labelName = encodeLabelSym(sym)
- // while (false) { body }
- case LabelDef(lname, Nil, Literal(Constant(()))) =>
- js.Skip()
+ val transformedRhs = withScopedVars(
+ enclosingLabelDefInfos := enclosingLabelDefInfos.get + (sym -> info)
+ ) {
+ genStatOrExpr(tree.rhs, isStat)
+ }
- // do { body } while (cond)
- case LabelDef(lname, Nil,
- Block(bodyStats,
- If(cond,
- Apply(target @ Ident(lname2), Nil),
- Literal(_)))) if (target.symbol == sym) =>
- js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond))
-
- // do { body } while (cond); result
- case LabelDef(lname, Nil,
- Block(
- bodyStats :+
- If(cond,
- Apply(target @ Ident(lname2), Nil),
- Literal(_)),
- result)) if (target.symbol == sym) =>
- js.Block(
- js.DoWhile(js.Block(bodyStats map genStat), genExpr(cond)),
- genExpr(result))
+ /** 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
+ }
+ }
- /* Arbitrary other label - we can jump to it from inside it.
- * This is typically for the label-defs implementing tail-calls.
- * It can also handle other weird LabelDefs generated by some compiler
- * plugins (see for example #1148).
- */
- case LabelDef(labelName, labelParams, rhs) =>
- val labelParamSyms = labelParams.map(_.symbol) map {
- s => if (s == fakeTailJumpParamRepl._1) fakeTailJumpParamRepl._2 else s
- }
+ 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)
+ })
+ })
+ })
+ }
+ }
- withScopedVars(
- enclosingLabelDefParams :=
- enclosingLabelDefParams.get + (tree.symbol -> labelParamSyms)
- ) {
- val bodyType = toIRType(tree.tpe)
- val labelIdent = encodeLabelSym(tree.symbol)
- val blockLabelIdent = freshLocalIdent()
+ 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
- js.Labeled(blockLabelIdent, bodyType, {
+ 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.Labeled(labelIdent, jstpe.NoType, {
- if (bodyType == jstpe.NoType)
- js.Block(genStat(rhs), js.Return(js.Undefined(), blockLabelIdent))
- else
- js.Return(genExpr(rhs), blockLabelIdent)
- })
+ 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()
}
}
@@ -2394,7 +2995,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val Try(block, catches, finalizer) = tree
val blockAST = genStatOrExpr(block, isStat)
- val resultType = toIRType(tree.tpe)
+
+ val resultType =
+ if (isStat) jstpe.VoidType
+ else toIRType(tree.tpe)
val handled =
if (catches.isEmpty) blockAST
@@ -2407,10 +3011,37 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
private def genTryCatch(body: js.Tree, catches: List[CaseDef],
- resultType: jstpe.Type,
- isStat: Boolean)(implicit pos: Position): js.Tree = {
+ 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)(jstpe.AnyType)
+ val origExceptVar = js.VarRef(exceptIdent.name)(jstpe.AnyType)
val mightCatchJavaScriptException = catches.exists { caseDef =>
caseDef.pat match {
@@ -2424,19 +3055,15 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
val (exceptValDef, exceptVar) = if (mightCatchJavaScriptException) {
- val valDef = js.VarDef(freshLocalIdent("e"),
- encodeClassType(ThrowableClass), mutable = false, {
- genApplyMethod(
- genLoadModule(RuntimePackageModule),
- Runtime_wrapJavaScriptException,
- List(origExceptVar))
- })
+ 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.Throw(origExceptVar)
+ val elseHandler: js.Tree = js.UnaryOp(js.UnaryOp.Throw, origExceptVar)
val handler = catches.foldRight(elseHandler) { (caseDef, elsep) =>
implicit val pos = caseDef.pos
@@ -2449,17 +3076,20 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
case Ident(nme.WILDCARD) =>
(ThrowableClass.tpe, None)
case Bind(_, _) =>
- (pat.symbol.tpe, Some(encodeLocalSym(pat.symbol)))
+ 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(bv) =>
+ case Some((boundVarIdent, boundVarOriginalName)) =>
val castException = genAsInstanceOf(exceptVar, tpe)
js.Block(
- js.VarDef(bv, toIRType(tpe), mutable = false, castException),
+ js.VarDef(boundVarIdent, boundVarOriginalName, toIRType(tpe),
+ mutable = false, castException),
genStatOrExpr(body, isStat))
})
@@ -2472,7 +3102,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
}
- js.TryCatch(body, exceptIdent,
+ js.TryCatch(body, exceptIdent, NoOriginalName,
js.Block(exceptValDef, handler))(resultType)
}
@@ -2487,29 +3117,45 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
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 = {
- if (isCtorDefaultParam(sym)) {
- isJSCtorDefaultParam(sym)
- } else {
- sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) &&
- isJSType(sym.owner) && {
- /* If this is a default parameter accessor on a
- * non-native JS class, we need to know if the method for which we
- * are the default parameter is exposed or not.
- * We do this by removing the $default suffix from the method name,
- * and looking up a member with that name in the owner.
- * Note that this does not work for local methods. But local methods
- * are never exposed.
- * Further note that overloads are easy, because either all or none
- * of them are exposed.
+ 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.
*/
- def isAttachedMethodExposed = {
- val methodName = nme.defaultGetterToMethod(sym.name)
- val ownerMethod = sym.owner.info.decl(methodName)
- ownerMethod.filter(isExposed).exists
+ 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)
}
-
- !isNonNativeJSClass(sym.owner) || isAttachedMethodExposed
}
}
}
@@ -2519,7 +3165,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
genApplyTypeApply(tree, isStat)
case _ if isJSDefaultParam =>
- js.Transient(UndefinedParam)(toIRType(sym.tpe.resultType))
+ js.Transient(UndefinedParam)
case Select(Super(_, _), _) =>
genSuperCall(tree, isStat)
@@ -2584,7 +3230,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val r = toIRType(to)
def isValueType(tpe: jstpe.Type): Boolean = tpe match {
- case jstpe.NoType | jstpe.BooleanType | jstpe.CharType |
+ case jstpe.VoidType | jstpe.BooleanType | jstpe.CharType |
jstpe.ByteType | jstpe.ShortType | jstpe.IntType | jstpe.LongType |
jstpe.FloatType | jstpe.DoubleType =>
true
@@ -2626,7 +3272,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
private def genThrowClassCastException()(implicit pos: Position): js.Tree = {
val ctor = ClassCastExceptionClass.info.member(
nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty)
- js.Throw(genNew(ClassCastExceptionClass, ctor, Nil))
+ js.UnaryOp(js.UnaryOp.Throw, genNew(ClassCastExceptionClass, ctor, Nil))
}
/** Gen JS code for a super call, of the form Class.super[mix].fun(args).
@@ -2664,10 +3310,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
if (isStaticModule(currentClassSym) && !isModuleInitialized.value &&
currentMethodSym.isClassConstructor) {
isModuleInitialized.value = true
- val thisType = jstpe.ClassType(encodeClassFullName(currentClassSym))
- val initModule = js.StoreModule(encodeClassRef(currentClassSym),
- js.This()(thisType))
- js.Block(superCall, initModule)
+ js.Block(superCall, js.StoreModule())
} else {
superCall
}
@@ -2675,10 +3318,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
/** Gen JS code for a constructor call (new).
+ *
* Further refined into:
- * * new String(...)
* * new of a hijacked boxed class
- * * new of an anonymous function class that was recorded as JS function
+ * * new of a JS function class
* * new of a JS class
* * new Array
* * regular new
@@ -2698,81 +3341,74 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
} else if (isJSFunctionDef(clsSym)) {
val classDef = consumeLazilyGeneratedAnonClass(clsSym)
genJSFunction(classDef, args.map(genExpr))
- } else if (clsSym.isAnonymousFunction) {
- val classDef = consumeLazilyGeneratedAnonClass(clsSym)
- tryGenAnonFunctionClass(classDef, args.map(genExpr)).getOrElse {
- // Cannot optimize anonymous function class. Generate full class.
- generatedClasses +=
- ((clsSym, None, nestedGenerateClass(clsSym)(genClass(classDef))))
- genNew(clsSym, ctor, genActualArgs(ctor, args))
- }
} else if (isJSType(clsSym)) {
genPrimitiveJSNew(tree)
} else {
toTypeRef(tpe) match {
- case cls: jstpe.ClassRef =>
- genNew(cls, ctor, genActualArgs(ctor, args))
+ 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.
- * Most label-applys are caught upstream (while and do..while loops,
- * jumps to next case of a pattern match), but some are still handled here:
- * * Jumps to enclosing label-defs, including tail-recursive calls
- * * Jump to the end of a pattern match
+ *
+ * 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
- if (enclosingLabelDefParams.contains(sym)) {
- genEnclosingLabelApply(tree)
- } else if (countsOfReturnsToMatchCase.contains(sym)) {
- /* Jump the to a next-`case` label of a pattern match.
- *
- * Such labels are not enclosing. Instead, they are forward jumps to a
- * following case LabelDef. For those labels, we generate a js.Return
- * and keep track of how many such returns we generate, so that the
- * enclosing `genTranslatedMatch` can optimize away the labeled blocks
- * in some cases, notably when they are not used at all or used only
- * once.
- *
- * Next-case labels have no argument.
- */
- assert(args.isEmpty, tree)
- countsOfReturnsToMatchCase(sym) += 1
- js.Return(js.Undefined(), encodeLabelSym(sym))
- } else if (countsOfReturnsToMatchEnd.contains(sym)) {
- /* Jump the to the match-end of a pattern match.
- * This is similar to the jumps to next-case (see above), except that
- * match-end labels hae exactly one argument, which is the result of the
- * pattern match (of type BoxedUnit if the match is in statement position).
- * We simply `return` the argument as the result of the labeled block
- * surrounding the match.
- */
- assert(args.size == 1, tree)
- countsOfReturnsToMatchEnd(sym) += 1
- js.Return(genExpr(args.head), encodeLabelSym(sym))
- } else {
- /* No other label apply should ever happen. If it does, then we
- * have missed a pattern of LabelDef/LabelApply and some new
- * translation must be found for it.
- */
+ 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 a label-apply to an enclosing label def.
+ /** Gen multiple "parallel" assignments.
*
- * This is typically used for tail-recursive calls.
- *
- * Basically this is compiled into
- * continue labelDefIdent;
- * but arguments need to be updated beforehand.
+ * 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
@@ -2784,20 +3420,17 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* If, after elimination of trivial assignments, only one assignment
* remains, then we do not use a temporary variable for this one.
*/
- private def genEnclosingLabelApply(tree: Apply): js.Tree = {
- implicit val pos = tree.pos
- val Apply(fun, args) = tree
- val sym = fun.symbol
+ 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 formalArgs = enclosingLabelDefParams(sym)
val quadruplets =
- List.newBuilder[(js.VarRef, jstpe.Type, js.Ident, js.Tree)]
+ List.newBuilder[(js.VarRef, jstpe.Type, js.LocalIdent, js.Tree)]
- for ((formalArgSym, arg) <- formalArgs.zip(args)) {
- val formalArg = encodeLocalSym(formalArgSym)
+ for ((formalArgSym, arg) <- targetSyms.zip(values)) {
+ val formalArgName = encodeLocalSymName(formalArgSym)
val actualArg = genExpr(arg)
/* #3267 The formal argument representing the special `this` of a
@@ -2814,7 +3447,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val isTailJumpThisLocalVar = formalArgSym.name == nme.THIS
val tpe =
- if (isTailJumpThisLocalVar) currentClassType
+ if (isTailJumpThisLocalVar) currentThisTypeNullable
else toIRType(formalArgSym.tpe)
val fixedActualArg =
@@ -2822,13 +3455,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
else actualArg
actualArg match {
- case js.VarRef(`formalArg`) =>
+ case js.VarRef(`formalArgName`) =>
// This is trivial assignment, we don't need it
case _ =>
mutatedLocalVars += formalArgSym
- quadruplets += ((js.VarRef(formalArg)(tpe), tpe,
- freshLocalIdent("temp$" + formalArg.name),
+ quadruplets += ((js.VarRef(formalArgName)(tpe), tpe,
+ freshLocalIdent(formalArgName.withPrefix("temp$")),
fixedActualArg))
}
}
@@ -2836,25 +3469,21 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
quadruplets.result()
}
- // The actual jump (return(labelDefIdent) undefined;)
- val jump = js.Return(js.Undefined(), encodeLabelSym(sym))
-
quadruplets match {
- case Nil => jump
+ case Nil =>
+ Nil
- case (formalArg, argType, _, actualArg) :: Nil =>
- js.Block(
- js.Assign(formalArg, actualArg),
- jump)
+ case (formalArg, _, _, actualArg) :: Nil =>
+ js.Assign(formalArg, actualArg) :: Nil
case _ =>
val tempAssignments =
for ((_, argType, tempArg, actualArg) <- quadruplets)
- yield js.VarDef(tempArg, argType, mutable = false, actualArg)
+ yield js.VarDef(tempArg, NoOriginalName, argType, mutable = false, actualArg)
val trueAssignments =
for ((formalArg, argType, tempArg, _) <- quadruplets)
- yield js.Assign(formalArg, js.VarRef(tempArg)(argType))
- js.Block(tempAssignments ++ trueAssignments :+ jump)
+ yield js.Assign(formalArg, js.VarRef(tempArg.name)(argType))
+ tempAssignments ::: trueAssignments
}
}
@@ -2872,74 +3501,94 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
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 (foreignIsImplClass(sym.owner)) {
- genTraitImplApply(sym, args map genExpr)
+ } 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))
+ genActualArgs(sym, args), inline = inline, noinline = noinline)
}
}
def genApplyMethodMaybeStatically(receiver: js.Tree,
- method: Symbol, arguments: List[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)
+ genApplyMethodStatically(receiver, method, arguments, inline = inline, noinline = noinline)
else
- genApplyMethod(receiver, method, arguments)
+ 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])(
+ 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")
- js.Apply(js.ApplyFlags.empty, receiver, encodeMethodSym(method), arguments)(
+ 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])(implicit pos: Position): js.Tree = {
+ 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.NoType
+ if (method.isClassConstructor) jstpe.VoidType
else toIRType(method.tpe.resultType)
- js.ApplyStatically(flags, receiver, encodeClassRef(method.owner),
+ js.ApplyStatically(flags, receiver, encodeClassName(method.owner),
methodIdent, arguments)(resultType)
}
- def genTraitImplApply(method: Symbol, arguments: List[js.Tree])(
- implicit pos: Position): js.Tree = {
- if (method.isMixinConstructor && isJSImplClass(method.owner)) {
- /* 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 {
- genApplyStatic(method, arguments)
- }
- }
-
def genApplyJSClassMethod(receiver: js.Tree, method: Symbol,
- arguments: List[js.Tree])(implicit pos: Position): js.Tree = {
- genApplyStatic(method, receiver :: arguments)
+ 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])(
+ def genApplyStatic(method: Symbol, arguments: List[js.Tree],
+ inline: Boolean = false, noinline: Boolean = false)(
implicit pos: Position): js.Tree = {
- js.ApplyStatic(js.ApplyFlags.empty.withPrivate(method.isPrivate),
- encodeClassRef(method.owner), encodeMethodSym(method), arguments)(
- toIRType(method.tpe.resultType))
+ 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)(
@@ -2994,7 +3643,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
js.UnaryOp(IntToLong, intValue)
}
case jstpe.FloatType =>
- js.UnaryOp(js.UnaryOp.DoubleToFloat, doubleValue)
+ if (from == jstpe.LongType)
+ js.UnaryOp(js.UnaryOp.LongToFloat, value)
+ else
+ js.UnaryOp(js.UnaryOp.DoubleToFloat, doubleValue)
case jstpe.DoubleType =>
doubleValue
}
@@ -3015,14 +3667,15 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
s"isInstanceOf[${sym.fullName}] not supported because it is a JS trait")
js.BooleanLiteral(true)
} else {
- js.Unbox(js.JSBinaryOp(
- js.JSBinaryOp.instanceof, value, genPrimitiveJSClass(sym)), 'Z')
+ 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, toTypeRef(to))
+ js.IsInstanceOf(value, toIRType(to).toNonNullable)
}
}
@@ -3031,7 +3684,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
implicit pos: Position): js.Tree = {
def default: js.Tree =
- js.AsInstanceOf(value, toTypeRef(to))
+ js.AsInstanceOf(value, toIRType(to))
val sym = to.typeSymbol
@@ -3058,13 +3711,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
implicit pos: Position): js.Tree = {
assert(!isJSFunctionDef(clazz),
s"Trying to instantiate a JS function def $clazz")
- genNew(encodeClassRef(clazz), ctor, arguments)
+ genNew(encodeClassName(clazz), ctor, arguments)
}
/** Gen JS code for a call to a Scala class constructor. */
- def genNew(cls: jstpe.ClassRef, ctor: Symbol, arguments: List[js.Tree])(
+ def genNew(className: ClassName, ctor: Symbol, arguments: List[js.Tree])(
implicit pos: Position): js.Tree = {
- js.New(cls, encodeMethodSym(ctor), arguments)
+ js.New(className, encodeMethodSym(ctor), arguments)
}
/** Gen JS code for a call to a constructor of a hijacked class.
@@ -3075,20 +3728,15 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
args: List[js.Tree])(implicit pos: Position): js.Tree = {
val flags = js.ApplyFlags.empty
- val encodedName = encodeClassFullName(clazz)
- val moduleClass = clazz.companionModule.moduleClass
+ val className = encodeClassName(clazz)
- val js.Ident(initName, origName) = encodeMethodSym(ctor)
- val newMethodName = initName match {
- case "init___" =>
- "$new__" + encodedName
- case _ =>
- "$new" + initName.stripPrefix("init_") + "__" + encodedName
- }
- val newMethodIdent = js.Ident(newMethodName, origName)
+ val initName = encodeMethodSym(ctor).name
+ val newName = MethodName(newSimpleMethodName, initName.paramTypeRefs,
+ jstpe.ClassRef(className))
+ val newMethodIdent = js.MethodIdent(newName)
- js.Apply(flags, genLoadModule(moduleClass), newMethodIdent, args)(
- jstpe.ClassType(encodedName))
+ js.ApplyStatic(flags, className, newMethodIdent, args)(
+ jstpe.ClassType(className, nullable = true))
}
/** Gen JS code for creating a new Array: new Array[T](length)
@@ -3098,22 +3746,26 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
*/
def genNewArray(arrayTypeRef: jstpe.ArrayTypeRef, arguments: List[js.Tree])(
implicit pos: Position): js.Tree = {
- assert(arguments.length <= arrayTypeRef.dimensions,
- "too many arguments for array constructor: found " + arguments.length +
- " but array has only " + arrayTypeRef.dimensions +
- " dimension(s)")
+ assert(arguments.size == 1,
+ "expected exactly 1 argument for array constructor: found " +
+ s"${arguments.length} at $pos")
- js.NewArray(arrayTypeRef, arguments)
+ js.NewArray(arrayTypeRef, arguments.head)
}
- /** Gen JS code for an array literal.
- */
- def genArrayValue(tree: Tree): js.Tree = {
- implicit val pos = tree.pos
+ /** 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)
+ js.ArrayValue(arrayTypeRef, elems.map(genExpr))
}
/** Gen JS code for a Match, i.e., a switch-able pattern match.
@@ -3173,105 +3825,67 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
implicit val pos = tree.pos
val Match(selector, cases) = tree
- /* We adapt the selector to IntType so that we can use it in a js.Match,
- * just like GenBCode does for the JVM. This seems to be redundant,
- * though, as anything that comes out of the pattern matching has already
- * been adapted to an Int (along with the cases). However, since GenBCode
- * adapts, we do the same, to be on the safe side (for example, a
- * compiler plugin could generate a Match with other types of
- * primitives ...).
+ /* 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 expr = adaptPrimitive(genExpr(selector), jstpe.IntType)
+ val genSelector = genExpr(selector)
- val resultType = toIRType(tree.tpe)
+ val resultType =
+ if (isStat) jstpe.VoidType
+ else toIRType(tree.tpe)
- val defaultLabelSym = cases.collectFirst {
+ val optDefaultLabelSymAndInfo = cases.collectFirst {
case CaseDef(Ident(nme.WILDCARD), EmptyTree,
body @ LabelDef(_, Nil, rhs)) if hasSynthCaseSymbol(body) =>
- body.symbol
- }.getOrElse(NoSymbol)
+ body.symbol -> new EnclosingLabelDefInfoWithResultAsAssigns(Nil)
+ }
- var clauses: List[(List[js.IntLiteral], js.Tree)] = Nil
+ var clauses: List[(List[js.MatchableLiteral], js.Tree)] = Nil
var optElseClause: Option[js.Tree] = None
- var optElseClauseLabel: Option[js.Ident] = None
-
- def genJumpToElseClause(implicit pos: ir.Position): js.Tree = {
- if (optElseClauseLabel.isEmpty)
- optElseClauseLabel = Some(freshLocalIdent("default"))
- js.Return(js.Undefined(), optElseClauseLabel.get)
- }
-
- for (caze @ CaseDef(pat, guard, body) <- cases) {
- assert(guard == EmptyTree, s"found a case guard at ${caze.pos}")
-
- def genBody(body: Tree): js.Tree = body match {
- case app @ Apply(_, Nil) if app.symbol == defaultLabelSym =>
- genJumpToElseClause
- case Block(List(app @ Apply(_, Nil)), _) if app.symbol == defaultLabelSym =>
- genJumpToElseClause
-
- case If(cond, thenp, elsep) =>
- js.If(genExpr(cond), genBody(thenp), genBody(elsep))(
- resultType)(body.pos)
-
- /* For #1955. If we receive a tree with the shape
- * if (cond) {
- * thenp
- * } else {
- * elsep
- * }
- * scala.runtime.BoxedUnit.UNIT
- * we rewrite it as
- * if (cond) {
- * thenp
- * scala.runtime.BoxedUnit.UNIT
- * } else {
- * elsep
- * scala.runtime.BoxedUnit.UNIT
- * }
- * so that it fits the shape of if/elses we can deal with.
- */
- case Block(List(If(cond, thenp, elsep)), s: Select)
- if s.symbol == definitions.BoxedUnit_UNIT =>
- val newThenp = Block(thenp, s).setType(s.tpe).setPos(thenp.pos)
- val newElsep = Block(elsep, s).setType(s.tpe).setPos(elsep.pos)
- js.If(genExpr(cond), genBody(newThenp), genBody(newElsep))(
- resultType)(body.pos)
- case _ =>
+ 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)
- }
- /* value.intValue implicitly adapts the constant value to an Int. This
- * is also what GenBCode for the JVM. See also the comment about
- * adaptPrimitive at the beginning of this method.
- */
- def genLiteral(lit: Literal): js.IntLiteral =
- js.IntLiteral(lit.value.intValue)(lit.pos)
+ def invalidCase(tree: Tree): Nothing =
+ abort(s"Invalid case in alternative in switch-like pattern match: $tree at: ${tree.pos}")
- pat match {
- case lit: Literal =>
- clauses = (List(genLiteral(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 => genLiteral(lit)
+ 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 _ =>
- abort("Invalid case in alternative in switch-like pattern match: " +
- tree + " at: " + tree.pos)
+ genBody(body)
+ })
+ case Alternative(alts) =>
+ val genAlts = {
+ alts map {
+ case lit: Literal => genMatchableLiteral(lit)
+ case _ => invalidCase(tree)
+ }
}
- }
- clauses = (genAlts, genBody(body)) :: clauses
- case _ =>
- abort("Invalid case statement in switch-like pattern match: " +
- tree + " at: " + (tree.pos))
+ clauses = (genAlts, genBody(body)) :: clauses
+ case _ =>
+ invalidCase(tree)
+ }
}
}
@@ -3285,113 +3899,171 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* `case n if ... =>`, which are used instead of `if` chains for
* convenience and/or readability.
*/
- def buildMatch(selector: js.Tree,
- cases: List[(List[js.IntLiteral], js.Tree)],
+ 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
- * `selector`.
+ * `genSelector`.
*/
- js.Block(exprToStat(selector), default)
+ 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.
*/
- js.If(js.BinaryOp(js.BinaryOp.Int_==, selector, uniqueAlt),
- caseRhs, default)(tpe)
+ 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 _ =>
- js.Match(selector, cases, default)(tpe)
+ genStatsAndExpr0
}
+ } else {
+ genBlockWithCaseLabelDefs(stats :+ expr, isStat)
}
- optElseClauseLabel.fold[js.Tree] {
- buildMatch(expr, clauses.reverse, elseClause, resultType)
- } { elseClauseLabel =>
- val matchResultLabel = freshLocalIdent("matchResult")
- val patchedClauses = for ((alts, body) <- clauses) yield {
- implicit val pos = body.pos
- val newBody =
- if (isStat) js.Block(body, js.Return(js.Undefined(), matchResultLabel))
- else js.Return(body, matchResultLabel)
- (alts, newBody)
- }
- js.Labeled(matchResultLabel, resultType, js.Block(List(
- js.Labeled(elseClauseLabel, jstpe.NoType, {
- buildMatch(expr, patchedClauses.reverse, js.Skip(), jstpe.NoType)
- }),
- elseClause
- )))
- }
+ /* 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 genBlock(tree: Block, isStat: Boolean): js.Tree = {
- implicit val pos = tree.pos
- val Block(stats, expr) = tree
+ private def genBlockWithCaseLabelDefs(trees: List[Tree], isStat: Boolean)(
+ implicit pos: Position): List[js.Tree] = {
- /** Predicate satisfied by LabelDefs produced by the pattern matcher */
- def isCaseLabelDef(tree: Tree) =
- tree.isInstanceOf[LabelDef] && hasSynthCaseSymbol(tree)
+ val (prologue, casesAndRest) = trees.span(!isCaseLabelDef(_))
- def translateMatch(expr: LabelDef) = {
- /* Block that appeared as the result of a translated match
- * Such blocks are recognized by having at least one element that is
- * a so-called case-label-def.
- * The method `genTranslatedMatch()` takes care of compiling the
- * actual match.
- *
- * The assumption is once we encounter a case, the remainder of the
- * block will consist of cases.
- * The prologue may be empty, usually it is the valdef that stores
- * the scrut.
- */
- val (prologue, cases) = stats.span(s => !isCaseLabelDef(s))
- assert(cases.forall(isCaseLabelDef),
- "Assumption on the form of translated matches broken: " + tree)
-
- val genPrologue = prologue map genStat
- val translatedMatch =
- genTranslatedMatch(cases.map(_.asInstanceOf[LabelDef]), expr)
-
- js.Block(genPrologue :+ translatedMatch)
- }
-
- expr match {
- case expr: LabelDef if isCaseLabelDef(expr) =>
- translateMatch(expr)
-
- // Sometimes the pattern matcher casts its final result
- case Apply(TypeApply(Select(expr: LabelDef, nme.asInstanceOf_Ob),
- List(targ)), Nil)
- if isCaseLabelDef(expr) =>
- genIsAsInstanceOf(translateMatch(expr), expr.tpe, targ.tpe,
- cast = true)
-
- // Peculiar shape generated by `return x match {...}` - #2928
- case Return(retExpr: LabelDef) if isCaseLabelDef(retExpr) =>
- val result = translateMatch(retExpr)
- val label = getEnclosingReturnLabel()
- if (result.tpe == jstpe.NoType) {
- // Could not actually reproduce this, but better be safe than sorry
- js.Block(result, js.Return(js.Undefined(), label))
- } else {
- js.Return(result, label)
- }
+ 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)
+ }
- case _ =>
- assert(!stats.exists(isCaseLabelDef), "Found stats with case label " +
- s"def in non-match block at ${tree.pos}: $tree")
+ genPrologue ::: genCasesAndRest
+ }
+ }
- /* Normal block */
- val statements = stats map genStat
- val expression = genStatOrExpr(expr, isStat)
- js.Block(statements :+ expression)
+ /** 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 a translated match
+ /** 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
@@ -3399,91 +4071,164 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
*
* 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 `return`s out of the block.
+ * - 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`.
*/
- def genTranslatedMatch(cases: List[LabelDef],
- matchEnd: LabelDef)(implicit pos: Position): js.Tree = {
-
- val matchEndSym = matchEnd.symbol
- countsOfReturnsToMatchEnd(matchEndSym) = 0
+ private def genTranslatedCases(cases: List[LabelDef], isStat: Boolean)(
+ implicit pos: Position): List[js.Tree] = {
- val nextCaseSyms = (cases.tail map (_.symbol)) :+ NoSymbol
+ assert(!cases.isEmpty,
+ s"genTranslatedCases called with no cases at $pos")
- val translatedCases = for {
- (LabelDef(_, Nil, rhs), nextCaseSym) <- cases zip nextCaseSyms
+ val translatedCasesInit = for {
+ (caseLabelDef, nextCaseSym) <- cases.zip(cases.tail.map(_.symbol))
} yield {
- if (nextCaseSym.exists)
- countsOfReturnsToMatchCase(nextCaseSym) = 0
+ implicit val pos = caseLabelDef.pos
+ assert(caseLabelDef.params.isEmpty,
+ s"found case LabelDef with parameters at $pos")
- def genCaseBody(tree: Tree): js.Tree = {
- implicit val pos = tree.pos
- tree match {
- case If(cond, thenp, elsep) =>
- js.If(genExpr(cond), genCaseBody(thenp), genCaseBody(elsep))(
- jstpe.NoType)
+ val info = new EnclosingLabelDefInfoWithResultAsAssigns(Nil)
- case Block(stats, expr) =>
- js.Block((stats map genStat) :+ genCaseBody(expr))
+ 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 Apply(_, Nil) if tree.symbol == nextCaseSym =>
- js.Skip()
+ 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 _ =>
- genStat(tree)
+ 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)
}
- val translatedBody = genCaseBody(rhs)
+ genOptimizedCaseLabeled(encodeLabelSym(nextCaseSym), translatedBody,
+ info.generatedReturns)
+ }
- if (!nextCaseSym.exists) {
- translatedBody
- } else {
- val returnCount = countsOfReturnsToMatchCase.remove(nextCaseSym).get
- genOptimizedCaseLabeled(encodeLabelSym(nextCaseSym), translatedBody,
- returnCount)
- }
+ 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)
}
- val returnCount = countsOfReturnsToMatchEnd.remove(matchEndSym).get
+ 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 LabelDef(_, List(matchEndParam), matchEndBody) = matchEnd
+ val innerResultType = toIRType(matchEndParam.tpe)
+ val optimized = genOptimizedMatchEndLabeled(encodeLabelSym(sym),
+ innerResultType, translatedCases, info.generatedReturns)
- val innerResultType = toIRType(matchEndParam.tpe)
- val optimized = genOptimizedMatchEndLabeled(encodeLabelSym(matchEndSym),
- innerResultType, translatedCases, returnCount)
+ matchEndBody match {
+ case Ident(_) if matchEndParam.symbol == matchEndBody.symbol =>
+ // matchEnd is identity.
+ optimized
- matchEndBody match {
- case Ident(_) if matchEndParam.symbol == matchEndBody.symbol =>
- // matchEnd is identity.
- optimized
+ case Literal(Constant(())) =>
+ // Unit return type.
+ 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())
+ }
- case _ =>
- // matchEnd does something.
- val ident = encodeLocalSym(matchEndParam.symbol)
- js.Block(
- js.VarDef(ident, innerResultType, mutable = false, optimized),
- genExpr(matchEndBody))
+ /* 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)
}
}
@@ -3491,7 +4236,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* 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 alltogether.
+ * 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
@@ -3529,12 +4274,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* all jumps to case labels are already caught upstream by `genCaseBody()`
* inside `genTranslatedMatch()`.
*/
- private def genOptimizedCaseLabeled(label: js.Ident,
+ private def genOptimizedCaseLabeled(label: LabelName,
translatedBody: js.Tree, returnCount: Int)(
implicit pos: Position): js.Tree = {
def default: js.Tree =
- js.Labeled(label, jstpe.NoType, translatedBody)
+ js.Labeled(label, jstpe.VoidType, translatedBody)
if (returnCount == 0) {
translatedBody
@@ -3544,21 +4289,21 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
translatedBody match {
case js.Block(stats) =>
val (stats1, testAndStats2) = stats.span {
- case js.If(_, js.Return(js.Undefined(), `label`), js.Skip()) =>
+ case js.If(_, js.Return(_, `label`), js.Skip()) =>
false
case _ =>
true
}
testAndStats2 match {
- case js.If(cond, _, _) :: stats2 =>
+ 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), js.Skip())(jstpe.NoType))
+ js.Block(stats1 :+ js.If(notCond, js.Block(stats2), returnedValue)(jstpe.VoidType))
case _ :: _ =>
throw new AssertionError("unreachable code")
@@ -3586,7 +4331,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* !!! There is quite of bit of code duplication with
* OptimizerCore.tryOptimizePatternMatch.
*/
- def genOptimizedMatchEndLabeled(label: js.Ident, tpe: jstpe.Type,
+ def genOptimizedMatchEndLabeled(label: LabelName, tpe: jstpe.Type,
translatedCases: List[js.Tree], returnCount: Int)(
implicit pos: Position): js.Tree = {
def default: js.Tree =
@@ -3726,9 +4471,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
case jstpe.LongType =>
js.BinaryOp(js.BinaryOp.Long_-, js.LongLiteral(0), src)
case jstpe.FloatType =>
- js.BinaryOp(js.BinaryOp.Float_-, js.FloatLiteral(0.0f), src)
+ js.BinaryOp(js.BinaryOp.Float_*, js.FloatLiteral(-1.0f), src)
case jstpe.DoubleType =>
- js.BinaryOp(js.BinaryOp.Double_-, js.DoubleLiteral(0), src)
+ js.BinaryOp(js.BinaryOp.Double_*, js.DoubleLiteral(-1.0), src)
}
case NOT =>
(opType: @unchecked) match {
@@ -3781,50 +4526,78 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
if (opType == jstpe.AnyType) rsrc_in
else adaptPrimitive(rsrc_in, if (isShift) jstpe.IntType else opType)
+ def regular(op: js.BinaryOp.Code): js.Tree =
+ js.BinaryOp(op, lsrc, rsrc)
+
(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_>=
+ def comparison(signedOp: js.BinaryOp.Code, unsignedOp: js.BinaryOp.Code): js.Tree = {
+ (lsrc, rsrc) match {
+ case (IntFlipSign(flippedLhs), IntFlipSign(flippedRhs)) =>
+ js.BinaryOp(unsignedOp, flippedLhs, flippedRhs)
+ case (IntFlipSign(flippedLhs), js.IntLiteral(r)) =>
+ js.BinaryOp(unsignedOp, flippedLhs, js.IntLiteral(r ^ Int.MinValue)(rsrc.pos))
+ case (js.IntLiteral(l), IntFlipSign(flippedRhs)) =>
+ js.BinaryOp(unsignedOp, js.IntLiteral(l ^ Int.MinValue)(lsrc.pos), flippedRhs)
+ case _ =>
+ regular(signedOp)
+ }
+ }
+
+ (code: @switch) match {
+ case ADD => regular(Int_+)
+ case SUB => regular(Int_-)
+ case MUL => regular(Int_*)
+ case DIV => regular(Int_/)
+ case MOD => regular(Int_%)
+ case OR => regular(Int_|)
+ case AND => regular(Int_&)
+ case XOR => regular(Int_^)
+ case LSL => regular(Int_<<)
+ case LSR => regular(Int_>>>)
+ case ASR => regular(Int_>>)
+ case EQ => regular(Int_==)
+ case NE => regular(Int_!=)
+
+ case LT => comparison(Int_<, Int_unsigned_<)
+ case LE => comparison(Int_<=, Int_unsigned_<=)
+ case GT => comparison(Int_>, Int_unsigned_>)
+ case GE => comparison(Int_>=, Int_unsigned_>=)
}
- 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_>=
+ def comparison(signedOp: js.BinaryOp.Code, unsignedOp: js.BinaryOp.Code): js.Tree = {
+ (lsrc, rsrc) match {
+ case (LongFlipSign(flippedLhs), LongFlipSign(flippedRhs)) =>
+ js.BinaryOp(unsignedOp, flippedLhs, flippedRhs)
+ case (LongFlipSign(flippedLhs), js.LongLiteral(r)) =>
+ js.BinaryOp(unsignedOp, flippedLhs, js.LongLiteral(r ^ Long.MinValue)(rsrc.pos))
+ case (js.LongLiteral(l), LongFlipSign(flippedRhs)) =>
+ js.BinaryOp(unsignedOp, js.LongLiteral(l ^ Long.MinValue)(lsrc.pos), flippedRhs)
+ case _ =>
+ regular(signedOp)
+ }
+ }
+
+ (code: @switch) match {
+ case ADD => regular(Long_+)
+ case SUB => regular(Long_-)
+ case MUL => regular(Long_*)
+ case DIV => regular(Long_/)
+ case MOD => regular(Long_%)
+ case OR => regular(Long_|)
+ case XOR => regular(Long_^)
+ case AND => regular(Long_&)
+ case LSL => regular(Long_<<)
+ case LSR => regular(Long_>>>)
+ case ASR => regular(Long_>>)
+ case EQ => regular(Long_==)
+ case NE => regular(Long_!=)
+ case LT => comparison(Long_<, Long_unsigned_<)
+ case LE => comparison(Long_<=, Long_unsigned_<=)
+ case GT => comparison(Long_>, Long_unsigned_>)
+ case GE => comparison(Long_>=, Long_unsigned_>=)
}
- js.BinaryOp(op, lsrc, rsrc)
case jstpe.FloatType =>
def withFloats(op: Int): js.Tree =
@@ -3887,8 +4660,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
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
+ case jstpe.AnyType | _:jstpe.ClassType => true
+ case _ => false
}
if (eqeq &&
// don't call equals if we have a literal null at either side
@@ -3917,12 +4690,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
}
- /** See comment in `genEqEqPrimitive()` about `mustUseAnyComparator`. */
- private lazy val shouldPreserveEqEqBugWithJLFloatDouble = {
- val v = scala.util.Properties.versionNumberString
- v.startsWith("2.11.") || v == "2.12.1"
- }
-
/** 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 = {
@@ -3938,9 +4705,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* 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).
- *
- * The latter case is only avoided in 2.12.2+, to remain bug-compatible
- * with the Scala/JVM compiler.
*/
val mustUseAnyComparator: Boolean = {
val lsym = ltpe.typeSymbol
@@ -3949,12 +4713,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
isMaybeBoxed(lsym) && isMaybeBoxed(rsym) && {
val areSameFinals =
ltpe.isFinalType && rtpe.isFinalType && lsym == rsym
- !areSameFinals || {
- (lsym == BoxedFloatClass || lsym == BoxedDoubleClass) && {
- // Bug-compatibility for Scala < 2.12.2
- !shouldPreserveEqEqBugWithJLFloatDouble
- }
- }
+ !areSameFinals || (lsym == BoxedFloatClass || lsym == BoxedDoubleClass)
}
}
}
@@ -3969,9 +4728,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
} else platform.externalEquals
// scalastyle:on line.size.limit
}
- val moduleClass = equalsMethod.owner
- val instance = genLoadModule(moduleClass)
- genApplyMethod(instance, equalsMethod, List(lsrc, rsrc))
+ 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)) {
@@ -3981,8 +4741,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
/* This requires to evaluate both operands in local values first.
* The optimizer will eliminate them if possible.
*/
- val ltemp = js.VarDef(freshLocalIdent(), lsrc.tpe, mutable = false, lsrc)
- val rtemp = js.VarDef(freshLocalIdent(), rsrc.tpe, mutable = false, rsrc)
+ 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,
@@ -4065,7 +4827,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
js.Assign(genSelect(fun.tpe.paramTypes(1)), arguments(1))
} else {
// length of the array
- js.ArrayLength(arrayValue)
+ js.UnaryOp(js.UnaryOp.Array_length,
+ js.UnaryOp(js.UnaryOp.CheckNotNull, arrayValue))
}
}
@@ -4078,16 +4841,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val newReceiver = genExpr(receiver)
val newArg = genStatOrExpr(arg, isStat)
newReceiver match {
- case js.This() =>
- // common case for which there is no side-effect nor NPE
+ case newReceiver: js.VarRef if !newReceiver.tpe.isNullable =>
+ // common case (notably for `this`) for which there is no side-effect nor NPE
newArg
case _ =>
- val NPECtor = getMemberMethod(NullPointerExceptionClass,
- nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty)
js.Block(
- js.If(js.BinaryOp(js.BinaryOp.===, newReceiver, js.Null()),
- js.Throw(genNew(NullPointerExceptionClass, NPECtor, Nil)),
- js.Skip())(jstpe.NoType),
+ js.UnaryOp(js.UnaryOp.CheckNotNull, newReceiver),
newArg)
}
}
@@ -4178,9 +4937,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val receiverType = toIRType(receiver.tpe)
val callTrgIdent = freshLocalIdent()
- val callTrgVarDef =
- js.VarDef(callTrgIdent, receiverType, mutable = false, genExpr(receiver))
- val callTrg = js.VarRef(callTrgIdent)(receiverType)
+ 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
@@ -4325,6 +5084,17 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
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
@@ -4374,11 +5144,33 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
}
+ /** 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.NoType => // for JS interop cases
+ 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 |
@@ -4393,18 +5185,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
def makePrimitiveUnbox(expr: js.Tree, tpe: Type)(
implicit pos: Position): js.Tree = {
toIRType(tpe) match {
- case jstpe.NoType => expr // for JS interop cases
- case jstpe.BooleanType => js.Unbox(expr, 'Z')
- case jstpe.CharType => js.Unbox(expr, 'C')
- case jstpe.ByteType => js.Unbox(expr, 'B')
- case jstpe.ShortType => js.Unbox(expr, 'S')
- case jstpe.IntType => js.Unbox(expr, 'I')
- case jstpe.LongType => js.Unbox(expr, 'J')
- case jstpe.FloatType => js.Unbox(expr, 'F')
- case jstpe.DoubleType => js.Unbox(expr, 'D')
-
- case _ =>
- abort(s"makePrimitiveUnbox requires a primitive type, found $tpe at $pos")
+ case jstpe.VoidType => expr // for JS interop cases
+ case irTpe => js.AsInstanceOf(expr, irTpe)
}
}
@@ -4487,7 +5269,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
fakeNewInstances.flatMap(genCaptureValuesFromFakeNewInstance(_))
}
}
- js.CreateJSClass(encodeClassRef(classSym),
+ js.CreateJSClass(encodeClassName(classSym),
superClassValue :: captureValues)
}
@@ -4500,10 +5282,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
genStatOrExpr(args(1), isStat)
}
- case LINKING_INFO =>
- // runtime.linkingInfo
- js.JSLinkingInfo()
-
case DEBUGGER =>
// js.special.debugger()
js.Debugger()
@@ -4521,28 +5299,131 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
case TYPEOF =>
// js.typeOf(arg)
val arg = genArgs1
- genAsInstanceOf(js.JSUnaryOp(js.JSUnaryOp.typeof, arg),
- StringClass.tpe)
+ 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.Unbox(js.JSBinaryOp(js.JSBinaryOp.in, arg1, arg2), 'Z')
+ js.AsInstanceOf(js.JSBinaryOp(js.JSBinaryOp.in, arg1, arg2),
+ jstpe.BooleanType)
case INSTANCEOF =>
// js.special.instanceof(arg1, arg2)
val (arg1, arg2) = genArgs2
- js.Unbox(js.JSBinaryOp(js.JSBinaryOp.instanceof, arg1, arg2), 'Z')
+ js.AsInstanceOf(js.JSBinaryOp(js.JSBinaryOp.instanceof, arg1, arg2),
+ jstpe.BooleanType)
case DELETE =>
// js.special.delete(arg1, arg2)
val (arg1, arg2) = genArgs2
- js.JSDelete(js.JSBracketSelect(arg1, arg2))
+ js.JSDelete(arg1, arg2)
case FORIN =>
/* js.special.forin(arg1, arg2)
@@ -4559,18 +5440,169 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* once, and after `arg1`.
*/
val (arg1, arg2) = genArgs2
- val objVarDef = js.VarDef(freshLocalIdent("obj"), jstpe.AnyType,
- mutable = false, arg1)
- val fVarDef = js.VarDef(freshLocalIdent("f"), jstpe.AnyType,
- mutable = false, arg2)
+ 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)(jstpe.AnyType)
+ val keyVarRef = js.VarRef(keyVarIdent.name)(jstpe.AnyType)
js.Block(
objVarDef,
fVarDef,
- js.ForIn(objVarDef.ref, keyVarIdent, {
+ 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()
}
}
@@ -4596,6 +5628,25 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
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
@@ -4616,10 +5667,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
s"non-native JS class at $pos")
genApplyMethod(genReceiver, sym, genScalaArgs)
} else if (sym.isClassConstructor) {
- assert(genReceiver.isInstanceOf[js.This],
- "Trying to call a JS super constructor with a non-`this` " +
- "receiver at " + pos)
- js.JSSuperConstructorCall(genJSArgs)
+ 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)
@@ -4653,81 +5702,88 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
}
- def hasExplicitJSEncoding =
- sym.hasAnnotation(JSNameAnnotation) ||
- sym.hasAnnotation(JSBracketAccessAnnotation) ||
- sym.hasAnnotation(JSBracketCallAnnotation)
+ 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 = sym.name match {
- case JSUnaryOpMethodName(code) if argc == 0 =>
+ 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 JSBinaryOpMethodName(code) if argc == 1 =>
+ 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 nme.apply if sym.owner.isSubClass(JSThisFunctionClass) =>
- requireNotSuper()
- genJSBracketMethodApplyOrGlobalRefApply(receiver,
- js.StringLiteral("call"), args)
-
- case nme.apply if !hasExplicitJSEncoding =>
+ case JSCallingConvention.Call =>
requireNotSuper()
- js.JSFunctionApply(ruleOutGlobalScope(receiver), args)
- case _ =>
- def jsFunName: js.Tree = genExpr(jsNameOf(sym))
-
- def genSuperReference(propName: js.Tree): js.Tree = {
- jsSuperClassValue.fold[js.Tree] {
- genJSBracketSelectOrGlobalRef(receiver, propName)
- } { superClassValue =>
- js.JSSuperBracketSelect(superClassValue,
- ruleOutGlobalScope(receiver), propName)
- }
+ if (sym.owner.isSubClass(JSThisFunctionClass)) {
+ genJSBracketMethodApplyOrGlobalRefApply(receiver,
+ js.StringLiteral("call"), args)
+ } else {
+ js.JSFunctionApply(ruleOutGlobalScope(receiver), args)
}
- def genSelectGet(propName: js.Tree): js.Tree =
- genSuperReference(propName)
+ case JSCallingConvention.Property(jsName) =>
+ argsNoSpread match {
+ case Nil => genSelectGet(genExpr(jsName))
+ case value :: Nil => genSelectSet(genExpr(jsName), value)
- def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree =
- js.Assign(genSuperReference(propName), value)
-
- def genCall(methodName: js.Tree,
- args: List[js.TreeOrJSSpread]): js.Tree = {
- jsSuperClassValue.fold[js.Tree] {
- genJSBracketMethodApplyOrGlobalRefApply(
- receiver, methodName, args)
- } { superClassValue =>
- js.JSSuperBracketCall(superClassValue,
- ruleOutGlobalScope(receiver), methodName, args)
- }
+ case _ =>
+ throw new AssertionError(
+ s"property methods should have 0 or 1 non-varargs arguments at $pos")
}
- if (jsInterop.isJSGetter(sym)) {
- assert(argc == 0,
- s"wrong number of arguments for call to JS getter $sym at $pos")
- genSelectGet(jsFunName)
- } else if (jsInterop.isJSSetter(sym)) {
- assert(argc == 1,
- s"wrong number of arguments for call to JS setter $sym at $pos")
- genSelectSet(jsFunName, argsNoSpread.head)
- } else if (jsInterop.isJSBracketAccess(sym)) {
- assert(argc == 1 || argc == 2,
- s"@JSBracketAccess methods should have 1 or 2 non-varargs arguments")
- argsNoSpread match {
- case List(keyArg) =>
- genSelectGet(keyArg)
- case List(keyArg, valueArg) =>
- genSelectSet(keyArg, valueArg)
- }
- } else if (jsInterop.isJSBracketCall(sym)) {
- val (methodName, actualArgs) = extractFirstArg(args)
- genCall(methodName, actualArgs)
- } else {
- genCall(jsFunName, args)
+ 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 {
@@ -4741,46 +5797,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
}
- private object JSUnaryOpMethodName {
- private val map = Map(
- nme.UNARY_+ -> js.JSUnaryOp.+,
- nme.UNARY_- -> js.JSUnaryOp.-,
- nme.UNARY_~ -> js.JSUnaryOp.~,
- nme.UNARY_! -> js.JSUnaryOp.!
- )
-
- def unapply(name: TermName): Option[js.JSUnaryOp.Code] =
- map.get(name)
- }
-
- private object JSBinaryOpMethodName {
- private val map = Map(
- nme.ADD -> js.JSBinaryOp.+,
- nme.SUB -> js.JSBinaryOp.-,
- nme.MUL -> js.JSBinaryOp.*,
- nme.DIV -> js.JSBinaryOp./,
- nme.MOD -> js.JSBinaryOp.%,
-
- nme.LSL -> js.JSBinaryOp.<<,
- nme.ASR -> js.JSBinaryOp.>>,
- nme.LSR -> js.JSBinaryOp.>>>,
- nme.OR -> js.JSBinaryOp.|,
- nme.AND -> js.JSBinaryOp.&,
- nme.XOR -> js.JSBinaryOp.^,
-
- nme.LT -> js.JSBinaryOp.<,
- nme.LE -> js.JSBinaryOp.<=,
- nme.GT -> js.JSBinaryOp.>,
- nme.GE -> js.JSBinaryOp.>=,
-
- nme.ZAND -> js.JSBinaryOp.&&,
- nme.ZOR -> js.JSBinaryOp.||
- )
-
- def unapply(name: TermName): Option[js.JSBinaryOp.Code] =
- map.get(name)
- }
-
/** 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.
@@ -4820,8 +5836,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
js.JSObjectConstr(Nil)
else if (cls == JSArrayClass && args0.isEmpty)
js.JSArrayConstr(Nil)
- else if (cls.isAnonymousClass)
- genAnonJSClassNew(cls, jsClassValue.get, args, fun.pos)
+ 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)
@@ -4836,14 +5852,14 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
implicit pos: Position): js.Tree = {
assert(!isStaticModule(sym) && !sym.isTraitOrInterface,
s"genPrimitiveJSClass called with non-class $sym")
- js.LoadJSConstructor(encodeClassRef(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(encodeClassRef(sym),
+ js.JSNew(js.CreateJSClass(encodeClassName(sym),
jsSuperClassValue :: args), Nil)
}
@@ -4887,18 +5903,21 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
}
- /** 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
+ /** Info about a Scala method param when called as JS method.
*
- * 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.
+ * @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.
*/
- private def genPrimitiveJSArgs(sym: Symbol, args: List[Tree])(
- implicit pos: Position): List[js.TreeOrJSSpread] = {
+ 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
@@ -4914,9 +5933,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* This is pretty fragile, but fortunately we have a huge test suite to
* back us up should scalac alter its behavior.
*
- * Anonymous JS classes are excluded for this treatment, since they are
- * instantiated in a completely different way.
- *
* 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.
*
@@ -4926,15 +5942,18 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* life as local defs, which are not exposed.
*/
- val isAnonJSClassConstructor =
- sym.isClassConstructor && sym.owner.isAnonymousClass
-
- val wereRepeated = enteringPhase(currentRun.uncurryPhase) {
+ val uncurryParams = enteringPhase(currentRun.uncurryPhase) {
for {
- params <- sym.tpe.paramss
- param <- params
+ paramUncurry <- sym.tpe.paramss.flatten
} yield {
- param.name -> isScalaRepeatedParamType(param.tpe)
+ val v = {
+ if (isRepeated(paramUncurry))
+ Some(repeatedToSingle(paramUncurry.tpe))
+ else
+ None
+ }
+
+ paramUncurry.name -> v
}
}.toMap
@@ -4943,36 +5962,58 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
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, paramSym) <- args zip sym.tpe.params) {
- val wasRepeated =
- if (isAnonJSClassConstructor) Some(false)
- else wereRepeated.get(paramSym.name)
-
- wasRepeated match {
- case Some(true) =>
- reversedArgs =
- genPrimitiveJSRepeatedParam(arg) reverse_::: reversedArgs
-
- case Some(false) =>
- val unboxedArg = genExpr(arg)
- val boxedArg = unboxedArg match {
- case js.Transient(UndefinedParam) =>
- unboxedArg
- case _ =>
- val tpe = paramTpes.getOrElse(paramSym.name, paramSym.tpe)
- ensureBoxed(unboxedArg, tpe)
- }
- reversedArgs ::= boxedArg
+ 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)
+ }
- case None =>
- /* This is a parameter introduced by explicitouter or lambdalift,
- * which we ignore.
- */
- assert(sym.isClassConstructor,
- s"Found an unknown param ${paramSym.name} in method " +
- s"${sym.fullName}, which is not a class constructor, at $pos")
+ reversedArgs ::= boxedArg
}
}
@@ -5059,12 +6100,21 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
}
- object WrapArray {
- private val isWrapArray: Set[Symbol] = {
- val wrapArrayModule =
- if (hasNewCollections) ScalaRunTimeModule
- else PredefModule
+ 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,
@@ -5131,78 +6181,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
// Synthesizers for JS functions -------------------------------------------
- /** Try and generate JS code for an anonymous function class.
- *
- * Returns Some() if the class could be rewritten that way, None
- * 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) = {
- *
- * }
- * }
- * new (o, c1, ..., cM)
- *
- * we generate a function:
- *
- * lambda[notype](
- * outer, capture1, ..., captureM, param1, ..., paramN) {
- *
- * }
- *
- * so that, at instantiation point, we can write:
- *
- * new AnonFunctionN(function)
- *
- * the latter tree is returned in case of success.
- *
- * Trickier things apply when the function is specialized.
- */
- private def tryGenAnonFunctionClass(cd: ClassDef,
- capturedArgs: List[js.Tree]): Option[js.Tree] = {
- // scalastyle:off return
- implicit val pos = cd.pos
- val sym = cd.symbol
- assert(sym.isAnonymousFunction,
- s"tryGenAndRecordAnonFunctionClass called with non-anonymous function $cd")
-
- if (!sym.superClass.fullName.startsWith("scala.runtime.AbstractFunction")) {
- /* This is an anonymous class for a non-LMF capable SAM in 2.12.
- * We must not rewrite it, as it would then not inherit from the
- * appropriate parent class and/or interface.
- */
- None
- } else {
- nestedGenerateClass(sym) {
- val (functionBase, arity) =
- tryGenAnonFunctionClassGeneric(cd, capturedArgs)(_ => return None)
-
- Some(genJSFunctionToScala(functionBase, arity))
- }
- }
- // scalastyle:on return
- }
-
- /** Gen a conversion from a JavaScript function into a Scala function. */
- private def genJSFunctionToScala(jsFunction: js.Tree, arity: Int)(
- implicit pos: Position): js.Tree = {
- val clsSym = getRequiredClass("scala.scalajs.runtime.AnonFunction" + arity)
- val ctor = clsSym.primaryConstructor
- genNew(clsSym, ctor, List(jsFunction))
- }
-
/** Gen JS code for a JS function class.
*
* This is called when emitting a ClassDef that represents an anonymous
@@ -5211,11 +6189,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* functions are not classes, we deconstruct the ClassDef, then
* reconstruct it to be a genuine Closure.
*
- * Compared to `tryGenAnonFunctionClass()`, 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.
+ * 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:
*
@@ -5228,8 +6204,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
*
* we generate a function:
*
- * lambda[notype](
- * outer, capture1, ..., captureM, param1, ..., paramN) {
+ * arrow-lambda(param1, ..., paramN) {
* outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM)
* }
*/
@@ -5239,26 +6214,18 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
s"genAndRecordJSFunctionClass called with non-JS function $cd")
nestedGenerateClass(sym) {
- val (function, _) = tryGenAnonFunctionClassGeneric(cd, captures)(msg =>
- abort(s"Could not generate function for JS function: $msg"))
-
- function
+ genJSFunctionInner(cd, captures)
}
}
- /** Code common to tryGenAndRecordAnonFunctionClass and
- * genAndRecordJSFunctionClass.
- */
- private def tryGenAnonFunctionClassGeneric(cd: ClassDef,
- initialCapturedArgs: List[js.Tree])(
- fail: (=> String) => Nothing): (js.Tree, Int) = {
+ /** 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
- // First checks
-
- if (sym.isSubClass(PartialFunctionClass))
- fail(s"Cannot rewrite PartialFunction $cd")
+ 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
@@ -5286,10 +6253,14 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
if (!ddsym.isPrimaryConstructor)
fail(s"Non-primary constructor $ddsym in anon function $cd")
} else {
- val name = dd.name.toString
- if (name == "apply" || (ddsym.isSpecialized && name.startsWith("apply$"))) {
- if ((applyDef eq null) || ddsym.isSpecialized)
+ 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")
@@ -5322,38 +6293,57 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
val usedCtorParams =
if (hasUnusedOuterCtorParam) ctorParams.tail
else ctorParams
- val ctorParamDefs = usedCtorParams map { p =>
- // in the apply method's context
- js.ParamDef(encodeLocalSym(p)(p.pos), toIRType(p.tpe),
- mutable = false, rest = false)(p.pos)
- }
+ val ctorParamDefs = usedCtorParams.map(genParamDef(_))
// Third step: emit the body of the apply method def
val applyMethod = withScopedVars(
- paramAccessorLocals := (paramAccessors zip ctorParamDefs).toMap,
- tryingToGenMethodAsJSFunction := true
+ paramAccessorLocals := (paramAccessors zip ctorParamDefs).toMap
) {
- try {
- genMethodWithCurrentLocalNameScope(applyDef).getOrElse(
- abort(s"Oops, $applyDef did not produce a method"))
- } catch {
- case e: CancelGenMethodAsJSFunction =>
- fail(e.getMessage)
- }
+ genMethodWithCurrentLocalNameScope(applyDef)
}
// Fourth step: patch the body to unbox parameters and box result
- val js.MethodDef(_, _, params, _, body) = applyMethod
- val (patchedParams, patchedBody) =
- patchFunBodyWithBoxes(applyDef.symbol, params, body.get)
+ 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 = JSThisFunctionClasses.exists(sym isSubClass _)
- assert(!isThisFunction || patchedParams.nonEmpty,
- s"Empty param list in ThisFunction: $cd")
+ 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
@@ -5365,30 +6355,30 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
if (isThisFunction) {
val thisParam :: actualParams = patchedParams
js.Closure(
- arrow = false,
+ js.ClosureFlags.function,
ctorParamDefs,
actualParams,
+ patchedRepeatedParam,
+ jstpe.AnyType,
js.Block(
- js.VarDef(thisParam.name, thisParam.ptpe, mutable = false,
+ js.VarDef(thisParam.name, thisParam.originalName,
+ thisParam.ptpe, mutable = false,
js.This()(thisParam.ptpe)(thisParam.pos))(thisParam.pos),
patchedBody),
capturedArgs)
} else {
- js.Closure(arrow = true, ctorParamDefs, patchedParams, patchedBody,
- capturedArgs)
+ js.Closure(js.ClosureFlags.arrow, ctorParamDefs, patchedParams,
+ patchedRepeatedParam, jstpe.AnyType, patchedBody, capturedArgs)
}
}
- val arity = params.size
-
- (closure, arity)
+ closure
}
}
/** Generate JS code for an anonymous function
*
- * Anonymous functions survive until the backend in 2.11 under
- * -Ydelambdafy:method (for Scala function types) and in 2.12 for any
+ * Anonymous functions survive until the backend for any
* LambdaMetaFactory-capable type.
*
* When they do, their body is always of the form
@@ -5401,30 +6391,48 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* 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 JS closure for the body:
+ * To translate them, we first construct a typed closure for the body:
* {{{
- * lambda(
- * _this, capture1, ..., captureM, arg1, ..., argN) {
- * _this.someMethod(arg1, ..., argN, capture1, ..., captureM)
+ * 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
- * `someMethod()` is boxed back.
+ * 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 Scala function types, we use the existing
- * `scala.scalajs.runtime.AnonFunctionN` from the library. For other
- * LMF-capable types, we generate a class on the fly, which looks like
- * this:
+ * 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 f: any
- * def (f: any) {
+ * val ...captureI: UI
+ * def (...captureI: UI) {
* super();
- * this.f = f
+ * ...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]
* }
- * def theSAMMethod(params: Types...): Type =
- * unbox((this.f)(boxParams...))
* }
* }}}
*/
@@ -5433,207 +6441,306 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
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)
+ global.delambdafy.FreeVarTraverser.freeVarsOf(originalFunction).toList
val target = targetTree.symbol
- val params = paramTrees.map(_.symbol)
- val allArgs = allArgs0 map genExpr
+ val isTargetStatic = compileAsStaticMethod(target)
- val formalCaptures = captureSyms.toList map { sym =>
- // Use the anonymous function pos
- js.ParamDef(encodeLocalSym(sym)(pos), toIRType(sym.tpe),
- mutable = false, rest = false)(pos)
+ // 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 actualCaptures = formalCaptures.map(_.ref)
- val formalArgs = params map { p =>
- // Use the param pos
- js.ParamDef(encodeLocalSym(p)(p.pos), toIRType(p.tpe),
- mutable = false, rest = false)(p.pos)
- }
+ 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)
+ }
+ }
- val isInImplClass = isImplClass(target.owner)
+ // 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")
- val (allFormalCaptures, body, allActualCaptures) = if (!isInImplClass) {
- val thisActualCapture = genExpr(receiver)
- val thisFormalCapture = js.ParamDef(
- freshLocalIdent("this")(receiver.pos),
- thisActualCapture.tpe, mutable = false, rest = false)(receiver.pos)
- val thisCaptureArg = thisFormalCapture.ref
+ /* 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 body = if (isJSType(receiver.tpe) && target.owner != ObjectClass) {
- assert(isNonNativeJSClass(target.owner) && !isExposed(target),
- s"A Function lambda is trying to call an exposed JS method ${target.fullName}")
- genApplyJSClassMethod(thisCaptureArg, target, allArgs)
- } else {
- genApplyMethodMaybeStatically(thisCaptureArg, target, allArgs)
+ val (samParamTypes, samResultType, targetResultType) = enteringPhase(currentRun.posterasurePhase) {
+ val methodType = sam.tpe.asInstanceOf[MethodType]
+ (methodType.params.map(_.info), methodType.resultType, target.tpe.finalResultType)
}
- (thisFormalCapture :: formalCaptures,
- body, thisActualCapture :: actualCaptures)
+ /* 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 {
- val body = genTraitImplApply(target, allArgs)
+ /* 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
- (formalCaptures, body, actualCaptures)
+ val ctorName = ir.Names.MethodName.constructor(allFormalCaptureTypeRefs)
+ val samWrapperClassName = synthesizeSAMWrapper(descriptor, sam, samBridges, closure, ctorName)
+ js.New(samWrapperClassName, js.MethodIdent(ctorName), closure.captureValues)
}
+ }
- val (patchedFormalArgs, patchedBody) = {
- patchFunBodyWithBoxes(target, formalArgs, body,
- useParamsBeforeLambdaLift = true)
+ 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
}
- val closure = js.Closure(
- arrow = true,
- allFormalCaptures,
- patchedFormalArgs,
- patchedBody,
- allActualCaptures)
-
- // Wrap the closure in the appropriate box for the SAM type
- val funSym = originalFunction.tpe.typeSymbolDirect
- if (isFunctionSymbol(funSym)) {
- /* This is a scala.FunctionN. We use the existing AnonFunctionN
- * wrapper.
- */
- genJSFunctionToScala(closure, params.size)
+ if (samBridges.isEmpty) {
+ // fast path
+ Nil
} else {
- /* This is an arbitrary SAM type (can only happen in 2.12).
- * We have to synthesize a class like LambdaMetaFactory would do on
- * the JVM.
+ /* Remove duplicates, e.g., if we override the same method declared
+ * in two super traits.
*/
- val sam = originalFunction.attachments.get[SAMFunctionCompat].getOrElse {
- abort(s"Cannot find the SAMFunction attachment on $originalFunction at $pos")
+ 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
}
- val samWrapperClassName = synthesizeSAMWrapper(funSym, sam)
- js.New(jstpe.ClassRef(samWrapperClassName), js.Ident("init___O"),
- List(closure))
+ builder.result()
}
}
- private def synthesizeSAMWrapper(funSym: Symbol, samInfo: SAMFunctionCompat)(
- implicit pos: Position): String = {
- val intfName = encodeClassFullName(funSym)
+ 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 generatedClassName = encodeClassFullName(currentClassSym) + suffix
+ val className = encodeClassName(currentClassSym).withSuffix(suffix)
- val classType = jstpe.ClassType(generatedClassName)
+ val thisType = jstpe.ClassType(className, nullable = false)
- // val f$1: Any
- val fFieldIdent = js.Ident("f$1", Some("f"))
- val fFieldDef = js.FieldDef(js.MemberFlags.empty, fFieldIdent,
- jstpe.AnyType)
+ // 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.f$1 = f; super() }
+ // def this(f: Any) = { ...this.captureI = captureI; super() }
val ctorDef = {
- val fParamDef = js.ParamDef(js.Ident("f"), jstpe.AnyType,
- mutable = false, rest = false)
+ 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.Ident("init___O"),
- List(fParamDef),
- jstpe.NoType,
+ js.MethodIdent(ctorName),
+ NoOriginalName,
+ closure.captureParams,
+ jstpe.VoidType,
Some(js.Block(List(
- js.Assign(
- js.Select(js.This()(classType), fFieldIdent)(jstpe.AnyType),
- fParamDef.ref),
+ js.Block(captureFieldAssignments),
js.ApplyStatically(js.ApplyFlags.empty.withConstructor(true),
- js.This()(classType),
- jstpe.ClassRef(ir.Definitions.ObjectClass),
- js.Ident("init___"),
- Nil)(jstpe.NoType)))))(
- js.OptimizerHints.empty, None)
- }
-
- // Compute the set of method symbols that we need to implement
- val sams = {
- val samsBuilder = List.newBuilder[Symbol]
- val seenEncodedNames = mutable.Set.empty[String]
-
- /* scala/bug#10512: any methods which `samInfo.sam` overrides need
- * bridges made for them.
- * On Scala < 2.12.5, `synthCls` is polyfilled to `NoSymbol` and hence
- * `samBridges` will always be empty. This causes our compiler to be
- * bug-compatible on these versions.
- */
- val synthCls = samInfo.synthCls
- val samBridges = if (synthCls == NoSymbol) {
- Nil
- } else {
- import scala.reflect.internal.Flags.BRIDGE
- synthCls.info.findMembers(excludedFlags = 0L, requiredFlags = BRIDGE).toList
+ 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))
}
- for (sam <- samInfo.sam :: samBridges) {
- /* Remove duplicates, e.g., if we override the same method declared
- * in two super traits.
- */
- if (seenEncodedNames.add(encodeMethodSym(sam).name))
- samsBuilder += sam
- }
+ val body = js.Block(localCaptureVarDefs, closure.body)
- samsBuilder.result()
+ js.MethodDef(js.MemberFlags.empty, encodeMethodSym(sam),
+ originalNameOfMethod(sam), closure.params, closure.resultType,
+ Some(body))(
+ js.OptimizerHints.empty, Unversioned)
}
- // def samMethod(...params): resultType = this.f$f(...params)
- val samMethodDefs = for (sam <- sams) yield {
- val jsParams = for (param <- sam.tpe.params) yield {
- js.ParamDef(encodeLocalSym(param), toIRType(param.tpe),
- mutable = false, rest = false)
- }
- val resultType = toIRType(sam.tpe.finalResultType)
+ 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, param) <- jsParams.zip(sam.tpe.params))
- yield (formal.ref, param.tpe)
- }.map((ensureBoxed _).tupled)
+ 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.JSFunctionApply(
- js.Select(js.This()(classType), fFieldIdent)(jstpe.AnyType),
- actualParams)
+ val call = js.Apply(js.ApplyFlags.empty, js.This()(thisType),
+ samMethodDef.name, actualParams)(samMethodDef.resultType)
- val body = fromAny(call, enteringPhase(currentRun.posterasurePhase) {
- sam.tpe.finalResultType
+ val body = adaptBoxesTupled(enteringPhase(currentRun.posterasurePhase) {
+ (call, sam.tpe.finalResultType, samBridge.tpe.finalResultType)
})
- js.MethodDef(js.MemberFlags.empty, encodeMethodSym(sam),
- jsParams, resultType, Some(body))(
- js.OptimizerHints.empty, None)
+ 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.Ident(generatedClassName),
+ js.ClassIdent(className),
+ NoOriginalName,
ClassKind.Class,
None,
- Some(js.Ident(ir.Definitions.ObjectClass)),
- List(js.Ident(intfName)),
+ Some(js.ClassIdent(descriptor.superClass)),
+ descriptor.interfaces.map(js.ClassIdent(_)),
None,
None,
- fFieldDef :: ctorDef :: samMethodDefs,
+ fields = captureFieldDefs,
+ methods = ctorDef :: samMethodDef :: samBridgeMethodDefs,
+ jsConstructor = None,
+ Nil,
+ Nil,
Nil)(
js.OptimizerHints.empty.withInline(true))
- generatedClasses += ((currentClassSym.get, Some(suffix), classDef))
+ generatedClasses += classDef -> pos
- generatedClassName
+ className
}
- private def patchFunBodyWithBoxes(methodSym: Symbol,
- params: List[js.ParamDef], body: js.Tree,
- useParamsBeforeLambdaLift: Boolean = false)(
- implicit pos: Position): (List[js.ParamDef], js.Tree) = {
- val methodType = enteringPhase(currentRun.posterasurePhase)(methodSym.tpe)
-
+ 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 <- methodType.params)
+ for (param <- methodSym.tpe.params)
yield param.name -> param.tpe
}.toMap
@@ -5653,41 +6760,67 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
methodSym.tpe.params
}
- val (patchedParams, paramsLocal) = (for {
- (param, paramSym) <- params zip paramSyms
+ (for {
+ ((param, paramSym), fromParamType) <- params.zip(paramSyms).zip(fromParamTypes)
} yield {
val paramTpe = paramTpes.getOrElse(paramSym.name, paramSym.tpe)
- val paramName = param.name
- val js.Ident(name, origName) = paramName
- val newOrigName = origName.getOrElse(name)
- val newNameIdent = freshLocalIdent(name)(paramName.pos)
- val patchedParam = js.ParamDef(newNameIdent, jstpe.AnyType,
- mutable = false, rest = param.rest)(param.pos)
- val paramLocal = js.VarDef(paramName, param.ptpe, mutable = false,
- fromAny(patchedParam.ref, paramTpe))
- (patchedParam, paramLocal)
+ genPatchedParam(param, adaptBoxes(_, fromParamType, paramTpe),
+ toIRType(underlyingOfEVT(fromParamType)))
}).unzip
+ }
- assert(!methodSym.isClassConstructor,
- s"Trying to patchFunBodyWithBoxes for constructor ${methodSym.fullName}")
-
- val patchedBody = js.Block(
- paramsLocal :+ ensureBoxed(body, methodType.resultType))
+ 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)
+ }
- (patchedParams, patchedBody)
+ private def underlyingOfEVT(tpe: Type): Type = tpe match {
+ case tpe: ErasedValueType => tpe.erasedUnderlying
+ case _ => tpe
}
- // Methods to deal with JSName ---------------------------------------------
+ /** 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(_))
- def genPropertyName(name: JSName)(implicit pos: Position): js.PropertyName = {
- name match {
- case JSName.Literal(name) => js.StringLiteral(name)
+ val body = {
+ val inst = genNew(clsSym, ctor, paramDefs.map(_.ref))
+ genApplyMethod(inst, DynamicImportThunkClass_apply, Nil)
+ }
- case JSName.Computed(sym) =>
- js.ComputedName(genComputedJSName(sym), encodeComputedNameIdentity(sym))
+ 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)
@@ -5714,6 +6847,30 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
// 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.
@@ -5738,17 +6895,46 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
require(sym0.isModuleOrModuleClass,
"genLoadModule called with non-module symbol: " + sym0)
- 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 {
- val cls = encodeClassRef(sym)
- val tree =
- if (isJSType(sym)) js.LoadJSModule(cls)
- else js.LoadModule(cls)
+ 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)
+ }
+ }
}
}
@@ -5794,37 +6980,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* Otherwise, report a compile error.
*/
private def genJSBracketSelectOrGlobalRef(qual: MaybeGlobalScope,
- item: js.Tree)(implicit pos: Position): js.Tree = {
+ item: js.Tree)(implicit pos: Position): js.AssignLhs = {
qual match {
case MaybeGlobalScope.NotGlobalScope(qualTree) =>
- js.JSBracketSelect(qualTree, item)
+ js.JSSelect(qualTree, item)
case MaybeGlobalScope.GlobalScope(_) =>
- item match {
- case js.StringLiteral(value) =>
- if (value == "arguments") {
- reporter.error(pos,
- "Selecting a field of the global scope whose name is " +
- "`arguments` is not allowed." +
- GenericGlobalObjectInformationMsg)
- js.JSGlobalRef(js.Ident("erroneous"))
- } else if (js.isValidIdentifier(value)) {
- js.JSGlobalRef(js.Ident(value))
- } else {
- reporter.error(pos,
- "Selecting a field of the global scope whose name is " +
- "not a valid JavaScript identifier is not allowed." +
- GenericGlobalObjectInformationMsg)
- js.JSGlobalRef(js.Ident("erroneous"))
- }
-
- case _ =>
- reporter.error(pos,
- "Selecting a field of the global scope with a dynamic " +
- "name is not allowed." +
- GenericGlobalObjectInformationMsg)
- js.JSGlobalRef(js.Ident("erroneous"))
- }
+ genJSGlobalRef(item, "Selecting a field", "selection")
}
}
@@ -5844,47 +7006,104 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
implicit pos: Position): js.Tree = {
receiver match {
case MaybeGlobalScope.NotGlobalScope(receiverTree) =>
- js.JSBracketMethodApply(receiverTree, method, args)
+ js.JSMethodApply(receiverTree, method, args)
case MaybeGlobalScope.GlobalScope(_) =>
- method match {
- case js.StringLiteral(value) =>
- if (value == "arguments") {
- reporter.error(pos,
- "Calling a method of the global scope whose name is " +
- "`arguments` is not allowed." +
- GenericGlobalObjectInformationMsg)
- js.Undefined()
- } else if (js.isValidIdentifier(value)) {
- js.JSFunctionApply(js.JSGlobalRef(js.Ident(value)), args)
- } else {
- reporter.error(pos,
- "Calling a method of the global scope whose name is not " +
- "a valid JavaScript identifier is not allowed." +
- GenericGlobalObjectInformationMsg)
- js.Undefined()
- }
-
- case _ =>
- reporter.error(pos,
- "Calling a method of the global scope with a dynamic " +
- "name is not allowed." +
- GenericGlobalObjectInformationMsg)
- js.Undefined()
+ 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")
}
}
- /** Generate access to a static member */
- private def genStaticMember(sym: Symbol)(implicit pos: Position) = {
- /* Actually, there is no static member in Scala.js. If we come here, that
- * is because we found the symbol in a Java-emitted .class in the
- * classpath. But the corresponding implementation in Scala.js will
- * actually be a val in the companion module.
- * We cannot use the .class files produced by our reimplementations of
- * these classes (in which the symbol would be a Scala accessor) because
- * that crashes the rest of scalac (at least for some choice symbols).
- * Hence we cheat here.
+ 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._
@@ -5893,24 +7112,15 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
case UNITVAL => js.Undefined()
}
} else {
- val instance = genLoadModule(sym.owner)
- val method = encodeStaticMemberSym(sym)
- js.Apply(js.ApplyFlags.empty, instance, method, Nil)(toIRType(sym.tpe))
+ val className = encodeClassName(sym.owner)
+ val method = encodeStaticFieldGetterSym(sym)
+ js.ApplyStatic(js.ApplyFlags.empty, className, method, Nil)(toIRType(sym.tpe))
}
}
}
- private lazy val isScala211WithXexperimental = {
- scala.util.Properties.versionNumberString.startsWith("2.11.") &&
- settings.Xexperimental.value
- }
-
- private lazy val hasNewCollections = {
- val v = scala.util.Properties.versionNumberString
- !v.startsWith("2.10.") &&
- !v.startsWith("2.11.") &&
- !v.startsWith("2.12.")
- }
+ 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.
@@ -5929,19 +7139,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
!sym.isTrait && isJSType(sym) && !sym.hasAnnotation(JSNativeAnnotation)
def isNestedJSClass(sym: Symbol): Boolean =
- sym.isLifted && !sym.originalOwner.isModuleClass && isJSType(sym)
+ 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 class is the impl class of a JS trait. */
- private def isJSImplClass(sym: Symbol): Boolean =
- isImplClass(sym) && isJSType(traitOfImplClass(sym))
-
- private def traitOfImplClass(sym: Symbol): Symbol =
- sym.owner.info.decl(sym.name.dropRight(nme.IMPL_CLASS_SUFFIX.length))
-
/** Tests whether the given member is exposed, i.e., whether it was
* originally a public or protected member of a non-native JS class.
*/
@@ -5955,23 +7158,22 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
}
/** Test whether `sym` is the symbol of a JS function definition */
- private def isJSFunctionDef(sym: Symbol): Boolean =
- sym.isAnonymousClass && AllJSFunctionClasses.exists(sym isSubClass _)
-
- private def isJSCtorDefaultParam(sym: Symbol) = {
- isCtorDefaultParam(sym) &&
- isJSType(patchedLinkedClassOfClass(sym.owner))
- }
-
- private def isJSNativeCtorDefaultParam(sym: Symbol) = {
- isCtorDefaultParam(sym) &&
- isJSNativeClass(patchedLinkedClassOfClass(sym.owner))
- }
-
- private def isCtorDefaultParam(sym: Symbol) = {
- sym.hasFlag(reflect.internal.Flags.DEFAULTPARAM) &&
- sym.owner.isModuleClass &&
- nme.defaultGetterToMethod(sym.name) == nme.CONSTRUCTOR
+ 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 = {
@@ -6011,17 +7213,66 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
else result.alternatives.head
}
- /** Whether a field is suspected to be mutable in the IR's terms
- *
- * A field is mutable in the IR, if it is assigned to elsewhere than in the
- * constructor of its class.
+ 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.
*
- * Mixed-in fields are always mutable, since they will be assigned to in
- * a trait initializer (rather than a constructor).
+ * `DefaultParamInfo.isApplicable(sym)` must be true for this class to make
+ * sense.
*/
- private def suspectFieldMutable(sym: Symbol) = {
- import scala.reflect.internal.Flags
- sym.hasFlag(Flags.MIXEDIN) || sym.isMutable
+ 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 =
@@ -6061,7 +7312,26 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
JavaScriptExceptionClass isSubClass tpe.typeSymbol
def isStaticModule(sym: Symbol): Boolean =
- sym.isModuleClass && !isImplClass(sym) && !sym.isLifted
+ 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
@@ -6076,7 +7346,183 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G)
* 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 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))
+ }
+ }
+
+ private object IntFlipSign {
+ def unapply(tree: js.Tree): Option[js.Tree] = tree match {
+ case js.BinaryOp(js.BinaryOp.Int_^, lhs, js.IntLiteral(Int.MinValue)) =>
+ Some(lhs)
+ case js.BinaryOp(js.BinaryOp.Int_^, js.IntLiteral(Int.MinValue), rhs) =>
+ Some(rhs)
+ case _ =>
+ None
+ }
+ }
+
+ private object LongFlipSign {
+ def unapply(tree: js.Tree): Option[js.Tree] = tree match {
+ case js.BinaryOp(js.BinaryOp.Long_^, lhs, js.LongLiteral(Long.MinValue)) =>
+ Some(lhs)
+ case js.BinaryOp(js.BinaryOp.Long_^, js.LongLiteral(Long.MinValue), rhs) =>
+ Some(rhs)
+ case _ =>
+ None
+ }
+ }
+
+ private abstract class JavalibOpBody {
+ /** Generates the body of this special method, given references to the receiver and parameters. */
+ def generate(receiver: js.Tree, args: List[js.Tree])(implicit pos: ir.Position): js.Tree
+ }
+
+ private object JavalibOpBody {
+ private def checkNotNullIf(arg: js.Tree, checkNulls: Boolean)(implicit pos: ir.Position): js.Tree =
+ if (checkNulls && arg.tpe.isNullable) js.UnaryOp(js.UnaryOp.CheckNotNull, arg)
+ else arg
+
+ /* These are case classes for convenience (for the apply method).
+ * They are not intended for pattern matching.
+ */
+
+ /** UnaryOp applying to the `this` parameter. */
+ final case class ThisUnaryOp(op: js.UnaryOp.Code) extends JavalibOpBody {
+ def generate(receiver: js.Tree, args: List[js.Tree])(implicit pos: ir.Position): js.Tree = {
+ assert(args.isEmpty)
+ js.UnaryOp(op, receiver)
+ }
+ }
+
+ /** BinaryOp applying to the `this` parameter and the regular parameter. */
+ final case class ThisBinaryOp(op: js.BinaryOp.Code, checkNulls: Boolean = false) extends JavalibOpBody {
+ def generate(receiver: js.Tree, args: List[js.Tree])(implicit pos: ir.Position): js.Tree = {
+ val List(rhs) = args: @unchecked
+ js.BinaryOp(op, receiver, checkNotNullIf(rhs, checkNulls))
+ }
+ }
+
+ /** UnaryOp applying to the only regular parameter (`this` is ignored). */
+ final case class ArgUnaryOp(op: js.UnaryOp.Code, checkNulls: Boolean = false) extends JavalibOpBody {
+ def generate(receiver: js.Tree, args: List[js.Tree])(implicit pos: ir.Position): js.Tree = {
+ val List(arg) = args: @unchecked
+ js.UnaryOp(op, checkNotNullIf(arg, checkNulls))
+ }
+ }
+
+ /** BinaryOp applying to the two regular paramters (`this` is ignored). */
+ final case class ArgBinaryOp(op: js.BinaryOp.Code, checkNulls: Boolean = false) extends JavalibOpBody {
+ def generate(receiver: js.Tree, args: List[js.Tree])(implicit pos: ir.Position): js.Tree = {
+ val List(lhs, rhs) = args: @unchecked
+ js.BinaryOp(op, checkNotNullIf(lhs, checkNulls), checkNotNullIf(rhs, checkNulls))
+ }
+ }
+ }
+
+ /** Methods of the javalib whose body must be replaced by a dedicated
+ * UnaryOp or BinaryOp.
+ *
+ * We use IR encoded names to identify them, rather than scalac Symbols.
+ * There is no fundamental reason for that. It makes it easier to define
+ * this map in a declarative way, especially when overloaded methods are
+ * concerned (Array.newInstance). It also allows to define it independently
+ * of the Global instance, but that is marginal.
+ */
+ private lazy val JavalibMethodsWithOpBody: Map[(ClassName, MethodName), JavalibOpBody] = {
+ import JavalibOpBody._
+ import js.{UnaryOp => unop, BinaryOp => binop}
+ import jstpe.{BooleanRef => Z, CharRef => C, IntRef => I, LongRef => J, FloatRef => F, DoubleRef => D}
+ import MethodName.{apply => m}
+
+ val O = jswkn.ObjectRef
+ val CC = jstpe.ClassRef(jswkn.ClassClass)
+ val T = jstpe.ClassRef(jswkn.BoxedStringClass)
+
+ val byClass: Map[ClassName, Map[MethodName, JavalibOpBody]] = Map(
+ jswkn.BoxedIntegerClass.withSuffix("$") -> Map(
+ m("toUnsignedLong", List(I), J) -> ArgUnaryOp(unop.UnsignedIntToLong),
+ m("divideUnsigned", List(I, I), I) -> ArgBinaryOp(binop.Int_unsigned_/),
+ m("remainderUnsigned", List(I, I), I) -> ArgBinaryOp(binop.Int_unsigned_%),
+ m("numberOfLeadingZeros", List(I), I) -> ArgUnaryOp(unop.Int_clz)
+ ),
+ jswkn.BoxedLongClass.withSuffix("$") -> Map(
+ m("divideUnsigned", List(J, J), J) -> ArgBinaryOp(binop.Long_unsigned_/),
+ m("remainderUnsigned", List(J, J), J) -> ArgBinaryOp(binop.Long_unsigned_%),
+ m("numberOfLeadingZeros", List(J), I) -> ArgUnaryOp(unop.Long_clz)
+ ),
+ jswkn.BoxedFloatClass.withSuffix("$") -> Map(
+ m("floatToIntBits", List(F), I) -> ArgUnaryOp(unop.Float_toBits),
+ m("intBitsToFloat", List(I), F) -> ArgUnaryOp(unop.Float_fromBits)
+ ),
+ jswkn.BoxedDoubleClass.withSuffix("$") -> Map(
+ m("doubleToLongBits", List(D), J) -> ArgUnaryOp(unop.Double_toBits),
+ m("longBitsToDouble", List(J), D) -> ArgUnaryOp(unop.Double_fromBits)
+ ),
+ jswkn.BoxedStringClass -> Map(
+ m("length", Nil, I) -> ThisUnaryOp(unop.String_length),
+ m("charAt", List(I), C) -> ThisBinaryOp(binop.String_charAt)
+ ),
+ jswkn.ClassClass -> Map(
+ // Unary operators
+ m("getName", Nil, T) -> ThisUnaryOp(unop.Class_name),
+ m("isPrimitive", Nil, Z) -> ThisUnaryOp(unop.Class_isPrimitive),
+ m("isInterface", Nil, Z) -> ThisUnaryOp(unop.Class_isInterface),
+ m("isArray", Nil, Z) -> ThisUnaryOp(unop.Class_isArray),
+ m("getComponentType", Nil, CC) -> ThisUnaryOp(unop.Class_componentType),
+ m("getSuperclass", Nil, CC) -> ThisUnaryOp(unop.Class_superClass),
+ // Binary operators
+ m("isInstance", List(O), Z) -> ThisBinaryOp(binop.Class_isInstance),
+ m("isAssignableFrom", List(CC), Z) -> ThisBinaryOp(binop.Class_isAssignableFrom, checkNulls = true),
+ m("cast", List(O), O) -> ThisBinaryOp(binop.Class_cast)
+ ),
+ ClassName("java.lang.System$") -> Map(
+ m("identityHashCode", List(O), I) -> ArgUnaryOp(unop.IdentityHashCode)
+ ),
+ ClassName("java.lang.reflect.Array$") -> Map(
+ m("newInstance", List(CC, I), O) -> ArgBinaryOp(binop.Class_newArray, checkNulls = true)
+ )
+ )
+
+ for {
+ (cls, methods) <- byClass
+ (methodName, body) <- methods
+ } yield {
+ (cls, methodName) -> body
+ }
+ }
+}
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala
index 140e82dd46..bcac2098ea 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala
@@ -20,8 +20,11 @@ import scala.reflect.{ClassTag, classTag}
import scala.reflect.internal.Flags
import org.scalajs.ir
-import ir.{Trees => js, Types => jstpe}
-import ir.Trees.OptimizerHints
+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
@@ -45,7 +48,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
*
* @param classSym symbol of the class we export for
*/
- def genMemberExports(classSym: Symbol): List[js.MemberDef] = {
+ def genMemberExports(classSym: Symbol): List[js.JSMethodPropDef] = {
val allExports = classSym.info.members.filter(jsInterop.isExport(_))
val newlyDecldExports = if (classSym.superClass == NoSymbol) {
@@ -64,195 +67,159 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
}
def genJSClassDispatchers(classSym: Symbol,
- dispatchMethodsNames: List[JSName]): List[js.MemberDef] = {
+ dispatchMethodsNames: List[JSName]): List[js.JSMethodPropDef] = {
dispatchMethodsNames
.map(genJSClassDispatcher(classSym, _))
}
- def genConstructorExports(
- classSym: Symbol): List[js.TopLevelMethodExportDef] = {
+ 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
+ }
+ }
- val constructors = classSym.tpe.member(nme.CONSTRUCTOR).alternatives
+ private def checkSameKind(tups: List[(jsInterop.ExportInfo, Symbol)]): Option[ExportKind] = {
+ assert(tups.nonEmpty, "must have at least one export")
- // Generate exports from constructors and their annotations
- val ctorExports = for {
- ctor <- constructors
- exp <- jsInterop.registeredExportsOf(ctor)
- } yield (exp, ctor)
+ val firstSym = tups.head._2
+ val overallKind = ExportKind(firstSym)
+ var bad = false
- if (ctorExports.isEmpty) {
- Nil
- } else {
- val exports = for {
- (jsName, specs) <- ctorExports.groupBy(_._1.jsName) // group by exported name
- } yield {
- val ctors = specs.map(s => ExportedSymbol(s._2))
+ for ((info, sym) <- tups.tail) {
+ val kind = ExportKind(sym)
- implicit val pos = ctors.head.pos
+ if (kind != overallKind) {
+ bad = true
+ reporter.error(info.pos, "export overload conflicts with export of " +
+ s"$firstSym: they are of different types ($kind / $overallKind)")
+ }
+ }
- val methodDef = withNewLocalNameScope {
- genExportMethod(ctors, JSName.Literal(jsName), static = true)
- }
+ if (bad) None
+ else Some(overallKind)
+ }
- js.TopLevelMethodExportDef(methodDef)
- }
+ private def checkSingleField(tups: List[(jsInterop.ExportInfo, Symbol)]): Symbol = {
+ assert(tups.nonEmpty, "must have at least one export")
+
+ val firstSym = tups.head._2
- exports.toList
+ 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 genJSClassExports(
- classSym: Symbol): List[js.TopLevelJSClassExportDef] = {
- for {
- exp <- jsInterop.registeredExportsOf(classSym)
+ def genTopLevelExports(classSym: Symbol): List[js.TopLevelExportDef] = {
+ val exports = for {
+ sym <- List(classSym) ++ classSym.info.members
+ info <- jsInterop.topLevelExportsOf(sym)
} yield {
- implicit val pos = exp.pos
-
- exp.destination match {
- case ExportDestination.Normal | ExportDestination.TopLevel =>
- js.TopLevelJSClassExportDef(exp.jsName)
- case ExportDestination.Static =>
- throw new AssertionError(
- "Found a class export static for " + classSym.fullName)
- }
+ (info, sym)
}
- }
-
- def genModuleAccessorExports(
- classSym: Symbol): List[js.TopLevelExportDef] = {
for {
- exp <- jsInterop.registeredExportsOf(classSym)
+ (info, tups) <- stableGroupByWithoutHashCode(exports)(_._1)
+ kind <- checkSameKind(tups)
} yield {
- implicit val pos = exp.pos
-
- exp.destination match {
- case ExportDestination.Normal =>
- throw new AssertionError(
- "Found a non-top-level module export for " + classSym.fullName)
- case ExportDestination.TopLevel =>
- js.TopLevelModuleExportDef(exp.jsName)
- case ExportDestination.Static =>
- throw new AssertionError(
- "Found a module export static for " + classSym.fullName)
- }
- }
- }
+ import ExportKind._
- def genTopLevelExports(classSym: Symbol): List[js.TopLevelExportDef] =
- genTopLevelOrStaticExports[js.TopLevelExportDef](classSym, ExportDestination.TopLevel)
+ implicit val pos = info.pos
- def genStaticExports(classSym: Symbol): List[js.MemberDef] =
- genTopLevelOrStaticExports[js.MemberDef](classSym, ExportDestination.Static)
+ kind match {
+ case Module =>
+ js.TopLevelModuleExportDef(info.moduleID, info.jsName)
- private def genTopLevelOrStaticExports[A <: js.IRNode: ClassTag](
- classSym: Symbol, destination: ExportDestination): List[A] = {
- require(
- destination == ExportDestination.TopLevel ||
- destination == ExportDestination.Static,
- destination)
+ case JSClass =>
+ assert(isNonNativeJSClass(classSym), "found export on non-JS class")
+ js.TopLevelJSClassExportDef(info.moduleID, info.jsName)
- val exportsNamesAndPositions = {
- genTopLevelOrStaticFieldExports(classSym, destination) ++
- genTopLevelOrStaticMethodExports(classSym, destination)
- }
+ case Constructor | Method =>
+ val methodDef = withNewLocalNameScope {
+ genExportMethod(tups.map(_._2), JSName.Literal(info.jsName), static = true,
+ allowCallsiteInlineSingle = false)
+ }
- for {
- exportsWithSameName <- exportsNamesAndPositions.groupBy(_._2).values
- duplicate <- exportsWithSameName.tail
- } {
- val strKind =
- if (destination == ExportDestination.TopLevel) "top-level"
- else "static"
- reporter.error(duplicate._3,
- s"Duplicate $strKind export with name '${duplicate._2}': " +
- "a field may not share its exported name with another field or " +
- "method")
- }
+ js.TopLevelMethodExportDef(info.moduleID, methodDef)
- exportsNamesAndPositions.map(_._1)
- }
+ case Property =>
+ throw new AssertionError("found top-level exported property")
- private def genTopLevelOrStaticFieldExports[A <: js.IRNode: ClassTag](
- classSym: Symbol,
- destination: ExportDestination): List[(A, String, Position)] = {
- (for {
- fieldSym <- classSym.info.members
- if !fieldSym.isMethod && fieldSym.isTerm && !fieldSym.isModule
- export <- jsInterop.registeredExportsOf(fieldSym)
- if export.destination == destination
- } yield {
- implicit val pos = fieldSym.pos
-
- val tree = if (destination == ExportDestination.Static) {
- // static fields must always be mutable
- val flags = js.MemberFlags.empty
- .withNamespace(js.MemberNamespace.PublicStatic)
- .withMutable(true)
- val name = js.StringLiteral(export.jsName)
- val irTpe = genExposedFieldIRType(fieldSym)
- checkedCast[A](js.FieldDef(flags, name, irTpe))
- } else {
- checkedCast[A](
- js.TopLevelFieldExportDef(export.jsName, encodeFieldSym(fieldSym)))
+ case Field =>
+ val sym = checkSingleField(tups)
+ js.TopLevelFieldExportDef(info.moduleID, info.jsName, encodeFieldSym(sym))
}
-
- (tree, export.jsName, pos)
- }).toList
+ }
}
- private def genTopLevelOrStaticMethodExports[A <: js.IRNode: ClassTag](
- classSym: Symbol,
- destination: ExportDestination): List[(A, String, Position)] = {
- val allRelevantExports = for {
- methodSym <- classSym.info.members
- if methodSym.isMethod && !methodSym.isConstructor
- export <- jsInterop.registeredExportsOf(methodSym)
- if export.destination == destination
+ def genStaticExports(classSym: Symbol): (List[js.JSFieldDef], List[js.JSMethodPropDef]) = {
+ val exports = (for {
+ sym <- classSym.info.members
+ info <- jsInterop.staticExportsOf(sym)
} yield {
- (export, methodSym)
- }
+ (info, sym)
+ }).toList
+
+ val fields = List.newBuilder[js.JSFieldDef]
+ val methodProps = List.newBuilder[js.JSMethodPropDef]
for {
- (jsName, tups) <- allRelevantExports.groupBy(_._1.jsName).toList
- } yield {
- implicit val pos = tups.head._1.pos
+ (info, tups) <- stableGroupByWithoutHashCode(exports)(_._1)
+ kind <- checkSameKind(tups)
+ } {
+ def alts = tups.map(_._2)
- val alts = tups.map(_._2).toList
- val firstAlt = alts.head
- val isProp = jsInterop.isJSProperty(firstAlt)
+ implicit val pos = info.pos
- // Check for conflict between method vs property
+ import ExportKind._
- for {
- conflicting <- alts.tail
- if jsInterop.isJSProperty(conflicting) != isProp
- } {
- val kindStr = if (isProp) "method" else "property"
- reporter.error(conflicting.pos,
- s"Exported $kindStr $jsName conflicts with ${firstAlt.nameString}")
- }
+ kind match {
+ case Method =>
+ methodProps += genMemberExportOrDispatcher(
+ JSName.Literal(info.jsName), isProp = false, alts, static = true,
+ allowCallsiteInlineSingle = false)
- // Generate the export
+ case Property =>
+ methodProps += genMemberExportOrDispatcher(
+ JSName.Literal(info.jsName), isProp = true, alts, static = true,
+ allowCallsiteInlineSingle = false)
- val exportedMember = genMemberExportOrDispatcher(classSym,
- JSName.Literal(jsName), isProp, alts, static = true)
+ case Field =>
+ val sym = checkSingleField(tups)
- val exportDef = {
- if (destination == ExportDestination.Static)
- checkedCast[A](exportedMember)
- else
- checkedCast[A](js.TopLevelMethodExportDef(exportedMember.asInstanceOf[js.MethodDef]))
- }
+ // 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)
- (exportDef, jsName, pos)
+ case kind =>
+ throw new AssertionError(s"unexpected static export kind: $kind")
+ }
}
- }
- private def checkedCast[A: ClassTag](x: js.IRNode): A =
- classTag[A].runtimeClass.asInstanceOf[Class[A]].cast(x)
+ (fields.result(), methodProps.result())
+ }
- private def genMemberExport(classSym: Symbol, name: TermName): js.MemberDef = {
+ 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
@@ -280,13 +247,21 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
s"Exported $kind $jsName conflicts with ${alts.head.fullName}")
}
- genMemberExportOrDispatcher(classSym, JSName.Literal(jsName), isProp,
- alts, static = false)
+ genMemberExportOrDispatcher(JSName.Literal(jsName), isProp, alts,
+ static = false, allowCallsiteInlineSingle = false)
}
- private def genJSClassDispatcher(classSym: Symbol, name: JSName): js.MemberDef = {
+ private def genJSClassDispatcher(classSym: Symbol, name: JSName): js.JSMethodPropDef = {
val alts = classSym.info.members.toList.filter { sym =>
- sym.isMethod && !sym.isBridge && jsNameOf(sym) == name
+ 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,
@@ -299,52 +274,26 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
reporter.error(alts.head.pos,
s"Conflicting properties and methods for ${classSym.fullName}::$name.")
implicit val pos = alts.head.pos
- js.PropertyDef(js.MemberFlags.empty, genPropertyName(name), None, None)
+ js.JSPropertyDef(js.MemberFlags.empty, genExpr(name), None, None)(Unversioned)
} else {
- genMemberExportOrDispatcher(classSym, name, isProp, alts,
- static = false)
+ genMemberExportOrDispatcher(name, isProp, alts, static = false,
+ allowCallsiteInlineSingle = true)
}
}
- def genMemberExportOrDispatcher(classSym: Symbol, jsName: JSName,
- isProp: Boolean, alts: List[Symbol], static: Boolean): js.MemberDef = {
+ def genMemberExportOrDispatcher(jsName: JSName, isProp: Boolean,
+ alts: List[Symbol], static: Boolean,
+ allowCallsiteInlineSingle: Boolean): js.JSMethodPropDef = {
withNewLocalNameScope {
if (isProp)
- genExportProperty(alts, jsName, static)
+ genExportProperty(alts, jsName, static, allowCallsiteInlineSingle)
else
- genExportMethod(alts.map(ExportedSymbol), jsName, static)
- }
- }
-
- def genJSConstructorExport(
- alts: List[Symbol]): (Option[List[js.ParamDef]], js.MethodDef) = {
- val exporteds = alts.map(ExportedSymbol)
-
- val isLiftedJSCtor = exporteds.head.isLiftedJSConstructor
- assert(exporteds.tail.forall(_.isLiftedJSConstructor == isLiftedJSCtor),
- s"Alternative constructors $alts do not agree on whether they are " +
- "lifted JS constructors or not")
- val captureParams = if (!isLiftedJSCtor) {
- None
- } else {
- Some(for {
- exported <- exporteds
- param <- exported.captureParamsFront ::: exported.captureParamsBack
- } yield {
- implicit val pos = param.sym.pos
- js.ParamDef(encodeLocalSym(param.sym), toIRType(param.tpe),
- mutable = false, rest = false)
- })
+ genExportMethod(alts, jsName, static, allowCallsiteInlineSingle)
}
-
- val ctorDef = genExportMethod(exporteds, JSName.Literal("constructor"),
- static = false)
-
- (captureParams, ctorDef)
}
private def genExportProperty(alts: List[Symbol], jsName: JSName,
- static: Boolean): js.PropertyDef = {
+ static: Boolean, allowCallsiteInlineSingle: Boolean): js.JSPropertyDef = {
assert(!alts.isEmpty,
s"genExportProperty with empty alternatives for $jsName")
@@ -360,43 +309,43 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
val (getter, setters) = alts.partition(_.tpe.params.isEmpty)
// We can have at most one getter
- if (getter.size > 1) {
- /* Member export of properties should be caught earlier, so if we get
- * here with a non-static export, something went horribly wrong.
- */
- assert(static,
- s"Found more than one instance getter to export for name $jsName.")
- for (duplicate <- getter.tail) {
- reporter.error(duplicate.pos,
- s"Duplicate static getter export with name '${jsName.displayName}'")
- }
- }
+ if (getter.size > 1)
+ reportCannotDisambiguateError(jsName, alts)
val getterBody = getter.headOption.map { getterSym =>
- genApplyForSym(minArgc = 0, hasRestParam = false,
- ExportedSymbol(getterSym), static)
+ genApplyForSym(new FormalArgsRegistry(0, false), getterSym, static,
+ inline = allowCallsiteInlineSingle)
}
val setterArgAndBody = {
if (setters.isEmpty) {
None
} else {
- val arg = genFormalArg(1)
- val body = genExportSameArgc(jsName, minArgc = 1,
- hasRestParam = false, alts = setters.map(ExportedSymbol),
- paramIndex = 0, static = static)
+ 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.PropertyDef(flags, genPropertyName(jsName), getterBody,
- setterArgAndBody)
+ 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[Exported], jsName: JSName,
- static: Boolean): js.MethodDef = {
+ 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")
@@ -411,14 +360,37 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
// toString() is always exported. We might need to add it here
// to get correct overloading.
val needsToString =
- jsName == JSName.Literal("toString") && alts0.forall(_.params.nonEmpty)
+ jsName == JSName.Literal("toString") && alts0.forall(_.tpe.params.nonEmpty)
if (needsToString)
- ExportedSymbol(Object_toString) :: alts0
+ 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)
@@ -455,52 +427,84 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
} yield (argc, method)
}
- // Create a map: argCount -> methods (methods may appear multiple times)
- val methodByArgCount =
- methodArgCounts.groupBy(_._1).map(kv => kv._1 -> kv._2.map(_._2).toSet)
+ /** 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)
+ }
- // Minimum number of arguments that must be given
- val minArgc = methodByArgCount.keys.min
+ /* 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
-
- // List of formal parameters
val needsRestParam = maxArgc != minArgc || hasVarArg
- val formalArgs = genFormalArgs(minArgc, needsRestParam)
+ val formalArgsRegistry = new FormalArgsRegistry(minArgc, needsRestParam)
- // Create tuples: (methods, argCounts). This will be the cases we generate
- val caseDefinitions =
- methodByArgCount.groupBy(_._2).map(kv => kv._1 -> kv._2.keySet)
+ // 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.values.flatten.toList
+ 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 = for {
+ val cases: List[(List[js.IntLiteral], js.Tree)] = for {
(methods, argcs) <- caseDefinitions
- if methods.nonEmpty && argcs.nonEmpty
-
- // exclude default case we're generating anyways for varargs
- if methods != varArgMeths.toSet
+ if methods.nonEmpty && argcs.nonEmpty && !isSameAsVarArgMethods(methods)
+ } yield {
+ val argcAlternatives = argcs.map(argc => js.IntLiteral(argc - minArgc))
- // body of case to disambiguates methods with current count
- caseBody = genExportSameArgc(jsName, minArgc, needsRestParam,
- methods.toList, paramIndex = 0, static, Some(argcs.min))
+ // 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))
- // argc in reverse order
- argcList = argcs.toList.sortBy(- _)
- } yield (argcList.map(argc => js.IntLiteral(argc - minArgc)), caseBody)
+ (argcAlternatives, caseBody)
+ }
def defaultCase = {
if (!hasVarArg) {
genThrowTypeError()
} else {
- genExportSameArgc(jsName, minArgc, needsRestParam, varArgMeths,
- paramIndex = 0, static = static)
+ genOverloadDispatchSameArgc(jsName, formalArgsRegistry, varArgMeths,
+ tpe, paramIndex = 0)
}
}
@@ -512,17 +516,14 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
else {
assert(needsRestParam,
"Trying to read rest param length but needsRestParam is false")
+ val restArgRef = formalArgsRegistry.genRestArgRef()
js.Match(
- js.Unbox(js.JSBracketSelect(
- genRestArgRef(),
- js.StringLiteral("length")),
- 'I'),
- cases.toList, defaultCase)(jstpe.AnyType)
+ js.AsInstanceOf(js.JSSelect(restArgRef, js.StringLiteral("length")), jstpe.IntType),
+ cases, defaultCase)(tpe)
}
}
- js.MethodDef(flags, genPropertyName(jsName),
- formalArgs, jstpe.AnyType, Some(body))(OptimizerHints.empty, None)
+ (formalArgs, restParam, body)
}
/**
@@ -533,47 +534,61 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
* @param paramIndex Index where to start disambiguation
* @param maxArgc only use that many arguments
*/
- private def genExportSameArgc(jsName: JSName, minArgc: Int,
- hasRestParam: Boolean, alts: List[Exported], paramIndex: Int,
- static: Boolean, maxArgc: Option[Int] = None): js.Tree = {
+ 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.pos
+ implicit val pos = alts.head.sym.pos
- if (alts.size == 1)
- alts.head.genBody(minArgc, hasRestParam, static)
- else if (maxArgc.exists(_ <= paramIndex) ||
+ 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)
+ reportCannotDisambiguateError(jsName, alts.map(_.sym))
js.Undefined()
} else {
- val altsByTypeTest = groupByWithoutHashCode(alts) { exported =>
+ val altsByTypeTest = stableGroupByWithoutHashCode(alts) { exported =>
typeTestForTpe(exported.exportArgTypeAt(paramIndex))
}
if (altsByTypeTest.size == 1) {
// Testing this parameter is not doing any us good
- genExportSameArgc(jsName, minArgc, hasRestParam, alts,
- paramIndex+1, static, maxArgc)
+ genOverloadDispatchSameArgc(jsName, formalArgsRegistry, alts, tpe,
+ paramIndex + 1, maxArgc)
} else {
// Sort them so that, e.g., isInstanceOf[String]
// comes before isInstanceOf[Object]
- val sortedAltsByTypeTest = topoSortDistinctsBy(
- altsByTypeTest)(_._1)(RTTypeTest.Ordering)
+ 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.pos
+ implicit val pos = subAlts.head.sym.pos
- val paramRef = genFormalArgRef(paramIndex+1, minArgc)
- val genSubAlts = genExportSameArgc(jsName, minArgc, hasRestParam,
- subAlts, paramIndex+1, static, maxArgc)
+ val paramRef = formalArgsRegistry.genArgRef(paramIndex)
+ val genSubAlts = genOverloadDispatchSameArgc(jsName, formalArgsRegistry,
+ subAlts, tpe, paramIndex + 1, maxArgc)
def hasDefaultParam = subAlts.exists { exported =>
val params = exported.params
@@ -582,8 +597,8 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
}
val optCond = typeTest match {
- case HijackedTypeTest(boxedClassName, _) =>
- Some(js.IsInstanceOf(paramRef, jstpe.ClassRef(boxedClassName)))
+ case PrimitiveTypeTest(tpe, _) =>
+ Some(js.IsInstanceOf(paramRef, tpe))
case InstanceOfTypeTest(tpe) =>
Some(genIsInstanceOf(paramRef, tpe))
@@ -600,7 +615,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
js.BinaryOp(js.BinaryOp.===, paramRef, js.Undefined()))(
jstpe.BooleanType)
}
- js.If(condOrUndef, genSubAlts, elsep)(jstpe.AnyType)
+ js.If(condOrUndef, genSubAlts, elsep)(tpe)
}
}
}
@@ -608,7 +623,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
}
private def reportCannotDisambiguateError(jsName: JSName,
- alts: List[Exported]): Unit = {
+ alts: List[Symbol]): Unit = {
val currentClass = currentClassSym.get
/* Find a position that is in the current class for decent error reporting.
@@ -617,21 +632,26 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
* same error in all compilers.
*/
val validPositions = alts.collect {
- case alt if alt.sym.owner == currentClass => alt.sym.pos
+ case alt if alt.owner == currentClass => alt.pos
}
val pos =
if (validPositions.isEmpty) currentClass.pos
else validPositions.maxBy(_.point)
val kind =
- if (isNonNativeJSClass(currentClass)) "method"
- else "exported method"
+ 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(_.typeInfo).sorted.mkString("\n ")
+ val altsTypesInfo = alts.map(_.tpe.toString).sorted.mkString("\n ")
reporter.error(pos,
- s"Cannot disambiguate overloads for $kind $displayName with types\n" +
+ s"Cannot disambiguate overloads for $fullKind $displayName with types\n" +
s" $altsTypesInfo")
}
@@ -640,155 +660,181 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
* and potentially the argument array. Also inserts default parameters if
* required.
*/
- private def genApplyForSym(minArgc: Int, hasRestParam: Boolean,
- exported: Exported, static: Boolean): js.Tree = {
+ private def genApplyForSym(formalArgsRegistry: FormalArgsRegistry,
+ sym: Symbol, static: Boolean, inline: Boolean): js.Tree = {
if (isNonNativeJSClass(currentClassSym) &&
- exported.sym.owner != currentClassSym.get) {
- assert(!static,
- s"nonsensical JS super call in static export of ${exported.sym}")
- genApplyForSymJSSuperCall(minArgc, hasRestParam, exported)
+ sym.owner != currentClassSym.get) {
+ assert(!static, s"nonsensical JS super call in static export of $sym")
+ genApplyForSymJSSuperCall(formalArgsRegistry, sym)
} else {
- genApplyForSymNonJSSuperCall(minArgc, exported, static)
+ genApplyForSymNonJSSuperCall(formalArgsRegistry, sym, static, inline)
}
}
- private def genApplyForSymJSSuperCall(minArgc: Int, hasRestParam: Boolean,
- exported: Exported): js.Tree = {
- implicit val pos = exported.pos
+ private def genApplyForSymJSSuperCall(
+ formalArgsRegistry: FormalArgsRegistry, sym: Symbol): js.Tree = {
+ implicit val pos = sym.pos
- val sym = exported.sym
assert(!sym.isClassConstructor,
"Trying to genApplyForSymJSSuperCall for the constructor " +
sym.fullName)
- val restArg =
- if (hasRestParam) js.JSSpread(genRestArgRef()) :: Nil
- else Nil
-
- val allArgs =
- (1 to minArgc).map(genFormalArgRef(_, minArgc)) ++: restArg
+ val allArgs = formalArgsRegistry.genAllArgsRefsForForwarder()
val superClass = {
val superClassSym = currentClassSym.superClass
if (isNestedJSClass(superClassSym)) {
- js.VarRef(js.Ident(JSSuperClassParamName))(jstpe.AnyType)
+ js.VarRef(JSSuperClassParamName)(jstpe.AnyType)
} else {
- js.LoadJSConstructor(encodeClassRef(superClassSym))
+ js.LoadJSConstructor(encodeClassName(superClassSym))
}
}
- val receiver = js.This()(jstpe.AnyType)
+ 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.JSSuperBracketSelect(superClass, receiver, nameString)
+ 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.JSSuperBracketSelect(superClass, receiver, nameString),
+ js.Assign(js.JSSuperSelect(superClass, receiver, nameString),
allArgs.head.asInstanceOf[js.Tree])
} else {
- js.JSSuperBracketCall(superClass, receiver, nameString, allArgs)
+ js.JSSuperMethodCall(superClass, receiver, nameString, allArgs)
}
}
- private def genApplyForSymNonJSSuperCall(minArgc: Int,
- exported: Exported, static: Boolean): js.Tree = {
- implicit val pos = exported.pos
+ private def genApplyForSymNonJSSuperCall(
+ formalArgsRegistry: FormalArgsRegistry, sym: Symbol,
+ static: Boolean, inline: Boolean): js.Tree = {
+ implicit val pos = sym.pos
- // the (single) type of the repeated parameter if any
- val repeatedTpe =
- exported.params.lastOption.withFilter(_.isRepeated).map(_.tpe)
+ val varDefs = new mutable.ListBuffer[js.VarDef]
- val normalArgc = exported.params.size -
- (if (repeatedTpe.isDefined) 1 else 0)
+ for ((param, i) <- jsParamInfos(sym).zipWithIndex) {
+ val rhs = genScalaArg(sym, i, formalArgsRegistry, param, static, captures = Nil)(
+ prevArgsCount => varDefs.take(prevArgsCount).toList.map(_.ref))
- // optional repeated parameter list
- val jsVarArgPrep = repeatedTpe map { tpe =>
- val rhs = genJSArrayToVarArgs(genVarargRef(normalArgc, minArgc))
- val ident = freshLocalIdent("prep" + normalArgc)
- js.VarDef(ident, rhs.tpe, mutable = false, rhs)
+ varDefs += js.VarDef(freshLocalIdent("prep" + i), NoOriginalName,
+ rhs.tpe, mutable = false, rhs)
}
- // normal arguments
- val jsArgRefs = (1 to normalArgc).toList.map(
- i => genFormalArgRef(i, minArgc))
-
- // Generate JS code to prepare arguments (default getters and unboxes)
- val jsArgPrep = genPrepareArgs(jsArgRefs, exported) ++ jsVarArgPrep
- val jsArgPrepRefs = jsArgPrep.map(_.ref)
-
- // Combine prep'ed formal arguments with captures
- def varRefForCaptureParam(param: ParamSpec): js.Tree =
- js.VarRef(encodeLocalSym(param.sym))(toIRType(param.sym.tpe))
- val allJSArgs = {
- exported.captureParamsFront.map(varRefForCaptureParam) :::
- jsArgPrepRefs :::
- exported.captureParamsBack.map(varRefForCaptureParam)
- }
+ val builtVarDefs = varDefs.result()
- val jsResult = genResult(exported, allJSArgs, static)
+ val jsResult = genResult(sym, builtVarDefs.map(_.ref), static, inline)
- js.Block(jsArgPrep :+ jsResult)
+ js.Block(builtVarDefs :+ jsResult)
}
- /** Generate the necessary JavaScript code to prepare the arguments of an
- * exported method (unboxing and default parameter handling)
+ /** Generates a Scala argument from dispatched JavaScript arguments
+ * (unboxing and default parameter handling).
*/
- private def genPrepareArgs(jsArgs: List[js.Tree], exported: Exported)(
- implicit pos: Position): List[js.VarDef] = {
-
- val result = new mutable.ListBuffer[js.VarDef]
+ 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 = {
- for {
- (jsArg, (param, i)) <- jsArgs.zip(exported.params.zipWithIndex)
- } yield {
+ 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 argument is undefined and there is a default getter, call it
- val verifiedOrDefault = if (param.hasDefault) {
- js.If(js.BinaryOp(js.BinaryOp.===, jsArg, js.Undefined()), {
- genCallDefaultGetter(exported.sym, i, param.sym.pos) {
- prevArgsCount => result.take(prevArgsCount).toList.map(_.ref)
- }
- }, {
- // Otherwise, unbox the argument
- unboxedArg
- })(unboxedArg.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
}
-
- result += js.VarDef(freshLocalIdent("prep" + i),
- verifiedOrDefault.tpe, mutable = false, verifiedOrDefault)
}
-
- result.toList
}
- private def genCallDefaultGetter(sym: Symbol, paramIndex: Int,
- paramPos: Position)(
+ 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 trgSym = {
- if (sym.isClassConstructor) {
+ 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 inner classes the sym.owner.companionModule can be broken,
- * therefore companionModule is fetched at uncurryPhase.
+ * For classes nested inside modules the sym.owner.companionModule
+ * can be broken, therefore companionModule is fetched at
+ * uncurryPhase.
*/
- val companionModule = enteringPhase(currentRun.namerPhase) {
- sym.owner.companionModule
+ val trgSym = enteringPhase(currentRun.uncurryPhase) {
+ owner.linkedClassOfClass
}
- companionModule.moduleClass
+ (trgSym, genLoadModule(trgSym))
} else {
- sym.owner
+ assert(static, "expected static")
+ assert(captures.isEmpty, "expected empty captures")
+ (owner, genLoadModule(owner))
}
}
+
val defaultGetter = trgSym.tpe.member(
nme.defaultGetterName(sym.name, paramIndex + 1))
@@ -797,17 +843,22 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
assert(!defaultGetter.isOverloaded,
s"found overloaded default getter $defaultGetter")
- val trgTree = {
- if (sym.isClassConstructor) genLoadModule(trgSym)
- else js.This()(encodeClassType(trgSym))
- }
-
// Pass previous arguments to defaultGetter
val defaultGetterArgs = previousArgsValues(defaultGetter.tpe.params.size)
- if (isJSType(trgSym)) {
+ val callGetter = if (isJSType(trgSym)) {
if (isNonNativeJSClass(defaultGetter.owner)) {
- genApplyJSClassMethod(trgTree, defaultGetter, defaultGetterArgs)
+ 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 " +
@@ -817,155 +868,45 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
} 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(exported: Exported, args: List[js.Tree],
- static: Boolean)(implicit pos: Position): js.Tree = {
- val sym = exported.sym
-
+ 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 if (sym.owner == ObjectClass)
- js.This()(jstpe.ClassType(ir.Definitions.ObjectClass))
else
- js.This()(encodeClassType(sym.owner))
- }
-
- def boxIfNeeded(call: js.Tree): js.Tree = {
- ensureBoxed(call,
- enteringPhase(currentRun.posterasurePhase)(sym.tpe.resultType))
+ js.This()(currentThisType)
}
if (isNonNativeJSClass(currentClassSym)) {
assert(sym.owner == currentClassSym.get, sym.fullName)
- boxIfNeeded(genApplyJSClassMethod(receiver, sym, args))
+ ensureResultBoxed(genApplyJSClassMethod(receiver, sym, args, inline = inline), sym)
} else {
if (sym.isClassConstructor)
genNew(currentClassSym, sym, args)
else if (sym.isPrivate)
- boxIfNeeded(genApplyMethodStatically(receiver, sym, args))
+ ensureResultBoxed(genApplyMethodStatically(receiver, sym, args, inline = inline), sym)
else
- boxIfNeeded(genApplyMethod(receiver, sym, args))
+ ensureResultBoxed(genApplyMethod(receiver, sym, args, inline = inline), sym)
}
}
- private final class ParamSpec(val sym: Symbol, val tpe: Type,
- val isRepeated: Boolean, val hasDefault: Boolean) {
- override def toString(): String =
- s"ParamSpec(${sym.name}, $tpe, $isRepeated, $hasDefault)"
- }
-
- private object ParamSpec extends (Symbol => ParamSpec) {
- def apply(sym: Symbol): ParamSpec = {
- val hasDefault = sym.hasFlag(Flags.DEFAULTPARAM)
- val repeated = isRepeated(sym)
- val tpe = if (repeated) repeatedToSingle(sym.tpe) else sym.tpe
- new ParamSpec(sym, tpe, repeated, hasDefault)
- }
- }
+ // 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]) {
- private sealed abstract class Exported {
- def sym: Symbol
- def pos: Position
- def isLiftedJSConstructor: Boolean
- def params: immutable.IndexedSeq[ParamSpec]
- def captureParamsFront: List[ParamSpec]
- def captureParamsBack: List[ParamSpec]
- def exportArgTypeAt(paramIndex: Int): Type
- def genBody(minArgc: Int, hasRestParam: Boolean, static: Boolean): js.Tree
- def typeInfo: String
- def hasRepeatedParam: Boolean
- }
+ assert(!params.exists(_.capture), "illegal capture params in Exported")
- private case class ExportedSymbol(sym: Symbol) extends Exported {
- private val isAnonJSClassConstructor =
- sym.isClassConstructor && sym.owner.isAnonymousClass && isJSType(sym.owner)
-
- val isLiftedJSConstructor =
- sym.isClassConstructor && isNestedJSClass(sym.owner)
-
- val (params, captureParamsFront, captureParamsBack) = {
- val allParamsUncurry =
- enteringPhase(currentRun.uncurryPhase)(sym.paramss.flatten.map(ParamSpec))
- val allParamsPosterasure =
- enteringPhase(currentRun.posterasurePhase)(sym.paramss.flatten.map(ParamSpec))
- val allParamsNow = sym.paramss.flatten.map(ParamSpec)
-
- def mergeUncurryPosterasure(paramsUncurry: List[ParamSpec],
- paramsPosterasure: List[ParamSpec]): List[ParamSpec] = {
- for {
- (paramUncurry, paramPosterasure) <- paramsUncurry.zip(paramsPosterasure)
- } yield {
- if (paramUncurry.isRepeated) paramUncurry
- else paramPosterasure
- }
- }
-
- if (!isLiftedJSConstructor && !isAnonJSClassConstructor) {
- /* Easy case: all params are formal params, and we only need to
- * travel back before uncurry to handle repeated params, or before
- * posterasure for other params.
- */
- assert(allParamsUncurry.size == allParamsPosterasure.size,
- s"Found ${allParamsUncurry.size} params entering uncurry but " +
- s"${allParamsPosterasure.size} params entering posterasure for " +
- s"non-lifted symbol ${sym.fullName}")
- val formalParams =
- mergeUncurryPosterasure(allParamsUncurry, allParamsPosterasure)
- (formalParams.toIndexedSeq, Nil, Nil)
- } else {
- /* The `arg$outer` param is added by explicitouter (between uncurry
- * and posterasure) while the other capture params are added by
- * lambdalift (between posterasure and now).
- *
- * Note that lambdalift creates new symbols even for parameters that
- * are not the result of lambda lifting, but it preserves their
- * `name`s.
- */
-
- val hasOuterParam = {
- allParamsPosterasure.size == allParamsUncurry.size + 1 &&
- allParamsPosterasure.head.sym.name == jsnme.arg_outer
- }
- assert(
- hasOuterParam ||
- allParamsPosterasure.size == allParamsUncurry.size,
- s"Found ${allParamsUncurry.size} params entering uncurry but " +
- s"${allParamsPosterasure.size} params entering posterasure for " +
- s"lifted constructor symbol ${sym.fullName}")
-
- val nonOuterParamsPosterasure =
- if (hasOuterParam) allParamsPosterasure.tail
- else allParamsPosterasure
- val formalParams =
- mergeUncurryPosterasure(allParamsUncurry, nonOuterParamsPosterasure)
-
- val startOfRealParams =
- allParamsNow.map(_.sym.name).indexOfSlice(allParamsUncurry.map(_.sym.name))
- val (captureParamsFront, restOfParamsNow) =
- allParamsNow.splitAt(startOfRealParams)
- val captureParamsBack = restOfParamsNow.drop(formalParams.size)
-
- if (isAnonJSClassConstructor) {
- /* For an anonymous JS class constructor, we put the capture
- * parameters back as formal parameters.
- */
- val allFormalParams =
- captureParamsFront ::: formalParams ::: captureParamsBack
- (allFormalParams.toIndexedSeq, Nil, Nil)
- } else {
- (formalParams.toIndexedSeq, captureParamsFront, captureParamsBack)
- }
- }
- }
-
- val hasRepeatedParam = params.nonEmpty && params.last.isRepeated
-
- def pos: Position = sym.pos
-
- def exportArgTypeAt(paramIndex: Int): Type = {
+ final def exportArgTypeAt(paramIndex: Int): Type = {
if (paramIndex < params.length) {
params(paramIndex).tpe
} else {
@@ -975,17 +916,22 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
}
}
- def genBody(minArgc: Int, hasRestParam: Boolean, static: Boolean): js.Tree =
- genApplyForSym(minArgc, hasRestParam, this, static)
+ def genBody(formalArgsRegistry: FormalArgsRegistry): js.Tree
+
+ lazy val hasRepeatedParam = params.lastOption.exists(_.repeated)
+ }
- def typeInfo: String = sym.tpe.toString
+ 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 HijackedTypeTest(
- boxedClassName: String, rank: Int) extends 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 {
@@ -1000,47 +946,16 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
private case object NoTypeTest extends RTTypeTest
- private object RTTypeTest {
- implicit object Ordering extends PartialOrdering[RTTypeTest] {
- override def tryCompare(lhs: RTTypeTest, rhs: RTTypeTest): Option[Int] = {
- if (lteq(lhs, rhs)) if (lteq(rhs, lhs)) Some(0) else Some(-1)
- else if (lteq(rhs, lhs)) Some(1) else None
- }
-
- override def lteq(lhs: RTTypeTest, rhs: RTTypeTest): Boolean = {
- (lhs, rhs) match {
- // NoTypeTest is always last
- case (_, NoTypeTest) => true
- case (NoTypeTest, _) => false
-
- case (HijackedTypeTest(_, rank1), HijackedTypeTest(_, rank2)) =>
- rank1 <= rank2
-
- case (InstanceOfTypeTest(t1), InstanceOfTypeTest(t2)) =>
- t1 <:< t2
-
- case (_: HijackedTypeTest, _: InstanceOfTypeTest) => true
- case (_: InstanceOfTypeTest, _: HijackedTypeTest) => false
- }
- }
-
- override def equiv(lhs: RTTypeTest, rhs: RTTypeTest): Boolean = {
- lhs == rhs
- }
- }
- }
-
// Very simple O(n²) topological sort for elements assumed to be distinct
- private def topoSortDistinctsBy[A <: AnyRef, B](coll: List[A])(f: A => B)(
- implicit ord: PartialOrdering[B]): List[A] = {
-
+ 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) || !ord.lteq(f(x), f(y))))
+ y => (x eq y) || !lteq(x, y)))
assert(!rhs.isEmpty, s"cycle while ordering $coll")
loop(lhs ::: rhs.tail, rhs.head :: acc)
}
@@ -1055,96 +970,121 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent {
InstanceOfTypeTest(tpe.valueClazz.typeConstructor)
case _ =>
- import ir.{Definitions => Defs}
(toIRType(tpe): @unchecked) match {
- case jstpe.AnyType => NoTypeTest
-
- case jstpe.NoType => HijackedTypeTest(Defs.BoxedUnitClass, 0)
- case jstpe.BooleanType => HijackedTypeTest(Defs.BoxedBooleanClass, 1)
- case jstpe.CharType => HijackedTypeTest(Defs.BoxedCharacterClass, 2)
- case jstpe.ByteType => HijackedTypeTest(Defs.BoxedByteClass, 3)
- case jstpe.ShortType => HijackedTypeTest(Defs.BoxedShortClass, 4)
- case jstpe.IntType => HijackedTypeTest(Defs.BoxedIntegerClass, 5)
- case jstpe.LongType => HijackedTypeTest(Defs.BoxedLongClass, 6)
- case jstpe.FloatType => HijackedTypeTest(Defs.BoxedFloatClass, 7)
- case jstpe.DoubleType => HijackedTypeTest(Defs.BoxedDoubleClass, 8)
-
- case jstpe.ClassType(Defs.BoxedUnitClass) => HijackedTypeTest(Defs.BoxedUnitClass, 0)
- case jstpe.ClassType(Defs.BoxedStringClass) => HijackedTypeTest(Defs.BoxedStringClass, 9)
- case jstpe.ClassType(_) => InstanceOfTypeTest(tpe)
-
- case jstpe.ArrayType(_) => InstanceOfTypeTest(tpe)
+ 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)
}
}
}
- // Group-by that does not rely on hashCode(), only equals() - O(n²)
- private def groupByWithoutHashCode[A, B](
+ /** 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
- val m = new ArrayBuffer[(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(elem)))
- else m(index) = (key, elem :: m(index)._2)
+ if (index < 0)
+ m += ((key, List.newBuilder[A] += elem))
+ else
+ m(index)._2 += elem
}
- m.toList
+ m.toList.map(kv => kv._1 -> kv._2.result())
}
private def genThrowTypeError(msg: String = "No matching overload")(
implicit pos: Position): js.Tree = {
- js.Throw(js.StringLiteral(msg))
+ js.UnaryOp(js.UnaryOp.Throw, js.StringLiteral(msg))
}
- private def genFormalArgs(minArgc: Int, needsRestParam: Boolean)(
- implicit pos: Position): List[js.ParamDef] = {
- val fixedParams = (1 to minArgc map genFormalArg).toList
- if (needsRestParam) fixedParams :+ genRestFormalArg()
- else fixedParams
- }
+ class FormalArgsRegistry(minArgc: Int, needsRestParam: Boolean) {
+ private val fixedParamNames: scala.collection.immutable.IndexedSeq[LocalName] =
+ (0 until minArgc).toIndexedSeq.map(_ => freshLocalIdent("arg")(NoPosition).name)
- private def genFormalArg(index: Int)(implicit pos: Position): js.ParamDef = {
- js.ParamDef(js.Ident("arg$" + index), jstpe.AnyType,
- mutable = false, rest = false)
- }
+ private val restParamName: LocalName =
+ if (needsRestParam) freshLocalIdent("rest")(NoPosition).name
+ else null
- private def genRestFormalArg()(implicit pos: Position): js.ParamDef = {
- js.ParamDef(js.Ident("arg$rest"), jstpe.AnyType,
- mutable = false, rest = true)
- }
+ 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)
+ }
- private def genFormalArgRef(index: Int, minArgc: Int)(
- implicit pos: Position): js.Tree = {
- if (index <= minArgc)
- js.VarRef(js.Ident("arg$" + index))(jstpe.AnyType)
- else
- js.JSBracketSelect(genRestArgRef(), js.IntLiteral(index - 1 - minArgc))
- }
+ val restParam = {
+ if (needsRestParam) {
+ Some(js.ParamDef(js.LocalIdent(restParamName),
+ NoOriginalName, jstpe.AnyType, mutable = false))
+ } else {
+ None
+ }
+ }
- private def genVarargRef(fixedParamCount: Int, minArgc: Int)(
- implicit pos: Position): js.Tree = {
- val restParam = genRestArgRef()
- assert(fixedParamCount >= minArgc,
- s"genVarargRef($fixedParamCount, $minArgc) at $pos")
- if (fixedParamCount == minArgc) restParam
- else {
- js.JSBracketMethodApply(restParam, js.StringLiteral("slice"), List(
- js.IntLiteral(fixedParamCount - minArgc)))
+ (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))
}
- }
- private def genRestArgRef()(implicit pos: Position): js.Tree =
- js.VarRef(js.Ident("arg$rest"))(jstpe.AnyType)
+ 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)))
+ }
+ }
- private def hasRepeatedParam(sym: Symbol) = {
- enteringPhase(currentRun.uncurryPhase) {
- sym.paramss.flatten.lastOption.exists(isRepeated _)
+ 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
index ed8a3e8cb5..10d2ac4f21 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSFiles.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSFiles.scala
@@ -30,31 +30,21 @@ trait GenJSFiles[G <: Global with Singleton] extends SubComponent {
import global._
import jsAddons._
- def genIRFile(cunit: CompilationUnit, sym: Symbol, suffix: Option[String],
- tree: ir.Trees.ClassDef): Unit = {
- val outfile = getFileFor(cunit, sym, suffix.getOrElse("") + ".sjsir")
+ 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, sym: Symbol,
- suffix: String) = {
+ private def getFileFor(cunit: CompilationUnit, className: ir.Names.ClassName,
+ suffix: String): AbstractFile = {
val baseDir: AbstractFile =
settings.outputDirs.outputDirFor(cunit.source.file)
- val fullName = sym.fullName match {
- case "java.lang._String" => "java.lang.String"
- case fullName => fullName
- }
-
- val pathParts = fullName.split("[./]")
+ val pathParts = className.nameString.split('.')
val dir = pathParts.init.foldLeft(baseDir)(_.subdirectoryNamed(_))
-
- var filename = pathParts.last
- if (sym.isModuleClass && !isImplClass(sym))
- filename = filename + nme.MODULE_SUFFIX_STRING
-
- dir fileNamed (filename + suffix)
+ 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
index 59c103a5ad..e91b74d4ff 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala
@@ -37,6 +37,8 @@ trait JSDefinitions {
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")
@@ -44,22 +46,22 @@ trait JSDefinitions {
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 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 JavaScriptExceptionClass = getClassIfDefined("scala.scalajs.js.JavaScriptException")
lazy val JSNameAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSName")
@@ -72,8 +74,7 @@ trait JSDefinitions {
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 JavaDefaultMethodAnnotation = getRequiredClass("scala.scalajs.js.annotation.JavaDefaultMethod")
+ lazy val JSOperatorAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSOperator")
lazy val JSImportNamespaceObject = getRequiredModule("scala.scalajs.js.annotation.JSImport.Namespace")
@@ -94,20 +95,33 @@ trait JSDefinitions {
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_wrapJavaScriptException = getMemberMethod(RuntimePackageModule, newTermName("wrapJavaScriptException"))
- lazy val Runtime_unwrapJavaScriptException = getMemberMethod(RuntimePackageModule, newTermName("unwrapJavaScriptException"))
lazy val Runtime_toScalaVarArgs = getMemberMethod(RuntimePackageModule, newTermName("toScalaVarArgs"))
lazy val Runtime_toJSVarArgs = getMemberMethod(RuntimePackageModule, newTermName("toJSVarArgs"))
lazy val Runtime_constructorOf = getMemberMethod(RuntimePackageModule, newTermName("constructorOf"))
@@ -115,7 +129,20 @@ trait JSDefinitions {
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_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)
@@ -129,6 +156,11 @@ trait JSDefinitions {
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
index c503e8afbc..263f1def30 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala
@@ -17,20 +17,16 @@ import scala.collection.mutable
import scala.tools.nsc._
import org.scalajs.ir
-import ir.{Trees => js, Types => jstpe}
+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 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
- */
+/** Encoding of symbol names for the IR. */
trait JSEncoding[G <: Global with Singleton] extends SubComponent {
self: GenJSCode[G] =>
@@ -51,24 +47,34 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent {
* local name scope using [[reserveLocalName]]. Otherwise, this name can
* clash with another local identifier.
*/
- final val JSSuperClassParamName = "$superClass"
+ 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[String]]
- private val returnLabelName = new ScopedVar[VarBox[Option[String]]]
- private val localSymbolNames = new ScopedVar[mutable.Map[Symbol, String]]
- private val isReserved = Set("arguments", "eval")
+ 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,
- returnLabelName := null,
- localSymbolNames := mutable.Map.empty
+ localSymbolNames := mutable.Map.empty,
+ usedLabelNames := mutable.Set.empty,
+ labelSymbolNames := mutable.Map.empty,
+ returnLabelName := null
)(body)
}
- def reserveLocalName(name: String): Unit = {
+ def reserveLocalName(name: LocalName): Unit = {
require(usedLocalNames.isEmpty,
s"Trying to reserve the name '$name' but names have already been " +
"allocated")
@@ -85,116 +91,172 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent {
case None =>
inner
case Some(labelName) =>
- js.Labeled(js.Ident(labelName), tpe, inner)
+ js.Labeled(labelName, tpe, inner)
}
}
}
- private def freshName(base: String = "x"): String = {
+ private def freshNameGeneric[N <: ir.Names.Name](base: N,
+ usedNamesSet: mutable.Set[N])(
+ withSuffix: (N, String) => N): N = {
+
var suffix = 1
- var longName = base
- while (usedLocalNames(longName) || isReserved(longName)) {
+ var result = base
+ while (usedNamesSet(result)) {
suffix += 1
- longName = base+"$"+suffix
+ result = withSuffix(base, "$" + suffix)
}
- usedLocalNames += longName
- mangleJSName(longName)
+ usedNamesSet += result
+ result
}
- def freshLocalIdent()(implicit pos: ir.Position): js.Ident =
- js.Ident(freshName(), None)
+ 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)
+ })
+ }
- def freshLocalIdent(base: String)(implicit pos: ir.Position): js.Ident =
- js.Ident(freshName(base), Some(base))
+ private def freshLabelName(base: LabelName): LabelName =
+ freshNameGeneric(base, usedLabelNames)(_.withSuffix(_))
- private def localSymbolName(sym: Symbol): String =
- localSymbolNames.getOrElseUpdate(sym, freshName(sym.name.toString))
+ def freshLabelName(base: String): LabelName =
+ freshLabelName(LabelName(base))
- def getEnclosingReturnLabel()(implicit pos: ir.Position): js.Ident = {
+ 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(freshName("_return"))
- js.Ident(box.value.get)
+ box.value = Some(freshLabelName("_return"))
+ box.value.get
}
// Encoding methods ----------------------------------------------------------
- def encodeLabelSym(sym: Symbol)(implicit pos: Position): js.Ident = {
+ def encodeLabelSym(sym: Symbol): LabelName = {
require(sym.isLabel, "encodeLabelSym called with non-label symbol: " + sym)
- js.Ident(localSymbolName(sym), Some(sym.unexpandedName.decoded))
+ labelSymbolName(sym)
}
- 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)
+ 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))
+ }
- val name0 = encodeMemberNameInternal(sym)
- val name =
- if (name0.charAt(name0.length()-1) != ' ') name0
- else name0.substring(0, name0.length()-1)
-
- /* Java-defined fields are always accessed as if they were private. This
- * is necessary because they are defined as private by our .scala source
- * files, but they are considered `!isPrivate` at use site, since their
- * symbols come from Java-emitted .class files. Fortunately, we can
- * easily detect those as `isJavaDefined`. This includes fields of Ref
- * types (IntRef, ObjectRef, etc.) which were special-cased at use-site
- * in Scala.js < 0.6.15.
- * Caveat: because of this, changing the length of the superclass chain of
- * a Java-defined class is a binary incompatible change.
- *
- * We also special case outer fields. This essentially fixes #2382, which
- * is caused by a class having various $outer pointers in its hierarchy
- * that point to different outer instances. Without this fix, they all
- * collapse to the same field in the IR.
- *
- * TODO We should probably consider emitting *all* fields with an ancestor
- * count. We cannot do that in a binary compatible way, though. This is
- * filed as #2629.
- */
- val idSuffix: String = {
- val usePerClassSuffix = {
- sym.isPrivate ||
- sym.isJavaDefined ||
- sym.isOuterField
- }
- if (usePerClassSuffix)
- sym.owner.ancestors.count(!_.isTraitOrInterface).toString
- else
- "f"
- }
+ def encodeFieldSymAsStringLiteral(sym: Symbol)(
+ implicit pos: Position): js.StringLiteral = {
- val encodedName = name + "$" + idSuffix
- js.Ident(mangleJSName(encodedName), Some(sym.unexpandedName.decoded))
+ 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.Ident = {
+ implicit pos: Position): js.MethodIdent = {
require(sym.isMethod,
"encodeMethodSym called with non-method symbol: " + sym)
- val encodedName =
- if (sym.isClassConstructor) "init_"
- else mangleJSName(encodeMemberNameInternal(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 paramsString = makeParamsString(sym, reflProxy)
+ val name = sym.name
+ val simpleName = SimpleMethodName(name.toString())
- js.Ident(encodedName + paramsString,
- Some(sym.unexpandedName.decoded + paramsString))
+ 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 encodeStaticMemberSym(sym: Symbol)(implicit pos: Position): js.Ident = {
+ def encodeStaticFieldGetterSym(sym: Symbol)(
+ implicit pos: Position): js.MethodIdent = {
+
require(sym.isStaticMember,
- "encodeStaticMemberSym called with non-static symbol: " + sym)
- js.Ident(
- mangleJSName(encodeMemberNameInternal(sym)) + "__" + internalName(sym.tpe),
- Some(sym.unexpandedName.decoded))
+ "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)
}
- def encodeLocalSym(sym: Symbol)(implicit pos: Position): js.Ident = {
+ /** 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.
@@ -204,102 +266,62 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent {
require(sym.isValueParameter ||
(!sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule),
"encodeLocalSym called with non-local symbol: " + sym)
- js.Ident(localSymbolName(sym), Some(sym.unexpandedName.decoded))
+ localSymbolName(sym)
}
- def foreignIsImplClass(sym: Symbol): Boolean =
- sym.isModuleClass && nme.isImplClassName(sym.name)
-
def encodeClassType(sym: Symbol): jstpe.Type = {
if (sym == definitions.ObjectClass) jstpe.AnyType
else if (isJSType(sym)) jstpe.AnyType
else {
assert(sym != definitions.ArrayClass,
"encodeClassType() cannot be called with ArrayClass")
- jstpe.ClassType(encodeClassFullName(sym))
+ jstpe.ClassType(encodeClassName(sym), nullable = true)
}
}
- def encodeClassRef(sym: Symbol): jstpe.ClassRef =
- jstpe.ClassRef(encodeClassFullName(sym))
+ def encodeClassNameIdent(sym: Symbol)(implicit pos: Position): js.ClassIdent =
+ js.ClassIdent(encodeClassName(sym))
- def encodeClassFullNameIdent(sym: Symbol)(implicit pos: Position): js.Ident = {
- js.Ident(encodeClassFullName(sym), Some(sym.fullName))
- }
+ private val BoxedStringModuleClassName = ClassName("java.lang.String$")
- def encodeClassFullName(sym: Symbol): String = {
+ def encodeClassName(sym: Symbol): ClassName = {
assert(!sym.isPrimitiveValueClass,
- s"Illegal encodeClassFullName(${sym.fullName}")
+ s"Illegal encodeClassName(${sym.fullName}")
if (sym == jsDefinitions.HackedStringClass) {
- ir.Definitions.BoxedStringClass
+ jswkn.BoxedStringClass
} else if (sym == jsDefinitions.HackedStringModClass) {
- "jl_String$"
- } else if (sym == definitions.BoxedUnitClass) {
+ 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
- ir.Definitions.BoxedUnitClass
- } else if (sym == jsDefinitions.BoxedUnitModClass) {
- // Same for its module class
- "jl_Void$"
+ jswkn.BoxedUnitClass
} else {
- ir.Definitions.encodeClassName(
- sym.fullName + (if (needsModuleClassSuffix(sym)) "$" else ""))
+ ClassName(sym.fullName + (if (needsModuleClassSuffix(sym)) "$" else ""))
}
}
def needsModuleClassSuffix(sym: Symbol): Boolean =
- sym.isModuleClass && !foreignIsImplClass(sym)
+ sym.isModuleClass && !sym.isJavaDefined
- def encodeComputedNameIdentity(sym: Symbol): String = {
- assert(sym.owner.isModuleClass, sym)
- encodeClassFullName(sym.owner) + "__" + encodeMemberNameInternal(sym)
+ 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)
}
- private def encodeMemberNameInternal(sym: Symbol): String =
- sym.name.toString.replace("_", "$und")
+ def originalNameOfField(sym: Symbol): OriginalName =
+ originalNameOf(sym.name.dropLocal)
- // Encoding of method signatures
+ def originalNameOfMethod(sym: Symbol): OriginalName =
+ originalNameOf(sym.name)
- private def makeParamsString(sym: Symbol, reflProxy: Boolean): String = {
- val tpe = sym.tpe
-
- val paramTypeNames0 = tpe.params map (p => internalName(p.tpe))
-
- val hasExplicitThisParameter = isNonNativeJSClass(sym.owner)
- val paramTypeNames =
- if (!hasExplicitThisParameter) paramTypeNames0
- else internalName(sym.owner.toTypeConstructor) :: paramTypeNames0
+ def originalNameOfClass(sym: Symbol): OriginalName =
+ originalNameOf(sym.fullNameAsName('.'))
- val paramAndResultTypeNames = {
- if (sym.isClassConstructor)
- paramTypeNames
- else if (reflProxy)
- paramTypeNames :+ ""
- else
- paramTypeNames :+ internalName(tpe.resultType)
- }
- paramAndResultTypeNames.mkString("__", "__", "")
- }
-
- /** Computes the internal name for a type. */
- private def internalName(tpe: Type): String = toTypeRef(tpe) match {
- case jstpe.ClassRef("sr_Nothing$") => ir.Definitions.NothingClass
- case jstpe.ClassRef("sr_Null$") => ir.Definitions.NullClass
- case jstpe.ClassRef(cls) => cls
-
- case jstpe.ArrayTypeRef(cls, depth) =>
- val builder = new java.lang.StringBuilder(cls.length + depth)
- for (i <- 0 until depth)
- builder.append('A')
- builder.append(cls)
- builder.toString()
+ private def originalNameOf(name: Name): OriginalName = {
+ val originalName = nme.unexpandedName(name).decoded
+ if (originalName == name.toString) NoOriginalName
+ else OriginalName(originalName)
}
-
- /** 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/org/scalajs/nscplugin/JSGlobalAddons.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala
index dfcd3eb8e2..dea4d5529d 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala
@@ -17,6 +17,7 @@ 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
*
@@ -37,21 +38,6 @@ trait JSGlobalAddons extends JSDefinitions
JSGlobalAddons.this.asInstanceOf[ThisJSGlobalAddons]
}
- sealed abstract class ExportDestination
-
- 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 object TopLevel extends ExportDestination
-
- /** Export as a static member of the companion class. */
- case object Static extends ExportDestination
- }
-
/** 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.
@@ -101,9 +87,13 @@ trait JSGlobalAddons extends JSDefinitions
import scala.reflect.NameTransformer
import scala.reflect.internal.Flags
- /** Symbols of constructors and modules that are to be exported */
- private val exportedSymbols =
- mutable.Map.empty[Symbol, List[ExportInfo]]
+ /** 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 =
@@ -113,12 +103,19 @@ trait JSGlobalAddons extends JSDefinitions
private val methodExportPrefix = exportPrefix + "meth$"
private val propExportPrefix = exportPrefix + "prop$"
- trait ExportInfo {
- val jsName: String
+ /** Info for a non-member export. */
+ sealed trait ExportInfo {
val pos: Position
- val destination: ExportDestination
}
+ /* 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
}
@@ -135,21 +132,154 @@ trait JSGlobalAddons extends JSDefinitions
}
}
+ 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 = {
- exportedSymbols.clear()
+ topLevelExports.clear()
+ staticExports.clear()
jsNativeLoadSpecs.clear()
}
- def registerForExport(sym: Symbol, infos: List[ExportInfo]): Unit = {
- assert(!exportedSymbols.contains(sym),
- "Same symbol exported twice: " + sym)
- exportedSymbols.put(sym, infos)
+ def registerTopLevelExports(sym: Symbol, infos: List[TopLevelExportInfo]): Unit = {
+ assert(!topLevelExports.contains(sym), s"symbol exported twice: $sym")
+ topLevelExports.put(sym, infos)
}
- def registeredExportsOf(sym: Symbol): List[ExportInfo] = {
- exportedSymbols.getOrElse(sym, Nil)
+ 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
@@ -197,8 +327,8 @@ trait JSGlobalAddons extends JSDefinitions
/** has this symbol to be translated into a JS getter (both directions)? */
def isJSGetter(sym: Symbol): Boolean = {
- /* We only get here when `sym.isMethod`, thus `sym.isModule` implies that
- * `sym` is the module's accessor. In 2.12, module accessors are synthesized
+ /* `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 {
@@ -214,23 +344,6 @@ trait JSGlobalAddons extends JSDefinitions
def isJSSetter(sym: Symbol): Boolean =
nme.isSetterName(sym.name) && sym.isMethod && !sym.isConstructor
- /** Is this field symbol a static field at the IR level? */
- def isFieldStatic(sym: Symbol): Boolean = {
- sym.owner.isModuleClass && // usually false, avoids a lookup in the map
- registeredExportsOf(sym).nonEmpty
- }
-
- /** The export info of a static field.
- *
- * Requires `isFieldStatic(sym)`.
- *
- * The result is non-empty. If it contains an `ExportInfo` with
- * `isStatic = true`, then it is the only element in the list. Otherwise,
- * all elements have `isTopLevel = true`.
- */
- def staticFieldInfoOf(sym: Symbol): List[ExportInfo] =
- registeredExportsOf(sym)
-
/** has this symbol to be translated into a JS bracket access (JS to Scala) */
def isJSBracketAccess(sym: Symbol): Boolean =
sym.hasAnnotation(JSBracketAccessAnnotation)
@@ -248,9 +361,10 @@ trait JSGlobalAddons extends JSDefinitions
sym.getAnnotation(JSNameAnnotation).fold[JSName] {
JSName.Literal(defaultJSNameOf(sym))
} { annotation =>
- annotation.args.head match {
- case Literal(Constant(name: String)) => JSName.Literal(name)
- case tree => JSName.Computed(tree.symbol)
+ annotation.constantAtIndex(0).collect {
+ case Constant(name: String) => JSName.Literal(name)
+ }.getOrElse {
+ JSName.Computed(annotation.args.head.symbol)
}
}
}
@@ -264,31 +378,19 @@ trait JSGlobalAddons extends JSDefinitions
/** Stores the JS native load spec of a symbol for the current compilation
* run.
*/
- def storeJSNativeLoadSpec(sym: Symbol, spec: JSNativeLoadSpec): Unit = {
- assert(sym.isClass,
- s"storeJSNativeLoadSpec called for non-class symbol $sym")
-
+ 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 = {
- assert(sym.isClass,
- s"jsNativeLoadSpecOf called for non-class symbol $sym")
-
+ 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] = {
- assert(sym.isClass,
- s"jsNativeLoadSpecOfOption called for non-class symbol $sym")
-
+ 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
index 5b337ac4fa..a199b87f98 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala
@@ -46,21 +46,34 @@ abstract class JSPrimitives {
final val UNITVAL = JS_NATIVE + 1 // () value, which is undefined
- final val JS_IMPORT = UNITVAL + 1 // js.import.apply(specifier)
+ final val JS_NEW_TARGET = UNITVAL + 1 // js.new.target
- final val CONSTRUCTOROF = JS_IMPORT + 1 // runtime.constructorOf(clazz)
+ 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 LINKING_INFO = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.linkingInfo
-
- final val IN = LINKING_INFO + 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 DEBUGGER = FORIN + 1 // js.special.debugger
-
- final val LastJSPrimitiveCode = DEBUGGER
+ final val DYNAMIC_IMPORT = WITH_CONTEXTUAL_JS_CLASS_VALUE + 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)
@@ -86,23 +99,38 @@ abstract class JSPrimitives {
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_linkingInfo, LINKING_INFO)
+ 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 =
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala
index aa51601591..e9217c04a7 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala
@@ -16,7 +16,8 @@ import scala.collection.mutable
import scala.tools.nsc.Global
-import org.scalajs.ir.Trees.isValidIdentifier
+import org.scalajs.ir.Trees.TopLevelExportDef.isValidTopLevelExportName
+import org.scalajs.ir.WellKnownNames.DefaultModuleID
/**
* Prepare export generation
@@ -32,144 +33,94 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
import scala.reflect.internal.Flags
- case class ExportInfo(jsName: String,
+ 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)
- extends jsInterop.ExportInfo
- /** Generate the exporter for the given DefDef
- * or ValDef (abstract val in class, val in trait or lazy val;
- * these don't get DefDefs until the fields phase)
+ /** Generate exports for the given Symbol.
*
- * If this DefDef is a constructor, it is registered to be exported by
- * GenJSCode instead and no trees are returned.
+ * * Registers top-level and static exports.
+ * * Returns (non-static) exporters for this symbol.
*/
- def genExportMember(baseSym: Symbol): List[Tree] = {
- val clsSym = baseSym.owner
+ def genExport(sym: Symbol): List[Tree] = {
+ // Scala classes are never exported: Their constructors are.
+ val isScalaClass = sym.isClass && !sym.isTrait && !sym.isModuleClass && !isJSAny(sym)
- val exports = exportsOf(baseSym)
+ /* Filter case class apply (and unapply) to work around
+ * https://github.com/scala/bug/issues/8826
+ */
+ val isCaseApplyOrUnapplyParam = sym.isLocalToBlock && sym.owner.isCaseApplyOrUnapply
- // Helper function for errors
- def err(msg: String) = {
- reporter.error(exports.head.pos, msg)
- Nil
- }
+ /* Filter constructors of module classes: The module classes themselves will
+ * be exported.
+ */
+ val isModuleClassCtor = sym.isConstructor && sym.owner.isModuleClass
- def memType = if (baseSym.isConstructor) "constructor" else "method"
+ val exports =
+ if (isScalaClass || isCaseApplyOrUnapplyParam || isModuleClassCtor) Nil
+ else exportsOf(sym)
- if (exports.isEmpty) {
- Nil
- } else if (!hasLegalExportVisibility(baseSym)) {
- err(s"You may only export public and protected ${memType}s")
- } else if (baseSym.isMacro) {
- err("You may not export a macro")
- } else if (isJSAny(clsSym)) {
- err(s"You may not export a $memType of a subclass of js.Any")
- } else if (scalaPrimitives.isPrimitive(baseSym)) {
- err("You may not export a primitive")
- } else if (baseSym.isLocalToBlock) {
- // We exclude case class apply (and unapply) to work around SI-8826
- if (clsSym.isCaseApplyOrUnapply) {
- Nil
- } else {
- err("You may not export a local definition")
- }
- } 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 (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 (checkClassOrModuleExports(clsSym, exports.head.pos))
- jsInterop.registerForExport(baseSym, exports)
-
- Nil
- } else {
- assert(!baseSym.isBridge,
- s"genExportMember called for bridge symbol $baseSym")
-
- // Reset interface flag: Any trait will contain non-empty methods
- clsSym.resetFlag(Flags.INTERFACE)
+ assert(exports.isEmpty || !sym.isBridge,
+ s"found exports for bridge symbol $sym. exports: $exports")
- val (normalExports, topLevelAndStaticExports) =
- exports.partition(_.destination == ExportDestination.Normal)
+ 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.
+ /* 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.
*/
- jsInterop.registerForExport(baseSym, topLevelAndStaticExports)
-
- // Actually generate exporter methods
- normalExports.flatMap(exp => genExportDefs(baseSym, exp.jsName, exp.pos))
+ if (sym.isGetter)
+ registerStaticAndTopLevelExports(sym.accessed, topLevelAndStaticExports)
+ } else {
+ registerStaticAndTopLevelExports(sym, topLevelAndStaticExports)
}
- }
-
- /** Check and (potentially) register a class or module for export.
- *
- * Note that Scala classes are never registered for export, their
- * constructors are.
- */
- def registerClassOrModuleExports(sym: Symbol): Unit = {
- val exports = exportsOf(sym)
- def isScalaClass = !sym.isModuleClass && !isJSAny(sym)
- if (exports.nonEmpty && checkClassOrModuleExports(sym, exports.head.pos) &&
- !isScalaClass) {
- jsInterop.registerForExport(sym, exports)
- }
+ // For normal exports, generate exporter methods.
+ normalExports.flatMap(exp => genExportDefs(sym, exp.jsName, exp.pos))
}
- /** Check a class or module for export.
- *
- * There are 2 ways that this method can be reached:
- * - via `registerClassOrModuleExports`
- * - via `genExportMember` (constructor of Scala class)
- */
- private def checkClassOrModuleExports(sym: Symbol, errPos: Position): Boolean = {
- val isMod = sym.isModuleClass
-
- def err(msg: String) = {
- reporter.error(errPos, msg)
- false
+ 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)
}
- def hasAnyNonPrivateCtor: Boolean =
- sym.info.member(nme.CONSTRUCTOR).filter(!isPrivateMaybeWithin(_)).exists
-
- def isJSNative = sym.hasAnnotation(JSNativeAnnotation)
+ if (topLevel.nonEmpty)
+ jsInterop.registerTopLevelExports(sym, topLevel)
- if (sym.isTrait) {
- err("You may not export a trait")
- } else if (isJSNative) {
- err("You may not export a native JS " + (if (isMod) "object" else "class"))
- } else if (!hasLegalExportVisibility(sym)) {
- err("You may only export public and protected " +
- (if (isMod) "objects" else "classes"))
- } else if (sym.isLocalToBlock) {
- err("You may not export a local " +
- (if (isMod) "object" else "class"))
- } else if (!sym.isStatic) {
- err("You may not export a nested " +
- (if (isMod) "object" else s"class. $createFactoryInOuterClassHint"))
- } else if (sym.isAbstractClass) {
- err("You may not export an abstract class")
- } else if (!isMod && !hasAnyNonPrivateCtor) {
- /* This test is only relevant for JS classes but doesn't hurt for Scala
- * classes as we could not reach it if there were only private
- * constructors.
- */
- err("You may not export a class that has only private constructors")
- } else {
- true
+ val static = exports.collect {
+ case info @ ExportInfo(jsName, ExportDestination.Static) =>
+ jsInterop.StaticExportInfo(jsName)(info.pos)
}
- }
- private def createFactoryInOuterClassHint = {
- "Create an exported factory method in the outer class to work " +
- "around this limitation."
+ if (static.nonEmpty)
+ jsInterop.registerStaticExports(sym, static)
}
/** retrieves the names a sym should be exported to from its annotations
@@ -186,23 +137,47 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
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))
- // Is this a member export (i.e. not a class or module export)?
- val isMember = !sym.isClass && !sym.isConstructor
-
- // Annotations for this member on the whole unit
+ /* 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 = {
- if (isMember && sym.isPublic && !sym.isSynthetic)
- sym.owner.annotations.filter(_.symbol == JSExportAllAnnotation)
+ 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 <- directAnnots ++ unitAnnots
+ annot <- allAnnots
} yield {
val isExportAll = annot.symbol == JSExportAllAnnotation
val isTopLevelExport = annot.symbol == JSExportTopLevelAnnotation
@@ -212,28 +187,45 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
assert(!isTopLevelExport || hasExplicitName,
"Found a top-level export without an explicit name at " + annot.pos)
- def explicitName = annot.stringArg(0).getOrElse {
- reporter.error(annot.pos,
- s"The argument to ${annot.symbol.name} must be a literal string")
- "dummy"
- }
-
val name = {
- if (hasExplicitName) explicitName
- else if (sym.isConstructor) decodedFullName(sym.owner)
- else if (sym.isClass) decodedFullName(sym)
- else sym.unexpandedName.decoded.stripSuffix("_=")
+ 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) ExportDestination.TopLevel
- else if (isStaticExport) ExportDestination.Static
- else ExportDestination.Normal
- }
+ 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
+ }
+ }
- // Enforce proper setter signature
- if (jsInterop.isJSSetter(sym))
- checkSetterSignature(sym, annot.pos, exported = true)
+ ExportDestination.TopLevel(moduleID)
+ } else if (isStaticExport) {
+ ExportDestination.Static
+ } else {
+ ExportDestination.Normal
+ }
+ }
// Enforce no __ in name
if (!isTopLevelExport && name.contains("__")) {
@@ -244,12 +236,30 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
"An exported name may not contain a double underscore (`__`)")
}
- /* Illegal function application exports, i.e., method named 'apply'
- * without an explicit export name.
- */
- if (isMember && !hasExplicitName && sym.name == nme.apply) {
- destination match {
- case ExportDestination.Normal =>
+ // 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 &&
@@ -267,45 +277,9 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
"application. Add @JSExport(\"apply\") to export under the " +
"name apply.")
}
-
- case ExportDestination.TopLevel =>
- throw new AssertionError(
- "Found a top-level export without an explicit name at " +
- annot.pos)
-
- case ExportDestination.Static =>
- reporter.error(annot.pos,
- "A member cannot be exported to function application as " +
- "static. Use @JSExportStatic(\"apply\") to export it under " +
- "the name 'apply'.")
- }
- }
-
- val symOwner =
- if (sym.isConstructor) sym.owner.owner
- else sym.owner
-
- // Destination-specific restrictions
- destination match {
- case ExportDestination.Normal =>
- // Make sure we do not override the default export of toString
- def isIllegalToString = {
- isMember && 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'")
}
- // Disallow @JSExport on non-members.
- if (!isMember && !sym.isTrait) {
- reporter.error(annot.pos,
- "@JSExport is forbidden on objects and classes. " +
- "Use @JSExportTopLevel instead.")
- }
-
- case ExportDestination.TopLevel =>
+ case _: ExportDestination.TopLevel =>
if (sym.isLazy) {
reporter.error(annot.pos,
"You may not export a lazy val to the top level")
@@ -314,20 +288,17 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
"You may not export a getter or a setter to the top level")
}
- /* Disallow non-static methods.
- * Note: Non-static classes have more specific error messages in
- * checkClassOrModuleExports
- */
- if (sym.isMethod && (!symOwner.isStatic || !symOwner.isModuleClass)) {
+ // 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 (!isValidIdentifier(name)) {
+ if (!isValidTopLevelExportName(name)) {
reporter.error(annot.pos,
"The top-level export name must be a valid JavaScript " +
- "identifier")
+ "identifier name")
}
case ExportDestination.Static =>
@@ -346,130 +317,174 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
"non-native JS class may export its members as static.")
}
- if (isMember) {
- if (sym.isLazy) {
- reporter.error(annot.pos,
- "You may not export a lazy val as static")
- }
- } else {
- if (sym.isTrait) {
- reporter.error(annot.pos,
- "You may not export a trait as static.")
- } else {
- reporter.error(annot.pos,
- "Implementation restriction: cannot export a class or " +
- "object 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)
}
- /* Filter out static exports of accessors (as they are not actually
- * exported, their fields are). The above is only used to uniformly perform
- * checks.
+ 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).
*/
- val filteredExports = if (!sym.isAccessor || sym.accessed == NoSymbol) {
- allExportInfos
- } else {
- /* For accessors, we need to apply some special logic to static exports.
- * When tested on accessors, they actually apply on *fields*, not on the
- * accessors. We use the same code paths hereabove to uniformly perform
- * relevant checks, but at the end of the day, we have to throw away the
- * ExportInfo.
- * However, we must make sure 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).
- */
- val (topLevelAndStaticExportInfos, actualExportInfos) =
- allExportInfos.partition(_.destination != ExportDestination.Normal)
-
- if (sym.isGetter) {
- topLevelAndStaticExportInfos.find {
- _.destination == ExportDestination.Static
- }.foreach { firstStatic =>
- for {
- duplicate <- topLevelAndStaticExportInfos
- if duplicate ne firstStatic
- } {
- if (duplicate.destination == ExportDestination.Static) {
- reporter.error(duplicate.pos,
- "Fields (val or var) cannot be exported as static more " +
- "than once")
- } else {
- reporter.error(duplicate.pos,
- "Fields (val or var) cannot be exported both as static " +
- "and at the top-level")
- }
- }
- }
+ if (sym.isGetter) {
+ for {
+ firstStatic <- allExportInfos.find(_.destination == ExportDestination.Static).toList
+ duplicate <- allExportInfos
+ if duplicate ne firstStatic
+ } {
+ duplicate.destination match {
+ case ExportDestination.Normal => // OK
- jsInterop.registerForExport(sym.accessed, topLevelAndStaticExportInfos)
+ 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")
+ }
}
-
- actualExportInfos
}
- filteredExports.distinct
+ allExportInfos.distinct
}
- /** Just like sym.fullName, but does not encode components */
- private def decodedFullName(sym: Symbol): String = {
- if (sym.isRoot || sym.isRootPackage || sym == NoSymbol) sym.name.decoded
- else if (sym.owner.isEffectiveRoot) sym.name.decoded
- else decodedFullName(sym.effectiveOwner.enclClass) + '.' + sym.name.decoded
+ /** 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 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))
+ /** 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 = defSym.cloneSymbol
-
- // Set position of symbol
- expSym.pos = pos
-
- // Alter type for new method (lift return type to Any)
- // The return type is lifted, in order to avoid bridge
- // construction and to detect methods whose signature only differs
- // in the return type.
- // Attention: This will cause boxes for primitive value types and value
- // classes. However, since we have restricted the return types, we can
- // always safely remove these boxes again in the back-end.
- if (!defSym.isConstructor)
- expSym.setInfo(retToAny(expSym.tpe))
-
- // Change name for new method
- expSym.name = scalaName
-
- // Update flags
- expSym.setFlag(Flags.SYNTHETIC)
- expSym.resetFlag(
- Flags.DEFERRED | // We always have a body
- Flags.ACCESSOR | // We are never a "direct" accessor
- Flags.CASEACCESSOR | // And a fortiori not a case accessor
- Flags.LAZY | // We are not a lazy val (even if we export one)
- Flags.OVERRIDE // Synthetic methods need not bother with this
- )
-
- // Remove export annotations
- expSym.removeAnnotation(JSExportAnnotation)
-
- // Add symbol to class
- clsSym.info.decls.enter(expSym)
+ 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(clsSym, defSym, expSym, pos)
+ 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, defSym, expSym, i + 1, pos)
+ } yield genExportDefaultGetter(clsSym, sym, expSym, i + 1, pos)
exporter :: defaultGetters
}
@@ -494,27 +509,46 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
clsSym.info.decls.enter(expGetter)
- genProxyDefDef(clsSym, trgGetter, expGetter, pos)
+ genProxyDefDef(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) {
+ private def genProxyDefDef(trgSym: Symbol, proxySym: Symbol, pos: Position) = atPos(pos) {
+ val tpeParams = proxySym.typeParams.map(gen.mkAttributedIdent(_))
- // 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 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)
+ }
}
- // Construct proxied function call
- val sel = Select(This(clsSym), trgSym)
- val rhs = proxySym.paramss.foldLeft[Tree](sel) {
- (fun,params) => Apply(fun, params map spliceParam)
+ 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))
@@ -528,26 +562,6 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] =>
case _ => AnyClass.tpe
}
- /** Whether the given symbol has a visibility that allows exporting */
- private def hasLegalExportVisibility(sym: Symbol): Boolean =
- sym.isPublic || sym.isProtected && !sym.isProtectedLocal
-
- /** 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)
- }
-
/** Whether a symbol is an annotation that goes directly on a member */
private lazy val isDirectMemberAnnot = Set[Symbol](
JSExportAnnotation,
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala
index 2c2642c87d..592b9aa381 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala
@@ -18,7 +18,7 @@ import nsc._
import scala.collection.immutable.ListMap
import scala.collection.mutable
-import org.scalajs.ir.Trees.{isValidIdentifier, JSNativeLoadSpec}
+import org.scalajs.ir.Trees.{JSGlobalRef, JSNativeLoadSpec}
/** Prepares classes extending js.Any for JavaScript interop
*
@@ -51,7 +51,9 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
import definitions._
import rootMirror._
import jsDefinitions._
- import jsInterop.JSName
+ import jsInterop.{JSCallingConvention, JSName}
+
+ import scala.reflect.internal.Flags
val phaseName: String = "jsinterop"
override def description: String = "prepare ASTs for JavaScript interop"
@@ -83,12 +85,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
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
-
/** Kind of the directly enclosing (most nested) owner. */
private var enclosingOwner: OwnerKind = OwnerKind.None
@@ -146,78 +142,133 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
private val exporters = mutable.Map.empty[Symbol, mutable.ListBuffer[Tree]]
override def transform(tree: Tree): Tree = {
- checkInternalAnnotations(tree)
-
- val preTransformedTree = tree match {
- // Handle js.Anys
- case idef: ImplDef if isJSAny(idef) =>
- transformJSAny(idef)
-
- // In native JS things, only js.Any stuff is allowed
- case idef: ImplDef 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 (!idef.symbol.isSynthetic) {
- reporter.error(idef.pos,
- "Native JS traits, classes and objects cannot contain inner " +
- "Scala traits, classes or objects (i.e., not extending js.Any)")
- }
- super.transform(tree)
-
- // Catch the definition of scala.Enumeration itself
- case cldef: ClassDef if cldef.symbol == ScalaEnumClass =>
- enterOwner(OwnerKind.EnumImpl) { super.transform(cldef) }
+ tree match {
+ case tree: MemberDef => transformMemberDef(tree)
+ case tree: Template => transformTemplateTree(tree)
+ case _ => transformStatOrExpr(tree)
+ }
+ }
- // Catch Scala Enumerations to transform calls to scala.Enumeration.Value
- case idef: ImplDef if isScalaEnum(idef) =>
- val sym = idef.symbol
+ private def transformMemberDef(tree: MemberDef): Tree = {
+ val sym = moduleToModuleClass(tree.symbol)
- checkJSAnySpecificAnnotsOnNonJSAny(idef)
+ checkInternalAnnotations(sym)
- val kind =
- if (idef.isInstanceOf[ModuleDef]) OwnerKind.EnumMod
- else OwnerKind.EnumClass
- enterOwner(kind) { super.transform(idef) }
+ /* 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)
- // Catch (Scala) ClassDefs to forbid js.Anys
- case cldef: ClassDef =>
- val sym = cldef.symbol
+ checkJSCallingConventionAnnots(sym)
- checkJSAnySpecificAnnotsOnNonJSAny(cldef)
+ // @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 (sym == UnionClass)
- sym.addAnnotation(JSTypeAnnot)
+ 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 (shouldPrepareExports)
- registerClassOrModuleExports(sym)
+ 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
+ }
+ }
- enterOwner(OwnerKind.NonEnumScalaClass) { super.transform(cldef) }
+ 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)
+ }
- // Module export sanity check (export generated in JSCode phase)
- case modDef: ModuleDef =>
- val sym = modDef.symbol
+ case _:TypeDef | _:PackageDef =>
+ super.transform(tree)
+ }
- checkJSAnySpecificAnnotsOnNonJSAny(modDef)
+ /* 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)
- if (shouldPrepareExports)
- registerClassOrModuleExports(sym.moduleClass)
+ transformedTree
+ }
- enterOwner(OwnerKind.NonEnumScalaMod) { super.transform(modDef) }
+ private def transformScalaImplDef(tree: ImplDef): Tree = {
+ val sym = moduleToModuleClass(tree.symbol)
+ val isModuleDef = tree.isInstanceOf[ModuleDef]
- // ValOrDefDef's that are local to a block must not be transformed
- case vddef: ValOrDefDef if vddef.symbol.isLocalToBlock =>
- super.transform(tree)
+ // 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)")
+ }
+ }
- // Catch ValDef in js.Any
- case vdef: ValDef if enclosingOwner is OwnerKind.JSType =>
- transformValOrDefDefInJSType(vdef)
+ if (sym == UnionClass)
+ sym.addAnnotation(JSTypeAnnot)
- // Catch DefDef in js.Any
- case ddef: DefDef if enclosingOwner is OwnerKind.JSType =>
- transformValOrDefDefInJSType(fixPublicBeforeTyper(ddef))
+ 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 =>
@@ -225,26 +276,89 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
treeCopy.ValDef(tree, mods, name, transform(tpt), nrhs)
// Exporter generation
- case _: ValOrDefDef if tree.symbol.isMethod =>
- val sym = tree.symbol
- if (shouldPrepareExports) {
- // Generate exporters for this ddef if required
- exporters.getOrElseUpdate(sym.owner,
- mutable.ListBuffer.empty) ++= genExportMember(sym)
- }
+ 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 js.FunctionN or js.ThisFunctionN.
+ * 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) && !AllJSFunctionClasses.contains(tpeSym)) {
- reporter.error(tree.pos,
- "Using an anonymous function as a SAM for the JavaScript " +
- "type " + tpeSym.fullNameString + " is not allowed. " +
- "Use an anonymous class instead.")
+ 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)
@@ -278,6 +392,34 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
|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 =>
@@ -299,6 +441,59 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
}
}
+ /* 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
@@ -322,7 +517,7 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
if predef.symbol == PredefModule =>
if (scalaJSOpts.fixClassOf) {
// Replace call by literal constant containing type
- if (typer.checkClassType(tpeArg)) {
+ 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")
@@ -392,8 +587,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
case _ => super.transform(tree)
}
-
- postTransform(preTransformedTree)
}
private def validateJSConstructorOf(tree: Tree, tpeArg: Tree): Unit = {
@@ -416,90 +609,15 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
}
}
- private def postTransform(tree: Tree) = tree match {
- case _ if !shouldPrepareExports =>
- tree
-
- case Template(parents, self, body) =>
- 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 <- body) {
- 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
- exporters.get(clsSym).fold {
- tree // nothing to change
- } { exports =>
- treeCopy.Template(tree, parents, self, body ::: exports.toList)
- }
-
- case memDef: MemberDef =>
- val sym = memDef.symbol
- if (shouldPrepareExports && sym.isLocalToBlock) {
- // Exports are never valid on local definitions, but delegate complaining.
- val exports = genExportMember(sym)
- assert(exports.isEmpty, "Generated exports for local definition.")
- }
-
- // Expose objects (modules) members of non-native JS classes
- if (sym.isModule && (enclosingOwner is OwnerKind.JSNonNative)) {
- if (shouldModuleBeExposed(sym))
- sym.addAnnotation(ExposedJSMemberAnnot)
- }
-
- memDef
-
- case _ => tree
- }
-
- /**
- * Performs checks and rewrites specific to classes / objects extending
- * js.Any
+ /** Performs checks and rewrites specific to classes / objects extending
+ * js.Any.
*/
- private def transformJSAny(implDef: ImplDef) = {
- val sym = implDef match {
- case _: ModuleDef => implDef.symbol.moduleClass
- case _ => implDef.symbol
- }
-
- lazy val badParent = sym.info.parents find { t =>
- /* We have to allow scala.Dynamic to be able to define js.Dynamic
- * and similar constructs. This causes the unsoundness filed as #1385.
- */
- !(t <:< JSAnyClass.tpe || t =:= AnyRefClass.tpe || t =:= DynamicClass.tpe)
- }
-
- def isNativeJSTraitType(tpe: Type): Boolean = {
- val sym = tpe.typeSymbol
- sym.isTrait && sym.hasAnnotation(JSNativeAnnotation)
- }
-
- val isJSAnonFun = isJSLambda(sym)
+ private def transformJSImplDef(implDef: ImplDef): Tree = {
+ val sym = moduleToModuleClass(implDef.symbol)
sym.addAnnotation(JSTypeAnnot)
- /* Anonymous functions are considered native, since they are handled
- * specially in the backend.
- */
- val isJSNative = sym.hasAnnotation(JSNativeAnnotation) || isJSAnonFun
+ val isJSNative = sym.hasAnnotation(JSNativeAnnotation)
// Forbid @EnableReflectiveInstantiation on JS types
sym.getAnnotation(EnableReflectiveInstantiationAnnotation).foreach {
@@ -509,25 +627,49 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
"extending js.Any.")
}
+ // Forbid package objects that extends js.Any
if (sym.isPackageObjectClass)
reporter.error(implDef.pos, "Package objects may not extend js.Any.")
- def strKind =
- if (sym.isTrait) "trait"
- else if (sym.isModuleClass) "object"
- else "class"
-
// 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 that we do not extend a trait that does not extends js.Any
- if (!isJSAnonFun && badParent.isDefined) {
- val badName = badParent.get.typeSymbol.fullName
- reporter.error(implDef.pos, s"${sym.nameString} extends ${badName} " +
- "which does not extend js.Any.")
+ // 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
@@ -546,117 +688,68 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
"classes or objects")
}
- // Unless it is a trait, it cannot inherit directly from AnyRef
- if (!sym.isTrait && sym.info.parents.exists(_ =:= AnyRefClass.tpe)) {
- reporter.error(implDef.pos,
- s"A non-native JS $strKind cannot directly extend AnyRef. " +
- "It must extend a JS class (native or not).")
- }
-
- // Check that we do not inherit directly from a native JS trait
- if (sym.info.parents.exists(isNativeJSTraitType)) {
- reporter.error(implDef.pos,
- s"A non-native JS $strKind cannot directly extend a "+
- "native JS trait.")
- }
-
// 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 that there is no JS-native-specific annotation
- checkJSNativeSpecificAnnotsOnNonJSNative(implDef)
- }
-
- if (shouldCheckLiterals) {
- checkJSNameArgument(implDef)
- checkJSGlobalLiteral(sym)
- checkJSImportLiteral(sym)
- }
-
- // Checks for native JS stuff, excluding JS anon functions
- if (isJSNative && !isJSAnonFun) {
- // Check if we may have a JS native here
- if (sym.isLocalToBlock) {
- reporter.error(implDef.pos,
- "Local native JS classes and objects are not allowed")
- } else if (anyEnclosingOwner is OwnerKind.ScalaClass) {
- reporter.error(implDef.pos,
- "Scala traits and classes may not have inner native JS " +
- "traits, classes or objects")
- } else if (enclosingOwner is OwnerKind.JSNonNative) {
- reporter.error(implDef.pos, "non-native JS classes, traits and " +
- "objects may not have inner native JS classes, traits or objects")
- } else if (!sym.isTrait) {
- /* Compute the loading spec now, before `flatten` destroys the name.
- * We store it in a global map.
- */
- val optLoadSpec = checkAndComputeJSNativeLoadSpecOf(implDef.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) ||
- annotSym == JSNameAnnotation) {
- reporter.error(annot.pos,
- s"Traits may not have an @${annotSym.nameString} annotation.")
- }
- }
- }
}
- if (shouldPrepareExports)
- registerClassOrModuleExports(sym)
-
// Check for consistency of JS semantics across overriding
for (overridingPair <- new overridingPairs.Cursor(sym).iterator) {
val low = overridingPair.low
val high = overridingPair.high
- def errorPos = {
- if (sym == low.owner) low.pos
- else if (sym == high.owner) high.pos
- else sym.pos
- }
+ 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))
+ def memberDefString(membSym: Symbol): String =
+ membSym.defStringSeenAs(sym.thisType.memberType(membSym))
- // Check for overrides with different JS names - issue #1983
- if (jsInterop.jsNameOf(low) != jsInterop.jsNameOf(high)) {
- val msg = {
- def memberDefStringWithJSName(membSym: Symbol) = {
- memberDefString(membSym) +
- membSym.locationString + " with JSName '" +
- jsInterop.jsNameOf(membSym).displayName + '\''
+ // 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"
}
- "A member of a JS class is overriding another member with a different JS name.\n\n" +
- memberDefStringWithJSName(low) + "\n" +
- " is conflicting with\n" +
- memberDefStringWithJSName(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)
+ reporter.error(errorPos, msg)
}
- 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.")
+ /* 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.")
+ }
}
}
}
@@ -670,14 +763,74 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
else OwnerKind.JSNativeClass
}
}
- enterOwner(kind) { super.transform(implDef) }
+ 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
@@ -703,14 +856,17 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
case JSName.Computed(_) => "" // compile error above
}
- val ownerLoadSpec = jsInterop.jsNativeLoadSpecOf(sym.owner)
+ val ownerLoadSpec = jsInterop.jsNativeLoadSpecOfOption(sym.owner)
val loadSpec = ownerLoadSpec match {
- case Global(globalRef, path) =>
+ case None =>
+ // The owner is a JSGlobalScope
+ makeGlobalRefNativeLoadSpec(jsName, Nil)
+ case Some(Global(globalRef, path)) =>
Global(globalRef, path :+ jsName)
- case Import(module, path) =>
+ case Some(Import(module, path)) =>
Import(module, path :+ jsName)
- case ImportWithGlobalFallback(
- Import(module, modulePath), Global(globalRef, globalPath)) =>
+ case Some(ImportWithGlobalFallback(
+ Import(module, modulePath), Global(globalRef, globalPath))) =>
ImportWithGlobalFallback(
Import(module, modulePath :+ jsName),
Global(globalRef, globalPath :+ jsName))
@@ -725,43 +881,56 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
def parseGlobalPath(pathName: String): Global = {
val globalRef :: path = parsePath(pathName)
- if (!isValidIdentifier(globalRef)) {
- reporter.error(pos,
- "The name of a JS global variable must be a valid JS " +
- s"identifier (got '$globalRef')")
- }
- JSNativeLoadSpec.Global(globalRef, path)
+ makeGlobalRefNativeLoadSpec(globalRef, path)
}
checkAndGetJSNativeLoadingSpecAnnotOf(pos, sym) match {
case Some(annot) if annot.symbol == JSGlobalScopeAnnotation =>
if (!sym.isModuleClass) {
reporter.error(annot.pos,
- "Only native JS objects can have an @JSGlobalScope annotation.")
+ "@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 needsExplicitJSName = {
- (enclosingOwner is OwnerKind.ScalaMod) &&
- !sym.owner.isPackageObjectClass
- }
-
- if (needsExplicitJSName) {
+ 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 classes and objects inside non-native objects " +
- "must have an explicit name in @JSGlobal")
+ "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 {
- "" // do not care because it does not compile anyway
+ "" // 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 path = annot.stringArg(1).fold[List[String]](Nil)(parsePath)
val importSpec = Import(module, path)
val loadSpec = annot.stringArg(2).fold[JSNativeLoadSpec] {
importSpec
@@ -771,114 +940,195 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
}
Some(loadSpec)
+ case Some(annot) =>
+ abort(s"checkAndGetJSNativeLoadingSpecAnnotOf returned unexpected annotation $annot")
+
case None =>
- // We already emitted an error. Just propagate something.
- 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) = {
+ private def transformValOrDefDefInJSType(tree: ValOrDefDef): Tree = {
val sym = tree.symbol
assert(!sym.isLocalToBlock, s"$tree at ${tree.pos}")
- if (shouldPrepareExports) {
- // Exports are never valid on members of JS types, but delegate
- // complaining.
- val exports = genExportMember(sym)
- assert(exports.isEmpty, "Generated exports for member JS type.")
+ 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\")")
- /* Add the @ExposedJSMember annotation to exposed symbols in
- * non-native JS classes.
- */
- if (enclosingOwner is OwnerKind.JSNonNative) {
- def shouldBeExposed: Boolean = {
- !sym.isConstructor &&
- !sym.isValueParameter &&
- !sym.isParamWithDefault &&
- !sym.isSynthetic &&
- !isPrivateMaybeWithin(sym)
+ 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")
}
- if (shouldBeExposed) {
- sym.addAnnotation(ExposedJSMemberAnnot)
- /* The field being accessed must also be exposed, although it's
- * private.
- */
- if (sym.isAccessor)
- sym.accessed.addAnnotation(ExposedJSMemberAnnot)
+ 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")
}
- }
- }
- /* If this is an accessor, copy a potential @JSName annotation from
- * the field since otherwise it will get lost for traits (since they
- * have no fields).
- *
- * Do this only if the accessor does not already have an @JSName itself
- * (this happens almost all the time now that @JSName is annotated with
- * @field @getter @setter).
- */
- if (sym.isAccessor && !sym.hasAnnotation(JSNameAnnotation))
- sym.accessed.getAnnotation(JSNameAnnotation).foreach(sym.addAnnotation)
+ 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")
- if (sym.name == nme.apply && !sym.hasAnnotation(JSNameAnnotation)) {
- if (jsInterop.isJSGetter(sym)) {
- reporter.error(sym.pos, s"A member named apply represents function " +
- "application in JavaScript. A parameterless member should be " +
- "exported as a property. You must add @JSName(\"apply\")")
- } else if (enclosingOwner is OwnerKind.JSNonNative) {
- reporter.error(sym.pos,
- "A non-native JS class cannot declare a method " +
- "named `apply` without `@JSName`")
- }
+ 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 (jsInterop.isJSBracketAccess(sym)) {
- if (enclosingOwner is OwnerKind.JSNonNative) {
- reporter.error(tree.pos,
- "@JSBracketAccess is not allowed in non-native JS classes")
- } else {
- val paramCount = sym.paramss.map(_.size).sum
- if (paramCount != 1 && paramCount != 2) {
+ 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 methods must have one or two parameters")
- } else if (paramCount == 2 &&
- sym.tpe.finalResultType.typeSymbol != UnitClass) {
+ "@JSBracketAccess is not allowed in non-native JS classes")
+
+ case JSCallingConvention.BracketCall =>
reporter.error(tree.pos,
- "@JSBracketAccess methods with two parameters must return Unit")
- }
+ "@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,
- "@JSBracketAccess methods may not have repeated parameters")
+ reporter.error(param.pos, s"$subject may not have repeated parameters")
} else if (param.isParamWithDefault) {
- reporter.error(param.pos,
- "@JSBracketAccess methods may not have default parameters")
+ reporter.error(param.pos, s"$subject may not have default parameters")
}
}
}
- }
- if (jsInterop.isJSBracketCall(sym)) {
- if (enclosingOwner is OwnerKind.JSNonNative) {
- reporter.error(tree.pos,
- "@JSBracketCall is not allowed in non-native JS classes")
- } else {
- // 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")
- }
+ 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")
+ }
}
}
@@ -887,24 +1137,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
reporter.error(tree.pos, "Methods in a js.Any may not be @native")
}
- if (sym.hasAnnotation(JSGlobalAnnotation)) {
- reporter.error(tree.pos,
- "Methods and fields cannot be annotated with @JSGlobal.")
- } else if (sym.hasAnnotation(JSImportAnnotation)) {
- reporter.error(tree.pos,
- "Methods and fields cannot be annotated with @JSImport.")
- }
-
- if (shouldCheckLiterals)
- checkJSNameArgument(tree)
-
- // Check that there is at most one @JSName annotation.
- val allJSNameAnnots = sym.annotations.filter(_.symbol == JSNameAnnotation)
- for (duplicate <- allJSNameAnnots.drop(1)) { // does not throw if empty
- reporter.error(duplicate.pos,
- "A member can only have a single @JSName annotation.")
- }
-
/* In native JS types, there should not be any private member, except
* private[this] constructors.
*/
@@ -976,25 +1208,23 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
}
}
- /* Check that the right-hand-side is `js.undefined`.
- *
- * On 2.12+, fields are created later than this phase, and getters
- * still hold the right-hand-side that we need to check (we
- * identify this case with `sym.accessed == NoSymbol`).
- * On 2.11 and before, however, the getter has already been
- * rewritten to read the field, so we must not check it.
- * In either case, setters must not be checked.
- */
- if (!sym.isAccessor || (sym.isGetter && sym.accessed == NoSymbol)) {
+ // 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 _ =>
- 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.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`.")
+ }
}
}
}
@@ -1004,14 +1234,11 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
if (sym.isPrimaryConstructor || sym.isValueParameter ||
sym.isParamWithDefault || sym.isAccessor ||
sym.isParamAccessor || sym.isDeferred || sym.isSynthetic ||
- AllJSFunctionClasses.contains(sym.owner) ||
(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), js.Functions and js.ThisFunctions (they need abstract
- * methods for SAM treatment), and any member of a non-native JS
- * class/trait.
+ * 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.
@@ -1027,102 +1254,166 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
"extending js.Any may only call the primary constructor")
}
} else {
- // Check that the tree's body is either empty or calls js.native
- tree.rhs match {
- case sel: Select if sel.symbol == JSPackage_native =>
- case _ =>
- val pos = if (tree.rhs != EmptyTree) tree.rhs.pos else tree.pos
- reporter.error(pos,
- "Concrete members of JS native types may only call js.native.")
- }
-
- if (sym.tpe.resultType.typeSymbol == NothingClass &&
- tree.tpt.asInstanceOf[TypeTree].original == null) {
- // Warn if resultType is Nothing and not ascribed
- val name = sym.name.decoded.trim
- reporter.warning(tree.pos, s"The type of $name got inferred " +
- "as Nothing. To suppress this warning, explicitly ascribe " +
- "the type.")
- }
+ // Check that the tree's rhs is exactly `= js.native`
+ checkRHSCallsJSNative(tree, "Concrete members of JS native types")
}
super.transform(tree)
}
- private def checkJSAnySpecificAnnotsOnNonJSAny(implDef: ImplDef): Unit = {
- val sym = implDef.symbol
+ 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.")
+ }
- if (sym.hasAnnotation(JSNativeAnnotation)) {
- reporter.error(implDef.pos,
- "Classes, traits and objects not extending js.Any may not have an " +
- "@js.native annotation")
- } else {
- checkJSNativeSpecificAnnotsOnNonJSNative(implDef)
+ // 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(
- implDef: ImplDef): Unit = {
- val sym = implDef.symbol
-
- val allowJSName = {
- sym.isModuleOrModuleClass &&
- (enclosingOwner is OwnerKind.JSNonNative) &&
- shouldModuleBeExposed(sym)
- }
+ memberDef: MemberDef): Unit = {
+ val sym = memberDef.symbol
for (annot <- sym.annotations) {
- if (annot.symbol == JSNameAnnotation && !allowJSName) {
- reporter.error(annot.pos,
- "Non JS-native classes, traits and objects may not have an " +
- "@JSName annotation.")
- } else if (annot.symbol == JSGlobalAnnotation) {
- reporter.error(annot.pos,
- "Non JS-native classes, traits and objects may not have an " +
- "@JSGlobal annotation.")
- } else if (annot.symbol == JSImportAnnotation) {
- reporter.error(annot.pos,
- "Non JS-native classes, traits and objects may not have an " +
- "@JSImport annotation.")
- } else if (annot.symbol == JSGlobalScopeAnnotation) {
- reporter.error(annot.pos,
- "Only native JS objects can have an @JSGlobalScope annotation.")
+ 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(member: MemberDef): Unit = {
- for (annot <- member.symbol.getAnnotation(JSNameAnnotation)) {
- val argTree = annot.args.head
- if (argTree.tpe.typeSymbol == StringClass) {
- if (!argTree.isInstanceOf[Literal]) {
- 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 == member.symbol.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")
- }
+ 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.tpe.typeSymbol isSubClass JSAnyClass
+ sym.isSubClass(JSAnyClass)
/** Checks that a setter has the right signature.
*
@@ -1151,29 +1442,19 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
}
}
- private def isJSAny(implDef: ImplDef): Boolean = isJSAny(implDef.symbol)
-
- private def isJSLambda(sym: Symbol) = sym.isAnonymousClass &&
- AllJSFunctionClasses.exists(sym.tpe.typeSymbol isSubClass _)
-
- private def isScalaEnum(implDef: ImplDef) =
- implDef.symbol.tpe.typeSymbol isSubClass ScalaEnumClass
-
/** 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 `@JSGlobal` on [[sym]] is a literal.
+ /** Checks that the optional argument to an `@JSGlobal` annotation is a
+ * literal.
*
- * Reports an error on each annotation where this is not the case.
+ * Reports an error on the annotation if it is not the case.
*/
- private def checkJSGlobalLiteral(sym: Symbol): Unit = {
- for {
- annot <- sym.getAnnotation(JSGlobalAnnotation)
- if annot.args.nonEmpty
- } {
+ private def checkJSGlobalLiteral(annot: AnnotationInfo): Unit = {
+ if (annot.args.nonEmpty) {
assert(annot.args.size == 1,
s"@JSGlobal annotation $annot has more than 1 argument")
@@ -1185,42 +1466,40 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
}
}
- /** Checks that arguments to `@JSImport` on [[sym]] are literals.
+ /** Checks that arguments to an `@JSImport` annotation are literals.
*
* The second argument can also be the singleton `JSImport.Namespace`
* object.
*
- * Reports an error on each annotation where this is not the case.
+ * Reports an error on the annotation if it is not the case.
*/
- private def checkJSImportLiteral(sym: Symbol): Unit = {
- for {
- annot <- sym.getAnnotation(JSImportAnnotation)
- } {
- assert(annot.args.size == 2 || annot.args.size == 3,
- s"@JSImport annotation $annot does not have exactly 2 or 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.")
- }
+ 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 = {
- 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 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 = annot.args.size < 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.")
- }
+ 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.")
}
}
@@ -1315,18 +1594,23 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
private def checkAndGetJSNativeLoadingSpecAnnotOf(
pos: Position, sym: Symbol): Option[Annotation] = {
+
for (annot <- sym.getAnnotation(JSNameAnnotation)) {
- reporter.error(annot.pos, "@JSName annotations are not allowed on top " +
- "level classes or objects (or classes and objects inside Scala objects).")
+ 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 =
- "Native JS classes and objects must have exactly one " +
- "annotation among @JSGlobal, @JSImport and @JSGlobalScope."
+ 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 =>
@@ -1351,25 +1635,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
private lazy val ScalaEnumClass = getRequiredClass("scala.Enumeration")
- /** checks if the primary constructor of the ClassDef `cldef` does not
- * take any arguments
- */
- private def primCtorNoArg(cldef: ClassDef) =
- getPrimCtor(cldef.symbol.tpe).forall(_.paramss == List(List()))
-
- /** 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
- }
-
- private def shouldModuleBeExposed(sym: Symbol) = {
- assert(sym.isModuleOrModuleClass, sym)
- !sym.isLocalToBlock && !sym.isSynthetic && !isPrivateMaybeWithin(sym)
- }
-
private def wasPublicBeforeTyper(sym: Symbol): Boolean =
sym.hasAnnotation(WasPublicBeforeTyperClass)
@@ -1390,7 +1655,7 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
}
}
- private def checkInternalAnnotations(tree: Tree): Unit = {
+ 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`).
*/
@@ -1400,16 +1665,18 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G)
annotation.symbol == JSOptionalAnnotation
}
- if (tree.isInstanceOf[MemberDef]) {
- for (annotation <- tree.symbol.annotations) {
- if (isCompilerAnnotation(annotation)) {
- reporter.error(annotation.pos,
- s"$annotation is for compiler internal use only. " +
- "Do not use it yourself.")
- }
+ 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 {
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSOptions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSOptions.scala
index ba0efb2f6f..50cc0bf1c8 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSOptions.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSOptions.scala
@@ -26,10 +26,23 @@ trait ScalaJSOptions {
* 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 {
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala
index 4bf38838ef..a67adbb948 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala
@@ -40,8 +40,8 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin {
}
}
- /** Called when the JS ASTs are generated. Override for testing */
- def generatedJSAST(clDefs: List[Trees.ClassDef]): Unit = {}
+ /** 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.
@@ -55,28 +55,19 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin {
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
}
- /** Checks and registers module exports on the symbol.
- *
- * This bridge allows other plugins to register new modules for export
- * between jsinterop and jscode phases. It is meant to be accessed using
- * reflection. The calling code still must insert the `@JSExport` annotation
- * to the module.
- */
- @deprecated("Might be removed at any time, use at your own risk.", "0.6.24")
- def registerModuleExports(sym: Symbol): Unit =
- PrepInteropComponent.registerClassOrModuleExports(sym)
-
object PreTyperComponentComponent extends PreTyperComponent(global) {
val runsAfter = List("parser")
override val runsBefore = List("namer")
@@ -86,7 +77,7 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin {
val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons
val scalaJSOpts = ScalaJSPlugin.this.scalaJSOpts
override val runsAfter = List("typer")
- override val runsBefore = List("pickle")
+ override val runsBefore = List("pickler")
}
object ExplicitInnerJSComponent extends ExplicitInnerJS[global.type](global) {
@@ -107,18 +98,21 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin {
override val runsAfter = List("mixin")
override val runsBefore = List("delambdafy", "cleanup", "terminal")
- def generatedJSAST(clDefs: List[Trees.ClassDef]): Unit =
- ScalaJSPlugin.this.generatedJSAST(clDefs)
+ def generatedJSAST(clDef: Trees.ClassDef): Unit =
+ ScalaJSPlugin.this.generatedJSAST(clDef)
}
- override def processOptions(options: List[String],
- error: String => Unit): Unit = {
+ 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("->")
@@ -161,6 +155,8 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin {
"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"""
@@ -168,7 +164,11 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin {
| Change the location the source URIs in the emitted IR point to
| - strips away the prefix FROM_URI (if it matches)
| - optionally prefixes the TO_URI, where stripping has been performed
- | - any number of occurences are allowed. Processing is done on a first match basis.
+ | - 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.
diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala
index 993f9393e5..eae31fdb14 100644
--- a/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala
+++ b/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala
@@ -14,8 +14,7 @@ package org.scalajs.nscplugin
import scala.tools.nsc._
-import org.scalajs.ir
-import ir.{Definitions, Types}
+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 {
@@ -26,7 +25,7 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent {
private lazy val primitiveIRTypeMap: Map[Symbol, Types.Type] = {
Map(
- UnitClass -> Types.NoType,
+ UnitClass -> Types.VoidType,
BooleanClass -> Types.BooleanType,
CharClass -> Types.CharType,
ByteClass -> Types.ByteType,
@@ -40,19 +39,19 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent {
)
}
- private lazy val primitiveClassRefNameMap: Map[Symbol, String] = {
+ private lazy val primitiveRefMap: Map[Symbol, Types.NonArrayTypeRef] = {
Map(
- UnitClass -> Definitions.VoidClass,
- BooleanClass -> Definitions.BooleanClass,
- CharClass -> Definitions.CharClass,
- ByteClass -> Definitions.ByteClass,
- ShortClass -> Definitions.ShortClass,
- IntClass -> Definitions.IntClass,
- LongClass -> Definitions.LongClass,
- FloatClass -> Definitions.FloatClass,
- DoubleClass -> Definitions.DoubleClass,
- NothingClass -> encodeClassFullName(RuntimeNothingClass),
- NullClass -> encodeClassFullName(RuntimeNullClass)
+ 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))
)
}
@@ -61,22 +60,22 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent {
if (arrayDepth == 0)
primitiveIRTypeMap.getOrElse(base, encodeClassType(base))
else
- Types.ArrayType(makeArrayTypeRef(base, arrayDepth))
+ Types.ArrayType(makeArrayTypeRef(base, arrayDepth), nullable = true)
}
def toTypeRef(t: Type): Types.TypeRef = {
val (base, arrayDepth) = convert(t)
if (arrayDepth == 0)
- Types.ClassRef(makeClassRefName(base))
+ makeNonArrayTypeRef(base)
else
makeArrayTypeRef(base, arrayDepth)
}
- private def makeClassRefName(sym: Symbol): String =
- primitiveClassRefNameMap.getOrElse(sym, encodeClassFullName(sym))
+ 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(makeClassRefName(base), depth)
+ Types.ArrayTypeRef(makeNonArrayTypeRef(base), depth)
// The following code was modeled after backend.icode.TypeKinds.toTypeKind
@@ -94,12 +93,12 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent {
*/
private def convert(t: Type): (Symbol, Int) = t.normalize match {
case ThisType(ArrayClass) => (ObjectClass, 0)
- case ThisType(sym) => (convertBase(sym), 0)
- case SingleType(_, sym) => (convertBase(sym), 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) => (convertBase(sym), 0)
+ 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
@@ -115,7 +114,7 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent {
* 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 => (convertBase(tpe.valueClazz), 0)
+ case tpe: ErasedValueType => (tpe.valueClazz, 0)
// For sure WildcardTypes shouldn't reach here either, but when
// debugging such situations this may come in handy.
@@ -134,23 +133,9 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent {
val convertedArg = convert(targs.head)
(convertedArg._1, convertedArg._2 + 1)
case _ if sym.isClass =>
- (convertBase(sym), 0)
+ (sym, 0)
case _ =>
assert(sym.isType, sym) // it must be compiling Array[a]
(ObjectClass, 0)
}
-
- /** Convert a class ref, definitely not an array type. */
- private def convertBase(sym: Symbol): Symbol = {
- if (isImplClass(sym)) {
- // 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)
- traitSym
- else
- sym
- } else {
- sym
- }
- }
}
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
index 66706b21d8..9ed869cbcc 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala
@@ -14,6 +14,7 @@ package org.scalajs.nscplugin.test
import org.scalajs.nscplugin.test.util._
import org.junit.Test
+import org.junit.Assume._
// scalastyle:off line.size.limit
@@ -23,8 +24,13 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
"""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 = {
+ def noIsInstanceOnJS(): Unit = {
"""
@js.native
@@ -44,7 +50,7 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
}
@Test
- def jsConstructorOfErrors: Unit = {
+ def jsConstructorOfErrors(): Unit = {
"""
class ScalaClass
@@ -69,6 +75,10 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
| ^
"""
+ val singletonPrefix =
+ if (allowsSingletonClassOf) "non-trait "
+ else ""
+
"""
@js.native @JSGlobal class NativeJSClass extends js.Object
@js.native trait NativeJSTrait extends js.Object
@@ -95,11 +105,11 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
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: class type required but NativeJSObject.type found
+ |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
@@ -111,7 +121,7 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
|newSource1.scala:18: error: non-trait class type required but JSTrait found
| val e = js.constructorOf[JSTrait]
| ^
- |newSource1.scala:19: error: class type required but JSObject.type found
+ |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
@@ -131,7 +141,7 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
}
@Test
- def jsConstructorTagErrors: Unit = {
+ def jsConstructorTagErrors(): Unit = {
"""
class ScalaClass
@@ -156,6 +166,10 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
| ^
"""
+ val singletonPrefix =
+ if (allowsSingletonClassOf) "non-trait "
+ else ""
+
"""
@js.native @JSGlobal class NativeJSClass extends js.Object
@js.native trait NativeJSTrait extends js.Object
@@ -182,11 +196,11 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
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: class type required but NativeJSObject.type found
+ |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
@@ -198,7 +212,7 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
|newSource1.scala:18: error: non-trait class type required but JSTrait found
| val e = js.constructorTag[JSTrait]
| ^
- |newSource1.scala:19: error: class type required but JSObject.type found
+ |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
@@ -218,7 +232,8 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
}
@Test
- def runtimeConstructorOfErrors: Unit = {
+ def runtimeConstructorOfErrorsDisallowedSingletonTypes(): Unit = {
+ assumeTrue(!allowsSingletonClassOf)
"""
import scala.scalajs.runtime
@@ -245,6 +260,42 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
| ^
"""
+ }
+
+ @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
@@ -299,4 +350,24 @@ class DiverseErrorsTest extends DirectTest with TestHelpers {
}
+ @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
index 25bce41771..f930632797 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/EnumerationInteropTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/EnumerationInteropTest.scala
@@ -19,7 +19,7 @@ import org.junit.Test
class EnumerationInteropTest extends DirectTest with TestHelpers {
@Test
- def warnIfUnableToTransformValue: Unit = {
+ def warnIfUnableToTransformValue(): Unit = {
"""
class A extends Enumeration {
@@ -49,7 +49,7 @@ class EnumerationInteropTest extends DirectTest with TestHelpers {
}
@Test
- def warnIfNoNameVal: Unit = {
+ def warnIfNoNameVal(): Unit = {
"""
class A extends Enumeration {
@@ -73,7 +73,7 @@ class EnumerationInteropTest extends DirectTest with TestHelpers {
}
@Test
- def warnIfNullValue: Unit = {
+ def warnIfNullValue(): Unit = {
"""
class A extends Enumeration {
@@ -97,7 +97,7 @@ class EnumerationInteropTest extends DirectTest with TestHelpers {
}
@Test
- def warnIfNullNewVal: Unit = {
+ def warnIfNullNewVal(): Unit = {
"""
class A extends Enumeration {
@@ -121,7 +121,7 @@ class EnumerationInteropTest extends DirectTest with TestHelpers {
}
@Test
- def warnIfExtNoNameVal: Unit = {
+ def warnIfExtNoNameVal(): Unit = {
"""
class A extends Enumeration {
@@ -145,7 +145,7 @@ class EnumerationInteropTest extends DirectTest with TestHelpers {
}
@Test
- def warnIfExtNullNameVal: Unit = {
+ def warnIfExtNullNameVal(): Unit = {
"""
class A extends Enumeration {
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
index c677a67434..7e553d378f 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/InternalAnnotationsTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/InternalAnnotationsTest.scala
@@ -24,17 +24,17 @@ class InternalAnnotationsTest extends DirectTest with TestHelpers {
"import scala.scalajs.js, js.annotation._, js.annotation.internal._"
@Test
- def exposedJSMember: Unit = {
+ def exposedJSMember(): Unit = {
test("ExposedJSMember")
}
@Test
- def jsType: Unit = {
+ def jsType(): Unit = {
test("JSType")
}
@Test
- def jsOptional: Unit = {
+ def jsOptional(): Unit = {
test("JSOptional")
}
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
index f06bde55b8..df493ce0ff 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSDynamicLiteralTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSDynamicLiteralTest.scala
@@ -24,7 +24,7 @@ class JSDynamicLiteralTest extends DirectTest with TestHelpers {
"""
@Test
- def callApplyOnly: Unit = {
+ def callApplyOnly(): Unit = {
// selectDynamic (with any name)
expr"""
@@ -61,7 +61,7 @@ class JSDynamicLiteralTest extends DirectTest with TestHelpers {
}
@Test
- def goodTypesOnly: Unit = {
+ def goodTypesOnly(): Unit = {
// Bad value type (applyDynamic)
"""
@@ -113,7 +113,7 @@ class JSDynamicLiteralTest extends DirectTest with TestHelpers {
}
@Test
- def noNonLiteralMethodName: Unit = {
+ def noNonLiteralMethodName(): Unit = {
// applyDynamicNamed
"""
@@ -144,7 +144,7 @@ class JSDynamicLiteralTest extends DirectTest with TestHelpers {
}
@Test
- def keyDuplicationWarning: Unit = {
+ def keyDuplicationWarning(): Unit = {
// detects duplicate named keys
expr"""
lit(a = "1", b = "2", a = "3")
@@ -255,7 +255,7 @@ class JSDynamicLiteralTest extends DirectTest with TestHelpers {
expr"""
val a = "x"
lit("a" -> "1", a -> "2", a -> "3")
- """.hasNoWarns
+ """.hasNoWarns()
// should not warn if the key/value pairs are not literal
"""
@@ -263,7 +263,7 @@ class JSDynamicLiteralTest extends DirectTest with TestHelpers {
val tup = "x" -> lit()
def foo = lit(tup, tup)
}
- """.hasNoWarns
+ """.hasNoWarns()
// should warn only for the literal keys when in
// the presence of non literal keys
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportASTTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportASTTest.scala
index 628d7559eb..01fe141a4a 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportASTTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportASTTest.scala
@@ -22,7 +22,7 @@ import org.scalajs.ir.{Trees => js}
class JSExportASTTest extends JSASTTest {
@Test
- def inheritExportMethods: Unit = {
+ def inheritExportMethods(): Unit = {
"""
import scala.scalajs.js.annotation.JSExport
@@ -36,7 +36,7 @@ class JSExportASTTest extends JSASTTest {
override def foo = 2
}
""".hasExactly(1, "definitions of property `foo`") {
- case js.PropertyDef(_, js.StringLiteral("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
index 1fe4def542..234d3a1bb6 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala
@@ -13,9 +13,10 @@
package org.scalajs.nscplugin.test
import org.scalajs.nscplugin.test.util._
-import org.junit.Test
+import org.scalajs.nscplugin.test.util.VersionDependentUtils.methodSig
import org.junit.Assume._
+import org.junit.Test
// scalastyle:off line.size.limit
@@ -29,7 +30,89 @@ class JSExportTest extends DirectTest with TestHelpers {
"""
@Test
- def noJSExportClass: Unit = {
+ 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
@@ -38,17 +121,17 @@ class JSExportTest extends DirectTest with TestHelpers {
class B
""" hasErrors
"""
- |newSource1.scala:3: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
+ |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 objects and classes. Use @JSExportTopLevel instead.
+ |newSource1.scala:6: error: @JSExport is forbidden on top-level objects and classes. Use @JSExportTopLevel instead.
| @JSExport("Foo")
| ^
"""
}
@Test
- def noJSExportObject: Unit = {
+ def noJSExportObject(): Unit = {
"""
@JSExport
object A
@@ -57,17 +140,17 @@ class JSExportTest extends DirectTest with TestHelpers {
object B
""" hasErrors
"""
- |newSource1.scala:3: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
+ |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 objects and classes. Use @JSExportTopLevel instead.
+ |newSource1.scala:6: error: @JSExport is forbidden on top-level objects and classes. Use @JSExportTopLevel instead.
| @JSExport("Foo")
| ^
"""
}
@Test
- def noDoubleUnderscoreExport: Unit = {
+ def noDoubleUnderscoreExport(): Unit = {
"""
class A {
@JSExport(name = "__")
@@ -88,7 +171,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def doubleUnderscoreOKInTopLevelExport: Unit = {
+ def doubleUnderscoreOKInTopLevelExport(): Unit = {
"""
@JSExportTopLevel("__A")
class A
@@ -103,11 +186,11 @@ class JSExportTest extends DirectTest with TestHelpers {
@JSExportTopLevel("__d")
val d: Boolean = true
}
- """.hasNoWarns
+ """.hasNoWarns()
}
@Test
- def noConflictingExport: Unit = {
+ def noConflictingExport(): Unit = {
"""
class Confl {
@JSExport("value")
@@ -136,11 +219,11 @@ class JSExportTest extends DirectTest with TestHelpers {
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: (x: Confl#Box)Object
+ |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
| ^
"""
@@ -154,10 +237,10 @@ class JSExportTest extends DirectTest with TestHelpers {
def rtType(x: js.Dynamic) = x
}
""" hasErrors
- """
+ s"""
|newSource1.scala:7: error: Cannot disambiguate overloads for exported method rtType with types
- | (x: scala.scalajs.js.Any)Object
- | (x: scala.scalajs.js.Dynamic)Object
+ | ${methodSig("(x: scala.scalajs.js.Any)", "Object")}
+ | ${methodSig("(x: scala.scalajs.js.Dynamic)", "Object")}
| @JSExport
| ^
"""
@@ -171,10 +254,10 @@ class JSExportTest extends DirectTest with TestHelpers {
def foo(x: Int*) = x
}
""" hasErrors
- """
+ s"""
|newSource1.scala:7: error: Cannot disambiguate overloads for exported method foo with types
- | (x: Int, ys: Seq)Object
- | (x: Seq)Object
+ | ${methodSig("(x: Int, ys: Seq)", "Object")}
+ | ${methodSig("(x: Seq)", "Object")}
| @JSExport
| ^
"""
@@ -187,10 +270,10 @@ class JSExportTest extends DirectTest with TestHelpers {
def foo(x: String*) = x
}
""" hasErrors
- """
+ s"""
|newSource1.scala:6: error: Cannot disambiguate overloads for exported method foo with types
- | (x: Int)Object
- | (x: Seq)Object
+ | ${methodSig("(x: Int)", "Object")}
+ | ${methodSig("(x: Seq)", "Object")}
| @JSExport
| ^
"""
@@ -203,10 +286,10 @@ class JSExportTest extends DirectTest with TestHelpers {
def foo(x: Double, y: String)(z: String*) = x
}
""" hasErrors
- """
+ s"""
|newSource1.scala:6: error: Cannot disambiguate overloads for exported method foo with types
- | (x: Double, y: String, z: Int)Object
- | (x: Double, y: String, z: Seq)Object
+ | ${methodSig("(x: Double, y: String, z: Int)", "Object")}
+ | ${methodSig("(x: Double, y: String, z: Seq)", "Object")}
| @JSExport
| ^
"""
@@ -220,10 +303,10 @@ class JSExportTest extends DirectTest with TestHelpers {
def a(x: Any) = 2
}
""" hasErrors
- """
+ s"""
|newSource1.scala:7: error: Cannot disambiguate overloads for exported method a with types
- | (x: Object)Object
- | (x: scala.scalajs.js.Any)Object
+ | ${methodSig("(x: Object)", "Object")}
+ | ${methodSig("(x: scala.scalajs.js.Any)", "Object")}
| @JSExport
| ^
"""
@@ -231,7 +314,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportLocal: Unit = {
+ def noExportLocal(): Unit = {
// Local class
"""
class A {
@@ -245,10 +328,10 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:5: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
+ |newSource1.scala:5: error: You may not export constructors of local classes
| @JSExport
| ^
- |newSource1.scala:8: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
+ |newSource1.scala:8: error: You may not export a local definition
| @JSExport
| ^
"""
@@ -266,10 +349,10 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:5: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
+ |newSource1.scala:5: error: You may not export a local definition
| @JSExport
| ^
- |newSource1.scala:8: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
+ |newSource1.scala:8: error: You may not export a local definition
| @JSExport
| ^
"""
@@ -322,7 +405,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noMiddleVarArg: Unit = {
+ def noMiddleVarArg(): Unit = {
"""
class A {
@@ -331,7 +414,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:4: error: In an exported method, a *-parameter must come last (through all parameter lists)
+ |newSource1.scala:4: error: In an exported method or constructor, a *-parameter must come last (through all parameter lists)
| @JSExport
| ^
"""
@@ -339,7 +422,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noMiddleDefaultParam: Unit = {
+ def noMiddleDefaultParam(): Unit = {
"""
class A {
@@ -348,7 +431,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:4: error: In an exported method, all parameters with defaults must be at the end
+ |newSource1.scala:4: error: In an exported method or constructor, all parameters with defaults must be at the end
| @JSExport
| ^
"""
@@ -356,7 +439,30 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportAbstractClass: Unit = {
+ 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")
@@ -366,9 +472,6 @@ class JSExportTest extends DirectTest with TestHelpers {
@JSExportTopLevel("B")
def this() = this(5)
}
-
- @JSExportTopLevel("C")
- abstract class C extends js.Object
""" hasErrors
"""
|newSource1.scala:3: error: You may not export an abstract class
@@ -377,15 +480,12 @@ class JSExportTest extends DirectTest with TestHelpers {
|newSource1.scala:7: error: You may not export an abstract class
| @JSExportTopLevel("B")
| ^
- |newSource1.scala:11: error: You may not export an abstract class
- | @JSExportTopLevel("C")
- | ^
"""
}
@Test
- def noJSExportOnTrait: Unit = {
+ def noJSExportOnTrait(): Unit = {
"""
@JSExport
@@ -410,10 +510,28 @@ class JSExportTest extends DirectTest with TestHelpers {
| ^
"""
+ """
+ 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 = {
+ def noExportNonPublicClassOrObject(): Unit = {
"""
@JSExportTopLevel("A")
@@ -427,20 +545,28 @@ class JSExportTest extends DirectTest with TestHelpers {
@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 public and protected classes
+ |newSource1.scala:3: error: You may only export constructors of public and protected classes
| @JSExportTopLevel("A")
| ^
- |newSource1.scala:6: error: You may only export public and protected classes
+ |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 classes
+ |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 classes
+ |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")
+ | ^
"""
"""
@@ -457,16 +583,16 @@ class JSExportTest extends DirectTest with TestHelpers {
protected[this] object D extends js.Object
""" hasErrors
"""
- |newSource1.scala:3: error: You may only export public and protected objects
+ |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 objects
+ |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 objects
+ |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 objects
+ |newSource1.scala:12: error: You may only export public and protected definitions
| @JSExportTopLevel("D")
| ^
"""
@@ -474,7 +600,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportNonPublicMember: Unit = {
+ def noExportNonPublicMember(): Unit = {
"""
class A {
@@ -486,10 +612,10 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:4: error: You may only export public and protected methods
+ |newSource1.scala:4: error: You may only export public and protected definitions
| @JSExport
| ^
- |newSource1.scala:7: error: You may only export public and protected methods
+ |newSource1.scala:7: error: You may only export public and protected definitions
| @JSExport
| ^
"""
@@ -497,89 +623,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportNestedClass: Unit = {
-
- """
- class A {
- @JSExport
- class Nested {
- @JSExport
- def this(x: Int) = this()
- }
-
- @JSExport
- class Nested2 extends js.Object
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
- | @JSExport
- | ^
- |newSource1.scala:6: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
- | @JSExport
- | ^
- |newSource1.scala:10: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noNestedExportClass: Unit = {
-
- """
- object A {
- @JSExport
- class Nested {
- @JSExport
- def this(x: Int) = this
- }
-
- @JSExport
- class Nested2 extends js.Object
- }
- """ hasErrors
- """
-
- |newSource1.scala:4: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
- | @JSExport
- | ^
- |newSource1.scala:6: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
- | @JSExport
- | ^
- |newSource1.scala:10: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noNestedExportObject: Unit = {
-
- """
- object A {
- @JSExport
- object Nested
-
- @JSExport
- object Nested2 extends js.Object
- }
- """ hasErrors
- """
- |newSource1.scala:4: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
- | @JSExport
- | ^
- |newSource1.scala:7: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead.
- | @JSExport
- | ^
- """
-
- }
-
- @Test
- def noExportTopLevelNestedObject: Unit = {
+ def noExportTopLevelNestedObject(): Unit = {
"""
class A {
@@ -591,10 +635,10 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:4: error: You may not export a nested object
+ |newSource1.scala:4: error: Only static objects may export their members to the top level
| @JSExportTopLevel("Nested")
| ^
- |newSource1.scala:7: error: You may not export a nested object
+ |newSource1.scala:7: error: Only static objects may export their members to the top level
| @JSExportTopLevel("Nested2")
| ^
"""
@@ -602,7 +646,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportJSNative: Unit = {
+ def noExportJSNative(): Unit = {
"""
import scala.scalajs.js
@@ -613,7 +657,7 @@ class JSExportTest extends DirectTest with TestHelpers {
object A extends js.Object
""" hasErrors
"""
- |newSource1.scala:5: error: You may not export a native JS object
+ |newSource1.scala:5: error: You may not export a native JS definition
| @JSExportTopLevel("A")
| ^
"""
@@ -643,18 +687,33 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:5: error: You may not export a native JS class
+ |newSource1.scala:5: error: You may not export a native JS definition
| @JSExportTopLevel("A")
| ^
- |newSource1.scala:9: error: You may not export a constructor of a subclass of js.Any
+ |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 = {
+ def noExportJSMember(): Unit = {
"""
import scala.scalajs.js
@@ -667,7 +726,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:8: error: You may not export a method of a subclass of js.Any
+ |newSource1.scala:8: error: You may not export a member of a subclass of js.Any
| @JSExport
| ^
"""
@@ -681,7 +740,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:6: error: You may not export a method of a subclass of js.Any
+ |newSource1.scala:6: error: You may not export a member of a subclass of js.Any
| @JSExport
| ^
"""
@@ -689,7 +748,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noBadSetterType: Unit = {
+ def noBadSetterType(): Unit = {
// Bad param list
"""
@@ -746,7 +805,44 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noBadToStringExport: Unit = {
+ 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 {
@@ -763,7 +859,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noBadNameExportAll: Unit = {
+ def noBadNameExportAll(): Unit = {
"""
@JSExportAll
@@ -784,7 +880,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noConflictingMethodAndProperty: Unit = {
+ def noConflictingMethodAndProperty(): Unit = {
// Basic case
"""
@@ -829,7 +925,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def gracefulDoubleDefaultFail: Unit = {
+ 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
@@ -849,7 +945,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noNonLiteralExportNames: Unit = {
+ def noNonLiteralExportNames(): Unit = {
"""
object A {
@@ -867,13 +963,37 @@ class JSExportTest extends DirectTest with TestHelpers {
"""
|newSource1.scala:9: error: The argument to JSExport must be a literal string
| @JSExport(A.a)
- | ^
+ | ^
"""
}
@Test
- def noExportImplicitApply: Unit = {
+ 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 {
@@ -918,28 +1038,44 @@ class JSExportTest extends DirectTest with TestHelpers {
@JSExport("apply")
def apply(): Int = 1
}
- """.hasNoWarns
+ """.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 = {
+ def exportObjectAsToString(): Unit = {
"""
@JSExportTopLevel("toString")
object ExportAsToString
- """.succeeds
+ """.succeeds()
}
- private def since(v: String): String = {
- val version = scala.util.Properties.versionNumberString
- if (version.startsWith("2.11.")) ""
- else s" (since $v)"
- }
-
@Test
- def noExportTopLevelTrait: Unit = {
+ def noExportTopLevelTrait(): Unit = {
"""
@JSExportTopLevel("foo")
trait A
@@ -976,7 +1112,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportTopLevelLazyVal: Unit = {
+ def noExportTopLevelLazyVal(): Unit = {
"""
object A {
@JSExportTopLevel("foo")
@@ -991,7 +1127,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportTopLevelInvalidJSIdentifier: Unit = {
+ def noExportTopLevelInvalidJSIdentifier(): Unit = {
"""
@JSExportTopLevel("not-a-valid-JS-identifier-1")
object A
@@ -1014,29 +1150,29 @@ class JSExportTest extends DirectTest with TestHelpers {
object D
""" hasErrors
"""
- |newSource1.scala:3: error: The top-level export name must be a valid JavaScript identifier
+ |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
+ |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
+ |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
+ |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
+ |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
+ |newSource1.scala:20: error: The top-level export name must be a valid JavaScript identifier name
| @JSExportTopLevel("")
| ^
"""
}
@Test
- def noExportTopLevelNamespaced: Unit = {
+ def noExportTopLevelNamespaced(): Unit = {
"""
@JSExportTopLevel("namespaced.export1")
object A
@@ -1052,26 +1188,26 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:3: error: The top-level export name must be a valid JavaScript identifier
+ |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
+ |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
+ |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
+ |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
+ |newSource1.scala:12: error: The top-level export name must be a valid JavaScript identifier name
| @JSExportTopLevel("namespaced.export5")
| ^
"""
}
@Test
- def noExportTopLevelGetter: Unit = {
+ def noExportTopLevelGetter(): Unit = {
"""
object A {
@JSExportTopLevel("foo")
@@ -1086,7 +1222,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportTopLevelSetter: Unit = {
+ def noExportTopLevelSetter(): Unit = {
"""
object A {
@JSExportTopLevel("foo")
@@ -1101,7 +1237,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportTopLevelFieldsWithSameName: Unit = {
+ def noExportTopLevelFieldsWithSameName(): Unit = {
"""
object A {
@JSExportTopLevel("foo")
@@ -1112,14 +1248,14 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:5: error: Duplicate top-level export with name 'foo': a field may not share its exported name with another field or method
- | val a: Int = 1
- | ^
+ |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 = {
+ def noExportTopLevelFieldsAndMethodsWithSameName(): Unit = {
"""
object A {
@JSExportTopLevel("foo")
@@ -1130,7 +1266,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:7: error: Duplicate top-level export with name 'foo': a field may not share its exported name with another field or method
+ |newSource1.scala:4: error: export overload conflicts with export of method b: they are of different types (Field / Method)
| @JSExportTopLevel("foo")
| ^
"""
@@ -1145,14 +1281,14 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:4: error: Duplicate top-level export with name 'foo': a field may not share its exported name with another field or method
+ |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 = {
+ def noExportTopLevelNonStatic(): Unit = {
"""
class A {
@JSExportTopLevel("foo")
@@ -1186,7 +1322,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:4: error: You may not export a nested object
+ |newSource1.scala:4: error: Only static objects may export their members to the top level
| @JSExportTopLevel("Foo")
| ^
"""
@@ -1198,7 +1334,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:4: error: You may not export a nested object
+ |newSource1.scala:4: error: Only static objects may export their members to the top level
| @JSExportTopLevel("Foo")
| ^
"""
@@ -1210,7 +1346,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" 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.
+ |newSource1.scala:4: error: Only static objects may export their members to the top level
| @JSExportTopLevel("Foo")
| ^
"""
@@ -1222,14 +1358,14 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" 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.
+ |newSource1.scala:4: error: Only static objects may export their members to the top level
| @JSExportTopLevel("Foo")
| ^
"""
}
@Test
- def noExportTopLevelLocal: Unit = {
+ def noExportTopLevelLocal(): Unit = {
// Local class
"""
class A {
@@ -1243,10 +1379,10 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:5: error: You may not export a local class
+ |newSource1.scala:5: error: You may not export constructors of local classes
| @JSExportTopLevel("A")
| ^
- |newSource1.scala:8: error: You may not export a local class
+ |newSource1.scala:8: error: You may not export a local definition
| @JSExportTopLevel("B")
| ^
"""
@@ -1264,17 +1400,17 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:5: error: You may not export a local object
+ |newSource1.scala:5: error: You may not export a local definition
| @JSExportTopLevel("A")
| ^
- |newSource1.scala:8: error: You may not export a local object
+ |newSource1.scala:8: error: You may not export a local definition
| @JSExportTopLevel("B")
| ^
"""
}
@Test
- def noExportTopLevelJSModule: Unit = {
+ def noExportTopLevelJSModule(): Unit = {
"""
object A extends js.Object {
@JSExportTopLevel("foo")
@@ -1282,14 +1418,14 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:4: error: You may not export a method of a subclass of js.Any
+ |newSource1.scala:4: error: You may not export a member of a subclass of js.Any
| @JSExportTopLevel("foo")
| ^
"""
}
@Test
- def noExportStaticModule: Unit = {
+ def noExportStaticModule(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1306,7 +1442,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportStaticTrait: Unit = {
+ def noExportStaticTrait(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1316,14 +1452,14 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:6: error: You may not export a trait as static.
+ |newSource1.scala:6: error: You may not export a trait
| @JSExportStatic
| ^
"""
}
@Test
- def noExportStaticClass: Unit = {
+ def noExportStaticClass(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1356,7 +1492,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportStaticValTwice: Unit = {
+ def noExportStaticValTwice(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1374,7 +1510,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportStaticVarTwice: Unit = {
+ def noExportStaticVarTwice(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1392,7 +1528,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportStaticLazyVal: Unit = {
+ def noExportStaticLazyVal(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1409,7 +1545,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportValAsStaticAndTopLevel: Unit = {
+ def noExportValAsStaticAndTopLevel(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1427,7 +1563,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportVarAsStaticAndTopLevel: Unit = {
+ def noExportVarAsStaticAndTopLevel(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1445,7 +1581,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportSetterWithBadSetterType: Unit = {
+ def noExportSetterWithBadSetterType(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1462,7 +1598,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportStaticCollapsingMethods: Unit = {
+ def noExportStaticCollapsingMethods(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1474,17 +1610,17 @@ class JSExportTest extends DirectTest with TestHelpers {
def bar(x: Int): Int = x + 1
}
""" hasErrors
- """
+ s"""
|newSource1.scala:10: error: Cannot disambiguate overloads for exported method foo with types
- | (x: Int)Int
- | (x: Int)Int
+ | ${methodSig("(x: Int)", "Int")}
+ | ${methodSig("(x: Int)", "Int")}
| def bar(x: Int): Int = x + 1
| ^
"""
}
@Test
- def noExportStaticCollapsingGetters: Unit = {
+ def noExportStaticCollapsingGetters(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1496,15 +1632,17 @@ class JSExportTest extends DirectTest with TestHelpers {
def bar: Int = 2
}
""" hasErrors
- """
- |newSource1.scala:7: error: Duplicate static getter export with name 'foo'
- | def foo: Int = 1
+ 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 = {
+ def noExportStaticCollapsingSetters(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1516,17 +1654,17 @@ class JSExportTest extends DirectTest with TestHelpers {
def bar_=(v: Int): Unit = ()
}
""" hasErrors
- """
- |newSource1.scala:10: error: Cannot disambiguate overloads for exported method foo with types
- | (v: Int)Unit
- | (v: Int)Unit
+ 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 = {
+ def noExportStaticFieldsWithSameName(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1539,14 +1677,14 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:7: error: Duplicate static export with name 'a': a field may not share its exported name with another field or method
- | val a: Int = 1
- | ^
+ |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 = {
+ def noExportStaticFieldsAndMethodsWithSameName(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1559,8 +1697,8 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:9: error: Duplicate static export with name 'a': a field may not share its exported name with another field or method
- | @JSExportStatic("a")
+ |newSource1.scala:6: error: export overload conflicts with export of method b: they are of different types (Field / Method)
+ | @JSExportStatic
| ^
"""
@@ -1576,14 +1714,14 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:6: error: Duplicate static export with name 'a': a field may not share its exported name with another field or method
+ |newSource1.scala:6: error: export overload conflicts with export of value b: they are of different types (Method / Field)
| @JSExportStatic
| ^
"""
}
@Test
- def noExportStaticFieldsAndPropertiesWithSameName: Unit = {
+ def noExportStaticFieldsAndPropertiesWithSameName(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1596,8 +1734,8 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:9: error: Duplicate static export with name 'a': a field may not share its exported name with another field or method
- | @JSExportStatic("a")
+ |newSource1.scala:6: error: export overload conflicts with export of method b: they are of different types (Field / Property)
+ | @JSExportStatic
| ^
"""
@@ -1613,14 +1751,14 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:6: error: Duplicate static export with name 'a': a field may not share its exported name with another field or method
+ |newSource1.scala:6: error: export overload conflicts with export of value b: they are of different types (Property / Field)
| @JSExportStatic
| ^
"""
}
@Test
- def noExportStaticPropertiesAndMethodsWithSameName: Unit = {
+ def noExportStaticPropertiesAndMethodsWithSameName(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1633,9 +1771,9 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:7: error: Exported property a conflicts with b
- | def a: Int = 1
- | ^
+ |newSource1.scala:6: error: export overload conflicts with export of method b: they are of different types (Property / Method)
+ | @JSExportStatic
+ | ^
"""
"""
@@ -1650,14 +1788,14 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:7: error: Exported method a conflicts with b
- | def a(x: Int): Int = x + 1
- | ^
+ |newSource1.scala:6: error: export overload conflicts with export of method b: they are of different types (Method / Property)
+ | @JSExportStatic
+ | ^
"""
}
@Test
- def noExportStaticNonStatic: Unit = {
+ def noExportStaticNonStatic(): Unit = {
"""
class A {
class StaticContainer extends js.Object
@@ -1676,7 +1814,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportStaticInJSModule: Unit = {
+ def noExportStaticInJSModule(): Unit = {
"""
class StaticContainer extends js.Object
@@ -1686,7 +1824,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:6: error: You may not export a method of a subclass of js.Any
+ |newSource1.scala:6: error: You may not export a member of a subclass of js.Any
| @JSExportStatic
| ^
"""
@@ -1702,14 +1840,14 @@ class JSExportTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:8: error: You may not export a method of a subclass of js.Any
+ |newSource1.scala:8: error: You may not export a member of a subclass of js.Any
| @JSExportStatic
| ^
"""
}
@Test
- def noExportStaticIfWrongCompanionType: Unit = {
+ def noExportStaticIfWrongCompanionType(): Unit = {
"""
class StaticContainer
@@ -1756,7 +1894,7 @@ class JSExportTest extends DirectTest with TestHelpers {
}
@Test
- def noExportStaticFieldAfterStatOrNonStaticField: Unit = {
+ def noExportStaticFieldAfterStatOrNonStaticField(): Unit = {
for {
offendingDecl <- Seq(
"val a: Int = 1",
@@ -1824,6 +1962,6 @@ class JSExportTest extends DirectTest with TestHelpers {
@JSExportStatic
var c: Int = 1
}
- """.succeeds
+ """.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
index aa3a68da32..19d3f0f8d3 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala
@@ -13,14 +13,19 @@
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
@@ -39,7 +44,7 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers {
var `not-a-valid-identifier-var`: Int = js.native
def `not-a-valid-identifier-def`(): Int = js.native
- def +(that: Int): Int = js.native
+ @JSOperator def +(that: Int): Int = js.native
def apply(x: Int): Int = js.native
@@ -63,7 +68,7 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers {
}
@Test
- def canAccessLegitMembers: Unit = {
+ def canAccessLegitMembers(): Unit = {
s"""
object Main {
def main(): Unit = {
@@ -80,11 +85,11 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers {
val f = SomeGlobalScope.bracketCall("validDef")(4)
}
}
- """.hasNoWarns
+ """.hasNoWarns()
}
@Test
- def noLoadGlobalValue: Unit = {
+ def noLoadGlobalValue(): Unit = {
s"""
object Main {
def main(): Unit = {
@@ -106,7 +111,7 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers {
}
@Test
- def rejectInvalidJSIdentifiers: Unit = {
+ def rejectInvalidJSIdentifiers(): Unit = {
s"""
object Main {
def main(): Unit = {
@@ -165,7 +170,45 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers {
}
@Test
- def rejectJSOperators: Unit = {
+ 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 = {
@@ -197,7 +240,7 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers {
}
@Test
- def rejectApply: Unit = {
+ def rejectApply(): Unit = {
"""
object Main {
def main(): Unit = {
@@ -206,6 +249,9 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers {
}
""" 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)
@@ -228,7 +274,7 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers {
}
@Test
- def rejectDynamicNames: Unit = {
+ def rejectDynamicNames(): Unit = {
s"""
object Main {
def dynName: String = "foo"
@@ -289,45 +335,195 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers {
}
@Test
- def rejectArguments: Unit = {
- s"""
+ 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 = {
- val a = js.Dynamic.global.arguments
- js.Dynamic.global.arguments = null
- val b = js.Dynamic.global.arguments(5)
-
- val c = SomeGlobalScope.arguments
- SomeGlobalScope.arguments = null
- val d = SomeGlobalScope.arguments2(5)
+ 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:41: error: Selecting a field of the global scope whose name is `arguments` is not allowed.
- | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
- | val a = js.Dynamic.global.arguments
- | ^
- |newSource1.scala:42: error: Selecting a field of the global scope whose name is `arguments` is not allowed.
- | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
- | js.Dynamic.global.arguments = null
+ |newSource1.scala:44: error: Illegal assignment to global this.
+ | js.Dynamic.global.`this` = 0
| ^
- |newSource1.scala:43: error: Calling a method of the global scope whose name is `arguments` is not allowed.
- | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
- | val b = js.Dynamic.global.arguments(5)
- | ^
- |newSource1.scala:45: error: Selecting a field of the global scope whose name is `arguments` is not allowed.
- | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
- | val c = SomeGlobalScope.arguments
- | ^
- |newSource1.scala:46: error: Selecting a field of the global scope whose name is `arguments` is not allowed.
- | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
- | SomeGlobalScope.arguments = null
- | ^
- |newSource1.scala:47: error: Calling a method of the global scope whose name is `arguments` is not allowed.
- | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information.
- | val d = SomeGlobalScope.arguments2(5)
- | ^
+ |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
index 3a4ccad599..8d79f251a3 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala
@@ -35,19 +35,17 @@ class JSInteropTest extends DirectTest with TestHelpers {
"JSGlobalScope" -> "@JSGlobalScope"
)
+ private def version = scala.util.Properties.versionNumberString
+
private def ifHasNewRefChecks(msg: String): String = {
- val version = scala.util.Properties.versionNumberString
- if (version.startsWith("2.10.") ||
- version.startsWith("2.11.") ||
- version.startsWith("2.12.")) {
+ if (version.startsWith("2.12.")) {
""
} else {
msg.stripMargin.trim()
}
}
- @Test
- def warnJSPackageObjectDeprecated: Unit = {
+ @Test def warnJSPackageObjectDeprecated: Unit = {
s"""
package object jspackage extends js.Object
@@ -60,8 +58,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noJSNameAnnotOnNonJSNative: Unit = {
+ @Test def noJSNameAnnotOnNonJSNative: Unit = {
for {
obj <- Seq("class", "trait", "object")
@@ -77,11 +74,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSName(Sym.sym)
$obj B extends js.Object
""" hasErrors
- s"""
- |newSource1.scala:5: error: Non JS-native classes, traits and objects may not have an @JSName annotation.
+ """
+ |newSource1.scala:5: error: @JSName can only be used on members of JS types.
| @JSName("foo")
| ^
- |newSource1.scala:12: error: Non JS-native classes, traits and objects may not have an @JSName annotation.
+ |newSource1.scala:12: error: @JSName can only be used on members of JS types.
| @JSName(Sym.sym)
| ^
"""
@@ -101,20 +98,55 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSName(Sym.sym)
$obj B
""" hasErrors
- s"""
- |newSource1.scala:5: error: Non JS-native classes, traits and objects may not have an @JSName annotation.
+ """
+ |newSource1.scala:5: error: @JSName can only be used on members of JS types.
| @JSName("foo")
| ^
- |newSource1.scala:12: error: Non JS-native classes, traits and objects may not have an @JSName annotation.
+ |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 = {
+ @Test def okJSNameOnNestedObjects: Unit = {
"""
class A extends js.Object {
@@ -124,7 +156,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSName("bar")
object tata extends js.Object
}
- """.hasNoWarns
+ """.hasNoWarns()
"""
class A extends js.Object {
@@ -136,18 +168,17 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:6: error: Non JS-native classes, traits and objects may not have an @JSName annotation.
+ |newSource1.scala:6: error: @JSName cannot be used on private members.
| @JSName("foo")
| ^
- |newSource1.scala:9: error: Non JS-native classes, traits and objects may not have an @JSName annotation.
+ |newSource1.scala:9: error: @JSName cannot be used on private members.
| @JSName("bar")
| ^
"""
}
- @Test
- def noJSGlobalAnnotOnNonJSNative: Unit = {
+ @Test def noJSGlobalAnnotOnNonJSNative: Unit = {
for {
obj <- Seq("class", "trait", "object")
@@ -159,11 +190,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSGlobal("Foo")
$obj B extends js.Object
""" hasErrors
- s"""
- |newSource1.scala:5: error: Non JS-native classes, traits and objects may not have an @JSGlobal annotation.
+ """
+ |newSource1.scala:5: error: @JSGlobal can only be used on native JS definitions (with @js.native).
| @JSGlobal
| ^
- |newSource1.scala:8: error: Non JS-native classes, traits and objects may not have an @JSGlobal annotation.
+ |newSource1.scala:8: error: @JSGlobal can only be used on native JS definitions (with @js.native).
| @JSGlobal("Foo")
| ^
"""
@@ -179,20 +210,55 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSGlobal("Foo")
$obj B
""" hasErrors
- s"""
- |newSource1.scala:5: error: Non JS-native classes, traits and objects may not have an @JSGlobal annotation.
+ """
+ |newSource1.scala:5: error: @JSGlobal can only be used on native JS definitions (with @js.native).
| @JSGlobal
| ^
- |newSource1.scala:8: error: Non JS-native classes, traits and objects may not have an @JSGlobal annotation.
+ |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 = {
+ @Test def noJSImportAnnotOnNonJSNative: Unit = {
for {
obj <- Seq("class", "trait", "object")
@@ -201,8 +267,8 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSImport("foo", JSImport.Namespace)
$obj A extends js.Object
""" hasErrors
- s"""
- |newSource1.scala:5: error: Non JS-native classes, traits and objects may not have an @JSImport annotation.
+ """
+ |newSource1.scala:5: error: @JSImport can only be used on native JS definitions (with @js.native).
| @JSImport("foo", JSImport.Namespace)
| ^
"""
@@ -215,13 +281,49 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSImport("foo", JSImport.Namespace)
$obj A
""" hasErrors
- s"""
- |newSource1.scala:5: error: Non JS-native classes, traits and objects may not have an @JSImport annotation.
+ """
+ |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 {
@@ -229,8 +331,8 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSImport("foo", JSImport.Namespace, globalFallback = "Foo")
$obj A extends js.Object
""" hasErrors
- s"""
- |newSource1.scala:5: error: Non JS-native classes, traits and objects may not have an @JSImport annotation.
+ """
+ |newSource1.scala:5: error: @JSImport can only be used on native JS definitions (with @js.native).
| @JSImport("foo", JSImport.Namespace, globalFallback = "Foo")
| ^
"""
@@ -243,24 +345,59 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSImport("foo", JSImport.Namespace, globalFallback = "Foo")
$obj A
""" hasErrors
- s"""
- |newSource1.scala:5: error: Non JS-native classes, traits and objects may not have an @JSImport annotation.
+ """
+ |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 = {
+ @Test def noJSGlobalScopeAnnotOnNonJSNative: Unit = {
"""
@JSGlobalScope
object A extends js.Object
""" hasErrors
"""
- |newSource1.scala:5: error: Only native JS objects can have an @JSGlobalScope annotation.
+ |newSource1.scala:5: error: @JSGlobalScope can only be used on native JS objects (with @js.native).
| @JSGlobalScope
| ^
"""
@@ -270,14 +407,13 @@ class JSInteropTest extends DirectTest with TestHelpers {
object A
""" hasErrors
"""
- |newSource1.scala:5: error: Only native JS objects can have an @JSGlobalScope annotation.
+ |newSource1.scala:5: error: @JSGlobalScope can only be used on native JS objects (with @js.native).
| @JSGlobalScope
| ^
"""
}
- @Test
- def noJSNameAnnotOnClass: Unit = {
+ @Test def noJSNameAnnotOnClass: Unit = {
"""
@js.native
@JSName("Foo")
@@ -288,67 +424,141 @@ class JSInteropTest extends DirectTest with TestHelpers {
abstract class B extends js.Object
""" hasErrors
"""
- |newSource1.scala:6: error: @JSName annotations are not allowed on top level classes or objects (or classes and objects inside Scala objects).
+ |newSource1.scala:6: error: @JSName can only be used on members of JS types.
| @JSName("Foo")
| ^
- |newSource1.scala:7: error: Native JS classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ |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 annotations are not allowed on top level classes or objects (or classes and objects inside Scala objects).
+ |newSource1.scala:10: error: @JSName can only be used on members of JS types.
| @JSName("Foo")
| ^
- |newSource1.scala:11: error: Native JS classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ |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 = {
+ @Test def noJSNameAnnotOnObject: Unit = {
"""
@js.native
@JSName("Foo")
object A extends js.Object
""" hasErrors
"""
- |newSource1.scala:6: error: @JSName annotations are not allowed on top level classes or objects (or classes and objects inside Scala objects).
+ |newSource1.scala:6: error: @JSName can only be used on members of JS types.
| @JSName("Foo")
| ^
- |newSource1.scala:7: error: Native JS classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ |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 = {
+ @Test def noJSNameAnnotOnTrait: Unit = {
s"""
- @js.native
- @JSName("foo")
- trait A extends js.Object
-
object Sym {
val sym = js.Symbol()
}
- @js.native
- @JSName(Sym.sym)
- trait B extends js.Object
+ @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"""
- |newSource1.scala:6: error: Traits may not have an @JSName annotation.
- | @JSName("foo")
- | ^
- |newSource1.scala:14: error: Traits may not have an @JSName annotation.
- | @JSName(Sym.sym)
- | ^
+ 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 = {
+ @Test def noJSGlobalAnnotOnTrait: Unit = {
s"""
@js.native
@@ -374,8 +584,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noJSImportAnnotOnTrait: Unit = {
+ @Test def noJSImportAnnotOnTrait: Unit = {
s"""
@js.native
@@ -401,94 +610,204 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
+ @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
} {
- val kinds = {
- if (firstAnnotName == "JSGlobalScope" || secondAnnotName == "JSGlobalScope")
- Seq("object")
- else
- Seq("class", "object")
- }
-
- for (kind <- kinds) {
- val snippet = {
- s"""
- |@js.native
- |$firstAnnot
- |$secondAnnot
- |$kind A extends js.Object
- """.stripMargin
- }
-
- snippet hasErrors s"""
- |newSource1.scala:7: error: Native JS classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ 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 = {
+ @Test def noJSNativeAnnotWithoutJSAny: Unit = {
+ // With the correct amount of native load spec annotations
"""
- @js.native
+ @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
- trait A
- """ hasErrors
- """
- |newSource1.scala:6: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
- | trait A
- | ^
- """
+ class A
+
+ @js.native @JSGlobal
+ trait B
- """
@js.native
- object A
- """ hasErrors
- """
- |newSource1.scala:6: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
- | object A
- | ^
- """
+ object C
- """
@js.native
- class A extends Enumeration
- """ hasErrors
- """
- |newSource1.scala:6: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
- | class A extends Enumeration
- | ^
- """
+ class D extends Enumeration
- """
@js.native
- object A extends Enumeration
+ object E extends Enumeration
""" hasErrors
"""
|newSource1.scala:6: error: Classes, traits and objects not extending js.Any may not have an @js.native annotation
- | object A extends Enumeration
+ | 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 = {
+ @Test def noInnerScalaClassTraitObjectInJSNative: Unit = {
for {
outer <- Seq("class", "trait")
@@ -512,8 +831,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noInnerNonNativeJSClassTraitObjectInJSNative: Unit = {
+ @Test def noInnerNonNativeJSClassTraitObjectInJSNative: Unit = {
for {
outer <- Seq("class", "trait")
@@ -537,8 +855,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noScalaStuffInsideNativeJSObject: Unit = {
+ @Test def noScalaStuffInsideNativeJSObject: Unit = {
for {
inner <- Seq("class", "trait", "object")
@@ -559,8 +876,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noNonSyntheticCompanionInsideNativeJSObject: Unit = {
+ @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
@@ -581,12 +897,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
@js.native @JSGlobal object A extends js.Object {
@js.native class B(x: Int = ???) extends js.Object
}
- """.succeeds
+ """.succeeds()
}
- @Test
- def noNonNativeJSTypesInsideNativeJSObject: Unit = {
+ @Test def noNonNativeJSTypesInsideNativeJSObject: Unit = {
for {
inner <- Seq("class", "object")
@@ -607,8 +922,399 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noBadSetters: Unit = {
+ @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
@@ -637,8 +1343,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noBadBracketAccess: Unit = {
+ @Test def noBadBracketAccess: Unit = {
"""
@js.native
@@ -704,8 +1409,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noBadBracketCall: Unit = {
+ @Test def noBadBracketCall: Unit = {
"""
@js.native
@@ -724,81 +1428,254 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
@Test
- def onlyJSTraits: Unit = {
-
+ def noJSOperatorAndJSName: Unit = {
"""
- trait A
-
@js.native
@JSGlobal
- class B extends js.Object with A
+ class A extends js.Object {
+ @JSOperator
+ @JSName("bar")
+ def +(x: Int): Int = js.native
+ }
""" hasErrors
"""
- |newSource1.scala:9: error: B extends A which does not extend js.Any.
- | class B extends js.Object with A
- | ^
+ |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 B extends js.Object with java.io.Serializable
+ class A extends js.Object {
+ @JSBracketAccess
+ @JSName("bar")
+ def bar(x: Int): Int = js.native
+ }
""" 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
- | ^
+ |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall.
+ | @JSName("bar")
+ | ^
"""
-
}
- @Test
- def noCaseClassObject: Unit = {
-
+ // #4284
+ @Test def noBracketCallAndJSName: Unit = {
"""
@js.native
@JSGlobal
- case class A(x: Int) extends js.Object
+ class A extends js.Object {
+ @JSBracketCall
+ @JSName("bar")
+ def bar(x: Int): Int = js.native
+ }
""" 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
- | ^
+ |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
- case object B extends js.Object
+ class A extends js.Object {
+ @JSBracketAccess
+ @JSBracketCall
+ def bar(x: Int): Int = js.native
+ }
""" hasErrors
"""
- |newSource1.scala:7: error: Classes and objects extending js.Any may not have a case modifier
- | case object B extends js.Object
- | ^
+ |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall.
+ | @JSBracketCall
+ | ^
"""
+ }
+ @Test def noBadUnaryOp: Unit = {
"""
- case class A(x: Int) extends js.Object
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSOperator
+ def unary_!(x: Int*): Int = js.native
+ }
""" 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
- | ^
+ |newSource1.scala:9: error: @JSOperator methods with the name 'unary_!' may not have any parameters
+ | def unary_!(x: Int*): Int = js.native
+ | ^
"""
"""
- case object B extends js.Object
+ @js.native
+ @JSGlobal
+ class A extends js.Object {
+ @JSOperator
+ def unary_-(x: Int): Int = js.native
+ }
""" hasErrors
"""
- |newSource1.scala:5: error: Classes and objects extending js.Any may not have a case modifier
- | case object B extends js.Object
- | ^
+ |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 noNativeJSNestedInScalaClassTrait: Unit = {
+ @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")
@@ -820,7 +1697,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
s"""
- |newSource1.scala:7: error: Scala traits and classes may not have inner native JS traits, classes or objects
+ |newSource1.scala:7: error: Scala traits and classes may not have native JS members
| $inner Inner extends js.Object
| ${" " * inner.length}^
"""
@@ -828,8 +1705,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noNativeJSNestedInNonNativeJS: Unit = {
+ @Test def noNativeJSNestedInNonNativeJS: Unit = {
val outers = List("class", "trait", "object")
val inners = List("class", "trait", "object")
@@ -851,7 +1727,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
s"""
- |newSource1.scala:7: error: non-native JS classes, traits and objects may not have inner native JS classes, traits or objects
+ |newSource1.scala:7: error: non-native JS classes, traits and objects may not have native JS members
| $inner Inner extends js.Object
| ${" " * inner.length}^
"""
@@ -859,48 +1735,65 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noLocalClass: Unit = {
-
+ @Test def noLocalJSNative: Unit = {
"""
object A {
def a = {
- @js.native
- @JSGlobal
+ @js.native @JSGlobal
class B extends js.Object
- }
- }
- """ hasErrors
- """
- |newSource1.scala:9: error: Local native JS classes and objects are not allowed
- | class B extends js.Object
- | ^
- """
- }
+ @js.native @JSGlobal
+ object C extends js.Object
- @Test
- def noLocalObject: Unit = {
+ @js.native @JSGlobal
+ val d: Int = js.native
- """
- object A {
- def a = {
- @js.native
- @JSGlobal
- object B extends js.Object
+ @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:9: error: Local native JS classes and objects are not allowed
- | object B extends js.Object
+ |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 = {
+ @Test def noNativeInJSAny: Unit = {
"""
@js.native
@@ -918,8 +1811,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def checkJSAnyBody: Unit = {
+ @Test def checkJSAnyBody: Unit = {
"""
@js.native
@@ -940,8 +1832,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noWarnJSAnyDeferred: Unit = {
+ @Test def noWarnJSAnyDeferred: Unit = {
"""
@js.native
@@ -950,7 +1841,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
def value: Int
val x: Int
}
- """.hasNoWarns
+ """.hasNoWarns()
"""
@js.native
@@ -958,12 +1849,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
def value: Int
val x: Int
}
- """.hasNoWarns
+ """.hasNoWarns()
}
- @Test
- def noCallSecondaryCtor: Unit = {
+ @Test def noCallSecondaryCtor: Unit = {
"""
@js.native
@@ -981,8 +1871,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noPrivateMemberInNative: Unit = {
+ @Test def noPrivateMemberInNative: Unit = {
"""
@js.native
@@ -1033,8 +1922,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noPrivateConstructorInNative: Unit = {
+ @Test def noPrivateConstructorInNative: Unit = {
"""
@js.native
@@ -1062,12 +1950,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
@js.native
@JSGlobal
class A private[this] () extends js.Object
- """.hasNoWarns
+ """.hasNoWarns()
}
- @Test
- def noUseJsNative: Unit = {
+ @Test def noUseJsNative: Unit = {
"""
class A {
@@ -1082,8 +1969,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def warnNothingInNativeJS: Unit = {
+ @Test def warnNothingInNativeJS: Unit = {
"""
@js.native
@@ -1104,222 +1990,679 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def nativeClassMustHaveLoadingSpec: Unit = {
+ @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 and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ |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 and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ |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 nativeObjectMustHaveLoadingSpec: Unit = {
+ @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 classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
+ |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 noNativeClassObjectWithoutExplicitNameInsideScalaObject: Unit = {
+ @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
- class B extends js.Object
+ @JSGlobal
+ class apply extends js.Object
+
+ @js.native
+ @JSGlobal
+ object apply extends js.Object
}
""" hasErrors
"""
- |newSource1.scala:7: error: Native JS classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
- | class B extends js.Object
- | ^
+ |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
- object B extends js.Object
+ @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 classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
- | object B extends js.Object
- | ^
+ |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
- class B extends js.Object
+ val apply: Int = js.native
}
""" hasErrors
"""
- |newSource1.scala:7: error: Native JS classes and objects inside non-native objects must have an explicit name in @JSGlobal
+ |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
- object B extends js.Object
+ 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 classes and objects inside non-native objects must have an explicit name in @JSGlobal
+ |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
| ^
"""
- // From issue #2401
+ """
+ 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
- object B extends js.Object
+ @JSGlobal
+ class foo_= extends js.Object
@js.native
@JSGlobal
- object C extends js.Object
+ object foo_= extends js.Object
}
""" hasErrors
"""
- |newSource1.scala:7: error: Native JS classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
- | object B extends js.Object
- | ^
+ |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
- class B extends js.Object
+ @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
- class C extends js.Object
+ 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 classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
- | class B extends js.Object
- | ^
+ |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 {
- @JSName("InnerB")
@js.native
- class B extends js.Object
+ @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
+ | ^
+ """
- @JSName("InnerC")
+ """
+ object A {
@js.native
- abstract class C extends js.Object
+ @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
+ | ^
+ """
- @JSName("InnerD")
+ """
+ object A {
@js.native
- object D extends js.Object
+ @JSImport("foo.js")
+ def foo_=(x: Int): Int = js.native
}
""" hasErrors
"""
- |newSource1.scala:6: error: @JSName annotations are not allowed on top level classes or objects (or classes and objects inside Scala objects).
- | @JSName("InnerB")
- | ^
- |newSource1.scala:8: error: Native JS classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
- | class B extends js.Object
- | ^
- |newSource1.scala:10: error: @JSName annotations are not allowed on top level classes or objects (or classes and objects inside Scala objects).
- | @JSName("InnerC")
- | ^
- |newSource1.scala:12: error: Native JS classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
- | abstract class C extends js.Object
- | ^
- |newSource1.scala:14: error: @JSName annotations are not allowed on top level classes or objects (or classes and objects inside Scala objects).
- | @JSName("InnerD")
+ |newSource1.scala:7: error: Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport
+ | @JSImport("foo.js")
| ^
- |newSource1.scala:16: error: Native JS classes and objects must have exactly one annotation among @JSGlobal, @JSImport and @JSGlobalScope.
- | object D extends js.Object
- | ^
+ |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 {
- @JSGlobal("InnerB")
- @js.native
- class B extends js.Object
-
- @JSGlobal("InnerC")
@js.native
- object C extends js.Object
+ @JSImport("foo.js", "foo")
+ def foo_=(x: Int): Int = js.native
}
- """.hasNoWarns
+ """ 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 {
- @JSImport("InnerB", JSImport.Namespace)
+ @JSGlobal("foo")
@js.native
- class B extends js.Object
+ class foo_= extends js.Object
- @JSImport("InnerC", JSImport.Namespace)
+ @JSGlobal("foo")
@js.native
- object C extends js.Object
+ object foo_= extends js.Object
}
- """.hasNoWarns
+ """.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("InnerB", JSImport.Namespace, globalFallback = "Foo")
+ @JSImport("foo.js", "foo_=")
@js.native
- class B extends js.Object
+ class foo_= extends js.Object
- @JSImport("InnerC", JSImport.Namespace, globalFallback = "Foo")
+ @JSImport("foo.js", "foo_=")
@js.native
- object C extends js.Object
+ object foo_= extends js.Object
}
- """.hasNoWarns
+ """.hasNoWarns()
"""
- object A {
- @js.native
- trait B 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
- @JSGlobal
- object A extends js.Object {
- @js.native
- class B extends js.Object
+ object foo_= extends js.Object
+ object A {
+ @JSImport("foo.js", "foo_=", globalFallback = "foo")
@js.native
- trait C extends js.Object
+ class foo_= extends js.Object
+ @JSImport("foo.js", "foo_=", globalFallback = "foo")
@js.native
- object D extends js.Object
+ object foo_= extends js.Object
}
- """.hasNoWarns
+ """.hasNoWarns()
}
- @Test
- def noNonLiteralJSName: Unit = {
+ @Test def noNonLiteralJSName: Unit = {
"""
import js.annotation.JSName
@@ -1346,8 +2689,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noNonStaticStableJSNameSymbol: Unit = {
+ @Test def noNonStaticStableJSNameSymbol: Unit = {
"""
import js.annotation.JSName
@@ -1389,8 +2731,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noSelfReferenceJSNameSymbol: Unit = {
+ @Test def noSelfReferenceJSNameSymbol: Unit = {
"""
object A extends js.Object {
@@ -1416,12 +2757,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSName(a)
def foo: Int = js.native
}
- """.succeeds
+ """.succeeds()
}
- @Test
- def noJSGlobalOnMembersOfClassesAndTraits: Unit = {
+ @Test def noJSGlobalOnMembersOfClassesAndTraits: Unit = {
for (outer <- Seq("class", "trait")) {
s"""
@@ -1452,15 +2792,15 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:8: error: Methods and fields cannot be annotated with @JSGlobal.
- | val bar1: Int = js.native
- | ^
- |newSource1.scala:10: error: Methods and fields cannot be annotated with @JSGlobal.
- | var bar2: Int = js.native
- | ^
- |newSource1.scala:12: error: Methods and fields cannot be annotated with @JSGlobal.
- | def bar3: Int = js.native
- | ^
+ |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")
| ^
@@ -1478,8 +2818,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noJSGlobalOnMembersOfObjects: Unit = {
+ @Test def noJSGlobalOnMembersOfObjects: Unit = {
s"""
@js.native @JSGlobal
@@ -1509,15 +2848,15 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:8: error: Methods and fields cannot be annotated with @JSGlobal.
- | val bar1: Int = js.native
- | ^
- |newSource1.scala:10: error: Methods and fields cannot be annotated with @JSGlobal.
- | var bar2: Int = js.native
- | ^
- |newSource1.scala:12: error: Methods and fields cannot be annotated with @JSGlobal.
- | def bar3: Int = js.native
- | ^
+ |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")
| ^
@@ -1534,8 +2873,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noJSImportOnMembersOfClassesAndTraits: Unit = {
+ @Test def noJSImportOnMembersOfClassesAndTraits: Unit = {
for {
outer <- Seq("class", "trait")
@@ -1561,15 +2899,15 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
s"""
- |newSource1.scala:8: error: Methods and fields cannot be annotated with @JSImport.
- | val bar1: Int = js.native
- | ^
- |newSource1.scala:10: error: Methods and fields cannot be annotated with @JSImport.
- | var bar2: Int = js.native
- | ^
- |newSource1.scala:12: error: Methods and fields cannot be annotated with @JSImport.
- | def bar3: Int = js.native
- | ^
+ |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)
| ^
@@ -1581,8 +2919,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noJSImportOnMembersOfObjects: Unit = {
+ @Test def noJSImportOnMembersOfObjects: Unit = {
for {
fallbackStr <- Seq("", ", globalFallback = \"Foo\"")
@@ -1607,15 +2944,15 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
s"""
- |newSource1.scala:8: error: Methods and fields cannot be annotated with @JSImport.
- | val bar1: Int = js.native
- | ^
- |newSource1.scala:10: error: Methods and fields cannot be annotated with @JSImport.
- | var bar2: Int = js.native
- | ^
- |newSource1.scala:12: error: Methods and fields cannot be annotated with @JSImport.
- | def bar3: Int = js.native
- | ^
+ |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)
| ^
@@ -1627,8 +2964,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noNonLiteralJSGlobal: Unit = {
+ @Test def noNonLiteralJSGlobal: Unit = {
"""
object A {
@@ -1654,8 +2990,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noNonJSIdentifierJSGlobal: Unit = {
+ @Test def noNonJSIdentifierJSGlobal: Unit = {
"""
@js.native
@@ -1738,8 +3073,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noNonLiteralJSImport: Unit = {
+ @Test def noNonLiteralJSImport: Unit = {
// Without global fallback
@@ -1950,8 +3284,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
- @Test
- def noApplyProperty: Unit = {
+ @Test def noApplyProperty: Unit = {
// def apply
@@ -1975,7 +3308,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSName("apply")
def apply: Int = js.native
}
- """.succeeds
+ """.succeeds()
// val apply
@@ -1999,7 +3332,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSName("apply")
val apply: Int = js.native
}
- """.succeeds
+ """.succeeds()
// var apply
@@ -2023,12 +3356,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSName("apply")
var apply: Int = js.native
}
- """.succeeds
+ """.succeeds()
}
- @Test
- def noAbstractLocalJSClass: Unit = {
+ @Test def noAbstractLocalJSClass: Unit = {
"""
object Enclosing {
def method(): Unit = {
@@ -2043,8 +3375,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
"""
}
- @Test
- def noLoadJSConstructorOfUnstableRef: Unit = {
+ @Test def noLoadJSConstructorOfUnstableRef: Unit = {
"""
class Enclosing {
class InnerJSClass extends js.Object
@@ -2106,8 +3437,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
""".fails()
}
- @Test
- def noJSSymbolNameOnNestedNativeClassesAndObjects: Unit = {
+ @Test def noJSSymbolNameOnNestedNativeClassesAndObjects: Unit = {
for {
kind <- Seq("class", "object")
} {
@@ -2124,7 +3454,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
$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)
| ^
@@ -2132,8 +3462,89 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
}
- @Test
- def noDuplicateJSNameAnnotOnMember: Unit = {
+ @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")
} {
@@ -2151,15 +3562,14 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:13: error: A member can only have a single @JSName annotation.
+ |newSource1.scala:13: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall.
| @JSName("foo")
| ^
"""
}
}
- @Test
- def nonNativeJSTypesNameOverrideErrors: Unit = {
+ @Test def nonNativeJSTypesNameOverrideErrors: Unit = {
"""
abstract class A extends js.Object {
def bar(): Int
@@ -2167,7 +3577,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
class B extends A {
override def bar() = 1
}
- """.hasNoWarns
+ """.hasNoWarns()
"""
trait A extends js.Object {
@@ -2178,7 +3588,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSName("foo")
override def bar() = 1
}
- """.hasNoWarns
+ """.hasNoWarns()
"""
abstract class A extends js.Object {
@@ -2189,7 +3599,26 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSName("foo")
override def bar() = 1
}
- """.hasNoWarns
+ """.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 {
@@ -2202,11 +3631,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:11: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'baz'
+ |override def bar(): Int in class B called from JS as method 'baz'
| is conflicting with
- |def bar(): Int in class A with JSName 'foo'
+ |def bar(): Int in class A called from JS as method 'foo'
|
| override def bar() = 1
| ^
@@ -2222,11 +3651,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'bar'
+ |override def bar(): Int in class B called from JS as method 'bar'
| is conflicting with
- |def bar(): Int in class A with JSName 'foo'
+ |def bar(): Int in class A called from JS as method 'foo'
|
| override def bar() = 1
| ^
@@ -2245,19 +3674,19 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'bar'
+ |override def bar(): String in class B called from JS as method 'bar'
| is conflicting with
- |def bar(): Object in class A with JSName 'foo'
+ |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 name.
+ |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 with JSName 'bar'
+ |override def bar(): String in class C called from JS as method 'bar'
| is conflicting with
- |def bar(): Object in class A with JSName 'foo'
+ |def bar(): Object in class A called from JS as method 'foo'
|
| override def bar() = "1"
| ^
@@ -2276,19 +3705,19 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'foo'
+ |override def bar(): String in class B called from JS as method 'foo'
| is conflicting with
- |def bar(): Object in class A with JSName 'bar'
+ |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 name.
+ |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 with JSName 'bar'
+ |override def bar(): String in class C called from JS as method 'bar'
| is conflicting with
- |override def bar(): String in class B with JSName 'foo'
+ |override def bar(): String in class B called from JS as method 'foo'
|
| override def bar() = "1"
| ^
@@ -2305,20 +3734,20 @@ class JSInteropTest extends DirectTest with TestHelpers {
class C extends B
""" hasErrors
s"""
- |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'foo'
+ |def foo: Int in class A called from JS as property 'foo'
| is conflicting with
- |def foo: Int in trait B with JSName 'bar'
+ |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 name.
+ |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 with JSName 'foo'
+ |def foo: Int in class A called from JS as property 'foo'
| is conflicting with
- |def foo: Int in trait B with JSName 'bar'
+ |def foo: Int in trait B called from JS as property 'bar'
|
| class C extends B
| ^
@@ -2336,20 +3765,20 @@ class JSInteropTest extends DirectTest with TestHelpers {
class C extends B
""" hasErrors
s"""
- |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'bar'
+ |def foo: Int in class A called from JS as property 'bar'
| is conflicting with
- |def foo: Int in trait B with JSName 'foo'
+ |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 name.
+ |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 with JSName 'bar'
+ |def foo: Int in class A called from JS as property 'bar'
| is conflicting with
- |def foo: Int in trait B with JSName 'foo'
+ |def foo: Int in trait B called from JS as property 'foo'
|
| class C extends B
| ^
@@ -2366,11 +3795,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'foo'
+ |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 with JSName 'bar'
+ |def foo(x: Int): Int in class A called from JS as method 'bar'
|
| override def foo(x: Int): Int = x
| ^
@@ -2386,11 +3815,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'foo'
+ |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 with JSName 'bar'
+ |def foo(x: Int): Int in trait A called from JS as method 'bar'
|
| override def foo(x: Int): Int = x
| ^
@@ -2409,19 +3838,19 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'bar'
+ |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 with JSName 'foo'
+ |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 name.
+ |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 with JSName 'foo'
+ |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 with JSName 'bar'
+ |def foo(x: Int): Int in class A called from JS as method 'bar'
|
| override def foo(x: Int): Int = x
| ^
@@ -2440,19 +3869,19 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:10: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'foo'
+ |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 with JSName 'bar'
+ |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 name.
+ |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 with JSName 'foo'
+ |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 with JSName 'bar'
+ |def foo(x: Int): Int in trait B called from JS as method 'bar'
|
| override def foo(x: Int): Int = x
| ^
@@ -2469,11 +3898,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
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 name.
+ |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 with JSName 'bar'
+ |def foo: Int in trait B called from JS as property 'bar'
| is conflicting with
- |def foo: Int in trait A with JSName 'foo'
+ |def foo: Int in trait A called from JS as property 'foo'
|
| trait C extends A with B
| ^
@@ -2490,19 +3919,18 @@ class JSInteropTest extends DirectTest with TestHelpers {
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 name.
+ |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 with JSName 'bar'
+ |def foo: Int in trait B called from JS as property 'bar'
| is conflicting with
- |def foo: Int in trait A with JSName 'foo'
+ |def foo: Int in trait A called from JS as property 'foo'
|
| abstract class C extends A with B
| ^
"""
}
- @Test
- def nonNativeJSTypesJSNameWithSymbolOverrideErrors: Unit = {
+ @Test def nonNativeJSTypesJSNameWithSymbolOverrideErrors: Unit = {
"""
object Syms {
val sym1 = js.Symbol()
@@ -2516,7 +3944,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSName(Syms.sym1)
override def bar() = 1
}
- """.hasNoWarns
+ """.hasNoWarns()
"""
object Syms {
@@ -2531,7 +3959,7 @@ class JSInteropTest extends DirectTest with TestHelpers {
@JSName(Syms.sym1)
override def bar() = 1
}
- """.hasNoWarns
+ """.hasNoWarns()
"""
object Syms {
@@ -2549,11 +3977,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:16: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'Syms.sym2'
+ |override def bar(): Int in class B called from JS as method 'Syms.sym2'
| is conflicting with
- |def bar(): Int in class A with JSName 'Syms.sym1'
+ |def bar(): Int in class A called from JS as method 'Syms.sym1'
|
| override def bar() = 1
| ^
@@ -2574,11 +4002,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:15: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'baz'
+ |override def bar(): Int in class B called from JS as method 'baz'
| is conflicting with
- |def bar(): Int in class A with JSName 'Syms.sym1'
+ |def bar(): Int in class A called from JS as method 'Syms.sym1'
|
| override def bar() = 1
| ^
@@ -2599,11 +4027,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:15: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'Syms.sym1'
+ |override def bar(): Int in class B called from JS as method 'Syms.sym1'
| is conflicting with
- |def bar(): Int in class A with JSName 'foo'
+ |def bar(): Int in class A called from JS as method 'foo'
|
| override def bar() = 1
| ^
@@ -2623,11 +4051,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'bar'
+ |override def bar(): Int in class B called from JS as method 'bar'
| is conflicting with
- |def bar(): Int in class A with JSName 'Syms.sym1'
+ |def bar(): Int in class A called from JS as method 'Syms.sym1'
|
| override def bar() = 1
| ^
@@ -2650,19 +4078,19 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'bar'
+ |override def bar(): String in class B called from JS as method 'bar'
| is conflicting with
- |def bar(): Object in class A with JSName 'Syms.sym1'
+ |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 name.
+ |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 with JSName 'bar'
+ |override def bar(): String in class C called from JS as method 'bar'
| is conflicting with
- |def bar(): Object in class A with JSName 'Syms.sym1'
+ |def bar(): Object in class A called from JS as method 'Syms.sym1'
|
| override def bar() = "1"
| ^
@@ -2685,19 +4113,19 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'Syms.sym1'
+ |override def bar(): String in class B called from JS as method 'Syms.sym1'
| is conflicting with
- |def bar(): Object in class A with JSName 'bar'
+ |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 name.
+ |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 with JSName 'bar'
+ |override def bar(): String in class C called from JS as method 'bar'
| is conflicting with
- |override def bar(): String in class B with JSName 'Syms.sym1'
+ |override def bar(): String in class B called from JS as method 'Syms.sym1'
|
| override def bar() = "1"
| ^
@@ -2718,20 +4146,20 @@ class JSInteropTest extends DirectTest with TestHelpers {
class C extends B
""" hasErrors
s"""
- |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'foo'
+ |def foo: Int in class A called from JS as property 'foo'
| is conflicting with
- |def foo: Int in trait B with JSName 'Syms.sym1'
+ |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 name.
+ |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 with JSName 'foo'
+ |def foo: Int in class A called from JS as property 'foo'
| is conflicting with
- |def foo: Int in trait B with JSName 'Syms.sym1'
+ |def foo: Int in trait B called from JS as property 'Syms.sym1'
|
| class C extends B
| ^
@@ -2753,20 +4181,20 @@ class JSInteropTest extends DirectTest with TestHelpers {
class C extends B
""" hasErrors
s"""
- |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'Syms.sym1'
+ |def foo: Int in class A called from JS as property 'Syms.sym1'
| is conflicting with
- |def foo: Int in trait B with JSName 'foo'
+ |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 name.
+ |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 with JSName 'Syms.sym1'
+ |def foo: Int in class A called from JS as property 'Syms.sym1'
| is conflicting with
- |def foo: Int in trait B with JSName 'foo'
+ |def foo: Int in trait B called from JS as property 'foo'
|
| class C extends B
| ^
@@ -2787,11 +4215,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'foo'
+ |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 with JSName 'Syms.sym1'
+ |def foo(x: Int): Int in class A called from JS as method 'Syms.sym1'
|
| override def foo(x: Int): Int = x
| ^
@@ -2811,11 +4239,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'foo'
+ |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 with JSName 'Syms.sym1'
+ |def foo(x: Int): Int in trait A called from JS as method 'Syms.sym1'
|
| override def foo(x: Int): Int = x
| ^
@@ -2838,19 +4266,19 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'Syms.sym1'
+ |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 with JSName 'foo'
+ |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 name.
+ |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 with JSName 'foo'
+ |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 with JSName 'Syms.sym1'
+ |def foo(x: Int): Int in class A called from JS as method 'Syms.sym1'
|
| override def foo(x: Int): Int = x
| ^
@@ -2873,19 +4301,19 @@ class JSInteropTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS name.
+ |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 with JSName 'foo'
+ |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 with JSName 'Syms.sym1'
+ |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 name.
+ |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 with JSName 'foo'
+ |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 with JSName 'Syms.sym1'
+ |def foo(x: Int): Int in trait B called from JS as method 'Syms.sym1'
|
| override def foo(x: Int): Int = x
| ^
@@ -2906,11 +4334,11 @@ class JSInteropTest extends DirectTest with TestHelpers {
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 name.
+ |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 with JSName 'Syms.sym1'
+ |def foo: Int in trait B called from JS as property 'Syms.sym1'
| is conflicting with
- |def foo: Int in trait A with JSName 'foo'
+ |def foo: Int in trait A called from JS as property 'foo'
|
| trait C extends A with B
| ^
@@ -2931,19 +4359,162 @@ class JSInteropTest extends DirectTest with TestHelpers {
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 name.
+ |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 with JSName 'Syms.sym1'
+ |def foo: Int in trait B called from JS as property 'Syms.sym1'
| is conflicting with
- |def foo: Int in trait A with JSName 'foo'
+ |def foo: Int in trait A called from JS as property 'foo'
|
| abstract class C extends A with B
| ^
"""
}
- @Test
- def noDefaultConstructorArgsIfModuleIsJSNative: Unit = {
+ // #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
@@ -2971,8 +4542,8 @@ class JSInteropTest extends DirectTest with TestHelpers {
"""
}
- @Test // #2547
- def noDefaultOverrideCrash: Unit = {
+ // #2547
+ @Test def noDefaultOverrideCrash: Unit = {
"""
@js.native
@JSGlobal
@@ -3009,4 +4580,91 @@ class JSInteropTest extends DirectTest with TestHelpers {
| ^
"""
}
+
+ // # 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
index 4c87f0aab7..c275ea0d59 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSOptionalTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSOptionalTest.scala
@@ -64,6 +64,37 @@ class JSOptionalTest extends DirectTest with TestHelpers {
"""
}
+ @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"""
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala
index b247fc2165..4eedcb3447 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala
@@ -13,6 +13,7 @@
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
@@ -28,8 +29,7 @@ class JSSAMTest extends DirectTest with TestHelpers {
"""
@Test
- def noSAMAsJSTypeGeneric: Unit = {
-
+ def noSAMAsJSType: Unit = {
"""
@js.native
trait Foo extends js.Object {
@@ -40,44 +40,155 @@ class JSSAMTest extends DirectTest with TestHelpers {
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 bar: Bar = x => x + 1
+ val foobar: Foobar = x => x + 1
}
- """.fails()
-
+ """ 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 noSAMAsJSType212: Unit = {
-
- val version = scala.util.Properties.versionNumberString
- assumeTrue(!version.startsWith("2.11."))
-
+ def noSAMOfNativeJSFunctionType: Unit = {
"""
@js.native
- trait Foo extends js.Object {
- def foo(x: Int): Int
+ trait Foo extends js.Function {
+ def apply(x: Int): Int
}
- trait Bar extends js.Object {
- def bar(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
+ val bar: Bar = x => x + 1
}
""" hasErrors
"""
- |newSource1.scala:15: error: Using an anonymous function as a SAM for the JavaScript type Foo is not allowed. Use an anonymous class instead.
+ |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:16: error: Using an anonymous function as a SAM for the JavaScript type Bar is not allowed. Use an anonymous class instead.
- | val Bar: Bar = 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/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
index 4b958593de..fa8faf6071 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/MatchASTTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/MatchASTTest.scala
@@ -25,13 +25,14 @@ class MatchASTTest extends JSASTTest {
def stripIdentityMatchEndNonUnitResult: Unit = {
"""
object A {
- def foo = "a" match {
+ def aString: String = "a"
+ def foo = aString match {
case "a" => true
case "b" => false
}
}
""".hasExactly(1, "local variable") {
- case js.VarDef(_, _, _, _) =>
+ case js.VarDef(_, _, _, _, _) =>
}
}
@@ -39,13 +40,14 @@ class MatchASTTest extends JSASTTest {
def stripIdentityMatchEndUnitResult: Unit = {
"""
object A {
- def foo = "a" match {
+ def aString: String = "a"
+ def foo = aString match {
case "a" =>
case "b" =>
}
}
""".hasExactly(1, "local variable") {
- case js.VarDef(_, _, _, _) =>
+ case js.VarDef(_, _, _, _, _) =>
}
}
diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala
index 065b0e7c4f..a6d37efc1f 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala
@@ -13,6 +13,7 @@
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
@@ -33,7 +34,7 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
class A extends js.Any
""" hasErrors
"""
- |newSource1.scala:5: error: A non-native JS class cannot directly extend AnyRef. It must extend a JS class (native or not).
+ |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
| ^
"""
@@ -42,7 +43,7 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
object A extends js.Any
""" hasErrors
"""
- |newSource1.scala:5: error: A non-native JS object cannot directly extend AnyRef. It must extend a JS class (native or not).
+ |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
| ^
"""
@@ -65,33 +66,215 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
}
""" hasErrors
"""
- |newSource1.scala:8: error: A non-native JS class cannot directly extend a native JS trait.
+ |newSource1.scala:8: error: Non-native JS types cannot directly extend native JS traits.
| class A extends NativeTrait
| ^
- |newSource1.scala:10: error: A non-native JS trait cannot directly extend a native JS trait.
+ |newSource1.scala:10: error: Non-native JS types cannot directly extend native JS traits.
| trait B extends NativeTrait
| ^
- |newSource1.scala:12: error: A non-native JS object cannot directly extend a native JS trait.
+ |newSource1.scala:12: error: Non-native JS types cannot directly extend native JS traits.
| object C extends NativeTrait
| ^
- |newSource1.scala:15: error: A non-native JS class cannot directly extend a native JS trait.
+ |newSource1.scala:15: error: Non-native JS types cannot directly extend native JS traits.
| val x = new NativeTrait {}
| ^
"""
}
@Test
- def noApplyMethod: Unit = {
+ 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 method named `apply` without `@JSName`
+ |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
@@ -134,10 +317,10 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
def bar(): Int = 24
}
""" hasErrors
- """
+ s"""
|newSource1.scala:9: error: Cannot disambiguate overloads for method bar with types
- | ()Int
- | ()Int
+ | ${methodSig("()", "Int")}
+ | ${methodSig("()", "Int")}
| def bar(): Int = 24
| ^
"""
@@ -150,10 +333,10 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
def foo(): Int = 42
}
""" hasErrors
- """
+ s"""
|newSource1.scala:9: error: Cannot disambiguate overloads for method bar with types
- | ()Int
- | ()Int
+ | ${methodSig("()", "Int")}
+ | ${methodSig("()", "Int")}
| def foo(): Int = 42
| ^
"""
@@ -168,10 +351,10 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
def bar(): Int = 24
}
""" hasErrors
- """
+ s"""
|newSource1.scala:11: error: Cannot disambiguate overloads for method bar with types
- | ()Int
- | ()Int
+ | ${methodSig("()", "Int")}
+ | ${methodSig("()", "Int")}
| def bar(): Int = 24
| ^
"""
@@ -188,10 +371,10 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
def bar(): Int = 24
}
""" hasErrors
- """
+ s"""
|newSource1.scala:13: error: Cannot disambiguate overloads for method bar with types
- | ()Int
- | ()Int
+ | ${methodSig("()", "Int")}
+ | ${methodSig("()", "Int")}
| def bar(): Int = 24
| ^
"""
@@ -210,10 +393,10 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
def foo(): Int = 42
}
""" hasErrors
- """
+ s"""
|newSource1.scala:14: error: Cannot disambiguate overloads for method foo with types
- | (x: Int)Int
- | (x: Int)Int
+ | ${methodSig("(x: Int)", "Int")}
+ | ${methodSig("(x: Int)", "Int")}
| class Bar extends Foo {
| ^
"""
@@ -397,7 +580,7 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
class C private[Enclosing] () extends js.Object
}
- """.succeeds
+ """.succeeds()
"""
object Enclosing {
@@ -405,7 +588,7 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
final private[Enclosing] def foo(i: Int): Int = i
}
}
- """.succeeds
+ """.succeeds()
"""
object Enclosing {
@@ -414,7 +597,7 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
private[this] def bar(i: Int): Int = i + 1
}
}
- """.succeeds
+ """.succeeds()
"""
object Enclosing {
@@ -422,7 +605,7 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
final private[Enclosing] def foo(i: Int): Int = i
}
}
- """.succeeds
+ """.succeeds()
"""
object Enclosing {
@@ -431,7 +614,7 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
private[this] def bar(i: Int): Int = i + 1
}
}
- """.succeeds
+ """.succeeds()
"""
object Enclosing {
@@ -559,7 +742,7 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
@JSName("apply")
def apply: Int = 42
}
- """.succeeds
+ """.succeeds()
// val apply
@@ -579,7 +762,7 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
@JSName("apply")
val apply: Int = 42
}
- """.succeeds
+ """.succeeds()
// var apply
@@ -599,7 +782,7 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
@JSName("apply")
var apply: Int = 42
}
- """.succeeds
+ """.succeeds()
}
@Test
@@ -643,6 +826,14 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
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
"""
@@ -652,21 +843,59 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers {
|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
- def noCallOtherConstructorsWithLeftOutDefaultParams: Unit = {
+ @Test // #4511
+ def noConflictingProperties: Unit = {
"""
- class A(x: Int, y: String = "default") extends js.Object {
- def this() = this(12)
+ class A extends js.Object {
+ def a: Unit = ()
+
+ @JSName("a")
+ def b: Unit = ()
}
""" hasErrors
- """
- |newSource1.scala:5: error: Implementation restriction: in a JS class, a secondary constructor calling another constructor with default parameters must provide the values of all parameters.
- | class A(x: Int, y: String = "default") extends js.Object {
+ 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
index 0775b420f4..f38e2adf28 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala
@@ -15,12 +15,59 @@ 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
@@ -123,14 +170,160 @@ class OptimizationTest extends JSASTTest {
// 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") {
- case js.JSLinkingInfo() =>
+ 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") =>
}
}
@@ -290,26 +483,194 @@ class OptimizationTest extends JSASTTest {
}
}
+ @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`) =>
+ }
+ }
+
+ @Test
+ def unsignedComparisonsInt: Unit = {
+ import js.BinaryOp._
+
+ val comparisons = List(
+ (Int_unsigned_<, "<"),
+ (Int_unsigned_<=, "<="),
+ (Int_unsigned_>, ">"),
+ (Int_unsigned_>=, ">=")
+ )
+
+ for ((op, codeOp) <- comparisons) {
+ s"""
+ class Test {
+ private final val SignBit = Int.MinValue
+
+ def unsignedComparisonsInt(x: Int, y: Int): Unit = {
+ (x ^ 0x80000000) $codeOp (y ^ 0x80000000)
+ (SignBit ^ x) $codeOp (y ^ SignBit)
+ (SignBit ^ x) $codeOp 0x80000010
+ 0x00000020 $codeOp (y ^ SignBit)
+ }
+ }
+ """.hasExactly(4, "unsigned comparisons") {
+ case js.BinaryOp(`op`, _, _) =>
+ }.hasNot("any Int_^") {
+ case js.BinaryOp(Int_^, _, _) =>
+ }.hasNot("any signed comparison") {
+ case js.BinaryOp(Int_< | Int_<= | Int_> | Int_>=, _, _) =>
+ }
+ }
+ }
+
+ @Test
+ def unsignedComparisonsLong: Unit = {
+ import js.BinaryOp._
+
+ val comparisons = List(
+ (Long_unsigned_<, "<"),
+ (Long_unsigned_<=, "<="),
+ (Long_unsigned_>, ">"),
+ (Long_unsigned_>=, ">=")
+ )
+
+ for ((op, codeOp) <- comparisons) {
+ s"""
+ class Test {
+ private final val SignBit = Long.MinValue
+
+ def unsignedComparisonsInt(x: Long, y: Long): Unit = {
+ (x ^ 0x8000000000000000L) $codeOp (y ^ 0x8000000000000000L)
+ (SignBit ^ x) $codeOp (y ^ SignBit)
+ (SignBit ^ x) $codeOp 0x8000000000000010L
+ 0x0000000000000020L $codeOp (y ^ SignBit)
+ }
+ }
+ """.hasExactly(4, "unsigned comparisons") {
+ case js.BinaryOp(`op`, _, _) =>
+ }.hasNot("any Long_^") {
+ case js.BinaryOp(Long_^, _, _) =>
+ }.hasNot("any signed comparison") {
+ case js.BinaryOp(Long_< | Long_<= | Long_> | Long_>=, _, _) =>
+ }
+ }
+ }
}
object OptimizationTest {
- private val hasOldCollections = {
- val version = scala.util.Properties.versionNumberString
+ private val ArrayModuleClass = ClassName("scala.Array$")
- version.startsWith("2.10.") ||
- version.startsWith("2.11.") ||
- version.startsWith("2.12.")
- }
+ private val applySimpleMethodName = SimpleMethodName("apply")
+
+ private val hasOldCollections =
+ scala.util.Properties.versionNumberString.startsWith("2.12.")
private object WrapArrayCall {
- private val Suffix =
- if (hasOldCollections) "__scm_WrappedArray"
- else "__sci_ArraySeq"
+ 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.startsWith("wrap") && methodName.endsWith(Suffix)
+ methodName.simpleName.nameString.startsWith("wrap") &&
+ methodName.resultTypeRef == WrappedArrayTypeRef
}
}
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
index 837587eb3b..5fd603c286 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/DirectTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/DirectTest.scala
@@ -33,19 +33,19 @@ abstract class DirectTest {
/** create settings objects for test from arg string */
def newSettings(args: List[String]): Settings = {
val s = new Settings
- s processArguments (args, true)
+ s.processArguments(args, true)
s
}
def newScalaJSCompiler(args: String*): Global = {
- val settings = newSettings(
+ val settings0 = newSettings(
List(
"-d", testOutputPath,
"-bootclasspath", scalaLibPath,
"-classpath", classpath.mkString(File.pathSeparator)) ++
extraArgs ++ args.toList)
- lazy val global: Global = new Global(settings, newReporter(settings)) {
+ lazy val global: Global = new Global(settings0, newReporter(settings0)) {
private implicit class PluginCompat(val plugin: Plugin) {
def options: List[String] = {
val prefix = plugin.name + ":"
@@ -60,7 +60,7 @@ abstract class DirectTest {
override lazy val plugins = {
val scalaJSPlugin = newScalaJSPlugin(global)
- scalaJSPlugin.processOptions(scalaJSPlugin.options,
+ scalaJSPlugin.init(scalaJSPlugin.options,
msg => throw new IllegalArgumentException(msg))
scalaJSPlugin :: Nil
}
@@ -95,8 +95,7 @@ abstract class DirectTest {
def compileString(sourceCode: String): Boolean =
compileString(defaultGlobal)(sourceCode)
- // Cannot reuse global, otherwise compiler crashes with Scala >= 2.11.5
- // on following tests:
+ // Cannot reuse global, otherwise the compiler crashes on the following tests:
// - org.scalajs.nscplugin.test.JSExportTest
// - org.scalajs.nscplugin.test.JSDynamicLiteralTest
// Filed as #1443
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
index 402c1c461f..5a74a397fb 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/JSASTTest.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/JSASTTest.scala
@@ -17,6 +17,7 @@ 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._
@@ -27,7 +28,10 @@ import ir.{Trees => js}
abstract class JSASTTest extends DirectTest {
- private var lastAST: JSAST = _
+ 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]
@@ -62,9 +66,24 @@ abstract class JSASTTest extends DirectTest {
super.traverseClassDef(classDef)
}
- override def traverseMemberDef(memberDef: js.MemberDef): Unit = {
- handle(memberDef)
- super.traverseMemberDef(memberDef)
+ 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(
@@ -85,13 +104,15 @@ abstract class JSASTTest extends DirectTest {
def has(trgName: String)(pf: Pat): this.type = {
val tr = new PFTraverser(pf)
- assertTrue(s"AST should have $trgName", tr.find)
+ 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)
- assertFalse(s"AST should not have $trgName", tr.find)
+ if (tr.find)
+ fail(s"AST should not have $trgName but was\n$show")
this
}
@@ -99,45 +120,77 @@ abstract class JSASTTest extends DirectTest {
var actualCount = 0
val tr = new PFTraverser(pf.andThen(_ => actualCount += 1))
tr.traverse()
- assertEquals(s"AST has the wrong number of $trgName", count, actualCount)
+ 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: this.type = {
- clDefs foreach println _
- 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: List[js.ClassDef]): Unit = {
- lastAST = new JSAST(cld)
+ 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 = {
- if (!compileString(global)(code))
- throw new IllegalArgumentException("snippet did not compile")
- lastAST
+ 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 = {
- if (!compileSources(global)(source))
- throw new IllegalArgumentException("snippet did not compile")
- lastAST
+ 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
index e5c788b84f..112b9aad99 100644
--- a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/TestHelpers.scala
+++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/TestHelpers.scala
@@ -34,56 +34,50 @@ trait TestHelpers extends DirectTest {
/** 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 = {
- val reps = repResult {
- assertFalse("snippet shouldn't compile", compileString(preamble + code))
- }
- assertEquals("should have right errors",
- expected.stripMargin.trim, reps.trim)
+ 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 = {
- val reps = repResult {
- assertTrue("snippet should compile", compileString(preamble + code))
- }
- assertEquals("should have right warnings",
- expected.stripMargin.trim, reps.trim)
+ assertTrue("snippet should compile\n" + output, success)
+ assertEquals("should have right warnings", expected.stripMargin.trim, output)
}
def containsWarns(expected: String): Unit = {
- val reps = repResult {
- assertTrue("snippet should compile", compileString(preamble + code))
- }
+ assertTrue("snippet should compile\n" + output, success)
assertTrue("should contain the right warnings",
- reps.trim.contains(expected.stripMargin.trim))
+ output.contains(expected.stripMargin.trim))
}
def hasNoWarns(): Unit = {
- val reps = repResult {
- assertTrue("snippet should compile", compileString(preamble + code))
- }
- assertTrue("should not have warnings", reps.isEmpty)
+ assertTrue("snippet should compile\n" + output, success)
+ assertTrue("should not have warnings\n" + output, output.isEmpty)
}
def fails(): Unit =
- assertFalse("snippet shouldn't compile", compileString(preamble + code))
+ assertFalse("snippet shouldn't compile", success)
def warns(): Unit = {
- val reps = repResult {
- assertTrue("snippet should compile", compileString(preamble + code))
- }
- assertFalse("should have warnings", reps.isEmpty)
+ assertTrue("snippet should compile\n" + output, success)
+ assertFalse("should have warnings", output.isEmpty)
}
def succeeds(): Unit =
- assertTrue("snippet should compile", compileString(preamble + code))
-
- private def repResult(body: => Unit) = {
- errBuffer.reset()
- body
- errBuffer.toString.replaceAll("\r\n?", "\n")
- }
+ assertTrue("snippet should compile\n" + output, success)
}
implicit class CodeWrappers(sc: StringContext) {
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/examples/helloworld/helloworld-2.11-fastopt.html b/examples/helloworld/helloworld-2.11-fastopt.html
deleted file mode 100644
index 7b30469669..0000000000
--- a/examples/helloworld/helloworld-2.11-fastopt.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
- Hello world - Scala.js example
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/examples/helloworld/helloworld-2.11.html b/examples/helloworld/helloworld-2.11.html
deleted file mode 100644
index eb9d7cfd40..0000000000
--- a/examples/helloworld/helloworld-2.11.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
- Hello world - Scala.js example
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/examples/helloworld/helloworld-2.12-fastopt.html b/examples/helloworld/helloworld-2.12-fastopt.html
index 6a71ad21e8..922690d7ac 100644
--- a/examples/helloworld/helloworld-2.12-fastopt.html
+++ b/examples/helloworld/helloworld-2.12-fastopt.html
@@ -11,7 +11,7 @@
-
+
diff --git a/examples/helloworld/helloworld-2.12.html b/examples/helloworld/helloworld-2.12.html
index 80e6bfbe9a..a970c0c7bd 100644
--- a/examples/helloworld/helloworld-2.12.html
+++ b/examples/helloworld/helloworld-2.12.html
@@ -11,7 +11,7 @@
-
+