├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .sbtopts ├── .scalafmt.conf ├── README.md ├── build.sbt ├── package-lock.json ├── package.json ├── project ├── TestSuiteLinkerOptions.scala ├── WasmLinkerPlugin.scala ├── build.properties └── plugins.sbt ├── run.mjs ├── sample └── src │ ├── main │ └── scala │ │ └── Sample.scala │ └── test │ └── scala │ └── sample │ └── SampleTest.scala ├── scalajs-test-suite └── src │ ├── main │ └── scala │ │ └── org │ │ └── scalajs │ │ └── testsuite │ │ └── utils │ │ └── BuildInfo.scala │ └── test │ └── scala │ └── org │ └── scalajs │ └── testsuite │ └── jsinterop │ ├── TopLevelExportsTest.scala │ └── WasmExportLoopback.scala ├── test-suite └── src │ └── main │ └── scala │ └── testsuite │ ├── Assert.scala │ └── core │ ├── Add.scala │ ├── ArrayReflectTest.scala │ ├── ArrayTest.scala │ ├── AsInstanceOfTest.scala │ ├── BasicListTest.scala │ ├── ClassOfTest.scala │ ├── CloneTest.scala │ ├── ClosureTest.scala │ ├── ControlStructuresTest.scala │ ├── FieldsTest.scala │ ├── FloatingPointRemTest.scala │ ├── GetClassTest.scala │ ├── HijackedClassesDispatchTest.scala │ ├── HijackedClassesMonoTest.scala │ ├── HijackedClassesUpcastTest.scala │ ├── ImportTest.scala │ ├── InterfaceCall.scala │ ├── IsInstanceOfTest.scala │ ├── JLObjectInstanceTest.scala │ ├── JSExportTopLevelTest.scala │ ├── JSForInTest.scala │ ├── JSImportCallTest.scala │ ├── JSImportMetaTest.scala │ ├── JSInteropTest.scala │ ├── MatchTest.scala │ ├── NonNativeJSClassTest.scala │ ├── Simple.scala │ ├── StaticFieldsTest.scala │ ├── StaticMethodTest.scala │ ├── StringEncodingTest.scala │ ├── ThrowAndTryTest.scala │ ├── ThrowablesTest.scala │ ├── ToStringTest.scala │ ├── TryFinallyReturnTest.scala │ ├── UnitPatMatTest.scala │ ├── VirtualDispatch.scala │ └── WrapUnwrapThrowableTest.scala ├── testrun.html ├── tests └── src │ └── test │ └── scala │ └── tests │ ├── CoreTests.scala │ └── TestSuites.scala └── wasm └── src └── main └── scala └── org └── scalajs └── linker ├── backend ├── WebAssemblyLinkerBackend.scala ├── wasmemitter │ ├── ClassEmitter.scala │ ├── CoreWasmLib.scala │ ├── EmbeddedConstants.scala │ ├── Emitter.scala │ ├── FunctionEmitter.scala │ ├── LibraryPatches.scala │ ├── LoaderContent.scala │ ├── Preprocessor.scala │ ├── SWasmGen.scala │ ├── SpecialNames.scala │ ├── TypeTransformer.scala │ ├── VarGen.scala │ └── WasmContext.scala └── webassembly │ ├── BinaryWriter.scala │ ├── FunctionBuilder.scala │ ├── Identitities.scala │ ├── Instructions.scala │ ├── ModuleBuilder.scala │ ├── Modules.scala │ ├── TextWriter.scala │ └── Types.scala └── standard ├── WebAssemblyLinkerImpl.scala └── WebAssemblyStandardLinkerImpl.scala /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-java@v4 17 | with: 18 | distribution: 'temurin' 19 | java-version: '21' 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: '22' 23 | - name: npm install 24 | run: npm install 25 | - name: Tests 26 | run: sbt tests/test 27 | - name: Run the Sample 28 | run: sbt sample/run 29 | - name: Test the Sample 30 | run: sbt sample/test 31 | 32 | # Scala.js test suite - separated in multiple steps to see at a glance where things fail 33 | - name: Fetch Scala.js sources for the test suite 34 | run: sbt scalajs-test-suite/fetchScalaJSSource 35 | - name: Compile the Scala.js test suite 36 | run: sbt scalajs-test-suite/Test/compile 37 | - name: Link the Scala.js test suite 38 | run: sbt scalajs-test-suite/Test/fastLinkJS 39 | - name: Run the Scala.js test suite 40 | run: sbt scalajs-test-suite/test 41 | - name: Link and run the Scala.js test suite with fullLinkJS 42 | run: sbt 'set Global/scalaJSStage := FullOptStage' scalajs-test-suite/test 43 | 44 | # Make sure we can emit the .wat file without crashing 45 | - name: Link the Scala.js test suite with PrettyPrint 46 | run: sbt 'set `scalajs-test-suite`/scalaJSLinkerConfig ~= { _.withPrettyPrint(true) }' scalajs-test-suite/Test/fastLinkJS 47 | 48 | - name: Format 49 | run: sbt scalafmtCheckAll 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # sbt specific 5 | dist/* 6 | target/ 7 | lib_managed/ 8 | src_managed/ 9 | project/boot/ 10 | project/plugins/project/ 11 | project/local-plugins.sbt 12 | .history 13 | .ensime 14 | .ensime_cache/ 15 | .sbt-scripted/ 16 | local.sbt 17 | 18 | # Bloop 19 | .bsp 20 | 21 | # VS Code 22 | .vscode/ 23 | 24 | # Metals 25 | .bloop/ 26 | .metals/ 27 | metals.sbt 28 | 29 | # IDEA 30 | .idea 31 | .idea_modules 32 | /.worksheet/ 33 | 34 | node_modules 35 | 36 | /scalajs-test-suite/fetched-sources/ 37 | -------------------------------------------------------------------------------- /.sbtopts: -------------------------------------------------------------------------------- 1 | -J-Xmx2G 2 | -J-Xms512M 3 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "3.7.15" 2 | runner.dialect = scala213 3 | maxColumn = 100 4 | literals.hexDigits = "Upper" 5 | 6 | project.excludeFilters = [ 7 | // to avoid "invalid unicode surrogate pair" 8 | "/test-suite/src/main/scala/testsuite/core/StringEncodingTest.scala" 9 | 10 | // imported sources 11 | "/scalajs-test-suite/fetched-sources/.*" 12 | ] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Experimental Scala.js linker backend for WasmGC 2 | 3 | [](https://github.com/tanishiking/scala-wasm/actions/workflows/ci.yml) 4 | 5 | ### Prerequisites 6 | 7 | This project requires Node.js >= 22 to get enough support of WasmGC. 8 | 9 | If you are using NVM, you can instal Node.js 22 as follows: 10 | 11 | ```sh 12 | # Install Node.js v22 13 | $ nvm install 22 14 | # Switch to Node.js 22 15 | $ nvm use 22 16 | ``` 17 | 18 | ### Setup 19 | 20 | Before doing anything else, run `npm install`. 21 | 22 | ### Run the sample 23 | 24 | In `sample/src/main/scala/Sample.scala` you can find a sandbox to play around. 25 | 26 | You can build and run it like any other Scala.js project from sbt: 27 | 28 | - `sample/fastLinkJS` compiles and links the project with the WebAssembly backend. 29 | - `sample/run` runs the sample linked to WebAssembly with `node`. 30 | 31 | You may want to look at the output in `sample/target/scala-2.12/sample-fastopt/` to convince yourself that it was compiled to WebAssembly. 32 | 33 | In that directory, you will also find a `main.wat` file, which is not used for execution. 34 | It contains the WebAsembly Text Format representation of `main.wasm`, for exploratory and debugging purposes. 35 | This is only true by default for the `sample`. 36 | 37 | :warning: If you modify the linker code, you need to `reload` and `sample/clean` for your changes to take effect on the sample. 38 | 39 | You can also use the `run.mjs` script to play with `@JSExportTopLevel` exports. 40 | 41 | - Run from the command line with `node --experimental-wasm-exnref run.mjs`. 42 | - Run from the command line with `DENO_V8_FLAGS=--experimental-wasm-exnref deno run --allow-read run.mjs`. 43 | - Run from the browser by starting an HTTP server (e.g., `python -m http.server`) and navigate to `testrun.html`. 44 | 45 | If you encounter the `Invalid opcode 0x1f` error with Node.js, you need to use a Node.js >= 22. 46 | 47 | ### Unit test suite 48 | 49 | Run the unit test suite with `tests/test`. 50 | 51 | - `tests/test` will 52 | - Link every test suite from `testSuite` with the WebAssembly backend 53 | - Run the produced WebAssembly module and check for any uncaught error 54 | - Each Scala program in `test-suite` should have a `def main(): Unit` function. The test passes if the function successfully executes without throwing. 55 | - When you add a test, 56 | - Add a file under `test-suite` 57 | - Add a test case to `tests/src/test/scala/tests/TestSuites.scala` 58 | 59 | By default, `.wat` files are not generated, as they are quite big (several hundreds of KB for most of the tests). 60 | You can enable them by adding `withPrettyPrint(true)` to the linker configuration in `tests/src/test/scala/tests/CoreTests.scala`. 61 | 62 | ### Scala.js integration test suite 63 | 64 | Run the entire Scala.js test suite, linked with the WebAssembly backend, with: 65 | 66 | ``` 67 | > scalajs-test-suite/test 68 | ``` 69 | 70 | When you modify the linker, you need to `reload` and `scalajs-test-suite/clean` for your changes to take effect. 71 | Since recompiling the test suite from scratch every time is slow, you can replace the `clean` by manually removing only the linker output with: 72 | 73 | ``` 74 | $ rm -r scalajs-test-suite/target/scala-2.12/scalajs-test-suite-test-fastopt/ 75 | ``` 76 | 77 | By default, `.wat` files are not generated for the Scala.js test suite, as they are very big (they exceed 100 MB). 78 | It is usually easier to minimize an issue in `sample/test`, but if you really want the big `.wat` file for the Scala.js test suite, you can enable it with 79 | 80 | ```scala 81 | > set `scalajs-test-suite`/scalaJSLinkerConfig ~= { _.withPrettyPrint(true) } 82 | ``` 83 | 84 | ### Debugging tools 85 | 86 | - The WasmGC reference interpreter can be used to validate and convert between the binary and text form: 87 | - https://github.com/WebAssembly/gc/tree/main/interpreter 88 | - Use docker image for it https://github.com/tanishiking/wasmgc-docker 89 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import org.scalajs.jsenv.nodejs.NodeJSEnv 2 | 3 | import org.scalajs.linker.interface.ESVersion 4 | import org.scalajs.linker.interface.OutputPatterns 5 | 6 | val scalaV = "2.12.19" 7 | 8 | val fetchScalaJSSource = taskKey[File]("Fetches the source code of Scala.js") 9 | 10 | val writePackageJSON = taskKey[Unit]( 11 | "Write package.json to configure module type for Node.js" 12 | ) 13 | 14 | // Include wasm.jvm on the classpath used to dynamically load Scala.js linkers 15 | Global / scalaJSLinkerImpl / fullClasspath := 16 | (wasm.jvm / Compile / fullClasspath).value 17 | 18 | inThisBuild( 19 | Def.settings( 20 | scalacOptions ++= Seq( 21 | "-encoding", 22 | "utf-8", 23 | "-feature", 24 | "-deprecation", 25 | "-Xfatal-warnings" 26 | ), 27 | scalaJSLinkerConfig ~= { 28 | _.withESFeatures(_.withESVersion(ESVersion.ES2016)) 29 | }, 30 | jsEnv := { 31 | // Enable support for exnref and try_table 32 | new NodeJSEnv( 33 | NodeJSEnv.Config().withArgs(List("--enable-source-maps", "--experimental-wasm-exnref")) 34 | ) 35 | } 36 | ) 37 | ) 38 | 39 | lazy val wasm = crossProject(JVMPlatform, JSPlatform) 40 | .crossType(CrossType.Pure) 41 | .in(file("wasm")) 42 | .settings( 43 | name := "wasm", 44 | version := "0.1.0-SNAPSHOT", 45 | scalaVersion := scalaV, 46 | libraryDependencies ++= Seq( 47 | "org.scala-js" %%% "scalajs-linker" % "1.16.0" 48 | ) 49 | ) 50 | 51 | lazy val sample = project 52 | .in(file("sample")) 53 | .enablePlugins(WasmLinkerPlugin, ScalaJSJUnitPlugin) 54 | .settings( 55 | scalaVersion := scalaV, 56 | scalaJSUseMainModuleInitializer := true, 57 | // Emit .wat files for exploratory and debugging purposes 58 | scalaJSLinkerConfig ~= { _.withPrettyPrint(true) }, 59 | ) 60 | 61 | lazy val testSuite = project 62 | .in(file("test-suite")) 63 | .enablePlugins(ScalaJSPlugin) 64 | .settings( 65 | scalaVersion := scalaV, 66 | scalaJSUseMainModuleInitializer := true, 67 | scalacOptions -= "-Xfatal-warnings", // for unpaired surrogate code units in StringEncodingTest.scala 68 | ) 69 | 70 | lazy val tests = project 71 | .in(file("tests")) 72 | .enablePlugins(ScalaJSPlugin) 73 | .settings( 74 | scalaVersion := scalaV, 75 | libraryDependencies ++= Seq( 76 | "org.scalameta" %%% "munit" % "0.7.29" % Test, 77 | "org.scala-js" %%% "scala-js-macrotask-executor" % "1.1.1" % Test 78 | ), 79 | scalaJSLinkerConfig ~= { 80 | // Generate CoreTests as an ES module so that it can import the main.mjs files 81 | // Give it an `.mjs` extension so that Node.js actually interprets it as an ES module 82 | _.withModuleKind(ModuleKind.ESModule) 83 | .withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")), 84 | }, 85 | envVars += { 86 | val testSuiteClasspath = Attributed.data((testSuite / Compile / fullClasspath).value) 87 | "SCALAJS_CLASSPATH" -> testSuiteClasspath.mkString(";") 88 | }, 89 | ) 90 | .dependsOn(wasm.js) 91 | 92 | lazy val `scalajs-test-suite` = project 93 | .in(file("scalajs-test-suite")) 94 | .enablePlugins(WasmLinkerPlugin, ScalaJSJUnitPlugin) 95 | .settings( 96 | fetchScalaJSSource / artifactPath := 97 | baseDirectory.value / "fetched-sources" / scalaJSVersion, 98 | 99 | fetchScalaJSSource := { 100 | import org.eclipse.jgit.api._ 101 | 102 | val s = streams.value 103 | val ver = scalaJSVersion 104 | val trgDir = (fetchScalaJSSource / artifactPath).value 105 | 106 | if (!trgDir.exists) { 107 | s.log.info(s"Fetching Scala.js source version $ver") 108 | 109 | // Make parent dirs 110 | IO.createDirectory(trgDir) 111 | 112 | // Clone Scala.js source code 113 | new CloneCommand() 114 | .setDirectory(trgDir) 115 | .setURI("https://github.com/scala-js/scala-js.git") 116 | .call() 117 | } 118 | 119 | // Checkout the proper ref. We do this anyway so we fail if something is wrong. 120 | val git = Git.open(trgDir) 121 | s.log.info(s"Checking out Scala.js source version $ver") 122 | git.checkout().setName(s"v$ver").call() 123 | 124 | trgDir 125 | }, 126 | 127 | // We need them in `main` as well as in `test` 128 | libraryDependencies += "org.scala-js" %% "scalajs-junit-test-runtime" % scalaJSVersion, 129 | 130 | scalacOptions --= Seq("-deprecation", "-Xfatal-warnings"), 131 | 132 | Test / unmanagedResourceDirectories ++= { 133 | val base = (fetchScalaJSSource / artifactPath).value 134 | val testDir = base / "test-suite/js/src/test" 135 | 136 | scalaJSLinkerConfig.value.moduleKind match { 137 | case ModuleKind.NoModule => Nil 138 | case ModuleKind.CommonJSModule => Seq(testDir / "resources-commonjs") 139 | case ModuleKind.ESModule => Seq(testDir / "resources-esmodule") 140 | } 141 | }, 142 | 143 | Compile / unmanagedSourceDirectories ++= { 144 | val base = (fetchScalaJSSource / artifactPath).value 145 | Seq( 146 | base / "junit-async/js/src/main/scala", 147 | base / "test-suite/shared/src/main/scala", 148 | base / "test-suite/js/src/main/scala", 149 | ) 150 | }, 151 | 152 | Compile / unmanagedSources := (Compile / unmanagedSources).dependsOn(fetchScalaJSSource).value, 153 | Test / unmanagedSources := (Test / unmanagedSources).dependsOn(fetchScalaJSSource).value, 154 | 155 | Compile / sources ~= { sources => 156 | sources 157 | .filter(_.getName != "TypecheckingMacros.scala") 158 | .filter(_.getName != "Typechecking.scala") 159 | }, 160 | 161 | Test / unmanagedSourceDirectories ++= { 162 | val base = (fetchScalaJSSource / artifactPath).value 163 | Seq( 164 | base / "test-suite/shared/src/test/scala/", 165 | base / "test-suite/shared/src/test/require-scala2/", 166 | base / "test-suite/shared/src/test/scala-old-collections", 167 | 168 | base / "test-suite/js/src/test/require-dynamic-import", 169 | base / "test-suite/js/src/test/esmodule", 170 | base / "test-suite/js/src/test/require-exponent-op", 171 | base / "test-suite/js/src/test/require-modules", 172 | base / "test-suite/js/src/test/require-new-target", 173 | base / "test-suite/js/src/test/require-scala2", 174 | base / "test-suite/js/src/test/scala", 175 | base / "test-suite/js/src/test/scala-old-collections", 176 | ) 177 | }, 178 | 179 | // Remove sources of tests that cause linking/validating to fail 180 | Test / sources := { 181 | def endsWith(f: File, suffix: String): Boolean = 182 | f.getPath().replace('\\', '/').endsWith(suffix) 183 | 184 | (Test / sources).value 185 | .filterNot(endsWith(_, "/UnionTypeTest.scala")) // requires typechecking macros 186 | .filterNot(endsWith(_, "/jsinterop/ExportsTest.scala")) // js.dynamicImport (multi-modules) 187 | .filterNot(endsWith(_, "/ExportLoopback.scala")) 188 | }, 189 | 190 | Test / scalacOptions += "-P:scalajs:genStaticForwardersForNonTopLevelObjects", 191 | Test / scalacOptions += "-P:scalajs:nowarnGlobalExecutionContext", 192 | 193 | scalaJSLinkerConfig ~= { _.withSemantics(build.TestSuiteLinkerOptions.semantics _) }, 194 | Test / scalaJSModuleInitializers ++= build.TestSuiteLinkerOptions.moduleInitializers, 195 | 196 | scalaJSLinkerConfig ~= { _.withESFeatures(_.withESVersion(ESVersion.ES2016)) }, 197 | 198 | Test / jsEnvInput := { 199 | val base = fetchScalaJSSource.value 200 | val f = (base / "test-suite/js/src/test/resources/NonNativeJSTypeTestNatives.js").toPath 201 | val resourcesInput = org.scalajs.jsenv.Input.Script(f) 202 | 203 | resourcesInput +: (Test / jsEnvInput).value 204 | }, 205 | 206 | // Write package.json inside target/ to automatically configure the right module type 207 | Compile / jsEnvInput := (Compile / jsEnvInput).dependsOn(writePackageJSON).value, 208 | Test / jsEnvInput := (Test / jsEnvInput).dependsOn(writePackageJSON).value, 209 | writePackageJSON := { 210 | val packageType = scalaJSLinkerConfig.value.moduleKind match { 211 | case ModuleKind.NoModule => "commonjs" 212 | case ModuleKind.CommonJSModule => "commonjs" 213 | case ModuleKind.ESModule => "module" 214 | } 215 | val path = target.value / "package.json" 216 | IO.write(path, s"""{"type": "$packageType"}\n""") 217 | }, 218 | 219 | Test / testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-s"), 220 | 221 | // Ignore individual tests that do not pass 222 | Test / testOptions += Tests.Filter({ testName => 223 | !IgnoredTestNames.contains(testName) 224 | }), 225 | ) 226 | 227 | lazy val IgnoredTestNames: Set[String] = { 228 | Set( 229 | // Cannot call wasmObject.toString() from JavaScript: 230 | // boxValueClassesGivenToJSInteropMethod failed: scala.scalajs.js.JavaScriptException: TypeError: vc.toString is not a function 231 | "org.scalajs.testsuite.compiler.InteroperabilityTest", 232 | // TypeError: WebAssembly objects are opaque 233 | "org.scalajs.testsuite.javalib.lang.SystemJSTest", 234 | // throwablesAreTrueErrors failed: org.junit.ComparisonFailure: expected:<[object [Error]]> but was:<[object [Object]]> 235 | // throwablesAreJSErrors failed: java.lang.AssertionError: null 236 | "org.scalajs.testsuite.javalib.lang.ThrowableJSTest", 237 | // jsError/jsObject failed: AssertionError because Wasm objects are not instanceof Error/Object 238 | "org.scalajs.testsuite.compiler.RuntimeTypeTestsJSTest", 239 | // No support for stack traces 240 | "org.scalajs.testsuite.library.StackTraceTest", 241 | ) 242 | } 243 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scala-wasm", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "fs": "^0.0.1-security", 9 | "http": "^0.0.1-security", 10 | "jszip": "^3.5.0" 11 | } 12 | }, 13 | "node_modules/core-util-is": { 14 | "version": "1.0.3", 15 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 16 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" 17 | }, 18 | "node_modules/fs": { 19 | "version": "0.0.1-security", 20 | "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", 21 | "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" 22 | }, 23 | "node_modules/http": { 24 | "version": "0.0.1-security", 25 | "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz", 26 | "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==" 27 | }, 28 | "node_modules/immediate": { 29 | "version": "3.0.6", 30 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", 31 | "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" 32 | }, 33 | "node_modules/inherits": { 34 | "version": "2.0.4", 35 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 36 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 37 | }, 38 | "node_modules/isarray": { 39 | "version": "1.0.0", 40 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 41 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" 42 | }, 43 | "node_modules/jszip": { 44 | "version": "3.10.1", 45 | "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", 46 | "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", 47 | "dependencies": { 48 | "lie": "~3.3.0", 49 | "pako": "~1.0.2", 50 | "readable-stream": "~2.3.6", 51 | "setimmediate": "^1.0.5" 52 | } 53 | }, 54 | "node_modules/lie": { 55 | "version": "3.3.0", 56 | "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", 57 | "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", 58 | "dependencies": { 59 | "immediate": "~3.0.5" 60 | } 61 | }, 62 | "node_modules/pako": { 63 | "version": "1.0.11", 64 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", 65 | "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" 66 | }, 67 | "node_modules/process-nextick-args": { 68 | "version": "2.0.1", 69 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 70 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 71 | }, 72 | "node_modules/readable-stream": { 73 | "version": "2.3.8", 74 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 75 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 76 | "dependencies": { 77 | "core-util-is": "~1.0.0", 78 | "inherits": "~2.0.3", 79 | "isarray": "~1.0.0", 80 | "process-nextick-args": "~2.0.0", 81 | "safe-buffer": "~5.1.1", 82 | "string_decoder": "~1.1.1", 83 | "util-deprecate": "~1.0.1" 84 | } 85 | }, 86 | "node_modules/safe-buffer": { 87 | "version": "5.1.2", 88 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 89 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 90 | }, 91 | "node_modules/setimmediate": { 92 | "version": "1.0.5", 93 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", 94 | "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" 95 | }, 96 | "node_modules/string_decoder": { 97 | "version": "1.1.1", 98 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 99 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 100 | "dependencies": { 101 | "safe-buffer": "~5.1.0" 102 | } 103 | }, 104 | "node_modules/util-deprecate": { 105 | "version": "1.0.2", 106 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 107 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "fs": "^0.0.1-security", 4 | "http": "^0.0.1-security", 5 | "jszip": "^3.5.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /project/TestSuiteLinkerOptions.scala: -------------------------------------------------------------------------------- 1 | package build 2 | 3 | import org.scalajs.linker.interface._ 4 | 5 | object TestSuiteLinkerOptions { 6 | 7 | def semantics(s: Semantics): Semantics = { 8 | import Semantics.RuntimeClassNameMapper 9 | 10 | s.withRuntimeClassNameMapper( 11 | RuntimeClassNameMapper.keepAll().andThen( 12 | RuntimeClassNameMapper.regexReplace( 13 | raw"""^org\.scalajs\.testsuite\.compiler\.ReflectionTest\$$RenamedTestClass$$""".r, 14 | "renamed.test.Class") 15 | ).andThen( 16 | RuntimeClassNameMapper.regexReplace( 17 | raw"""^org\.scalajs\.testsuite\.compiler\.ReflectionTest\$$Prefix""".r, 18 | "renamed.test.byprefix.") 19 | ).andThen( 20 | RuntimeClassNameMapper.regexReplace( 21 | raw"""^org\.scalajs\.testsuite\.compiler\.ReflectionTest\$$OtherPrefix""".r, 22 | "renamed.test.byotherprefix.") 23 | ) 24 | ) 25 | } 26 | 27 | def moduleInitializers: List[ModuleInitializer] = { 28 | val module = "org.scalajs.testsuite.compiler.ModuleInitializers" 29 | List( 30 | ModuleInitializer.mainMethod(module, "mainNoArgs"), 31 | ModuleInitializer.mainMethodWithArgs(module, "mainWithArgs"), 32 | ModuleInitializer.mainMethodWithArgs(module, "mainWithArgs", List("foo", "bar")), 33 | ModuleInitializer.mainMethod(module + "$NoLinkedClass", "main"), 34 | ModuleInitializer.mainMethod(module + "$WithLinkedClass", "main") 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /project/WasmLinkerPlugin.scala: -------------------------------------------------------------------------------- 1 | package build 2 | 3 | import sbt._ 4 | import sbt.Keys._ 5 | 6 | import org.scalajs.linker._ 7 | import org.scalajs.linker.interface.{ModuleKind, _} 8 | 9 | import org.scalajs.sbtplugin._ 10 | import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ 11 | 12 | object WasmLinkerPlugin extends AutoPlugin { 13 | override lazy val requires = ScalaJSPlugin 14 | 15 | /** A `LinkerImpl` that reflectively loads our `WebAssemblyLinkerImpl`. */ 16 | private class WasmLinkerImpl(base: LinkerImpl.Reflect) 17 | extends LinkerImpl.Forwarding(base) { 18 | 19 | private val loader = base.loader 20 | 21 | private val clearableLinkerMethod = { 22 | Class.forName("org.scalajs.linker.standard.WebAssemblyLinkerImpl", true, loader) 23 | .getMethod("clearableLinker", classOf[StandardConfig]) 24 | } 25 | 26 | override def clearableLinker(config: StandardConfig): ClearableLinker = 27 | clearableLinkerMethod.invoke(null, config).asInstanceOf[ClearableLinker] 28 | } 29 | 30 | override def projectSettings: Seq[Setting[_]] = Def.settings( 31 | // Use a separate cache box for the LinkerImpl in this project (don't use the Global one) 32 | scalaJSLinkerImplBox := new CacheBox, 33 | 34 | // Use our custom WasmLinkerImpl as the linker implementation used by fast/fullLinkJS 35 | scalaJSLinkerImpl := { 36 | val cp = (scalaJSLinkerImpl / fullClasspath).value 37 | scalaJSLinkerImplBox.value.ensure { 38 | new WasmLinkerImpl(LinkerImpl.reflect(Attributed.data(cp))) 39 | } 40 | }, 41 | 42 | // Automatically install all the configs required by the Wasm backend 43 | scalaJSLinkerConfig ~= { prev => 44 | prev 45 | .withModuleKind(ModuleKind.ESModule) 46 | .withSemantics(_.optimized) 47 | .withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")) 48 | .withOptimizer(false) 49 | }, 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.9.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") 2 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") 3 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") 4 | 5 | libraryDependencies += "org.eclipse.jgit" % "org.eclipse.jgit.pgm" % "3.2.0.201312181205-r" 6 | -------------------------------------------------------------------------------- /run.mjs: -------------------------------------------------------------------------------- 1 | import { test, field } from "./sample/target/scala-2.12/sample-fastopt/main.mjs"; 2 | 3 | console.log(field); 4 | const o = test(7); 5 | console.log(o); 6 | console.log(field); 7 | -------------------------------------------------------------------------------- /sample/src/main/scala/Sample.scala: -------------------------------------------------------------------------------- 1 | package sample 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation._ 5 | 6 | object Main { 7 | @JSExportTopLevel("field") 8 | var exportedField: Int = 42 9 | 10 | @JSExportTopLevel("test") 11 | def test(i: Int): Boolean = { 12 | println("Hello") 13 | exportedField = 53 14 | println(exportedField) 15 | true 16 | } 17 | 18 | def main(args: Array[String]): Unit = { 19 | println("hello world") 20 | } 21 | 22 | // Tested in SampleTest.scala 23 | def square(x: Int): Int = x * x 24 | 25 | private def println(x: Any): Unit = 26 | js.Dynamic.global.console.log("" + x) 27 | } 28 | -------------------------------------------------------------------------------- /sample/src/test/scala/sample/SampleTest.scala: -------------------------------------------------------------------------------- 1 | package sample 2 | 3 | import org.junit.Assert._ 4 | import org.junit.Test 5 | 6 | class SampleTest { 7 | @Test 8 | def testSquare(): Unit = { 9 | assertEquals(16, Main.square(4)) 10 | 11 | // Uncomment to get a test failure 12 | // assertEquals(16, Main.square(5)) 13 | } 14 | 15 | @Test 16 | def testException(): Unit = { 17 | assertThrows( 18 | classOf[IllegalArgumentException], 19 | { () => 20 | throwIllegalArgument() 21 | } 22 | ) 23 | 24 | // Uncomment to get a test failure 25 | /*assertThrows( 26 | classOf[UnsupportedOperationException], 27 | { () => 28 | throwIllegalArgument() 29 | } 30 | )*/ 31 | } 32 | 33 | def throwIllegalArgument(): Unit = 34 | throw new IllegalArgumentException() 35 | } 36 | -------------------------------------------------------------------------------- /scalajs-test-suite/src/main/scala/org/scalajs/testsuite/utils/BuildInfo.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.testsuite.utils 2 | 3 | private[utils] object BuildInfo { 4 | final val scalaVersion = "2.12.19" 5 | final val hasSourceMaps = false 6 | final val isNoModule = false 7 | final val isESModule = true 8 | final val isCommonJSModule = false 9 | final val usesClosureCompiler = false 10 | final val hasMinifiedNames = true 11 | final val compliantAsInstanceOfs = false 12 | final val compliantArrayIndexOutOfBounds = false 13 | final val compliantArrayStores = false 14 | final val compliantNegativeArraySizes = false 15 | final val compliantNullPointers = false 16 | final val compliantStringIndexOutOfBounds = false 17 | final val compliantModuleInit = false 18 | final val strictFloats = true 19 | final val productionMode = false 20 | final val esVersion = 6 21 | final val useECMAScript2015Semantics = true 22 | } 23 | -------------------------------------------------------------------------------- /scalajs-test-suite/src/test/scala/org/scalajs/testsuite/jsinterop/WasmExportLoopback.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala.js (https://www.scala-js.org/) 3 | * 4 | * Copyright EPFL. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (https://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package org.scalajs.testsuite.jsinterop 14 | 15 | import scala.scalajs.js 16 | import scala.scalajs.js.annotation._ 17 | 18 | import scala.concurrent.Future 19 | 20 | object ExportLoopback { 21 | val exportsNamespace: Future[js.Dynamic] = 22 | js.`import`[js.Dynamic]("./main.mjs").toFuture 23 | } 24 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/Assert.scala: -------------------------------------------------------------------------------- 1 | package testsuite 2 | 3 | /** Assertion helpers. */ 4 | object Assert { 5 | def ok(cond: Boolean): Unit = 6 | if (!cond) fail() 7 | 8 | def assertSame(expected: Any, actual: Any): Unit = 9 | ok(expected.asInstanceOf[AnyRef] eq actual.asInstanceOf[AnyRef]) 10 | 11 | def fail(): Unit = 12 | throw new AssertionError("assertion failed") 13 | } 14 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/Add.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object Add { 6 | def main(): Unit = { 7 | ok(1 + 1 == 2) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ArrayReflectTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object ArrayReflectTest { 6 | def main(): Unit = { 7 | testSimpleNewInstance() 8 | testMultiNewInstance() 9 | } 10 | 11 | private def testSimpleNewInstance(): Unit = { 12 | val a = java.lang.reflect.Array.newInstance(classOf[Int], 5) 13 | ok(a.isInstanceOf[Array[Int]]) 14 | assertSame(classOf[Array[Int]], a.getClass()) 15 | val a1 = a.asInstanceOf[Array[Int]] 16 | assertSame(5, a1.length) 17 | assertSame(0, a1(0)) 18 | 19 | val b = java.lang.reflect.Array.newInstance(classOf[String], 5) 20 | ok(b.isInstanceOf[Array[String]]) 21 | assertSame(classOf[Array[String]], b.getClass()) 22 | val b1 = b.asInstanceOf[Array[String]] 23 | assertSame(5, b1.length) 24 | assertSame(null, b1(0)) 25 | 26 | val c = java.lang.reflect.Array.newInstance(classOf[Array[Object]], 5) 27 | ok(c.isInstanceOf[Array[Array[Object]]]) 28 | assertSame(classOf[Array[Array[Object]]], c.getClass()) 29 | val c1 = c.asInstanceOf[Array[Array[Object]]] 30 | assertSame(5, c1.length) 31 | assertSame(null, c1(0)) 32 | } 33 | 34 | private def testMultiNewInstance(): Unit = { 35 | val a = java.lang.reflect.Array.newInstance(classOf[Int], 5, 10) 36 | ok(a.isInstanceOf[Array[Array[Int]]]) 37 | assertSame(classOf[Array[Array[Int]]], a.getClass()) 38 | val a1 = a.asInstanceOf[Array[Array[Int]]] 39 | assertSame(5, a1.length) 40 | ok(a1(3).isInstanceOf[Array[Int]]) 41 | val a2 = a1(3).asInstanceOf[Array[Int]] 42 | assertSame(10, a2.length) 43 | assertSame(0, a2(0)) 44 | ok(a1(1) ne a1(0)) 45 | 46 | val b = java.lang.reflect.Array.newInstance(classOf[String], 5, 10) 47 | ok(b.isInstanceOf[Array[Array[String]]]) 48 | assertSame(classOf[Array[Array[String]]], b.getClass()) 49 | val b1 = b.asInstanceOf[Array[Array[String]]] 50 | assertSame(5, b1.length) 51 | ok(b1(3).isInstanceOf[Array[String]]) 52 | val b2 = b1(3).asInstanceOf[Array[String]] 53 | assertSame(10, b2.length) 54 | assertSame(null, b2(0)) 55 | ok(b1(1) ne b1(0)) 56 | 57 | val c = java.lang.reflect.Array.newInstance(classOf[Array[Object]], 5, 10) 58 | ok(c.isInstanceOf[Array[Array[Array[Object]]]]) 59 | assertSame(classOf[Array[Array[Array[Object]]]], c.getClass()) 60 | val c1 = c.asInstanceOf[Array[Array[Array[Object]]]] 61 | assertSame(5, c1.length) 62 | ok(c1(3).isInstanceOf[Array[Array[Object]]]) 63 | val c2 = c1(3).asInstanceOf[Array[Array[Object]]] 64 | assertSame(10, c2.length) 65 | assertSame(null, c2(0)) 66 | ok(c1(1) ne c1(0)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ArrayTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert 4 | 5 | object ArrayTest { 6 | def main(): Unit = { 7 | Assert.ok( 8 | testLength() && testSelect() && testNew() 9 | ) 10 | } 11 | 12 | def testLength(): Boolean = { 13 | Array(1, 2, 3).length == 3 && 14 | (Array(Array(1, 2), Array(2), Array(3))).length == 3 15 | } 16 | 17 | def testSelect(): Boolean = { 18 | val a = Array(Array(1), Array(2), Array(3)) 19 | a(0)(0) == 1 && { 20 | a(0)(0) = 100 // Assign(ArraySelect(...), ...) 21 | a(0)(0) == 100 // ArraySelect(...) 22 | } && { 23 | a(1) = Array(1, 2, 3) 24 | a(1).length == 3 && a(1)(0) == 1 25 | } 26 | } 27 | 28 | def testNew(): Boolean = { 29 | (Array.emptyBooleanArray.length == 0) && 30 | (new Array[Int](10)).length == 10 && 31 | (new Array[Int](1))(0) == 0 && 32 | (new Array[Array[Array[Int]]](5))(0) == null 33 | } 34 | 35 | // TODO: Array.ofDim[T](...) 36 | } 37 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/AsInstanceOfTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object AsInstanceOfTest { 6 | def main(): Unit = { 7 | ok( 8 | testInt(5) && 9 | testClasses(new Child()) && 10 | testString("foo", true) 11 | ) 12 | } 13 | 14 | def testClasses(c: Child): Boolean = { 15 | val c1 = c.asInstanceOf[Child] 16 | val c2 = c.asInstanceOf[Parent] 17 | c1.foo() == 5 && c2.foo() == 5 18 | } 19 | 20 | def testInt(x: Int): Boolean = { 21 | val x1 = x.asInstanceOf[Int] 22 | x1 == 5 23 | } 24 | 25 | def testString(s: String, b: Boolean): Boolean = { 26 | val s1 = s.asInstanceOf[String] 27 | val s2 = ("" + b).asInstanceOf[String] 28 | s1.length() == 3 && s2.length() == 4 29 | } 30 | 31 | class Parent { 32 | def foo(): Int = 5 33 | } 34 | class Child extends Parent 35 | } 36 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/BasicListTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object BasicListTest { 6 | def main(): Unit = { 7 | val l = List(5, 13, 29) 8 | val x = l.tail.head 9 | assertSame(13, x) 10 | 11 | val l2 = l.map(x => x * 2) 12 | assertSame(3, l2.size) 13 | assertSame(26, l2(1)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ClassOfTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import java.io.Serializable 4 | import java.lang.Cloneable 5 | 6 | import testsuite.Assert._ 7 | import testsuite.Assert 8 | 9 | object ClassOfTest { 10 | def main(): Unit = { 11 | testGetName() 12 | testUniqueness() 13 | testIsPrimitive() 14 | testIsInterface() 15 | testIsArray() 16 | testIsInstance() 17 | testIsAssignableFrom() 18 | testCast() 19 | testGetComponentType() 20 | } 21 | 22 | def testGetName(): Unit = { 23 | Assert.assertSame("java.lang.String", classOf[String].getName()) 24 | Assert.assertSame("java.lang.StringBuilder", classOf[java.lang.StringBuilder].getName()) 25 | Assert.assertSame("int", classOf[Int].getName()) 26 | Assert.assertSame("void", classOf[Unit].getName()) 27 | 28 | Assert.assertSame("[Ljava.lang.Object;", classOf[Array[AnyRef]].getName()) 29 | Assert.assertSame("[Ljava.lang.String;", classOf[Array[String]].getName()) 30 | Assert.assertSame("[[Ljava.lang.CharSequence;", classOf[Array[Array[CharSequence]]].getName()) 31 | 32 | Assert.assertSame("[Z", classOf[Array[Boolean]].getName()) 33 | Assert.assertSame("[C", classOf[Array[Char]].getName()) 34 | Assert.assertSame("[B", classOf[Array[Byte]].getName()) 35 | Assert.assertSame("[S", classOf[Array[Short]].getName()) 36 | Assert.assertSame("[I", classOf[Array[Int]].getName()) 37 | Assert.assertSame("[J", classOf[Array[Long]].getName()) 38 | Assert.assertSame("[F", classOf[Array[Float]].getName()) 39 | Assert.assertSame("[D", classOf[Array[Double]].getName()) 40 | } 41 | 42 | def testUniqueness(): Unit = { 43 | Assert.assertSame(classOf[String], classOf[String]) 44 | Assert.assertSame(classOf[java.lang.StringBuilder], classOf[java.lang.StringBuilder]) 45 | Assert.assertSame(classOf[CharSequence], classOf[CharSequence]) 46 | Assert.assertSame(classOf[Int], classOf[Int]) 47 | 48 | Assert.assertSame(classOf[Array[Int]], classOf[Array[Int]]) 49 | Assert.assertSame(classOf[Array[Array[java.lang.Byte]]], classOf[Array[Array[java.lang.Byte]]]) 50 | Assert.assertSame(classOf[Array[Array[Int]]], classOf[Array[Array[Int]]]) 51 | } 52 | 53 | def testIsPrimitive(): Unit = { 54 | Assert.assertSame(false, classOf[AnyRef].isPrimitive()) 55 | Assert.assertSame(false, classOf[String].isPrimitive()) 56 | Assert.assertSame(false, classOf[CharSequence].isPrimitive()) 57 | Assert.assertSame(false, classOf[Cloneable].isPrimitive()) 58 | Assert.assertSame(false, classOf[Serializable].isPrimitive()) 59 | Assert.assertSame(false, classOf[java.lang.Iterable[Any]].isPrimitive()) 60 | Assert.assertSame(false, classOf[Array[Int]].isPrimitive()) 61 | Assert.assertSame(false, classOf[Array[String]].isPrimitive()) 62 | Assert.assertSame(false, classOf[Array[CharSequence]].isPrimitive()) 63 | 64 | Assert.assertSame(true, classOf[Int].isPrimitive()) 65 | Assert.assertSame(true, classOf[Unit].isPrimitive()) 66 | } 67 | 68 | def testIsInterface(): Unit = { 69 | Assert.assertSame(false, classOf[AnyRef].isInterface()) 70 | Assert.assertSame(false, classOf[String].isInterface()) 71 | Assert.assertSame(false, classOf[Int].isInterface()) 72 | Assert.assertSame(false, classOf[Array[Int]].isInterface()) 73 | Assert.assertSame(false, classOf[Array[String]].isInterface()) 74 | Assert.assertSame(false, classOf[Array[CharSequence]].isInterface()) 75 | 76 | Assert.assertSame(true, classOf[CharSequence].isInterface()) 77 | Assert.assertSame(true, classOf[Cloneable].isInterface()) 78 | Assert.assertSame(true, classOf[Serializable].isInterface()) 79 | Assert.assertSame(true, classOf[java.lang.Iterable[Any]].isInterface()) 80 | } 81 | 82 | def testIsArray(): Unit = { 83 | Assert.assertSame(false, classOf[AnyRef].isArray()) 84 | Assert.assertSame(false, classOf[String].isArray()) 85 | Assert.assertSame(false, classOf[Int].isArray()) 86 | Assert.assertSame(false, classOf[CharSequence].isArray()) 87 | Assert.assertSame(false, classOf[Cloneable].isArray()) 88 | Assert.assertSame(false, classOf[Serializable].isArray()) 89 | Assert.assertSame(false, classOf[java.lang.Iterable[Any]].isArray()) 90 | 91 | Assert.assertSame(true, classOf[Array[Int]].isArray()) 92 | Assert.assertSame(true, classOf[Array[String]].isArray()) 93 | Assert.assertSame(true, classOf[Array[CharSequence]].isArray()) 94 | } 95 | 96 | def testIsInstance(): Unit = { 97 | def test(expected: Boolean, cls: Class[_], value: Any): Unit = 98 | assertSame(expected, cls.isInstance(value)) 99 | 100 | test(false, classOf[AnyRef], null) 101 | test(true, classOf[AnyRef], 5) 102 | test(true, classOf[AnyRef], 'A') 103 | test(true, classOf[AnyRef], "foo") 104 | test(true, classOf[AnyRef], ()) 105 | test(true, classOf[AnyRef], new AnyRef) 106 | test(true, classOf[AnyRef], new Parent) 107 | test(true, classOf[AnyRef], new Array[Int](1)) 108 | 109 | test(false, classOf[Comparable[_]], null) 110 | test(false, classOf[Comparable[_]], ()) 111 | test(true, classOf[Comparable[_]], 5) 112 | test(true, classOf[Comparable[_]], "foo") 113 | test(true, classOf[Comparable[_]], true) 114 | test(true, classOf[Comparable[_]], 6L) 115 | test(true, classOf[Comparable[_]], 'A') 116 | test(false, classOf[CharSequence], 5) 117 | test(true, classOf[CharSequence], "foo") 118 | test(false, classOf[CharSequence], 5L) 119 | test(false, classOf[CharSequence], 'A') 120 | 121 | test(false, classOf[Child], null) 122 | test(true, classOf[Parent], new Parent) 123 | test(true, classOf[Parent], new Child) 124 | test(false, classOf[Child], new Parent) 125 | test(true, classOf[Child], new Child) 126 | test(false, classOf[Child], 5) 127 | 128 | test(false, classOf[Interface], null) 129 | test(true, classOf[Interface], new Parent) 130 | test(false, classOf[OtherInterface], new Parent) 131 | test(true, classOf[Interface], new Child) 132 | test(true, classOf[OtherInterface], new Child) 133 | test(false, classOf[Interface], 5) 134 | 135 | test(false, classOf[Array[Int]], null) 136 | test(true, classOf[Array[Int]], new Array[Int](1)) 137 | test(false, classOf[Array[Int]], new Array[Byte](1)) 138 | test(true, classOf[Array[String]], new Array[String](1)) 139 | test(true, classOf[Array[Object]], new Array[String](1)) 140 | test(false, classOf[Array[Interface]], new Array[String](1)) 141 | 142 | test(true, classOf[Character], 'A') 143 | test(false, classOf[Character], 5) 144 | test(true, classOf[Integer], 5) 145 | test(false, classOf[Integer], 5.5) 146 | test(true, classOf[java.lang.Boolean], true) 147 | test(false, classOf[java.lang.Boolean], 5) 148 | test(true, classOf[java.lang.Void], ()) 149 | test(false, classOf[java.lang.Void], 5) 150 | } 151 | 152 | def testIsAssignableFrom(): Unit = { 153 | def testEquiv(cls1: Class[_], cls2: Class[_]): Unit = { 154 | assertSame(true, cls1.isAssignableFrom(cls2)) 155 | assertSame(true, cls2.isAssignableFrom(cls1)) 156 | } 157 | 158 | def testStrictSuper(cls1: Class[_], cls2: Class[_]): Unit = { 159 | assertSame(true, cls1.isAssignableFrom(cls2)) 160 | assertSame(false, cls2.isAssignableFrom(cls1)) 161 | } 162 | 163 | def testUnrelated(cls1: Class[_], cls2: Class[_]): Unit = { 164 | assertSame(false, cls1.isAssignableFrom(cls2)) 165 | assertSame(false, cls2.isAssignableFrom(cls1)) 166 | } 167 | 168 | // Same class is always assignable, including primitives and even void 169 | testEquiv(classOf[Int], classOf[Int]) 170 | testEquiv(classOf[Unit], classOf[Unit]) 171 | testEquiv(classOf[Object], classOf[Object]) 172 | testEquiv(classOf[String], classOf[String]) 173 | testEquiv(classOf[Comparable[_]], classOf[Comparable[_]]) 174 | testEquiv(classOf[Array[Int]], classOf[Array[Int]]) 175 | testEquiv(classOf[Interface], classOf[Interface]) 176 | testEquiv(classOf[OtherInterface], classOf[OtherInterface]) 177 | testEquiv(classOf[Parent], classOf[Parent]) 178 | testEquiv(classOf[Child], classOf[Child]) 179 | 180 | // Primitives are not assignable to/from anything elsething else 181 | testUnrelated(classOf[Int], classOf[Unit]) 182 | testUnrelated(classOf[Int], classOf[Double]) 183 | testUnrelated(classOf[Int], classOf[Object]) 184 | testUnrelated(classOf[Int], classOf[String]) 185 | testUnrelated(classOf[Int], classOf[Comparable[_]]) 186 | testUnrelated(classOf[Int], classOf[Cloneable]) 187 | testUnrelated(classOf[Int], classOf[Serializable]) 188 | 189 | // Everything non-primitive is assignable to Object, including arrays 190 | testStrictSuper(classOf[Object], classOf[String]) 191 | testStrictSuper(classOf[Object], classOf[Cloneable]) 192 | testStrictSuper(classOf[Object], classOf[Serializable]) 193 | testStrictSuper(classOf[Object], classOf[Comparable[_]]) 194 | testStrictSuper(classOf[Object], classOf[Array[Int]]) 195 | testStrictSuper(classOf[Object], classOf[Array[Array[String]]]) 196 | 197 | // Subclasses are assignable to superclasses 198 | testStrictSuper(classOf[Interface], classOf[OtherInterface]) 199 | testStrictSuper(classOf[Interface], classOf[Parent]) 200 | testStrictSuper(classOf[Interface], classOf[Child]) 201 | testStrictSuper(classOf[OtherInterface], classOf[Child]) 202 | testStrictSuper(classOf[Parent], classOf[Child]) 203 | 204 | // Unrelated classes are not assignable 205 | testUnrelated(classOf[String], classOf[Parent]) 206 | testUnrelated(classOf[Parent], classOf[OtherInterface]) 207 | testUnrelated(classOf[Parent], classOf[Cloneable]) 208 | testUnrelated(classOf[Parent], classOf[Serializable]) 209 | testUnrelated(classOf[Comparable[_]], classOf[Cloneable]) 210 | testUnrelated(classOf[Comparable[_]], classOf[Serializable]) 211 | testUnrelated(classOf[Comparable[_]], classOf[Parent]) 212 | 213 | // Arrays are covariant 214 | testStrictSuper(classOf[Array[Object]], classOf[Array[String]]) 215 | testStrictSuper(classOf[Array[Object]], classOf[Array[Interface]]) 216 | testStrictSuper(classOf[Array[Object]], classOf[Array[Array[Int]]]) 217 | testStrictSuper(classOf[Array[Interface]], classOf[Array[Parent]]) 218 | testStrictSuper(classOf[Array[Parent]], classOf[Array[Child]]) 219 | 220 | // Arrays are Cloneable, including covariantly 221 | testStrictSuper(classOf[Cloneable], classOf[Array[Int]]) 222 | testStrictSuper(classOf[Cloneable], classOf[Array[Object]]) 223 | testStrictSuper(classOf[Cloneable], classOf[Array[String]]) 224 | testStrictSuper(classOf[Cloneable], classOf[Array[Array[Int]]]) 225 | testStrictSuper(classOf[Cloneable], classOf[Array[Array[Object]]]) 226 | testStrictSuper(classOf[Array[Cloneable]], classOf[Array[Array[Int]]]) 227 | testStrictSuper(classOf[Array[Cloneable]], classOf[Array[Array[Array[String]]]]) 228 | testUnrelated(classOf[Array[Cloneable]], classOf[Array[Int]]) 229 | 230 | // Arrays are Serializable, including covariantly 231 | testStrictSuper(classOf[Serializable], classOf[Array[Int]]) 232 | testStrictSuper(classOf[Serializable], classOf[Array[Object]]) 233 | testStrictSuper(classOf[Serializable], classOf[Array[String]]) 234 | testStrictSuper(classOf[Serializable], classOf[Array[Array[Int]]]) 235 | testStrictSuper(classOf[Serializable], classOf[Array[Array[Object]]]) 236 | testStrictSuper(classOf[Array[Serializable]], classOf[Array[Array[Int]]]) 237 | testStrictSuper(classOf[Array[Serializable]], classOf[Array[Array[Array[String]]]]) 238 | testUnrelated(classOf[Array[Serializable]], classOf[Array[Int]]) 239 | 240 | // Arrays are unrelated to other interfaces 241 | testUnrelated(classOf[Interface], classOf[Array[Int]]) 242 | testUnrelated(classOf[Interface], classOf[Array[Object]]) 243 | testUnrelated(classOf[Interface], classOf[Array[String]]) 244 | testUnrelated(classOf[Interface], classOf[Array[Interface]]) 245 | testUnrelated(classOf[Interface], classOf[Array[Array[Int]]]) 246 | testUnrelated(classOf[Interface], classOf[Array[Array[Object]]]) 247 | } 248 | 249 | def testCast(): Unit = { 250 | /* This is always an identity, so there isn't anything to really test. 251 | * We only make sure that everything is wired up correctly. 252 | */ 253 | 254 | val child: AnyRef = new Child 255 | val child2: Parent = classOf[Parent].cast(child) 256 | assertSame(child, child2) 257 | } 258 | 259 | def testGetComponentType(): Unit = { 260 | Assert.assertSame(null, classOf[AnyRef].getComponentType()) 261 | Assert.assertSame(null, classOf[String].getComponentType()) 262 | Assert.assertSame(null, classOf[Int].getComponentType()) 263 | Assert.assertSame(null, classOf[CharSequence].getComponentType()) 264 | Assert.assertSame(null, classOf[java.lang.Iterable[Any]].getComponentType()) 265 | 266 | Assert.assertSame(classOf[Int], classOf[Array[Int]].getComponentType()) 267 | Assert.assertSame(classOf[String], classOf[Array[String]].getComponentType()) 268 | Assert.assertSame(classOf[AnyRef], classOf[Array[AnyRef]].getComponentType()) 269 | 270 | Assert.assertSame( 271 | classOf[Array[CharSequence]], 272 | classOf[Array[Array[CharSequence]]].getComponentType() 273 | ) 274 | 275 | Assert.assertSame( 276 | classOf[Array[Long]], 277 | classOf[Array[Array[Long]]].getComponentType() 278 | ) 279 | 280 | Assert.assertSame( 281 | classOf[Array[Array[java.lang.Byte]]], 282 | classOf[Array[Array[Array[java.lang.Byte]]]].getComponentType() 283 | ) 284 | 285 | Assert.assertSame( 286 | classOf[Array[ClassForUniqueGetComponentTypeTest]].getComponentType(), 287 | classOf[Array[ClassForUniqueGetComponentTypeTest]].getComponentType() 288 | ) 289 | } 290 | 291 | trait Interface 292 | trait OtherInterface extends Interface 293 | class Parent extends Interface 294 | class Child extends Parent with OtherInterface 295 | 296 | class ClassForUniqueGetComponentTypeTest 297 | } 298 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/CloneTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert 4 | 5 | object CloneTest { 6 | def main(): Unit = { 7 | testDirect() 8 | testIndirectlyImpl() 9 | testShallowCopying() 10 | } 11 | 12 | private def testDirect(): Unit = { 13 | val foo = new Foo(0, "0") 14 | val copy = foo.clone().asInstanceOf[Foo] 15 | Assert.ok(foo.x == copy.x) 16 | Assert.ok(foo.y == copy.y) 17 | } 18 | 19 | private def testIndirectlyImpl(): Unit = { 20 | val bar = new Bar(1, "1") 21 | val copy = bar.clone().asInstanceOf[Bar] 22 | Assert.ok(bar.x == copy.x) 23 | Assert.ok(bar.y == copy.y) 24 | } 25 | 26 | private def testShallowCopying(): Unit = { 27 | val foo = new Foo(0, "foo") 28 | val bar = new Bar(1, "bar") 29 | val baz = new Baz(foo, bar) 30 | val copy = baz.clone().asInstanceOf[Baz] 31 | Assert.assertSame(baz.foo, copy.foo) 32 | Assert.assertSame(baz.bar, copy.bar) 33 | } 34 | } 35 | 36 | class Foo(val x: Int, val y: String) extends Cloneable { 37 | override def clone(): Object = super.clone() 38 | } 39 | 40 | class Bar( 41 | override val x: Int, 42 | override val y: String 43 | ) extends Foo(x, y) 44 | 45 | class Baz(val foo: Foo, val bar: Bar) extends Cloneable { 46 | override def clone(): Object = super.clone() 47 | } 48 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ClosureTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert.ok 6 | 7 | object ClosureTest { 8 | def main(): Unit = { 9 | testClosure() 10 | testClosureThis() 11 | 12 | // TODO We cannot test closures with ...rest params yet because they need Seq's 13 | 14 | testGiveToActualJSCode() 15 | } 16 | 17 | def testClosure(): Unit = { 18 | def makeClosure(x: Int, y: String): js.Function2[Boolean, String, String] = 19 | (z, w) => s"$x $y $z $w" 20 | 21 | val f = makeClosure(5, "foo") 22 | ok(f(true, "bar") == "5 foo true bar") 23 | } 24 | 25 | def testClosureThis(): Unit = { 26 | def makeClosure(x: Int, y: String): js.ThisFunction2[Any, Boolean, String, String] = 27 | (ths, z, w) => s"$ths $x $y $z $w" 28 | 29 | val f = makeClosure(5, "foo") 30 | ok(f(new Obj, true, "bar") == "Obj 5 foo true bar") 31 | } 32 | 33 | def testGiveToActualJSCode(): Unit = { 34 | val arr = js.Array(2, 3, 5, 7, 11) 35 | val f: js.Function1[Int, Int] = x => x * 2 36 | val result = arr.asInstanceOf[js.Dynamic].map(f).asInstanceOf[js.Array[Int]] 37 | ok(result.length == 5) 38 | ok(result(0) == 4) 39 | ok(result(1) == 6) 40 | ok(result(2) == 10) 41 | ok(result(3) == 14) 42 | ok(result(4) == 22) 43 | } 44 | 45 | class Obj { 46 | override def toString(): String = "Obj" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ControlStructuresTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.annotation.tailrec 4 | import testsuite.Assert.ok 5 | 6 | object ControlStructuresTest { 7 | def main(): Unit = { 8 | testIf() 9 | testWhile() 10 | testFib() 11 | } 12 | 13 | private def testIf(): Unit = { 14 | def test(x: Int): String = 15 | if (x < 0) "negative" 16 | else if (x == 0) "zero" 17 | else "positive" 18 | ok(test(1) == "positive") 19 | ok(test(0) == "zero") 20 | ok(test(-1) == "negative") 21 | } 22 | 23 | private def testWhile(): Unit = { 24 | var x = 10 25 | while (x > 0) { x = x - 1 } 26 | ok(x == 0) 27 | } 28 | 29 | private def testFib(): Unit = { 30 | def testFib(i: Int, expected: Int): Unit = { 31 | ok(RecFib.fib(i) == expected) 32 | ok(LoopFib.fib(i) == expected) 33 | ok(TailRecFib.fib(i) == expected) 34 | } 35 | testFib(0, 0) 36 | testFib(1, 1) 37 | testFib(2, 1) 38 | testFib(3, 2) 39 | testFib(4, 3) 40 | testFib(5, 5) 41 | testFib(6, 8) 42 | } 43 | } 44 | 45 | object RecFib { 46 | def fib(n: Int): Int = if (n <= 1) n else fib(n - 1) + fib(n - 2) 47 | } 48 | 49 | object LoopFib { 50 | def fib(n: Int): Int = { 51 | var a = 0 52 | var b = 1 53 | var i = 0 54 | while (i < n) { 55 | val temp = b 56 | b = a + b 57 | a = temp 58 | i += 1 59 | } 60 | a 61 | } 62 | } 63 | 64 | object TailRecFib { 65 | def fib(n: Int): Int = fibLoop(n, 0, 1) 66 | @tailrec 67 | private final def fibLoop(n: Int, a: Int, b: Int): Int = 68 | if (n == 0) a 69 | else fibLoop(n - 1, b, a + b) 70 | } 71 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/FieldsTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object FieldsTest { 6 | def main(): Unit = { 7 | val parent = new Parent(5) 8 | ok(parent.x == 5) 9 | ok(parent.getX == 5) 10 | 11 | val child = new Child(6, "foo") 12 | ok(child.x == 6) 13 | ok(child.getX == 6) 14 | ok(child.foo() == 3) 15 | 16 | val child2 = new Child(-6, "foo") 17 | ok(child2.x == -6) 18 | ok(child2.getX == -6) 19 | ok(child2.foo() == -3) 20 | } 21 | 22 | class Parent(val x: Int) { 23 | def getX: Int = x 24 | } 25 | 26 | class Child(x2: Int, y: String) extends Parent(x2) { 27 | def foo(): Int = if (x >= 0) y.length() else -y.length() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/FloatingPointRemTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | /** Tests for `Float_%` and `Double_%`, which do not have Wasm equivalent. 6 | * 7 | * They are specified by https://262.ecma-international.org/#sec-numeric-types-number-remainder 8 | */ 9 | object FloatingPointRemTest { 10 | def main(): Unit = { 11 | testFloat() 12 | testDouble() 13 | } 14 | 15 | def testFloat(): Unit = { 16 | def test(expected: Float, x: Float, y: Float): Unit = 17 | assertSame(expected, x % y) 18 | 19 | // If n is NaN, return NaN 20 | test(Float.NaN, Float.NaN, Float.NaN) 21 | test(Float.NaN, Float.NaN, Float.PositiveInfinity) 22 | test(Float.NaN, Float.NaN, Float.NegativeInfinity) 23 | test(Float.NaN, Float.NaN, +0.0f) 24 | test(Float.NaN, Float.NaN, -0.0f) 25 | test(Float.NaN, Float.NaN, 2.1f) 26 | test(Float.NaN, Float.NaN, 5.5f) 27 | test(Float.NaN, Float.NaN, -151.189f) 28 | 29 | // If d is NaN, return NaN 30 | test(Float.NaN, Float.NaN, Float.NaN) 31 | test(Float.NaN, Float.PositiveInfinity, Float.NaN) 32 | test(Float.NaN, Float.NegativeInfinity, Float.NaN) 33 | test(Float.NaN, +0.0f, Float.NaN) 34 | test(Float.NaN, -0.0f, Float.NaN) 35 | test(Float.NaN, 2.1f, Float.NaN) 36 | test(Float.NaN, 5.5f, Float.NaN) 37 | test(Float.NaN, -151.189f, Float.NaN) 38 | 39 | // If n is PositiveInfinity, return NaN 40 | test(Float.NaN, Float.PositiveInfinity, Float.PositiveInfinity) 41 | test(Float.NaN, Float.PositiveInfinity, Float.NegativeInfinity) 42 | test(Float.NaN, Float.PositiveInfinity, +0.0f) 43 | test(Float.NaN, Float.PositiveInfinity, -0.0f) 44 | test(Float.NaN, Float.PositiveInfinity, 2.1f) 45 | test(Float.NaN, Float.PositiveInfinity, 5.5f) 46 | test(Float.NaN, Float.PositiveInfinity, -151.189f) 47 | 48 | // If n is NegativeInfinity, return NaN 49 | test(Float.NaN, Float.NegativeInfinity, Float.PositiveInfinity) 50 | test(Float.NaN, Float.NegativeInfinity, Float.NegativeInfinity) 51 | test(Float.NaN, Float.NegativeInfinity, +0.0f) 52 | test(Float.NaN, Float.NegativeInfinity, -0.0f) 53 | test(Float.NaN, Float.NegativeInfinity, 2.1f) 54 | test(Float.NaN, Float.NegativeInfinity, 5.5f) 55 | test(Float.NaN, Float.NegativeInfinity, -151.189f) 56 | 57 | // If d is PositiveInfinity, return n 58 | test(+0.0f, +0.0f, Float.PositiveInfinity) 59 | test(-0.0f, -0.0f, Float.PositiveInfinity) 60 | test(2.1f, 2.1f, Float.PositiveInfinity) 61 | test(5.5f, 5.5f, Float.PositiveInfinity) 62 | test(-151.189f, -151.189f, Float.PositiveInfinity) 63 | 64 | // If d is NegativeInfinity, return n 65 | test(+0.0f, +0.0f, Float.NegativeInfinity) 66 | test(-0.0f, -0.0f, Float.NegativeInfinity) 67 | test(2.1f, 2.1f, Float.NegativeInfinity) 68 | test(5.5f, 5.5f, Float.NegativeInfinity) 69 | test(-151.189f, -151.189f, Float.NegativeInfinity) 70 | 71 | // If d is +0.0, return NaN 72 | test(Float.NaN, +0.0f, +0.0f) 73 | test(Float.NaN, -0.0f, +0.0f) 74 | test(Float.NaN, 2.1f, +0.0f) 75 | test(Float.NaN, 5.5f, +0.0f) 76 | test(Float.NaN, -151.189f, +0.0f) 77 | 78 | // If d is -0.0, return NaN 79 | test(Float.NaN, +0.0f, -0.0f) 80 | test(Float.NaN, -0.0f, -0.0f) 81 | test(Float.NaN, 2.1f, -0.0f) 82 | test(Float.NaN, 5.5f, -0.0f) 83 | test(Float.NaN, -151.189f, -0.0f) 84 | 85 | // If n is +0.0, return n 86 | test(+0.0f, +0.0f, 2.1f) 87 | test(+0.0f, +0.0f, 5.5f) 88 | test(+0.0f, +0.0f, -151.189f) 89 | 90 | // If n is -0.0, return n 91 | test(-0.0f, -0.0f, 2.1f) 92 | test(-0.0f, -0.0f, 5.5f) 93 | test(-0.0f, -0.0f, -151.189f) 94 | 95 | // Non-special values 96 | // { val l = List(2.1f, 5.5f, -151.189f); for (n <- l; d <- l) println(s" test(${n % d}f, ${n}f, ${d}f)") } 97 | test(0.0f, 2.1f, 2.1f) 98 | test(2.1f, 2.1f, 5.5f) 99 | test(2.1f, 2.1f, -151.189f) 100 | test(1.3000002f, 5.5f, 2.1f) 101 | test(0.0f, 5.5f, 5.5f) 102 | test(5.5f, 5.5f, -151.189f) 103 | test(-2.0890021f, -151.189f, 2.1f) 104 | test(-2.6889954f, -151.189f, 5.5f) 105 | test(-0.0f, -151.189f, -151.189f) 106 | } 107 | 108 | def testDouble(): Unit = { 109 | def test(expected: Double, n: Double, d: Double): Unit = 110 | assertSame(expected, n % d) 111 | 112 | // If n is NaN, return NaN 113 | test(Double.NaN, Double.NaN, Double.NaN) 114 | test(Double.NaN, Double.NaN, Double.PositiveInfinity) 115 | test(Double.NaN, Double.NaN, Double.NegativeInfinity) 116 | test(Double.NaN, Double.NaN, +0.0) 117 | test(Double.NaN, Double.NaN, -0.0) 118 | test(Double.NaN, Double.NaN, 2.1) 119 | test(Double.NaN, Double.NaN, 5.5) 120 | test(Double.NaN, Double.NaN, -151.189) 121 | 122 | // If d is NaN, return NaN 123 | test(Double.NaN, Double.NaN, Double.NaN) 124 | test(Double.NaN, Double.PositiveInfinity, Double.NaN) 125 | test(Double.NaN, Double.NegativeInfinity, Double.NaN) 126 | test(Double.NaN, +0.0, Double.NaN) 127 | test(Double.NaN, -0.0, Double.NaN) 128 | test(Double.NaN, 2.1, Double.NaN) 129 | test(Double.NaN, 5.5, Double.NaN) 130 | test(Double.NaN, -151.189, Double.NaN) 131 | 132 | // If n is PositiveInfinity, return NaN 133 | test(Double.NaN, Double.PositiveInfinity, Double.PositiveInfinity) 134 | test(Double.NaN, Double.PositiveInfinity, Double.NegativeInfinity) 135 | test(Double.NaN, Double.PositiveInfinity, +0.0) 136 | test(Double.NaN, Double.PositiveInfinity, -0.0) 137 | test(Double.NaN, Double.PositiveInfinity, 2.1) 138 | test(Double.NaN, Double.PositiveInfinity, 5.5) 139 | test(Double.NaN, Double.PositiveInfinity, -151.189) 140 | 141 | // If n is NegativeInfinity, return NaN 142 | test(Double.NaN, Double.NegativeInfinity, Double.PositiveInfinity) 143 | test(Double.NaN, Double.NegativeInfinity, Double.NegativeInfinity) 144 | test(Double.NaN, Double.NegativeInfinity, +0.0) 145 | test(Double.NaN, Double.NegativeInfinity, -0.0) 146 | test(Double.NaN, Double.NegativeInfinity, 2.1) 147 | test(Double.NaN, Double.NegativeInfinity, 5.5) 148 | test(Double.NaN, Double.NegativeInfinity, -151.189) 149 | 150 | // If d is PositiveInfinity, return n 151 | test(+0.0, +0.0, Double.PositiveInfinity) 152 | test(-0.0, -0.0, Double.PositiveInfinity) 153 | test(2.1, 2.1, Double.PositiveInfinity) 154 | test(5.5, 5.5, Double.PositiveInfinity) 155 | test(-151.189, -151.189, Double.PositiveInfinity) 156 | 157 | // If d is NegativeInfinity, return n 158 | test(+0.0, +0.0, Double.NegativeInfinity) 159 | test(-0.0, -0.0, Double.NegativeInfinity) 160 | test(2.1, 2.1, Double.NegativeInfinity) 161 | test(5.5, 5.5, Double.NegativeInfinity) 162 | test(-151.189, -151.189, Double.NegativeInfinity) 163 | 164 | // If d is +0.0, return NaN 165 | test(Double.NaN, +0.0, +0.0) 166 | test(Double.NaN, -0.0, +0.0) 167 | test(Double.NaN, 2.1, +0.0) 168 | test(Double.NaN, 5.5, +0.0) 169 | test(Double.NaN, -151.189, +0.0) 170 | 171 | // If d is -0.0, return NaN 172 | test(Double.NaN, +0.0, -0.0) 173 | test(Double.NaN, -0.0, -0.0) 174 | test(Double.NaN, 2.1, -0.0) 175 | test(Double.NaN, 5.5, -0.0) 176 | test(Double.NaN, -151.189, -0.0) 177 | 178 | // If n is +0.0, return n 179 | test(+0.0, +0.0, 2.1) 180 | test(+0.0, +0.0, 5.5) 181 | test(+0.0, +0.0, -151.189) 182 | 183 | // If n is -0.0, return n 184 | test(-0.0, -0.0, 2.1) 185 | test(-0.0, -0.0, 5.5) 186 | test(-0.0, -0.0, -151.189) 187 | 188 | // Non-special values 189 | // { val l = List(2.1, 5.5, -151.189); for (n <- l; d <- l) println(s" test(${n % d}, $n, $d)") } 190 | test(0.0, 2.1, 2.1) 191 | test(2.1, 2.1, 5.5) 192 | test(2.1, 2.1, -151.189) 193 | test(1.2999999999999998, 5.5, 2.1) 194 | test(0.0, 5.5, 5.5) 195 | test(5.5, 5.5, -151.189) 196 | test(-2.0889999999999866, -151.189, 2.1) 197 | test(-2.688999999999993, -151.189, 5.5) 198 | test(-0.0, -151.189, -151.189) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/GetClassTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.assertSame 4 | 5 | object GetClassTest { 6 | def main(): Unit = { 7 | testNoHijackedDispatch() 8 | testWithHijackedDispath() 9 | } 10 | 11 | class Foo 12 | 13 | class Bar extends Foo 14 | 15 | def testNoHijackedDispatch(): Unit = { 16 | def getClassOfFoo(x: Foo): Class[_] = x.getClass() 17 | 18 | assertSame(classOf[Foo], getClassOfFoo(new Foo)) 19 | assertSame(classOf[Bar], getClassOfFoo(new Bar)) 20 | } 21 | 22 | def testWithHijackedDispath(): Unit = { 23 | def getClassOf(x: Any): Class[_] = x.getClass() 24 | 25 | assertSame(classOf[Foo], getClassOf(new Foo)) 26 | assertSame(classOf[Bar], getClassOf(new Bar)) 27 | 28 | assertSame(classOf[java.lang.Boolean], getClassOf(true)) 29 | assertSame(classOf[java.lang.Boolean], getClassOf(false)) 30 | assertSame(classOf[java.lang.Void], getClassOf(())) 31 | assertSame(classOf[java.lang.String], getClassOf("foo")) 32 | 33 | assertSame(classOf[java.lang.Byte], getClassOf(0.0)) 34 | assertSame(classOf[java.lang.Byte], getClassOf(56)) 35 | assertSame(classOf[java.lang.Byte], getClassOf(-128)) 36 | assertSame(classOf[java.lang.Short], getClassOf(200)) 37 | assertSame(classOf[java.lang.Short], getClassOf(-32000)) 38 | assertSame(classOf[java.lang.Integer], getClassOf(500000)) 39 | assertSame(classOf[java.lang.Integer], getClassOf(Int.MinValue)) 40 | 41 | assertSame(classOf[java.lang.Float], getClassOf(1.5)) 42 | assertSame(classOf[java.lang.Double], getClassOf(1.4)) 43 | assertSame(classOf[java.lang.Double], getClassOf(Float.MaxValue.toDouble * 8.0)) 44 | 45 | assertSame(classOf[java.lang.Float], getClassOf(-0.0)) 46 | assertSame(classOf[java.lang.Float], getClassOf(Double.PositiveInfinity)) 47 | assertSame(classOf[java.lang.Float], getClassOf(Double.NegativeInfinity)) 48 | assertSame(classOf[java.lang.Float], getClassOf(Double.NaN)) 49 | 50 | assertSame(classOf[Array[Int]], getClassOf(new Array[Int](3))) 51 | assertSame(classOf[Array[Int]], getClassOf(Array(1, 2, 3))) 52 | 53 | assertSame(classOf[Array[Boolean]], getClassOf(new Array[Boolean](3))) 54 | assertSame(classOf[Array[Boolean]], getClassOf(Array(false, true))) 55 | 56 | assertSame(classOf[Array[AnyRef]], getClassOf(new Array[AnyRef](3))) 57 | assertSame(classOf[Array[AnyRef]], getClassOf(Array(null): Array[AnyRef])) 58 | 59 | assertSame(classOf[Array[String]], getClassOf(new Array[String](3))) 60 | assertSame(classOf[Array[String]], getClassOf(Array("foo", "bar"))) 61 | 62 | assertSame(classOf[Array[Array[Int]]], getClassOf(new Array[Array[Int]](3))) 63 | assertSame(classOf[Array[Array[Int]]], getClassOf(Array(Array(1, 2, 3)))) 64 | 65 | assertSame(null, getClassOf(scala.scalajs.js.Math)) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/HijackedClassesDispatchTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object HijackedClassesDispatchTest { 6 | def main(): Unit = { 7 | val obj = new Test() 8 | val otherObj = new Test() 9 | val obj2 = new Test2() 10 | val otherObj2 = new Test2() 11 | ok( 12 | testToString(true, "true") && 13 | testToString(54321, "54321") && 14 | testToString(obj, "Test class") && 15 | testToStringStartsWith(obj2, "testsuite.core.HijackedClassesDispatchTest$Test2@") && 16 | testToString('A', "A") && 17 | testToStringStartsWith(new Array[Int](1), "[I@") && 18 | testToStringStartsWith(new Array[String](1), "[Ljava.lang.String;@") && 19 | testToStringStartsWith(new Array[Array[AnyRef]](1), "[[Ljava.lang.Object;@") && 20 | testHashCode(true, 1231) && 21 | testHashCode(54321, 54321) && 22 | testHashCode("foo", 101574) && 23 | testHashCode(obj, 123) && 24 | testHashCode(obj2, 1) && // first object for which we ask idHashCode 25 | testHashCode(obj2, 1) && // it is also 1 the second time we ask 26 | testHashCode('A', 65) && 27 | testIntValue(Int.box(5), 5) && 28 | testIntValue(Long.box(6L), 6) && 29 | testIntValue(Double.box(7.5), 7) && 30 | testIntValue(new CustomNumber(), 789) && 31 | testLength("foo", 3) && 32 | testLength(new CustomCharSeq(), 54) && 33 | testCharAt("foobar", 3, 'b') && 34 | testCharAt(new CustomCharSeq(), 3, 'A') && 35 | testEquals(true, 1, false) && 36 | testEquals(1.0, 1, true) && 37 | testEquals("foo", "foo", true) && 38 | testEquals("foo", "bar", false) && 39 | testEquals(obj, obj2, false) && 40 | testEquals(obj, otherObj, true) && 41 | testEquals(obj2, otherObj2, false) && 42 | testNotifyAll(true) && 43 | testNotifyAll(obj) 44 | ) 45 | } 46 | 47 | def testToString(x: Any, expected: String): Boolean = 48 | x.toString() == expected 49 | 50 | def testToStringStartsWith(x: Any, expectedPrefix: String): Boolean = 51 | x.toString().startsWith(expectedPrefix) 52 | 53 | def testHashCode(x: Any, expected: Int): Boolean = 54 | x.hashCode() == expected 55 | 56 | def testIntValue(x: Number, expected: Int): Boolean = 57 | x.intValue() == expected 58 | 59 | def testLength(x: CharSequence, expected: Int): Boolean = 60 | x.length() == expected 61 | 62 | def testCharAt(x: CharSequence, i: Int, expected: Char): Boolean = 63 | x.charAt(i) == expected 64 | 65 | def testEquals(x: Any, y: Any, expected: Boolean): Boolean = 66 | x.asInstanceOf[AnyRef].equals(y) == expected 67 | 68 | def testNotifyAll(x: Any): Boolean = { 69 | // This is just to test that the call validates and does not trap 70 | x.asInstanceOf[AnyRef].notifyAll() 71 | true 72 | } 73 | 74 | class Test { 75 | override def toString(): String = "Test class" 76 | 77 | override def hashCode(): Int = 123 78 | 79 | override def equals(that: Any): Boolean = 80 | that.isInstanceOf[Test] 81 | } 82 | 83 | class Test2 84 | 85 | class CustomNumber() extends Number { 86 | def value(): Int = 789 87 | def intValue(): Int = value() 88 | def longValue(): Long = 789L 89 | def floatValue(): Float = 789.0f 90 | def doubleValue(): Double = 789.0 91 | } 92 | 93 | class CustomCharSeq extends CharSequence { 94 | def length(): Int = 54 95 | override def toString(): String = "CustomCharSeq" 96 | def charAt(index: Int): Char = 'A' 97 | def subSequence(start: Int, end: Int): CharSequence = this 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/HijackedClassesMonoTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object HijackedClassesMonoTest { 6 | def main(): Unit = { 7 | ok(testInteger(5)) 8 | ok(testString("foo")) 9 | } 10 | 11 | def testInteger(x: Int): Boolean = { 12 | x.hashCode() == 5 13 | } 14 | 15 | def testString(foo: String): Boolean = { 16 | foo.length() == 3 && 17 | foo.hashCode() == 101574 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/HijackedClassesUpcastTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object HijackedClassesUpcastTest { 6 | def main(): Unit = { 7 | ok( 8 | testBoolean(true) && 9 | testInteger(5) && 10 | testIntegerNull(null) && 11 | testString("foo") && 12 | testStringNull(null) && 13 | testCharacter('A') 14 | ) 15 | 16 | } 17 | 18 | def testBoolean(x: Boolean): Boolean = { 19 | val x1 = identity(x) 20 | x1 && { 21 | val x2: Any = x1 22 | x2 match { 23 | case x3: Boolean => x3 24 | case _ => false 25 | } 26 | } 27 | } 28 | 29 | def testInteger(x: Int): Boolean = { 30 | val x1 = identity(x) 31 | x1 == 5 && { 32 | val x2: Any = x1 33 | x2 match { 34 | case x3: Int => x3 + 1 == 6 35 | case _ => false 36 | } 37 | } 38 | } 39 | 40 | def testIntegerNull(x: Any): Boolean = { 41 | !x.isInstanceOf[Int] && 42 | !x.isInstanceOf[java.lang.Integer] && 43 | (x.asInstanceOf[Int] == 0) && { 44 | val x2 = x.asInstanceOf[java.lang.Integer] 45 | x2 == null 46 | } 47 | } 48 | 49 | def testString(x: String): Boolean = { 50 | val x1 = identity(x) 51 | x1.length() == 3 && { 52 | val x2: Any = x1 53 | x2 match { 54 | case x3: String => x3.length() == 3 55 | case _ => false 56 | } 57 | } 58 | } 59 | 60 | def testStringNull(x: Any): Boolean = { 61 | !x.isInstanceOf[String] && { 62 | val x2 = x.asInstanceOf[String] 63 | x2 == null 64 | } 65 | } 66 | 67 | def testCharacter(x: Char): Boolean = { 68 | val x1 = identity(x) 69 | x1 == 'A' && { 70 | val x2: Any = x1 71 | x2 match { 72 | case x3: Char => (x3 + 1).toChar == 'B' 73 | case _ => false 74 | } 75 | } 76 | } 77 | 78 | @noinline 79 | def identity[A](x: A): A = x 80 | } 81 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ImportTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation._ 5 | 6 | import testsuite.Assert._ 7 | 8 | object ImportTest { 9 | @js.native 10 | @JSImport("node:querystring") 11 | def escape(str: String): String = js.native 12 | 13 | def main(): Unit = { 14 | assertSame("foo%26ba%2Bbar%3Dfoobabar", escape("foo&ba+bar=foobabar")) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/InterfaceCall.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object InterfaceCall { 6 | def main(): Unit = { 7 | val c = new Concrete() 8 | val cAddSub: AddSub = c 9 | val cZero: Zero = c 10 | ok(cAddSub.plus(5, 7) == 12) 11 | ok(cAddSub.minus(5, 7) == -2) 12 | ok(cZero.zero == 0) 13 | ok(cAddSub.plus(cZero.zero, 1) == 1 && cAddSub.minus(1, cZero.zero) == 1) 14 | 15 | val o = new OtherConcrete() 16 | val oAddSub: AddSub = o 17 | val oZero: Zero = o 18 | ok(oAddSub.plus(5, 7) == 12) 19 | ok(oAddSub.minus(5, 7) == -2) 20 | ok(oZero.zero == 5) 21 | ok(oAddSub.plus(oZero.zero, 1) == 6 && oAddSub.minus(1, oZero.zero) == -4) 22 | } 23 | 24 | class Concrete extends AddSub with Zero { 25 | override def zero: Int = 0 26 | } 27 | 28 | class OtherConcrete extends Zero with Sub with AddSub { 29 | override def zero: Int = 5 30 | } 31 | 32 | trait Adder { 33 | def plus(a: Int, b: Int) = a + b 34 | } 35 | 36 | trait Sub { 37 | def minus(a: Int, b: Int): Int = a - b 38 | } 39 | 40 | trait AddSub extends Adder with Sub 41 | 42 | trait Zero { 43 | def zero: Int 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/IsInstanceOfTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object IsInstanceOfTest { 6 | def main(): Unit = { 7 | ok(testInheritance()) 8 | ok(testMixinAll(new Child())) 9 | ok(testMixinAll(new Parent())) 10 | ok(testMixinAll(new Base {})) 11 | ok(testMixin()) 12 | ok(testPrimitiveIsInstanceOfBase(5)) 13 | ok(testPrimitiveIsInstanceOfBase("foo")) 14 | 15 | ok(!testInt("foo")) 16 | ok(!testInt(2147483648L)) 17 | ok(testInt(3)) 18 | ok(!testInt(new Child())) 19 | ok(testString("foo")) 20 | ok(!testString(new Child())) 21 | 22 | testArrays() 23 | } 24 | 25 | private def testInheritance(): Boolean = { 26 | val child: Any = new Child() 27 | val parent: Any = new Parent() 28 | val unrelated: Any = new UnrelatedClass() 29 | child.isInstanceOf[AbstractParent] && 30 | child.isInstanceOf[Parent] && 31 | child.isInstanceOf[Child] && 32 | parent.isInstanceOf[AbstractParent] && 33 | parent.isInstanceOf[Parent] && 34 | !parent.isInstanceOf[Child] && 35 | !unrelated.isInstanceOf[AbstractParent] && 36 | !unrelated.isInstanceOf[Parent] && 37 | !unrelated.isInstanceOf[Child] 38 | } 39 | 40 | private def testMixinAll(o: Base): Boolean = { 41 | o.isInstanceOf[Base] && o.isInstanceOf[Base1] && o.isInstanceOf[Base2] 42 | } 43 | 44 | private def testMixin(): Boolean = { 45 | val base1 = new Base1 {} 46 | val base2 = new Base2 {} 47 | base1.isInstanceOf[Base1] && 48 | !base1.isInstanceOf[Base2] && 49 | !base1.isInstanceOf[Base] && 50 | !base2.isInstanceOf[Base1] && 51 | base2.isInstanceOf[Base2] && 52 | !base2.isInstanceOf[Base] 53 | } 54 | 55 | private def testArrays(): Unit = { 56 | val child: Any = new Child 57 | val booleanArray: Any = new Array[Boolean](1) 58 | val intArray: Any = new Array[Int](1) 59 | val stringArray: Any = new Array[String](1) 60 | val intArrayArray: Any = new Array[Array[Int]](1) 61 | val stringArrayArray: Any = new Array[Array[String]](1) 62 | val objectArray: Any = new Array[Object](1) 63 | 64 | // direct instances 65 | ok(booleanArray.isInstanceOf[Array[Boolean]]) 66 | ok(intArray.isInstanceOf[Array[Int]]) 67 | ok(stringArray.isInstanceOf[Array[String]]) 68 | ok(intArrayArray.isInstanceOf[Array[Array[Int]]]) 69 | ok(stringArrayArray.isInstanceOf[Array[Array[String]]]) 70 | ok(objectArray.isInstanceOf[Array[Object]]) 71 | 72 | // primitive arrays are not instances of any other type 73 | ok(!booleanArray.isInstanceOf[Array[Int]]) 74 | ok(!booleanArray.isInstanceOf[Child]) 75 | ok(!booleanArray.isInstanceOf[Array[Array[Boolean]]]) 76 | ok(!booleanArray.isInstanceOf[Array[Object]]) 77 | 78 | // different dimensions 79 | ok(!intArray.isInstanceOf[Array[Array[Int]]]) 80 | ok(!intArrayArray.isInstanceOf[Array[Int]]) 81 | ok(!intArrayArray.isInstanceOf[Array[Array[Array[Int]]]]) 82 | ok(!stringArrayArray.isInstanceOf[Array[String]]) 83 | ok(!stringArrayArray.isInstanceOf[Array[Array[Array[String]]]]) 84 | ok(!objectArray.isInstanceOf[Array[Array[Object]]]) 85 | 86 | // reference array types are covariant at run-time (IR and JVM semantics) 87 | ok(stringArray.isInstanceOf[Array[Object]]) 88 | ok(intArrayArray.isInstanceOf[Array[Object]]) 89 | ok(!objectArray.isInstanceOf[Array[String]]) 90 | 91 | // non-arrays are not instances of any array type 92 | ok(!child.isInstanceOf[Array[Boolean]]) 93 | ok(!child.isInstanceOf[Array[Int]]) 94 | ok(!child.isInstanceOf[Array[String]]) 95 | ok(!child.isInstanceOf[Array[Array[Int]]]) 96 | ok(!child.isInstanceOf[Array[Array[String]]]) 97 | ok(!child.isInstanceOf[Array[Object]]) 98 | 99 | // corner case for reachability of the typeData 100 | val arrayOfClassUsedOnlyForArrayTypeTest = new Array[ClassUsedOnlyForArrayTypeTest](1) 101 | ok(arrayOfClassUsedOnlyForArrayTypeTest.isInstanceOf[Array[InterfaceUsedOnlyForArrayTypeTest]]) 102 | ok(!intArray.isInstanceOf[Array[InterfaceUsedOnlyForArrayTypeTest]]) 103 | ok(!objectArray.isInstanceOf[Array[InterfaceUsedOnlyForArrayTypeTest]]) 104 | } 105 | 106 | private def testPrimitiveIsInstanceOfBase(p: Any): Boolean = 107 | !p.isInstanceOf[Base] 108 | 109 | private def testInt(e: Any): Boolean = e.isInstanceOf[Int] 110 | private def testString(e: Any): Boolean = e.isInstanceOf[String] 111 | 112 | trait InterfaceUsedOnlyForArrayTypeTest 113 | class ClassUsedOnlyForArrayTypeTest extends InterfaceUsedOnlyForArrayTypeTest 114 | } 115 | 116 | abstract class AbstractParent 117 | 118 | class Parent extends AbstractParent with Base { 119 | def foo(): Int = 5 120 | } 121 | class Child extends Parent 122 | 123 | class UnrelatedClass 124 | 125 | trait Base1 126 | trait Base2 127 | trait Base extends Base1 with Base2 128 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JLObjectInstanceTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object JLObjectInstanceTest { 6 | def main(): Unit = { 7 | val obj = new Object() 8 | val hashCode = obj.hashCode() 9 | val str = obj.toString() 10 | assertSame("java.lang.Object@" + Integer.toHexString(hashCode), str) 11 | 12 | assertSame(true, (obj: Any).isInstanceOf[Object]) 13 | assertSame(false, (obj: Any).isInstanceOf[Comparable[_]]) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JSExportTopLevelTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.annotation._ 5 | 6 | object JSExportTopLevelTest { 7 | def main(): Unit = { 8 | /* The main() function of this test does nothing. 9 | * The real test happens after loading the module from TestSuites.scala. 10 | */ 11 | () 12 | } 13 | 14 | @JSExportTopLevel("immutableField") 15 | val immutableField: String = "my immutable field value" 16 | 17 | @JSExportTopLevel("mutableField") 18 | var mutableField: Int = 42 19 | 20 | @JSExportTopLevel("simpleFunction") 21 | def simpleFunction(x: Int): Int = 22 | x * x 23 | 24 | @JSExportTopLevel("functionWithRest") 25 | def functionWithRest(x: Int, rest: Int*): Int = 26 | x * rest.sum 27 | 28 | @JSExportTopLevel("SimpleClass") 29 | class SimpleClass(val foo: Int) extends js.Object 30 | 31 | @JSExportTopLevel("SimpleObject") 32 | object SimpleObject extends js.Object { 33 | val bar: String = "the bar field" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JSForInTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert._ 6 | 7 | object JSForInTest { 8 | def main(): Unit = { 9 | val obj = new js.Object().asInstanceOf[js.Dynamic] 10 | obj.foo = "foobar" 11 | obj.bar = 5 12 | 13 | var iter = 0 14 | 15 | js.special.forin(obj) { key => 16 | if (iter == 0) 17 | assertSame("foo", key) 18 | else 19 | assertSame("bar", key) 20 | iter += 1 21 | } 22 | 23 | assertSame(2, iter) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JSImportCallTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert._ 6 | 7 | object JSImportCallTest { 8 | def main(): Unit = { 9 | /* We cannot actually wait for the result of an asynchronous computation 10 | * in our tests, so we only test that we indeed get a Promise. 11 | */ 12 | val promise = js.`import`[js.Any]("node:fs") 13 | ok((promise: Any).isInstanceOf[js.Promise[_]]) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JSImportMetaTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert._ 6 | 7 | object JSImportMetaTest { 8 | def main(): Unit = { 9 | val meta = js.`import`.meta 10 | assertSame("object", js.typeOf(meta)) 11 | assertSame("string", js.typeOf(meta.url)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/JSInteropTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | import testsuite.Assert.ok 5 | 6 | object JSInteropTest { 7 | def main(): Unit = { 8 | ok( 9 | testBasicTopLevel() && 10 | testBasicStatic() && 11 | testBasicInstance() && 12 | testArray() && 13 | testObject() && 14 | testOperators() && 15 | testLinkingInfo() 16 | ) 17 | } 18 | 19 | def testBasicTopLevel(): Boolean = { 20 | ("" + js.undefined) == "undefined" && 21 | js.eval("3 + 4").asInstanceOf[Int] == 7 && 22 | js.isUndefined(()) 23 | } 24 | 25 | def testBasicStatic(): Boolean = { 26 | js.Math.PI == 3.1415926535897932 && 27 | js.Math.abs(-5.6) == 5.6 && 28 | js.Math.clz32(6548) == 19 && 29 | js.Math.min(5, 8, 2, 12, 3) == 2 30 | } 31 | 32 | def testBasicInstance(): Boolean = { 33 | val d = new js.Date(1710190169564.0) 34 | d.getTime() == 1710190169564.0 && 35 | d.getUTCFullYear() == 2024 && { 36 | d.setTime(0.0) 37 | d.getTime() == 0.0 38 | } 39 | } 40 | 41 | def testArray(): Boolean = { 42 | val a = js.Array(1, 5, 3) 43 | a.length == 3 && 44 | a(0) == 1 && 45 | a(2) == 3 && { 46 | a(0) = 65 47 | a.push(78) 48 | a.length == 4 && 49 | a(0) == 65 && 50 | a(3) == 78 51 | } 52 | } 53 | 54 | def testObject(): Boolean = { 55 | val o = new js.Object().asInstanceOf[js.Dynamic] 56 | js.isUndefined(o.foo) && { 57 | o.foo = 5 58 | o.hasOwnProperty("foo").asInstanceOf[Boolean] && 59 | o.foo.asInstanceOf[Int] == 5 && { 60 | js.special.delete(o, "foo") 61 | !o.hasOwnProperty("foo").asInstanceOf[Boolean] 62 | } 63 | } 64 | } 65 | 66 | def testOperators(): Boolean = { 67 | val x = 5.asInstanceOf[js.Dynamic] 68 | val y = 11.asInstanceOf[js.Dynamic] 69 | same(+x, 5) && 70 | same(-x, -5) && 71 | same(~x, -6) && 72 | same(!x, false) && 73 | same(js.typeOf(x), "number") && // regular typeof 74 | same(js.typeOf(js.Dynamic.global.Date), "function") && // typeof global ref 75 | same(x + y, 16) && 76 | same(x - y, -6) && 77 | same(x * y, 55) && 78 | same(x / y, 0.45454545454545453) && 79 | same(y % x, 1) && 80 | same(x << 3.asInstanceOf[js.Dynamic], 40) && 81 | same(x >> 1.asInstanceOf[js.Dynamic], 2) && 82 | same(x >>> 2.asInstanceOf[js.Dynamic], 1) && 83 | same(x & y, 1) && 84 | same(x | y, 15) && 85 | same(x ^ y, 14) && 86 | same(x < y, true) && 87 | same(x <= y, true) && 88 | same(x > y, false) && 89 | same(x >= y, false) && 90 | same(x && y, 11) && 91 | same(x || y, 5) && 92 | same(x ** 3.asInstanceOf[js.Dynamic], 125) 93 | } 94 | 95 | def testLinkingInfo(): Boolean = { 96 | val linkingInfo = scala.scalajs.runtime.linkingInfo 97 | linkingInfo.esVersion >= 6 && 98 | linkingInfo.assumingES6 == true 99 | } 100 | 101 | def same(a: js.Any, b: js.Any): Boolean = js.special.strictEquals(a, b) 102 | } 103 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/MatchTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object MatchTest { 6 | def main(): Unit = { 7 | testFib() 8 | testString() 9 | testNull() 10 | } 11 | 12 | private def testFib(): Unit = { 13 | def test(x: Int, expected: Int) = ok(fib(x) == expected) 14 | def fib(x: Int): Int = x match { 15 | case 0 => 0 16 | case 1 | 2 => 1 17 | case _ => fib(x - 1) + fib(x - 2) 18 | } 19 | test(0, 0) 20 | test(1, 1) 21 | test(2, 1) 22 | test(3, 2) 23 | test(4, 3) 24 | test(5, 5) 25 | test(6, 8) 26 | } 27 | 28 | private def testString(): Unit = { 29 | def test(x: String, expected: String) = ok(animalSound(x) == expected) 30 | def animalSound(animal: String) = animal match { 31 | case "dog" => "bow-wow" 32 | case "cat" => "meow" 33 | case _ => "unknown" 34 | } 35 | test("dog", "bow-wow") 36 | test("cat", "meow") 37 | test("???", "unknown") 38 | } 39 | 40 | private def testNull(): Unit = { 41 | def strTest(x: String) = x match { 42 | case null => "null" 43 | case "foo" => "bar" 44 | case "bar" => "babar" 45 | case x => x 46 | } 47 | ok(strTest(null) == "null") 48 | ok(strTest("a") == "a") 49 | } 50 | 51 | // TODO: Add test case that no default case _ 52 | // we can't test with node v22 at this moment due to the lack of try_table 53 | } 54 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/NonNativeJSClassTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert._ 6 | 7 | object NonNativeJSClassTest { 8 | def main(): Unit = { 9 | testBasic() 10 | testClassCaptures() 11 | testInheritanceAndSuper() 12 | testObject() 13 | testNewTarget() 14 | } 15 | 16 | def testBasic(): Unit = { 17 | val o = new MyClass("string param") 18 | 19 | // Field access and initialization 20 | assertSame(0, o.asInstanceOf[js.Dynamic].i) 21 | assertSame(0, o.i) 22 | o.i = 42 23 | assertSame(42, o.i) 24 | 25 | // Method call 26 | assertSame(8, o.foobar(5)) 27 | 28 | // Property read/write 29 | assertSame("prop string param", o.prop) 30 | o.prop = "foobar" 31 | assertSame("prop set foobar", o.prop) 32 | 33 | // Instance test -- also ensures that we get the same MyClass class 34 | assertSame(true, (o: Any).isInstanceOf[MyClass]) 35 | } 36 | 37 | def testClassCaptures(): Unit = { 38 | def localJSClass(capture: Int): js.Dynamic = { 39 | class Local(s: String) extends MyClass(s) { 40 | def getCapture(): Int = capture 41 | } 42 | js.constructorOf[Local] 43 | } 44 | 45 | // Create classes 46 | 47 | val localClass1 = localJSClass(5) 48 | assertSame("function", js.typeOf(localClass1)) 49 | val localClass2 = localJSClass(6) 50 | assertSame(true, localClass2 ne localClass1) 51 | 52 | val obj1 = js.Dynamic.newInstance(localClass1)("foobar") 53 | assertSame("prop foobar", obj1.prop) 54 | assertSame(5, obj1.getCapture()) 55 | 56 | val obj2 = js.Dynamic.newInstance(localClass2)("babar") 57 | assertSame("prop babar", obj2.prop) 58 | assertSame(6, obj2.getCapture()) 59 | 60 | // Instance tests -- the two local classes are independent 61 | assertSame(true, (obj1: Any).isInstanceOf[MyClass]) 62 | assertSame(true, js.special.instanceof(obj1, localClass1)) 63 | assertSame(false, js.special.instanceof(obj1, localClass2)) 64 | assertSame(true, js.special.instanceof(obj2, localClass2)) 65 | } 66 | 67 | def testInheritanceAndSuper(): Unit = { 68 | val sub = new Sub() 69 | 70 | assertSame("prop subarg sub", sub.prop) 71 | sub.prop = "new value" 72 | assertSame("prop set from sub new value sub", sub.prop) 73 | 74 | assertSame(15, sub.foobar(6)) 75 | 76 | assertSame(true, (sub: Any).isInstanceOf[MyClass]) 77 | assertSame(true, (sub: Any).isInstanceOf[Sub]) 78 | } 79 | 80 | def testObject(): Unit = { 81 | assertSame(5, MyObject.foo(4)) 82 | 83 | val obj1 = MyObject 84 | val obj2 = MyObject 85 | assertSame(obj1, obj2) 86 | } 87 | 88 | def testNewTarget(): Unit = { 89 | val parent = new NewTargetParent 90 | assertSame(js.constructorOf[NewTargetParent], parent.myNewTarget) 91 | 92 | val child = new NewTargetChild 93 | assertSame(js.constructorOf[NewTargetChild], child.myNewTarget) 94 | } 95 | 96 | class MyClass(private var s: String) extends js.Object { 97 | var i: Int = _ 98 | 99 | def foobar(x: Int): Int = x + 3 100 | 101 | def prop: String = "prop " + s 102 | def prop_=(v: String): Unit = 103 | s = "set " + v 104 | } 105 | 106 | class Sub extends MyClass("subarg") { 107 | override def foobar(x: Int): Int = super.foobar(x * 2) 108 | 109 | override def prop: String = super.prop + " sub" 110 | override def prop_=(v: String): Unit = 111 | super.prop_=("from sub " + v) 112 | } 113 | 114 | object MyObject extends js.Object { 115 | def foo(x: Int): Int = x + 1 116 | } 117 | 118 | class NewTargetParent extends js.Object { 119 | val myNewTarget = js.`new`.target 120 | } 121 | 122 | class NewTargetChild extends NewTargetParent 123 | } 124 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/Simple.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object Simple { 6 | def main(): Unit = { 7 | ok(true) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/StaticFieldsTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js.annotation.JSExportTopLevel 4 | 5 | import testsuite.Assert.assertSame 6 | 7 | object StaticFieldsTest { 8 | /* We use @JSExportTopLevel to force the fields to be encoded as static in, 9 | * the IR; not for the exported behavior per se. 10 | */ 11 | 12 | @JSExportTopLevel("StaticFieldsTest_valStatic") 13 | val valStatic: Int = 42 14 | 15 | @JSExportTopLevel("StaticFieldsTest_varStatic") 16 | var varStatic: String = "hello" 17 | 18 | def main(): Unit = { 19 | assertSame(42, valStatic) 20 | assertSame("hello", varStatic) 21 | varStatic = "foo" 22 | assertSame("foo", varStatic) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/StaticMethodTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object StaticMethodTest { 6 | def main(): Unit = { 7 | ok(java.lang.Integer.sum(5, 65) == 70) 8 | ok(java.lang.Integer.reverseBytes(0x01020304) == 0x04030201) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/StringEncodingTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object StringEncodingTest { 6 | def main(): Unit = { 7 | illformedUTF16Test() 8 | wellformedUTF16Test() 9 | } 10 | 11 | def illformedUTF16Test(): Unit = { 12 | val s = "\ud834a\udd1e" 13 | assertSame(3, s.length()) 14 | assertSame(0xd834, s.charAt(0).toInt) 15 | assertSame('a'.toInt, s.charAt(1).toInt) 16 | assertSame(0xdd1e, s.charAt(2).toInt) 17 | } 18 | 19 | def wellformedUTF16Test(): Unit = { 20 | val s = "\ud834\udd1e" 21 | assertSame(2, s.length()) 22 | assertSame(0xd834, s.charAt(0).toInt) 23 | assertSame(0xdd1e, s.charAt(1).toInt) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ThrowAndTryTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object ThrowAndTryTest { 6 | def main(): Unit = { 7 | testTryCatch() 8 | testTryCatchWithRefResultType() 9 | testTryFinally() 10 | } 11 | 12 | private def testTryCatch(): Unit = { 13 | try { 14 | doNotThrow() 15 | } catch { 16 | case e: IllegalArgumentException => 17 | fail() 18 | } 19 | 20 | try { 21 | throwIllegalArgument() 22 | fail() 23 | } catch { 24 | case e: IllegalArgumentException => 25 | assertSame("boom", e.getMessage()) 26 | } 27 | 28 | try { 29 | try { 30 | throwIllegalArgument() 31 | fail() 32 | } catch { 33 | case e: UnsupportedOperationException => 34 | // This one should not be caught 35 | fail() 36 | } 37 | fail() // the exception should have been rethrown 38 | } catch { 39 | case e: IllegalArgumentException => 40 | assertSame("boom", e.getMessage()) 41 | } 42 | } 43 | 44 | private class A(val value: Int) 45 | 46 | private def testTryCatchWithRefResultType(): Unit = { 47 | val x = 48 | try { 49 | new A(doNotThrow()) 50 | } catch { 51 | case e: IllegalArgumentException => new A(20) 52 | } 53 | assertSame(5, x.value) 54 | 55 | val y = 56 | try { 57 | new A(throwIllegalArgument()) 58 | } catch { 59 | case e: IllegalArgumentException => new A(20) 60 | } 61 | assertSame(20, y.value) 62 | } 63 | 64 | private def testTryFinally(): Unit = { 65 | var didFinally = false 66 | 67 | try { 68 | try { 69 | throwIllegalArgument() 70 | fail() 71 | } finally { 72 | didFinally = true 73 | } 74 | fail() // the exception should have been rethrown 75 | } catch { 76 | case e: IllegalArgumentException => () 77 | } 78 | ok(didFinally) 79 | 80 | didFinally = false 81 | try { 82 | doNotThrow() 83 | } finally { 84 | didFinally = true 85 | } 86 | ok(didFinally) 87 | } 88 | 89 | private def throwIllegalArgument(): Int = 90 | throw new IllegalArgumentException("boom") 91 | 92 | private def doNotThrow(): Int = 5 93 | } 94 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ThrowablesTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | import testsuite.Assert.ok 5 | 6 | /** Test that we can manipulate instances of exception classes, but not throw them. */ 7 | object ThrowablesTest { 8 | def main(): Unit = { 9 | testBasicThrowable() 10 | testBasicException() 11 | 12 | testEmptyStackTrace() 13 | } 14 | 15 | def testBasicThrowable(): Unit = { 16 | val t = new Throwable("boom") 17 | ok(t.getMessage() == "boom") 18 | ok(t.getCause() == null) 19 | ok(t.toString() == "java.lang.Throwable: boom") 20 | 21 | val t2 = new Throwable("derived", t) 22 | ok(t2.getMessage() == "derived") 23 | ok(t2.getCause() eq t) 24 | ok(t2.toString() == "java.lang.Throwable: derived") 25 | } 26 | 27 | def testBasicException(): Unit = { 28 | val t = new Exception("boom") 29 | ok(t.getMessage() == "boom") 30 | ok(t.getCause() == null) 31 | ok(t.toString() == "java.lang.Exception: boom") 32 | 33 | val t2 = new Exception("derived", t) 34 | ok(t2.getMessage() == "derived") 35 | ok(t2.getCause() eq t) 36 | ok(t2.toString() == "java.lang.Exception: derived") 37 | } 38 | 39 | def testEmptyStackTrace(): Unit = { 40 | val t = new Throwable("boom") 41 | val stack = t.getStackTrace() 42 | ok(stack.length == 0) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/ToStringTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object ToStringTest { 6 | def main(): Unit = { 7 | ok( 8 | testBoolean(true, "true") && 9 | testBoolean(false, "false") && 10 | !testBoolean(true, "tru") && // confidence test 11 | testAny(true, "true") && 12 | testAny(false, "false") && 13 | testChar('A', "A") && 14 | testAny('A', "A") && 15 | testByte(54.toByte, "54") && 16 | testAny(54.toByte, "54") && 17 | testShort(6543.toShort, "6543") && 18 | testAny(6543.toShort, "6543") && 19 | testInt(-3423456, "-3423456") && 20 | testAny(-3423456, "-3423456") && 21 | testLong(1234567891011L, "1234567891011") && 22 | testAny(1234567891011L, "1234567891011") && 23 | testFloat(1.5f, "1.5") && 24 | testAny(1.5f, "1.5") && 25 | testDouble(1.4, "1.4") && 26 | testAny(1.4, "1.4") && 27 | testString("foo", "foo") && 28 | testAny("foo", "foo") && 29 | testString(null, "null") && 30 | testAny(null, "null") && 31 | testUndef((), "undefined") && 32 | testAny((), "undefined") && 33 | testMyToString(new MyToString(), "my toString") && 34 | testMyToString(null, "null") && 35 | testAny(new MyToString(), "my toString") && 36 | testToStringNull(new ToStringNull(), "null") && 37 | testToStringNull(null, "null") && 38 | testAny(new ToStringNull(), "null") && 39 | testConcat(1, "foo", "1foo") && 40 | testConcat(2, null, "2null") 41 | ) 42 | } 43 | 44 | def testBoolean(x: Boolean, expected: String): Boolean = 45 | "" + x == expected 46 | 47 | def testChar(x: Char, expected: String): Boolean = 48 | "" + x == expected 49 | 50 | def testByte(x: Byte, expected: String): Boolean = 51 | "" + x == expected 52 | 53 | def testShort(x: Short, expected: String): Boolean = 54 | "" + x == expected 55 | 56 | def testInt(x: Int, expected: String): Boolean = 57 | "" + x == expected 58 | 59 | def testLong(x: Long, expected: String): Boolean = 60 | "" + x == expected 61 | 62 | def testFloat(x: Float, expected: String): Boolean = 63 | "" + x == expected 64 | 65 | def testDouble(x: Double, expected: String): Boolean = 66 | "" + x == expected 67 | 68 | def testString(x: String, expected: String): Boolean = 69 | "" + x == expected 70 | 71 | def testUndef(x: Unit, expected: String): Boolean = 72 | "" + x == expected 73 | 74 | def testMyToString(x: MyToString, expected: String): Boolean = 75 | "" + x == expected 76 | 77 | def testToStringNull(x: ToStringNull, expected: String): Boolean = 78 | "" + x == expected 79 | 80 | def testAny(x: Any, expected: String): Boolean = 81 | "" + x == expected 82 | 83 | def testConcat(x: Int, y: String, expected: String): Boolean = 84 | "" + x + y == expected 85 | 86 | class MyToString { 87 | @noinline 88 | override def toString(): String = "my toString" 89 | } 90 | 91 | class ToStringNull { 92 | @noinline 93 | override def toString(): String = null // evil 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/TryFinallyReturnTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.assertSame 4 | 5 | /** Imported from the Scala test case `test/files/run/finally.scala`. */ 6 | object TryFinallyReturnTest { 7 | // Simulate a `println` API that we can check afterwards 8 | var printlnOutput: String = "" 9 | 10 | def println(x: Any): Unit = 11 | printlnOutput = printlnOutput + x + "\n" 12 | 13 | val expectedOutput = raw"""Running throwCatchFinally 14 | hi 15 | In Finally 16 | java.lang.RuntimeException: ouch 17 | ---------------------------------------- 18 | Running retCatch 19 | java.lang.Exception 20 | in finally 21 | ---------------------------------------- 22 | Running throwCatch 23 | java.lang.Exception 24 | in finally 25 | CAUGHT: java.lang.Exception 26 | ---------------------------------------- 27 | Running retBody 28 | in finally 29 | ---------------------------------------- 30 | Running throwBody 31 | java.lang.Exception 32 | in finally 33 | ---------------------------------------- 34 | Running retFinally 35 | body 36 | in finally 1 37 | in finally 2 38 | ---------------------------------------- 39 | Running throwFinally 40 | body 41 | in finally 42 | java.lang.Exception 43 | ---------------------------------------- 44 | Running nestedFinallyBlocks 45 | in finally 1 46 | in finally 2 47 | ---------------------------------------- 48 | Running nestedFinallyBlocks2 49 | in try 1 50 | in finally 1 51 | in fall-through 52 | in finally 2 53 | ---------------------------------------- 54 | """ 55 | 56 | def main(): Unit = { 57 | test(throwCatchFinally(), "throwCatchFinally") 58 | test(retCatch(), "retCatch") 59 | test(throwCatch(), "throwCatch") 60 | test(retBody(), "retBody") 61 | test(throwBody(), "throwBody") 62 | test(retFinally(), "retFinally") 63 | test(throwFinally(), "throwFinally") 64 | test(nestedFinallyBlocks(), "nestedFinallyBlocks") 65 | test(nestedFinallyBlocks2(), "nestedFinallyBlocks2") 66 | 67 | assertSame(expectedOutput, printlnOutput) 68 | } 69 | 70 | // test that finally is not covered by any exception handlers. 71 | def throwCatchFinally(): Unit = { 72 | try { 73 | bar() 74 | } catch { 75 | case e: Throwable => println(e) 76 | } 77 | } 78 | 79 | // test that finally is not covered by any exception handlers. 80 | def bar(): Unit = { 81 | try { 82 | println("hi") 83 | } catch { 84 | case e: Throwable => println("SHOULD NOT GET HERE") 85 | } finally { 86 | println("In Finally") 87 | throw new RuntimeException("ouch") 88 | } 89 | } 90 | 91 | // return in catch (finally is executed) 92 | def retCatch(): Unit = { 93 | try { 94 | throw new Exception 95 | } catch { 96 | case e: Throwable => 97 | println(e); 98 | return 99 | } finally println("in finally") 100 | } 101 | 102 | // throw in catch (finally is executed, exception propagated) 103 | def throwCatch(): Unit = { 104 | try { 105 | throw new Exception 106 | } catch { 107 | case e: Throwable => 108 | println(e); 109 | throw e 110 | } finally println("in finally") 111 | } 112 | 113 | // return inside body (finally is executed) 114 | def retBody(): Unit = { 115 | try { 116 | return 117 | } catch { 118 | case e: Throwable => 119 | println(e); 120 | throw e 121 | } finally println("in finally") 122 | } 123 | 124 | // throw inside body (finally and catch are executed) 125 | def throwBody(): Unit = { 126 | try { 127 | throw new Exception 128 | } catch { 129 | case e: Throwable => 130 | println(e); 131 | } finally println("in finally") 132 | } 133 | 134 | // return inside finally (each finally is executed once) 135 | def retFinally(): Unit = { 136 | try { 137 | try println("body") 138 | finally { 139 | println("in finally 1") 140 | return 141 | } 142 | } finally println("in finally 2") 143 | } 144 | 145 | // throw inside finally (finally is executed once, exception is propagated) 146 | def throwFinally(): Unit = { 147 | try { 148 | try println("body") 149 | finally { 150 | println("in finally") 151 | throw new Exception 152 | } 153 | } catch { 154 | case e: Throwable => println(e) 155 | } 156 | } 157 | 158 | // nested finally blocks with return value 159 | def nestedFinallyBlocks(): Int = 160 | try { 161 | try { 162 | return 10 163 | } finally { 164 | try { () } 165 | catch { case _: Throwable => () } 166 | println("in finally 1") 167 | } 168 | } finally { 169 | println("in finally 2") 170 | } 171 | 172 | def nestedFinallyBlocks2(): Int = { 173 | try { 174 | try { 175 | println("in try 1") 176 | } finally { 177 | println("in finally 1") 178 | } 179 | println("in fall-through") 180 | 0 181 | } finally { 182 | println("in finally 2") 183 | } 184 | } 185 | 186 | def test[A](m: => A, name: String): Unit = { 187 | println("Running %s".format(name)) 188 | try { 189 | m 190 | } catch { 191 | case e: Throwable => println("CAUGHT: " + e) 192 | } 193 | println("-" * 40) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/UnitPatMatTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert._ 4 | 5 | object UnitPatMatTest { 6 | def main(): Unit = { 7 | assertEquals("failure", 5, 5) 8 | } 9 | 10 | @noinline 11 | def assertEquals(message: String, expected: Any, actual: Any): Unit = { 12 | if (!java.util.Objects.equals(expected, actual)) { 13 | (expected, actual) match { 14 | case (expectedString: String, actualString: String) => 15 | val cleanMsg: String = if (message == null) "" else message 16 | throw new AssertionError(cleanMsg) 17 | 18 | case _ => 19 | failNotEquals(message, expected, actual) 20 | } 21 | } 22 | } 23 | 24 | @noinline 25 | def failNotEquals(message: String, expected: Any, actual: Any): Unit = 26 | throw new AssertionError(message) 27 | } 28 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/VirtualDispatch.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import testsuite.Assert.ok 4 | 5 | object VirtualDispatch { 6 | def main(): Unit = { 7 | val a = new A 8 | val b = new B 9 | 10 | ok( 11 | testA(a) && 12 | testB(a, isInstanceOfA = true) && 13 | testB(b, isInstanceOfA = false) && 14 | testC(a, isInstanceOfA = true) && 15 | testC(b, isInstanceOfA = false) 16 | ) 17 | } 18 | 19 | def testA(a: A): Boolean = { 20 | a.a == 2 && a.impl == 2 && a.b == 1 && a.c == 1 21 | } 22 | 23 | def testB(b: B, isInstanceOfA: Boolean): Boolean = { 24 | if (isInstanceOfA) { 25 | b.b == 1 && b.c == 1 && b.impl == 2 26 | } else { 27 | b.b == 1 && b.c == 1 && b.impl == 0 28 | } 29 | } 30 | 31 | def testC(c: C, isInstanceOfA: Boolean): Boolean = { 32 | if (isInstanceOfA) { 33 | c.c == 1 && c.impl == 2 34 | } else { 35 | c.c == 1 && c.impl == 0 36 | } 37 | } 38 | 39 | class A extends B { 40 | def a: Int = 2 41 | override def impl = 2 42 | } 43 | 44 | class B extends C { 45 | def b: Int = 1 46 | override def c: Int = 1 47 | } 48 | 49 | abstract class C { 50 | def c: Int 51 | def impl: Int = 0 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test-suite/src/main/scala/testsuite/core/WrapUnwrapThrowableTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.core 2 | 3 | import scala.scalajs.js 4 | 5 | import testsuite.Assert.{assertSame, fail} 6 | 7 | object WrapUnwrapThrowableTest { 8 | def main(): Unit = { 9 | testWrapAsThrowable() 10 | testUnwrapFromThrowable() 11 | } 12 | 13 | def testWrapAsThrowable(): Unit = { 14 | // Wraps a js.Object 15 | val obj = new js.Object 16 | val e1 = js.special.wrapAsThrowable(obj) 17 | e1 match { 18 | case e1: js.JavaScriptException => assertSame(obj, e1.exception) 19 | case _ => fail() 20 | } 21 | 22 | // Wraps null 23 | val e2 = js.special.wrapAsThrowable(null) 24 | e2 match { 25 | case e2: js.JavaScriptException => assertSame(null, e2.exception) 26 | case _ => fail() 27 | } 28 | 29 | // Does not wrap a Throwable 30 | val th = new IllegalArgumentException 31 | assertSame(th, js.special.wrapAsThrowable(th)) 32 | 33 | // Does not double-wrap 34 | assertSame(e1, js.special.wrapAsThrowable(e1)) 35 | } 36 | 37 | def testUnwrapFromThrowable(): Unit = { 38 | // Unwraps a JavaScriptException 39 | val obj = new js.Object 40 | assertSame(obj, js.special.unwrapFromThrowable(new js.JavaScriptException(obj))) 41 | 42 | // Does not unwrap a Throwable 43 | val th = new IllegalArgumentException 44 | assertSame(th, js.special.unwrapFromThrowable(th)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /testrun.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |Look at the console
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/src/test/scala/tests/CoreTests.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import scala.concurrent.Future 4 | 5 | import scala.scalajs.js 6 | import scala.scalajs.js.annotation._ 7 | 8 | import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits._ 9 | 10 | import org.scalajs.linker._ 11 | import org.scalajs.linker.interface._ 12 | import org.scalajs.linker.standard.WebAssemblyLinkerImpl 13 | 14 | import org.scalajs.logging._ 15 | 16 | /** The `tests/test` command compiles the Scala sources under `test-suite` into Wasm with static 17 | * initializers (see `cli/src/main/scala/Main.scala`), where these static initializers should point 18 | * to each main method in the classes of test-suites. 19 | * 20 | * These static initializers will be invoked from the start function in Wasm, which in turn will be 21 | * invoked on instantiation of the Wasm module. 22 | * 23 | * Once we compile the test-suites into Wasm and put them under the `./target` directory, 24 | * `tests/test` will instantiate those Wasm modules. If the test suites have an assertion failure, 25 | * the Wasm module will execute `unreachable` and fail during instantiation. 26 | */ 27 | class CoreTests extends munit.FunSuite { 28 | import CoreTests._ 29 | 30 | private val classpath = { 31 | val cpEnvVar = js.Dynamic.global.process.env.SCALAJS_CLASSPATH 32 | 33 | (cpEnvVar: Any) match { 34 | case cpEnvVar: String if cpEnvVar != "" => 35 | cpEnvVar.split(';').toList 36 | case _ => 37 | throw new IllegalArgumentException("The classpath was not provided.") 38 | } 39 | } 40 | 41 | private val globalIRCache = StandardImpl.irFileCache() 42 | 43 | private val linkerConfig = StandardConfig() 44 | .withESFeatures(_.withESVersion(ESVersion.ES2016)) // to be able to link `**` 45 | .withSemantics(_.optimized) // because that's the only thing we actually support at the moment 46 | .withModuleKind(ModuleKind.ESModule) 47 | .withOptimizer(false) 48 | .withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs")) 49 | 50 | TestSuites.suites.foreach { suite => 51 | test(suite.className) { 52 | testOneSuite(suite) 53 | } 54 | } 55 | 56 | private def testOneSuite(suite: TestSuites.TestSuite): Future[Unit] = { 57 | val className = suite.className 58 | val methodName = suite.methodName 59 | 60 | val cache = globalIRCache.newCache 61 | val irFilesFuture = NodeIRContainer.fromClasspath(classpath).map(_._1).flatMap(cache.cached _) 62 | 63 | val logger = new ScalaConsoleLogger(Level.Error) 64 | 65 | val linker = WebAssemblyLinkerImpl.linker(linkerConfig) 66 | val moduleInitializers = List(ModuleInitializer.mainMethod(className, methodName)) 67 | 68 | val outputDirRelToPwd = s"./target/$className/" 69 | createDir(outputDirRelToPwd) 70 | val output = NodeOutputDirectory(outputDirRelToPwd) 71 | 72 | val outputDirRelToMyJSFile = s"../../../../target/$className/" 73 | 74 | for { 75 | irFiles <- irFilesFuture 76 | linkerResult <- linker.link(irFiles, moduleInitializers, output, logger) 77 | moduleExports <- js.`import`[js.Dynamic](s"$outputDirRelToMyJSFile/main.mjs").toFuture 78 | } yield { 79 | for (postLoadTests <- suite.postLoadTests) 80 | postLoadTests(moduleExports) 81 | } 82 | } 83 | } 84 | 85 | object CoreTests { 86 | def createDir(dir: String): Unit = 87 | mkdirSync(dir, js.Dynamic.literal(recursive = true)) 88 | 89 | @js.native 90 | @JSImport("node:fs") 91 | def mkdirSync(path: String, options: js.Object = js.native): Unit = js.native 92 | } 93 | -------------------------------------------------------------------------------- /tests/src/test/scala/tests/TestSuites.scala: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import scala.scalajs.js 4 | 5 | object TestSuites { 6 | case class TestSuite( 7 | className: String, 8 | methodName: String = "main", 9 | postLoadTests: Option[js.Dynamic => Unit] = None 10 | ) 11 | 12 | val suites = List( 13 | TestSuite("testsuite.core.Simple"), 14 | TestSuite("testsuite.core.Add"), 15 | TestSuite("testsuite.core.ArrayTest"), 16 | TestSuite("testsuite.core.ArrayReflectTest"), 17 | TestSuite("testsuite.core.BasicListTest"), 18 | TestSuite("testsuite.core.CloneTest"), 19 | TestSuite("testsuite.core.ControlStructuresTest"), 20 | TestSuite("testsuite.core.VirtualDispatch"), 21 | TestSuite("testsuite.core.InterfaceCall"), 22 | TestSuite("testsuite.core.AsInstanceOfTest"), 23 | TestSuite("testsuite.core.IsInstanceOfTest"), 24 | TestSuite("testsuite.core.ClassOfTest"), 25 | TestSuite("testsuite.core.ClosureTest"), 26 | TestSuite("testsuite.core.FieldsTest"), 27 | TestSuite("testsuite.core.FloatingPointRemTest"), 28 | TestSuite("testsuite.core.GetClassTest"), 29 | TestSuite("testsuite.core.JLObjectInstanceTest"), 30 | TestSuite("testsuite.core.JSForInTest"), 31 | TestSuite("testsuite.core.JSImportCallTest"), 32 | TestSuite("testsuite.core.JSImportMetaTest"), 33 | TestSuite("testsuite.core.JSInteropTest"), 34 | TestSuite("testsuite.core.HijackedClassesDispatchTest"), 35 | TestSuite("testsuite.core.HijackedClassesMonoTest"), 36 | TestSuite("testsuite.core.HijackedClassesUpcastTest"), 37 | TestSuite("testsuite.core.ImportTest"), 38 | TestSuite("testsuite.core.NonNativeJSClassTest"), 39 | TestSuite("testsuite.core.StaticFieldsTest"), 40 | TestSuite("testsuite.core.StaticMethodTest"), 41 | TestSuite("testsuite.core.ThrowAndTryTest"), 42 | TestSuite("testsuite.core.ThrowablesTest"), 43 | TestSuite("testsuite.core.TryFinallyReturnTest"), 44 | TestSuite("testsuite.core.ToStringTest"), 45 | TestSuite("testsuite.core.UnitPatMatTest"), 46 | TestSuite("testsuite.core.MatchTest"), 47 | TestSuite("testsuite.core.WrapUnwrapThrowableTest"), 48 | TestSuite("testsuite.core.StringEncodingTest"), 49 | TestSuite( 50 | "testsuite.core.JSExportTopLevelTest", 51 | postLoadTests = Some({ moduleExports => 52 | import munit.Assertions.assert 53 | 54 | assert((moduleExports.immutableField: Any) == "my immutable field value") 55 | assert((moduleExports.mutableField: Any) == 42) 56 | assert((moduleExports.simpleFunction(5): Any) == 25) 57 | assert((moduleExports.functionWithRest(3, 5, 6, 7): Any) == 54) 58 | assert((moduleExports.SimpleObject.bar: Any) == "the bar field") 59 | 60 | val obj = js.Dynamic.newInstance(moduleExports.SimpleClass)(456) 61 | assert((obj.foo: Any) == 456) 62 | }) 63 | ) 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/WebAssemblyLinkerBackend.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | 5 | import java.nio.ByteBuffer 6 | import java.nio.charset.StandardCharsets 7 | 8 | import org.scalajs.logging.Logger 9 | 10 | import org.scalajs.linker._ 11 | import org.scalajs.linker.interface._ 12 | import org.scalajs.linker.interface.unstable._ 13 | import org.scalajs.linker.standard._ 14 | 15 | import org.scalajs.linker.backend.javascript.{ByteArrayWriter, SourceMapWriter} 16 | import org.scalajs.linker.backend.webassembly._ 17 | 18 | import org.scalajs.linker.backend.wasmemitter.Emitter 19 | 20 | final class WebAssemblyLinkerBackend( 21 | linkerConfig: StandardConfig, 22 | val coreSpec: CoreSpec 23 | ) extends LinkerBackend { 24 | require( 25 | linkerConfig.moduleKind == ModuleKind.ESModule, 26 | s"The WebAssembly backend only supports ES modules; was ${linkerConfig.moduleKind}." 27 | ) 28 | require( 29 | !linkerConfig.optimizer, 30 | "The WebAssembly backend does not support the optimizer yet." 31 | ) 32 | 33 | val loaderJSFileName = OutputPatternsImpl.jsFile(linkerConfig.outputPatterns, "__loader") 34 | 35 | private val emitter: Emitter = 36 | new Emitter(Emitter.Config(coreSpec, loaderJSFileName)) 37 | 38 | val symbolRequirements: SymbolRequirement = emitter.symbolRequirements 39 | 40 | def injectedIRFiles: Seq[IRFile] = emitter.injectedIRFiles 41 | 42 | def emit(moduleSet: ModuleSet, output: OutputDirectory, logger: Logger)(implicit 43 | ec: ExecutionContext 44 | ): Future[Report] = { 45 | val onlyModule = moduleSet.modules match { 46 | case onlyModule :: Nil => 47 | onlyModule 48 | case modules => 49 | throw new UnsupportedOperationException( 50 | "The WebAssembly backend does not support multiple modules. Found: " + 51 | modules.map(_.id.id).mkString(", ") 52 | ) 53 | } 54 | val moduleID = onlyModule.id.id 55 | 56 | val emitterResult = emitter.emit(onlyModule, logger) 57 | val wasmModule = emitterResult.wasmModule 58 | 59 | val outputImpl = OutputDirectoryImpl.fromOutputDirectory(output) 60 | 61 | val watFileName = s"$moduleID.wat" 62 | val wasmFileName = s"$moduleID.wasm" 63 | val sourceMapFileName = s"$wasmFileName.map" 64 | val jsFileName = OutputPatternsImpl.jsFile(linkerConfig.outputPatterns, moduleID) 65 | 66 | val filesToProduce0 = Set( 67 | wasmFileName, 68 | loaderJSFileName, 69 | jsFileName 70 | ) 71 | val filesToProduce1 = 72 | if (linkerConfig.sourceMap) filesToProduce0 + sourceMapFileName 73 | else filesToProduce0 74 | val filesToProduce = 75 | if (linkerConfig.prettyPrint) filesToProduce1 + watFileName 76 | else filesToProduce1 77 | 78 | def maybeWriteWatFile(): Future[Unit] = { 79 | if (linkerConfig.prettyPrint) { 80 | val textOutput = new TextWriter(wasmModule).write() 81 | val textOutputBytes = textOutput.getBytes(StandardCharsets.UTF_8) 82 | outputImpl.writeFull(watFileName, ByteBuffer.wrap(textOutputBytes)) 83 | } else { 84 | Future.unit 85 | } 86 | } 87 | 88 | def writeWasmFile(): Future[Unit] = { 89 | val emitDebugInfo = !linkerConfig.minify 90 | 91 | if (linkerConfig.sourceMap) { 92 | val sourceMapWriter = new ByteArrayWriter 93 | 94 | val wasmFileURI = s"./$wasmFileName" 95 | val sourceMapURI = s"./$sourceMapFileName" 96 | 97 | val smWriter = 98 | new SourceMapWriter(sourceMapWriter, wasmFileURI, linkerConfig.relativizeSourceMapBase) 99 | val binaryOutput = new BinaryWriter.WithSourceMap( 100 | wasmModule, 101 | emitDebugInfo, 102 | smWriter, 103 | sourceMapURI 104 | ).write() 105 | smWriter.complete() 106 | 107 | outputImpl.writeFull(wasmFileName, ByteBuffer.wrap(binaryOutput)).flatMap { _ => 108 | outputImpl.writeFull(sourceMapFileName, sourceMapWriter.toByteBuffer()) 109 | } 110 | } else { 111 | val binaryOutput = new BinaryWriter(wasmModule, emitDebugInfo).write() 112 | outputImpl.writeFull(wasmFileName, ByteBuffer.wrap(binaryOutput)) 113 | } 114 | } 115 | 116 | def writeLoaderFile(): Future[Unit] = 117 | outputImpl.writeFull(loaderJSFileName, ByteBuffer.wrap(emitterResult.loaderContent)) 118 | 119 | def writeJSFile(): Future[Unit] = { 120 | val jsFileOutputBytes = emitterResult.jsFileContent.getBytes(StandardCharsets.UTF_8) 121 | outputImpl.writeFull(jsFileName, ByteBuffer.wrap(jsFileOutputBytes)) 122 | } 123 | 124 | for { 125 | existingFiles <- outputImpl.listFiles() 126 | _ <- Future.sequence(existingFiles.filterNot(filesToProduce).map(outputImpl.delete(_))) 127 | _ <- maybeWriteWatFile() 128 | _ <- writeWasmFile() 129 | _ <- writeLoaderFile() 130 | _ <- writeJSFile() 131 | } yield { 132 | val reportModule = new ReportImpl.ModuleImpl( 133 | moduleID, 134 | jsFileName, 135 | None, 136 | linkerConfig.moduleKind 137 | ) 138 | new ReportImpl(List(reportModule)) 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/EmbeddedConstants.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.wasmemitter 2 | 3 | object EmbeddedConstants { 4 | /* Values returned by the `jsValueType` helper. 5 | * 6 | * 0: false 7 | * 1: true 8 | * 2: string 9 | * 3: number 10 | * 4: undefined 11 | * 5: everything else 12 | * 13 | * This encoding has the following properties: 14 | * 15 | * - false and true also return their value as the appropriate i32. 16 | * - the types implementing `Comparable` are consecutive from 0 to 3. 17 | */ 18 | 19 | final val JSValueTypeFalse = 0 20 | final val JSValueTypeTrue = 1 21 | final val JSValueTypeString = 2 22 | final val JSValueTypeNumber = 3 23 | final val JSValueTypeUndefined = 4 24 | final val JSValueTypeBigInt = 5 25 | final val JSValueTypeSymbol = 6 26 | final val JSValueTypeOther = 7 27 | 28 | // Values for `typeData.kind` 29 | 30 | final val KindVoid = 0 31 | final val KindBoolean = 1 32 | final val KindChar = 2 33 | final val KindByte = 3 34 | final val KindShort = 4 35 | final val KindInt = 5 36 | final val KindLong = 6 37 | final val KindFloat = 7 38 | final val KindDouble = 8 39 | final val KindArray = 9 40 | final val KindObject = 10 // j.l.Object 41 | final val KindBoxedUnit = 11 42 | final val KindBoxedBoolean = 12 43 | final val KindBoxedCharacter = 13 44 | final val KindBoxedByte = 14 45 | final val KindBoxedShort = 15 46 | final val KindBoxedInteger = 16 47 | final val KindBoxedLong = 17 48 | final val KindBoxedFloat = 18 49 | final val KindBoxedDouble = 19 50 | final val KindBoxedString = 20 51 | final val KindClass = 21 52 | final val KindInterface = 22 53 | final val KindJSType = 23 54 | 55 | final val KindLastPrimitive = KindDouble 56 | } 57 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.wasmemitter 2 | 3 | import org.scalajs.ir.Names._ 4 | import org.scalajs.ir.Types._ 5 | import org.scalajs.ir.OriginalName 6 | import org.scalajs.ir.Position 7 | 8 | import org.scalajs.linker.interface._ 9 | import org.scalajs.linker.interface.unstable._ 10 | import org.scalajs.linker.standard._ 11 | import org.scalajs.linker.standard.ModuleSet.ModuleID 12 | 13 | import org.scalajs.linker.backend.webassembly.FunctionBuilder 14 | import org.scalajs.linker.backend.webassembly.{Instructions => wa} 15 | import org.scalajs.linker.backend.webassembly.{Modules => wamod} 16 | import org.scalajs.linker.backend.webassembly.{Types => watpe} 17 | 18 | import org.scalajs.logging.Logger 19 | 20 | import SpecialNames._ 21 | import VarGen._ 22 | 23 | final class Emitter(config: Emitter.Config) { 24 | import Emitter._ 25 | 26 | private val classEmitter = new ClassEmitter(config.coreSpec) 27 | 28 | val symbolRequirements: SymbolRequirement = 29 | Emitter.symbolRequirements(config.coreSpec) 30 | 31 | // Our injected IR files are handled by WebAssemblyStandardLinkerImpl instead 32 | def injectedIRFiles: Seq[IRFile] = Nil 33 | 34 | def emit(module: ModuleSet.Module, logger: Logger): Result = { 35 | /* Sort by ancestor count so that superclasses always appear before 36 | * subclasses, then tie-break by name for stability. 37 | */ 38 | val sortedClasses = module.classDefs.sortWith { (a, b) => 39 | val cmp = Integer.compare(a.ancestors.size, b.ancestors.size) 40 | if (cmp != 0) cmp < 0 41 | else a.className.compareTo(b.className) < 0 42 | } 43 | 44 | implicit val ctx: WasmContext = 45 | Preprocessor.preprocess(sortedClasses, module.topLevelExports) 46 | 47 | // Sort for stability 48 | val allImportedModules: List[String] = module.externalDependencies.toList.sorted 49 | 50 | // Gen imports of external modules on the Wasm side 51 | for (moduleName <- allImportedModules) { 52 | val id = genGlobalID.forImportedModule(moduleName) 53 | val origName = OriginalName("import." + moduleName) 54 | ctx.moduleBuilder.addImport( 55 | wamod.Import( 56 | "__scalaJSImports", 57 | moduleName, 58 | wamod.ImportDesc.Global(id, origName, watpe.RefType.anyref, isMutable = false) 59 | ) 60 | ) 61 | } 62 | 63 | CoreWasmLib.genPreClasses() 64 | sortedClasses.foreach { clazz => 65 | classEmitter.genClassDef(clazz) 66 | } 67 | module.topLevelExports.foreach { tle => 68 | classEmitter.genTopLevelExport(tle) 69 | } 70 | CoreWasmLib.genPostClasses() 71 | 72 | complete( 73 | sortedClasses, 74 | module.initializers.toList, 75 | module.topLevelExports 76 | ) 77 | 78 | val wasmModule = ctx.moduleBuilder.build() 79 | 80 | val loaderContent = LoaderContent.bytesContent 81 | val jsFileContent = 82 | buildJSFileContent(module, module.id.id + ".wasm", allImportedModules) 83 | 84 | new Result(wasmModule, loaderContent, jsFileContent) 85 | } 86 | 87 | private def complete( 88 | sortedClasses: List[LinkedClass], 89 | moduleInitializers: List[ModuleInitializer.Initializer], 90 | topLevelExportDefs: List[LinkedTopLevelExport] 91 | )(implicit ctx: WasmContext): Unit = { 92 | /* Before generating the string globals in `genStartFunction()`, make sure 93 | * to allocate the ones that will be required by the module initializers. 94 | */ 95 | for (init <- moduleInitializers) { 96 | ModuleInitializerImpl.fromInitializer(init) match { 97 | case ModuleInitializerImpl.MainMethodWithArgs(_, _, args) => 98 | args.foreach(ctx.addConstantStringGlobal(_)) 99 | case ModuleInitializerImpl.VoidMainMethod(_, _) => 100 | () // nothing to do 101 | } 102 | } 103 | 104 | // string 105 | val (stringPool, stringPoolCount) = ctx.getFinalStringPool() 106 | ctx.moduleBuilder.addData( 107 | wamod.Data( 108 | genDataID.string, 109 | OriginalName("stringPool"), 110 | stringPool, 111 | wamod.Data.Mode.Passive 112 | ) 113 | ) 114 | ctx.addGlobal( 115 | wamod.Global( 116 | genGlobalID.stringLiteralCache, 117 | OriginalName("stringLiteralCache"), 118 | watpe.RefType(genTypeID.anyArray), 119 | wa.Expr( 120 | List( 121 | wa.I32Const(stringPoolCount), 122 | wa.ArrayNewDefault(genTypeID.anyArray) 123 | ) 124 | ), 125 | isMutable = false 126 | ) 127 | ) 128 | 129 | genStartFunction(sortedClasses, moduleInitializers, topLevelExportDefs) 130 | genDeclarativeElements() 131 | } 132 | 133 | private def genStartFunction( 134 | sortedClasses: List[LinkedClass], 135 | moduleInitializers: List[ModuleInitializer.Initializer], 136 | topLevelExportDefs: List[LinkedTopLevelExport] 137 | )(implicit ctx: WasmContext): Unit = { 138 | import org.scalajs.ir.Trees._ 139 | 140 | implicit val pos = Position.NoPosition 141 | 142 | val fb = 143 | new FunctionBuilder(ctx.moduleBuilder, genFunctionID.start, OriginalName("start"), pos) 144 | 145 | // Initialize itables 146 | for (clazz <- sortedClasses if clazz.kind.isClass && clazz.hasDirectInstances) { 147 | val className = clazz.className 148 | val classInfo = ctx.getClassInfo(className) 149 | 150 | if (classInfo.classImplementsAnyInterface) { 151 | val interfaces = clazz.ancestors.map(ctx.getClassInfo(_)).filter(_.isInterface) 152 | val resolvedMethodInfos = classInfo.resolvedMethodInfos 153 | 154 | interfaces.foreach { iface => 155 | val idx = ctx.getItableIdx(iface) 156 | fb += wa.GlobalGet(genGlobalID.forITable(className)) 157 | fb += wa.I32Const(idx) 158 | 159 | for (method <- iface.tableEntries) 160 | fb += ctx.refFuncWithDeclaration(resolvedMethodInfos(method).tableEntryID) 161 | fb += wa.StructNew(genTypeID.forITable(iface.name)) 162 | fb += wa.ArraySet(genTypeID.itables) 163 | } 164 | } 165 | } 166 | 167 | locally { 168 | // For array classes, resolve methods in jl.Object 169 | val globalID = genGlobalID.arrayClassITable 170 | val resolvedMethodInfos = ctx.getClassInfo(ObjectClass).resolvedMethodInfos 171 | 172 | for { 173 | interfaceName <- List(SerializableClass, CloneableClass) 174 | // Use getClassInfoOption in case the reachability analysis got rid of those interfaces 175 | interfaceInfo <- ctx.getClassInfoOption(interfaceName) 176 | } { 177 | fb += wa.GlobalGet(globalID) 178 | fb += wa.I32Const(ctx.getItableIdx(interfaceInfo)) 179 | 180 | for (method <- interfaceInfo.tableEntries) 181 | fb += ctx.refFuncWithDeclaration(resolvedMethodInfos(method).tableEntryID) 182 | fb += wa.StructNew(genTypeID.forITable(interfaceName)) 183 | fb += wa.ArraySet(genTypeID.itables) 184 | } 185 | } 186 | 187 | // Initialize the JS private field symbols 188 | 189 | for (clazz <- sortedClasses if clazz.kind.isJSClass) { 190 | for (fieldDef <- clazz.fields) { 191 | fieldDef match { 192 | case FieldDef(flags, name, _, _) if !flags.namespace.isStatic => 193 | fb += wa.Call(genFunctionID.newSymbol) 194 | fb += wa.GlobalSet(genGlobalID.forJSPrivateField(name.name)) 195 | case _ => 196 | () 197 | } 198 | } 199 | } 200 | 201 | // Emit the static initializers 202 | 203 | for (clazz <- sortedClasses if clazz.hasStaticInitializer) { 204 | val funcID = genFunctionID.forMethod( 205 | MemberNamespace.StaticConstructor, 206 | clazz.className, 207 | StaticInitializerName 208 | ) 209 | fb += wa.Call(funcID) 210 | } 211 | 212 | // Initialize the top-level exports that require it 213 | 214 | for (tle <- topLevelExportDefs) { 215 | // Load the (initial) exported value on the stack 216 | tle.tree match { 217 | case TopLevelJSClassExportDef(_, exportName) => 218 | fb += wa.Call(genFunctionID.loadJSClass(tle.owningClass)) 219 | case TopLevelModuleExportDef(_, exportName) => 220 | fb += wa.Call(genFunctionID.loadModule(tle.owningClass)) 221 | case TopLevelMethodExportDef(_, methodDef) => 222 | fb += ctx.refFuncWithDeclaration(genFunctionID.forExport(tle.exportName)) 223 | if (methodDef.restParam.isDefined) { 224 | fb += wa.I32Const(methodDef.args.size) 225 | fb += wa.Call(genFunctionID.makeExportedDefRest) 226 | } else { 227 | fb += wa.Call(genFunctionID.makeExportedDef) 228 | } 229 | case TopLevelFieldExportDef(_, _, fieldIdent) => 230 | /* Usually redundant, but necessary if the static field is never 231 | * explicitly set and keeps its default (zero) value instead. In that 232 | * case this initial call is required to publish that zero value (as 233 | * opposed to the default `undefined` value of the JS `let`). 234 | */ 235 | fb += wa.GlobalGet(genGlobalID.forStaticField(fieldIdent.name)) 236 | } 237 | 238 | // Call the export setter 239 | fb += wa.Call(genFunctionID.forTopLevelExportSetter(tle.exportName)) 240 | } 241 | 242 | // Emit the module initializers 243 | 244 | moduleInitializers.foreach { init => 245 | def genCallStatic(className: ClassName, methodName: MethodName): Unit = { 246 | val funcID = genFunctionID.forMethod(MemberNamespace.PublicStatic, className, methodName) 247 | fb += wa.Call(funcID) 248 | } 249 | 250 | ModuleInitializerImpl.fromInitializer(init) match { 251 | case ModuleInitializerImpl.MainMethodWithArgs(className, encodedMainMethodName, args) => 252 | // vtable of Array[String] 253 | fb += wa.GlobalGet(genGlobalID.forVTable(BoxedStringClass)) 254 | fb += wa.I32Const(1) 255 | fb += wa.Call(genFunctionID.arrayTypeData) 256 | 257 | // itable of Array[String] 258 | fb += wa.GlobalGet(genGlobalID.arrayClassITable) 259 | 260 | // underlying array of args 261 | args.foreach(arg => fb ++= ctx.getConstantStringInstr(arg)) 262 | fb += wa.ArrayNewFixed(genTypeID.anyArray, args.size) 263 | 264 | // array object 265 | val stringArrayTypeRef = ArrayTypeRef(ClassRef(BoxedStringClass), 1) 266 | fb += wa.StructNew(genTypeID.forArrayClass(stringArrayTypeRef)) 267 | 268 | // call 269 | genCallStatic(className, encodedMainMethodName) 270 | 271 | case ModuleInitializerImpl.VoidMainMethod(className, encodedMainMethodName) => 272 | genCallStatic(className, encodedMainMethodName) 273 | } 274 | } 275 | 276 | // Finish the start function 277 | 278 | fb.buildAndAddToModule() 279 | ctx.moduleBuilder.setStart(genFunctionID.start) 280 | } 281 | 282 | private def genDeclarativeElements()(implicit ctx: WasmContext): Unit = { 283 | // Aggregated Elements 284 | 285 | val funcDeclarations = ctx.getAllFuncDeclarations() 286 | 287 | if (funcDeclarations.nonEmpty) { 288 | /* Functions that are referred to with `ref.func` in the Code section 289 | * must be declared ahead of time in one of the earlier sections 290 | * (otherwise the module does not validate). It can be the Global section 291 | * if they are meaningful there (which is why `ref.func` in the vtables 292 | * work out of the box). In the absence of any other specific place, an 293 | * Element section with the declarative mode is the recommended way to 294 | * introduce these declarations. 295 | */ 296 | val exprs = funcDeclarations.map { funcID => 297 | wa.Expr(List(wa.RefFunc(funcID))) 298 | } 299 | ctx.moduleBuilder.addElement( 300 | wamod.Element(watpe.RefType.funcref, exprs, wamod.Element.Mode.Declarative) 301 | ) 302 | } 303 | } 304 | 305 | private def buildJSFileContent( 306 | module: ModuleSet.Module, 307 | wasmFileName: String, 308 | importedModules: List[String] 309 | ): String = { 310 | val (moduleImports, importedModulesItems) = (for { 311 | (moduleName, idx) <- importedModules.zipWithIndex 312 | } yield { 313 | val identName = s"imported$idx" 314 | val escapedModuleName = "\"" + moduleName + "\"" 315 | val moduleImport = s"import * as $identName from $escapedModuleName" 316 | val item = s" $escapedModuleName: $identName," 317 | (moduleImport, item) 318 | }).unzip 319 | 320 | val (exportDecls, exportSetters) = (for { 321 | exportName <- module.topLevelExports.map(_.exportName) 322 | } yield { 323 | val identName = s"exported$exportName" 324 | val decl = s"let $identName;\nexport { $identName as $exportName };" 325 | val setter = s" $exportName: (x) => $identName = x," 326 | (decl, setter) 327 | }).unzip 328 | 329 | s""" 330 | |${moduleImports.mkString("\n")} 331 | | 332 | |import { load as __load } from './${config.loaderModuleName}'; 333 | | 334 | |${exportDecls.mkString("\n")} 335 | | 336 | |await __load('./${wasmFileName}', { 337 | |${importedModulesItems.mkString("\n")} 338 | |}, { 339 | |${exportSetters.mkString("\n")} 340 | |}); 341 | """.stripMargin.trim() + "\n" 342 | } 343 | } 344 | 345 | object Emitter { 346 | 347 | /** Configuration for the Emitter. */ 348 | final class Config private ( 349 | val coreSpec: CoreSpec, 350 | val loaderModuleName: String 351 | ) 352 | 353 | object Config { 354 | def apply(coreSpec: CoreSpec, loaderModuleName: String): Config = 355 | new Config(coreSpec, loaderModuleName) 356 | } 357 | 358 | final class Result( 359 | val wasmModule: wamod.Module, 360 | val loaderContent: Array[Byte], 361 | val jsFileContent: String 362 | ) 363 | 364 | /** Builds the symbol requirements of our back-end. 365 | * 366 | * The symbol requirements tell the LinkerFrontend that we need these symbols to always be 367 | * reachable, even if no "user-land" IR requires them. They are roots for the reachability 368 | * analysis, together with module initializers and top-level exports. If we don't do this, the 369 | * linker frontend will dead-code eliminate our box classes. 370 | */ 371 | private def symbolRequirements(coreSpec: CoreSpec): SymbolRequirement = { 372 | val factory = SymbolRequirement.factory("wasm") 373 | 374 | factory.multiple( 375 | factory.instantiateClass(ClassClass, ClassCtor), 376 | factory.instantiateClass(CharBoxClass, CharBoxCtor), 377 | factory.instantiateClass(LongBoxClass, LongBoxCtor), 378 | 379 | // See genIdentityHashCode in HelperFunctions 380 | factory.callMethodStatically(BoxedDoubleClass, hashCodeMethodName), 381 | factory.callMethodStatically(BoxedStringClass, hashCodeMethodName) 382 | ) 383 | } 384 | 385 | } 386 | -------------------------------------------------------------------------------- /wasm/src/main/scala/org/scalajs/linker/backend/wasmemitter/LibraryPatches.scala: -------------------------------------------------------------------------------- 1 | package org.scalajs.linker.backend.wasmemitter 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | 5 | import org.scalajs.ir.ClassKind._ 6 | import org.scalajs.ir.Names._ 7 | import org.scalajs.ir.OriginalName 8 | import org.scalajs.ir.OriginalName.NoOriginalName 9 | import org.scalajs.ir.Position 10 | import org.scalajs.ir.Trees._ 11 | import org.scalajs.ir.Types._ 12 | import org.scalajs.ir.{EntryPointsInfo, Version} 13 | 14 | import org.scalajs.linker.interface.IRFile 15 | import org.scalajs.linker.interface.unstable.IRFileImpl 16 | 17 | import SpecialNames._ 18 | 19 | /** Patches that we apply to the standard library classes to make them wasm-friendly. */ 20 | object LibraryPatches { 21 | def patchIRFiles(irFiles: Seq[IRFile])(implicit ec: ExecutionContext): Future[Seq[IRFile]] = { 22 | val derivedIRFiles: Future[Seq[Option[IRFile]]] = Future.traverse(irFiles) { irFile => 23 | val irFileImpl = IRFileImpl.fromIRFile(irFile) 24 | irFileImpl.entryPointsInfo.flatMap { entryPointsInfo => 25 | entryPointsInfo.className match { 26 | case BoxedCharacterClass | BoxedLongClass => 27 | irFileImpl.tree.map { classDef => 28 | Some(new MemClassDefIRFile(deriveBoxClass(classDef))) 29 | } 30 | case _ => 31 | Future.successful(None) 32 | } 33 | } 34 | } 35 | 36 | val leanerJSExceptionIRFile = 37 | org.scalajs.linker.backend.emitter.PrivateLibHolder.files.find { irFile => 38 | IRFileImpl.fromIRFile(irFile).path.contains("JavaScriptException") 39 | }.get 40 | 41 | derivedIRFiles.map { derived => 42 | derived.flatten ++ Seq(StackTraceIRFile) ++ irFiles ++ Seq(leanerJSExceptionIRFile) 43 | } 44 | } 45 | 46 | private val EAF = ApplyFlags.empty 47 | private val EMF = MemberFlags.empty 48 | private val EOH = OptimizerHints.empty 49 | private val NON = NoOriginalName 50 | private val NOV = Version.Unversioned 51 | 52 | private def trivialCtor(enclosingClassName: ClassName)(implicit pos: Position): MethodDef = { 53 | val flags = MemberFlags.empty.withNamespace(MemberNamespace.Constructor) 54 | MethodDef( 55 | flags, 56 | MethodIdent(NoArgConstructorName), 57 | NON, 58 | Nil, 59 | NoType, 60 | Some( 61 | ApplyStatically( 62 | EAF.withConstructor(true), 63 | This()(ClassType(enclosingClassName)), 64 | ObjectClass, 65 | MethodIdent(NoArgConstructorName), 66 | Nil 67 | )(NoType) 68 | ) 69 | )(EOH, NOV) 70 | } 71 | 72 | private val StackTraceIRFile: IRFile = { 73 | implicit val noPosition: Position = Position.NoPosition 74 | 75 | val StackTraceModuleClass = ClassName("java.lang.StackTrace$") 76 | val StackTraceElementClass = ClassName("java.lang.StackTraceElement") 77 | 78 | val arrayOfSTERef = ArrayTypeRef(ClassRef(StackTraceElementClass), 1) 79 | 80 | val O = ClassRef(ObjectClass) 81 | 82 | val classDef = ClassDef( 83 | ClassIdent(StackTraceModuleClass), 84 | NON, 85 | ModuleClass, 86 | None, 87 | Some(ClassIdent(ObjectClass)), 88 | Nil, 89 | None, 90 | None, 91 | Nil, 92 | List( 93 | trivialCtor(StackTraceModuleClass), 94 | MethodDef( 95 | EMF, 96 | MethodIdent(MethodName("captureJSError", List(ClassRef(ThrowableClass)), O)), 97 | NON, 98 | List( 99 | ParamDef(LocalIdent(LocalName("th")), NON, ClassType(ThrowableClass), mutable = false) 100 | ), 101 | AnyType, 102 | Some( 103 | JSNew(JSGlobalRef("Error"), Nil) 104 | ) 105 | )(EOH.withInline(true), NOV), 106 | MethodDef( 107 | EMF, 108 | MethodIdent(MethodName("extract", List(O), arrayOfSTERef)), 109 | NON, 110 | List(ParamDef(LocalIdent(LocalName("jsError")), NON, AnyType, mutable = false)), 111 | ArrayType(arrayOfSTERef), 112 | Some( 113 | ArrayValue(arrayOfSTERef, Nil) 114 | ) 115 | )(EOH.withInline(true), NOV), 116 | MethodDef( 117 | EMF, 118 | MethodIdent(MethodName("getCurrentStackTrace", Nil, arrayOfSTERef)), 119 | NON, 120 | Nil, 121 | ArrayType(arrayOfSTERef), 122 | Some( 123 | ArrayValue(arrayOfSTERef, Nil) 124 | ) 125 | )(EOH.withInline(true), NOV) 126 | ), 127 | None, 128 | Nil, 129 | Nil, 130 | Nil 131 | )(EOH) 132 | 133 | new MemClassDefIRFile(classDef) 134 | } 135 | 136 | /** Generates the accompanying Box class of `Character` or `Long`. 137 | * 138 | * These box classes will be used as the generic representation of `char`s and `long`s when they 139 | * are upcast to `java.lang.Character`/`java.lang.Long` or any of their supertypes. 140 | * 141 | * The generated Box classes mimic the public structure of the corresponding hijacked classes. 142 | * Whereas the hijacked classes instances *are* the primitives (conceptually), the box classes 143 | * contain an explicit `value` field of the primitive type. They delegate all their instance 144 | * methods to the corresponding methods of the hijacked class, applied on the `value` primitive. 145 | * 146 | * For example, given the hijacked class 147 | * 148 | * {{{ 149 | * hijacked class Long extends java.lang.Number with Comparable { 150 | * def longValue;J(): long = this.asInstanceOf[long] 151 | * def toString;T(): string = Long$.toString(this.longValue;J()) 152 | * def compareTo;jlLong;Z(that: java.lang.Long): boolean = 153 | * Long$.compare(this.longValue;J(), that.longValue;J()) 154 | * } 155 | * }}} 156 | * 157 | * we generate 158 | * 159 | * {{{ 160 | * class LongBox extends java.lang.Number with Comparable { 161 | * val value: long 162 | * def