diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 42849628e7..718146327c 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -4,5 +4,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - run: ./ci/check-cla.sh + - name: Verify CLA + uses: scala/cla-checker@v1 + with: + author: ${{ github.event.pull_request.user.login }} diff --git a/CODINGSTYLE.md b/CODINGSTYLE.md index da517b020e..eb79ec4986 100644 --- a/CODINGSTYLE.md +++ b/CODINGSTYLE.md @@ -63,13 +63,13 @@ f( arg1, arg2, arg3, - arg4 + arg4, ) ``` Notes about the list style: * The parentheses must be on individual lines. -* A trailing comma will become mandatory if/once we drop 2.11. +* The trailing comma is mandatory. * This style is relatively new, so a lot of code does not comply to it; apply the boy scout rule where this does not cause unnecessary diffs. ### Blank lines @@ -227,7 +227,7 @@ value :: list ``` When calling a method declared with an empty pair of parentheses, always use `()`. -Not doing so causes (fatal) warnings when calling Scala-declared methods in Scala 2.13.3+. +Not doing so causes (fatal) warnings when calling Scala-declared methods in Scala 2.13.x. For consistency, we also apply this rule to all Java-defined methods, including `toString()`. ### Method definition diff --git a/DEVELOPING.md b/DEVELOPING.md index bf409ce03e..29dc5d18fa 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -170,8 +170,8 @@ To publish your changes locally to be used in a separate project, use the following incantations. `SCALA_VERSION` refers to the Scala version used by the separate project. - > ;ir2_12/publishLocal;linkerInterface2_12/publishLocal;linker2_12/publishLocal;testAdapter2_12/publishLocal;sbtPlugin/publishLocal - > ++SCALA_VERSION - > ;compiler2_12/publishLocal;library2_12/publishLocal;testInterface2_12/publishLocal;testBridge2_12/publishLocal;jUnitRuntime2_12/publishLocal;jUnitPlugin2_12/publishLocal + > ;ir2_12/publishLocal;linkerInterface2_12/publishLocal;linker2_12/publishLocal;testAdapter2_12/publishLocal;sbtPlugin/publishLocal;javalib/publishLocal;javalibintf/publishLocal + > ;library2_12/publishLocal;testInterface2_12/publishLocal;testBridge2_12/publishLocal;jUnitRuntime2_12/publishLocal;jUnitPlugin2_12/publishLocal;scalalib2_12/publishLocal + > ++SCALA_VERSION compiler2_12/publishLocal -If using a non-2.12.x version for the Scala version, the `2_12` suffixes must be adapted in the last command (not in the first command). +If using a non-2.12.x version for the Scala version, the `2_12` suffixes must be adapted in the second and third command (not in the first command). diff --git a/Jenkinsfile b/Jenkinsfile index 3bdcde1623..c1a4c70069 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -64,7 +64,7 @@ rm -rf $TEST_LOCAL_IVY_HOME mkdir $TEST_LOCAL_IVY_HOME ln -s "$LOC_IVY_HOME/cache" "$TEST_LOCAL_IVY_HOME/cache" -export SBT_OPTS="-J-Xmx5G -J-XX:MaxPermSize=512M -Dsbt.boot.directory=$LOC_SBT_BOOT -Dsbt.ivy.home=$TEST_LOCAL_IVY_HOME -Divy.home=$TEST_LOCAL_IVY_HOME -Dsbt.global.base=$LOC_SBT_BASE" +export SBT_OPTS="-J-Xmx5G -Dsbt.boot.directory=$LOC_SBT_BOOT -Dsbt.ivy.home=$TEST_LOCAL_IVY_HOME -Divy.home=$TEST_LOCAL_IVY_HOME -Dsbt.global.base=$LOC_SBT_BASE" export COURSIER_CACHE="$LOC_CS_CACHE" export NODE_PATH="$HOME/node_modules/" @@ -76,7 +76,7 @@ setJavaVersion() { export PATH=$JAVA_HOME/bin:$PATH } -# Define sbtretry +# Define sbtretry and sbtnoretry sbtretry() { local TIMEOUT=45m @@ -95,10 +95,22 @@ sbtretry() { fi if [ "$CODE" -ne 0 ]; then echo "FAILED TWICE" + echo "Command was: sbt" "$@" return $CODE fi fi } + +sbtnoretry() { + echo "RUNNING sbt" "$@" + sbt $SBT_OPTS "$@" + CODE=$? + if [ "$CODE" -ne 0 ]; then + echo "FAILED" + echo "Command was: sbt" "$@" + return $CODE + fi +} ''' def Tasks = [ @@ -108,23 +120,21 @@ def Tasks = [ sbtretry ++$scala helloworld$v/run && sbtretry 'set scalaJSStage in Global := FullOptStage' \ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withPrettyPrint(true))' \ - ++$scala helloworld$v/run \ - helloworld$v/clean && + ++$scala helloworld$v/run && sbtretry 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withOptimizer(false))' \ - ++$scala helloworld$v/run \ - helloworld$v/clean && + ++$scala helloworld$v/run && sbtretry 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withSemantics(_.withAsInstanceOfs(CheckedBehavior.Unchecked)))' \ - ++$scala helloworld$v/run \ - helloworld$v/clean && + ++$scala helloworld$v/run && + sbtretry ++$scala \ + 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)))' \ + helloworld$v/run && sbtretry ++$scala \ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ - helloworld$v/run \ - helloworld$v/clean && + helloworld$v/run && sbtretry ++$scala \ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ - helloworld$v/run \ - helloworld$v/clean && + helloworld$v/run && sbtretry ++$scala \ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ helloworld$v/run && @@ -139,19 +149,23 @@ def Tasks = [ sbtretry ++$scala \ 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ 'set scalaJSStage in Global := FullOptStage' \ - helloworld$v/run \ - helloworld$v/clean && + helloworld$v/run && sbtretry ++$scala testingExample$v/testHtmlJSDom && sbtretry ++$scala \ 'set scalaJSLinkerConfig in testingExample.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ 'set scalaJSLinkerConfig in testingExample.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ - testingExample$v/testHtml \ - testingExample$v/clean && + testingExample$v/testHtml && sbtretry 'set scalaJSStage in Global := FullOptStage' \ - ++$scala testingExample$v/testHtmlJSDom \ - testingExample$v/clean && - sbtretry ++$scala testSuiteJVM$v/test testSuiteJVM$v/clean testSuiteExJVM$v/test testSuiteExJVM$v/clean && + ++$scala testingExample$v/testHtmlJSDom && + sbtretry ++$scala testSuiteJVM$v/test testSuiteExJVM$v/test && sbtretry ++$scala testSuite$v/test && + sbtretry ++$scala \ + testSuite$v/saveForStabilityTest \ + testSuite$v/checkStability \ + testSuite$v/forceRelinkForStabilityTest \ + testSuite$v/checkStability \ + testSuite$v/clean \ + testSuite$v/checkStability && sbtretry ++$scala testSuiteEx$v/test && sbtretry 'set scalaJSStage in Global := FullOptStage' \ ++$scala testSuiteEx$v/test && @@ -164,23 +178,25 @@ def Tasks = [ 'set scalaJSLinkerConfig in reversi.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ 'set scalaJSLinkerConfig in reversi.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ reversi$v/fastLinkJS \ - reversi$v/fullLinkJS \ - reversi$v/clean && + reversi$v/fullLinkJS && sbtretry ++$scala \ 'set scalaJSLinkerConfig in reversi.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("reversi"))))' \ 'set scalaJSLinkerConfig in reversi.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ reversi$v/fastLinkJS \ - reversi$v/fullLinkJS \ - reversi$v/clean && + reversi$v/fullLinkJS && sbtretry ++$scala \ reversi$v/fastLinkJS \ reversi$v/fullLinkJS \ reversi$v/checksizes && - sbtretry ++$scala compiler$v/compile:doc library$v/compile:doc \ + sbtretry ++$scala \ + 'set Global/enableMinifyEverywhere := true' \ + reversi$v/checksizes && + sbtretry ++$scala javalibintf/compile:doc compiler$v/compile:doc library$v/compile:doc \ testInterface$v/compile:doc testBridge$v/compile:doc && sbtretry ++$scala headerCheck && sbtretry ++$scala partest$v/fetchScalaSource && sbtretry ++$scala \ + javalibintf/mimaReportBinaryIssues \ library$v/mimaReportBinaryIssues \ testInterface$v/mimaReportBinaryIssues \ jUnitRuntime$v/mimaReportBinaryIssues @@ -189,83 +205,78 @@ def Tasks = [ "test-suite-default-esversion": ''' setJavaVersion $java npm install && - sbtretry ++$scala jUnitTestOutputsJVM$v/test jUnitTestOutputsJS$v/test testBridge$v/test \ + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + jUnitTestOutputsJVM$v/test jUnitTestOutputsJS$v/test testBridge$v/test \ 'set scalaJSStage in Global := FullOptStage' jUnitTestOutputsJS$v/test testBridge$v/test && - sbtretry ++$scala $testSuite$v/test $testSuite$v/testHtmlJSDom && - sbtretry 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/testHtmlJSDom \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + $testSuite$v/test $testSuite$v/testHtmlJSDom && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ - 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ + $testSuite$v/test \ + $testSuite$v/testHtmlJSDom && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ + 'set scalaJSStage in Global := FullOptStage' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)))' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)).withOptimizer(false))' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)))' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAllowBigIntsForLongs(true)).withOptimizer(false))' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAvoidLetsAndConsts(false).withAvoidClasses(false)))' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withAvoidLetsAndConsts(false).withAvoidClasses(false)))' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalacOptions in $testSuite.v$v += "-Xexperimental"' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalacOptions in $testSuite.v$v += "-Xexperimental"' \ - 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ - ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ + $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("org.scalajs.testsuite"))))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ - ++$scala $testSuite$v/test && - sbtretry \ + $testSuite$v/test && + # The following tests the same thing whether testMinify is true or false; we also set it for regularity. + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test + $testSuite$v/test ''', "test-suite-custom-esversion-force-polyfills": ''' @@ -277,13 +288,11 @@ def Tasks = [ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && + ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && + ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ @@ -292,30 +301,12 @@ def Tasks = [ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set Seq(jsEnv in $testSuite.v$v := new NodeJSEnvForcePolyfills(ESVersion.$esVersion), MyScalaJSPlugin.wantSourceMaps in $testSuite.v$v := ("$esVersion" != "ES5_1"))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean + ++$scala $testSuite$v/test ''', "test-suite-custom-esversion": ''' @@ -325,48 +316,29 @@ def Tasks = [ ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && + ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && + ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && + ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ ++$scala $testSuite$v/test && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && - sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= { _.withSemantics(_.withStrictFloats(false)) }' \ - 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion).withAllowBigIntsForLongs(true)))' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && + ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion).withAllowBigIntsForLongs(true)).withOptimizer(false))' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && + ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ ++$scala $testSuite$v/test && @@ -378,8 +350,7 @@ def Tasks = [ sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.CommonJSModule))' \ 'set scalaJSStage in Global := FullOptStage' \ - ++$scala $testSuite$v/test \ - $testSuite$v/clean && + ++$scala $testSuite$v/test && sbtretry 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ ++$scala $testSuite$v/test && @@ -394,6 +365,83 @@ def Tasks = [ ++$scala $testSuite$v/test ''', + "test-suite-webassembly": ''' + setJavaVersion $java + npm install && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + helloworld$v/run && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + 'set scalaJSStage in Global := FullOptStage' \ + 'set scalaJSLinkerConfig in helloworld.v$v ~= (_.withPrettyPrint(true))' \ + helloworld$v/run && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in reversi.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + reversi$v/fastLinkJS \ + reversi$v/fullLinkJS && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in jUnitTestOutputsJS.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + 'set scalaJSLinkerConfig in testBridge.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + jUnitTestOutputsJS$v/test testBridge$v/test \ + 'set scalaJSStage in Global := FullOptStage' \ + jUnitTestOutputsJS$v/test testBridge$v/test && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + $testSuite$v/test && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + 'set scalaJSStage in Global := FullOptStage' \ + $testSuite$v/test && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ + $testSuite$v/test && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ + 'set scalaJSStage in Global := FullOptStage' \ + $testSuite$v/test && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ + $testSuite$v/test && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ + 'set scalaJSStage in Global := FullOptStage' \ + $testSuite$v/test && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= makeCompliant' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ + $testSuite$v/test && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + testingExample$v/testHtml && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + 'set scalaJSStage in Global := FullOptStage' \ + testingExample$v/testHtml && + sbtretry ++$scala \ + 'set Global/enableWasmEverywhere := true' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withESFeatures(_.withESVersion(ESVersion.$esVersion)))' \ + irJS$v/fastLinkJS + ''', + /* For the bootstrap tests to be able to call * `testSuite/test:fastOptJS`, `scalaJSStage in testSuite` must be * `FastOptStage`, even when `scalaJSStage in Global` is `FullOptStage`. @@ -401,84 +449,88 @@ def Tasks = [ "bootstrap": ''' setJavaVersion $java npm install && - sbt ++$scala linker$v/test && - sbt linkerPrivateLibrary/test && - sbt ++$scala irJS$v/test linkerJS$v/test linkerInterfaceJS$v/test && - sbt 'set scalaJSStage in Global := FullOptStage' \ + sbtnoretry ++$scala linker$v/test && + sbtnoretry linkerPrivateLibrary/test && + sbtnoretry ++$scala irJS$v/test && + sbtnoretry ++$scala linkerInterfaceJS$v/test && + sbtnoretry ++$scala linkerJS$v/test && + sbtnoretry 'set scalaJSStage in Global := FullOptStage' \ + 'set scalaJSStage in testSuite.v$v := FastOptStage' \ + ++$scala irJS$v/test && + sbtnoretry 'set scalaJSStage in Global := FullOptStage' \ + 'set scalaJSStage in testSuite.v$v := FastOptStage' \ + ++$scala linkerInterfaceJS$v/test && + sbtnoretry 'set scalaJSStage in Global := FullOptStage' \ 'set scalaJSStage in testSuite.v$v := FastOptStage' \ - ++$scala irJS$v/test linkerJS$v/test linkerInterfaceJS$v/test && - sbt ++$scala testSuite$v/bootstrap:test && - sbt 'set scalaJSStage in Global := FullOptStage' \ + ++$scala linkerJS$v/test && + sbtnoretry ++$scala testSuite$v/bootstrap:test && + sbtnoretry 'set scalaJSStage in Global := FullOptStage' \ 'set scalaJSStage in testSuite.v$v := FastOptStage' \ ++$scala testSuite$v/bootstrap:test && - sbt ++$scala irJS$v/mimaReportBinaryIssues \ + sbtnoretry ++$scala irJS$v/mimaReportBinaryIssues \ linkerInterfaceJS$v/mimaReportBinaryIssues linkerJS$v/mimaReportBinaryIssues ''', "tools": ''' setJavaVersion $java npm install && - sbt ++$scala ir$v/test linkerInterface$v/test \ + sbtnoretry ++$scala ir$v/test linkerInterface$v/test \ linker$v/compile testAdapter$v/test \ ir$v/mimaReportBinaryIssues \ linkerInterface$v/mimaReportBinaryIssues linker$v/mimaReportBinaryIssues \ testAdapter$v/mimaReportBinaryIssues && - sbt ++$scala ir$v/compile:doc \ + sbtnoretry ++$scala ir$v/compile:doc \ linkerInterface$v/compile:doc linker$v/compile:doc \ testAdapter$v/compile:doc ''', - "tools-sbtplugin": ''' + // These are agnostic to the Scala version + "sbt-plugin-and-scalastyle": ''' setJavaVersion $java npm install && - sbt ++$scala ir$v/test linkerInterface$v/compile \ - linker$v/compile testAdapter$v/test \ - sbtPlugin/package \ - ir$v/mimaReportBinaryIssues \ - linkerInterface$v/mimaReportBinaryIssues linker$v/mimaReportBinaryIssues \ - testAdapter$v/mimaReportBinaryIssues \ - sbtPlugin/mimaReportBinaryIssues && - sbt ++$scala scalastyleCheck && - sbt ++$scala ir$v/compile:doc \ - linkerInterface$v/compile:doc linker$v/compile:doc \ - testAdapter$v/compile:doc \ - sbtPlugin/compile:doc && - sbt sbtPlugin/scripted + sbtnoretry \ + sbtPlugin/compile:doc \ + sbtPlugin/mimaReportBinaryIssues \ + scalastyleCheck && + sbtnoretry sbtPlugin/scripted ''', "partest-noopt": ''' setJavaVersion $java npm install && - sbt ++$scala package "partestSuite$v/testOnly -- --showDiff" + sbtnoretry ++$scala partestSuite$v/test:compile && + sbtnoretry ++$scala "partestSuite$v/testOnly -- $partestopts --showDiff" ''', "partest-fastopt": ''' setJavaVersion $java npm install && - sbt ++$scala package "partestSuite$v/testOnly -- --fastOpt --showDiff" + sbtnoretry ++$scala partestSuite$v/test:compile && + sbtnoretry ++$scala "partestSuite$v/testOnly -- $partestopts --fastOpt --showDiff" ''', "partest-fullopt": ''' setJavaVersion $java npm install && - sbt ++$scala package "partestSuite$v/testOnly -- --fullOpt --showDiff" + sbtnoretry ++$scala partestSuite$v/test:compile && + sbtnoretry ++$scala "partestSuite$v/testOnly -- $partestopts --fullOpt --showDiff" + ''', + + "scala3-compat": ''' + setJavaVersion $java + npm install && + sbtnoretry ++$scala! ir2_13/test ''' ] def mainJavaVersion = "1.8" -def otherJavaVersions = ["11", "16"] +def otherJavaVersions = ["11", "17", "21"] def allJavaVersions = otherJavaVersions.clone() allJavaVersions << mainJavaVersion -def mainScalaVersion = "2.12.15" -def mainScalaVersions = ["2.11.12", "2.12.15", "2.13.8"] +def mainScalaVersion = "2.12.20" +def mainScalaVersions = ["2.12.20", "2.13.16"] def otherScalaVersions = [ - "2.11.12", - "2.12.1", - "2.12.2", - "2.12.3", - "2.12.4", - "2.12.5", "2.12.6", "2.12.7", "2.12.8", @@ -488,61 +540,72 @@ def otherScalaVersions = [ "2.12.12", "2.12.13", "2.12.14", - "2.13.0", - "2.13.1", - "2.13.2", + "2.12.15", + "2.12.16", + "2.12.17", + "2.12.18", + "2.12.19", "2.13.3", "2.13.4", "2.13.5", "2.13.6", - "2.13.7" + "2.13.7", + "2.13.8", + "2.13.9", + "2.13.10", + "2.13.11", + "2.13.12", + "2.12.13", + "2.12.14", + "2.12.15" ] +def scala3Version = "3.6.3" + def allESVersions = [ "ES5_1", "ES2015", - // "ES2016", // We do not use anything specifically from ES2016 + // "ES2016", // Technically we have the '**' operator dependent on ES2016, but it's not enough to justify testing this version "ES2017", "ES2018", // "ES2019", // We do not use anything specifically from ES2019 - "ES2020" - // "ES2021", // We do not use anything specifically from ES2021 + "ES2020", + "ES2021" // We do not use anything specifically from ES2021, but always test the latest to avoid #4675 ] - -// Scala 2.11 does not support newer Java versions -def isExcludedForScala211(javaVersion) { - return javaVersion != "1.8" && javaVersion != "11" -} - -def isExcludedScalaJavaCombo(scalaVersion, javaVersion) { - return scalaVersion.startsWith("2.11.") && isExcludedForScala211(javaVersion) -} +def defaultESVersion = "ES2015" +def latestESVersion = "ES2021" // The 'quick' matrix def quickMatrix = [] mainScalaVersions.each { scalaVersion -> allJavaVersions.each { javaVersion -> - if (!isExcludedScalaJavaCombo(scalaVersion, javaVersion)) - quickMatrix.add([task: "main", scala: scalaVersion, java: javaVersion]) + quickMatrix.add([task: "main", scala: scalaVersion, java: javaVersion]) + quickMatrix.add([task: "tools", scala: scalaVersion, java: javaVersion]) } - quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testSuite: "testSuite"]) + quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "false", testSuite: "testSuite"]) + quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "true", testSuite: "testSuite"]) quickMatrix.add([task: "test-suite-custom-esversion", scala: scalaVersion, java: mainJavaVersion, esVersion: "ES5_1", testSuite: "testSuite"]) - quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testSuite: "scalaTestSuite"]) + quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, esVersion: defaultESVersion, testMinify: "false", testSuite: "testSuite"]) + quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, esVersion: latestESVersion, testMinify: "false", testSuite: "testSuite"]) + quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, esVersion: defaultESVersion, testMinify: "false", testSuite: "testSuiteEx"]) + quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: mainJavaVersion, testMinify: "false", testSuite: "scalaTestSuite"]) quickMatrix.add([task: "test-suite-custom-esversion", scala: scalaVersion, java: mainJavaVersion, esVersion: "ES5_1", testSuite: "scalaTestSuite"]) + quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, esVersion: defaultESVersion, testMinify: "false", testSuite: "scalaTestSuite"]) quickMatrix.add([task: "bootstrap", scala: scalaVersion, java: mainJavaVersion]) - quickMatrix.add([task: "partest-fastopt", scala: scalaVersion, java: mainJavaVersion]) + quickMatrix.add([task: "partest-fastopt", scala: scalaVersion, java: mainJavaVersion, partestopts: ""]) + quickMatrix.add([task: "partest-fastopt", scala: scalaVersion, java: mainJavaVersion, partestopts: "--wasm"]) } allESVersions.each { esVersion -> quickMatrix.add([task: "test-suite-custom-esversion-force-polyfills", scala: mainScalaVersion, java: mainJavaVersion, esVersion: esVersion, testSuite: "testSuite"]) } allJavaVersions.each { javaVersion -> - if (!isExcludedForScala211(javaVersion)) { - // the sbt plugin tests want to compile everything for 2.11, 2.12 and 2.13 - quickMatrix.add([task: "tools-sbtplugin", scala: "2.12.15", java: javaVersion]) - quickMatrix.add([task: "tools", scala: "2.11.12", java: javaVersion]) + // the `scala` version is irrelevant here + // We exclude JDK 21 because our sbt scripted tests use old sbt versions (on purpose), which do not support JDK 21 + if (javaVersion != '21') { + quickMatrix.add([task: "sbt-plugin-and-scalastyle", scala: mainScalaVersion, java: javaVersion]) } - quickMatrix.add([task: "tools", scala: "2.13.8", java: javaVersion]) } +quickMatrix.add([task: "scala3-compat", scala: scala3Version, java: mainJavaVersion]) // The 'full' matrix def fullMatrix = quickMatrix.clone() @@ -551,11 +614,16 @@ otherScalaVersions.each { scalaVersion -> } mainScalaVersions.each { scalaVersion -> otherJavaVersions.each { javaVersion -> - if (!isExcludedScalaJavaCombo(scalaVersion, javaVersion)) - quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: javaVersion, testSuite: "testSuite"]) + quickMatrix.add([task: "test-suite-default-esversion", scala: scalaVersion, java: javaVersion, testMinify: "false", testSuite: "testSuite"]) + quickMatrix.add([task: "test-suite-webassembly", scala: scalaVersion, java: mainJavaVersion, esVersion: defaultESVersion, testMinify: "false", testSuite: "testSuite"]) } - fullMatrix.add([task: "partest-noopt", scala: scalaVersion, java: mainJavaVersion]) - fullMatrix.add([task: "partest-fullopt", scala: scalaVersion, java: mainJavaVersion]) + fullMatrix.add([task: "partest-noopt", scala: scalaVersion, java: mainJavaVersion, partestopts: ""]) + fullMatrix.add([task: "partest-noopt", scala: scalaVersion, java: mainJavaVersion, partestopts: "--wasm"]) + fullMatrix.add([task: "partest-fullopt", scala: scalaVersion, java: mainJavaVersion, partestopts: ""]) + fullMatrix.add([task: "partest-fullopt", scala: scalaVersion, java: mainJavaVersion, partestopts: "--wasm"]) +} +otherJavaVersions.each { javaVersion -> + fullMatrix.add([task: "scala3-compat", scala: scala3Version, java: javaVersion]) } def Matrices = [ @@ -592,9 +660,9 @@ matrix.each { taskDef -> buildDefs.put(fullTaskName, { node('linuxworker') { checkout scm - sh "git clean -fdx && rm -rf partest/fetchedSources/" - writeFile file: 'ciscript.sh', text: ciScript, encoding: 'UTF-8' retry(2) { + sh "git clean -fdx && rm -rf partest/fetchedSources/" + writeFile file: 'ciscript.sh', text: ciScript, encoding: 'UTF-8' timeout(time: 4, unit: 'HOURS') { sh "echo '$fullTaskName' && cat ciscript.sh && sh ciscript.sh" } diff --git a/RELEASING.md b/RELEASING.md index 60965a93fa..b143e9a93e 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -26,6 +26,7 @@ 1. When merging the release announcement PR (after proper review): - Update the latest/ URLs (use `~/setlatestapi.sh ` on webserver) + - Create a release on the core scala-js repository. - Announce on Twitter using the @scala_js account - Announce on [Gitter](https://gitter.im/scala-js/scala-js) - Cross-post as an Announcement in Scala Users ([example][7]) diff --git a/TESTING.md b/TESTING.md index f013e257f5..d26fafe4c3 100644 --- a/TESTING.md +++ b/TESTING.md @@ -4,8 +4,8 @@ This file contains test cases that should be manually executed. The following HTML-runners must be manually tested: - examples/helloworld/helloworld-{2.11|2.12}{|-fastopt}.html - examples/reversi/reversi-{2.11|2.12}{|-fastopt}.html + examples/helloworld/helloworld-2.12{|-fastopt}.html + examples/reversi/reversi-2.12{|-fastopt}.html ## HTML-Test Runner with Modules @@ -25,11 +25,30 @@ $ python3 -m http.server // Open http://localhost:8000/test-suite/js/.2.12/target/scala-2.12/scalajs-test-suite-fastopt-test-html/index.html ``` +## HTML-Test Runner with WebAssembly + +WebAssembly requires modules, so this is manual as well. + +This test currently requires Chrome (or another V8-based browser) with `--wasm-experimental-exnref` enabled. +That option can be configured as "Experimental WebAssembly" at [chrome://flags/#enable-experimental-webassembly-features](chrome://flags/#enable-experimental-webassembly-features). + +``` +$ sbt +> set Global/enableWasmEverywhere := true +> testingExample2_12/testHtml +> testSuite2_12/testHtml +> exit +$ python3 -m http.server + +// Open http://localhost:8000/examples/testing/.2.12/target/scala-2.12/testing-fastopt-test-html/index.html +// Open http://localhost:8000/test-suite/js/.2.12/target/scala-2.12/scalajs-test-suite-fastopt-test-html/index.html +``` + ## Sourcemaps To test source maps, do the following on: - examples/reversi/reversi-{2.11|2.12}{|-fastopt}.html + examples/reversi/reversi-2.12{|-fastopt}.html 1. Open the respective file in Google Chrome 2. Set a break-point in the HTML launcher on the `new Reversi` statement diff --git a/appveyor.yml b/appveyor.yml index d1baab4712..4f0c93e14c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ version: '{build}' image: Visual Studio 2015 environment: global: - NODEJS_VERSION: "14" + NODEJS_VERSION: "16" JAVA_HOME: C:\Program Files\Java\jdk1.8.0 install: - ps: Install-Product node $env:NODEJS_VERSION @@ -15,6 +15,8 @@ test_script: # Very far from testing everything, but at least it is a good sanity check # For slow things (partest and scripted), we execute only one test - cmd: sbt ";clean;testSuite2_12/test;linker2_12/test;partestSuite2_12/testOnly -- --fastOpt run/option-fold.scala" + # Module splitting has some logic for case-insensitive filesystems, which we must test on Windows + - cmd: sbt ";setSmallESModulesForAppVeyorCI;testSuite2_12/test" cache: - C:\sbt - C:\Users\appveyor\.ivy2\cache diff --git a/assets/additional-doc-styles.css b/assets/additional-doc-styles.css index 6bde1713cb..c0d38e0efb 100644 --- a/assets/additional-doc-styles.css +++ b/assets/additional-doc-styles.css @@ -1,4 +1,4 @@ -.badge-ecma6, .badge-ecma2015, .badge-ecma2017, .badge-ecma2018, .badge-ecma2019, .badge-ecma2020, .badge-ecma2021 { +.badge-ecma6, .badge-ecma2015, .badge-ecma2016, .badge-ecma2017, .badge-ecma2018, .badge-ecma2019, .badge-ecma2020, .badge-ecma2021 { background-color: #E68A00; } diff --git a/build.sbt b/build.sbt index e56269acab..e9bdde6b6b 100644 --- a/build.sbt +++ b/build.sbt @@ -11,10 +11,12 @@ val linker = Build.linker val linkerJS = Build.linkerJS val testAdapter = Build.testAdapter val sbtPlugin = Build.plugin -val javalanglib = Build.javalanglib +val javalibintf = Build.javalibintf +val javalibInternal = Build.javalibInternal val javalib = Build.javalib -val scalalib = Build.scalalib +val scalalibInternal = Build.scalalibInternal val libraryAux = Build.libraryAux +val scalalib = Build.scalalib val library = Build.library val testInterface = Build.testInterface val testBridge = Build.testBridge diff --git a/ci/check-cla.sh b/ci/check-cla.sh deleted file mode 100755 index 7dfa8197a0..0000000000 --- a/ci/check-cla.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -set -eux - -AUTHOR=$GITHUB_ACTOR -echo "Pull request submitted by $AUTHOR"; -URL_AUTHOR=$(jq -rn --arg x "$AUTHOR" '$x|@uri') -signed=$(curl -s "https://www.lightbend.com/contribute/cla/scala/check/$URL_AUTHOR" | jq -r ".signed"); -if [ "$signed" = "true" ] ; then - echo "CLA check for $AUTHOR successful"; -else - echo "CLA check for $AUTHOR failed"; - echo "Please sign the Scala CLA to contribute to Scala.js."; - echo "Go to https://www.lightbend.com/contribute/cla/scala and then"; - echo "comment on the pull request to ask for a new check."; - exit 1; -fi; diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala b/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala index f208b05c3d..2c8951a67f 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/CompatComponent.scala @@ -25,28 +25,19 @@ import scala.tools.nsc._ * @author Sébastien Doeraene */ trait CompatComponent { - import CompatComponent.{infiniteLoop, noImplClasses} + import CompatComponent.infiniteLoop val global: Global import global._ implicit final class SymbolCompat(self: Symbol) { - def originalOwner: Symbol = - global.originalOwner.getOrElse(self, self.rawowner) - - def implClass: Symbol = NoSymbol - - def isTraitOrInterface: Boolean = self.isTrait || self.isInterface + def isScala3Defined: Boolean = false } implicit final class GlobalCompat( self: CompatComponent.this.global.type) { - object originalOwner { - def getOrElse(sym: Symbol, orElse: => Symbol): Symbol = infiniteLoop() - } - // Added in Scala 2.13.2 for configurable warnings object runReporting { def warning(pos: Position, msg: String, cat: Any, site: Symbol): Unit = @@ -63,55 +54,22 @@ trait CompatComponent { infiniteLoop() } - private implicit final class FlagsCompat(self: Flags.type) { - def IMPLCLASS: Long = infiniteLoop() - } - - lazy val scalaUsesImplClasses: Boolean = - definitions.SeqClass.implClass != NoSymbol // a trait we know has an impl class - - def isImplClass(sym: Symbol): Boolean = - scalaUsesImplClasses && sym.hasFlag(Flags.IMPLCLASS) - - lazy val isScala211: Boolean = scalaUsesImplClasses - - implicit final class StdTermNamesCompat(self: global.nme.type) { - def IMPL_CLASS_SUFFIX: String = noImplClasses() - - def isImplClassName(name: Name): Boolean = false + // DottyEnumSingleton was introduced in 2.13.6 to identify Scala 3 `enum` singleton cases. + object AttachmentsCompatDef { + object DottyEnumSingleton extends PlainAttachment } - implicit final class StdTypeNamesCompat(self: global.tpnme.type) { - def IMPL_CLASS_SUFFIX: String = noImplClasses() - - def interfaceName(implname: Name): TypeName = noImplClasses() - } - - // SAMFunction was introduced in 2.12 for LMF-capable SAM types - - object SAMFunctionAttachCompatDef { - case class SAMFunction(samTp: Type, sam: Symbol, synthCls: Symbol) - extends PlainAttachment - } - - object SAMFunctionAttachCompat { - import SAMFunctionAttachCompatDef._ + object AttachmentsCompat { + import AttachmentsCompatDef._ object Inner { import global._ - type SAMFunctionAlias = SAMFunction - val SAMFunctionAlias = SAMFunction + val DottyEnumSingletonAlias = DottyEnumSingleton } } - type SAMFunctionCompat = SAMFunctionAttachCompat.Inner.SAMFunctionAlias - lazy val SAMFunctionCompat = SAMFunctionAttachCompat.Inner.SAMFunctionAlias - - implicit final class SAMFunctionCompatOps(self: SAMFunctionCompat) { - // Introduced in 2.12.5 to synthesize bridges in LMF classes - def synthCls: Symbol = NoSymbol - } + lazy val DottyEnumSingletonCompat = AttachmentsCompat.Inner.DottyEnumSingletonAlias /* global.genBCode.bTypes.initializeCoreBTypes() * Early 2.12.x versions require that this method be called from @@ -127,6 +85,7 @@ trait CompatComponent { object WarningCategoryCompat { object Reporting { object WarningCategory { + val Deprecation: Any = null val Other: Any = null } } @@ -146,7 +105,4 @@ trait CompatComponent { object CompatComponent { private def infiniteLoop(): Nothing = throw new AssertionError("Infinite loop in Compat") - - private def noImplClasses(): Nothing = - throw new AssertionError("No impl classes in this version") } diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitInnerJS.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitInnerJS.scala index 4e0e4855c8..01ac4b8a85 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitInnerJS.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitInnerJS.scala @@ -113,13 +113,6 @@ abstract class ExplicitInnerJS[G <: Global with Singleton](val global: G) /** This class does not change linearization. */ override protected def changesBaseClasses: Boolean = false - /** Whether vals in traits are represented by their getter. - * This is true in 2.12+, since the addition of the `fields` phase. - * @see https://github.com/scala/scala/pull/5141 - */ - private lazy val traitValsHoldTheirGetterSymbol = - !scala.util.Properties.versionNumberString.startsWith("2.11.") - protected def newTransformer(unit: CompilationUnit): Transformer = new ExplicitInnerJSTransformer(unit) @@ -198,7 +191,7 @@ abstract class ExplicitInnerJS[G <: Global with Singleton](val global: G) addAnnotsIfInJSClass(accessor) decls1.enter(accessor) - if (!clazz.isTrait || !traitValsHoldTheirGetterSymbol) { + if (!clazz.isTrait) { val fieldName = accessorName.append(nme.LOCAL_SUFFIX_STRING) val fieldFlags = Flags.SYNTHETIC | Flags.ARTIFACT | Flags.PrivateLocal @@ -275,7 +268,7 @@ abstract class ExplicitInnerJS[G <: Global with Singleton](val global: G) } } - if (!currentOwner.isTrait || !traitValsHoldTheirGetterSymbol) { + if (!currentOwner.isTrait) { val jsclassField = jsclassAccessor.accessed assert(jsclassField != NoSymbol, jsclassAccessor.fullName) newDecls += localTyper.typedValDef(ValDef(jsclassField, rhs)) @@ -288,14 +281,11 @@ abstract class ExplicitInnerJS[G <: Global with Singleton](val global: G) } } else if (currentOwner.isStaticOwner) { // #4086 - val maybeModuleSym = - if (declSym.isModuleClass) declSym.module // Necessary for Scala 2.11 - else declSym - if (isExposedModule(maybeModuleSym)) { + if (isExposedModule(declSym)) { val getter = - currentOwner.info.member(jsobjectGetterNameFor(maybeModuleSym)) + currentOwner.info.member(jsobjectGetterNameFor(declSym)) newDecls += localTyper.typedDefDef { - DefDef(getter, gen.mkAttributedRef(maybeModuleSym)) + DefDef(getter, gen.mkAttributedRef(declSym)) } } } diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitLocalJS.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitLocalJS.scala index 194ec08d8a..42eff98571 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitLocalJS.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/ExplicitLocalJS.scala @@ -317,7 +317,7 @@ abstract class ExplicitLocalJS[G <: Global with Singleton](val global: G) case Apply(fun @ Select(sup: Super, _), _) if !fun.symbol.isConstructor && isInnerOrLocalJSClass(sup.symbol.superClass) => - wrapWithContextualJSClassValue(sup.symbol.superClass.tpe_*) { + wrapWithContextualSuperJSClassValue(sup.symbol.superClass) { super.transform(tree) } @@ -325,7 +325,7 @@ abstract class ExplicitLocalJS[G <: Global with Singleton](val global: G) case Apply(TypeApply(fun @ Select(sup: Super, _), _), _) if !fun.symbol.isConstructor && isInnerOrLocalJSClass(sup.symbol.superClass) => - wrapWithContextualJSClassValue(sup.symbol.superClass.tpe_*) { + wrapWithContextualSuperJSClassValue(sup.symbol.superClass) { super.transform(tree) } @@ -394,6 +394,38 @@ abstract class ExplicitLocalJS[G <: Global with Singleton](val global: G) } } + /** Wraps with the contextual super JS class value for super calls. */ + private def wrapWithContextualSuperJSClassValue(superClass: Symbol)( + tree: Tree): Tree = { + /* #4801 We need to interpret the superClass type as seen from the + * current class' thisType. + * + * For example, in the test NestedJSClassTest.extendInnerJSClassInClass, + * the original `superClass.tpe_*` is + * + * OuterNativeClass_Issue4402.this.InnerClass + * + * because `InnerClass` is path-dependent. However, the path + * `OuterNativeClass.this` is only valid within `OuterNativeClass` + * itself. In the context of the current local class `Subclass`, this + * path must be replaced by the actual path `outer.`. This is precisely + * the role of `asSeenFrom`. We tell it to replace any `superClass.this` + * by `currentClass.this`, and it also transitively replaces paths for + * outer classes of `superClass`, matching them with the corresponding + * outer paths of `currentClass.thisType` if necessary. The result for + * that test case is + * + * outer.InnerClass + */ + val jsClassTypeInSuperClass = superClass.tpe_* + val jsClassTypeAsSeenFromThis = + jsClassTypeInSuperClass.asSeenFrom(currentClass.thisType, superClass) + + wrapWithContextualJSClassValue(jsClassTypeAsSeenFromThis) { + tree + } + } + private def wrapWithContextualJSClassValue(jsClassType: Type)( tree: Tree): Tree = { wrapWithContextualJSClassValue(genJSConstructorOf(tree, jsClassType)) { diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index d8c0a67850..dc1348ea22 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -26,10 +26,26 @@ import scala.annotation.tailrec import scala.reflect.internal.Flags import org.scalajs.ir -import org.scalajs.ir.{Trees => js, Types => jstpe, ClassKind, Hashers, OriginalName} -import org.scalajs.ir.Names.{LocalName, FieldName, SimpleMethodName, MethodName, ClassName} +import org.scalajs.ir.{ + Trees => js, + Types => jstpe, + WellKnownNames => jswkn, + ClassKind, + Hashers, + OriginalName +} +import org.scalajs.ir.Names.{ + LocalName, + LabelName, + SimpleFieldName, + FieldName, + SimpleMethodName, + MethodName, + ClassName +} import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.Trees.OptimizerHints +import org.scalajs.ir.Version.Unversioned import org.scalajs.nscplugin.util.{ScopedVar, VarBox} import ScopedVar.withScopedVars @@ -66,8 +82,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val phaseName: String = "jscode" override val description: String = "generate JavaScript code from ASTs" - /** testing: this will be called when ASTs are generated */ - def generatedJSAST(clDefs: List[js.ClassDef]): Unit + /** testing: this will be called for each generated `ClassDef`. */ + def generatedJSAST(clDef: js.ClassDef): Unit /** Implicit conversion from nsc Position to ir.Position. */ implicit def pos2irPos(pos: Position): ir.Position = { @@ -150,24 +166,40 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val currentClassSym = new ScopedVar[Symbol] private val fieldsMutatedInCurrentClass = new ScopedVar[mutable.Set[Name]] private val generatedSAMWrapperCount = new ScopedVar[VarBox[Int]] - - private def currentClassType = encodeClassType(currentClassSym) + private val delambdafyTargetDefDefs = new ScopedVar[mutable.Map[Symbol, DefDef]] + private val methodsAllowingJSAwait = new ScopedVar[mutable.Set[Symbol]] + + def currentThisTypeNullable: jstpe.Type = + encodeClassType(currentClassSym) + + def currentThisType: jstpe.Type = { + currentThisTypeNullable match { + case tpe @ jstpe.ClassType(cls, _) => + jswkn.BoxedClassToPrimType.getOrElse(cls, tpe.toNonNullable) + case tpe @ jstpe.AnyType => + // We are in a JS class, in which even `this` is nullable + tpe + case tpe => + throw new AssertionError( + s"Unexpected IR this type $tpe for class ${currentClassSym.get}") + } + } // Per method body private val currentMethodSym = new ScopedVar[Symbol] - private val thisLocalVarIdent = new ScopedVar[Option[js.LocalIdent]] - private val fakeTailJumpParamRepl = new ScopedVar[(Symbol, Symbol)] + private val thisLocalVarName = new ScopedVar[Option[LocalName]] private val enclosingLabelDefInfos = new ScopedVar[Map[Symbol, EnclosingLabelDefInfo]] private val isModuleInitialized = new ScopedVar[VarBox[Boolean]] private val undefinedDefaultParams = new ScopedVar[mutable.Set[Symbol]] private val mutableLocalVars = new ScopedVar[mutable.Set[Symbol]] private val mutatedLocalVars = new ScopedVar[mutable.Set[Symbol]] - private def withPerMethodBodyState[A](methodSym: Symbol)(body: => A): A = { + private def withPerMethodBodyState[A](methodSym: Symbol, + initThisLocalVarName: Option[LocalName] = None)(body: => A): A = { + withScopedVars( currentMethodSym := methodSym, - thisLocalVarIdent := None, - fakeTailJumpParamRepl := (NoSymbol, NoSymbol), + thisLocalVarName := initThisLocalVarName, enclosingLabelDefInfos := Map.empty, isModuleInitialized := new VarBox(false), undefinedDefaultParams := mutable.Set.empty, @@ -179,8 +211,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } // For anonymous methods - // These have a default, since we always read them. - private val tryingToGenMethodAsJSFunction = new ScopedVar[Boolean](false) + // It has a default, since we always read it. private val paramAccessorLocals = new ScopedVar(Map.empty[Symbol, js.ParamDef]) /* Contextual JS class value for some operations of nested JS classes that @@ -198,11 +229,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - private class CancelGenMethodAsJSFunction(message: String) - extends scala.util.control.ControlThrowable { - override def getMessage(): String = message - } - // Rewriting of anonymous function classes --------------------------------- /** Start nested generation of a class. @@ -215,41 +241,154 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) currentClassSym := clsSym, fieldsMutatedInCurrentClass := mutable.Set.empty, generatedSAMWrapperCount := new VarBox(0), + delambdafyTargetDefDefs := mutable.Map.empty, + methodsAllowingJSAwait := mutable.Set.empty, currentMethodSym := null, - thisLocalVarIdent := null, - fakeTailJumpParamRepl := null, + thisLocalVarName := null, enclosingLabelDefInfos := null, isModuleInitialized := null, undefinedDefaultParams := null, mutableLocalVars := null, mutatedLocalVars := null, - tryingToGenMethodAsJSFunction := false, paramAccessorLocals := Map.empty )(withNewLocalNameScope(body)) } + // Global state for tracking methods that reference `this` ----------------- + + /* scalac generates private instance methods for + * a) local defs and + * b) anonymous functions. + * + * In many cases, these methods do not need access to the enclosing `this` + * value. For every other local variable, `lambdalift` only adds them as + * arguments when they are needed; but `this` is always implicitly added + * because all such methods are instance methods. + * + * We run a separate analysis to figure out which of those methods actually + * need their `this` value. We compile those that do not as static methods, + * as if they were `isStaticMember`. + * + * The `delambdafy` phase of scalac performs a similar analysis, although + * as it runs after our backend (for other reasons), we do not benefit from + * it. Our analysis is much simpler because it deals with local defs and + * anonymous functions in a unified way. + * + * Performing this analysis is particularly important for lifted methods + * that appear within arguments to a super constructor call. The lexical + * scope of Scala guarantees that they cannot use `this`, but if they are + * compiled as instance methods, they force the constructor to leak the + * `this` value before the super constructor call, which is invalid. + * While most of the time this analysis is only an optimization, in those + * (rare) situations it is required for correctness. See for example + * the partest `run/t8733.scala`. + */ + + private var statifyCandidateMethodsThatReferenceThis: scala.collection.Set[Symbol] = null + + /** Is that given method a statify-candidate? + * + * If a method is a statify-candidate, we will analyze whether it actually + * needs its `this` value. If it does not, we will compile it as a static + * method in the IR. + * + * A method is a statify-candidate if it is a lifted method (a local def) + * or the target of an anonymous function. + * + * TODO Currently we also require that the method owner not be a JS type. + * We should relax that restriction in the future. + */ + private def isStatifyCandidate(sym: Symbol): Boolean = + (sym.isLiftedMethod || sym.isDelambdafyTarget) && !isJSType(sym.owner) + + /** Do we compile the given method as a static method in the IR? + * + * This is true if one of the following is true: + * + * - It is `isStaticMember`, or + * - It is a statify-candidate method and it does not reference `this`. + */ + private def compileAsStaticMethod(sym: Symbol): Boolean = { + sym.isStaticMember || { + isStatifyCandidate(sym) && + !statifyCandidateMethodsThatReferenceThis.contains(sym) + } + } + + /** Finds all statify-candidate methods that reference `this`, inspired by Delambdafy. */ + private final class ThisReferringMethodsTraverser extends Traverser { + // the set of statify-candidate methods that directly refer to `this` + private val roots = mutable.Set.empty[Symbol] + + // for each statify-candidate method `m`, the set of statify-candidate methods that call `m` + private val methodReferences = mutable.Map.empty[Symbol, mutable.Set[Symbol]] + + def methodReferencesThisIn(tree: Tree): collection.Set[Symbol] = { + traverse(tree) + propagateReferences() + } + + private def addRoot(symbol: Symbol): Unit = + roots += symbol + + private def addReference(from: Symbol, to: Symbol): Unit = + methodReferences.getOrElseUpdate(to, mutable.Set.empty) += from + + private def propagateReferences(): collection.Set[Symbol] = { + val result = mutable.Set.empty[Symbol] + + def rec(symbol: Symbol): Unit = { + // mark `symbol` as referring to `this` + if (result.add(symbol)) { + // `symbol` was not yet in the set; propagate further + methodReferences.getOrElse(symbol, Nil).foreach(rec(_)) + } + } + + roots.foreach(rec(_)) + + result + } + + private var currentMethod: Symbol = NoSymbol + + override def traverse(tree: Tree): Unit = tree match { + case _: DefDef => + if (isStatifyCandidate(tree.symbol)) { + currentMethod = tree.symbol + super.traverse(tree) + currentMethod = NoSymbol + } else { + // No need to traverse other methods; we always assume they refer to `this`. + } + case Function(_, Apply(target, _)) => + /* We don't drill into functions because at this phase they will always refer to `this`. + * They will be of the form {(args...) => this.anonfun(args...)} + * but we do need to make note of the lifted body method in case it refers to `this`. + */ + if (currentMethod.exists) + addReference(from = currentMethod, to = target.symbol) + case Apply(sel @ Select(This(_), _), args) if isStatifyCandidate(sel.symbol) => + if (currentMethod.exists) + addReference(from = currentMethod, to = sel.symbol) + super.traverseTrees(args) + case This(_) => + // Note: tree.symbol != enclClass is possible if it is a module loaded with genLoadModule + if (currentMethod.exists && tree.symbol == currentMethod.enclClass) + addRoot(currentMethod) + case _ => + super.traverse(tree) + } + } + // Global class generation state ------------------------------------------- private val lazilyGeneratedAnonClasses = mutable.Map.empty[Symbol, ClassDef] - private val generatedClasses = ListBuffer.empty[js.ClassDef] + private val generatedClasses = ListBuffer.empty[(js.ClassDef, Position)] private val generatedStaticForwarderClasses = ListBuffer.empty[(Symbol, js.ClassDef)] private def consumeLazilyGeneratedAnonClass(sym: Symbol): ClassDef = { - /* If we are trying to generate an method as JSFunction, we cannot - * actually consume the symbol, since we might fail trying and retry. - * We will then see the same tree again and not find the symbol anymore. - * - * If we are sure this is the only generation, we remove the symbol to - * make sure we don't generate the same class twice. - */ - val optDef = { - if (tryingToGenMethodAsJSFunction) - lazilyGeneratedAnonClasses.get(sym) - else - lazilyGeneratedAnonClasses.remove(sym) - } - - optDef.getOrElse { + lazilyGeneratedAnonClasses.remove(sym).getOrElse { abort("Couldn't find tree for lazily generated anonymous class " + s"${sym.fullName} at ${sym.pos}") } @@ -286,6 +425,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ override def apply(cunit: CompilationUnit): Unit = { try { + statifyCandidateMethodsThatReferenceThis = + new ThisReferringMethodsTraverser().methodReferencesThisIn(cunit.body) + def collectClassDefs(tree: Tree): List[ClassDef] = { tree match { case EmptyTree => Nil @@ -295,39 +437,26 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } val allClassDefs = collectClassDefs(cunit.body) - /* There are three types of anonymous classes we want to generate - * only once we need them so we can inline them at construction site: + /* There are two types of anonymous classes we want to generate only + * once we need them, so we can inline them at construction site: * - * - anonymous class that are JS types, which includes: - * - lambdas for js.FunctionN and js.ThisFunctionN (SAMs). (We may - * not generate actual Scala classes for these). - * - anonymous (non-lambda) JS classes. These classes may not have - * their own prototype. Therefore, their constructor *must* be - * inlined. - * - lambdas for scala.FunctionN. This is only an optimization and may - * fail. In the case of failure, we fall back to generating a - * fully-fledged Scala class. + * - Lambdas for `js.Function` SAMs, including `js.FunctionN`, + * `js.ThisFunctionN` and custom JS function SAMs. We must generate + * JS functions for these, instead of actual classes. + * - Anonymous (non-lambda) JS classes. These classes may not have + * their own prototype. Therefore, their constructor *must* be + * inlined. * * Since for all these, we don't know how they inter-depend, we just * store them in a map at this point. */ - val (lazyAnons, fullClassDefs0) = allClassDefs.partition { cd => + val (lazyAnons, fullClassDefs) = allClassDefs.partition { cd => val sym = cd.symbol - isAnonymousJSClass(sym) || isJSFunctionDef(sym) || sym.isAnonymousFunction + isAnonymousJSClass(sym) || isJSFunctionDef(sym) } lazilyGeneratedAnonClasses ++= lazyAnons.map(cd => cd.symbol -> cd) - /* Under Scala 2.11 with -Xexperimental, anonymous JS function classes - * can be referred to in private method signatures, which means they - * must exist at the IR level, as `AbstractJSType`s. - */ - val fullClassDefs = if (isScala211WithXexperimental) { - lazyAnons.filter(cd => isJSFunctionDef(cd.symbol)) ::: fullClassDefs0 - } else { - fullClassDefs0 - } - /* Finally, we emit true code for the remaining class defs. */ for (cd <- fullClassDefs) { val sym = cd.symbol @@ -337,50 +466,33 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val isPrimitive = isPrimitiveValueClass(sym) || (sym == ArrayClass) - if (!isPrimitive && !isJSImplClass(sym)) { + if (!isPrimitive) { withScopedVars( currentClassSym := sym, fieldsMutatedInCurrentClass := mutable.Set.empty, - generatedSAMWrapperCount := new VarBox(0) + generatedSAMWrapperCount := new VarBox(0), + delambdafyTargetDefDefs := mutable.Map.empty, + methodsAllowingJSAwait := mutable.Set.empty ) { - try { - val tree = if (isJSType(sym)) { - if (!sym.isTraitOrInterface && isNonNativeJSClass(sym) && - !isJSFunctionDef(sym)) { - genNonNativeJSClass(cd) - } else { - genJSClassData(cd) - } - } else if (sym.isTraitOrInterface) { - genInterface(cd) + val tree = if (isJSType(sym)) { + if (!sym.isTraitOrInterface && isNonNativeJSClass(sym) && + !isJSFunctionDef(sym)) { + genNonNativeJSClass(cd) } else { - genClass(cd) + genJSClassData(cd) } - - generatedClasses += tree - } catch { - case e: ir.InvalidIRException => - e.tree match { - case ir.Trees.Transient(UndefinedParam) => - reporter.error(sym.pos, - "Found a dangling UndefinedParam at " + - s"${e.tree.pos}. This is likely due to a bad " + - "interaction between a macro or a compiler plugin " + - "and the Scala.js compiler plugin. If you hit " + - "this, please let us know.") - - case _ => - reporter.error(sym.pos, - "The Scala.js compiler generated invalid IR for " + - "this class. Please report this as a bug. IR: " + - e.tree) - } + } else if (sym.isTraitOrInterface) { + genInterface(cd) + } else { + genClass(cd) } + + generatedClasses += tree -> sym.pos } } } - val clDefs = if (generatedStaticForwarderClasses.isEmpty) { + val clDefs: List[(js.ClassDef, Position)] = if (generatedStaticForwarderClasses.isEmpty) { /* Fast path, applicable under -Xno-forwarders, as well as when all * the `object`s of a compilation unit have a companion class. */ @@ -407,7 +519,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) classDef.name.name.nameString.toLowerCase(java.util.Locale.ENGLISH) val generatedCaseInsensitiveNames = - regularClasses.map(caseInsensitiveNameOf).toSet + regularClasses.map(pair => caseInsensitiveNameOf(pair._1)).toSet val staticForwarderClasses = generatedStaticForwarderClasses.toList .withFilter { case (site, classDef) => if (!generatedCaseInsensitiveNames.contains(caseInsensitiveNameOf(classDef))) { @@ -423,32 +535,86 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) false } } - .map(_._2) + .map(pair => (pair._2, pair._1.pos)) regularClasses ::: staticForwarderClasses } - generatedJSAST(clDefs) + for ((classDef, pos) <- clDefs) { + try { + val hashedClassDef = Hashers.hashClassDef(classDef) + generatedJSAST(hashedClassDef) + genIRFile(cunit, hashedClassDef) + } catch { + case e: ir.InvalidIRException => + e.optTree match { + case Some(tree @ ir.Trees.Transient(UndefinedParam)) => + reporter.error(pos, + "Found a dangling UndefinedParam at " + + s"${tree.pos}. This is likely due to a bad " + + "interaction between a macro or a compiler plugin " + + "and the Scala.js compiler plugin. If you hit " + + "this, please let us know.") + + case Some(tree) => + reporter.error(pos, + "The Scala.js compiler generated invalid IR for " + + "this class. Please report this as a bug. IR: " + + tree) - for (tree <- clDefs) { - genIRFile(cunit, tree) + case None => + reporter.error(pos, + "The Scala.js compiler generated invalid IR for this class. " + + "Please report this as a bug. " + + e.getMessage()) + } + } } } catch { // Handle exceptions in exactly the same way as the JVM backend case ex: InterruptedException => throw ex case ex: Throwable => - if (settings.debug) + if (settings.debug.value) ex.printStackTrace() globalError(s"Error while emitting ${cunit.source}\n${ex.getMessage}") } finally { lazilyGeneratedAnonClasses.clear() generatedStaticForwarderClasses.clear() generatedClasses.clear() + statifyCandidateMethodsThatReferenceThis = null pos2irPosCache.clear() } } + private def collectDefDefs(impl: Template): List[DefDef] = { + val b = List.newBuilder[DefDef] + + for (stat <- impl.body) { + stat match { + case stat: DefDef => + if (stat.symbol.isDelambdafyTarget) + delambdafyTargetDefDefs += stat.symbol -> stat + else + b += stat + + case EmptyTree | _:ValDef => + () + + case _ => + abort(s"Unexpected tree in template: $stat at ${stat.pos}") + } + } + + b.result() + } + + private def consumeDelambdafyTarget(sym: Symbol): DefDef = { + delambdafyTargetDefDefs.remove(sym).getOrElse { + abort(s"Cannot resolve delambdafy target $sym at ${sym.pos}") + } + } + // Generate a class -------------------------------------------------------- /** Gen the IR ClassDef for a class definition (maybe a module class). @@ -496,37 +662,23 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // Generate members (constructor + methods) - val generatedNonFieldMembers = new ListBuffer[js.MemberDef] - - def gen(tree: Tree): Unit = { - tree match { - case EmptyTree => () - case Template(_, _, body) => body foreach gen + val methodsBuilder = List.newBuilder[js.MethodDef] + val jsNativeMembersBuilder = List.newBuilder[js.JSNativeMemberDef] - case ValDef(mods, name, tpt, rhs) => - () // fields are added via genClassFields() - - case dd: DefDef => - if (dd.symbol.hasAnnotation(JSNativeAnnotation)) - generatedNonFieldMembers += genJSNativeMemberDef(dd) - else - generatedNonFieldMembers ++= genMethod(dd) - - case _ => abort("Illegal tree in gen of genClass(): " + tree) - } + for (dd <- collectDefDefs(impl)) { + if (dd.symbol.hasAnnotation(JSNativeAnnotation)) + jsNativeMembersBuilder += genJSNativeMemberDef(dd) + else + methodsBuilder ++= genMethod(dd) } - gen(impl) + val fields = if (!isHijacked) genClassFields(cd) else Nil - // Generate fields if necessary (and add to methods + ctors) - val generatedMembers = - if (!isHijacked) genClassFields(cd) ++ generatedNonFieldMembers.toList - else generatedNonFieldMembers.toList // No fields needed + val jsNativeMembers = jsNativeMembersBuilder.result() + val generatedMethods = methodsBuilder.result() - // Generate member exports val memberExports = genMemberExports(sym) - // Generate the exported members, constructors and accessors val topLevelExportDefs = genTopLevelExports(sym) // Static initializer @@ -555,7 +707,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) reflectInit.toList ::: staticModuleInit.toList if (staticInitializerStats.nonEmpty) { List(genStaticConstructorWithStats( - ir.Names.StaticInitializerName, + jswkn.StaticInitializerName, js.Block(staticInitializerStats))) } else { Nil @@ -566,12 +718,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (isDynamicImportThunk) List(genDynamicImportForwarder(sym)) else Nil - val allMemberDefsExceptStaticForwarders = - generatedMembers ::: memberExports ::: optStaticInitializer ::: optDynamicImportForwarder + val allMethodsExceptStaticForwarders: List[js.MethodDef] = + generatedMethods ::: optStaticInitializer ::: optDynamicImportForwarder // Add static forwarders - val allMemberDefs = if (!isCandidateForForwarders(sym)) { - allMemberDefsExceptStaticForwarders + val allMethods = if (!isCandidateForForwarders(sym)) { + allMethodsExceptStaticForwarders } else { if (sym.isModuleClass) { /* If the module class has no linked class, we must create one to @@ -586,34 +738,35 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) originalName, ClassKind.Class, None, - Some(js.ClassIdent(ir.Names.ObjectClass)), + Some(js.ClassIdent(jswkn.ObjectClass)), Nil, None, None, - forwarders, - Nil + fields = Nil, + methods = forwarders, + jsConstructor = None, + jsMethodProps = Nil, + jsNativeMembers = Nil, + topLevelExportDefs = Nil )(js.OptimizerHints.empty) generatedStaticForwarderClasses += sym -> forwardersClassDef } } - allMemberDefsExceptStaticForwarders + allMethodsExceptStaticForwarders } else { val forwarders = genStaticForwardersForClassOrInterface( - allMemberDefsExceptStaticForwarders, sym) - allMemberDefsExceptStaticForwarders ::: forwarders + allMethodsExceptStaticForwarders, sym) + allMethodsExceptStaticForwarders ::: forwarders } } - // Hashed definitions of the class - val hashedMemberDefs = Hashers.hashMemberDefs(allMemberDefs) - // The complete class definition val kind = if (isStaticModule(sym)) ClassKind.ModuleClass else if (isHijacked) ClassKind.HijackedClass else ClassKind.Class - val classDefinition = js.ClassDef( + js.ClassDef( classIdent, originalName, kind, @@ -622,11 +775,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) genClassInterfaces(sym, forJSClass = false), None, None, - hashedMemberDefs, + fields, + allMethods, + jsConstructor = None, + memberExports, + jsNativeMembers, topLevelExportDefs)( optimizerHints) - - classDefinition } /** Gen the IR ClassDef for a non-native JS class. */ @@ -654,46 +809,33 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val generatedMethods = new ListBuffer[js.MethodDef] val dispatchMethodNames = new ListBuffer[JSName] - def gen(tree: Tree): Unit = { - tree match { - case EmptyTree => () - case Template(_, _, body) => body foreach gen - - case ValDef(mods, name, tpt, rhs) => - () // fields are added via genClassFields() - - case dd: DefDef => - val sym = dd.symbol - val exposed = isExposed(sym) - - if (sym.isClassConstructor) { - constructorTrees += dd - } else if (exposed && sym.isAccessor && !sym.isLazy) { - /* Exposed accessors must not be emitted, since the field they - * access is enough. - */ - } else if (sym.hasAnnotation(JSOptionalAnnotation)) { - // Optional methods must not be emitted - } else { - generatedMethods ++= genMethod(dd) + for (dd <- collectDefDefs(cd.impl)) { + val sym = dd.symbol + val exposed = isExposed(sym) - // Collect the names of the dispatchers we have to create - if (exposed && !sym.isDeferred) { - /* We add symbols that we have to expose here. This way we also - * get inherited stuff that is implemented in this class. - */ - dispatchMethodNames += jsNameOf(sym) - } - } + if (sym.isClassConstructor) { + constructorTrees += dd + } else if (exposed && sym.isAccessor && !sym.isLazy) { + /* Exposed accessors must not be emitted, since the field they + * access is enough. + */ + } else if (sym.hasAnnotation(JSOptionalAnnotation)) { + // Optional methods must not be emitted + } else { + generatedMethods ++= genMethod(dd) - case _ => abort("Illegal tree in gen of genClass(): " + tree) + // Collect the names of the dispatchers we have to create + if (exposed && !sym.isDeferred) { + /* We add symbols that we have to expose here. This way we also + * get inherited stuff that is implemented in this class. + */ + dispatchMethodNames += jsNameOf(sym) + } } } - gen(cd.impl) - // Static members (exported from the companion object) - val staticMembers = { + val (staticFields, staticExports) = { /* Phase travel is necessary for non-top-level classes, because flatten * breaks their companionModule. This is tracked upstream at * https://github.com/scala/scala-dev/issues/403 @@ -701,19 +843,20 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val companionModuleClass = exitingPhase(currentRun.picklerPhase)(sym.linkedClassOfClass) if (companionModuleClass == NoSymbol) { - Nil + (Nil, Nil) } else { - val exports = withScopedVars(currentClassSym := companionModuleClass) { - genStaticExports(companionModuleClass) + val (staticFields, staticExports) = { + withScopedVars(currentClassSym := companionModuleClass) { + genStaticExports(companionModuleClass) + } } - if (exports.exists(_.isInstanceOf[js.JSFieldDef])) { - val classInitializer = genStaticConstructorWithStats( - ir.Names.ClassInitializerName, - genLoadModule(companionModuleClass)) - exports :+ classInitializer - } else { - exports + + if (staticFields.nonEmpty) { + generatedMethods += genStaticConstructorWithStats( + jswkn.ClassInitializerName, genLoadModule(companionModuleClass)) } + + (staticFields, staticExports) } } @@ -746,24 +889,17 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } // Generate fields (and add to methods + ctors) - val generatedMembers = { - genClassFields(cd) ::: - generatedCtor :: - genJSClassDispatchers(sym, dispatchMethodNames.result().distinct) ::: - generatedMethods.toList ::: - staticMembers - } + val fields = genClassFields(cd) - // Hashed definitions of the class - val hashedMemberDefs = - Hashers.hashMemberDefs(generatedMembers) + val jsMethodProps = + genJSClassDispatchers(sym, dispatchMethodNames.result().distinct) ::: staticExports // The complete class definition val kind = if (isStaticModule(sym)) ClassKind.JSModuleClass else ClassKind.JSClass - val classDefinition = js.ClassDef( + js.ClassDef( classIdent, originalNameOfClass(sym), kind, @@ -772,11 +908,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) genClassInterfaces(sym, forJSClass = true), jsSuperClass = jsClassCaptures.map(_.head.ref), None, - hashedMemberDefs, + fields ::: staticFields, + generatedMethods.toList, + Some(generatedCtor), + jsMethodProps, + jsNativeMembers = Nil, topLevelExports)( OptimizerHints.empty) - - classDefinition } /** Generate an instance of an anonymous (non-lambda) JS class inline @@ -801,56 +939,35 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // Partition class members. val privateFieldDefs = ListBuffer.empty[js.FieldDef] - val classDefMembers = ListBuffer.empty[js.MemberDef] - val instanceMembers = ListBuffer.empty[js.MemberDef] - var constructor: Option[js.JSMethodDef] = None + val jsFieldDefs = ListBuffer.empty[js.JSFieldDef] - origJsClass.memberDefs.foreach { + origJsClass.fields.foreach { case fdef: js.FieldDef => privateFieldDefs += fdef case fdef: js.JSFieldDef => - instanceMembers += fdef - - case mdef: js.MethodDef => - assert(mdef.flags.namespace.isStatic, - "Non-static, unexported method in non-native JS class") - classDefMembers += mdef - - case mdef: js.JSMethodDef => - mdef.name match { - case js.StringLiteral("constructor") => - assert(!mdef.flags.namespace.isStatic, "Exported static method") - assert(constructor.isEmpty, "two ctors in class") - constructor = Some(mdef) - - case _ => - assert(!mdef.flags.namespace.isStatic, "Exported static method") - instanceMembers += mdef - } - - case property: js.JSPropertyDef => - instanceMembers += property - - case nativeMemberDef: js.JSNativeMemberDef => - abort("illegal native JS member in JS class at " + nativeMemberDef.pos) + jsFieldDefs += fdef } + assert(origJsClass.jsNativeMembers.isEmpty, + "Found JS native members in anonymous JS class at " + pos) + assert(origJsClass.topLevelExportDefs.isEmpty, "Found top-level exports in anonymous JS class at " + pos) // Make new class def with static members val newClassDef = { implicit val pos = origJsClass.pos - val parent = js.ClassIdent(ir.Names.ObjectClass) + val parent = js.ClassIdent(jswkn.ObjectClass) js.ClassDef(origJsClass.name, origJsClass.originalName, ClassKind.AbstractJSType, None, Some(parent), interfaces = Nil, - jsSuperClass = None, jsNativeLoadSpec = None, - classDefMembers.toList, Nil)( + jsSuperClass = None, jsNativeLoadSpec = None, fields = Nil, + methods = origJsClass.methods, jsConstructor = None, jsMethodProps = Nil, + jsNativeMembers = Nil, topLevelExportDefs = Nil)( origJsClass.optimizerHints) } - generatedClasses += newClassDef + generatedClasses += newClassDef -> pos // Construct inline class definition @@ -858,7 +975,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) throw new AssertionError( s"no class captures for anonymous JS class at $pos") } - val js.JSMethodDef(_, _, ctorParams, ctorRestParam, ctorBody) = constructor.getOrElse { + val js.JSConstructorDef(_, ctorParams, ctorRestParam, ctorBody) = origJsClass.jsConstructor.getOrElse { throw new AssertionError("No ctor found") } assert(ctorParams.isEmpty && ctorRestParam.isEmpty, @@ -874,27 +991,22 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * FIXME This could clash with a local variable of the constructor or a JS * class capture. How do we avoid this? */ - val selfName = freshLocalIdent("this")(pos) + val selfIdent = freshLocalIdent("this")(pos) def selfRef(implicit pos: ir.Position) = - js.VarRef(selfName)(jstpe.AnyType) + js.VarRef(selfIdent.name)(jstpe.AnyType) def memberLambda(params: List[js.ParamDef], restParam: Option[js.ParamDef], body: js.Tree)(implicit pos: ir.Position) = { - js.Closure(arrow = false, captureParams = Nil, params, restParam, body, - captureValues = Nil) + js.Closure(js.ClosureFlags.function, captureParams = Nil, params, + restParam, jstpe.AnyType, body, captureValues = Nil) } - val memberDefinitions0 = instanceMembers.toList.map { - case fdef: js.FieldDef => - throw new AssertionError("unexpected FieldDef") - - case fdef: js.JSFieldDef => - implicit val pos = fdef.pos - js.Assign(js.JSSelect(selfRef, fdef.name), jstpe.zeroOf(fdef.ftpe)) - - case mdef: js.MethodDef => - throw new AssertionError("unexpected MethodDef") + val fieldDefinitions = jsFieldDefs.toList.map { fdef => + implicit val pos = fdef.pos + js.Assign(js.JSSelect(selfRef, fdef.name), jstpe.zeroOf(fdef.ftpe)) + } + val memberDefinitions0 = origJsClass.jsMethodProps.toList.map { case mdef: js.JSMethodDef => implicit val pos = mdef.pos val impl = memberLambda(mdef.args, mdef.restParam, mdef.body) @@ -916,13 +1028,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.JSMethodApply(js.JSGlobalRef("Object"), js.StringLiteral("defineProperty"), List(selfRef, pdef.name, descriptor)) - - case nativeMemberDef: js.JSNativeMemberDef => - abort("illegal native JS member in JS class at " + nativeMemberDef.pos) } + val memberDefinitions1 = fieldDefinitions ::: memberDefinitions0 + val memberDefinitions = if (privateFieldDefs.isEmpty) { - memberDefinitions0 + memberDefinitions1 } else { /* Private fields, declared in FieldDefs, are stored in a separate * object, itself stored as a non-enumerable field of the `selfRef`. @@ -962,43 +1073,46 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) ) ) } - definePrivateFieldsObj :: memberDefinitions0 + definePrivateFieldsObj :: memberDefinitions1 } // Transform the constructor body. - val inlinedCtorStats = new ir.Transformers.Transformer { - override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match { - // The super constructor call. Transform this into a simple new call. - case js.JSSuperConstructorCall(args) => - implicit val pos = tree.pos - - val newTree = { - val ident = - origJsClass.superClass.getOrElse(abort("No superclass")) - if (args.isEmpty && ident.name == JSObjectClassName) - js.JSObjectConstr(Nil) - else - js.JSNew(jsSuperClassRef, args) - } - - js.Block( - js.VarDef(selfName, thisOriginalName, jstpe.AnyType, - mutable = false, newTree) :: - memberDefinitions)(NoPosition) + val inlinedCtorStats = { + val beforeSuper = ctorBody.beforeSuper + + val superCall = { + implicit val pos = ctorBody.superCall.pos + val js.JSSuperConstructorCall(args) = ctorBody.superCall + + val newTree = { + val ident = + origJsClass.superClass.getOrElse(abort("No superclass")) + if (args.isEmpty && ident.name == JSObjectClassName) + js.JSObjectConstr(Nil) + else + js.JSNew(jsSuperClassRef, args) + } - case js.This() => selfRef(tree.pos) + val selfVarDef = js.VarDef(selfIdent.copy(), // copy for the correct `pos` + thisOriginalName, jstpe.AnyType, mutable = false, newTree) + selfVarDef :: memberDefinitions + } - // Don't traverse closure boundaries - case closure: js.Closure => - val newCaptureValues = closure.captureValues.map(transformExpr) - closure.copy(captureValues = newCaptureValues)(closure.pos) + // After the super call, substitute `selfRef` for `this` + val afterSuper = new ir.Transformers.LocalScopeTransformer { + override def transform(tree: js.Tree): js.Tree = tree match { + case js.This() => + selfRef(tree.pos) + case tree => + super.transform(tree) + } + }.transformTrees(ctorBody.afterSuper) - case tree => - super.transform(tree, isStat) - } - }.transform(ctorBody, isStat = true) + beforeSuper ::: superCall ::: afterSuper + } - val closure = js.Closure(arrow = true, jsClassCaptures, Nil, None, + // Wrap everything in a lambda, for namespacing + val closure = js.Closure(js.ClosureFlags.arrow, jsClassCaptures, Nil, None, jstpe.AnyType, js.Block(inlinedCtorStats, selfRef), jsSuperClassValue :: args) js.JSFunctionApply(closure, Nil) } @@ -1025,7 +1139,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.ClassDef(classIdent, originalNameOfClass(sym), kind, None, superClass, genClassInterfaces(sym, forJSClass = true), None, jsNativeLoadSpec, - Nil, Nil)( + Nil, Nil, None, Nil, Nil, Nil)( OptimizerHints.empty) } @@ -1039,32 +1153,16 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val classIdent = encodeClassNameIdent(sym) - // fill in class info builder - def gen(tree: Tree): List[js.MethodDef] = { - tree match { - case EmptyTree => Nil - case Template(_, _, body) => body.flatMap(gen) - - case dd: DefDef => - genMethod(dd).toList - - case _ => - abort("Illegal tree in gen of genInterface(): " + tree) - } - } - val generatedMethods = gen(cd.impl) + val generatedMethods = collectDefDefs(cd.impl).flatMap(genMethod(_)) val interfaces = genClassInterfaces(sym, forJSClass = false) val allMemberDefs = if (!isCandidateForForwarders(sym)) generatedMethods else generatedMethods ::: genStaticForwardersForClassOrInterface(generatedMethods, sym) - // Hashed definitions of the interface - val hashedMemberDefs = - Hashers.hashMemberDefs(allMemberDefs) - js.ClassDef(classIdent, originalNameOfClass(sym), ClassKind.Interface, - None, None, interfaces, None, None, hashedMemberDefs, Nil)( + None, None, interfaces, None, None, fields = Nil, methods = allMemberDefs, + None, Nil, Nil, Nil)( OptimizerHints.empty) } @@ -1095,9 +1193,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * the companion class. This is because in the IR, only methods with the * same `MethodName` (including signature) and that are also * `PublicStatic` would collide. Since we never emit any `PublicStatic` - * method otherwise (except in 2.11 impl classes, which have no companion), - * there can be no collision. If that assumption is broken, an error - * message is emitted asking the user to report a bug. + * method otherwise, there can be no collision. If that assumption is broken, + * an error message is emitted asking the user to report a bug. * * It is important that we always emit forwarders, because some Java APIs * actually have a public static method and a public instance method with @@ -1116,7 +1213,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * static forwarders? */ def isCandidateForForwarders(sym: Symbol): Boolean = { - !settings.noForwarders && sym.isStatic && !isImplClass(sym) && { + !settings.noForwarders.value && sym.isStatic && { // Reject non-top-level objects unless opted in via the appropriate option scalaJSOpts.genStaticForwardersForNonTopLevelObjects || !sym.name.containsChar('$') // this is the same test that scalac performs @@ -1132,8 +1229,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * Precondition: `isCandidateForForwarders(sym)` is true */ def genStaticForwardersForClassOrInterface( - existingMembers: List[js.MemberDef], sym: Symbol)( - implicit pos: Position): List[js.MemberDef] = { + existingMethods: List[js.MethodDef], sym: Symbol)( + implicit pos: Position): List[js.MethodDef] = { /* Phase travel is necessary for non-top-level classes, because flatten * breaks their companionModule. This is tracked upstream at * https://github.com/scala/scala-dev/issues/403 @@ -1144,41 +1241,24 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } else { val moduleClass = module.moduleClass if (!isJSType(moduleClass)) - genStaticForwardersFromModuleClass(existingMembers, moduleClass) + genStaticForwardersFromModuleClass(existingMethods, moduleClass) else Nil } } - private lazy val dontUseExitingUncurryForForwarders = - scala.util.Properties.versionNumberString.startsWith("2.11.") - /** Gen the static forwarders for the methods of a module class. * * Precondition: `isCandidateForForwarders(moduleClass)` is true */ - def genStaticForwardersFromModuleClass(existingMembers: List[js.MemberDef], + def genStaticForwardersFromModuleClass(existingMethods: List[js.MethodDef], moduleClass: Symbol)( - implicit pos: Position): List[js.MemberDef] = { + implicit pos: Position): List[js.MethodDef] = { assert(moduleClass.isModuleClass, moduleClass) - val hasAnyExistingPublicStaticMethod = existingMembers.exists { - case js.MethodDef(flags, _, _, _, _, _) => - flags.namespace == js.MemberNamespace.PublicStatic - case _ => - false - } - if (hasAnyExistingPublicStaticMethod) { - reporter.error(pos, - "Unexpected situation: found existing public static methods in " + - s"the class ${moduleClass.fullName} while trying to generate " + - "static forwarders for its companion object. " + - "Please report this as a bug in Scala.js.") - } - def listMembersBasedOnFlags = { - // Copy-pasted from BCodeHelpers (it's somewhere else in 2.11.x) + // Copy-pasted from BCodeHelpers. val ExcludedForwarderFlags: Long = { import scala.tools.nsc.symtab.Flags._ SPECIALIZED | LIFTED | PROTECTED | STATIC | EXPANDEDNAME | PRIVATE | MACRO @@ -1187,21 +1267,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, symtab.Flags.METHOD) } - /* See BCodeHelprs.addForwarders in 2.12+ for why we normally use - * exitingUncurry. In 2.11.x we do not use it, because Scala/JVM did not - * use it back then, and using it on that version causes mixed in methods - * not to be found (this notably breaks `extends App` as the `main` - * method that it defines is not found). - * - * This means that in 2.11.x we suffer from - * https://github.com/scala/bug/issues/10812, like upstream Scala/JVM, - * but it does not really affect Scala.js because the IR methods are not - * used for compilation, only for linking, and for linking it is fine to - * have additional, unexpected bridges. - */ - val members = - if (dontUseExitingUncurryForForwarders) listMembersBasedOnFlags - else exitingUncurry(listMembersBasedOnFlags) + // See BCodeHelprs.addForwarders in 2.12+ for why we use exitingUncurry. + val members = exitingUncurry(listMembersBasedOnFlags) def isExcluded(m: Symbol): Boolean = { def isOfJLObject: Boolean = { @@ -1234,7 +1301,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.MethodDef(flags, methodIdent, originalName, jsParams, resultType, Some { genApplyMethod(genLoadModule(moduleClass), m, jsParams.map(_.ref)) - })(OptimizerHints.empty, None) + })(OptimizerHints.empty, Unversioned) } } @@ -1310,7 +1377,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * Anyway, scalac also has problems with uninitialized value * class values, if they come from a generic context. */ - jstpe.ClassType(encodeClassName(tpe.valueClazz)) + jstpe.ClassType(encodeClassName(tpe.valueClazz), nullable = true) case _ => /* Other types are not boxed, so we can initialize them to @@ -1329,9 +1396,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.MethodIdent(name), NoOriginalName, Nil, - jstpe.NoType, + jstpe.VoidType, Some(stats))( - OptimizerHints.empty, None) + OptimizerHints.empty, Unversioned) } private def genRegisterReflectiveInstantiation(sym: Symbol)( @@ -1351,7 +1418,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val fqcnArg = js.StringLiteral(sym.fullName + "$") val runtimeClassArg = js.ClassOf(toTypeRef(sym.info)) val loadModuleFunArg = - js.Closure(arrow = true, Nil, Nil, None, genLoadModule(sym), Nil) + js.Closure(js.ClosureFlags.arrow, Nil, Nil, None, jstpe.AnyType, genLoadModule(sym), Nil) val stat = genApplyMethod( genLoadModule(ReflectModule), @@ -1401,9 +1468,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val paramTypesArray = js.JSArrayConstr(parameterTypes) - val newInstanceFun = js.Closure(arrow = true, Nil, formalParams, None, { - genNew(sym, ctor, actualParams) - }, Nil) + val newInstanceFun = js.Closure(js.ClosureFlags.arrow, Nil, + formalParams, None, jstpe.AnyType, genNew(sym, ctor, actualParams), Nil) js.JSArrayConstr(List(paramTypesArray, newInstanceFun)) } @@ -1425,7 +1491,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // Constructor of a non-native JS class ------------------------------ def genJSClassCapturesAndConstructor(constructorTrees: List[DefDef])( - implicit pos: Position): (List[js.ParamDef], js.JSMethodDef) = { + implicit pos: Position): (List[js.ParamDef], js.JSConstructorDef) = { /* We need to merge all Scala constructors into a single one because * JavaScript only allows a single one. * @@ -1498,21 +1564,22 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) (exports.result(), jsClassCaptures.result()) } + // The name 'constructor' is used for error reporting here val (formalArgs, restParam, overloadDispatchBody) = genOverloadDispatch(JSName.Literal("constructor"), exports, jstpe.IntType) val overloadVar = js.VarDef(freshLocalIdent("overload"), NoOriginalName, jstpe.IntType, mutable = false, overloadDispatchBody) - val ctorStats = genJSClassCtorStats(overloadVar.ref, ctorTree) - - val constructorBody = js.Block( - paramVarDefs ::: List(overloadVar, ctorStats, js.Undefined())) + val constructorBody = wrapJSCtorBody( + paramVarDefs :+ overloadVar, + genJSClassCtorBody(overloadVar.ref, ctorTree), + js.Undefined() :: Nil + ) - val constructorDef = js.JSMethodDef( - js.MemberFlags.empty, - js.StringLiteral("constructor"), - formalArgs, restParam, constructorBody)(OptimizerHints.empty, None) + val constructorDef = js.JSConstructorDef( + js.MemberFlags.empty.withNamespace(js.MemberNamespace.Constructor), + formalArgs, restParam, constructorBody)(OptimizerHints.empty, Unversioned) (jsClassCaptures, constructorDef) } @@ -1522,18 +1589,78 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val sym = dd.symbol assert(sym.isPrimaryConstructor, s"called with non-primary ctor: $sym") + var preSuperStats = List.newBuilder[js.Tree] var jsSuperCall: Option[js.JSSuperConstructorCall] = None - val jsStats = List.newBuilder[js.Tree] + val postSuperStats = mutable.ListBuffer.empty[js.Tree] - /* Move all statements after the super constructor call since JS - * cannot access `this` before the super constructor call. + /* Move param accessor initializers and early initializers after the + * super constructor call since JS cannot access `this` before the super + * constructor call. * * scalac inserts statements before the super constructor call for early * initializers and param accessor initializers (including val's and var's - * declared in the params). We move those after the super constructor - * call, and are therefore executed later than for a Scala class. + * declared in the params). Those statements include temporary local `val` + * definitions (for true early initializers only) and the assignments, + * whose rhs'es are always simple Idents (either constructor params or the + * temporary local `val`s). + * + * There can also be local `val`s before the super constructor call for + * default arguments to the super constructor. These must remain before. + * + * Our strategy is therefore to move only the field assignments after the + * super constructor call. They are therefore executed later than for a + * Scala class (as specified for non-native JS classes semantics). + * However, side effects and evaluation order of all the other + * computations remains unchanged. + * + * For a somewhat extreme example of the shapes we can get here, consider + * the source code: + * + * class Parent(output: Any = "output", callbackObject: Any = "callbackObject") extends js.Object { + * println(s"Parent constructor; $output; $callbackObject") + * } + * + * class Child(val foo: Int, callbackObject: Any, val bar: Int) extends { + * val xyz = foo + bar + * val yz = { println(xyz); xyz + 2 } + * } with Parent(callbackObject = { println(foo); xyz + bar }) { + * println("Child constructor") + * println(xyz) + * } + * + * At this phase, for the constructor of `Child`, we receive the following + * scalac Tree: + * + * def (foo: Int, callbackObject: Object, bar: Int): helloworld.Child = { + * Child.this.foo = foo; // param accessor assignment, moved + * Child.this.bar = bar; // param accessor assignment, moved + * val xyz: Int = foo.+(bar); // note that these still use the ctor params, not the fields + * Child.this.xyz = xyz; // early initializer, moved + * val yz: Int = { + * scala.Predef.println(scala.Int.box(xyz)); // note that this uses the local val, not the field + * xyz.+(2) + * }; + * Child.this.yz = yz; // early initializer, moved + * { + * val x$1: Int = { + * scala.Predef.println(scala.Int.box(foo)); + * xyz.+(bar) // here as well, we use the local vals, not the fields + * }; + * val x$2: Object = helloworld.this.Parent.$default$1(); + * Child.super.(x$2, scala.Int.box(x$1)) + * }; + * scala.Predef.println("Child constructor"); + * scala.Predef.println(scala.Int.box(Child.this.xyz())); + * () + * } + * */ withPerMethodBodyState(sym) { + def isThisFieldAssignment(tree: Tree): Boolean = tree match { + case Assign(Select(ths: This, _), Ident(_)) => ths.symbol == currentClassSym.get + case _ => false + } + flatStats(stats).foreach { case tree @ Apply(fun @ Select(Super(This(_), _), _), args) if fun.symbol.isClassConstructor => @@ -1541,22 +1668,42 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) implicit val pos = tree.pos jsSuperCall = Some(js.JSSuperConstructorCall(genPrimitiveJSArgs(fun.symbol, args))) - case stat => - val jsStat = genStat(stat) + case tree if jsSuperCall.isDefined => + // Once we're past the super constructor call, everything goes after. + postSuperStats += genStat(tree) + + case tree if isThisFieldAssignment(tree) => + /* If that shape appears before the jsSuperCall, it is an early + * initializer or param accessor initializer. We move it. + */ + postSuperStats += genStat(tree) - assert(jsSuperCall.isDefined || !jsStat.isInstanceOf[js.VarDef], - "Trying to move a local VarDef after the super constructor call " + - s"of a non-native JS class at ${dd.pos}") + case tree @ OuterPointerNullCheck(outer, assign) if isThisFieldAssignment(assign) => + /* Variant of the above with an outer pointer null check. The actual + * null check remains before the super call, while the associated + * assignment is moved after. + */ + preSuperStats += js.UnaryOp(js.UnaryOp.CheckNotNull, genExpr(outer))(tree.pos) + postSuperStats += genStat(assign) - jsStats += jsStat + case stat => + // Other statements are left before. + preSuperStats += genStat(stat) } } assert(jsSuperCall.isDefined, "Did not find Super call in primary JS " + s"construtor at ${dd.pos}") + /* Insert a StoreModule if required. + * Do this now so we have the pos of the super ctor call. + * +=: prepends to the ListBuffer in O(1) -- yes, it's a cryptic name. + */ + if (isStaticModule(currentClassSym)) + js.StoreModule()(jsSuperCall.get.pos) +=: postSuperStats + new PrimaryJSCtor(sym, genParamsAndInfo(sym, vparamss), - jsSuperCall.get :: jsStats.result()) + js.JSConstructorBody(preSuperStats.result(), jsSuperCall.get, postSuperStats.result())(dd.pos)) } private def genSecondaryJSClassCtor(dd: DefDef): SplitSecondaryJSCtor = { @@ -1656,9 +1803,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) (jsExport, jsClassCaptures) } - /** generates a sequence of JS constructor statements based on a constructor tree. */ - private def genJSClassCtorStats(overloadVar: js.VarRef, - ctorTree: ConstructorTree[PrimaryJSCtor])(implicit pos: Position): js.Tree = { + /** Generates a JS constructor body based on a constructor tree. */ + private def genJSClassCtorBody(overloadVar: js.VarRef, + ctorTree: ConstructorTree[PrimaryJSCtor])(implicit pos: Position): js.JSConstructorBody = { /* generates a statement that conditionally executes body iff the chosen * overload is any of the descendants of `tree` (including itself). @@ -1683,7 +1830,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - js.If(cond, body, js.Skip())(jstpe.NoType) + js.If(cond, body, js.Skip())(jstpe.VoidType) } /* preStats / postStats use pre/post order traversal respectively to @@ -1755,13 +1902,19 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val primaryCtor = ctorTree.ctor val secondaryCtorTrees = ctorTree.subCtors - js.Block( - secondaryCtorTrees.map(preStats(_, primaryCtor.paramsAndInfo)) ++ - primaryCtor.body ++ + wrapJSCtorBody( + secondaryCtorTrees.map(preStats(_, primaryCtor.paramsAndInfo)), + primaryCtor.body, secondaryCtorTrees.map(postStats(_)) ) } + private def wrapJSCtorBody(before: List[js.Tree], body: js.JSConstructorBody, + after: List[js.Tree]): js.JSConstructorBody = { + js.JSConstructorBody(before ::: body.beforeSuper, body.superCall, + body.afterSuper ::: after)(body.pos) + } + private sealed trait JSCtor { val sym: Symbol val paramsAndInfo: List[(js.VarRef, JSParamInfo)] @@ -1769,7 +1922,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) private class PrimaryJSCtor(val sym: Symbol, val paramsAndInfo: List[(js.VarRef, JSParamInfo)], - val body: List[js.Tree]) extends JSCtor + val body: js.JSConstructorBody) extends JSCtor private class SplitSecondaryJSCtor(val sym: Symbol, val paramsAndInfo: List[(js.VarRef, JSParamInfo)], @@ -1796,7 +1949,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * - Abstract methods in non-native JS classes * - Default accessor of a native JS constructor * - Constructors of hijacked classes - * - Methods with the {{{@JavaDefaultMethod}}} annotation mixed in classes. */ def genMethod(dd: DefDef): Option[js.MethodDef] = { val sym = dd.symbol @@ -1858,10 +2010,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) None } else if (sym.isClassConstructor && isHijackedClass(sym.owner)) { None - } else if (scalaUsesImplClasses && !isImplClass(sym.owner) && - !isAbstract && sym.hasAnnotation(JavaDefaultMethodAnnotation)) { - // Do not emit trait impl forwarders with @JavaDefaultMethod - None } else { withNewLocalNameScope { Some(genMethodWithCurrentLocalNameScope(dd)) @@ -1876,16 +2024,15 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * * Constructors are emitted by generating their body as a statement. * - * Interface methods with the {{{@JavaDefaultMethod}}} annotation produce - * default methods forwarding to the trait impl class method. - * * Other (normal) methods are emitted with `genMethodDef()`. */ - def genMethodWithCurrentLocalNameScope(dd: DefDef): js.MethodDef = { + def genMethodWithCurrentLocalNameScope(dd: DefDef, + initThisLocalVarName: Option[LocalName] = None): js.MethodDef = { + implicit val pos = dd.pos val sym = dd.symbol - withPerMethodBodyState(sym) { + withPerMethodBodyState(sym, initThisLocalVarName) { val methodName = encodeMethodSym(sym) val originalName = originalNameOfMethod(sym) @@ -1898,44 +2045,18 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } val jsMethodDef = if (isAbstractMethod(dd)) { - val body = if (scalaUsesImplClasses && - sym.hasAnnotation(JavaDefaultMethodAnnotation)) { - /* For an interface method with @JavaDefaultMethod, make it a - * default method calling the impl class method. - */ - val implClassSym = sym.owner.implClass - val implMethodSym = implClassSym.info.member(sym.name).suchThat { s => - s.isMethod && - s.tpe.params.size == sym.tpe.params.size + 1 && - s.tpe.params.head.tpe =:= sym.owner.toTypeConstructor && - s.tpe.params.tail.zip(sym.tpe.params).forall { - case (sParam, symParam) => - sParam.tpe =:= symParam.tpe - } - } - Some(genApplyStatic(implMethodSym, - js.This()(currentClassType) :: jsParams.map(_.ref))) - } else { - None - } js.MethodDef(js.MemberFlags.empty, methodName, originalName, - jsParams, toIRType(sym.tpe.resultType), body)( - OptimizerHints.empty, None) + jsParams, toIRType(sym.tpe.resultType), None)( + OptimizerHints.empty, Unversioned) } else { - def isTraitImplForwarder = dd.rhs match { - case app: Apply => isImplClass(app.symbol.owner) - case _ => false - } - val shouldMarkInline = { sym.hasAnnotation(InlineAnnotationClass) || - sym.name.startsWith(nme.ANON_FUN_NAME) || + sym.name.containsName(nme.ANON_FUN_NAME) || adHocInlineMethods.contains(sym.fullName) } val shouldMarkNoinline = { sym.hasAnnotation(NoinlineAnnotationClass) && - !isTraitImplForwarder && !ignoreNoinlineAnnotation(sym) } @@ -1949,12 +2070,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val namespace = js.MemberNamespace.Constructor js.MethodDef( js.MemberFlags.empty.withNamespace(namespace), methodName, - originalName, jsParams, jstpe.NoType, Some(genStat(dd.rhs)))( - optimizerHints, None) + originalName, jsParams, jstpe.VoidType, Some(genStat(dd.rhs)))( + optimizerHints, Unversioned) } else { val resultIRType = toIRType(sym.tpe.resultType) val namespace = { - if (sym.isStaticMember) { + if (compileAsStaticMethod(sym)) { if (sym.isPrivate) js.MemberNamespace.PrivateStatic else js.MemberNamespace.PublicStatic } else { @@ -1978,8 +2099,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) methodDef } else { val patches = ( - unmutatedMutableLocalVars.map(encodeLocalSym(_).name -> false) ::: - mutatedImmutableLocalVals.map(encodeLocalSym(_).name -> true) + unmutatedMutableLocalVars.map(encodeLocalSymName(_) -> false) ::: + mutatedImmutableLocalVals.map(encodeLocalSymName(_) -> true) ).toMap patchMutableFlagOfLocals(methodDef, patches) } @@ -2008,18 +2129,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - def isAbstractMethod(dd: DefDef): Boolean = { - /* When scalac uses impl classes, we cannot trust `rhs` to be - * `EmptyTree` for deferred methods (probably due to an internal bug - * of scalac), as can be seen in run/t6443.scala. - * However, when it does not use impl class anymore, we have to use - * `rhs == EmptyTree` as predicate, just like the JVM back-end does. - */ - if (scalaUsesImplClasses) - dd.symbol.isDeferred || dd.symbol.owner.isInterface - else - dd.rhs == EmptyTree - } + def isAbstractMethod(dd: DefDef): Boolean = + dd.rhs == EmptyTree private val adHocInlineMethods = Set( "scala.collection.mutable.ArrayOps$ofRef.newBuilder$extension", @@ -2045,23 +2156,18 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } yield { js.ParamDef(name, originalName, ptpe, newMutable(name.name, mutable))(p.pos) } - val transformer = new ir.Transformers.Transformer { - override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match { + val transformer = new ir.Transformers.LocalScopeTransformer { + override def transform(tree: js.Tree): js.Tree = tree match { case js.VarDef(name, originalName, vtpe, mutable, rhs) => - assert(isStat, s"found a VarDef in expression position at ${tree.pos}") super.transform(js.VarDef(name, originalName, vtpe, - newMutable(name.name, mutable), rhs)(tree.pos), isStat) - case js.Closure(arrow, captureParams, params, restParam, body, captureValues) => - js.Closure(arrow, captureParams, params, restParam, body, - captureValues.map(transformExpr))(tree.pos) + newMutable(name.name, mutable), rhs)(tree.pos)) case _ => - super.transform(tree, isStat) + super.transform(tree) } } - val newBody = body.map( - b => transformer.transform(b, isStat = resultType == jstpe.NoType)) + val newBody = transformer.transformTreeOpt(body) js.MethodDef(flags, methodName, originalName, newParams, resultType, - newBody)(methodDef.optimizerHints, None)(methodDef.pos) + newBody)(methodDef.optimizerHints, Unversioned)(methodDef.pos) } /** Patches the type of selected param defs in a [[js.MethodDef]]. @@ -2073,31 +2179,27 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) private def patchTypeOfParamDefs(methodDef: js.MethodDef, patches: Map[LocalName, jstpe.Type]): js.MethodDef = { - def newType(name: js.LocalIdent, oldType: jstpe.Type): jstpe.Type = - patches.getOrElse(name.name, oldType) + def newType(name: LocalName, oldType: jstpe.Type): jstpe.Type = + patches.getOrElse(name, oldType) val js.MethodDef(flags, methodName, originalName, params, resultType, body) = methodDef val newParams = for { p @ js.ParamDef(name, originalName, ptpe, mutable) <- params } yield { - js.ParamDef(name, originalName, newType(name, ptpe), mutable)(p.pos) + js.ParamDef(name, originalName, newType(name.name, ptpe), mutable)(p.pos) } - val transformer = new ir.Transformers.Transformer { - override def transform(tree: js.Tree, isStat: Boolean): js.Tree = tree match { + val transformer = new ir.Transformers.LocalScopeTransformer { + override def transform(tree: js.Tree): js.Tree = tree match { case tree @ js.VarRef(name) => js.VarRef(name)(newType(name, tree.tpe))(tree.pos) - case js.Closure(arrow, captureParams, params, restParam, body, captureValues) => - js.Closure(arrow, captureParams, params, restParam, body, - captureValues.map(transformExpr))(tree.pos) case _ => - super.transform(tree, isStat) + super.transform(tree) } } - val newBody = body.map( - b => transformer.transform(b, isStat = resultType == jstpe.NoType)) + val newBody = transformer.transformTreeOpt(body) js.MethodDef(flags, methodName, originalName, newParams, resultType, - newBody)(methodDef.optimizerHints, None)(methodDef.pos) + newBody)(methodDef.optimizerHints, Unversioned)(methodDef.pos) } /** Generates the JSNativeMemberDef of a JS native method. */ @@ -2127,7 +2229,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) optimizerHints: OptimizerHints): js.MethodDef = { implicit val pos = tree.pos - val bodyIsStat = resultIRType == jstpe.NoType + val bodyIsStat = resultIRType == jstpe.VoidType def genBodyWithinReturnableScope(): js.Tree = tree match { case Block( @@ -2135,72 +2237,52 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) rhs) => // This method has tail jumps - // To be called from within withScopedVars - def genInnerBody() = { - js.Block(otherStats.map(genStat) :+ ( - if (bodyIsStat) genStat(rhs) - else genExpr(rhs))) - } - - initialThis match { - case Ident(_) => - /* This case happens in trait implementation classes, until - * Scala 2.11. In the method, all usages of `this` have been - * replaced by the method's formal parameter `$this`. However, - * there is still a declaration of the pseudo local variable - * `_$this`, which is used in the param list of the label. We - * need to remember it now, so that when we build the JS version - * of the formal params for the label, we can redirect the - * assignment to `$this` instead of the otherwise unused - * `_$this`. - */ - withScopedVars( - fakeTailJumpParamRepl := (thisDef.symbol, initialThis.symbol) - ) { - genInnerBody() - } - - case _ => - val thisSym = thisDef.symbol - if (thisSym.isMutable) - mutableLocalVars += thisSym - - val thisLocalIdent = encodeLocalSym(thisSym) - val thisLocalType = currentClassType - - val genRhs = { - /* #3267 In default methods, scalac will type its _$this - * pseudo-variable as the *self-type* of the enclosing class, - * instead of the enclosing class type itself. However, it then - * considers *usages* of _$this as if its type were the - * enclosing class type. The latter makes sense, since it is - * compiled as `this` in the bytecode, which necessarily needs - * to be the enclosing class type. Only the declared type of - * _$this is wrong. - * - * In our case, since we generate an actual local variable for - * _$this, we must make sure to type it correctly, as the - * enclosing class type. However, this means the rhs of the - * ValDef does not match, which is why we have to adapt it - * here. - */ - forceAdapt(genExpr(initialThis), thisLocalType) - } + val thisSym = thisDef.symbol + if (thisSym.isMutable) + mutableLocalVars += thisSym - val thisLocalVarDef = js.VarDef(thisLocalIdent, thisOriginalName, - thisLocalType, thisSym.isMutable, genRhs) + /* The `thisLocalIdent` must be nullable. Even though we initially + * assign it to `this`, which is non-nullable, tail-recursive calls + * may reassign it to a different value, which in general will be + * nullable. + */ + val thisLocalIdent = encodeLocalSym(thisSym) + val thisLocalType = currentThisTypeNullable + + val genRhs = { + /* #3267 In default methods, scalac will type its _$this + * pseudo-variable as the *self-type* of the enclosing class, + * instead of the enclosing class type itself. However, it then + * considers *usages* of _$this as if its type were the + * enclosing class type. The latter makes sense, since it is + * compiled as `this` in the bytecode, which necessarily needs + * to be the enclosing class type. Only the declared type of + * _$this is wrong. + * + * In our case, since we generate an actual local variable for + * _$this, we must make sure to type it correctly, as the + * enclosing class type. However, this means the rhs of the + * ValDef does not match, which is why we have to adapt it + * here. + */ + forceAdapt(genExpr(initialThis), thisLocalType) + } - val innerBody = { - withScopedVars( - thisLocalVarIdent := Some(thisLocalIdent) - ) { - genInnerBody() - } - } + val thisLocalVarDef = js.VarDef(thisLocalIdent, thisOriginalName, + thisLocalType, thisSym.isMutable, genRhs) - js.Block(thisLocalVarDef, innerBody) + val innerBody = { + withScopedVars( + thisLocalVarName := Some(thisLocalIdent.name) + ) { + js.Block(otherStats.map(genStat) :+ ( + if (bodyIsStat) genStat(rhs) + else genExpr(rhs))) + } } + js.Block(thisLocalVarDef, innerBody) + case _ => if (bodyIsStat) genStat(tree) else genExpr(tree) @@ -2216,12 +2298,47 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) isJSFunctionDef(currentClassSym)) { val flags = js.MemberFlags.empty.withNamespace(namespace) val body = { - if (isImplClass(currentClassSym)) { - val thisParam = jsParams.head - withScopedVars( - thisLocalVarIdent := Some(thisParam.name) - ) { - genBody() + def genAsUnaryOp(op: js.UnaryOp.Code): js.Tree = + js.UnaryOp(op, genThis()) + def genAsBinaryOp(op: js.BinaryOp.Code): js.Tree = + js.BinaryOp(op, genThis(), jsParams.head.ref) + def genAsBinaryOpRhsNotNull(op: js.BinaryOp.Code): js.Tree = + js.BinaryOp(op, genThis(), js.UnaryOp(js.UnaryOp.CheckNotNull, jsParams.head.ref)) + + if (currentClassSym.get == HackedStringClass) { + /* Hijack the bodies of String.length and String.charAt and replace + * them with String_length and String_charAt operations, respectively. + */ + methodName.name match { + case `lengthMethodName` => genAsUnaryOp(js.UnaryOp.String_length) + case `charAtMethodName` => genAsBinaryOp(js.BinaryOp.String_charAt) + case _ => genBody() + } + } else if (currentClassSym.get == ClassClass) { + // Similar, for the Class_x operations + methodName.name match { + case `getNameMethodName` => genAsUnaryOp(js.UnaryOp.Class_name) + case `isPrimitiveMethodName` => genAsUnaryOp(js.UnaryOp.Class_isPrimitive) + case `isInterfaceMethodName` => genAsUnaryOp(js.UnaryOp.Class_isInterface) + case `isArrayMethodName` => genAsUnaryOp(js.UnaryOp.Class_isArray) + case `getComponentTypeMethodName` => genAsUnaryOp(js.UnaryOp.Class_componentType) + case `getSuperclassMethodName` => genAsUnaryOp(js.UnaryOp.Class_superClass) + + case `isInstanceMethodName` => genAsBinaryOp(js.BinaryOp.Class_isInstance) + case `isAssignableFromMethodName` => genAsBinaryOpRhsNotNull(js.BinaryOp.Class_isAssignableFrom) + case `castMethodName` => genAsBinaryOp(js.BinaryOp.Class_cast) + + case _ => genBody() + } + } else if (currentClassSym.get == JavaLangReflectArrayModClass) { + methodName.name match { + case `arrayNewInstanceMethodName` => + val List(jlClassParam, lengthParam) = jsParams + js.BinaryOp(js.BinaryOp.Class_newArray, + js.UnaryOp(js.UnaryOp.CheckNotNull, jlClassParam.ref), + lengthParam.ref) + case _ => + genBody() } } else { genBody() @@ -2229,13 +2346,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } js.MethodDef(flags, methodName, originalName, jsParams, resultIRType, Some(body))( - optimizerHints, None) + optimizerHints, Unversioned) } else { assert(!namespace.isStatic, tree.pos) val thisLocalIdent = freshLocalIdent("this") withScopedVars( - thisLocalVarIdent := Some(thisLocalIdent) + thisLocalVarName := Some(thisLocalIdent.name) ) { val staticNamespace = if (namespace.isPrivate) js.MemberNamespace.PrivateStatic @@ -2247,7 +2364,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.MethodDef(flags, methodName, originalName, thisParamDef :: jsParams, resultIRType, Some(genBody()))( - optimizerHints, None) + optimizerHints, Unversioned) } } } @@ -2257,7 +2374,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * @param tree * The tree to adapt. * @param tpe - * The target type, which must be either `AnyType` or `ClassType(_)`. + * The target type, which must be either `AnyType` or `ClassType`. */ private def forceAdapt(tree: js.Tree, tpe: jstpe.Type): js.Tree = { if (tree.tpe == tpe || tpe == jstpe.AnyType) { @@ -2292,7 +2409,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) tree match { case js.Block(stats :+ expr) => js.Block(stats :+ exprToStat(expr)) - case _:js.Literal | _:js.This | _:js.VarRef => + case _:js.Literal | _:js.VarRef => js.Skip() case _ => tree @@ -2303,8 +2420,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ def genExpr(tree: Tree): js.Tree = { val result = genStatOrExpr(tree, isStat = false) - assert(result.tpe != jstpe.NoType, - s"genExpr($tree) returned a tree with type NoType at pos ${tree.pos}") + assert(result.tpe != jstpe.VoidType, + s"genExpr($tree) returned a tree with type VoidType at pos ${tree.pos}") result } @@ -2378,34 +2495,66 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) toIRType(sym.tpe), sym.isMutable, rhsTree) } - case If(cond, thenp, elsep) => - val tpe = - if (isStat) jstpe.NoType - else toIRType(tree.tpe) + case tree @ If(cond, thenp, elsep) => + def default: js.Tree = { + val tpe = + if (isStat) jstpe.VoidType + else toIRType(tree.tpe) + + js.If(genExpr(cond), genStatOrExpr(thenp, isStat), + genStatOrExpr(elsep, isStat))(tpe) + } - js.If(genExpr(cond), genStatOrExpr(thenp, isStat), - genStatOrExpr(elsep, isStat))(tpe) + if (isStat && currentMethodSym.isClassConstructor) { + /* Nested classes that need an outer pointer have a weird shape for + * assigning it, with an explicit null check. It looks like this: + * + * if ($outer.eq(null)) + * throw null + * else + * this.$outer = $outer + * + * This is a bad shape for our optimizations, notably because the + * explicit null check cannot be considered UB at the IR level if + * we compile it as is, although in the original *language*, that + * would clearly fall into UB. + * + * Therefore, we intercept that shape and rewrite it as follows + * instead: + * + * ($outer) // null check subject to UB + * this.$outer = $outer // the `else` branch in general + */ + tree match { + case OuterPointerNullCheck(outer, elsep) => + js.Block( + js.UnaryOp(js.UnaryOp.CheckNotNull, genExpr(outer)), + genStat(elsep) + ) + case _ => + default + } + } else { + default + } case Return(expr) => - js.Return(toIRType(expr.tpe) match { - case jstpe.NoType => js.Block(genStat(expr), js.Undefined()) - case _ => genExpr(expr) - }, getEnclosingReturnLabel()) + js.Return( + genStatOrExpr(expr, isStat = toIRType(expr.tpe) == jstpe.VoidType), + getEnclosingReturnLabel()) case t: Try => genTry(t, isStat) case Throw(expr) => val ex = genExpr(expr) - js.Throw { - if (isMaybeJavaScriptException(expr.tpe)) { - genApplyMethod( - genLoadModule(RuntimePackageModule), - Runtime_unwrapJavaScriptException, - List(ex)) - } else { - ex - } + ex match { + case js.New(cls, _, _) if cls != JavaScriptExceptionClassName => + // Common case where ex is neither null nor a js.JavaScriptException + js.UnaryOp(js.UnaryOp.Throw, ex) + case _ => + js.UnaryOp(js.UnaryOp.Throw, + js.UnaryOp(js.UnaryOp.UnwrapFromThrowable, js.UnaryOp(js.UnaryOp.CheckNotNull, ex))) } /* !!! Copy-pasted from `CleanUp.scala` upstream and simplified with @@ -2615,7 +2764,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case _ => mutatedLocalVars += sym js.Assign( - js.VarRef(encodeLocalSym(sym))(toIRType(sym.tpe)), + js.VarRef(encodeLocalSymName(sym))(toIRType(sym.tpe)), genRhs) } @@ -2627,7 +2776,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case mtch: Match => genMatch(mtch, isStat) - /** Anonymous function (in 2.12, or with -Ydelambdafy:method in 2.11) */ + /** Anonymous function */ case fun: Function => genAnonFunction(fun) @@ -2640,27 +2789,40 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } // end of GenJSCode.genExpr() + /** Extractor for the shape of outer pointer null check. + * + * See the comment in `case If(...) =>` of `genExpr`. + * + * When successful, returns the pair `(outer, elsep)` where `outer` is the + * `Ident` of the outer constructor parameter, and `elsep` is the else + * branch of the condition. + */ + private object OuterPointerNullCheck { + def unapply(tree: If): Option[(Ident, Tree)] = tree match { + case If(Apply(fun @ Select(outer: Ident, nme.eq), Literal(Constant(null)) :: Nil), + Throw(Literal(Constant(null))), elsep) + if outer.symbol.isOuterParam && fun.symbol == definitions.Object_eq => + Some((outer, elsep)) + case _ => + None + } + } + /** Gen JS this of the current class. * Normally encoded straightforwardly as a JS this. * But must be replaced by the tail-jump-this local variable if there * is one. */ private def genThis()(implicit pos: Position): js.Tree = { - thisLocalVarIdent.fold[js.Tree] { - if (tryingToGenMethodAsJSFunction) { - throw new CancelGenMethodAsJSFunction( - "Trying to generate `this` inside the body") + thisLocalVarName.fold[js.Tree] { + if (isJSFunctionDef(currentClassSym)) { + abort( + "Unexpected `this` reference inside the body of a JS function class: " + + currentClassSym.fullName) } - js.This()(currentClassType) - } { thisLocalIdent => - // .copy() to get the correct position - val tpe = { - if (isImplClass(currentClassSym)) - encodeClassType(traitOfImplClass(currentClassSym)) - else - currentClassType - } - js.VarRef(thisLocalIdent.copy())(tpe) + js.This()(currentThisType) + } { thisLocalName => + js.VarRef(thisLocalName)(currentThisTypeNullable) } } @@ -2694,7 +2856,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * are transformed into * {{{ * ...labelParams = ...args; - * return@labelName (void 0) + * return@labelName; * }}} * * This is always correct, so it can handle arbitrary labels and jumps @@ -2707,13 +2869,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) implicit val pos = tree.pos val sym = tree.symbol - val labelParamSyms = tree.params.map(_.symbol).map { s => - if (s == fakeTailJumpParamRepl._1) fakeTailJumpParamRepl._2 else s - } + val labelParamSyms = tree.params.map(_.symbol) val info = new EnclosingLabelDefInfoWithResultAsAssigns(labelParamSyms) - val labelIdent = encodeLabelSym(sym) - val labelName = labelIdent.name + val labelName = encodeLabelSym(sym) val transformedRhs = withScopedVars( enclosingLabelDefInfos := enclosingLabelDefInfos.get + (sym -> info) @@ -2728,7 +2887,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ object ReturnFromThisLabel { def unapply(tree: js.Return): Option[js.Tree] = { - if (tree.label.name == labelName) Some(exprToStat(tree.expr)) + if (tree.label == labelName) Some(exprToStat(tree.expr)) else None } } @@ -2737,7 +2896,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (transformedRhs.tpe == jstpe.NothingType) { // In this case, we do not need the outer block label js.While(js.BooleanLiteral(true), { - js.Labeled(labelIdent, jstpe.NoType, { + js.Labeled(labelName, jstpe.VoidType, { transformedRhs match { // Eliminate a trailing return@lab case js.Block(stats :+ ReturnFromThisLabel(exprAsStat)) => @@ -2749,17 +2908,17 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) }) } else { // When all else has failed, we need the full machinery - val blockLabelIdent = freshLabelIdent("block") + val blockLabelName = freshLabelName("block") val bodyType = - if (isStat) jstpe.NoType + if (isStat) jstpe.VoidType else toIRType(tree.tpe) - js.Labeled(blockLabelIdent, bodyType, { + js.Labeled(blockLabelName, bodyType, { js.While(js.BooleanLiteral(true), { - js.Labeled(labelIdent, jstpe.NoType, { + js.Labeled(labelName, jstpe.VoidType, { if (isStat) - js.Block(transformedRhs, js.Return(js.Undefined(), blockLabelIdent)) + js.Block(transformedRhs, js.Return(js.Skip(), blockLabelName)) else - js.Return(transformedRhs, blockLabelIdent) + js.Return(transformedRhs, blockLabelName) }) }) }) @@ -2871,7 +3030,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val blockAST = genStatOrExpr(block, isStat) val resultType = - if (isStat) jstpe.NoType + if (isStat) jstpe.VoidType else toIRType(tree.tpe) val handled = @@ -2884,11 +3043,38 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - private def genTryCatch(body: js.Tree, catches: List[CaseDef], - resultType: jstpe.Type, - isStat: Boolean)(implicit pos: Position): js.Tree = { + private def genTryCatch(body: js.Tree, catches: List[CaseDef], + resultType: jstpe.Type, isStat: Boolean)( + implicit pos: Position): js.Tree = { + + catches match { + case CaseDef(Ident(nme.WILDCARD), _, catchAllBody) :: Nil => + genTryCatchCatchIgnoreAll(body, catchAllBody, resultType, isStat) + + case CaseDef(Typed(Ident(nme.WILDCARD), tpt), _, catchAllBody) :: Nil + if tpt.tpe.typeSymbol == ThrowableClass => + genTryCatchCatchIgnoreAll(body, catchAllBody, resultType, isStat) + + case _ => + genTryCatchNotIgnoreAll(body, catches, resultType, isStat) + } + } + + private def genTryCatchCatchIgnoreAll(body: js.Tree, catchAllBody: Tree, + resultType: jstpe.Type, isStat: Boolean)( + implicit pos: Position): js.Tree = { + + js.TryCatch(body, freshLocalIdent("e"), NoOriginalName, + genStatOrExpr(catchAllBody, isStat))( + resultType) + } + + private def genTryCatchNotIgnoreAll(body: js.Tree, catches: List[CaseDef], + resultType: jstpe.Type, isStat: Boolean)( + implicit pos: Position): js.Tree = { + val exceptIdent = freshLocalIdent("e") - val origExceptVar = js.VarRef(exceptIdent)(jstpe.AnyType) + val origExceptVar = js.VarRef(exceptIdent.name)(jstpe.AnyType) val mightCatchJavaScriptException = catches.exists { caseDef => caseDef.pat match { @@ -2903,18 +3089,14 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val (exceptValDef, exceptVar) = if (mightCatchJavaScriptException) { val valDef = js.VarDef(freshLocalIdent("e"), NoOriginalName, - encodeClassType(ThrowableClass), mutable = false, { - genApplyMethod( - genLoadModule(RuntimePackageModule), - Runtime_wrapJavaScriptException, - List(origExceptVar)) - }) + encodeClassType(ThrowableClass), mutable = false, + js.UnaryOp(js.UnaryOp.WrapAsThrowable, origExceptVar)) (valDef, valDef.ref) } else { (js.Skip(), origExceptVar) } - val elseHandler: js.Tree = js.Throw(origExceptVar) + val elseHandler: js.Tree = js.UnaryOp(js.UnaryOp.Throw, origExceptVar) val handler = catches.foldRight(elseHandler) { (caseDef, elsep) => implicit val pos = caseDef.pos @@ -3081,7 +3263,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val r = toIRType(to) def isValueType(tpe: jstpe.Type): Boolean = tpe match { - case jstpe.NoType | jstpe.BooleanType | jstpe.CharType | + case jstpe.VoidType | jstpe.BooleanType | jstpe.CharType | jstpe.ByteType | jstpe.ShortType | jstpe.IntType | jstpe.LongType | jstpe.FloatType | jstpe.DoubleType => true @@ -3123,7 +3305,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) private def genThrowClassCastException()(implicit pos: Position): js.Tree = { val ctor = ClassCastExceptionClass.info.member( nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty) - js.Throw(genNew(ClassCastExceptionClass, ctor, Nil)) + js.UnaryOp(js.UnaryOp.Throw, genNew(ClassCastExceptionClass, ctor, Nil)) } /** Gen JS code for a super call, of the form Class.super[mix].fun(args). @@ -3161,10 +3343,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (isStaticModule(currentClassSym) && !isModuleInitialized.value && currentMethodSym.isClassConstructor) { isModuleInitialized.value = true - val className = encodeClassName(currentClassSym) - val initModule = - js.StoreModule(className, js.This()(jstpe.ClassType(className))) - js.Block(superCall, initModule) + js.Block(superCall, js.StoreModule()) } else { superCall } @@ -3172,10 +3351,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } /** Gen JS code for a constructor call (new). + * * Further refined into: - * * new String(...) * * new of a hijacked boxed class - * * new of an anonymous function class that was recorded as JS function + * * new of a JS function class * * new of a JS class * * new Array * * regular new @@ -3195,13 +3374,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } else if (isJSFunctionDef(clsSym)) { val classDef = consumeLazilyGeneratedAnonClass(clsSym) genJSFunction(classDef, args.map(genExpr)) - } else if (clsSym.isAnonymousFunction) { - val classDef = consumeLazilyGeneratedAnonClass(clsSym) - tryGenAnonFunctionClass(classDef, args.map(genExpr)).getOrElse { - // Cannot optimize anonymous function class. Generate full class. - generatedClasses += nestedGenerateClass(clsSym)(genClass(classDef)) - genNew(clsSym, ctor, genActualArgs(ctor, args)) - } } else if (isJSType(clsSym)) { genPrimitiveJSNew(tree) } else { @@ -3212,6 +3384,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) genNewArray(arr, args.map(genExpr)) case prim: jstpe.PrimRef => abort(s"unexpected primitive type $prim in New at $pos") + case typeRef: jstpe.TransientTypeRef => + abort(s"unexpected special type ref $typeRef in New at $pos") } } } @@ -3249,7 +3423,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val paramSyms = info.paramSyms assertArgCountMatches(paramSyms.size) - val jump = js.Return(js.Undefined(), labelIdent) + val jump = js.Return(js.Skip(), labelIdent) if (args.isEmpty) { // fast path, applicable notably to loops and case labels @@ -3289,7 +3463,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) List.newBuilder[(js.VarRef, jstpe.Type, js.LocalIdent, js.Tree)] for ((formalArgSym, arg) <- targetSyms.zip(values)) { - val formalArg = encodeLocalSym(formalArgSym) + val formalArgName = encodeLocalSymName(formalArgSym) val actualArg = genExpr(arg) /* #3267 The formal argument representing the special `this` of a @@ -3306,7 +3480,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val isTailJumpThisLocalVar = formalArgSym.name == nme.THIS val tpe = - if (isTailJumpThisLocalVar) currentClassType + if (isTailJumpThisLocalVar) currentThisTypeNullable else toIRType(formalArgSym.tpe) val fixedActualArg = @@ -3314,13 +3488,13 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) else actualArg actualArg match { - case js.VarRef(`formalArg`) => + case js.VarRef(`formalArgName`) => // This is trivial assignment, we don't need it case _ => mutatedLocalVars += formalArgSym - quadruplets += ((js.VarRef(formalArg)(tpe), tpe, - freshLocalIdent(formalArg.name.withPrefix("temp$")), + quadruplets += ((js.VarRef(formalArgName)(tpe), tpe, + freshLocalIdent(formalArgName.withPrefix("temp$")), fixedActualArg)) } } @@ -3341,7 +3515,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) yield js.VarDef(tempArg, NoOriginalName, argType, mutable = false, actualArg) val trueAssignments = for ((formalArg, argType, tempArg, _) <- quadruplets) - yield js.Assign(formalArg, js.VarRef(tempArg)(argType)) + yield js.Assign(formalArg, js.VarRef(tempArg.name)(argType)) tempAssignments ::: trueAssignments } } @@ -3360,6 +3534,15 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val Apply(fun @ Select(receiver, _), args) = tree val sym = fun.symbol + val inline = { + tree.hasAttachment[InlineCallsiteAttachment.type] || + fun.hasAttachment[InlineCallsiteAttachment.type] // nullary methods + } + val noinline = { + tree.hasAttachment[NoInlineCallsiteAttachment.type] || + fun.hasAttachment[NoInlineCallsiteAttachment.type] // nullary methods + } + if (isJSType(receiver.tpe) && sym.owner != ObjectClass) { if (!isNonNativeJSClass(sym.owner) || isExposed(sym)) genPrimitiveJSCall(tree, isStat) @@ -3367,64 +3550,78 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) genApplyJSClassMethod(genExpr(receiver), sym, genActualArgs(sym, args)) } else if (sym.hasAnnotation(JSNativeAnnotation)) { genJSNativeMemberCall(tree, isStat) - } else if (sym.isStaticMember) { - if (sym.isMixinConstructor && isJSImplClass(sym.owner)) { + } else if (compileAsStaticMethod(sym)) { + if (sym.isMixinConstructor) { /* Do not emit a call to the $init$ method of JS traits. * This exception is necessary because optional JS fields cause the * creation of a $init$ method, which we must not call. */ js.Skip() } else { - genApplyStatic(sym, args.map(genExpr)) + genApplyStatic(sym, args.map(genExpr), inline = inline, noinline = noinline) } } else { genApplyMethodMaybeStatically(genExpr(receiver), sym, - genActualArgs(sym, args)) + genActualArgs(sym, args), inline = inline, noinline = noinline) } } def genApplyMethodMaybeStatically(receiver: js.Tree, - method: Symbol, arguments: List[js.Tree])( + method: Symbol, arguments: List[js.Tree], + inline: Boolean = false, noinline: Boolean = false)( implicit pos: Position): js.Tree = { if (method.isPrivate || method.isClassConstructor) - genApplyMethodStatically(receiver, method, arguments) + genApplyMethodStatically(receiver, method, arguments, inline = inline, noinline = noinline) else - genApplyMethod(receiver, method, arguments) + genApplyMethod(receiver, method, arguments, inline = inline, noinline = noinline) } /** Gen JS code for a call to a Scala method. */ def genApplyMethod(receiver: js.Tree, - method: Symbol, arguments: List[js.Tree])( + method: Symbol, arguments: List[js.Tree], + inline: Boolean = false, noinline: Boolean = false)( implicit pos: Position): js.Tree = { assert(!method.isPrivate, s"Cannot generate a dynamic call to private method $method at $pos") - js.Apply(js.ApplyFlags.empty, receiver, encodeMethodSym(method), arguments)( + val flags = js.ApplyFlags.empty + .withInline(inline) + .withNoinline(noinline) + + js.Apply(flags, receiver, encodeMethodSym(method), arguments)( toIRType(method.tpe.resultType)) } def genApplyMethodStatically(receiver: js.Tree, method: Symbol, - arguments: List[js.Tree])(implicit pos: Position): js.Tree = { + arguments: List[js.Tree], inline: Boolean = false, noinline: Boolean = false)( + implicit pos: Position): js.Tree = { val flags = js.ApplyFlags.empty .withPrivate(method.isPrivate && !method.isClassConstructor) .withConstructor(method.isClassConstructor) + .withInline(inline) + .withNoinline(noinline) val methodIdent = encodeMethodSym(method) val resultType = - if (method.isClassConstructor) jstpe.NoType + if (method.isClassConstructor) jstpe.VoidType else toIRType(method.tpe.resultType) js.ApplyStatically(flags, receiver, encodeClassName(method.owner), methodIdent, arguments)(resultType) } def genApplyJSClassMethod(receiver: js.Tree, method: Symbol, - arguments: List[js.Tree])(implicit pos: Position): js.Tree = { - genApplyStatic(method, receiver :: arguments) + arguments: List[js.Tree], inline: Boolean = false)( + implicit pos: Position): js.Tree = { + genApplyStatic(method, receiver :: arguments, inline = inline) } - def genApplyStatic(method: Symbol, arguments: List[js.Tree])( + def genApplyStatic(method: Symbol, arguments: List[js.Tree], + inline: Boolean = false, noinline: Boolean = false)( implicit pos: Position): js.Tree = { - js.ApplyStatic(js.ApplyFlags.empty.withPrivate(method.isPrivate), - encodeClassName(method.owner), encodeMethodSym(method), arguments)( - toIRType(method.tpe.resultType)) + val flags = js.ApplyFlags.empty + .withPrivate(method.isPrivate) + .withInline(inline) + .withNoinline(noinline) + js.ApplyStatic(flags, encodeClassName(method.owner), + encodeMethodSym(method), arguments)(toIRType(method.tpe.resultType)) } private def adaptPrimitive(value: js.Tree, to: jstpe.Type)( @@ -3511,7 +3708,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // The Scala type system prevents x.isInstanceOf[Null] and ...[Nothing] assert(sym != NullClass && sym != NothingClass, s"Found a .isInstanceOf[$sym] at $pos") - js.IsInstanceOf(value, toIRType(to)) + js.IsInstanceOf(value, toIRType(to).toNonNullable) } } @@ -3572,7 +3769,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val newMethodIdent = js.MethodIdent(newName) js.ApplyStatic(flags, className, newMethodIdent, args)( - jstpe.ClassType(className)) + jstpe.ClassType(className, nullable = true)) } /** Gen JS code for creating a new Array: new Array[T](length) @@ -3582,12 +3779,11 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ def genNewArray(arrayTypeRef: jstpe.ArrayTypeRef, arguments: List[js.Tree])( implicit pos: Position): js.Tree = { - assert(arguments.length <= arrayTypeRef.dimensions, - "too many arguments for array constructor: found " + arguments.length + - " but array has only " + arrayTypeRef.dimensions + - " dimension(s)") + assert(arguments.size == 1, + "expected exactly 1 argument for array constructor: found " + + s"${arguments.length} at $pos") - js.NewArray(arrayTypeRef, arguments) + js.NewArray(arrayTypeRef, arguments.head) } /** Gen JS code for an array literal. */ @@ -3666,101 +3862,63 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * true `int`s can reach the back-end, as asserted by the String-switch * transformation in `cleanup`. Therefore, we do not adapt, preserving * the `string`s and `null`s that come out of the pattern matching in - * Scala 2.13.2+. + * Scala 2.13.x. */ val genSelector = genExpr(selector) val resultType = - if (isStat) jstpe.NoType + if (isStat) jstpe.VoidType else toIRType(tree.tpe) - val defaultLabelSym = cases.collectFirst { + val optDefaultLabelSymAndInfo = cases.collectFirst { case CaseDef(Ident(nme.WILDCARD), EmptyTree, body @ LabelDef(_, Nil, rhs)) if hasSynthCaseSymbol(body) => - body.symbol - }.getOrElse(NoSymbol) + body.symbol -> new EnclosingLabelDefInfoWithResultAsAssigns(Nil) + } var clauses: List[(List[js.MatchableLiteral], js.Tree)] = Nil var optElseClause: Option[js.Tree] = None - var optElseClauseLabel: Option[js.LabelIdent] = None - - def genJumpToElseClause(implicit pos: ir.Position): js.Tree = { - if (optElseClauseLabel.isEmpty) - optElseClauseLabel = Some(freshLabelIdent("default")) - js.Return(js.Undefined(), optElseClauseLabel.get) - } - - for (caze @ CaseDef(pat, guard, body) <- cases) { - assert(guard == EmptyTree, s"found a case guard at ${caze.pos}") - - def genBody(body: Tree): js.Tree = body match { - case app @ Apply(_, Nil) if app.symbol == defaultLabelSym => - genJumpToElseClause - case Block(List(app @ Apply(_, Nil)), _) if app.symbol == defaultLabelSym => - genJumpToElseClause - - case If(cond, thenp, elsep) => - js.If(genExpr(cond), genBody(thenp), genBody(elsep))( - resultType)(body.pos) - - /* For #1955. If we receive a tree with the shape - * if (cond) { - * thenp - * } else { - * elsep - * } - * scala.runtime.BoxedUnit.UNIT - * we rewrite it as - * if (cond) { - * thenp - * scala.runtime.BoxedUnit.UNIT - * } else { - * elsep - * scala.runtime.BoxedUnit.UNIT - * } - * so that it fits the shape of if/elses we can deal with. - */ - case Block(List(If(cond, thenp, elsep)), s: Select) - if s.symbol == definitions.BoxedUnit_UNIT => - val newThenp = Block(thenp, s).setType(s.tpe).setPos(thenp.pos) - val newElsep = Block(elsep, s).setType(s.tpe).setPos(elsep.pos) - js.If(genExpr(cond), genBody(newThenp), genBody(newElsep))( - resultType)(body.pos) - case _ => + withScopedVars( + enclosingLabelDefInfos := enclosingLabelDefInfos.get ++ optDefaultLabelSymAndInfo.toList + ) { + for (caze @ CaseDef(pat, guard, body) <- cases) { + assert(guard == EmptyTree, s"found a case guard at ${caze.pos}") + + def genBody(body: Tree): js.Tree = genStatOrExpr(body, isStat) - } - def invalidCase(tree: Tree): Nothing = - abort(s"Invalid case in alternative in switch-like pattern match: $tree at: ${tree.pos}") + def invalidCase(tree: Tree): Nothing = + abort(s"Invalid case in alternative in switch-like pattern match: $tree at: ${tree.pos}") - def genMatchableLiteral(tree: Literal): js.MatchableLiteral = { - genExpr(tree) match { - case matchableLiteral: js.MatchableLiteral => matchableLiteral - case otherExpr => invalidCase(tree) + def genMatchableLiteral(tree: Literal): js.MatchableLiteral = { + genExpr(tree) match { + case matchableLiteral: js.MatchableLiteral => matchableLiteral + case otherExpr => invalidCase(tree) + } } - } - pat match { - case lit: Literal => - clauses = (List(genMatchableLiteral(lit)), genBody(body)) :: clauses - case Ident(nme.WILDCARD) => - optElseClause = Some(body match { - case LabelDef(_, Nil, rhs) if hasSynthCaseSymbol(body) => - genBody(rhs) - case _ => - genBody(body) - }) - case Alternative(alts) => - val genAlts = { - alts map { - case lit: Literal => genMatchableLiteral(lit) - case _ => invalidCase(tree) + pat match { + case lit: Literal => + clauses = (List(genMatchableLiteral(lit)), genBody(body)) :: clauses + case Ident(nme.WILDCARD) => + optElseClause = Some(body match { + case LabelDef(_, Nil, rhs) if hasSynthCaseSymbol(body) => + genBody(rhs) + case _ => + genBody(body) + }) + case Alternative(alts) => + val genAlts = { + alts map { + case lit: Literal => genMatchableLiteral(lit) + case _ => invalidCase(tree) + } } - } - clauses = (genAlts, genBody(body)) :: clauses - case _ => - invalidCase(tree) + clauses = (genAlts, genBody(body)) :: clauses + case _ => + invalidCase(tree) + } } } @@ -3803,23 +3961,23 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - optElseClauseLabel.fold[js.Tree] { - buildMatch(clauses.reverse, elseClause, resultType) - } { elseClauseLabel => - val matchResultLabel = freshLabelIdent("matchResult") - val patchedClauses = for ((alts, body) <- clauses) yield { - implicit val pos = body.pos - val newBody = - if (isStat) js.Block(body, js.Return(js.Undefined(), matchResultLabel)) - else js.Return(body, matchResultLabel) - (alts, newBody) - } - js.Labeled(matchResultLabel, resultType, js.Block(List( - js.Labeled(elseClauseLabel, jstpe.NoType, { - buildMatch(patchedClauses.reverse, js.Skip(), jstpe.NoType) + optDefaultLabelSymAndInfo match { + case Some((defaultLabelSym, defaultLabelInfo)) if defaultLabelInfo.generatedReturns > 0 => + val matchResultLabel = freshLabelName("matchResult") + val patchedClauses = for ((alts, body) <- clauses) yield { + implicit val pos = body.pos + val newBody = js.Return(body, matchResultLabel) + (alts, newBody) + } + js.Labeled(matchResultLabel, resultType, js.Block(List( + js.Labeled(encodeLabelSym(defaultLabelSym), jstpe.VoidType, { + buildMatch(patchedClauses.reverse, js.Skip(), jstpe.VoidType) }), elseClause - ))) + ))) + + case _ => + buildMatch(clauses.reverse, elseClause, resultType) } } @@ -3857,8 +4015,14 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val Block(stats, expr) = tree val genStatsAndExpr = if (!stats.exists(isCaseLabelDef(_))) { - // fast path - stats.map(genStat(_)) :+ genStatOrExpr(expr, isStat) + // #4684 Collapse { ; BoxedUnit } to + val genStatsAndExpr0 = stats.map(genStat(_)) :+ genStatOrExpr(expr, isStat) + genStatsAndExpr0 match { + case (undefParam @ js.Transient(UndefinedParam)) :: js.Undefined() :: Nil => + undefParam :: Nil + case _ => + genStatsAndExpr0 + } } else { genBlockWithCaseLabelDefs(stats :+ expr, isStat) } @@ -3907,12 +4071,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val translatedMatch = genTranslatedMatch(cases, matchEnd) val genMore = genBlockWithCaseLabelDefs(more, isStat) val label = getEnclosingReturnLabel() - if (translatedMatch.tpe == jstpe.NoType) { - // Could not actually reproduce this, but better be safe than sorry - translatedMatch :: js.Return(js.Undefined(), label) :: genMore - } else { - js.Return(translatedMatch, label) :: genMore - } + js.Return(translatedMatch, label) :: genMore // Otherwise, there is no matchEnd, only consecutive cases case Nil => @@ -3991,7 +4150,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) tree match { case If(cond, thenp, elsep) => js.If(genExpr(cond), genCaseBody(thenp), genCaseBody(elsep))( - jstpe.NoType) + jstpe.VoidType) case Block(stats, Literal(Constant(()))) => // Generated a lot by the async transform @@ -4046,7 +4205,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def genMatchEndBody(): js.Tree = { genStatOrExpr(matchEndBody, - isStat = toIRType(matchEndBody.tpe) == jstpe.NoType) + isStat = toIRType(matchEndBody.tpe) == jstpe.VoidType) } matchEnd.params match { @@ -4100,7 +4259,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) ) { genTranslatedCases } - val optimized = genOptimizedMatchEndLabeled(labelIdent, jstpe.NoType, + val optimized = genOptimizedMatchEndLabeled(labelIdent, jstpe.VoidType, translatedCases, info.generatedReturns) js.Block(varDefs ::: optimized :: genMatchEndBody() :: Nil) } @@ -4148,12 +4307,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * all jumps to case labels are already caught upstream by `genCaseBody()` * inside `genTranslatedMatch()`. */ - private def genOptimizedCaseLabeled(label: js.LabelIdent, + private def genOptimizedCaseLabeled(label: LabelName, translatedBody: js.Tree, returnCount: Int)( implicit pos: Position): js.Tree = { def default: js.Tree = - js.Labeled(label, jstpe.NoType, translatedBody) + js.Labeled(label, jstpe.VoidType, translatedBody) if (returnCount == 0) { translatedBody @@ -4163,21 +4322,21 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) translatedBody match { case js.Block(stats) => val (stats1, testAndStats2) = stats.span { - case js.If(_, js.Return(js.Undefined(), `label`), js.Skip()) => + case js.If(_, js.Return(_, `label`), js.Skip()) => false case _ => true } testAndStats2 match { - case js.If(cond, _, _) :: stats2 => + case js.If(cond, js.Return(returnedValue, _), _) :: stats2 => val notCond = cond match { case js.UnaryOp(js.UnaryOp.Boolean_!, notCond) => notCond case _ => js.UnaryOp(js.UnaryOp.Boolean_!, cond) } - js.Block(stats1 :+ js.If(notCond, js.Block(stats2), js.Skip())(jstpe.NoType)) + js.Block(stats1 :+ js.If(notCond, js.Block(stats2), returnedValue)(jstpe.VoidType)) case _ :: _ => throw new AssertionError("unreachable code") @@ -4205,7 +4364,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * !!! There is quite of bit of code duplication with * OptimizerCore.tryOptimizePatternMatch. */ - def genOptimizedMatchEndLabeled(label: js.LabelIdent, tpe: jstpe.Type, + def genOptimizedMatchEndLabeled(label: LabelName, tpe: jstpe.Type, translatedCases: List[js.Tree], returnCount: Int)( implicit pos: Position): js.Tree = { def default: js.Tree = @@ -4506,8 +4665,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def genAnyEquality(eqeq: Boolean, not: Boolean): js.Tree = { // Arrays, Null, Nothing never have a custom equals() method def canHaveCustomEquals(tpe: jstpe.Type): Boolean = tpe match { - case jstpe.AnyType | jstpe.ClassType(_) => true - case _ => false + case jstpe.AnyType | _:jstpe.ClassType => true + case _ => false } if (eqeq && // don't call equals if we have a literal null at either side @@ -4536,12 +4695,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - /** See comment in `genEqEqPrimitive()` about `mustUseAnyComparator`. */ - private lazy val shouldPreserveEqEqBugWithJLFloatDouble = { - val v = scala.util.Properties.versionNumberString - v.startsWith("2.11.") || v == "2.12.1" - } - /** Gen JS code for a call to Any.== */ def genEqEqPrimitive(ltpe: Type, rtpe: Type, lsrc: js.Tree, rsrc: js.Tree)( implicit pos: Position): js.Tree = { @@ -4557,9 +4710,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * own equals method will do ok), except for java.lang.Float and * java.lang.Double: their `equals` have different behavior around `NaN` * and `-0.0`, see Javadoc (scala-dev#329, #2799). - * - * The latter case is only avoided in 2.12.2+, to remain bug-compatible - * with the Scala/JVM compiler. */ val mustUseAnyComparator: Boolean = { val lsym = ltpe.typeSymbol @@ -4568,12 +4718,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) isMaybeBoxed(lsym) && isMaybeBoxed(rsym) && { val areSameFinals = ltpe.isFinalType && rtpe.isFinalType && lsym == rsym - !areSameFinals || { - (lsym == BoxedFloatClass || lsym == BoxedDoubleClass) && { - // Bug-compatibility for Scala < 2.12.2 - !shouldPreserveEqEqBugWithJLFloatDouble - } - } + !areSameFinals || (lsym == BoxedFloatClass || lsym == BoxedDoubleClass) } } } @@ -4687,7 +4832,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.Assign(genSelect(fun.tpe.paramTypes(1)), arguments(1)) } else { // length of the array - js.ArrayLength(arrayValue) + js.UnaryOp(js.UnaryOp.Array_length, + js.UnaryOp(js.UnaryOp.CheckNotNull, arrayValue)) } } @@ -4700,16 +4846,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val newReceiver = genExpr(receiver) val newArg = genStatOrExpr(arg, isStat) newReceiver match { - case js.This() => - // common case for which there is no side-effect nor NPE + case newReceiver: js.VarRef if !newReceiver.tpe.isNullable => + // common case (notably for `this`) for which there is no side-effect nor NPE newArg case _ => - val NPECtor = getMemberMethod(NullPointerExceptionClass, - nme.CONSTRUCTOR).suchThat(_.tpe.params.isEmpty) js.Block( - js.If(js.BinaryOp(js.BinaryOp.===, newReceiver, js.Null()), - js.Throw(genNew(NullPointerExceptionClass, NPECtor, Nil)), - js.Skip())(jstpe.NoType), + js.UnaryOp(js.UnaryOp.CheckNotNull, newReceiver), newArg) } } @@ -4802,7 +4944,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val callTrgIdent = freshLocalIdent() val callTrgVarDef = js.VarDef(callTrgIdent, NoOriginalName, receiverType, mutable = false, genExpr(receiver)) - val callTrg = js.VarRef(callTrgIdent)(receiverType) + val callTrg = js.VarRef(callTrgIdent.name)(receiverType) val arguments = args zip sym.tpe.params map { case (arg, param) => /* No need for enteringPosterasure, because value classes are not @@ -5007,11 +5149,33 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } + /** Adapt boxes on a tree from and to the given types after posterasure. + * + * @param expr + * Tree to be adapted. + * @param fromTpeEnteringPosterasure + * The type of `expr` as it was entering the posterasure phase. + * @param toTpeEnteringPosterausre + * The type of the adapted tree as it would be entering the posterasure phase. + */ + def adaptBoxes(expr: js.Tree, fromTpeEnteringPosterasure: Type, + toTpeEnteringPosterasure: Type)( + implicit pos: Position): js.Tree = { + if (fromTpeEnteringPosterasure =:= toTpeEnteringPosterasure) { + expr + } else { + /* Upcast to `Any` then downcast to `toTpe`. This is not very smart. + * We rely on the optimizer to get rid of unnecessary casts. + */ + fromAny(ensureBoxed(expr, fromTpeEnteringPosterasure), toTpeEnteringPosterasure) + } + } + /** Gen a boxing operation (tpe is the primitive type) */ def makePrimitiveBox(expr: js.Tree, tpe: Type)( implicit pos: Position): js.Tree = { toIRType(tpe) match { - case jstpe.NoType => // for JS interop cases + case jstpe.VoidType => // for JS interop cases js.Block(expr, js.Undefined()) case jstpe.BooleanType | jstpe.CharType | jstpe.ByteType | jstpe.ShortType | jstpe.IntType | jstpe.LongType | jstpe.FloatType | @@ -5026,8 +5190,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def makePrimitiveUnbox(expr: js.Tree, tpe: Type)( implicit pos: Position): js.Tree = { toIRType(tpe) match { - case jstpe.NoType => expr // for JS interop cases - case irTpe => js.AsInstanceOf(expr, irTpe) + case jstpe.VoidType => expr // for JS interop cases + case irTpe => js.AsInstanceOf(expr, irTpe) } } @@ -5123,14 +5287,10 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) genStatOrExpr(args(1), isStat) } - case LINKING_INFO => - // runtime.linkingInfo - js.JSLinkingInfo() - case IDENTITY_HASH_CODE => // runtime.identityHashCode(arg) val arg = genArgs1 - js.IdentityHashCode(arg) + js.UnaryOp(js.UnaryOp.IdentityHashCode, arg) case DEBUGGER => // js.special.debugger() @@ -5176,6 +5336,44 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // js.import.meta js.JSImportMeta() + case JS_ASYNC => + // js.async(arg) + assert(args.size == 1, + s"Expected exactly 1 argument for JS primitive $code but got " + + s"${args.size} at $pos") + val Block(stats, fun @ Function(_, Apply(target, _))) = args.head + methodsAllowingJSAwait += target.symbol + val genStats = stats.map(genStat(_)) + val asyncExpr = genAnonFunction(fun) match { + case js.NewLambda(_, closure: js.Closure) + if closure.params.isEmpty && closure.resultType == jstpe.AnyType => + val newFlags = closure.flags.withTyped(false).withAsync(true) + js.JSFunctionApply(closure.copy(flags = newFlags), Nil) + case other => + abort(s"Unexpected tree generated for the Function0 argument to js.async at $pos: $other") + } + js.Block(genStats, asyncExpr) + + case JS_AWAIT => + // js.await(arg)(permit) + val (arg, permitValue) = genArgs2 + if (!methodsAllowingJSAwait.contains(currentMethodSym)) { + // This is an orphan await + if (!(args(1).tpe <:< WasmJSPI_allowOrphanJSAwaitModuleClass.toTypeConstructor)) { + reporter.error(pos, + "Illegal use of js.await().\n" + + "It can only be used inside a js.async {...} block, without any lambda,\n" + + "by-name argument or nested method in-between.\n" + + "If you compile for WebAssembly, you can allow arbitrary js.await()\n" + + "calls by adding the following import:\n" + + "import scala.scalajs.js.wasm.JSPI.allowOrphanJSAwait") + } + } + /* In theory we should evaluate `permit` after `arg` but before the `JSAwait`. + * It *should* always be side-effect-free, though, so we just discard it. + */ + js.JSAwait(arg) + case DYNAMIC_IMPORT => assert(args.size == 1, s"Expected exactly 1 argument for JS primitive $code but got " + @@ -5257,13 +5455,164 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val fVarDef = js.VarDef(freshLocalIdent("f"), NoOriginalName, jstpe.AnyType, mutable = false, arg2) val keyVarIdent = freshLocalIdent("key") - val keyVarRef = js.VarRef(keyVarIdent)(jstpe.AnyType) + val keyVarRef = js.VarRef(keyVarIdent.name)(jstpe.AnyType) js.Block( objVarDef, fVarDef, js.ForIn(objVarDef.ref, keyVarIdent, NoOriginalName, { js.JSFunctionApply(fVarDef.ref, List(keyVarRef)) })) + + case JS_THROW => + // js.special.throw(arg) + js.UnaryOp(js.UnaryOp.Throw, genArgs1) + + case JS_TRY_CATCH => + /* js.special.tryCatch(arg1, arg2) + * + * We must generate: + * + * val body = arg1 + * val handler = arg2 + * try { + * body() + * } catch (e) { + * handler(e) + * } + * + * with temporary vals, because `arg2` must be evaluated before + * `body` executes. Moreover, exceptions thrown while evaluating + * the function values `arg1` and `arg2` must not be caught. + */ + val (arg1, arg2) = genArgs2 + val bodyVarDef = js.VarDef(freshLocalIdent("body"), NoOriginalName, + jstpe.AnyType, mutable = false, arg1) + val handlerVarDef = js.VarDef(freshLocalIdent("handler"), NoOriginalName, + jstpe.AnyType, mutable = false, arg2) + val exceptionVarIdent = freshLocalIdent("e") + val exceptionVarRef = js.VarRef(exceptionVarIdent.name)(jstpe.AnyType) + js.Block( + bodyVarDef, + handlerVarDef, + js.TryCatch( + js.JSFunctionApply(bodyVarDef.ref, Nil), + exceptionVarIdent, + NoOriginalName, + js.JSFunctionApply(handlerVarDef.ref, List(exceptionVarRef)) + )(jstpe.AnyType) + ) + + case WRAP_AS_THROWABLE => + // js.special.wrapAsThrowable(arg) + js.UnaryOp(js.UnaryOp.WrapAsThrowable, genArgs1) + + case UNWRAP_FROM_THROWABLE => + // js.special.unwrapFromThrowable(arg) + js.UnaryOp(js.UnaryOp.UnwrapFromThrowable, + js.UnaryOp(js.UnaryOp.CheckNotNull, genArgs1)) + + case LINKTIME_IF => + // LinkingInfo.linkTimeIf(cond, thenp, elsep) + val cond = genLinkTimeExpr(args(0)) + val thenp = genExpr(args(1)) + val elsep = genExpr(args(2)) + val tpe = + if (isStat) jstpe.VoidType + else toIRType(tree.tpe) + js.LinkTimeIf(cond, thenp, elsep)(tpe) + + case LINKTIME_PROPERTY => + // LinkingInfo.linkTimePropertyXXX("...") + val arg = genArgs1 + val tpe: jstpe.Type = toIRType(tree.tpe) match { + case jstpe.ClassType(jswkn.BoxedStringClass, _) => jstpe.StringType + case irType => irType + } + arg match { + case js.StringLiteral(name) => + js.LinkTimeProperty(name)(tpe) + case _ => + reporter.error(args.head.pos, + "The argument of linkTimePropertyXXX must be a String literal: \"...\"") + js.LinkTimeProperty("erroneous")(tpe) + } + } + } + + private def genLinkTimeExpr(tree: Tree): js.Tree = { + import scalaPrimitives._ + + implicit val pos = tree.pos + + def invalid(): js.Tree = { + reporter.error(tree.pos, + "Illegal expression in the condition of a linkTimeIf. " + + "Valid expressions are: boolean and int primitives; " + + "references to link-time properties; " + + "primitive operations on booleans; " + + "and comparisons on ints.") + js.BooleanLiteral(false) + } + + tree match { + case Literal(c) => + c.tag match { + case BooleanTag => js.BooleanLiteral(c.booleanValue) + case IntTag => js.IntLiteral(c.intValue) + case _ => invalid() + } + + case Apply(fun @ Select(receiver, _), args) => + fun.symbol.getAnnotation(LinkTimePropertyAnnotation) match { + case Some(annotation) => + val propName = annotation.constantAtIndex(0).get.stringValue + js.LinkTimeProperty(propName)(toIRType(tree.tpe)) + + case None if isPrimitive(fun.symbol) => + val code = getPrimitive(fun.symbol) + + def genLhs: js.Tree = genLinkTimeExpr(receiver) + def genRhs: js.Tree = genLinkTimeExpr(args.head) + + def unaryOp(op: js.UnaryOp.Code): js.Tree = + js.UnaryOp(op, genLhs) + def binaryOp(op: js.BinaryOp.Code): js.Tree = + js.BinaryOp(op, genLhs, genRhs) + + toIRType(receiver.tpe) match { + case jstpe.BooleanType => + (code: @switch) match { + case ZNOT => unaryOp(js.UnaryOp.Boolean_!) + case EQ => binaryOp(js.BinaryOp.Boolean_==) + case NE | XOR => binaryOp(js.BinaryOp.Boolean_!=) + case OR => binaryOp(js.BinaryOp.Boolean_|) + case AND => binaryOp(js.BinaryOp.Boolean_&) + case ZOR => js.LinkTimeIf(genLhs, js.BooleanLiteral(true), genRhs)(jstpe.BooleanType) + case ZAND => js.LinkTimeIf(genLhs, genRhs, js.BooleanLiteral(false))(jstpe.BooleanType) + case _ => invalid() + } + + case jstpe.IntType => + (code: @switch) match { + case EQ => binaryOp(js.BinaryOp.Int_==) + case NE => binaryOp(js.BinaryOp.Int_!=) + case LT => binaryOp(js.BinaryOp.Int_<) + case LE => binaryOp(js.BinaryOp.Int_<=) + case GT => binaryOp(js.BinaryOp.Int_>) + case GE => binaryOp(js.BinaryOp.Int_>=) + case _ => invalid() + } + + case _ => + invalid() + } + + case None => // if !isPrimitive + invalid() + } + + case _ => + invalid() } } @@ -5375,8 +5724,16 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def genSelectGet(propName: js.Tree): js.Tree = genSuperReference(propName) - def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree = - js.Assign(genSuperReference(propName), value) + def genSelectSet(propName: js.Tree, value: js.Tree): js.Tree = { + val lhs = genSuperReference(propName) + lhs match { + case js.JSGlobalRef(js.JSGlobalRef.FileLevelThis) => + reporter.error(pos, + "Illegal assignment to global this.") + case _ => + } + js.Assign(lhs, value) + } def genCall(methodName: js.Tree, args: List[js.TreeOrJSSpread]): js.Tree = { @@ -5834,78 +6191,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // Synthesizers for JS functions ------------------------------------------- - /** Try and generate JS code for an anonymous function class. - * - * Returns Some() if the class could be rewritten that way, None - * otherwise. - * - * We make the following assumptions on the form of such classes: - * - It is an anonymous function - * - Includes being anonymous, final, and having exactly one constructor - * - It is not a PartialFunction - * - It has no field other than param accessors - * - It has exactly one constructor - * - It has exactly one non-bridge method apply if it is not specialized, - * or a method apply$...$sp and a forwarder apply if it is specialized. - * - As a precaution: it is synthetic - * - * From a class looking like this: - * - * final class (outer, capture1, ..., captureM) extends AbstractionFunctionN[...] { - * def apply(param1, ..., paramN) = { - * - * } - * } - * new (o, c1, ..., cM) - * - * we generate a function: - * - * lambda[notype]( - * outer, capture1, ..., captureM, param1, ..., paramN) { - * - * } - * - * so that, at instantiation point, we can write: - * - * new AnonFunctionN(function) - * - * the latter tree is returned in case of success. - * - * Trickier things apply when the function is specialized. - */ - private def tryGenAnonFunctionClass(cd: ClassDef, - capturedArgs: List[js.Tree]): Option[js.Tree] = { - // scalastyle:off return - implicit val pos = cd.pos - val sym = cd.symbol - assert(sym.isAnonymousFunction, - s"tryGenAndRecordAnonFunctionClass called with non-anonymous function $cd") - - if (!sym.superClass.fullName.startsWith("scala.runtime.AbstractFunction")) { - /* This is an anonymous class for a non-LMF capable SAM in 2.12. - * We must not rewrite it, as it would then not inherit from the - * appropriate parent class and/or interface. - */ - None - } else { - nestedGenerateClass(sym) { - val (functionBase, arity) = - tryGenAnonFunctionClassGeneric(cd, capturedArgs)(_ => return None) - - Some(genJSFunctionToScala(functionBase, arity)) - } - } - // scalastyle:on return - } - - /** Gen a conversion from a JavaScript function into a Scala function. */ - private def genJSFunctionToScala(jsFunction: js.Tree, arity: Int)( - implicit pos: Position): js.Tree = { - val clsSym = getRequiredClass("scala.scalajs.runtime.AnonFunction" + arity) - val ctor = clsSym.primaryConstructor - genNew(clsSym, ctor, List(jsFunction)) - } - /** Gen JS code for a JS function class. * * This is called when emitting a ClassDef that represents an anonymous @@ -5914,11 +6199,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * functions are not classes, we deconstruct the ClassDef, then * reconstruct it to be a genuine Closure. * - * Compared to `tryGenAnonFunctionClass()`, this function must - * always succeed, because we really cannot afford keeping them as - * anonymous classes. The good news is that it can do so, because the - * body of SAM lambdas is hoisted in the enclosing class. Hence, the - * apply() method is just a forwarder to calling that hoisted method. + * We can always do so, because the body of SAM lambdas is hoisted in the + * enclosing class. Hence, the apply() method is just a forwarder to + * calling that hoisted method. * * From a class looking like this: * @@ -5931,8 +6214,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * * we generate a function: * - * lambda[notype]( - * outer, capture1, ..., captureM, param1, ..., paramN) { + * arrow-lambda(param1, ..., paramN) { * outer.lambdaImpl(param1, ..., paramN, capture1, ..., captureM) * } */ @@ -5942,26 +6224,18 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) s"genAndRecordJSFunctionClass called with non-JS function $cd") nestedGenerateClass(sym) { - val (function, _) = tryGenAnonFunctionClassGeneric(cd, captures)(msg => - abort(s"Could not generate function for JS function: $msg")) - - function + genJSFunctionInner(cd, captures) } } - /** Code common to tryGenAndRecordAnonFunctionClass and - * genAndRecordJSFunctionClass. - */ - private def tryGenAnonFunctionClassGeneric(cd: ClassDef, - initialCapturedArgs: List[js.Tree])( - fail: (=> String) => Nothing): (js.Tree, Int) = { + /** The code of `genJSFunction` that is inside the `nestedGenerateClass` wrapper. */ + private def genJSFunctionInner(cd: ClassDef, + initialCapturedArgs: List[js.Tree]): js.Closure = { implicit val pos = cd.pos val sym = cd.symbol - // First checks - - if (sym.isSubClass(PartialFunctionClass)) - fail(s"Cannot rewrite PartialFunction $cd") + def fail(reason: String): Nothing = + abort(s"Could not generate function for JS function: $reason") // First step: find the apply method def, and collect param accessors @@ -5989,10 +6263,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (!ddsym.isPrimaryConstructor) fail(s"Non-primary constructor $ddsym in anon function $cd") } else { - val name = dd.name.toString - if (name == "apply" || (ddsym.isSpecialized && name.startsWith("apply$"))) { - if ((applyDef eq null) || ddsym.isSpecialized) + if (dd.name == nme.apply) { + if (!ddsym.isBridge) { + if (applyDef ne null) + fail(s"Found duplicate apply method $ddsym in $cd") applyDef = dd + } } else if (ddsym.hasAnnotation(JSOptionalAnnotation)) { // Ignore (this is useful for default parameters in custom JS function types) } else { @@ -6032,24 +6308,15 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // Third step: emit the body of the apply method def val applyMethod = withScopedVars( - paramAccessorLocals := (paramAccessors zip ctorParamDefs).toMap, - tryingToGenMethodAsJSFunction := true + paramAccessorLocals := (paramAccessors zip ctorParamDefs).toMap ) { - try { - genMethodWithCurrentLocalNameScope(applyDef) - } catch { - case e: CancelGenMethodAsJSFunction => - fail(e.getMessage) - } + genMethodWithCurrentLocalNameScope(applyDef) } // Fourth step: patch the body to unbox parameters and box result - val hasRepeatedParam = { - sym.superClass == JSFunctionClass && // Scala functions are known not to have repeated params - enteringUncurry { - applyDef.symbol.paramss.flatten.lastOption.exists(isRepeated(_)) - } + val hasRepeatedParam = enteringUncurry { + applyDef.symbol.paramss.flatten.lastOption.exists(isRepeated(_)) } val js.MethodDef(_, _, _, params, _, body) = applyMethod @@ -6058,7 +6325,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (hasRepeatedParam) params.init else params patchFunParamsWithBoxes(applyDef.symbol, nonRepeatedParams, - useParamsBeforeLambdaLift = false) + useParamsBeforeLambdaLift = false, + fromParamTypes = nonRepeatedParams.map(_ => ObjectTpe)) } val (patchedRepeatedParam, repeatedParamLocal) = { @@ -6066,7 +6334,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * But that lowers the type to iterable. */ if (hasRepeatedParam) { - val (p, l) = genPatchedParam(params.last, genJSArrayToVarArgs(_)) + val (p, l) = genPatchedParam(params.last, genJSArrayToVarArgs(_), jstpe.AnyType) (Some(p), Some(l)) } else { (None, None) @@ -6082,8 +6350,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val ok = patchedParams.nonEmpty if (!ok) { reporter.error(pos, - "The SAM or apply method for a js.ThisFunction must have a " + - "leading non-varargs parameter") + "The apply method for a js.ThisFunction must have a leading non-varargs parameter") } ok } @@ -6098,10 +6365,11 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (isThisFunction) { val thisParam :: actualParams = patchedParams js.Closure( - arrow = false, + js.ClosureFlags.function, ctorParamDefs, actualParams, patchedRepeatedParam, + jstpe.AnyType, js.Block( js.VarDef(thisParam.name, thisParam.originalName, thisParam.ptpe, mutable = false, @@ -6109,21 +6377,18 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) patchedBody), capturedArgs) } else { - js.Closure(arrow = true, ctorParamDefs, patchedParams, - patchedRepeatedParam, patchedBody, capturedArgs) + js.Closure(js.ClosureFlags.arrow, ctorParamDefs, patchedParams, + patchedRepeatedParam, jstpe.AnyType, patchedBody, capturedArgs) } } - val arity = params.size - - (closure, arity) + closure } } /** Generate JS code for an anonymous function * - * Anonymous functions survive until the backend in 2.11 under - * -Ydelambdafy:method (for Scala function types) and in 2.12 for any + * Anonymous functions survive until the backend for any * LambdaMetaFactory-capable type. * * When they do, their body is always of the form @@ -6136,30 +6401,48 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) * We identify the captures using the same method as the `delambdafy` * phase. We have an additional hack for `this`. * - * To translate them, we first construct a JS closure for the body: + * To translate them, we first construct a typed closure for the body: * {{{ - * lambda( - * _this, capture1, ..., captureM, arg1, ..., argN) { - * _this.someMethod(arg1, ..., argN, capture1, ..., captureM) + * typed-lambda<_this = this, capture1: U1 = capture1, ..., captureM: UM = captureM>( + * arg1: T1, ..., argN: TN): TR = { + * val arg1Unboxed: S1 = arg1.asInstanceOf[S1]; + * ... + * val argNUnboxed: SN = argN.asInstanceOf[SN]; + * // inlined body of `someMethod`, boxed * } * }}} * In the closure, input params are unboxed before use, and the result of - * `someMethod()` is boxed back. + * the body of `someMethod` is boxed back. The Si and SR are the types + * found in the target `someMethod`; the Ti and TR are the types found in + * the SAM method to be implemented. It is common for `Si` to be different + * from `Ti`. For example, in a Scala function `(x: Int) => x`, + * `S1 = SR = int`, but `T1 = TR = any`, because `scala.Function1` defines + * an `apply` method that erases to using `any`'s. * * Then, we wrap that closure in a class satisfying the expected type. - * For Scala function types, we use the existing - * `scala.scalajs.runtime.AnonFunctionN` from the library. For other - * LMF-capable types, we generate a class on the fly, which looks like - * this: + * For SAM types that do not need any bridges (including all Scala + * function types), we use a `NewLambda` node. + * + * When bridges are required (which is rare), we generate a class on the + * fly. In that case, we "inline" the captures of the typed closure as + * fields of the class, and its body as the body of the main SAM method + * implementation. Overall, it looks like this: * {{{ * class AnonFun extends Object with FunctionalInterface { - * val f: any - * def (f: any) { + * val ...captureI: UI + * def (...captureI: UI) { * super(); - * this.f = f + * ...this.captureI = captureI; + * } + * // main SAM method implementation + * def theSAMMethod(params: Ti...): TR = { + * val ...captureI = this.captureI; + * // inline body of the typed-lambda + * } + * // a bridge + * def theSAMMethod(params: Vi...): VR = { + * this.theSAMMethod(...params.asInstanceOf[Ti]).asInstanceOf[VR] * } - * def theSAMMethod(params: Types...): Type = - * unbox((this.f)(boxParams...)) * } * }}} */ @@ -6168,80 +6451,187 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val Function(paramTrees, Apply( targetTree @ Select(receiver, _), allArgs0)) = originalFunction + // Extract information about the SAM type we are implementing + val samClassSym = originalFunction.tpe.typeSymbolDirect + val (superClass, interfaces, sam, samBridges) = if (isFunctionSymbol(samClassSym)) { + // This is a scala.FunctionN SAM; extend the corresponding AbstractFunctionN class + val arity = paramTrees.size + val superClass = AbstractFunctionClass(arity) + val sam = superClass.info.member(nme.apply) + (superClass, Nil, sam, Nil) + } else { + // This is an arbitrary SAM interface + val samInfo = originalFunction.attachments.get[SAMFunction].getOrElse { + abort(s"Cannot find the SAMFunction attachment on $originalFunction at $pos") + } + (ObjectClass, samClassSym :: Nil, samInfo.sam, samBridgesFor(samInfo)) + } + val captureSyms = - global.delambdafy.FreeVarTraverser.freeVarsOf(originalFunction) + global.delambdafy.FreeVarTraverser.freeVarsOf(originalFunction).toList val target = targetTree.symbol - val params = paramTrees.map(_.symbol) - - val allArgs = allArgs0 map genExpr - - val formalCaptures = captureSyms.toList.map(genParamDef(_, pos)) - val actualCaptures = formalCaptures.map(_.ref) - val formalArgs = params.map(genParamDef(_)) + val isTargetStatic = compileAsStaticMethod(target) - val (allFormalCaptures, body, allActualCaptures) = if (!target.isStaticMember) { - val thisActualCapture = genExpr(receiver) - val thisFormalCapture = js.ParamDef( - freshLocalIdent("this")(receiver.pos), thisOriginalName, - thisActualCapture.tpe, mutable = false)(receiver.pos) - val thisCaptureArg = thisFormalCapture.ref + // Gen actual captures in the local name scope of the enclosing method + val actualCaptures: List[js.Tree] = { + val base = captureSyms.map(genVarRef(_)) + if (isTargetStatic) + base + else + genExpr(receiver) :: base + } - val body = if (isJSType(receiver.tpe) && target.owner != ObjectClass) { - assert(isNonNativeJSClass(target.owner) && !isExposed(target), - s"A Function lambda is trying to call an exposed JS method ${target.fullName}") - genApplyJSClassMethod(thisCaptureArg, target, allArgs) + val closure: js.Closure = withNewLocalNameScope { + // Gen the formal capture params for the closure + val thisFormalCapture: Option[js.ParamDef] = if (isTargetStatic) { + None } else { - genApplyMethodMaybeStatically(thisCaptureArg, target, allArgs) + Some(js.ParamDef( + freshLocalIdent("this")(receiver.pos), thisOriginalName, + toIRType(receiver.tpe), mutable = false)(receiver.pos)) } + val formalCaptures: List[js.ParamDef] = + thisFormalCapture.toList ::: captureSyms.map(genParamDef(_, pos)) - (thisFormalCapture :: formalCaptures, - body, thisActualCapture :: actualCaptures) - } else { - val body = genApplyStatic(target, allArgs) + // Gen the inlined target method body + val genMethodDef = { + genMethodWithCurrentLocalNameScope(consumeDelambdafyTarget(target), + initThisLocalVarName = thisFormalCapture.map(_.name.name)) + } + val js.MethodDef(methodFlags, _, _, methodParams, _, methodBody) = genMethodDef - (formalCaptures, body, actualCaptures) - } + /* If the target method was not supposed to be static, but genMethodDef + * turns out to be static, it means it is a non-exposed method of a JS + * class. The `this` param was turned into a regular param, for which + * we need a `js.VarDef`. + */ + val (maybeThisParamAsVarDef, remainingMethodParams) = { + if (methodFlags.namespace.isStatic && !isTargetStatic) { + val thisParamDef :: remainingMethodParams = methodParams: @unchecked + val thisParamAsVarDef = js.VarDef(thisParamDef.name, thisParamDef.originalName, + thisParamDef.ptpe, thisParamDef.mutable, thisFormalCapture.get.ref) + (thisParamAsVarDef :: Nil, remainingMethodParams) + } else { + (Nil, methodParams) + } + } - val (patchedFormalArgs, paramsLocals) = - patchFunParamsWithBoxes(target, formalArgs, useParamsBeforeLambdaLift = true) + // After that, the args found in the `Function` node had better match the remaining method params + assert(remainingMethodParams.size == allArgs0.size, + s"Arity mismatch: $remainingMethodParams <-> $allArgs0 at $pos") + + /* Declare each method param as a VarDef, initialized to the corresponding arg. + * In practice, all the args are `This` nodes or `VarRef` nodes, so the + * optimizer will alias those VarDefs away. + * We do this because we have different Symbols, hence different + * encoded LocalIdents. + */ + val methodParamsAsVarDefs = for ((methodParam, arg) <- remainingMethodParams.zip(allArgs0)) yield { + js.VarDef(methodParam.name, methodParam.originalName, methodParam.ptpe, + methodParam.mutable, genExpr(arg)) + } - val patchedBody = - js.Block(paramsLocals :+ ensureResultBoxed(body, target)) + val (samParamTypes, samResultType, targetResultType) = enteringPhase(currentRun.posterasurePhase) { + val methodType = sam.tpe.asInstanceOf[MethodType] + (methodType.params.map(_.info), methodType.resultType, target.tpe.finalResultType) + } - val closure = js.Closure( - arrow = true, - allFormalCaptures, + /* Adapt the params and result so that they are boxed from the outside. + * + * TODO In total we generate *3* locals for each original param: the + * patched ParamDef, the VarDef for the unboxed value, and a VarDef for + * the original parameter of the delambdafy target. In theory we only + * need 2: can we make it so? + */ + val formalArgs = paramTrees.map(p => genParamDef(p.symbol)) + val (patchedFormalArgs, paramsLocals) = + patchFunParamsWithBoxes(target, formalArgs, useParamsBeforeLambdaLift = true, fromParamTypes = samParamTypes) + val patchedBodyWithBox = + adaptBoxes(methodBody.get, targetResultType, samResultType) + + // Finally, assemble all the pieces + val fullClosureBody = js.Block( + paramsLocals ::: + maybeThisParamAsVarDef ::: + methodParamsAsVarDefs ::: + patchedBodyWithBox :: + Nil + ) + js.Closure( + js.ClosureFlags.typed, + formalCaptures, patchedFormalArgs, restParam = None, - patchedBody, - allActualCaptures) - - // Wrap the closure in the appropriate box for the SAM type - val funSym = originalFunction.tpe.typeSymbolDirect - if (isFunctionSymbol(funSym)) { - /* This is a scala.FunctionN. We use the existing AnonFunctionN - * wrapper. + resultType = toIRType(underlyingOfEVT(samResultType)), + fullClosureBody, + actualCaptures + ) + } + + // Build the descriptor + val closureType = closure.tpe.asInstanceOf[jstpe.ClosureType] + val descriptor = js.NewLambda.Descriptor( + encodeClassName(superClass), interfaces.map(encodeClassName(_)), + encodeMethodSym(sam).name, closureType.paramTypes, + closureType.resultType) + + /* Wrap the closure in the appropriate box for the SAM type. + * Use a `NewLambda` if we do not need any bridges; otherwise synthesize + * a SAM wrapper class. + */ + if (samBridges.isEmpty) { + // No bridges are needed; we can directly use a NewLambda + js.NewLambda(descriptor, closure)(encodeClassType(samClassSym).toNonNullable) + } else { + /* We need bridges; expand the `NewLambda` into a synthesized class. + * Captures of the closure are turned into fields of the wrapper class. */ - genJSFunctionToScala(closure, params.size) + val formalCaptureTypeRefs = captureSyms.map(sym => toTypeRef(sym.info)) + val allFormalCaptureTypeRefs = + if (isTargetStatic) formalCaptureTypeRefs + else toTypeRef(receiver.tpe) :: formalCaptureTypeRefs + + val ctorName = ir.Names.MethodName.constructor(allFormalCaptureTypeRefs) + val samWrapperClassName = synthesizeSAMWrapper(descriptor, sam, samBridges, closure, ctorName) + js.New(samWrapperClassName, js.MethodIdent(ctorName), closure.captureValues) + } + } + + private def samBridgesFor(samInfo: SAMFunction)(implicit pos: Position): List[Symbol] = { + /* scala/bug#10512: any methods which `samInfo.sam` overrides need + * bridges made for them. + */ + val samBridges = { + import scala.reflect.internal.Flags.BRIDGE + samInfo.synthCls.info.findMembers(excludedFlags = 0L, requiredFlags = BRIDGE).toList + } + + if (samBridges.isEmpty) { + // fast path + Nil } else { - /* This is an arbitrary SAM type (can only happen in 2.12). - * We have to synthesize a class like LambdaMetaFactory would do on - * the JVM. + /* Remove duplicates, e.g., if we override the same method declared + * in two super traits. */ - val sam = originalFunction.attachments.get[SAMFunctionCompat].getOrElse { - abort(s"Cannot find the SAMFunction attachment on $originalFunction at $pos") + val builder = List.newBuilder[Symbol] + val seenMethodNames = mutable.Set.empty[MethodName] + + seenMethodNames.add(encodeMethodSym(samInfo.sam).name) + + for (samBridge <- samBridges) { + if (seenMethodNames.add(encodeMethodSym(samBridge).name)) + builder += samBridge } - val samWrapperClassName = synthesizeSAMWrapper(funSym, sam) - js.New(samWrapperClassName, js.MethodIdent(ObjectArgConstructorName), - List(closure)) + builder.result() } } - private def synthesizeSAMWrapper(funSym: Symbol, samInfo: SAMFunctionCompat)( + private def synthesizeSAMWrapper(descriptor: js.NewLambda.Descriptor, + sam: Symbol, samBridges: List[Symbol], closure: js.Closure, + ctorName: ir.Names.MethodName)( implicit pos: Position): ClassName = { - val intfName = encodeClassName(funSym) val suffix = { generatedSAMWrapperCount.value += 1 @@ -6250,88 +6640,85 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } val className = encodeClassName(currentClassSym).withSuffix(suffix) - val classType = jstpe.ClassType(className) + val thisType = jstpe.ClassType(className, nullable = false) - // val f: Any - val fFieldIdent = js.FieldIdent(FieldName("f")) - val fFieldDef = js.FieldDef(js.MemberFlags.empty, fFieldIdent, - NoOriginalName, jstpe.AnyType) + // val captureI: CaptureTypeI + val captureFieldDefs = for (captureParam <- closure.captureParams) yield { + val simpleFieldName = SimpleFieldName(captureParam.name.name.encoded) + val ident = js.FieldIdent(FieldName(className, simpleFieldName)) + js.FieldDef(js.MemberFlags.empty, ident, captureParam.originalName, captureParam.ptpe) + } - // def this(f: Any) = { this.f = f; super() } + // def this(f: Any) = { ...this.captureI = captureI; super() } val ctorDef = { - val fParamDef = js.ParamDef(js.LocalIdent(LocalName("f")), - NoOriginalName, jstpe.AnyType, mutable = false) + val captureFieldAssignments = for { + (captureFieldDef, captureParam) <- captureFieldDefs.zip(closure.captureParams) + } yield { + js.Assign( + js.Select(js.This()(thisType), captureFieldDef.name)(captureFieldDef.ftpe), + captureParam.ref) + } js.MethodDef( js.MemberFlags.empty.withNamespace(js.MemberNamespace.Constructor), - js.MethodIdent(ObjectArgConstructorName), + js.MethodIdent(ctorName), NoOriginalName, - List(fParamDef), - jstpe.NoType, + closure.captureParams, + jstpe.VoidType, Some(js.Block(List( - js.Assign( - js.Select(js.This()(classType), className, fFieldIdent)( - jstpe.AnyType), - fParamDef.ref), + js.Block(captureFieldAssignments), js.ApplyStatically(js.ApplyFlags.empty.withConstructor(true), - js.This()(classType), - ir.Names.ObjectClass, - js.MethodIdent(ir.Names.NoArgConstructorName), - Nil)(jstpe.NoType)))))( - js.OptimizerHints.empty, None) - } - - // Compute the set of method symbols that we need to implement - val sams = { - val samsBuilder = List.newBuilder[Symbol] - val seenMethodNames = mutable.Set.empty[MethodName] - - /* scala/bug#10512: any methods which `samInfo.sam` overrides need - * bridges made for them. - * On Scala < 2.12.5, `synthCls` is polyfilled to `NoSymbol` and hence - * `samBridges` will always be empty. This causes our compiler to be - * bug-compatible on these versions. - */ - val synthCls = samInfo.synthCls - val samBridges = if (synthCls == NoSymbol) { - Nil - } else { - import scala.reflect.internal.Flags.BRIDGE - synthCls.info.findMembers(excludedFlags = 0L, requiredFlags = BRIDGE).toList + js.This()(thisType), + jswkn.ObjectClass, + js.MethodIdent(jswkn.NoArgConstructorName), + Nil)(jstpe.VoidType)))))( + js.OptimizerHints.empty, Unversioned) + } + + /* def samMethod(...closure.params): closure.resultType = { + * val captureI: CaptureTypeI = this.captureI; + * ... + * closure.body + * } + */ + val samMethodDef: js.MethodDef = { + val localCaptureVarDefs = for { + (captureParam, captureFieldDef) <- closure.captureParams.zip(captureFieldDefs) + } yield { + js.VarDef(captureParam.name, captureParam.originalName, captureParam.ptpe, mutable = false, + js.Select(js.This()(thisType), captureFieldDef.name)(captureFieldDef.ftpe)) } - for (sam <- samInfo.sam :: samBridges) { - /* Remove duplicates, e.g., if we override the same method declared - * in two super traits. - */ - if (seenMethodNames.add(encodeMethodSym(sam).name)) - samsBuilder += sam - } + val body = js.Block(localCaptureVarDefs, closure.body) - samsBuilder.result() + js.MethodDef(js.MemberFlags.empty, encodeMethodSym(sam), + originalNameOfMethod(sam), closure.params, closure.resultType, + Some(body))( + js.OptimizerHints.empty, Unversioned) } - // def samMethod(...params): resultType = this.f(...params) - val samMethodDefs = for (sam <- sams) yield { - val jsParams = sam.tpe.params.map(genParamDef(_, pos)) - val resultType = toIRType(sam.tpe.finalResultType) + val adaptBoxesTupled = (adaptBoxes(_, _, _)).tupled + + // def samBridgeMethod(...params): resultType = this.samMethod(...params) // (with adaptBoxes) + val samBridgeMethodDefs = for (samBridge <- samBridges) yield { + val jsParams = samBridge.tpe.params.map(genParamDef(_, pos)) + val resultType = toIRType(samBridge.tpe.finalResultType) val actualParams = enteringPhase(currentRun.posterasurePhase) { - for ((formal, param) <- jsParams.zip(sam.tpe.params)) - yield (formal.ref, param.tpe) - }.map((ensureBoxed _).tupled) + for (((formal, bridgeParam), samParam) <- jsParams.zip(samBridge.tpe.params).zip(sam.tpe.params)) + yield (formal.ref, bridgeParam.tpe, samParam.tpe) + }.map(adaptBoxesTupled) - val call = js.JSFunctionApply( - js.Select(js.This()(classType), className, fFieldIdent)(jstpe.AnyType), - actualParams) + val call = js.Apply(js.ApplyFlags.empty, js.This()(thisType), + samMethodDef.name, actualParams)(samMethodDef.resultType) - val body = fromAny(call, enteringPhase(currentRun.posterasurePhase) { - sam.tpe.finalResultType + val body = adaptBoxesTupled(enteringPhase(currentRun.posterasurePhase) { + (call, sam.tpe.finalResultType, samBridge.tpe.finalResultType) }) - js.MethodDef(js.MemberFlags.empty, encodeMethodSym(sam), - originalNameOfMethod(sam), jsParams, resultType, + js.MethodDef(js.MemberFlags.empty, encodeMethodSym(samBridge), + originalNameOfMethod(samBridge), jsParams, resultType, Some(body))( - js.OptimizerHints.empty, None) + js.OptimizerHints.empty, Unversioned) } // The class definition @@ -6340,21 +6727,26 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) NoOriginalName, ClassKind.Class, None, - Some(js.ClassIdent(ir.Names.ObjectClass)), - List(js.ClassIdent(intfName)), + Some(js.ClassIdent(descriptor.superClass)), + descriptor.interfaces.map(js.ClassIdent(_)), None, None, - fFieldDef :: ctorDef :: samMethodDefs, + fields = captureFieldDefs, + methods = ctorDef :: samMethodDef :: samBridgeMethodDefs, + jsConstructor = None, + Nil, + Nil, Nil)( js.OptimizerHints.empty.withInline(true)) - generatedClasses += classDef + generatedClasses += classDef -> pos className } private def patchFunParamsWithBoxes(methodSym: Symbol, - params: List[js.ParamDef], useParamsBeforeLambdaLift: Boolean)( + params: List[js.ParamDef], useParamsBeforeLambdaLift: Boolean, + fromParamTypes: List[Type])( implicit pos: Position): (List[js.ParamDef], List[js.VarDef]) = { // See the comment in genPrimitiveJSArgs for a rationale about this val paramTpes = enteringPhase(currentRun.posterasurePhase) { @@ -6379,26 +6771,33 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } (for { - (param, paramSym) <- params zip paramSyms + ((param, paramSym), fromParamType) <- params.zip(paramSyms).zip(fromParamTypes) } yield { val paramTpe = paramTpes.getOrElse(paramSym.name, paramSym.tpe) - genPatchedParam(param, fromAny(_, paramTpe)) + genPatchedParam(param, adaptBoxes(_, fromParamType, paramTpe), + toIRType(underlyingOfEVT(fromParamType))) }).unzip } - private def genPatchedParam(param: js.ParamDef, rhs: js.VarRef => js.Tree)( + private def genPatchedParam(param: js.ParamDef, rhs: js.VarRef => js.Tree, + fromParamType: jstpe.Type)( implicit pos: Position): (js.ParamDef, js.VarDef) = { val paramNameIdent = param.name val origName = param.originalName val newNameIdent = freshLocalIdent(paramNameIdent.name)(paramNameIdent.pos) val newOrigName = origName.orElse(paramNameIdent.name) - val patchedParam = js.ParamDef(newNameIdent, newOrigName, jstpe.AnyType, + val patchedParam = js.ParamDef(newNameIdent, newOrigName, fromParamType, mutable = false)(param.pos) val paramLocal = js.VarDef(paramNameIdent, origName, param.ptpe, mutable = false, rhs(patchedParam.ref)) (patchedParam, paramLocal) } + private def underlyingOfEVT(tpe: Type): Type = tpe match { + case tpe: ErasedValueType => tpe.erasedUnderlying + case _ => tpe + } + /** Generates a static method instantiating and calling this * DynamicImportThunk's `apply`: * @@ -6426,7 +6825,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) NoOriginalName, paramDefs, jstpe.AnyType, - Some(body))(OptimizerHints.empty, None) + Some(body))(OptimizerHints.empty, Unversioned) } } @@ -6459,7 +6858,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) // Utilities --------------------------------------------------------------- def genVarRef(sym: Symbol)(implicit pos: Position): js.VarRef = - js.VarRef(encodeLocalSym(sym))(toIRType(sym.tpe)) + js.VarRef(encodeLocalSymName(sym))(toIRType(sym.tpe)) def genParamDef(sym: Symbol): js.ParamDef = genParamDef(sym, toIRType(sym.tpe)) @@ -6506,17 +6905,46 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) require(sym0.isModuleOrModuleClass, "genLoadModule called with non-module symbol: " + sym0) - val sym = if (sym0.isModule) sym0.moduleClass else sym0 - // Does that module refer to the global scope? - if (sym.hasAnnotation(JSGlobalScopeAnnotation)) { - MaybeGlobalScope.GlobalScope(pos) - } else { - val className = encodeClassName(sym) - val tree = - if (isJSType(sym)) js.LoadJSModule(className) - else js.LoadModule(className) + if (sym0.isModule && sym0.isScala3Defined && sym0.hasAttachment[DottyEnumSingletonCompat.type]) { + /* #4739 This is a reference to a singleton `case` from a Scala 3 `enum`. + * It is not a module. Instead, it is a static field (accessed through + * a static getter) in the `enum` class. + * We use `originalOwner` and `rawname` because that's what the JVM back-end uses. + */ + val className = encodeClassName(sym0.originalOwner) + val getterSimpleName = sym0.rawname.toString() + val getterMethodName = MethodName(getterSimpleName, Nil, toTypeRef(sym0.tpe)) + val tree = js.ApplyStatic(js.ApplyFlags.empty, className, js.MethodIdent(getterMethodName), Nil)( + toIRType(sym0.tpe)) MaybeGlobalScope.NotGlobalScope(tree) + } else { + val sym = if (sym0.isModule) sym0.moduleClass else sym0 + + // Does that module refer to the global scope? + if (sym.hasAnnotation(JSGlobalScopeAnnotation)) { + MaybeGlobalScope.GlobalScope(pos) + } else { + if (sym == currentClassSym.get && isModuleInitialized.get != null && isModuleInitialized.value) { + /* This is a LoadModule(myClass) after the StoreModule(). It is + * guaranteed to always return the `this` value. We eagerly replace + * it by a `This()` node to help the elidable constructors analysis + * of the linker. If we don't do this, then the analysis must + * tolerate `LoadModule(myClass)` after `StoreModule()` to be + * side-effect-free, but that would weaken the guarantees resulting + * from the analysis. In particular, it cannot guarantee that the + * result of a `LoadModule()` of a module with elidable constructors + * is always fully initialized. + */ + MaybeGlobalScope.NotGlobalScope(genThis()) + } else { + val className = encodeClassName(sym) + val tree = + if (isJSType(sym)) js.LoadJSModule(className) + else js.LoadModule(className) + MaybeGlobalScope.NotGlobalScope(tree) + } + } } } @@ -6568,31 +6996,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.JSSelect(qualTree, item) case MaybeGlobalScope.GlobalScope(_) => - item match { - case js.StringLiteral(value) => - if (js.JSGlobalRef.isValidJSGlobalRefName(value)) { - js.JSGlobalRef(value) - } else if (js.JSGlobalRef.ReservedJSIdentifierNames.contains(value)) { - reporter.error(pos, - "Invalid selection in the global scope of the reserved " + - s"identifier name `$value`." + - GenericGlobalObjectInformationMsg) - js.JSGlobalRef("erroneous") - } else { - reporter.error(pos, - "Selecting a field of the global scope whose name is " + - "not a valid JavaScript identifier is not allowed." + - GenericGlobalObjectInformationMsg) - js.JSGlobalRef("erroneous") - } - - case _ => - reporter.error(pos, - "Selecting a field of the global scope with a dynamic " + - "name is not allowed." + - GenericGlobalObjectInformationMsg) - js.JSGlobalRef("erroneous") - } + genJSGlobalRef(item, "Selecting a field", "selection") } } @@ -6615,31 +7019,43 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.JSMethodApply(receiverTree, method, args) case MaybeGlobalScope.GlobalScope(_) => - method match { - case js.StringLiteral(value) => - if (js.JSGlobalRef.isValidJSGlobalRefName(value)) { - js.JSFunctionApply(js.JSGlobalRef(value), args) - } else if (js.JSGlobalRef.ReservedJSIdentifierNames.contains(value)) { - reporter.error(pos, - "Invalid call in the global scope of the reserved " + - s"identifier name `$value`." + - GenericGlobalObjectInformationMsg) - js.Undefined() - } else { - reporter.error(pos, - "Calling a method of the global scope whose name is not " + - "a valid JavaScript identifier is not allowed." + - GenericGlobalObjectInformationMsg) - js.Undefined() - } - - case _ => - reporter.error(pos, - "Calling a method of the global scope with a dynamic " + - "name is not allowed." + - GenericGlobalObjectInformationMsg) - js.Undefined() + val globalRef = genJSGlobalRef(method, "Calling a method", "call") + js.JSFunctionApply(globalRef, args) + } + } + + private def genJSGlobalRef(propName: js.Tree, + actionFull: String, actionSimpleNoun: String)( + implicit pos: Position): js.JSGlobalRef = { + propName match { + case js.StringLiteral(value) => + if (js.JSGlobalRef.isValidJSGlobalRefName(value)) { + if (value == "await") { + global.runReporting.warning(pos, + s"$actionFull of the global scope with the name '$value' is deprecated.\n" + + " It may produce invalid JavaScript code causing a SyntaxError in some environments." + + GenericGlobalObjectInformationMsg, + WarningCategory.Deprecation, + currentMethodSym.get) + } + js.JSGlobalRef(value) + } else if (js.JSGlobalRef.ReservedJSIdentifierNames.contains(value)) { + reporter.error(pos, + s"Invalid $actionSimpleNoun in the global scope of the reserved identifier name `$value`." + + GenericGlobalObjectInformationMsg) + js.JSGlobalRef("erroneous") + } else { + reporter.error(pos, + s"$actionFull of the global scope whose name is not a valid JavaScript identifier is not allowed." + + GenericGlobalObjectInformationMsg) + js.JSGlobalRef("erroneous") } + + case _ => + reporter.error(pos, + s"$actionFull of the global scope with a dynamic name is not allowed." + + GenericGlobalObjectInformationMsg) + js.JSGlobalRef("erroneous") } } @@ -6655,14 +7071,12 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) js.JSSelect(qual, genPrivateFieldsSymbol()), encodeFieldSymAsStringLiteral(sym)) } else { - js.JSPrivateSelect(qual, encodeClassName(sym.owner), - encodeFieldSym(sym)) + js.JSPrivateSelect(qual, encodeFieldSym(sym)) } (f, true) } else if (jsInterop.topLevelExportsOf(sym).nonEmpty) { - val f = js.SelectStatic(encodeClassName(sym.owner), - encodeFieldSym(sym))(jstpe.AnyType) + val f = js.SelectStatic(encodeFieldSym(sym))(jstpe.AnyType) (f, true) } else if (jsInterop.staticExportsOf(sym).nonEmpty) { val exportInfo = jsInterop.staticExportsOf(sym).head @@ -6673,7 +7087,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) (f, true) } else { - val className = encodeClassName(sym.owner) val fieldIdent = encodeFieldSym(sym) /* #4370 Fields cannot have type NothingType, so we box them as @@ -6683,11 +7096,11 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) */ toIRType(sym.tpe) match { case jstpe.NothingType => - val f = js.Select(qual, className, fieldIdent)( + val f = js.Select(qual, fieldIdent)( encodeClassType(RuntimeNothingClass)) (f, true) case ftpe => - val f = js.Select(qual, className, fieldIdent)(ftpe) + val f = js.Select(qual, fieldIdent)(ftpe) (f, false) } } @@ -6716,16 +7129,8 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } - private lazy val isScala211WithXexperimental = { - scala.util.Properties.versionNumberString.startsWith("2.11.") && - settings.Xexperimental.value - } - - private lazy val hasNewCollections = { - val v = scala.util.Properties.versionNumberString - !v.startsWith("2.11.") && - !v.startsWith("2.12.") - } + private lazy val hasNewCollections = + !scala.util.Properties.versionNumberString.startsWith("2.12.") /** Tests whether the given type represents a JavaScript type, * i.e., whether it extends scala.scalajs.js.Any. @@ -6750,13 +7155,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) private def isJSNativeClass(sym: Symbol): Boolean = sym.hasAnnotation(JSNativeAnnotation) - /** Tests whether the given class is the impl class of a JS trait. */ - private def isJSImplClass(sym: Symbol): Boolean = - isImplClass(sym) && isJSType(traitOfImplClass(sym)) - - private def traitOfImplClass(sym: Symbol): Symbol = - sym.owner.info.decl(sym.name.dropRight(nme.IMPL_CLASS_SUFFIX.length)) - /** Tests whether the given member is exposed, i.e., whether it was * originally a public or protected member of a non-native JS class. */ @@ -6924,7 +7322,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) JavaScriptExceptionClass isSubClass tpe.typeSymbol def isStaticModule(sym: Symbol): Boolean = - sym.isModuleClass && !isImplClass(sym) && !sym.isLifted + sym.isModuleClass && !sym.isLifted def isAnonymousJSClass(sym: Symbol): Boolean = { /* sym.isAnonymousClass simply checks if @@ -6962,7 +7360,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) def traverse(traverser: ir.Traversers.Traverser): Unit = () - def transform(transformer: ir.Transformers.Transformer, isStat: Boolean)( + def transform(transformer: ir.Transformers.Transformer)( implicit pos: ir.Position): js.Tree = { js.Transient(this) } @@ -6974,11 +7372,43 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) private object GenJSCode { private val JSObjectClassName = ClassName("scala.scalajs.js.Object") + private val JavaScriptExceptionClassName = ClassName("scala.scalajs.js.JavaScriptException") private val newSimpleMethodName = SimpleMethodName("new") private val ObjectArgConstructorName = - MethodName.constructor(List(jstpe.ClassRef(ir.Names.ObjectClass))) + MethodName.constructor(List(jswkn.ObjectRef)) + + private val lengthMethodName = + MethodName("length", Nil, jstpe.IntRef) + private val charAtMethodName = + MethodName("charAt", List(jstpe.IntRef), jstpe.CharRef) + + private val getNameMethodName = + MethodName("getName", Nil, jstpe.ClassRef(jswkn.BoxedStringClass)) + private val isPrimitiveMethodName = + MethodName("isPrimitive", Nil, jstpe.BooleanRef) + private val isInterfaceMethodName = + MethodName("isInterface", Nil, jstpe.BooleanRef) + private val isArrayMethodName = + MethodName("isArray", Nil, jstpe.BooleanRef) + private val getComponentTypeMethodName = + MethodName("getComponentType", Nil, jstpe.ClassRef(jswkn.ClassClass)) + private val getSuperclassMethodName = + MethodName("getSuperclass", Nil, jstpe.ClassRef(jswkn.ClassClass)) + + private val isInstanceMethodName = + MethodName("isInstance", List(jstpe.ClassRef(jswkn.ObjectClass)), jstpe.BooleanRef) + private val isAssignableFromMethodName = + MethodName("isAssignableFrom", List(jstpe.ClassRef(jswkn.ClassClass)), jstpe.BooleanRef) + private val castMethodName = + MethodName("cast", List(jstpe.ClassRef(jswkn.ObjectClass)), jstpe.ClassRef(jswkn.ObjectClass)) + + private val arrayNewInstanceMethodName = { + MethodName("newInstance", + List(jstpe.ClassRef(jswkn.ClassClass), jstpe.IntRef), + jstpe.ClassRef(jswkn.ObjectClass)) + } private val thisOriginalName = OriginalName("this") diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala index 7eab47a5d3..bcac2098ea 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala @@ -20,10 +20,11 @@ import scala.reflect.{ClassTag, classTag} import scala.reflect.internal.Flags import org.scalajs.ir -import org.scalajs.ir.{Trees => js, Types => jstpe} +import org.scalajs.ir.{Trees => js, Types => jstpe, WellKnownNames => jswkn} import org.scalajs.ir.Names.LocalName import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.Trees.OptimizerHints +import org.scalajs.ir.Version.Unversioned import org.scalajs.nscplugin.util.ScopedVar import ScopedVar.withScopedVars @@ -47,7 +48,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { * * @param classSym symbol of the class we export for */ - def genMemberExports(classSym: Symbol): List[js.MemberDef] = { + def genMemberExports(classSym: Symbol): List[js.JSMethodPropDef] = { val allExports = classSym.info.members.filter(jsInterop.isExport(_)) val newlyDecldExports = if (classSym.superClass == NoSymbol) { @@ -66,7 +67,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { } def genJSClassDispatchers(classSym: Symbol, - dispatchMethodsNames: List[JSName]): List[js.MemberDef] = { + dispatchMethodsNames: List[JSName]): List[js.JSMethodPropDef] = { dispatchMethodsNames .map(genJSClassDispatcher(classSym, _)) } @@ -133,8 +134,8 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { (info, sym) } - (for { - (info, tups) <- exports.groupBy(_._1) + for { + (info, tups) <- stableGroupByWithoutHashCode(exports)(_._1) kind <- checkSameKind(tups) } yield { import ExportKind._ @@ -151,7 +152,8 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { case Constructor | Method => val methodDef = withNewLocalNameScope { - genExportMethod(tups.map(_._2), JSName.Literal(info.jsName), static = true) + genExportMethod(tups.map(_._2), JSName.Literal(info.jsName), static = true, + allowCallsiteInlineSingle = false) } js.TopLevelMethodExportDef(info.moduleID, methodDef) @@ -163,10 +165,10 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { val sym = checkSingleField(tups) js.TopLevelFieldExportDef(info.moduleID, info.jsName, encodeFieldSym(sym)) } - }).toList + } } - def genStaticExports(classSym: Symbol): List[js.MemberDef] = { + def genStaticExports(classSym: Symbol): (List[js.JSFieldDef], List[js.JSMethodPropDef]) = { val exports = (for { sym <- classSym.info.members info <- jsInterop.staticExportsOf(sym) @@ -174,10 +176,13 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { (info, sym) }).toList - (for { - (info, tups) <- exports.groupBy(_._1) + val fields = List.newBuilder[js.JSFieldDef] + val methodProps = List.newBuilder[js.JSMethodPropDef] + + for { + (info, tups) <- stableGroupByWithoutHashCode(exports)(_._1) kind <- checkSameKind(tups) - } yield { + } { def alts = tups.map(_._2) implicit val pos = info.pos @@ -186,12 +191,14 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { kind match { case Method => - genMemberExportOrDispatcher( - JSName.Literal(info.jsName), isProp = false, alts, static = true) + methodProps += genMemberExportOrDispatcher( + JSName.Literal(info.jsName), isProp = false, alts, static = true, + allowCallsiteInlineSingle = false) case Property => - genMemberExportOrDispatcher( - JSName.Literal(info.jsName), isProp = true, alts, static = true) + methodProps += genMemberExportOrDispatcher( + JSName.Literal(info.jsName), isProp = true, alts, static = true, + allowCallsiteInlineSingle = false) case Field => val sym = checkSingleField(tups) @@ -202,15 +209,17 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { .withMutable(true) val name = js.StringLiteral(info.jsName) val irTpe = genExposedFieldIRType(sym) - js.JSFieldDef(flags, name, irTpe) + fields += js.JSFieldDef(flags, name, irTpe) case kind => throw new AssertionError(s"unexpected static export kind: $kind") } - }).toList + } + + (fields.result(), methodProps.result()) } - private def genMemberExport(classSym: Symbol, name: TermName): js.MemberDef = { + private def genMemberExport(classSym: Symbol, name: TermName): js.JSMethodPropDef = { /* This used to be `.member(name)`, but it caused #3538, since we were * sometimes selecting mixin forwarders, whose type history does not go * far enough back in time to see varargs. We now explicitly exclude @@ -238,10 +247,11 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { s"Exported $kind $jsName conflicts with ${alts.head.fullName}") } - genMemberExportOrDispatcher(JSName.Literal(jsName), isProp, alts, static = false) + genMemberExportOrDispatcher(JSName.Literal(jsName), isProp, alts, + static = false, allowCallsiteInlineSingle = false) } - private def genJSClassDispatcher(classSym: Symbol, name: JSName): js.MemberDef = { + private def genJSClassDispatcher(classSym: Symbol, name: JSName): js.JSMethodPropDef = { val alts = classSym.info.members.toList.filter { sym => sym.isMethod && !sym.isBridge && @@ -264,24 +274,26 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { reporter.error(alts.head.pos, s"Conflicting properties and methods for ${classSym.fullName}::$name.") implicit val pos = alts.head.pos - js.JSPropertyDef(js.MemberFlags.empty, genExpr(name), None, None) + js.JSPropertyDef(js.MemberFlags.empty, genExpr(name), None, None)(Unversioned) } else { - genMemberExportOrDispatcher(name, isProp, alts, static = false) + genMemberExportOrDispatcher(name, isProp, alts, static = false, + allowCallsiteInlineSingle = true) } } def genMemberExportOrDispatcher(jsName: JSName, isProp: Boolean, - alts: List[Symbol], static: Boolean): js.MemberDef = { + alts: List[Symbol], static: Boolean, + allowCallsiteInlineSingle: Boolean): js.JSMethodPropDef = { withNewLocalNameScope { if (isProp) - genExportProperty(alts, jsName, static) + genExportProperty(alts, jsName, static, allowCallsiteInlineSingle) else - genExportMethod(alts, jsName, static) + genExportMethod(alts, jsName, static, allowCallsiteInlineSingle) } } private def genExportProperty(alts: List[Symbol], jsName: JSName, - static: Boolean): js.JSPropertyDef = { + static: Boolean, allowCallsiteInlineSingle: Boolean): js.JSPropertyDef = { assert(!alts.isEmpty, s"genExportProperty with empty alternatives for $jsName") @@ -301,7 +313,8 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { reportCannotDisambiguateError(jsName, alts) val getterBody = getter.headOption.map { getterSym => - genApplyForSym(new FormalArgsRegistry(0, false), getterSym, static) + genApplyForSym(new FormalArgsRegistry(0, false), getterSym, static, + inline = allowCallsiteInlineSingle) } val setterArgAndBody = { @@ -310,20 +323,29 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { } else { val formalArgsRegistry = new FormalArgsRegistry(1, false) val (List(arg), None) = formalArgsRegistry.genFormalArgs() - val body = genOverloadDispatchSameArgc(jsName, formalArgsRegistry, - alts = setters.map(new ExportedSymbol(_, static)), jstpe.AnyType, - paramIndex = 0) + + val body = { + if (setters.size == 1) { + genApplyForSym(formalArgsRegistry, setters.head, static, + inline = allowCallsiteInlineSingle) + } else { + genOverloadDispatchSameArgc(jsName, formalArgsRegistry, + alts = setters.map(new ExportedSymbol(_, static)), jstpe.AnyType, + paramIndex = 0) + } + } + Some((arg, body)) } } - js.JSPropertyDef(flags, genExpr(jsName), getterBody, setterArgAndBody) + js.JSPropertyDef(flags, genExpr(jsName), getterBody, setterArgAndBody)(Unversioned) } /** generates the exporter function (i.e. exporter for non-properties) for * a given name */ private def genExportMethod(alts0: List[Symbol], jsName: JSName, - static: Boolean): js.JSMethodDef = { + static: Boolean, allowCallsiteInlineSingle: Boolean): js.JSMethodDef = { assert(alts0.nonEmpty, "need at least one alternative to generate exporter method") @@ -348,11 +370,23 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { val overloads = alts.map(new ExportedSymbol(_, static)) - val (formalArgs, restParam, body) = - genOverloadDispatch(jsName, overloads, jstpe.AnyType) + val (formalArgs, restParam, body) = { + if (overloads.size == 1) { + val trg = overloads.head + val minArgc = trg.params.lastIndexWhere(p => !p.hasDefault && !p.repeated) + 1 + val formalArgsRegistry = new FormalArgsRegistry(minArgc, + needsRestParam = trg.params.size > minArgc) + val body = genApplyForSym(formalArgsRegistry, trg.sym, static, + inline = allowCallsiteInlineSingle) + val (formalArgs, restParam) = formalArgsRegistry.genFormalArgs() + (formalArgs, restParam, body) + } else { + genOverloadDispatch(jsName, overloads, jstpe.AnyType) + } + } js.JSMethodDef(flags, genExpr(jsName), formalArgs, restParam, body)( - OptimizerHints.empty, None) + OptimizerHints.empty, Unversioned) } def genOverloadDispatch(jsName: JSName, alts: List[Exported], tpe: jstpe.Type)( @@ -393,12 +427,28 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { } yield (argc, method) } - // Create a map: argCount -> methods (methods may appear multiple times) - val methodByArgCount = - methodArgCounts.groupBy(_._1).map(kv => kv._1 -> kv._2.map(_._2).toSet) + /** Like groupBy, but returns a sorted List instead of an unordered Map. */ + def sortedGroupBy[A, K, O](xs: List[A])(grouper: A => K)( + sorter: ((K, List[A])) => O)(implicit ord: Ordering[O]): List[(K, List[A])] = { + xs.groupBy(grouper).toList.sortBy(sorter) + } + + /* Create tuples: (argCount, methods). + * Methods may appear multiple times. + * + * The method lists preserve the order out of `methodArgCounts`, so if + * two such lists contain the same set of methods, they are equal. + * + * The resulting list is sorted by argCount. This is both for stability + * and because we then rely on the fact that the head is the minimum. + */ + val methodByArgCount: List[(Int, List[Exported])] = { + sortedGroupBy(methodArgCounts)(_._1)(_._1) // sort by the Int + .map(kv => kv._1 -> kv._2.map(_._2)) // preserves the relative order of the Exported's + } // Create the formal args registry - val minArgc = methodByArgCount.keys.min + val minArgc = methodByArgCount.head._1 // it is sorted by argCount, so the head is the minimum val hasVarArg = varArgMeths.nonEmpty val needsRestParam = maxArgc != minArgc || hasVarArg val formalArgsRegistry = new FormalArgsRegistry(minArgc, needsRestParam) @@ -406,32 +456,48 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { // List of formal parameters val (formalArgs, restParam) = formalArgsRegistry.genFormalArgs() - // Create tuples: (methods, argCounts). This will be the cases we generate - val caseDefinitions = - methodByArgCount.groupBy(_._2).map(kv => kv._1 -> kv._2.keySet) + /* Create tuples: (methods, argCounts). These will be the cases we generate. + * + * Within each element, the argCounts are sorted. (This sort order is + * inherited from the sort order of `methodByArgCount`.) + * + * For stability, the list as a whole is sorted by the minimum (head) of + * argCounts. + */ + val caseDefinitions: List[(List[Exported], List[Int])] = { + sortedGroupBy(methodByArgCount)(_._2)(_._2.head._1) // sort by the minimum of the Ints + .map(kv => kv._1 -> kv._2.map(_._1)) // the Ints are still sorted from `methodByArgCount` + } // Verify stuff about caseDefinitions assert({ - val argcs = caseDefinitions.values.flatten.toList + val argcs = caseDefinitions.map(_._2).flatten argcs == argcs.distinct && argcs.forall(_ <= maxArgc) }, "every argc should appear only once and be lower than max") + /* We will avoid generating cases where the set of methods is exactly the + * the set of varargs methods. Since all the `Exported` in `alts`, and + * hence in `varArgMeths` and `methods`, are distinct, we can do + * something faster than converting both sides to sets. + */ + def isSameAsVarArgMethods(methods: List[Exported]): Boolean = + methods.size == varArgMeths.size && methods.forall(varArgMeths.contains(_)) + // Generate a case block for each (methods, argCounts) tuple - val cases = for { + val cases: List[(List[js.IntLiteral], js.Tree)] = for { (methods, argcs) <- caseDefinitions - if methods.nonEmpty && argcs.nonEmpty - - // exclude default case we're generating anyways for varargs - if methods != varArgMeths.toSet + if methods.nonEmpty && argcs.nonEmpty && !isSameAsVarArgMethods(methods) + } yield { + val argcAlternatives = argcs.map(argc => js.IntLiteral(argc - minArgc)) - // body of case to disambiguates methods with current count - caseBody = genOverloadDispatchSameArgc(jsName, formalArgsRegistry, - methods.toList, tpe, paramIndex = 0, Some(argcs.min)) + // body of case to disambiguate methods with current count + val maxUsableArgc = argcs.head // i.e., the *minimum* of the argcs here + val caseBody = genOverloadDispatchSameArgc(jsName, formalArgsRegistry, + methods, tpe, paramIndex = 0, Some(maxUsableArgc)) - // argc in reverse order - argcList = argcs.toList.sortBy(- _) - } yield (argcList.map(argc => js.IntLiteral(argc - minArgc)), caseBody) + (argcAlternatives, caseBody) + } def defaultCase = { if (!hasVarArg) { @@ -453,7 +519,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { val restArgRef = formalArgsRegistry.genRestArgRef() js.Match( js.AsInstanceOf(js.JSSelect(restArgRef, js.StringLiteral("length")), jstpe.IntType), - cases.toList, defaultCase)(tpe) + cases, defaultCase)(tpe) } } @@ -486,7 +552,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { reportCannotDisambiguateError(jsName, alts.map(_.sym)) js.Undefined() } else { - val altsByTypeTest = groupByWithoutHashCode(alts) { exported => + val altsByTypeTest = stableGroupByWithoutHashCode(alts) { exported => typeTestForTpe(exported.exportArgTypeAt(paramIndex)) } @@ -595,13 +661,13 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { * required. */ private def genApplyForSym(formalArgsRegistry: FormalArgsRegistry, - sym: Symbol, static: Boolean): js.Tree = { + sym: Symbol, static: Boolean, inline: Boolean): js.Tree = { if (isNonNativeJSClass(currentClassSym) && sym.owner != currentClassSym.get) { assert(!static, s"nonsensical JS super call in static export of $sym") genApplyForSymJSSuperCall(formalArgsRegistry, sym) } else { - genApplyForSymNonJSSuperCall(formalArgsRegistry, sym, static) + genApplyForSymNonJSSuperCall(formalArgsRegistry, sym, static, inline) } } @@ -618,13 +684,13 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { val superClass = { val superClassSym = currentClassSym.superClass if (isNestedJSClass(superClassSym)) { - js.VarRef(js.LocalIdent(JSSuperClassParamName))(jstpe.AnyType) + js.VarRef(JSSuperClassParamName)(jstpe.AnyType) } else { js.LoadJSConstructor(encodeClassName(superClassSym)) } } - val receiver = js.This()(jstpe.AnyType) + val receiver = js.This()(currentThisType) val nameString = genExpr(jsNameOf(sym)) if (jsInterop.isJSGetter(sym)) { @@ -643,7 +709,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { private def genApplyForSymNonJSSuperCall( formalArgsRegistry: FormalArgsRegistry, sym: Symbol, - static: Boolean): js.Tree = { + static: Boolean, inline: Boolean): js.Tree = { implicit val pos = sym.pos val varDefs = new mutable.ListBuffer[js.VarDef] @@ -658,7 +724,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { val builtVarDefs = varDefs.result() - val jsResult = genResult(sym, builtVarDefs.map(_.ref), static) + val jsResult = genResult(sym, builtVarDefs.map(_.ref), static, inline) js.Block(builtVarDefs :+ jsResult) } @@ -708,7 +774,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { * dispatchers, we know the default getter is on `this`. This applies * to both top-level and nested classes. */ - (owner, js.This()(encodeClassType(owner))) + (owner, js.This()(currentThisType)) } else if (isNested) { assert(captures.size == 1, s"expected exactly one capture got $captures ($sym at $pos)") @@ -780,7 +846,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { // Pass previous arguments to defaultGetter val defaultGetterArgs = previousArgsValues(defaultGetter.tpe.params.size) - if (isJSType(trgSym)) { + val callGetter = if (isJSType(trgSym)) { if (isNonNativeJSClass(defaultGetter.owner)) { if (defaultGetter.hasAnnotation(JSOptionalAnnotation)) js.Undefined() @@ -802,33 +868,38 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { } else { genApplyMethod(trgTree, defaultGetter, defaultGetterArgs) } + + // #4684 If the getter returns void, we must "box" it by returning undefined + if (callGetter.tpe == jstpe.VoidType) + js.Block(callGetter, js.Undefined()) + else + callGetter } /** Generate the final forwarding call to the exported method. */ private def genResult(sym: Symbol, args: List[js.Tree], - static: Boolean)(implicit pos: Position): js.Tree = { + static: Boolean, inline: Boolean)(implicit pos: Position): js.Tree = { def receiver = { if (static) genLoadModule(sym.owner) - else if (sym.owner == ObjectClass) - js.This()(jstpe.ClassType(ir.Names.ObjectClass)) else - js.This()(encodeClassType(sym.owner)) + js.This()(currentThisType) } if (isNonNativeJSClass(currentClassSym)) { assert(sym.owner == currentClassSym.get, sym.fullName) - ensureResultBoxed(genApplyJSClassMethod(receiver, sym, args), sym) + ensureResultBoxed(genApplyJSClassMethod(receiver, sym, args, inline = inline), sym) } else { if (sym.isClassConstructor) genNew(currentClassSym, sym, args) else if (sym.isPrivate) - ensureResultBoxed(genApplyMethodStatically(receiver, sym, args), sym) + ensureResultBoxed(genApplyMethodStatically(receiver, sym, args, inline = inline), sym) else - ensureResultBoxed(genApplyMethod(receiver, sym, args), sym) + ensureResultBoxed(genApplyMethod(receiver, sym, args, inline = inline), sym) } } + // Note: GenJSCode creates an anonymous subclass of Exported for JS class constructors. abstract class Exported(val sym: Symbol, // Parameters participating in overload resolution. val params: immutable.IndexedSeq[JSParamInfo]) { @@ -853,13 +924,13 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { private class ExportedSymbol(sym: Symbol, static: Boolean) extends Exported(sym, jsParamInfos(sym).toIndexedSeq) { def genBody(formalArgsRegistry: FormalArgsRegistry): js.Tree = - genApplyForSym(formalArgsRegistry, sym, static) + genApplyForSym(formalArgsRegistry, sym, static, inline = false) } } private sealed abstract class RTTypeTest - private case class PrimitiveTypeTest(tpe: jstpe.Type, rank: Int) + private case class PrimitiveTypeTest(tpe: jstpe.PrimType, rank: Int) extends RTTypeTest // scalastyle:off equals.hash.code @@ -899,12 +970,10 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { InstanceOfTypeTest(tpe.valueClazz.typeConstructor) case _ => - import org.scalajs.ir.Names - (toIRType(tpe): @unchecked) match { - case jstpe.AnyType => NoTypeTest + case jstpe.AnyType | jstpe.AnyNotNullType => NoTypeTest - case jstpe.NoType => PrimitiveTypeTest(jstpe.UndefType, 0) + case jstpe.VoidType => PrimitiveTypeTest(jstpe.UndefType, 0) case jstpe.BooleanType => PrimitiveTypeTest(jstpe.BooleanType, 1) case jstpe.CharType => PrimitiveTypeTest(jstpe.CharType, 2) case jstpe.ByteType => PrimitiveTypeTest(jstpe.ByteType, 3) @@ -914,36 +983,44 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { case jstpe.FloatType => PrimitiveTypeTest(jstpe.FloatType, 7) case jstpe.DoubleType => PrimitiveTypeTest(jstpe.DoubleType, 8) - case jstpe.ClassType(Names.BoxedUnitClass) => PrimitiveTypeTest(jstpe.UndefType, 0) - case jstpe.ClassType(Names.BoxedStringClass) => PrimitiveTypeTest(jstpe.StringType, 9) - case jstpe.ClassType(_) => InstanceOfTypeTest(tpe) + case jstpe.ClassType(jswkn.BoxedUnitClass, _) => PrimitiveTypeTest(jstpe.UndefType, 0) + case jstpe.ClassType(jswkn.BoxedStringClass, _) => PrimitiveTypeTest(jstpe.StringType, 9) + case jstpe.ClassType(_, _) => InstanceOfTypeTest(tpe) - case jstpe.ArrayType(_) => InstanceOfTypeTest(tpe) + case jstpe.ArrayType(_, _) => InstanceOfTypeTest(tpe) } } } - // Group-by that does not rely on hashCode(), only equals() - O(n²) - private def groupByWithoutHashCode[A, B]( + /** Stable group-by that does not rely on hashCode(), only equals() - O(n²). + * + * In addition to preserving the relative order of elements in the value + * lists (like `groupBy`), this stable group-by also preserves the relative + * order of they keys, by their first appearance in the collection. + */ + private def stableGroupByWithoutHashCode[A, B]( coll: List[A])(f: A => B): List[(B, List[A])] = { - import scala.collection.mutable.ArrayBuffer - val m = new ArrayBuffer[(B, List[A])] + import scala.collection.mutable.{ArrayBuffer, Builder} + + val m = new ArrayBuffer[(B, Builder[A, List[A]])] m.sizeHint(coll.length) for (elem <- coll) { val key = f(elem) val index = m.indexWhere(_._1 == key) - if (index < 0) m += ((key, List(elem))) - else m(index) = (key, elem :: m(index)._2) + if (index < 0) + m += ((key, List.newBuilder[A] += elem)) + else + m(index)._2 += elem } - m.toList + m.toList.map(kv => kv._1 -> kv._2.result()) } private def genThrowTypeError(msg: String = "No matching overload")( implicit pos: Position): js.Tree = { - js.Throw(js.StringLiteral(msg)) + js.UnaryOp(js.UnaryOp.Throw, js.StringLiteral(msg)) } class FormalArgsRegistry(minArgc: Int, needsRestParam: Boolean) { @@ -974,7 +1051,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { def genArgRef(index: Int)(implicit pos: Position): js.Tree = { if (index < minArgc) - js.VarRef(js.LocalIdent(fixedParamNames(index)))(jstpe.AnyType) + js.VarRef(fixedParamNames(index))(jstpe.AnyType) else js.JSSelect(genRestArgRef(), js.IntLiteral(index - minArgc)) } @@ -994,16 +1071,16 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { def genRestArgRef()(implicit pos: Position): js.Tree = { assert(needsRestParam, s"trying to generate a reference to non-existent rest param at $pos") - js.VarRef(js.LocalIdent(restParamName))(jstpe.AnyType) + js.VarRef(restParamName)(jstpe.AnyType) } def genAllArgsRefsForForwarder()(implicit pos: Position): List[js.TreeOrJSSpread] = { val fixedArgRefs = fixedParamNames.toList.map { paramName => - js.VarRef(js.LocalIdent(paramName))(jstpe.AnyType) + js.VarRef(paramName)(jstpe.AnyType) } if (needsRestParam) { - val restArgRef = js.VarRef(js.LocalIdent(restParamName))(jstpe.AnyType) + val restArgRef = js.VarRef(restParamName)(jstpe.AnyType) fixedArgRefs :+ js.JSSpread(restArgRef) } else { fixedArgRefs diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala index c0423f9485..58c4910233 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSDefinitions.scala @@ -37,6 +37,8 @@ trait JSDefinitions { lazy val JavaLangVoidClass = getRequiredClass("java.lang.Void") + lazy val JavaLangReflectArrayModClass = getModuleIfDefined("java.lang.reflect.Array").moduleClass + lazy val BoxedUnitModClass = BoxedUnitModule.moduleClass lazy val ScalaJSJSPackageModule = getPackageObject("scala.scalajs.js") @@ -45,6 +47,8 @@ trait JSDefinitions { lazy val JSPackage_native = getMemberMethod(ScalaJSJSPackageModule, newTermName("native")) lazy val JSPackage_undefined = getMemberMethod(ScalaJSJSPackageModule, newTermName("undefined")) lazy val JSPackage_dynamicImport = getMemberMethod(ScalaJSJSPackageModule, newTermName("dynamicImport")) + lazy val JSPackage_async = getMemberMethod(ScalaJSJSPackageModule, newTermName("async")) + lazy val JSPackage_await = getMemberMethod(ScalaJSJSPackageModule, newTermName("await")) lazy val JSNativeAnnotation = getRequiredClass("scala.scalajs.js.native") @@ -70,8 +74,7 @@ trait JSDefinitions { lazy val JSImportAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSImport") lazy val JSGlobalAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSGlobal") lazy val JSGlobalScopeAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSGlobalScope") - - lazy val JavaDefaultMethodAnnotation = getRequiredClass("scala.scalajs.js.annotation.JavaDefaultMethod") + lazy val JSOperatorAnnotation = getRequiredClass("scala.scalajs.js.annotation.JSOperator") lazy val JSImportNamespaceObject = getRequiredModule("scala.scalajs.js.annotation.JSImport.Namespace") @@ -107,11 +110,18 @@ trait JSDefinitions { lazy val Special_instanceof = getMemberMethod(SpecialPackageModule, newTermName("instanceof")) lazy val Special_delete = getMemberMethod(SpecialPackageModule, newTermName("delete")) lazy val Special_forin = getMemberMethod(SpecialPackageModule, newTermName("forin")) + lazy val Special_throw = getMemberMethod(SpecialPackageModule, newTermName("throw")) + lazy val Special_tryCatch = getMemberMethod(SpecialPackageModule, newTermName("tryCatch")) + lazy val Special_wrapAsThrowable = getMemberMethod(SpecialPackageModule, newTermName("wrapAsThrowable")) + lazy val Special_unwrapFromThrowable = getMemberMethod(SpecialPackageModule, newTermName("unwrapFromThrowable")) lazy val Special_debugger = getMemberMethod(SpecialPackageModule, newTermName("debugger")) + lazy val WasmJSPIModule = getRequiredModule("scala.scalajs.js.wasm.JSPI") + lazy val WasmJSPIModuleClass = WasmJSPIModule.moduleClass + lazy val WasmJSPI_allowOrphanJSAwaitModule = getMemberModule(WasmJSPIModuleClass, newTermName("allowOrphanJSAwait")) + lazy val WasmJSPI_allowOrphanJSAwaitModuleClass = WasmJSPI_allowOrphanJSAwaitModule.moduleClass + lazy val RuntimePackageModule = getPackageObject("scala.scalajs.runtime") - lazy val Runtime_wrapJavaScriptException = getMemberMethod(RuntimePackageModule, newTermName("wrapJavaScriptException")) - lazy val Runtime_unwrapJavaScriptException = getMemberMethod(RuntimePackageModule, newTermName("unwrapJavaScriptException")) lazy val Runtime_toScalaVarArgs = getMemberMethod(RuntimePackageModule, newTermName("toScalaVarArgs")) lazy val Runtime_toJSVarArgs = getMemberMethod(RuntimePackageModule, newTermName("toJSVarArgs")) lazy val Runtime_constructorOf = getMemberMethod(RuntimePackageModule, newTermName("constructorOf")) @@ -124,6 +134,14 @@ trait JSDefinitions { lazy val Runtime_identityHashCode = getMemberMethod(RuntimePackageModule, newTermName("identityHashCode")) lazy val Runtime_dynamicImport = getMemberMethod(RuntimePackageModule, newTermName("dynamicImport")) + lazy val LinkingInfoModule = getRequiredModule("scala.scalajs.LinkingInfo") + lazy val LinkingInfo_linkTimeIf = getMemberMethod(LinkingInfoModule, newTermName("linkTimeIf")) + lazy val LinkingInfo_linkTimePropertyBoolean = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyBoolean")) + lazy val LinkingInfo_linkTimePropertyInt = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyInt")) + lazy val LinkingInfo_linkTimePropertyString = getMemberMethod(LinkingInfoModule, newTermName("linkTimePropertyString")) + + lazy val LinkTimePropertyAnnotation = getRequiredClass("scala.scalajs.annotation.linkTimeProperty") + lazy val DynamicImportThunkClass = getRequiredClass("scala.scalajs.runtime.DynamicImportThunk") lazy val DynamicImportThunkClass_apply = getMemberMethod(DynamicImportThunkClass, nme.apply) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala index 2d7105cd91..263f1def30 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSEncoding.scala @@ -17,8 +17,8 @@ import scala.collection.mutable import scala.tools.nsc._ import org.scalajs.ir -import org.scalajs.ir.{Trees => js, Types => jstpe} -import org.scalajs.ir.Names.{LocalName, LabelName, FieldName, SimpleMethodName, MethodName, ClassName} +import org.scalajs.ir.{Trees => js, Types => jstpe, WellKnownNames => jswkn} +import org.scalajs.ir.Names.{LocalName, LabelName, SimpleFieldName, FieldName, SimpleMethodName, MethodName, ClassName} import org.scalajs.ir.OriginalName import org.scalajs.ir.OriginalName.NoOriginalName import org.scalajs.ir.UTF8String @@ -91,7 +91,7 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { case None => inner case Some(labelName) => - js.Labeled(js.LabelIdent(labelName), tpe, inner) + js.Labeled(labelName, tpe, inner) } } } @@ -151,35 +151,33 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { private def freshLabelName(base: LabelName): LabelName = freshNameGeneric(base, usedLabelNames)(_.withSuffix(_)) - private def freshLabelName(base: String): LabelName = + def freshLabelName(base: String): LabelName = freshLabelName(LabelName(base)) - def freshLabelIdent(base: String)(implicit pos: ir.Position): js.LabelIdent = - js.LabelIdent(freshLabelName(base)) - private def labelSymbolName(sym: Symbol): LabelName = labelSymbolNames.getOrElseUpdate(sym, freshLabelName(sym.name.toString)) - def getEnclosingReturnLabel()(implicit pos: ir.Position): js.LabelIdent = { + def getEnclosingReturnLabel()(implicit pos: Position): LabelName = { val box = returnLabelName.get if (box == null) throw new IllegalStateException(s"No enclosing returnable scope at $pos") if (box.value.isEmpty) box.value = Some(freshLabelName("_return")) - js.LabelIdent(box.value.get) + box.value.get } // Encoding methods ---------------------------------------------------------- - def encodeLabelSym(sym: Symbol)(implicit pos: Position): js.LabelIdent = { + def encodeLabelSym(sym: Symbol): LabelName = { require(sym.isLabel, "encodeLabelSym called with non-label symbol: " + sym) - js.LabelIdent(labelSymbolName(sym)) + labelSymbolName(sym) } def encodeFieldSym(sym: Symbol)(implicit pos: Position): js.FieldIdent = { requireSymIsField(sym) - val name = sym.name.dropLocal - js.FieldIdent(FieldName(name.toString())) + val className = encodeClassName(sym.owner) + val simpleName = SimpleFieldName(sym.name.dropLocal.toString()) + js.FieldIdent(FieldName(className, simpleName)) } def encodeFieldSymAsStringLiteral(sym: Symbol)( @@ -239,7 +237,7 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { def encodeDynamicImportForwarderIdent(params: List[Symbol])( implicit pos: Position): js.MethodIdent = { val paramTypeRefs = params.map(sym => paramOrResultTypeRef(sym.tpe)) - val resultTypeRef = jstpe.ClassRef(ir.Names.ObjectClass) + val resultTypeRef = jstpe.ClassRef(jswkn.ObjectClass) val methodName = MethodName(dynamicImportForwarderSimpleName, paramTypeRefs, resultTypeRef) @@ -255,7 +253,10 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { } } - def encodeLocalSym(sym: Symbol)(implicit pos: Position): js.LocalIdent = { + def encodeLocalSym(sym: Symbol)(implicit pos: Position): js.LocalIdent = + js.LocalIdent(encodeLocalSymName(sym)) + + def encodeLocalSymName(sym: Symbol): LocalName = { /* The isValueParameter case is necessary to work around an internal bug * of scalac: for some @varargs methods, the owner of some parameters is * the enclosing class rather the method, so !sym.owner.isClass fails. @@ -265,7 +266,7 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { require(sym.isValueParameter || (!sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule), "encodeLocalSym called with non-local symbol: " + sym) - js.LocalIdent(localSymbolName(sym)) + localSymbolName(sym) } def encodeClassType(sym: Symbol): jstpe.Type = { @@ -274,7 +275,7 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { else { assert(sym != definitions.ArrayClass, "encodeClassType() cannot be called with ArrayClass") - jstpe.ClassType(encodeClassName(sym)) + jstpe.ClassType(encodeClassName(sym), nullable = true) } } @@ -287,20 +288,20 @@ trait JSEncoding[G <: Global with Singleton] extends SubComponent { assert(!sym.isPrimitiveValueClass, s"Illegal encodeClassName(${sym.fullName}") if (sym == jsDefinitions.HackedStringClass) { - ir.Names.BoxedStringClass + jswkn.BoxedStringClass } else if (sym == jsDefinitions.HackedStringModClass) { BoxedStringModuleClassName } else if (sym == definitions.BoxedUnitClass || sym == jsDefinitions.BoxedUnitModClass) { // Rewire scala.runtime.BoxedUnit to java.lang.Void, as the IR expects // BoxedUnit$ is a JVM artifact - ir.Names.BoxedUnitClass + jswkn.BoxedUnitClass } else { ClassName(sym.fullName + (if (needsModuleClassSuffix(sym)) "$" else "")) } } def needsModuleClassSuffix(sym: Symbol): Boolean = - sym.isModuleClass && !sym.isJavaDefined && !isImplClass(sym) + sym.isModuleClass && !sym.isJavaDefined def originalNameOfLocal(sym: Symbol): OriginalName = { val irName = localSymbolName(sym) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala index 3d802614cd..dea4d5529d 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSGlobalAddons.scala @@ -185,10 +185,12 @@ trait JSGlobalAddons extends JSDefinitions sym.name match { case nme.apply => Call - case JSUnaryOpMethodName(code) if pc == 0 => + case JSUnaryOpMethodName(code, defaultsToOp) + if (defaultsToOp || sym.hasAnnotation(JSOperatorAnnotation)) && pc == 0 => UnaryOp(code) - case JSBinaryOpMethodName(code) if pc == 1 => + case JSBinaryOpMethodName(code, defaultsToOp) + if (defaultsToOp || sym.hasAnnotation(JSOperatorAnnotation)) && pc == 1 => BinaryOp(code) case _ => @@ -208,49 +210,51 @@ trait JSGlobalAddons extends JSDefinitions of(sym) == Call } - private object JSUnaryOpMethodName { - private val map = Map[Name, js.JSUnaryOp.Code]( - nme.UNARY_+ -> js.JSUnaryOp.+, - nme.UNARY_- -> js.JSUnaryOp.-, - nme.UNARY_~ -> js.JSUnaryOp.~, - nme.UNARY_! -> js.JSUnaryOp.! + object JSUnaryOpMethodName { + private val map = Map[Name, (js.JSUnaryOp.Code, Boolean)]( + nme.UNARY_+ -> (js.JSUnaryOp.+, true), + nme.UNARY_- -> (js.JSUnaryOp.-, true), + nme.UNARY_~ -> (js.JSUnaryOp.~, true), + nme.UNARY_! -> (js.JSUnaryOp.!, true) ) /* We use Name instead of TermName to work around * https://github.com/scala/bug/issues/11534 */ - def unapply(name: Name): Option[js.JSUnaryOp.Code] = + def unapply(name: Name): Option[(js.JSUnaryOp.Code, Boolean)] = map.get(name) } - private object JSBinaryOpMethodName { - private val map = Map[Name, js.JSBinaryOp.Code]( - nme.ADD -> js.JSBinaryOp.+, - nme.SUB -> js.JSBinaryOp.-, - nme.MUL -> js.JSBinaryOp.*, - nme.DIV -> js.JSBinaryOp./, - nme.MOD -> js.JSBinaryOp.%, - - nme.LSL -> js.JSBinaryOp.<<, - nme.ASR -> js.JSBinaryOp.>>, - nme.LSR -> js.JSBinaryOp.>>>, - nme.OR -> js.JSBinaryOp.|, - nme.AND -> js.JSBinaryOp.&, - nme.XOR -> js.JSBinaryOp.^, - - nme.LT -> js.JSBinaryOp.<, - nme.LE -> js.JSBinaryOp.<=, - nme.GT -> js.JSBinaryOp.>, - nme.GE -> js.JSBinaryOp.>=, - - nme.ZAND -> js.JSBinaryOp.&&, - nme.ZOR -> js.JSBinaryOp.|| + object JSBinaryOpMethodName { + private val map = Map[Name, (js.JSBinaryOp.Code, Boolean)]( + nme.ADD -> (js.JSBinaryOp.+, true), + nme.SUB -> (js.JSBinaryOp.-, true), + nme.MUL -> (js.JSBinaryOp.*, true), + nme.DIV -> (js.JSBinaryOp./, true), + nme.MOD -> (js.JSBinaryOp.%, true), + + nme.LSL -> (js.JSBinaryOp.<<, true), + nme.ASR -> (js.JSBinaryOp.>>, true), + nme.LSR -> (js.JSBinaryOp.>>>, true), + nme.OR -> (js.JSBinaryOp.|, true), + nme.AND -> (js.JSBinaryOp.&, true), + nme.XOR -> (js.JSBinaryOp.^, true), + + nme.LT -> (js.JSBinaryOp.<, true), + nme.LE -> (js.JSBinaryOp.<=, true), + nme.GT -> (js.JSBinaryOp.>, true), + nme.GE -> (js.JSBinaryOp.>=, true), + + nme.ZAND -> (js.JSBinaryOp.&&, true), + nme.ZOR -> (js.JSBinaryOp.||, true), + + global.encode("**") -> (js.JSBinaryOp.**, false) ) /* We use Name instead of TermName to work around * https://github.com/scala/bug/issues/11534 */ - def unapply(name: Name): Option[js.JSBinaryOp.Code] = + def unapply(name: Name): Option[(js.JSBinaryOp.Code, Boolean)] = map.get(name) } diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala b/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala index ade5ab2c2c..cf6f896453 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/JSPrimitives.scala @@ -51,22 +51,30 @@ abstract class JSPrimitives { final val JS_IMPORT = JS_NEW_TARGET + 1 // js.import.apply(specifier) final val JS_IMPORT_META = JS_IMPORT + 1 // js.import.meta - final val CONSTRUCTOROF = JS_IMPORT_META + 1 // runtime.constructorOf(clazz) + final val JS_ASYNC = JS_IMPORT_META + 1 // js.async + final val JS_AWAIT = JS_ASYNC + 1 // js.await + + final val CONSTRUCTOROF = JS_AWAIT + 1 // runtime.constructorOf(clazz) final val CREATE_INNER_JS_CLASS = CONSTRUCTOROF + 1 // runtime.createInnerJSClass final val CREATE_LOCAL_JS_CLASS = CREATE_INNER_JS_CLASS + 1 // runtime.createLocalJSClass final val WITH_CONTEXTUAL_JS_CLASS_VALUE = CREATE_LOCAL_JS_CLASS + 1 // runtime.withContextualJSClassValue - final val LINKING_INFO = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.linkingInfo - final val IDENTITY_HASH_CODE = LINKING_INFO + 1 // runtime.identityHashCode + final val IDENTITY_HASH_CODE = WITH_CONTEXTUAL_JS_CLASS_VALUE + 1 // runtime.identityHashCode final val DYNAMIC_IMPORT = IDENTITY_HASH_CODE + 1 // runtime.dynamicImport - final val STRICT_EQ = DYNAMIC_IMPORT + 1 // js.special.strictEquals - final val IN = STRICT_EQ + 1 // js.special.in - final val INSTANCEOF = IN + 1 // js.special.instanceof - final val DELETE = INSTANCEOF + 1 // js.special.delete - final val FORIN = DELETE + 1 // js.special.forin - final val DEBUGGER = FORIN + 1 // js.special.debugger - - final val LastJSPrimitiveCode = DEBUGGER + final val STRICT_EQ = DYNAMIC_IMPORT + 1 // js.special.strictEquals + final val IN = STRICT_EQ + 1 // js.special.in + final val INSTANCEOF = IN + 1 // js.special.instanceof + final val DELETE = INSTANCEOF + 1 // js.special.delete + final val FORIN = DELETE + 1 // js.special.forin + final val JS_THROW = FORIN + 1 // js.special.throw + final val JS_TRY_CATCH = JS_THROW + 1 // js.special.tryCatch + final val WRAP_AS_THROWABLE = JS_TRY_CATCH + 1 // js.special.wrapAsThrowable + final val UNWRAP_FROM_THROWABLE = WRAP_AS_THROWABLE + 1 // js.special.unwrapFromThrowable + final val DEBUGGER = UNWRAP_FROM_THROWABLE + 1 // js.special.debugger + final val LINKTIME_IF = DEBUGGER + 1 // LinkingInfo.linkTimeIf + final val LINKTIME_PROPERTY = LINKTIME_IF + 1 // LinkingInfo.linkTimePropertyXXX + + final val LastJSPrimitiveCode = LINKTIME_PROPERTY /** Initialize the map of primitive methods (for GenJSCode) */ def init(): Unit = initWithPrimitives(addPrimitive) @@ -92,6 +100,8 @@ abstract class JSPrimitives { addPrimitive(JSPackage_typeOf, TYPEOF) addPrimitive(JSPackage_native, JS_NATIVE) + addPrimitive(JSPackage_async, JS_ASYNC) + addPrimitive(JSPackage_await, JS_AWAIT) addPrimitive(BoxedUnit_UNIT, UNITVAL) @@ -105,7 +115,6 @@ abstract class JSPrimitives { addPrimitive(Runtime_createLocalJSClass, CREATE_LOCAL_JS_CLASS) addPrimitive(Runtime_withContextualJSClassValue, WITH_CONTEXTUAL_JS_CLASS_VALUE) - addPrimitive(Runtime_linkingInfo, LINKING_INFO) addPrimitive(Runtime_identityHashCode, IDENTITY_HASH_CODE) addPrimitive(Runtime_dynamicImport, DYNAMIC_IMPORT) @@ -114,7 +123,16 @@ abstract class JSPrimitives { addPrimitive(Special_instanceof, INSTANCEOF) addPrimitive(Special_delete, DELETE) addPrimitive(Special_forin, FORIN) + addPrimitive(Special_throw, JS_THROW) + addPrimitive(Special_tryCatch, JS_TRY_CATCH) + addPrimitive(Special_wrapAsThrowable, WRAP_AS_THROWABLE) + addPrimitive(Special_unwrapFromThrowable, UNWRAP_FROM_THROWABLE) addPrimitive(Special_debugger, DEBUGGER) + + addPrimitive(LinkingInfo_linkTimeIf, LINKTIME_IF) + addPrimitive(LinkingInfo_linkTimePropertyBoolean, LINKTIME_PROPERTY) + addPrimitive(LinkingInfo_linkTimePropertyInt, LINKTIME_PROPERTY) + addPrimitive(LinkingInfo_linkTimePropertyString, LINKTIME_PROPERTY) } def isJavaScriptPrimitive(code: Int): Boolean = diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala index e2ac9a6a88..e9217c04a7 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSExports.scala @@ -16,8 +16,8 @@ import scala.collection.mutable import scala.tools.nsc.Global -import org.scalajs.ir.Names.DefaultModuleID import org.scalajs.ir.Trees.TopLevelExportDef.isValidTopLevelExportName +import org.scalajs.ir.WellKnownNames.DefaultModuleID /** * Prepare export generation @@ -54,90 +54,54 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => private case class ExportInfo(jsName: String, destination: ExportDestination)(val pos: Position) - /** Generate the exporter for the given DefDef - * or ValDef (abstract val in class, val in trait or lazy val; - * these don't get DefDefs until the fields phase) + /** Generate exports for the given Symbol. * - * If this DefDef is a constructor, it is registered to be exported by - * GenJSCode instead and no trees are returned. + * * Registers top-level and static exports. + * * Returns (non-static) exporters for this symbol. */ - def genExportMember(baseSym: Symbol): List[Tree] = { - val clsSym = baseSym.owner + def genExport(sym: Symbol): List[Tree] = { + // Scala classes are never exported: Their constructors are. + val isScalaClass = sym.isClass && !sym.isTrait && !sym.isModuleClass && !isJSAny(sym) - val exports = exportsOf(baseSym) - - // Helper function for errors - def err(msg: String) = { - reporter.error(exports.head.pos, msg) - Nil - } + /* Filter case class apply (and unapply) to work around + * https://github.com/scala/bug/issues/8826 + */ + val isCaseApplyOrUnapplyParam = sym.isLocalToBlock && sym.owner.isCaseApplyOrUnapply - def memType = if (baseSym.isConstructor) "constructor" else "method" + /* Filter constructors of module classes: The module classes themselves will + * be exported. + */ + val isModuleClassCtor = sym.isConstructor && sym.owner.isModuleClass - if (exports.isEmpty) { - Nil - } else if (!hasLegalExportVisibility(baseSym)) { - err(s"You may only export public and protected ${memType}s") - } else if (baseSym.isMacro) { - err("You may not export a macro") - } else if (isJSAny(clsSym)) { - err(s"You may not export a $memType of a subclass of js.Any") - } else if (scalaPrimitives.isPrimitive(baseSym)) { - err("You may not export a primitive") - } else if (baseSym.isLocalToBlock) { - // We exclude case class apply (and unapply) to work around SI-8826 - if (clsSym.isCaseApplyOrUnapply) { - Nil - } else { - err("You may not export a local definition") - } - } else if (hasIllegalRepeatedParam(baseSym)) { - err(s"In an exported $memType, a *-parameter must come last " + - "(through all parameter lists)") - } else if (hasIllegalDefaultParam(baseSym)) { - err(s"In an exported $memType, all parameters with defaults " + - "must be at the end") - } else if (baseSym.isConstructor) { - // we can generate constructors entirely in the backend, since they - // do not need inheritance and such. But we want to check their sanity - // here by previous tests and the following ones. - if (checkClassOrModuleExports(clsSym, exports.head.pos)) - registerStaticAndTopLevelExports(baseSym, exports) - - Nil - } else { - assert(!baseSym.isBridge, - s"genExportMember called for bridge symbol $baseSym") + val exports = + if (isScalaClass || isCaseApplyOrUnapplyParam || isModuleClassCtor) Nil + else exportsOf(sym) - // Reset interface flag: Any trait will contain non-empty methods - clsSym.resetFlag(Flags.INTERFACE) + assert(exports.isEmpty || !sym.isBridge, + s"found exports for bridge symbol $sym. exports: $exports") - val (normalExports, topLevelAndStaticExports) = - exports.partition(_.destination == ExportDestination.Normal) + val (normalExports, topLevelAndStaticExports) = + exports.partition(_.destination == ExportDestination.Normal) - /* We can handle top level exports and static exports entirely in the - * backend. So just register them here. + /* We can handle top level exports and static exports entirely in the + * backend. So just register them here. + * + * For accessors, we need to apply some special logic to static and + * top-level exports: They actually apply to the *fields*, not to the + * accessors. + */ + if (sym.isAccessor && sym.accessed != NoSymbol) { + /* Only forward registration from the getter (not the setter) to avoid + * duplicate registration. */ - registerStaticAndTopLevelExports(baseSym, topLevelAndStaticExports) - - // Actually generate exporter methods - normalExports.flatMap(exp => genExportDefs(baseSym, exp.jsName, exp.pos)) + if (sym.isGetter) + registerStaticAndTopLevelExports(sym.accessed, topLevelAndStaticExports) + } else { + registerStaticAndTopLevelExports(sym, topLevelAndStaticExports) } - } - /** Check and (potentially) register a class or module for export. - * - * Note that Scala classes are never registered for export, their - * constructors are. - */ - def registerClassOrModuleExports(sym: Symbol): Unit = { - val exports = exportsOf(sym) - def isScalaClass = !sym.isModuleClass && !isJSAny(sym) - - if (exports.nonEmpty && checkClassOrModuleExports(sym, exports.head.pos) && - !isScalaClass) { - registerStaticAndTopLevelExports(sym, exports) - } + // For normal exports, generate exporter methods. + normalExports.flatMap(exp => genExportDefs(sym, exp.jsName, exp.pos)) } private def registerStaticAndTopLevelExports(sym: Symbol, @@ -159,56 +123,6 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => jsInterop.registerStaticExports(sym, static) } - /** Check a class or module for export. - * - * There are 2 ways that this method can be reached: - * - via `registerClassOrModuleExports` - * - via `genExportMember` (constructor of Scala class) - */ - private def checkClassOrModuleExports(sym: Symbol, errPos: Position): Boolean = { - val isMod = sym.isModuleClass - - def err(msg: String) = { - reporter.error(errPos, msg) - false - } - - def hasAnyNonPrivateCtor: Boolean = - sym.info.member(nme.CONSTRUCTOR).filter(!isPrivateMaybeWithin(_)).exists - - def isJSNative = sym.hasAnnotation(JSNativeAnnotation) - - if (sym.isTrait) { - err("You may not export a trait") - } else if (isJSNative) { - err("You may not export a native JS " + (if (isMod) "object" else "class")) - } else if (!hasLegalExportVisibility(sym)) { - err("You may only export public and protected " + - (if (isMod) "objects" else "classes")) - } else if (sym.isLocalToBlock) { - err("You may not export a local " + - (if (isMod) "object" else "class")) - } else if (!sym.isStatic) { - err("You may not export a nested " + - (if (isMod) "object" else s"class. $createFactoryInOuterClassHint")) - } else if (sym.isAbstractClass && !isJSAny(sym)) { - err("You may not export an abstract class") - } else if (!isMod && !hasAnyNonPrivateCtor) { - /* This test is only relevant for JS classes but doesn't hurt for Scala - * classes as we could not reach it if there were only private - * constructors. - */ - err("You may not export a class that has only private constructors") - } else { - true - } - } - - private def createFactoryInOuterClassHint = { - "Create an exported factory method in the outer class to work " + - "around this limitation." - } - /** retrieves the names a sym should be exported to from its annotations * * Note that for accessor symbols, the annotations of the accessed symbol @@ -223,23 +137,47 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => else sym } + val symOwner = + if (sym.isConstructor) sym.owner.owner + else sym.owner + // Annotations that are directly on the member val directAnnots = trgSym.annotations.filter( annot => isDirectMemberAnnot(annot.symbol)) - // Is this a member export (i.e. not a class or module export)? - val isMember = !sym.isClass && !sym.isConstructor - - // Annotations for this member on the whole unit + /* Annotations for this member on the whole unit + * + * Note that for top-level classes / modules this is always empty, because + * packages cannot have annotations. + */ val unitAnnots = { - if (isMember && sym.isPublic && !sym.isSynthetic) - sym.owner.annotations.filter(_.symbol == JSExportAllAnnotation) + val useExportAll = { + sym.isPublic && + !sym.isSynthetic && + !sym.isConstructor && + !sym.isTrait && + (!sym.isClass || sym.isModuleClass) + } + + if (useExportAll) + symOwner.annotations.filter(_.symbol == JSExportAllAnnotation) else Nil } + val allAnnots = { + val allAnnots0 = directAnnots ++ unitAnnots + + if (allAnnots0.nonEmpty) { + if (checkExportTarget(sym, allAnnots0.head.pos)) allAnnots0 + else Nil // prevent code generation from running to avoid crashes. + } else { + Nil + } + } + val allExportInfos = for { - annot <- directAnnots ++ unitAnnots + annot <- allAnnots } yield { val isExportAll = annot.symbol == JSExportAllAnnotation val isTopLevelExport = annot.symbol == JSExportTopLevelAnnotation @@ -256,12 +194,16 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => s"The argument to ${annot.symbol.name} must be a literal string") "dummy" } - } else if (sym.isConstructor) { - decodedFullName(sym.owner) - } else if (sym.isClass) { - decodedFullName(sym) } else { - sym.unexpandedName.decoded.stripSuffix("_=") + val nameBase = + (if (sym.isConstructor) sym.owner else sym).unexpandedName + + if (nme.isSetterName(nameBase) && !jsInterop.isJSSetter(sym)) { + reporter.error(annot.pos, "You must set an explicit name when " + + "exporting a non-setter with a name ending in _=") + } + + nameBase.decoded.stripSuffix("_=") } } @@ -285,10 +227,6 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => } } - // Enforce proper setter signature - if (jsInterop.isJSSetter(sym)) - checkSetterSignature(sym, annot.pos, exported = true) - // Enforce no __ in name if (!isTopLevelExport && name.contains("__")) { // Get position for error message @@ -298,12 +236,30 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => "An exported name may not contain a double underscore (`__`)") } - /* Illegal function application exports, i.e., method named 'apply' - * without an explicit export name. - */ - if (isMember && !hasExplicitName && sym.name == nme.apply) { - destination match { - case ExportDestination.Normal => + // Destination-specific restrictions + destination match { + case ExportDestination.Normal => + if (symOwner.hasPackageFlag) { + // Disallow @JSExport on top-level definitions. + reporter.error(annot.pos, + "@JSExport is forbidden on top-level objects and classes. " + + "Use @JSExportTopLevel instead.") + } + + // Make sure we do not override the default export of toString + def isIllegalToString = { + name == "toString" && sym.name != nme.toString_ && + sym.tpe.params.isEmpty && !jsInterop.isJSGetter(sym) + } + if (isIllegalToString) { + reporter.error(annot.pos, "You may not export a zero-argument " + + "method named other than 'toString' under the name 'toString'") + } + + /* Illegal function application exports, i.e., method named 'apply' + * without an explicit export name. + */ + if (!hasExplicitName && sym.name == nme.apply) { def shouldBeTolerated = { isExportAll && directAnnots.exists { annot => annot.symbol == JSExportAnnotation && @@ -321,42 +277,6 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => "application. Add @JSExport(\"apply\") to export under the " + "name apply.") } - - case _: ExportDestination.TopLevel => - throw new AssertionError( - "Found a top-level export without an explicit name at " + - annot.pos) - - case ExportDestination.Static => - reporter.error(annot.pos, - "A member cannot be exported to function application as " + - "static. Use @JSExportStatic(\"apply\") to export it under " + - "the name 'apply'.") - } - } - - val symOwner = - if (sym.isConstructor) sym.owner.owner - else sym.owner - - // Destination-specific restrictions - destination match { - case ExportDestination.Normal => - // Make sure we do not override the default export of toString - def isIllegalToString = { - isMember && name == "toString" && sym.name != nme.toString_ && - sym.tpe.params.isEmpty && !jsInterop.isJSGetter(sym) - } - if (isIllegalToString) { - reporter.error(annot.pos, "You may not export a zero-argument " + - "method named other than 'toString' under the name 'toString'") - } - - // Disallow @JSExport on non-members. - if (!isMember && !sym.isTrait) { - reporter.error(annot.pos, - "@JSExport is forbidden on objects and classes. " + - "Use @JSExportTopLevel instead.") } case _: ExportDestination.TopLevel => @@ -368,11 +288,8 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => "You may not export a getter or a setter to the top level") } - /* Disallow non-static methods. - * Note: Non-static classes have more specific error messages in - * checkClassOrModuleExports - */ - if (sym.isMethod && (!symOwner.isStatic || !symOwner.isModuleClass)) { + // Disallow non-static definitions. + if (!symOwner.isStatic || !symOwner.isModuleClass) { reporter.error(annot.pos, "Only static objects may export their members to the top level") } @@ -400,20 +317,23 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => "non-native JS class may export its members as static.") } - if (isMember) { - if (sym.isLazy) { - reporter.error(annot.pos, - "You may not export a lazy val as static") - } - } else { - if (sym.isTrait) { - reporter.error(annot.pos, - "You may not export a trait as static.") - } else { - reporter.error(annot.pos, - "Implementation restriction: cannot export a class or " + - "object as static") - } + if (sym.isLazy) { + reporter.error(annot.pos, + "You may not export a lazy val as static") + } + + // Illegal function application export + if (!hasExplicitName && sym.name == nme.apply) { + reporter.error(annot.pos, + "A member cannot be exported to function application as " + + "static. Use @JSExportStatic(\"apply\") to export it under " + + "the name 'apply'.") + } + + if (sym.isClass || sym.isConstructor) { + reporter.error(annot.pos, + "Implementation restriction: cannot export a class or " + + "object as static") } } @@ -431,110 +351,140 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => } .foreach(_ => reporter.warning(sym.pos, s"Found duplicate @JSExport")) - /* Filter out static exports of accessors (as they are not actually - * exported, their fields are). The above is only used to uniformly perform - * checks. + /* Check that no field is exported *twice* as static, nor both as static + * and as top-level (it is possible to export a field several times as + * top-level, though). */ - val filteredExports = if (!sym.isAccessor || sym.accessed == NoSymbol) { - allExportInfos - } else { - /* For accessors, we need to apply some special logic to static exports. - * When tested on accessors, they actually apply on *fields*, not on the - * accessors. We use the same code paths hereabove to uniformly perform - * relevant checks, but at the end of the day, we have to throw away the - * ExportInfo. - * However, we must make sure that no field is exported *twice* as static, - * nor both as static and as top-level (it is possible to export a field - * several times as top-level, though). - */ - val (topLevelAndStaticExportInfos, actualExportInfos) = - allExportInfos.partition(_.destination != ExportDestination.Normal) - - if (sym.isGetter) { - topLevelAndStaticExportInfos.find { - _.destination == ExportDestination.Static - }.foreach { firstStatic => - for { - duplicate <- topLevelAndStaticExportInfos - if duplicate ne firstStatic - } { - if (duplicate.destination == ExportDestination.Static) { - reporter.error(duplicate.pos, - "Fields (val or var) cannot be exported as static more " + - "than once") - } else { - reporter.error(duplicate.pos, - "Fields (val or var) cannot be exported both as static " + - "and at the top-level") - } - } - } + if (sym.isGetter) { + for { + firstStatic <- allExportInfos.find(_.destination == ExportDestination.Static).toList + duplicate <- allExportInfos + if duplicate ne firstStatic + } { + duplicate.destination match { + case ExportDestination.Normal => // OK - registerStaticAndTopLevelExports(sym.accessed, topLevelAndStaticExportInfos) - } + case ExportDestination.Static => + reporter.error(duplicate.pos, + "Fields (val or var) cannot be exported as static more " + + "than once") - actualExportInfos + case _: ExportDestination.TopLevel => + reporter.error(duplicate.pos, + "Fields (val or var) cannot be exported both as static " + + "and at the top-level") + } + } } - filteredExports.distinct + allExportInfos.distinct } - /** Just like sym.fullName, but does not encode components */ - private def decodedFullName(sym: Symbol): String = { - if (sym.isRoot || sym.isRootPackage || sym == NoSymbol) sym.name.decoded - else if (sym.owner.isEffectiveRoot) sym.name.decoded - else decodedFullName(sym.effectiveOwner.enclClass) + '.' + sym.name.decoded + /** Checks whether the given target is suitable for export and exporting + * should be performed. + * + * Reports any errors for unsuitable targets. + * @returns a boolean indicating whether exporting should be performed. Note: + * a result of true is not a guarantee that no error was emitted. But it is + * a guarantee that the target is not "too broken" to run the rest of + * the generation. This approximation is done to avoid having to complicate + * shared code verifying conditions. + */ + private def checkExportTarget(sym: Symbol, errPos: Position): Boolean = { + def err(msg: String) = { + reporter.error(errPos, msg) + false + } + + def hasLegalExportVisibility(sym: Symbol) = + sym.isPublic || sym.isProtected && !sym.isProtectedLocal + + lazy val params = sym.paramss.flatten + + def hasIllegalDefaultParam = { + val isDefParam = (_: Symbol).hasFlag(Flags.DEFAULTPARAM) + params.reverse.dropWhile(isDefParam).exists(isDefParam) + } + + def hasAnyNonPrivateCtor: Boolean = + sym.info.member(nme.CONSTRUCTOR).filter(!isPrivateMaybeWithin(_)).exists + + if (sym.isTrait) { + err("You may not export a trait") + } else if (sym.hasAnnotation(JSNativeAnnotation)) { + err("You may not export a native JS definition") + } else if (!hasLegalExportVisibility(sym)) { + err("You may only export public and protected definitions") + } else if (sym.isConstructor && !hasLegalExportVisibility(sym.owner)) { + err("You may only export constructors of public and protected classes") + } else if (sym.isMacro) { + err("You may not export a macro") + } else if (isJSAny(sym.owner)) { + err("You may not export a member of a subclass of js.Any") + } else if (scalaPrimitives.isPrimitive(sym)) { + err("You may not export a primitive") + } else if (sym.isLocalToBlock) { + err("You may not export a local definition") + } else if (sym.isConstructor && sym.owner.isLocalToBlock) { + err("You may not export constructors of local classes") + } else if (params.nonEmpty && params.init.exists(isRepeated _)) { + err("In an exported method or constructor, a *-parameter must come last " + + "(through all parameter lists)") + } else if (hasIllegalDefaultParam) { + err("In an exported method or constructor, all parameters with " + + "defaults must be at the end") + } else if (sym.isConstructor && sym.owner.isAbstractClass && !isJSAny(sym)) { + err("You may not export an abstract class") + } else if (sym.isClass && !sym.isModuleClass && isJSAny(sym) && !hasAnyNonPrivateCtor) { + /* This test is only relevant for JS classes: We'll complain on the + * individual exported constructors in case of a Scala class. + */ + err("You may not export a class that has only private constructors") + } else { + if (jsInterop.isJSSetter(sym)) + checkSetterSignature(sym, errPos, exported = true) + + true // ok even if a setter has the wrong signature. + } } - /** generate an exporter for a DefDef including default parameter methods */ - private def genExportDefs(defSym: Symbol, jsName: String, pos: Position) = { - val clsSym = defSym.owner - val scalaName = - jsInterop.scalaExportName(jsName, jsInterop.isJSProperty(defSym)) + /** generate an exporter for a target including default parameter methods */ + private def genExportDefs(sym: Symbol, jsName: String, pos: Position) = { + val siblingSym = + if (sym.isConstructor) sym.owner + else sym + + val clsSym = siblingSym.owner + + val isProperty = sym.isModuleClass || isJSAny(sym) || jsInterop.isJSProperty(sym) + val scalaName = jsInterop.scalaExportName(jsName, isProperty) + + val copiedFlags = siblingSym.flags & (Flags.PROTECTED | Flags.FINAL) // Create symbol for new method - val expSym = defSym.cloneSymbol - - // Set position of symbol - expSym.pos = pos - - // Alter type for new method (lift return type to Any) - // The return type is lifted, in order to avoid bridge - // construction and to detect methods whose signature only differs - // in the return type. - // Attention: This will cause boxes for primitive value types and value - // classes. However, since we have restricted the return types, we can - // always safely remove these boxes again in the back-end. - if (!defSym.isConstructor) - expSym.setInfo(retToAny(expSym.tpe)) - - // Change name for new method - expSym.name = scalaName - - // Update flags - expSym.setFlag(Flags.SYNTHETIC) - expSym.resetFlag( - Flags.DEFERRED | // We always have a body - Flags.ACCESSOR | // We are never a "direct" accessor - Flags.CASEACCESSOR | // And a fortiori not a case accessor - Flags.LAZY | // We are not a lazy val (even if we export one) - Flags.OVERRIDE // Synthetic methods need not bother with this - ) - - // Remove export annotations - expSym.removeAnnotation(JSExportAnnotation) - - // Add symbol to class - clsSym.info.decls.enter(expSym) + val expSym = clsSym.newMethod(scalaName, pos, Flags.SYNTHETIC | copiedFlags) + expSym.privateWithin = siblingSym.privateWithin + + val expSymTpe = { + /* Alter type for new method (lift return type to Any) + * The return type is lifted, in order to avoid bridge + * construction and to detect methods whose signature only differs + * in the return type. + */ + if (sym.isClass) NullaryMethodType(AnyClass.tpe) + else retToAny(sym.tpe.cloneInfo(expSym)) + } + + expSym.setInfoAndEnter(expSymTpe) // Construct exporter DefDef tree - val exporter = genProxyDefDef(clsSym, defSym, expSym, pos) + val exporter = genProxyDefDef(sym, expSym, pos) // Construct exporters for default getters val defaultGetters = for { (param, i) <- expSym.paramss.flatten.zipWithIndex if param.hasFlag(Flags.DEFAULTPARAM) - } yield genExportDefaultGetter(clsSym, defSym, expSym, i + 1, pos) + } yield genExportDefaultGetter(clsSym, sym, expSym, i + 1, pos) exporter :: defaultGetters } @@ -559,27 +509,46 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => clsSym.info.decls.enter(expGetter) - genProxyDefDef(clsSym, trgGetter, expGetter, pos) + genProxyDefDef(trgGetter, expGetter, pos) } else EmptyTree } /** generate a DefDef tree (from [[proxySym]]) that calls [[trgSym]] */ - private def genProxyDefDef(clsSym: Symbol, trgSym: Symbol, - proxySym: Symbol, pos: Position) = atPos(pos) { + private def genProxyDefDef(trgSym: Symbol, proxySym: Symbol, pos: Position) = atPos(pos) { + val tpeParams = proxySym.typeParams.map(gen.mkAttributedIdent(_)) - // Helper to ascribe repeated argument lists when calling - def spliceParam(sym: Symbol) = { - if (isRepeated(sym)) - Typed(Ident(sym), Ident(tpnme.WILDCARD_STAR)) - else - Ident(sym) + // Construct proxied function call + val nonPolyFun = { + if (trgSym.isConstructor) { + val clsTpe = trgSym.owner.tpe + val tpe = gen.mkTypeApply(TypeTree(clsTpe), tpeParams) + Select(New(tpe), trgSym) + } else if (trgSym.isModuleClass) { + assert(proxySym.paramss.isEmpty, + s"got a module export with non-empty paramss. target: $trgSym, proxy: $proxySym at $pos") + gen.mkAttributedRef(trgSym.sourceModule) + } else if (trgSym.isClass) { + assert(isJSAny(trgSym), s"got a class export for a non-JS class ($trgSym) at $pos") + assert(proxySym.paramss.isEmpty, + s"got a class export with non-empty paramss. target: $trgSym, proxy: $proxySym at $pos") + val tpe = gen.mkTypeApply(TypeTree(trgSym.tpe), tpeParams) + gen.mkTypeApply(gen.mkAttributedRef(JSPackage_constructorOf), List(tpe)) + } else { + val fun = gen.mkAttributedRef(trgSym) + gen.mkTypeApply(fun, tpeParams) + } } - // Construct proxied function call - val sel = Select(This(clsSym), trgSym) - val rhs = proxySym.paramss.foldLeft[Tree](sel) { - (fun,params) => Apply(fun, params map spliceParam) + val rhs = proxySym.paramss.foldLeft(nonPolyFun) { (fun, params) => + val args = params.map { param => + val ident = gen.mkAttributedIdent(param) + + if (isRepeated(param)) Typed(ident, Ident(tpnme.WILDCARD_STAR)) + else ident + } + + Apply(fun, args) } typer.typedDefDef(DefDef(proxySym, rhs)) @@ -593,26 +562,6 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => case _ => AnyClass.tpe } - /** Whether the given symbol has a visibility that allows exporting */ - private def hasLegalExportVisibility(sym: Symbol): Boolean = - sym.isPublic || sym.isProtected && !sym.isProtectedLocal - - /** checks whether this type has a repeated parameter elsewhere than at the end - * of all the params - */ - private def hasIllegalRepeatedParam(sym: Symbol): Boolean = { - val params = sym.paramss.flatten - params.nonEmpty && params.init.exists(isRepeated _) - } - - /** checks whether there are default parameters not at the end of - * the flattened parameter list - */ - private def hasIllegalDefaultParam(sym: Symbol): Boolean = { - val isDefParam = (_: Symbol).hasFlag(Flags.DEFAULTPARAM) - sym.paramss.flatten.reverse.dropWhile(isDefParam).exists(isDefParam) - } - /** Whether a symbol is an annotation that goes directly on a member */ private lazy val isDirectMemberAnnot = Set[Symbol]( JSExportAnnotation, diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala index a4ed63cd5e..592b9aa381 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/PrepJSInterop.scala @@ -53,6 +53,8 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) import jsDefinitions._ import jsInterop.{JSCallingConvention, JSName} + import scala.reflect.internal.Flags + val phaseName: String = "jsinterop" override def description: String = "prepare ASTs for JavaScript interop" @@ -164,14 +166,16 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) else checkJSNativeSpecificAnnotsOnNonJSNative(tree) - checkJSNameAnnots(sym) - checkDuplicateJSMemberAnnots(sym) + checkJSCallingConventionAnnots(sym) // @unchecked needed because MemberDef is not marked `sealed` val transformedTree: Tree = (tree: @unchecked) match { case tree: ImplDef => - if (shouldPrepareExports) - registerClassOrModuleExports(sym) + if (shouldPrepareExports) { + val exports = genExport(sym) + if (exports.nonEmpty) + exporters.getOrElseUpdate(sym.owner, mutable.ListBuffer.empty) ++= exports + } if ((enclosingOwner is OwnerKind.JSNonNative) && sym.owner.isTrait && !sym.isTrait) { reporter.error(tree.pos, @@ -191,8 +195,14 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) * messages for that are handled by genExportMember). */ if (shouldPrepareExports && (sym.isMethod || sym.isLocalToBlock)) { - exporters.getOrElseUpdate(sym.owner, mutable.ListBuffer.empty) ++= - genExportMember(sym) + val exports = genExport(sym) + if (exports.nonEmpty) { + val target = + if (sym.isConstructor) sym.owner.owner + else sym.owner + + exporters.getOrElseUpdate(target, mutable.ListBuffer.empty) ++= exports + } } if (sym.isLocalToBlock) { @@ -308,6 +318,11 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) val transformedBodyWithExports = exporters.get(clsSym).fold { transformedBody } { exports => + assert(exports.nonEmpty, s"found empty exporters for $clsSym" ) + + // Reset interface flag: We're adding non-empty methods. + clsSym.resetFlag(Flags.INTERFACE) + transformedBody ::: exports.toList } @@ -453,16 +468,19 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) typer.typed { atPos(tree.pos) { + /* gen.mkSuperInitCall would be nicer, but that doesn't get past the typer: + * + * scala.reflect.internal.Types$TypeError: + * stable identifier required, but $anon.super. found. + */ val superCtorCall = gen.mkMethodCall( Super(clsSym, tpnme.EMPTY), - ObjectClass.primaryConstructor, Nil, Nil) + DynamicImportThunkClass.primaryConstructor, Nil, Nil) // class $anon extends DynamicImportThunk val clsDef = ClassDef(clsSym, List( // def () = { super.(); () } - DefDef(ctorSym, - // `gen.mkUnitBlock(gen.mkSuperInitCall)` would be better but that fails on 2.11. - Block(superCtorCall, Literal(Constant(())))), + DefDef(ctorSym, gen.mkUnitBlock(superCtorCall)), // def apply(): Any = body DefDef(applySym, newBody))) @@ -601,9 +619,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) val isJSNative = sym.hasAnnotation(JSNativeAnnotation) - val isJSFunctionSAMInScala211 = - isScala211 && sym.name == tpnme.ANON_FUN_NAME && sym.superClass == JSFunctionClass - // Forbid @EnableReflectiveInstantiation on JS types sym.getAnnotation(EnableReflectiveInstantiationAnnotation).foreach { annot => @@ -647,11 +662,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) * This causes the unsoundness filed as #1385. */ () - case SerializableClass if isJSFunctionSAMInScala211 => - /* Ignore the scala.Serializable trait that Scala 2.11 adds on all - * SAM classes when on a JS function SAM. - */ - () case parentSym => /* This is a Scala class or trait other than AnyRef and Dynamic, * which is never valid. @@ -662,32 +672,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) } } - // Require that the SAM of a JS function def be `apply` (2.11-only here) - if (isJSFunctionSAMInScala211) { - if (!sym.info.decl(nme.apply).filter(JSCallingConvention.isCall(_)).exists) { - val samType = sym.parentSymbols.find(_ != JSFunctionClass).getOrElse { - /* This shouldn't happen, but fall back on this symbol (which has a - * compiler-generated name) not to crash. - */ - sym - } - reporter.error(implDef.pos, - "Using an anonymous function as a SAM for the JavaScript type " + - s"${samType.fullNameString} is not allowed because its single " + - "abstract method is not named `apply`. " + - "Use an anonymous class instead.") - } - } - - // Disallow bracket access / bracket call - if (jsInterop.isJSBracketAccess(sym)) { - reporter.error(implDef.pos, - "@JSBracketAccess is not allowed on JS classes and objects") - } else if (jsInterop.isJSBracketCall(sym)) { - reporter.error(implDef.pos, - "@JSBracketCall is not allowed on JS classes and objects") - } - // Checks for non-native JS stuff if (!isJSNative) { // It cannot be in a native JS class or trait @@ -975,12 +959,6 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) if (sym.isLazy || jsInterop.isJSSetter(sym)) { reporter.error(tree.pos, "@js.native is not allowed on vars, lazy vals and setter defs") - } else if (jsInterop.isJSBracketAccess(sym)) { - reporter.error(tree.pos, - "@JSBracketAccess is not allowed on @js.native vals and defs") - } else if (jsInterop.isJSBracketCall(sym)) { - reporter.error(tree.pos, - "@JSBracketCall is not allowed on @js.native vals and defs") } if (!sym.isAccessor) @@ -1010,6 +988,35 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) "application in JavaScript. A parameterless member should be " + "exported as a property. You must add @JSName(\"apply\")") + case jsInterop.JSUnaryOpMethodName(_, _) => + if (sym.hasAnnotation(JSOperatorAnnotation)) { + if (sym.paramss.map(_.size).sum != 0) { + reporter.error(tree.pos, + s"@JSOperator methods with the name '${sym.nameString}' may not have any parameters") + } + } else if (!sym.annotations.exists(annot => JSCallingConventionAnnots.contains(annot.symbol))) { + reporter.warning(tree.pos, + s"Method '${sym.nameString}' should have an explicit @JSName or @JSOperator annotation " + + "because its name is one of the JavaScript operators") + } + + case jsInterop.JSBinaryOpMethodName(_, _) => + if (sym.hasAnnotation(JSOperatorAnnotation)) { + if (sym.paramss.map(_.size).sum != 1) { + reporter.error(tree.pos, + s"@JSOperator methods with the name '${sym.nameString}' must have exactly one parameter") + } + } else if (!sym.annotations.exists(annot => JSCallingConventionAnnots.contains(annot.symbol))) { + reporter.warning(tree.pos, + s"Method '${sym.nameString}' should have an explicit @JSName or @JSOperator annotation " + + "because its name is one of the JavaScript operators") + } + + case _ if sym.hasAnnotation(JSOperatorAnnotation) => + reporter.error(tree.pos, + s"@JSOperator cannot be used on a method with the name '${sym.nameString}' " + + "because it is not one of the JavaScript operators") + case nme.equals_ if sym.tpe.matches(Any_equals.tpe) => reporter.warning(sym.pos, "Overriding equals in a JS class does " + "not change how it is compared. To silence this warning, change " + @@ -1037,15 +1044,8 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) * synthetic `apply` method if it is in a SAM for a JS function * type. */ - val isJSFunctionSAM = { - /* Under 2.11, sym.owner.isAnonymousFunction does not properly - * recognize anonymous functions here (because they seem to not - * be marked as synthetic). - */ - sym.isSynthetic && - sym.owner.name == tpnme.ANON_FUN_NAME && - sym.owner.superClass == JSFunctionClass - } + val isJSFunctionSAM = + sym.isSynthetic && sym.owner.isAnonymousFunction if (!isJSFunctionSAM) { reporter.error(sym.pos, "A non-native JS class cannot declare a concrete method " + @@ -1208,16 +1208,8 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) } } - /* Check that the right-hand-side is `js.undefined`. - * - * On 2.12+, fields are created later than this phase, and getters - * still hold the right-hand-side that we need to check (we - * identify this case with `sym.accessed == NoSymbol`). - * On 2.11 and before, however, the getter has already been - * rewritten to read the field, so we must not check it. - * In either case, setters must not be checked. - */ - if (!sym.isAccessor || (sym.isGetter && sym.accessed == NoSymbol)) { + // Check that the right-hand-side is `js.undefined`. + if (!sym.isSetter) { // Check that the tree's body is `js.undefined` tree.rhs match { case sel: Select if sel.symbol == JSPackage_undefined => @@ -1312,37 +1304,52 @@ abstract class PrepJSInterop[G <: Global with Singleton](val global: G) } } - private def checkJSNameAnnots(sym: Symbol): Unit = { - for (annot <- sym.getAnnotation(JSNameAnnotation)) { - // Check everything about the first @JSName annotation - if (sym.isLocalToBlock || (enclosingOwner isnt OwnerKind.JSType)) { - reporter.error(annot.pos, - "@JSName can only be used on members of JS types.") - } else if (sym.isTrait) { - reporter.error(annot.pos, - "@JSName cannot be used on traits.") - } else if ((sym.isMethod || sym.isClass) && isPrivateMaybeWithin(sym)) { - reporter.error(annot.pos, - "@JSName cannot be used on private members.") - } else { - if (shouldCheckLiterals) - checkJSNameArgument(sym, annot) - } - } - } + private def checkJSCallingConventionAnnots(sym: Symbol): Unit = { + val callingConvAnnots = sym.annotations.filter(annot => JSCallingConventionAnnots.contains(annot.symbol)) - private def checkDuplicateJSMemberAnnots(sym: Symbol): Unit = { - sym.annotations - .filter(annot => JSMemberAnnots.contains(annot.symbol)) - .drop(1) - .foreach { annot => - reporter.error(annot.pos, "A member can have at most one " + - "annotation among @JSName, @JSBracketAccess and @JSBracketCall.") - } + callingConvAnnots match { + case Nil => + () // OK + + case annot :: rest => + def annotName: String = annot.symbol.nameString + + if (sym.isLocalToBlock || (enclosingOwner isnt OwnerKind.JSType)) { + reporter.error(annot.pos, + s"@$annotName can only be used on members of JS types.") + } else if (sym.isTrait) { + reporter.error(annot.pos, + s"@$annotName cannot be used on traits.") + } else if ((sym.isMethod || sym.isClass) && isPrivateMaybeWithin(sym)) { + reporter.error(annot.pos, + s"@$annotName cannot be used on private members.") + } else { + annot.symbol match { + case JSNameAnnotation => + if (shouldCheckLiterals) + checkJSNameArgument(sym, annot) + case JSOperatorAnnotation | JSBracketAccessAnnotation | JSBracketCallAnnotation => + if (!sym.isMethod) { + reporter.error(annot.pos, + s"@$annotName can only be used on methods.") + } + case _ => + throw new AssertionError( + s"Found unexpected annotation ${annot.symbol} " + + s"in calling convention annots at ${annot.pos}") + } + } + + for (duplicateAnnot <- rest) { + reporter.error(duplicateAnnot.pos, + "A member can have at most one annotation among " + + "@JSName, @JSOperator, @JSBracketAccess and @JSBracketCall.") + } + } } - private lazy val JSMemberAnnots: Set[Symbol] = - Set(JSNameAnnotation, JSBracketAccessAnnotation, JSBracketCallAnnotation) + private lazy val JSCallingConventionAnnots: Set[Symbol] = + Set(JSNameAnnotation, JSOperatorAnnotation, JSBracketAccessAnnotation, JSBracketCallAnnotation) /** Checks that argument to @JSName on [[member]] is a literal. * Reports an error on each annotation where this is not the case. diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala index 5e7ca6faba..a67adbb948 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/ScalaJSPlugin.scala @@ -40,8 +40,8 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin { } } - /** Called when the JS ASTs are generated. Override for testing */ - def generatedJSAST(clDefs: List[Trees.ClassDef]): Unit = {} + /** Called for each generated `ClassDef`. Override for testing. */ + def generatedJSAST(clDef: Trees.ClassDef): Unit = {} /** A trick to avoid early initializers while still enforcing that `global` * is initialized early. @@ -68,17 +68,6 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin { var absSourceMap: Option[URI] = None } - /** Checks and registers module exports on the symbol. - * - * This bridge allows other plugins to register new modules for export - * between jsinterop and jscode phases. It is meant to be accessed using - * reflection. The calling code still must insert the `@JSExport` annotation - * to the module. - */ - @deprecated("Might be removed at any time, use at your own risk.", "0.6.24") - def registerModuleExports(sym: Symbol): Unit = - PrepInteropComponent.registerClassOrModuleExports(sym) - object PreTyperComponentComponent extends PreTyperComponent(global) { val runsAfter = List("parser") override val runsBefore = List("namer") @@ -88,7 +77,7 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin { val jsAddons: ScalaJSPlugin.this.jsAddons.type = ScalaJSPlugin.this.jsAddons val scalaJSOpts = ScalaJSPlugin.this.scalaJSOpts override val runsAfter = List("typer") - override val runsBefore = List("pickle") + override val runsBefore = List("pickler") } object ExplicitInnerJSComponent extends ExplicitInnerJS[global.type](global) { @@ -109,8 +98,8 @@ class ScalaJSPlugin(val global: Global) extends NscPlugin { override val runsAfter = List("mixin") override val runsBefore = List("delambdafy", "cleanup", "terminal") - def generatedJSAST(clDefs: List[Trees.ClassDef]): Unit = - ScalaJSPlugin.this.generatedJSAST(clDefs) + def generatedJSAST(clDef: Trees.ClassDef): Unit = + ScalaJSPlugin.this.generatedJSAST(clDef) } override def init(options: List[String], error: String => Unit): Boolean = { diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala b/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala index c063a2a02a..eae31fdb14 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/TypeConversions.scala @@ -25,7 +25,7 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent { private lazy val primitiveIRTypeMap: Map[Symbol, Types.Type] = { Map( - UnitClass -> Types.NoType, + UnitClass -> Types.VoidType, BooleanClass -> Types.BooleanType, CharClass -> Types.CharType, ByteClass -> Types.ByteType, @@ -60,7 +60,7 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent { if (arrayDepth == 0) primitiveIRTypeMap.getOrElse(base, encodeClassType(base)) else - Types.ArrayType(makeArrayTypeRef(base, arrayDepth)) + Types.ArrayType(makeArrayTypeRef(base, arrayDepth), nullable = true) } def toTypeRef(t: Type): Types.TypeRef = { @@ -93,12 +93,12 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent { */ private def convert(t: Type): (Symbol, Int) = t.normalize match { case ThisType(ArrayClass) => (ObjectClass, 0) - case ThisType(sym) => (convertBase(sym), 0) - case SingleType(_, sym) => (convertBase(sym), 0) + case ThisType(sym) => (sym, 0) + case SingleType(_, sym) => (sym, 0) case ConstantType(_) => convert(t.underlying) case TypeRef(_, sym, args) => convertMaybeArray(sym, args) case ClassInfoType(_, _, ArrayClass) => abort("ClassInfoType to ArrayClass!") - case ClassInfoType(_, _, sym) => (convertBase(sym), 0) + case ClassInfoType(_, _, sym) => (sym, 0) // !!! Iulian says types which make no sense after erasure should not reach here, // which includes the ExistentialType, AnnotatedType, RefinedType. I don't know @@ -114,7 +114,7 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent { * run/valueclasses-classtag-existential. I have no idea how icode does * not fail this test: we do everything the same as icode up to here. */ - case tpe: ErasedValueType => (convertBase(tpe.valueClazz), 0) + case tpe: ErasedValueType => (tpe.valueClazz, 0) // For sure WildcardTypes shouldn't reach here either, but when // debugging such situations this may come in handy. @@ -133,23 +133,9 @@ trait TypeConversions[G <: Global with Singleton] extends SubComponent { val convertedArg = convert(targs.head) (convertedArg._1, convertedArg._2 + 1) case _ if sym.isClass => - (convertBase(sym), 0) + (sym, 0) case _ => assert(sym.isType, sym) // it must be compiling Array[a] (ObjectClass, 0) } - - /** Convert a class ref, definitely not an array type. */ - private def convertBase(sym: Symbol): Symbol = { - if (isImplClass(sym)) { - // pos/spec-List.scala is the sole failure if we don't check for NoSymbol - val traitSym = sym.owner.info.decl(tpnme.interfaceName(sym.name)) - if (traitSym != NoSymbol) - traitSym - else - sym - } else { - sym - } - } } diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/CallSiteInlineTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/CallSiteInlineTest.scala new file mode 100644 index 0000000000..b4c33256b6 --- /dev/null +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/CallSiteInlineTest.scala @@ -0,0 +1,115 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.nscplugin.test + +import util._ + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.ir.{Trees => js, Types => jstpe} +import org.scalajs.ir.Names +import org.scalajs.ir.Names._ + +class CallSiteInlineTest extends JSASTTest { + + @Test + def testInline: Unit = { + val flags = { + """ + object A { + println("F"): @inline + } + """.extractOne("println call") { + case js.Apply(flags, _, SMN("println"), _) => flags + } + } + + assertTrue(flags.inline) + } + + @Test + def testNoinline: Unit = { + val flags = { + """ + object A { + println("F"): @noinline + } + """.extractOne("println call") { + case js.Apply(flags, _, SMN("println"), _) => flags + } + } + + assertTrue(flags.noinline) + } + + @Test + def testInlineNullary: Unit = { + val flags = { + """ + object A { + Map.empty: @inline + } + """.extractOne("Map.empty") { + case js.Apply(flags, _, SMN("empty"), _) => flags + } + } + + assertTrue(flags.inline) + } + + @Test + def testNoinlineNullary: Unit = { + val flags = { + """ + object A { + Map.empty: @noinline + } + """.extractOne("Map.empty") { + case js.Apply(flags, _, SMN("empty"), _) => flags + } + } + + assertTrue(flags.noinline) + } + + @Test + def testInlineStatic: Unit = { + val flags = { + """ + object A { + Integer.compare(1, 2): @inline + } + """.extractOne("compare call") { + case js.ApplyStatic(flags, _, SMN("compare"), _) => flags + } + } + + assertTrue(flags.inline) + } + + @Test + def testNoinlineStatic: Unit = { + val flags = { + """ + object A { + Integer.compare(1, 2): @noinline + } + """.extractOne("compare call") { + case js.ApplyStatic(flags, _, SMN("compare"), _) => flags + } + } + + assertTrue(flags.noinline) + } +} diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala index ef625a67c0..9ed869cbcc 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/DiverseErrorsTest.scala @@ -26,14 +26,8 @@ class DiverseErrorsTest extends DirectTest with TestHelpers { private def version = scala.util.Properties.versionNumberString - private val allowsSingletonClassOf = ( - !version.startsWith("2.11.") && - !version.startsWith("2.12.") && - version != "2.13.0" && - version != "2.13.1" && - version != "2.13.2" && - version != "2.13.3" - ) + private val allowsSingletonClassOf = + !version.startsWith("2.12.") && version != "2.13.3" @Test def noIsInstanceOnJS(): Unit = { diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSAsyncAwaitTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSAsyncAwaitTest.scala new file mode 100644 index 0000000000..d8147fad0a --- /dev/null +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSAsyncAwaitTest.scala @@ -0,0 +1,83 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.nscplugin.test + +import org.scalajs.nscplugin.test.util._ +import org.junit.Test + +// scalastyle:off line.size.limit + +class JSAsyncAwaitTest extends DirectTest with TestHelpers { + + override def preamble: String = + """import scala.scalajs.js + """ + + @Test + def orphanAwait(): Unit = { + """ + class A { + def foo(x: js.Promise[Int]): Int = + js.await(x) + } + """ hasErrors + """ + |newSource1.scala:5: error: Illegal use of js.await(). + |It can only be used inside a js.async {...} block, without any lambda, + |by-name argument or nested method in-between. + |If you compile for WebAssembly, you can allow arbitrary js.await() + |calls by adding the following import: + |import scala.scalajs.js.wasm.JSPI.allowOrphanJSAwait + | js.await(x) + | ^ + """ + + """ + class A { + def foo(x: js.Promise[Int]): js.Promise[Int] = js.async { + val f: () => Int = () => js.await(x) + f() + } + } + """ hasErrors + """ + |newSource1.scala:5: error: Illegal use of js.await(). + |It can only be used inside a js.async {...} block, without any lambda, + |by-name argument or nested method in-between. + |If you compile for WebAssembly, you can allow arbitrary js.await() + |calls by adding the following import: + |import scala.scalajs.js.wasm.JSPI.allowOrphanJSAwait + | val f: () => Int = () => js.await(x) + | ^ + """ + + """ + class A { + def foo(x: js.Promise[Int]): js.Promise[Int] = js.async { + def f(): Int = js.await(x) + f() + } + } + """ hasErrors + """ + |newSource1.scala:5: error: Illegal use of js.await(). + |It can only be used inside a js.async {...} block, without any lambda, + |by-name argument or nested method in-between. + |If you compile for WebAssembly, you can allow arbitrary js.await() + |calls by adding the following import: + |import scala.scalajs.js.wasm.JSPI.allowOrphanJSAwait + | def f(): Int = js.await(x) + | ^ + """ + } +} diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala index b61acd71ed..234d3a1bb6 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSExportTest.scala @@ -121,10 +121,10 @@ class JSExportTest extends DirectTest with TestHelpers { class B """ hasErrors """ - |newSource1.scala:3: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. + |newSource1.scala:3: error: @JSExport is forbidden on top-level objects and classes. Use @JSExportTopLevel instead. | @JSExport | ^ - |newSource1.scala:6: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. + |newSource1.scala:6: error: @JSExport is forbidden on top-level objects and classes. Use @JSExportTopLevel instead. | @JSExport("Foo") | ^ """ @@ -140,10 +140,10 @@ class JSExportTest extends DirectTest with TestHelpers { object B """ hasErrors """ - |newSource1.scala:3: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. + |newSource1.scala:3: error: @JSExport is forbidden on top-level objects and classes. Use @JSExportTopLevel instead. | @JSExport | ^ - |newSource1.scala:6: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. + |newSource1.scala:6: error: @JSExport is forbidden on top-level objects and classes. Use @JSExportTopLevel instead. | @JSExport("Foo") | ^ """ @@ -328,10 +328,10 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:5: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. + |newSource1.scala:5: error: You may not export constructors of local classes | @JSExport | ^ - |newSource1.scala:8: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. + |newSource1.scala:8: error: You may not export a local definition | @JSExport | ^ """ @@ -349,10 +349,10 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:5: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. + |newSource1.scala:5: error: You may not export a local definition | @JSExport | ^ - |newSource1.scala:8: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. + |newSource1.scala:8: error: You may not export a local definition | @JSExport | ^ """ @@ -414,7 +414,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:4: error: In an exported method, a *-parameter must come last (through all parameter lists) + |newSource1.scala:4: error: In an exported method or constructor, a *-parameter must come last (through all parameter lists) | @JSExport | ^ """ @@ -431,7 +431,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:4: error: In an exported method, all parameters with defaults must be at the end + |newSource1.scala:4: error: In an exported method or constructor, all parameters with defaults must be at the end | @JSExport | ^ """ @@ -441,6 +441,29 @@ class JSExportTest extends DirectTest with TestHelpers { @Test def noExportAbstractClass(): Unit = { + """ + object Foo { + @JSExport + abstract class A + + abstract class B(x: Int) { + @JSExport + def this() = this(5) + } + + @JSExport // ok! + abstract class C extends js.Object + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export an abstract class + | @JSExport + | ^ + |newSource1.scala:8: error: You may not export an abstract class + | @JSExport + | ^ + """ + """ @JSExportTopLevel("A") abstract class A @@ -487,6 +510,24 @@ class JSExportTest extends DirectTest with TestHelpers { | ^ """ + """ + object A { + @JSExport + trait Test + + @JSExport + trait Test2 extends js.Object + } + """ hasErrors + """ + |newSource1.scala:4: error: You may not export a trait + | @JSExport + | ^ + |newSource1.scala:7: error: You may not export a trait + | @JSExport + | ^ + """ + } @Test @@ -504,20 +545,28 @@ class JSExportTest extends DirectTest with TestHelpers { @JSExportTopLevel("D") protected[this] class D extends js.Object + + private class E(x: Int) { + @JSExportTopLevel("E") + def this() = this(1) + } """ hasErrors """ - |newSource1.scala:3: error: You may only export public and protected classes + |newSource1.scala:3: error: You may only export constructors of public and protected classes | @JSExportTopLevel("A") | ^ - |newSource1.scala:6: error: You may only export public and protected classes + |newSource1.scala:6: error: You may only export constructors of public and protected classes | @JSExportTopLevel("B") | ^ - |newSource1.scala:9: error: You may only export public and protected classes + |newSource1.scala:9: error: You may only export public and protected definitions | @JSExportTopLevel("C") | ^ - |newSource1.scala:12: error: You may only export public and protected classes + |newSource1.scala:12: error: You may only export public and protected definitions | @JSExportTopLevel("D") | ^ + |newSource1.scala:16: error: You may only export constructors of public and protected classes + | @JSExportTopLevel("E") + | ^ """ """ @@ -534,16 +583,16 @@ class JSExportTest extends DirectTest with TestHelpers { protected[this] object D extends js.Object """ hasErrors """ - |newSource1.scala:3: error: You may only export public and protected objects + |newSource1.scala:3: error: You may only export public and protected definitions | @JSExportTopLevel("A") | ^ - |newSource1.scala:6: error: You may only export public and protected objects + |newSource1.scala:6: error: You may only export public and protected definitions | @JSExportTopLevel("B") | ^ - |newSource1.scala:9: error: You may only export public and protected objects + |newSource1.scala:9: error: You may only export public and protected definitions | @JSExportTopLevel("C") | ^ - |newSource1.scala:12: error: You may only export public and protected objects + |newSource1.scala:12: error: You may only export public and protected definitions | @JSExportTopLevel("D") | ^ """ @@ -563,92 +612,10 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:4: error: You may only export public and protected methods - | @JSExport - | ^ - |newSource1.scala:7: error: You may only export public and protected methods - | @JSExport - | ^ - """ - - } - - @Test - def noExportNestedClass(): Unit = { - - """ - class A { - @JSExport - class Nested { - @JSExport - def this(x: Int) = this() - } - - @JSExport - class Nested2 extends js.Object - } - """ hasErrors - """ - |newSource1.scala:4: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. - | @JSExport - | ^ - |newSource1.scala:6: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. - | @JSExport - | ^ - |newSource1.scala:10: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. - | @JSExport - | ^ - """ - - } - - @Test - def noNestedExportClass: Unit = { - - """ - object A { - @JSExport - class Nested { - @JSExport - def this(x: Int) = this - } - - @JSExport - class Nested2 extends js.Object - } - """ hasErrors - """ - - |newSource1.scala:4: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. - | @JSExport - | ^ - |newSource1.scala:6: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. - | @JSExport - | ^ - |newSource1.scala:10: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. - | @JSExport - | ^ - """ - - } - - @Test - def noNestedExportObject(): Unit = { - - """ - object A { - @JSExport - object Nested - - @JSExport - object Nested2 extends js.Object - } - """ hasErrors - """ - |newSource1.scala:4: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. + |newSource1.scala:4: error: You may only export public and protected definitions | @JSExport | ^ - |newSource1.scala:7: error: @JSExport is forbidden on objects and classes. Use @JSExportTopLevel instead. + |newSource1.scala:7: error: You may only export public and protected definitions | @JSExport | ^ """ @@ -668,10 +635,10 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:4: error: You may not export a nested object + |newSource1.scala:4: error: Only static objects may export their members to the top level | @JSExportTopLevel("Nested") | ^ - |newSource1.scala:7: error: You may not export a nested object + |newSource1.scala:7: error: Only static objects may export their members to the top level | @JSExportTopLevel("Nested2") | ^ """ @@ -690,7 +657,7 @@ class JSExportTest extends DirectTest with TestHelpers { object A extends js.Object """ hasErrors """ - |newSource1.scala:5: error: You may not export a native JS object + |newSource1.scala:5: error: You may not export a native JS definition | @JSExportTopLevel("A") | ^ """ @@ -720,14 +687,29 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:5: error: You may not export a native JS class + |newSource1.scala:5: error: You may not export a native JS definition | @JSExportTopLevel("A") | ^ - |newSource1.scala:9: error: You may not export a constructor of a subclass of js.Any + |newSource1.scala:9: error: You may not export a member of a subclass of js.Any | @JSExportTopLevel("A") | ^ """ + """ + import scala.scalajs.js + + object A { + @JSExport("A") + @js.native + @JSGlobal("a") + def a(x: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:6: error: You may not export a native JS definition + | @JSExport("A") + | ^ + """ } @Test @@ -744,7 +726,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:8: error: You may not export a method of a subclass of js.Any + |newSource1.scala:8: error: You may not export a member of a subclass of js.Any | @JSExport | ^ """ @@ -758,7 +740,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:6: error: You may not export a method of a subclass of js.Any + |newSource1.scala:6: error: You may not export a member of a subclass of js.Any | @JSExport | ^ """ @@ -822,6 +804,43 @@ class JSExportTest extends DirectTest with TestHelpers { } + @Test + def noNonSetterNameForNonSetter(): Unit = { + + """ + class A { + @JSExport + class A_= + } + """ hasErrors + """ + |newSource1.scala:4: error: You must set an explicit name when exporting a non-setter with a name ending in _= + | @JSExport + | ^ + """ + + """ + class A { + @JSExport + object A_= + } + """ hasErrors + """ + |newSource1.scala:4: error: You must set an explicit name when exporting a non-setter with a name ending in _= + | @JSExport + | ^ + """ + + // Not a Scala.js error, but we check it anyways to complete the test suite. + """ + class A { + @JSExport + val A_= = 1 + } + """.fails() // error is different on 2.12 / 2.13 + + } + @Test def noBadToStringExport(): Unit = { @@ -1021,6 +1040,28 @@ class JSExportTest extends DirectTest with TestHelpers { } """.hasNoWarns() + """ + class StaticContainer extends js.Object + + object StaticContainer { + @JSExportStatic + def apply(): Int = 1 + } + """ hasErrors + """ + |newSource1.scala:6: error: A member cannot be exported to function application as static. Use @JSExportStatic("apply") to export it under the name 'apply'. + | @JSExportStatic + | ^ + """ + + """ + class StaticContainer extends js.Object + + object StaticContainer { + @JSExportStatic("apply") + def apply(): Int = 1 + } + """.hasNoWarns() } @Test @@ -1033,12 +1074,6 @@ class JSExportTest extends DirectTest with TestHelpers { } - private def since(v: String): String = { - val version = scala.util.Properties.versionNumberString - if (version.startsWith("2.11.")) "" - else s" (since $v)" - } - @Test def noExportTopLevelTrait(): Unit = { """ @@ -1287,7 +1322,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:4: error: You may not export a nested object + |newSource1.scala:4: error: Only static objects may export their members to the top level | @JSExportTopLevel("Foo") | ^ """ @@ -1299,7 +1334,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:4: error: You may not export a nested object + |newSource1.scala:4: error: Only static objects may export their members to the top level | @JSExportTopLevel("Foo") | ^ """ @@ -1311,7 +1346,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:4: error: You may not export a nested class. Create an exported factory method in the outer class to work around this limitation. + |newSource1.scala:4: error: Only static objects may export their members to the top level | @JSExportTopLevel("Foo") | ^ """ @@ -1323,7 +1358,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:4: error: You may not export a nested class. Create an exported factory method in the outer class to work around this limitation. + |newSource1.scala:4: error: Only static objects may export their members to the top level | @JSExportTopLevel("Foo") | ^ """ @@ -1344,10 +1379,10 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:5: error: You may not export a local class + |newSource1.scala:5: error: You may not export constructors of local classes | @JSExportTopLevel("A") | ^ - |newSource1.scala:8: error: You may not export a local class + |newSource1.scala:8: error: You may not export a local definition | @JSExportTopLevel("B") | ^ """ @@ -1365,10 +1400,10 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:5: error: You may not export a local object + |newSource1.scala:5: error: You may not export a local definition | @JSExportTopLevel("A") | ^ - |newSource1.scala:8: error: You may not export a local object + |newSource1.scala:8: error: You may not export a local definition | @JSExportTopLevel("B") | ^ """ @@ -1383,7 +1418,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:4: error: You may not export a method of a subclass of js.Any + |newSource1.scala:4: error: You may not export a member of a subclass of js.Any | @JSExportTopLevel("foo") | ^ """ @@ -1417,7 +1452,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:6: error: You may not export a trait as static. + |newSource1.scala:6: error: You may not export a trait | @JSExportStatic | ^ """ @@ -1789,7 +1824,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:6: error: You may not export a method of a subclass of js.Any + |newSource1.scala:6: error: You may not export a member of a subclass of js.Any | @JSExportStatic | ^ """ @@ -1805,7 +1840,7 @@ class JSExportTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:8: error: You may not export a method of a subclass of js.Any + |newSource1.scala:8: error: You may not export a member of a subclass of js.Any | @JSExportStatic | ^ """ diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala index 5041885be3..19d3f0f8d3 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSGlobalScopeTest.scala @@ -13,14 +13,19 @@ package org.scalajs.nscplugin.test import org.scalajs.nscplugin.test.util._ +import org.scalajs.nscplugin.test.util.VersionDependentUtils.scalaSupportsNoWarn import org.junit.Test import org.junit.Ignore +import org.junit.Assume._ // scalastyle:off line.size.limit class JSGlobalScopeTest extends DirectTest with TestHelpers { + override def extraArgs: List[String] = + super.extraArgs :+ "-deprecation" + override def preamble: String = { """ import scala.scalajs.js @@ -39,7 +44,7 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers { var `not-a-valid-identifier-var`: Int = js.native def `not-a-valid-identifier-def`(): Int = js.native - def +(that: Int): Int = js.native + @JSOperator def +(that: Int): Int = js.native def apply(x: Int): Int = js.native @@ -244,6 +249,9 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers { } """ hasErrors s""" + |newSource1.scala:41: warning: method apply in object global is deprecated (since forever): The global scope cannot be called as function. + | val a = js.Dynamic.global(3) + | ^ |newSource1.scala:41: error: Loading the global scope as a value (anywhere but as the left-hand-side of a `.`-selection) is not allowed. | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information. | val a = js.Dynamic.global(3) @@ -334,7 +342,7 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers { "extends", "false", "finally", "for", "function", "if", "implements", "import", "in", "instanceof", "interface", "let", "new", "null", "package", "private", "protected", "public", "return", "static", - "super", "switch", "this", "throw", "true", "try", "typeof", "var", + "super", "switch", "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield") for (reservedIdentifier <- reservedIdentifiers) { @@ -390,4 +398,133 @@ class JSGlobalScopeTest extends DirectTest with TestHelpers { } } + @Test + def warnAboutAwaitReservedWord_Issue4705(): Unit = { + val reservedIdentifiers = List("await") + + for (reservedIdentifier <- reservedIdentifiers) { + val spaces = " " * reservedIdentifier.length() + + s""" + @js.native + @JSGlobalScope + object CustomGlobalScope extends js.Any { + var `$reservedIdentifier`: Int = js.native + @JSName("$reservedIdentifier") + def `${reservedIdentifier}2`(x: Int): Int = js.native + } + + object Main { + def main(): Unit = { + val a = js.Dynamic.global.`$reservedIdentifier` + js.Dynamic.global.`$reservedIdentifier` = 5 + val b = js.Dynamic.global.`$reservedIdentifier`(5) + + val c = CustomGlobalScope.`$reservedIdentifier` + CustomGlobalScope.`$reservedIdentifier` = 5 + val d = CustomGlobalScope.`${reservedIdentifier}2`(5) + } + } + """ hasWarns + s""" + |newSource1.scala:49: warning: Selecting a field of the global scope with the name '$reservedIdentifier' is deprecated. + | It may produce invalid JavaScript code causing a SyntaxError in some environments. + | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information. + | val a = js.Dynamic.global.`$reservedIdentifier` + | ^ + |newSource1.scala:50: warning: Selecting a field of the global scope with the name '$reservedIdentifier' is deprecated. + | It may produce invalid JavaScript code causing a SyntaxError in some environments. + | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information. + | js.Dynamic.global.`$reservedIdentifier` = 5 + | ^ + |newSource1.scala:51: warning: Calling a method of the global scope with the name '$reservedIdentifier' is deprecated. + | It may produce invalid JavaScript code causing a SyntaxError in some environments. + | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information. + | val b = js.Dynamic.global.`$reservedIdentifier`(5) + | $spaces^ + |newSource1.scala:53: warning: Selecting a field of the global scope with the name '$reservedIdentifier' is deprecated. + | It may produce invalid JavaScript code causing a SyntaxError in some environments. + | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information. + | val c = CustomGlobalScope.`$reservedIdentifier` + | ^ + |newSource1.scala:54: warning: Selecting a field of the global scope with the name '$reservedIdentifier' is deprecated. + | It may produce invalid JavaScript code causing a SyntaxError in some environments. + | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information. + | CustomGlobalScope.`$reservedIdentifier` = 5 + | $spaces^ + |newSource1.scala:55: warning: Calling a method of the global scope with the name '$reservedIdentifier' is deprecated. + | It may produce invalid JavaScript code causing a SyntaxError in some environments. + | See https://www.scala-js.org/doc/interoperability/global-scope.html for further information. + | val d = CustomGlobalScope.`${reservedIdentifier}2`(5) + | $spaces^ + """ + } + } + + @Test + def noWarnAboutAwaitReservedWordIfSelectivelyDisabled(): Unit = { + assumeTrue(scalaSupportsNoWarn) + + val reservedIdentifiers = List("await") + + for (reservedIdentifier <- reservedIdentifiers) { + val spaces = " " * reservedIdentifier.length() + + s""" + import scala.annotation.nowarn + + @js.native + @JSGlobalScope + object CustomGlobalScope extends js.Any { + var `$reservedIdentifier`: Int = js.native + @JSName("$reservedIdentifier") + def `${reservedIdentifier}2`(x: Int): Int = js.native + } + + object Main { + @nowarn("cat=deprecation") + def main(): Unit = { + val a = js.Dynamic.global.`$reservedIdentifier` + js.Dynamic.global.`$reservedIdentifier` = 5 + val b = js.Dynamic.global.`$reservedIdentifier`(5) + + val c = CustomGlobalScope.`$reservedIdentifier` + CustomGlobalScope.`$reservedIdentifier` = 5 + val d = CustomGlobalScope.`${reservedIdentifier}2`(5) + } + } + """.hasNoWarns() + } + } + + @Test + def rejectAssignmentToGlobalThis(): Unit = { + """ + import scala.scalajs.js + import scala.scalajs.js.annotation._ + + object Main { + def main(): Unit = { + js.Dynamic.global.`this` = 0 + GlobalScope.globalThis = 0 + } + } + + @js.native + @JSGlobalScope + object GlobalScope extends js.Any { + @JSName("this") + var globalThis: Any = js.native + } + """ hasErrors + s""" + |newSource1.scala:44: error: Illegal assignment to global this. + | js.Dynamic.global.`this` = 0 + | ^ + |newSource1.scala:45: error: Illegal assignment to global this. + | GlobalScope.globalThis = 0 + | ^ + """ + } + } diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala index c9dac28830..8d79f251a3 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala @@ -38,8 +38,7 @@ class JSInteropTest extends DirectTest with TestHelpers { private def version = scala.util.Properties.versionNumberString private def ifHasNewRefChecks(msg: String): String = { - if (version.startsWith("2.11.") || - version.startsWith("2.12.")) { + if (version.startsWith("2.12.")) { "" } else { msg.stripMargin.trim() @@ -966,15 +965,15 @@ class JSInteropTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:8: error: @JSBracketAccess is not allowed on @js.native vals and defs - | val a: Int = js.native - | ^ - |newSource1.scala:12: error: @JSBracketAccess is not allowed on @js.native vals and defs - | def b: Int = js.native - | ^ - |newSource1.scala:16: error: @JSBracketAccess is not allowed on @js.native vals and defs - | def c(x: Int): Int = js.native - | ^ + |newSource1.scala:7: error: @JSBracketAccess can only be used on members of JS types. + | @JSBracketAccess + | ^ + |newSource1.scala:11: error: @JSBracketAccess can only be used on members of JS types. + | @JSBracketAccess + | ^ + |newSource1.scala:15: error: @JSBracketAccess can only be used on members of JS types. + | @JSBracketAccess + | ^ """ } @@ -995,15 +994,15 @@ class JSInteropTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:8: error: @JSBracketCall is not allowed on @js.native vals and defs - | val a: Int = js.native - | ^ - |newSource1.scala:12: error: @JSBracketCall is not allowed on @js.native vals and defs - | def b: Int = js.native - | ^ - |newSource1.scala:16: error: @JSBracketCall is not allowed on @js.native vals and defs - | def c(x: Int): Int = js.native - | ^ + |newSource1.scala:7: error: @JSBracketCall can only be used on members of JS types. + | @JSBracketCall + | ^ + |newSource1.scala:11: error: @JSBracketCall can only be used on members of JS types. + | @JSBracketCall + | ^ + |newSource1.scala:15: error: @JSBracketCall can only be used on members of JS types. + | @JSBracketCall + | ^ """ } @@ -1428,6 +1427,24 @@ class JSInteropTest extends DirectTest with TestHelpers { } + @Test + def noJSOperatorAndJSName: Unit = { + """ + @js.native + @JSGlobal + class A extends js.Object { + @JSOperator + @JSName("bar") + def +(x: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall. + | @JSName("bar") + | ^ + """ + } + @Test // #4284 def noBracketAccessAndJSName: Unit = { """ @@ -1440,7 +1457,7 @@ class JSInteropTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSBracketAccess and @JSBracketCall. + |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall. | @JSName("bar") | ^ """ @@ -1458,7 +1475,7 @@ class JSInteropTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSBracketAccess and @JSBracketCall. + |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall. | @JSName("bar") | ^ """ @@ -1476,12 +1493,56 @@ class JSInteropTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSBracketAccess and @JSBracketCall. + |newSource1.scala:9: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall. | @JSBracketCall | ^ """ } + @Test def noBadUnaryOp: Unit = { + """ + @js.native + @JSGlobal + class A extends js.Object { + @JSOperator + def unary_!(x: Int*): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:9: error: @JSOperator methods with the name 'unary_!' may not have any parameters + | def unary_!(x: Int*): Int = js.native + | ^ + """ + + """ + @js.native + @JSGlobal + class A extends js.Object { + @JSOperator + def unary_-(x: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:9: error: @JSOperator methods with the name 'unary_-' may not have any parameters + | def unary_-(x: Int): Int = js.native + | ^ + """ + + """ + @js.native + @JSGlobal + class A extends js.Object { + @JSOperator + def unary_%(): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:9: error: @JSOperator cannot be used on a method with the name 'unary_%' because it is not one of the JavaScript operators + | def unary_%(): Int = js.native + | ^ + """ + } + @Test def noBadBinaryOp: Unit = { """ @js.native @@ -1491,10 +1552,55 @@ class JSInteropTest extends DirectTest with TestHelpers { } """ hasErrors """ + |newSource1.scala:8: warning: Method '+' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators + | def +(x: Int*): Int = js.native + | ^ |newSource1.scala:8: error: methods representing binary operations may not have repeated parameters | def +(x: Int*): Int = js.native | ^ """ + + """ + @js.native + @JSGlobal + class A extends js.Object { + @JSOperator + def +(x: Int*): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:9: error: methods representing binary operations may not have repeated parameters + | def +(x: Int*): Int = js.native + | ^ + """ + + """ + @js.native + @JSGlobal + class A extends js.Object { + @JSOperator + def +(x: Int, y: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:9: error: @JSOperator methods with the name '+' must have exactly one parameter + | def +(x: Int, y: Int): Int = js.native + | ^ + """ + + """ + @js.native + @JSGlobal + class A extends js.Object { + @JSOperator + def %%(x: Int): Int = js.native + } + """ hasErrors + """ + |newSource1.scala:9: error: @JSOperator cannot be used on a method with the name '%%' because it is not one of the JavaScript operators + | def %%(x: Int): Int = js.native + | ^ + """ } @Test def onlyJSTraits: Unit = { @@ -3370,12 +3476,12 @@ class JSInteropTest extends DirectTest with TestHelpers { object B extends js.Object """ hasErrors """ - |newSource1.scala:8: error: @JSBracketCall is not allowed on JS classes and objects - | class A extends js.Object - | ^ - |newSource1.scala:13: error: @JSBracketAccess is not allowed on JS classes and objects - | object B extends js.Object - | ^ + |newSource1.scala:7: error: @JSBracketCall can only be used on members of JS types. + | @JSBracketCall + | ^ + |newSource1.scala:12: error: @JSBracketAccess can only be used on members of JS types. + | @JSBracketAccess + | ^ """ // Non-native @@ -3387,12 +3493,12 @@ class JSInteropTest extends DirectTest with TestHelpers { object B extends js.Object """ hasErrors """ - |newSource1.scala:6: error: @JSBracketCall is not allowed on JS classes and objects - | class A extends js.Object - | ^ - |newSource1.scala:9: error: @JSBracketAccess is not allowed on JS classes and objects - | object B extends js.Object - | ^ + |newSource1.scala:5: error: @JSBracketCall can only be used on members of JS types. + | @JSBracketCall + | ^ + |newSource1.scala:8: error: @JSBracketAccess can only be used on members of JS types. + | @JSBracketAccess + | ^ """ // Nested native @@ -3410,12 +3516,12 @@ class JSInteropTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:10: error: @JSBracketCall is not allowed on JS classes and objects - | class A extends js.Object - | ^ - |newSource1.scala:14: error: @JSBracketAccess is not allowed on JS classes and objects - | object B extends js.Object - | ^ + |newSource1.scala:8: error: @JSBracketCall can only be used on methods. + | @JSBracketCall + | ^ + |newSource1.scala:12: error: @JSBracketAccess can only be used on methods. + | @JSBracketAccess + | ^ """ // Nested non-native @@ -3429,12 +3535,12 @@ class JSInteropTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:7: error: @JSBracketCall is not allowed on JS classes and objects - | object A extends js.Object - | ^ - |newSource1.scala:10: error: @JSBracketAccess is not allowed on JS classes and objects - | class B extends js.Object - | ^ + |newSource1.scala:6: error: @JSBracketCall can only be used on methods. + | @JSBracketCall + | ^ + |newSource1.scala:9: error: @JSBracketAccess can only be used on methods. + | @JSBracketAccess + | ^ """ } @@ -3456,7 +3562,7 @@ class JSInteropTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:13: error: A member can have at most one annotation among @JSName, @JSBracketAccess and @JSBracketCall. + |newSource1.scala:13: error: A member can have at most one annotation among @JSName, @JSOperator, @JSBracketAccess and @JSBracketCall. | @JSName("foo") | ^ """ @@ -4313,19 +4419,11 @@ class JSInteropTest extends DirectTest with TestHelpers { val postUnarySpace = { val hasNoSpace = { - version.startsWith("2.11.") || - version == "2.12.1" || - version == "2.12.2" || - version == "2.12.3" || - version == "2.12.4" || - version == "2.12.5" || version == "2.12.6" || version == "2.12.7" || version == "2.12.8" || version == "2.12.9" || - version == "2.12.10" || - version == "2.13.0" || - version == "2.13.1" + version == "2.12.10" } if (hasNoSpace) "" else " " @@ -4336,6 +4434,7 @@ class JSInteropTest extends DirectTest with TestHelpers { @js.native @JSGlobal class A extends js.Object { + @JSOperator def unary_+ : Int } @@ -4345,7 +4444,7 @@ class JSInteropTest extends DirectTest with TestHelpers { } """ hasErrors s""" - |newSource1.scala:13: error: A member of a JS class is overriding another member with a different JS calling convention. + |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention. | |def unary_+$postUnarySpace: Int in class B called from JS as property 'unary_+' | is conflicting with @@ -4365,13 +4464,19 @@ class JSInteropTest extends DirectTest with TestHelpers { @JSName("unary_+") override def unary_+(x: String): Int = 2 } - """.succeeds() + """ hasWarns + """ + |newSource1.scala:6: warning: Method 'unary_+' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators + | def unary_+(x: String): Int = 1 + | ^ + """ // binary op vs thing named like it """ @js.native @JSGlobal class A extends js.Object { + @JSOperator def ||(x: Int): Int } @@ -4381,7 +4486,7 @@ class JSInteropTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:13: error: A member of a JS class is overriding another member with a different JS calling convention. + |newSource1.scala:14: error: A member of a JS class is overriding another member with a different JS calling convention. | |def ||(x: Int): Int in class B called from JS as method '||' | is conflicting with @@ -4401,7 +4506,12 @@ class JSInteropTest extends DirectTest with TestHelpers { @JSName("||") override def ||(): Int = 2 } - """.succeeds() + """ hasWarns + """ + |newSource1.scala:6: warning: Method '||' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators + | def ||(): Int = 1 + | ^ + """ } @Test def noDefaultConstructorArgsIfModuleIsJSNative: Unit = { diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala index edfa1afd01..2e2c1664f2 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSNewTargetTest.scala @@ -122,16 +122,6 @@ class JSNewTargetTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:4: error: Illegal use of js.`new`.target. - |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var. - |It cannot be used inside a lambda or by-name parameter, nor in any other location. - | val x = () => js.`new`.target - | ^ - |newSource1.scala:5: error: Illegal use of js.`new`.target. - |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var. - |It cannot be used inside a lambda or by-name parameter, nor in any other location. - | val y = Option(null).getOrElse(js.`new`.target) - | ^ |newSource1.scala:6: error: Illegal use of js.`new`.target. |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var. |It cannot be used inside a lambda or by-name parameter, nor in any other location. @@ -142,6 +132,16 @@ class JSNewTargetTest extends DirectTest with TestHelpers { |It cannot be used inside a lambda or by-name parameter, nor in any other location. | val w: js.ThisFunction0[Any, Any] = (x: Any) => js.`new`.target | ^ + |newSource1.scala:4: error: Illegal use of js.`new`.target. + |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var. + |It cannot be used inside a lambda or by-name parameter, nor in any other location. + | val x = () => js.`new`.target + | ^ + |newSource1.scala:5: error: Illegal use of js.`new`.target. + |It can only be used in the constructor of a JS class, as a statement or in the rhs of a val or var. + |It cannot be used inside a lambda or by-name parameter, nor in any other location. + | val y = Option(null).getOrElse(js.`new`.target) + | ^ """ } diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSOptionalTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSOptionalTest.scala index 2fa4d0b6e9..c275ea0d59 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSOptionalTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSOptionalTest.scala @@ -82,12 +82,12 @@ class JSOptionalTest extends DirectTest with TestHelpers { | ^ """ - // Also for custom JS function types (2.11 has more errors than expected here) + // Also for custom JS function types s""" trait A extends js.Function { def apply(x: js.UndefOr[Int] = 1): Int } - """ containsErrors + """ hasErrors """ |newSource1.scala:6: error: Members of non-native JS traits may not have default parameters unless their default is `js.undefined`. | def apply(x: js.UndefOr[Int] = 1): Int diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala index 157a38156a..4eedcb3447 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSSAMTest.scala @@ -22,13 +22,6 @@ import org.junit.Test class JSSAMTest extends DirectTest with TestHelpers { - override def extraArgs: List[String] = { - if (scalaVersion.startsWith("2.11.")) - super.extraArgs :+ "-Xexperimental" - else - super.extraArgs - } - override def preamble: String = """ import scala.scalajs.js @@ -36,46 +29,7 @@ class JSSAMTest extends DirectTest with TestHelpers { """ @Test - def noSAMAsJSType211: Unit = { - assumeTrue(scalaVersion.startsWith("2.11.")) - - """ - @js.native - trait Foo extends js.Object { - def foo(x: Int): Int - } - - trait Bar extends js.Object { - def bar(x: Int): Int - } - - class Foobar extends js.Function { - def foobar(x: Int): Int - } - - class A { - val foo: Foo = x => x + 1 - val bar: Bar = x => x + 1 - val foobar: Foobar = x => x + 1 - } - """ hasErrors - """ - |newSource1.scala:19: error: Non-native JS types cannot directly extend native JS traits. - | val foo: Foo = x => x + 1 - | ^ - |newSource1.scala:20: error: $anonfun extends scala.Serializable which does not extend js.Any. - | val bar: Bar = x => x + 1 - | ^ - |newSource1.scala:21: error: $anonfun extends scala.Serializable which does not extend js.Any. - | val foobar: Foobar = x => x + 1 - | ^ - """ - } - - @Test - def noSAMAsJSType212Plus: Unit = { - assumeTrue(!scalaVersion.startsWith("2.11.")) - + def noSAMAsJSType: Unit = { """ @js.native trait Foo extends js.Object { @@ -110,39 +64,7 @@ class JSSAMTest extends DirectTest with TestHelpers { } @Test - def noSAMOfNativeJSFunctionType211: Unit = { - assumeTrue(scalaVersion.startsWith("2.11.")) - - """ - @js.native - trait Foo extends js.Function { - def apply(x: Int): Int - } - - @js.native - trait Bar extends js.Function { - def bar(x: Int = 5): Int - } - - class A { - val foo: Foo = x => x + 1 - val bar: Bar = x => x + 1 - } - """ hasErrors - """ - |newSource1.scala:16: error: Non-native JS types cannot directly extend native JS traits. - | val foo: Foo = x => x + 1 - | ^ - |newSource1.scala:17: error: Non-native JS types cannot directly extend native JS traits. - | val bar: Bar = x => x + 1 - | ^ - """ - } - - @Test - def noSAMOfNativeJSFunctionType212Plus: Unit = { - assumeTrue(!scalaVersion.startsWith("2.11.")) - + def noSAMOfNativeJSFunctionType: Unit = { """ @js.native trait Foo extends js.Function { @@ -204,10 +126,10 @@ class JSSAMTest extends DirectTest with TestHelpers { } """ hasErrors """ - |newSource1.scala:14: error: The SAM or apply method for a js.ThisFunction must have a leading non-varargs parameter + |newSource1.scala:14: error: The apply method for a js.ThisFunction must have a leading non-varargs parameter | val badThisFunction1: BadThisFunction1 = () => 42 | ^ - |newSource1.scala:15: error: The SAM or apply method for a js.ThisFunction must have a leading non-varargs parameter + |newSource1.scala:15: error: The apply method for a js.ThisFunction must have a leading non-varargs parameter | val badThisFunction2: BadThisFunction2 = args => args.size | ^ """ diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/LinkTimeIfTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/LinkTimeIfTest.scala new file mode 100644 index 0000000000..881c0e9a2f --- /dev/null +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/LinkTimeIfTest.scala @@ -0,0 +1,109 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.nscplugin.test + +import util._ + +import org.junit.Test +import org.junit.Assert._ + +// scalastyle:off line.size.limit + +class LinkTimeIfTest extends TestHelpers { + override def preamble: String = "import scala.scalajs.LinkingInfo._" + + private final val IllegalLinkTimeIfArgMessage = { + "Illegal expression in the condition of a linkTimeIf. " + + "Valid expressions are: boolean and int primitives; " + + "references to link-time properties; " + + "primitive operations on booleans; " + + "and comparisons on ints." + } + + @Test + def linkTimeErrorInvalidOp(): Unit = { + """ + object A { + def foo = + linkTimeIf((esVersion + 1) < ESVersion.ES2015) { } { } + } + """ hasErrors + s""" + |newSource1.scala:4: error: $IllegalLinkTimeIfArgMessage + | linkTimeIf((esVersion + 1) < ESVersion.ES2015) { } { } + | ^ + """ + } + + @Test + def linkTimeErrorInvalidEntities(): Unit = { + """ + object A { + def foo(x: String) = { + val bar = 1 + linkTimeIf(bar == 0) { } { } + } + } + """ hasErrors + s""" + |newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage + | linkTimeIf(bar == 0) { } { } + | ^ + """ + + // String comparison is a `BinaryOp.===`, which is not allowed + """ + object A { + def foo(x: String) = + linkTimeIf("foo" == x) { } { } + } + """ hasErrors + s""" + |newSource1.scala:4: error: $IllegalLinkTimeIfArgMessage + | linkTimeIf("foo" == x) { } { } + | ^ + """ + + """ + object A { + def bar = true + def foo(x: String) = + linkTimeIf(bar || !bar) { } { } + } + """ hasErrors + s""" + |newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage + | linkTimeIf(bar || !bar) { } { } + | ^ + |newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage + | linkTimeIf(bar || !bar) { } { } + | ^ + """ + } + + @Test + def linkTimeCondInvalidTree(): Unit = { + """ + object A { + def bar = true + def foo(x: String) = + linkTimeIf(if (bar) true else false) { } { } + } + """ hasErrors + s""" + |newSource1.scala:5: error: $IllegalLinkTimeIfArgMessage + | linkTimeIf(if (bar) true else false) { } { } + | ^ + """ + } +} diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala index 262a1b9479..a6d37efc1f 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/NonNativeJSTypeTest.scala @@ -179,9 +179,15 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers { } """ hasErrors """ + |newSource1.scala:6: warning: Method 'unary_+' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators + | def unary_+ : Int = 1 + | ^ |newSource1.scala:6: error: A non-native JS class cannot declare a method named like a unary operation without `@JSName` | def unary_+ : Int = 1 | ^ + |newSource1.scala:7: warning: Method 'unary_-' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators + | def unary_-() : Int = 1 + | ^ |newSource1.scala:7: error: A non-native JS class cannot declare a method named like a unary operation without `@JSName` | def unary_-() : Int = 1 | ^ @@ -206,9 +212,15 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers { } """ hasErrors """ + |newSource1.scala:6: warning: Method '+' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators + | def +(x: Int): Int = x + | ^ |newSource1.scala:6: error: A non-native JS class cannot declare a method named like a binary operation without `@JSName` | def +(x: Int): Int = x | ^ + |newSource1.scala:7: warning: Method '&&' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators + | def &&(x: String): String = x + | ^ |newSource1.scala:7: error: A non-native JS class cannot declare a method named like a binary operation without `@JSName` | def &&(x: String): String = x | ^ @@ -223,7 +235,15 @@ class NonNativeJSTypeTest extends DirectTest with TestHelpers { @JSName("&&") def &&(x: String): String = x } - """.succeeds() + """ hasWarns + """ + |newSource1.scala:6: warning: Method '+' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators + | def + : Int = 2 + | ^ + |newSource1.scala:8: warning: Method '-' should have an explicit @JSName or @JSOperator annotation because its name is one of the JavaScript operators + | def -(x: Int, y: Int): Int = 7 + | ^ + """ } @Test // #4281 diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala index 870965e893..b10bef4b95 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala @@ -15,6 +15,7 @@ package org.scalajs.nscplugin.test import util._ import org.junit.Test +import org.junit.Assert._ import org.scalajs.ir.{Trees => js, Types => jstpe} import org.scalajs.ir.Names._ @@ -169,14 +170,14 @@ class OptimizationTest extends JSASTTest { // Verify the optimized emitted code for 'new js.Object' and 'new js.Array' """ import scala.scalajs.js - class A { val o = new js.Object val a = new js.Array } """. - hasNot("any reference to the global scope") { - case js.JSLinkingInfo() => + hasNot("any reference to the global scope nor loading JS constructor") { + case js.JSGlobalRef(_) => + case js.LoadJSConstructor(_) => } } @@ -249,7 +250,7 @@ class OptimizationTest extends JSASTTest { } } """.hasNot("non-return labeled block") { - case js.Labeled(name, _, _) if !name.name.nameString.startsWith("_return") => + case js.Labeled(name, _, _) if !name.nameString.startsWith("_return") => } } @@ -322,7 +323,7 @@ class OptimizationTest extends JSASTTest { } } """.hasNot("non-return labeled block") { - case js.Labeled(name, _, _) if !name.name.nameString.startsWith("_return") => + case js.Labeled(name, _, _) if !name.nameString.startsWith("_return") => } } @@ -494,6 +495,93 @@ class OptimizationTest extends JSASTTest { case cl: js.ClassDef if !allowedNames.contains(cl.name.name) => } } + + @Test + def noWrapJavaScriptExceptionWhenCatchingWildcardThrowable: Unit = { + """ + class Test { + def foo(): Int = throw new IllegalArgumentException("foo") + + def testCatchFullWildcard(): Int = { + try { + foo() + } catch { + case _ => -1 // causes an expected Scala compile warning + } + } + + def testCatchWildcardOfTypeThrowable(): Int = { + try { + foo() + } catch { + case _: Throwable => -1 + } + } + } + """.hasNot("WrapAsThrowable") { + case js.UnaryOp(js.UnaryOp.WrapAsThrowable, _) => + } + + // Confidence check + """ + class Test { + def foo(): Int = throw new IllegalArgumentException("foo") + + def testCatchWildcardOfTypeRuntimeException(): Int = { + try { + foo() + } catch { + case _: RuntimeException => -1 + } + } + } + """.hasExactly(1, "WrapAsThrowable") { + case js.UnaryOp(js.UnaryOp.WrapAsThrowable, _) => + } + } + + @Test + def callSiteInlineSingleDispatchJSMethods: Unit = { + val fooName = SimpleMethodName("foo") + val aName = ClassName("A") + + val flags = { + """ + import scala.scalajs.js + + class A extends js.Object { + def foo(x: Int, y: Int = 2): Int = x + y + } + """.extractOne("foo dispatch call") { + case js.ApplyStatic(flags, `aName`, SMN("foo"), _) => + flags + } + } + + assertTrue(flags.inline) + } + + @Test + def loadModuleAfterStoreModuleIsThis: Unit = { + val testName = ClassName("Test$") + + """ + object Test { + private val selfPair = (Test, Test) + } + """.hasNot("LoadModule") { + case js.LoadModule(_) => + } + + // Confidence check + """ + object Test { + private def selfPair = (Test, Test) + } + """.hasExactly(2, "LoadModule") { + case js.LoadModule(`testName`) => + } + } } object OptimizationTest { @@ -502,12 +590,8 @@ object OptimizationTest { private val applySimpleMethodName = SimpleMethodName("apply") - private val hasOldCollections = { - val version = scala.util.Properties.versionNumberString - - version.startsWith("2.11.") || - version.startsWith("2.12.") - } + private val hasOldCollections = + scala.util.Properties.versionNumberString.startsWith("2.12.") private object WrapArrayCall { private val WrappedArrayTypeRef = { diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersASTTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersASTTest.scala index e718a73ff4..3505a06eaf 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersASTTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersASTTest.scala @@ -43,10 +43,10 @@ class StaticForwardersASTTest extends JSASTTest { case cd: ClassDef if cd.name.name == ClassName("Foo") => cd } - val staticMethodNames = classDef.memberDefs.collect { - case MethodDef(flags, MethodIdent(name), _, _, _, _) if flags.namespace.isStatic => - name - }.sortBy(_.simpleName) + val staticMethodNames = classDef.methods + .withFilter(_.flags.namespace.isStatic) + .map(_.name.name) + .sortBy(_.simpleName) assertEquals( List( @@ -74,10 +74,10 @@ class StaticForwardersASTTest extends JSASTTest { case cd: ClassDef if cd.name.name == ClassName("Foo") => cd } - val staticMethodNames = classDef.memberDefs.collect { - case MethodDef(flags, MethodIdent(name), _, _, _, _) if flags.namespace.isStatic => - name - }.sortBy(_.simpleName) + val staticMethodNames = classDef.methods + .withFilter(_.flags.namespace.isStatic) + .map(_.name.name) + .sortBy(_.simpleName) assertEquals( List( diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsTopLevelOnlyTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsTopLevelOnlyTest.scala index e3e9cf39ea..1b2438655e 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsTopLevelOnlyTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/StaticForwardersWarningsTopLevelOnlyTest.scala @@ -24,24 +24,6 @@ class StaticForwardersWarningsTopLevelOnlyTest extends DirectTest with TestHelpe @Test def warnWhenAvoidingStaticForwardersForTopLevelObject: Unit = { - val jvmBackendIssuesWarningOfItsOwn = { - !scalaVersion.startsWith("2.11.") && - scalaVersion != "2.12.1" && - scalaVersion != "2.12.2" && - scalaVersion != "2.12.3" && - scalaVersion != "2.12.4" - } - val jvmBackendMessage = if (!jvmBackendIssuesWarningOfItsOwn) { - "" - } else { - """ - |newSource1.scala:4: warning: Generated class a differs only in case from A. - | Such classes will overwrite one another on case-insensitive filesystems. - | object a { - | ^ - """ - } - """ class A @@ -52,7 +34,11 @@ class StaticForwardersWarningsTopLevelOnlyTest extends DirectTest with TestHelpe s""" |newSource1.scala:4: warning: Not generating the static forwarders of a because its name differs only in case from the name of another class or trait in this compilation unit. | object a { - | ^$jvmBackendMessage + | ^ + |newSource1.scala:4: warning: Generated class a differs only in case from A. + | Such classes will overwrite one another on case-insensitive filesystems. + | object a { + | ^ """ } diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/DirectTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/DirectTest.scala index 72a2978c05..5fd603c286 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/DirectTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/DirectTest.scala @@ -38,14 +38,14 @@ abstract class DirectTest { } def newScalaJSCompiler(args: String*): Global = { - val settings = newSettings( + val settings0 = newSettings( List( "-d", testOutputPath, "-bootclasspath", scalaLibPath, "-classpath", classpath.mkString(File.pathSeparator)) ++ extraArgs ++ args.toList) - lazy val global: Global = new Global(settings, newReporter(settings)) { + lazy val global: Global = new Global(settings0, newReporter(settings0)) { private implicit class PluginCompat(val plugin: Plugin) { def options: List[String] = { val prefix = plugin.name + ":" diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/JSASTTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/JSASTTest.scala index 7b1e52ca44..5a74a397fb 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/JSASTTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/JSASTTest.scala @@ -17,6 +17,7 @@ import language.implicitConversions import scala.tools.nsc._ import scala.reflect.internal.util.SourceFile +import scala.collection.mutable import scala.util.control.ControlThrowable import org.junit.Assert._ @@ -27,7 +28,10 @@ import ir.{Trees => js} abstract class JSASTTest extends DirectTest { - private var lastAST: JSAST = _ + object SMN { + def unapply(ident: js.MethodIdent): Some[String] = + Some(ident.name.simpleName.nameString) + } class JSAST(val clDefs: List[js.ClassDef]) { type Pat = PartialFunction[js.IRNode, Unit] @@ -62,9 +66,24 @@ abstract class JSASTTest extends DirectTest { super.traverseClassDef(classDef) } - override def traverseMemberDef(memberDef: js.MemberDef): Unit = { - handle(memberDef) - super.traverseMemberDef(memberDef) + override def traverseAnyFieldDef(fieldDef: js.AnyFieldDef): Unit = { + handle(fieldDef) + super.traverseAnyFieldDef(fieldDef) + } + + override def traverseMethodDef(methodDef: js.MethodDef): Unit = { + handle(methodDef) + super.traverseMethodDef(methodDef) + } + + override def traverseJSConstructorDef(jsConstructor: js.JSConstructorDef): Unit = { + handle(jsConstructor) + super.traverseJSConstructorDef(jsConstructor) + } + + override def traverseJSMethodPropDef(jsMethodPropDef: js.JSMethodPropDef): Unit = { + handle(jsMethodPropDef) + super.traverseJSMethodPropDef(jsMethodPropDef) } override def traverseTopLevelExportDef( @@ -133,26 +152,45 @@ abstract class JSASTTest extends DirectTest { implicit def string2ast(str: String): JSAST = stringAST(str) + private var generatedClassDefs: Option[mutable.ListBuffer[js.ClassDef]] = None + + private def captureGeneratedClassDefs(body: => Unit): JSAST = { + if (generatedClassDefs.isDefined) + throw new IllegalStateException(s"Nested or concurrent calls to captureGeneratedClassDefs") + + val buffer = new mutable.ListBuffer[js.ClassDef] + generatedClassDefs = Some(buffer) + try { + body + new JSAST(buffer.toList) + } finally { + generatedClassDefs = None + } + } + override def newScalaJSPlugin(global: Global): ScalaJSPlugin = { new ScalaJSPlugin(global) { - override def generatedJSAST(cld: List[js.ClassDef]): Unit = { - lastAST = new JSAST(cld) + override def generatedJSAST(cld: js.ClassDef): Unit = { + for (buffer <- generatedClassDefs) + buffer += cld } } } def stringAST(code: String): JSAST = stringAST(defaultGlobal)(code) def stringAST(global: Global)(code: String): JSAST = { - if (!compileString(global)(code)) - throw new IllegalArgumentException("snippet did not compile") - lastAST + captureGeneratedClassDefs { + if (!compileString(global)(code)) + throw new IllegalArgumentException("snippet did not compile") + } } def sourceAST(source: SourceFile): JSAST = sourceAST(defaultGlobal)(source) def sourceAST(global: Global)(source: SourceFile): JSAST = { - if (!compileSources(global)(source)) - throw new IllegalArgumentException("snippet did not compile") - lastAST + captureGeneratedClassDefs { + if (!compileSources(global)(source)) + throw new IllegalArgumentException("snippet did not compile") + } } } diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/VersionDependentUtils.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/VersionDependentUtils.scala index fe976ebe9e..7e52a1edf8 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/util/VersionDependentUtils.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/util/VersionDependentUtils.scala @@ -15,25 +15,12 @@ package org.scalajs.nscplugin.test.util object VersionDependentUtils { val scalaVersion = scala.util.Properties.versionNumberString - /** Does the current Scala version support the `@nowarn` annotation? */ - val scalaSupportsNoWarn = { - !scalaVersion.startsWith("2.11.") && - !scalaVersion.startsWith("2.12.") && - scalaVersion != "2.13.0" && - scalaVersion != "2.13.1" - } + private val isScala212 = scalaVersion.startsWith("2.12.") - private val usesColonInMethodSig = { - /* Yes, this is the same test as in scalaSupportsNoWarn, but that's - * completely coincidental, so we have a copy. - */ - !scalaVersion.startsWith("2.11.") && - !scalaVersion.startsWith("2.12.") && - scalaVersion != "2.13.0" && - scalaVersion != "2.13.1" - } + /** Does the current Scala version support the `@nowarn` annotation? */ + val scalaSupportsNoWarn = !isScala212 def methodSig(params: String, resultType: String): String = - if (usesColonInMethodSig) params + ": " + resultType + if (!isScala212) params + ": " + resultType else params + resultType } diff --git a/examples/helloworld/helloworld-2.11-fastopt.html b/examples/helloworld/helloworld-2.11-fastopt.html deleted file mode 100644 index 8f7eceddd9..0000000000 --- a/examples/helloworld/helloworld-2.11-fastopt.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - Hello world - Scala.js example - - - - -
-
- - - - - - - diff --git a/examples/helloworld/helloworld-2.11.html b/examples/helloworld/helloworld-2.11.html deleted file mode 100644 index ad39b7a9fe..0000000000 --- a/examples/helloworld/helloworld-2.11.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - Hello world - Scala.js example - - - - -
-
- - - - - - - diff --git a/examples/reversi/reversi-2.11-fastopt.html b/examples/reversi/reversi-2.11-fastopt.html deleted file mode 100644 index 0f60bc72c3..0000000000 --- a/examples/reversi/reversi-2.11-fastopt.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - Reversi - Scala.js example - - - - -

Reversi - Scala.js example

- -

Somewhat inspired by -http://davidbau.com/reversi/

- -
-
- - - - - - - - - diff --git a/examples/reversi/reversi-2.11.html b/examples/reversi/reversi-2.11.html deleted file mode 100644 index fe7b7a8dce..0000000000 --- a/examples/reversi/reversi-2.11.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - Reversi - Scala.js example - - - - -

Reversi - Scala.js example

- -

Somewhat inspired by -http://davidbau.com/reversi/

- -
-
- - - - - - - - - diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ClassKind.scala b/ir/shared/src/main/scala/org/scalajs/ir/ClassKind.scala index 9c8b42410a..0b46f5e25a 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ClassKind.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ClassKind.scala @@ -27,6 +27,11 @@ sealed abstract class ClassKind { case _ => false } + def isNativeJSClass: Boolean = this match { + case NativeJSClass | NativeJSModuleClass => true + case _ => false + } + def isJSType: Boolean = this match { case AbstractJSType | JSClass | JSModuleClass | NativeJSClass | NativeJSModuleClass => @@ -45,6 +50,7 @@ sealed abstract class ClassKind { case _ => false } + @deprecated("not a meaningful operation", since = "1.13.2") def withoutModuleAccessor: ClassKind = this match { case ModuleClass => Class case JSModuleClass => JSClass diff --git a/ir/shared/src/main/scala/org/scalajs/ir/EntryPointsInfo.scala b/ir/shared/src/main/scala/org/scalajs/ir/EntryPointsInfo.scala index e0f6f239b8..e64f831d69 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/EntryPointsInfo.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/EntryPointsInfo.scala @@ -24,13 +24,9 @@ object EntryPointsInfo { def forClassDef(classDef: ClassDef): EntryPointsInfo = { val hasEntryPoint = { classDef.topLevelExportDefs.nonEmpty || - classDef.memberDefs.exists { - case m: MethodDef => + classDef.methods.exists(m => m.flags.namespace == MemberNamespace.StaticConstructor && - m.methodName.isStaticInitializer - case _ => - false - } + m.methodName.isStaticInitializer) } new EntryPointsInfo(classDef.name.name, hasEntryPoint) } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala index 06f75eb392..599e9e8c1c 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Hashers.scala @@ -13,7 +13,6 @@ package org.scalajs.ir import java.io.{DataOutputStream, OutputStream} -import java.util.Arrays import Names._ import Trees._ @@ -23,8 +22,9 @@ import Tags._ object Hashers { def hashMethodDef(methodDef: MethodDef): MethodDef = { - if (methodDef.hash.isDefined) methodDef - else { + if (methodDef.version.isHash) { + methodDef + } else { val hasher = new TreeHasher() val MethodDef(flags, name, originalName, args, resultType, body) = methodDef @@ -40,13 +40,36 @@ object Hashers { val hash = hasher.finalizeHash() MethodDef(flags, name, originalName, args, resultType, body)( - methodDef.optimizerHints, Some(hash))(methodDef.pos) + methodDef.optimizerHints, hash)(methodDef.pos) + } + } + + def hashJSConstructorDef(ctorDef: JSConstructorDef): JSConstructorDef = { + if (ctorDef.version.isHash) { + ctorDef + } else { + val hasher = new TreeHasher() + val JSConstructorDef(flags, params, restParam, body) = ctorDef + + hasher.mixPos(ctorDef.pos) + hasher.mixInt(MemberFlags.toBits(flags)) + hasher.mixParamDefs(params) + restParam.foreach(hasher.mixParamDef(_)) + hasher.mixPos(body.pos) + hasher.mixTrees(body.allStats) + hasher.mixInt(OptimizerHints.toBits(ctorDef.optimizerHints)) + + val hash = hasher.finalizeHash() + + JSConstructorDef(flags, params, restParam, body)( + ctorDef.optimizerHints, hash)(ctorDef.pos) } } def hashJSMethodDef(methodDef: JSMethodDef): JSMethodDef = { - if (methodDef.hash.isDefined) methodDef - else { + if (methodDef.version.isHash) { + methodDef + } else { val hasher = new TreeHasher() val JSMethodDef(flags, name, params, restParam, body) = methodDef @@ -61,46 +84,61 @@ object Hashers { val hash = hasher.finalizeHash() JSMethodDef(flags, name, params, restParam, body)( - methodDef.optimizerHints, Some(hash))(methodDef.pos) + methodDef.optimizerHints, hash)(methodDef.pos) + } + } + + def hashJSPropertyDef(propDef: JSPropertyDef): JSPropertyDef = { + if (propDef.version.isHash) { + propDef + } else { + val hasher = new TreeHasher() + val JSPropertyDef(flags, name, getterBody, setterArgAndBody) = propDef + + hasher.mixPos(propDef.pos) + hasher.mixInt(MemberFlags.toBits(flags)) + hasher.mixTree(name) + getterBody.foreach(hasher.mixTree(_)) + setterArgAndBody.foreach { case (param, body) => + hasher.mixParamDef(param) + hasher.mixTree(body) + } + + val hash = hasher.finalizeHash() + + JSPropertyDef(flags, name, getterBody, setterArgAndBody)(hash)(propDef.pos) } } - /** Hash definitions from a ClassDef where applicable */ - def hashMemberDefs(memberDefs: List[MemberDef]): List[MemberDef] = memberDefs.map { - case methodDef: MethodDef => hashMethodDef(methodDef) - case methodDef: JSMethodDef => hashJSMethodDef(methodDef) - case otherDef => otherDef + def hashTopLevelExportDef(tle: TopLevelExportDef): TopLevelExportDef = tle match { + case TopLevelMethodExportDef(moduleID, methodDef) => + TopLevelMethodExportDef(moduleID, hashJSMethodDef(methodDef))(tle.pos) + + case _:TopLevelFieldExportDef | _:TopLevelModuleExportDef | + _:TopLevelJSClassExportDef => + tle } /** Hash the definitions in a ClassDef (where applicable) */ def hashClassDef(classDef: ClassDef): ClassDef = { import classDef._ - val newMemberDefs = hashMemberDefs(memberDefs) + val newMethods = methods.map(hashMethodDef(_)) + val newJSConstructorDef = jsConstructor.map(hashJSConstructorDef(_)) + val newExportedMembers = jsMethodProps.map { + case methodDef: JSMethodDef => hashJSMethodDef(methodDef) + case propDef: JSPropertyDef => hashJSPropertyDef(propDef) + } + val newTopLevelExportDefs = topLevelExportDefs.map(hashTopLevelExportDef(_)) ClassDef(name, originalName, kind, jsClassCaptures, superClass, interfaces, - jsSuperClass, jsNativeLoadSpec, newMemberDefs, topLevelExportDefs)( + jsSuperClass, jsNativeLoadSpec, fields, newMethods, newJSConstructorDef, + newExportedMembers, jsNativeMembers, newTopLevelExportDefs)( optimizerHints) } - def hashesEqual(x: TreeHash, y: TreeHash): Boolean = - Arrays.equals(x.hash, y.hash) - - def hashAsVersion(hash: TreeHash): String = { - // 2 chars per byte, 20 bytes in a hash - val size = 2 * 20 - val builder = new StringBuilder(size) - - def hexDigit(digit: Int): Char = Character.forDigit(digit, 16) - - for (b <- hash.hash) - builder.append(hexDigit((b >> 4) & 0x0f)).append(hexDigit(b & 0x0f)) - - builder.toString - } - private final class TreeHasher { - private[this] val digestBuilder = new SHA1.DigestBuilder + private val digestBuilder = new SHA1.DigestBuilder - private[this] val digestStream = { + private val digestStream = { new DataOutputStream(new OutputStream { def write(b: Int): Unit = digestBuilder.update(b.toByte) @@ -113,8 +151,8 @@ object Hashers { }) } - def finalizeHash(): TreeHash = - new TreeHash(digestBuilder.finalizeDigest()) + def finalizeHash(): Version = + Version.fromHash(digestBuilder.finalizeDigest()) def mixParamDef(paramDef: ParamDef): Unit = { mixPos(paramDef.pos) @@ -147,7 +185,7 @@ object Hashers { case Labeled(label, tpe, body) => mixTag(TagLabeled) - mixLabelIdent(label) + mixName(label) mixType(tpe) mixTree(body) @@ -159,7 +197,7 @@ object Hashers { case Return(expr, label) => mixTag(TagReturn) mixTree(expr) - mixLabelIdent(label) + mixName(label) case If(cond, thenp, elsep) => mixTag(TagIf) @@ -168,16 +206,18 @@ object Hashers { mixTree(elsep) mixType(tree.tpe) + case LinkTimeIf(cond, thenp, elsep) => + mixTag(TagLinkTimeIf) + mixTree(cond) + mixTree(thenp) + mixTree(elsep) + mixType(tree.tpe) + case While(cond, body) => mixTag(TagWhile) mixTree(cond) mixTree(body) - case DoWhile(body, cond) => - mixTag(TagDoWhile) - mixTree(body) - mixTree(cond) - case ForIn(obj, keyVar, keyVarOriginalName, body) => mixTag(TagForIn) mixTree(obj) @@ -199,10 +239,6 @@ object Hashers { mixTree(finalizer) mixType(tree.tpe) - case Throw(expr) => - mixTag(TagThrow) - mixTree(expr) - case Match(selector, cases, default) => mixTag(TagMatch) mixTree(selector) @@ -213,6 +249,10 @@ object Hashers { mixTree(default) mixType(tree.tpe) + case JSAwait(arg) => + mixTag(TagJSAwait) + mixTree(arg) + case Debugger() => mixTag(TagDebugger) @@ -226,21 +266,17 @@ object Hashers { mixTag(TagLoadModule) mixName(className) - case StoreModule(className, value) => + case StoreModule() => mixTag(TagStoreModule) - mixName(className) - mixTree(value) - case Select(qualifier, className, field) => + case Select(qualifier, field) => mixTag(TagSelect) mixTree(qualifier) - mixName(className) mixFieldIdent(field) mixType(tree.tpe) - case SelectStatic(className, field) => + case SelectStatic(field) => mixTag(TagSelectStatic) - mixName(className) mixFieldIdent(field) mixType(tree.tpe) @@ -281,6 +317,24 @@ object Hashers { mixMethodIdent(method) mixTrees(args) + case ApplyTypedClosure(flags, fun, args) => + mixTag(TagApplyTypedClosure) + mixInt(ApplyFlags.toBits(flags)) + mixTree(fun) + mixTrees(args) + + case NewLambda(descriptor, fun) => + val NewLambda.Descriptor(superClass, interfaces, methodName, paramTypes, resultType) = + descriptor + mixTag(TagNewLambda) + mixName(superClass) + mixNames(interfaces) + mixMethodName(methodName) + mixTypes(paramTypes) + mixType(resultType) + mixTree(fun) + mixType(tree.tpe) + case UnaryOp(op, lhs) => mixTag(TagUnaryOp) mixInt(op) @@ -292,20 +346,16 @@ object Hashers { mixTree(lhs) mixTree(rhs) - case NewArray(typeRef, lengths) => + case NewArray(typeRef, length) => mixTag(TagNewArray) mixArrayTypeRef(typeRef) - mixTrees(lengths) + mixTrees(length :: Nil) // mixed as a list for historical reasons case ArrayValue(typeRef, elems) => mixTag(TagArrayValue) mixArrayTypeRef(typeRef) mixTrees(elems) - case ArrayLength(array) => - mixTag(TagArrayLength) - mixTree(array) - case ArraySelect(array, index) => mixTag(TagArraySelect) mixTree(array) @@ -320,7 +370,7 @@ object Hashers { case RecordSelect(record, field) => mixTag(TagRecordSelect) mixTree(record) - mixFieldIdent(field) + mixSimpleFieldIdent(field) mixType(tree.tpe) case IsInstanceOf(expr, testType) => @@ -333,27 +383,14 @@ object Hashers { mixTree(expr) mixType(tpe) - case GetClass(expr) => - mixTag(TagGetClass) - mixTree(expr) - - case Clone(expr) => - mixTag(TagClone) - mixTree(expr) - - case IdentityHashCode(expr) => - mixTag(TagIdentityHashCode) - mixTree(expr) - case JSNew(ctor, args) => mixTag(TagJSNew) mixTree(ctor) mixTreeOrJSSpreads(args) - case JSPrivateSelect(qualifier, className, field) => + case JSPrivateSelect(qualifier, field) => mixTag(TagJSPrivateSelect) mixTree(qualifier) - mixName(className) mixFieldIdent(field) case JSSelect(qualifier, item) => @@ -442,9 +479,6 @@ object Hashers { mixTag(TagJSTypeOfGlobalRef) mixTree(globalRef) - case JSLinkingInfo() => - mixTag(TagJSLinkingInfo) - case Undefined() => mixTag(TagUndefined) @@ -491,21 +525,30 @@ object Hashers { mixTag(TagClassOf) mixTypeRef(typeRef) - case VarRef(ident) => - mixTag(TagVarRef) - mixLocalIdent(ident) - mixType(tree.tpe) - - case This() => - mixTag(TagThis) + case VarRef(name) => + if (name.isThis) { + // "Optimized" representation, like in Serializers + mixTag(TagThis) + } else { + mixTag(TagVarRef) + mixName(name) + } mixType(tree.tpe) - case Closure(arrow, captureParams, params, restParam, body, captureValues) => + case Closure(flags, captureParams, params, restParam, resultType, body, captureValues) => mixTag(TagClosure) - mixBoolean(arrow) + mixByte(ClosureFlags.toBits(flags).toByte) mixParamDefs(captureParams) mixParamDefs(params) - restParam.foreach(mixParamDef(_)) + if (flags.typed) { + if (restParam.isDefined) + throw new InvalidIRException(tree, "Cannot hash a typed closure with a rest param") + mixType(resultType) + } else { + if (resultType != AnyType) + throw new InvalidIRException(tree, "Cannot hash a JS closure with a result type != AnyType") + restParam.foreach(mixParamDef(_)) + } mixTree(body) mixTrees(captureValues) @@ -514,6 +557,11 @@ object Hashers { mixName(className) mixTrees(captureValues) + case LinkTimeProperty(name) => + mixTag(TagLinkTimeProperty) + mixString(name) + mixType(tree.tpe) + case Transient(value) => throw new InvalidIRException(tree, "Cannot hash a transient IR node (its value is of class " + @@ -543,7 +591,7 @@ object Hashers { def mixTypeRef(typeRef: TypeRef): Unit = typeRef match { case PrimRef(tpe) => tpe match { - case NoType => mixTag(TagVoidRef) + case VoidType => mixTag(TagVoidRef) case BooleanType => mixTag(TagBooleanRef) case CharType => mixTag(TagCharRef) case ByteType => mixTag(TagByteRef) @@ -561,6 +609,10 @@ object Hashers { case typeRef: ArrayTypeRef => mixTag(TagArrayTypeRef) mixArrayTypeRef(typeRef) + case TransientTypeRef(name) => + mixTag(TagTransientTypeRefHashingOnly) + mixName(name) + // The `tpe` is intentionally ignored here; see doc of `TransientTypeRef`. } def mixArrayTypeRef(arrayTypeRef: ArrayTypeRef): Unit = { @@ -569,29 +621,35 @@ object Hashers { } def mixType(tpe: Type): Unit = tpe match { - case AnyType => mixTag(TagAnyType) - case NothingType => mixTag(TagNothingType) - case UndefType => mixTag(TagUndefType) - case BooleanType => mixTag(TagBooleanType) - case CharType => mixTag(TagCharType) - case ByteType => mixTag(TagByteType) - case ShortType => mixTag(TagShortType) - case IntType => mixTag(TagIntType) - case LongType => mixTag(TagLongType) - case FloatType => mixTag(TagFloatType) - case DoubleType => mixTag(TagDoubleType) - case StringType => mixTag(TagStringType) - case NullType => mixTag(TagNullType) - case NoType => mixTag(TagNoType) - - case ClassType(className) => - mixTag(TagClassType) + case AnyType => mixTag(TagAnyType) + case AnyNotNullType => mixTag(TagAnyNotNullType) + case NothingType => mixTag(TagNothingType) + case UndefType => mixTag(TagUndefType) + case BooleanType => mixTag(TagBooleanType) + case CharType => mixTag(TagCharType) + case ByteType => mixTag(TagByteType) + case ShortType => mixTag(TagShortType) + case IntType => mixTag(TagIntType) + case LongType => mixTag(TagLongType) + case FloatType => mixTag(TagFloatType) + case DoubleType => mixTag(TagDoubleType) + case StringType => mixTag(TagStringType) + case NullType => mixTag(TagNullType) + case VoidType => mixTag(TagVoidType) + + case ClassType(className, nullable) => + mixTag(if (nullable) TagClassType else TagNonNullClassType) mixName(className) - case ArrayType(arrayTypeRef) => - mixTag(TagArrayType) + case ArrayType(arrayTypeRef, nullable) => + mixTag(if (nullable) TagArrayType else TagNonNullArrayType) mixArrayTypeRef(arrayTypeRef) + case ClosureType(paramTypes, resultType, nullable) => + mixTag(if (nullable) TagClosureType else TagNonNullClosureType) + mixTypes(paramTypes) + mixType(resultType) + case RecordType(fields) => mixTag(TagRecordType) for (RecordType.Field(name, originalName, tpe, mutable) <- fields) { @@ -602,19 +660,24 @@ object Hashers { } } + def mixTypes(tpes: List[Type]): Unit = + tpes.foreach(mixType) + def mixLocalIdent(ident: LocalIdent): Unit = { mixPos(ident.pos) mixName(ident.name) } - def mixLabelIdent(ident: LabelIdent): Unit = { + def mixSimpleFieldIdent(ident: SimpleFieldIdent): Unit = { mixPos(ident.pos) mixName(ident.name) } def mixFieldIdent(ident: FieldIdent): Unit = { + // For historical reasons, the className comes *before* the position + mixName(ident.name.className) mixPos(ident.pos) - mixName(ident.name) + mixName(ident.name.simpleName) } def mixMethodIdent(ident: MethodIdent): Unit = { @@ -630,6 +693,11 @@ object Hashers { def mixName(name: Name): Unit = mixBytes(name.encoded.bytes) + def mixNames(names: List[Name]): Unit = { + mixInt(names.size) + names.foreach(mixName(_)) + } + def mixMethodName(name: MethodName): Unit = { mixName(name.simpleName) mixInt(name.paramTypeRefs.size) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/InvalidIRException.scala b/ir/shared/src/main/scala/org/scalajs/ir/InvalidIRException.scala index f481798458..2e8a272388 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/InvalidIRException.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/InvalidIRException.scala @@ -12,5 +12,12 @@ package org.scalajs.ir -class InvalidIRException(val tree: Trees.IRNode, message: String) - extends Exception(message) +class InvalidIRException(val optTree: Option[Trees.IRNode], message: String) + extends Exception(message) { + + def this(tree: Trees.IRNode, message: String) = + this(Some(tree), message) + + def this(message: String) = + this(None, message) +} diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Names.scala b/ir/shared/src/main/scala/org/scalajs/ir/Names.scala index 004db0c98b..685949052f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Names.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Names.scala @@ -12,20 +12,11 @@ package org.scalajs.ir -import scala.annotation.switch +import scala.annotation.{switch, tailrec} import Types._ object Names { - private final val ConstructorSimpleEncodedName: UTF8String = - UTF8String("") - - private final val StaticInitializerSimpleEncodedName: UTF8String = - UTF8String("") - - private final val ClassInitializerSimpleEncodedName: UTF8String = - UTF8String("") - // scalastyle:off equals.hash.code // we define hashCode() once in Name, but equals() separately in its subclasses @@ -73,6 +64,10 @@ object Names { * * Local names must be non-empty, and can contain any Unicode code point * except `/ . ; [`. + * + * As an exception, the local name `".this"` represents the `this` binding. + * It cannot be used to declare variables (in `VarDef`s or `ParamDef`s) but + * can be referred to with a `VarRef`. */ final class LocalName private (encoded: UTF8String) extends Name(encoded) with Comparable[LocalName] { @@ -88,27 +83,45 @@ object Names { protected def stringPrefix: String = "LocalName" - final def withPrefix(prefix: LocalName): LocalName = + final def isThis: Boolean = + this eq LocalName.This + + final def withPrefix(prefix: LocalName): LocalName = { + require(!isThis && !prefix.isThis, "cannot concatenate LocalName.This") new LocalName(prefix.encoded ++ this.encoded) + } final def withPrefix(prefix: String): LocalName = LocalName(UTF8String(prefix) ++ this.encoded) - final def withSuffix(suffix: LocalName): LocalName = + final def withSuffix(suffix: LocalName): LocalName = { + require(!isThis && !suffix.isThis, "cannot concatenate LocalName.This") new LocalName(this.encoded ++ suffix.encoded) + } final def withSuffix(suffix: String): LocalName = LocalName(this.encoded ++ UTF8String(suffix)) } object LocalName { - def apply(name: UTF8String): LocalName = - new LocalName(validateSimpleEncodedName(name)) + private final val ThisEncodedName: UTF8String = + UTF8String(".this") + + /** The unique `LocalName` with encoded name `.this`. */ + val This: LocalName = + new LocalName(ThisEncodedName) + + def apply(name: UTF8String): LocalName = { + if (UTF8String.equals(name, ThisEncodedName)) + This + else + new LocalName(validateSimpleEncodedName(name)) + } def apply(name: String): LocalName = LocalName(UTF8String(name)) - private[Names] def fromFieldName(name: FieldName): LocalName = + private[Names] def fromSimpleFieldName(name: SimpleFieldName): LocalName = new LocalName(name.encoded) } @@ -146,38 +159,90 @@ object Names { LabelName(UTF8String(name)) } - /** The name of a field. + /** The simple name of a field (excluding its enclosing class). * * Field names must be non-empty, and can contain any Unicode code point * except `/ . ; [`. */ - final class FieldName private (encoded: UTF8String) - extends Name(encoded) with Comparable[FieldName] { + final class SimpleFieldName private (encoded: UTF8String) + extends Name(encoded) with Comparable[SimpleFieldName] { - type ThisName = FieldName + type ThisName = SimpleFieldName override def equals(that: Any): Boolean = { (this eq that.asInstanceOf[AnyRef]) || (that match { - case that: FieldName => equalsName(that) - case _ => false + case that: SimpleFieldName => equalsName(that) + case _ => false }) } - protected def stringPrefix: String = "FieldName" + protected def stringPrefix: String = "SimpleFieldName" - final def withSuffix(suffix: String): FieldName = - FieldName(this.encoded ++ UTF8String(suffix)) + final def withSuffix(suffix: String): SimpleFieldName = + SimpleFieldName(this.encoded ++ UTF8String(suffix)) final def toLocalName: LocalName = - LocalName.fromFieldName(this) + LocalName.fromSimpleFieldName(this) } - object FieldName { - def apply(name: UTF8String): FieldName = - new FieldName(validateSimpleEncodedName(name)) + object SimpleFieldName { + def apply(name: UTF8String): SimpleFieldName = + new SimpleFieldName(validateSimpleEncodedName(name)) - def apply(name: String): FieldName = - FieldName(UTF8String(name)) + def apply(name: String): SimpleFieldName = + SimpleFieldName(UTF8String(name)) + } + + /** The full name of a field, including its simple name and its enclosing + * class name. + */ + final class FieldName private ( + val className: ClassName, val simpleName: SimpleFieldName) + extends Comparable[FieldName] { + + import FieldName._ + + private val _hashCode: Int = { + import scala.util.hashing.MurmurHash3._ + var acc = -1025990011 // "FieldName".hashCode() + acc = mix(acc, className.##) + acc = mix(acc, simpleName.##) + finalizeHash(acc, 2) + } + + override def equals(that: Any): Boolean = { + (this eq that.asInstanceOf[AnyRef]) || (that match { + case that: FieldName => + this._hashCode == that._hashCode && // fail fast on different hash codes + this.className == that.className && + this.simpleName == that.simpleName + case _ => + false + }) + } + + override def hashCode(): Int = _hashCode + + def compareTo(that: FieldName): Int = { + val classNameCmp = this.className.compareTo(that.className) + if (classNameCmp != 0) + classNameCmp + else + this.simpleName.compareTo(that.simpleName) + } + + protected def stringPrefix: String = "FieldName" + + def nameString: String = + className.nameString + "::" + simpleName.nameString + + override def toString(): String = + "FieldName<" + nameString + ">" + } + + object FieldName { + def apply(className: ClassName, simpleName: SimpleFieldName): FieldName = + new FieldName(className, simpleName) } /** The simple name of a method (excluding its signature). @@ -214,16 +279,25 @@ object Names { } object SimpleMethodName { + private final val ConstructorSimpleEncodedName: UTF8String = + UTF8String("") + + private final val StaticInitializerSimpleEncodedName: UTF8String = + UTF8String("") + + private final val ClassInitializerSimpleEncodedName: UTF8String = + UTF8String("") + /** The unique `SimpleMethodName` with encoded name ``. */ - private val Constructor: SimpleMethodName = + val Constructor: SimpleMethodName = new SimpleMethodName(ConstructorSimpleEncodedName) /** The unique `SimpleMethodName` with encoded name ``. */ - private val StaticInitializer: SimpleMethodName = + val StaticInitializer: SimpleMethodName = new SimpleMethodName(StaticInitializerSimpleEncodedName) /** The unique `SimpleMethodName` with encoded name ``. */ - private val ClassInitializer: SimpleMethodName = + val ClassInitializer: SimpleMethodName = new SimpleMethodName(ClassInitializerSimpleEncodedName) def apply(name: UTF8String): SimpleMethodName = { @@ -258,20 +332,24 @@ object Names { SimpleMethodName(UTF8String(name)) } - val ConstructorSimpleName: SimpleMethodName = - SimpleMethodName(ConstructorSimpleEncodedName) + @deprecated("Use SimpleMethodName.Constructor instead", "1.14.0") + def ConstructorSimpleName: SimpleMethodName = + SimpleMethodName.Constructor - val StaticInitializerSimpleName: SimpleMethodName = - SimpleMethodName(StaticInitializerSimpleEncodedName) + @deprecated("Use SimpleMethodName.StaticInitializer instead", "1.14.0") + def StaticInitializerSimpleName: SimpleMethodName = + SimpleMethodName.StaticInitializer - val ClassInitializerSimpleName: SimpleMethodName = - SimpleMethodName(ClassInitializerSimpleEncodedName) + @deprecated("Use SimpleMethodName.ClassInitializer instead", "1.14.0") + def ClassInitializerSimpleName: SimpleMethodName = + SimpleMethodName.ClassInitializer /** The full name of a method, including its simple name and its signature. */ final class MethodName private (val simpleName: SimpleMethodName, val paramTypeRefs: List[TypeRef], val resultTypeRef: TypeRef, - val isReflectiveProxy: Boolean) { + val isReflectiveProxy: Boolean) + extends Comparable[MethodName] { import MethodName._ @@ -300,6 +378,35 @@ object Names { override def hashCode(): Int = _hashCode + def compareTo(that: MethodName): Int = { + @tailrec + def compareParamTypeRefs(xs: List[TypeRef], ys: List[TypeRef]): Int = (xs, ys) match { + case (x :: xr, y :: yr) => + val cmp = x.compareTo(y) + if (cmp != 0) cmp + else compareParamTypeRefs(xr, yr) + case _ => + java.lang.Boolean.compare(xs.isEmpty, ys.isEmpty) + } + + val simpleCmp = this.simpleName.compareTo(that.simpleName) + if (simpleCmp != 0) { + simpleCmp + } else { + val paramsCmp = compareParamTypeRefs(this.paramTypeRefs, that.paramTypeRefs) + if (paramsCmp != 0) { + paramsCmp + } else { + val reflProxyCmp = java.lang.Boolean.compare( + this.isReflectiveProxy, that.isReflectiveProxy) + if (reflProxyCmp != 0) + reflProxyCmp + else + this.resultTypeRef.compareTo(that.resultTypeRef) + } + } + } + protected def stringPrefix: String = "MethodName" def nameString: String = { @@ -308,7 +415,7 @@ object Names { def appendTypeRef(typeRef: TypeRef): Unit = typeRef match { case PrimRef(tpe) => tpe match { - case NoType => builder.append('V') + case VoidType => builder.append('V') case BooleanType => builder.append('Z') case CharType => builder.append('C') case ByteType => builder.append('B') @@ -329,6 +436,8 @@ object Names { i += 1 } appendTypeRef(base) + case TransientTypeRef(name) => + builder.append('t').append(name.nameString) } builder.append(simpleName.nameString) @@ -364,9 +473,6 @@ object Names { } object MethodName { - private val ReflectiveProxyResultTypeRef = ClassRef(ObjectClass) - private final val ReflectiveProxyResultTypeName = "java.lang.Object" - def apply(simpleName: SimpleMethodName, paramTypeRefs: List[TypeRef], resultTypeRef: TypeRef, isReflectiveProxy: Boolean): MethodName = { if ((simpleName.isConstructor || simpleName.isStaticInitializer || @@ -374,11 +480,18 @@ object Names { throw new IllegalArgumentException( "A constructor or static initializer must have a void result type") } - if (isReflectiveProxy && resultTypeRef != ReflectiveProxyResultTypeRef) { - throw new IllegalArgumentException( - "A reflective proxy must have a result type of " + - ReflectiveProxyResultTypeName) + + if (isReflectiveProxy) { + /* It is fine to use WellKnownNames here because nothing in `Names` + * nor `Types` ever creates a reflective proxy name. So this code path + * is not reached during their initialization. + */ + if (resultTypeRef != WellKnownNames.ObjectRef) { + throw new IllegalArgumentException( + "A reflective proxy must have a result type of java.lang.Object") + } } + new MethodName(simpleName, paramTypeRefs, resultTypeRef, isReflectiveProxy) } @@ -396,13 +509,17 @@ object Names { } def constructor(paramTypeRefs: List[TypeRef]): MethodName = { - new MethodName(ConstructorSimpleName, paramTypeRefs, VoidRef, + new MethodName(SimpleMethodName.Constructor, paramTypeRefs, VoidRef, isReflectiveProxy = false) } def reflectiveProxy(simpleName: SimpleMethodName, paramTypeRefs: List[TypeRef]): MethodName = { - apply(simpleName, paramTypeRefs, ReflectiveProxyResultTypeRef, + /* It is fine to use WellKnownNames here because nothing in `Names` + * nor `Types` ever creates a reflective proxy name. So this code path + * is not reached during their initialization. + */ + apply(simpleName, paramTypeRefs, WellKnownNames.ObjectRef, isReflectiveProxy = true) } @@ -446,88 +563,6 @@ object Names { // scalastyle:on equals.hash.code - /** `java.lang.Object`, the root of the class hierarchy. */ - val ObjectClass: ClassName = ClassName("java.lang.Object") - - // Hijacked classes - val BoxedUnitClass: ClassName = ClassName("java.lang.Void") - val BoxedBooleanClass: ClassName = ClassName("java.lang.Boolean") - val BoxedCharacterClass: ClassName = ClassName("java.lang.Character") - val BoxedByteClass: ClassName = ClassName("java.lang.Byte") - val BoxedShortClass: ClassName = ClassName("java.lang.Short") - val BoxedIntegerClass: ClassName = ClassName("java.lang.Integer") - val BoxedLongClass: ClassName = ClassName("java.lang.Long") - val BoxedFloatClass: ClassName = ClassName("java.lang.Float") - val BoxedDoubleClass: ClassName = ClassName("java.lang.Double") - val BoxedStringClass: ClassName = ClassName("java.lang.String") - - /** The set of all hijacked classes. */ - val HijackedClasses: Set[ClassName] = Set( - BoxedUnitClass, - BoxedBooleanClass, - BoxedCharacterClass, - BoxedByteClass, - BoxedShortClass, - BoxedIntegerClass, - BoxedLongClass, - BoxedFloatClass, - BoxedDoubleClass, - BoxedStringClass - ) - - /** The class of things returned by `ClassOf` and `GetClass`. */ - val ClassClass: ClassName = ClassName("java.lang.Class") - - /** `java.lang.Cloneable`, which is an ancestor of array classes and is used - * by `Clone`. - */ - val CloneableClass: ClassName = ClassName("java.lang.Cloneable") - - /** `java.io.Serializable`, which is an ancestor of array classes. */ - val SerializableClass: ClassName = ClassName("java.io.Serializable") - - /** The exception thrown by a division by 0. */ - val ArithmeticExceptionClass: ClassName = - ClassName("java.lang.ArithmeticException") - - /** The exception thrown by an `ArraySelect` that is out of bounds. */ - val ArrayIndexOutOfBoundsExceptionClass: ClassName = - ClassName("java.lang.ArrayIndexOutOfBoundsException") - - /** The exception thrown by an `AsInstanceOf` that fails. */ - val ClassCastExceptionClass: ClassName = - ClassName("java.lang.ClassCastException") - - /** The set of classes and interfaces that are ancestors of array classes. */ - private[ir] val AncestorsOfPseudoArrayClass: Set[ClassName] = { - /* This would logically be defined in Types, but that introduces a cyclic - * dependency between the initialization of Names and Types. - */ - Set(ObjectClass, CloneableClass, SerializableClass) - } - - /** Name of a constructor without argument. - * - * This is notably the signature of constructors of module classes. - */ - final val NoArgConstructorName: MethodName = - MethodName.constructor(Nil) - - /** This is used to construct a java.lang.Class. */ - final val ObjectArgConstructorName: MethodName = - MethodName.constructor(List(ClassRef(ObjectClass))) - - /** Name of the static initializer method. */ - final val StaticInitializerName: MethodName = - MethodName(StaticInitializerSimpleName, Nil, VoidRef) - - /** Name of the class initializer method. */ - final val ClassInitializerName: MethodName = - MethodName(ClassInitializerSimpleName, Nil, VoidRef) - - /** ModuleID of the default module */ - final val DefaultModuleID: String = "main" - // --------------------------------------------------- // ----- Private helpers for validation of names ----- // --------------------------------------------------- diff --git a/ir/shared/src/main/scala/org/scalajs/ir/OriginalName.scala b/ir/shared/src/main/scala/org/scalajs/ir/OriginalName.scala index d2211095d5..7ae6745e53 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/OriginalName.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/OriginalName.scala @@ -53,6 +53,10 @@ final class OriginalName private (private val bytes: Array[Byte]) if (isDefined) this else OriginalName(name) + // new in 1.16.0; added as last overload to preserve binary compatibility + def orElse(name: FieldName): OriginalName = + orElse(name.simpleName) + def getOrElse(name: Name): UTF8String = getOrElse(name.encoded) @@ -71,6 +75,10 @@ final class OriginalName private (private val bytes: Array[Byte]) else UTF8String(name) } + // new in 1.16.0; added as last overload to preserve binary compatibility + def getOrElse(name: FieldName): UTF8String = + getOrElse(name.simpleName) + override def toString(): String = if (isDefined) s"OriginalName($unsafeGet)" else "NoOriginalName" diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Position.scala b/ir/shared/src/main/scala/org/scalajs/ir/Position.scala index c2b60fb598..3406627ee2 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Position.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Position.scala @@ -20,9 +20,7 @@ final case class Position( /** Zero-based column number. */ column: Int ) { - def show: String = s"$line:$column" - - def isEmpty: Boolean = { + private val _isEmpty: Boolean = { def isEmptySlowPath(): Boolean = { source.getScheme == null && source.getRawAuthority == null && source.getRawQuery == null && source.getRawFragment == null @@ -30,6 +28,10 @@ final case class Position( source.getRawPath == "" && isEmptySlowPath() } + def show: String = s"$line:$column" + + def isEmpty: Boolean = _isEmpty + def isDefined: Boolean = !isEmpty def orElse(that: => Position): Position = if (isDefined) this else that diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala index 737d3189b4..9a05ed7788 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Printers.scala @@ -77,18 +77,31 @@ object Printers { print(end) } - protected def printBlock(tree: Tree): Unit = { - tree match { - case Block(trees) => - printColumn(trees, "{", ";", "}") + protected final def printRow(ts: List[Type], start: String, sep: String, + end: String)(implicit dummy: DummyImplicit): Unit = { + print(start) + var rest = ts + while (rest.nonEmpty) { + print(rest.head) + rest = rest.tail + if (rest.nonEmpty) + print(sep) + } + print(end) + } - case _ => - print('{'); indent(); println() - print(tree) - undent(); println(); print('}') + protected def printBlock(tree: Tree): Unit = { + val trees = tree match { + case Block(trees) => trees + case Skip() => Nil + case _ => tree :: Nil } + printBlock(trees) } + protected def printBlock(trees: List[Tree]): Unit = + printColumn(trees, "{", ";", "}") + protected def printSig(args: List[ParamDef], restParam: Option[ParamDef], resultType: Type): Unit = { print("(") @@ -107,7 +120,7 @@ object Printers { print(")") - if (resultType != NoType) { + if (resultType != VoidType) { print(": ") print(resultType) print(" = ") @@ -123,7 +136,7 @@ object Printers { def printAnyNode(node: IRNode): Unit = { node match { case node: LocalIdent => print(node) - case node: LabelIdent => print(node) + case node: SimpleFieldIdent => print(node) case node: FieldIdent => print(node) case node: MethodIdent => print(node) case node: ClassIdent => print(node) @@ -132,6 +145,7 @@ object Printers { case node: JSSpread => print(node) case node: ClassDef => print(node) case node: MemberDef => print(node) + case node: JSConstructorBody => printBlock(node.allStats) case node: TopLevelExportDef => print(node) } } @@ -172,7 +186,7 @@ object Printers { case Labeled(label, tpe, body) => print(label) - if (tpe != NoType) { + if (tpe != VoidType) { print('[') print(tpe) print(']') @@ -188,8 +202,10 @@ object Printers { case Return(expr, label) => print("return@") print(label) - print(" ") - print(expr) + if (!expr.isInstanceOf[Skip]) { + print(" ") + print(expr) + } case If(cond, BooleanLiteral(true), elsep) => print(cond) @@ -217,19 +233,20 @@ object Printers { printBlock(elsep) } + case LinkTimeIf(cond, thenp, elsep) => + print("link-time-if (") + print(cond) + print(") ") + printBlock(thenp) + print(" else ") + printBlock(elsep) + case While(cond, body) => print("while (") print(cond) print(") ") printBlock(body) - case DoWhile(body, cond) => - print("do ") - printBlock(body) - print(" while (") - print(cond) - print(')') - case ForIn(obj, keyVar, keyVarOriginalName, body) => print("for (val ") print(keyVar) @@ -265,10 +282,6 @@ object Printers { print(" finally ") printBlock(finalizer) - case Throw(expr) => - print("throw ") - print(expr) - case Match(selector, cases, default) => print("match (") print(selector) @@ -287,6 +300,11 @@ object Printers { undent() undent(); println(); print('}') + case JSAwait(arg) => + print("await(") + print(arg) + print(")") + case Debugger() => print("debugger") @@ -303,22 +321,15 @@ object Printers { print("mod:") print(className) - case StoreModule(className, value) => - print("mod:") - print(className) - print("<-") - print(value) + case StoreModule() => + print("") - case Select(qualifier, className, field) => + case Select(qualifier, field) => print(qualifier) print('.') - print(className) - print("::") print(field) - case SelectStatic(className, field) => - print(className) - print("::") + case SelectStatic(field) => print(field) case SelectJSNativeMember(className, member) => @@ -356,29 +367,85 @@ object Printers { print(method) printArgs(args) + case ApplyTypedClosure(flags, fun, args) => + print(fun) + printArgs(args) + + case NewLambda(descriptor, fun) => + val NewLambda.Descriptor(superClass, interfaces, methodName, paramTypes, resultType) = + descriptor + + print("("); indent(); println() + + print("extends ") + print(superClass) + if (interfaces.nonEmpty) { + print(" implements ") + print(interfaces.head) + for (intf <- interfaces.tail) { + print(", ") + print(intf) + } + } + print(',') + println() + + print("def ") + print(methodName) + printRow(paramTypes, "(", ", ", "): ") + print(resultType) + print(',') + println() + + print(fun) + + undent(); println(); print(')') + case UnaryOp(op, lhs) => import UnaryOp._ - print('(') - print((op: @switch) match { + + def p(prefix: String, suffix: String): Unit = { + print(prefix) + print(lhs) + print(suffix) + } + + (op: @switch) match { case Boolean_! => - "!" + p("(!", ")") case IntToChar => - "(char)" + p("((char)", ")") case IntToByte => - "(byte)" + p("((byte)", ")") case IntToShort => - "(short)" + p("((short)", ")") case CharToInt | ByteToInt | ShortToInt | LongToInt | DoubleToInt => - "(int)" + p("((int)", ")") case IntToLong | DoubleToLong => - "(long)" + p("((long)", ")") case DoubleToFloat | LongToFloat => - "(float)" + p("((float)", ")") case IntToDouble | LongToDouble | FloatToDouble => - "(double)" - }) - print(lhs) - print(')') + p("((double)", ")") + + case String_length => p("", ".length") + case CheckNotNull => p("", ".notNull") + case Class_name => p("", ".name") + case Class_isPrimitive => p("", ".isPrimitive") + case Class_isInterface => p("", ".isInterface") + case Class_isArray => p("", ".isArray") + case Class_componentType => p("", ".componentType") + case Class_superClass => p("", ".superClass") + case Array_length => p("", ".length") + case GetClass => p("", ".getClass()") + + case Clone => p("(", ")") + case IdentityHashCode => p("(", ")") + case WrapAsThrowable => p("(", ")") + case UnwrapFromThrowable => p("(", ")") + + case Throw => p("throw ", "") + } case BinaryOp(BinaryOp.Int_-, IntLiteral(0), rhs) => print("(-") @@ -411,6 +478,25 @@ object Printers { print(rhs) print(')') + case BinaryOp(BinaryOp.String_charAt, lhs, rhs) => + print(lhs) + print('[') + print(rhs) + print(']') + + case BinaryOp(op, lhs, rhs) if BinaryOp.isClassOp(op) => + import BinaryOp._ + print((op: @switch) match { + case Class_isInstance => "isInstance(" + case Class_isAssignableFrom => "isAssignableFrom(" + case Class_cast => "cast(" + case Class_newArray => "newArray(" + }) + print(lhs) + print(", ") + print(rhs) + print(')') + case BinaryOp(op, lhs, rhs) => import BinaryOp._ print('(') @@ -490,25 +576,19 @@ object Printers { print(rhs) print(')') - case NewArray(typeRef, lengths) => + case NewArray(typeRef, length) => print("new ") print(typeRef.base) - for (length <- lengths) { - print('[') - print(length) - print(']') - } - for (dim <- lengths.size until typeRef.dimensions) + print('[') + print(length) + print(']') + for (dim <- 1 until typeRef.dimensions) print("[]") case ArrayValue(typeRef, elems) => print(typeRef) printArgs(elems) - case ArrayLength(array) => - print(array) - print(".length") - case ArraySelect(array, index) => print(array) print('[') @@ -544,29 +624,14 @@ object Printers { print(tpe) print(']') - case GetClass(expr) => - print(expr) - print(".getClass()") - - case Clone(expr) => - print("(") - print(expr) - print(')') - - case IdentityHashCode(expr) => - print("(") - print(expr) - print(')') - // JavaScript expressions case JSNew(ctor, args) => def containsOnlySelectsFromAtom(tree: Tree): Boolean = tree match { - case JSPrivateSelect(qual, _, _) => containsOnlySelectsFromAtom(qual) - case JSSelect(qual, _) => containsOnlySelectsFromAtom(qual) - case VarRef(_) => true - case This() => true - case _ => false // in particular, Apply + case JSPrivateSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case JSSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case VarRef(_) => true + case _ => false // in particular, Apply } if (containsOnlySelectsFromAtom(ctor)) { print("new ") @@ -578,11 +643,9 @@ object Printers { } printArgs(args) - case JSPrivateSelect(qualifier, className, field) => + case JSPrivateSelect(qualifier, field) => print(qualifier) print('.') - print(className) - print("::") print(field) case JSSelect(qualifier, item) => @@ -706,6 +769,8 @@ object Printers { case `in` => "in" case `instanceof` => "instanceof" + + case ** => "**" }) print(" ") print(rhs) @@ -749,13 +814,10 @@ object Printers { print(globalRef) print(")") - case JSLinkingInfo() => - print("") - // Literals case Undefined() => - print("(void 0)") + print("undefined") case Null() => print("null") @@ -841,17 +903,23 @@ object Printers { // Atomic expressions - case VarRef(ident) => - print(ident) - - case This() => - print("this") - - case Closure(arrow, captureParams, params, restParam, body, captureValues) => - if (arrow) - print("(arrow-lambda<") + case VarRef(name) => + if (name.isThis) + print("this") + else + print(name) + + case Closure(flags, captureParams, params, restParam, resultType, body, captureValues) => + print("(") + if (flags.async) + print("async ") + if (flags.typed) + print("typed-lambda") + else if (flags.arrow) + print("arrow-lambda") else - print("(lambda<") + print("lambda") + print("<") var first = true for ((param, value) <- captureParams.zip(captureValues)) { if (first) @@ -863,7 +931,7 @@ object Printers { print(value) } print(">") - printSig(params, restParam, AnyType) + printSig(params, restParam, resultType) printBlock(body) print(')') @@ -872,6 +940,11 @@ object Printers { print(className) printRow(captureValues, "](", ", ", ")") + case LinkTimeProperty(name) => + print("(") + print(name) + print(")") + // Transient case Transient(value) => @@ -931,7 +1004,8 @@ object Printers { print(spec) } print(" ") - printColumn(memberDefs ::: topLevelExportDefs, "{", "", "}") + printColumn(fields ::: methods ::: jsConstructor.toList ::: + jsMethodProps ::: jsNativeMembers ::: topLevelExportDefs, "{", "", "}") } def print(memberDef: MemberDef): Unit = { @@ -971,6 +1045,14 @@ object Printers { printBlock(body) } + case tree: JSConstructorDef => + val JSConstructorDef(flags, args, restParam, body) = tree + print(tree.optimizerHints) + print(flags.namespace.prefixString) + print("def constructor") + printSig(args, restParam, AnyType) + printBlock(body.allStats) + case tree: JSMethodDef => val JSMethodDef(flags, name, args, restParam, body) = tree print(tree.optimizerHints) @@ -997,7 +1079,7 @@ object Printers { print(flags.namespace.prefixString) print("set ") printJSMemberName(name) - printSig(arg :: Nil, None, NoType) + printSig(arg :: Nil, None, VoidType) printBlock(body) } @@ -1047,27 +1129,43 @@ object Printers { print(base) for (i <- 1 to dims) print("[]") + case TransientTypeRef(name) => + print(name) } def print(tpe: Type): Unit = tpe match { - case AnyType => print("any") - case NothingType => print("nothing") - case UndefType => print("void") - case BooleanType => print("boolean") - case CharType => print("char") - case ByteType => print("byte") - case ShortType => print("short") - case IntType => print("int") - case LongType => print("long") - case FloatType => print("float") - case DoubleType => print("double") - case StringType => print("string") - case NullType => print("null") - case ClassType(className) => print(className) - case NoType => print("") - - case ArrayType(arrayTypeRef) => + case AnyType => print("any") + case AnyNotNullType => print("any!") + case NothingType => print("nothing") + case UndefType => print("undef") + case BooleanType => print("boolean") + case CharType => print("char") + case ByteType => print("byte") + case ShortType => print("short") + case IntType => print("int") + case LongType => print("long") + case FloatType => print("float") + case DoubleType => print("double") + case StringType => print("string") + case NullType => print("null") + case VoidType => print("void") + + case ClassType(className, nullable) => + print(className) + if (!nullable) + print("!") + + case ArrayType(arrayTypeRef, nullable) => print(arrayTypeRef) + if (!nullable) + print("!") + + case ClosureType(paramTypes, resultType, nullable) => + printRow(paramTypes, "((", ", ", ") => ") + print(resultType) + print(')') + if (!nullable) + print('!') case RecordType(fields) => print('(') @@ -1089,7 +1187,7 @@ object Printers { def print(ident: LocalIdent): Unit = print(ident.name) - def print(ident: LabelIdent): Unit = + def print(ident: SimpleFieldIdent): Unit = print(ident.name) def print(ident: FieldIdent): Unit = @@ -1104,6 +1202,9 @@ object Printers { def print(name: Name): Unit = printEscapeJS(name.nameString, out) + def print(name: FieldName): Unit = + printEscapeJS(name.nameString, out) + def print(name: MethodName): Unit = printEscapeJS(name.nameString, out) diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index f3381569b6..23292cbcdc 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,8 +17,8 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.10.1-SNAPSHOT", - binaryEmitted = "1.8" + current = "1.20.0-SNAPSHOT", + binaryEmitted = "1.20-SNAPSHOT" ) /** Helper class to allow for testing of logic. */ diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala index a8a1e13bb9..628630dfa1 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala @@ -22,10 +22,14 @@ import scala.collection.mutable import scala.concurrent._ import Names._ +import OriginalName.NoOriginalName import Position._ import Trees._ +import LinkTimeProperty.{ProductionMode, ESVersion, UseECMAScript2015Semantics, IsWebAssembly, LinkerVersion} import Types._ import Tags._ +import Version.Unversioned +import WellKnownNames._ import Utils.JumpBackByteArrayOutputStream @@ -38,6 +42,18 @@ object Serializers { */ final val IRMagicNumber = 0xCAFE4A53 + /** A regex for a compatible stable binary IR version from which we may need + * to migrate things with hacks. + */ + private val CompatibleStableIRVersionRegex = { + val prefix = java.util.regex.Pattern.quote(ScalaJSVersions.binaryCross + ".") + new scala.util.matching.Regex(prefix + "(\\d+)") + } + + // For deserialization hack + private final val DynamicImportThunkClass = + ClassName("scala.scalajs.runtime.DynamicImportThunk") + def serialize(stream: OutputStream, classDef: ClassDef): Unit = { new Serializer().serialize(stream, classDef) } @@ -114,24 +130,24 @@ object Serializers { } private final class Serializer { - private[this] val bufferUnderlying = new JumpBackByteArrayOutputStream - private[this] val buffer = new DataOutputStream(bufferUnderlying) + private val bufferUnderlying = new JumpBackByteArrayOutputStream + private val buffer = new DataOutputStream(bufferUnderlying) - private[this] val files = mutable.ListBuffer.empty[URI] - private[this] val fileIndexMap = mutable.Map.empty[URI, Int] + private val files = mutable.ListBuffer.empty[URI] + private val fileIndexMap = mutable.Map.empty[URI, Int] private def fileToIndex(file: URI): Int = fileIndexMap.getOrElseUpdate(file, (files += file).size - 1) - private[this] val encodedNames = mutable.ListBuffer.empty[UTF8String] - private[this] val encodedNameIndexMap = mutable.Map.empty[EncodedNameKey, Int] + private val encodedNames = mutable.ListBuffer.empty[UTF8String] + private val encodedNameIndexMap = mutable.Map.empty[EncodedNameKey, Int] private def encodedNameToIndex(encoded: UTF8String): Int = { val byteString = new EncodedNameKey(encoded) encodedNameIndexMap.getOrElseUpdate(byteString, (encodedNames += encoded).size - 1) } - private[this] val methodNames = mutable.ListBuffer.empty[MethodName] - private[this] val methodNameIndexMap = mutable.Map.empty[MethodName, Int] + private val methodNames = mutable.ListBuffer.empty[MethodName] + private val methodNameIndexMap = mutable.Map.empty[MethodName, Int] private def methodNameToIndex(methodName: MethodName): Int = { methodNameIndexMap.getOrElseUpdate(methodName, { // need to reserve the internal simple names @@ -143,6 +159,8 @@ object Serializers { encodedNameToIndex(className.encoded) case ArrayTypeRef(base, _) => reserveTypeRef(base) + case typeRef: TransientTypeRef => + throw new InvalidIRException(s"Cannot serialize a transient type ref: $typeRef") } encodedNameToIndex(methodName.simpleName.encoded) @@ -152,12 +170,12 @@ object Serializers { }) } - private[this] val strings = mutable.ListBuffer.empty[String] - private[this] val stringIndexMap = mutable.Map.empty[String, Int] + private val strings = mutable.ListBuffer.empty[String] + private val stringIndexMap = mutable.Map.empty[String, Int] private def stringToIndex(str: String): Int = stringIndexMap.getOrElseUpdate(str, (strings += str).size - 1) - private[this] var lastPosition: Position = Position.NoPosition + private var lastPosition: Position = Position.NoPosition def serialize(stream: OutputStream, classDef: ClassDef): Unit = { // Write tree to buffer and record files, names and strings @@ -192,7 +210,7 @@ object Serializers { def writeTypeRef(typeRef: TypeRef): Unit = typeRef match { case PrimRef(tpe) => tpe match { - case NoType => s.writeByte(TagVoidRef) + case VoidType => s.writeByte(TagVoidRef) case BooleanType => s.writeByte(TagBooleanRef) case CharType => s.writeByte(TagCharRef) case ByteType => s.writeByte(TagByteRef) @@ -211,6 +229,13 @@ object Serializers { s.writeByte(TagArrayTypeRef) writeTypeRef(base) s.writeInt(dimensions) + case typeRef: TransientTypeRef => + throw new InvalidIRException(s"Cannot serialize a transient type ref: $typeRef") + } + + def writeTypeRefs(typeRefs: List[TypeRef]): Unit = { + s.writeInt(typeRefs.size) + typeRefs.foreach(writeTypeRef(_)) } // Emit the method names @@ -218,8 +243,7 @@ object Serializers { methodNames.foreach { methodName => s.writeInt(encodedNameIndexMap( new EncodedNameKey(methodName.simpleName.encoded))) - s.writeInt(methodName.paramTypeRefs.size) - methodName.paramTypeRefs.foreach(writeTypeRef(_)) + writeTypeRefs(methodName.paramTypeRefs) writeTypeRef(methodName.resultTypeRef) s.writeBoolean(methodName.isReflectiveProxy) writeName(methodName.simpleName) @@ -258,7 +282,7 @@ object Serializers { case Labeled(label, tpe, body) => writeTagAndPos(TagLabeled) - writeLabelIdent(label); writeType(tpe); writeTree(body) + writeName(label); writeType(tpe); writeTree(body) case Assign(lhs, rhs) => writeTagAndPos(TagAssign) @@ -266,21 +290,22 @@ object Serializers { case Return(expr, label) => writeTagAndPos(TagReturn) - writeTree(expr); writeLabelIdent(label) + writeTree(expr); writeName(label) case If(cond, thenp, elsep) => writeTagAndPos(TagIf) writeTree(cond); writeTree(thenp); writeTree(elsep) writeType(tree.tpe) + case LinkTimeIf(cond, thenp, elsep) => + writeTagAndPos(TagLinkTimeIf) + writeTree(cond); writeTree(thenp); writeTree(elsep) + writeType(tree.tpe) + case While(cond, body) => writeTagAndPos(TagWhile) writeTree(cond); writeTree(body) - case DoWhile(body, cond) => - writeTagAndPos(TagDoWhile) - writeTree(body); writeTree(cond) - case ForIn(obj, keyVar, keyVarOriginalName, body) => writeTagAndPos(TagForIn) writeTree(obj); writeLocalIdent(keyVar) @@ -296,10 +321,6 @@ object Serializers { writeTagAndPos(TagTryFinally) writeTree(block); writeTree(finalizer) - case Throw(expr) => - writeTagAndPos(TagThrow) - writeTree(expr) - case Match(selector, cases, default) => writeTagAndPos(TagMatch) writeTree(selector) @@ -310,6 +331,10 @@ object Serializers { writeTree(default) writeType(tree.tpe) + case JSAwait(arg) => + writeTagAndPos(TagJSAwait) + writeTree(arg) + case Debugger() => writeTagAndPos(TagDebugger) @@ -321,18 +346,17 @@ object Serializers { writeTagAndPos(TagLoadModule) writeName(className) - case StoreModule(className, value) => + case StoreModule() => writeTagAndPos(TagStoreModule) - writeName(className); writeTree(value) - case Select(qualifier, className, field) => + case Select(qualifier, field) => writeTagAndPos(TagSelect) - writeTree(qualifier); writeName(className); writeFieldIdent(field) + writeTree(qualifier); writeFieldIdent(field) writeType(tree.tpe) - case SelectStatic(className, field) => + case SelectStatic(field) => writeTagAndPos(TagSelectStatic) - writeName(className); writeFieldIdent(field) + writeFieldIdent(field) writeType(tree.tpe) case SelectJSNativeMember(className, member) => @@ -358,6 +382,22 @@ object Serializers { writeTagAndPos(TagApplyDynamicImport) writeApplyFlags(flags); writeName(className); writeMethodIdent(method); writeTrees(args) + case ApplyTypedClosure(flags, fun, args) => + writeTagAndPos(TagApplyTypedClosure) + writeApplyFlags(flags); writeTree(fun); writeTrees(args) + + case NewLambda(descriptor, fun) => + val NewLambda.Descriptor(superClass, interfaces, methodName, paramTypes, resultType) = + descriptor + writeTagAndPos(TagNewLambda) + writeName(superClass) + writeNames(interfaces) + writeMethodName(methodName) + writeTypes(paramTypes) + writeType(resultType) + writeTree(fun) + writeType(tree.tpe) + case UnaryOp(op, lhs) => writeTagAndPos(TagUnaryOp) writeByte(op); writeTree(lhs) @@ -366,18 +406,15 @@ object Serializers { writeTagAndPos(TagBinaryOp) writeByte(op); writeTree(lhs); writeTree(rhs) - case NewArray(tpe, lengths) => + case NewArray(tpe, length) => writeTagAndPos(TagNewArray) - writeArrayTypeRef(tpe); writeTrees(lengths) + writeArrayTypeRef(tpe) + writeTrees(length :: Nil) // written as a list of historical reasons case ArrayValue(tpe, elems) => writeTagAndPos(TagArrayValue) writeArrayTypeRef(tpe); writeTrees(elems) - case ArrayLength(array) => - writeTagAndPos(TagArrayLength) - writeTree(array) - case ArraySelect(array, index) => writeTagAndPos(TagArraySelect) writeTree(array); writeTree(index) @@ -389,7 +426,7 @@ object Serializers { case RecordSelect(record, field) => writeTagAndPos(TagRecordSelect) - writeTree(record); writeFieldIdent(field) + writeTree(record); writeSimpleFieldIdent(field) writeType(tree.tpe) case IsInstanceOf(expr, testType) => @@ -400,25 +437,13 @@ object Serializers { writeTagAndPos(TagAsInstanceOf) writeTree(expr); writeType(tpe) - case GetClass(expr) => - writeTagAndPos(TagGetClass) - writeTree(expr) - - case Clone(expr) => - writeTagAndPos(TagClone) - writeTree(expr) - - case IdentityHashCode(expr) => - writeTagAndPos(TagIdentityHashCode) - writeTree(expr) - case JSNew(ctor, args) => writeTagAndPos(TagJSNew) writeTree(ctor); writeTreeOrJSSpreads(args) - case JSPrivateSelect(qualifier, className, field) => + case JSPrivateSelect(qualifier, field) => writeTagAndPos(TagJSPrivateSelect) - writeTree(qualifier); writeName(className); writeFieldIdent(field) + writeTree(qualifier); writeFieldIdent(field) case JSSelect(qualifier, item) => writeTagAndPos(TagJSSelect) @@ -494,9 +519,6 @@ object Serializers { writeTagAndPos(TagJSTypeOfGlobalRef) writeTree(globalRef) - case JSLinkingInfo() => - writeTagAndPos(TagJSLinkingInfo) - case Undefined() => writeTagAndPos(TagUndefined) @@ -543,21 +565,33 @@ object Serializers { writeTagAndPos(TagClassOf) writeTypeRef(typeRef) - case VarRef(ident) => - writeTagAndPos(TagVarRef) - writeLocalIdent(ident) - writeType(tree.tpe) - - case This() => - writeTagAndPos(TagThis) + case VarRef(name) => + if (name.isThis) { + // "Optimized" representation that is compatible with IR < 1.18 + writeTagAndPos(TagThis) + } else { + writeTagAndPos(TagVarRef) + writeName(name) + } writeType(tree.tpe) - case Closure(arrow, captureParams, params, restParam, body, captureValues) => + case Closure(flags, captureParams, params, restParam, resultType, body, captureValues) => writeTagAndPos(TagClosure) - writeBoolean(arrow) + writeClosureFlags(flags) writeParamDefs(captureParams) writeParamDefs(params) - writeOptParamDef(restParam) + + // Compatible with IR < v1.19, which had no `resultType` + if (flags.typed) { + if (restParam.isDefined) + throw new InvalidIRException(tree, "Cannot serialize a typed closure with a rest param") + writeType(resultType) + } else { + if (resultType != AnyType) + throw new InvalidIRException(tree, "Cannot serialize a JS closure with a result type != AnyType") + writeOptParamDef(restParam) + } + writeTree(body) writeTrees(captureValues) @@ -566,6 +600,11 @@ object Serializers { writeName(className) writeTrees(captureValues) + case LinkTimeProperty(name) => + writeTagAndPos(TagLinkTimeProperty) + writeString(name) + writeType(tree.tpe) + case Transient(value) => throw new InvalidIRException(tree, "Cannot serialize a transient IR node (its value is of class " + @@ -616,7 +655,7 @@ object Serializers { writeClassIdents(interfaces) writeOptTree(jsSuperClass) writeJSNativeLoadSpec(jsNativeLoadSpec) - writeMemberDefs(memberDefs) + writeMemberDefs(fields ::: methods ::: jsConstructor.toList ::: jsMethodProps ::: jsNativeMembers) writeTopLevelExportDefs(topLevelExportDefs) writeInt(OptimizerHints.toBits(optimizerHints)) } @@ -628,7 +667,7 @@ object Serializers { case FieldDef(flags, name, originalName, ftpe) => writeByte(TagFieldDef) writeInt(MemberFlags.toBits(flags)) - writeFieldIdent(name) + writeFieldIdentForEnclosingClass(name) writeOriginalName(originalName) writeType(ftpe) @@ -642,7 +681,7 @@ object Serializers { val MethodDef(flags, name, originalName, args, resultType, body) = methodDef writeByte(TagMethodDef) - writeOptHash(methodDef.hash) + writeOptHash(methodDef.version) // Prepare for back-jump and write dummy length bufferUnderlying.markJump() @@ -659,11 +698,35 @@ object Serializers { writeInt(length) bufferUnderlying.continue() + case ctorDef: JSConstructorDef => + val JSConstructorDef(flags, args, restParam, body) = ctorDef + + writeByte(TagJSConstructorDef) + writeOptHash(ctorDef.version) + + // Prepare for back-jump and write dummy length + bufferUnderlying.markJump() + writeInt(-1) + + // Write out ctor def + writeInt(MemberFlags.toBits(flags)) + writeParamDefs(args); writeOptParamDef(restParam) + writePosition(body.pos) + writeTrees(body.beforeSuper) + writeTree(body.superCall) + writeTrees(body.afterSuper) + writeInt(OptimizerHints.toBits(ctorDef.optimizerHints)) + + // Jump back and write true length + val length = bufferUnderlying.jumpBack() + writeInt(length) + bufferUnderlying.continue() + case methodDef: JSMethodDef => val JSMethodDef(flags, name, args, restParam, body) = methodDef writeByte(TagJSMethodDef) - writeOptHash(methodDef.hash) + writeOptHash(methodDef.version) // Prepare for back-jump and write dummy length bufferUnderlying.markJump() @@ -679,8 +742,17 @@ object Serializers { writeInt(length) bufferUnderlying.continue() - case JSPropertyDef(flags, name, getter, setterArgAndBody) => + case propDef: JSPropertyDef => + val JSPropertyDef(flags, name, getter, setterArgAndBody) = propDef + writeByte(TagJSPropertyDef) + writeOptHash(propDef.version) + + // Prepare for back-jump and write dummy length + bufferUnderlying.markJump() + writeInt(-1) + + // Write out prop def writeInt(MemberFlags.toBits(flags)) writeTree(name) writeOptTree(getter) @@ -689,6 +761,11 @@ object Serializers { writeParamDef(arg); writeTree(body) } + // Jump back and write true length + val length = bufferUnderlying.jumpBack() + writeInt(length) + bufferUnderlying.continue() + case JSNativeMemberDef(flags, name, jsNativeLoadSpec) => writeByte(TagJSNativeMemberDef) writeInt(MemberFlags.toBits(flags)) @@ -720,7 +797,7 @@ object Serializers { case TopLevelFieldExportDef(moduleID, exportName, field) => writeByte(TagTopLevelFieldExportDef) - writeString(moduleID); writeString(exportName); writeFieldIdent(field) + writeString(moduleID); writeString(exportName); writeFieldIdentForEnclosingClass(field) } } @@ -735,14 +812,21 @@ object Serializers { writeName(ident.name) } - def writeLabelIdent(ident: LabelIdent): Unit = { + def writeSimpleFieldIdent(ident: SimpleFieldIdent): Unit = { writePosition(ident.pos) writeName(ident.name) } def writeFieldIdent(ident: FieldIdent): Unit = { + // For historical reasons, the className comes *before* the position + writeName(ident.name.className) writePosition(ident.pos) - writeName(ident.name) + writeName(ident.name.simpleName) + } + + def writeFieldIdentForEnclosingClass(ident: FieldIdent): Unit = { + writePosition(ident.pos) + writeName(ident.name.simpleName) } def writeMethodIdent(ident: MethodIdent): Unit = { @@ -768,6 +852,11 @@ object Serializers { def writeName(name: Name): Unit = buffer.writeInt(encodedNameToIndex(name.encoded)) + def writeNames(names: List[Name]): Unit = { + buffer.writeInt(names.size) + names.foreach(writeName(_)) + } + def writeMethodName(name: MethodName): Unit = buffer.writeInt(methodNameToIndex(name)) @@ -797,29 +886,35 @@ object Serializers { def writeType(tpe: Type): Unit = { tpe match { - case AnyType => buffer.write(TagAnyType) - case NothingType => buffer.write(TagNothingType) - case UndefType => buffer.write(TagUndefType) - case BooleanType => buffer.write(TagBooleanType) - case CharType => buffer.write(TagCharType) - case ByteType => buffer.write(TagByteType) - case ShortType => buffer.write(TagShortType) - case IntType => buffer.write(TagIntType) - case LongType => buffer.write(TagLongType) - case FloatType => buffer.write(TagFloatType) - case DoubleType => buffer.write(TagDoubleType) - case StringType => buffer.write(TagStringType) - case NullType => buffer.write(TagNullType) - case NoType => buffer.write(TagNoType) - - case ClassType(className) => - buffer.write(TagClassType) + case AnyType => buffer.write(TagAnyType) + case AnyNotNullType => buffer.write(TagAnyNotNullType) + case NothingType => buffer.write(TagNothingType) + case UndefType => buffer.write(TagUndefType) + case BooleanType => buffer.write(TagBooleanType) + case CharType => buffer.write(TagCharType) + case ByteType => buffer.write(TagByteType) + case ShortType => buffer.write(TagShortType) + case IntType => buffer.write(TagIntType) + case LongType => buffer.write(TagLongType) + case FloatType => buffer.write(TagFloatType) + case DoubleType => buffer.write(TagDoubleType) + case StringType => buffer.write(TagStringType) + case NullType => buffer.write(TagNullType) + case VoidType => buffer.write(TagVoidType) + + case ClassType(className, nullable) => + buffer.write(if (nullable) TagClassType else TagNonNullClassType) writeName(className) - case ArrayType(arrayTypeRef) => - buffer.write(TagArrayType) + case ArrayType(arrayTypeRef, nullable) => + buffer.write(if (nullable) TagArrayType else TagNonNullArrayType) writeArrayTypeRef(arrayTypeRef) + case ClosureType(paramTypes, resultType, nullable) => + buffer.write(if (nullable) TagClosureType else TagNonNullClosureType) + writeTypes(paramTypes) + writeType(resultType) + case RecordType(fields) => buffer.write(TagRecordType) buffer.writeInt(fields.size) @@ -832,10 +927,15 @@ object Serializers { } } + def writeTypes(tpes: List[Type]): Unit = { + buffer.writeInt(tpes.size) + tpes.foreach(writeType) + } + def writeTypeRef(typeRef: TypeRef): Unit = typeRef match { case PrimRef(tpe) => tpe match { - case NoType => buffer.writeByte(TagVoidRef) + case VoidType => buffer.writeByte(TagVoidRef) case BooleanType => buffer.writeByte(TagBooleanRef) case CharType => buffer.writeByte(TagCharRef) case ByteType => buffer.writeByte(TagByteRef) @@ -853,6 +953,8 @@ object Serializers { case typeRef: ArrayTypeRef => buffer.writeByte(TagArrayTypeRef) writeArrayTypeRef(typeRef) + case typeRef: TransientTypeRef => + throw new InvalidIRException(s"Cannot serialize a transient type ref: $typeRef") } def writeArrayTypeRef(typeRef: ArrayTypeRef): Unit = { @@ -860,9 +962,17 @@ object Serializers { buffer.writeInt(typeRef.dimensions) } + def writeTypeRefs(typeRefs: List[TypeRef]): Unit = { + buffer.writeInt(typeRefs.size) + typeRefs.foreach(writeTypeRef(_)) + } + def writeApplyFlags(flags: ApplyFlags): Unit = buffer.writeInt(ApplyFlags.toBits(flags)) + def writeClosureFlags(flags: ClosureFlags): Unit = + buffer.writeByte(ClosureFlags.toBits(flags)) + def writePosition(pos: Position): Unit = { import buffer._ import PositionFormat._ @@ -937,10 +1047,11 @@ object Serializers { } } - def writeOptHash(optHash: Option[TreeHash]): Unit = { - buffer.writeBoolean(optHash.isDefined) - for (hash <- optHash) - buffer.write(hash.hash) + def writeOptHash(version: Version): Unit = { + val isHash = version.isHash + buffer.writeBoolean(isHash) + if (isHash) + version.writeHash(buffer) } def writeString(s: String): Unit = @@ -955,18 +1066,33 @@ object Serializers { private final class Deserializer(buf: ByteBuffer) { require(buf.order() == ByteOrder.BIG_ENDIAN) - private[this] var hacks: Hacks = _ - private[this] var files: Array[URI] = _ - private[this] var encodedNames: Array[UTF8String] = _ - private[this] var localNames: Array[LocalName] = _ - private[this] var labelNames: Array[LabelName] = _ - private[this] var fieldNames: Array[FieldName] = _ - private[this] var simpleMethodNames: Array[SimpleMethodName] = _ - private[this] var classNames: Array[ClassName] = _ - private[this] var methodNames: Array[MethodName] = _ - private[this] var strings: Array[String] = _ + private var hacks: Hacks = null + private var files: Array[URI] = null + private var encodedNames: Array[UTF8String] = null + private var localNames: Array[LocalName] = null + private var labelNames: Array[LabelName] = null + private var simpleFieldNames: Array[SimpleFieldName] = null + private var simpleMethodNames: Array[SimpleMethodName] = null + private var classNames: Array[ClassName] = null + private var methodNames: Array[MethodName] = null + private var strings: Array[String] = null + + /** Uniqueness cache for FieldName's. + * + * For historical reasons, the `ClassName` and `SimpleFieldName` + * components of `FieldName`s are store separately in the `.sjsir` format. + * Since most if not all occurrences of any particular `FieldName` + * typically come from a single `.sjsir` file, we use a uniqueness cache + * to make them all `eq`, consuming less memory and speeding up equality + * tests. + */ + private val uniqueFieldNames = mutable.AnyRefMap.empty[FieldName, FieldName] + + private var lastPosition: Position = Position.NoPosition - private[this] var lastPosition: Position = Position.NoPosition + private var enclosingClassName: ClassName = null + private var thisTypeForHack: Option[Type] = None + private var patchDynamicImportThunkSuperCtorCall: Boolean = false def deserializeEntryPointsInfo(): EntryPointsInfo = { hacks = new Hacks(sourceVersion = readHeader()) @@ -985,7 +1111,7 @@ object Serializers { } localNames = new Array(encodedNames.length) labelNames = new Array(encodedNames.length) - fieldNames = new Array(encodedNames.length) + simpleFieldNames = new Array(encodedNames.length) simpleMethodNames = new Array(encodedNames.length) classNames = new Array(encodedNames.length) methodNames = Array.fill(readInt()) { @@ -1055,17 +1181,17 @@ object Serializers { case TagVarDef => VarDef(readLocalIdent(), readOriginalName(), readType(), readBoolean(), readTree()) case TagSkip => Skip() case TagBlock => Block(readTrees()) - case TagLabeled => Labeled(readLabelIdent(), readType(), readTree()) + case TagLabeled => Labeled(readLabelName(), readType(), readTree()) case TagAssign => val lhs0 = readTree() - val lhs = if (hacks.use14 && lhs0.tpe == NothingType) { + val lhs = if (hacks.useBelow(5) && lhs0.tpe == NothingType) { /* Note [Nothing FieldDef rewrite] * (throw qual.field[null]) = rhs --> qual.field[null] = rhs */ lhs0 match { - case Throw(sel: Select) if sel.tpe == NullType => sel - case _ => lhs0 + case UnaryOp(UnaryOp.Throw, sel: Select) if sel.tpe == NullType => sel + case _ => lhs0 } } else { lhs0 @@ -1075,10 +1201,23 @@ object Serializers { Assign(lhs.asInstanceOf[AssignLhs], rhs) - case TagReturn => Return(readTree(), readLabelIdent()) - case TagIf => If(readTree(), readTree(), readTree())(readType()) - case TagWhile => While(readTree(), readTree()) - case TagDoWhile => DoWhile(readTree(), readTree()) + case TagReturn => + Return(readTree(), readLabelName()) + case TagIf => + If(readTree(), readTree(), readTree())(readType()) + case TagLinkTimeIf => + LinkTimeIf(readTree(), readTree(), readTree())(readType()) + case TagWhile => + While(readTree(), readTree()) + + case TagDoWhile => + if (!hacks.useBelow(13)) + throw new IOException(s"Found invalid pre-1.13 DoWhile loop at $pos") + // Rewrite `do { body } while (cond)` to `while ({ body; cond }) {}` + val body = readTree() + val cond = readTree() + While(Block(body, cond), Skip()) + case TagForIn => ForIn(readTree(), readLocalIdent(), readOriginalName(), readTree()) case TagTryCatch => @@ -1087,64 +1226,204 @@ object Serializers { case TagTryFinally => TryFinally(readTree(), readTree()) - case TagThrow => Throw(readTree()) - case TagMatch => + case TagMatch => Match(readTree(), List.fill(readInt()) { (readTrees().map(_.asInstanceOf[MatchableLiteral]), readTree()) }, readTree())(readType()) + + case TagJSAwait => + JSAwait(readTree()) + case TagDebugger => Debugger() - case TagNew => New(readClassName(), readMethodIdent(), readTrees()) - case TagLoadModule => LoadModule(readClassName()) - case TagStoreModule => StoreModule(readClassName(), readTree()) + case TagNew => + val tree = New(readClassName(), readMethodIdent(), readTrees()) + if (hacks.useBelow(19)) + anonFunctionNewNodeHackBelow19(tree) + else + tree + + case TagLoadModule => + LoadModule(readClassName()) + + case TagStoreModule => + if (hacks.useBelow(16)) { + val cls = readClassName() + val rhs = readTree() + rhs match { + case This() if cls == enclosingClassName => + // ok + case _ => + throw new IOException( + s"Illegal legacy StoreModule(${cls.nameString}, $rhs) " + + s"found in class ${enclosingClassName.nameString}") + } + } + StoreModule() case TagSelect => val qualifier = readTree() - val className = readClassName() val field = readFieldIdent() val tpe = readType() - if (hacks.use14 && tpe == NothingType) { + if (hacks.useBelow(5) && tpe == NothingType) { /* Note [Nothing FieldDef rewrite] * qual.field[nothing] --> throw qual.field[null] */ - Throw(Select(qualifier, className, field)(NullType)) + UnaryOp(UnaryOp.Throw, Select(qualifier, field)(NullType)) } else { - Select(qualifier, className, field)(tpe) + Select(qualifier, field)(tpe) } - case TagSelectStatic => SelectStatic(readClassName(), readFieldIdent())(readType()) + case TagSelectStatic => SelectStatic(readFieldIdent())(readType()) case TagSelectJSNativeMember => SelectJSNativeMember(readClassName(), readMethodIdent()) case TagApply => Apply(readApplyFlags(), readTree(), readMethodIdent(), readTrees())( readType()) + case TagApplyStatically => - ApplyStatically(readApplyFlags(), readTree(), readClassName(), - readMethodIdent(), readTrees())(readType()) + val flags = readApplyFlags() + val receiver = readTree() + val className0 = readClassName() + val method = readMethodIdent() + val args = readTrees() + val tpe = readType() + + val className = { + if (patchDynamicImportThunkSuperCtorCall && method.name.isConstructor) + DynamicImportThunkClass + else + className0 + } + + ApplyStatically(flags, receiver, className, method, args)(tpe) + case TagApplyStatic => ApplyStatic(readApplyFlags(), readClassName(), readMethodIdent(), readTrees())(readType()) case TagApplyDynamicImport => ApplyDynamicImport(readApplyFlags(), readClassName(), readMethodIdent(), readTrees()) + case TagApplyTypedClosure => + ApplyTypedClosure(readApplyFlags(), readTree(), readTrees()) + case TagNewLambda => + val descriptor = NewLambda.Descriptor(readClassName(), + readClassNames(), readMethodName(), readTypes(), readType()) + NewLambda(descriptor, readTree())(readType()) + + case TagUnaryOp => UnaryOp(readByte(), readTree()) + case TagBinaryOp => BinaryOp(readByte(), readTree(), readTree()) + + case TagArrayLength | TagGetClass | TagClone | TagIdentityHashCode | + TagWrapAsThrowable | TagUnwrapFromThrowable | TagThrow => + if (!hacks.useBelow(18)) { + throw new IOException( + s"Illegal legacy node $tag found in class ${enclosingClassName.nameString}") + } + + val lhs = readTree() + def checkNotNullLhs: Tree = UnaryOp(UnaryOp.CheckNotNull, lhs) + + (tag: @switch) match { + case TagArrayLength => + UnaryOp(UnaryOp.Array_length, checkNotNullLhs) + case TagGetClass => + UnaryOp(UnaryOp.GetClass, checkNotNullLhs) + case TagClone => + UnaryOp(UnaryOp.Clone, checkNotNullLhs) + case TagIdentityHashCode => + UnaryOp(UnaryOp.IdentityHashCode, lhs) + case TagWrapAsThrowable => + UnaryOp(UnaryOp.WrapAsThrowable, lhs) + case TagUnwrapFromThrowable => + UnaryOp(UnaryOp.UnwrapFromThrowable, checkNotNullLhs) + case TagThrow => + val patchedLhs = + if (hacks.useBelow(11)) throwArgumentHackBelow11(lhs) + else lhs + UnaryOp(UnaryOp.Throw, patchedLhs) + } + + case TagNewArray => + val arrayTypeRef = readArrayTypeRef() + val lengths = readTrees() + lengths match { + case length :: Nil => + NewArray(arrayTypeRef, length) + + case _ => + if (hacks.useBelow(17)) { + // Rewrite as a call to j.l.r.Array.newInstance + val ArrayTypeRef(base, origDims) = arrayTypeRef + val newDims = origDims - lengths.size + if (newDims < 0) { + throw new IOException( + s"Illegal legacy NewArray node with ${lengths.size} lengths but dimension $origDims at $pos") + } + val newBase = + if (newDims == 0) base + else ArrayTypeRef(base, newDims) + + ApplyStatic( + ApplyFlags.empty, + HackNames.ReflectArrayClass, + MethodIdent(HackNames.newInstanceMultiName), + List(ClassOf(newBase), ArrayValue(ArrayTypeRef(IntRef, 1), lengths)))( + AnyType) + } else { + throw new IOException( + s"Illegal NewArray node with multiple lengths for IR version 1.17+ at $pos") + } + } + + case TagArrayValue => ArrayValue(readArrayTypeRef(), readTrees()) + case TagArraySelect => ArraySelect(readTree(), readTree())(readType()) + case TagRecordValue => RecordValue(readType().asInstanceOf[RecordType], readTrees()) + + case TagIsInstanceOf => + val expr = readTree() + val testType0 = readType() + val testType = if (hacks.useBelow(17)) { + testType0 match { + case ClassType(className, true) => ClassType(className, nullable = false) + case ArrayType(arrayTypeRef, true) => ArrayType(arrayTypeRef, nullable = false) + case AnyType => AnyNotNullType + case _ => testType0 + } + } else { + testType0 + } + IsInstanceOf(expr, testType) - case TagUnaryOp => UnaryOp(readByte(), readTree()) - case TagBinaryOp => BinaryOp(readByte(), readTree(), readTree()) - case TagNewArray => NewArray(readArrayTypeRef(), readTrees()) - case TagArrayValue => ArrayValue(readArrayTypeRef(), readTrees()) - case TagArrayLength => ArrayLength(readTree()) - case TagArraySelect => ArraySelect(readTree(), readTree())(readType()) - case TagRecordValue => RecordValue(readType().asInstanceOf[RecordType], readTrees()) - case TagIsInstanceOf => IsInstanceOf(readTree(), readType()) case TagAsInstanceOf => AsInstanceOf(readTree(), readType()) - case TagGetClass => GetClass(readTree()) - case TagClone => Clone(readTree()) - case TagIdentityHashCode => IdentityHashCode(readTree()) - case TagJSNew => JSNew(readTree(), readTreeOrJSSpreads()) - case TagJSPrivateSelect => JSPrivateSelect(readTree(), readClassName(), readFieldIdent()) - case TagJSSelect => JSSelect(readTree(), readTree()) + case TagJSNew => JSNew(readTree(), readTreeOrJSSpreads()) + case TagJSPrivateSelect => JSPrivateSelect(readTree(), readFieldIdent()) + + case TagJSSelect => + if (hacks.useBelow(18) && buf.get(buf.position()) == TagJSLinkingInfo) { + val jsLinkingInfo = readTree() + readTree() match { + case StringLiteral("productionMode") => + LinkTimeProperty(ProductionMode)(BooleanType) + case StringLiteral("esVersion") => + LinkTimeProperty(ESVersion)(IntType) + case StringLiteral("assumingES6") => + LinkTimeProperty(UseECMAScript2015Semantics)(BooleanType) + case StringLiteral("isWebAssembly") => + LinkTimeProperty(IsWebAssembly)(BooleanType) + case StringLiteral("linkerVersion") => + LinkTimeProperty(LinkerVersion)(StringType) + case StringLiteral("fileLevelThis") => + JSGlobalRef(JSGlobalRef.FileLevelThis) + case otherItem => + JSSelect(jsLinkingInfo, otherItem) + } + } else { + JSSelect(readTree(), readTree()) + } + case TagJSFunctionApply => JSFunctionApply(readTree(), readTreeOrJSSpreads()) case TagJSMethodApply => JSMethodApply(readTree(), readTree(), readTreeOrJSSpreads()) case TagJSSuperSelect => JSSuperSelect(readTree(), readTree(), readTree()) @@ -1164,7 +1443,21 @@ object Serializers { JSObjectConstr(List.fill(readInt())((readTree(), readTree()))) case TagJSGlobalRef => JSGlobalRef(readString()) case TagJSTypeOfGlobalRef => JSTypeOfGlobalRef(readTree().asInstanceOf[JSGlobalRef]) - case TagJSLinkingInfo => JSLinkingInfo() + + case TagJSLinkingInfo => + if (hacks.useBelow(18)) { + JSObjectConstr(List( + (StringLiteral("productionMode"), LinkTimeProperty(ProductionMode)(BooleanType)), + (StringLiteral("esVersion"), LinkTimeProperty(ESVersion)(IntType)), + (StringLiteral("assumingES6"), LinkTimeProperty(UseECMAScript2015Semantics)(BooleanType)), + (StringLiteral("isWebAssembly"), LinkTimeProperty(IsWebAssembly)(BooleanType)), + (StringLiteral("linkerVersion"), LinkTimeProperty(LinkerVersion)(StringType)), + (StringLiteral("fileLevelThis"), JSGlobalRef(JSGlobalRef.FileLevelThis)) + )) + } else { + throw new IOException( + s"Found invalid pre-1.18 JSLinkingInfo def at ${pos}") + } case TagUndefined => Undefined() case TagNull => Null() @@ -1180,16 +1473,212 @@ object Serializers { case TagClassOf => ClassOf(readTypeRef()) case TagVarRef => - VarRef(readLocalIdent())(readType()) + val name = + if (hacks.useBelow(18)) readLocalIdent().name + else readLocalName() + VarRef(name)(readType()) + case TagThis => - This()(readType()) + val tpe = readType() + This()(thisTypeForHack.getOrElse(tpe)) + case TagClosure => - val arrow = readBoolean() + val flags = readClosureFlags() val captureParams = readParamDefs() - val (params, restParam) = readParamDefsWithRest() - Closure(arrow, captureParams, params, restParam, readTree(), readTrees()) + + val (params, restParam, resultType) = if (flags.typed) { + (readParamDefs(), None, readType()) + } else { + val (params, restParam) = readParamDefsWithRest() + (params, restParam, AnyType) + } + + val body = if (thisTypeForHack.isEmpty) { + // Fast path; always taken for IR >= 1.17 + readTree() + } else { + val prevThisTypeForHack = thisTypeForHack + thisTypeForHack = None + try { + readTree() + } finally { + thisTypeForHack = prevThisTypeForHack + } + } + val captureValues = readTrees() + Closure(flags, captureParams, params, restParam, resultType, body, captureValues) + case TagCreateJSClass => CreateJSClass(readClassName(), readTrees()) + + case TagLinkTimeProperty => + LinkTimeProperty(readString())(readType()) + } + } + + /** Patches the argument of a `Throw` for IR version below 1.11. + * + * Prior to Scala.js 1.11, `Throw(e)` was emitted by the compiler with + * the somewhat implied assumption that it would "throw an NPE" (but + * subject to UB so not really) when `e` is a `null` `Throwable`. + * + * Moreover, there was no other user-space way to emit a `Throw(e)` in the + * IR (`js.special.throw` was introduced in 1.11), so *all* `Throw` nodes + * are part of the semantics of a Scala `throw expr` or of an implicit + * re-throw in a Scala `try..catch`. + * + * In Scala.js 1.11, we explicitly ruled out the NPE behavior of `Throw`, + * so that `Throw(e)` only ever throws the value of `e`, while the NPE UB + * is specified by `UnwrapFromThrowable`. Among other things, this allows + * the user-space code `js.special.throw(e)` to indiscriminately throw `e` + * even if it is `null`. Later, in Scala.js 1.18, we further separated the + * null check of `UnwrapFromThrowable` to be an explicit `CheckNotNull`. + * + * With this hack, we patch `Throw(e)` by inserting an appropriate + * `CheckNotNull`. + * + * However, we must not do that when the previous Scala.js compiler + * already provides the *unwrapped* exception. This happened in two + * situations: + * + * - when automatically re-throwing an unhandled exception at the end of a + * `try..catch`, or + * - when throwing a maybe-JavaScriptException, with an explicit call to + * `runtime.package$.unwrapJavaScriptException(x)`. + * + * Fortunately, in both situations, the type of the `expr` is always + * `AnyType`. We can accurately use that test to know whether we need to + * apply the patch. + */ + private def throwArgumentHackBelow11(expr: Tree)(implicit pos: Position): Tree = { + if (expr.tpe == AnyType) + expr + else if (!expr.tpe.isNullable) + expr // no CheckNotNull needed; common case because of `throw new ...` + else + UnaryOp(UnaryOp.CheckNotNull, expr) + } + + /** Rewrites `New` nodes of `AnonFunctionN`s coming from before 1.19 into `NewLambda` nodes. + * + * Before 1.19, the codegen for `scala.FunctionN` lambda was of the following shape: + * {{{ + * new scala.scalajs.runtime.AnonFunctionN(arrow-lambda<...captures>(...args: any): any = { + * body + * }) + * }}} + * + * This function rewrites such calls to `NewLambda` nodes, using the new + * definition of these classes: + * {{{ + * (scala.scalajs.runtime.AnonFunctionN, + * apply;Ljava.lang.Object;...;Ljava.lang.Object, + * any, any, (typed-lambda<...captures>(...args: any): any = { + * body + * })) + * }}} + * + * The rewrite ensures that previously published lambdas get the same + * optimizations on Wasm as those recompiled with 1.19+. + * + * The rewrite also applies to Scala 3's `AnonFunctionXXL` classes, with + * an additional adaptation of the parameter's type. It rewrites + * {{{ + * new scala.scalajs.runtime.AnonFunctionXXL(arrow-lambda<...captures>(argArray: any): any = { + * body + * }) + * }}} + * to + * {{{ + * (scala.scalajs.runtime.AnonFunctionXXL, + * apply;Ljava.lang.Object[];Ljava.lang.Object, + * any, any, (typed-lambda<...captures>(argArray: jl.Object[]): any = { + * newBody + * })) + * }}} + * where `newBody` is `body` transformed to adapt the type of `argArray` + * everywhere. + * + * Tests are in `sbt-plugin/src/sbt-test/linker/anonfunction-compat/`. + * + * --- + * + * In case the argument is not an arrow-lambda of the expected shape, we + * use a fallback. This never happens for our published codegens, but + * could happen for other valid IR. We rewrite + * {{{ + * new scala.scalajs.runtime.AnonFunctionN(jsFunctionArg) + * }}} + * to + * {{{ + * (scala.scalajs.runtime.AnonFunctionN, + * apply;Ljava.lang.Object;...;Ljava.lang.Object, + * any, any, (typed-lambda(...args: any): any = { + * f(...args) + * })) + * }}} + * + * This code path is not tested in the CI, but can be locally tested by + * commenting out the `case Closure(...) =>`. + */ + private def anonFunctionNewNodeHackBelow19(tree: New): Tree = { + tree match { + case New(cls, _, funArg :: Nil) => + def makeFallbackTypedClosure(paramTypes: List[Type]): Closure = { + implicit val pos = funArg.pos + val fParamDef = ParamDef(LocalIdent(LocalName("f")), NoOriginalName, AnyType, mutable = false) + val xParamDefs = paramTypes.zipWithIndex.map { case (ptpe, i) => + ParamDef(LocalIdent(LocalName(s"x$i")), NoOriginalName, ptpe, mutable = false) + } + Closure(ClosureFlags.typed, List(fParamDef), xParamDefs, None, AnyType, + JSFunctionApply(fParamDef.ref, xParamDefs.map(_.ref)), + List(funArg)) + } + + cls match { + case HackNames.AnonFunctionClass(arity) => + val typedClosure = funArg match { + // The shape produced by our earlier compilers, which we can optimally rewrite + case Closure(ClosureFlags.arrow, captureParams, params, None, AnyType, body, captureValues) + if params.lengthCompare(arity) == 0 => + Closure(ClosureFlags.typed, captureParams, params, None, AnyType, + body, captureValues)(funArg.pos) + + // Fallback for other shapes (theoretically required; dead code in practice) + case _ => + makeFallbackTypedClosure(List.fill(arity)(AnyType)) + } + + NewLambda(HackNames.anonFunctionDescriptors(arity), typedClosure)(tree.tpe)(tree.pos) + + case HackNames.AnonFunctionXXLClass => + val typedClosure = funArg match { + // The shape produced by our earlier compilers, which we can optimally rewrite + case Closure(ClosureFlags.arrow, captureParams, oldParam :: Nil, None, AnyType, body, captureValues) => + // Here we need to adapt the type of the parameter from `any` to `jl.Object[]`. + val newParam = oldParam.copy(ptpe = HackNames.ObjectArrayType)(oldParam.pos) + val newBody = new Transformers.LocalScopeTransformer { + override def transform(tree: Tree): Tree = tree match { + case tree @ VarRef(newParam.name.name) => tree.copy()(newParam.ptpe)(tree.pos) + case _ => super.transform(tree) + } + }.transform(body) + Closure(ClosureFlags.typed, captureParams, List(newParam), None, AnyType, + newBody, captureValues)(funArg.pos) + + // Fallback for other shapes (theoretically required; dead code in practice) + case _ => + makeFallbackTypedClosure(List(HackNames.ObjectArrayType)) + } + + NewLambda(HackNames.anonFunctionXXLDescriptor, typedClosure)(tree.tpe)(tree.pos) + + case _ => + tree + } + + case _ => + tree } } @@ -1198,9 +1687,26 @@ object Serializers { def readClassDef(): ClassDef = { implicit val pos = readPosition() + val name = readClassIdent() + val cls = name.name + enclosingClassName = cls + val originalName = readOriginalName() val kind = ClassKind.fromByte(readByte()) + + if (hacks.useBelow(17)) { + thisTypeForHack = kind match { + case ClassKind.Class | ClassKind.ModuleClass | ClassKind.Interface => + Some(ClassType(cls, nullable = false)) + case ClassKind.HijackedClass if hacks.useBelow(11) => + // Use getOrElse as safety guard for otherwise invalid inputs + Some(BoxedClassToPrimType.getOrElse(cls, ClassType(cls, nullable = false))) + case _ => + None + } + } + val hasJSClassCaptures = readBoolean() val jsClassCaptures = if (!hasJSClassCaptures) None @@ -1208,271 +1714,755 @@ object Serializers { val superClass = readOptClassIdent() val parents = readClassIdents() - /* jsSuperClass is not hacked like in readMemberDef.bodyHack15. The + if (hacks.useBelow(18) && kind.isClass) { + /* In 1.18, we started enforcing the constructor chaining discipline. + * Unfortunately, we used to generate a wrong super constructor call in + * synthetic classes extending `DynamicImportThunk`, so we patch them. + */ + patchDynamicImportThunkSuperCtorCall = + superClass.exists(_.name == DynamicImportThunkClass) + } + + /* jsSuperClass is not hacked like in readMemberDef.bodyHackBelow6. The * compilers before 1.6 always use a simple VarRef() as jsSuperClass, * when there is one, so no hack is required. */ val jsSuperClass = readOptTree() val jsNativeLoadSpec = readJSNativeLoadSpec() - val memberDefs = readMemberDefs(name.name, kind) - val topLevelExportDefs = readTopLevelExportDefs(name.name, kind) + + // Read member defs + val fieldsBuilder = List.newBuilder[AnyFieldDef] + val methodsBuilder = List.newBuilder[MethodDef] + val jsConstructorBuilder = new OptionBuilder[JSConstructorDef] + val jsMethodPropsBuilder = List.newBuilder[JSMethodPropDef] + val jsNativeMembersBuilder = List.newBuilder[JSNativeMemberDef] + + for (_ <- 0 until readInt()) { + implicit val pos = readPosition() + readByte() match { + case TagFieldDef => fieldsBuilder += readFieldDef() + case TagJSFieldDef => fieldsBuilder += readJSFieldDef() + case TagMethodDef => methodsBuilder += readMethodDef(cls, kind) + case TagJSConstructorDef => jsConstructorBuilder += readJSConstructorDef(kind) + case TagJSMethodDef => jsMethodPropsBuilder += readJSMethodDef() + case TagJSPropertyDef => jsMethodPropsBuilder += readJSPropertyDef() + case TagJSNativeMemberDef => jsNativeMembersBuilder += readJSNativeMemberDef() + } + } + + val topLevelExportDefs = readTopLevelExportDefs() val optimizerHints = OptimizerHints.fromBits(readInt()) - ClassDef(name, originalName, kind, jsClassCaptures, superClass, parents, - jsSuperClass, jsNativeLoadSpec, memberDefs, topLevelExportDefs)( + + val fields = fieldsBuilder.result() + + val methods = { + val methods0 = methodsBuilder.result() + if (hacks.useBelow(5) && kind.isJSClass) { + // #4409: Filter out abstract methods in non-native JS classes for version < 1.5 + methods0.filter(_.body.isDefined) + } else if (hacks.useBelow(17) && cls == ClassClass) { + jlClassMethodsHackBelow17(methods0) + } else if (hacks.useBelow(17) && cls == HackNames.ReflectArrayModClass) { + jlReflectArrayMethodsHackBelow17(methods0) + } else { + methods0 + } + } + + val (jsConstructor, jsMethodProps) = { + if (hacks.useBelow(11) && kind.isJSClass) { + assert(jsConstructorBuilder.result().isEmpty, "found JSConstructorDef in pre 1.11 IR") + jsConstructorHackBelow11(kind, jsMethodPropsBuilder.result()) + } else { + (jsConstructorBuilder.result(), jsMethodPropsBuilder.result()) + } + } + + val jsNativeMembers = jsNativeMembersBuilder.result() + + val classDef = ClassDef(name, originalName, kind, jsClassCaptures, superClass, parents, + jsSuperClass, jsNativeLoadSpec, fields, methods, jsConstructor, + jsMethodProps, jsNativeMembers, topLevelExportDefs)( optimizerHints) + + if (hacks.useBelow(19)) + anonFunctionClassDefHackBelow19(classDef) + else + classDef } - def readMemberDef(owner: ClassName, ownerKind: ClassKind): MemberDef = { - implicit val pos = readPosition() - val tag = readByte() + private def jlClassMethodsHackBelow17(methods: List[MethodDef]): List[MethodDef] = { + for (method <- methods) yield { + implicit val pos = method.pos + + val methodName = method.methodName + val methodSimpleNameString = methodName.simpleName.nameString + + val thisJLClass = This()(ClassType(ClassClass, nullable = false)) - def bodyHack15(body: Tree, isStat: Boolean): Tree = { - if (!hacks.use15) { - body + if (methodName.isConstructor) { + val newName = MethodIdent(NoArgConstructorName)(method.name.pos) + val newBody = ApplyStatically(ApplyFlags.empty.withConstructor(true), + thisJLClass, ObjectClass, newName, Nil)(VoidType) + MethodDef(method.flags, newName, method.originalName, + Nil, VoidType, Some(newBody))( + method.optimizerHints, method.version) } else { - /* #4442 and #4601: Patch Labeled, If, Match and TryCatch nodes in - * statement position to have type NoType. These 4 nodes are the - * control structures whose result type is explicitly specified (and - * not derived from their children like Block or TryFinally, or - * constant like While). - */ - new Transformers.Transformer { - override def transform(tree: Tree, isStat: Boolean): Tree = { - val newTree = super.transform(tree, isStat) - if (isStat && newTree.tpe != NoType) { - newTree match { - case Labeled(label, _, body) => - Labeled(label, NoType, body)(newTree.pos) - case If(cond, thenp, elsep) => - If(cond, thenp, elsep)(NoType)(newTree.pos) - case Match(selector, cases, default) => - Match(selector, cases, default)(NoType)(newTree.pos) - case TryCatch(block, errVar, errVarOriginalName, handler) => - TryCatch(block, errVar, errVarOriginalName, handler)(NoType)(newTree.pos) + def argRef = method.args.head.ref + def argRefNotNull = UnaryOp(UnaryOp.CheckNotNull, argRef) + + var forceInline = true // reset to false in the `case _ =>` + + val newBody: Tree = methodSimpleNameString match { + case "getName" => UnaryOp(UnaryOp.Class_name, thisJLClass) + case "isPrimitive" => UnaryOp(UnaryOp.Class_isPrimitive, thisJLClass) + case "isInterface" => UnaryOp(UnaryOp.Class_isInterface, thisJLClass) + case "isArray" => UnaryOp(UnaryOp.Class_isArray, thisJLClass) + case "getComponentType" => UnaryOp(UnaryOp.Class_componentType, thisJLClass) + case "getSuperclass" => UnaryOp(UnaryOp.Class_superClass, thisJLClass) + + case "isInstance" => BinaryOp(BinaryOp.Class_isInstance, thisJLClass, argRef) + case "isAssignableFrom" => BinaryOp(BinaryOp.Class_isAssignableFrom, thisJLClass, argRefNotNull) + case "cast" => BinaryOp(BinaryOp.Class_cast, thisJLClass, argRef) + + case _ => + forceInline = false + + /* Unfortunately, some of the other methods directly referred to + * `this.data["name"]`, instead of building on `this.getName()`. + * We must replace those occurrences with a `Class_name` as well. + */ + val transformer = new Transformers.Transformer { + override def transform(tree: Tree): Tree = tree match { + case JSSelect(_, StringLiteral("name")) => + implicit val pos = tree.pos + UnaryOp(UnaryOp.Class_name, thisJLClass) case _ => - newTree + super.transform(tree) } - } else { - newTree } - } - }.transform(body, isStat) + transformer.transform(method.body.get) + } + + val newOptimizerHints = + if (forceInline) method.optimizerHints.withInline(true) + else method.optimizerHints + + MethodDef(method.flags, method.name, method.originalName, + method.args, method.resultType, Some(newBody))( + newOptimizerHints, method.version) } } + } - def bodyHack15Expr(body: Tree): Tree = bodyHack15(body, isStat = false) + private def jlReflectArrayMethodsHackBelow17(methods: List[MethodDef]): List[MethodDef] = { + /* Basically this method hard-codes new implementations for the two + * overloads of newInstance. + * It is horrible, but better than pollute everything else in the linker. + */ - (tag: @switch) match { - case TagFieldDef => - val flags = MemberFlags.fromBits(readInt()) - val name = readFieldIdent() - val originalName = readOriginalName() + import HackNames._ - val ftpe0 = readType() - val ftpe = if (hacks.use14 && ftpe0 == NothingType) { - /* Note [Nothing FieldDef rewrite] - * val field: nothing --> val field: null - */ - NullType - } else { - ftpe0 - } + def paramDef(name: String, ptpe: Type)(implicit pos: Position): ParamDef = + ParamDef(LocalIdent(LocalName(name)), NoOriginalName, ptpe, mutable = false) - FieldDef(flags, name, originalName, ftpe) + def varDef(name: String, vtpe: Type, rhs: Tree, mutable: Boolean = false)( + implicit pos: Position): VarDef = { + VarDef(LocalIdent(LocalName(name)), NoOriginalName, vtpe, mutable, rhs) + } - case TagJSFieldDef => - JSFieldDef(MemberFlags.fromBits(readInt()), readTree(), readType()) + def arrayLength(t: Tree)(implicit pos: Position): Tree = + UnaryOp(UnaryOp.Array_length, UnaryOp(UnaryOp.CheckNotNull, t)) + + def getClass(t: Tree)(implicit pos: Position): Tree = + UnaryOp(UnaryOp.GetClass, UnaryOp(UnaryOp.CheckNotNull, t)) + + val jlClassRef = ClassRef(ClassClass) + val intArrayTypeRef = ArrayTypeRef(IntRef, 1) + val objectRef = ClassRef(ObjectClass) + val objectArrayTypeRef = ArrayTypeRef(objectRef, 1) + + val jlClassType = ClassType(ClassClass, nullable = true) + + val newInstanceRecName = MethodName("newInstanceRec", + List(jlClassRef, intArrayTypeRef, IntRef), objectRef) + + val EAF = ApplyFlags.empty + + val newInstanceRecMethod = { + /* def newInstanceRec(componentType: jl.Class, dimensions: int[], offset: int): any = { + * val length: int = dimensions[offset] + * val result: any = newInstance(componentType, length) + * val innerOffset = offset + 1 + * if (innerOffset < dimensions.length) { + * val result2: Object[] = result.asInstanceOf[Object[]] + * val innerComponentType: jl.Class = componentType.getComponentType() + * var i: Int = 0 + * while (i != length) + * result2[i] = newInstanceRec(innerComponentType, dimensions, innerOffset) + * i = i + 1 + * } + * } + * result + * } + */ + + implicit val pos = Position.NoPosition + + val getComponentTypeName = MethodName("getComponentType", Nil, jlClassRef) + + val ths = This()(ClassType(ReflectArrayModClass, nullable = false)) + + val componentType = paramDef("componentType", jlClassType) + val dimensions = paramDef("dimensions", ArrayType(intArrayTypeRef, nullable = true)) + val offset = paramDef("offset", IntType) + + val length = varDef("length", IntType, ArraySelect(dimensions.ref, offset.ref)(IntType)) + val result = varDef("result", AnyType, + Apply(EAF, ths, MethodIdent(newInstanceSingleName), List(componentType.ref, length.ref))(AnyType)) + val innerOffset = varDef("innerOffset", IntType, + BinaryOp(BinaryOp.Int_+, offset.ref, IntLiteral(1))) + + val result2 = varDef("result2", ArrayType(objectArrayTypeRef, nullable = true), + AsInstanceOf(result.ref, ArrayType(objectArrayTypeRef, nullable = true))) + val innerComponentType = varDef("innerComponentType", jlClassType, + Apply(EAF, componentType.ref, MethodIdent(getComponentTypeName), Nil)(jlClassType)) + val i = varDef("i", IntType, IntLiteral(0), mutable = true) + + val body = { + Block( + length, + result, + innerOffset, + If(BinaryOp(BinaryOp.Int_<, innerOffset.ref, arrayLength(dimensions.ref)), { + Block( + result2, + innerComponentType, + i, + While(BinaryOp(BinaryOp.Int_!=, i.ref, length.ref), { + Block( + Assign( + ArraySelect(result2.ref, i.ref)(AnyType), + Apply(EAF, ths, MethodIdent(newInstanceRecName), + List(innerComponentType.ref, dimensions.ref, innerOffset.ref))(AnyType) + ), + Assign( + i.ref, + BinaryOp(BinaryOp.Int_+, i.ref, IntLiteral(1)) + ) + ) + }) + ) + }, Skip())(VoidType), + result.ref + ) + } - case TagMethodDef => - val optHash = readOptHash() - // read and discard the length - val len = readInt() - assert(len >= 0) + MethodDef(MemberFlags.empty, MethodIdent(newInstanceRecName), + NoOriginalName, List(componentType, dimensions, offset), AnyType, + Some(body))( + OptimizerHints.empty, Version.fromInt(1)) + } + + val newMethods = for (method <- methods) yield { + method.methodName match { + case `newInstanceSingleName` => + // newInstance(jl.Class, int) --> newArray(jlClass.notNull, length) + + implicit val pos = method.pos + + val List(jlClassParam, lengthParam) = method.args - val flags = MemberFlags.fromBits(readInt()) + val newBody = BinaryOp(BinaryOp.Class_newArray, + UnaryOp(UnaryOp.CheckNotNull, jlClassParam.ref), + lengthParam.ref) - val name = { - /* In versions 1.0 and 1.1 of the IR, static initializers and - * class initializers were conflated into one concept, which was - * handled differently in the linker based on whether the owner - * was a JS type or not. They were serialized as ``. - * Starting with 1.2, `` is only for class initializers. - * If we read a definition for a `` in a non-JS type, we - * rewrite it as a static initializers instead (``). + MethodDef(method.flags, method.name, method.originalName, + method.args, method.resultType, Some(newBody))( + method.optimizerHints.withInline(true), method.version) + + case `newInstanceMultiName` => + /* newInstance(jl.Class, int[]) --> + * var outermostComponentType: jl.Class = jlClassParam + * var i: int = 1 + * while (i != lengths.length) { + * outermostComponentType = getClass(this.newInstance(outermostComponentType, 0)) + * i = i + 1 + * } + * newInstanceRec(outermostComponentType, lengths, 0) */ - val name0 = readMethodIdent() - if (hacks.use11 && - name0.name == ClassInitializerName && - !ownerKind.isJSType) { - MethodIdent(StaticInitializerName)(name0.pos) - } else { - name0 + + implicit val pos = method.pos + + val List(jlClassParam, lengthsParam) = method.args + + val newBody = { + val outermostComponentType = varDef("outermostComponentType", + jlClassType, jlClassParam.ref, mutable = true) + val i = varDef("i", IntType, IntLiteral(1), mutable = true) + + Block( + outermostComponentType, + i, + While(BinaryOp(BinaryOp.Int_!=, i.ref, arrayLength(lengthsParam.ref)), { + Block( + Assign( + outermostComponentType.ref, + getClass(Apply(EAF, This()(ClassType(ReflectArrayModClass, nullable = false)), + MethodIdent(newInstanceSingleName), + List(outermostComponentType.ref, IntLiteral(0)))(AnyType)) + ), + Assign( + i.ref, + BinaryOp(BinaryOp.Int_+, i.ref, IntLiteral(1)) + ) + ) + }), + Apply(EAF, This()(ClassType(ReflectArrayModClass, nullable = false)), + MethodIdent(newInstanceRecName), + List(outermostComponentType.ref, lengthsParam.ref, IntLiteral(0)))( + AnyType) + ) } + + MethodDef(method.flags, method.name, method.originalName, + method.args, method.resultType, Some(newBody))( + method.optimizerHints, method.version) + + case _ => + method + } + } + + newInstanceRecMethod :: newMethods + } + + private def jsConstructorHackBelow11(ownerKind: ClassKind, + jsMethodProps: List[JSMethodPropDef]): (Option[JSConstructorDef], List[JSMethodPropDef]) = { + val jsConstructorBuilder = new OptionBuilder[JSConstructorDef] + val jsMethodPropsBuilder = List.newBuilder[JSMethodPropDef] + + jsMethodProps.foreach { + case methodDef @ JSMethodDef(flags, StringLiteral("constructor"), args, restParam, body) + if flags.namespace == MemberNamespace.Public => + val bodyStats = body match { + case Block(stats) => stats + case _ => body :: Nil } - val originalName = readOriginalName() - val args = readParamDefs() - val resultType = readType() - val body = readOptTree() - val optimizerHints = OptimizerHints.fromBits(readInt()) - - if (hacks.use10 && - flags.namespace == MemberNamespace.Public && - owner == HackNames.SystemModule && - name.name == HackNames.identityHashCodeName) { - /* #3976: 1.0 javalib relied on wrong linker dispatch. - * We simply replace it with a correct implementation. - */ - assert(args.size == 1) - - val patchedBody = Some(IdentityHashCode(args(0).ref)) - val patchedOptimizerHints = OptimizerHints.empty.withInline(true) - - MethodDef(flags, name, originalName, args, resultType, patchedBody)( - patchedOptimizerHints, optHash) - } else if (hacks.use14 && - flags.namespace == MemberNamespace.Public && - owner == ObjectClass && - name.name == HackNames.cloneName) { - /* #4391: In version 1.5, we introduced a dedicated IR node for the - * primitive operation behind `Object.clone()`. This allowed to - * simplify the linker by removing several special-cases that - * treated it specially (for example, preventing it from being - * inlined if the receiver could be an array). The simplifications - * mean that the old implementation is not valid anymore, and so we - * must force using the new implementation if we read IR from an - * older version. - */ - assert(args.isEmpty) - - val thisValue = This()(ClassType(ObjectClass)) - val cloneableClassType = ClassType(CloneableClass) - - val patchedBody = Some { - If(IsInstanceOf(thisValue, cloneableClassType), - Clone(AsInstanceOf(thisValue, cloneableClassType)), - Throw(New( - HackNames.CloneNotSupportedExceptionClass, - MethodIdent(NoArgConstructorName), - Nil)))(cloneableClassType) - } - val patchedOptimizerHints = OptimizerHints.empty.withInline(true) + bodyStats.span(!_.isInstanceOf[JSSuperConstructorCall]) match { + case (beforeSuper, (superCall: JSSuperConstructorCall) :: afterSuper0) => + val newFlags = flags.withNamespace(MemberNamespace.Constructor) + val afterSuper = maybeHackJSConstructorDefAfterSuper(ownerKind, afterSuper0, superCall.pos) + val newBody = JSConstructorBody(beforeSuper, superCall, afterSuper)(body.pos) + val ctorDef = JSConstructorDef(newFlags, args, restParam, newBody)( + methodDef.optimizerHints, Unversioned)(methodDef.pos) + jsConstructorBuilder += Hashers.hashJSConstructorDef(ctorDef) + + case _ => + /* This is awkward: we have an old-style JS constructor that is + * structurally invalid. We crash in order not to silently + * ignore errors. + */ + throw new IOException( + s"Found invalid pre-1.11 JS constructor def at ${methodDef.pos}:\n${methodDef.show}") + } - MethodDef(flags, name, originalName, args, resultType, patchedBody)( - patchedOptimizerHints, optHash) - } else { - val patchedBody = body.map(bodyHack15(_, isStat = resultType == NoType)) - MethodDef(flags, name, originalName, args, resultType, patchedBody)( - optimizerHints, optHash) + case exportedMember => + jsMethodPropsBuilder += exportedMember + } + + (jsConstructorBuilder.result(), jsMethodPropsBuilder.result()) + } + + /** Rewrites `scala.scalajs.runtime.AnonFunctionN`s from before 1.19. + * + * Before 1.19, these classes were defined as + * {{{ + * // final in source code + * class AnonFunctionN extends AbstractFunctionN { + * val f: any + * def this(f: any) = { + * this.f = f; + * super() + * } + * def apply(...args: any): any = f(...args) + * } + * }}} + * + * Starting with 1.19, they were rewritten to be used as SAM classes for + * `NewLambda` nodes. The new IR shape is + * {{{ + * // sealed abstract in source code + * class AnonFunctionN extends AbstractFunctionN { + * def this() = super() + * } + * }}} + * + * This function rewrites those classes to the new shape. + * + * The rewrite also applies to Scala 3's `AnonFunctionXXL`. + * + * Tests are in `sbt-plugin/src/sbt-test/linker/anonfunction-compat/`. + */ + private def anonFunctionClassDefHackBelow19(classDef: ClassDef): ClassDef = { + import classDef._ + + if (!HackNames.allAnonFunctionClasses.contains(className)) { + classDef + } else { + val newCtor: MethodDef = { + // Find the old constructor to get its position and version + val oldCtor = methods.find(_.methodName.isConstructor).getOrElse { + throw new InvalidIRException(classDef, + s"Did not find a constructor in ${className.nameString}") } + implicit val pos = oldCtor.pos + + // constructor def () = this.superClass::() + MethodDef( + MemberFlags.empty.withNamespace(MemberNamespace.Constructor), + MethodIdent(NoArgConstructorName), + NoOriginalName, + Nil, + VoidType, + Some { + ApplyStatically( + ApplyFlags.empty.withConstructor(true), + This()(ClassType(className, nullable = false)), + superClass.get.name, + MethodIdent(NoArgConstructorName), + Nil + )(VoidType) + } + )(OptimizerHints.empty, oldCtor.version) + } + + ClassDef( + name, + originalName, + kind, + jsClassCaptures, + superClass, + interfaces, + jsSuperClass, + jsNativeLoadSpec, + fields = Nil, // throws away the `f` field + methods = List(newCtor), // throws away the old constructor and `apply` method + jsConstructor, + jsMethodProps, + jsNativeMembers, + topLevelExportDefs + )(OptimizerHints.empty)(pos) // throws away the `@inline` + } + } + + private def readFieldDef()(implicit pos: Position): FieldDef = { + val flags = MemberFlags.fromBits(readInt()) + val name = readFieldIdentForEnclosingClass() + val originalName = readOriginalName() + + val ftpe0 = readType() + val ftpe = if (hacks.useBelow(5) && ftpe0 == NothingType) { + /* Note [Nothing FieldDef rewrite] + * val field: nothing --> val field: null + */ + NullType + } else { + ftpe0 + } + + FieldDef(flags, name, originalName, ftpe) + } + + private def readJSFieldDef()(implicit pos: Position): JSFieldDef = + JSFieldDef(MemberFlags.fromBits(readInt()), readTree(), readType()) + + private def readMethodDef(owner: ClassName, ownerKind: ClassKind)( + implicit pos: Position): MethodDef = { + val optHash = readOptHash() + // read and discard the length + val len = readInt() + assert(len >= 0) + + val flags = MemberFlags.fromBits(readInt()) + + val name = { + /* In versions 1.0 and 1.1 of the IR, static initializers and + * class initializers were conflated into one concept, which was + * handled differently in the linker based on whether the owner + * was a JS type or not. They were serialized as ``. + * Starting with 1.2, `` is only for class initializers. + * If we read a definition for a `` in a non-JS type, we + * rewrite it as a static initializers instead (``). + */ + val name0 = readMethodIdent() + if (hacks.useBelow(2) && + name0.name == ClassInitializerName && + !ownerKind.isJSType) { + MethodIdent(StaticInitializerName)(name0.pos) + } else { + name0 + } + } + + val originalName = readOriginalName() + val args = readParamDefs() + val resultType = readType() + val body = readOptTree() + val optimizerHints = OptimizerHints.fromBits(readInt()) + + if (hacks.useBelow(1) && + flags.namespace == MemberNamespace.Public && + owner == HackNames.SystemModule && + name.name == HackNames.identityHashCodeName) { + /* #3976: Before 1.1, the javalib relied on wrong linker dispatch. + * We simply replace it with a correct implementation. + */ + assert(args.size == 1) + + val patchedBody = Some(UnaryOp(UnaryOp.IdentityHashCode, args(0).ref)) + val patchedOptimizerHints = OptimizerHints.empty.withInline(true) + + MethodDef(flags, name, originalName, args, resultType, patchedBody)( + patchedOptimizerHints, optHash) + } else if (hacks.useBelow(5) && + flags.namespace == MemberNamespace.Public && + owner == ObjectClass && + name.name == HackNames.cloneName) { + /* #4391: In version 1.5, we introduced a dedicated IR node for the + * primitive operation behind `Object.clone()`. This allowed to + * simplify the linker by removing several special-cases that + * treated it specially (for example, preventing it from being + * inlined if the receiver could be an array). The simplifications + * mean that the old implementation is not valid anymore, and so we + * must force using the new implementation if we read IR from an + * older version. + */ + assert(args.isEmpty) + + val thisValue = This()(ClassType(ObjectClass, nullable = false)) + val cloneableClassType = ClassType(CloneableClass, nullable = true) + + val patchedBody = Some { + If(IsInstanceOf(thisValue, cloneableClassType.toNonNullable), + UnaryOp(UnaryOp.Clone, + UnaryOp(UnaryOp.CheckNotNull, AsInstanceOf(thisValue, cloneableClassType))), + UnaryOp(UnaryOp.Throw, + New( + HackNames.CloneNotSupportedExceptionClass, + MethodIdent(NoArgConstructorName), + Nil)))(cloneableClassType) + } + val patchedOptimizerHints = OptimizerHints.empty.withInline(true) + + MethodDef(flags, name, originalName, args, resultType, patchedBody)( + patchedOptimizerHints, optHash) + } else { + val patchedBody = body.map(bodyHackBelow6(_, isStat = resultType == VoidType)) + MethodDef(flags, name, originalName, args, resultType, patchedBody)( + optimizerHints, optHash) + } + } + + private def readJSConstructorDef(ownerKind: ClassKind)( + implicit pos: Position): JSConstructorDef = { + + val optHash = readOptHash() + // read and discard the length + val len = readInt() + assert(len >= 0) + + /* JSConstructorDef was introduced in 1.11. Therefore, by + * construction, they never need the body hack below 1.6. + */ + + val flags = MemberFlags.fromBits(readInt()) + val (params, restParam) = readParamDefsWithRest() + val bodyPos = readPosition() + val beforeSuper = readTrees() + val superCall = readTree().asInstanceOf[JSSuperConstructorCall] + val afterSuper0 = readTrees() + val afterSuper = maybeHackJSConstructorDefAfterSuper(ownerKind, afterSuper0, superCall.pos) + val body = JSConstructorBody(beforeSuper, superCall, afterSuper)(bodyPos) + JSConstructorDef(flags, params, restParam, body)( + OptimizerHints.fromBits(readInt()), optHash) + } + + private def maybeHackJSConstructorDefAfterSuper(ownerKind: ClassKind, + afterSuper0: List[Tree], superCallPos: Position): List[Tree] = { + if (hacks.useBelow(18) && ownerKind == ClassKind.JSModuleClass) { + afterSuper0 match { + case StoreModule() :: _ => afterSuper0 + case _ => StoreModule()(superCallPos) :: afterSuper0 + } + } else { + afterSuper0 + } + } + + private def readJSMethodDef()(implicit pos: Position): JSMethodDef = { + val optHash = readOptHash() + // read and discard the length + val len = readInt() + assert(len >= 0) + val flags = MemberFlags.fromBits(readInt()) + val name = bodyHackBelow6Expr(readTree()) + val (params, restParam) = readParamDefsWithRest() + val body = bodyHackBelow6Expr(readTree()) + JSMethodDef(flags, name, params, restParam, body)( + OptimizerHints.fromBits(readInt()), optHash) + } - case TagJSMethodDef => + private def readJSPropertyDef()(implicit pos: Position): JSPropertyDef = { + val optHash: Version = { + if (hacks.useBelow(13)) { + Unversioned + } else { val optHash = readOptHash() // read and discard the length val len = readInt() assert(len >= 0) + optHash + } + } - val flags = MemberFlags.fromBits(readInt()) - val name = bodyHack15Expr(readTree()) - val (params, restParam) = readParamDefsWithRest() - val body = bodyHack15Expr(readTree()) - JSMethodDef(flags, name, params, restParam, body)( - OptimizerHints.fromBits(readInt()), optHash) - - case TagJSPropertyDef => - val flags = MemberFlags.fromBits(readInt()) - val name = bodyHack15Expr(readTree()) - val getterBody = readOptTree().map(bodyHack15Expr(_)) - val setterArgAndBody = { - if (readBoolean()) - Some((readParamDef(), bodyHack15Expr(readTree()))) - else - None - } - JSPropertyDef(flags, name, getterBody, setterArgAndBody) - - case TagJSNativeMemberDef => - val flags = MemberFlags.fromBits(readInt()) - val name = readMethodIdent() - val jsNativeLoadSpec = readJSNativeLoadSpec().get - JSNativeMemberDef(flags, name, jsNativeLoadSpec) + val flags = MemberFlags.fromBits(readInt()) + val name = bodyHackBelow6Expr(readTree()) + val getterBody = readOptTree().map(bodyHackBelow6Expr(_)) + val setterArgAndBody = { + if (readBoolean()) + Some((readParamDef(), bodyHackBelow6Expr(readTree()))) + else + None } + JSPropertyDef(flags, name, getterBody, setterArgAndBody)(optHash) } - def readMemberDefs(owner: ClassName, ownerKind: ClassKind): List[MemberDef] = { - val memberDefs = List.fill(readInt())(readMemberDef(owner, ownerKind)) + private def readJSNativeMemberDef()(implicit pos: Position): JSNativeMemberDef = { + val flags = MemberFlags.fromBits(readInt()) + val name = readMethodIdent() + val jsNativeLoadSpec = readJSNativeLoadSpec().get + JSNativeMemberDef(flags, name, jsNativeLoadSpec) + } - // #4409: Filter out abstract methods in non-native JS classes for version < 1.5 - if (ownerKind.isJSClass) { - if (hacks.use14) { - memberDefs.filter { m => - m match { - case MethodDef(_, _, _, _, _, None) => false - case _ => true - } - } - } else { - /* #4388 This check should be moved to a link-time check dependent on - * `checkIR`, but currently we only have the post-BaseLinker IR - * checker, at which points those methods have already been - * eliminated. - */ - for (m <- memberDefs) { - m match { - case MethodDef(_, _, _, _, _, None) => - throw new InvalidIRException(m, - "Invalid abstract method in non-native JS class") - case _ => - // ok - } - } - memberDefs + /* #4442 and #4601: Patch Labeled, If, Match and TryCatch nodes in + * statement position to have type VoidType. These 4 nodes are the + * control structures whose result type is explicitly specified (and + * not derived from their children like Block or TryFinally, or + * constant like While). + */ + private object BodyHackBelow6Transformer extends Transformers.Transformer { + def transformStat(tree: Tree): Tree = { + implicit val pos = tree.pos + + tree match { + // Nodes that we actually need to alter + case Labeled(label, _, body) => + Labeled(label, VoidType, transformStat(body)) + case If(cond, thenp, elsep) => + If(transform(cond), transformStat(thenp), transformStat(elsep))(VoidType) + case Match(selector, cases, default) => + Match(transform(selector), cases.map(c => (c._1, transformStat(c._2))), + transformStat(default))(VoidType) + case TryCatch(block, errVar, errVarOriginalName, handler) => + TryCatch(transformStat(block), errVar, errVarOriginalName, + transformStat(handler))(VoidType) + + // Nodes for which we need to forward the statement position + case Block(stats) => + Block(stats.map(transformStat(_))) + case TryFinally(block, finalizer) => + Block(transformStat(block), transformStat(finalizer)) + + // For everything else, delegate to transform + case _ => + transform(tree) } - } else { - memberDefs } + + override def transform(tree: Tree): Tree = { + implicit val pos = tree.pos + + tree match { + // Nodes that force a statement position for some of their parts + case Block(stats) => + Block(stats.init.map(transformStat(_)), transform(stats.last)) + case While(cond, body) => + While(transform(cond), transformStat(body)) + case ForIn(obj, keyVar, keyVarOriginalName, body) => + ForIn(transform(obj), keyVar, keyVarOriginalName, transformStat(body)) + case TryFinally(block, finalizer) => + TryFinally(transform(block), transformStat(finalizer)) + + case _ => + super.transform(tree) + } + } + + def transform(tree: Tree, isStat: Boolean): Tree = + if (isStat) transformStat(tree) + else transform(tree) } - def readTopLevelExportDef(owner: ClassName, - ownerKind: ClassKind): TopLevelExportDef = { + private def bodyHackBelow6(body: Tree, isStat: Boolean): Tree = + if (!hacks.useBelow(6)) body + else BodyHackBelow6Transformer.transform(body, isStat) + + private def bodyHackBelow6Expr(body: Tree): Tree = bodyHackBelow6(body, isStat = false) + + def readTopLevelExportDef(): TopLevelExportDef = { implicit val pos = readPosition() val tag = readByte() - def readJSMethodDef(): JSMethodDef = - readMemberDef(owner, ownerKind).asInstanceOf[JSMethodDef] + def readJSMethodDef(): JSMethodDef = { + implicit val pos = readPosition() + val tag = readByte() + assert(tag == TagJSMethodDef, s"unexpected tag $tag") + this.readJSMethodDef() + } def readModuleID(): String = - if (hacks.use12) DefaultModuleID + if (hacks.useBelow(3)) DefaultModuleID else readString() (tag: @switch) match { case TagTopLevelJSClassExportDef => TopLevelJSClassExportDef(readModuleID(), readString()) case TagTopLevelModuleExportDef => TopLevelModuleExportDef(readModuleID(), readString()) case TagTopLevelMethodExportDef => TopLevelMethodExportDef(readModuleID(), readJSMethodDef()) - case TagTopLevelFieldExportDef => TopLevelFieldExportDef(readModuleID(), readString(), readFieldIdent()) + + case TagTopLevelFieldExportDef => + TopLevelFieldExportDef(readModuleID(), readString(), readFieldIdentForEnclosingClass()) } } - def readTopLevelExportDefs(owner: ClassName, - ownerKind: ClassKind): List[TopLevelExportDef] = { - List.fill(readInt())(readTopLevelExportDef(owner, ownerKind)) - } + def readTopLevelExportDefs(): List[TopLevelExportDef] = + List.fill(readInt())(readTopLevelExportDef()) def readLocalIdent(): LocalIdent = { implicit val pos = readPosition() LocalIdent(readLocalName()) } - def readLabelIdent(): LabelIdent = { + def readFieldIdent(): FieldIdent = { + // For historical reasons, the className comes *before* the position + val className = readClassName() implicit val pos = readPosition() - LabelIdent(readLabelName()) + val simpleName = readSimpleFieldName() + FieldIdent(makeFieldName(className, simpleName)) } - def readFieldIdent(): FieldIdent = { + def readFieldIdentForEnclosingClass(): FieldIdent = { implicit val pos = readPosition() - FieldIdent(readFieldName()) + val simpleName = readSimpleFieldName() + FieldIdent(makeFieldName(enclosingClassName, simpleName)) + } + + private def makeFieldName(className: ClassName, simpleName: SimpleFieldName): FieldName = { + val newFieldName = FieldName(className, simpleName) + uniqueFieldNames.getOrElseUpdate(newFieldName, newFieldName) } def readMethodIdent(): MethodIdent = { @@ -1500,7 +2490,7 @@ object Serializers { val ptpe = readType() val mutable = readBoolean() - if (hacks.use14) { + if (hacks.useBelow(5)) { val rest = readBoolean() assert(!rest, "Illegal rest parameter") } @@ -1512,7 +2502,7 @@ object Serializers { List.fill(readInt())(readParamDef()) def readParamDefsWithRest(): (List[ParamDef], Option[ParamDef]) = { - if (hacks.use14) { + if (hacks.useBelow(5)) { val (params, isRest) = List.fill(readInt()) { implicit val pos = readPosition() (ParamDef(readLocalIdent(), readOriginalName(), readType(), readBoolean()), readBoolean()) @@ -1538,27 +2528,36 @@ object Serializers { def readType(): Type = { val tag = readByte() (tag: @switch) match { - case TagAnyType => AnyType - case TagNothingType => NothingType - case TagUndefType => UndefType - case TagBooleanType => BooleanType - case TagCharType => CharType - case TagByteType => ByteType - case TagShortType => ShortType - case TagIntType => IntType - case TagLongType => LongType - case TagFloatType => FloatType - case TagDoubleType => DoubleType - case TagStringType => StringType - case TagNullType => NullType - case TagNoType => NoType - - case TagClassType => ClassType(readClassName()) - case TagArrayType => ArrayType(readArrayTypeRef()) + case TagAnyType => AnyType + case TagAnyNotNullType => AnyNotNullType + case TagNothingType => NothingType + case TagUndefType => UndefType + case TagBooleanType => BooleanType + case TagCharType => CharType + case TagByteType => ByteType + case TagShortType => ShortType + case TagIntType => IntType + case TagLongType => LongType + case TagFloatType => FloatType + case TagDoubleType => DoubleType + case TagStringType => StringType + case TagNullType => NullType + case TagVoidType => VoidType + + case TagClassType => ClassType(readClassName(), nullable = true) + case TagArrayType => ArrayType(readArrayTypeRef(), nullable = true) + + case TagNonNullClassType => ClassType(readClassName(), nullable = false) + case TagNonNullArrayType => ArrayType(readArrayTypeRef(), nullable = false) + + case TagClosureType | TagNonNullClosureType => + val paramTypes = readTypes() + val resultType = readType() + ClosureType(paramTypes, resultType, nullable = tag == TagClosureType) case TagRecordType => RecordType(List.fill(readInt()) { - val name = readFieldName() + val name = readSimpleFieldName() val originalName = readString() val tpe = readType() val mutable = readBoolean() @@ -1567,6 +2566,9 @@ object Serializers { } } + def readTypes(): List[Type] = + List.fill(readInt())(readType()) + def readTypeRef(): TypeRef = { readByte() match { case TagVoidRef => VoidRef @@ -1591,6 +2593,14 @@ object Serializers { def readApplyFlags(): ApplyFlags = ApplyFlags.fromBits(readInt()) + def readClosureFlags(): ClosureFlags = { + /* Before 1.19, the `flags` were a single `Boolean` for the `arrow` flag. + * The bit pattern of `flags` was crafted so that it matches the old + * boolean encoding for common values. + */ + ClosureFlags.fromBits(readByte()) + } + def readPosition(): Position = { import PositionFormat._ @@ -1650,13 +2660,13 @@ object Serializers { } } - def readOptHash(): Option[TreeHash] = { + def readOptHash(): Version = { if (readBoolean()) { val hash = new Array[Byte](20) buf.get(hash) - Some(new TreeHash(hash)) + Version.fromHash(hash) } else { - None + Unversioned } } @@ -1680,6 +2690,12 @@ object Serializers { } private def readLabelName(): LabelName = { + /* Before 1.18, `LabelName`s were always wrapped in `LabelIdent`s, whose + * encoding was a `Position` followed by the actual `LabelName`. + */ + if (hacks.useBelow(18)) + readPosition() // intentional discard + val i = readInt() val existing = labelNames(i) if (existing ne null) { @@ -1691,14 +2707,14 @@ object Serializers { } } - private def readFieldName(): FieldName = { + private def readSimpleFieldName(): SimpleFieldName = { val i = readInt() - val existing = fieldNames(i) + val existing = simpleFieldNames(i) if (existing ne null) { existing } else { - val result = FieldName(encodedNames(i)) - fieldNames(i) = result + val result = SimpleFieldName(encodedNames(i)) + simpleFieldNames(i) = result result } } @@ -1727,6 +2743,9 @@ object Serializers { } } + private def readClassNames(): List[ClassName] = + List.fill(readInt())(readClassName()) + private def readMethodName(): MethodName = methodNames(readInt()) @@ -1793,32 +2812,89 @@ object Serializers { } } - /** Hacks for backwards compatible deserializing. */ - private final class Hacks(sourceVersion: String) { - val use10: Boolean = sourceVersion == "1.0" - - val use11: Boolean = use10 || sourceVersion == "1.1" - - val use12: Boolean = use11 || sourceVersion == "1.2" - - private val use13: Boolean = use12 || sourceVersion == "1.3" - - val use14: Boolean = use13 || sourceVersion == "1.4" + /** Hacks for backwards compatible deserializing. + * + * `private[ir]` for testing purposes only. + */ + private[ir] final class Hacks(sourceVersion: String) { + private val fromVersion = sourceVersion match { + case CompatibleStableIRVersionRegex(minorDigits) => minorDigits.toInt + case _ => Int.MaxValue // never use any hack + } - val use15: Boolean = use14 || sourceVersion == "1.5" + /** Should we use the hacks to migrate from an IR version below `targetVersion`? */ + def useBelow(targetVersion: Int): Boolean = + fromVersion < targetVersion } /** Names needed for hacks. */ private object HackNames { + val AnonFunctionXXLClass = + ClassName("scala.scalajs.runtime.AnonFunctionXXL") // from the Scala 3 library val CloneNotSupportedExceptionClass = ClassName("java.lang.CloneNotSupportedException") val SystemModule: ClassName = ClassName("java.lang.System$") + val ReflectArrayClass = + ClassName("java.lang.reflect.Array") + val ReflectArrayModClass = + ClassName("java.lang.reflect.Array$") + + val ObjectArrayType = ArrayType(ArrayTypeRef(ObjectRef, 1), nullable = true) + + private val applySimpleName = SimpleMethodName("apply") val cloneName: MethodName = - MethodName("clone", Nil, ClassRef(ObjectClass)) + MethodName("clone", Nil, ObjectRef) val identityHashCodeName: MethodName = - MethodName("identityHashCode", List(ClassRef(ObjectClass)), IntRef) + MethodName("identityHashCode", List(ObjectRef), IntRef) + val newInstanceSingleName: MethodName = + MethodName("newInstance", List(ClassRef(ClassClass), IntRef), ObjectRef) + val newInstanceMultiName: MethodName = + MethodName("newInstance", List(ClassRef(ClassClass), ArrayTypeRef(IntRef, 1)), ObjectRef) + + private val anonFunctionArities: Map[ClassName, Int] = + (0 to 22).map(arity => ClassName(s"scala.scalajs.runtime.AnonFunction$arity") -> arity).toMap + val allAnonFunctionClasses: Set[ClassName] = + anonFunctionArities.keySet + AnonFunctionXXLClass + + object AnonFunctionClass { + def unapply(cls: ClassName): Option[Int] = + anonFunctionArities.get(cls) + } + + lazy val anonFunctionDescriptors: IndexedSeq[NewLambda.Descriptor] = { + anonFunctionArities.toIndexedSeq.sortBy(_._2).map { case (className, arity) => + NewLambda.Descriptor( + superClass = className, + interfaces = Nil, + methodName = MethodName(applySimpleName, List.fill(arity)(ObjectRef), ObjectRef), + paramTypes = List.fill(arity)(AnyType), + resultType = AnyType + ) + } + } + + lazy val anonFunctionXXLDescriptor: NewLambda.Descriptor = { + NewLambda.Descriptor( + superClass = AnonFunctionXXLClass, + interfaces = Nil, + methodName = MethodName(applySimpleName, List(ObjectArrayType.arrayTypeRef), ObjectRef), + paramTypes = List(ObjectArrayType), + resultType = AnyType + ) + } + } + + private class OptionBuilder[T] { + private var value: Option[T] = None + + def +=(x: T): Unit = { + require(value.isEmpty) + value = Some(x) + } + + def result(): Option[T] = value } /* Note [Nothing FieldDef rewrite] diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala b/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala index 2ea3eac68f..dc2862b7ec 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Tags.scala @@ -31,7 +31,7 @@ private[ir] object Tags { final val TagReturn = TagAssign + 1 final val TagIf = TagReturn + 1 final val TagWhile = TagIf + 1 - final val TagDoWhile = TagWhile + 1 + final val TagDoWhile = TagWhile + 1 // removed in 1.13 final val TagForIn = TagDoWhile + 1 final val TagTryCatch = TagForIn + 1 final val TagTryFinally = TagTryCatch + 1 @@ -122,6 +122,22 @@ private[ir] object Tags { final val TagJSNewTarget = TagJSImportMeta + 1 + // New in 1.11 + + final val TagWrapAsThrowable = TagJSNewTarget + 1 + final val TagUnwrapFromThrowable = TagWrapAsThrowable + 1 + + // New in 1.18 + final val TagLinkTimeProperty = TagUnwrapFromThrowable + 1 + + // New in 1.19 + final val TagApplyTypedClosure = TagLinkTimeProperty + 1 + final val TagNewLambda = TagApplyTypedClosure + 1 + final val TagJSAwait = TagNewLambda + 1 + + // New in 1.20 + final val TagLinkTimeIf = TagJSAwait + 1 + // Tags for member defs final val TagFieldDef = 1 @@ -134,6 +150,10 @@ private[ir] object Tags { final val TagJSNativeMemberDef = TagJSPropertyDef + 1 + // New in 1.11 + + final val TagJSConstructorDef = TagJSNativeMemberDef + 1 + // Tags for top-level export defs final val TagTopLevelJSClassExportDef = 1 @@ -159,7 +179,21 @@ private[ir] object Tags { final val TagClassType = TagNullType + 1 final val TagArrayType = TagClassType + 1 final val TagRecordType = TagArrayType + 1 - final val TagNoType = TagRecordType + 1 + final val TagVoidType = TagRecordType + 1 + + @deprecated("Use TagVoidType instead", since = "1.18.0") + final val TagNoType = TagVoidType + + // New in 1.17 + + final val TagAnyNotNullType = TagVoidType + 1 + final val TagNonNullClassType = TagAnyNotNullType + 1 + final val TagNonNullArrayType = TagNonNullClassType + 1 + + // New in 1.19 + + final val TagClosureType = TagNonNullArrayType + 1 + final val TagNonNullClosureType = TagClosureType + 1 // Tags for TypeRefs @@ -177,6 +211,10 @@ private[ir] object Tags { final val TagClassRef = TagNothingRef + 1 final val TagArrayTypeRef = TagClassRef + 1 + // New in 1.19 + + final val TagTransientTypeRefHashingOnly = TagArrayTypeRef + 1 + // Tags for JS native loading specs final val TagJSNativeLoadSpecNone = 0 diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala index bd45d5d60f..e95a154e1c 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Transformers.scala @@ -14,211 +14,200 @@ package org.scalajs.ir import Trees._ import Types._ +import Version.Unversioned object Transformers { abstract class Transformer { - final def transformStat(tree: Tree): Tree = - transform(tree, isStat = true) - - final def transformExpr(tree: Tree): Tree = - transform(tree, isStat = false) - - def transformExprOrJSSpread(tree: TreeOrJSSpread): TreeOrJSSpread = { + final def transformTreeOrJSSpread(tree: TreeOrJSSpread): TreeOrJSSpread = { implicit val pos = tree.pos tree match { - case JSSpread(items) => JSSpread(transformExpr(items)) - case tree: Tree => transformExpr(tree) + case JSSpread(items) => JSSpread(transform(items)) + case tree: Tree => transform(tree) } } - def transform(tree: Tree, isStat: Boolean): Tree = { + final def transformTrees(trees: List[Tree]): List[Tree] = + trees.map(transform(_)) + + final def transformTreeOpt(treeOpt: Option[Tree]): Option[Tree] = + treeOpt.map(transform(_)) + + def transform(tree: Tree): Tree = { implicit val pos = tree.pos tree match { // Definitions case VarDef(ident, originalName, vtpe, mutable, rhs) => - VarDef(ident, originalName, vtpe, mutable, transformExpr(rhs)) + VarDef(ident, originalName, vtpe, mutable, transform(rhs)) // Control flow constructs case Block(stats) => - Block(stats.init.map(transformStat) :+ transform(stats.last, isStat)) + Block(transformTrees(stats)) case Labeled(label, tpe, body) => - Labeled(label, tpe, transform(body, isStat)) + Labeled(label, tpe, transform(body)) case Assign(lhs, rhs) => - Assign(transformExpr(lhs).asInstanceOf[AssignLhs], transformExpr(rhs)) + Assign(transform(lhs).asInstanceOf[AssignLhs], transform(rhs)) case Return(expr, label) => - Return(transformExpr(expr), label) + Return(transform(expr), label) case If(cond, thenp, elsep) => - If(transformExpr(cond), transform(thenp, isStat), - transform(elsep, isStat))(tree.tpe) + If(transform(cond), transform(thenp), transform(elsep))(tree.tpe) - case While(cond, body) => - While(transformExpr(cond), transformStat(body)) + case LinkTimeIf(cond, thenp, elsep) => + LinkTimeIf(transform(cond), transform(thenp), transform(elsep))(tree.tpe) - case DoWhile(body, cond) => - DoWhile(transformStat(body), transformExpr(cond)) + case While(cond, body) => + While(transform(cond), transform(body)) case ForIn(obj, keyVar, keyVarOriginalName, body) => - ForIn(transformExpr(obj), keyVar, keyVarOriginalName, - transformStat(body)) + ForIn(transform(obj), keyVar, keyVarOriginalName, transform(body)) case TryCatch(block, errVar, errVarOriginalName, handler) => - TryCatch(transform(block, isStat), errVar, errVarOriginalName, - transform(handler, isStat))(tree.tpe) + TryCatch(transform(block), errVar, errVarOriginalName, + transform(handler))(tree.tpe) case TryFinally(block, finalizer) => - TryFinally(transform(block, isStat), transformStat(finalizer)) - - case Throw(expr) => - Throw(transformExpr(expr)) + TryFinally(transform(block), transform(finalizer)) case Match(selector, cases, default) => - Match(transformExpr(selector), - cases map (c => (c._1, transform(c._2, isStat))), - transform(default, isStat))(tree.tpe) + Match(transform(selector), cases.map(c => (c._1, transform(c._2))), + transform(default))(tree.tpe) + + case JSAwait(arg) => + JSAwait(transform(arg)) // Scala expressions case New(className, ctor, args) => - New(className, ctor, args map transformExpr) + New(className, ctor, transformTrees(args)) - case StoreModule(className, value) => - StoreModule(className, transformExpr(value)) - - case Select(qualifier, className, field) => - Select(transformExpr(qualifier), className, field)(tree.tpe) + case Select(qualifier, field) => + Select(transform(qualifier), field)(tree.tpe) case Apply(flags, receiver, method, args) => - Apply(flags, transformExpr(receiver), method, - args map transformExpr)(tree.tpe) + Apply(flags, transform(receiver), method, + transformTrees(args))(tree.tpe) case ApplyStatically(flags, receiver, className, method, args) => - ApplyStatically(flags, transformExpr(receiver), className, method, - args map transformExpr)(tree.tpe) + ApplyStatically(flags, transform(receiver), className, method, + transformTrees(args))(tree.tpe) case ApplyStatic(flags, className, method, args) => - ApplyStatic(flags, className, method, args map transformExpr)(tree.tpe) + ApplyStatic(flags, className, method, transformTrees(args))(tree.tpe) case ApplyDynamicImport(flags, className, method, args) => - ApplyDynamicImport(flags, className, method, args.map(transformExpr)) + ApplyDynamicImport(flags, className, method, transformTrees(args)) + + case ApplyTypedClosure(flags, fun, args) => + ApplyTypedClosure(flags, transform(fun), transformTrees(args)) + + case NewLambda(descriptor, fun) => + NewLambda(descriptor, transform(fun))(tree.tpe) case UnaryOp(op, lhs) => - UnaryOp(op, transformExpr(lhs)) + UnaryOp(op, transform(lhs)) case BinaryOp(op, lhs, rhs) => - BinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + BinaryOp(op, transform(lhs), transform(rhs)) - case NewArray(tpe, lengths) => - NewArray(tpe, lengths map transformExpr) + case NewArray(tpe, length) => + NewArray(tpe, transform(length)) case ArrayValue(tpe, elems) => - ArrayValue(tpe, elems map transformExpr) - - case ArrayLength(array) => - ArrayLength(transformExpr(array)) + ArrayValue(tpe, transformTrees(elems)) case ArraySelect(array, index) => - ArraySelect(transformExpr(array), transformExpr(index))(tree.tpe) + ArraySelect(transform(array), transform(index))(tree.tpe) case RecordValue(tpe, elems) => - RecordValue(tpe, elems map transformExpr) + RecordValue(tpe, transformTrees(elems)) case RecordSelect(record, field) => - RecordSelect(transformExpr(record), field)(tree.tpe) + RecordSelect(transform(record), field)(tree.tpe) case IsInstanceOf(expr, testType) => - IsInstanceOf(transformExpr(expr), testType) + IsInstanceOf(transform(expr), testType) case AsInstanceOf(expr, tpe) => - AsInstanceOf(transformExpr(expr), tpe) - - case GetClass(expr) => - GetClass(transformExpr(expr)) - - case Clone(expr) => - Clone(transformExpr(expr)) - - case IdentityHashCode(expr) => - IdentityHashCode(transformExpr(expr)) + AsInstanceOf(transform(expr), tpe) // JavaScript expressions case JSNew(ctor, args) => - JSNew(transformExpr(ctor), args.map(transformExprOrJSSpread)) + JSNew(transform(ctor), args.map(transformTreeOrJSSpread)) - case JSPrivateSelect(qualifier, className, field) => - JSPrivateSelect(transformExpr(qualifier), className, field) + case JSPrivateSelect(qualifier, field) => + JSPrivateSelect(transform(qualifier), field) case JSSelect(qualifier, item) => - JSSelect(transformExpr(qualifier), transformExpr(item)) + JSSelect(transform(qualifier), transform(item)) case JSFunctionApply(fun, args) => - JSFunctionApply(transformExpr(fun), args.map(transformExprOrJSSpread)) + JSFunctionApply(transform(fun), args.map(transformTreeOrJSSpread)) case JSMethodApply(receiver, method, args) => - JSMethodApply(transformExpr(receiver), transformExpr(method), - args.map(transformExprOrJSSpread)) + JSMethodApply(transform(receiver), transform(method), + args.map(transformTreeOrJSSpread)) case JSSuperSelect(superClass, qualifier, item) => - JSSuperSelect(superClass, transformExpr(qualifier), - transformExpr(item)) + JSSuperSelect(superClass, transform(qualifier), transform(item)) case JSSuperMethodCall(superClass, receiver, method, args) => - JSSuperMethodCall(superClass, transformExpr(receiver), - transformExpr(method), args.map(transformExprOrJSSpread)) + JSSuperMethodCall(superClass, transform(receiver), + transform(method), args.map(transformTreeOrJSSpread)) case JSSuperConstructorCall(args) => - JSSuperConstructorCall(args.map(transformExprOrJSSpread)) + JSSuperConstructorCall(args.map(transformTreeOrJSSpread)) case JSImportCall(arg) => - JSImportCall(transformExpr(arg)) + JSImportCall(transform(arg)) case JSDelete(qualifier, item) => - JSDelete(transformExpr(qualifier), transformExpr(item)) + JSDelete(transform(qualifier), transform(item)) case JSUnaryOp(op, lhs) => - JSUnaryOp(op, transformExpr(lhs)) + JSUnaryOp(op, transform(lhs)) case JSBinaryOp(op, lhs, rhs) => - JSBinaryOp(op, transformExpr(lhs), transformExpr(rhs)) + JSBinaryOp(op, transform(lhs), transform(rhs)) case JSArrayConstr(items) => - JSArrayConstr(items.map(transformExprOrJSSpread)) + JSArrayConstr(items.map(transformTreeOrJSSpread)) case JSObjectConstr(fields) => JSObjectConstr(fields.map { field => - (transformExpr(field._1), transformExpr(field._2)) + (transform(field._1), transform(field._2)) }) case JSTypeOfGlobalRef(globalRef) => - JSTypeOfGlobalRef(transformExpr(globalRef).asInstanceOf[JSGlobalRef]) + JSTypeOfGlobalRef(transform(globalRef).asInstanceOf[JSGlobalRef]) // Atomic expressions - case Closure(arrow, captureParams, params, restParam, body, captureValues) => - Closure(arrow, captureParams, params, restParam, transformExpr(body), - captureValues.map(transformExpr)) + case Closure(flags, captureParams, params, restParam, resultType, body, captureValues) => + Closure(flags, captureParams, params, restParam, resultType, + transform(body), transformTrees(captureValues)) case CreateJSClass(className, captureValues) => - CreateJSClass(className, captureValues.map(transformExpr)) + CreateJSClass(className, transformTrees(captureValues)) // Transients case Transient(value) => - value.transform(this, isStat) + value.transform(this) // Trees that need not be transformed - case _:Skip | _:Debugger | _:LoadModule | _:SelectStatic | _:SelectJSNativeMember | - _:LoadJSConstructor | _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | - _:JSLinkingInfo | _:Literal | _:VarRef | _:This | _:JSGlobalRef => + case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | + _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | + _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | + _:Literal | _:VarRef | _:JSGlobalRef | _:LinkTimeProperty => tree } } @@ -228,41 +217,68 @@ object Transformers { def transformClassDef(tree: ClassDef): ClassDef = { import tree._ ClassDef(name, originalName, kind, jsClassCaptures, superClass, - interfaces, jsSuperClass.map(transformExpr), jsNativeLoadSpec, - memberDefs.map(transformMemberDef), + interfaces, transformTreeOpt(jsSuperClass), jsNativeLoadSpec, + fields.map(transformAnyFieldDef(_)), + methods.map(transformMethodDef), jsConstructor.map(transformJSConstructorDef), + jsMethodProps.map(transformJSMethodPropDef), jsNativeMembers, topLevelExportDefs.map(transformTopLevelExportDef))( tree.optimizerHints)(tree.pos) } - def transformMemberDef(memberDef: MemberDef): MemberDef = { - implicit val pos = memberDef.pos - - memberDef match { - case _:AnyFieldDef | _:JSNativeMemberDef => - memberDef - - case memberDef: MethodDef => - val MethodDef(flags, name, originalName, args, resultType, body) = memberDef - val newBody = body.map(transform(_, isStat = resultType == NoType)) - MethodDef(flags, name, originalName, args, resultType, newBody)( - memberDef.optimizerHints, None) - - case memberDef: JSMethodDef => - val JSMethodDef(flags, name, args, restParam, body) = memberDef - JSMethodDef(flags, name, args, restParam, transformExpr(body))( - memberDef.optimizerHints, None) - - case JSPropertyDef(flags, name, getterBody, setterArgAndBody) => - JSPropertyDef( - flags, - name, - getterBody.map(transformStat), - setterArgAndBody map { case (arg, body) => - (arg, transformStat(body)) - }) + def transformAnyFieldDef(fieldDef: AnyFieldDef): AnyFieldDef = + fieldDef + + def transformMethodDef(methodDef: MethodDef): MethodDef = { + val MethodDef(flags, name, originalName, args, resultType, body) = methodDef + val newBody = transformTreeOpt(body) + MethodDef(flags, name, originalName, args, resultType, newBody)( + methodDef.optimizerHints, Unversioned)(methodDef.pos) + } + + def transformJSConstructorDef(jsConstructor: JSConstructorDef): JSConstructorDef = { + val JSConstructorDef(flags, args, restParam, body) = jsConstructor + JSConstructorDef(flags, args, restParam, transformJSConstructorBody(body))( + jsConstructor.optimizerHints, Unversioned)(jsConstructor.pos) + } + + def transformJSMethodPropDef(jsMethodPropDef: JSMethodPropDef): JSMethodPropDef = { + jsMethodPropDef match { + case jsMethodDef: JSMethodDef => + transformJSMethodDef(jsMethodDef) + + case jsPropertyDef: JSPropertyDef => + transformJSPropertyDef(jsPropertyDef) } } + def transformJSMethodDef(jsMethodDef: JSMethodDef): JSMethodDef = { + val JSMethodDef(flags, name, args, restParam, body) = jsMethodDef + JSMethodDef(flags, transform(name), args, restParam, transform(body))( + jsMethodDef.optimizerHints, Unversioned)(jsMethodDef.pos) + } + + def transformJSPropertyDef(jsPropertyDef: JSPropertyDef): JSPropertyDef = { + val JSPropertyDef(flags, name, getterBody, setterArgAndBody) = jsPropertyDef + JSPropertyDef( + flags, + transform(name), + transformTreeOpt(getterBody), + setterArgAndBody.map { case (arg, body) => + (arg, transform(body)) + } + )(Unversioned)(jsPropertyDef.pos) + } + + def transformJSConstructorBody(body: JSConstructorBody): JSConstructorBody = { + implicit val pos = body.pos + + val newBeforeSuper = transformTrees(body.beforeSuper) + val newSuperCall = transform(body.superCall).asInstanceOf[JSSuperConstructorCall] + val newAfterSuper = transformTrees(body.afterSuper) + + JSConstructorBody(newBeforeSuper, newSuperCall, newAfterSuper) + } + def transformTopLevelExportDef( exportDef: TopLevelExportDef): TopLevelExportDef = { @@ -274,10 +290,24 @@ object Transformers { exportDef case TopLevelMethodExportDef(moduleID, methodDef) => - TopLevelMethodExportDef(moduleID, - transformMemberDef(methodDef).asInstanceOf[JSMethodDef]) + TopLevelMethodExportDef(moduleID, transformJSMethodDef(methodDef)) } } } + /** Transformer that only transforms in the local scope. + * + * In practice, this means stopping at `Closure` boundaries: their + * `captureValues` are transformed, but not their other members. + */ + abstract class LocalScopeTransformer extends Transformer { + override def transform(tree: Tree): Tree = tree match { + case Closure(flags, captureParams, params, restParam, resultType, body, captureValues) => + Closure(flags, captureParams, params, restParam, resultType, body, + transformTrees(captureValues))(tree.pos) + case _ => + super.transform(tree) + } + } + } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala index d0bac2ffc1..15c9da9093 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Traversers.scala @@ -48,13 +48,14 @@ object Traversers { traverse(thenp) traverse(elsep) - case While(cond, body) => + case LinkTimeIf(cond, thenp, elsep) => traverse(cond) - traverse(body) + traverse(thenp) + traverse(elsep) - case DoWhile(body, cond) => - traverse(body) + case While(cond, body) => traverse(cond) + traverse(body) case ForIn(obj, _, _, body) => traverse(obj) @@ -68,23 +69,20 @@ object Traversers { traverse(block) traverse(finalizer) - case Throw(expr) => - traverse(expr) - case Match(selector, cases, default) => traverse(selector) cases foreach (c => (c._1 map traverse, traverse(c._2))) traverse(default) + case JSAwait(arg) => + traverse(arg) + // Scala expressions case New(_, _, args) => args foreach traverse - case StoreModule(_, value) => - traverse(value) - - case Select(qualifier, _, _) => + case Select(qualifier, _) => traverse(qualifier) case Apply(_, receiver, _, args) => @@ -101,6 +99,13 @@ object Traversers { case ApplyDynamicImport(_, _, _, args) => args.foreach(traverse) + case ApplyTypedClosure(_, fun, args) => + traverse(fun) + args.foreach(traverse) + + case NewLambda(_, fun) => + traverse(fun) + case UnaryOp(op, lhs) => traverse(lhs) @@ -108,15 +113,12 @@ object Traversers { traverse(lhs) traverse(rhs) - case NewArray(tpe, lengths) => - lengths foreach traverse + case NewArray(tpe, length) => + traverse(length) case ArrayValue(tpe, elems) => elems foreach traverse - case ArrayLength(array) => - traverse(array) - case ArraySelect(array, index) => traverse(array) traverse(index) @@ -133,22 +135,13 @@ object Traversers { case AsInstanceOf(expr, _) => traverse(expr) - case GetClass(expr) => - traverse(expr) - - case Clone(expr) => - traverse(expr) - - case IdentityHashCode(expr) => - traverse(expr) - // JavaScript expressions case JSNew(ctor, args) => traverse(ctor) args.foreach(traverseTreeOrJSSpread) - case JSPrivateSelect(qualifier, _, _) => + case JSPrivateSelect(qualifier, _) => traverse(qualifier) case JSSelect(qualifier, item) => @@ -206,7 +199,7 @@ object Traversers { // Atomic expressions - case Closure(arrow, captureParams, params, restParam, body, captureValues) => + case Closure(flags, captureParams, params, restParam, resultType, body, captureValues) => traverse(body) captureValues.foreach(traverse) @@ -220,28 +213,37 @@ object Traversers { // Trees that need not be traversed - case _:Skip | _:Debugger | _:LoadModule | _:SelectStatic | _:SelectJSNativeMember | - _:LoadJSConstructor | _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | - _:JSLinkingInfo | _:Literal | _:VarRef | _:This | _:JSGlobalRef => + case _:Skip | _:Debugger | _:LoadModule | _:StoreModule | + _:SelectStatic | _:SelectJSNativeMember | _:LoadJSConstructor | + _:LoadJSModule | _:JSNewTarget | _:JSImportMeta | + _:Literal | _:VarRef | _:JSGlobalRef | _:LinkTimeProperty => } def traverseClassDef(tree: ClassDef): Unit = { tree.jsSuperClass.foreach(traverse) - tree.memberDefs.foreach(traverseMemberDef) + tree.fields.foreach(traverseAnyFieldDef) + tree.methods.foreach(traverseMethodDef) + tree.jsConstructor.foreach(traverseJSConstructorDef) + tree.jsMethodProps.foreach(traverseJSMethodPropDef) tree.topLevelExportDefs.foreach(traverseTopLevelExportDef) } - def traverseMemberDef(memberDef: MemberDef): Unit = { - memberDef match { - case _:AnyFieldDef | _:JSNativeMemberDef => + def traverseAnyFieldDef(fieldDef: AnyFieldDef): Unit = () + + def traverseMethodDef(methodDef: MethodDef): Unit = + methodDef.body.foreach(traverse) - case MethodDef(_, _, _, _, _, body) => - body.foreach(traverse) + def traverseJSConstructorDef(jsConstructor: JSConstructorDef): Unit = + jsConstructor.body.allStats.foreach(traverse) - case JSMethodDef(_, _, _, _, body) => + def traverseJSMethodPropDef(jsMethodPropDef: JSMethodPropDef): Unit = { + jsMethodPropDef match { + case JSMethodDef(_, name, _, _, body) => + traverse(name) traverse(body) - case JSPropertyDef(_, _, getterBody, setterArgAndBody) => + case JSPropertyDef(_, name, getterBody, setterArgAndBody) => + traverse(name) getterBody.foreach(traverse) setterArgAndBody.foreach(argAndBody => traverse(argAndBody._2)) } @@ -253,9 +255,23 @@ object Traversers { _:TopLevelFieldExportDef => case TopLevelMethodExportDef(_, methodDef) => - traverseMemberDef(methodDef) + traverseJSMethodPropDef(methodDef) } } } + /** Traverser that only traverses the local scope. + * + * In practice, this means stopping at `Closure` boundaries: their + * `captureValues` are traversed, but not their other members. + */ + abstract class LocalScopeTraverser extends Traverser { + override def traverse(tree: Tree): Unit = tree match { + case Closure(_, _, _, _, _, _, captureValues) => + captureValues.foreach(traverse(_)) + case _ => + super.traverse(tree) + } + } + } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala index 85a77eead6..23a2eb7118 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Trees.scala @@ -18,11 +18,14 @@ import Names._ import OriginalName.NoOriginalName import Position.NoPosition import Types._ +import WellKnownNames._ object Trees { - /* The case classes for IR Nodes are sealed instead of final because making - * them final triggers bugs with Scala 2.11.x and 2.12.{1-4}, in combination - * with their `implicit val pos`. + /* The case classes for IR Nodes are sealed instead of final for historical + * reasons. Making them final used to trigger bugs with Scala 2.12.{1-4}, in + * combination with their `implicit val pos`. + * TODO Now that we dropped support for Scala 2.12.5 and below, we should + * revisit this. */ /** Base class for all nodes in the IR. @@ -56,7 +59,7 @@ object Trees { sealed case class LocalIdent(name: LocalName)(implicit val pos: Position) extends IRNode - sealed case class LabelIdent(name: LabelName)(implicit val pos: Position) + sealed case class SimpleFieldIdent(name: SimpleFieldName)(implicit val pos: Position) extends IRNode sealed case class FieldIdent(name: FieldName)(implicit val pos: Position) @@ -99,21 +102,21 @@ object Trees { sealed case class VarDef(name: LocalIdent, originalName: OriginalName, vtpe: Type, mutable: Boolean, rhs: Tree)( implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + val tpe = VoidType - def ref(implicit pos: Position): VarRef = VarRef(name)(vtpe) + def ref(implicit pos: Position): VarRef = VarRef(name.name)(vtpe) } sealed case class ParamDef(name: LocalIdent, originalName: OriginalName, ptpe: Type, mutable: Boolean)( implicit val pos: Position) extends IRNode { - def ref(implicit pos: Position): VarRef = VarRef(name)(ptpe) + def ref(implicit pos: Position): VarRef = VarRef(name.name)(ptpe) } // Control flow constructs sealed case class Skip()(implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + val tpe = VoidType } sealed class Block private (val stats: List[Tree])( @@ -147,17 +150,17 @@ object Trees { def unapply(block: Block): Some[List[Tree]] = Some(block.stats) } - sealed case class Labeled(label: LabelIdent, tpe: Type, body: Tree)( + sealed case class Labeled(label: LabelName, tpe: Type, body: Tree)( implicit val pos: Position) extends Tree sealed trait AssignLhs extends Tree sealed case class Assign(lhs: AssignLhs, rhs: Tree)( implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + val tpe = VoidType } - sealed case class Return(expr: Tree, label: LabelIdent)( + sealed case class Return(expr: Tree, label: LabelName)( implicit val pos: Position) extends Tree { val tpe = NothingType } @@ -165,24 +168,50 @@ object Trees { sealed case class If(cond: Tree, thenp: Tree, elsep: Tree)(val tpe: Type)( implicit val pos: Position) extends Tree + /** Link-time `if` expression. + * + * The `cond` must be a well-typed link-time tree of type `boolean`. + * + * A link-time tree is a `Tree` matching the following sub-grammar: + * + * {{{ + * link-time-tree ::= + * BooleanLiteral + * | IntLiteral + * | StringLiteral + * | LinkTimeProperty + * | UnaryOp(link-time-unary-op, link-time-tree) + * | BinaryOp(link-time-binary-op, link-time-tree, link-time-tree) + * | LinkTimeIf(link-time-tree, link-time-tree, link-time-tree) + * + * link-time-unary-op ::= + * Boolean_! + * + * link-time-binary-op ::= + * Boolean_== | Boolean_!= | Boolean_| | Boolean_& + * | Int_== | Int_!= | Int_< | Int_<= | Int_> | Int_>= + * }}} + * + * Note: nested `LinkTimeIf` nodes in the `cond` are used to encode + * short-circuiting boolean `&&` and `||`, just like we do with regular + * `If` nodes. + */ + sealed case class LinkTimeIf(cond: Tree, thenp: Tree, elsep: Tree)( + val tpe: Type)(implicit val pos: Position) + extends Tree + sealed case class While(cond: Tree, body: Tree)( implicit val pos: Position) extends Tree { - // cannot be in expression position, unless it is infinite val tpe = cond match { case BooleanLiteral(true) => NothingType - case _ => NoType + case _ => VoidType } } - sealed case class DoWhile(body: Tree, cond: Tree)( - implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position - } - sealed case class ForIn(obj: Tree, keyVar: LocalIdent, keyVarOriginalName: OriginalName, body: Tree)( implicit val pos: Position) extends Tree { - val tpe = NoType + val tpe = VoidType } sealed case class TryCatch(block: Tree, errVar: LocalIdent, @@ -194,10 +223,6 @@ object Trees { val tpe = block.tpe } - sealed case class Throw(expr: Tree)(implicit val pos: Position) extends Tree { - val tpe = NothingType - } - /** A break-free switch (without fallthrough behavior). * * Unlike a JavaScript switch, it can be used in expression position. @@ -225,8 +250,24 @@ object Trees { sealed case class Match(selector: Tree, cases: List[(List[MatchableLiteral], Tree)], default: Tree)(val tpe: Type)(implicit val pos: Position) extends Tree + /** `await arg`. + * + * This is directly equivalent to a JavaScript `await` expression. + * + * If used directly within a [[Closure]] node with the `async` flag, this + * node is always valid. However, when used anywhere else, it is an "orphan" + * await. Orphan awaits only link when targeting WebAssembly. + * + * This is not a `UnaryOp` because of the above strict scoping rule. For + * example, unless it is orphan to begin with, it is not safe to pull this + * node out of or into an intervening closure, contrary to `UnaryOp`s. + */ + sealed case class JSAwait(arg: Tree)(implicit val pos: Position) extends Tree { + val tpe = AnyType + } + sealed case class Debugger()(implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + val tpe = VoidType } // Scala expressions @@ -234,26 +275,27 @@ object Trees { sealed case class New(className: ClassName, ctor: MethodIdent, args: List[Tree])( implicit val pos: Position) extends Tree { - val tpe = ClassType(className) + val tpe = ClassType(className, nullable = false) } sealed case class LoadModule(className: ClassName)( implicit val pos: Position) extends Tree { - val tpe = ClassType(className) + /* With Compliant moduleInits, `LoadModule`s are nullable! + * The linker components have dedicated code to consider `LoadModule`s as + * non-nullable depending on the semantics, but the `tpe` here must be + * nullable in the general case. + */ + val tpe = ClassType(className, nullable = true) } - sealed case class StoreModule(className: ClassName, value: Tree)( - implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + sealed case class StoreModule()(implicit val pos: Position) extends Tree { + val tpe = VoidType } - sealed case class Select(qualifier: Tree, className: ClassName, - field: FieldIdent)( - val tpe: Type)( + sealed case class Select(qualifier: Tree, field: FieldIdent)(val tpe: Type)( implicit val pos: Position) extends AssignLhs - sealed case class SelectStatic(className: ClassName, field: FieldIdent)( - val tpe: Type)( + sealed case class SelectStatic(field: FieldIdent)(val tpe: Type)( implicit val pos: Position) extends AssignLhs sealed case class SelectJSNativeMember(className: ClassName, member: MethodIdent)( @@ -265,10 +307,7 @@ object Trees { /** Apply an instance method with dynamic dispatch (the default). */ sealed case class Apply(flags: ApplyFlags, receiver: Tree, method: MethodIdent, args: List[Tree])( - val tpe: Type)(implicit val pos: Position) extends Tree { - - require(!flags.isPrivate, "invalid flag Private for Apply") - } + val tpe: Type)(implicit val pos: Position) extends Tree /** Apply an instance method with static dispatch (e.g., super calls). */ sealed case class ApplyStatically(flags: ApplyFlags, receiver: Tree, @@ -285,16 +324,137 @@ object Trees { method: MethodIdent, args: List[Tree])( implicit val pos: Position) extends Tree { val tpe = AnyType + } + + /** Apply a typed closure + * + * The given `fun` must have a closure type. + * + * The arguments' types must match (be subtypes of) the parameter types of + * the closure type. + * + * The `tpe` of this node is the result type of the closure type, or + * `nothing` if the latter is `nothing`. + * + * Evaluation steps are as follows: + * + * 1. Let `funV` be the result of evaluating `fun`. + * 2. If `funV` is `nullClosure`, trigger an NPE undefined behavior. + * 3. Let `argsV` be the result of evaluating `args`, in order. + * 4. Invoke `funV` with arguments `argsV`, and return the result. + */ + sealed case class ApplyTypedClosure(flags: ApplyFlags, fun: Tree, args: List[Tree])( + implicit val pos: Position) + extends Tree { + + val tpe: Type = fun.tpe match { + case ClosureType(_, resultType, _) => resultType + case NothingType => NothingType + case _ => NothingType // never a valid tree + } + } + + /** New lambda instance of a SAM class. + * + * Functionally, a `NewLambda` is equivalent to an instance of an anonymous + * class with the following shape: + * + * {{{ + * val funV: ((...Ts) => R)! = fun; + * (new superClass with interfaces { + * def () = this.superClass::() + * def methodName(...args: Ts): R = funV(...args) + * }): tpe + * }}} + * + * where `superClass`, `interfaces`, `methodName`, `Ts` and `R` are taken + * from the `descriptor`. `Ts` and `R` are the `paramTypes` and `resultType` + * of the descriptor. They are required because there is no one-to-one + * mapping between `TypeRef`s and `Type`s, and we want the shape of the + * class to be a deterministic function of the `descriptor`. + * + * The `fun` must have type `((...Ts) => R)!`. + * + * Intuitively, `tpe` must be a supertype of `superClass! & ...interfaces!`. + * Since our type system does not have intersection types, in practice this + * means that there must exist `C ∈ { superClass } ∪ interfaces` such that + * `tpe` is a supertype of `C!`. + * + * The uniqueness of the anonymous class and its run-time class name are + * not guaranteed. + */ + sealed case class NewLambda(descriptor: NewLambda.Descriptor, fun: Tree)( + val tpe: Type)( + implicit val pos: Position) + extends Tree - require(!flags.isPrivate, "invalid flag Private for ApplyDynamicImport") - require(!flags.isConstructor, "invalid flag Constructor for ApplyDynamicImport") + object NewLambda { + final case class Descriptor(superClass: ClassName, + interfaces: List[ClassName], methodName: MethodName, + paramTypes: List[Type], resultType: Type) { + + require(paramTypes.size == methodName.paramTypeRefs.size) + + private val _hashCode: Int = { + import scala.util.hashing.MurmurHash3._ + var acc = 1546348150 // "NewLambda.Descriptor".hashCode() + acc = mix(acc, superClass.##) + acc = mix(acc, interfaces.##) + acc = mix(acc, methodName.##) + acc = mix(acc, paramTypes.##) + acc = mixLast(acc, resultType.##) + finalizeHash(acc, 5) + } + + // Overridden despite the 'case class' because we want the fail fast on different hash codes + override def equals(that: Any): Boolean = { + (this eq that.asInstanceOf[AnyRef]) || (that match { + case that: Descriptor => + this._hashCode == that._hashCode && // fail fast on different hash codes + this.superClass == that.superClass && + this.interfaces == that.interfaces && + this.methodName == that.methodName && + this.paramTypes == that.paramTypes && + this.resultType == that.resultType + case _ => + false + }) + } + + // Overridden despite the 'case class' because we want to store it + override def hashCode(): Int = _hashCode + + // Overridden despite the 'case class' because we want the better prefix string + override def toString(): String = + s"NewLambda.Descriptor($superClass, $interfaces, $methodName, $paramTypes, $resultType)" + } } - /** Unary operation (always preserves pureness). */ + /** Unary operation. + * + * All unary operations follow common evaluation steps: + * + * 1. Let `lhsValue` be the result of evaluating `lhs`. + * 2. Perform an operation that depends on `op` and `lhsValue`. + * + * The `Class_x` operations take a `jl.Class!` argument, i.e., a + * non-nullable `jl.Class`. + * + * Likewise, `Array_length`, `GetClass`, `Clone` and `UnwrapFromThrowable` + * take arguments of non-nullable types. + * + * `CheckNotNull` throws NPEs subject to UB. + * + * `Throw` always throws, obviously. + * + * `Clone` and `WrapAsThrowable` are side-effect-free but not pure. + * + * Otherwise, unary operations preserve pureness. + */ sealed case class UnaryOp(op: UnaryOp.Code, lhs: Tree)( implicit val pos: Position) extends Tree { - val tpe = UnaryOp.resultTypeOf(op) + val tpe = UnaryOp.resultTypeOf(op, lhs.tpe) } object UnaryOp { @@ -326,8 +486,42 @@ object Trees { // Long -> Float (neither widening nor narrowing), introduced in 1.6 final val LongToFloat = 16 - def resultTypeOf(op: Code): Type = (op: @switch) match { - case Boolean_! => + // String.length, introduced in 1.11 + final val String_length = 17 + + // Null check, introduced in 1.17 + final val CheckNotNull = 18 + + // Class operations, introduced in 1.17 + final val Class_name = 19 + final val Class_isPrimitive = 20 + final val Class_isInterface = 21 + final val Class_isArray = 22 + final val Class_componentType = 23 + final val Class_superClass = 24 + + // Misc ops introduced in 1.18, which used to have dedicated IR nodes + final val Array_length = 25 + final val GetClass = 26 + final val Clone = 27 + final val IdentityHashCode = 28 + final val WrapAsThrowable = 29 + final val UnwrapFromThrowable = 30 + final val Throw = 31 + + def isClassOp(op: Code): Boolean = + op >= Class_name && op <= Class_superClass + + def isPureOp(op: Code): Boolean = (op: @switch) match { + case CheckNotNull | Clone | WrapAsThrowable | Throw => false + case _ => true + } + + def isSideEffectFreeOp(op: Code): Boolean = + op != CheckNotNull && op != Throw + + def resultTypeOf(op: Code, argType: Type): Type = (op: @switch) match { + case Boolean_! | Class_isPrimitive | Class_isInterface | Class_isArray => BooleanType case IntToChar => CharType @@ -335,7 +529,8 @@ object Trees { ByteType case IntToShort => ShortType - case CharToInt | ByteToInt | ShortToInt | LongToInt | DoubleToInt => + case CharToInt | ByteToInt | ShortToInt | LongToInt | DoubleToInt | + String_length | Array_length | IdentityHashCode => IntType case IntToLong | DoubleToLong => LongType @@ -343,10 +538,51 @@ object Trees { FloatType case IntToDouble | LongToDouble | FloatToDouble => DoubleType + case CheckNotNull | Clone => + argType.toNonNullable + case Class_name => + StringType + case Class_componentType | Class_superClass | GetClass => + ClassType(ClassClass, nullable = true) + case WrapAsThrowable => + ClassType(ThrowableClass, nullable = false) + case UnwrapFromThrowable => + AnyType + case Throw => + NothingType } } - /** Binary operation (always preserves pureness). */ + /** Binary operation. + * + * All binary operations follow common evaluation steps: + * + * 1. Let `lhsValue` be the result of evaluating `lhs`. + * 2. Let `rhsValue` be the result of evaluating `rhs`. + * 3. Perform an operation that depends on `op`, `lhsValue` and `rhsValue`. + * + * Unless `lhsValue` throws, `rhsValue` will therefore always be evaluated, + * even if the operation `op` would throw based only on `lhsValue`. + * + * The integer dividing operators (`Int_/`, `Int_%`, `Long_/` and `Long_%`) + * throw an `ArithmeticException` when their right-hand-side is 0. That + * exception is not subject to undefined behavior. + * + * `String_charAt` throws a `StringIndexOutOfBoundsException`. + * + * The `Class_x` operations take a `jl.Class!` as lhs, i.e., a + * non-nullable `jl.Class`. `Class_isAssignableFrom` also takes a + * `jl.Class!` as rhs. + * + * - `Class_isInstance` and `Class_isAssignableFrom` are pure. + * - `Class_cast` throws a CCE if its second argument is non-null and + * not an instance of the class represented by its first argument. + * - `Class_newArray` throws a `NegativeArraySizeException` if its second + * argument is negative and an `IllegalArgumentException` if its first + * argument is `classOf[Unit]`. + * + * Otherwise, binary operations preserve pureness. + */ sealed case class BinaryOp(op: BinaryOp.Code, lhs: Tree, rhs: Tree)( implicit val pos: Position) extends Tree { @@ -426,12 +662,25 @@ object Trees { final val Double_> = 56 final val Double_>= = 57 + // New in 1.11 + final val String_charAt = 58 + + // New in 1.17 + final val Class_isInstance = 59 + final val Class_isAssignableFrom = 60 + final val Class_cast = 61 + final val Class_newArray = 62 + + def isClassOp(op: Code): Boolean = + op >= Class_isInstance && op <= Class_newArray + def resultTypeOf(op: Code): Type = (op: @switch) match { case === | !== | Boolean_== | Boolean_!= | Boolean_| | Boolean_& | Int_== | Int_!= | Int_< | Int_<= | Int_> | Int_>= | Long_== | Long_!= | Long_< | Long_<= | Long_> | Long_>= | - Double_== | Double_!= | Double_< | Double_<= | Double_> | Double_>= => + Double_== | Double_!= | Double_< | Double_<= | Double_> | Double_>= | + Class_isInstance | Class_isAssignableFrom => BooleanType case String_+ => StringType @@ -445,24 +694,23 @@ object Trees { FloatType case Double_+ | Double_- | Double_* | Double_/ | Double_% => DoubleType + case String_charAt => + CharType + case Class_cast => + AnyType + case Class_newArray => + AnyNotNullType } } - sealed case class NewArray(typeRef: ArrayTypeRef, lengths: List[Tree])( + sealed case class NewArray(typeRef: ArrayTypeRef, length: Tree)( implicit val pos: Position) extends Tree { - require(lengths.nonEmpty && lengths.size <= typeRef.dimensions) - - val tpe = ArrayType(typeRef) + val tpe = ArrayType(typeRef, nullable = false) } sealed case class ArrayValue(typeRef: ArrayTypeRef, elems: List[Tree])( implicit val pos: Position) extends Tree { - val tpe = ArrayType(typeRef) - } - - sealed case class ArrayLength(array: Tree)(implicit val pos: Position) - extends Tree { - val tpe = IntType + val tpe = ArrayType(typeRef, nullable = false) } sealed case class ArraySelect(array: Tree, index: Tree)(val tpe: Type)( @@ -471,7 +719,7 @@ object Trees { sealed case class RecordValue(tpe: RecordType, elems: List[Tree])( implicit val pos: Position) extends Tree - sealed case class RecordSelect(record: Tree, field: FieldIdent)( + sealed case class RecordSelect(record: Tree, field: SimpleFieldIdent)( val tpe: Type)( implicit val pos: Position) extends AssignLhs @@ -486,21 +734,6 @@ object Trees { implicit val pos: Position) extends Tree - sealed case class GetClass(expr: Tree)(implicit val pos: Position) - extends Tree { - val tpe = ClassType(ClassClass) - } - - sealed case class Clone(expr: Tree)(implicit val pos: Position) - extends Tree { - val tpe: Type = expr.tpe // this is OK because our type system does not have singleton types - } - - sealed case class IdentityHashCode(expr: Tree)(implicit val pos: Position) - extends Tree { - val tpe = IntType - } - // JavaScript expressions sealed case class JSNew(ctor: Tree, args: List[TreeOrJSSpread])( @@ -508,8 +741,7 @@ object Trees { val tpe = AnyType } - sealed case class JSPrivateSelect(qualifier: Tree, className: ClassName, - field: FieldIdent)( + sealed case class JSPrivateSelect(qualifier: Tree, field: FieldIdent)( implicit val pos: Position) extends AssignLhs { val tpe = AnyType } @@ -654,7 +886,7 @@ object Trees { */ sealed case class JSSuperConstructorCall(args: List[TreeOrJSSpread])( implicit val pos: Position) extends Tree { - val tpe = NoType + val tpe = VoidType } /** JavaScript dynamic import of the form `import(arg)`. @@ -747,14 +979,10 @@ object Trees { implicit val pos: Position) extends Tree { - val tpe = NoType // cannot be in expression position + val tpe = VoidType } - /** Unary operation (always preserves pureness). - * - * Operations which do not preserve pureness are not allowed in this tree. - * These are notably ++ and -- - */ + /** JavaScript unary operation. */ sealed case class JSUnaryOp(op: JSUnaryOp.Code, lhs: Tree)( implicit val pos: Position) extends Tree { val tpe = JSUnaryOp.resultTypeOf(op) @@ -775,11 +1003,7 @@ object Trees { AnyType } - /** Binary operation (always preserves pureness). - * - * Operations which do not preserve pureness are not allowed in this tree. - * These are notably +=, -=, *=, /= and %= - */ + /** JavaScript binary operation. */ sealed case class JSBinaryOp(op: JSBinaryOp.Code, lhs: Tree, rhs: Tree)( implicit val pos: Position) extends Tree { val tpe = JSBinaryOp.resultTypeOf(op) @@ -816,6 +1040,9 @@ object Trees { final val in = 20 final val instanceof = 21 + // New in 1.12 + final val ** = 22 + def resultTypeOf(op: Code): Type = op match { case === | !== => /* We assume that ECMAScript will never pervert `===` and `!==` to the @@ -831,12 +1058,12 @@ object Trees { sealed case class JSArrayConstr(items: List[TreeOrJSSpread])( implicit val pos: Position) extends Tree { - val tpe = AnyType + val tpe = AnyNotNullType } sealed case class JSObjectConstr(fields: List[(Tree, Tree)])( implicit val pos: Position) extends Tree { - val tpe = AnyType + val tpe = AnyNotNullType } sealed case class JSGlobalRef(name: String)( @@ -861,6 +1088,11 @@ object Trees { * - The identifier `arguments`, because any attempt to refer to it always * refers to the magical `arguments` pseudo-array from the enclosing * function, rather than a global variable. + * + * This set does *not* contain `await`, although it is a reserved word + * within ES modules. It used to be allowed before 1.11.0, and even + * browsers do not seem to reject it. For compatibility reasons, we only + * warn about it at compile time, but the IR allows it. */ final val ReservedJSIdentifierNames: Set[String] = Set( "arguments", "break", "case", "catch", "class", "const", "continue", @@ -878,8 +1110,15 @@ object Trees { * [[isJSIdentifierName]]) *and* it is not reserved (see * [[ReservedJSIdentifierNames]]). */ - def isValidJSGlobalRefName(name: String): Boolean = - isJSIdentifierName(name) && !ReservedJSIdentifierNames.contains(name) + def isValidJSGlobalRefName(name: String): Boolean = { + (isJSIdentifierName(name) && !ReservedJSIdentifierNames.contains(name)) || + name == FileLevelThis + } + + /** The JavaScript value that is an alias to `this` + * at the top-level of the generated file. + */ + final val FileLevelThis = "this" } sealed case class JSTypeOfGlobalRef(globalRef: JSGlobalRef)( @@ -887,13 +1126,13 @@ object Trees { val tpe = AnyType } - sealed case class JSLinkingInfo()(implicit val pos: Position) extends Tree { - val tpe = AnyType - } - // Literals - /** Marker for literals. Literals are always pure. */ + /** Marker for literals. Literals are always pure. + * + * All `Literal`s can be compared for equality. The equality does not take + * the `pos` into account. + */ sealed trait Literal extends Tree /** Marker for literals that can be used in a [[Match]] case. @@ -949,11 +1188,25 @@ object Trees { sealed case class FloatLiteral(value: Float)( implicit val pos: Position) extends Literal { val tpe = FloatType + + override def equals(that: Any): Boolean = that match { + case that: FloatLiteral => java.lang.Float.compare(this.value, that.value) == 0 + case _ => false + } + + override def hashCode(): Int = java.lang.Float.hashCode(value) } sealed case class DoubleLiteral(value: Double)( implicit val pos: Position) extends Literal { val tpe = DoubleType + + override def equals(that: Any): Boolean = that match { + case that: DoubleLiteral => java.lang.Double.compare(this.value, that.value) == 0 + case _ => false + } + + override def hashCode(): Int = java.lang.Double.hashCode(value) } sealed case class StringLiteral(value: String)( @@ -963,29 +1216,65 @@ object Trees { sealed case class ClassOf(typeRef: TypeRef)( implicit val pos: Position) extends Literal { - val tpe = ClassType(ClassClass) + val tpe = ClassType(ClassClass, nullable = false) + } + + sealed case class LinkTimeProperty(name: String)(val tpe: Type)( + implicit val pos: Position) + extends Tree + + object LinkTimeProperty { + final val ProductionMode = "core/productionMode" + final val ESVersion = "core/esVersion" + final val UseECMAScript2015Semantics = "core/useECMAScript2015Semantics" + final val IsWebAssembly = "core/isWebAssembly" + final val LinkerVersion = "core/linkerVersion" } // Atomic expressions - sealed case class VarRef(ident: LocalIdent)(val tpe: Type)( + sealed case class VarRef(name: LocalName)(val tpe: Type)( implicit val pos: Position) extends AssignLhs - sealed case class This()(val tpe: Type)(implicit val pos: Position) - extends Tree + /** Convenience constructor and extractor for `VarRef`s representing `this` bindings. */ + object This { + def apply()(tpe: Type)(implicit pos: Position): VarRef = + VarRef(LocalName.This)(tpe) + + def unapply(tree: VarRef): Boolean = + tree.name.isThis + } /** Closure with explicit captures. * - * @param arrow - * If `true`, the closure is an Arrow Function (`=>`), which does not have - * an `this` parameter, and cannot be constructed (called with `new`). - * If `false`, it is a regular Function (`function`). + * If `flags.typed` is `true`, this is a typed closure with a `ClosureType`. + * In that case, `flags.arrow` must be `true`, and `restParam` must be + * `None`. Typed closures cannot be passed to JavaScript code. This is + * enforced at the type system level, since `ClosureType`s are not subtypes + * of `AnyType`. The only meaningful operation one can perform on a typed + * closure is to call it using `ApplyTypedClosure`. + * + * If `flags.typed` is `false`, this is a JavaScript closure with type + * `AnyNotNullType`. In that case, the `ptpe` or all `params` and + * `resultType` must all be `AnyType`. + * + * If `flags.arrow` is `true`, the closure is an Arrow Function (`=>`), + * which does not have a `this` parameter, and cannot be constructed (called + * with `new`). If `false`, it is a regular Function (`function`), which + * does have a `this` parameter of type `AnyType`. Typed closures are always + * Arrow functions, since they do not have a `this` parameter. + * + * If `flags.async` is `true`, it is an `async` closure. Async closures + * return a `Promise` of their body, and can contain [[JSAwait]] nodes. + * `flags.typed` and `flags.async` cannot both be `true`. */ - sealed case class Closure(arrow: Boolean, captureParams: List[ParamDef], - params: List[ParamDef], restParam: Option[ParamDef], body: Tree, - captureValues: List[Tree])( + sealed case class Closure(flags: ClosureFlags, captureParams: List[ParamDef], + params: List[ParamDef], restParam: Option[ParamDef], resultType: Type, + body: Tree, captureValues: List[Tree])( implicit val pos: Position) extends Tree { - val tpe = AnyType + val tpe: Type = + if (flags.typed) ClosureType(params.map(_.ptpe), resultType, nullable = false) + else AnyNotNullType } /** Creates a JavaScript class value. @@ -1038,7 +1327,7 @@ object Trees { * * Implementations should transform contained trees and potentially adjust the result. */ - def transform(transformer: Transformers.Transformer, isStat: Boolean)( + def transform(transformer: Transformers.Transformer)( implicit pos: Position): Tree /** Prints the IR representation of this transient node. @@ -1092,7 +1381,11 @@ object Trees { */ val jsSuperClass: Option[Tree], val jsNativeLoadSpec: Option[JSNativeLoadSpec], - val memberDefs: List[MemberDef], + val fields: List[AnyFieldDef], + val methods: List[MethodDef], + val jsConstructor: Option[JSConstructorDef], + val jsMethodProps: List[JSMethodPropDef], + val jsNativeMembers: List[JSNativeMemberDef], val topLevelExportDefs: List[TopLevelExportDef] )( val optimizerHints: OptimizerHints @@ -1110,13 +1403,17 @@ object Trees { interfaces: List[ClassIdent], jsSuperClass: Option[Tree], jsNativeLoadSpec: Option[JSNativeLoadSpec], - memberDefs: List[MemberDef], + fields: List[AnyFieldDef], + methods: List[MethodDef], + jsConstructor: Option[JSConstructorDef], + jsMethodProps: List[JSMethodPropDef], + jsNativeMembers: List[JSNativeMemberDef], topLevelExportDefs: List[TopLevelExportDef])( optimizerHints: OptimizerHints)( implicit pos: Position): ClassDef = { new ClassDef(name, originalName, kind, jsClassCaptures, superClass, - interfaces, jsSuperClass, jsNativeLoadSpec, memberDefs, - topLevelExportDefs)( + interfaces, jsSuperClass, jsNativeLoadSpec, fields, methods, + jsConstructor, jsMethodProps, jsNativeMembers, topLevelExportDefs)( optimizerHints) } } @@ -1125,12 +1422,17 @@ object Trees { /** Any member of a `ClassDef`. * - * Partitioned into `AnyFieldDef`, `MethodDef` and `JSMethodPropDef`. + * Partitioned into `AnyFieldDef`, `MethodDef`, `JSConstructorDef` and + * `JSMethodPropDef`. */ sealed abstract class MemberDef extends IRNode { val flags: MemberFlags } + sealed trait VersionedMemberDef extends MemberDef { + val version: Version + } + sealed abstract class AnyFieldDef extends MemberDef { // val name: Ident | Tree val ftpe: Type @@ -1146,50 +1448,42 @@ object Trees { sealed case class MethodDef(flags: MemberFlags, name: MethodIdent, originalName: OriginalName, args: List[ParamDef], resultType: Type, body: Option[Tree])( - val optimizerHints: OptimizerHints, val hash: Option[TreeHash])( - implicit val pos: Position) extends MemberDef { - - require(!flags.isMutable, "nonsensical mutable MethodDef") + val optimizerHints: OptimizerHints, val version: Version)( + implicit val pos: Position) extends VersionedMemberDef { + def methodName: MethodName = name.name + } - require(!name.name.isReflectiveProxy || flags.namespace == MemberNamespace.Public, - "reflective proxies must be in the public (non-static) namespace") - require(name.name.isConstructor == (flags.namespace == MemberNamespace.Constructor), - "a member can have a constructor name iff it is in the constructor namespace") - require((name.name.isStaticInitializer || name.name.isClassInitializer) == - (flags.namespace == MemberNamespace.StaticConstructor), - "a member can have a static constructor name iff it is in the static constructor namespace") + sealed case class JSConstructorDef(flags: MemberFlags, + args: List[ParamDef], restParam: Option[ParamDef], body: JSConstructorBody)( + val optimizerHints: OptimizerHints, val version: Version)( + implicit val pos: Position) + extends VersionedMemberDef - def methodName: MethodName = name.name + sealed case class JSConstructorBody( + beforeSuper: List[Tree], superCall: JSSuperConstructorCall, afterSuper: List[Tree])( + implicit val pos: Position) + extends IRNode { + val allStats: List[Tree] = beforeSuper ::: superCall :: afterSuper } - sealed abstract class JSMethodPropDef extends MemberDef + sealed abstract class JSMethodPropDef extends VersionedMemberDef sealed case class JSMethodDef(flags: MemberFlags, name: Tree, args: List[ParamDef], restParam: Option[ParamDef], body: Tree)( - val optimizerHints: OptimizerHints, val hash: Option[TreeHash])( + val optimizerHints: OptimizerHints, val version: Version)( implicit val pos: Position) - extends JSMethodPropDef { - - require(!flags.isMutable, "nonsensical mutable MethodDef") - } + extends JSMethodPropDef sealed case class JSPropertyDef(flags: MemberFlags, name: Tree, getterBody: Option[Tree], setterArgAndBody: Option[(ParamDef, Tree)])( + val version: Version)( implicit val pos: Position) - extends JSMethodPropDef { - - require(!flags.isMutable, "nonsensical mutable PropertyDef") - } + extends JSMethodPropDef sealed case class JSNativeMemberDef(flags: MemberFlags, name: MethodIdent, jsNativeLoadSpec: JSNativeLoadSpec)( implicit val pos: Position) - extends MemberDef { - - require(!flags.isMutable, "nonsensical mutable JSNativeMemberDef") - require(flags.namespace == MemberNamespace.PublicStatic, - "JSNativeMemberDef must have the namespace PublicStatic") - } + extends MemberDef // Top-level export defs @@ -1281,6 +1575,10 @@ object Trees { def isConstructor: Boolean = (bits & ConstructorBit) != 0 + def inline: Boolean = (bits & InlineBit) != 0 + + def noinline: Boolean = (bits & NoinlineBit) != 0 + def withPrivate(value: Boolean): ApplyFlags = if (value) new ApplyFlags((bits & ~ConstructorBit) | PrivateBit) else new ApplyFlags(bits & ~PrivateBit) @@ -1288,6 +1586,14 @@ object Trees { def withConstructor(value: Boolean): ApplyFlags = if (value) new ApplyFlags((bits & ~PrivateBit) | ConstructorBit) else new ApplyFlags(bits & ~ConstructorBit) + + def withInline(value: Boolean): ApplyFlags = + if (value) new ApplyFlags(bits | InlineBit) + else new ApplyFlags(bits & ~InlineBit) + + def withNoinline(value: Boolean): ApplyFlags = + if (value) new ApplyFlags(bits | NoinlineBit) + else new ApplyFlags(bits & ~NoinlineBit) } object ApplyFlags { @@ -1297,6 +1603,12 @@ object Trees { private final val ConstructorShift = 1 private final val ConstructorBit = 1 << ConstructorShift + private final val InlineShift = 2 + private final val InlineBit = 1 << InlineShift + + private final val NoinlineShift = 3 + private final val NoinlineBit = 1 << NoinlineShift + final val empty: ApplyFlags = new ApplyFlags(0) @@ -1307,6 +1619,60 @@ object Trees { flags.bits } + final class ClosureFlags private (private val bits: Int) extends AnyVal { + import ClosureFlags._ + + def arrow: Boolean = (bits & ArrowBit) != 0 + + def typed: Boolean = (bits & TypedBit) != 0 + + def async: Boolean = (bits & AsyncBit) != 0 + + def withArrow(arrow: Boolean): ClosureFlags = + if (arrow) new ClosureFlags(bits | ArrowBit) + else new ClosureFlags(bits & ~ArrowBit) + + def withTyped(typed: Boolean): ClosureFlags = + if (typed) new ClosureFlags(bits | TypedBit) + else new ClosureFlags(bits & ~TypedBit) + + def withAsync(async: Boolean): ClosureFlags = + if (async) new ClosureFlags(bits | AsyncBit) + else new ClosureFlags(bits & ~AsyncBit) + } + + object ClosureFlags { + /* The Arrow flag is assigned bit 0 for the serialized encoding to be + * directly compatible with the `arrow` parameter from IR < v1.19. + */ + private final val ArrowShift = 0 + private final val ArrowBit = 1 << ArrowShift + + private final val TypedShift = 1 + private final val TypedBit = 1 << TypedShift + + private final val AsyncShift = 2 + private final val AsyncBit = 1 << AsyncShift + + /** `function` closure base flags. */ + final val function: ClosureFlags = + new ClosureFlags(0) + + /** Arrow `=>` closure base flags. */ + final val arrow: ClosureFlags = + new ClosureFlags(ArrowBit) + + /** Base flags for a typed closure. */ + final val typed: ClosureFlags = + new ClosureFlags(ArrowBit | TypedBit) + + private[ir] def fromBits(bits: Byte): ClosureFlags = + new ClosureFlags(bits & 0xff) + + private[ir] def toBits(flags: ClosureFlags): Byte = + flags.bits.toByte + } + final class MemberNamespace private ( val ordinal: Int) // intentionally public extends AnyVal { @@ -1488,12 +1854,4 @@ object Trees { extends JSNativeLoadSpec } - - /** A hash of a tree (usually a MethodDef). - * - * Contains a SHA-1 hash. - */ - final class TreeHash(val hash: Array[Byte]) { - assert(hash.length == 20) - } } diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Types.scala b/ir/shared/src/main/scala/org/scalajs/ir/Types.scala index 4f4ead9da6..0fde4f7e37 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Types.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Types.scala @@ -12,8 +12,6 @@ package org.scalajs.ir -import scala.annotation.tailrec - import Names._ import Trees._ @@ -36,44 +34,72 @@ object Types { printer.print(this) writer.toString() } + + /** Is `null` an admissible value of this type? */ + def isNullable: Boolean = this match { + case AnyType | NullType => true + case ClassType(_, nullable) => nullable + case ArrayType(_, nullable) => nullable + case ClosureType(_, _, nullable) => nullable + case _ => false + } + + /** A type that accepts the same values as this type except `null`, unless + * this type is `VoidType`. + * + * If `this` is `VoidType`, returns this type. + * + * For all other types `tpe`, `tpe.toNonNullable.isNullable` is `false`. + */ + def toNonNullable: Type } - sealed abstract class PrimType extends Type - - sealed abstract class PrimTypeWithRef extends PrimType { - def primRef: PrimRef = this match { - case NoType => VoidRef - case BooleanType => BooleanRef - case CharType => CharRef - case ByteType => ByteRef - case ShortType => ShortRef - case IntType => IntRef - case LongType => LongRef - case FloatType => FloatRef - case DoubleType => DoubleRef - case NullType => NullRef - case NothingType => NothingRef + sealed abstract class PrimType extends Type { + final def toNonNullable: PrimType = this match { + case NullType => NothingType + case _ => this } } - /** Any type (the top type of this type system). - * A variable of this type can contain any value, including `undefined` - * and `null` and any JS value. This type supports a very limited set - * of Scala operations, the ones common to all values. Basically only - * reference equality tests and instance tests. It also supports all - * JavaScript operations, since all Scala objects are also genuine - * JavaScript objects. + /* Each PrimTypeWithRef creates its corresponding `PrimRef`. Therefore, it + * takes the parameters that need to be passed to the `PrimRef` constructor. + * This little dance ensures proper initialization safety between + * `PrimTypeWithRef`s and `PrimRef`s. + */ + sealed abstract class PrimTypeWithRef(primRefCharCode: Char, primRefDisplayName: String) + extends PrimType { + val primRef: PrimRef = new PrimRef(this, primRefCharCode, primRefDisplayName) + } + + /** Any type. + * + * This is the supertype of all value types that can be passed to JavaScript + * code. Record types are the canonical counter-example: they are not + * subtypes of `any` because their values cannot be given to JavaScript. + * + * This type supports a very limited set of Scala operations, the ones + * common to all values. Basically only reference equality tests and + * instance tests. It also supports all JavaScript operations, since all + * Scala objects are also genuine JavaScript values. + * * The type java.lang.Object in the back-end maps to [[AnyType]] because it * can hold JS values (not only instances of Scala.js classes). */ - case object AnyType extends Type + case object AnyType extends Type { + def toNonNullable: AnyNotNullType.type = AnyNotNullType + } + + /** Any type except `null`. */ + case object AnyNotNullType extends Type { + def toNonNullable: this.type = this + } // Can't link to Nothing - #1969 /** Nothing type (the bottom type of this type system). * Expressions from which one can never come back are typed as `Nothing`. * For example, `throw` and `return`. */ - case object NothingType extends PrimTypeWithRef + case object NothingType extends PrimTypeWithRef('E', "nothing") /** The type of `undefined`. */ case object UndefType extends PrimType @@ -81,42 +107,42 @@ object Types { /** Boolean type. * It does not accept `null` nor `undefined`. */ - case object BooleanType extends PrimTypeWithRef + case object BooleanType extends PrimTypeWithRef('Z', "boolean") /** `Char` type, a 16-bit UTF-16 code unit. * It does not accept `null` nor `undefined`. */ - case object CharType extends PrimTypeWithRef + case object CharType extends PrimTypeWithRef('C', "char") /** 8-bit signed integer type. * It does not accept `null` nor `undefined`. */ - case object ByteType extends PrimTypeWithRef + case object ByteType extends PrimTypeWithRef('B', "byte") /** 16-bit signed integer type. * It does not accept `null` nor `undefined`. */ - case object ShortType extends PrimTypeWithRef + case object ShortType extends PrimTypeWithRef('S', "short") /** 32-bit signed integer type. * It does not accept `null` nor `undefined`. */ - case object IntType extends PrimTypeWithRef + case object IntType extends PrimTypeWithRef('I', "int") /** 64-bit signed integer type. * It does not accept `null` nor `undefined`. */ - case object LongType extends PrimTypeWithRef + case object LongType extends PrimTypeWithRef('J', "long") /** Float type (32-bit). * It does not accept `null` nor `undefined`. */ - case object FloatType extends PrimTypeWithRef + case object FloatType extends PrimTypeWithRef('F', "float") /** Double type (64-bit). * It does not accept `null` nor `undefined`. */ - case object DoubleType extends PrimTypeWithRef + case object DoubleType extends PrimTypeWithRef('D', "double") /** String type. * It does not accept `null` nor `undefined`. @@ -127,33 +153,92 @@ object Types { * It does not accept `undefined`. * The null type is a subtype of all class types and array types. */ - case object NullType extends PrimTypeWithRef + case object NullType extends PrimTypeWithRef('N', "null") /** Class (or interface) type. */ - final case class ClassType(className: ClassName) extends Type + final case class ClassType(className: ClassName, nullable: Boolean) extends Type { + def toNullable: ClassType = ClassType(className, nullable = true) - /** Array type. */ - final case class ArrayType(arrayTypeRef: ArrayTypeRef) extends Type + def toNonNullable: ClassType = ClassType(className, nullable = false) + } + + /** Array type. + * + * Although the array type itself may be non-nullable, the *elements* of an + * array are always nullable for non-primitive types. This is unavoidable, + * since arrays can be created with their elements initialized with the zero + * of the element type. + */ + final case class ArrayType(arrayTypeRef: ArrayTypeRef, nullable: Boolean) extends Type { + def toNullable: ArrayType = ArrayType(arrayTypeRef, nullable = true) + + def toNonNullable: ArrayType = ArrayType(arrayTypeRef, nullable = false) + } + + /** Closure type. + * + * This is the type of a typed closure. Parameters and result are + * statically typed according to the `closureTypeRef` components. + * + * Closure types may be nullable. `Null()` is a valid value of a nullable + * closure type. This is unfortunately required to have default values of + * closure types, which in turn is required to be used as the type of a + * field. + * + * Closure types are non-variant in both parameter and result types. + * + * Closure types are *not* subtypes of `AnyType`. That statically prevents + * them from going into JavaScript code or in any other universal context. + * They do not support type tests nor casts. + * + * The following subtyping relationships hold for any closure type `CT`: + * {{{ + * nothing <: CT <: void + * }}} + * For a nullable closure type `CT`, additionally the following subtyping + * relationship holds: + * {{{ + * null <: CT + * }}} + */ + final case class ClosureType(paramTypes: List[Type], resultType: Type, + nullable: Boolean) extends Type { + def toNonNullable: ClosureType = + ClosureType(paramTypes, resultType, nullable = false) + } /** Record type. + * * Used by the optimizer to inline classes as records with multiple fields. * They are desugared as several local variables by JSDesugaring. * Record types cannot cross method boundaries, so they cannot appear as * the type of fields or parameters, nor as result types of methods. * The compiler itself never generates record types. + * + * Record types currently do not feature any form of subtyping. For R1 to be + * a subtype of R2, it must have the same fields, in the same order, with + * equivalent types. + * + * Record types are not subtypes of `any`. As such, they can never be passed + * to JavaScript. */ final case class RecordType(fields: List[RecordType.Field]) extends Type { - def findField(name: FieldName): RecordType.Field = + def findField(name: SimpleFieldName): RecordType.Field = fields.find(_.name == name).get + + def toNonNullable: this.type = this } object RecordType { - final case class Field(name: FieldName, originalName: OriginalName, + final case class Field(name: SimpleFieldName, originalName: OriginalName, tpe: Type, mutable: Boolean) } - /** No type. */ - case object NoType extends PrimTypeWithRef + /** Void type, the top of type of our type system. */ + case object VoidType extends PrimTypeWithRef('V', "void") + + @deprecated("Use VoidType instead", since = "1.18.0") + lazy val NoType: VoidType.type = VoidType /** Type reference (allowed for classOf[], is/asInstanceOf[]). * @@ -170,7 +255,36 @@ object Types { * type refs that are used in method signatures, and which therefore dictate * JVM/IR overloading. */ - sealed abstract class TypeRef { + sealed abstract class TypeRef extends Comparable[TypeRef] { + final def compareTo(that: TypeRef): Int = this match { + case thiz: PrimRef => + that match { + case that: PrimRef => Character.compare(thiz.charCode, that.charCode) + case _ => -1 + } + case thiz: ClassRef => + that match { + case that: ClassRef => thiz.className.compareTo(that.className) + case _: PrimRef => 1 + case _ => -1 + } + case thiz: ArrayTypeRef => + that match { + case that: ArrayTypeRef => + if (thiz.dimensions != that.dimensions) thiz.dimensions - that.dimensions + else thiz.base.compareTo(that.base) + case _: TransientTypeRef => + -1 + case _ => + 1 + } + case thiz: TransientTypeRef => + that match { + case that: TransientTypeRef => thiz.name.compareTo(that.name) + case _ => 1 + } + } + def show(): String = { val writer = new java.io.StringWriter val printer = new Printers.IRTreePrinter(writer) @@ -183,8 +297,12 @@ object Types { sealed abstract class NonArrayTypeRef extends TypeRef + // scalastyle:off equals.hash.code + // PrimRef uses reference equality, but has a stable hashCode() method + /** Primitive type reference. */ - final case class PrimRef private[ir] (tpe: PrimTypeWithRef) + final class PrimRef private[Types] (val tpe: PrimTypeWithRef, + charCodeInit: Char, displayNameInit: String) // "Init" variants so we can have good Scaladoc on the val's extends NonArrayTypeRef { /** The display name of this primitive type. @@ -196,19 +314,7 @@ object Types { * For `NullType` and `NothingType`, the names are `"null"` and * `"nothing"`, respectively. */ - val displayName: String = tpe match { - case NoType => "void" - case BooleanType => "boolean" - case CharType => "char" - case ByteType => "byte" - case ShortType => "short" - case IntType => "int" - case LongType => "long" - case FloatType => "float" - case DoubleType => "double" - case NullType => "null" - case NothingType => "nothing" - } + val displayName: String = displayNameInit /** The char code of this primitive type. * @@ -220,32 +326,30 @@ object Types { * For `NullType` and `NothingType`, the char codes are `'N'` and `'E'`, * respectively. */ - val charCode: Char = tpe match { - case NoType => 'V' - case BooleanType => 'Z' - case CharType => 'C' - case ByteType => 'B' - case ShortType => 'S' - case IntType => 'I' - case LongType => 'J' - case FloatType => 'F' - case DoubleType => 'D' - case NullType => 'N' - case NothingType => 'E' - } + val charCode: Char = charCodeInit + + // Stable hash code, corresponding to reference equality + override def hashCode(): Int = charCode.## } - final val VoidRef = PrimRef(NoType) - final val BooleanRef = PrimRef(BooleanType) - final val CharRef = PrimRef(CharType) - final val ByteRef = PrimRef(ByteType) - final val ShortRef = PrimRef(ShortType) - final val IntRef = PrimRef(IntType) - final val LongRef = PrimRef(LongType) - final val FloatRef = PrimRef(FloatType) - final val DoubleRef = PrimRef(DoubleType) - final val NullRef = PrimRef(NullType) - final val NothingRef = PrimRef(NothingType) + // scalastyle:on equals.hash.code + + object PrimRef { + def unapply(typeRef: PrimRef): Some[PrimTypeWithRef] = + Some(typeRef.tpe) + } + + final val VoidRef = VoidType.primRef + final val BooleanRef = BooleanType.primRef + final val CharRef = CharType.primRef + final val ByteRef = ByteType.primRef + final val ShortRef = ShortType.primRef + final val IntRef = IntType.primRef + final val LongRef = LongType.primRef + final val FloatRef = FloatType.primRef + final val DoubleRef = DoubleType.primRef + final val NullRef = NullType.primRef + final val NothingRef = NothingType.primRef /** Class (or interface) type. */ final case class ClassRef(className: ClassName) extends NonArrayTypeRef { @@ -261,11 +365,28 @@ object Types { object ArrayTypeRef { def of(innerType: TypeRef): ArrayTypeRef = innerType match { - case innerType: NonArrayTypeRef => ArrayTypeRef(innerType, 1) - case ArrayTypeRef(base, dim) => ArrayTypeRef(base, dim + 1) + case innerType: NonArrayTypeRef => ArrayTypeRef(innerType, 1) + case ArrayTypeRef(base, dim) => ArrayTypeRef(base, dim + 1) + case innerType: TransientTypeRef => throw new IllegalArgumentException(innerType.toString()) } } + /** Transient TypeRef to store any type as a method parameter or result type. + * + * `TransientTypeRef` cannot be serialized. It is only used in the linker to + * support some of its desugarings and/or optimizations. + * + * `TransientTypeRef`s cannot be used for methods in the `Public` namespace. + * + * The `name` is used for equality, hashing, and sorting. It is assumed that + * all occurrences of a `TransientTypeRef` with the same `name` associated + * to an enclosing method namespace (enclosing class, member namespace and + * simple method name) have the same `tpe`. + */ + final case class TransientTypeRef(name: LabelName)(val tpe: Type) extends TypeRef { + def displayName: String = name.nameString + } + /** Generates a literal zero of the given type. */ def zeroOf(tpe: Type)(implicit pos: Position): Tree = tpe match { case BooleanType => BooleanLiteral(false) @@ -279,12 +400,15 @@ object Types { case StringType => StringLiteral("") case UndefType => Undefined() - case NullType | AnyType | _:ClassType | _:ArrayType => Null() + case NullType | AnyType | ClassType(_, true) | ArrayType(_, true) | + ClosureType(_, _, true) => + Null() case tpe: RecordType => RecordValue(tpe, tpe.fields.map(f => zeroOf(f.tpe))) - case NothingType | NoType => + case NothingType | VoidType | ClassType(_, false) | ArrayType(_, false) | + ClosureType(_, _, false) | AnyNotNullType => throw new IllegalArgumentException(s"cannot generate a zero for $tpe") } @@ -294,66 +418,77 @@ object Types { */ def isSubtype(lhs: Type, rhs: Type)( isSubclass: (ClassName, ClassName) => Boolean): Boolean = { + + /* It is fine to use WellKnownNames here because nothing in `Names` nor + * `Types` calls `isSubtype`. So this code path is not reached during their + * initialization. + */ + import WellKnownNames.{AncestorsOfPseudoArrayClass, ObjectClass, PrimTypeToBoxedClass} + + def isSubnullable(lhs: Boolean, rhs: Boolean): Boolean = + rhs || !lhs + (lhs == rhs) || ((lhs, rhs) match { - case (_, NoType) => true - case (NoType, _) => false - case (_, AnyType) => true case (NothingType, _) => true + case (_, VoidType) => true + case (VoidType, _) => false + + case (NullType, _) => rhs.isNullable + + case (ClosureType(lhsParamTypes, lhsResultType, lhsNullable), + ClosureType(rhsParamTypes, rhsResultType, rhsNullable)) => + isSubnullable(lhsNullable, rhsNullable) && + lhsParamTypes == rhsParamTypes && + lhsResultType == rhsResultType + + case (_: ClosureType, _) => false + case (_, _: ClosureType) => false - case (ClassType(lhsClass), ClassType(rhsClass)) => + case (_: RecordType, _) => false + case (_, _: RecordType) => false + + case (_, AnyType) => true + case (_, AnyNotNullType) => !lhs.isNullable + + case (ClassType(lhsClass, lhsNullable), ClassType(rhsClass, rhsNullable)) => + isSubnullable(lhsNullable, rhsNullable) && isSubclass(lhsClass, rhsClass) + + case (primType: PrimType, ClassType(rhsClass, _)) => + val lhsClass = PrimTypeToBoxedClass.getOrElse(primType, { + throw new AssertionError(s"unreachable case for isSubtype($lhs, $rhs)") + }) isSubclass(lhsClass, rhsClass) - case (NullType, ClassType(_)) => true - case (NullType, ArrayType(_)) => true - - case (UndefType, ClassType(className)) => - isSubclass(BoxedUnitClass, className) - case (BooleanType, ClassType(className)) => - isSubclass(BoxedBooleanClass, className) - case (CharType, ClassType(className)) => - isSubclass(BoxedCharacterClass, className) - case (ByteType, ClassType(className)) => - isSubclass(BoxedByteClass, className) - case (ShortType, ClassType(className)) => - isSubclass(BoxedShortClass, className) - case (IntType, ClassType(className)) => - isSubclass(BoxedIntegerClass, className) - case (LongType, ClassType(className)) => - isSubclass(BoxedLongClass, className) - case (FloatType, ClassType(className)) => - isSubclass(BoxedFloatClass, className) - case (DoubleType, ClassType(className)) => - isSubclass(BoxedDoubleClass, className) - case (StringType, ClassType(className)) => - isSubclass(BoxedStringClass, className) - - case (ArrayType(ArrayTypeRef(lhsBase, lhsDims)), - ArrayType(ArrayTypeRef(rhsBase, rhsDims))) => - if (lhsDims < rhsDims) { - false // because Array[A] rhsDims) { - rhsBase match { - case ClassRef(ObjectClass) => - true // because Array[Array[A]] <: Array[Object] - case _ => - false - } - } else { // lhsDims == rhsDims - // lhsBase must be <: rhsBase - (lhsBase, rhsBase) match { - case (ClassRef(lhsBaseName), ClassRef(rhsBaseName)) => - /* All things must be considered subclasses of Object for this - * purpose, even JS types and interfaces, which do not have - * Object in their ancestors. - */ - rhsBaseName == ObjectClass || isSubclass(lhsBaseName, rhsBaseName) - case _ => - lhsBase eq rhsBase + case (ArrayType(ArrayTypeRef(lhsBase, lhsDims), lhsNullable), + ArrayType(ArrayTypeRef(rhsBase, rhsDims), rhsNullable)) => + isSubnullable(lhsNullable, rhsNullable) && { + if (lhsDims < rhsDims) { + false // because Array[A] rhsDims) { + rhsBase match { + case ClassRef(ObjectClass) => + true // because Array[Array[A]] <: Array[Object] + case _ => + false + } + } else { // lhsDims == rhsDims + // lhsBase must be <: rhsBase + (lhsBase, rhsBase) match { + case (ClassRef(lhsBaseName), ClassRef(rhsBaseName)) => + /* All things must be considered subclasses of Object for this + * purpose, even JS types and interfaces, which do not have + * Object in their ancestors. + */ + rhsBaseName == ObjectClass || isSubclass(lhsBaseName, rhsBaseName) + case _ => + lhsBase eq rhsBase + } } } - case (ArrayType(_), ClassType(className)) => + case (ArrayType(_, lhsNullable), ClassType(className, rhsNullable)) => + isSubnullable(lhsNullable, rhsNullable) && AncestorsOfPseudoArrayClass.contains(className) case _ => diff --git a/ir/shared/src/main/scala/org/scalajs/ir/UTF8String.scala b/ir/shared/src/main/scala/org/scalajs/ir/UTF8String.scala index 00eb0c2f11..8e4fd87a8f 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/UTF8String.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/UTF8String.scala @@ -12,7 +12,7 @@ package org.scalajs.ir -import java.nio.CharBuffer +import java.nio.{ByteBuffer, CharBuffer} import java.nio.charset.CharacterCodingException import java.nio.charset.CodingErrorAction import java.nio.charset.StandardCharsets.UTF_8 @@ -48,6 +48,9 @@ final class UTF8String private (private[ir] val bytes: Array[Byte]) System.arraycopy(that.bytes, 0, result, thisLen, thatLen) new UTF8String(result) } + + def writeTo(buffer: ByteBuffer): Unit = + buffer.put(bytes) } object UTF8String { diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Utils.scala b/ir/shared/src/main/scala/org/scalajs/ir/Utils.scala index 807ecd56bd..3e54a88091 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/Utils.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/Utils.scala @@ -14,10 +14,6 @@ package org.scalajs.ir private[ir] object Utils { - /* !!! BEGIN CODE VERY SIMILAR TO linker/.../javascript/Utils.scala and - * js-envs/.../JSUtils.scala - */ - private final val EscapeJSChars = "\\b\\t\\n\\v\\f\\r\\\"\\\\" private[ir] def printEscapeJS(str: String, out: java.io.Writer): Unit = { @@ -64,10 +60,6 @@ private[ir] object Utils { } } - /* !!! END CODE VERY SIMILAR TO linker/.../javascript/Utils.scala and - * js-envs/.../JSUtils.scala - */ - /** A ByteArrayOutput stream that allows to jump back to a given * position and complete some bytes. Methods must be called in the * following order only: diff --git a/ir/shared/src/main/scala/org/scalajs/ir/Version.scala b/ir/shared/src/main/scala/org/scalajs/ir/Version.scala new file mode 100644 index 0000000000..0228d63c86 --- /dev/null +++ b/ir/shared/src/main/scala/org/scalajs/ir/Version.scala @@ -0,0 +1,177 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.ir + +import java.util.Arrays +import java.io.OutputStream +import java.nio.ByteBuffer + +/** A version of a thing + * + * Versions are always optional, [[Version.Unversioned]] being the sentinel. + * + * The remaining versions come in two fundamentally different flavors: + * - Hashes: They are stable in serialized form, [[Serializers]] will write + * them to IR files. The only way to create these versions is via + * [[Hashers]]. + * - Non hashes: Not guaranteed to be stable / collision free across different + * programs. Never written to IR files. + */ +final class Version private (private val v: Array[Byte]) extends AnyVal { + import Version.Type + + /** Checks whether two versions are known to be the same. + * + * Returns false if either of the versions is [[Version.Unversioned]] + */ + def sameVersion(that: Version): Boolean = { + if (!this.isVersioned || !that.isVersioned) false + else Arrays.equals(this.v, that.v) + } + + private[ir] def isHash: Boolean = isVersioned && v(0) == Type.Hash + + private[ir] def writeHash(out: OutputStream): Unit = { + require(isHash) + out.write(v, 1, 20) + } + + @inline + private def isVersioned: Boolean = v != null + + // For debugging purposes + override def toString(): String = { + if (v == null) { + "Unversioned" + } else { + val typeByte = v(0) + val otherBytesStr = v.iterator.drop(1).map(b => "%02x".format(b & 0xff)).mkString + s"Version($typeByte, $otherBytesStr)" + } + } +} + +object Version { + private object Type { + val Hash: Byte = 0x00 + val Ephemeral: Byte = 0x02 + val Combined: Byte = 0x03 + } + + val Unversioned: Version = new Version(null) + + /** Create a non-hash version from the given bytes. + * + * Guaranteed to differ from: + * - all hash versions. + * - versions returned from [[combine]]. + * - versions with different bytes. + */ + def fromBytes(bytes: Array[Byte]): Version = + make(Type.Ephemeral, bytes) + + /** Create a non-hash version from a Byte. + * + * Strictly equivalent to (but potentially more efficient): + * {{{ + * fromBytes(Array[Byte](i)) + * }}} + */ + def fromByte(i: Byte): Version = + new Version(Array(Type.Ephemeral, i)) + + /** Create a non-hash version from an Int. + * + * Strictly equivalent to (but potentially more efficient): + * {{{ + * fromBytes(ByteBuffer.allocate(4).putInt(i).array()) + * }}} + */ + def fromInt(i: Int): Version = { + val buf = ByteBuffer.allocate(5) + buf.put(Type.Ephemeral) + buf.putInt(i) + new Version(buf.array()) + } + + /** Create a non-hash version from a Long. + * + * Strictly equivalent to (but potentially more efficient): + * {{{ + * fromBytes(ByteBuffer.allocate(8).putLong(i).array()) + * }}} + */ + def fromLong(l: Long): Version = { + val buf = ByteBuffer.allocate(9) + buf.put(Type.Ephemeral) + buf.putLong(l) + new Version(buf.array()) + } + + /** Create a non-hash version from the given [[UTF8String]]. + * + * Strictly equivalent to (but potentially more efficient): + * {{{ + * fromBytes(Array.tabulate(utf8String.length)(utf8String(_))) + * }}} + */ + def fromUTF8String(utf8String: UTF8String): Version = + make(Type.Ephemeral, utf8String.bytes) + + /** Create a combined, non-hash version from the given bytes. + * + * Returns [[Unversioned]] if at least one of versions is [[Unversioned]]. + * + * The returned version is to differ from: + * - all hash versions. + * - all non-hash versions created with `from` methods. + * - combined versions created with different (ordered) version lists + * (including the empty list). + * + * @note This can be used to create tagged versions (for alternatives): + * {{{ + * Versions.combine(Versions.fromInt(0), underlying) + * }}} + */ + def combine(versions: Version*): Version = { + if (versions.forall(_.isVersioned)) { + val buf = ByteBuffer.allocate(1 + 4 + versions.map(_.v.length + 4).sum) + + buf.put(Type.Combined) + buf.putInt(versions.length) + + for (version <- versions) { + buf.putInt(version.v.length) + buf.put(version.v) + } + + new Version(buf.array()) + } else { + Unversioned + } + } + + private[ir] def fromHash(hash: Array[Byte]): Version = { + require(hash.length == 20) + make(Type.Hash, hash) + } + + private def make(tpe: Byte, bytes: Array[Byte]): Version = { + val len = bytes.length + val v = new Array[Byte](len + 1) + v(0) = tpe + + System.arraycopy(bytes, 0, v, 1, len) + new Version(v) + } +} diff --git a/ir/shared/src/main/scala/org/scalajs/ir/WellKnownNames.scala b/ir/shared/src/main/scala/org/scalajs/ir/WellKnownNames.scala new file mode 100644 index 0000000000..cc85cd47da --- /dev/null +++ b/ir/shared/src/main/scala/org/scalajs/ir/WellKnownNames.scala @@ -0,0 +1,158 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.ir + +import Names._ +import Types._ + +/** Names for "well-known" classes and methods. + * + * Well-known classes and methods have a dedicated meaning in the semantics of + * the IR. For example, `java.lang.Class` is well-known because it is the type + * of `ClassOf` nodes. + */ +object WellKnownNames { + + /** `java.lang.Object`, the root of the class hierarchy. */ + val ObjectClass: ClassName = ClassName("java.lang.Object") + + /** `ClassRef(ObjectClass)`. */ + val ObjectRef: ClassRef = ClassRef(ObjectClass) + + // Hijacked classes + val BoxedUnitClass: ClassName = ClassName("java.lang.Void") + val BoxedBooleanClass: ClassName = ClassName("java.lang.Boolean") + val BoxedCharacterClass: ClassName = ClassName("java.lang.Character") + val BoxedByteClass: ClassName = ClassName("java.lang.Byte") + val BoxedShortClass: ClassName = ClassName("java.lang.Short") + val BoxedIntegerClass: ClassName = ClassName("java.lang.Integer") + val BoxedLongClass: ClassName = ClassName("java.lang.Long") + val BoxedFloatClass: ClassName = ClassName("java.lang.Float") + val BoxedDoubleClass: ClassName = ClassName("java.lang.Double") + val BoxedStringClass: ClassName = ClassName("java.lang.String") + + /** The set of all hijacked classes. */ + val HijackedClasses: Set[ClassName] = Set( + BoxedUnitClass, + BoxedBooleanClass, + BoxedCharacterClass, + BoxedByteClass, + BoxedShortClass, + BoxedIntegerClass, + BoxedLongClass, + BoxedFloatClass, + BoxedDoubleClass, + BoxedStringClass + ) + + /** Map from hijacked classes to their respective primitive types. */ + val BoxedClassToPrimType: Map[ClassName, PrimType] = Map( + BoxedUnitClass -> UndefType, + BoxedBooleanClass -> BooleanType, + BoxedCharacterClass -> CharType, + BoxedByteClass -> ByteType, + BoxedShortClass -> ShortType, + BoxedIntegerClass -> IntType, + BoxedLongClass -> LongType, + BoxedFloatClass -> FloatType, + BoxedDoubleClass -> DoubleType, + BoxedStringClass -> StringType + ) + + /** Map from primitive types to their respective boxed (hijacked) classes. */ + val PrimTypeToBoxedClass: Map[PrimType, ClassName] = + BoxedClassToPrimType.map(_.swap) + + /** The class of things returned by `ClassOf` and `GetClass`. */ + val ClassClass: ClassName = ClassName("java.lang.Class") + + /** `java.lang.Cloneable`, which is an ancestor of array classes and is used + * by `Clone`. + */ + val CloneableClass: ClassName = ClassName("java.lang.Cloneable") + + /** `java.io.Serializable`, which is an ancestor of array classes. */ + val SerializableClass: ClassName = ClassName("java.io.Serializable") + + /** The superclass of all throwables. + * + * This is the result type of `WrapAsThrowable` nodes, as well as the input + * type of `UnwrapFromThrowable`. + */ + val ThrowableClass = ClassName("java.lang.Throwable") + + /** The exception thrown by a division by 0. */ + val ArithmeticExceptionClass: ClassName = + ClassName("java.lang.ArithmeticException") + + /** The exception thrown by an `ArraySelect` that is out of bounds. */ + val ArrayIndexOutOfBoundsExceptionClass: ClassName = + ClassName("java.lang.ArrayIndexOutOfBoundsException") + + /** The exception thrown by an `Assign(ArraySelect, ...)` where the value cannot be stored. */ + val ArrayStoreExceptionClass: ClassName = + ClassName("java.lang.ArrayStoreException") + + /** The exception thrown by a `NewArray(...)` with a negative size. */ + val NegativeArraySizeExceptionClass: ClassName = + ClassName("java.lang.NegativeArraySizeException") + + /** The exception thrown by a variety of nodes for `null` arguments. + * + * - `Apply` and `ApplyStatically` for the receiver, + * - `Select` for the qualifier, + * - `ArrayLength` and `ArraySelect` for the array, + * - `GetClass`, `Clone` and `UnwrapFromException` for their respective only arguments. + */ + val NullPointerExceptionClass: ClassName = + ClassName("java.lang.NullPointerException") + + /** The exception thrown by a `BinaryOp.String_charAt` that is out of bounds. */ + val StringIndexOutOfBoundsExceptionClass: ClassName = + ClassName("java.lang.StringIndexOutOfBoundsException") + + /** The exception thrown by an `AsInstanceOf` that fails. */ + val ClassCastExceptionClass: ClassName = + ClassName("java.lang.ClassCastException") + + /** The exception thrown by a `Class_newArray` if the first argument is `classOf[Unit]`. */ + val IllegalArgumentExceptionClass: ClassName = + ClassName("java.lang.IllegalArgumentException") + + /** The set of classes and interfaces that are ancestors of array classes. */ + private[ir] val AncestorsOfPseudoArrayClass: Set[ClassName] = { + /* This would logically be defined in Types, but that introduces a cyclic + * dependency between the initialization of Names and Types. + */ + Set(ObjectClass, CloneableClass, SerializableClass) + } + + /** Name of a constructor without argument. + * + * This is notably the signature of constructors of module classes. + */ + final val NoArgConstructorName: MethodName = + MethodName.constructor(Nil) + + /** Name of the static initializer method. */ + final val StaticInitializerName: MethodName = + MethodName(SimpleMethodName.StaticInitializer, Nil, VoidRef) + + /** Name of the class initializer method. */ + final val ClassInitializerName: MethodName = + MethodName(SimpleMethodName.ClassInitializer, Nil, VoidRef) + + /** ModuleID of the default module */ + final val DefaultModuleID: String = "main" + +} diff --git a/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala index 3a95a15b55..612174ffff 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/HashersTest.scala @@ -14,6 +14,8 @@ package org.scalajs.ir import scala.language.implicitConversions +import java.io.ByteArrayOutputStream + import org.junit.Test import org.junit.Assert._ @@ -27,18 +29,18 @@ import Types._ import TestIRBuilder._ class HashersTest { - private def assertHashEquals(expected: String, actual: Option[TreeHash]): Unit = { - assertTrue(actual.isDefined) - assertEquals(expected, hashAsVersion(actual.get)) - } - - @Test def testHashAsVersion(): Unit = { - val hash: TreeHash = new TreeHash(Array( - 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 0xc3, 0x7f, - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xbb, 0x34 - ).map(_.toByte)) + private def assertHashEquals(expected: String, actual: Version): Unit = { + assertTrue(actual.isHash) + + val actualBytes = { + val out = new ByteArrayOutputStream + actual.writeHash(out) + out.close() + out.toByteArray() + } + val actualString = actualBytes.map(b => "%02x".format(b & 0xff)).mkString - assertEquals("1032547698badcfec37f0123456789abcdefbb34", hashAsVersion(hash)) + assertEquals(expected, actualString) } private val bodyWithInterestingStuff = Block( @@ -75,7 +77,7 @@ class HashersTest { @Test def testHashMethodDef(): Unit = { def test(expected: String, methodDef: MethodDef): Unit = { val hashedMethodDef = hashMethodDef(methodDef) - assertHashEquals(expected, hashedMethodDef.hash) + assertHashEquals(expected, hashedMethodDef.version) } val mIIMethodName = MethodName("m", List(I), I) @@ -85,30 +87,30 @@ class HashersTest { MethodDef(MemberFlags.empty, mIIMethodName, NON, List(ParamDef("x", NON, IntType, mutable = false)), IntType, None)( - NoOptHints, None) + NoOptHints, UNV) ) test( - "309805e5680ffa1804811ff5c9ebc77e91846957", + "82df9d6beb7df0ee9f501380323bdb2038cc50cb", MethodDef(MemberFlags.empty, mIIMethodName, NON, List(ParamDef("x", NON, IntType, mutable = false)), IntType, Some(bodyWithInterestingStuff))( - NoOptHints, None) + NoOptHints, UNV) ) } @Test def testHashJSMethodDef(): Unit = { def test(expected: String, methodDef: JSMethodDef): Unit = { val hashedMethodDef = hashJSMethodDef(methodDef) - assertHashEquals(expected, hashedMethodDef.hash) + assertHashEquals(expected, hashedMethodDef.version) } test( - "c0f1ef1b22fd1cfdc9bba78bf3e0f433e9f82fc1", + "d0fa6c753502e3d1df34e53ca6f6afb5cbdcd9d4", JSMethodDef(MemberFlags.empty, s("m"), List(ParamDef("x", NON, AnyType, mutable = false)), None, bodyWithInterestingStuff)( - NoOptHints, None) + NoOptHints, UNV) ) } diff --git a/ir/shared/src/test/scala/org/scalajs/ir/NamesTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/NamesTest.scala new file mode 100644 index 0000000000..c0667c3b93 --- /dev/null +++ b/ir/shared/src/test/scala/org/scalajs/ir/NamesTest.scala @@ -0,0 +1,77 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.ir + +import org.junit.Test +import org.junit.Assert._ + +import Names._ +import Types._ +import WellKnownNames._ + +class NamesTest { + @Test def nameStringLocalName(): Unit = { + assertEquals("foo", LocalName("foo").nameString) + assertEquals(".this", LocalName.This.nameString) + } + + @Test def nameStringLabelName(): Unit = { + assertEquals("foo", LabelName("foo").nameString) + } + + @Test def nameStringSimpleFieldName(): Unit = { + assertEquals("foo", SimpleFieldName("foo").nameString) + } + + @Test def nameStringFieldName(): Unit = { + assertEquals("a.B::foo", + FieldName(ClassName("a.B"), SimpleFieldName("foo")).nameString) + } + + @Test def nameStringSimpleMethodName(): Unit = { + assertEquals("foo", SimpleMethodName("foo").nameString) + assertEquals("", SimpleMethodName.Constructor.nameString) + assertEquals("", SimpleMethodName.StaticInitializer.nameString) + assertEquals("", SimpleMethodName.ClassInitializer.nameString) + } + + @Test def nameStringMethodName(): Unit = { + assertEquals("foo;I", MethodName("foo", Nil, IntRef).nameString) + assertEquals("foo;Z;I", MethodName("foo", List(BooleanRef), IntRef).nameString) + assertEquals("foo;Z;V", MethodName("foo", List(BooleanRef), VoidRef).nameString) + + assertEquals("foo;S;Ljava.io.Serializable;V", + MethodName("foo", List(ShortRef, ClassRef(SerializableClass)), VoidRef).nameString) + + assertEquals(";I;V", MethodName.constructor(List(IntRef)).nameString) + + assertEquals("foo;Z;R", MethodName.reflectiveProxy("foo", List(BooleanRef)).nameString) + + val refAndNameStrings: List[(TypeRef, String)] = List( + ClassRef(ObjectClass) -> "Ljava.lang.Object", + ClassRef(SerializableClass) -> "Ljava.io.Serializable", + ClassRef(BoxedStringClass) -> "Ljava.lang.String", + ArrayTypeRef(ClassRef(ObjectClass), 2) -> "[[Ljava.lang.Object", + ArrayTypeRef(ShortRef, 1) -> "[S", + TransientTypeRef(LabelName("bar"))(CharType) -> "tbar" + ) + for ((ref, nameString) <- refAndNameStrings) { + assertEquals(s"foo;$nameString;V", + MethodName("foo", List(ref), VoidRef).nameString) + } + } + + @Test def nameStringClassName(): Unit = { + assertEquals("a.B", ClassName("a.B").nameString) + } +} diff --git a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala index 6135e16e87..fd49eb406e 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -22,11 +22,12 @@ import OriginalName.NoOriginalName import Printers._ import Trees._ import Types._ +import WellKnownNames._ import TestIRBuilder._ class PrintersTest { - import MemberNamespace.{Private, PublicStatic => Static, PrivateStatic} + import MemberNamespace.{Constructor, Private, PublicStatic => Static, PrivateStatic} /** An original name. */ private val TestON = OriginalName("orig name") @@ -50,8 +51,9 @@ class PrintersTest { @Test def printType(): Unit = { assertPrintEquals("any", AnyType) + assertPrintEquals("any!", AnyNotNullType) assertPrintEquals("nothing", NothingType) - assertPrintEquals("void", UndefType) + assertPrintEquals("undef", UndefType) assertPrintEquals("boolean", BooleanType) assertPrintEquals("char", CharType) assertPrintEquals("byte", ByteType) @@ -62,12 +64,20 @@ class PrintersTest { assertPrintEquals("double", DoubleType) assertPrintEquals("string", StringType) assertPrintEquals("null", NullType) - assertPrintEquals("", NoType) + assertPrintEquals("void", VoidType) - assertPrintEquals("java.lang.Object", ClassType(ObjectClass)) + assertPrintEquals("java.lang.Object", ClassType(ObjectClass, nullable = true)) + assertPrintEquals("java.lang.String!", + ClassType(BoxedStringClass, nullable = false)) assertPrintEquals("java.lang.Object[]", arrayType(ObjectClass, 1)) assertPrintEquals("int[][]", arrayType(IntRef, 2)) + assertPrintEquals("java.lang.String[]!", + ArrayType(ArrayTypeRef(BoxedStringClass, 1), nullable = false)) + + assertPrintEquals("(() => int)", ClosureType(Nil, IntType, nullable = true)) + assertPrintEquals("((any, java.lang.String!) => boolean)!", + ClosureType(List(AnyType, ClassType(BoxedStringClass, nullable = false)), BooleanType, nullable = false)) assertPrintEquals("(x: int, var y: any)", RecordType(List( @@ -80,6 +90,8 @@ class PrintersTest { assertPrintEquals("java.lang.Object[]", ArrayTypeRef(ObjectClass, 1)) assertPrintEquals("int[][]", ArrayTypeRef(IntRef, 2)) + + assertPrintEquals("foo", TransientTypeRef(LabelName("foo"))(IntType)) } @Test def printVarDef(): Unit = { @@ -122,7 +134,7 @@ class PrintersTest { | 6 |} """, - Labeled("lab", NoType, i(6))) + Labeled("lab", VoidType, i(6))) assertPrintEquals( """ @@ -139,7 +151,7 @@ class PrintersTest { | 6 |} """, - Labeled("lab", NoType, Block(i(5), i(6)))) + Labeled("lab", VoidType, Block(i(5), i(6)))) } @Test def printAssign(): Unit = { @@ -149,6 +161,7 @@ class PrintersTest { @Test def printReturn(): Unit = { assertPrintEquals("return@lab 5", Return(i(5), "lab")) + assertPrintEquals("return@lab", Return(Skip(), "lab")) } @Test def printIf(): Unit = { @@ -168,7 +181,7 @@ class PrintersTest { | 5 |} """, - If(b(true), i(5), Skip())(NoType)) + If(b(true), i(5), Skip())(VoidType)) assertPrintEquals( """ @@ -189,24 +202,69 @@ class PrintersTest { If(ref("x", BooleanType), ref("y", BooleanType), b(false))(BooleanType)) } - @Test def printWhile(): Unit = { + @Test def printLinkTimeIf(): Unit = { assertPrintEquals( """ - |while (true) { + |link-time-if (true) { | 5 + |} else { + | 6 |} """, - While(b(true), i(5))) + LinkTimeIf(b(true), i(5), i(6))(IntType)) + + assertPrintEquals( + """ + |link-time-if (true) { + | 5 + |} else { + |} + """, + LinkTimeIf(b(true), i(5), Skip())(VoidType)) + + assertPrintEquals( + """ + |link-time-if (true) { + | 5 + |} else { + | link-time-if (false) { + | 6 + | } else { + | 7 + | } + |} + """, + LinkTimeIf(b(true), i(5), LinkTimeIf(b(false), i(6), i(7))(IntType))(IntType)) + + assertPrintEquals( + """ + |link-time-if (x) { + | true + |} else { + | y + |} + """, + LinkTimeIf(ref("x", BooleanType), b(true), ref("y", BooleanType))(BooleanType)) + + assertPrintEquals( + """ + |link-time-if (x) { + | y + |} else { + | false + |} + """, + LinkTimeIf(ref("x", BooleanType), ref("y", BooleanType), b(false))(BooleanType)) } - @Test def printDoWhile(): Unit = { + @Test def printWhile(): Unit = { assertPrintEquals( """ - |do { + |while (true) { | 5 - |} while (true) + |} """, - DoWhile(i(5), b(true))) + While(b(true), i(5))) } @Test def printForIn(): Unit = { @@ -271,10 +329,6 @@ class PrintersTest { TryFinally(TryCatch(i(5), "e", NON, i(6))(IntType), i(7))) } - @Test def printThrow(): Unit = { - assertPrintEquals("throw null", Throw(Null())) - } - @Test def printMatch(): Unit = { assertPrintEquals( """ @@ -296,6 +350,10 @@ class PrintersTest { i(11))(IntType)) } + @Test def printJSAwait(): Unit = { + assertPrintEquals("await(p)", JSAwait(ref("p", AnyType))) + } + @Test def printDebugger(): Unit = { assertPrintEquals("debugger", Debugger()) } @@ -312,23 +370,22 @@ class PrintersTest { } @Test def printStoreModule(): Unit = { - assertPrintEquals("mod:scala.Predef$<-this", - StoreModule("scala.Predef$", This()("scala.Predef$"))) + assertPrintEquals("", StoreModule()) } @Test def printSelect(): Unit = { assertPrintEquals("x.test.Test::f", - Select(ref("x", "test.Test"), "test.Test", "f")(IntType)) + Select(ref("x", "test.Test"), FieldName("test.Test", "f"))(IntType)) } @Test def printSelectStatic(): Unit = { assertPrintEquals("test.Test::f", - SelectStatic("test.Test", "f")(IntType)) + SelectStatic(FieldName("test.Test", "f"))(IntType)) } @Test def printApply(): Unit = { assertPrintEquals("x.m;V()", - Apply(EAF, ref("x", "test.Test"), MethodName("m", Nil, V), Nil)(NoType)) + Apply(EAF, ref("x", "test.Test"), MethodName("m", Nil, V), Nil)(VoidType)) assertPrintEquals("x.m;I;I(5)", Apply(EAF, ref("x", "test.Test"), MethodName("m", List(I), I), List(i(5)))(IntType)) @@ -340,7 +397,7 @@ class PrintersTest { @Test def printApplyStatically(): Unit = { assertPrintEquals("x.test.Test::m;V()", ApplyStatically(EAF, ref("x", "test.Test"), "test.Test", - MethodName("m", Nil, V), Nil)(NoType)) + MethodName("m", Nil, V), Nil)(VoidType)) assertPrintEquals("x.test.Test::m;I;I(5)", ApplyStatically(EAF, ref("x", "test.Test"), "test.Test", MethodName("m", List(I), I), List(i(5)))(IntType)) @@ -350,12 +407,12 @@ class PrintersTest { assertPrintEquals("x.test.Test::private::m;V()", ApplyStatically(EAF.withPrivate(true), ref("x", "test.Test"), - "test.Test", MethodName("m", Nil, V), Nil)(NoType)) + "test.Test", MethodName("m", Nil, V), Nil)(VoidType)) } @Test def printApplyStatic(): Unit = { assertPrintEquals("test.Test::m;V()", - ApplyStatic(EAF, "test.Test", MethodName("m", Nil, V), Nil)(NoType)) + ApplyStatic(EAF, "test.Test", MethodName("m", Nil, V), Nil)(VoidType)) assertPrintEquals("test.Test::m;I;I(5)", ApplyStatic(EAF, "test.Test", MethodName("m", List(I), I), List(i(5)))(IntType)) @@ -365,7 +422,7 @@ class PrintersTest { assertPrintEquals("test.Test::private::m;V()", ApplyStatic(EAF.withPrivate(true), "test.Test", MethodName("m", Nil, V), - Nil)(NoType)) + Nil)(VoidType)) } @Test def printApplyDynamicImportStatic(): Unit = { @@ -373,6 +430,47 @@ class PrintersTest { ApplyDynamicImport(EAF, "test.Test", MethodName("m", Nil, O), Nil)) } + @Test def printApplyTypedClosure(): Unit = { + assertPrintEquals("f()", + ApplyTypedClosure(EAF, ref("f", NothingType), Nil)) + assertPrintEquals("f(1)", + ApplyTypedClosure(EAF, ref("f", NothingType), List(i(1)))) + assertPrintEquals("f(1, 2)", + ApplyTypedClosure(EAF, ref("f", NothingType), List(i(1), i(2)))) + } + + @Test def printNewLambda(): Unit = { + assertPrintEquals( + s""" + |( + | extends java.lang.Object implements java.lang.Comparable, + | def compareTo;Ljava.lang.Object;Z(any): boolean, + | (typed-lambda<>(that: any): boolean = { + | true + | }) + |) + """, + NewLambda( + NewLambda.Descriptor( + ObjectClass, + List("java.lang.Comparable"), + MethodName(SimpleMethodName("compareTo"), List(ClassRef(ObjectClass)), BooleanRef), + List(AnyType), + BooleanType + ), + Closure( + ClosureFlags.typed, + Nil, + List(ParamDef("that", NON, AnyType, mutable = false)), + None, + BooleanType, + BooleanLiteral(true), + Nil + ) + )(ClassType("java.lang.Comparable", nullable = false)) + ) + } + @Test def printUnaryOp(): Unit = { import UnaryOp._ @@ -396,6 +494,26 @@ class PrintersTest { assertPrintEquals("((long)x)", UnaryOp(DoubleToLong, ref("x", DoubleType))) assertPrintEquals("((float)x)", UnaryOp(LongToFloat, ref("x", LongType))) + + assertPrintEquals("x.length", UnaryOp(String_length, ref("x", StringType))) + + assertPrintEquals("x.notNull", UnaryOp(CheckNotNull, ref("x", AnyType))) + + val classVarRef = ref("x", ClassType(ClassClass, nullable = false)) + assertPrintEquals("x.name", UnaryOp(Class_name, classVarRef)) + assertPrintEquals("x.isPrimitive", UnaryOp(Class_isPrimitive, classVarRef)) + assertPrintEquals("x.isInterface", UnaryOp(Class_isInterface, classVarRef)) + assertPrintEquals("x.isArray", UnaryOp(Class_isArray, classVarRef)) + assertPrintEquals("x.componentType", UnaryOp(Class_componentType, classVarRef)) + assertPrintEquals("x.superClass", UnaryOp(Class_superClass, classVarRef)) + + assertPrintEquals("x.length", UnaryOp(Array_length, ref("x", arrayType(IntRef, 1)))) + assertPrintEquals("x.getClass()", UnaryOp(GetClass, ref("x", AnyType))) + assertPrintEquals("(x)", UnaryOp(Clone, ref("x", arrayType(ObjectClass, 1)))) + assertPrintEquals("(x)", UnaryOp(IdentityHashCode, ref("x", AnyType))) + assertPrintEquals("(e)", UnaryOp(WrapAsThrowable, ref("e", AnyType))) + assertPrintEquals("(e)", + UnaryOp(UnwrapFromThrowable, ref("e", ClassType(ThrowableClass, nullable = true)))) } @Test def printPseudoUnaryOp(): Unit = { @@ -538,13 +656,23 @@ class PrintersTest { BinaryOp(Double_>, ref("x", DoubleType), ref("y", DoubleType))) assertPrintEquals("(x >=[double] y)", BinaryOp(Double_>=, ref("x", DoubleType), ref("y", DoubleType))) + + assertPrintEquals("x[y]", + BinaryOp(String_charAt, ref("x", StringType), ref("y", IntType))) + + val classVarRef = ref("x", ClassType(ClassClass, nullable = false)) + assertPrintEquals("isInstance(x, y)", BinaryOp(Class_isInstance, classVarRef, ref("y", AnyType))) + assertPrintEquals("isAssignableFrom(x, y)", + BinaryOp(Class_isAssignableFrom, classVarRef, ref("y", ClassType(ClassClass, nullable = false)))) + assertPrintEquals("cast(x, y)", BinaryOp(Class_cast, classVarRef, ref("y", AnyType))) + assertPrintEquals("newArray(x, y)", BinaryOp(Class_newArray, classVarRef, ref("y", IntType))) } @Test def printNewArray(): Unit = { - assertPrintEquals("new int[3]", NewArray(ArrayTypeRef(IntRef, 1), List(i(3)))) - assertPrintEquals("new int[3][]", NewArray(ArrayTypeRef(IntRef, 2), List(i(3)))) - assertPrintEquals("new java.lang.Object[3][4][][]", - NewArray(ArrayTypeRef(ObjectClass, 4), List(i(3), i(4)))) + assertPrintEquals("new int[3]", NewArray(ArrayTypeRef(IntRef, 1), i(3))) + assertPrintEquals("new int[3][]", NewArray(ArrayTypeRef(IntRef, 2), i(3))) + assertPrintEquals("new java.lang.Object[3][][][]", + NewArray(ArrayTypeRef(ObjectClass, 4), i(3))) } @Test def printArrayValue(): Unit = { @@ -557,10 +685,6 @@ class PrintersTest { ArrayValue(ArrayTypeRef(IntRef, 2), List(Null()))) } - @Test def printArrayLength(): Unit = { - assertPrintEquals("x.length", ArrayLength(ref("x", arrayType(IntRef, 1)))) - } - @Test def printArraySelect(): Unit = { assertPrintEquals("x[3]", ArraySelect(ref("x", arrayType(IntRef, 1)), i(3))(IntType)) @@ -576,48 +700,38 @@ class PrintersTest { } @Test def printIsInstanceOf(): Unit = { - assertPrintEquals("x.isInstanceOf[java.lang.String]", - IsInstanceOf(ref("x", AnyType), ClassType(BoxedStringClass))) + assertPrintEquals("x.isInstanceOf[java.lang.String!]", + IsInstanceOf(ref("x", AnyType), ClassType(BoxedStringClass, nullable = false))) + assertPrintEquals("x.isInstanceOf[int]", + IsInstanceOf(ref("x", AnyType), IntType)) } @Test def printAsInstanceOf(): Unit = { assertPrintEquals("x.asInstanceOf[java.lang.String]", - AsInstanceOf(ref("x", AnyType), ClassType(BoxedStringClass))) + AsInstanceOf(ref("x", AnyType), ClassType(BoxedStringClass, nullable = true))) assertPrintEquals("x.asInstanceOf[int]", AsInstanceOf(ref("x", AnyType), IntType)) } - @Test def printGetClass(): Unit = { - assertPrintEquals("x.getClass()", GetClass(ref("x", AnyType))) - } - - @Test def printClone(): Unit = { - assertPrintEquals("(x)", Clone(ref("x", arrayType(ObjectClass, 1)))) - } - - @Test def printIdentityHashCode(): Unit = { - assertPrintEquals("(x)", IdentityHashCode(ref("x", AnyType))) - } - @Test def printJSNew(): Unit = { assertPrintEquals("new C()", JSNew(ref("C", AnyType), Nil)) assertPrintEquals("new C(4, 5)", JSNew(ref("C", AnyType), List(i(4), i(5)))) assertPrintEquals("new x.test.Test::C(4, 5)", - JSNew(JSPrivateSelect(ref("x", AnyType), "test.Test", "C"), List(i(4), i(5)))) + JSNew(JSPrivateSelect(ref("x", AnyType), FieldName("test.Test", "C")), List(i(4), i(5)))) assertPrintEquals("""new x["C"]()""", JSNew(JSSelect(ref("x", AnyType), StringLiteral("C")), Nil)) val fApplied = JSFunctionApply(ref("f", AnyType), Nil) assertPrintEquals("new (f())()", JSNew(fApplied, Nil)) assertPrintEquals("new (f().test.Test::C)(4, 5)", - JSNew(JSPrivateSelect(fApplied, "test.Test", "C"), List(i(4), i(5)))) + JSNew(JSPrivateSelect(fApplied, FieldName("test.Test", "C")), List(i(4), i(5)))) assertPrintEquals("""new (f()["C"])()""", JSNew(JSSelect(fApplied, StringLiteral("C")), Nil)) } @Test def printJSPrivateSelect(): Unit = { assertPrintEquals("x.test.Test::f", - JSPrivateSelect(ref("x", AnyType), "test.Test", "f")) + JSPrivateSelect(ref("x", AnyType), FieldName("test.Test", "f"))) } @Test def printJSSelect(): Unit = { @@ -631,12 +745,12 @@ class PrintersTest { JSFunctionApply(ref("f", AnyType), List(i(3), i(4)))) assertPrintEquals("(0, x.test.Test::f)()", - JSFunctionApply(JSPrivateSelect(ref("x", AnyType), "test.Test", "f"), Nil)) + JSFunctionApply(JSPrivateSelect(ref("x", AnyType), FieldName("test.Test", "f")), Nil)) assertPrintEquals("""(0, x["f"])()""", JSFunctionApply(JSSelect(ref("x", AnyType), StringLiteral("f")), Nil)) assertPrintEquals("(0, x.test.Test::f)()", - JSFunctionApply(Select(ref("x", "test.Test"), "test.Test", "f")(AnyType), + JSFunctionApply(Select(ref("x", "test.Test"), FieldName("test.Test", "f"))(AnyType), Nil)) } @@ -749,6 +863,9 @@ class PrintersTest { JSBinaryOp(JSBinaryOp.in, ref("x", AnyType), ref("y", AnyType))) assertPrintEquals("(x instanceof y)", JSBinaryOp(JSBinaryOp.instanceof, ref("x", AnyType), ref("y", AnyType))) + + assertPrintEquals("(x ** y)", + JSBinaryOp(JSBinaryOp.**, ref("x", AnyType), ref("y", AnyType))) } @Test def printJSArrayConstr(): Unit = { @@ -777,12 +894,8 @@ class PrintersTest { assertPrintEquals("(typeof global:Foo)", JSTypeOfGlobalRef(JSGlobalRef("Foo"))) } - @Test def printJSLinkingInfo(): Unit = { - assertPrintEquals("", JSLinkingInfo()) - } - @Test def printUndefined(): Unit = { - assertPrintEquals("(void 0)", Undefined()) + assertPrintEquals("undefined", Undefined()) } @Test def printNull(): Unit = { @@ -857,9 +970,6 @@ class PrintersTest { @Test def printVarRef(): Unit = { assertPrintEquals("x", VarRef("x")(IntType)) - } - - @Test def printThis(): Unit = { assertPrintEquals("this", This()(AnyType)) } @@ -870,7 +980,7 @@ class PrintersTest { | 5 |}) """, - Closure(false, Nil, Nil, None, i(5), Nil)) + Closure(ClosureFlags.function, Nil, Nil, None, AnyType, i(5), Nil)) assertPrintEquals( """ @@ -879,12 +989,13 @@ class PrintersTest { |}) """, Closure( - true, + ClosureFlags.arrow, List( ParamDef("x", NON, AnyType, mutable = false), ParamDef("y", TestON, IntType, mutable = false)), List(ParamDef("z", NON, AnyType, mutable = false)), None, + AnyType, ref("z", AnyType), List(ref("a", IntType), i(6)))) @@ -894,9 +1005,54 @@ class PrintersTest { | z |}) """, - Closure(false, Nil, Nil, + Closure(ClosureFlags.function, Nil, Nil, + Some(ParamDef("z", NON, AnyType, mutable = false)), + AnyType, ref("z", AnyType), Nil)) + + assertPrintEquals( + """ + |(async lambda<>(...z: any): any = { + | z + |}) + """, + Closure(ClosureFlags.function.withAsync(true), Nil, Nil, + Some(ParamDef("z", NON, AnyType, mutable = false)), + AnyType, ref("z", AnyType), Nil)) + + assertPrintEquals( + """ + |(async arrow-lambda<>(...z: any): any = { + | z + |}) + """, + Closure(ClosureFlags.arrow.withAsync(true), Nil, Nil, Some(ParamDef("z", NON, AnyType, mutable = false)), - ref("z", AnyType), Nil)) + AnyType, ref("z", AnyType), Nil)) + + assertPrintEquals( + """ + |(typed-lambda<>() { + | 5 + |}) + """, + Closure(ClosureFlags.typed, Nil, Nil, None, VoidType, i(5), Nil)) + + assertPrintEquals( + """ + |(typed-lambda(z: int): int = { + | z + |}) + """, + Closure( + ClosureFlags.typed, + List( + ParamDef("x", NON, AnyType, mutable = false), + ParamDef("y", TestON, IntType, mutable = false)), + List(ParamDef("z", NON, IntType, mutable = false)), + None, + IntType, + ref("z", IntType), + List(ref("a", IntType), i(6)))) } @Test def printCreateJSClass(): Unit = { @@ -907,13 +1063,21 @@ class PrintersTest { CreateJSClass("Foo", List(ref("x", IntType), ref("y", AnyType)))) } + @Test def printLinkTimeProperty(): Unit = { + assertPrintEquals( + """ + |(foo) + """, + LinkTimeProperty("foo")(StringType)) + } + @Test def printTransient(): Unit = { class MyTransient(expr: Tree) extends Transient.Value { val tpe: Type = AnyType def traverse(traverser: Traversers.Traverser): Unit = ??? - def transform(transformer: Transformers.Transformer, isStat: Boolean)( + def transform(transformer: Transformers.Transformer)( implicit pos: Position): Tree = ??? def printIR(out: Printers.IRTreePrinter): Unit = { @@ -932,7 +1096,7 @@ class PrintersTest { def makeForKind(kind: ClassKind): ClassDef = { ClassDef("Test", NON, kind, None, Some(ObjectClass), Nil, None, None, Nil, - Nil)( + Nil, None, Nil, Nil, Nil)( NoOptHints) } @@ -1004,7 +1168,7 @@ class PrintersTest { def makeForParents(superClass: Option[ClassIdent], interfaces: List[ClassIdent]): ClassDef = { ClassDef("Test", NON, ClassKind.Class, None, superClass, interfaces, None, - None, Nil, Nil)( + None, Nil, Nil, None, Nil, Nil, Nil)( NoOptHints) } @@ -1037,7 +1201,8 @@ class PrintersTest { |} """, ClassDef("Test", NON, ClassKind.NativeJSClass, None, Some(ObjectClass), Nil, - None, Some(JSNativeLoadSpec.Global("Foo", List("Bar"))), Nil, Nil)( + None, Some(JSNativeLoadSpec.Global("Foo", List("Bar"))), Nil, Nil, None, + Nil, Nil, Nil)( NoOptHints)) assertPrintEquals( @@ -1046,7 +1211,8 @@ class PrintersTest { |} """, ClassDef("Test", NON, ClassKind.NativeJSClass, None, Some(ObjectClass), Nil, - None, Some(JSNativeLoadSpec.Import("foo", List("Bar"))), Nil, Nil)( + None, Some(JSNativeLoadSpec.Import("foo", List("Bar"))), Nil, Nil, None, + Nil, Nil, Nil)( NoOptHints)) assertPrintEquals( @@ -1058,7 +1224,8 @@ class PrintersTest { None, Some(JSNativeLoadSpec.ImportWithGlobalFallback( JSNativeLoadSpec.Import("foo", List("Bar")), - JSNativeLoadSpec.Global("Baz", List("Foobar")))), Nil, Nil)( + JSNativeLoadSpec.Global("Baz", List("Foobar")))), Nil, Nil, None, + Nil, Nil, Nil)( NoOptHints)) } @@ -1070,7 +1237,7 @@ class PrintersTest { |} """, ClassDef("Test", NON, ClassKind.JSClass, Some(Nil), Some(ObjectClass), Nil, - None, None, Nil, Nil)( + None, None, Nil, Nil, None, Nil, Nil, Nil)( NoOptHints)) assertPrintEquals( @@ -1084,7 +1251,7 @@ class PrintersTest { ParamDef("x", NON, IntType, mutable = false), ParamDef("y", TestON, StringType, mutable = false) )), - Some(ObjectClass), Nil, None, None, Nil, Nil)( + Some(ObjectClass), Nil, None, None, Nil, Nil, None, Nil, Nil, Nil)( NoOptHints)) } @@ -1097,7 +1264,8 @@ class PrintersTest { """, ClassDef("Test", NON, ClassKind.JSClass, Some(List(ParamDef("sup", NON, AnyType, mutable = false))), - Some("Bar"), Nil, Some(ref("sup", AnyType)), None, Nil, Nil)( + Some("Bar"), Nil, Some(ref("sup", AnyType)), None, Nil, Nil, None, + Nil, Nil, Nil)( NoOptHints)) } @@ -1108,7 +1276,7 @@ class PrintersTest { |} """, ClassDef("Test", NON, ClassKind.Class, None, Some(ObjectClass), Nil, - None, None, Nil, Nil)( + None, None, Nil, Nil, None, Nil, Nil, Nil)( NoOptHints.withInline(true))) } @@ -1119,7 +1287,7 @@ class PrintersTest { |} """, ClassDef("Test", TestON, ClassKind.ModuleClass, None, Some(ObjectClass), - Nil, None, None, Nil, Nil)( + Nil, None, None, Nil, Nil, None, Nil, Nil, Nil)( NoOptHints)) } @@ -1127,28 +1295,38 @@ class PrintersTest { assertPrintEquals( """ |module class Test extends java.lang.Object { - | val x: int - | var y: int + | val Test::x: int + | def m;I(): int = + | constructor def constructor(): any = { + | super() + | } + | def "o"(): any = { + | 5 + | } + | static native p;Ljava.lang.Object loadfrom global:foo | export top[moduleID="main"] module "Foo" |} """, ClassDef("Test", NON, ClassKind.ModuleClass, None, Some(ObjectClass), Nil, None, None, - List( - FieldDef(MemberFlags.empty, "x", NON, IntType), - FieldDef(MemberFlags.empty.withMutable(true), "y", NON, IntType)), - List( - TopLevelModuleExportDef("main", "Foo")))( + List(FieldDef(MemberFlags.empty, FieldName("Test", "x"), NON, IntType)), + List(MethodDef(MemberFlags.empty, MethodName("m", Nil, I), NON, Nil, IntType, None)(NoOptHints, UNV)), + Some(JSConstructorDef(MemberFlags.empty.withNamespace(Constructor), Nil, None, + JSConstructorBody(Nil, JSSuperConstructorCall(Nil), Nil))(NoOptHints, UNV)), + List(JSMethodDef(MemberFlags.empty, StringLiteral("o"), Nil, None, i(5))(NoOptHints, UNV)), + List(JSNativeMemberDef(MemberFlags.empty.withNamespace(Static), MethodName("p", Nil, O), + JSNativeLoadSpec.Global("foo", Nil))), + List(TopLevelModuleExportDef("main", "Foo")))( NoOptHints)) } @Test def printFieldDef(): Unit = { - assertPrintEquals("val x: int", - FieldDef(MemberFlags.empty, "x", NON, IntType)) - assertPrintEquals("var y: any", - FieldDef(MemberFlags.empty.withMutable(true), "y", NON, AnyType)) - assertPrintEquals("val x{orig name}: int", - FieldDef(MemberFlags.empty, "x", TestON, IntType)) + assertPrintEquals("val Test::x: int", + FieldDef(MemberFlags.empty, FieldName("Test", "x"), NON, IntType)) + assertPrintEquals("var Test::y: any", + FieldDef(MemberFlags.empty.withMutable(true), FieldName("Test", "y"), NON, AnyType)) + assertPrintEquals("val Test::x{orig name}: int", + FieldDef(MemberFlags.empty, FieldName("Test", "x"), TestON, IntType)) } @Test def printJSFieldDef(): Unit = { @@ -1173,7 +1351,7 @@ class PrintersTest { """, MethodDef(MemberFlags.empty, mIIMethodName, NON, List(ParamDef("x", NON, IntType, mutable = false)), - IntType, None)(NoOptHints, None)) + IntType, None)(NoOptHints, UNV)) assertPrintEquals( """ @@ -1183,7 +1361,7 @@ class PrintersTest { """, MethodDef(MemberFlags.empty, mIIMethodName, NON, List(ParamDef("x", NON, IntType, mutable = false)), - IntType, Some(i(5)))(NoOptHints, None)) + IntType, Some(i(5)))(NoOptHints, UNV)) assertPrintEquals( """ @@ -1193,7 +1371,7 @@ class PrintersTest { """, MethodDef(MemberFlags.empty, mIIMethodName, NON, List(ParamDef("x", NON, IntType, mutable = false)), - IntType, Some(i(5)))(NoOptHints.withInline(true), None)) + IntType, Some(i(5)))(NoOptHints.withInline(true), UNV)) assertPrintEquals( """ @@ -1203,7 +1381,7 @@ class PrintersTest { """, MethodDef(MemberFlags.empty, mIVMethodName, NON, List(ParamDef("x", NON, IntType, mutable = false)), - NoType, Some(i(5)))(NoOptHints, None)) + VoidType, Some(i(5)))(NoOptHints, UNV)) assertPrintEquals( """ @@ -1213,7 +1391,7 @@ class PrintersTest { """, MethodDef(MemberFlags.empty.withNamespace(Static), mIIMethodName, NON, List(ParamDef("x", NON, IntType, mutable = false)), - IntType, Some(i(5)))(NoOptHints, None)) + IntType, Some(i(5)))(NoOptHints, UNV)) assertPrintEquals( """ @@ -1223,7 +1401,7 @@ class PrintersTest { """, MethodDef(MemberFlags.empty.withNamespace(Private), mIIMethodName, NON, List(ParamDef("x", NON, IntType, mutable = false)), - IntType, Some(i(5)))(NoOptHints, None)) + IntType, Some(i(5)))(NoOptHints, UNV)) assertPrintEquals( """ @@ -1233,7 +1411,7 @@ class PrintersTest { """, MethodDef(MemberFlags.empty.withNamespace(PrivateStatic), mIIMethodName, NON, List(ParamDef("x", NON, IntType, mutable = false)), - IntType, Some(i(5)))(NoOptHints, None)) + IntType, Some(i(5)))(NoOptHints, UNV)) assertPrintEquals( """ @@ -1241,7 +1419,48 @@ class PrintersTest { """, MethodDef(MemberFlags.empty, mIIMethodName, TestON, List(ParamDef("x", TestON, IntType, mutable = false)), - IntType, None)(NoOptHints, None)) + IntType, None)(NoOptHints, UNV)) + } + + @Test def printJSConstructorDef(): Unit = { + assertPrintEquals( + """ + |constructor def constructor(x: any): any = { + | 5; + | super(6); + | undefined + |} + """, + JSConstructorDef(MemberFlags.empty.withNamespace(Constructor), + List(ParamDef("x", NON, AnyType, mutable = false)), None, + JSConstructorBody(List(i(5)), JSSuperConstructorCall(List(i(6))), List(Undefined())))( + NoOptHints, UNV)) + + assertPrintEquals( + """ + |constructor def constructor(x: any, ...y: any): any = { + | super(6); + | 7 + |} + """, + JSConstructorDef(MemberFlags.empty.withNamespace(Constructor), + List(ParamDef("x", NON, AnyType, mutable = false)), + Some(ParamDef("y", NON, AnyType, mutable = false)), + JSConstructorBody(Nil, JSSuperConstructorCall(List(i(6))), List(i(7))))( + NoOptHints, UNV)) + + // This example is an invalid constructor, but it should be printed anyway + assertPrintEquals( + """ + |def constructor(x{orig name}: any): any = { + | 5; + | super(6) + |} + """, + JSConstructorDef(MemberFlags.empty, + List(ParamDef("x", TestON, AnyType, mutable = false)), None, + JSConstructorBody(List(i(5)), JSSuperConstructorCall(List(i(6))), Nil))( + NoOptHints, UNV)) } @Test def printJSMethodDef(): Unit = { @@ -1253,7 +1472,7 @@ class PrintersTest { """, JSMethodDef(MemberFlags.empty, StringLiteral("m"), List(ParamDef("x", NON, AnyType, mutable = false)), None, - i(5))(NoOptHints, None)) + i(5))(NoOptHints, UNV)) assertPrintEquals( """ @@ -1264,7 +1483,7 @@ class PrintersTest { JSMethodDef(MemberFlags.empty, StringLiteral("m"), List(ParamDef("x", NON, AnyType, mutable = false)), Some(ParamDef("y", NON, AnyType, mutable = false)), - i(5))(NoOptHints, None)) + i(5))(NoOptHints, UNV)) assertPrintEquals( """ @@ -1274,7 +1493,7 @@ class PrintersTest { """, JSMethodDef(MemberFlags.empty.withNamespace(Static), StringLiteral("m"), List(ParamDef("x", NON, AnyType, mutable = false)), None, - i(5))(NoOptHints, None)) + i(5))(NoOptHints, UNV)) assertPrintEquals( """ @@ -1284,7 +1503,7 @@ class PrintersTest { """, JSMethodDef(MemberFlags.empty, StringLiteral("m"), List(ParamDef("x", TestON, AnyType, mutable = false)), None, - i(5))(NoOptHints, None)) + i(5))(NoOptHints, UNV)) } @Test def printJSPropertyDef(): Unit = { @@ -1302,7 +1521,7 @@ class PrintersTest { | 5 |} """, - JSPropertyDef(flags, StringLiteral("prop"), Some(i(5)), None)) + JSPropertyDef(flags, StringLiteral("prop"), Some(i(5)), None)(UNV)) assertPrintEquals( s""" @@ -1312,7 +1531,7 @@ class PrintersTest { """, JSPropertyDef(flags, StringLiteral("prop"), None, - Some((ParamDef("x", NON, AnyType, mutable = false), i(7))))) + Some((ParamDef("x", NON, AnyType, mutable = false), i(7))))(UNV)) assertPrintEquals( s""" @@ -1322,7 +1541,7 @@ class PrintersTest { """, JSPropertyDef(flags, StringLiteral("prop"), None, - Some((ParamDef("x", TestON, AnyType, mutable = false), i(7))))) + Some((ParamDef("x", TestON, AnyType, mutable = false), i(7))))(UNV)) assertPrintEquals( s""" @@ -1336,7 +1555,7 @@ class PrintersTest { JSPropertyDef(flags, StringLiteral("prop"), Some(i(5)), Some((ParamDef("x", NON, AnyType, mutable = false), - i(7))))) + i(7))))(UNV)) } } @@ -1361,14 +1580,14 @@ class PrintersTest { TopLevelMethodExportDef("main", JSMethodDef( MemberFlags.empty.withNamespace(Static), StringLiteral("foo"), List(ParamDef("x", NON, AnyType, mutable = false)), None, - i(5))(NoOptHints, None))) + i(5))(NoOptHints, UNV))) } @Test def printTopLevelFieldExportDef(): Unit = { assertPrintEquals( """ - |export top[moduleID="main"] static field x$1 as "x" + |export top[moduleID="main"] static field Test::x$1 as "x" """, - TopLevelFieldExportDef("main", "x", "x$1")) + TopLevelFieldExportDef("main", "x", FieldName("Test", "x$1"))) } } diff --git a/ir/shared/src/test/scala/org/scalajs/ir/SerializersTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/SerializersTest.scala new file mode 100644 index 0000000000..a8c18507d9 --- /dev/null +++ b/ir/shared/src/test/scala/org/scalajs/ir/SerializersTest.scala @@ -0,0 +1,61 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.ir + +import org.junit.Test +import org.junit.Assert._ + +class SerializersTest { + @Test def testHacksUseBelow(): Unit = { + import Serializers.Hacks + + val hacks1_0 = new Hacks("1.0") + assertFalse(hacks1_0.useBelow(0)) + assertTrue(hacks1_0.useBelow(1)) + assertTrue(hacks1_0.useBelow(5)) + assertTrue(hacks1_0.useBelow(15)) + assertTrue(hacks1_0.useBelow(1000)) + + val hacks1_7 = new Hacks("1.7") + assertFalse(hacks1_7.useBelow(0)) + assertFalse(hacks1_7.useBelow(1)) + assertFalse(hacks1_7.useBelow(5)) + assertFalse(hacks1_7.useBelow(7)) + assertTrue(hacks1_7.useBelow(8)) + assertTrue(hacks1_7.useBelow(15)) + assertTrue(hacks1_7.useBelow(1000)) + + val hacks1_50 = new Hacks("1.50") + assertFalse(hacks1_50.useBelow(0)) + assertFalse(hacks1_50.useBelow(1)) + assertFalse(hacks1_50.useBelow(5)) + assertFalse(hacks1_50.useBelow(15)) + assertTrue(hacks1_50.useBelow(1000)) + + // Non-stable versions never get any hacks + val hacks1_9_snapshot = new Hacks("1.9-SNAPSHOT") + assertFalse(hacks1_9_snapshot.useBelow(0)) + assertFalse(hacks1_9_snapshot.useBelow(1)) + assertFalse(hacks1_9_snapshot.useBelow(5)) + assertFalse(hacks1_9_snapshot.useBelow(15)) + assertFalse(hacks1_9_snapshot.useBelow(1000)) + + // Incompatible versions never get any hacks + val hacks2_5 = new Hacks("2.5") + assertFalse(hacks2_5.useBelow(0)) + assertFalse(hacks2_5.useBelow(1)) + assertFalse(hacks2_5.useBelow(5)) + assertFalse(hacks2_5.useBelow(15)) + assertFalse(hacks2_5.useBelow(1000)) + } +} diff --git a/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala b/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala index 6b80faff48..ac2e9cecd0 100644 --- a/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala +++ b/ir/shared/src/test/scala/org/scalajs/ir/TestIRBuilder.scala @@ -19,10 +19,11 @@ import OriginalName.NoOriginalName import Printers._ import Trees._ import Types._ +import WellKnownNames._ object TestIRBuilder { - implicit val dummyPos = Position.NoPosition + implicit val dummyPos: Position = Position.NoPosition /** Empty ApplyFlags, for short. */ val EAF = ApplyFlags.empty @@ -30,32 +31,39 @@ object TestIRBuilder { /** No original name, for short. */ val NON = NoOriginalName + /** Unversioned, for short */ + val UNV = Version.Unversioned + /** No optimizer hints, for short. */ val NoOptHints = OptimizerHints.empty // String -> Name conversions - implicit def string2fieldName(name: String): FieldName = - FieldName(name) + implicit def string2localName(name: String): LocalName = + LocalName(name) + implicit def string2labelName(name: String): LabelName = + LabelName(name) + implicit def string2simpleFieldName(name: String): SimpleFieldName = + SimpleFieldName(name) implicit def string2className(name: String): ClassName = ClassName(name) // String -> Ident conversions implicit def string2localIdent(name: String): LocalIdent = LocalIdent(LocalName(name)) - implicit def string2labelIdent(name: String): LabelIdent = - LabelIdent(LabelName(name)) - implicit def string2fieldIdent(name: String): FieldIdent = - FieldIdent(FieldName(name)) + implicit def string2simpleFieldIdent(name: String): SimpleFieldIdent = + SimpleFieldIdent(SimpleFieldName(name)) implicit def string2classIdent(name: String): ClassIdent = ClassIdent(ClassName(name)) // String -> Type and TypeRef conversions implicit def string2classType(className: String): ClassType = - ClassType(ClassName(className)) + ClassType(ClassName(className), nullable = true) implicit def string2classRef(className: String): ClassRef = ClassRef(ClassName(className)) // Name -> Ident conversions + implicit def fieldName2fieldIdent(name: FieldName): FieldIdent = + FieldIdent(name) implicit def methodName2methodIdent(name: MethodName): MethodIdent = MethodIdent(name) implicit def className2classRef(className: ClassName): ClassRef = @@ -74,9 +82,9 @@ object TestIRBuilder { def d(value: Double): DoubleLiteral = DoubleLiteral(value) def s(value: String): StringLiteral = StringLiteral(value) - def ref(ident: LocalIdent, tpe: Type): VarRef = VarRef(ident)(tpe) + def ref(name: LocalName, tpe: Type): VarRef = VarRef(name)(tpe) def arrayType(base: NonArrayTypeRef, dimensions: Int): ArrayType = - ArrayType(ArrayTypeRef(base, dimensions)) + ArrayType(ArrayTypeRef(base, dimensions), nullable = true) } diff --git a/ir/shared/src/test/scala/org/scalajs/ir/VersionTest.scala b/ir/shared/src/test/scala/org/scalajs/ir/VersionTest.scala new file mode 100644 index 0000000000..a934b5f34d --- /dev/null +++ b/ir/shared/src/test/scala/org/scalajs/ir/VersionTest.scala @@ -0,0 +1,132 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.ir + +import java.io.ByteArrayOutputStream + +import org.junit.Test +import org.junit.Assert._ + +class VersionTest { + import Version._ + + private def testEq(x: Version, y: Version) = { + assertTrue(x.sameVersion(y)) + assertTrue(y.sameVersion(x)) + } + + private def testNe(x: Version, y: Version) = { + assertFalse(x.sameVersion(y)) + assertFalse(y.sameVersion(x)) + } + + @Test + def testUnversioned(): Unit = { + testNe(Unversioned, Unversioned) + testNe(Unversioned, fromInt(1)) + testNe(Unversioned, fromLong(1L)) + testNe(Unversioned, fromBytes(new Array(2))) + testNe(Unversioned, fromHash(new Array(20))) + testNe(Unversioned, combine(fromInt(1), fromInt(2))) + } + + @Test + def testFromHash(): Unit = { + val v = fromHash(Array.fill(20)(0)) + + testEq(v, fromHash(Array.fill(20)(0))) + testNe(v, fromHash(Array.fill(20)(1))) + } + + @Test + def testFromBytes(): Unit = { + val v = fromBytes(Array(1)) + + testEq(v, fromBytes(Array(1))) + testNe(v, fromBytes(Array(2))) + testNe(v, fromBytes(Array(1, 2))) + testNe(v, fromBytes(Array())) + } + + @Test + def testFromInt(): Unit = { + val v = fromInt(2) + + testEq(v, fromInt(2)) + testEq(v, fromBytes(Array(0, 0, 0, 2))) + testNe(v, fromInt(3)) + testNe(v, fromBytes(Array(0))) + } + + @Test + def testFromLong(): Unit = { + val v = fromLong(2L) + + testEq(v, fromLong(2L)) + testEq(v, fromBytes(Array[Byte](0, 0, 0, 0, 0, 0, 0, 2))) + testNe(v, fromLong(3L)) + testNe(v, fromInt(2)) + testNe(v, fromBytes(Array[Byte](0))) + } + + @Test + def testCombine(): Unit = { + val v = combine(fromBytes(Array(1)), fromBytes(Array(2))) + + testEq(v, combine(fromBytes(Array(1)), fromBytes(Array(2)))) + testNe(v, fromBytes(Array(1, 2))) + testNe(v, combine()) + testNe(v, combine(fromBytes(Array(1)))) + + testEq(combine(), combine()) + } + + @Test + def testKinds(): Unit = { + // Hash doesn't equal ephemeral. + testNe(fromHash(Array.fill(20)(1)), fromBytes(Array.fill(20)(1))) + + // Combined doesn't equal hash or ephemeral + val v = combine(fromBytes(Array.fill(11)(0))) + + // Internal representation of combined of the above. + // (length 20, so it could be a hash). + val a = Array[Byte]( + 0, 0, 0, 1, // number of versions + 0, 0, 0, 12, // length of the version + 0x02, // type of the version (ephemeral) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // payload of the version + ) + + testNe(v, fromHash(a)) + testNe(v, fromBytes(a)) + } + + @Test + def testIsHash(): Unit = { + assertFalse(Unversioned.isHash) + assertFalse(fromBytes(Array()).isHash) + assertFalse(combine().isHash) + assertTrue(fromHash(Array.fill(20)(0)).isHash) + assertFalse(combine(fromHash(Array.fill(20)(0))).isHash) + } + + @Test + def testWriteHash(): Unit = { + val out = new ByteArrayOutputStream + + fromHash(Array.fill(20)(1)).writeHash(out) + + assertArrayEquals(Array.fill[Byte](20)(1), out.toByteArray()) + } +} diff --git a/javalib/src/main/scala/java/io/BufferedReader.scala b/javalib/src/main/scala/java/io/BufferedReader.scala index 3257c5b9d1..4c31435afa 100644 --- a/javalib/src/main/scala/java/io/BufferedReader.scala +++ b/javalib/src/main/scala/java/io/BufferedReader.scala @@ -16,7 +16,7 @@ class BufferedReader(in: Reader, sz: Int) extends Reader { def this(in: Reader) = this(in, 4096) - private[this] var buf = new Array[Char](sz) + private[this] var buf: Array[Char] = new Array[Char](sz) /** Last valid value in the buffer (exclusive) */ private[this] var end = 0 diff --git a/javalib/src/main/scala/java/io/FilterReader.scala b/javalib/src/main/scala/java/io/FilterReader.scala new file mode 100644 index 0000000000..810c875dde --- /dev/null +++ b/javalib/src/main/scala/java/io/FilterReader.scala @@ -0,0 +1,35 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.io + +abstract class FilterReader protected (protected val in: Reader) extends Reader { + + in.getClass() // null check + + override def close(): Unit = in.close() + + override def mark(readLimit: Int): Unit = in.mark(readLimit) + + override def markSupported(): Boolean = in.markSupported() + + override def read(): Int = in.read() + + override def read(buffer: Array[Char], offset: Int, count: Int): Int = + in.read(buffer, offset, count) + + override def ready(): Boolean = in.ready() + + override def reset(): Unit = in.reset() + + override def skip(count: Long): Long = in.skip(count) +} diff --git a/javalib/src/main/scala/java/io/InputStream.scala b/javalib/src/main/scala/java/io/InputStream.scala index 4259dc95a8..c6b706b7a5 100644 --- a/javalib/src/main/scala/java/io/InputStream.scala +++ b/javalib/src/main/scala/java/io/InputStream.scala @@ -12,6 +12,8 @@ package java.io +import java.util.Arrays + abstract class InputStream extends Closeable { def read(): Int @@ -44,6 +46,67 @@ abstract class InputStream extends Closeable { } } + def readAllBytes(): Array[Byte] = + readNBytes(Integer.MAX_VALUE) + + def readNBytes(len: Int): Array[Byte] = { + if (len < 0) { + throw new IllegalArgumentException + } else if (len == 0) { + new Array[Byte](0) + } else { + var bytesRead = 0 + + /* Allocate a buffer. + * + * Note that the implementation is required to grow memory proportional to + * the amount read, not the amount requested. Therefore, we cannot simply + * allocate an array of length len. + */ + var buf = new Array[Byte](Math.min(len, 1024)) + + var lastRead = 0 + + while (bytesRead < len && lastRead != -1) { + if (buf.length == bytesRead) { + /* Note that buf.length < Integer.MAX_VALUE, because: + * - bytesRead < len (loop condition) + * - len <= Integer.MAX_VALUE (because of its type) + */ + val newLen = + if (Integer.MAX_VALUE / 2 > buf.length) Integer.MAX_VALUE + else buf.length * 2 + buf = Arrays.copyOf(buf, Math.min(len, newLen)) + } + + lastRead = read(buf, bytesRead, buf.length - bytesRead) + if (lastRead > 0) + bytesRead += lastRead + } + + if (buf.length > bytesRead) + Arrays.copyOf(buf, bytesRead) + else + buf + } + } + + def readNBytes(b: Array[Byte], off: Int, len: Int): Int = { + if (off < 0 || len < 0 || len > b.length - off) + throw new IndexOutOfBoundsException + + var bytesRead = 0 + var lastRead = 0 + while (bytesRead < len && lastRead != -1) { + lastRead = read(b, off + bytesRead, len - bytesRead) + if (lastRead > 0) { + bytesRead += lastRead + } + } + + bytesRead + } + def skip(n: Long): Long = { var skipped = 0 while (skipped < n && read() != -1) @@ -51,6 +114,22 @@ abstract class InputStream extends Closeable { skipped } + def skipNBytes(n: Long): Unit = { + var remaining = n + while (remaining > 0) { + val skipped = skip(remaining) + if (skipped < 0 || skipped > remaining) { + throw new IOException + } else if (skipped == 0) { + if (read() == -1) + throw new EOFException + remaining -= 1 + } else { + remaining -= skipped + } + } + } + def available(): Int = 0 def close(): Unit = () @@ -62,4 +141,66 @@ abstract class InputStream extends Closeable { def markSupported(): Boolean = false + def transferTo(out: OutputStream): Long = { + out.getClass() // Trigger NPE (if enabled). + + var transferred = 0L + val buf = new Array[Byte](4096) + var bytesRead = 0 + + while (bytesRead != -1) { + bytesRead = read(buf) + if (bytesRead != -1) { + out.write(buf, 0, bytesRead) + transferred += bytesRead + } + } + + transferred + } +} + +object InputStream { + def nullInputStream(): InputStream = new InputStream { + private[this] var closed = false + + @inline + private def ensureOpen(): Unit = { + if (closed) + throw new IOException + } + + override def available(): Int = { + ensureOpen() + 0 + } + + def read(): Int = { + ensureOpen() + -1 + } + + override def readNBytes(n: Int): Array[Byte] = { + ensureOpen() + super.readNBytes(n) + } + + override def readNBytes(b: Array[Byte], off: Int, len: Int): Int = { + ensureOpen() + super.readNBytes(b, off, len) + } + + override def skip(n: Long): Long = { + ensureOpen() + 0L + } + + override def skipNBytes(n: Long): Unit = { + ensureOpen() + super.skipNBytes(n) + } + + override def close(): Unit = + closed = true + } } diff --git a/javalib/src/main/scala/java/io/OutputStream.scala b/javalib/src/main/scala/java/io/OutputStream.scala index af6c6b370c..a88173ebfd 100644 --- a/javalib/src/main/scala/java/io/OutputStream.scala +++ b/javalib/src/main/scala/java/io/OutputStream.scala @@ -35,3 +35,32 @@ abstract class OutputStream extends Object with Closeable with Flushable { def close(): Unit = () } + +object OutputStream { + def nullOutputStream(): OutputStream = new OutputStream { + private[this] var closed = false + + private def ensureOpen(): Unit = { + if (closed) + throw new IOException + } + + def write(b: Int): Unit = ensureOpen() + + override def write(b: Array[Byte]): Unit = { + ensureOpen() + + b.length // Null check + } + + override def write(b: Array[Byte], off: Int, len: Int): Unit = { + ensureOpen() + + if (off < 0 || len < 0 || len > b.length - off) + throw new IndexOutOfBoundsException() + } + + override def close(): Unit = + closed = true + } +} diff --git a/javalib/src/main/scala/java/io/PrintStream.scala b/javalib/src/main/scala/java/io/PrintStream.scala index d6141609c3..7868abd03c 100644 --- a/javalib/src/main/scala/java/io/PrintStream.scala +++ b/javalib/src/main/scala/java/io/PrintStream.scala @@ -79,9 +79,9 @@ class PrintStream private (_out: OutputStream, autoFlush: Boolean, private var errorFlag: Boolean = false override def flush(): Unit = - ensureOpenAndTrapIOExceptions(out.flush()) + ensureOpenAndTrapIOExceptions(() => out.flush()) - override def close(): Unit = trapIOExceptions { + override def close(): Unit = trapIOExceptions { () => if (!closing) { closing = true encoder.close() @@ -133,7 +133,7 @@ class PrintStream private (_out: OutputStream, autoFlush: Boolean, */ override def write(b: Int): Unit = { - ensureOpenAndTrapIOExceptions { + ensureOpenAndTrapIOExceptions { () => out.write(b) if (autoFlush && b == '\n') flush() @@ -141,7 +141,7 @@ class PrintStream private (_out: OutputStream, autoFlush: Boolean, } override def write(buf: Array[Byte], off: Int, len: Int): Unit = { - ensureOpenAndTrapIOExceptions { + ensureOpenAndTrapIOExceptions { () => out.write(buf, off, len) if (autoFlush) flush() @@ -157,17 +157,17 @@ class PrintStream private (_out: OutputStream, autoFlush: Boolean, def print(s: String): Unit = printString(if (s == null) "null" else s) def print(obj: AnyRef): Unit = printString(String.valueOf(obj)) - private def printString(s: String): Unit = ensureOpenAndTrapIOExceptions { + private def printString(s: String): Unit = ensureOpenAndTrapIOExceptions { () => encoder.write(s) encoder.flushBuffer() } - def print(s: Array[Char]): Unit = ensureOpenAndTrapIOExceptions { + def print(s: Array[Char]): Unit = ensureOpenAndTrapIOExceptions { () => encoder.write(s) encoder.flushBuffer() } - def println(): Unit = ensureOpenAndTrapIOExceptions { + def println(): Unit = ensureOpenAndTrapIOExceptions { () => encoder.write('\n') // In Scala.js the line separator is always LF encoder.flushBuffer() if (autoFlush) @@ -214,15 +214,15 @@ class PrintStream private (_out: OutputStream, autoFlush: Boolean, this } - @inline private[this] def trapIOExceptions(body: => Unit): Unit = { + @inline private[this] def trapIOExceptions(body: Runnable): Unit = { try { - body + body.run() } catch { case _: IOException => setError() } } - @inline private[this] def ensureOpenAndTrapIOExceptions(body: => Unit): Unit = { + @inline private[this] def ensureOpenAndTrapIOExceptions(body: Runnable): Unit = { if (closed) setError() else trapIOExceptions(body) } diff --git a/javalib/src/main/scala/java/io/PrintWriter.scala b/javalib/src/main/scala/java/io/PrintWriter.scala index 5e3facf333..57833e56f5 100644 --- a/javalib/src/main/scala/java/io/PrintWriter.scala +++ b/javalib/src/main/scala/java/io/PrintWriter.scala @@ -41,9 +41,9 @@ class PrintWriter(protected[io] var out: Writer, private var errorFlag: Boolean = false def flush(): Unit = - ensureOpenAndTrapIOExceptions(out.flush()) + ensureOpenAndTrapIOExceptions(() => out.flush()) - def close(): Unit = trapIOExceptions { + def close(): Unit = trapIOExceptions { () => if (!closed) { flush() closed = true @@ -76,19 +76,19 @@ class PrintWriter(protected[io] var out: Writer, protected[io] def clearError(): Unit = errorFlag = false override def write(c: Int): Unit = - ensureOpenAndTrapIOExceptions(out.write(c)) + ensureOpenAndTrapIOExceptions(() => out.write(c)) override def write(buf: Array[Char], off: Int, len: Int): Unit = - ensureOpenAndTrapIOExceptions(out.write(buf, off, len)) + ensureOpenAndTrapIOExceptions(() => out.write(buf, off, len)) override def write(buf: Array[Char]): Unit = - ensureOpenAndTrapIOExceptions(out.write(buf)) + ensureOpenAndTrapIOExceptions(() => out.write(buf)) override def write(s: String, off: Int, len: Int): Unit = - ensureOpenAndTrapIOExceptions(out.write(s, off, len)) + ensureOpenAndTrapIOExceptions(() => out.write(s, off, len)) override def write(s: String): Unit = - ensureOpenAndTrapIOExceptions(out.write(s)) + ensureOpenAndTrapIOExceptions(() => out.write(s)) def print(b: Boolean): Unit = write(String.valueOf(b)) def print(c: Char): Unit = write(c) @@ -147,15 +147,15 @@ class PrintWriter(protected[io] var out: Writer, this } - @inline private[this] def trapIOExceptions(body: => Unit): Unit = { + @inline private[this] def trapIOExceptions(body: Runnable): Unit = { try { - body + body.run() } catch { case _: IOException => setError() } } - @inline private[this] def ensureOpenAndTrapIOExceptions(body: => Unit): Unit = { + @inline private[this] def ensureOpenAndTrapIOExceptions(body: Runnable): Unit = { if (closed) setError() else trapIOExceptions(body) } diff --git a/javalanglib/src/main/scala/java/lang/Appendable.scala b/javalib/src/main/scala/java/lang/Appendable.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/Appendable.scala rename to javalib/src/main/scala/java/lang/Appendable.scala diff --git a/javalanglib/src/main/scala/java/lang/AutoCloseable.scala b/javalib/src/main/scala/java/lang/AutoCloseable.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/AutoCloseable.scala rename to javalib/src/main/scala/java/lang/AutoCloseable.scala diff --git a/javalanglib/src/main/scala/java/lang/Boolean.scala b/javalib/src/main/scala/java/lang/Boolean.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/Boolean.scala rename to javalib/src/main/scala/java/lang/Boolean.scala diff --git a/javalanglib/src/main/scala/java/lang/Byte.scala b/javalib/src/main/scala/java/lang/Byte.scala similarity index 97% rename from javalanglib/src/main/scala/java/lang/Byte.scala rename to javalib/src/main/scala/java/lang/Byte.scala index 2c563b9e73..ef2287af35 100644 --- a/javalanglib/src/main/scala/java/lang/Byte.scala +++ b/javalib/src/main/scala/java/lang/Byte.scala @@ -83,7 +83,7 @@ object Byte { def parseByte(s: String, radix: Int): scala.Byte = { val r = Integer.parseInt(s, radix) if (r < MIN_VALUE || r > MAX_VALUE) - throw new NumberFormatException("For input string: \"" + s + "\"") + throw new NumberFormatException(s"""For input string: "$s"""") else r.toByte } diff --git a/javalib/src/main/scala/java/lang/CharSequence.scala b/javalib/src/main/scala/java/lang/CharSequence.scala new file mode 100644 index 0000000000..0de3e83e49 --- /dev/null +++ b/javalib/src/main/scala/java/lang/CharSequence.scala @@ -0,0 +1,48 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.lang + +trait CharSequence { + def length(): scala.Int + def charAt(index: scala.Int): scala.Char + def subSequence(start: scala.Int, end: scala.Int): CharSequence + def toString(): String +} + +private[lang] object CharSequence { + /** Wraps an `Array[Char]` as a `CharSequence` to reuse algorithms. + * + * `subSequence` has an inefficient implementation. Avoid using this class + * for algorithms that use that method. + */ + @inline + private[lang] def ofArray(array: Array[Char]): OfArray = new OfArray(array) + + /** Wraps an `Array[Char]` as a `CharSequence` to reuse algorithms. + * + * `subSequence` has an inefficient implementation. Avoid using this class + * for algorithms that use that method. + */ + @inline + private[lang] final class OfArray(array: Array[Char]) extends CharSequence { + def length(): Int = array.length + def charAt(index: Int): Char = array(index) + + // This is not efficient but we do not actually use it + def subSequence(start: Int, end: Int): CharSequence = + new OfArray(java.util.Arrays.copyOfRange(array, start, end)) + + override def toString(): String = + String.valueOf(array) + } +} diff --git a/javalanglib/src/main/scala/java/lang/Character.scala b/javalib/src/main/scala/java/lang/Character.scala similarity index 58% rename from javalanglib/src/main/scala/java/lang/Character.scala rename to javalib/src/main/scala/java/lang/Character.scala index de01826d1b..a085f427d7 100644 --- a/javalanglib/src/main/scala/java/lang/Character.scala +++ b/javalib/src/main/scala/java/lang/Character.scala @@ -13,7 +13,10 @@ package java.lang import scala.annotation.{tailrec, switch} + import scala.scalajs.js +import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.ESVersion import java.lang.constant.Constable import java.util.{ArrayList, Arrays, HashMap} @@ -117,26 +120,26 @@ object Character { @inline def hashCode(value: Char): Int = value.toInt - @inline def toString(c: Char): String = { - js.Dynamic.global.String - .fromCharCode(c.toInt.asInstanceOf[js.Dynamic]) - .asInstanceOf[String] - } + @inline def toString(c: Char): String = + "" + c + // Wasm intrinsic def toString(codePoint: Int): String = { - if (isBmpCodePoint(codePoint)) { - js.Dynamic.global.String - .fromCharCode(codePoint.asInstanceOf[js.Dynamic]) - .asInstanceOf[String] - } else if (isValidCodePoint(codePoint)) { - js.Dynamic.global.String - .fromCharCode( - highSurrogate(codePoint).toInt.asInstanceOf[js.Dynamic], - lowSurrogate(codePoint).toInt.asInstanceOf[js.Dynamic] - ) - .asInstanceOf[String] - } else { + if (!isValidCodePoint(codePoint)) throw new IllegalArgumentException() + + if (LinkingInfo.esVersion >= ESVersion.ES2015) { + js.Dynamic.global.String.fromCodePoint(codePoint).asInstanceOf[String] + } else { + if (codePoint < MIN_SUPPLEMENTARY_CODE_POINT) { + js.Dynamic.global.String + .fromCharCode(codePoint) + .asInstanceOf[String] + } else { + js.Dynamic.global.String + .fromCharCode(highSurrogate(codePoint).toInt, lowSurrogate(codePoint).toInt) + .asInstanceOf[String] + } } } @@ -193,6 +196,74 @@ object Character { // Code point manipulation in character sequences --------------------------- + @noinline + def codePointAt(seq: CharSequence, index: Int): Int = + codePointAtImpl(seq, index) + + @noinline + def codePointAt(a: Array[Char], index: Int): Int = + codePointAtImpl(CharSequence.ofArray(a), index) + + @noinline + def codePointAt(a: Array[Char], index: Int, limit: Int): Int = { + // implicit null check and bounds check + if (!(limit <= a.length && 0 <= index && index < limit)) + throw new IndexOutOfBoundsException() + + if (index == limit - 1) + a(index).toInt // the only case where `limit` makes a difference + else + codePointAt(a, index) + } + + @inline + private[lang] def codePointAtImpl(seq: CharSequence, index: Int): Int = { + val high = seq.charAt(index) // implicit null check and bounds check + if (isHighSurrogate(high) && (index + 1 < seq.length())) { + val low = seq.charAt(index + 1) + if (isLowSurrogate(low)) + toCodePoint(high, low) + else + high.toInt + } else { + high.toInt + } + } + + @noinline + def codePointBefore(seq: CharSequence, index: Int): Int = + codePointBeforeImpl(seq, index) + + @noinline + def codePointBefore(a: Array[Char], index: Int): Int = + codePointBeforeImpl(CharSequence.ofArray(a), index) + + @noinline + def codePointBefore(a: Array[Char], index: Int, start: Int): Int = { + // implicit null check and bounds check + if (!(index <= a.length && 0 <= start && start < index)) + throw new IndexOutOfBoundsException() + + if (index == start + 1) + a(start).toInt // the only case where `start` makes a difference + else + codePointBefore(a, index) + } + + @inline + private[lang] def codePointBeforeImpl(seq: CharSequence, index: Int): Int = { + val low = seq.charAt(index - 1) // implicit null check and bounds check + if (isLowSurrogate(low) && index > 1) { + val high = seq.charAt(index - 2) + if (isHighSurrogate(high)) + toCodePoint(high, low) + else + low.toInt + } else { + low.toInt + } + } + def toChars(codePoint: Int, dst: Array[Char], dstIndex: Int): Int = { if (isBmpCodePoint(codePoint)) { dst(dstIndex) = codePoint.toChar @@ -215,20 +286,99 @@ object Character { throw new IllegalArgumentException() } - def codePointCount(seq: CharSequence, beginIndex: Int, endIndex: Int): Int = { + @noinline + def codePointCount(seq: CharSequence, beginIndex: Int, endIndex: Int): Int = + codePointCountImpl(seq, beginIndex, endIndex) + + @noinline + def codePointCount(a: Array[Char], offset: Int, count: Int): Int = + codePointCountImpl(CharSequence.ofArray(a), offset, offset + count) + + @inline + private[lang] def codePointCountImpl(seq: CharSequence, beginIndex: Int, endIndex: Int): Int = { + // Bounds check (and implicit null check) if (endIndex > seq.length() || beginIndex < 0 || endIndex < beginIndex) - throw new IndexOutOfBoundsException + throw new IndexOutOfBoundsException() + var res = endIndex - beginIndex var i = beginIndex val end = endIndex - 1 while (i < end) { - if (isSurrogatePair(seq.charAt(i), seq.charAt(i + 1))) + if (isHighSurrogate(seq.charAt(i)) && isLowSurrogate(seq.charAt(i + 1))) res -= 1 i += 1 } res } + @noinline + def offsetByCodePoints(seq: CharSequence, index: Int, codePointOffset: Int): Int = + offsetByCodePointsImpl(seq, index, codePointOffset) + + @noinline + def offsetByCodePoints(a: Array[Char], start: Int, count: Int, index: Int, + codePointOffset: Int): Int = { + + val len = a.length // implicit null check + + // Bounds check + val limit = start + count + if (start < 0 || count < 0 || limit > len || index < start || index > limit) + throw new IndexOutOfBoundsException() + + offsetByCodePointsInternal(CharSequence.ofArray(a), start, limit, index, codePointOffset) + } + + @inline + private[lang] def offsetByCodePointsImpl(seq: CharSequence, index: Int, codePointOffset: Int): Int = { + val len = seq.length() // implicit null check + + // Bounds check + if (index < 0 || index > len) + throw new IndexOutOfBoundsException() + + offsetByCodePointsInternal(seq, start = 0, limit = len, index, codePointOffset) + } + + @inline + private[lang] def offsetByCodePointsInternal(seq: CharSequence, start: Int, + limit: Int, index: Int, codePointOffset: Int): Int = { + + if (codePointOffset >= 0) { + var i = 0 + var result = index + while (i != codePointOffset) { + if (result >= limit) + throw new IndexOutOfBoundsException() + if ((result < limit - 1) && + isHighSurrogate(seq.charAt(result)) && + isLowSurrogate(seq.charAt(result + 1))) { + result += 2 + } else { + result += 1 + } + i += 1 + } + result + } else { + var i = 0 + var result = index + while (i != codePointOffset) { + if (result <= start) + throw new IndexOutOfBoundsException() + if ((result > start + 1) && + isLowSurrogate(seq.charAt(result - 1)) && + isHighSurrogate(seq.charAt(result - 2))) { + result -= 2 + } else { + result -= 1 + } + i -= 1 + } + result + } + } + // Unicode Character Database-related functions ----------------------------- def getType(ch: scala.Char): Int = getType(ch.toInt) @@ -597,12 +747,13 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { val upperCaseCP: Int = Character.toUpperCase(cp) if (titleCaseCP != upperCaseCP) { - println(s" case ${format(cp)} => ${format(titleCaseCP)}") + println(s" case ${format(cp)} => ${format(titleCaseCP)}") } } */ def toTitleCase(codePoint: scala.Int): scala.Int = { (codePoint: @switch) match { + // Begin Generated, last updated with Temurin-21+35 (build 21+35-LTS) case 0x01c4 => 0x01c5 case 0x01c5 => 0x01c5 case 0x01c6 => 0x01c5 @@ -615,7 +766,55 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { case 0x01f1 => 0x01f2 case 0x01f2 => 0x01f2 case 0x01f3 => 0x01f2 - case _ => toUpperCase(codePoint) + case 0x10d0 => 0x10d0 + case 0x10d1 => 0x10d1 + case 0x10d2 => 0x10d2 + case 0x10d3 => 0x10d3 + case 0x10d4 => 0x10d4 + case 0x10d5 => 0x10d5 + case 0x10d6 => 0x10d6 + case 0x10d7 => 0x10d7 + case 0x10d8 => 0x10d8 + case 0x10d9 => 0x10d9 + case 0x10da => 0x10da + case 0x10db => 0x10db + case 0x10dc => 0x10dc + case 0x10dd => 0x10dd + case 0x10de => 0x10de + case 0x10df => 0x10df + case 0x10e0 => 0x10e0 + case 0x10e1 => 0x10e1 + case 0x10e2 => 0x10e2 + case 0x10e3 => 0x10e3 + case 0x10e4 => 0x10e4 + case 0x10e5 => 0x10e5 + case 0x10e6 => 0x10e6 + case 0x10e7 => 0x10e7 + case 0x10e8 => 0x10e8 + case 0x10e9 => 0x10e9 + case 0x10ea => 0x10ea + case 0x10eb => 0x10eb + case 0x10ec => 0x10ec + case 0x10ed => 0x10ed + case 0x10ee => 0x10ee + case 0x10ef => 0x10ef + case 0x10f0 => 0x10f0 + case 0x10f1 => 0x10f1 + case 0x10f2 => 0x10f2 + case 0x10f3 => 0x10f3 + case 0x10f4 => 0x10f4 + case 0x10f5 => 0x10f5 + case 0x10f6 => 0x10f6 + case 0x10f7 => 0x10f7 + case 0x10f8 => 0x10f8 + case 0x10f9 => 0x10f9 + case 0x10fa => 0x10fa + case 0x10fd => 0x10fd + case 0x10fe => 0x10fe + case 0x10ff => 0x10ff + // End generated + + case _ => toUpperCase(codePoint) } } @@ -735,18 +934,18 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { historicalMap.get(b.toString) match { case Some((historicalName, properName)) => - println(s""" val $b = addUnicodeBlock("$properName", "$historicalName", $minCodePoint, $maxCodePoint)""") + println(s""" val $b = addUnicodeBlock("$properName", "$historicalName", $minCodePoint, $maxCodePoint)""") case None => val properBlockName = blockNameMap.getOrElse(b.toString, throw new IllegalArgumentException("$b")) val jvmBlockName = properBlockName.toUpperCase.replaceAll("[\\s\\-_]", "_") assert(jvmBlockName == b.toString) - println(s""" val $jvmBlockName = addUnicodeBlock("$properBlockName", $minCodePoint, $maxCodePoint)""") + println(s""" val $jvmBlockName = addUnicodeBlock("$properBlockName", $minCodePoint, $maxCodePoint)""") } } */ ////////////////////////////////////////////////////////////////////////// - // Begin Generated, last updated with (AdoptOpenJDK) (build 1.8.0_265-b01) + // Begin Generated, last updated with Temurin-21+35 (build 21+35-LTS) ////////////////////////////////////////////////////////////////////////// // scalastyle:off line.size.limit @@ -769,6 +968,8 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { val NKO = addUnicodeBlock("NKo", 0x07c0, 0x07ff) val SAMARITAN = addUnicodeBlock("Samaritan", 0x0800, 0x083f) val MANDAIC = addUnicodeBlock("Mandaic", 0x0840, 0x085f) + val SYRIAC_SUPPLEMENT = addUnicodeBlock("Syriac Supplement", 0x0860, 0x086f) + val ARABIC_EXTENDED_B = addUnicodeBlock("Arabic Extended-B", 0x0870, 0x089f) val ARABIC_EXTENDED_A = addUnicodeBlock("Arabic Extended-A", 0x08a0, 0x08ff) val DEVANAGARI = addUnicodeBlock("Devanagari", 0x0900, 0x097f) val BENGALI = addUnicodeBlock("Bengali", 0x0980, 0x09ff) @@ -805,11 +1006,14 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { val KHMER_SYMBOLS = addUnicodeBlock("Khmer Symbols", 0x19e0, 0x19ff) val BUGINESE = addUnicodeBlock("Buginese", 0x1a00, 0x1a1f) val TAI_THAM = addUnicodeBlock("Tai Tham", 0x1a20, 0x1aaf) + val COMBINING_DIACRITICAL_MARKS_EXTENDED = addUnicodeBlock("Combining Diacritical Marks Extended", 0x1ab0, 0x1aff) val BALINESE = addUnicodeBlock("Balinese", 0x1b00, 0x1b7f) val SUNDANESE = addUnicodeBlock("Sundanese", 0x1b80, 0x1bbf) val BATAK = addUnicodeBlock("Batak", 0x1bc0, 0x1bff) val LEPCHA = addUnicodeBlock("Lepcha", 0x1c00, 0x1c4f) val OL_CHIKI = addUnicodeBlock("Ol Chiki", 0x1c50, 0x1c7f) + val CYRILLIC_EXTENDED_C = addUnicodeBlock("Cyrillic Extended-C", 0x1c80, 0x1c8f) + val GEORGIAN_EXTENDED = addUnicodeBlock("Georgian Extended", 0x1c90, 0x1cbf) val SUNDANESE_SUPPLEMENT = addUnicodeBlock("Sundanese Supplement", 0x1cc0, 0x1ccf) val VEDIC_EXTENSIONS = addUnicodeBlock("Vedic Extensions", 0x1cd0, 0x1cff) val PHONETIC_EXTENSIONS = addUnicodeBlock("Phonetic Extensions", 0x1d00, 0x1d7f) @@ -883,11 +1087,14 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { val REJANG = addUnicodeBlock("Rejang", 0xa930, 0xa95f) val HANGUL_JAMO_EXTENDED_A = addUnicodeBlock("Hangul Jamo Extended-A", 0xa960, 0xa97f) val JAVANESE = addUnicodeBlock("Javanese", 0xa980, 0xa9df) + val MYANMAR_EXTENDED_B = addUnicodeBlock("Myanmar Extended-B", 0xa9e0, 0xa9ff) val CHAM = addUnicodeBlock("Cham", 0xaa00, 0xaa5f) val MYANMAR_EXTENDED_A = addUnicodeBlock("Myanmar Extended-A", 0xaa60, 0xaa7f) val TAI_VIET = addUnicodeBlock("Tai Viet", 0xaa80, 0xaadf) val MEETEI_MAYEK_EXTENSIONS = addUnicodeBlock("Meetei Mayek Extensions", 0xaae0, 0xaaff) val ETHIOPIC_EXTENDED_A = addUnicodeBlock("Ethiopic Extended-A", 0xab00, 0xab2f) + val LATIN_EXTENDED_E = addUnicodeBlock("Latin Extended-E", 0xab30, 0xab6f) + val CHEROKEE_SUPPLEMENT = addUnicodeBlock("Cherokee Supplement", 0xab70, 0xabbf) val MEETEI_MAYEK = addUnicodeBlock("Meetei Mayek", 0xabc0, 0xabff) val HANGUL_SYLLABLES = addUnicodeBlock("Hangul Syllables", 0xac00, 0xd7af) val HANGUL_JAMO_EXTENDED_B = addUnicodeBlock("Hangul Jamo Extended-B", 0xd7b0, 0xd7ff) @@ -914,44 +1121,132 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { val PHAISTOS_DISC = addUnicodeBlock("Phaistos Disc", 0x101d0, 0x101ff) val LYCIAN = addUnicodeBlock("Lycian", 0x10280, 0x1029f) val CARIAN = addUnicodeBlock("Carian", 0x102a0, 0x102df) + val COPTIC_EPACT_NUMBERS = addUnicodeBlock("Coptic Epact Numbers", 0x102e0, 0x102ff) val OLD_ITALIC = addUnicodeBlock("Old Italic", 0x10300, 0x1032f) val GOTHIC = addUnicodeBlock("Gothic", 0x10330, 0x1034f) + val OLD_PERMIC = addUnicodeBlock("Old Permic", 0x10350, 0x1037f) val UGARITIC = addUnicodeBlock("Ugaritic", 0x10380, 0x1039f) val OLD_PERSIAN = addUnicodeBlock("Old Persian", 0x103a0, 0x103df) val DESERET = addUnicodeBlock("Deseret", 0x10400, 0x1044f) val SHAVIAN = addUnicodeBlock("Shavian", 0x10450, 0x1047f) val OSMANYA = addUnicodeBlock("Osmanya", 0x10480, 0x104af) + val OSAGE = addUnicodeBlock("Osage", 0x104b0, 0x104ff) + val ELBASAN = addUnicodeBlock("Elbasan", 0x10500, 0x1052f) + val CAUCASIAN_ALBANIAN = addUnicodeBlock("Caucasian Albanian", 0x10530, 0x1056f) + val VITHKUQI = addUnicodeBlock("Vithkuqi", 0x10570, 0x105bf) + val LINEAR_A = addUnicodeBlock("Linear A", 0x10600, 0x1077f) + val LATIN_EXTENDED_F = addUnicodeBlock("Latin Extended-F", 0x10780, 0x107bf) val CYPRIOT_SYLLABARY = addUnicodeBlock("Cypriot Syllabary", 0x10800, 0x1083f) val IMPERIAL_ARAMAIC = addUnicodeBlock("Imperial Aramaic", 0x10840, 0x1085f) + val PALMYRENE = addUnicodeBlock("Palmyrene", 0x10860, 0x1087f) + val NABATAEAN = addUnicodeBlock("Nabataean", 0x10880, 0x108af) + val HATRAN = addUnicodeBlock("Hatran", 0x108e0, 0x108ff) val PHOENICIAN = addUnicodeBlock("Phoenician", 0x10900, 0x1091f) val LYDIAN = addUnicodeBlock("Lydian", 0x10920, 0x1093f) val MEROITIC_HIEROGLYPHS = addUnicodeBlock("Meroitic Hieroglyphs", 0x10980, 0x1099f) val MEROITIC_CURSIVE = addUnicodeBlock("Meroitic Cursive", 0x109a0, 0x109ff) val KHAROSHTHI = addUnicodeBlock("Kharoshthi", 0x10a00, 0x10a5f) val OLD_SOUTH_ARABIAN = addUnicodeBlock("Old South Arabian", 0x10a60, 0x10a7f) + val OLD_NORTH_ARABIAN = addUnicodeBlock("Old North Arabian", 0x10a80, 0x10a9f) + val MANICHAEAN = addUnicodeBlock("Manichaean", 0x10ac0, 0x10aff) val AVESTAN = addUnicodeBlock("Avestan", 0x10b00, 0x10b3f) val INSCRIPTIONAL_PARTHIAN = addUnicodeBlock("Inscriptional Parthian", 0x10b40, 0x10b5f) val INSCRIPTIONAL_PAHLAVI = addUnicodeBlock("Inscriptional Pahlavi", 0x10b60, 0x10b7f) + val PSALTER_PAHLAVI = addUnicodeBlock("Psalter Pahlavi", 0x10b80, 0x10baf) val OLD_TURKIC = addUnicodeBlock("Old Turkic", 0x10c00, 0x10c4f) + val OLD_HUNGARIAN = addUnicodeBlock("Old Hungarian", 0x10c80, 0x10cff) + val HANIFI_ROHINGYA = addUnicodeBlock("Hanifi Rohingya", 0x10d00, 0x10d3f) val RUMI_NUMERAL_SYMBOLS = addUnicodeBlock("Rumi Numeral Symbols", 0x10e60, 0x10e7f) + val YEZIDI = addUnicodeBlock("Yezidi", 0x10e80, 0x10ebf) + val ARABIC_EXTENDED_C = addUnicodeBlock("Arabic Extended-C", 0x10ec0, 0x10eff) + val OLD_SOGDIAN = addUnicodeBlock("Old Sogdian", 0x10f00, 0x10f2f) + val SOGDIAN = addUnicodeBlock("Sogdian", 0x10f30, 0x10f6f) + val OLD_UYGHUR = addUnicodeBlock("Old Uyghur", 0x10f70, 0x10faf) + val CHORASMIAN = addUnicodeBlock("Chorasmian", 0x10fb0, 0x10fdf) + val ELYMAIC = addUnicodeBlock("Elymaic", 0x10fe0, 0x10fff) val BRAHMI = addUnicodeBlock("Brahmi", 0x11000, 0x1107f) val KAITHI = addUnicodeBlock("Kaithi", 0x11080, 0x110cf) val SORA_SOMPENG = addUnicodeBlock("Sora Sompeng", 0x110d0, 0x110ff) val CHAKMA = addUnicodeBlock("Chakma", 0x11100, 0x1114f) + val MAHAJANI = addUnicodeBlock("Mahajani", 0x11150, 0x1117f) val SHARADA = addUnicodeBlock("Sharada", 0x11180, 0x111df) + val SINHALA_ARCHAIC_NUMBERS = addUnicodeBlock("Sinhala Archaic Numbers", 0x111e0, 0x111ff) + val KHOJKI = addUnicodeBlock("Khojki", 0x11200, 0x1124f) + val MULTANI = addUnicodeBlock("Multani", 0x11280, 0x112af) + val KHUDAWADI = addUnicodeBlock("Khudawadi", 0x112b0, 0x112ff) + val GRANTHA = addUnicodeBlock("Grantha", 0x11300, 0x1137f) + val NEWA = addUnicodeBlock("Newa", 0x11400, 0x1147f) + val TIRHUTA = addUnicodeBlock("Tirhuta", 0x11480, 0x114df) + val SIDDHAM = addUnicodeBlock("Siddham", 0x11580, 0x115ff) + val MODI = addUnicodeBlock("Modi", 0x11600, 0x1165f) + val MONGOLIAN_SUPPLEMENT = addUnicodeBlock("Mongolian Supplement", 0x11660, 0x1167f) val TAKRI = addUnicodeBlock("Takri", 0x11680, 0x116cf) + val AHOM = addUnicodeBlock("Ahom", 0x11700, 0x1174f) + val DOGRA = addUnicodeBlock("Dogra", 0x11800, 0x1184f) + val WARANG_CITI = addUnicodeBlock("Warang Citi", 0x118a0, 0x118ff) + val DIVES_AKURU = addUnicodeBlock("Dives Akuru", 0x11900, 0x1195f) + val NANDINAGARI = addUnicodeBlock("Nandinagari", 0x119a0, 0x119ff) + val ZANABAZAR_SQUARE = addUnicodeBlock("Zanabazar Square", 0x11a00, 0x11a4f) + val SOYOMBO = addUnicodeBlock("Soyombo", 0x11a50, 0x11aaf) + val UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A = addUnicodeBlock("Unified Canadian Aboriginal Syllabics Extended-A", 0x11ab0, 0x11abf) + val PAU_CIN_HAU = addUnicodeBlock("Pau Cin Hau", 0x11ac0, 0x11aff) + val DEVANAGARI_EXTENDED_A = addUnicodeBlock("Devanagari Extended-A", 0x11b00, 0x11b5f) + val BHAIKSUKI = addUnicodeBlock("Bhaiksuki", 0x11c00, 0x11c6f) + val MARCHEN = addUnicodeBlock("Marchen", 0x11c70, 0x11cbf) + val MASARAM_GONDI = addUnicodeBlock("Masaram Gondi", 0x11d00, 0x11d5f) + val GUNJALA_GONDI = addUnicodeBlock("Gunjala Gondi", 0x11d60, 0x11daf) + val MAKASAR = addUnicodeBlock("Makasar", 0x11ee0, 0x11eff) + val KAWI = addUnicodeBlock("Kawi", 0x11f00, 0x11f5f) + val LISU_SUPPLEMENT = addUnicodeBlock("Lisu Supplement", 0x11fb0, 0x11fbf) + val TAMIL_SUPPLEMENT = addUnicodeBlock("Tamil Supplement", 0x11fc0, 0x11fff) val CUNEIFORM = addUnicodeBlock("Cuneiform", 0x12000, 0x123ff) val CUNEIFORM_NUMBERS_AND_PUNCTUATION = addUnicodeBlock("Cuneiform Numbers and Punctuation", 0x12400, 0x1247f) + val EARLY_DYNASTIC_CUNEIFORM = addUnicodeBlock("Early Dynastic Cuneiform", 0x12480, 0x1254f) + val CYPRO_MINOAN = addUnicodeBlock("Cypro-Minoan", 0x12f90, 0x12fff) val EGYPTIAN_HIEROGLYPHS = addUnicodeBlock("Egyptian Hieroglyphs", 0x13000, 0x1342f) + val EGYPTIAN_HIEROGLYPH_FORMAT_CONTROLS = addUnicodeBlock("Egyptian Hieroglyph Format Controls", 0x13430, 0x1345f) + val ANATOLIAN_HIEROGLYPHS = addUnicodeBlock("Anatolian Hieroglyphs", 0x14400, 0x1467f) val BAMUM_SUPPLEMENT = addUnicodeBlock("Bamum Supplement", 0x16800, 0x16a3f) + val MRO = addUnicodeBlock("Mro", 0x16a40, 0x16a6f) + val TANGSA = addUnicodeBlock("Tangsa", 0x16a70, 0x16acf) + val BASSA_VAH = addUnicodeBlock("Bassa Vah", 0x16ad0, 0x16aff) + val PAHAWH_HMONG = addUnicodeBlock("Pahawh Hmong", 0x16b00, 0x16b8f) + val MEDEFAIDRIN = addUnicodeBlock("Medefaidrin", 0x16e40, 0x16e9f) val MIAO = addUnicodeBlock("Miao", 0x16f00, 0x16f9f) + val IDEOGRAPHIC_SYMBOLS_AND_PUNCTUATION = addUnicodeBlock("Ideographic Symbols and Punctuation", 0x16fe0, 0x16fff) + val TANGUT = addUnicodeBlock("Tangut", 0x17000, 0x187ff) + val TANGUT_COMPONENTS = addUnicodeBlock("Tangut Components", 0x18800, 0x18aff) + val KHITAN_SMALL_SCRIPT = addUnicodeBlock("Khitan Small Script", 0x18b00, 0x18cff) + val TANGUT_SUPPLEMENT = addUnicodeBlock("Tangut Supplement", 0x18d00, 0x18d7f) + val KANA_EXTENDED_B = addUnicodeBlock("Kana Extended-B", 0x1aff0, 0x1afff) val KANA_SUPPLEMENT = addUnicodeBlock("Kana Supplement", 0x1b000, 0x1b0ff) + val KANA_EXTENDED_A = addUnicodeBlock("Kana Extended-A", 0x1b100, 0x1b12f) + val SMALL_KANA_EXTENSION = addUnicodeBlock("Small Kana Extension", 0x1b130, 0x1b16f) + val NUSHU = addUnicodeBlock("Nushu", 0x1b170, 0x1b2ff) + val DUPLOYAN = addUnicodeBlock("Duployan", 0x1bc00, 0x1bc9f) + val SHORTHAND_FORMAT_CONTROLS = addUnicodeBlock("Shorthand Format Controls", 0x1bca0, 0x1bcaf) + val ZNAMENNY_MUSICAL_NOTATION = addUnicodeBlock("Znamenny Musical Notation", 0x1cf00, 0x1cfcf) val BYZANTINE_MUSICAL_SYMBOLS = addUnicodeBlock("Byzantine Musical Symbols", 0x1d000, 0x1d0ff) val MUSICAL_SYMBOLS = addUnicodeBlock("Musical Symbols", 0x1d100, 0x1d1ff) val ANCIENT_GREEK_MUSICAL_NOTATION = addUnicodeBlock("Ancient Greek Musical Notation", 0x1d200, 0x1d24f) + val KAKTOVIK_NUMERALS = addUnicodeBlock("Kaktovik Numerals", 0x1d2c0, 0x1d2df) + val MAYAN_NUMERALS = addUnicodeBlock("Mayan Numerals", 0x1d2e0, 0x1d2ff) val TAI_XUAN_JING_SYMBOLS = addUnicodeBlock("Tai Xuan Jing Symbols", 0x1d300, 0x1d35f) val COUNTING_ROD_NUMERALS = addUnicodeBlock("Counting Rod Numerals", 0x1d360, 0x1d37f) val MATHEMATICAL_ALPHANUMERIC_SYMBOLS = addUnicodeBlock("Mathematical Alphanumeric Symbols", 0x1d400, 0x1d7ff) + val SUTTON_SIGNWRITING = addUnicodeBlock("Sutton SignWriting", 0x1d800, 0x1daaf) + val LATIN_EXTENDED_G = addUnicodeBlock("Latin Extended-G", 0x1df00, 0x1dfff) + val GLAGOLITIC_SUPPLEMENT = addUnicodeBlock("Glagolitic Supplement", 0x1e000, 0x1e02f) + val CYRILLIC_EXTENDED_D = addUnicodeBlock("Cyrillic Extended-D", 0x1e030, 0x1e08f) + val NYIAKENG_PUACHUE_HMONG = addUnicodeBlock("Nyiakeng Puachue Hmong", 0x1e100, 0x1e14f) + val TOTO = addUnicodeBlock("Toto", 0x1e290, 0x1e2bf) + val WANCHO = addUnicodeBlock("Wancho", 0x1e2c0, 0x1e2ff) + val NAG_MUNDARI = addUnicodeBlock("Nag Mundari", 0x1e4d0, 0x1e4ff) + val ETHIOPIC_EXTENDED_B = addUnicodeBlock("Ethiopic Extended-B", 0x1e7e0, 0x1e7ff) + val MENDE_KIKAKUI = addUnicodeBlock("Mende Kikakui", 0x1e800, 0x1e8df) + val ADLAM = addUnicodeBlock("Adlam", 0x1e900, 0x1e95f) + val INDIC_SIYAQ_NUMBERS = addUnicodeBlock("Indic Siyaq Numbers", 0x1ec70, 0x1ecbf) + val OTTOMAN_SIYAQ_NUMBERS = addUnicodeBlock("Ottoman Siyaq Numbers", 0x1ed00, 0x1ed4f) val ARABIC_MATHEMATICAL_ALPHABETIC_SYMBOLS = addUnicodeBlock("Arabic Mathematical Alphabetic Symbols", 0x1ee00, 0x1eeff) val MAHJONG_TILES = addUnicodeBlock("Mahjong Tiles", 0x1f000, 0x1f02f) val DOMINO_TILES = addUnicodeBlock("Domino Tiles", 0x1f030, 0x1f09f) @@ -960,12 +1255,23 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { val ENCLOSED_IDEOGRAPHIC_SUPPLEMENT = addUnicodeBlock("Enclosed Ideographic Supplement", 0x1f200, 0x1f2ff) val MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS = addUnicodeBlock("Miscellaneous Symbols and Pictographs", 0x1f300, 0x1f5ff) val EMOTICONS = addUnicodeBlock("Emoticons", 0x1f600, 0x1f64f) + val ORNAMENTAL_DINGBATS = addUnicodeBlock("Ornamental Dingbats", 0x1f650, 0x1f67f) val TRANSPORT_AND_MAP_SYMBOLS = addUnicodeBlock("Transport and Map Symbols", 0x1f680, 0x1f6ff) val ALCHEMICAL_SYMBOLS = addUnicodeBlock("Alchemical Symbols", 0x1f700, 0x1f77f) + val GEOMETRIC_SHAPES_EXTENDED = addUnicodeBlock("Geometric Shapes Extended", 0x1f780, 0x1f7ff) + val SUPPLEMENTAL_ARROWS_C = addUnicodeBlock("Supplemental Arrows-C", 0x1f800, 0x1f8ff) + val SUPPLEMENTAL_SYMBOLS_AND_PICTOGRAPHS = addUnicodeBlock("Supplemental Symbols and Pictographs", 0x1f900, 0x1f9ff) + val CHESS_SYMBOLS = addUnicodeBlock("Chess Symbols", 0x1fa00, 0x1fa6f) + val SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A = addUnicodeBlock("Symbols and Pictographs Extended-A", 0x1fa70, 0x1faff) + val SYMBOLS_FOR_LEGACY_COMPUTING = addUnicodeBlock("Symbols for Legacy Computing", 0x1fb00, 0x1fbff) val CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B = addUnicodeBlock("CJK Unified Ideographs Extension B", 0x20000, 0x2a6df) val CJK_UNIFIED_IDEOGRAPHS_EXTENSION_C = addUnicodeBlock("CJK Unified Ideographs Extension C", 0x2a700, 0x2b73f) val CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D = addUnicodeBlock("CJK Unified Ideographs Extension D", 0x2b740, 0x2b81f) + val CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E = addUnicodeBlock("CJK Unified Ideographs Extension E", 0x2b820, 0x2ceaf) + val CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F = addUnicodeBlock("CJK Unified Ideographs Extension F", 0x2ceb0, 0x2ebef) val CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT = addUnicodeBlock("CJK Compatibility Ideographs Supplement", 0x2f800, 0x2fa1f) + val CJK_UNIFIED_IDEOGRAPHS_EXTENSION_G = addUnicodeBlock("CJK Unified Ideographs Extension G", 0x30000, 0x3134f) + val CJK_UNIFIED_IDEOGRAPHS_EXTENSION_H = addUnicodeBlock("CJK Unified Ideographs Extension H", 0x31350, 0x323af) val TAGS = addUnicodeBlock("Tags", 0xe0000, 0xe007f) val VARIATION_SELECTORS_SUPPLEMENT = addUnicodeBlock("Variation Selectors Supplement", 0xe0100, 0xe01ef) val SUPPLEMENTARY_PRIVATE_USE_AREA_A = addUnicodeBlock("Supplementary Private Use Area-A", 0xf0000, 0xfffff) @@ -1008,8 +1314,8 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { } } - // Based on Unicode 6.2.0, extended with chars 00BB, 20BC-20BF and 32FF - // Generated with OpenJDK 1.8.0_222 + // Based on Unicode 15.0 + // Generated with Temurin-21+35 (build 21+35-LTS) // Types of characters from 0 to 255 private[this] lazy val charTypesFirst256: Array[Int] = Array(15, 15, 15, 15, @@ -1036,7 +1342,7 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { * They where generated with the following script, which can be pasted into * a Scala REPL. -def formatLargeArray(array: Array[Int], indent: String): String = { +def formatLargeArrayStr(array: Array[String], indent: String): String = { val indentMinus1 = indent.substring(1) val builder = new java.lang.StringBuilder builder.append(indentMinus1) @@ -1054,6 +1360,9 @@ def formatLargeArray(array: Array[Int], indent: String): String = { builder.toString() } +def formatLargeArray(array: Array[Int], indent: String): String = + formatLargeArrayStr(array.map(_.toString()), indent) + val indicesAndTypes = (256 to Character.MAX_CODE_POINT) .map(i => (i, Character.getType(i))) .foldLeft[List[(Int, Int)]](Nil) { @@ -1091,137 +1400,172 @@ println(" )") 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 2, 1, 2, 2, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 69, 1, 27, 18, 4, 12, 14, 5, 7, 1, 1, 1, 17, 112, 1, 1, 1, 1, 1, 1, 1, - 1, 2, 1, 3, 1, 5, 2, 1, 1, 3, 1, 1, 1, 2, 1, 17, 1, 9, 35, 1, 2, 3, 3, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, - 1, 1, 1, 1, 1, 2, 2, 51, 48, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 2, 1, + 1, 2, 1, 3, 1, 1, 4, 2, 1, 1, 3, 1, 1, 1, 2, 1, 17, 1, 9, 35, 1, 2, 3, + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 1, 1, 1, 1, 1, 2, 2, 51, 48, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 38, 2, 1, 6, 1, 39, 1, 1, 1, 4, 1, - 1, 45, 1, 1, 1, 2, 1, 2, 1, 1, 8, 27, 5, 3, 2, 11, 5, 1, 3, 2, 1, 2, 2, - 11, 1, 2, 2, 32, 1, 10, 21, 10, 4, 2, 1, 99, 1, 1, 7, 1, 1, 6, 2, 2, 1, - 4, 2, 10, 3, 2, 1, 14, 1, 1, 1, 1, 30, 27, 2, 89, 11, 1, 14, 10, 33, 9, - 2, 1, 3, 1, 5, 22, 4, 1, 9, 1, 3, 1, 5, 2, 15, 1, 25, 3, 2, 1, 65, 1, - 1, 11, 55, 27, 1, 3, 1, 54, 1, 1, 1, 1, 3, 8, 4, 1, 2, 1, 7, 10, 2, 2, - 10, 1, 1, 6, 1, 7, 1, 1, 2, 1, 8, 2, 2, 2, 22, 1, 7, 1, 1, 3, 4, 2, 1, - 1, 3, 4, 2, 2, 2, 2, 1, 1, 8, 1, 4, 2, 1, 3, 2, 2, 10, 2, 2, 6, 1, 1, - 5, 2, 1, 1, 6, 4, 2, 2, 22, 1, 7, 1, 2, 1, 2, 1, 2, 2, 1, 1, 3, 2, 4, - 2, 2, 3, 3, 1, 7, 4, 1, 1, 7, 10, 2, 3, 1, 11, 2, 1, 1, 9, 1, 3, 1, 22, - 1, 7, 1, 2, 1, 5, 2, 1, 1, 3, 5, 1, 2, 1, 1, 2, 1, 2, 1, 15, 2, 2, 2, - 10, 1, 1, 15, 1, 2, 1, 8, 2, 2, 2, 22, 1, 7, 1, 2, 1, 5, 2, 1, 1, 1, 1, - 1, 4, 2, 2, 2, 2, 1, 8, 1, 1, 4, 2, 1, 3, 2, 2, 10, 1, 1, 6, 10, 1, 1, - 1, 6, 3, 3, 1, 4, 3, 2, 1, 1, 1, 2, 3, 2, 3, 3, 3, 12, 4, 2, 1, 2, 3, - 3, 1, 3, 1, 2, 1, 6, 1, 14, 10, 3, 6, 1, 1, 6, 3, 1, 8, 1, 3, 1, 23, 1, - 10, 1, 5, 3, 1, 3, 4, 1, 3, 1, 4, 7, 2, 1, 2, 6, 2, 2, 2, 10, 8, 7, 1, - 2, 2, 1, 8, 1, 3, 1, 23, 1, 10, 1, 5, 2, 1, 1, 1, 1, 5, 1, 1, 2, 1, 2, - 2, 7, 2, 7, 1, 1, 2, 2, 2, 10, 1, 2, 15, 2, 1, 8, 1, 3, 1, 41, 2, 1, 3, - 4, 1, 3, 1, 3, 1, 1, 8, 1, 8, 2, 2, 2, 10, 6, 3, 1, 6, 2, 2, 1, 18, 3, - 24, 1, 9, 1, 1, 2, 7, 3, 1, 4, 3, 3, 1, 1, 1, 8, 18, 2, 1, 12, 48, 1, - 2, 7, 4, 1, 6, 1, 8, 1, 10, 2, 37, 2, 1, 1, 2, 2, 1, 1, 2, 1, 6, 4, 1, - 7, 1, 3, 1, 1, 1, 1, 2, 2, 1, 4, 1, 2, 6, 1, 2, 1, 2, 5, 1, 1, 1, 6, 2, - 10, 2, 4, 32, 1, 3, 15, 1, 1, 3, 2, 6, 10, 10, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 2, 8, 1, 36, 4, 14, 1, 5, 1, 2, 5, 11, 1, 36, 1, 8, 1, 6, 1, 2, - 5, 4, 2, 37, 43, 2, 4, 1, 6, 1, 2, 2, 2, 1, 10, 6, 6, 2, 2, 4, 3, 1, 3, - 2, 7, 3, 4, 13, 1, 2, 2, 6, 1, 1, 1, 10, 3, 1, 2, 38, 1, 1, 5, 1, 2, - 43, 1, 1, 332, 1, 4, 2, 7, 1, 1, 1, 4, 2, 41, 1, 4, 2, 33, 1, 4, 2, 7, - 1, 1, 1, 4, 2, 15, 1, 57, 1, 4, 2, 67, 2, 3, 9, 20, 3, 16, 10, 6, 85, - 11, 1, 620, 2, 17, 1, 26, 1, 1, 3, 75, 3, 3, 15, 13, 1, 4, 3, 11, 18, - 3, 2, 9, 18, 2, 12, 13, 1, 3, 1, 2, 12, 52, 2, 1, 7, 8, 1, 2, 11, 3, 1, - 3, 1, 1, 1, 2, 10, 6, 10, 6, 6, 1, 4, 3, 1, 1, 10, 6, 35, 1, 52, 8, 41, - 1, 1, 5, 70, 10, 29, 3, 3, 4, 2, 3, 4, 2, 1, 6, 3, 4, 1, 3, 2, 10, 30, - 2, 5, 11, 44, 4, 17, 7, 2, 6, 10, 1, 3, 34, 23, 2, 3, 2, 2, 53, 1, 1, - 1, 7, 1, 1, 1, 1, 2, 8, 6, 10, 2, 1, 10, 6, 10, 6, 7, 1, 6, 82, 4, 1, - 47, 1, 1, 5, 1, 1, 5, 1, 2, 7, 4, 10, 7, 10, 9, 9, 3, 2, 1, 30, 1, 4, - 2, 2, 1, 1, 2, 2, 10, 44, 1, 1, 2, 3, 1, 1, 3, 2, 8, 4, 36, 8, 8, 2, 2, - 3, 5, 10, 3, 3, 10, 30, 6, 2, 64, 8, 8, 3, 1, 13, 1, 7, 4, 1, 4, 2, 1, - 2, 9, 44, 63, 13, 1, 34, 37, 39, 21, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 38, 2, + 1, 6, 41, 1, 1, 2, 2, 1, 1, 45, 1, 1, 1, 2, 1, 2, 1, 1, 8, 27, 4, 4, 2, + 11, 6, 3, 2, 1, 2, 2, 11, 1, 1, 3, 32, 1, 10, 21, 10, 4, 2, 1, 99, 1, + 1, 7, 1, 1, 6, 2, 2, 1, 4, 2, 10, 3, 2, 1, 14, 1, 1, 1, 1, 30, 27, 2, + 89, 11, 1, 14, 10, 33, 9, 2, 1, 3, 1, 2, 1, 2, 22, 4, 1, 9, 1, 3, 1, 5, + 2, 15, 1, 25, 3, 2, 1, 1, 11, 5, 24, 1, 6, 1, 2, 6, 8, 41, 1, 24, 1, + 32, 1, 54, 1, 1, 1, 1, 3, 8, 4, 1, 2, 1, 7, 10, 2, 2, 10, 1, 1, 15, 1, + 2, 1, 8, 2, 2, 2, 22, 1, 7, 1, 1, 3, 4, 2, 1, 1, 3, 4, 2, 2, 2, 2, 1, + 1, 8, 1, 4, 2, 1, 3, 2, 2, 10, 2, 2, 6, 1, 1, 1, 1, 1, 2, 2, 1, 1, 6, + 4, 2, 2, 22, 1, 7, 1, 2, 1, 2, 1, 2, 2, 1, 1, 3, 2, 4, 2, 2, 3, 3, 1, + 7, 4, 1, 1, 7, 10, 2, 3, 1, 1, 10, 2, 1, 1, 9, 1, 3, 1, 22, 1, 7, 1, 2, + 1, 5, 2, 1, 1, 3, 5, 1, 2, 1, 1, 2, 1, 2, 1, 15, 2, 2, 2, 10, 1, 1, 7, + 1, 6, 1, 1, 2, 1, 8, 2, 2, 2, 22, 1, 7, 1, 2, 1, 5, 2, 1, 1, 1, 1, 1, + 4, 2, 2, 2, 2, 1, 7, 2, 1, 4, 2, 1, 3, 2, 2, 10, 1, 1, 6, 10, 1, 1, 1, + 6, 3, 3, 1, 4, 3, 2, 1, 1, 1, 2, 3, 2, 3, 3, 3, 12, 4, 2, 1, 2, 3, 3, + 1, 3, 1, 2, 1, 6, 1, 14, 10, 3, 6, 1, 1, 5, 1, 3, 1, 8, 1, 3, 1, 23, 1, + 16, 2, 1, 1, 3, 4, 1, 3, 1, 4, 7, 2, 1, 3, 2, 1, 2, 2, 2, 2, 10, 7, 1, + 7, 1, 1, 1, 2, 1, 8, 1, 3, 1, 23, 1, 10, 1, 5, 2, 1, 1, 1, 1, 5, 1, 1, + 2, 1, 2, 2, 7, 2, 6, 2, 1, 2, 2, 2, 10, 1, 2, 1, 12, 2, 2, 9, 1, 3, 1, + 41, 2, 1, 3, 4, 1, 3, 1, 3, 1, 1, 1, 4, 3, 1, 7, 3, 2, 2, 10, 9, 1, 6, + 1, 1, 2, 1, 18, 3, 24, 1, 9, 1, 1, 2, 7, 3, 1, 4, 3, 3, 1, 1, 1, 8, 6, + 10, 2, 2, 1, 12, 48, 1, 2, 7, 4, 1, 6, 1, 8, 1, 10, 2, 37, 2, 1, 1, 1, + 5, 1, 24, 1, 1, 1, 10, 1, 2, 9, 1, 2, 5, 1, 1, 1, 7, 1, 10, 2, 4, 32, + 1, 3, 15, 1, 1, 3, 2, 6, 10, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 8, 1, + 36, 4, 14, 1, 5, 1, 2, 5, 11, 1, 36, 1, 8, 1, 6, 1, 2, 5, 4, 2, 37, 43, + 2, 4, 1, 6, 1, 2, 2, 2, 1, 10, 6, 6, 2, 2, 4, 3, 1, 3, 2, 7, 3, 4, 13, + 1, 2, 2, 6, 1, 1, 1, 10, 3, 1, 2, 38, 1, 1, 5, 1, 2, 43, 1, 1, 3, 329, + 1, 4, 2, 7, 1, 1, 1, 4, 2, 41, 1, 4, 2, 33, 1, 4, 2, 7, 1, 1, 1, 4, 2, + 15, 1, 57, 1, 4, 2, 67, 2, 3, 9, 20, 3, 16, 10, 6, 86, 2, 6, 2, 1, 620, + 1, 1, 17, 1, 26, 1, 1, 3, 75, 3, 3, 8, 7, 18, 3, 1, 9, 19, 2, 1, 2, 9, + 18, 2, 12, 13, 1, 3, 1, 2, 12, 52, 2, 1, 7, 8, 1, 2, 11, 3, 1, 3, 1, 1, + 1, 2, 10, 6, 10, 6, 6, 1, 4, 3, 1, 1, 10, 6, 35, 1, 53, 7, 5, 2, 34, 1, + 1, 5, 70, 10, 31, 1, 3, 4, 2, 3, 4, 2, 1, 6, 3, 4, 1, 3, 2, 10, 30, 2, + 5, 11, 44, 4, 26, 6, 10, 1, 3, 34, 23, 2, 2, 1, 2, 2, 53, 1, 1, 1, 7, + 1, 1, 1, 1, 2, 8, 6, 10, 2, 1, 10, 6, 10, 6, 7, 1, 6, 2, 14, 1, 16, 49, + 4, 1, 47, 1, 1, 5, 1, 1, 5, 1, 2, 8, 3, 10, 7, 10, 9, 9, 2, 1, 2, 1, + 30, 1, 4, 2, 2, 1, 3, 2, 10, 44, 1, 1, 2, 3, 1, 1, 3, 2, 8, 4, 36, 8, + 8, 2, 2, 3, 5, 10, 3, 3, 10, 30, 6, 2, 9, 7, 43, 2, 3, 8, 8, 3, 1, 13, + 1, 7, 4, 1, 6, 1, 2, 1, 2, 1, 5, 44, 63, 13, 1, 34, 37, 64, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 6, - 2, 6, 2, 8, 8, 8, 8, 6, 2, 6, 2, 8, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 14, - 2, 8, 8, 8, 8, 8, 8, 5, 1, 2, 4, 1, 1, 1, 3, 3, 1, 2, 4, 1, 3, 4, 2, 2, - 4, 1, 3, 8, 5, 3, 2, 3, 1, 2, 4, 1, 2, 1, 11, 5, 6, 2, 1, 1, 1, 2, 1, - 1, 1, 8, 1, 1, 5, 1, 9, 1, 1, 4, 2, 3, 1, 1, 1, 11, 1, 1, 1, 10, 1, 5, - 5, 6, 1, 1, 2, 6, 3, 1, 1, 1, 10, 3, 1, 1, 1, 13, 3, 32, 16, 13, 4, 1, - 3, 12, 15, 2, 1, 4, 1, 2, 1, 3, 2, 3, 1, 1, 1, 2, 1, 5, 6, 1, 1, 1, 1, - 1, 1, 4, 1, 1, 4, 1, 4, 1, 2, 2, 2, 5, 1, 4, 1, 1, 2, 1, 1, 16, 35, 1, - 1, 4, 1, 6, 5, 5, 2, 4, 1, 2, 1, 2, 1, 7, 1, 31, 2, 2, 1, 1, 1, 31, - 268, 8, 4, 20, 2, 7, 1, 1, 81, 1, 30, 25, 40, 6, 18, 12, 39, 25, 11, - 21, 60, 78, 22, 183, 1, 9, 1, 54, 8, 111, 1, 144, 1, 103, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 44, 5, 1, 1, 31, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 16, 256, 131, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 63, 1, 1, 1, 1, 32, 1, 1, 258, 48, 21, 2, 6, 3, 10, - 166, 47, 1, 47, 1, 1, 1, 3, 2, 1, 1, 1, 1, 1, 1, 4, 1, 1, 2, 1, 6, 2, - 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 9, 8, 6, 2, 6, 2, 8, 8, 8, 8, 6, 2, 6, 2, 8, 1, 1, 1, 1, 1, 1, + 1, 1, 8, 8, 14, 2, 8, 8, 8, 8, 8, 8, 5, 1, 2, 4, 1, 1, 1, 3, 3, 1, 2, + 4, 1, 3, 4, 2, 2, 4, 1, 3, 8, 5, 3, 2, 3, 1, 2, 4, 1, 2, 1, 11, 5, 6, + 2, 1, 1, 1, 2, 1, 1, 1, 8, 1, 1, 5, 1, 9, 1, 1, 4, 2, 3, 1, 1, 1, 11, + 1, 1, 1, 10, 1, 5, 1, 10, 1, 1, 2, 6, 3, 1, 1, 1, 10, 3, 1, 1, 1, 13, + 3, 33, 15, 13, 4, 1, 3, 12, 15, 2, 1, 4, 1, 2, 1, 3, 2, 3, 1, 1, 1, 2, + 1, 5, 6, 1, 1, 1, 1, 1, 1, 4, 1, 1, 4, 1, 4, 1, 2, 2, 2, 5, 1, 4, 1, 1, + 2, 1, 1, 16, 35, 1, 1, 4, 1, 2, 4, 5, 5, 2, 4, 1, 2, 1, 2, 1, 7, 1, 31, + 2, 2, 1, 1, 1, 31, 268, 8, 1, 1, 1, 1, 20, 2, 7, 1, 1, 81, 1, 30, 25, + 40, 6, 69, 25, 11, 21, 60, 78, 22, 183, 1, 9, 1, 54, 8, 111, 1, 248, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 44, 5, 1, 1, 31, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 16, 256, 131, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 63, 1, 1, 1, 1, 32, 1, 1, 258, 48, 21, 2, 6, + 39, 2, 32, 1, 105, 48, 48, 1, 1, 3, 2, 1, 1, 1, 1, 1, 1, 4, 1, 1, 2, 1, + 6, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 2, 6, 1, 1, 1, 1, 3, 1, 1, 5, 4, 1, 2, 38, 1, 1, 5, 1, 2, 56, - 7, 1, 1, 14, 1, 23, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, - 32, 2, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 9, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 5, 1, 10, 2, 68, 26, 1, 89, 12, 214, 26, 12, 4, 1, - 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 2, 1, 9, 4, 2, 1, 5, 2, 3, 1, 1, 1, 2, 1, 86, 2, 2, 2, 2, 1, 1, - 90, 1, 3, 1, 5, 41, 3, 94, 1, 2, 4, 10, 27, 5, 36, 12, 16, 31, 1, 10, - 30, 8, 1, 15, 32, 10, 39, 15, 320, 6582, 10, 64, 20941, 51, 21, 1, - 1143, 3, 55, 9, 40, 6, 2, 268, 1, 3, 16, 10, 2, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 10, 1, 1, + 1, 1, 1, 1, 1, 2, 6, 1, 1, 1, 1, 3, 1, 1, 5, 4, 1, 2, 38, 1, 1, 5, 1, + 2, 56, 7, 1, 1, 14, 1, 23, 9, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, + 7, 1, 32, 2, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 9, 1, 2, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 10, 2, 4, 1, 1, 1, 13, 2, 3, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 34, 26, 1, 89, 12, 214, 26, 12, 4, 1, 3, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 9, + 4, 2, 1, 5, 2, 3, 1, 1, 1, 2, 1, 86, 2, 2, 2, 2, 1, 1, 90, 1, 3, 1, 5, + 43, 1, 94, 1, 2, 4, 10, 32, 36, 12, 16, 31, 1, 10, 30, 8, 1, 15, 32, + 10, 39, 15, 320, 6592, 64, 21013, 1, 1143, 3, 55, 9, 40, 6, 2, 268, 1, + 3, 16, 10, 2, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 7, 1, 70, 10, 2, 6, 8, 23, 9, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 3, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 70, 10, 2, 6, 8, + 23, 9, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 2, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 77, 2, 1, 7, 1, 3, 1, 4, 1, 23, 2, 2, 1, 4, 4, 6, - 2, 1, 1, 6, 52, 4, 8, 2, 50, 16, 1, 9, 2, 10, 6, 18, 6, 3, 1, 4, 10, - 28, 8, 2, 23, 11, 2, 11, 1, 29, 3, 3, 1, 47, 1, 2, 4, 2, 1, 4, 13, 1, - 1, 10, 4, 2, 32, 41, 6, 2, 2, 2, 2, 9, 3, 1, 8, 1, 1, 2, 10, 2, 4, 16, - 1, 6, 3, 1, 1, 4, 48, 1, 1, 3, 2, 2, 5, 2, 1, 1, 1, 24, 2, 1, 2, 11, 1, - 2, 2, 2, 1, 2, 1, 1, 10, 6, 2, 6, 2, 6, 9, 7, 1, 7, 145, 35, 2, 1, 2, - 1, 2, 1, 1, 1, 2, 10, 6, 11172, 12, 23, 4, 49, 4, 2048, 6400, 366, 2, - 106, 38, 7, 12, 5, 5, 1, 1, 10, 1, 13, 1, 5, 1, 1, 1, 2, 1, 2, 1, 108, - 16, 17, 363, 1, 1, 16, 64, 2, 54, 40, 12, 1, 1, 2, 16, 7, 1, 1, 1, 6, - 7, 9, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, - 4, 3, 3, 1, 4, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 1, 2, 4, 5, 1, - 135, 2, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 2, 10, 2, 3, 2, 26, 1, 1, 1, - 1, 1, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 10, 1, 45, 2, 31, 3, 6, 2, - 6, 2, 6, 2, 3, 3, 2, 1, 1, 1, 2, 1, 1, 4, 2, 10, 3, 2, 2, 12, 1, 26, 1, - 19, 1, 2, 1, 15, 2, 14, 34, 123, 5, 3, 4, 45, 3, 9, 53, 4, 17, 1, 5, - 12, 52, 45, 1, 130, 29, 3, 49, 47, 31, 1, 4, 12, 17, 1, 8, 1, 53, 30, - 1, 1, 36, 4, 8, 1, 5, 42, 40, 40, 78, 2, 10, 854, 6, 2, 1, 1, 44, 1, 2, - 3, 1, 2, 23, 1, 1, 8, 160, 22, 6, 3, 1, 26, 5, 1, 64, 56, 6, 2, 64, 1, - 3, 1, 2, 5, 4, 4, 1, 3, 1, 27, 4, 3, 4, 1, 8, 8, 9, 7, 29, 2, 1, 128, - 54, 3, 7, 22, 2, 8, 19, 5, 8, 128, 73, 535, 31, 385, 1, 1, 1, 53, 15, - 7, 4, 20, 10, 16, 2, 1, 45, 3, 4, 2, 2, 2, 1, 4, 14, 25, 7, 10, 6, 3, - 36, 5, 1, 8, 1, 10, 4, 60, 2, 1, 48, 3, 9, 2, 4, 4, 7, 10, 1190, 43, 1, - 1, 1, 2, 6, 1, 1, 8, 10, 2358, 879, 145, 99, 13, 4, 2956, 1071, 13265, - 569, 1223, 69, 11, 1, 46, 16, 4, 13, 16480, 2, 8190, 246, 10, 39, 2, - 60, 2, 3, 3, 6, 8, 8, 2, 7, 30, 4, 48, 34, 66, 3, 1, 186, 87, 9, 18, - 142, 26, 26, 26, 7, 1, 18, 26, 26, 1, 1, 2, 2, 1, 2, 2, 2, 4, 1, 8, 4, - 1, 1, 1, 7, 1, 11, 26, 26, 2, 1, 4, 2, 8, 1, 7, 1, 26, 2, 1, 4, 1, 5, - 1, 1, 3, 7, 1, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 28, 2, - 25, 1, 25, 1, 6, 25, 1, 25, 1, 6, 25, 1, 25, 1, 6, 25, 1, 25, 1, 6, 25, - 1, 25, 1, 6, 1, 1, 2, 50, 5632, 4, 1, 27, 1, 2, 1, 1, 2, 1, 1, 10, 1, - 4, 1, 1, 1, 1, 6, 1, 4, 1, 1, 1, 1, 1, 1, 3, 1, 2, 1, 1, 2, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 4, 1, 7, 1, 4, 1, 4, 1, 1, 1, 10, 1, 17, - 5, 3, 1, 5, 1, 17, 52, 2, 270, 44, 4, 100, 12, 15, 2, 14, 2, 15, 1, 15, - 32, 11, 5, 31, 1, 60, 4, 43, 75, 29, 13, 43, 5, 9, 7, 2, 174, 33, 15, - 6, 1, 70, 3, 20, 12, 37, 1, 5, 21, 17, 15, 63, 1, 1, 1, 182, 1, 4, 3, - 62, 2, 4, 12, 24, 147, 70, 4, 11, 48, 70, 58, 116, 2188, 42711, 41, - 4149, 11, 222, 16354, 542, 722403, 1, 30, 96, 128, 240, 65040, 65534, - 2, 65534 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 5, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 4, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24, 3, 1, 1, 1, 2, 1, + 7, 1, 3, 1, 4, 1, 23, 2, 2, 1, 4, 1, 3, 6, 2, 1, 1, 6, 52, 4, 8, 2, 50, + 16, 2, 8, 2, 10, 6, 18, 6, 3, 1, 1, 2, 1, 10, 28, 8, 2, 23, 11, 2, 11, + 1, 29, 3, 3, 1, 47, 1, 2, 4, 2, 2, 3, 13, 1, 1, 10, 4, 2, 5, 1, 1, 9, + 10, 5, 1, 41, 6, 2, 2, 2, 2, 9, 3, 1, 8, 1, 1, 2, 10, 2, 4, 16, 1, 6, + 3, 1, 1, 1, 1, 50, 1, 1, 3, 2, 2, 5, 2, 1, 1, 1, 24, 2, 1, 2, 11, 1, 2, + 2, 2, 1, 2, 1, 1, 10, 6, 2, 6, 2, 6, 9, 7, 1, 7, 1, 43, 1, 4, 9, 1, 2, + 4, 80, 35, 2, 1, 2, 1, 2, 1, 1, 1, 2, 10, 6, 11172, 12, 23, 4, 49, 4, + 2048, 6400, 366, 2, 106, 38, 7, 12, 5, 5, 1, 1, 10, 1, 13, 1, 5, 1, 1, + 1, 2, 1, 2, 1, 108, 17, 16, 363, 1, 1, 16, 64, 2, 54, 7, 1, 32, 12, 1, + 3, 16, 7, 1, 1, 1, 6, 16, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 2, 1, 1, 4, 3, 3, 1, 4, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, + 1, 1, 2, 4, 5, 1, 135, 2, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 2, 10, 2, 3, + 2, 26, 1, 1, 1, 1, 1, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 10, 1, 45, + 2, 31, 3, 6, 2, 6, 2, 6, 2, 3, 3, 2, 1, 1, 1, 2, 1, 1, 4, 2, 10, 3, 2, + 2, 12, 1, 26, 1, 19, 1, 2, 1, 15, 2, 14, 34, 123, 5, 3, 4, 45, 3, 9, + 53, 4, 17, 2, 3, 1, 13, 3, 1, 47, 45, 1, 130, 29, 3, 49, 15, 1, 27, 4, + 32, 4, 9, 20, 1, 8, 1, 5, 38, 5, 5, 30, 1, 1, 36, 4, 8, 1, 5, 42, 40, + 40, 78, 2, 10, 6, 36, 4, 36, 4, 40, 8, 52, 11, 1, 11, 1, 15, 1, 7, 1, + 2, 1, 11, 1, 15, 1, 7, 1, 2, 67, 311, 9, 22, 10, 8, 24, 6, 1, 42, 1, 9, + 69, 6, 2, 1, 1, 44, 1, 2, 3, 1, 2, 23, 1, 1, 8, 23, 2, 7, 31, 8, 9, 48, + 19, 1, 2, 5, 5, 22, 6, 3, 1, 26, 5, 1, 64, 56, 4, 2, 2, 16, 2, 46, 1, + 3, 1, 2, 5, 4, 4, 1, 3, 1, 29, 2, 3, 4, 1, 9, 7, 9, 7, 29, 2, 1, 29, 3, + 32, 8, 1, 28, 2, 4, 5, 7, 9, 54, 3, 7, 22, 2, 8, 19, 5, 8, 18, 7, 4, + 12, 7, 80, 73, 55, 51, 13, 51, 7, 6, 36, 4, 8, 10, 294, 31, 1, 42, 1, + 2, 1, 2, 2, 75, 3, 29, 10, 1, 8, 22, 11, 4, 5, 22, 18, 4, 4, 38, 21, 7, + 20, 23, 9, 1, 1, 1, 53, 15, 7, 4, 20, 10, 1, 2, 2, 1, 9, 3, 1, 45, 3, + 4, 2, 2, 2, 1, 4, 1, 10, 1, 2, 25, 7, 10, 6, 3, 36, 5, 1, 8, 1, 10, 4, + 1, 2, 1, 8, 35, 1, 2, 1, 9, 2, 1, 48, 3, 9, 2, 4, 4, 4, 1, 1, 1, 10, 1, + 1, 1, 3, 1, 20, 11, 18, 1, 25, 3, 3, 2, 1, 1, 2, 6, 1, 2, 1, 62, 7, 1, + 1, 1, 4, 1, 15, 1, 10, 1, 6, 47, 1, 3, 8, 5, 10, 6, 2, 2, 1, 8, 2, 2, + 2, 22, 1, 7, 1, 2, 1, 5, 1, 2, 1, 2, 1, 4, 2, 2, 2, 3, 2, 1, 6, 1, 5, + 5, 2, 2, 7, 3, 5, 139, 53, 3, 8, 2, 3, 1, 1, 4, 5, 10, 2, 1, 1, 1, 3, + 30, 48, 3, 6, 1, 1, 4, 2, 1, 2, 2, 1, 1, 8, 10, 166, 47, 3, 4, 2, 4, 2, + 1, 2, 23, 4, 2, 34, 48, 3, 8, 2, 1, 1, 2, 3, 1, 11, 10, 6, 13, 19, 43, + 1, 1, 1, 2, 6, 1, 1, 1, 1, 6, 10, 54, 27, 2, 3, 2, 4, 1, 5, 4, 10, 2, + 3, 1, 7, 185, 44, 3, 9, 1, 2, 1, 100, 32, 32, 10, 9, 12, 8, 2, 1, 2, 8, + 1, 2, 1, 24, 6, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 3, 9, 10, 70, 8, 2, + 39, 3, 4, 2, 2, 4, 1, 1, 1, 1, 1, 27, 1, 10, 40, 6, 1, 1, 4, 8, 1, 8, + 1, 6, 2, 3, 46, 13, 1, 2, 3, 1, 5, 13, 73, 7, 10, 246, 9, 1, 37, 1, 7, + 1, 6, 1, 1, 1, 5, 10, 10, 19, 3, 2, 30, 2, 22, 1, 1, 7, 1, 2, 1, 2, 73, + 7, 1, 2, 1, 38, 6, 3, 1, 1, 2, 1, 7, 1, 1, 8, 10, 6, 6, 1, 2, 1, 32, 5, + 1, 2, 1, 2, 1, 1, 1, 1, 7, 10, 310, 19, 2, 2, 2, 7, 2, 1, 1, 13, 1, 34, + 2, 5, 3, 2, 1, 1, 1, 13, 10, 86, 1, 15, 21, 8, 4, 17, 13, 1, 922, 102, + 111, 1, 5, 11, 196, 2636, 97, 2, 13, 1072, 16, 1, 6, 15, 4010, 583, + 8633, 569, 7, 31, 1, 10, 4, 2, 79, 1, 10, 6, 30, 2, 5, 1, 10, 48, 7, 5, + 4, 4, 1, 1, 10, 10, 1, 7, 1, 21, 5, 19, 688, 32, 32, 23, 4, 101, 75, 4, + 1, 1, 55, 7, 4, 13, 64, 2, 1, 1, 1, 11, 2, 14, 6136, 8, 1238, 42, 9, + 8935, 4, 1, 7, 1, 2, 1, 291, 15, 1, 29, 3, 2, 1, 14, 4, 8, 396, 2308, + 107, 5, 13, 3, 9, 7, 10, 2, 1, 2, 1, 4, 4700, 46, 2, 23, 9, 116, 60, + 246, 10, 39, 2, 60, 2, 3, 3, 6, 8, 8, 2, 7, 30, 4, 61, 21, 66, 3, 1, + 122, 20, 12, 20, 12, 87, 9, 25, 135, 26, 26, 26, 7, 1, 18, 26, 26, 1, + 1, 2, 2, 1, 2, 2, 2, 4, 1, 8, 4, 1, 1, 1, 7, 1, 11, 26, 26, 2, 1, 4, 2, + 8, 1, 7, 1, 26, 2, 1, 4, 1, 5, 1, 1, 3, 7, 1, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 28, 2, 25, 1, 25, 1, 6, 25, 1, 25, 1, 6, 25, 1, + 25, 1, 6, 25, 1, 25, 1, 6, 25, 1, 25, 1, 6, 1, 1, 2, 50, 512, 55, 4, + 50, 8, 1, 14, 1, 2, 5, 15, 5, 1, 15, 1104, 10, 1, 20, 6, 6, 213, 7, 1, + 17, 2, 7, 1, 2, 1, 5, 5, 62, 33, 1, 112, 45, 3, 7, 7, 2, 10, 4, 1, 1, + 320, 30, 1, 17, 44, 4, 10, 5, 1, 464, 27, 1, 4, 10, 742, 7, 1, 4, 1, 2, + 1, 15, 1, 197, 2, 9, 7, 41, 34, 34, 7, 1, 4, 10, 4, 2, 785, 59, 1, 3, + 1, 4, 76, 45, 1, 15, 194, 4, 1, 27, 1, 2, 1, 1, 2, 1, 1, 10, 1, 4, 1, + 1, 1, 1, 6, 1, 4, 1, 1, 1, 1, 1, 1, 3, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 2, 1, 1, 2, 4, 1, 7, 1, 4, 1, 4, 1, 1, 1, 10, 1, 17, 5, 3, + 1, 5, 1, 17, 52, 2, 270, 44, 4, 100, 12, 15, 2, 15, 1, 15, 1, 37, 10, + 13, 161, 56, 29, 13, 44, 4, 9, 7, 2, 14, 6, 154, 251, 5, 728, 4, 17, 3, + 13, 3, 119, 4, 95, 6, 12, 4, 1, 15, 12, 4, 56, 8, 10, 6, 40, 8, 30, 2, + 2, 78, 340, 12, 14, 2, 13, 3, 9, 7, 46, 1, 7, 8, 14, 4, 9, 7, 9, 7, + 147, 1, 55, 37, 10, 1030, 42720, 32, 4154, 6, 222, 2, 5762, 14, 7473, + 3103, 542, 1506, 4939, 5, 4192, 711761, 1, 30, 96, 128, 240, 65040, + 65534, 2, 65534 ) uncompressDeltas(deltas) } @@ -1240,134 +1584,167 @@ println(" )") 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 4, 27, 4, 27, 4, 27, 4, 27, 4, 27, 6, 1, 2, 1, 2, 4, 27, 1, 2, 0, - 4, 2, 24, 0, 27, 1, 24, 1, 0, 1, 0, 1, 2, 1, 0, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 25, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 28, 6, 7, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 0, 1, 0, 4, 24, 0, 2, 0, 24, 20, 0, 26, 0, 6, 20, - 6, 24, 6, 24, 6, 24, 6, 0, 5, 0, 5, 24, 0, 16, 0, 25, 24, 26, 24, 28, 6, - 24, 0, 24, 5, 4, 5, 6, 9, 24, 5, 6, 5, 24, 5, 6, 16, 28, 6, 4, 6, 28, 6, - 5, 9, 5, 28, 5, 24, 0, 16, 5, 6, 5, 6, 0, 5, 6, 5, 0, 9, 5, 6, 4, 28, 24, - 4, 0, 5, 6, 4, 6, 4, 6, 4, 6, 0, 24, 0, 5, 6, 0, 24, 0, 5, 0, 5, 0, 6, 0, - 6, 8, 5, 6, 8, 6, 5, 8, 6, 8, 6, 8, 5, 6, 5, 6, 24, 9, 24, 4, 5, 0, 5, 0, - 6, 8, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 6, 5, 8, 6, 0, 8, 0, 8, 6, - 5, 0, 8, 0, 5, 0, 5, 6, 0, 9, 5, 26, 11, 28, 26, 0, 6, 8, 0, 5, 0, 5, 0, - 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 6, 0, 8, 6, 0, 6, 0, 6, 0, 6, 0, 5, 0, 5, - 0, 9, 6, 5, 6, 0, 6, 8, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 6, 5, 8, - 6, 0, 6, 8, 0, 8, 6, 0, 5, 0, 5, 6, 0, 9, 24, 26, 0, 6, 8, 0, 5, 0, 5, 0, - 5, 0, 5, 0, 5, 0, 5, 0, 6, 5, 8, 6, 8, 6, 0, 8, 0, 8, 6, 0, 6, 8, 0, 5, - 0, 5, 6, 0, 9, 28, 5, 11, 0, 6, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, - 5, 0, 5, 0, 5, 0, 8, 6, 8, 0, 8, 0, 8, 6, 0, 5, 0, 8, 0, 9, 11, 28, 26, - 28, 0, 8, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 6, 8, 0, 6, 0, 6, 0, 6, 0, - 5, 0, 5, 6, 0, 9, 0, 11, 28, 0, 8, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 6, 5, - 8, 6, 8, 0, 6, 8, 0, 8, 6, 0, 8, 0, 5, 0, 5, 6, 0, 9, 0, 5, 0, 8, 0, 5, - 0, 5, 0, 5, 0, 5, 8, 6, 0, 8, 0, 8, 6, 5, 0, 8, 0, 5, 6, 0, 9, 11, 0, 28, - 5, 0, 8, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 6, 0, 8, 6, 0, 6, 0, 8, 0, 8, - 24, 0, 5, 6, 5, 6, 0, 26, 5, 4, 6, 24, 9, 24, 0, 5, 0, 5, 0, 5, 0, 5, 0, - 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 6, 5, 6, 0, 6, 5, 0, 5, 0, - 4, 0, 6, 0, 9, 0, 5, 0, 5, 28, 24, 28, 24, 28, 6, 28, 9, 11, 28, 6, 28, - 6, 28, 6, 21, 22, 21, 22, 8, 5, 0, 5, 0, 6, 8, 6, 24, 6, 5, 6, 0, 6, 0, - 28, 6, 28, 0, 28, 24, 28, 24, 0, 5, 8, 6, 8, 6, 8, 6, 8, 6, 5, 9, 24, 5, - 8, 6, 5, 6, 5, 8, 5, 8, 5, 6, 5, 6, 8, 6, 8, 6, 5, 8, 9, 8, 6, 28, 1, 0, - 1, 0, 1, 0, 5, 24, 4, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, - 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 6, 24, 11, 0, 5, 28, 0, 5, - 0, 20, 5, 24, 5, 12, 5, 21, 22, 0, 5, 24, 10, 0, 5, 0, 5, 6, 0, 5, 6, 24, + 4, 2, 24, 1, 0, 27, 1, 24, 1, 0, 1, 0, 1, 2, 1, 0, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 25, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 28, 6, 7, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 0, 1, 0, 4, 24, 2, 24, + 20, 0, 28, 26, 0, 6, 20, 6, 24, 6, 24, 6, 24, 6, 0, 5, 0, 5, 24, 0, 16, + 25, 24, 26, 24, 28, 6, 24, 16, 24, 5, 4, 5, 6, 9, 24, 5, 6, 5, 24, 5, 6, + 16, 28, 6, 4, 6, 28, 6, 5, 9, 5, 28, 5, 24, 0, 16, 5, 6, 5, 6, 0, 5, 6, + 5, 0, 9, 5, 6, 4, 28, 24, 4, 0, 6, 26, 5, 6, 4, 6, 4, 6, 4, 6, 0, 24, 0, + 5, 6, 0, 24, 0, 5, 0, 5, 27, 5, 0, 16, 0, 6, 5, 4, 6, 16, 6, 8, 5, 6, 8, + 6, 5, 8, 6, 8, 6, 8, 5, 6, 5, 6, 24, 9, 24, 4, 5, 6, 8, 0, 5, 0, 5, 0, 5, + 0, 5, 0, 5, 0, 5, 0, 6, 5, 8, 6, 0, 8, 0, 8, 6, 5, 0, 8, 0, 5, 0, 5, 6, + 0, 9, 5, 26, 11, 28, 26, 5, 24, 6, 0, 6, 8, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, + 0, 5, 0, 5, 0, 6, 0, 8, 6, 0, 6, 0, 6, 0, 6, 0, 5, 0, 5, 0, 9, 6, 5, 6, + 24, 0, 6, 8, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 6, 5, 8, 6, 0, 6, 8, + 0, 8, 6, 0, 5, 0, 5, 6, 0, 9, 24, 26, 0, 5, 6, 0, 6, 8, 0, 5, 0, 5, 0, 5, + 0, 5, 0, 5, 0, 5, 0, 6, 5, 8, 6, 8, 6, 0, 8, 0, 8, 6, 0, 6, 8, 0, 5, 0, + 5, 6, 0, 9, 28, 5, 11, 0, 6, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, + 0, 5, 0, 5, 0, 8, 6, 8, 0, 8, 0, 8, 6, 0, 5, 0, 8, 0, 9, 11, 28, 26, 28, + 0, 6, 8, 6, 5, 0, 5, 0, 5, 0, 5, 0, 6, 5, 6, 8, 0, 6, 0, 6, 0, 6, 0, 5, + 0, 5, 0, 5, 6, 0, 9, 0, 24, 11, 28, 5, 6, 8, 24, 5, 0, 5, 0, 5, 0, 5, 0, + 5, 0, 6, 5, 8, 6, 8, 0, 6, 8, 0, 8, 6, 0, 8, 0, 5, 0, 5, 6, 0, 9, 0, 5, + 8, 0, 6, 8, 5, 0, 5, 0, 5, 6, 5, 8, 6, 0, 8, 0, 8, 6, 5, 28, 0, 5, 8, 11, + 5, 6, 0, 9, 11, 28, 5, 0, 6, 8, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 6, 0, 8, + 6, 0, 6, 0, 8, 0, 9, 0, 8, 24, 0, 5, 6, 5, 6, 0, 26, 5, 4, 6, 24, 9, 24, + 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 6, 5, 6, 5, 0, 5, 0, 4, 0, 6, 0, 9, + 0, 5, 0, 5, 28, 24, 28, 24, 28, 6, 28, 9, 11, 28, 6, 28, 6, 28, 6, 21, + 22, 21, 22, 8, 5, 0, 5, 0, 6, 8, 6, 24, 6, 5, 6, 0, 6, 0, 28, 6, 28, 0, + 28, 24, 28, 24, 0, 5, 8, 6, 8, 6, 8, 6, 8, 6, 5, 9, 24, 5, 8, 6, 5, 6, 5, + 8, 5, 8, 5, 6, 5, 6, 8, 6, 8, 6, 5, 8, 9, 8, 6, 28, 1, 0, 1, 0, 1, 0, 2, + 24, 4, 2, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, + 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 6, 24, 11, 0, 5, 28, 0, 1, 0, 2, 0, 20, + 5, 28, 24, 5, 12, 5, 21, 22, 0, 5, 24, 10, 5, 0, 5, 6, 8, 0, 5, 6, 8, 24, 0, 5, 6, 0, 5, 0, 5, 0, 6, 0, 5, 6, 8, 6, 8, 6, 8, 6, 24, 4, 24, 26, 5, - 6, 0, 9, 0, 11, 0, 24, 20, 24, 6, 12, 0, 9, 0, 5, 4, 5, 0, 5, 6, 5, 0, 5, - 0, 5, 0, 6, 8, 6, 8, 0, 8, 6, 8, 6, 0, 28, 0, 24, 9, 5, 0, 5, 0, 5, 0, 8, - 5, 8, 0, 9, 11, 0, 28, 5, 6, 8, 0, 24, 5, 8, 6, 8, 6, 0, 6, 8, 6, 8, 6, - 8, 6, 0, 6, 9, 0, 9, 0, 24, 4, 24, 0, 6, 8, 5, 6, 8, 6, 8, 6, 8, 6, 8, 5, - 0, 9, 24, 28, 6, 28, 0, 6, 8, 5, 8, 6, 8, 6, 8, 6, 8, 5, 9, 5, 6, 8, 6, - 8, 6, 8, 6, 8, 0, 24, 5, 8, 6, 8, 6, 0, 24, 9, 0, 5, 9, 5, 4, 24, 0, 24, - 0, 6, 24, 6, 8, 6, 5, 6, 5, 8, 6, 5, 0, 2, 4, 2, 4, 2, 4, 6, 0, 6, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 0, 1, 0, 2, 1, 2, 1, 2, 0, 1, 0, 2, 0, 1, 0, 1, - 0, 1, 0, 1, 2, 1, 2, 0, 2, 3, 2, 3, 2, 3, 2, 0, 2, 1, 3, 27, 2, 27, 2, 0, - 2, 1, 3, 27, 2, 0, 2, 1, 0, 27, 2, 1, 27, 0, 2, 0, 2, 1, 3, 27, 0, 12, - 16, 20, 24, 29, 30, 21, 29, 30, 21, 29, 24, 13, 14, 16, 12, 24, 29, 30, - 24, 23, 24, 25, 21, 22, 24, 25, 24, 23, 24, 12, 16, 0, 16, 11, 4, 0, 11, - 25, 21, 22, 4, 11, 25, 21, 22, 0, 4, 0, 26, 0, 6, 7, 6, 7, 6, 0, 28, 1, - 28, 1, 28, 2, 1, 2, 1, 2, 28, 1, 28, 25, 1, 28, 1, 28, 1, 28, 1, 28, 1, - 28, 2, 1, 2, 5, 2, 28, 2, 1, 25, 1, 2, 28, 25, 28, 2, 28, 11, 10, 1, 2, - 10, 11, 0, 25, 28, 25, 28, 25, 28, 25, 28, 25, 28, 25, 28, 25, 28, 25, - 28, 25, 28, 25, 28, 25, 28, 25, 28, 21, 22, 28, 25, 28, 25, 28, 25, 28, - 0, 28, 0, 28, 0, 11, 28, 11, 28, 25, 28, 25, 28, 25, 28, 25, 28, 0, 28, - 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 11, 28, 25, 21, - 22, 25, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 25, 28, 25, 21, 22, 21, - 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, - 22, 25, 21, 22, 21, 22, 25, 21, 22, 25, 28, 25, 28, 25, 0, 28, 0, 1, 0, - 2, 0, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 4, 1, 2, 1, 2, 1, + 6, 0, 9, 0, 11, 0, 24, 20, 24, 6, 16, 6, 9, 0, 5, 4, 5, 0, 5, 6, 5, 6, 5, + 0, 5, 0, 5, 0, 6, 8, 6, 8, 0, 8, 6, 8, 6, 0, 28, 0, 24, 9, 5, 0, 5, 0, 5, + 0, 5, 0, 9, 11, 0, 28, 5, 6, 8, 6, 0, 24, 5, 8, 6, 8, 6, 0, 6, 8, 6, 8, + 6, 8, 6, 0, 6, 9, 0, 9, 0, 24, 4, 24, 0, 6, 7, 6, 0, 6, 8, 5, 6, 8, 6, 8, + 6, 8, 6, 8, 5, 0, 9, 24, 28, 6, 28, 24, 0, 6, 8, 5, 8, 6, 8, 6, 8, 6, 5, + 9, 5, 6, 8, 6, 8, 6, 8, 6, 8, 0, 24, 5, 8, 6, 8, 6, 0, 24, 9, 0, 5, 9, 5, + 4, 24, 2, 0, 1, 0, 1, 24, 0, 6, 24, 6, 8, 6, 5, 6, 5, 6, 5, 8, 6, 5, 0, + 2, 4, 2, 4, 2, 4, 6, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 28, - 1, 2, 1, 2, 6, 1, 2, 0, 24, 11, 24, 2, 0, 2, 0, 2, 0, 5, 0, 4, 24, 0, 6, - 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 6, 24, 29, 30, 29, - 30, 24, 29, 30, 24, 29, 30, 24, 20, 24, 20, 24, 29, 30, 24, 29, 30, 21, - 22, 21, 22, 21, 22, 21, 22, 24, 4, 24, 20, 0, 28, 0, 28, 0, 28, 0, 28, 0, - 12, 24, 28, 4, 5, 10, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 28, 21, 22, - 21, 22, 21, 22, 21, 22, 20, 21, 22, 28, 10, 6, 8, 20, 4, 28, 10, 4, 5, - 24, 28, 0, 5, 0, 6, 27, 4, 5, 20, 5, 24, 4, 5, 0, 5, 0, 5, 0, 28, 11, 28, - 5, 0, 28, 0, 5, 28, 0, 11, 28, 11, 28, 11, 28, 11, 28, 11, 28, 5, 0, 28, - 5, 0, 5, 4, 5, 0, 28, 0, 5, 4, 24, 5, 4, 24, 5, 9, 5, 0, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 5, 6, 7, 24, 6, 24, 4, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 0, 6, 5, 10, 6, 24, 0, 27, 4, 27, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 4, 2, 1, 2, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 2, 4, 27, 1, 2, 1, 2, 0, 1, 2, 1, 2, 0, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 0, 4, 2, 5, 6, 5, 6, 5, 6, 5, 8, 6, 8, 28, 0, 11, 28, - 26, 28, 0, 5, 24, 0, 8, 5, 8, 6, 0, 24, 9, 0, 6, 5, 24, 5, 0, 9, 5, 6, - 24, 5, 6, 8, 0, 24, 5, 0, 6, 8, 5, 6, 8, 6, 8, 6, 8, 24, 0, 4, 9, 0, 24, - 0, 5, 6, 8, 6, 8, 6, 0, 5, 6, 5, 6, 8, 0, 9, 0, 24, 5, 4, 5, 28, 5, 8, 0, - 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 0, 5, 4, 24, 5, 8, 6, 8, 24, 5, 4, 8, 6, - 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 8, 6, 8, 6, 8, 24, 8, 6, 0, 9, 0, 5, - 0, 5, 0, 5, 0, 19, 18, 5, 0, 5, 0, 2, 0, 2, 0, 5, 6, 5, 25, 5, 0, 5, 0, - 5, 0, 5, 0, 5, 0, 5, 27, 0, 5, 21, 22, 0, 5, 0, 5, 0, 5, 26, 28, 0, 6, - 24, 21, 22, 24, 0, 6, 0, 24, 20, 23, 21, 22, 21, 22, 21, 22, 21, 22, 21, - 22, 21, 22, 21, 22, 21, 22, 24, 21, 22, 24, 23, 24, 0, 24, 20, 21, 22, - 21, 22, 21, 22, 24, 25, 20, 25, 0, 24, 26, 24, 0, 5, 0, 5, 0, 16, 0, 24, - 26, 24, 21, 22, 24, 25, 24, 20, 24, 9, 24, 25, 24, 1, 21, 24, 22, 27, 23, - 27, 2, 21, 25, 22, 25, 21, 22, 24, 21, 22, 24, 5, 4, 5, 4, 5, 0, 5, 0, 5, - 0, 5, 0, 5, 0, 26, 25, 27, 28, 26, 0, 28, 25, 28, 0, 16, 28, 0, 5, 0, 5, - 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 24, 0, 11, 0, 28, 10, 11, 28, 11, 0, 28, - 0, 28, 6, 0, 5, 0, 5, 0, 5, 0, 11, 0, 5, 10, 5, 10, 0, 5, 0, 24, 5, 0, 5, - 24, 10, 0, 1, 2, 5, 0, 9, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 24, 11, - 0, 5, 11, 0, 24, 5, 0, 24, 0, 5, 0, 5, 0, 5, 6, 0, 6, 0, 6, 5, 0, 5, 0, - 5, 0, 6, 0, 6, 11, 0, 24, 0, 5, 11, 24, 0, 5, 0, 24, 5, 0, 11, 5, 0, 11, - 0, 5, 0, 11, 0, 8, 6, 8, 5, 6, 24, 0, 11, 9, 0, 6, 8, 5, 8, 6, 8, 6, 24, - 16, 24, 0, 5, 0, 9, 0, 6, 5, 6, 8, 6, 0, 9, 24, 0, 6, 8, 5, 8, 6, 8, 5, - 24, 0, 9, 0, 5, 6, 8, 6, 8, 6, 8, 6, 0, 9, 0, 5, 0, 10, 0, 24, 0, 5, 0, - 5, 0, 5, 0, 5, 8, 0, 6, 4, 0, 5, 0, 28, 0, 28, 0, 28, 8, 6, 28, 8, 16, 6, - 28, 6, 28, 6, 28, 0, 28, 6, 28, 0, 28, 0, 11, 0, 1, 2, 1, 2, 0, 2, 1, 2, - 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 2, 0, 2, 0, 2, 0, 2, 1, 2, 1, 0, 1, 0, - 1, 0, 1, 0, 2, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 0, 1, 25, 2, 25, 2, 1, 25, 2, 25, 2, 1, 25, 2, 25, 2, 1, 25, - 2, 25, 2, 1, 25, 2, 25, 2, 1, 2, 0, 9, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, - 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, - 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, - 5, 0, 5, 0, 5, 0, 5, 0, 25, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, - 11, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, - 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, - 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 5, 0, 5, 0, 5, 0, 5, 0, 16, 0, 16, 0, - 6, 0, 18, 0, 18, 0 + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 0, 1, 0, 2, 1, 2, 1, + 2, 0, 1, 0, 2, 0, 1, 0, 1, 0, 1, 0, 1, 2, 1, 2, 0, 2, 3, 2, 3, 2, 3, 2, + 0, 2, 1, 3, 27, 2, 27, 2, 0, 2, 1, 3, 27, 2, 0, 2, 1, 0, 27, 2, 1, 27, 0, + 2, 0, 2, 1, 3, 27, 0, 12, 16, 20, 24, 29, 30, 21, 29, 30, 21, 29, 24, 13, + 14, 16, 12, 24, 29, 30, 24, 23, 24, 25, 21, 22, 24, 25, 24, 23, 24, 12, + 16, 0, 16, 11, 4, 0, 11, 25, 21, 22, 4, 11, 25, 21, 22, 0, 4, 0, 26, 0, + 6, 7, 6, 7, 6, 0, 28, 1, 28, 1, 28, 2, 1, 2, 1, 2, 28, 1, 28, 25, 1, 28, + 1, 28, 1, 28, 1, 28, 1, 28, 2, 1, 2, 5, 2, 28, 2, 1, 25, 1, 2, 28, 25, + 28, 2, 28, 11, 10, 1, 2, 10, 11, 28, 0, 25, 28, 25, 28, 25, 28, 25, 28, + 25, 28, 25, 28, 25, 28, 25, 28, 25, 28, 25, 28, 21, 22, 21, 22, 28, 25, + 28, 21, 22, 28, 25, 28, 25, 28, 25, 28, 0, 28, 0, 11, 28, 11, 28, 25, 28, + 25, 28, 25, 28, 25, 28, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, + 21, 22, 11, 28, 25, 21, 22, 25, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, + 25, 28, 25, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, + 22, 21, 22, 21, 22, 21, 22, 25, 21, 22, 21, 22, 25, 21, 22, 25, 28, 25, + 28, 25, 28, 0, 28, 0, 28, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 4, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 28, 1, 2, 1, 2, 6, 1, 2, 0, 24, 11, 24, 2, 0, 2, 0, + 2, 0, 5, 0, 4, 24, 0, 6, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, + 5, 0, 6, 24, 29, 30, 29, 30, 24, 29, 30, 24, 29, 30, 24, 20, 24, 20, 24, + 29, 30, 24, 29, 30, 21, 22, 21, 22, 21, 22, 21, 22, 24, 4, 24, 20, 24, + 20, 24, 21, 24, 28, 24, 21, 22, 21, 22, 21, 22, 21, 22, 20, 0, 28, 0, 28, + 0, 28, 0, 28, 0, 12, 24, 28, 4, 5, 10, 21, 22, 21, 22, 21, 22, 21, 22, + 21, 22, 28, 21, 22, 21, 22, 21, 22, 21, 22, 20, 21, 22, 28, 10, 6, 8, 20, + 4, 28, 10, 4, 5, 24, 28, 0, 5, 0, 6, 27, 4, 5, 20, 5, 24, 4, 5, 0, 5, 0, + 5, 0, 28, 11, 28, 5, 28, 0, 5, 28, 0, 11, 28, 11, 28, 11, 28, 11, 28, 11, + 28, 5, 28, 5, 4, 5, 0, 28, 0, 5, 4, 24, 5, 4, 24, 5, 9, 5, 0, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 5, 6, 7, 24, 6, 24, + 4, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 4, 6, 5, 10, 6, 24, 0, 27, 4, 27, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 4, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 4, 27, 1, 2, 1, 2, 5, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 0, 1, 2, 0, 2, 0, 2, + 1, 2, 1, 2, 0, 4, 1, 2, 5, 4, 2, 5, 6, 5, 6, 5, 6, 5, 8, 6, 8, 28, 6, 0, + 11, 28, 26, 28, 0, 5, 24, 0, 8, 5, 8, 6, 0, 24, 9, 0, 6, 5, 24, 5, 24, 5, + 6, 9, 5, 6, 24, 5, 6, 8, 0, 24, 5, 0, 6, 8, 5, 6, 8, 6, 8, 6, 8, 24, 0, + 4, 9, 0, 24, 5, 6, 4, 5, 9, 5, 0, 5, 6, 8, 6, 8, 6, 0, 5, 6, 5, 6, 8, 0, + 9, 0, 24, 5, 4, 5, 28, 5, 8, 6, 8, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6, 5, 0, 5, + 4, 24, 5, 8, 6, 8, 24, 5, 4, 8, 6, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 2, + 27, 4, 2, 4, 27, 0, 2, 5, 8, 6, 8, 6, 8, 24, 8, 6, 0, 9, 0, 5, 0, 5, 0, + 5, 0, 19, 18, 5, 0, 5, 0, 2, 0, 2, 0, 5, 6, 5, 25, 5, 0, 5, 0, 5, 0, 5, + 0, 5, 0, 5, 27, 0, 5, 22, 21, 28, 5, 0, 5, 0, 28, 0, 5, 26, 28, 6, 24, + 21, 22, 24, 0, 6, 24, 20, 23, 21, 22, 21, 22, 21, 22, 21, 22, 21, 22, 21, + 22, 21, 22, 21, 22, 24, 21, 22, 24, 23, 24, 0, 24, 20, 21, 22, 21, 22, + 21, 22, 24, 25, 20, 25, 0, 24, 26, 24, 0, 5, 0, 5, 0, 16, 0, 24, 26, 24, + 21, 22, 24, 25, 24, 20, 24, 9, 24, 25, 24, 1, 21, 24, 22, 27, 23, 27, 2, + 21, 25, 22, 25, 21, 22, 24, 21, 22, 24, 5, 4, 5, 4, 5, 0, 5, 0, 5, 0, 5, + 0, 5, 0, 26, 25, 27, 28, 26, 0, 28, 25, 28, 0, 16, 28, 0, 5, 0, 5, 0, 5, + 0, 5, 0, 5, 0, 5, 0, 5, 0, 24, 0, 11, 0, 28, 10, 11, 28, 11, 28, 0, 28, + 0, 28, 0, 28, 6, 0, 5, 0, 5, 0, 6, 11, 0, 5, 11, 0, 5, 10, 5, 10, 0, 5, + 6, 0, 5, 0, 24, 5, 0, 5, 24, 10, 0, 1, 2, 5, 0, 9, 0, 1, 0, 2, 0, 5, 0, + 5, 0, 24, 1, 0, 1, 0, 1, 0, 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 5, 0, 5, 0, 5, + 0, 4, 0, 4, 0, 4, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 24, 11, 5, 28, + 11, 5, 0, 11, 0, 5, 0, 5, 0, 11, 5, 11, 0, 24, 5, 0, 24, 0, 5, 0, 11, 5, + 11, 0, 11, 5, 6, 0, 6, 0, 6, 5, 0, 5, 0, 5, 0, 6, 0, 6, 11, 0, 24, 0, 5, + 11, 24, 5, 11, 0, 5, 28, 5, 6, 0, 11, 24, 0, 5, 0, 24, 5, 0, 11, 5, 0, + 11, 5, 0, 24, 0, 11, 0, 5, 0, 1, 0, 2, 0, 11, 5, 6, 0, 9, 0, 11, 0, 5, 0, + 6, 20, 0, 5, 0, 6, 5, 11, 5, 0, 5, 6, 11, 24, 0, 5, 6, 24, 0, 5, 11, 0, + 5, 0, 8, 6, 8, 5, 6, 24, 0, 11, 9, 6, 5, 6, 5, 0, 6, 8, 5, 8, 6, 8, 6, + 24, 16, 24, 6, 0, 16, 0, 5, 0, 9, 0, 6, 5, 6, 8, 6, 0, 9, 24, 5, 8, 5, 0, + 5, 6, 24, 5, 0, 6, 8, 5, 8, 6, 8, 5, 24, 6, 24, 8, 6, 9, 5, 24, 5, 24, 0, + 11, 0, 5, 0, 5, 8, 6, 8, 6, 8, 6, 24, 6, 5, 6, 0, 5, 0, 5, 0, 5, 0, 5, 0, + 5, 24, 0, 5, 6, 8, 6, 0, 9, 0, 6, 8, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, + 0, 6, 5, 8, 6, 8, 0, 8, 0, 8, 0, 5, 0, 8, 0, 5, 8, 0, 6, 0, 6, 0, 5, 8, + 6, 8, 6, 8, 6, 5, 24, 9, 24, 0, 24, 6, 5, 0, 5, 8, 6, 8, 6, 8, 6, 8, 6, + 5, 24, 5, 0, 9, 0, 5, 8, 6, 0, 8, 6, 8, 6, 24, 5, 6, 0, 5, 8, 6, 8, 6, 8, + 6, 24, 5, 0, 9, 0, 24, 0, 5, 6, 8, 6, 8, 6, 8, 6, 5, 24, 0, 9, 0, 5, 0, + 6, 8, 6, 8, 6, 0, 9, 11, 24, 28, 5, 0, 5, 8, 6, 8, 6, 24, 0, 1, 2, 9, 11, + 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 8, 0, 8, 0, 6, 8, 6, 5, 8, 5, 8, 6, 24, 0, + 9, 0, 5, 0, 5, 8, 6, 0, 6, 8, 6, 5, 24, 5, 8, 0, 5, 6, 5, 6, 8, 5, 6, 24, + 6, 0, 5, 6, 8, 6, 5, 6, 8, 6, 24, 5, 24, 0, 5, 0, 24, 0, 5, 0, 5, 8, 6, + 0, 6, 8, 6, 5, 24, 0, 9, 11, 0, 24, 5, 0, 6, 0, 8, 6, 8, 6, 8, 6, 0, 5, + 0, 5, 0, 5, 6, 0, 6, 0, 6, 0, 6, 5, 6, 0, 9, 0, 5, 0, 5, 0, 5, 8, 0, 6, + 0, 8, 6, 8, 6, 5, 0, 9, 0, 5, 6, 8, 24, 0, 6, 5, 8, 5, 0, 5, 8, 6, 0, 8, + 6, 8, 6, 24, 9, 0, 5, 0, 11, 28, 26, 28, 0, 24, 5, 0, 10, 0, 24, 0, 5, 0, + 5, 24, 0, 5, 16, 6, 5, 6, 0, 5, 0, 5, 0, 5, 0, 9, 0, 24, 5, 0, 9, 0, 5, + 0, 6, 24, 0, 5, 6, 24, 28, 4, 24, 28, 0, 9, 0, 11, 0, 5, 0, 5, 0, 1, 2, + 11, 24, 0, 5, 0, 6, 5, 8, 0, 6, 4, 0, 4, 24, 4, 6, 0, 8, 0, 5, 0, 5, 0, + 5, 0, 4, 0, 4, 0, 4, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, + 5, 0, 5, 0, 28, 6, 24, 16, 0, 6, 0, 6, 0, 28, 0, 28, 0, 28, 0, 28, 8, 6, + 28, 8, 16, 6, 28, 6, 28, 6, 28, 0, 28, 6, 28, 0, 11, 0, 11, 0, 28, 0, 11, + 0, 1, 2, 1, 2, 0, 2, 1, 2, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 2, 0, 2, 0, + 2, 0, 2, 1, 2, 1, 0, 1, 0, 1, 0, 1, 0, 2, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 0, 1, 25, 2, 25, 2, 1, 25, 2, 25, + 2, 1, 25, 2, 25, 2, 1, 25, 2, 25, 2, 1, 25, 2, 25, 2, 1, 2, 0, 9, 28, 6, + 28, 6, 28, 6, 28, 6, 28, 24, 0, 6, 0, 6, 0, 2, 5, 2, 0, 2, 0, 6, 0, 6, 0, + 6, 0, 6, 0, 6, 0, 4, 0, 6, 0, 5, 0, 6, 4, 0, 9, 0, 5, 28, 0, 5, 6, 0, 5, + 6, 9, 0, 26, 0, 5, 4, 6, 9, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 11, 6, 0, 1, + 2, 6, 4, 0, 9, 0, 24, 0, 11, 28, 11, 26, 11, 0, 11, 28, 11, 0, 5, 0, 5, + 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, + 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, + 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 25, 0, 28, 0, 28, 0, 28, 0, + 28, 0, 28, 0, 28, 0, 11, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, + 27, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, + 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, 0, 28, + 0, 28, 0, 28, 0, 28, 0, 9, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, + 5, 0, 16, 0, 16, 0, 6, 0, 18, 0, 18, 0 ) /* Indices representing the start of ranges of codePoint that have the same @@ -1399,12 +1776,12 @@ println(" )") 2, 3, 4, 1, 4, 1, 1, 1, 1, 4, 9, 5, 1, 1, 18, 5, 4, 9, 2, 1, 1, 1, 8, 2, 31, 2, 4, 5, 1, 9, 2, 2, 19, 5, 2, 9, 5, 2, 2, 4, 24, 2, 16, 8, 4, 20, 2, 7, 2, 1085, 14, 74, 1, 2, 4, 1, 2, 1, 3, 5, 4, 5, 3, 3, 14, 403, - 22, 2, 21, 8, 1, 7, 6, 3, 1, 4, 5, 1, 2, 2, 5, 4, 1, 1, 3, 2, 2, 10, 6, - 2, 2, 12, 19, 1, 4, 2, 1, 1, 1, 2, 1, 1, 4, 5, 2, 6, 3, 24, 2, 11, 2, - 4, 4, 1, 2, 2, 2, 4, 43, 2, 8, 1, 40, 5, 1, 1, 1, 3, 5, 5, 3, 4, 1, 3, - 5, 1, 1, 772, 4, 3, 2, 1, 2, 14, 2, 2, 10, 478, 10, 2, 8, 52797, 6, 5, - 2, 162, 2, 18, 1, 1, 1, 28, 1, 1, 1, 29, 1, 1, 1, 1, 2, 1, 2, 55159, 1, - 57, 1, 57, 1, 57, 1, 57, 1 + 22, 2, 6, 1, 14, 8, 1, 7, 6, 3, 1, 4, 5, 1, 2, 2, 5, 4, 1, 1, 3, 2, 2, + 10, 6, 2, 2, 12, 19, 1, 4, 2, 1, 1, 1, 2, 1, 1, 4, 5, 2, 6, 3, 24, 2, + 11, 2, 4, 4, 1, 2, 2, 2, 4, 43, 2, 8, 1, 40, 5, 1, 1, 1, 3, 5, 5, 3, 4, + 1, 3, 5, 1, 1, 256, 1, 515, 4, 3, 2, 1, 2, 14, 2, 2, 10, 43, 8, 427, + 10, 2, 8, 52797, 6, 5, 2, 162, 2, 18, 1, 1, 1, 28, 1, 1, 1, 29, 1, 1, + 1, 1, 2, 1, 2, 55159, 1, 57, 1, 57, 1, 57, 1, 57, 1 ) uncompressDeltas(deltas) } @@ -1477,34 +1854,36 @@ println(" )") 2, 2, 0, 5, 1, 0, 2, 1, 16, 0, 1, 101, 7, 0, 2, 4, 1, 0, 1, 0, 2, 2, 0, 1, 0, 1, 0, 2, 1, 35, 0, 1, 30, 1, 1, 0, 2, 1, 0, 2, 3, 0, 1, 2, 0, 1, 1, 0, 3, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 2, 0, 160, 7, 1, 0, 1, 0, 9, - 0, 1, 24, 4, 0, 1, 9, 0, 1, 3, 0, 1, 5, 0, 43, 0, 3, 119, 0, 1, 0, 14, - 0, 1, 0, 1, 0, 2, 1, 0, 2, 1, 0, 3, 6, 0, 3, 1, 0, 2, 2, 0, 5, 0, 60, - 0, 1, 16, 0, 1, 3, 1, 1, 0, 2, 0, 103, 0, 1, 16, 0, 1, 48, 1, 0, 61, 0, - 1, 16, 0, 1, 110, 0, 1, 16, 0, 1, 110, 0, 1, 16, 0, 1, 127, 0, 1, 127, - 0, 1, 7, 0, 2, 101, 0, 1, 16, 0, 1, 109, 0, 2, 16, 0, 1, 124, 0, 1, - 109, 0, 3, 13, 0, 4, 108, 0, 3, 13, 0, 4, 76, 0, 2, 27, 0, 1, 1, 0, 1, - 1, 0, 1, 55, 0, 2, 1, 0, 1, 5, 0, 4, 2, 0, 1, 1, 2, 1, 1, 2, 0, 62, 0, - 1, 112, 0, 1, 1, 0, 2, 82, 0, 1, 719, 3, 0, 948, 0, 1, 31, 0, 1, 157, - 0, 1, 10, 1, 0, 203, 0, 1, 143, 0, 1, 0, 1, 1, 219, 1, 1, 71, 0, 1, 20, - 8, 0, 2, 0, 1, 48, 5, 6, 0, 2, 1, 1, 0, 2, 115, 0, 1, 15, 0, 1, 38, 1, - 1, 0, 7, 0, 54, 0, 2, 58, 0, 1, 11, 0, 2, 67, 0, 1, 152, 3, 0, 1, 0, 6, - 0, 2, 4, 0, 1, 0, 1, 0, 7, 4, 0, 1, 6, 1, 0, 3, 2, 0, 198, 2, 1, 0, 7, - 1, 0, 2, 4, 0, 37, 4, 1, 1, 2, 0, 1, 1, 720, 2, 2, 0, 4, 3, 0, 2, 0, 4, - 1, 0, 3, 0, 2, 0, 1, 1, 0, 1, 6, 0, 1, 0, 3070, 3, 0, 141, 0, 1, 96, - 32, 0, 554, 0, 6, 105, 0, 2, 30164, 1, 0, 4, 10, 0, 32, 2, 0, 80, 2, 0, - 276, 0, 1, 37, 0, 1, 151, 0, 1, 27, 18, 0, 57, 0, 3, 37, 0, 1, 95, 0, - 1, 12, 0, 1, 239, 1, 0, 1, 2, 1, 2, 2, 0, 5, 2, 0, 1, 1, 0, 52, 0, 1, - 246, 0, 1, 20272, 0, 1, 769, 7, 7, 0, 2, 0, 973, 0, 1, 226, 0, 1, 149, - 5, 0, 1682, 0, 1, 1, 1, 0, 40, 1, 2, 4, 0, 1, 165, 1, 1, 573, 4, 0, - 387, 2, 0, 153, 0, 2, 0, 3, 1, 0, 1, 4, 245, 0, 1, 56, 0, 1, 57, 0, 2, - 69, 3, 0, 48, 0, 2, 62, 0, 1, 76, 0, 1, 9, 0, 1, 106, 0, 2, 178, 0, 2, - 80, 0, 2, 16, 0, 1, 24, 7, 0, 3, 5, 0, 205, 0, 1, 3, 0, 1, 23, 1, 0, - 99, 0, 2, 251, 0, 2, 126, 0, 1, 118, 0, 2, 115, 0, 1, 269, 0, 2, 258, - 0, 2, 4, 0, 1, 156, 0, 1, 83, 0, 1, 18, 0, 1, 81, 0, 1, 421, 0, 1, 258, - 0, 1, 1, 0, 2, 81, 0, 1, 19800, 0, 5, 59, 7, 0, 1209, 0, 2, 19628, 0, - 1, 5318, 0, 5, 3, 0, 6, 8, 0, 8, 2, 5, 2, 30, 4, 0, 148, 3, 0, 3515, 7, - 0, 1, 17, 0, 2, 7, 0, 1, 2, 0, 1, 5, 0, 261, 7, 0, 437, 4, 0, 1504, 0, - 7, 109, 6, 1 + 0, 1, 24, 4, 0, 1, 9, 0, 1, 3, 0, 1, 5, 0, 43, 0, 3, 59, 2, 3, 0, 4, 0, + 42, 5, 5, 0, 14, 0, 1, 0, 1, 0, 2, 1, 0, 2, 1, 0, 3, 6, 0, 3, 1, 0, 2, + 2, 0, 5, 0, 60, 0, 1, 16, 0, 1, 3, 1, 1, 0, 2, 0, 103, 0, 1, 16, 0, 1, + 48, 1, 0, 61, 0, 1, 16, 0, 1, 110, 0, 1, 16, 0, 1, 110, 0, 1, 16, 0, 1, + 127, 0, 1, 110, 0, 1, 16, 0, 1, 7, 0, 2, 101, 0, 1, 16, 0, 1, 109, 0, + 2, 16, 0, 1, 124, 0, 1, 109, 0, 3, 13, 0, 4, 108, 0, 3, 13, 0, 4, 76, + 0, 2, 27, 0, 1, 1, 0, 1, 1, 0, 1, 55, 0, 2, 1, 0, 1, 5, 0, 4, 2, 0, 1, + 1, 2, 1, 1, 2, 0, 62, 0, 1, 112, 0, 1, 1, 0, 2, 82, 0, 1, 719, 3, 0, + 948, 0, 2, 30, 0, 1, 157, 0, 1, 10, 1, 0, 203, 0, 1, 143, 0, 1, 0, 1, + 1, 219, 1, 1, 71, 0, 1, 20, 8, 0, 2, 0, 1, 48, 5, 6, 0, 2, 1, 1, 0, 2, + 0, 2, 2, 0, 5, 1, 0, 4, 0, 101, 0, 1, 15, 0, 1, 38, 1, 1, 0, 7, 0, 54, + 0, 2, 58, 0, 1, 11, 0, 2, 67, 0, 1, 152, 3, 0, 1, 0, 6, 0, 2, 4, 0, 1, + 0, 1, 0, 7, 4, 0, 1, 6, 1, 0, 3, 2, 0, 198, 2, 1, 0, 7, 1, 0, 2, 4, 0, + 37, 5, 0, 1, 2, 0, 1, 1, 720, 2, 2, 0, 4, 3, 0, 2, 0, 4, 1, 0, 3, 0, 2, + 0, 1, 1, 0, 1, 6, 0, 1, 0, 3070, 3, 0, 141, 0, 1, 96, 32, 0, 554, 0, 6, + 105, 0, 2, 30164, 1, 0, 4, 10, 0, 32, 2, 0, 80, 2, 0, 276, 0, 1, 37, 0, + 1, 151, 0, 1, 27, 18, 0, 57, 0, 3, 37, 0, 1, 95, 0, 1, 12, 0, 1, 239, + 1, 0, 1, 2, 1, 2, 2, 0, 5, 2, 0, 1, 1, 0, 52, 0, 1, 246, 0, 1, 20272, + 0, 1, 769, 7, 7, 0, 2, 0, 973, 0, 1, 226, 0, 1, 149, 5, 0, 1682, 0, 1, + 1, 1, 0, 40, 1, 2, 4, 0, 1, 165, 1, 1, 573, 4, 0, 65, 5, 0, 317, 2, 0, + 80, 0, 3, 70, 0, 2, 0, 3, 1, 0, 1, 4, 49, 1, 1, 0, 1, 1, 192, 0, 1, 41, + 0, 1, 14, 0, 1, 57, 0, 2, 69, 3, 0, 48, 0, 2, 62, 0, 1, 76, 0, 1, 9, 0, + 1, 106, 0, 2, 178, 0, 2, 80, 0, 2, 16, 0, 1, 24, 7, 0, 3, 5, 0, 89, 0, + 3, 113, 0, 1, 3, 0, 1, 23, 1, 0, 99, 0, 2, 251, 0, 2, 126, 0, 1, 118, + 0, 2, 115, 0, 1, 269, 0, 2, 258, 0, 2, 4, 0, 1, 156, 0, 1, 83, 0, 1, + 18, 0, 1, 81, 0, 1, 421, 0, 1, 258, 0, 1, 1, 0, 2, 81, 0, 1, 425, 0, 2, + 16876, 0, 1, 2496, 0, 5, 59, 7, 0, 1209, 0, 2, 19628, 0, 1, 5318, 0, 5, + 3, 0, 6, 8, 0, 8, 2, 5, 2, 30, 4, 0, 148, 3, 0, 3515, 7, 0, 1, 17, 0, + 2, 7, 0, 1, 2, 0, 1, 5, 0, 100, 1, 0, 160, 7, 0, 375, 1, 0, 61, 4, 0, + 508, 0, 3, 0, 1, 0, 254, 1, 1, 736, 0, 7, 109, 6, 1 ) uncompressDeltas(deltas) } @@ -1566,13 +1945,29 @@ println(" )") * Each of them is directly followed by 9 other code points mapping to the * digits 1 to 9, in order. Conversely, there are no other non-ASCII code * point mapping to digits from 0 to 9. + +val zeroCodePointReprs = for { + cp <- 0x80 to Character.MAX_CODE_POINT + if Character.digit(cp, 10) == 0 +} yield { + String.format("0x%x", cp) +} +println("nonASCIIZeroDigitCodePoints:") +println(" Array(") +println(formatLargeArrayStr(zeroCodePointReprs.toArray, " ")) +println(" )") + */ private[this] lazy val nonASCIIZeroDigitCodePoints: Array[Int] = { - Array(0x660, 0x6f0, 0x7c0, 0x966, 0x9e6, 0xa66, 0xae6, 0xb66, 0xbe6, - 0xc66, 0xce6, 0xd66, 0xe50, 0xed0, 0xf20, 0x1040, 0x1090, 0x17e0, + Array( + 0x660, 0x6f0, 0x7c0, 0x966, 0x9e6, 0xa66, 0xae6, 0xb66, 0xbe6, 0xc66, + 0xce6, 0xd66, 0xde6, 0xe50, 0xed0, 0xf20, 0x1040, 0x1090, 0x17e0, 0x1810, 0x1946, 0x19d0, 0x1a80, 0x1a90, 0x1b50, 0x1bb0, 0x1c40, 0x1c50, - 0xa620, 0xa8d0, 0xa900, 0xa9d0, 0xaa50, 0xabf0, 0xff10, 0x104a0, - 0x11066, 0x110f0, 0x11136, 0x111d0, 0x116c0, 0x1d7ce, 0x1d7d8, 0x1d7e2, - 0x1d7ec, 0x1d7f6) + 0xa620, 0xa8d0, 0xa900, 0xa9d0, 0xa9f0, 0xaa50, 0xabf0, 0xff10, + 0x104a0, 0x10d30, 0x11066, 0x110f0, 0x11136, 0x111d0, 0x112f0, 0x11450, + 0x114d0, 0x11650, 0x116c0, 0x11730, 0x118e0, 0x11950, 0x11c50, 0x11d50, + 0x11da0, 0x11f50, 0x16a60, 0x16ac0, 0x16b50, 0x1d7ce, 0x1d7d8, 0x1d7e2, + 0x1d7ec, 0x1d7f6, 0x1e140, 0x1e2f0, 0x1e4f0, 0x1e950, 0x1fbf0 + ) } } diff --git a/javalanglib/src/main/scala/java/lang/Class.scala b/javalib/src/main/scala/java/lang/Class.scala similarity index 58% rename from javalanglib/src/main/scala/java/lang/Class.scala rename to javalib/src/main/scala/java/lang/Class.scala index 06c4f7a3a3..b0d80c788b 100644 --- a/javalanglib/src/main/scala/java/lang/Class.scala +++ b/javalib/src/main/scala/java/lang/Class.scala @@ -12,66 +12,42 @@ package java.lang -import scala.scalajs.js +import java.lang.constant.Constable +import java.io.Serializable -@js.native -private trait ScalaJSClassData[A] extends js.Object { - val name: String = js.native - val isPrimitive: scala.Boolean = js.native - val isInterface: scala.Boolean = js.native - val isArrayClass: scala.Boolean = js.native - - def isInstance(obj: Object): scala.Boolean = js.native - def isAssignableFrom(that: ScalaJSClassData[_]): scala.Boolean = js.native - def checkCast(obj: Object): scala.Unit = js.native - - def getSuperclass(): Class[_ >: A] = js.native - def getComponentType(): Class[_] = js.native - - def newArrayOfThisClass(dimensions: js.Array[Int]): AnyRef = js.native -} - -final class Class[A] private (data0: Object) extends Object { - private[this] val data: ScalaJSClassData[A] = - data0.asInstanceOf[ScalaJSClassData[A]] +final class Class[A] private () + extends Object with Serializable with Constable { private[this] var cachedSimpleName: String = _ - /** Access to `data` for other instances or `@inline` methods. - * - * Directly accessing the `data` field from `@inline` methods will cause - * scalac to make the field public and mangle its name. Since the Emitter - * relies on the field being called exactly `data` in some of its - * optimizations, we must avoid that. - * - * This non-`@noinline` method can be used to access the field without - * triggering scalac's mangling. Since it is a trivial accessor, the - * Scala.js optimizer will inline it anyway. - */ - private def getData(): ScalaJSClassData[A] = data - override def toString(): String = { (if (isInterface()) "interface " else if (isPrimitive()) "" else "class ")+getName() } - def isInstance(obj: Object): scala.Boolean = - data.isInstance(obj) + @inline + def isInstance(obj: Any): scala.Boolean = + throw new Error("Stub filled in by the compiler") + @inline def isAssignableFrom(that: Class[_]): scala.Boolean = - this.data.isAssignableFrom(that.getData()) + throw new Error("Stub filled in by the compiler") + @inline def isInterface(): scala.Boolean = - data.isInterface + throw new Error("Stub filled in by the compiler") + @inline def isArray(): scala.Boolean = - data.isArrayClass + throw new Error("Stub filled in by the compiler") + @inline def isPrimitive(): scala.Boolean = - data.isPrimitive + throw new Error("Stub filled in by the compiler") + @inline def getName(): String = - data.name + throw new Error("Stub filled in by the compiler") def getSimpleName(): String = { if (cachedSimpleName == null) @@ -103,7 +79,7 @@ final class Class[A] private (data0: Object) extends Object { if (isArray()) { getComponentType().getSimpleName() + "[]" } else { - val name = data.name + val name = getName() var idx = name.length - 1 // Include trailing '$'s for module class names @@ -134,20 +110,15 @@ final class Class[A] private (data0: Object) extends Object { } } + @inline def getSuperclass(): Class[_ >: A] = - data.getSuperclass() + throw new Error("Stub filled in by the compiler") + @inline def getComponentType(): Class[_] = - data.getComponentType() + throw new Error("Stub filled in by the compiler") @inline - def cast(obj: Object): A = { - getData().checkCast(obj) - obj.asInstanceOf[A] - } - - // java.lang.reflect.Array support - - private[lang] def newArrayOfThisClass(dimensions: js.Array[Int]): AnyRef = - data.newArrayOfThisClass(dimensions) + def cast(obj: Any): A = + throw new Error("Stub filled in by the compiler") } diff --git a/javalanglib/src/main/scala/java/lang/ClassLoader.scala b/javalib/src/main/scala/java/lang/ClassLoader.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/ClassLoader.scala rename to javalib/src/main/scala/java/lang/ClassLoader.scala diff --git a/javalanglib/src/main/scala/java/lang/ClassValue.scala b/javalib/src/main/scala/java/lang/ClassValue.scala similarity index 67% rename from javalanglib/src/main/scala/java/lang/ClassValue.scala rename to javalib/src/main/scala/java/lang/ClassValue.scala index c471b78031..0ab92d37cb 100644 --- a/javalanglib/src/main/scala/java/lang/ClassValue.scala +++ b/javalib/src/main/scala/java/lang/ClassValue.scala @@ -16,14 +16,14 @@ import java.util.HashMap import scala.scalajs.js import scala.scalajs.js.annotation._ -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion import Utils._ abstract class ClassValue[T] protected () { private val jsMap: js.Map[Class[_], T] = { - if (linkingInfo.esVersion >= ESVersion.ES2015 || js.typeOf(js.Dynamic.global.Map) != "undefined") + if (LinkingInfo.esVersion >= ESVersion.ES2015 || js.typeOf(js.Dynamic.global.Map) != "undefined") new js.Map() else null @@ -35,7 +35,7 @@ abstract class ClassValue[T] protected () { * emitting ES 2015 code, which allows to dead-code-eliminate the branches * using `HashMap`s, and therefore `HashMap` itself. */ - linkingInfo.esVersion >= ESVersion.ES2015 || jsMap != null + LinkingInfo.esVersion >= ESVersion.ES2015 || jsMap != null } /* We use a HashMap instead of an IdentityHashMap because the latter is @@ -49,24 +49,16 @@ abstract class ClassValue[T] protected () { protected def computeValue(`type`: Class[_]): T def get(`type`: Class[_]): T = { - /* We first perform `get`, and if the result is undefined/null, we use - * `has` to disambiguate a present undefined/null from an absent key. - * Since the purpose of ClassValue is to be used a cache indexed by Class - * values, the expected use case will have more hits than misses, and so - * this ordering should be faster on average than first performing `has` - * then `get`. - */ if (useJSMap) { - undefOrGetOrElseCompute(mapGet(jsMap, `type`)) { () => - if (mapHas(jsMap, `type`)) { - ().asInstanceOf[T] - } else { - val newValue = computeValue(`type`) - mapSet(jsMap, `type`, newValue) - newValue - } - } + mapGetOrElseUpdate(jsMap, `type`)(() => computeValue(`type`)) } else { + /* We first perform `get`, and if the result is null, we use + * `containsKey` to disambiguate a present null from an absent key. + * Since the purpose of ClassValue is to be used a cache indexed by Class + * values, the expected use case will have more hits than misses, and so + * this ordering should be faster on average than first performing `has` + * then `get`. + */ javaMap.get(`type`) match { case null => if (javaMap.containsKey(`type`)) { diff --git a/javalanglib/src/main/scala/java/lang/Cloneable.scala b/javalib/src/main/scala/java/lang/Cloneable.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/Cloneable.scala rename to javalib/src/main/scala/java/lang/Cloneable.scala diff --git a/javalanglib/src/main/scala/java/lang/Comparable.scala b/javalib/src/main/scala/java/lang/Comparable.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/Comparable.scala rename to javalib/src/main/scala/java/lang/Comparable.scala diff --git a/javalanglib/src/main/scala/java/lang/Double.scala b/javalib/src/main/scala/java/lang/Double.scala similarity index 92% rename from javalanglib/src/main/scala/java/lang/Double.scala rename to javalib/src/main/scala/java/lang/Double.scala index ae6140aff8..aa6e3bc8d9 100644 --- a/javalanglib/src/main/scala/java/lang/Double.scala +++ b/javalib/src/main/scala/java/lang/Double.scala @@ -15,6 +15,9 @@ package java.lang import java.lang.constant.{Constable, ConstantDesc} import scala.scalajs.js +import scala.scalajs.LinkingInfo + +import Utils._ /* This is a hijacked class. Its instances are primitive numbers. * Constructors are not emitted. @@ -106,7 +109,7 @@ object Double { def parseDouble(s: String): scala.Double = { val groups = doubleStrPat.exec(s) if (groups != null) - js.Dynamic.global.parseFloat(groups(1).asInstanceOf[js.Any]).asInstanceOf[scala.Double] + js.Dynamic.global.parseFloat(undefOrForceGet[String](groups(1))).asInstanceOf[scala.Double] else parseDoubleSlowPath(s) } @@ -114,16 +117,16 @@ object Double { // Slow path of `parseDouble` for hexadecimal notation and failure private def parseDoubleSlowPath(s: String): scala.Double = { def fail(): Nothing = - throw new NumberFormatException("For input string: \"" + s + "\"") + throw new NumberFormatException(s"""For input string: "$s"""") val groups = doubleStrHexPat.exec(s) if (groups == null) fail() - val signStr = groups(1).asInstanceOf[String] - val integralPartStr = groups(2).asInstanceOf[String] - val fractionalPartStr = groups(3).asInstanceOf[String] - val binaryExpStr = groups(4).asInstanceOf[String] + val signStr = undefOrForceGet(groups(1)) + val integralPartStr = undefOrForceGet(groups(2)) + val fractionalPartStr = undefOrForceGet(groups(3)) + val binaryExpStr = undefOrForceGet(groups(4)) if (integralPartStr == "" && fractionalPartStr == "") fail() @@ -236,11 +239,8 @@ object Double { * `binaryExp` (see below) did not stray too far from that range itself. */ - @inline def nativeParseInt(s: String, radix: Int): scala.Double = { - js.Dynamic.global - .parseInt(s.asInstanceOf[js.Any], radix.asInstanceOf[js.Any]) - .asInstanceOf[scala.Double] - } + @inline def nativeParseInt(s: String, radix: Int): scala.Double = + js.Dynamic.global.parseInt(s, radix).asInstanceOf[scala.Double] val mantissa = nativeParseInt(truncatedMantissaStr, 16) // Assert: mantissa != 0.0 && mantissa != scala.Double.PositiveInfinity @@ -364,12 +364,29 @@ object Double { @inline def isFinite(d: scala.Double): scala.Boolean = !isNaN(d) && !isInfinite(d) - @inline def hashCode(value: scala.Double): Int = - FloatingPointBits.numberHashCode(value) + @inline def hashCode(value: scala.Double): Int = { + if (LinkingInfo.isWebAssembly) + hashCodeForWasm(value) + else + FloatingPointBits.numberHashCode(value) + } + + // See FloatingPointBits for the spec of this computation + @inline + private def hashCodeForWasm(value: scala.Double): Int = { + val bits = doubleToLongBits(value) + val valueInt = value.toInt + if (doubleToLongBits(valueInt.toDouble) == bits) + valueInt + else + Long.hashCode(bits) + } + // Wasm intrinsic @inline def longBitsToDouble(bits: scala.Long): scala.Double = FloatingPointBits.longBitsToDouble(bits) + // Wasm intrinsic @inline def doubleToLongBits(value: scala.Double): scala.Long = FloatingPointBits.doubleToLongBits(value) diff --git a/javalanglib/src/main/scala/java/lang/Enum.scala b/javalib/src/main/scala/java/lang/Enum.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/Enum.scala rename to javalib/src/main/scala/java/lang/Enum.scala diff --git a/javalanglib/src/main/scala/java/lang/Float.scala b/javalib/src/main/scala/java/lang/Float.scala similarity index 78% rename from javalanglib/src/main/scala/java/lang/Float.scala rename to javalib/src/main/scala/java/lang/Float.scala index 22dc286415..a2d54c77fd 100644 --- a/javalanglib/src/main/scala/java/lang/Float.scala +++ b/javalib/src/main/scala/java/lang/Float.scala @@ -13,9 +13,9 @@ package java.lang import java.lang.constant.{Constable, ConstantDesc} -import java.math.BigInteger import scala.scalajs.js +import scala.scalajs.LinkingInfo._ /* This is a hijacked class. Its instances are primitive numbers. * Constructors are not emitted. @@ -113,7 +113,7 @@ object Float { val groups = parseFloatRegExp.exec(s) if (groups == null) - throw new NumberFormatException("For input string: \"" + s + "\"") + throw new NumberFormatException(s"""For input string: "$s"""") val absResult = if (undefOrIsDefined(groups(2))) { scala.Float.NaN @@ -122,14 +122,14 @@ object Float { } else if (undefOrIsDefined(groups(4))) { // Decimal notation val fullNumberStr = undefOrForceGet(groups(4)) - val integralPartStr = undefOrGetOrElse(groups(5), "") - val fractionalPartStr = undefOrGetOrElse(groups(6), "") + undefOrGetOrElse(groups(7), "") - val exponentStr = undefOrGetOrElse(groups(8), "0") + val integralPartStr = undefOrGetOrElse(groups(5))(() => "") + val fractionalPartStr = undefOrGetOrElse(groups(6))(() => "") + undefOrGetOrElse(groups(7))(() => "") + val exponentStr = undefOrGetOrElse(groups(8))(() => "0") parseFloatDecimal(fullNumberStr, integralPartStr, fractionalPartStr, exponentStr) } else { // Hexadecimal notation - val integralPartStr = undefOrGetOrElse(groups(10), "") - val fractionalPartStr = undefOrGetOrElse(groups(11), "") + undefOrGetOrElse(groups(12), "") + val integralPartStr = undefOrGetOrElse(groups(10))(() => "") + val fractionalPartStr = undefOrGetOrElse(groups(11))(() => "") + undefOrGetOrElse(groups(12))(() => "") val binaryExpStr = undefOrForceGet(groups(13)) parseFloatHexadecimal(integralPartStr, fractionalPartStr, binaryExpStr) } @@ -145,19 +145,12 @@ object Float { integralPartStr: String, fractionalPartStr: String, exponentStr: String): scala.Float = { - val z0 = js.Dynamic.global.parseFloat(fullNumberStr.asInstanceOf[js.Any]).asInstanceOf[scala.Double] + val z0 = js.Dynamic.global.parseFloat(fullNumberStr).asInstanceOf[scala.Double] val z = z0.toFloat val zDouble = z.toDouble if (zDouble == z0) { - /* This branch is always taken when strictFloats are disabled, and there - * is no Math.fround support. In that case, Floats are basically - * equivalent to Doubles, and we make no specific guarantee about the - * result, so we can quickly return `z`. - * More importantly, the computations in the `else` branch assume that - * Float operations are exact, so we must return early. - * - * This branch is also always taken when z0 is 0.0 or Infinity, which the + /* This branch is always taken when z0 is 0.0 or Infinity, which the * `else` branch assumes does not happen. */ z @@ -233,9 +226,23 @@ object Float { fractionalPartStr: String, exponentStr: String, zDown: scala.Float, zUp: scala.Float, mid: scala.Double): scala.Float = { + /* Get the best available implementation of big integers for the given platform. + * + * If JS bigint's are supported, use them. Otherwise fall back on + * `java.math.BigInteger`. + * + * We need a `linkTimeIf` here because the JS bigint implementation uses + * the `**` operator, which does not link when `esVersion < ESVersion.ES2016`. + */ + val bigIntImpl = linkTimeIf[BigIntImpl](esVersion >= ESVersion.ES2020) { + BigIntImpl.JSBigInt + } { + BigIntImpl.JBigInteger + } + // 1. Accurately parse the string with the representation f × 10ᵉ - val f: BigInteger = new BigInteger(integralPartStr + fractionalPartStr) + val f: bigIntImpl.Repr = bigIntImpl.fromString(integralPartStr + fractionalPartStr) val e: Int = Integer.parseInt(exponentStr) - fractionalPartStr.length() /* Note: we know that `e` is "reasonable" (in the range [-324, +308]). If @@ -264,28 +271,27 @@ object Float { * subnormal floats). */ if (biasedK == 0) - throw new AssertionError("parseFloatCorrection was given a subnormal mid: " + mid) + throw new AssertionError(s"parseFloatCorrection was given a subnormal mid: $mid") val mExplicitBits = midBits & ((1L << mbits) - 1) val mImplicit1Bit = 1L << mbits // the implicit '1' bit of a normalized floating-point number - val m = BigInteger.valueOf(mExplicitBits | mImplicit1Bit) + val m = bigIntImpl.fromUnsignedLong53(mExplicitBits | mImplicit1Bit) val k = biasedK - bias - mbits // 3. Accurately compare f × 10ᵉ to m × 2ᵏ - @inline def compare(x: BigInteger, y: BigInteger): Int = - x.compareTo(y) + import bigIntImpl.{multiplyBy2Pow, multiplyBy10Pow} val cmp = if (e >= 0) { if (k >= 0) - compare(multiplyBy10Pow(f, e), multiplyBy2Pow(m, k)) + bigIntImpl.compare(multiplyBy10Pow(f, e), multiplyBy2Pow(m, k)) else - compare(multiplyBy2Pow(multiplyBy10Pow(f, e), -k), m) // this branch may be dead code in practice + bigIntImpl.compare(multiplyBy2Pow(multiplyBy10Pow(f, e), -k), m) // this branch may be dead code in practice } else { if (k >= 0) - compare(f, multiplyBy2Pow(multiplyBy10Pow(m, -e), k)) + bigIntImpl.compare(f, multiplyBy2Pow(multiplyBy10Pow(m, -e), k)) else - compare(multiplyBy2Pow(f, -k), multiplyBy10Pow(m, -e)) + bigIntImpl.compare(multiplyBy2Pow(f, -k), multiplyBy10Pow(m, -e)) } // 4. Choose zDown or zUp depending on the result of the comparison @@ -300,11 +306,54 @@ object Float { zUp } - @inline private def multiplyBy10Pow(v: BigInteger, e: Int): BigInteger = - v.multiply(BigInteger.TEN.pow(e)) + /** An implementation of big integer arithmetics that we need in the above method. */ + private sealed abstract class BigIntImpl { + type Repr + + def fromString(str: String): Repr + + /** Creates a big integer from a `Long` that needs at most 53 bits (unsigned). */ + def fromUnsignedLong53(x: scala.Long): Repr + + def multiplyBy2Pow(v: Repr, e: Int): Repr + def multiplyBy10Pow(v: Repr, e: Int): Repr + + def compare(x: Repr, y: Repr): Int + } + + private object BigIntImpl { + object JSBigInt extends BigIntImpl { + type Repr = js.BigInt + + @inline def fromString(str: String): Repr = js.BigInt(str) - @inline private def multiplyBy2Pow(v: BigInteger, e: Int): BigInteger = - v.shiftLeft(e) + // The 53-bit restriction guarantees that the conversion to `Double` is lossless. + @inline def fromUnsignedLong53(x: scala.Long): Repr = js.BigInt(x.toDouble) + + @inline def multiplyBy2Pow(v: Repr, e: Int): Repr = v << js.BigInt(e) + @inline def multiplyBy10Pow(v: Repr, e: Int): Repr = v * (js.BigInt(10) ** js.BigInt(e)) + + @inline def compare(x: Repr, y: Repr): Int = { + if (x < y) -1 + else if (x > y) 1 + else 0 + } + } + + object JBigInteger extends BigIntImpl { + import java.math.BigInteger + + type Repr = BigInteger + + @inline def fromString(str: String): Repr = new BigInteger(str) + @inline def fromUnsignedLong53(x: scala.Long): Repr = BigInteger.valueOf(x) + + @inline def multiplyBy2Pow(v: Repr, e: Int): Repr = v.shiftLeft(e) + @inline def multiplyBy10Pow(v: Repr, e: Int): Repr = v.multiply(BigInteger.TEN.pow(e)) + + @inline def compare(x: Repr, y: Repr): Int = x.compareTo(y) + } + } private def parseFloatHexadecimal(integralPartStr: String, fractionalPartStr: String, binaryExpStr: String): scala.Float = { @@ -376,11 +425,13 @@ object Float { !isNaN(f) && !isInfinite(f) @inline def hashCode(value: scala.Float): Int = - FloatingPointBits.numberHashCode(value) + Double.hashCode(value.toDouble) + // Wasm intrinsic @inline def intBitsToFloat(bits: scala.Int): scala.Float = FloatingPointBits.intBitsToFloat(bits) + // Wasm intrinsic @inline def floatToIntBits(value: scala.Float): scala.Int = FloatingPointBits.floatToIntBits(value) diff --git a/javalanglib/src/main/scala/java/lang/FloatingPointBits.scala b/javalib/src/main/scala/java/lang/FloatingPointBits.scala similarity index 82% rename from javalanglib/src/main/scala/java/lang/FloatingPointBits.scala rename to javalib/src/main/scala/java/lang/FloatingPointBits.scala index 359991af0e..96e1c8f64c 100644 --- a/javalanglib/src/main/scala/java/lang/FloatingPointBits.scala +++ b/javalib/src/main/scala/java/lang/FloatingPointBits.scala @@ -20,11 +20,11 @@ import scala.scalajs.LinkingInfo.ESVersion /** Manipulating the bits of floating point numbers. */ private[lang] object FloatingPointBits { - import scala.scalajs.runtime.linkingInfo + import scala.scalajs.LinkingInfo private[this] val _areTypedArraysSupported = { // Here we use the `esVersion` test to dce the 4 subsequent tests - linkingInfo.esVersion >= ESVersion.ES2015 || { + LinkingInfo.esVersion >= ESVersion.ES2015 || { js.typeOf(global.ArrayBuffer) != "undefined" && js.typeOf(global.Int32Array) != "undefined" && js.typeOf(global.Float32Array) != "undefined" && @@ -42,7 +42,7 @@ private[lang] object FloatingPointBits { * * If we emit ES5, replace `areTypedArraysSupported` by * `_areTypedArraysSupported` so we do not calculate it multiple times. */ - linkingInfo.esVersion >= ESVersion.ES2015 || _areTypedArraysSupported + LinkingInfo.esVersion >= ESVersion.ES2015 || _areTypedArraysSupported } private val arrayBuffer = @@ -149,7 +149,7 @@ private[lang] object FloatingPointBits { float32Array(0) = value int32Array(0) } else { - floatToIntBitsPolyfill(value.toDouble) + floatToIntBitsPolyfill(value) } } @@ -181,8 +181,7 @@ private[lang] object FloatingPointBits { * Note that if typed arrays are not supported, it is almost certain that * fround is not supported natively, so Float operations are extremely slow. * - * We therefore do all computations in Doubles here, which is also more - * predictable, since the results do not depend on strict floats semantics. + * We therefore do all computations in Doubles here. */ private def intBitsToFloatPolyfill(bits: Int): scala.Double = { @@ -194,21 +193,23 @@ private[lang] object FloatingPointBits { decodeIEEE754(ebits, fbits, floatPowsOf2, scala.Float.MinPositiveValue, sign, e, f) } - private def floatToIntBitsPolyfill(value: scala.Double): Int = { + private def floatToIntBitsPolyfill(floatValue: scala.Float): Int = { // Some constants val ebits = 8 val fbits = 23 + // Force computations to be on Doubles + val value = floatValue.toDouble + // Determine sign bit and compute the absolute value av val sign = if (value < 0.0 || (value == 0.0 && 1.0 / value < 0.0)) -1 else 1 val s = sign & scala.Int.MinValue val av = sign * value // Compute e and f - val avr = forceFround(av) val powsOf2 = this.floatPowsOf2 // local cache - val e = encodeIEEE754Exponent(ebits, powsOf2, avr) - val f = encodeIEEE754MantissaBits(ebits, fbits, powsOf2, scala.Float.MinPositiveValue.toDouble, avr, e) + val e = encodeIEEE754Exponent(ebits, powsOf2, av) + val f = encodeIEEE754MantissaBits(ebits, fbits, powsOf2, scala.Float.MinPositiveValue.toDouble, av, e) // Encode s | (e << fbits) | rawToInt(f) @@ -277,37 +278,6 @@ private[lang] object FloatingPointBits { } } - /** Force rounding of `av` to fit in 32 bits (this is a manual `fround`). - * - * `av` must not be negative, i.e., `av < 0.0` must be false (it can be - * `NaN` or `Infinity`). - * - * When we use strict-float semantics, this is redundant, because the input - * came from a `Float` and is therefore guaranteed to be rounded already. - * However, here we don't know whether we use strict floats semantics or - * not, so we must always do it. This is not a big deal because, if this - * code is called, then any operation on `Float`s is calling the same code - * from the `CoreJSLib`, so doing one more such operation for - * `floatToIntBits` is negligible. - * - * TODO Remove this when we get rid of non-strict float semantics altogether. - */ - @inline - private def forceFround(av: scala.Double): scala.Double = { - // See the `fround` polyfill in CoreJSLib - val overflowThreshold = 3.4028235677973366e38 - val normalThreshold = 1.1754943508222875e-38 - if (av >= overflowThreshold) { - scala.Double.PositiveInfinity - } else if (av >= normalThreshold) { - val p = av * 536870913.0 // pow(2, 29) + 1 - p + (av - p) - } else { - val roundingFactor = scala.Double.MinPositiveValue / scala.Float.MinPositiveValue.toDouble - (av * roundingFactor) / roundingFactor - } - } - private def encodeIEEE754Exponent(ebits: Int, powsOf2: js.Array[scala.Double], av: scala.Double): Int = { @@ -348,7 +318,9 @@ private[lang] object FloatingPointBits { } } - @inline private def rawToInt(x: scala.Double): Int = - (x.asInstanceOf[js.Dynamic] | 0.asInstanceOf[js.Dynamic]).asInstanceOf[Int] + @inline private def rawToInt(x: scala.Double): Int = { + import scala.scalajs.js.DynamicImplicits.number2dynamic + (x | 0).asInstanceOf[Int] + } } diff --git a/javalanglib/src/main/scala/java/lang/InheritableThreadLocal.scala b/javalib/src/main/scala/java/lang/InheritableThreadLocal.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/InheritableThreadLocal.scala rename to javalib/src/main/scala/java/lang/InheritableThreadLocal.scala diff --git a/javalanglib/src/main/scala/java/lang/Integer.scala b/javalib/src/main/scala/java/lang/Integer.scala similarity index 85% rename from javalanglib/src/main/scala/java/lang/Integer.scala rename to javalib/src/main/scala/java/lang/Integer.scala index 531aef701b..a4c2694365 100644 --- a/javalanglib/src/main/scala/java/lang/Integer.scala +++ b/javalib/src/main/scala/java/lang/Integer.scala @@ -13,8 +13,11 @@ package java.lang import java.lang.constant.{Constable, ConstantDesc} +import java.util.function._ import scala.scalajs.js +import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.ESVersion /* This is a hijacked class. Its instances are primitive numbers. * Constructors are not emitted. @@ -84,7 +87,7 @@ object Integer { signed: scala.Boolean): scala.Int = { def fail(): Nothing = - throw new NumberFormatException("For input string: \"" + s + "\"") + throw new NumberFormatException(s"""For input string: "$s"""") val len = if (s == null) 0 else s.length @@ -130,7 +133,7 @@ object Integer { decodeGeneric(nm, valueOf(_, _)) @inline private[lang] def decodeGeneric[A](nm: String, - parse: js.Function2[String, Int, A]): A = { + parse: BiFunction[String, Int, A]): A = { val len = nm.length() var i = 0 @@ -196,6 +199,7 @@ object Integer { @inline def toUnsignedLong(x: Int): scala.Long = x.toLong & 0xffffffffL + // Wasm intrinsic def bitCount(i: scala.Int): scala.Int = { /* See http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel * @@ -217,10 +221,12 @@ object Integer { (((t2 + (t2 >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24 } + // Wasm intrinsic @inline def divideUnsigned(dividend: Int, divisor: Int): Int = if (divisor == 0) 0 / 0 else asInt(asUint(dividend) / asUint(divisor)) + // Wasm intrinsic @inline def remainderUnsigned(dividend: Int, divisor: Int): Int = if (divisor == 0) 0 % 0 else asInt(asUint(dividend) % asUint(divisor)) @@ -261,31 +267,43 @@ object Integer { reverseBytes((k & 0x0F0F0F0F) << 4 | (k >> 4) & 0x0F0F0F0F) } + // Wasm intrinsic @inline def rotateLeft(i: scala.Int, distance: scala.Int): scala.Int = (i << distance) | (i >>> -distance) + // Wasm intrinsic @inline def rotateRight(i: scala.Int, distance: scala.Int): scala.Int = (i >>> distance) | (i << -distance) @inline def signum(i: scala.Int): scala.Int = if (i == 0) 0 else if (i < 0) -1 else 1 - // Intrinsic - def numberOfLeadingZeros(i: scala.Int): scala.Int = { - // See Hacker's Delight, Section 5-3 - var x = i - if (x == 0) { - 32 + // Intrinsic, fallback on actual code for non-literal in JS + @inline def numberOfLeadingZeros(i: scala.Int): scala.Int = { + if (LinkingInfo.esVersion >= ESVersion.ES2015) js.Math.clz32(i) + else clz32Dynamic(i) + } + + private def clz32Dynamic(i: scala.Int) = { + if (js.typeOf(js.Dynamic.global.Math.clz32) == "function") { + js.Math.clz32(i) } else { - var r = 1 - if ((x & 0xffff0000) == 0) { x <<= 16; r += 16 } - if ((x & 0xff000000) == 0) { x <<= 8; r += 8 } - if ((x & 0xf0000000) == 0) { x <<= 4; r += 4 } - if ((x & 0xc0000000) == 0) { x <<= 2; r += 2 } - r + (x >> 31) + // See Hacker's Delight, Section 5-3 + var x = i + if (x == 0) { + 32 + } else { + var r = 1 + if ((x & 0xffff0000) == 0) { x <<= 16; r += 16 } + if ((x & 0xff000000) == 0) { x <<= 8; r += 8 } + if ((x & 0xf0000000) == 0) { x <<= 4; r += 4 } + if ((x & 0xc0000000) == 0) { x <<= 2; r += 2 } + r + (x >> 31) + } } } + // Wasm intrinsic @inline def numberOfTrailingZeros(i: scala.Int): scala.Int = if (i == 0) 32 else 31 - numberOfLeadingZeros(i & -i) @@ -299,7 +317,7 @@ object Integer { if (radix == 10 || radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { Integer.toString(i) } else { - import Utils.Implicits.enableJSNumberOps + import js.JSNumberOps.enableJSNumberOps i.toString(radix) } } @@ -313,14 +331,17 @@ object Integer { @inline def min(a: Int, b: Int): Int = Math.min(a, b) @inline private[this] def toStringBase(i: scala.Int, base: scala.Int): String = { - asUint(i).asInstanceOf[js.Dynamic] - .applyDynamic("toString")(base.asInstanceOf[js.Dynamic]) - .asInstanceOf[String] + import js.JSNumberOps.enableJSNumberOps + asUint(i).toString(base) } - @inline private def asInt(n: scala.Double): scala.Int = - (n.asInstanceOf[js.Dynamic] | 0.asInstanceOf[js.Dynamic]).asInstanceOf[Int] + @inline private def asInt(n: scala.Double): scala.Int = { + import js.DynamicImplicits.number2dynamic + (n | 0).asInstanceOf[Int] + } - @inline private def asUint(n: scala.Int): scala.Double = - (n.asInstanceOf[js.Dynamic] >>> 0.asInstanceOf[js.Dynamic]).asInstanceOf[scala.Double] + @inline private def asUint(n: scala.Int): scala.Double = { + import js.DynamicImplicits.number2dynamic + (n.toDouble >>> 0).asInstanceOf[scala.Double] + } } diff --git a/javalanglib/src/main/scala/java/lang/Iterable.scala b/javalib/src/main/scala/java/lang/Iterable.scala similarity index 87% rename from javalanglib/src/main/scala/java/lang/Iterable.scala rename to javalib/src/main/scala/java/lang/Iterable.scala index 478284343b..78416d2a99 100644 --- a/javalanglib/src/main/scala/java/lang/Iterable.scala +++ b/javalib/src/main/scala/java/lang/Iterable.scala @@ -15,12 +15,9 @@ package java.lang import java.util.Iterator import java.util.function.Consumer -import scala.scalajs.js.annotation.JavaDefaultMethod - trait Iterable[T] { def iterator(): Iterator[T] - @JavaDefaultMethod def forEach(action: Consumer[_ >: T]): Unit = { val iter = iterator() while (iter.hasNext()) diff --git a/javalanglib/src/main/scala/java/lang/Long.scala b/javalib/src/main/scala/java/lang/Long.scala similarity index 97% rename from javalanglib/src/main/scala/java/lang/Long.scala rename to javalib/src/main/scala/java/lang/Long.scala index 9330eb722e..0413372acf 100644 --- a/javalanglib/src/main/scala/java/lang/Long.scala +++ b/javalib/src/main/scala/java/lang/Long.scala @@ -144,7 +144,7 @@ object Long { val hi = (i >>> 32).toInt if (lo >> 31 == hi) { // It's a signed int32 - import Utils.Implicits.enableJSNumberOps + import js.JSNumberOps.enableJSNumberOps lo.toString(radix) } else if (hi < 0) { "-" + toUnsignedStringInternalLarge(-i, radix) @@ -157,7 +157,7 @@ object Long { private def toUnsignedStringImpl(i: scala.Long, radix: Int): String = { if ((i >>> 32).toInt == 0) { // It's an unsigned int32 - import Utils.Implicits.enableJSNumberOps + import js.JSNumberOps.enableJSNumberOps Utils.toUint(i.toInt).toString(radix) } else { toUnsignedStringInternalLarge(i, radix) @@ -166,7 +166,8 @@ object Long { // Must be called only with valid radix private def toUnsignedStringInternalLarge(i: scala.Long, radix: Int): String = { - import Utils.Implicits._ + import js.JSNumberOps.enableJSNumberOps + import js.JSStringOps.enableJSStringOps val radixInfo = StringRadixInfos(radix) val divisor = radixInfo.radixPowLength @@ -317,7 +318,7 @@ object Long { } private def parseLongError(s: String): Nothing = - throw new NumberFormatException("For input string: \"" + s + "\"") + throw new NumberFormatException(s"""For input string: "$s"""") @inline def `new`(value: scala.Long): Long = valueOf(value) @@ -347,11 +348,11 @@ object Long { @inline def compareUnsigned(x: scala.Long, y: scala.Long): scala.Int = compare(x ^ SignBit, y ^ SignBit) - // Intrinsic + // Intrinsic, except for JS when using bigint's for longs def divideUnsigned(dividend: scala.Long, divisor: scala.Long): scala.Long = divModUnsigned(dividend, divisor, isDivide = true) - // Intrinsic + // Intrinsic, except for JS when using bigint's for longs def remainderUnsigned(dividend: scala.Long, divisor: scala.Long): scala.Long = divModUnsigned(dividend, divisor, isDivide = false) @@ -407,6 +408,7 @@ object Long { if (lo != 0) 0 else Integer.lowestOneBit(hi)) } + // Wasm intrinsic @inline def bitCount(i: scala.Long): scala.Int = { val lo = i.toInt @@ -435,10 +437,12 @@ object Long { private def makeLongFromLoHi(lo: Int, hi: Int): scala.Long = (lo.toLong & 0xffffffffL) | (hi.toLong << 32) + // Wasm intrinsic @inline def rotateLeft(i: scala.Long, distance: scala.Int): scala.Long = (i << distance) | (i >>> -distance) + // Wasm intrinsic @inline def rotateRight(i: scala.Long, distance: scala.Int): scala.Long = (i >>> distance) | (i << -distance) @@ -451,6 +455,7 @@ object Long { else 1 } + // Wasm intrinsic @inline def numberOfLeadingZeros(l: scala.Long): Int = { val hi = (l >>> 32).toInt @@ -458,6 +463,7 @@ object Long { else Integer.numberOfLeadingZeros(l.toInt) + 32 } + // Wasm intrinsic @inline def numberOfTrailingZeros(l: scala.Long): Int = { val lo = l.toInt diff --git a/javalanglib/src/main/scala/java/lang/Math.scala b/javalib/src/main/scala/java/lang/Math.scala similarity index 83% rename from javalanglib/src/main/scala/java/lang/Math.scala rename to javalib/src/main/scala/java/lang/Math.scala index eebe0d67e4..7d77391990 100644 --- a/javalanglib/src/main/scala/java/lang/Math.scala +++ b/javalib/src/main/scala/java/lang/Math.scala @@ -16,7 +16,7 @@ package lang import scala.scalajs.js import js.Dynamic.{ global => g } -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion object Math { @@ -24,43 +24,108 @@ object Math { final val PI = 3.141592653589793 @inline private def assumingES6: scala.Boolean = - linkingInfo.esVersion >= ESVersion.ES2015 + LinkingInfo.esVersion >= ESVersion.ES2015 @inline def abs(a: scala.Int): scala.Int = if (a < 0) -a else a @inline def abs(a: scala.Long): scala.Long = if (a < 0) -a else a + + // Wasm intrinsics @inline def abs(a: scala.Float): scala.Float = js.Math.abs(a).toFloat @inline def abs(a: scala.Double): scala.Double = js.Math.abs(a) @inline def max(a: scala.Int, b: scala.Int): scala.Int = if (a > b) a else b @inline def max(a: scala.Long, b: scala.Long): scala.Long = if (a > b) a else b + + // Wasm intrinsics @inline def max(a: scala.Float, b: scala.Float): scala.Float = js.Math.max(a, b).toFloat @inline def max(a: scala.Double, b: scala.Double): scala.Double = js.Math.max(a, b) @inline def min(a: scala.Int, b: scala.Int): scala.Int = if (a < b) a else b @inline def min(a: scala.Long, b: scala.Long): scala.Long = if (a < b) a else b + + // Wasm intrinsics @inline def min(a: scala.Float, b: scala.Float): scala.Float = js.Math.min(a, b).toFloat @inline def min(a: scala.Double, b: scala.Double): scala.Double = js.Math.min(a, b) + // Wasm intrinsics @inline def ceil(a: scala.Double): scala.Double = js.Math.ceil(a) @inline def floor(a: scala.Double): scala.Double = js.Math.floor(a) + // Wasm intrinsic def rint(a: scala.Double): scala.Double = { - val rounded = js.Math.round(a) - val mod = a % 1.0 - // The following test is also false for specials (0's, Infinities and NaN) - if (mod == 0.5 || mod == -0.5) { - // js.Math.round(a) rounds up but we have to round to even - if (rounded % 2.0 == 0.0) rounded - else rounded - 1.0 - } else { - rounded - } + /* Is the integer-valued `x` odd? Fused by hand of `(x.toLong & 1L) != 0L`. + * Corner cases: returns false for Infinities and NaN. + */ + @inline def isOdd(x: scala.Double): scala.Boolean = + (x.asInstanceOf[js.Dynamic] & 1.asInstanceOf[js.Dynamic]).asInstanceOf[Int] != 0 + + /* js.Math.round(a) does *almost* what we want. It rounds to nearest, + * breaking ties *up*. We need to break ties to *even*. So we need to + * detect ties, and for them, detect if we rounded to odd instead of even. + * + * The reasons why the apparently simple algorithm below works are subtle, + * and vary a lot depending on the range of `a`: + * + * - a is NaN + * r is NaN, then the == is false + * -> return r + * + * - a is +-Infinity + * r == a, then == is true! but isOdd(r) is false + * -> return r + * + * - 2**53 <= abs(a) < Infinity + * r == a, r - 0.5 rounds back to a so == is true! + * fortunately, isOdd(r) is false because all a >= 2**53 are even + * -> return r + * + * - 2**52 <= abs(a) < 2**53 + * r == a (because all a's are integers in that range) + * - a is odd + * r - 0.5 rounds down (towards even) to r - 1.0 + * so a == r - 0.5 is false + * -> return r + * - a is even + * r - 0.5 rounds back up! (towards even) to r + * so a == r - 0.5 is true! + * but, isOdd(r) is false + * -> return r + * + * - 0.5 < abs(a) < 2**52 + * then -2**52 + 0.5 <= a <= 2**52 - 0.5 (because values in-between are not representable) + * since Math.round rounds *up* on ties, r is an integer in the range (-2**52, 2**52] + * r - 0.5 is therefore lossless + * so a == r - 0.5 accurately detects ties, and isOdd(r) breaks ties + * -> return `r`` or `r - 1.0` + * + * - a == +0.5 + * r == 1.0 + * a == r - 0.5 is true and isOdd(r) is true + * -> return `r - 1.0`, which is +0.0 + * + * - a == -0.5 + * r == -0.0 + * a == r - 0.5 is true and isOdd(r) is false + * -> return `r`, which is -0.0 + * + * - 0.0 <= abs(a) < 0.5 + * r == 0.0 with the same sign as a + * a == r - 0.5 is false + * -> return r + */ + val r = js.Math.round(a) + if ((a == r - 0.5) && isOdd(r)) + r - 1.0 + else + r } @inline def round(a: scala.Float): scala.Int = js.Math.round(a).toInt @inline def round(a: scala.Double): scala.Long = js.Math.round(a).toLong + // Wasm intrinsic @inline def sqrt(a: scala.Double): scala.Double = js.Math.sqrt(a) + @inline def pow(a: scala.Double, b: scala.Double): scala.Double = js.Math.pow(a, b) @inline def exp(a: scala.Double): scala.Double = js.Math.exp(a) diff --git a/javalanglib/src/main/scala/java/lang/Number.scala b/javalib/src/main/scala/java/lang/Number.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/Number.scala rename to javalib/src/main/scala/java/lang/Number.scala diff --git a/javalanglib/src/main/scala/java/lang/Readable.scala b/javalib/src/main/scala/java/lang/Readable.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/Readable.scala rename to javalib/src/main/scala/java/lang/Readable.scala diff --git a/javalanglib/src/main/scala/java/lang/Runnable.scala b/javalib/src/main/scala/java/lang/Runnable.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/Runnable.scala rename to javalib/src/main/scala/java/lang/Runnable.scala diff --git a/javalanglib/src/main/scala/java/lang/Runtime.scala b/javalib/src/main/scala/java/lang/Runtime.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/Runtime.scala rename to javalib/src/main/scala/java/lang/Runtime.scala diff --git a/javalanglib/src/main/scala/java/lang/Short.scala b/javalib/src/main/scala/java/lang/Short.scala similarity index 97% rename from javalanglib/src/main/scala/java/lang/Short.scala rename to javalib/src/main/scala/java/lang/Short.scala index f44729b500..12149680ef 100644 --- a/javalanglib/src/main/scala/java/lang/Short.scala +++ b/javalib/src/main/scala/java/lang/Short.scala @@ -82,7 +82,7 @@ object Short { def parseShort(s: String, radix: Int): scala.Short = { val r = Integer.parseInt(s, radix) if (r < MIN_VALUE || r > MAX_VALUE) - throw new NumberFormatException("For input string: \"" + s + "\"") + throw new NumberFormatException(s"""For input string: "$s"""") else r.toShort } diff --git a/javalanglib/src/main/scala/java/lang/StackTrace.scala b/javalib/src/main/scala/java/lang/StackTrace.scala similarity index 78% rename from javalanglib/src/main/scala/java/lang/StackTrace.scala rename to javalib/src/main/scala/java/lang/StackTrace.scala index ef7fac256e..31960903b9 100644 --- a/javalanglib/src/main/scala/java/lang/StackTrace.scala +++ b/javalib/src/main/scala/java/lang/StackTrace.scala @@ -15,88 +15,85 @@ package java.lang import scala.annotation.tailrec import scala.scalajs.js +import scala.scalajs.js.JSStringOps.enableJSStringOps import Utils._ -import Utils.Implicits.enableJSStringOps /** Conversions of JavaScript stack traces to Java stack traces. */ private[lang] object StackTrace { /* !!! Note that in this unit, we go to great lengths *not* to use anything - * from the Scala collections library. + * from the collections library, and in general to use as little non-JS APIs + * as possible. * - * This minimizes the risk of runtime errors during the process of decoding + * This minimizes the risk of run-time errors during the process of decoding * errors, which would be very bad if it happened. */ /** Returns the current stack trace. - * If the stack trace cannot be analyzed in meaningful way (because we don't - * know the browser), an empty array is returned. + * + * If the stack trace cannot be analyzed in a meaningful way (normally, + * only in case we don't know the engine's format for stack traces), an + * empty array is returned. */ def getCurrentStackTrace(): Array[StackTraceElement] = - extract(createException().asInstanceOf[js.Dynamic]) + extract(new js.Error()) - /** Captures browser-specific state recording the current stack trace. + /** Captures a JavaScript error object recording the stack trace of the given + * `Throwable`. + * * The state is stored as a magic field of the throwable, and will be used * by `extract()` to create an Array[StackTraceElement]. */ - @inline def captureState(throwable: Throwable): Unit = { - val throwableAsJSAny = throwable.asInstanceOf[js.Any] + @inline def captureJSError(throwable: Throwable): Any = { + val reference = js.special.unwrapFromThrowable(throwable) val identifyingString: Any = { js.constructorOf[js.Object].prototype .selectDynamic("toString") - .call(throwableAsJSAny) + .call(reference.asInstanceOf[js.Any]) } if ("[object Error]" == identifyingString) { - /* The `throwable` has an `[[ErrorData]]` internal slot, which is as good + /* The `reference` has an `[[ErrorData]]` internal slot, which is as good * a guarantee as any that it contains stack trace data itself. In * practice, this happens when we emit ES 2015 classes, and no other * compiler down the line has compiled them away as ES 5.1 functions and * prototypes. */ - captureState(throwable, throwable) - } else if (js.constructorOf[js.Error].captureStackTrace eq ().asInstanceOf[AnyRef]) { - captureState(throwable, createException()) + reference + } else if ((js.constructorOf[js.Error].captureStackTrace eq ().asInstanceOf[AnyRef]) || + js.Object.isSealed(throwable.asInstanceOf[js.Object])) { + /* If `captureStackTrace` is not available, or if the `throwable` instance + * is sealed (which notably happens on Wasm), create a JS `Error` with the + * current stack trace. + */ + new js.Error() } else { /* V8-specific. - * The Error.captureStackTrace(e) method records the current stack trace - * on `e` as would do `new Error()`, thereby turning `e` into a proper - * exception. This avoids creating a dummy exception, but is mostly - * important so that Node.js will show stack traces if the exception - * is never caught and reaches the global event queue. + * + * The `Error.captureStackTrace(e)` method records the current stack + * trace on `e` as would do `new Error()`, thereby turning `e` into a + * proper exception. This avoids creating a dummy exception, but is + * mostly important so that Node.js will show stack traces if the + * exception is never caught and reaches the global event queue. + * + * We use the `throwable` itself instead of the `reference` in this case, + * since the latter is not under our control, and could even be a + * primitive value which cannot be passed to `captureStackTrace`. */ - js.constructorOf[js.Error].captureStackTrace(throwableAsJSAny) - captureState(throwable, throwable) + js.constructorOf[js.Error].captureStackTrace(throwable.asInstanceOf[js.Any]) + throwable } } - /** Creates a JS Error with the current stack trace state. */ - @inline private def createException(): Any = - new js.Error() - - /** Captures browser-specific state recording the stack trace of a JS error. - * The state is stored as a magic field of the throwable, and will be used - * by `extract()` to create an Array[StackTraceElement]. - */ - @inline def captureState(throwable: Throwable, e: Any): Unit = - throwable.setStackTraceStateInternal(e) - - /** Extracts a throwable's stack trace from captured browser-specific state. - * If no stack trace state has been recorded, or if the state cannot be - * analyzed in meaningful way (because we don't know the browser), an - * empty array is returned. - */ - def extract(throwable: Throwable): Array[StackTraceElement] = - extract(throwable.getStackTraceStateInternal()) - - /** Extracts a stack trace from captured browser-specific stackdata. - * If no stack trace state has been recorded, or if the state cannot be - * analyzed in meaningful way (because we don't know the browser), an - * empty array is returned. + /** Extracts a stack trace from a JavaScript error object. + * If the provided error is not a JavaScript object, or if its stack data + * otherwise cannot be analyzed in a meaningful way (normally, only in case + * we don't know the engine's format for stack traces), an empty array is + * returned. */ - private def extract(stackdata: Any): Array[StackTraceElement] = { - val lines = normalizeStackTraceLines(stackdata.asInstanceOf[js.Dynamic]) + def extract(jsError: Any): Array[StackTraceElement] = { + val lines = normalizeStackTraceLines(jsError.asInstanceOf[js.Dynamic]) normalizedLinesToStackTrace(lines) } @@ -112,8 +109,7 @@ private[lang] object StackTrace { private def normalizedLinesToStackTrace( lines: js.Array[String]): Array[StackTraceElement] = { - val NormalizedFrameLine = """^([^\@]*)\@(.*):([0-9]+)$""".re - val NormalizedFrameLineWithColumn = """^([^\@]*)\@(.*):([0-9]+):([0-9]+)$""".re + val NormalizedFrameLine = """^([^@]*)@(.*?):([0-9]+)(?::([0-9]+))?$""".re @inline def parseInt(s: String): Int = js.Dynamic.global.parseInt(s).asInstanceOf[Int] @@ -123,27 +119,18 @@ private[lang] object StackTrace { while (i < lines.length) { val line = lines(i) if (!line.isEmpty) { - val mtch1 = NormalizedFrameLineWithColumn.exec(line) - if (mtch1 ne null) { + val mtch = NormalizedFrameLine.exec(line) + if (mtch ne null) { val classAndMethodName = - extractClassMethod(undefOrForceGet(mtch1(1))) - val elem = new StackTraceElement(classAndMethodName(0), - classAndMethodName(1), undefOrForceGet(mtch1(2)), - parseInt(undefOrForceGet(mtch1(3)))) - elem.setColumnNumber(parseInt(undefOrForceGet(mtch1(4)))) - trace.push(elem) + extractClassMethod(undefOrForceGet(mtch(1))) + trace.push(new StackTraceElement(classAndMethodName(0), + classAndMethodName(1), undefOrForceGet(mtch(2)), + parseInt(undefOrForceGet(mtch(3))), + undefOrFold(mtch(4))(() => -1)(parseInt(_)))) } else { - val mtch2 = NormalizedFrameLine.exec(line) - if (mtch2 ne null) { - val classAndMethodName = - extractClassMethod(undefOrForceGet(mtch2(1))) - trace.push(new StackTraceElement(classAndMethodName(0), - classAndMethodName(1), undefOrForceGet(mtch2(2)), - parseInt(undefOrForceGet(mtch2(3))))) - } else { - // just in case - trace.push(new StackTraceElement("", line, null, -1)) - } + // just in case + // (explicitly use the constructor with column number so that STE has an inlineable init) + trace.push(new StackTraceElement("", line, null, -1, -1)) } } i += 1 @@ -228,12 +215,12 @@ private[lang] object StackTrace { if (i < compressedPrefixes.length) { val prefix = compressedPrefixes(i) if (encodedName.startsWith(prefix)) - dictRawApply(decompressedPrefixes, prefix) + encodedName.substring(prefix.length) + dictRawApply(decompressedPrefixes, prefix) + encodedName.jsSubstring(prefix.length) else loop(i+1) } else { // no prefix matches - if (encodedName.startsWith("L")) encodedName.substring(1) + if (encodedName.startsWith("L")) encodedName.jsSubstring(1) else encodedName // just in case } } @@ -250,8 +237,8 @@ private[lang] object StackTrace { var index = 0 while (index <= 22) { if (index >= 2) - dictSet(dict, "T" + index, "scala_Tuple" + index) - dictSet(dict, "F" + index, "scala_Function" + index) + dictSet(dict, s"T$index", s"scala_Tuple$index") + dictSet(dict, s"F$index", s"scala_Function$index") index += 1 } @@ -284,7 +271,7 @@ private[lang] object StackTrace { } else { val methodNameLen = encodedName.indexOf("__") if (methodNameLen < 0) encodedName - else encodedName.substring(0, methodNameLen) + else encodedName.jsSubstring(0, methodNameLen) } } @@ -304,7 +291,7 @@ private[lang] object StackTrace { */ private def normalizeStackTraceLines(e: js.Dynamic): js.Array[String] = { - import Utils.DynamicImplicits.{truthValue, number2dynamic} + import js.DynamicImplicits.{truthValue, number2dynamic} /* You would think that we could test once and for all which "mode" to * adopt. But the format can actually differ for different exceptions @@ -426,7 +413,7 @@ private[lang] object StackTrace { while (i < len) { val mtch = lineRE.exec(lines(i)) if (mtch ne null) { - val fnName = undefOrGetOrElse(mtch(3), "{anonymous}") + val fnName = undefOrGetOrElse(mtch(3))(() => "{anonymous}") result.push( fnName + "()@" + undefOrForceGet(mtch(2)) + ":" + undefOrForceGet(mtch(1)) @@ -451,7 +438,7 @@ private[lang] object StackTrace { while (i < len) { val mtch = lineRE.exec(lines(i)) if (mtch ne null) { - val fnName = undefOrFold(mtch(1))("global code", _ + "()") + val fnName = undefOrFold(mtch(1))(() => "global code")(_ + "()") result.push(fnName + "@" + undefOrForceGet(mtch(2)) + ":" + undefOrForceGet(mtch(3))) } i += 1 @@ -471,7 +458,7 @@ private[lang] object StackTrace { val mtch = lineRE.exec(lines(i)) if (mtch ne null) { val location = undefOrForceGet(mtch(4)) + ":" + undefOrForceGet(mtch(1)) + ":" + undefOrForceGet(mtch(2)) - val fnName0 = undefOrGetOrElse(mtch(2), "global code") + val fnName0 = undefOrGetOrElse(mtch(2))(() => "global code") val fnName = fnName0 .jsReplace("""""".re, "$1") .jsReplace("""""".re, "{anonymous}") diff --git a/javalanglib/src/main/scala/java/lang/StackTraceElement.scala b/javalib/src/main/scala/java/lang/StackTraceElement.scala similarity index 65% rename from javalanglib/src/main/scala/java/lang/StackTraceElement.scala rename to javalib/src/main/scala/java/lang/StackTraceElement.scala index d9e5f81274..8795a1de82 100644 --- a/javalanglib/src/main/scala/java/lang/StackTraceElement.scala +++ b/javalib/src/main/scala/java/lang/StackTraceElement.scala @@ -15,10 +15,16 @@ package java.lang import scala.scalajs.js import js.annotation.JSExport +/* The primary constructor, taking a `columnNumber`, is not part of the JDK + * API. It is used internally in `java.lang.StackTrace`, and could be accessed + * by third-party libraries with a bit of IR manipulation. + */ final class StackTraceElement(declaringClass: String, methodName: String, - fileName: String, lineNumber: Int) extends AnyRef with java.io.Serializable { + fileName: String, lineNumber: Int, private[this] var columnNumber: Int) + extends AnyRef with java.io.Serializable { - private[this] var columnNumber: Int = -1 + def this(declaringClass: String, methodName: String, fileName: String, lineNumber: Int) = + this(declaringClass, methodName, fileName, lineNumber, -1) def getFileName(): String = fileName def getLineNumber(): Int = lineNumber @@ -26,14 +32,11 @@ final class StackTraceElement(declaringClass: String, methodName: String, def getMethodName(): String = methodName def isNativeMethod(): scala.Boolean = false - /* Not part of the JDK API, used internally in java.lang and accessible - * through reflection. - */ + // Not part of the JDK API, accessible through reflection. def getColumnNumber(): Int = columnNumber - /* Not part of the JDK API, used internally in java.lang and accessible - * through reflection. - */ + // Not part of the JDK API, accessible through reflection. + @deprecated("old internal API; use the constructor with a column number instead", "1.11.0") def setColumnNumber(columnNumber: Int): Unit = this.columnNumber = columnNumber @@ -41,6 +44,7 @@ final class StackTraceElement(declaringClass: String, methodName: String, case that: StackTraceElement => (getFileName() == that.getFileName()) && (getLineNumber() == that.getLineNumber()) && + (getColumnNumber() == that.getColumnNumber()) && (getClassName() == that.getClassName()) && (getMethodName() == that.getMethodName()) case _ => @@ -70,6 +74,10 @@ final class StackTraceElement(declaringClass: String, methodName: String, } override def hashCode(): Int = { - declaringClass.hashCode() ^ methodName.hashCode() + declaringClass.hashCode() ^ + methodName.hashCode() ^ + fileName.hashCode() ^ + lineNumber ^ + columnNumber } } diff --git a/javalanglib/src/main/scala/java/lang/StringBuffer.scala b/javalib/src/main/scala/java/lang/StringBuffer.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/StringBuffer.scala rename to javalib/src/main/scala/java/lang/StringBuffer.scala diff --git a/javalanglib/src/main/scala/java/lang/StringBuilder.scala b/javalib/src/main/scala/java/lang/StringBuilder.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/StringBuilder.scala rename to javalib/src/main/scala/java/lang/StringBuilder.scala diff --git a/javalanglib/src/main/scala/java/lang/System.scala b/javalib/src/main/scala/java/lang/System.scala similarity index 93% rename from javalanglib/src/main/scala/java/lang/System.scala rename to javalib/src/main/scala/java/lang/System.scala index 0a4bb25fd5..976ea7ff15 100644 --- a/javalanglib/src/main/scala/java/lang/System.scala +++ b/javalib/src/main/scala/java/lang/System.scala @@ -16,9 +16,10 @@ import java.io._ import scala.scalajs.js import scala.scalajs.js.Dynamic.global -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo import java.{util => ju} +import java.util.function._ object System { /* System contains a bag of unrelated features. If we naively implement @@ -71,7 +72,7 @@ object System { private object NanoTime { val getHighPrecisionTime: js.Function0[scala.Double] = { - import Utils.DynamicImplicits.truthValue + import js.DynamicImplicits.truthValue if (js.typeOf(global.performance) != "undefined") { if (global.performance.now) { @@ -102,27 +103,27 @@ object System { def mismatch(): Nothing = throw new ArrayStoreException("Incompatible array types") - def impl(srcLen: Int, destLen: Int, f: js.Function2[Int, Int, Any]): Unit = { + def impl(srcLen: Int, destLen: Int, f: BiConsumer[Int, Int]): Unit = { /* Perform dummy swaps to trigger an ArrayIndexOutOfBoundsException or * UBE if the positions / lengths are bad. */ if (srcPos < 0 || destPos < 0) - f(destPos, srcPos) + f.accept(destPos, srcPos) if (length < 0) - f(length, length) + f.accept(length, length) if (srcPos > srcLen - length || destPos > destLen - length) - f(destPos + length, srcPos + length) + f.accept(destPos + length, srcPos + length) if ((src ne dest) || destPos < srcPos || srcPos + length < destPos) { var i = 0 while (i < length) { - f(i + destPos, i + srcPos) + f.accept(i + destPos, i + srcPos) i += 1 } } else { var i = length - 1 while (i >= 0) { - f(i + destPos, i + srcPos) + f.accept(i + destPos, i + srcPos) i -= 1 } } @@ -182,8 +183,8 @@ object System { } @inline - def identityHashCode(x: Object): scala.Int = - scala.scalajs.runtime.identityHashCode(x) + def identityHashCode(x: Any): scala.Int = + scala.scalajs.runtime.identityHashCode(x.asInstanceOf[AnyRef]) // System properties -------------------------------------------------------- @@ -200,7 +201,7 @@ object System { dictSet(result, "java.vm.specification.vendor", "Oracle Corporation") dictSet(result, "java.vm.specification.name", "Java Virtual Machine Specification") dictSet(result, "java.vm.name", "Scala.js") - dictSet(result, "java.vm.version", linkingInfo.linkerVersion) + dictSet(result, "java.vm.version", LinkingInfo.linkerVersion) dictSet(result, "java.specification.version", "1.8") dictSet(result, "java.specification.vendor", "Oracle Corporation") dictSet(result, "java.specification.name", "Java Platform API Specification") @@ -233,11 +234,11 @@ object System { } def getProperty(key: String): String = - if (dict ne null) dictGetOrElse(dict, key, null) + if (dict ne null) dictGetOrElse(dict, key)(() => null) else properties.getProperty(key) def getProperty(key: String, default: String): String = - if (dict ne null) dictGetOrElse(dict, key, default) + if (dict ne null) dictGetOrElse(dict, key)(() => default) else properties.getProperty(key, default) def clearProperty(key: String): String = @@ -349,7 +350,7 @@ private final class JSConsoleBasedPrintStream(isErr: scala.Boolean) // This is the method invoked by Predef.println(x). @inline - override def println(obj: AnyRef): Unit = printString("" + obj + "\n") + override def println(obj: AnyRef): Unit = printString(s"$obj\n") private def printString(s: String): Unit = { var rest: String = s @@ -382,13 +383,13 @@ private final class JSConsoleBasedPrintStream(isErr: scala.Boolean) override def close(): Unit = () private def doWriteLine(line: String): Unit = { - import Utils.DynamicImplicits.truthValue + import js.DynamicImplicits.truthValue if (js.typeOf(global.console) != "undefined") { if (isErr && global.console.error) - global.console.error(line.asInstanceOf[js.Any]) + global.console.error(line) else - global.console.log(line.asInstanceOf[js.Any]) + global.console.log(line) } } } diff --git a/javalanglib/src/main/scala/java/lang/Thread.scala b/javalib/src/main/scala/java/lang/Thread.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/Thread.scala rename to javalib/src/main/scala/java/lang/Thread.scala diff --git a/javalanglib/src/main/scala/java/lang/ThreadLocal.scala b/javalib/src/main/scala/java/lang/ThreadLocal.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/ThreadLocal.scala rename to javalib/src/main/scala/java/lang/ThreadLocal.scala diff --git a/javalanglib/src/main/scala/java/lang/Throwables.scala b/javalib/src/main/scala/java/lang/Throwables.scala similarity index 91% rename from javalanglib/src/main/scala/java/lang/Throwables.scala rename to javalib/src/main/scala/java/lang/Throwables.scala index 3124009f1c..af7701641f 100644 --- a/javalanglib/src/main/scala/java/lang/Throwables.scala +++ b/javalib/src/main/scala/java/lang/Throwables.scala @@ -12,6 +12,8 @@ package java.lang +import java.util.function._ + import scala.scalajs.js import scala.scalajs.js.annotation.JSExport @@ -24,7 +26,7 @@ class Throwable protected (s: String, private var e: Throwable, def this(s: String) = this(s, null) def this(e: Throwable) = this(if (e == null) null else e.toString, e) - private[this] var stackTraceStateInternal: Any = _ + private[this] var jsErrorForStackTrace: Any = _ private[this] var stackTrace: Array[StackTraceElement] = _ /* We use an Array rather than, say, a List, so that Throwable does not @@ -45,26 +47,14 @@ class Throwable protected (s: String, private var e: Throwable, def getLocalizedMessage(): String = getMessage() def fillInStackTrace(): Throwable = { - StackTrace.captureState(this) + jsErrorForStackTrace = StackTrace.captureJSError(this) this } - /* Not part of the JDK API, used internally in java.lang and accessible - * through reflection. - */ - def getStackTraceStateInternal(): Any = - stackTraceStateInternal - - /* Not part of the JDK API, used internally in java.lang and accessible - * through reflection. - */ - def setStackTraceStateInternal(e: Any): Unit = - stackTraceStateInternal = e - def getStackTrace(): Array[StackTraceElement] = { if (stackTrace eq null) { if (writableStackTrace) - stackTrace = StackTrace.extract(this) + stackTrace = StackTrace.extract(jsErrorForStackTrace) else stackTrace = new Array[StackTraceElement](0) } @@ -92,21 +82,21 @@ class Throwable protected (s: String, private var e: Throwable, def printStackTrace(s: java.io.PrintWriter): Unit = printStackTraceImpl(s.println(_)) - private[this] def printStackTraceImpl(sprintln: js.Function1[String, Unit]): Unit = { + private[this] def printStackTraceImpl(sprintln: Consumer[String]): Unit = { getStackTrace() // will init it if still null // Message - sprintln(toString) + sprintln.accept(toString) // Trace if (stackTrace.length != 0) { var i = 0 while (i < stackTrace.length) { - sprintln(" at "+stackTrace(i)) + sprintln.accept(s" at ${stackTrace(i)}") i += 1 } } else { - sprintln(" ") + sprintln.accept(" ") } // Causes @@ -119,7 +109,7 @@ class Throwable protected (s: String, private var e: Throwable, val thisLength = thisTrace.length val parentLength = parentTrace.length - sprintln("Caused by: " + wCause.toString) + sprintln.accept(s"Caused by: $wCause") if (thisLength != 0) { /* Count how many frames are shared between this stack trace and the @@ -141,14 +131,14 @@ class Throwable protected (s: String, private var e: Throwable, val lengthToPrint = thisLength - sameFrameCount var i = 0 while (i < lengthToPrint) { - sprintln(" at "+thisTrace(i)) + sprintln.accept(s" at ${thisTrace(i)}") i += 1 } if (sameFrameCount > 0) - sprintln(" ... " + sameFrameCount + " more") + sprintln.accept(s" ... $sameFrameCount more") } else { - sprintln(" ") + sprintln.accept(" ") } } } @@ -161,7 +151,7 @@ class Throwable protected (s: String, private var e: Throwable, val className = getClass().getName() val message = getMessage() if (message eq null) className - else className + ": " + message + else s"$className: $message" } def addSuppressed(exception: Throwable): Unit = { @@ -346,7 +336,7 @@ class ArithmeticException(s: String) extends RuntimeException(s) { } class ArrayIndexOutOfBoundsException(s: String) extends IndexOutOfBoundsException(s) { - def this(index: Int) = this("Array index out of range: " + index) + def this(index: Int) = this(s"Array index out of range: $index") def this() = this(null) } @@ -370,7 +360,7 @@ class CloneNotSupportedException(s: String) extends Exception(s) { } class EnumConstantNotPresentException(e: Class[_ <: Enum[_]], c: String) - extends RuntimeException(e.getName() + "." + c) { + extends RuntimeException(s"${e.getName()}.$c") { def enumType(): Class[_ <: Enum[_]] = e def constantName(): String = c } @@ -468,12 +458,12 @@ class SecurityException(s: String, e: Throwable) extends RuntimeException(s, e) } class StringIndexOutOfBoundsException(s: String) extends IndexOutOfBoundsException(s) { - def this(index: Int) = this("String index out of range: " + index) + def this(index: Int) = this(s"String index out of range: $index") def this() = this(null) } class TypeNotPresentException(t: String, e: Throwable) - extends RuntimeException("Type " + t + " not present", e) { + extends RuntimeException(s"Type $t not present", e) { def typeName(): String = t } diff --git a/javalanglib/src/main/scala/java/lang/Utils.scala b/javalib/src/main/scala/java/lang/Utils.scala similarity index 57% rename from javalanglib/src/main/scala/java/lang/Utils.scala rename to javalib/src/main/scala/java/lang/Utils.scala index 04438b6ec6..94d323e026 100644 --- a/javalanglib/src/main/scala/java/lang/Utils.scala +++ b/javalib/src/main/scala/java/lang/Utils.scala @@ -14,10 +14,15 @@ package java.lang import scala.language.implicitConversions +import java.util.function._ + import scala.scalajs.js -import scala.scalajs.js.annotation.JSBracketAccess +import scala.scalajs.js.annotation._ + +private[java] object Utils { + @inline + def undefined: js.UndefOr[Nothing] = ().asInstanceOf[js.UndefOr[Nothing]] -private[lang] object Utils { @inline def isUndefined(x: Any): scala.Boolean = x.asInstanceOf[AnyRef] eq ().asInstanceOf[AnyRef] @@ -31,19 +36,25 @@ private[lang] object Utils { x.asInstanceOf[A] @inline - def undefOrGetOrElse[A](x: js.UndefOr[A], default: A): A = + def undefOrGetOrElse[A](x: js.UndefOr[A])(default: Supplier[A]): A = if (undefOrIsDefined(x)) undefOrForceGet(x) - else default + else default.get() @inline - def undefOrGetOrElseCompute[A](x: js.UndefOr[A])(default: js.Function0[A]): A = + def undefOrGetOrNull[A >: Null](x: js.UndefOr[A]): A = if (undefOrIsDefined(x)) undefOrForceGet(x) - else default() + else null + + @inline + def undefOrForeach[A](x: js.UndefOr[A])(f: Consumer[A]): Unit = { + if (undefOrIsDefined(x)) + f.accept(undefOrForceGet(x)) + } @inline - def undefOrFold[A, B](x: js.UndefOr[A])(default: B, f: js.Function1[A, B]): B = + def undefOrFold[A, B](x: js.UndefOr[A])(default: Supplier[B])(f: Function[A, B]): B = if (undefOrIsDefined(x)) f(undefOrForceGet(x)) - else default + else default.get() private object Cache { val safeHasOwnProperty = @@ -69,12 +80,17 @@ private[lang] object Utils { def rawUpdate(key: String, value: A): Unit = js.native } - def dictGetOrElse[A](dict: js.Dictionary[_ <: A], key: String, - default: A): A = { + @inline + def dictEmpty[A](): js.Dictionary[A] = + new js.Object().asInstanceOf[js.Dictionary[A]] + + @inline + def dictGetOrElse[A](dict: js.Dictionary[_ <: A], key: String)( + default: Supplier[A]): A = { if (dictContains(dict, key)) dictRawApply(dict, key) else - default + default.get() } def dictGetOrElseAndRemove[A](dict: js.Dictionary[_ <: A], key: String, @@ -106,8 +122,10 @@ private[lang] object Utils { @js.native private trait MapRaw[K, V] extends js.Object { def has(key: K): scala.Boolean = js.native - def get(key: K): js.UndefOr[V] = js.native + def get(key: K): V = js.native + @JSName("get") def getOrUndefined(key: K): js.UndefOr[V] = js.native def set(key: K, value: V): Unit = js.native + def keys(): js.Iterator[K] = js.native } @inline @@ -115,7 +133,7 @@ private[lang] object Utils { map.asInstanceOf[MapRaw[K, V]].has(key) @inline - def mapGet[K, V](map: js.Map[K, V], key: K): js.UndefOr[V] = + def mapGet[K, V](map: js.Map[K, V], key: K): V = map.asInstanceOf[MapRaw[K, V]].get(key) @inline @@ -123,37 +141,55 @@ private[lang] object Utils { map.asInstanceOf[MapRaw[K, V]].set(key, value) @inline - def forArrayElems[A](array: js.Array[A])(f: js.Function1[A, Any]): Unit = { + def mapGetOrElse[K, V](map: js.Map[K, V], key: K)(default: Supplier[V]): V = { + val value = map.asInstanceOf[MapRaw[K, V]].getOrUndefined(key) + if (!isUndefined(value) || mapHas(map, key)) value.asInstanceOf[V] + else default.get() + } + + @inline + def mapGetOrElseUpdate[K, V](map: js.Map[K, V], key: K)(default: Supplier[V]): V = { + mapGetOrElse(map, key) { () => + val value = default.get() + mapSet(map, key, value) + value + } + } + + @inline + def forArrayElems[A](array: js.Array[A])(f: Consumer[A]): Unit = { val len = array.length var i = 0 while (i != len) { - f(array(i)) + f.accept(array(i)) i += 1 } } - @inline def toUint(x: scala.Double): scala.Double = - (x.asInstanceOf[js.Dynamic] >>> 0.asInstanceOf[js.Dynamic]).asInstanceOf[scala.Double] - - object Implicits { - implicit def enableJSStringOps(x: String): js.JSStringOps = - x.asInstanceOf[js.JSStringOps] + @inline + def arrayRemove[A](array: js.Array[A], index: Int): Unit = + array.splice(index, 1) - implicit def enableJSNumberOps(x: Int): js.JSNumberOps = - x.asInstanceOf[js.JSNumberOps] + @inline + def arrayRemoveAndGet[A](array: js.Array[A], index: Int): A = + array.splice(index, 1)(0) - implicit def enableJSNumberOps(x: scala.Double): js.JSNumberOps = - x.asInstanceOf[js.JSNumberOps] + @inline + def arrayExists[A](array: js.Array[A])(f: Predicate[A]): scala.Boolean = { + // scalastyle:off return + val len = array.length + var i = 0 + while (i != len) { + if (f.test(array(i))) + return true + i += 1 + } + false + // scalastyle:on return } - object DynamicImplicits { - @inline implicit def truthValue(x: js.Dynamic): scala.Boolean = - (!(!x)).asInstanceOf[scala.Boolean] - - implicit def number2dynamic(x: scala.Double): js.Dynamic = - x.asInstanceOf[js.Dynamic] - - implicit def boolean2dynamic(x: scala.Boolean): js.Dynamic = - x.asInstanceOf[js.Dynamic] + @inline def toUint(x: scala.Double): scala.Double = { + import js.DynamicImplicits.number2dynamic + (x >>> 0).asInstanceOf[scala.Double] } } diff --git a/javalanglib/src/main/scala/java/lang/Void.scala b/javalib/src/main/scala/java/lang/Void.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/Void.scala rename to javalib/src/main/scala/java/lang/Void.scala diff --git a/javalanglib/src/main/scala/java/lang/_String.scala b/javalib/src/main/scala/java/lang/_String.scala similarity index 89% rename from javalanglib/src/main/scala/java/lang/_String.scala rename to javalib/src/main/scala/java/lang/_String.scala index 2d1ebcee0b..ea29540e37 100644 --- a/javalanglib/src/main/scala/java/lang/_String.scala +++ b/javalib/src/main/scala/java/lang/_String.scala @@ -18,17 +18,17 @@ import java.util.Comparator import scala.scalajs.js import scala.scalajs.js.annotation._ -import scala.scalajs.runtime.linkingInfo +import scala.scalajs.js.JSStringOps.enableJSStringOps +import scala.scalajs.LinkingInfo import scala.scalajs.LinkingInfo.ESVersion import java.lang.constant.{Constable, ConstantDesc} import java.nio.ByteBuffer import java.nio.charset.Charset import java.util.Locale +import java.util.function._ import java.util.regex._ -import Utils.Implicits.enableJSStringOps - /* This is the implementation of java.lang.String, which is a hijacked class. * Its instances are primitive strings. Constructors are not emitted. * @@ -48,86 +48,34 @@ final class _String private () // scalastyle:ignore this.asInstanceOf[String] @inline - def charAt(index: Int): Char = { - this.asInstanceOf[js.Dynamic] - .charCodeAt(index.asInstanceOf[js.Dynamic]) - .asInstanceOf[Int] - .toChar - } + def length(): Int = + throw new Error("stub") // body replaced by the compiler back-end + @inline + def charAt(index: Int): Char = + throw new Error("stub") // body replaced by the compiler back-end + + // Wasm intrinsic def codePointAt(index: Int): Int = { - if (linkingInfo.esVersion >= ESVersion.ES2015) { - this.asInstanceOf[js.Dynamic] - .codePointAt(index.asInstanceOf[js.Dynamic]) - .asInstanceOf[Int] + if (LinkingInfo.esVersion >= ESVersion.ES2015) { + charAt(index) // bounds check + this.asInstanceOf[js.Dynamic].codePointAt(index).asInstanceOf[Int] } else { - val high = charAt(index) - if (index+1 < length()) { - val low = charAt(index+1) - if (Character.isSurrogatePair(high, low)) - Character.toCodePoint(high, low) - else - high.toInt - } else { - high.toInt - } + Character.codePointAtImpl(this, index) } } - def codePointBefore(index: Int): Int = { - val low = charAt(index - 1) - if (index > 1) { - val high = charAt(index - 2) - if (Character.isSurrogatePair(high, low)) - Character.toCodePoint(high, low) - else - low.toInt - } else { - low.toInt - } - } + @noinline + def codePointBefore(index: Int): Int = + Character.codePointBeforeImpl(this, index) + @noinline def codePointCount(beginIndex: Int, endIndex: Int): Int = - Character.codePointCount(this, beginIndex, endIndex) + Character.codePointCountImpl(this, beginIndex, endIndex) - def offsetByCodePoints(index: Int, codePointOffset: Int): Int = { - val len = length() - if (index < 0 || index > len) - throw new StringIndexOutOfBoundsException(index) - - if (codePointOffset >= 0) { - var i = 0 - var result = index - while (i != codePointOffset) { - if (result >= len) - throw new StringIndexOutOfBoundsException - if ((result < len - 1) && - Character.isHighSurrogate(charAt(result)) && - Character.isLowSurrogate(charAt(result + 1))) { - result += 2 - } else { - result += 1 - } - i += 1 - } - result - } else { - var i = 0 - var result = index - while (i != codePointOffset) { - if (result <= 0) - throw new StringIndexOutOfBoundsException - if ((result > 1) && Character.isLowSurrogate(charAt(result - 1)) && - Character.isHighSurrogate(charAt(result - 2))) { - result -= 2 - } else { - result -= 1 - } - i -= 1 - } - result - } - } + @noinline + def offsetByCodePoints(index: Int, codePointOffset: Int): Int = + Character.offsetByCodePointsImpl(this, index, codePointOffset) override def hashCode(): Int = { var res = 0 @@ -215,18 +163,25 @@ final class _String private () // scalastyle:ignore def contains(s: CharSequence): scala.Boolean = indexOf(s.toString) != -1 - def endsWith(suffix: String): scala.Boolean = - thisString.jsSubstring(this.length() - suffix.length()) == suffix + @inline + def endsWith(suffix: String): scala.Boolean = { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { + suffix.getClass() // null check + thisString.asInstanceOf[js.Dynamic].endsWith(suffix).asInstanceOf[scala.Boolean] + } else { + thisString.jsSubstring(this.length() - suffix.length()) == suffix + } + } def getBytes(): Array[scala.Byte] = - getBytes(Charset.defaultCharset) + getBytes(Charset.defaultCharset()) def getBytes(charsetName: String): Array[scala.Byte] = getBytes(Charset.forName(charsetName)) def getBytes(charset: Charset): Array[scala.Byte] = { val buf = charset.encode(thisString) - val res = new Array[scala.Byte](buf.remaining) + val res = new Array[scala.Byte](buf.remaining()) buf.get(res) res } @@ -284,10 +239,6 @@ final class _String private () // scalastyle:ignore if (fromIndex < 0) -1 else thisString.jsLastIndexOf(str, fromIndex) - @inline - def length(): Int = - this.asInstanceOf[js.Dynamic].length.asInstanceOf[Int] - @inline def matches(regex: String): scala.Boolean = Pattern.matches(regex, thisString) @@ -299,8 +250,8 @@ final class _String private () // scalastyle:ignore ooffset: Int, len: Int): scala.Boolean = { if (other == null) { throw new NullPointerException() - } else if (toffset < 0 || ooffset < 0 || toffset + len > this.length() || - ooffset + len > other.length()) { + } else if (toffset < 0 || ooffset < 0 || len > this.length() - toffset || + len > other.length() - ooffset) { false } else if (len <= 0) { true @@ -320,6 +271,12 @@ final class _String private () // scalastyle:ignore def repeat(count: Int): String = { if (count < 0) { throw new IllegalArgumentException + } else if (LinkingInfo.esVersion >= ESVersion.ES2015) { + /* This will throw a `js.RangeError` if `count` is too large, instead of + * an `OutOfMemoryError`. That's fine because the behavior of `repeat` is + * not specified for `count` too large. + */ + this.asInstanceOf[js.Dynamic].repeat(count).asInstanceOf[String] } else if (thisString == "" || count == 0) { "" } else if (thisString.length > (Int.MaxValue / count)) { @@ -359,28 +316,53 @@ final class _String private () // scalastyle:ignore Pattern.compile(regex).split(thisString, limit) @inline - def startsWith(prefix: String): scala.Boolean = - startsWith(prefix, 0) + def startsWith(prefix: String): scala.Boolean = { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { + prefix.getClass() // null check + thisString.asInstanceOf[js.Dynamic].startsWith(prefix).asInstanceOf[scala.Boolean] + } else { + thisString.jsSubstring(0, prefix.length()) == prefix + } + } @inline def startsWith(prefix: String, toffset: Int): scala.Boolean = { - (toffset <= length() && toffset >= 0 && - thisString.jsSubstring(toffset, toffset + prefix.length()) == prefix) + if (LinkingInfo.esVersion >= ESVersion.ES2015) { + prefix.getClass() // null check + (toffset <= length() && toffset >= 0 && + thisString.asInstanceOf[js.Dynamic].startsWith(prefix, toffset).asInstanceOf[scala.Boolean]) + } else { + (toffset <= length() && toffset >= 0 && + thisString.jsSubstring(toffset, toffset + prefix.length()) == prefix) + } } @inline def subSequence(beginIndex: Int, endIndex: Int): CharSequence = substring(beginIndex, endIndex) + // Wasm intrinsic @inline - def substring(beginIndex: Int): String = + def substring(beginIndex: Int): String = { + // Bounds check + if (beginIndex < 0 || beginIndex > length()) + charAt(beginIndex) + thisString.jsSubstring(beginIndex) + } + // Wasm intrinsic @inline def substring(beginIndex: Int, endIndex: Int): String = { - this.asInstanceOf[js.Dynamic] - .substring(beginIndex.asInstanceOf[js.Dynamic], endIndex.asInstanceOf[js.Dynamic]) - .asInstanceOf[String] + // Bounds check + if (beginIndex < 0) + charAt(beginIndex) + if (endIndex > length()) + charAt(endIndex) + if (endIndex < beginIndex) + charAt(-1) + + thisString.jsSubstring(beginIndex, endIndex) } def toCharArray(): Array[Char] = { @@ -662,9 +644,7 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { * the character at the given index is not special. */ @inline - private def replaceCharsAtIndex( - replacementAtIndex: js.Function1[Int, String]): String = { - + private def replaceCharsAtIndex(replacementAtIndex: IntFunction[String]): String = { var prep = "" val len = this.length() var i = 0 @@ -692,7 +672,7 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { val len = length() var j = i while (j != len) { - val cp = codePointAt(j) + val cp = this.codePointAt(j) if (combiningClassNoneOrAboveOrOther(cp) != CombiningClassIsOther) return j j += charCount(cp) @@ -706,7 +686,7 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { import Character._ var j = i while (j > 0) { - val cp = codePointBefore(j) + val cp = this.codePointBefore(j) if (combiningClassNoneOrAboveOrOther(cp) != CombiningClassIsOther) return j j -= charCount(cp) @@ -797,7 +777,7 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { def indent(n: Int): String = { - def forEachLn(f: js.Function1[String, String]): String = { + def forEachLn(f: Function[String, String]): String = { var out = "" var i = 0 val xs = splitLines() @@ -918,7 +898,7 @@ for (cp <- 0 to Character.MAX_CODE_POINT) { result += codePoint.toChar // bad escape otherwise, this catches everything else including the Unicode ones case bad => - throw new IllegalArgumentException("Illegal escape: `\\" + bad + "`") + throw new IllegalArgumentException(s"Illegal escape: `\\$bad`") } // skip ahead 2 chars (\ and the escape char) at minimum, cases above can add more if needed i += 2 @@ -970,7 +950,7 @@ object _String { // scalastyle:ignore } def `new`(bytes: Array[scala.Byte]): String = - `new`(bytes, Charset.defaultCharset) + `new`(bytes, Charset.defaultCharset()) def `new`(bytes: Array[scala.Byte], charsetName: String): String = `new`(bytes, Charset.forName(charsetName)) @@ -979,7 +959,7 @@ object _String { // scalastyle:ignore charset.decode(ByteBuffer.wrap(bytes)).toString() def `new`(bytes: Array[scala.Byte], offset: Int, length: Int): String = - `new`(bytes, offset, length, Charset.defaultCharset) + `new`(bytes, offset, length, Charset.defaultCharset()) def `new`(bytes: Array[scala.Byte], offset: Int, length: Int, charsetName: String): String = @@ -1034,9 +1014,9 @@ object _String { // scalastyle:ignore `new`(data, offset, count) def format(format: String, args: Array[AnyRef]): String = - new java.util.Formatter().format(format, args: _*).toString() + new java.util.Formatter().format(format, args).toString() def format(l: Locale, format: String, args: Array[AnyRef]): String = - new java.util.Formatter(l).format(format, args: _*).toString() + new java.util.Formatter(l).format(format, args).toString() } diff --git a/javalanglib/src/main/scala/java/lang/annotation/Annotation.scala b/javalib/src/main/scala/java/lang/annotation/Annotation.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/annotation/Annotation.scala rename to javalib/src/main/scala/java/lang/annotation/Annotation.scala diff --git a/javalanglib/src/main/scala/java/lang/constant/Constable.scala b/javalib/src/main/scala/java/lang/constant/Constable.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/constant/Constable.scala rename to javalib/src/main/scala/java/lang/constant/Constable.scala diff --git a/javalanglib/src/main/scala/java/lang/constant/ConstantDesc.scala b/javalib/src/main/scala/java/lang/constant/ConstantDesc.scala similarity index 100% rename from javalanglib/src/main/scala/java/lang/constant/ConstantDesc.scala rename to javalib/src/main/scala/java/lang/constant/ConstantDesc.scala diff --git a/javalanglib/src/main/scala/java/lang/reflect/Array.scala b/javalib/src/main/scala/java/lang/reflect/Array.scala similarity index 76% rename from javalanglib/src/main/scala/java/lang/reflect/Array.scala rename to javalib/src/main/scala/java/lang/reflect/Array.scala index 688d97b5d1..ac2f23d2b0 100644 --- a/javalanglib/src/main/scala/java/lang/reflect/Array.scala +++ b/javalib/src/main/scala/java/lang/reflect/Array.scala @@ -17,18 +17,37 @@ import scala.scalajs.js import java.lang.Class object Array { + @inline def newInstance(componentType: Class[_], length: Int): AnyRef = - componentType.newArrayOfThisClass(js.Array(length)) + throw new Error("Stub filled in by the compiler") def newInstance(componentType: Class[_], dimensions: scala.Array[Int]): AnyRef = { - val jsDims = js.Array[Int]() + def rec(componentType: Class[_], offset: Int): AnyRef = { + val length = dimensions(offset) + val result = newInstance(componentType, length) + val innerOffset = offset + 1 + if (innerOffset < dimensions.length) { + val result2 = result.asInstanceOf[Array[AnyRef]] + val innerComponentType = componentType.getComponentType() + var i = 0 + while (i != length) { + result2(i) = rec(innerComponentType, innerOffset) + i += 1 + } + } + result + } + val len = dimensions.length - var i = 0 + if (len == 0) + throw new IllegalArgumentException() + var outermostComponentType = componentType + var i = 1 while (i != len) { - jsDims.push(dimensions(i)) + outermostComponentType = newInstance(outermostComponentType, 0).getClass() i += 1 } - componentType.newArrayOfThisClass(jsDims) + rec(outermostComponentType, 0) } def getLength(array: AnyRef): Int = array match { @@ -42,7 +61,7 @@ object Array { case array: Array[Long] => array.length case array: Array[Float] => array.length case array: Array[Double] => array.length - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def get(array: AnyRef, index: Int): AnyRef = array match { @@ -55,28 +74,28 @@ object Array { case array: Array[Long] => java.lang.Long.valueOf(array(index)) case array: Array[Float] => java.lang.Float.valueOf(array(index)) case array: Array[Double] => java.lang.Double.valueOf(array(index)) - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def getBoolean(array: AnyRef, index: Int): Boolean = array match { case array: Array[Boolean] => array(index) - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def getChar(array: AnyRef, index: Int): Char = array match { - case array: Array[Char] => array(index) - case _ => throw new IllegalArgumentException("argument type mismatch") + case array: Array[Char] => array(index) + case _ => mismatch(array) } def getByte(array: AnyRef, index: Int): Byte = array match { case array: Array[Byte] => array(index) - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def getShort(array: AnyRef, index: Int): Short = array match { case array: Array[Short] => array(index) case array: Array[Byte] => array(index) - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def getInt(array: AnyRef, index: Int): Int = array match { @@ -84,7 +103,7 @@ object Array { case array: Array[Char] => array(index) case array: Array[Byte] => array(index) case array: Array[Short] => array(index) - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def getLong(array: AnyRef, index: Int): Long = array match { @@ -93,7 +112,7 @@ object Array { case array: Array[Byte] => array(index) case array: Array[Short] => array(index) case array: Array[Int] => array(index) - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def getFloat(array: AnyRef, index: Int): Float = array match { @@ -103,7 +122,7 @@ object Array { case array: Array[Short] => array(index) case array: Array[Int] => array(index).toFloat case array: Array[Long] => array(index).toFloat - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def getDouble(array: AnyRef, index: Int): Double = array match { @@ -114,7 +133,7 @@ object Array { case array: Array[Int] => array(index) case array: Array[Long] => array(index).toDouble case array: Array[Float] => array(index) - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def set(array: AnyRef, index: Int, value: AnyRef): Unit = array match { @@ -129,13 +148,13 @@ object Array { case value: Long => setLong(array, index, value) case value: Float => setFloat(array, index, value) case value: Double => setDouble(array, index, value) - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } } def setBoolean(array: AnyRef, index: Int, value: Boolean): Unit = array match { case array: Array[Boolean] => array(index) = value - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def setChar(array: AnyRef, index: Int, value: Char): Unit = array match { @@ -144,7 +163,7 @@ object Array { case array: Array[Long] => array(index) = value case array: Array[Float] => array(index) = value case array: Array[Double] => array(index) = value - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def setByte(array: AnyRef, index: Int, value: Byte): Unit = array match { @@ -154,7 +173,7 @@ object Array { case array: Array[Long] => array(index) = value case array: Array[Float] => array(index) = value case array: Array[Double] => array(index) = value - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def setShort(array: AnyRef, index: Int, value: Short): Unit = array match { @@ -163,7 +182,7 @@ object Array { case array: Array[Long] => array(index) = value case array: Array[Float] => array(index) = value case array: Array[Double] => array(index) = value - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def setInt(array: AnyRef, index: Int, value: Int): Unit = array match { @@ -171,24 +190,29 @@ object Array { case array: Array[Long] => array(index) = value case array: Array[Float] => array(index) = value.toFloat case array: Array[Double] => array(index) = value - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def setLong(array: AnyRef, index: Int, value: Long): Unit = array match { case array: Array[Long] => array(index) = value case array: Array[Float] => array(index) = value.toFloat case array: Array[Double] => array(index) = value.toDouble - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def setFloat(array: AnyRef, index: Int, value: Float): Unit = array match { case array: Array[Float] => array(index) = value case array: Array[Double] => array(index) = value - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) } def setDouble(array: AnyRef, index: Int, value: Double): Unit = array match { case array: Array[Double] => array(index) = value - case _ => throw new IllegalArgumentException("argument type mismatch") + case _ => mismatch(array) + } + + private def mismatch(array: AnyRef): Nothing = { + array.getClass() // null check + throw new IllegalArgumentException("argument type mismatch") } } diff --git a/javalib/src/main/scala/java/math/BigDecimal.scala b/javalib/src/main/scala/java/math/BigDecimal.scala index 322ef61c44..d045ffc57e 100644 --- a/javalib/src/main/scala/java/math/BigDecimal.scala +++ b/javalib/src/main/scala/java/math/BigDecimal.scala @@ -58,8 +58,13 @@ object BigDecimal { private final val LongFivePows = newArrayOfPows(28, 5) - private final val LongFivePowsBitLength = - Array.tabulate[Int](LongFivePows.length)(i => bitLength(LongFivePows(i))) + private final val LongFivePowsBitLength = { + val len = LongFivePows.length + val result = new Array[Int](len) + for (i <- 0 until len) + result(i) = bitLength(LongFivePows(i)) + result + } /** An array of longs with powers of ten. * @@ -68,8 +73,13 @@ object BigDecimal { */ private[math] final val LongTenPows = newArrayOfPows(19, 10) - private final val LongTenPowsBitLength = - Array.tabulate[Int](LongTenPows.length)(i => bitLength(LongTenPows(i))) + private final val LongTenPowsBitLength = { + val len = LongTenPows.length + val result = new Array[Int](len) + for (i <- 0 until len) + result(i) = bitLength(LongTenPows(i)) + result + } private final val BigIntScaledByZeroLength = 11 @@ -77,15 +87,23 @@ object BigDecimal { * * ([0,0],[1,0],...,[10,0]). */ - private final val BigIntScaledByZero = - Array.tabulate[BigDecimal](BigIntScaledByZeroLength)(new BigDecimal(_, 0)) + private final val BigIntScaledByZero = { + val result = new Array[BigDecimal](BigIntScaledByZeroLength) + for (i <- 0 until BigIntScaledByZeroLength) + result(i) = new BigDecimal(i, 0) + result + } /** An array with the zero number scaled by the first positive scales. * * (0*10^0, 0*10^1, ..., 0*10^10). */ - private final val ZeroScaledBy = - Array.tabulate[BigDecimal](BigIntScaledByZeroLength)(new BigDecimal(0, _)) + private final val ZeroScaledBy = { + val result = new Array[BigDecimal](BigIntScaledByZeroLength) + for (i <- 0 until BigIntScaledByZeroLength) + result(i) = new BigDecimal(0, i) + result + } /** A string filled with 100 times the character `'0'`. * It is not a `final` val so that it isn't copied at every call site. @@ -205,8 +223,13 @@ object BigDecimal { else 0 } - private[math] def newArrayOfPows(len: Int, pow: Int): Array[Long] = - Array.iterate(1L, len)(_ * pow) + private[math] def newArrayOfPows(len: Int, pow: Int): Array[Long] = { + val result = new Array[Long](len) + result(0) = 1L + for (i <- 1 until len) + result(i) = result(i - 1) * pow + result + } /** Return an increment that can be -1,0 or 1, depending on {@code roundingMode}. * @@ -276,11 +299,20 @@ object BigDecimal { 32 - java.lang.Integer.numberOfLeadingZeros(smallValue) } - @inline - private def charNotEqualTo(c: Char, cs: Char*): Boolean = !cs.contains(c) + private def charNotEqualTo(c: Char, cs: Array[Char]): Boolean = !charEqualTo(c, cs) - @inline - private def charEqualTo(c: Char, cs: Char*): Boolean = cs.contains(c) + private def charEqualTo(c: Char, cs: Array[Char]): Boolean = { + // scalastyle:off return + val len = cs.length + var i = 0 + while (i != len) { + if (cs(i) == c) + return true + i += 1 + } + false + // scalastyle:on return + } @inline private def insertString(s: String, pos: Int, s2: String): String = @@ -374,12 +406,12 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { if (offset <= last && in(offset) == '+') { index += 1 // Fail if the next character is another sign. - if (index < last && charEqualTo(in(index), '+', '-')) + if (index < last && charEqualTo(in(index), Array('+', '-'))) throw new NumberFormatException("For input string: " + in.toString) } else { // check that '-' is not followed by another sign val isMinus = index <= last && in(index) == '-' - val nextIsSign = index + 1 < last && charEqualTo(in(index + 1), '+', '-') + val nextIsSign = index + 1 < last && charEqualTo(in(index + 1), Array('+', '-')) if (isMinus && nextIsSign) throw new NumberFormatException("For input string: " + in.toString) } @@ -388,7 +420,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { var counter = 0 var wasNonZero = false // Accumulating all digits until a possible decimal point - while (index <= last && charNotEqualTo(in(index), '.', 'e', 'E')) { + while (index <= last && charNotEqualTo(in(index), Array('.', 'e', 'E'))) { if (!wasNonZero) { if (in(index) == '0') counter += 1 else wasNonZero = true @@ -404,7 +436,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { index += 1 // Accumulating all digits until a possible exponent val begin = index - while (index <= last && charNotEqualTo(in(index), 'e', 'E')) { + while (index <= last && charNotEqualTo(in(index), Array('e', 'E'))) { if (!wasNonZero) { if (in(index) == '0') counter += 1 else wasNonZero = true @@ -420,7 +452,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { } // An exponent was found - if ((index <= last) && charEqualTo(in(index), 'e', 'E')) { + if ((index <= last) && charEqualTo(in(index), Array('e', 'E'))) { index += 1 // Checking for a possible sign of scale val indexIsPlus = index <= last && in(index) == '+' @@ -811,7 +843,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { val (q, l) = loop(1, q1.shiftRight(k), 0) // If abs(q) != 1 then the quotient is periodic - if (q.abs() != BigInteger.ONE) { + if (!q.abs().equals(BigInteger.ONE)) { throw new ArithmeticException( "Non-terminating decimal expansion; no exact representable decimal result") } @@ -1194,9 +1226,8 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { def stripTrailingZeros(): BigDecimal = { if (isZero) { - // Preserve RI compatibility, so BigDecimal.equals (which checks - // value *and* scale) continues to work. - this + // As specified by the JavaDoc, we must return BigDecimal.ZERO, which has a scale of 0 + BigDecimal.ZERO } else { val lastPow = BigTenPows.length - 1 @@ -1264,7 +1295,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { case that: BigDecimal => that._scale == this._scale && ( if (_bitLength < 64) that._smallValue == this._smallValue - else this._intVal == that._intVal) + else this._intVal.equals(that._intVal)) case _ => false } @@ -1477,116 +1508,14 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { def byteValueExact(): Byte = valueExact(8).toByte - override def floatValue(): Float = { - /* A similar code like in doubleValue() could be repeated here, - * but this simple implementation is quite efficient. */ - val powerOfTwo = this._bitLength - (_scale / Log2).toLong - val floatResult0: Float = signum().toFloat - val floatResult: Float = { - if (powerOfTwo < -149 || floatResult0 == 0.0f) // 'this' is very small - floatResult0 * 0.0f - else if (powerOfTwo > 129) // 'this' is very large - floatResult0 * Float.PositiveInfinity - else - doubleValue().toFloat - } - floatResult - } + @noinline override def floatValue(): Float = + java.lang.Float.parseFloat(toStringForFloatingPointValue()) - override def doubleValue(): Double = { - val sign = signum() - val powerOfTwo = this._bitLength - (_scale / Log2).toLong + @noinline override def doubleValue(): Double = + java.lang.Double.parseDouble(toStringForFloatingPointValue()) - if (powerOfTwo < -1074 || sign == 0) { - // Cases which 'this' is very small - sign * 0.0d - } else if (powerOfTwo > 1025) { - // Cases which 'this' is very large - sign * Double.PositiveInfinity - } else { - val mantissa0 = getUnscaledValue.abs() - var exponent = 1076 // bias + 53 - - val mantissa = { - if (_scale <= 0) { - mantissa0.multiply(powerOf10(-_scale)) - } else { - val powerOfTen: BigInteger = powerOf10(_scale) - val k = 100 - powerOfTwo.toInt - val m = { - if (k > 0) { - /* Computing (mantissa * 2^k) , where 'k' is a enough big - * power of '2' to can divide by 10^s */ - exponent -= k - mantissa0.shiftLeft(k) - } else { - mantissa0 - } - } - // Computing (mantissa * 2^k) / 10^s - val qr = m.divideAndRemainderImpl(powerOfTen) - // To check if the fractional part >= 0.5 - val compRem = qr.rem.shiftLeftOneBit().compareTo(powerOfTen) - // To add two rounded bits at end of mantissa - exponent -= 2 - qr.quot.shiftLeft(2).add(BigInteger.valueOf((compRem * (compRem + 3)) / 2 + 1)) - } - } - - val lowestSetBit = mantissa.getLowestSetBit() - val discardedSize = mantissa.bitLength() - 54 - var bits: Long = 0L // IEEE-754 Standard - var tempBits: Long = 0L // for temporal calculations - if (discardedSize > 0) { // (#bits > 54) - bits = mantissa.shiftRight(discardedSize).longValue() - tempBits = bits - if (((bits & 1) == 1 && lowestSetBit < discardedSize) || (bits & 3) == 3) - bits += 2 - } else { // (#bits <= 54) - bits = mantissa.longValue() << -discardedSize - tempBits = bits - if ((bits & 3) == 3) - bits += 2 - } - // Testing bit 54 to check if the carry creates a new binary digit - if ((bits & 0x40000000000000L) == 0) { - // To drop the last bit of mantissa (first discarded) - bits >>= 1 - exponent += discardedSize - } else { - // #bits = 54 - bits >>= 2 - exponent += (discardedSize + 1) - } - // To test if the 53-bits number fits in 'double' - if (exponent > 2046) { - // (exponent - bias > 1023) - sign * Double.PositiveInfinity - } else if (exponent < -53) { - sign * 0.0d - } else { - if (exponent <= 0) { - bits = tempBits >> 1 - tempBits = bits & (-1L >>> (63 + exponent)) - bits >>= (-exponent) - // To test if after discard bits, a new carry is generated - if (((bits & 3) == 3) || - (((bits & 1) == 1) && (tempBits != 0) && (lowestSetBit < discardedSize))) { - bits += 1 - } - exponent = 0 - bits >>= 1 - } - - // Construct the 64 double bits: [sign(1), exponent(11), mantissa(52)] - val resultBits = - (sign & 0x8000000000000000L) | - (exponent.toLong << 52) | - (bits & 0xFFFFFFFFFFFFFL) - java.lang.Double.longBitsToDouble(resultBits) - } - } - } + @inline private def toStringForFloatingPointValue(): String = + s"${unscaledValue()}e${-scale()}" def ulp(): BigDecimal = valueOf(1, _scale) diff --git a/javalib/src/main/scala/java/math/BigInteger.scala b/javalib/src/main/scala/java/math/BigInteger.scala index 2de2c425cb..9864183573 100644 --- a/javalib/src/main/scala/java/math/BigInteger.scala +++ b/javalib/src/main/scala/java/math/BigInteger.scala @@ -45,6 +45,7 @@ import scala.annotation.tailrec import java.util.Random import java.util.ScalaOps._ +import java.util.function._ object BigInteger { @@ -74,7 +75,12 @@ object BigInteger { new BigInteger(1, 4), new BigInteger(1, 5), new BigInteger(1, 6), new BigInteger(1, 7), new BigInteger(1, 8), new BigInteger(1, 9), TEN) - private final val TWO_POWS = Array.tabulate[BigInteger](32)(i => BigInteger.valueOf(1L << i)) + private final val TWO_POWS = { + val result = new Array[BigInteger](32) + for (i <- 0 until 32) + result(i) = BigInteger.valueOf(1L << i) + result + } /** The first non zero digit is either -1 if sign is zero, otherwise it is >= 0. * @@ -119,10 +125,9 @@ object BigInteger { reference } - @inline - private def checkCriticalArgument(expression: Boolean, errorMessage: => String): Unit = { - if (!expression) - throw new IllegalArgumentException(errorMessage) + private[math] def checkRangeBasedOnIntArrayLength(byteLength: Int): Unit = { + if (byteLength < 0 || byteLength >= ((Int.MaxValue + 1) >>> 5)) + throw new ArithmeticException("BigInteger would overflow supported range") } @inline @@ -211,7 +216,10 @@ class BigInteger extends Number with Comparable[BigInteger] { def this(numBits: Int, rnd: Random) = { this() - checkCriticalArgument(numBits >= 0, "numBits must be non-negative") + + if (numBits < 0) + throw new IllegalArgumentException("numBits must be non-negative") + if (numBits == 0) { sign = 0 numberLength = 1 @@ -496,6 +504,13 @@ class BigInteger extends Number with Comparable[BigInteger] { override def intValue(): Int = sign * digits(0) + def intValueExact(): Int = { + if (numberLength <= 1 && bitLength() < Integer.SIZE) + intValue() + else + throw new ArithmeticException("BigInteger out of int range") + } + def isProbablePrime(certainty: Int): Boolean = Primality.isProbablePrime(abs(), certainty) @@ -506,6 +521,13 @@ class BigInteger extends Number with Comparable[BigInteger] { sign * value } + def longValueExact(): Long = { + if (numberLength <= 2 && bitLength() < java.lang.Long.SIZE) + longValue() + else + throw new ArithmeticException("BigInteger out of long range") + } + def max(bi: BigInteger): BigInteger = { if (this.compareTo(bi) == GREATER) this else bi @@ -649,13 +671,13 @@ class BigInteger extends Number with Comparable[BigInteger] { def shiftLeft(n: Int): BigInteger = { if (n == 0 || sign == 0) this else if (n > 0) BitLevel.shiftLeft(this, n) - else BitLevel.shiftRight(this, -n) + else BitLevel.shiftRight(this, -n) // -n is interpreted as unsigned, so MinValue is fine } def shiftRight(n: Int): BigInteger = { if (n == 0 || sign == 0) this else if (n > 0) BitLevel.shiftRight(this, n) - else BitLevel.shiftLeft(this, -n) + else BitLevel.shiftLeft(this, -n) // -n is interpreted as unsigned, so MinValue is fine } def signum(): Int = sign @@ -715,9 +737,9 @@ class BigInteger extends Number with Comparable[BigInteger] { @inline @tailrec - def loopBytes(tempDigit: Int => Unit): Unit = { + def loopBytes(tempDigit: IntConsumer): Unit = { if (bytesLen > firstByteNumber) { - tempDigit(digitIndex) + tempDigit.accept(digitIndex) loopBytes(tempDigit) } } diff --git a/javalib/src/main/scala/java/math/BitLevel.scala b/javalib/src/main/scala/java/math/BitLevel.scala index e1e3dae417..1b83daa754 100644 --- a/javalib/src/main/scala/java/math/BitLevel.scala +++ b/javalib/src/main/scala/java/math/BitLevel.scala @@ -220,10 +220,11 @@ private[math] object BitLevel { * @return */ def shiftLeft(source: BigInteger, count: Int): BigInteger = { - val intCount: Int = count >> 5 + val intCount: Int = count >>> 5 // interpret count as unsigned to deal with -MinValue val andCount: Int = count & 31 val offset = if (andCount == 0) 0 else 1 val resLength: Int = source.numberLength + intCount + offset + BigInteger.checkRangeBasedOnIntArrayLength(resLength) val resDigits = new Array[Int](resLength) shiftLeft(resDigits, source.digits, intCount, andCount) val result = new BigInteger(source.sign, resLength, resDigits) @@ -298,7 +299,7 @@ private[math] object BitLevel { * @return */ def shiftRight(source: BigInteger, count: Int): BigInteger = { - val intCount: Int = count >> 5 + val intCount: Int = count >>> 5 // interpret count as unsigned to deal with -MinValue val andCount: Int = count & 31 // count of remaining bits if (intCount >= source.numberLength) { diff --git a/javalib/src/main/scala/java/math/Conversion.scala b/javalib/src/main/scala/java/math/Conversion.scala index 260996598a..cb8c233ef5 100644 --- a/javalib/src/main/scala/java/math/Conversion.scala +++ b/javalib/src/main/scala/java/math/Conversion.scala @@ -282,37 +282,4 @@ private[math] object Conversion { else result } } - - def bigInteger2Double(bi: BigInteger): Double = { - if (bi.numberLength < 2 || ((bi.numberLength == 2) && (bi.digits(1) > 0))) { - bi.longValue().toDouble - } else if (bi.numberLength > 32) { - if (bi.sign > 0) Double.PositiveInfinity - else Double.NegativeInfinity - } else { - val bitLen = bi.abs().bitLength() - var exponent: Long = bitLen - 1 - val delta = bitLen - 54 - val lVal = bi.abs().shiftRight(delta).longValue() - var mantissa = lVal & 0x1FFFFFFFFFFFFFL - - if (exponent == 1023 && mantissa == 0X1FFFFFFFFFFFFFL) { - if (bi.sign > 0) Double.PositiveInfinity - else Double.NegativeInfinity - } else if (exponent == 1023 && mantissa == 0x1FFFFFFFFFFFFEL) { - if (bi.sign > 0) Double.MaxValue - else -Double.MaxValue - } else { - val droppedBits = BitLevel.nonZeroDroppedBits(delta, bi.digits) - if (((mantissa & 1) == 1) && (((mantissa & 2) == 2) || droppedBits)) - mantissa += 2 - - mantissa >>= 1 - val resSign = if (bi.sign < 0) 0x8000000000000000L else 0 - exponent = ((1023 + exponent) << 52) & 0x7FF0000000000000L - val result = resSign | exponent | mantissa - java.lang.Double.longBitsToDouble(result) - } - } - } } diff --git a/javalib/src/main/scala/java/math/Logical.scala b/javalib/src/main/scala/java/math/Logical.scala index 62dc37493f..7f40b12da6 100644 --- a/javalib/src/main/scala/java/math/Logical.scala +++ b/javalib/src/main/scala/java/math/Logical.scala @@ -44,7 +44,7 @@ private[math] object Logical { // scalastyle:off return if (bi.sign == 0) { BigInteger.MINUS_ONE - } else if (bi == BigInteger.MINUS_ONE) { + } else if (bi.equals(BigInteger.MINUS_ONE)) { BigInteger.ZERO } else { val resDigits = new Array[Int](bi.numberLength + 1) @@ -88,9 +88,9 @@ private[math] object Logical { def and(bi: BigInteger, that: BigInteger): BigInteger = { if (that.sign == 0 || bi.sign == 0) BigInteger.ZERO - else if (that == BigInteger.MINUS_ONE) + else if (that.equals(BigInteger.MINUS_ONE)) bi - else if (bi == BigInteger.MINUS_ONE) + else if (bi.equals(BigInteger.MINUS_ONE)) that else if (bi.sign > 0 && that.sign > 0) andPositive(bi, that) @@ -235,9 +235,9 @@ private[math] object Logical { bi else if (bi.sign == 0) BigInteger.ZERO - else if (bi == BigInteger.MINUS_ONE) + else if (bi.equals(BigInteger.MINUS_ONE)) that.not() - else if (that == BigInteger.MINUS_ONE) + else if (that.equals(BigInteger.MINUS_ONE)) BigInteger.ZERO else if (bi.sign > 0 && that.sign > 0) andNotPositive(bi, that) @@ -446,7 +446,7 @@ private[math] object Logical { /** @see BigInteger#or(BigInteger) */ def or(bi: BigInteger, that: BigInteger): BigInteger = { - if (that == BigInteger.MINUS_ONE || bi == BigInteger.MINUS_ONE) { + if (that.equals(BigInteger.MINUS_ONE) || bi.equals(BigInteger.MINUS_ONE)) { BigInteger.MINUS_ONE } else if (that.sign == 0) { bi @@ -593,9 +593,9 @@ private[math] object Logical { bi } else if (bi.sign == 0) { that - } else if (that == BigInteger.MINUS_ONE) { + } else if (that.equals(BigInteger.MINUS_ONE)) { bi.not() - } else if (bi == BigInteger.MINUS_ONE) { + } else if (bi.equals(BigInteger.MINUS_ONE)) { that.not() } else if (bi.sign > 0) { if (that.sign > 0) { diff --git a/javalib/src/main/scala/java/math/Multiplication.scala b/javalib/src/main/scala/java/math/Multiplication.scala index 9f0ca4188e..859d9f926f 100644 --- a/javalib/src/main/scala/java/math/Multiplication.scala +++ b/javalib/src/main/scala/java/math/Multiplication.scala @@ -449,6 +449,11 @@ private[math] object Multiplication { } } - private def newArrayOfPows(len: Int, pow: Int): Array[Int] = - Array.iterate(1, len)(_ * pow) + private def newArrayOfPows(len: Int, pow: Int): Array[Int] = { + val result = new Array[Int](len) + result(0) = 1 + for (i <- 1 until len) + result(i) = result(i - 1) * pow + result + } } diff --git a/javalib/src/main/scala/java/math/Primality.scala b/javalib/src/main/scala/java/math/Primality.scala index 527fbe7a93..b7fd19101b 100644 --- a/javalib/src/main/scala/java/math/Primality.scala +++ b/javalib/src/main/scala/java/math/Primality.scala @@ -79,8 +79,13 @@ private[math] object Primality { (18, 13), (31, 23), (54, 43), (97, 75)) /** All {@code BigInteger} prime numbers with bit length lesser than 8 bits. */ - private val BiPrimes = - Array.tabulate[BigInteger](Primes.length)(i => BigInteger.valueOf(Primes(i))) + private val BiPrimes = { + val len = Primes.length + val result = new Array[BigInteger](len) + for (i <- 0 until len) + result(i) = BigInteger.valueOf(Primes(i)) + result + } /** A random number is generated until a probable prime number is found. * @@ -134,13 +139,15 @@ private[math] object Primality { Arrays.binarySearch(Primes, n.digits(0)) >= 0 } else { // To check if 'n' is divisible by some prime of the table - for (i <- 1 until Primes.length) { + var i: Int = 1 + val primesLength = Primes.length + while (i != primesLength) { if (Division.remainderArrayByInt(n.digits, n.numberLength, Primes(i)) == 0) return false + i += 1 } // To set the number of iterations necessary for Miller-Rabin test - var i: Int = 0 val bitLength = n.bitLength() i = 2 while (bitLength < Bits(i)) { @@ -218,13 +225,15 @@ private[math] object Primality { } } // To execute Miller-Rabin for non-divisible numbers by all first primes - for (j <- 0 until gapSize) { + var j = 0 + while (j != gapSize) { if (!isDivisible(j)) { Elementary.inplaceAdd(probPrime, j) if (millerRabin(probPrime, certainty)) { return probPrime } } + j += 1 } Elementary.inplaceAdd(startPoint, gapSize) } @@ -251,7 +260,9 @@ private[math] object Primality { val k = nMinus1.getLowestSetBit() val q = nMinus1.shiftRight(k) val rnd = new Random() - for (i <- 0 until t) { + + var i = 0 + while (i != t) { // To generate a witness 'x', first it use the primes of table if (i < Primes.length) { x = BiPrimes(i) @@ -267,17 +278,21 @@ private[math] object Primality { } y = x.modPow(q, n) - if (!(y.isOne || y == nMinus1)) { - for (j <- 1 until k) { - if (y != nMinus1) { + if (!(y.isOne || y.equals(nMinus1))) { + var j = 1 + while (j != k) { + if (!y.equals(nMinus1)) { y = y.multiply(y).mod(n) if (y.isOne) return false } + j += 1 } - if (y != nMinus1) + if (!y.equals(nMinus1)) return false } + + i += 1 } true // scalastyle:on return diff --git a/javalib/src/main/scala/java/math/RoundingMode.scala b/javalib/src/main/scala/java/math/RoundingMode.scala index afc7c3567a..58d800f71a 100644 --- a/javalib/src/main/scala/java/math/RoundingMode.scala +++ b/javalib/src/main/scala/java/math/RoundingMode.scala @@ -58,7 +58,7 @@ object RoundingMode { var i = 0 while (i != len) { val value = values(i) - if (value.name == name) + if (value.name() == name) return value i += 1 } diff --git a/javalib/src/main/scala/java/net/URI.scala b/javalib/src/main/scala/java/net/URI.scala index 553dd72e8b..cb19355b36 100644 --- a/javalib/src/main/scala/java/net/URI.scala +++ b/javalib/src/main/scala/java/net/URI.scala @@ -17,6 +17,7 @@ import scala.scalajs.js import scala.annotation.tailrec +import java.lang.Utils._ import java.nio._ import java.nio.charset.{CodingErrorAction, StandardCharsets} @@ -35,38 +36,60 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { if (_fld == null) throw new URISyntaxException(origStr, "Malformed URI") - private val _isAbsolute = fld(AbsScheme).isDefined - private val _isOpaque = fld(AbsOpaquePart).isDefined + private val _isAbsolute = undefOrIsDefined(_fld(AbsScheme)) + private val _isOpaque = undefOrIsDefined(_fld(AbsOpaquePart)) - @inline private def fld(idx: Int): js.UndefOr[String] = _fld(idx) + @inline private def fld(idx: Int): String = undefOrGetOrNull(_fld(idx)) - @inline private def fld(absIdx: Int, relIdx: Int): js.UndefOr[String] = - if (_isAbsolute) _fld(absIdx) else _fld(relIdx) + @inline private def fld(absIdx: Int, relIdx: Int): String = + if (_isAbsolute) fld(absIdx) else fld(relIdx) + /** Nullable */ private val _scheme = fld(AbsScheme) + /** Non-nullable */ private val _schemeSpecificPart = { if (!_isAbsolute) fld(RelSchemeSpecificPart) else if (_isOpaque) fld(AbsOpaquePart) else fld(AbsHierPart) - }.get + } + + /** Nullable */ + private val _authority = { + val authPart = fld(AbsAuthority, RelAuthority) + if (authPart == "") null else authPart + } - private val _authority = fld(AbsAuthority, RelAuthority).filter(_ != "") + /** Nullable */ private val _userInfo = fld(AbsUserInfo, RelUserInfo) + + /** Nullable */ private val _host = fld(AbsHost, RelHost) - private val _port = fld(AbsPort, RelPort).map(Integer.parseInt(_)) + /** `-1` means not present */ + private val _port = { + val portPart = fld(AbsPort, RelPort) + if (portPart == null) -1 else Integer.parseInt(portPart) + } + + /** Nullable */ private val _path = { - val useNetPath = fld(AbsAuthority, RelAuthority).isDefined - if (useNetPath) - fld(AbsNetPath, RelNetPath) orElse "" - else if (_isAbsolute) + val useNetPath = fld(AbsAuthority, RelAuthority) != null + if (useNetPath) { + val netPath = fld(AbsNetPath, RelNetPath) + if (netPath == null) "" else netPath + } else if (_isAbsolute) { fld(AbsAbsPath) - else - fld(RelAbsPath) orElse fld(RelRelPath) + } else { + val relAbsPath = fld(RelAbsPath) + if (relAbsPath != null) relAbsPath else fld(RelRelPath) + } } + /** Nullable */ private val _query = fld(AbsQuery, RelQuery) + + /** Nullable */ private val _fragment = fld(Fragment) // End of default ctor. Unset helper field @@ -94,26 +117,20 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { } def compareTo(that: URI): Int = { - import URI.{escapeAwareCompare => cmp} + import URI.{caseInsensitiveCompare, escapeAwareCompare => cmp} - @inline - def cmpOpt[T](x: js.UndefOr[T], y: js.UndefOr[T])(comparator: (T, T) => Int): Int = { - if (x == y) 0 - // Undefined components are considered less than defined components - else x.fold(-1)(s1 => y.fold(1)(s2 => comparator(s1, s2))) - } def comparePathQueryFragement(): Int = { - val cmpPath = cmpOpt(this._path, that._path)(cmp) + val cmpPath = cmp(this._path, that._path) if (cmpPath != 0) { cmpPath } else { - val cmpQuery = cmpOpt(this._query, that._query)(cmp) + val cmpQuery = cmp(this._query, that._query) if (cmpQuery != 0) cmpQuery - else cmpOpt(this._fragment, that._fragment)(cmp) + else cmp(this._fragment, that._fragment) } } - val cmpScheme = cmpOpt(this._scheme, that._scheme)(_.compareToIgnoreCase(_)) + val cmpScheme = caseInsensitiveCompare(this._scheme, that._scheme) if (cmpScheme != 0) { cmpScheme } else { @@ -126,22 +143,22 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { val cmpSchemeSpecificPart = cmp(this._schemeSpecificPart, that._schemeSpecificPart) if (cmpSchemeSpecificPart != 0) cmpSchemeSpecificPart else comparePathQueryFragement() - } else if (this._host.isDefined && that._host.isDefined) { - val cmpUserInfo = cmpOpt(this._userInfo, that._userInfo)(cmp) + } else if (this._host != null && that._host != null) { + val cmpUserInfo = cmp(this._userInfo, that._userInfo) if (cmpUserInfo != 0) { cmpUserInfo } else { - val cmpHost = cmpOpt(this._host, that._host)(_.compareToIgnoreCase(_)) + val cmpHost = caseInsensitiveCompare(this._host, that._host) if (cmpHost != 0) { cmpHost } else { - val cmpPort = cmpOpt(this._port, that._port)(_ - _) + val cmpPort = this._port - that._port // absent as -1 is smaller than valid port numbers if (cmpPort != 0) cmpPort else comparePathQueryFragement() } } } else { - val cmpAuthority = cmpOpt(this._authority, that._authority)(cmp) + val cmpAuthority = cmp(this._authority, that._authority) if (cmpAuthority != 0) cmpAuthority else comparePathQueryFragement() } @@ -154,50 +171,54 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { case _ => false } - def getAuthority(): String = _authority.map(decodeComponent).orNull - def getFragment(): String = _fragment.map(decodeComponent).orNull - def getHost(): String = _host.orNull - def getPath(): String = _path.map(decodeComponent).orNull - def getPort(): Int = _port.getOrElse(-1) - def getQuery(): String = _query.map(decodeComponent).orNull - def getRawAuthority(): String = _authority.orNull - def getRawFragment(): String = _fragment.orNull - def getRawPath(): String = _path.orNull - def getRawQuery(): String = _query.orNull + def getAuthority(): String = decodeComponent(_authority) + def getFragment(): String = decodeComponent(_fragment) + def getHost(): String = _host + def getPath(): String = decodeComponent(_path) + def getPort(): Int = _port + def getQuery(): String = decodeComponent(_query) + def getRawAuthority(): String = _authority + def getRawFragment(): String = _fragment + def getRawPath(): String = _path + def getRawQuery(): String = _query def getRawSchemeSpecificPart(): String = _schemeSpecificPart - def getRawUserInfo(): String = _userInfo.orNull - def getScheme(): String = _scheme.orNull + def getRawUserInfo(): String = _userInfo + def getScheme(): String = _scheme def getSchemeSpecificPart(): String = decodeComponent(_schemeSpecificPart) - def getUserInfo(): String = _userInfo.map(decodeComponent).orNull + def getUserInfo(): String = decodeComponent(_userInfo) override def hashCode(): Int = { - import scala.util.hashing.MurmurHash3._ + import java.util.internal.MurmurHash3._ import URI.normalizeEscapes + def normalizeEscapesHash(str: String): Int = + if (str == null) 0 + else normalizeEscapes(str).hashCode() + var acc = URI.uriSeed - acc = mix(acc, _scheme.map(_.toLowerCase).##) // scheme may not contain escapes + acc = mix(acc, if (_scheme == null) 0 else _scheme.toLowerCase.hashCode()) // scheme may not contain escapes if (this.isOpaque()) { - acc = mix(acc, normalizeEscapes(this._schemeSpecificPart).##) - } else if (this._host.isDefined) { - acc = mix(acc, normalizeEscapes(this._userInfo).##) - acc = mix(acc, this._host.map(_.toLowerCase).##) - acc = mix(acc, this._port.##) + acc = mix(acc, normalizeEscapesHash(this._schemeSpecificPart)) + } else if (this._host != null) { + acc = mix(acc, normalizeEscapesHash(this._userInfo)) + acc = mix(acc, this._host.toLowerCase.hashCode()) + acc = mix(acc, this._port.hashCode()) } else { - acc = mix(acc, normalizeEscapes(this._authority).##) + acc = mix(acc, normalizeEscapesHash(this._authority)) } - acc = mix(acc, normalizeEscapes(this._path).##) - acc = mix(acc, normalizeEscapes(this._query).##) - acc = mixLast(acc, normalizeEscapes(this._fragment).##) + acc = mix(acc, normalizeEscapesHash(this._path)) + acc = mix(acc, normalizeEscapesHash(this._query)) + acc = mixLast(acc, normalizeEscapesHash(this._fragment)) finalizeHash(acc, 3) } def isAbsolute(): Boolean = _isAbsolute def isOpaque(): Boolean = _isOpaque - def normalize(): URI = if (_isOpaque || _path.isEmpty) this else { + def normalize(): URI = if (_isOpaque || _path == null) this else { import js.JSStringOps._ - val origPath = _path.get + val origPath = _path val segments = origPath.jsSplit("/") @@ -277,19 +298,16 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { } def parseServerAuthority(): URI = { - if (_authority.nonEmpty && _host.isEmpty) + if (_authority != null && _host == null) throw new URISyntaxException(origStr, "No Host in URI") else this } def relativize(uri: URI): URI = { - def authoritiesEqual = this._authority.fold(uri._authority.isEmpty) { a1 => - uri._authority.fold(false)(a2 => URI.escapeAwareCompare(a1, a2) == 0) - } - - if (this.isOpaque() || uri.isOpaque() || - this._scheme != uri._scheme || !authoritiesEqual) uri - else { + if (this.isOpaque() || uri.isOpaque() || this._scheme != uri._scheme || + URI.escapeAwareCompare(this._authority, uri._authority) != 0) { + uri + } else { val thisN = this.normalize() val uriN = uri.normalize() @@ -309,8 +327,8 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { def resolve(uri: URI): URI = { if (uri.isAbsolute() || this.isOpaque()) uri - else if (uri._scheme.isEmpty && uri._authority.isEmpty && - uri._path.get == "" && uri._query.isEmpty) + else if (uri._scheme == null && uri._authority == null && + uri._path == "" && uri._query == null) // This is a special case for URIs like: "#foo". This allows to // just change the fragment in the current document. new URI( @@ -319,14 +337,14 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { this.getRawPath(), this.getRawQuery(), uri.getRawFragment()) - else if (uri._authority.isDefined) + else if (uri._authority != null) new URI( this.getScheme(), uri.getRawAuthority(), uri.getRawPath(), uri.getRawQuery(), uri.getRawFragment()) - else if (uri._path.get.startsWith("/")) + else if (uri._path.startsWith("/")) new URI( this.getScheme(), this.getRawAuthority(), @@ -334,8 +352,8 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { uri.getRawQuery(), uri.getRawFragment()) else { - val basePath = this._path.get - val relPath = uri._path.get + val basePath = this._path + val relPath = uri._path val endIdx = basePath.lastIndexOf('/') val path = if (endIdx == -1) relPath @@ -678,8 +696,8 @@ object URI { // scalastyle:on return } - // Fast-track, if no encoded components - if (containsNoEncodedComponent()) { + // Fast-track, if null or no encoded components + if (str == null || containsNoEncodedComponent()) { str } else { val inBuf = CharBuffer.wrap(str) @@ -822,9 +840,23 @@ object URI { str.jsReplace(nonASCIIQuoteRe, quoteStr) } + /** Case-insensitive comparison that accepts `null` values. + * + * `null` is considered smaller than any other value. + */ + private def caseInsensitiveCompare(x: String, y: String): Int = { + if (x == null) + if (y == null) 0 else -1 + else + if (y == null) 1 else x.compareToIgnoreCase(y) + } + /** Case-sensitive comparison that is case-insensitive inside URI * escapes. Will compare `a%A0` and `a%a0` as equal, but `a%A0` and * `A%A0` as different. + * + * Accepts `null` arguments. `null` is considered smaller than any other + * value. */ private def escapeAwareCompare(x: String, y: String): Int = { @tailrec @@ -846,12 +878,17 @@ object URI { } } - loop(0) + if (x == null) + if (y == null) 0 else -1 + else + if (y == null) 1 else loop(0) } - /** Upper-cases all URI escape sequences in `str`. Used for hashing */ - private def normalizeEscapes(maybeStr: js.UndefOr[String]): js.UndefOr[String] = { - maybeStr.map { str => + /** Upper-cases all URI escape sequences in the nullable `str`. Used for hashing */ + private def normalizeEscapes(str: String): String = { + if (str == null) { + null + } else { var i = 0 var res = "" while (i < str.length) { diff --git a/javalib/src/main/scala/java/net/URLDecoder.scala b/javalib/src/main/scala/java/net/URLDecoder.scala index 68e11e617d..5bf068cf36 100644 --- a/javalib/src/main/scala/java/net/URLDecoder.scala +++ b/javalib/src/main/scala/java/net/URLDecoder.scala @@ -21,21 +21,15 @@ import java.nio.charset.{Charset, CharsetDecoder} object URLDecoder { @Deprecated - def decode(s: String): String = decodeImpl(s, () => Charset.defaultCharset()) + def decode(s: String): String = decode(s, Charset.defaultCharset()) def decode(s: String, enc: String): String = { - decodeImpl(s, { () => - /* An exception is thrown only if the - * character encoding needs to be consulted - */ - if (!Charset.isSupported(enc)) - throw new UnsupportedEncodingException(enc) - else - Charset.forName(enc) - }) + if (!Charset.isSupported(enc)) + throw new UnsupportedEncodingException(enc) + decode(s, Charset.forName(enc)) } - private def decodeImpl(s: String, getCharset: js.Function0[Charset]): String = { + def decode(s: String, charset: Charset): String = { val len = s.length val charBuffer = CharBuffer.allocate(len) @@ -60,7 +54,7 @@ object URLDecoder { case '%' => if (decoder == null) { // equivalent to `byteBuffer == null` - decoder = getCharset().newDecoder() + decoder = charset.newDecoder() byteBuffer = ByteBuffer.allocate(len / 3) } else { byteBuffer.clear() diff --git a/javalib/src/main/scala/java/net/URLEncoder.scala b/javalib/src/main/scala/java/net/URLEncoder.scala new file mode 100644 index 0000000000..1f9d200b50 --- /dev/null +++ b/javalib/src/main/scala/java/net/URLEncoder.scala @@ -0,0 +1,121 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.net + +import scala.annotation.switch + +import java.io.UnsupportedEncodingException +import java.nio.{CharBuffer, ByteBuffer} +import java.nio.charset.{Charset, CharsetDecoder} + +import java.util.ScalaOps._ +import java.nio.charset.CodingErrorAction + +object URLEncoder { + private final val EncodeAsIsLength = 128 + + private val EncodedAsIs: Array[Boolean] = { + val r = new Array[Boolean](EncodeAsIsLength) // initialized with false + r('.') = true + r('-') = true + r('*') = true + r('_') = true + for (c <- '0'.toInt to '9'.toInt) + r(c) = true + for (c <- 'A'.toInt to 'Z'.toInt) + r(c) = true + for (c <- 'a'.toInt to 'z'.toInt) + r(c) = true + r + } + + private val PercentEncoded: Array[String] = { + val hexDigits = "0123456789ABCDEF" + val r = new Array[String](256) + for (b <- 0 until 256) + r(b) = "%" + hexDigits.charAt(b >>> 4) + hexDigits.charAt(b & 0xf) + r + } + + @Deprecated + def encode(s: String): String = encode(s, Charset.defaultCharset()) + + def encode(s: String, enc: String): String = { + if (!Charset.isSupported(enc)) + throw new UnsupportedEncodingException(enc) + encode(s, Charset.forName(enc)) + } + + def encode(s: String, charset: Charset): String = { + val EncodedAsIs = this.EncodedAsIs // local copy + + @inline def encodeAsIs(c: Char): Boolean = + c < EncodeAsIsLength && EncodedAsIs(c) + + @inline def encodeUsingCharset(c: Char): Boolean = + c != ' ' && !encodeAsIs(c) + + var len = s.length() + var i = 0 + + while (i != len && encodeAsIs(s.charAt(i))) + i += 1 + + if (i == len) { + s + } else { + val PercentEncoded = this.PercentEncoded // local copy + + val charBuffer = CharBuffer.wrap(s) + val encoder = charset.newEncoder().onUnmappableCharacter(CodingErrorAction.REPLACE) + val bufferArray = new Array[Byte](((len - i + 1) * encoder.maxBytesPerChar()).toInt) + val buffer = ByteBuffer.wrap(bufferArray) + + var result = s.substring(0, i) + + while (i != len) { + val startOfChunk = i + val firstChar = s.charAt(startOfChunk) + i += 1 + + if (encodeAsIs(firstChar)) { + // A chunk of characters encoded as is + while (i != len && encodeAsIs(s.charAt(i))) + i += 1 + result += s.substring(startOfChunk, i) + } else if (firstChar == ' ') { + // A single ' ' + result += "+" + } else { + /* A chunk of characters to encode using the charset. + * + * Encoding as big a chunk as possible is not only good for + * performance. It allows us to deal with surrogate pairs without + * additional logic. + */ + while (i != len && encodeUsingCharset(s.charAt(i))) + i += 1 + charBuffer.limit(i) // must be done before setting position + charBuffer.position(startOfChunk) + buffer.rewind() + encoder.reset().encode(charBuffer, buffer, true) + for (j <- 0 until buffer.position()) + result += PercentEncoded(bufferArray(j) & 0xff) + } + } + + result + } + } + +} diff --git a/javalib/src/main/scala/java/nio/Buffer.scala b/javalib/src/main/scala/java/nio/Buffer.scala index 165ca92107..2913e96650 100644 --- a/javalib/src/main/scala/java/nio/Buffer.scala +++ b/javalib/src/main/scala/java/nio/Buffer.scala @@ -12,6 +12,8 @@ package java.nio +import java.util.internal.GenericArrayOps._ + import scala.scalajs.js.typedarray._ abstract class Buffer private[nio] (val _capacity: Int) { @@ -192,8 +194,9 @@ abstract class Buffer private[nio] (val _capacity: Int) { } @inline private[nio] def validateArrayIndexRange( - array: Array[_], offset: Int, length: Int): Unit = { - if (offset < 0 || length < 0 || offset > array.length - length) + array: Array[ElementType], offset: Int, length: Int)( + implicit arrayOps: ArrayOps[ElementType]): Unit = { + if (offset < 0 || length < 0 || offset > arrayOps.length(array) - length) throw new IndexOutOfBoundsException } diff --git a/javalib/src/main/scala/java/nio/ByteBuffer.scala b/javalib/src/main/scala/java/nio/ByteBuffer.scala index 8b100204f8..ed073c6cf2 100644 --- a/javalib/src/main/scala/java/nio/ByteBuffer.scala +++ b/javalib/src/main/scala/java/nio/ByteBuffer.scala @@ -17,11 +17,15 @@ import scala.scalajs.js.typedarray._ object ByteBuffer { private final val HashSeed = -547316498 // "java.nio.ByteBuffer".## - def allocate(capacity: Int): ByteBuffer = + def allocate(capacity: Int): ByteBuffer = { + GenBuffer.validateAllocateCapacity(capacity) wrap(new Array[Byte](capacity)) + } - def allocateDirect(capacity: Int): ByteBuffer = + def allocateDirect(capacity: Int): ByteBuffer = { + GenBuffer.validateAllocateCapacity(capacity) TypedArrayByteBuffer.allocate(capacity) + } def wrap(array: Array[Byte], offset: Int, length: Int): ByteBuffer = HeapByteBuffer.wrap(array, 0, array.length, offset, length, false) @@ -31,14 +35,8 @@ object ByteBuffer { // Extended API - def wrap(array: ArrayBuffer): ByteBuffer = - TypedArrayByteBuffer.wrap(array) - - def wrap(array: ArrayBuffer, byteOffset: Int, length: Int): ByteBuffer = - TypedArrayByteBuffer.wrap(array, byteOffset, length) - - def wrap(array: Int8Array): ByteBuffer = - TypedArrayByteBuffer.wrap(array) + def wrapInt8Array(array: Int8Array): ByteBuffer = + TypedArrayByteBuffer.wrapInt8Array(array) } abstract class ByteBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/CharBuffer.scala b/javalib/src/main/scala/java/nio/CharBuffer.scala index 79443286ec..31adf671be 100644 --- a/javalib/src/main/scala/java/nio/CharBuffer.scala +++ b/javalib/src/main/scala/java/nio/CharBuffer.scala @@ -17,8 +17,10 @@ import scala.scalajs.js.typedarray._ object CharBuffer { private final val HashSeed = -182887236 // "java.nio.CharBuffer".## - def allocate(capacity: Int): CharBuffer = + def allocate(capacity: Int): CharBuffer = { + GenBuffer.validateAllocateCapacity(capacity) wrap(new Array[Char](capacity)) + } def wrap(array: Array[Char], offset: Int, length: Int): CharBuffer = HeapCharBuffer.wrap(array, 0, array.length, offset, length, false) @@ -27,15 +29,15 @@ object CharBuffer { wrap(array, 0, array.length) def wrap(csq: CharSequence, start: Int, end: Int): CharBuffer = - StringCharBuffer.wrap(csq, 0, csq.length, start, end - start) + StringCharBuffer.wrap(csq, 0, csq.length(), start, end - start) def wrap(csq: CharSequence): CharBuffer = - wrap(csq, 0, csq.length) + wrap(csq, 0, csq.length()) // Extended API - def wrap(array: Uint16Array): CharBuffer = - TypedArrayCharBuffer.wrap(array) + def wrapUint16Array(array: Uint16Array): CharBuffer = + TypedArrayCharBuffer.wrapUint16Array(array) } abstract class CharBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/DataViewExt.scala b/javalib/src/main/scala/java/nio/DataViewExt.scala new file mode 100644 index 0000000000..f034f2f915 --- /dev/null +++ b/javalib/src/main/scala/java/nio/DataViewExt.scala @@ -0,0 +1,46 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.nio + +import scala.scalajs.js.typedarray.DataView + +/** Copy of features in `scala.scalajs.js.typedarray.DateViewExt`. + * + * Defined as functions instead of extension methods, because the AnyVal over + * a JS type generates an `equals` method that references `BoxesRunTime`. + */ +private[nio] object DataViewExt { + /** Reads a 2's complement signed 64-bit integers from the data view. + * @param index Starting index + * @param littleEndian Whether the number is stored in little endian + */ + @inline + def dataViewGetInt64(dataView: DataView, index: Int, littleEndian: Boolean): Long = { + val high = dataView.getInt32(index + (if (littleEndian) 4 else 0), littleEndian) + val low = dataView.getInt32(index + (if (littleEndian) 0 else 4), littleEndian) + (high.toLong << 32) | (low.toLong & 0xffffffffL) + } + + /** Writes a 2's complement signed 64-bit integers to the data view. + * @param index Starting index + * @param value Value to be written + * @param littleEndian Whether to store the number in little endian + */ + @inline + def dataViewSetInt64(dataView: DataView, index: Int, value: Long, littleEndian: Boolean): Unit = { + val high = (value >>> 32).toInt + val low = value.toInt + dataView.setInt32(index + (if (littleEndian) 4 else 0), high, littleEndian) + dataView.setInt32(index + (if (littleEndian) 0 else 4), low, littleEndian) + } +} diff --git a/javalib/src/main/scala/java/nio/DataViewLongBuffer.scala b/javalib/src/main/scala/java/nio/DataViewLongBuffer.scala index 3ee08fee13..3d083001cb 100644 --- a/javalib/src/main/scala/java/nio/DataViewLongBuffer.scala +++ b/javalib/src/main/scala/java/nio/DataViewLongBuffer.scala @@ -12,8 +12,9 @@ package java.nio +import java.nio.DataViewExt._ + import scala.scalajs.js.typedarray._ -import DataViewExt._ private[nio] final class DataViewLongBuffer private ( override private[nio] val _dataView: DataView, @@ -86,11 +87,11 @@ private[nio] final class DataViewLongBuffer private ( @inline private[nio] def load(index: Int): Long = - _dataView.getInt64(8 * index, !isBigEndian) + dataViewGetInt64(_dataView, 8 * index, !isBigEndian) @inline private[nio] def store(index: Int, elem: Long): Unit = - _dataView.setInt64(8 * index, elem, !isBigEndian) + dataViewSetInt64(_dataView, 8 * index, elem, !isBigEndian) @inline override private[nio] def load(startIndex: Int, diff --git a/javalib/src/main/scala/java/nio/DoubleBuffer.scala b/javalib/src/main/scala/java/nio/DoubleBuffer.scala index 34c77ba0c5..20c1f8f5a2 100644 --- a/javalib/src/main/scala/java/nio/DoubleBuffer.scala +++ b/javalib/src/main/scala/java/nio/DoubleBuffer.scala @@ -17,8 +17,10 @@ import scala.scalajs.js.typedarray._ object DoubleBuffer { private final val HashSeed = 2140173175 // "java.nio.DoubleBuffer".## - def allocate(capacity: Int): DoubleBuffer = + def allocate(capacity: Int): DoubleBuffer = { + GenBuffer.validateAllocateCapacity(capacity) wrap(new Array[Double](capacity)) + } def wrap(array: Array[Double], offset: Int, length: Int): DoubleBuffer = HeapDoubleBuffer.wrap(array, 0, array.length, offset, length, false) @@ -28,8 +30,8 @@ object DoubleBuffer { // Extended API - def wrap(array: Float64Array): DoubleBuffer = - TypedArrayDoubleBuffer.wrap(array) + def wrapFloat64Array(array: Float64Array): DoubleBuffer = + TypedArrayDoubleBuffer.wrapFloat64Array(array) } abstract class DoubleBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/FloatBuffer.scala b/javalib/src/main/scala/java/nio/FloatBuffer.scala index dc816242c6..3def688001 100644 --- a/javalib/src/main/scala/java/nio/FloatBuffer.scala +++ b/javalib/src/main/scala/java/nio/FloatBuffer.scala @@ -17,8 +17,10 @@ import scala.scalajs.js.typedarray._ object FloatBuffer { private final val HashSeed = 1920204022 // "java.nio.FloatBuffer".## - def allocate(capacity: Int): FloatBuffer = + def allocate(capacity: Int): FloatBuffer = { + GenBuffer.validateAllocateCapacity(capacity) wrap(new Array[Float](capacity)) + } def wrap(array: Array[Float], offset: Int, length: Int): FloatBuffer = HeapFloatBuffer.wrap(array, 0, array.length, offset, length, false) @@ -28,8 +30,8 @@ object FloatBuffer { // Extended API - def wrap(array: Float32Array): FloatBuffer = - TypedArrayFloatBuffer.wrap(array) + def wrapFloat32Array(array: Float32Array): FloatBuffer = + TypedArrayFloatBuffer.wrapFloat32Array(array) } abstract class FloatBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/GenBuffer.scala b/javalib/src/main/scala/java/nio/GenBuffer.scala index 09eabb721d..2fc5f52d3f 100644 --- a/javalib/src/main/scala/java/nio/GenBuffer.scala +++ b/javalib/src/main/scala/java/nio/GenBuffer.scala @@ -12,9 +12,17 @@ package java.nio +import java.util.function._ +import java.util.internal.GenericArrayOps._ + private[nio] object GenBuffer { def apply[B <: Buffer](self: B): GenBuffer[B] = new GenBuffer(self) + + @inline def validateAllocateCapacity(capacity: Int): Unit = { + if (capacity < 0) + throw new IllegalArgumentException + } } /* The underlying `val self` is intentionally public because @@ -49,8 +57,8 @@ private[nio] final class GenBuffer[B <: Buffer] private (val self: B) } @inline - def generic_get(dst: Array[ElementType], - offset: Int, length: Int): BufferType = { + def generic_get(dst: Array[ElementType], offset: Int, length: Int)( + implicit arrayOps: ArrayOps[ElementType]): BufferType = { validateArrayIndexRange(dst, offset, length) load(getPosAndAdvanceRead(length), dst, offset, length) self @@ -82,8 +90,8 @@ private[nio] final class GenBuffer[B <: Buffer] private (val self: B) } @inline - def generic_put(src: Array[ElementType], - offset: Int, length: Int): BufferType = { + def generic_put(src: Array[ElementType], offset: Int, length: Int)( + implicit arrayOps: ArrayOps[ElementType]): BufferType = { ensureNotReadOnly() validateArrayIndexRange(src, offset, length) store(getPosAndAdvanceWrite(length), src, offset, length) @@ -116,13 +124,13 @@ private[nio] final class GenBuffer[B <: Buffer] private (val self: B) @inline def generic_hashCode(hashSeed: Int): Int = { - import scala.util.hashing.MurmurHash3._ + import java.util.internal.MurmurHash3._ val start = position() val end = limit() var h = hashSeed var i = start while (i != end) { - h = mix(h, load(i).##) + h = mix(h, load(i).hashCode()) i += 1 } finalizeHash(h, end-start) @@ -130,7 +138,7 @@ private[nio] final class GenBuffer[B <: Buffer] private (val self: B) @inline def generic_compareTo(that: BufferType)( - compare: (ElementType, ElementType) => Int): Int = { + compare: BiFunction[ElementType, ElementType, Int]): Int = { // scalastyle:off return if (self eq that) { 0 @@ -156,12 +164,13 @@ private[nio] final class GenBuffer[B <: Buffer] private (val self: B) @inline def generic_load(startIndex: Int, - dst: Array[ElementType], offset: Int, length: Int): Unit = { + dst: Array[ElementType], offset: Int, length: Int)( + implicit arrayOps: ArrayOps[ElementType]): Unit = { var selfPos = startIndex val endPos = selfPos + length var arrayIndex = offset while (selfPos != endPos) { - dst(arrayIndex) = load(selfPos) + arrayOps.set(dst, arrayIndex, load(selfPos)) selfPos += 1 arrayIndex += 1 } @@ -169,12 +178,13 @@ private[nio] final class GenBuffer[B <: Buffer] private (val self: B) @inline def generic_store(startIndex: Int, - src: Array[ElementType], offset: Int, length: Int): Unit = { + src: Array[ElementType], offset: Int, length: Int)( + implicit arrayOps: ArrayOps[ElementType]): Unit = { var selfPos = startIndex val endPos = selfPos + length var arrayIndex = offset while (selfPos != endPos) { - store(selfPos, src(arrayIndex)) + store(selfPos, arrayOps.get(src, arrayIndex)) selfPos += 1 arrayIndex += 1 } diff --git a/javalib/src/main/scala/java/nio/GenDataViewBuffer.scala b/javalib/src/main/scala/java/nio/GenDataViewBuffer.scala index 30592bbf4f..299a26271c 100644 --- a/javalib/src/main/scala/java/nio/GenDataViewBuffer.scala +++ b/javalib/src/main/scala/java/nio/GenDataViewBuffer.scala @@ -37,23 +37,11 @@ private[nio] object GenDataViewBuffer { val viewCapacity = (byteBufferLimit - byteBufferPos) / newDataViewBuffer.bytesPerElem val byteLength = viewCapacity * newDataViewBuffer.bytesPerElem - val dataView = newDataView( + val dataView = new DataView( byteArray.buffer, byteArray.byteOffset + byteBufferPos, byteLength) newDataViewBuffer(dataView, 0, viewCapacity, byteBuffer.isReadOnly(), byteBuffer.isBigEndian) } - - /* Work around for https://github.com/joyent/node/issues/6051 - * node 0.10 does not like creating a DataView whose byteOffset is equal to - * the buffer's length, even if byteLength == 0. - */ - @inline - private def newDataView(buffer: ArrayBuffer, byteOffset: Int, byteLength: Int): DataView = { - if (byteLength == 0) - lit(buffer = buffer, byteOffset = byteOffset, byteLength = byteLength).asInstanceOf[DataView] - else - new DataView(buffer, byteOffset, byteLength) - } } /* The underlying `val self` is intentionally public because @@ -65,8 +53,6 @@ private[nio] final class GenDataViewBuffer[B <: Buffer] private (val self: B) import self._ - import GenDataViewBuffer.newDataView - type NewThisDataViewBuffer = GenDataViewBuffer.NewDataViewBuffer[BufferType] @inline @@ -76,7 +62,7 @@ private[nio] final class GenDataViewBuffer[B <: Buffer] private (val self: B) val dataView = _dataView val pos = position() val newCapacity = limit() - pos - val slicedDataView = newDataView(dataView.buffer, + val slicedDataView = new DataView(dataView.buffer, dataView.byteOffset + bytesPerElem*pos, bytesPerElem*newCapacity) newDataViewBuffer(slicedDataView, 0, newCapacity, isReadOnly(), isBigEndian) diff --git a/javalib/src/main/scala/java/nio/GenHeapBuffer.scala b/javalib/src/main/scala/java/nio/GenHeapBuffer.scala index 64906c56c8..f4e5c8a40d 100644 --- a/javalib/src/main/scala/java/nio/GenHeapBuffer.scala +++ b/javalib/src/main/scala/java/nio/GenHeapBuffer.scala @@ -12,6 +12,8 @@ package java.nio +import java.util.internal.GenericArrayOps._ + private[nio] object GenHeapBuffer { def apply[B <: Buffer](self: B): GenHeapBuffer[B] = new GenHeapBuffer(self) @@ -25,8 +27,9 @@ private[nio] object GenHeapBuffer { def generic_wrap[BufferType <: Buffer, ElementType]( array: Array[ElementType], arrayOffset: Int, capacity: Int, initialPosition: Int, initialLength: Int, isReadOnly: Boolean)( - implicit newHeapBuffer: NewHeapBuffer[BufferType, ElementType]): BufferType = { - if (arrayOffset < 0 || capacity < 0 || arrayOffset+capacity > array.length) + implicit arrayOps: ArrayOps[ElementType], + newHeapBuffer: NewHeapBuffer[BufferType, ElementType]): BufferType = { + if (arrayOffset < 0 || capacity < 0 || arrayOffset+capacity > arrayOps.length(array)) throw new IndexOutOfBoundsException val initialLimit = initialPosition + initialLength if (initialPosition < 0 || initialLength < 0 || initialLimit > capacity) @@ -86,12 +89,12 @@ private[nio] final class GenHeapBuffer[B <: Buffer] private (val self: B) } @inline - def generic_load(index: Int): ElementType = - _array(_arrayOffset + index) + def generic_load(index: Int)(implicit arrayOps: ArrayOps[ElementType]): ElementType = + arrayOps.get(_array, _arrayOffset + index) @inline - def generic_store(index: Int, elem: ElementType): Unit = - _array(_arrayOffset + index) = elem + def generic_store(index: Int, elem: ElementType)(implicit arrayOps: ArrayOps[ElementType]): Unit = + arrayOps.set(_array, _arrayOffset + index, elem) @inline def generic_load(startIndex: Int, diff --git a/javalib/src/main/scala/java/nio/IntBuffer.scala b/javalib/src/main/scala/java/nio/IntBuffer.scala index 09cfa88515..34de3249b2 100644 --- a/javalib/src/main/scala/java/nio/IntBuffer.scala +++ b/javalib/src/main/scala/java/nio/IntBuffer.scala @@ -17,8 +17,10 @@ import scala.scalajs.js.typedarray._ object IntBuffer { private final val HashSeed = 39599817 // "java.nio.IntBuffer".## - def allocate(capacity: Int): IntBuffer = + def allocate(capacity: Int): IntBuffer = { + GenBuffer.validateAllocateCapacity(capacity) wrap(new Array[Int](capacity)) + } def wrap(array: Array[Int], offset: Int, length: Int): IntBuffer = HeapIntBuffer.wrap(array, 0, array.length, offset, length, false) @@ -28,8 +30,8 @@ object IntBuffer { // Extended API - def wrap(array: Int32Array): IntBuffer = - TypedArrayIntBuffer.wrap(array) + def wrapInt32Array(array: Int32Array): IntBuffer = + TypedArrayIntBuffer.wrapInt32Array(array) } abstract class IntBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/LongBuffer.scala b/javalib/src/main/scala/java/nio/LongBuffer.scala index c1879a2cf3..74a66c1df5 100644 --- a/javalib/src/main/scala/java/nio/LongBuffer.scala +++ b/javalib/src/main/scala/java/nio/LongBuffer.scala @@ -15,8 +15,10 @@ package java.nio object LongBuffer { private final val HashSeed = -1709696158 // "java.nio.LongBuffer".## - def allocate(capacity: Int): LongBuffer = + def allocate(capacity: Int): LongBuffer = { + GenBuffer.validateAllocateCapacity(capacity) wrap(new Array[Long](capacity)) + } def wrap(array: Array[Long], offset: Int, length: Int): LongBuffer = HeapLongBuffer.wrap(array, 0, array.length, offset, length, false) diff --git a/javalib/src/main/scala/java/nio/ShortBuffer.scala b/javalib/src/main/scala/java/nio/ShortBuffer.scala index d31b13fec8..2f8fd53ea1 100644 --- a/javalib/src/main/scala/java/nio/ShortBuffer.scala +++ b/javalib/src/main/scala/java/nio/ShortBuffer.scala @@ -17,8 +17,10 @@ import scala.scalajs.js.typedarray._ object ShortBuffer { private final val HashSeed = 383731478 // "java.nio.ShortBuffer".## - def allocate(capacity: Int): ShortBuffer = + def allocate(capacity: Int): ShortBuffer = { + GenBuffer.validateAllocateCapacity(capacity) wrap(new Array[Short](capacity)) + } def wrap(array: Array[Short], offset: Int, length: Int): ShortBuffer = HeapShortBuffer.wrap(array, 0, array.length, offset, length, false) @@ -28,8 +30,8 @@ object ShortBuffer { // Extended API - def wrap(array: Int16Array): ShortBuffer = - TypedArrayShortBuffer.wrap(array) + def wrapInt16Array(array: Int16Array): ShortBuffer = + TypedArrayShortBuffer.wrapInt16Array(array) } abstract class ShortBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/StringCharBuffer.scala b/javalib/src/main/scala/java/nio/StringCharBuffer.scala index ecd3d0e168..241534d7f5 100644 --- a/javalib/src/main/scala/java/nio/StringCharBuffer.scala +++ b/javalib/src/main/scala/java/nio/StringCharBuffer.scala @@ -100,7 +100,7 @@ private[nio] final class StringCharBuffer private ( private[nio] object StringCharBuffer { private[nio] def wrap(csq: CharSequence, csqOffset: Int, capacity: Int, initialPosition: Int, initialLength: Int): CharBuffer = { - if (csqOffset < 0 || capacity < 0 || csqOffset+capacity > csq.length) + if (csqOffset < 0 || capacity < 0 || csqOffset + capacity > csq.length()) throw new IndexOutOfBoundsException val initialLimit = initialPosition + initialLength if (initialPosition < 0 || initialLength < 0 || initialLimit > capacity) diff --git a/javalib/src/main/scala/java/nio/TypedArrayByteBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayByteBuffer.scala index 26f93b0012..d7c1479f69 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayByteBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayByteBuffer.scala @@ -12,8 +12,9 @@ package java.nio +import java.nio.DataViewExt._ + import scala.scalajs.js.typedarray._ -import DataViewExt._ private[nio] final class TypedArrayByteBuffer private ( override private[nio] val _typedArray: Int8Array, @@ -128,13 +129,13 @@ private[nio] final class TypedArrayByteBuffer private ( } @noinline def getLong(): Long = - _dataView.getInt64(getPosAndAdvanceRead(8), !isBigEndian) + dataViewGetInt64(_dataView, getPosAndAdvanceRead(8), !isBigEndian) @noinline def putLong(value: Long): ByteBuffer = - { ensureNotReadOnly(); _dataView.setInt64(getPosAndAdvanceWrite(8), value, !isBigEndian); this } + { ensureNotReadOnly(); dataViewSetInt64(_dataView, getPosAndAdvanceWrite(8), value, !isBigEndian); this } @noinline def getLong(index: Int): Long = - _dataView.getInt64(validateIndex(index, 8), !isBigEndian) + dataViewGetInt64(_dataView, validateIndex(index, 8), !isBigEndian) @noinline def putLong(index: Int, value: Long): ByteBuffer = - { ensureNotReadOnly(); _dataView.setInt64(validateIndex(index, 8), value, !isBigEndian); this } + { ensureNotReadOnly(); dataViewSetInt64(_dataView, validateIndex(index, 8), value, !isBigEndian); this } def asLongBuffer(): LongBuffer = DataViewLongBuffer.fromTypedArrayByteBuffer(this) @@ -225,13 +226,7 @@ private[nio] object TypedArrayByteBuffer { new TypedArrayByteBuffer(new Int8Array(capacity), 0, capacity, false) } - def wrap(array: ArrayBuffer): ByteBuffer = - wrap(new Int8Array(array)) - - def wrap(array: ArrayBuffer, byteOffset: Int, length: Int): ByteBuffer = - wrap(new Int8Array(array, byteOffset, length)) - - def wrap(typedArray: Int8Array): ByteBuffer = { + def wrapInt8Array(typedArray: Int8Array): ByteBuffer = { val buf = new TypedArrayByteBuffer(typedArray, 0, typedArray.length, false) buf._isBigEndian = ByteOrder.areTypedArraysBigEndian buf diff --git a/javalib/src/main/scala/java/nio/TypedArrayCharBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayCharBuffer.scala index 96a8d82056..71a51057d2 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayCharBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayCharBuffer.scala @@ -135,6 +135,6 @@ private[nio] object TypedArrayCharBuffer { def fromTypedArrayByteBuffer(byteBuffer: TypedArrayByteBuffer): CharBuffer = GenTypedArrayBuffer.generic_fromTypedArrayByteBuffer(byteBuffer) - def wrap(array: Uint16Array): CharBuffer = + def wrapUint16Array(array: Uint16Array): CharBuffer = new TypedArrayCharBuffer(array, 0, array.length, false) } diff --git a/javalib/src/main/scala/java/nio/TypedArrayDoubleBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayDoubleBuffer.scala index 5cb48beace..4211fb143b 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayDoubleBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayDoubleBuffer.scala @@ -128,6 +128,6 @@ private[nio] object TypedArrayDoubleBuffer { def fromTypedArrayByteBuffer(byteBuffer: TypedArrayByteBuffer): DoubleBuffer = GenTypedArrayBuffer.generic_fromTypedArrayByteBuffer(byteBuffer) - def wrap(array: Float64Array): DoubleBuffer = + def wrapFloat64Array(array: Float64Array): DoubleBuffer = new TypedArrayDoubleBuffer(array, 0, array.length, false) } diff --git a/javalib/src/main/scala/java/nio/TypedArrayFloatBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayFloatBuffer.scala index d485e87054..cab3cbc756 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayFloatBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayFloatBuffer.scala @@ -128,6 +128,6 @@ private[nio] object TypedArrayFloatBuffer { def fromTypedArrayByteBuffer(byteBuffer: TypedArrayByteBuffer): FloatBuffer = GenTypedArrayBuffer.generic_fromTypedArrayByteBuffer(byteBuffer) - def wrap(array: Float32Array): FloatBuffer = + def wrapFloat32Array(array: Float32Array): FloatBuffer = new TypedArrayFloatBuffer(array, 0, array.length, false) } diff --git a/javalib/src/main/scala/java/nio/TypedArrayIntBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayIntBuffer.scala index 2d73e5025e..8beab4ac58 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayIntBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayIntBuffer.scala @@ -128,6 +128,6 @@ private[nio] object TypedArrayIntBuffer { def fromTypedArrayByteBuffer(byteBuffer: TypedArrayByteBuffer): IntBuffer = GenTypedArrayBuffer.generic_fromTypedArrayByteBuffer(byteBuffer) - def wrap(array: Int32Array): IntBuffer = + def wrapInt32Array(array: Int32Array): IntBuffer = new TypedArrayIntBuffer(array, 0, array.length, false) } diff --git a/javalib/src/main/scala/java/nio/TypedArrayShortBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayShortBuffer.scala index 0c77246b34..09a9ca38dc 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayShortBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayShortBuffer.scala @@ -128,6 +128,6 @@ private[nio] object TypedArrayShortBuffer { def fromTypedArrayByteBuffer(byteBuffer: TypedArrayByteBuffer): ShortBuffer = GenTypedArrayBuffer.generic_fromTypedArrayByteBuffer(byteBuffer) - def wrap(array: Int16Array): ShortBuffer = + def wrapInt16Array(array: Int16Array): ShortBuffer = new TypedArrayShortBuffer(array, 0, array.length, false) } diff --git a/javalib/src/main/scala/java/nio/charset/Charset.scala b/javalib/src/main/scala/java/nio/charset/Charset.scala index 1c95bb7e3b..981bb16c07 100644 --- a/javalib/src/main/scala/java/nio/charset/Charset.scala +++ b/javalib/src/main/scala/java/nio/charset/Charset.scala @@ -12,6 +12,7 @@ package java.nio.charset +import java.lang.Utils._ import java.nio.{ByteBuffer, CharBuffer} import java.util.{Collections, HashSet, Arrays} import java.util.ScalaOps._ @@ -36,7 +37,7 @@ abstract class Charset protected (canonicalName: String, override final def toString(): String = name() - override final def hashCode(): Int = name().## + override final def hashCode(): Int = name().hashCode() override final def compareTo(that: Charset): Int = name().compareToIgnoreCase(that.name()) @@ -78,21 +79,37 @@ object Charset { def defaultCharset(): Charset = UTF_8 - def forName(charsetName: String): Charset = - CharsetMap.getOrElse(charsetName.toLowerCase, - throw new UnsupportedCharsetException(charsetName)) + def forName(charsetName: String): Charset = { + dictGetOrElse(CharsetMap, charsetName.toLowerCase()) { () => + throw new UnsupportedCharsetException(charsetName) + } + } def isSupported(charsetName: String): Boolean = - CharsetMap.contains(charsetName.toLowerCase) + dictContains(CharsetMap, charsetName.toLowerCase()) + + def availableCharsets(): java.util.SortedMap[String, Charset] = + availableCharsetsResult + + private lazy val availableCharsetsResult = { + val m = new java.util.TreeMap[String, Charset](String.CASE_INSENSITIVE_ORDER) + forArrayElems(allSJSCharsets) { c => + m.put(c.name(), c) + } + Collections.unmodifiableSortedMap(m) + } private lazy val CharsetMap = { - val m = js.Dictionary.empty[Charset] - for (c <- js.Array(US_ASCII, ISO_8859_1, UTF_8, UTF_16BE, UTF_16LE, UTF_16)) { - m(c.name().toLowerCase) = c + val m = dictEmpty[Charset]() + forArrayElems(allSJSCharsets) { c => + dictSet(m, c.name().toLowerCase(), c) val aliases = c._aliases for (i <- 0 until aliases.length) - m(aliases(i).toLowerCase) = c + dictSet(m, aliases(i).toLowerCase(), c) } m } + + private def allSJSCharsets = + js.Array(US_ASCII, ISO_8859_1, UTF_8, UTF_16BE, UTF_16LE, UTF_16) } diff --git a/javalib/src/main/scala/java/nio/charset/CoderResult.scala b/javalib/src/main/scala/java/nio/charset/CoderResult.scala index f7f73967f3..1a4213f192 100644 --- a/javalib/src/main/scala/java/nio/charset/CoderResult.scala +++ b/javalib/src/main/scala/java/nio/charset/CoderResult.scala @@ -14,6 +14,7 @@ package java.nio.charset import scala.annotation.switch +import java.lang.Utils._ import java.nio._ import scala.scalajs.js @@ -77,7 +78,7 @@ object CoderResult { } private def malformedForLengthImpl(length: Int): CoderResult = { - uniqueMalformed(length).fold { + undefOrFold(uniqueMalformed(length)) { () => val result = new CoderResult(Malformed, length) uniqueMalformed(length) = result result @@ -95,7 +96,7 @@ object CoderResult { } private def unmappableForLengthImpl(length: Int): CoderResult = { - uniqueUnmappable(length).fold { + undefOrFold(uniqueUnmappable(length)) { () => val result = new CoderResult(Unmappable, length) uniqueUnmappable(length) = result result diff --git a/javalib/src/main/scala/java/nio/charset/UTF_16_Common.scala b/javalib/src/main/scala/java/nio/charset/UTF_16_Common.scala index 1f95031166..be95d536e3 100644 --- a/javalib/src/main/scala/java/nio/charset/UTF_16_Common.scala +++ b/javalib/src/main/scala/java/nio/charset/UTF_16_Common.scala @@ -119,7 +119,7 @@ private[charset] abstract class UTF_16_Common protected ( UTF_16_Common.this, 2.0f, if (endianness == AutoEndian) 4.0f else 2.0f, // Character 0xfffd - if (endianness == LittleEndian) Array(-3, -1) else Array(-1, -3)) { + if (endianness == LittleEndian) Array(-3.toByte, -1.toByte) else Array(-1.toByte, -3.toByte)) { private var needToWriteBOM: Boolean = endianness == AutoEndian diff --git a/javalib/src/main/scala/java/util/AbstractCollection.scala b/javalib/src/main/scala/java/util/AbstractCollection.scala index d514ef55f1..34920f1bdb 100644 --- a/javalib/src/main/scala/java/util/AbstractCollection.scala +++ b/javalib/src/main/scala/java/util/AbstractCollection.scala @@ -18,6 +18,8 @@ import ScalaOps._ import java.lang.{reflect => jlr} +import java.util.function.Predicate + abstract class AbstractCollection[E] protected () extends Collection[E] { def iterator(): Iterator[E] def size(): Int @@ -33,9 +35,9 @@ abstract class AbstractCollection[E] protected () extends Collection[E] { def toArray[T <: AnyRef](a: Array[T]): Array[T] = { val toFill: Array[T] = if (a.length >= size()) a - else jlr.Array.newInstance(a.getClass.getComponentType, size()).asInstanceOf[Array[T]] + else jlr.Array.newInstance(a.getClass().getComponentType(), size()).asInstanceOf[Array[T]] - val iter = iterator + val iter = iterator() for (i <- 0 until size()) toFill(i) = iter.next().asInstanceOf[T] if (toFill.length > size()) @@ -78,11 +80,11 @@ abstract class AbstractCollection[E] protected () extends Collection[E] { def clear(): Unit = removeWhere(_ => true) - private def removeWhere(p: Any => Boolean): Boolean = { + private def removeWhere(p: Predicate[Any]): Boolean = { val iter = iterator() var changed = false while (iter.hasNext()) { - if (p(iter.next())) { + if (p.test(iter.next())) { iter.remove() changed = true } diff --git a/javalib/src/main/scala/java/util/AbstractMap.scala b/javalib/src/main/scala/java/util/AbstractMap.scala index d2ea01b065..00249d5911 100644 --- a/javalib/src/main/scala/java/util/AbstractMap.scala +++ b/javalib/src/main/scala/java/util/AbstractMap.scala @@ -94,7 +94,7 @@ abstract class AbstractMap[K, V] protected () extends java.util.Map[K, V] { entrySet().scalaOps.exists(entry => Objects.equals(key, entry.getKey())) def get(key: Any): V = { - entrySet().scalaOps.find(entry => Objects.equals(key, entry.getKey())).fold[V] { + entrySet().scalaOps.findFold(entry => Objects.equals(key, entry.getKey())) { () => null.asInstanceOf[V] } { entry => entry.getValue() diff --git a/javalib/src/main/scala/java/util/AbstractQueue.scala b/javalib/src/main/scala/java/util/AbstractQueue.scala index e1eb450d20..913779d91e 100644 --- a/javalib/src/main/scala/java/util/AbstractQueue.scala +++ b/javalib/src/main/scala/java/util/AbstractQueue.scala @@ -32,7 +32,7 @@ abstract class AbstractQueue[E] protected () } override def addAll(c: Collection[_ <: E]): Boolean = { - val iter = c.iterator + val iter = c.iterator() var changed = false while (iter.hasNext()) changed = add(iter.next()) || changed diff --git a/javalib/src/main/scala/java/util/AbstractRandomAccessListIterator.scala b/javalib/src/main/scala/java/util/AbstractRandomAccessListIterator.scala index 0be7690726..98b674f4c5 100644 --- a/javalib/src/main/scala/java/util/AbstractRandomAccessListIterator.scala +++ b/javalib/src/main/scala/java/util/AbstractRandomAccessListIterator.scala @@ -21,6 +21,9 @@ abstract private[util] class AbstractRandomAccessListIterator[E](private var i: i < end def next(): E = { + if (!hasNext()) + throw new NoSuchElementException() + last = i i += 1 get(last) @@ -30,6 +33,9 @@ abstract private[util] class AbstractRandomAccessListIterator[E](private var i: start < i def previous(): E = { + if (!hasPrevious()) + throw new NoSuchElementException() + i -= 1 last = i get(last) diff --git a/javalib/src/main/scala/java/util/ArrayDeque.scala b/javalib/src/main/scala/java/util/ArrayDeque.scala index 305cce18f2..b45e075d03 100644 --- a/javalib/src/main/scala/java/util/ArrayDeque.scala +++ b/javalib/src/main/scala/java/util/ArrayDeque.scala @@ -12,29 +12,36 @@ package java.util +import java.lang.Cloneable +import java.lang.Utils._ + +import java.util.ScalaOps._ + import scala.scalajs.js -class ArrayDeque[E] private (private var inner: js.Array[E]) +class ArrayDeque[E] private (initialCapacity: Int) extends AbstractCollection[E] with Deque[E] with Cloneable with Serializable { self => - private var status = 0 + private val inner: js.Array[E] = new js.Array[E](Math.max(initialCapacity, 16)) - def this(initialCapacity: Int) = { - this(new js.Array[E]) + fillNulls(0, inner.length) - if (initialCapacity < 0) - throw new IllegalArgumentException - } + private var status = 0 + private var startIndex = 0 // inclusive, 0 <= startIndex < inner.length + private var endIndex = inner.length // exclusive, 0 < endIndex <= inner.length + private var empty = true - def this() = - this(16) + def this() = this(16) def this(c: Collection[_ <: E]) = { - this() + this(c.size()) addAll(c) } + @inline + override def isEmpty(): Boolean = empty + def addFirst(e: E): Unit = offerFirst(e) @@ -45,8 +52,13 @@ class ArrayDeque[E] private (private var inner: js.Array[E]) if (e == null) { throw new NullPointerException() } else { - inner = e +: inner + ensureCapacityForAdd() + startIndex -= 1 + if (startIndex < 0) + startIndex = inner.length - 1 + inner(startIndex) = e status += 1 + empty = false true } } @@ -55,82 +67,104 @@ class ArrayDeque[E] private (private var inner: js.Array[E]) if (e == null) { throw new NullPointerException() } else { - inner += e + ensureCapacityForAdd() + endIndex += 1 + if (endIndex > inner.length) + endIndex = 1 + inner(endIndex - 1) = e status += 1 + empty = false true } } def removeFirst(): E = { - if (inner.isEmpty) + if (isEmpty()) throw new NoSuchElementException() else pollFirst() } def removeLast(): E = { - if (inner.isEmpty) + if (isEmpty()) throw new NoSuchElementException() else pollLast() } def pollFirst(): E = { - if (inner.isEmpty) null.asInstanceOf[E] + if (isEmpty()) null.asInstanceOf[E] else { - val res = inner.remove(0) + val res = inner(startIndex) + inner(startIndex) = null.asInstanceOf[E] // free reference for GC + startIndex += 1 + if (startIndex == endIndex) + empty = true + if (startIndex >= inner.length) + startIndex = 0 status += 1 res } } def pollLast(): E = { - if (inner.isEmpty) null.asInstanceOf[E] - else inner.pop() + if (isEmpty()) { + null.asInstanceOf[E] + } else { + val res = inner(endIndex - 1) + inner(endIndex - 1) = null.asInstanceOf[E] // free reference for GC + endIndex -= 1 + if (startIndex == endIndex) + empty = true + if (endIndex <= 0) + endIndex = inner.length + status += 1 + res + } } def getFirst(): E = { - if (inner.isEmpty) + if (isEmpty()) throw new NoSuchElementException() else peekFirst() } def getLast(): E = { - if (inner.isEmpty) + if (isEmpty()) throw new NoSuchElementException() else peekLast() } def peekFirst(): E = { - if (inner.isEmpty) null.asInstanceOf[E] - else inner.head + if (isEmpty()) null.asInstanceOf[E] + else inner(startIndex) } def peekLast(): E = { - if (inner.isEmpty) null.asInstanceOf[E] - else inner.last + if (isEmpty()) null.asInstanceOf[E] + else inner(endIndex - 1) } def removeFirstOccurrence(o: Any): Boolean = { - val index = inner.indexWhere(Objects.equals(_, o)) - if (index >= 0) { - inner.remove(index) - status += 1 - true - } else + val i = firstIndexOf(o) + if (i == -1) { false + } else { + removeAt(i) + true + } } def removeLastOccurrence(o: Any): Boolean = { - val index = inner.lastIndexWhere(Objects.equals(_, o)) - if (index >= 0) { - inner.remove(index) - status += 1 - true - } else + val i = lastIndexOf(o) + if (i == -1) { false + } else { + removeAt(i) + true + } } override def add(e: E): Boolean = { @@ -152,53 +186,274 @@ class ArrayDeque[E] private (private var inner: js.Array[E]) def pop(): E = removeFirst() - def size(): Int = inner.size + def size(): Int = { + if (isEmpty()) 0 + else if (endIndex > startIndex) endIndex - startIndex + else (endIndex + inner.length) - startIndex + } + + def iterator(): Iterator[E] = new Iterator[E] { + private def checkStatus() = { + if (self.status != expectedStatus) + throw new ConcurrentModificationException() + } + + private var expectedStatus = self.status + + private var lastIndex: Int = -1 + private var nextIndex: Int = + if (isEmpty()) -1 + else startIndex + + def hasNext(): Boolean = { + checkStatus() + nextIndex != -1 + } + + def next(): E = { + if (!hasNext()) // also checks status + throw new NoSuchElementException() - private def failFastIterator(startIndex: Int, nex: (Int) => Int) = { - new Iterator[E] { - private def checkStatus() = - if (self.status != actualStatus) - throw new ConcurrentModificationException() + lastIndex = nextIndex - private val actualStatus = self.status + nextIndex += 1 + if (nextIndex == endIndex) + nextIndex = -1 + else if (nextIndex >= inner.length) + nextIndex = 0 - private var index: Int = startIndex + inner(lastIndex) + } - def hasNext(): Boolean = { - checkStatus() - val n = nex(index) - (n >= 0) && (n < inner.size) + override def remove(): Unit = { + checkStatus() + if (lastIndex == -1) + throw new IllegalStateException() + + val laterShifted = removeAt(lastIndex) + lastIndex = -1 + expectedStatus = self.status + + if (laterShifted && nextIndex != -1) { + /* assert(nextIndex != 0) + * Why? Assume nextIndex == 0, that means the element we just removed + * was at the end of the ring-buffer. But in this case, removeAt shifts + * forward to avoid copying over the buffer boundary. + * Therefore, laterShifted cannot be true. + */ + nextIndex -= 1 } + } + } + + def descendingIterator(): Iterator[E] = new Iterator[E] { + private def checkStatus() = { + if (self.status != expectedStatus) + throw new ConcurrentModificationException() + } + + private var expectedStatus = self.status + + private var lastIndex: Int = -1 + private var nextIndex: Int = + if (isEmpty()) -1 + else endIndex - 1 + + def hasNext(): Boolean = { + checkStatus() + nextIndex != -1 + } - def next(): E = { - checkStatus() - index = nex(index) - inner(index) + def next(): E = { + if (!hasNext()) // also checks status + throw new NoSuchElementException() + + lastIndex = nextIndex + + if (nextIndex == startIndex) { + nextIndex = -1 + } else { + nextIndex -= 1 + if (nextIndex < 0) + nextIndex = inner.length - 1 } - override def remove(): Unit = { - checkStatus() - if (index < 0 || index >= inner.size) { + inner(lastIndex) + } + + override def remove(): Unit = { + checkStatus() + if (lastIndex == -1) throw new IllegalStateException() - } else { - inner.remove(index) - } + + + val laterShifted = removeAt(lastIndex) + expectedStatus = self.status + lastIndex = -1 + + if (!laterShifted && nextIndex != -1) { + /* assert(nextIndex < inner.length - 1) + * Why? Assume nextIndex == inner.length - 1, that means the element we + * just removed was at the beginning of the ring buffer (recall, this is + * a backwards iterator). However, in this case, removeAt would shift + * the next elements (in forward iteration order) backwards. + * That implies laterShifted, so we would not hit this branch. + */ + nextIndex += 1 } } } - def iterator(): Iterator[E] = - failFastIterator(-1, x => (x + 1)) - - def descendingIterator(): Iterator[E] = - failFastIterator(inner.size, x => (x - 1)) - - override def contains(o: Any): Boolean = inner.exists(Objects.equals(_, o)) + override def contains(o: Any): Boolean = firstIndexOf(o) != -1 override def remove(o: Any): Boolean = removeFirstOccurrence(o) override def clear(): Unit = { - if (!inner.isEmpty) status += 1 - inner.clear() + if (!isEmpty()) + status += 1 + empty = true + startIndex = 0 + endIndex = inner.length + } + + private def firstIndexOf(o: Any): Int = { + // scalastyle:off return + if (isEmpty()) + return -1 + val inner = this.inner // local copy + val capacity = inner.length // local copy + val endIndex = this.endIndex // local copy + var i = startIndex + do { + if (i >= capacity) + i = 0 + if (Objects.equals(inner(i), o)) + return i + i += 1 // let i overrun so we catch endIndex == capacity + } while (i != endIndex) + -1 + // scalastyle:on return + } + + private def lastIndexOf(o: Any): Int = { + // scalastyle:off return + if (isEmpty()) + return -1 + val inner = this.inner // local copy + val startIndex = this.startIndex // local copy + var i = endIndex + do { + i -= 1 + if (i < 0) + i = inner.length - 1 + if (Objects.equals(inner(i), o)) + return i + } while (i != startIndex) + -1 + // scalastyle:on return + } + + private def ensureCapacityForAdd(): Unit = { + if (isEmpty()) { + // Nothing to do (constructor ensures capacity is always non-zero). + } else if (startIndex == 0 && endIndex == inner.length) { + val oldCapacity = inner.length + inner.length *= 2 + // no copying required: We just keep adding to the end. + // However, ensure array is dense. + fillNulls(oldCapacity, inner.length) + } else if (startIndex == endIndex) { + val oldCapacity = inner.length + inner.length *= 2 + // move beginning of array to end + for (i <- 0 until endIndex) { + inner(i + oldCapacity) = inner(i) + inner(i) = null.asInstanceOf[E] // free old reference for GC + } + // ensure rest of array is dense + fillNulls(endIndex + oldCapacity, inner.length) + endIndex += oldCapacity + } + } + + /* Removes the element at index [[target]] + * + * The return value indicates which end of the queue was shifted onto the + * element to be removed. + * + * @returns true if elements after target were shifted onto target or target + * was the last element. Returns false, if elements before target were + * shifted onto target or target was the first element. + */ + private def removeAt(target: Int): Boolean = { + /* Note that if size == 1, we always take the first branch. + * Therefore, we need not handle the empty flag in this method. + */ + + if (target == startIndex) { + pollFirst() + false + } else if (target == endIndex - 1) { + pollLast() + true + } else if (target < endIndex) { + // Shift elements from endIndex towards target + for (i <- target until endIndex - 1) + inner(i) = inner(i + 1) + inner(endIndex - 1) = null.asInstanceOf[E] // free reference for GC + status += 1 + + /* Note that endIndex >= 2: + * By previous if: target < endIndex + * ==> target <= endIndex - 1 + * By previous if: target < endIndex - 1 (non-equality) + * ==> target <= endIndex - 2 + * By precondition: target >= 0 + * ==> 0 <= endIndex - 2 + * ==> endIndex >= 2 + * + * Therefore we do not need to perform an underflow check. + */ + endIndex -= 1 + + true + } else { + // Shift elements from startIndex towards target + + /* Note that target > startIndex. + * Why? Assume by contradiction: target <= startIndex + * By previous if: target >= endIndex. + * By previous if: target < startIndex (non-equality) + * ==> endIndex <= target < startIndex. + * ==> target is not in the active region of the ringbuffer. + * ==> contradiction. + */ + + // for (i <- target until startIndex by -1) + var i = target + while (i != startIndex) { + inner(i) = inner(i - 1) + i -= 1 + } + inner(startIndex) = null.asInstanceOf[E] // free reference for GC + + status += 1 + + /* Note that startIndex <= inner.length - 2: + * By previous proof: target > startIndex + * By precondition: target <= inner.length - 1 + * ==> startIndex < inner.length - 1 + * ==> startIndex <= inner.length - 2 + * + * Therefore we do not need to perform an overflow check. + */ + startIndex += 1 + false + } + } + + private def fillNulls(from: Int, until: Int): Unit = { + for (i <- from until until) + inner(i) = null.asInstanceOf[E] } } diff --git a/javalib/src/main/scala/java/util/ArrayList.scala b/javalib/src/main/scala/java/util/ArrayList.scala index e75d7107de..1c67de682b 100644 --- a/javalib/src/main/scala/java/util/ArrayList.scala +++ b/javalib/src/main/scala/java/util/ArrayList.scala @@ -12,79 +12,183 @@ package java.util +import java.lang.Cloneable +import java.lang.Utils._ +import java.util.ScalaOps._ + import scala.scalajs._ +import scala.scalajs.LinkingInfo.isWebAssembly -class ArrayList[E] private (private[ArrayList] val inner: js.Array[E]) +class ArrayList[E] private (innerInit: AnyRef, private var _size: Int) extends AbstractList[E] with RandomAccess with Cloneable with Serializable { self => + /* This class has two different implementations for handling the + * internal data storage, depending on whether we are on Wasm or JS. + * On JS, we utilize `js.Array`. On Wasm, for performance reasons, + * we avoid JS interop and use a scala.Array. + * The `_size` field (unused in JS) keeps track of the effective size + * of the underlying Array for the Wasm implementation. + */ + + private val innerJS: js.Array[E] = + if (isWebAssembly) null + else innerInit.asInstanceOf[js.Array[E]] + + private var innerWasm: Array[AnyRef] = + if (!isWebAssembly) null + else innerInit.asInstanceOf[Array[AnyRef]] + def this(initialCapacity: Int) = { - this(new js.Array[E]) - if (initialCapacity < 0) - throw new IllegalArgumentException + this( + { + if (initialCapacity < 0) + throw new IllegalArgumentException + if (isWebAssembly) new Array[AnyRef](initialCapacity) + else new js.Array[E] + }, + 0 + ) } - def this() = - this(new js.Array[E]) + def this() = this(16) def this(c: Collection[_ <: E]) = { - this() + this(c.size()) addAll(c) } def trimToSize(): Unit = { - // We ignore this as js.Array doesn't support explicit pre-allocation + if (isWebAssembly) + resizeTo(size()) + // We ignore this in JS as js.Array doesn't support explicit pre-allocation } def ensureCapacity(minCapacity: Int): Unit = { - // We ignore this as js.Array doesn't support explicit pre-allocation + if (isWebAssembly) { + if (innerWasm.length < minCapacity) { + if (minCapacity > (1 << 30)) + resizeTo(minCapacity) + else + resizeTo(((1 << 31) >>> (Integer.numberOfLeadingZeros(minCapacity - 1)) - 1)) + } + } + // We ignore this in JS as js.Array doesn't support explicit pre-allocation } def size(): Int = - inner.length - - override def clone(): AnyRef = - new ArrayList(inner.jsSlice(0)) + if (isWebAssembly) _size + else innerJS.length + + override def clone(): AnyRef = { + if (isWebAssembly) + new ArrayList(innerWasm.clone(), size()) + else + new ArrayList(innerJS.jsSlice(0), 0) + } def get(index: Int): E = { checkIndexInBounds(index) - inner(index) + if (isWebAssembly) + innerWasm(index).asInstanceOf[E] + else + innerJS(index) } override def set(index: Int, element: E): E = { val e = get(index) - inner(index) = element + if (isWebAssembly) + innerWasm(index) = element.asInstanceOf[AnyRef] + else + innerJS(index) = element e } override def add(e: E): Boolean = { - inner += e + if (isWebAssembly) { + if (size() >= innerWasm.length) + expand() + innerWasm(size()) = e.asInstanceOf[AnyRef] + _size += 1 + } else { + innerJS.push(e) + } true } override def add(index: Int, element: E): Unit = { checkIndexOnBounds(index) - inner.insert(index, element) + if (isWebAssembly) { + if (size() >= innerWasm.length) + expand() + System.arraycopy(innerWasm, index, innerWasm, index + 1, size() - index) + innerWasm(index) = element.asInstanceOf[AnyRef] + _size += 1 + } else { + innerJS.splice(index, 0, element) + } } override def remove(index: Int): E = { checkIndexInBounds(index) - inner.remove(index) + if (isWebAssembly) { + val removed = innerWasm(index).asInstanceOf[E] + System.arraycopy(innerWasm, index + 1, innerWasm, index, size() - index - 1) + innerWasm(size - 1) = null // free reference for GC + _size -= 1 + removed + } else { + arrayRemoveAndGet(innerJS, index) + } } override def clear(): Unit = - inner.clear() + if (isWebAssembly) { + Arrays.fill(innerWasm, null) // free references for GC + _size = 0 + } else { + innerJS.length = 0 + } override def addAll(index: Int, c: Collection[_ <: E]): Boolean = { c match { case other: ArrayList[_] => - inner.splice(index, 0, other.inner.toSeq: _*) + checkIndexOnBounds(index) + if (isWebAssembly) { + ensureCapacity(size() + other.size()) + System.arraycopy(innerWasm, index, innerWasm, index + other.size(), size() - index) + System.arraycopy(other.innerWasm, 0, innerWasm, index, other.size()) + _size += c.size() + } else { + innerJS.splice(index, 0, other.innerJS.toSeq: _*) + } other.size() > 0 case _ => super.addAll(index, c) } } - override protected def removeRange(fromIndex: Int, toIndex: Int): Unit = - inner.splice(fromIndex, toIndex - fromIndex) + override protected def removeRange(fromIndex: Int, toIndex: Int): Unit = { + if (fromIndex < 0 || toIndex > size() || toIndex < fromIndex) + throw new IndexOutOfBoundsException() + if (isWebAssembly) { + if (fromIndex != toIndex) { + System.arraycopy(innerWasm, toIndex, innerWasm, fromIndex, size() - toIndex) + val newSize = size() - toIndex + fromIndex + Arrays.fill(innerWasm, newSize, size(), null) // free references for GC + _size = newSize + } + } else { + innerJS.splice(fromIndex, toIndex - fromIndex) + } + } + + // Wasm only + private def expand(): Unit = { + resizeTo(Math.max(innerWasm.length * 2, 16)) + } + // Wasm only + private def resizeTo(newCapacity: Int): Unit = { + innerWasm = Arrays.copyOf(innerWasm, newCapacity) + } } diff --git a/javalib/src/main/scala/java/util/Arrays.scala b/javalib/src/main/scala/java/util/Arrays.scala index c2c6cc81a6..b4eb10b6b2 100644 --- a/javalib/src/main/scala/java/util/Arrays.scala +++ b/javalib/src/main/scala/java/util/Arrays.scala @@ -12,12 +12,12 @@ package java.util -import java.lang.{reflect => jlr} - import scala.scalajs.js import scala.annotation.tailrec +import java.util.internal.GenericArrayOps._ + import ScalaOps._ object Arrays { @@ -32,133 +32,6 @@ object Arrays { if (comparator == null) NaturalComparator else comparator - /** A custom typeclass for the operations we need in `Arrays` to implement - * the algorithms generically. - */ - private sealed trait ArrayOps[A] { - def length(a: Array[A]): Int - def get(a: Array[A], i: Int): A - def set(a: Array[A], i: Int, v: A): Unit - } - - /** A custom typeclass for the ability to create arrays of a given type. */ - private sealed trait ArrayCreateOps[A] { - def create(length: Int): Array[A] - } - - // ArrayOps and ArrayCreateOps instances for reference types - - private object ReusableAnyRefArrayOps extends ArrayOps[AnyRef] { - @inline def length(a: Array[AnyRef]): Int = a.length - @inline def get(a: Array[AnyRef], i: Int): AnyRef = a(i) - @inline def set(a: Array[AnyRef], i: Int, v: AnyRef): Unit = a(i) = v - } - - @inline - private implicit def specificAnyRefArrayOps[A <: AnyRef]: ArrayOps[A] = - ReusableAnyRefArrayOps.asInstanceOf[ArrayOps[A]] - - @inline - private final class ClassArrayOps[A <: AnyRef](clazz: Class[_ <: Array[A]]) - extends ArrayCreateOps[A] { - @inline def create(length: Int): Array[A] = - createArrayOfClass(clazz, length) - } - - @inline - private final class TemplateArrayOps[A <: AnyRef](template: Array[A]) - extends ArrayCreateOps[A] { - @inline def create(length: Int): Array[A] = - createArrayOfClass(template.getClass(), length) - } - - @inline - private def createArrayOfClass[A <: AnyRef](clazz: Class[_ <: Array[A]], length: Int): Array[A] = - jlr.Array.newInstance(clazz.getComponentType(), length).asInstanceOf[Array[A]] - - private implicit object AnyRefArrayCreateOps extends ArrayCreateOps[AnyRef] { - @inline def create(length: Int): Array[AnyRef] = new Array[AnyRef](length) - } - - /* ArrayOps and ArrayCreateOps instances for primitive types. - * - * With the exception of the one for Boolean, they also implement - * `java.util.Comparator` for the same element type. In a perfect design, we - * would define separate objects for that, but it would result in more - * generated code for no good reason. - */ - - private implicit object BooleanArrayOps - extends ArrayOps[Boolean] with ArrayCreateOps[Boolean] { - @inline def length(a: Array[Boolean]): Int = a.length - @inline def get(a: Array[Boolean], i: Int): Boolean = a(i) - @inline def set(a: Array[Boolean], i: Int, v: Boolean): Unit = a(i) = v - @inline def create(length: Int): Array[Boolean] = new Array[Boolean](length) - } - - private implicit object CharArrayOps - extends ArrayOps[Char] with ArrayCreateOps[Char] with Comparator[Char] { - @inline def length(a: Array[Char]): Int = a.length - @inline def get(a: Array[Char], i: Int): Char = a(i) - @inline def set(a: Array[Char], i: Int, v: Char): Unit = a(i) = v - @inline def create(length: Int): Array[Char] = new Array[Char](length) - @inline def compare(x: Char, y: Char): Int = java.lang.Character.compare(x, y) - } - - private implicit object ByteArrayOps - extends ArrayOps[Byte] with ArrayCreateOps[Byte] with Comparator[Byte] { - @inline def length(a: Array[Byte]): Int = a.length - @inline def get(a: Array[Byte], i: Int): Byte = a(i) - @inline def set(a: Array[Byte], i: Int, v: Byte): Unit = a(i) = v - @inline def create(length: Int): Array[Byte] = new Array[Byte](length) - @inline def compare(x: Byte, y: Byte): Int = java.lang.Byte.compare(x, y) - } - - private implicit object ShortArrayOps - extends ArrayOps[Short] with ArrayCreateOps[Short] with Comparator[Short] { - @inline def length(a: Array[Short]): Int = a.length - @inline def get(a: Array[Short], i: Int): Short = a(i) - @inline def set(a: Array[Short], i: Int, v: Short): Unit = a(i) = v - @inline def create(length: Int): Array[Short] = new Array[Short](length) - @inline def compare(x: Short, y: Short): Int = java.lang.Short.compare(x, y) - } - - private implicit object IntArrayOps - extends ArrayOps[Int] with ArrayCreateOps[Int] with Comparator[Int] { - @inline def length(a: Array[Int]): Int = a.length - @inline def get(a: Array[Int], i: Int): Int = a(i) - @inline def set(a: Array[Int], i: Int, v: Int): Unit = a(i) = v - @inline def create(length: Int): Array[Int] = new Array[Int](length) - @inline def compare(x: Int, y: Int): Int = java.lang.Integer.compare(x, y) - } - - private implicit object LongArrayOps - extends ArrayOps[Long] with ArrayCreateOps[Long] with Comparator[Long] { - @inline def length(a: Array[Long]): Int = a.length - @inline def get(a: Array[Long], i: Int): Long = a(i) - @inline def set(a: Array[Long], i: Int, v: Long): Unit = a(i) = v - @inline def create(length: Int): Array[Long] = new Array[Long](length) - @inline def compare(x: Long, y: Long): Int = java.lang.Long.compare(x, y) - } - - private implicit object FloatArrayOps - extends ArrayOps[Float] with ArrayCreateOps[Float] with Comparator[Float] { - @inline def length(a: Array[Float]): Int = a.length - @inline def get(a: Array[Float], i: Int): Float = a(i) - @inline def set(a: Array[Float], i: Int, v: Float): Unit = a(i) = v - @inline def create(length: Int): Array[Float] = new Array[Float](length) - @inline def compare(x: Float, y: Float): Int = java.lang.Float.compare(x, y) - } - - private implicit object DoubleArrayOps - extends ArrayOps[Double] with ArrayCreateOps[Double] with Comparator[Double] { - @inline def length(a: Array[Double]): Int = a.length - @inline def get(a: Array[Double], i: Int): Double = a(i) - @inline def set(a: Array[Double], i: Int, v: Double): Unit = a(i) = v - @inline def create(length: Int): Array[Double] = new Array[Double](length) - @inline def compare(x: Double, y: Double): Int = java.lang.Double.compare(x, y) - } - // Implementation of the API @noinline def sort(a: Array[Int]): Unit = @@ -467,7 +340,7 @@ object Arrays { return false var i = 0 while (i != len) { - if (!ops.get(a, i).equals(ops.get(b, i))) + if (!Objects.equals(ops.get(a, i), ops.get(b, i))) return false i += 1 } diff --git a/javalib/src/main/scala/java/util/Base64.scala b/javalib/src/main/scala/java/util/Base64.scala index 4a92dfeb3e..a88333d294 100644 --- a/javalib/src/main/scala/java/util/Base64.scala +++ b/javalib/src/main/scala/java/util/Base64.scala @@ -403,7 +403,7 @@ object Base64 { // -------------------------------------------------------------------------- class Encoder private[Base64] (table: Array[Byte], lineLength: Int = 0, - lineSeparator: Array[Byte] = Array.empty, withPadding: Boolean = true) { + lineSeparator: Array[Byte] = new Array[Byte](0), withPadding: Boolean = true) { def encode(src: Array[Byte]): Array[Byte] = { val dst = new Array[Byte](dstLength(src.length)) diff --git a/javalib/src/main/scala/java/util/BitSet.scala b/javalib/src/main/scala/java/util/BitSet.scala index a0df60afa7..5e2c4bd61f 100644 --- a/javalib/src/main/scala/java/util/BitSet.scala +++ b/javalib/src/main/scala/java/util/BitSet.scala @@ -13,6 +13,7 @@ package java.util import java.io.Serializable +import java.lang.Cloneable import java.lang.Integer.bitCount import java.lang.Integer.toUnsignedLong import java.nio.{ByteBuffer, LongBuffer} @@ -263,7 +264,7 @@ class BitSet private (private var bits: Array[Int]) extends Serializable with Cl if (result == 0) return new BitSet(0) - new BitSet(Array[Int](result)) + new BitSet(Array(result)) } else { val newbits = new Array[Int](idx2 - idx1 + 1) // first fill in the first and last indexes in the new bitset @@ -687,4 +688,3 @@ class BitSet private (private var bits: Array[Int]) extends Serializable with Cl throw new IndexOutOfBoundsException(s"bitIndex < 0: $bitIndex") } } - diff --git a/javalib/src/main/scala/java/util/Collection.scala b/javalib/src/main/scala/java/util/Collection.scala index 34af7828ea..d2c1956313 100644 --- a/javalib/src/main/scala/java/util/Collection.scala +++ b/javalib/src/main/scala/java/util/Collection.scala @@ -14,8 +14,6 @@ package java.util import java.util.function.Predicate -import scala.scalajs.js.annotation.JavaDefaultMethod - trait Collection[E] extends java.lang.Iterable[E] { def size(): Int def isEmpty(): Boolean @@ -29,7 +27,6 @@ trait Collection[E] extends java.lang.Iterable[E] { def addAll(c: Collection[_ <: E]): Boolean def removeAll(c: Collection[_]): Boolean - @JavaDefaultMethod def removeIf(filter: Predicate[_ >: E]): Boolean = { var result = false val iter = iterator() diff --git a/javalib/src/main/scala/java/util/Collections.scala b/javalib/src/main/scala/java/util/Collections.scala index ccd0dd2611..8f9a630de5 100644 --- a/javalib/src/main/scala/java/util/Collections.scala +++ b/javalib/src/main/scala/java/util/Collections.scala @@ -14,6 +14,7 @@ package java.util import java.{lang => jl} import java.io.Serializable +import java.util.function._ import scala.language.implicitConversions @@ -79,16 +80,16 @@ object Collections { binarySearchImpl(list, (elem: T) => c.compare(elem, key)) @inline - private def binarySearchImpl[E](list: List[E], compareToKey: E => Int): Int = { + private def binarySearchImpl[E](list: List[_ <: E], compareToKey: ToIntFunction[E]): Int = { def notFound(insertionPoint: Int): Int = { -insertionPoint - 1 } @tailrec - def binarySearch(lo: Int, hi: Int, get: Int => E): Int = { + def binarySearch(lo: Int, hi: Int, get: IntFunction[E]): Int = { if (lo < hi) { val mid = lo + (hi - lo) / 2 - val cmp = compareToKey(get(mid)) + val cmp = compareToKey.applyAsInt(get(mid)) if (cmp == 0) mid else if (cmp > 0) binarySearch(lo, mid, get) else binarySearch(mid + 1, hi, get) @@ -102,7 +103,7 @@ object Collections { binarySearch(0, list.size(), list.get(_)) case _ => - def getFrom(iter: ListIterator[E])(index: Int): E = { + def getFrom(iter: ListIterator[_ <: E])(index: Int): E = { val shift = index - iter.nextIndex() if (shift > 0) (0 until shift).foreach(_ => iter.next()) @@ -258,19 +259,19 @@ object Collections { } } - // Differs from original type definition, original: [T <: jl.Comparable[_ >: T]] - def min[T <: AnyRef with jl.Comparable[T]](coll: Collection[_ <: T]): T = - min(coll, naturalComparator[T]) + // Differs from original type definition, original: [T <: jl.Comparable[_ >: T]], returning T + def min[T <: AnyRef with jl.Comparable[T]](coll: Collection[_ <: T]): AnyRef = + min(coll, Comparator.naturalOrder[T]) def min[T](coll: Collection[_ <: T], comp: Comparator[_ >: T]): T = - coll.scalaOps.reduceLeft((a, b) => if (comp.compare(a, b) <= 0) a else b) + coll.scalaOps.reduceLeft[T]((a, b) => if (comp.compare(a, b) <= 0) a else b) - // Differs from original type definition, original: [T <: jl.Comparable[_ >: T]] - def max[T <: AnyRef with jl.Comparable[T]](coll: Collection[_ <: T]): T = - max(coll, naturalComparator[T]) + // Differs from original type definition, original: [T <: jl.Comparable[_ >: T]], returning T + def max[T <: AnyRef with jl.Comparable[T]](coll: Collection[_ <: T]): AnyRef = + max(coll, Comparator.naturalOrder[T]) def max[T](coll: Collection[_ <: T], comp: Comparator[_ >: T]): T = - coll.scalaOps.reduceLeft((a, b) => if (comp.compare(a, b) >= 0) a else b) + coll.scalaOps.reduceLeft[T]((a, b) => if (comp.compare(a, b) >= 0) a else b) def rotate(list: List[_], distance: Int): Unit = rotateImpl(list, distance) @@ -544,7 +545,7 @@ object Collections { } def enumeration[T](c: Collection[T]): Enumeration[T] = { - val it = c.iterator + val it = c.iterator() new Enumeration[T] { override def hasMoreElements(): Boolean = it.hasNext() @@ -604,20 +605,6 @@ object Collections { @inline private def modulo(a: Int, b: Int): Int = ((a % b) + b) % b - @inline - private def naturalComparator[T <: jl.Comparable[T]]: Comparator[T] = { - new Comparator[T] with Serializable { - final def compare(o1: T, o2: T): Int = o1.compareTo(o2) - } - } - - @inline - private implicit def comparatorToOrdering[E](cmp: Comparator[E]): Ordering[E] = { - new Ordering[E] { - final def compare(x: E, y: E): Int = cmp.compare(x, y) - } - } - private trait WrappedEquals { protected def inner: AnyRef diff --git a/javalib/src/main/scala/java/util/Comparator.scala b/javalib/src/main/scala/java/util/Comparator.scala index 6edd9b50a3..7cbf1ec521 100644 --- a/javalib/src/main/scala/java/util/Comparator.scala +++ b/javalib/src/main/scala/java/util/Comparator.scala @@ -12,15 +12,161 @@ package java.util -import scala.scalajs.js.annotation.JavaDefaultMethod +import java.util.function._ // scalastyle:off equals.hash.code -trait Comparator[A] { +/* A note about serializability: + * + * The JDK documentation states that returned comparators are serializable if + * their respective elements (Comparators / Functions) are serializable. + * + * Experimentation on `nullsFirst` has shown that the returned comparator always + * implements `Serializable` (and supposedly relies on the serialization + * mechanism itself to fail when it is unable to serialize a field). + * + * Our implementation mimics this behavior. + */ + +trait Comparator[A] { self => + import Comparator._ + def compare(o1: A, o2: A): Int def equals(obj: Any): Boolean - @JavaDefaultMethod def reversed(): Comparator[A] = Collections.reverseOrder(this) + + @inline + def thenComparing(other: Comparator[_ >: A]): Comparator[A] = { + other.getClass() // null check + new Comparator[A] with Serializable { + def compare(o1: A, o2: A) = { + val cmp = self.compare(o1, o2) + if (cmp != 0) cmp + else other.compare(o1, o2) + } + } + } + + def thenComparing[U](keyExtractor: Function[_ >: A, _ <: U], + keyComparator: Comparator[_ >: U]): Comparator[A] = { + thenComparing(comparing[A, U](keyExtractor, keyComparator)) + } + + /* Should be U <: Comparable[_ >: U] but scalac fails with + * > illegal cyclic reference involving type U + */ + def thenComparing[U <: Comparable[U]]( + keyExtractor: Function[_ >: A, _ <: U]): Comparator[A] = { + thenComparing(comparing[A, U](keyExtractor)) + } + + def thenComparingInt(keyExtractor: ToIntFunction[_ >: A]): Comparator[A] = + thenComparing(comparingInt(keyExtractor)) + + def thenComparingLong(keyExtractor: ToLongFunction[_ >: A]): Comparator[A] = + thenComparing(comparingLong(keyExtractor)) + + def thenComparingDouble(keyExtractor: ToDoubleFunction[_ >: A]): Comparator[A] = + thenComparing(comparingDouble(keyExtractor)) + +} + +object Comparator { + + /* Should be T <: Comparable[_ >: T] but scalac fails with + * > illegal cyclic reference involving type U + */ + def reverseOrder[T <: Comparable[T]](): Comparator[T] = + naturalOrder[T]().reversed() + + /* Should be T <: Comparable[_ >: T] but scalac fails with + * > illegal cyclic reference involving type U + */ + @inline + def naturalOrder[T <: Comparable[T]](): Comparator[T] = + ReusableNaturalComparator.asInstanceOf[Comparator[T]] + + /* Not the same object as NaturalComparator. + * + * Otherwise we'll get null back from TreeSet#comparator() (see #4796). + */ + private object ReusableNaturalComparator extends Comparator[Any] { + def compare(o1: Any, o2: Any): Int = + o1.asInstanceOf[Comparable[Any]].compareTo(o2) + } + + @inline + def nullsFirst[T](comparator: Comparator[_ >: T]): Comparator[T] = new Comparator[T] with Serializable { + def compare(o1: T, o2: T): Int = { + if (o1 == null && o2 == null) 0 + else if (o1 == null) -1 + else if (o2 == null) 1 + else if (comparator == null) 0 + else comparator.compare(o1, o2) + } + } + + @inline + def nullsLast[T](comparator: Comparator[_ >: T]): Comparator[T] = new Comparator[T] with Serializable { + def compare(o1: T, o2: T): Int = { + if (o1 == null && o2 == null) 0 + else if (o1 == null) 1 + else if (o2 == null) -1 + else if (comparator == null) 0 + else comparator.compare(o1, o2) + } + } + + @inline + def comparing[T, U](keyExtractor: Function[_ >: T, _ <: U], + keyComparator: Comparator[_ >: U]): Comparator[T] = { + keyExtractor.getClass() // null check + keyComparator.getClass() // null check + new Comparator[T] with Serializable { + def compare(o1: T, o2: T): Int = + keyComparator.compare(keyExtractor(o1), keyExtractor(o2)) + } + } + + /* Should be U <: Comparable[_ >: U] but scalac fails with + * > illegal cyclic reference involving type U + */ + @inline + def comparing[T, U <: Comparable[U]]( + keyExtractor: Function[_ >: T, _ <: U]): Comparator[T] = { + keyExtractor.getClass() // null check + new Comparator[T] with Serializable { + def compare(o1: T, o2: T): Int = + keyExtractor(o1).compareTo(keyExtractor(o2)) + } + } + + @inline + def comparingInt[T](keyExtractor: ToIntFunction[_ >: T]): Comparator[T] = { + keyExtractor.getClass() // null check + new Comparator[T] with Serializable { + def compare(o1: T, o2: T): Int = + Integer.compare(keyExtractor.applyAsInt(o1), keyExtractor.applyAsInt(o2)) + } + } + + @inline + def comparingLong[T](keyExtractor: ToLongFunction[_ >: T]): Comparator[T] = { + keyExtractor.getClass() // null check + new Comparator[T] with Serializable { + def compare(o1: T, o2: T): Int = + java.lang.Long.compare(keyExtractor.applyAsLong(o1), keyExtractor.applyAsLong(o2)) + } + } + + @inline + def comparingDouble[T](keyExtractor: ToDoubleFunction[_ >: T]): Comparator[T] = { + keyExtractor.getClass() // null check + new Comparator[T] with Serializable { + def compare(o1: T, o2: T): Int = + java.lang.Double.compare(keyExtractor.applyAsDouble(o1), keyExtractor.applyAsDouble(o2)) + } + } } diff --git a/javalib/src/main/scala/java/util/Date.scala b/javalib/src/main/scala/java/util/Date.scala index 93a15473a8..68fe483627 100644 --- a/javalib/src/main/scala/java/util/Date.scala +++ b/javalib/src/main/scala/java/util/Date.scala @@ -12,7 +12,10 @@ package java.util +import java.lang.Cloneable import java.time.Instant +import java.util.function._ + import scalajs.js class Date(private var millis: Long) extends Object @@ -67,9 +70,9 @@ class Date(private var millis: Long) extends Object } @inline - private def mutDate(mutator: js.Date => Unit): Unit = { + private def mutDate(mutator: Consumer[js.Date]): Unit = { val date = asDate() - mutator(date) + mutator.accept(date) millis = safeGetTime(date) } diff --git a/javalib/src/main/scala/java/util/Deque.scala b/javalib/src/main/scala/java/util/Deque.scala index 89b70bc615..d4a4e0918c 100644 --- a/javalib/src/main/scala/java/util/Deque.scala +++ b/javalib/src/main/scala/java/util/Deque.scala @@ -12,7 +12,7 @@ package java.util -trait Deque[E] extends Queue[E] { +trait Deque[E] extends Queue[E] with SequencedCollection[E] { def addFirst(e: E): Unit def addLast(e: E): Unit def offerFirst(e: E): Boolean diff --git a/javalib/src/main/scala/java/util/EventObject.scala b/javalib/src/main/scala/java/util/EventObject.scala index dfed2519ea..f792217e04 100644 --- a/javalib/src/main/scala/java/util/EventObject.scala +++ b/javalib/src/main/scala/java/util/EventObject.scala @@ -16,5 +16,5 @@ class EventObject(protected var source: AnyRef) { def getSource(): AnyRef = source override def toString(): String = - s"${getClass.getSimpleName}[source=$source]" + s"${getClass().getSimpleName()}[source=$source]" } diff --git a/javalib/src/main/scala/java/util/Formatter.scala b/javalib/src/main/scala/java/util/Formatter.scala index 5535fda2cc..909fab1929 100644 --- a/javalib/src/main/scala/java/util/Formatter.scala +++ b/javalib/src/main/scala/java/util/Formatter.scala @@ -16,6 +16,7 @@ import scala.annotation.switch import scala.scalajs.js import java.lang.{Double => JDouble} +import java.lang.Utils._ import java.io._ import java.math.{BigDecimal, BigInteger} @@ -50,9 +51,9 @@ final class Formatter private (private[this] var dest: Appendable, def this(a: Appendable, l: Locale) = this(a, new Formatter.LocaleLocaleInfo(l)) @inline - private def trapIOExceptions(body: => Unit): Unit = { + private def trapIOExceptions(body: Runnable): Unit = { try { - body + body.run() } catch { case th: IOException => lastIOException = th @@ -82,8 +83,8 @@ final class Formatter private (private[this] var dest: Appendable, @noinline private def sendToDestSlowPath(ss: js.Array[String]): Unit = { - trapIOExceptions { - ss.foreach(dest.append(_)) + trapIOExceptions { () => + forArrayElems(ss)(dest.append(_)) } } @@ -91,7 +92,7 @@ final class Formatter private (private[this] var dest: Appendable, if (!closed && (dest ne null)) { dest match { case cl: Closeable => - trapIOExceptions { + trapIOExceptions { () => cl.close() } case _ => @@ -105,7 +106,7 @@ final class Formatter private (private[this] var dest: Appendable, if (dest ne null) { dest match { case fl: Flushable => - trapIOExceptions { + trapIOExceptions { () => fl.flush() } case _ => @@ -334,7 +335,7 @@ final class Formatter private (private[this] var dest: Appendable, * Int range. */ private def parsePositiveInt(capture: js.UndefOr[String]): Int = { - capture.fold { + undefOrFold(capture) { () => -1 } { s => val x = js.Dynamic.global.parseInt(s, 10).asInstanceOf[Double] @@ -746,7 +747,7 @@ final class Formatter private (private[this] var dest: Appendable, width: Int, precision: Int, str: String): Unit = { val truncatedStr = - if (precision < 0) str + if (precision < 0 || precision >= str.length()) str else str.substring(0, precision) padAndSendToDestNoZeroPad(flags, width, applyUpperCase(localeInfo, flags, truncatedStr)) @@ -992,7 +993,7 @@ object Formatter { } @inline - private def assert(condition: Boolean, msg: => String): Unit = { + private def assert(condition: Boolean, msg: String): Unit = { if (!condition) throw new AssertionError(msg) } diff --git a/javalib/src/main/scala/java/util/HashMap.scala b/javalib/src/main/scala/java/util/HashMap.scala index 7333165c07..63aeff2881 100644 --- a/javalib/src/main/scala/java/util/HashMap.scala +++ b/javalib/src/main/scala/java/util/HashMap.scala @@ -14,6 +14,7 @@ package java.util import scala.annotation.tailrec +import java.lang.Cloneable import java.{util => ju} import java.util.function.{BiConsumer, BiFunction, Function} diff --git a/javalib/src/main/scala/java/util/HashSet.scala b/javalib/src/main/scala/java/util/HashSet.scala index 07d5f67fb1..f09868f25b 100644 --- a/javalib/src/main/scala/java/util/HashSet.scala +++ b/javalib/src/main/scala/java/util/HashSet.scala @@ -12,6 +12,8 @@ package java.util +import java.lang.Cloneable + class HashSet[E] private[util] (inner: HashMap[E, Any]) extends AbstractSet[E] with Set[E] with Cloneable with Serializable { diff --git a/javalib/src/main/scala/java/util/Hashtable.scala b/javalib/src/main/scala/java/util/Hashtable.scala index 9b353b1866..9667c74811 100644 --- a/javalib/src/main/scala/java/util/Hashtable.scala +++ b/javalib/src/main/scala/java/util/Hashtable.scala @@ -12,6 +12,7 @@ package java.util +import java.lang.Cloneable import java.{util => ju} /* This implementation allows `null` keys and values, although the JavaDoc diff --git a/javalib/src/main/scala/java/util/IdentityHashMap.scala b/javalib/src/main/scala/java/util/IdentityHashMap.scala index 43475573e5..cb236bf263 100644 --- a/javalib/src/main/scala/java/util/IdentityHashMap.scala +++ b/javalib/src/main/scala/java/util/IdentityHashMap.scala @@ -12,6 +12,7 @@ package java.util +import java.lang.Cloneable import java.{util => ju} import scala.annotation.tailrec @@ -49,21 +50,21 @@ class IdentityHashMap[K, V] private ( } override def containsKey(key: Any): Boolean = - inner.containsKey(IdentityBox(key)) + inner.containsKey(new IdentityBox(key)) override def containsValue(value: Any): Boolean = inner.valueIterator().scalaOps.exists(same(_, value)) override def get(key: Any): V = - inner.get(IdentityBox(key)) + inner.get(new IdentityBox(key)) override def isEmpty(): Boolean = inner.isEmpty() override def put(key: K, value: V): V = - inner.put(IdentityBox(key), value) + inner.put(new IdentityBox(key), value) override def remove(key: Any): V = - inner.remove(IdentityBox(key)) + inner.remove(new IdentityBox(key)) override def size(): Int = inner.size() @@ -168,7 +169,7 @@ class IdentityHashMap[K, V] private ( modified } } - removeAll(this.iterator, false) + removeAll(this.iterator(), false) } } @@ -240,7 +241,7 @@ class IdentityHashMap[K, V] private ( } object IdentityHashMap { - private final case class IdentityBox[+K](inner: K) { + private final class IdentityBox[+K](val inner: K) { override def equals(o: Any): Boolean = { o match { case o: IdentityBox[_] => diff --git a/javalib/src/main/scala/java/util/Iterator.scala b/javalib/src/main/scala/java/util/Iterator.scala index f6f2943a44..de610cc7a5 100644 --- a/javalib/src/main/scala/java/util/Iterator.scala +++ b/javalib/src/main/scala/java/util/Iterator.scala @@ -12,19 +12,15 @@ package java.util -import scala.scalajs.js.annotation.JavaDefaultMethod - import java.util.function.Consumer trait Iterator[E] { def hasNext(): Boolean def next(): E - @JavaDefaultMethod def remove(): Unit = throw new UnsupportedOperationException("remove") - @JavaDefaultMethod def forEachRemaining(action: Consumer[_ >: E]): Unit = { while (hasNext()) action.accept(next()) diff --git a/javalib/src/main/scala/java/util/LinkedHashMap.scala b/javalib/src/main/scala/java/util/LinkedHashMap.scala index ba46ffc844..958aeff409 100644 --- a/javalib/src/main/scala/java/util/LinkedHashMap.scala +++ b/javalib/src/main/scala/java/util/LinkedHashMap.scala @@ -17,7 +17,7 @@ import java.util.function.BiConsumer class LinkedHashMap[K, V](initialCapacity: Int, loadFactor: Float, accessOrder: Boolean) - extends HashMap[K, V](initialCapacity, loadFactor) { + extends HashMap[K, V](initialCapacity, loadFactor) with SequencedMap[K, V] { self => import LinkedHashMap._ diff --git a/javalib/src/main/scala/java/util/LinkedHashSet.scala b/javalib/src/main/scala/java/util/LinkedHashSet.scala index b67e126d8d..f24d3f5bd4 100644 --- a/javalib/src/main/scala/java/util/LinkedHashSet.scala +++ b/javalib/src/main/scala/java/util/LinkedHashSet.scala @@ -12,8 +12,10 @@ package java.util +import java.lang.Cloneable + class LinkedHashSet[E] private[util] (inner: LinkedHashMap[E, Any]) - extends HashSet[E](inner) with Set[E] with Cloneable with Serializable { + extends HashSet[E](inner) with SequencedSet[E] with Cloneable with Serializable { def this(initialCapacity: Int, loadFactor: Float) = this(new LinkedHashMap[E, Any](initialCapacity, loadFactor)) diff --git a/javalib/src/main/scala/java/util/LinkedList.scala b/javalib/src/main/scala/java/util/LinkedList.scala index 5bddb6f572..cd0f205b8d 100644 --- a/javalib/src/main/scala/java/util/LinkedList.scala +++ b/javalib/src/main/scala/java/util/LinkedList.scala @@ -12,7 +12,7 @@ package java.util -import scala.annotation.tailrec +import java.lang.Cloneable import ScalaOps._ @@ -123,7 +123,7 @@ class LinkedList[E]() extends AbstractSequentialList[E] _removeOccurrence(listIterator(), o) override def addAll(c: Collection[_ <: E]): Boolean = { - val iter = c.iterator + val iter = c.iterator() val changed = iter.hasNext() while (iter.hasNext()) addLast(iter.next()) diff --git a/javalib/src/main/scala/java/util/List.scala b/javalib/src/main/scala/java/util/List.scala index 33d219303a..982e373b01 100644 --- a/javalib/src/main/scala/java/util/List.scala +++ b/javalib/src/main/scala/java/util/List.scala @@ -14,17 +14,13 @@ package java.util import java.util.function.UnaryOperator -import scala.scalajs.js.annotation.JavaDefaultMethod - -trait List[E] extends Collection[E] { - @JavaDefaultMethod +trait List[E] extends SequencedCollection[E] { def replaceAll(operator: UnaryOperator[E]): Unit = { val iter = listIterator() while (iter.hasNext()) iter.set(operator.apply(iter.next())) } - @JavaDefaultMethod def sort(c: Comparator[_ >: E]): Unit = { val arrayBuf = toArray() Arrays.sort[AnyRef with E](arrayBuf.asInstanceOf[Array[AnyRef with E]], c) diff --git a/javalib/src/main/scala/java/util/Map.scala b/javalib/src/main/scala/java/util/Map.scala index 260bd05a92..c2250b2143 100644 --- a/javalib/src/main/scala/java/util/Map.scala +++ b/javalib/src/main/scala/java/util/Map.scala @@ -14,8 +14,6 @@ package java.util import java.util.function.{BiConsumer, BiFunction, Function} -import scala.scalajs.js.annotation.JavaDefaultMethod - import ScalaOps._ trait Map[K, V] { @@ -34,24 +32,20 @@ trait Map[K, V] { def equals(o: Any): Boolean def hashCode(): Int - @JavaDefaultMethod def getOrDefault(key: Any, defaultValue: V): V = if (containsKey(key)) get(key) else defaultValue - @JavaDefaultMethod def forEach(action: BiConsumer[_ >: K, _ >: V]): Unit = { for (entry <- entrySet().scalaOps) action.accept(entry.getKey(), entry.getValue()) } - @JavaDefaultMethod def replaceAll(function: BiFunction[_ >: K, _ >: V, _ <: V]): Unit = { for (entry <- entrySet().scalaOps) entry.setValue(function.apply(entry.getKey(), entry.getValue())) } - @JavaDefaultMethod def putIfAbsent(key: K, value: V): V = { val prevValue = get(key) if (prevValue == null) @@ -60,7 +54,6 @@ trait Map[K, V] { prevValue } - @JavaDefaultMethod def remove(key: Any, value: Any): Boolean = { if (containsKey(key) && Objects.equals(get(key), value)) { remove(key) @@ -70,7 +63,6 @@ trait Map[K, V] { } } - @JavaDefaultMethod def replace(key: K, oldValue: V, newValue: V): Boolean = { if (containsKey(key) && Objects.equals(get(key), oldValue)) { put(key, newValue) @@ -80,12 +72,10 @@ trait Map[K, V] { } } - @JavaDefaultMethod def replace(key: K, value: V): V = if (containsKey(key)) put(key, value) else null.asInstanceOf[V] - @JavaDefaultMethod def computeIfAbsent(key: K, mappingFunction: Function[_ >: K, _ <: V]): V = { val oldValue = get(key) if (oldValue != null) { @@ -98,7 +88,6 @@ trait Map[K, V] { } } - @JavaDefaultMethod def computeIfPresent(key: K, remappingFunction: BiFunction[_ >: K, _ >: V, _ <: V]): V = { val oldValue = get(key) if (oldValue == null) { @@ -110,7 +99,6 @@ trait Map[K, V] { } } - @JavaDefaultMethod def compute(key: K, remappingFunction: BiFunction[_ >: K, _ >: V, _ <: V]): V = { val oldValue = get(key) val newValue = remappingFunction.apply(key, oldValue) @@ -131,7 +119,6 @@ trait Map[K, V] { newValue } - @JavaDefaultMethod def merge(key: K, value: V, remappingFunction: BiFunction[_ >: V, _ >: V, _ <: V]): V = { Objects.requireNonNull(value) diff --git a/javalib/src/main/scala/java/util/NaturalComparator.scala b/javalib/src/main/scala/java/util/NaturalComparator.scala index b0b72d3dc1..3775df2e75 100644 --- a/javalib/src/main/scala/java/util/NaturalComparator.scala +++ b/javalib/src/main/scala/java/util/NaturalComparator.scala @@ -24,7 +24,7 @@ package java.util * Scala.js is configured with compliant `asInstanceOf`s. The behavior is * otherwise undefined. */ -private[util] object NaturalComparator extends Comparator[Any] { +private[util] object NaturalComparator extends Comparator[Any] with Serializable { def compare(o1: Any, o2: Any): Int = o1.asInstanceOf[Comparable[Any]].compareTo(o2) diff --git a/javalib/src/main/scala/java/util/Properties.scala b/javalib/src/main/scala/java/util/Properties.scala index 45e559214b..d70e639fa4 100644 --- a/javalib/src/main/scala/java/util/Properties.scala +++ b/javalib/src/main/scala/java/util/Properties.scala @@ -19,6 +19,7 @@ import java.{lang => jl} import java.{util => ju} import java.io._ import java.nio.charset.StandardCharsets +import java.util.function._ import scala.scalajs.js @@ -59,7 +60,7 @@ class Properties(protected val defaults: Properties) writer.write('#') writer.write(new Date().toString) - writer.write(System.lineSeparator) + writer.write(System.lineSeparator()) entrySet().scalaOps.foreach { entry => writer.write(encodeString(entry.getKey().asInstanceOf[String], @@ -67,7 +68,7 @@ class Properties(protected val defaults: Properties) writer.write('=') writer.write(encodeString(entry.getValue().asInstanceOf[String], isKey = false, toHex)) - writer.write(System.lineSeparator) + writer.write(System.lineSeparator()) } writer.flush() } @@ -114,8 +115,8 @@ class Properties(protected val defaults: Properties) } @inline @tailrec - private final def foreachAncestor(f: Properties => Unit): Unit = { - f(this) + private final def foreachAncestor(f: Consumer[Properties]): Unit = { + f.accept(this) if (defaults ne null) defaults.foreachAncestor(f) } @@ -299,7 +300,7 @@ class Properties(protected val defaults: Properties) if (isCrlf) { index += 1 } - writer.write(System.lineSeparator) + writer.write(System.lineSeparator()) def noExplicitComment = { index + 1 < chars.length && @@ -321,7 +322,7 @@ class Properties(protected val defaults: Properties) } index += 1 } - writer.write(System.lineSeparator) + writer.write(System.lineSeparator()) } private def encodeString(string: String, isKey: Boolean, diff --git a/javalib/src/main/scala/java/util/Random.scala b/javalib/src/main/scala/java/util/Random.scala index a2ae2eb3f6..7840b8c3c1 100644 --- a/javalib/src/main/scala/java/util/Random.scala +++ b/javalib/src/main/scala/java/util/Random.scala @@ -15,11 +15,23 @@ package java.util import scala.annotation.tailrec import scala.scalajs.js +import scala.scalajs.LinkingInfo -class Random(seed_in: Long) extends AnyRef with java.io.Serializable { +import java.util.random.RandomGenerator - private var seedHi: Int = _ // 24 msb of the seed - private var seedLo: Int = _ // 24 lsb of the seed +class Random(seed_in: Long) + extends AnyRef with RandomGenerator with java.io.Serializable { + + /* This class has two different implementations of seeding and computing + * bits, depending on whether we are on Wasm or JS. On Wasm, we use the + * implementation specified in the JavaDoc verbatim. On JS, however, that is + * too slow, due to the use of `Long`s. Therefore, we decompose the + * computations using 2x24 bits. See `nextJS()` for details. + */ + + private var seed: Long = _ // the full seed on Wasm (dce'ed on JS) + private var seedHi: Int = _ // 24 msb of the seed in JS (dce'ed on Wasm) + private var seedLo: Int = _ // 24 lsb of the seed in JS (dce'ed on Wasm) // see nextGaussian() private var nextNextGaussian: Double = _ @@ -31,12 +43,30 @@ class Random(seed_in: Long) extends AnyRef with java.io.Serializable { def setSeed(seed_in: Long): Unit = { val seed = ((seed_in ^ 0x5DEECE66DL) & ((1L << 48) - 1)) // as documented - seedHi = (seed >>> 24).toInt - seedLo = seed.toInt & ((1 << 24) - 1) + if (LinkingInfo.isWebAssembly) { + this.seed = seed + } else { + seedHi = (seed >>> 24).toInt + seedLo = seed.toInt & ((1 << 24) - 1) + } haveNextNextGaussian = false } - protected def next(bits: Int): Int = { + @noinline + protected def next(bits: Int): Int = + if (LinkingInfo.isWebAssembly) nextWasm(bits) + else nextJS(bits) + + @inline + private def nextWasm(bits: Int): Int = { + // as documented + val newSeed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1) + seed = newSeed + (newSeed >>> (48 - bits)).toInt + } + + @inline + private def nextJS(bits: Int): Int = { /* This method is originally supposed to work with a Long seed from which * 48 bits are used. * Since Longs are too slow, we manually decompose the 48-bit seed in two @@ -82,16 +112,16 @@ class Random(seed_in: Long) extends AnyRef with java.io.Serializable { result32 >>> (32 - bits) } - def nextDouble(): Double = { + override def nextDouble(): Double = { // ((next(26).toLong << 27) + next(27)) / (1L << 53).toDouble ((next(26).toDouble * (1L << 27).toDouble) + next(27).toDouble) / (1L << 53).toDouble } - def nextBoolean(): Boolean = next(1) != 0 + override def nextBoolean(): Boolean = next(1) != 0 - def nextInt(): Int = next(32) + override def nextInt(): Int = next(32) - def nextInt(n: Int): Int = { + override def nextInt(n: Int): Int = { if (n <= 0) { throw new IllegalArgumentException("n must be positive") } else if ((n & -n) == n) { // i.e., n is a power of 2 @@ -122,12 +152,12 @@ class Random(seed_in: Long) extends AnyRef with java.io.Serializable { def nextLong(): Long = (next(32).toLong << 32) + next(32) - def nextFloat(): Float = { + override def nextFloat(): Float = { // next(24).toFloat / (1 << 24).toFloat (next(24).toDouble / (1 << 24).toDouble).toFloat } - def nextBytes(bytes: Array[Byte]): Unit = { + override def nextBytes(bytes: Array[Byte]): Unit = { var i = 0 while (i < bytes.length) { var rnd = nextInt() diff --git a/javalib/src/main/scala/java/util/RedBlackTree.scala b/javalib/src/main/scala/java/util/RedBlackTree.scala index 336df6d2c4..a1554e264d 100644 --- a/javalib/src/main/scala/java/util/RedBlackTree.scala +++ b/javalib/src/main/scala/java/util/RedBlackTree.scala @@ -14,11 +14,11 @@ package java.util import scala.annotation.tailrec +import java.util.function._ + import scala.scalajs.js -/** The red-black tree implementation used by `TreeSet`s. - * - * It could also be used by `TreeMap`s in the future. +/** The red-black tree implementation used by `TreeSet`s and `TreeMap`s. * * This implementation was copied and adapted from * `scala.collection.mutable.RedBlackTree` as found in Scala 2.13.0. @@ -223,7 +223,12 @@ private[util] object RedBlackTree { def get[A, B](tree: Tree[A, B], key: Any)( implicit comp: Comparator[_ >: A]): B = { - nullableNodeFlatMap(getNode(tree.root, key))(_.value) + nullableNodeFlatMap(getNode(tree, key))(_.value) + } + + def getNode[A, B](tree: Tree[A, B], key: Any)( + implicit comp: Comparator[_ >: A]): Node[A, B] = { + getNode(tree.root, key) } @tailrec @@ -471,10 +476,10 @@ private[util] object RedBlackTree { // ---- deletion ---- def delete[A, B](tree: Tree[A, B], key: Any)( - implicit comp: Comparator[_ >: A]): B = { + implicit comp: Comparator[_ >: A]): Node[A, B] = { nullableNodeFlatMap(getNode(tree.root, key)) { node => deleteNode(tree, node) - node.value + node } } @@ -606,16 +611,22 @@ private[util] object RedBlackTree { /** Returns `null.asInstanceOf[C]` if `node eq null`, otherwise `f(node)`. */ @inline - private def nullableNodeFlatMap[A, B, C](node: Node[A, B])(f: Node[A, B] => C): C = + private def nullableNodeFlatMap[A, B, C](node: Node[A, B])(f: Function[Node[A, B], C]): C = if (node eq null) null.asInstanceOf[C] else f(node) /** Returns `null.asInstanceOf[A]` if `node eq null`, otherwise `node.key`. */ @inline - private def nullableNodeKey[A, B](node: Node[A, B]): A = + def nullableNodeKey[A, B](node: Node[A, B]): A = if (node eq null) null.asInstanceOf[A] else node.key + /** Returns `null.asInstanceOf[B]` if `node eq null`, otherwise `node.value`. */ + @inline + def nullableNodeValue[A, B](node: Node[A, B]): B = + if (node eq null) null.asInstanceOf[B] + else node.value + /** Returns the node that follows `node` in an in-order tree traversal. * * If `node` has the maximum key (and is, therefore, the last node), this diff --git a/javalib/src/main/scala/java/util/ScalaOps.scala b/javalib/src/main/scala/java/util/ScalaOps.scala index 4362f77fa8..a00c7d379f 100644 --- a/javalib/src/main/scala/java/util/ScalaOps.scala +++ b/javalib/src/main/scala/java/util/ScalaOps.scala @@ -12,21 +12,38 @@ package java.util +import java.util.function._ + /** Make some Scala collection APIs available on Java collections. */ private[java] object ScalaOps { implicit class IntScalaOps private[ScalaOps] (val __self: Int) extends AnyVal { @inline def until(end: Int): SimpleRange = new SimpleRange(__self, end) + + @inline def to(end: Int): SimpleInclusiveRange = + new SimpleInclusiveRange(__self, end) } @inline final class SimpleRange(start: Int, end: Int) { @inline - def foreach[U](f: Int => U): Unit = { + def foreach[U](f: IntConsumer): Unit = { var i = start while (i < end) { - f(i) + f.accept(i) + i += 1 + } + } + } + + @inline + final class SimpleInclusiveRange(start: Int, end: Int) { + @inline + def foreach[U](f: IntConsumer): Unit = { + var i = start + while (i <= end) { + f.accept(i) i += 1 } } @@ -42,28 +59,28 @@ private[java] object ScalaOps { val __self: java.lang.Iterable[A]) extends AnyVal { - @inline def foreach[U](f: A => U): Unit = + @inline def foreach(f: Consumer[A]): Unit = __self.iterator().scalaOps.foreach(f) - @inline def count(f: A => Boolean): Int = + @inline def count(f: Predicate[A]): Int = __self.iterator().scalaOps.count(f) - @inline def exists(f: A => Boolean): Boolean = + @inline def exists(f: Predicate[A]): Boolean = __self.iterator().scalaOps.exists(f) - @inline def forall(f: A => Boolean): Boolean = + @inline def forall(f: Predicate[A]): Boolean = __self.iterator().scalaOps.forall(f) - @inline def indexWhere(f: A => Boolean): Int = + @inline def indexWhere(f: Predicate[A]): Int = __self.iterator().scalaOps.indexWhere(f) - @inline def find(f: A => Boolean): Option[A] = - __self.iterator().scalaOps.find(f) + @inline def findFold[B](f: Predicate[A])(default: Supplier[B])(g: Function[A, B]): B = + __self.iterator().scalaOps.findFold(f)(default)(g) - @inline def foldLeft[B](z: B)(f: (B, A) => B): B = + @inline def foldLeft[B](z: B)(f: BiFunction[B, A, B]): B = __self.iterator().scalaOps.foldLeft(z)(f) - @inline def reduceLeft[B >: A](f: (B, A) => B): B = + @inline def reduceLeft[B >: A](f: BiFunction[B, A, B]): B = __self.iterator().scalaOps.reduceLeft(f) @inline def mkString(start: String, sep: String, end: String): String = @@ -79,32 +96,32 @@ private[java] object ScalaOps { class JavaIteratorOps[A] private[ScalaOps] (val __self: Iterator[A]) extends AnyVal { - @inline def foreach[U](f: A => U): Unit = { + @inline def foreach(f: Consumer[A]): Unit = { while (__self.hasNext()) - f(__self.next()) + f.accept(__self.next()) } - @inline def count(f: A => Boolean): Int = - foldLeft(0)((prev, x) => if (f(x)) prev + 1 else prev) + @inline def count(f: Predicate[A]): Int = + foldLeft(0)((prev, x) => if (f.test(x)) prev + 1 else prev) - @inline def exists(f: A => Boolean): Boolean = { + @inline def exists(f: Predicate[A]): Boolean = { // scalastyle:off return while (__self.hasNext()) { - if (f(__self.next())) + if (f.test(__self.next())) return true } false // scalastyle:on return } - @inline def forall(f: A => Boolean): Boolean = - !exists(x => !f(x)) + @inline def forall(f: Predicate[A]): Boolean = + !exists(x => !f.test(x)) - @inline def indexWhere(f: A => Boolean): Int = { + @inline def indexWhere(f: Predicate[A]): Int = { // scalastyle:off return var i = 0 while (__self.hasNext()) { - if (f(__self.next())) + if (f.test(__self.next())) return i i += 1 } @@ -112,25 +129,25 @@ private[java] object ScalaOps { // scalastyle:on return } - @inline def find(f: A => Boolean): Option[A] = { + @inline def findFold[B](f: Predicate[A])(default: Supplier[B])(g: Function[A, B]): B = { // scalastyle:off return while (__self.hasNext()) { val x = __self.next() - if (f(x)) - return Some(x) + if (f.test(x)) + return g(x) } - None + default.get() // scalastyle:on return } - @inline def foldLeft[B](z: B)(f: (B, A) => B): B = { + @inline def foldLeft[B](z: B)(f: BiFunction[B, A, B]): B = { var result: B = z while (__self.hasNext()) result = f(result, __self.next()) result } - @inline def reduceLeft[B >: A](f: (B, A) => B): B = { + @inline def reduceLeft[B >: A](f: BiFunction[B, A, B]): B = { if (!__self.hasNext()) throw new NoSuchElementException("collection is empty") foldLeft[B](__self.next())(f) @@ -159,9 +176,9 @@ private[java] object ScalaOps { class JavaEnumerationOps[A] private[ScalaOps] (val __self: Enumeration[A]) extends AnyVal { - @inline def foreach[U](f: A => U): Unit = { + @inline def foreach(f: Consumer[A]): Unit = { while (__self.hasMoreElements()) - f(__self.nextElement()) + f.accept(__self.nextElement()) } } diff --git a/javalib/src/main/scala/java/util/SequencedCollection.scala b/javalib/src/main/scala/java/util/SequencedCollection.scala new file mode 100644 index 0000000000..9f49537f33 --- /dev/null +++ b/javalib/src/main/scala/java/util/SequencedCollection.scala @@ -0,0 +1,15 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util + +trait SequencedCollection[E] extends Collection[E] diff --git a/javalib/src/main/scala/java/util/SequencedMap.scala b/javalib/src/main/scala/java/util/SequencedMap.scala new file mode 100644 index 0000000000..a1eb13cd23 --- /dev/null +++ b/javalib/src/main/scala/java/util/SequencedMap.scala @@ -0,0 +1,17 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util + +import java.util.Map.Entry + +trait SequencedMap[K, V] extends Map[K, V] diff --git a/javalib/src/main/scala/java/util/SequencedSet.scala b/javalib/src/main/scala/java/util/SequencedSet.scala new file mode 100644 index 0000000000..8bf692997a --- /dev/null +++ b/javalib/src/main/scala/java/util/SequencedSet.scala @@ -0,0 +1,15 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util + +trait SequencedSet[E] extends SequencedCollection[E] with Set[E] diff --git a/javalib/src/main/scala/java/util/SortedMap.scala b/javalib/src/main/scala/java/util/SortedMap.scala index e2ac54cfef..c5076c0019 100644 --- a/javalib/src/main/scala/java/util/SortedMap.scala +++ b/javalib/src/main/scala/java/util/SortedMap.scala @@ -12,7 +12,7 @@ package java.util -trait SortedMap[K, V] extends Map[K, V] { +trait SortedMap[K, V] extends SequencedMap[K, V] { def firstKey(): K def comparator(): Comparator[_ >: K] def lastKey(): K diff --git a/javalib/src/main/scala/java/util/SortedSet.scala b/javalib/src/main/scala/java/util/SortedSet.scala index b4f6fcc83d..95694ab938 100644 --- a/javalib/src/main/scala/java/util/SortedSet.scala +++ b/javalib/src/main/scala/java/util/SortedSet.scala @@ -12,7 +12,7 @@ package java.util -trait SortedSet[E] extends Set[E] { +trait SortedSet[E] extends SequencedSet[E] { def comparator(): Comparator[_ >: E] def subSet(fromElement: E, toElement: E): SortedSet[E] def headSet(toElement: E): SortedSet[E] diff --git a/javalib/src/main/scala/java/util/SplittableRandom.scala b/javalib/src/main/scala/java/util/SplittableRandom.scala index 9d394b909c..8fced3e262 100644 --- a/javalib/src/main/scala/java/util/SplittableRandom.scala +++ b/javalib/src/main/scala/java/util/SplittableRandom.scala @@ -12,6 +12,8 @@ package java.util +import java.util.random.RandomGenerator + /* * This is a clean room implementation derived from the original paper * and Java implementation mentioned there: @@ -23,7 +25,6 @@ package java.util */ private object SplittableRandom { - private final val DoubleULP = 1.0 / (1L << 53) private final val GoldenGamma = 0x9e3779b97f4a7c15L private var defaultGen: Long = new Random().nextLong() @@ -80,7 +81,8 @@ private object SplittableRandom { } -final class SplittableRandom private (private var seed: Long, gamma: Long) { +final class SplittableRandom private (private var seed: Long, gamma: Long) + extends RandomGenerator { import SplittableRandom._ def this(seed: Long) = { @@ -106,27 +108,13 @@ final class SplittableRandom private (private var seed: Long, gamma: Long) { seed } - def nextInt(): Int = mix32(nextSeed()) - - //def nextInt(bound: Int): Int - - //def nextInt(origin: Int, bound: Int): Int + /* According to the JavaDoc, this method is not overridden anymore. + * However, if we remove our override, we break tests in + * `SplittableRandomTest`. I don't know how the JDK produces the values it + * produces without that override. So we keep it on our side. + */ + override def nextInt(): Int = mix32(nextSeed()) def nextLong(): Long = mix64(nextSeed()) - //def nextLong(bound: Long): Long - - //def nextLong(origin: Long, bound: Long): Long - - def nextDouble(): Double = - (nextLong() >>> 11).toDouble * DoubleULP - - //def nextDouble(bound: Double): Double - - //def nextDouble(origin: Double, bound: Double): Double - - // this should be properly tested - // looks to work but just by chance maybe - def nextBoolean(): Boolean = nextInt() < 0 - } diff --git a/javalib/src/main/scala/java/util/StringJoiner.scala b/javalib/src/main/scala/java/util/StringJoiner.scala new file mode 100644 index 0000000000..6359910260 --- /dev/null +++ b/javalib/src/main/scala/java/util/StringJoiner.scala @@ -0,0 +1,62 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util + +@inline +final class StringJoiner private (delimiter: String, prefix: String, suffix: String) extends AnyRef { + /** The custom value to return if empty, set by `setEmptyValue` (nullable). + * + * If `null`, defaults to `prefix + suffix`. + */ + private var emptyValue: String = null + + /** The current value, excluding prefix and suffix. */ + private var value: String = "" + + /** Whether the string joiner is currently empty. */ + private var isEmpty: Boolean = true + + def this(delimiter: CharSequence) = + this(delimiter.toString(), "", "") + + def this(delimiter: CharSequence, prefix: CharSequence, suffix: CharSequence) = + this(delimiter.toString(), prefix.toString(), suffix.toString()) + + def setEmptyValue(emptyValue: CharSequence): StringJoiner = { + this.emptyValue = emptyValue.toString() + this + } + + override def toString(): String = + if (isEmpty && emptyValue != null) emptyValue + else prefix + value + suffix + + def add(newElement: CharSequence): StringJoiner = { + if (isEmpty) + isEmpty = false + else + value += delimiter + value += newElement // if newElement is null, adds "null" + this + } + + def merge(other: StringJoiner): StringJoiner = { + if (!other.isEmpty) // if `other` is empty, `merge` has no effect + add(other.value) // without prefix nor suffix, but with delimiters + this + } + + def length(): Int = + if (isEmpty && emptyValue != null) emptyValue.length() + else prefix.length() + value.length() + suffix.length() +} diff --git a/javalib/src/main/scala/java/util/Timer.scala b/javalib/src/main/scala/java/util/Timer.scala index 2873c481e3..e802db4a31 100644 --- a/javalib/src/main/scala/java/util/Timer.scala +++ b/javalib/src/main/scala/java/util/Timer.scala @@ -31,7 +31,7 @@ class Timer() { } private def checkDelay(delay: Long): Unit = { - if (delay < 0 || (delay + System.currentTimeMillis) < 0) + if (delay < 0 || (delay + System.currentTimeMillis()) < 0) throw new IllegalArgumentException("Negative delay.") } @@ -47,14 +47,14 @@ class Timer() { private def scheduleOnce(task: TimerTask, delay: Long): Unit = { acquire(task) - task.timeout(delay) { + task.timeout(delay) { () => task.scheduledOnceAndStarted = true task.doRun() } } private def getMillisUntil(time: Date): Long = - math.max(0L, time.getTime() - System.currentTimeMillis()) + Math.max(0L, time.getTime() - System.currentTimeMillis()) def schedule(task: TimerTask, delay: Long): Unit = { checkDelay(delay) @@ -70,16 +70,18 @@ class Timer() { private def schedulePeriodically( task: TimerTask, delay: Long, period: Long): Unit = { acquire(task) - task.timeout(delay) { - def loop(): Unit = { - val startTime = System.nanoTime() - task.doRun() - val endTime = System.nanoTime() - val duration = (endTime - startTime) / 1000000 - task.timeout(period - duration) { - loop() - } + + def loop(): Unit = { + val startTime = System.nanoTime() + task.doRun() + val endTime = System.nanoTime() + val duration = (endTime - startTime) / 1000000 + task.timeout(period - duration) { () => + loop() } + } + + task.timeout(delay) { () => loop() } } @@ -100,22 +102,24 @@ class Timer() { private def scheduleFixed( task: TimerTask, delay: Long, period: Long): Unit = { acquire(task) - task.timeout(delay) { - def loop(scheduledTime: Long): Unit = { - task.doRun() - val nextScheduledTime = scheduledTime + period - val nowTime = System.nanoTime / 1000000L - if (nowTime >= nextScheduledTime) { - // Re-run immediately. + + def loop(scheduledTime: Long): Unit = { + task.doRun() + val nextScheduledTime = scheduledTime + period + val nowTime = System.nanoTime() / 1000000L + if (nowTime >= nextScheduledTime) { + // Re-run immediately. + loop(nextScheduledTime) + } else { + // Re-run after a timeout. + task.timeout(nextScheduledTime - nowTime) { () => loop(nextScheduledTime) - } else { - // Re-run after a timeout. - task.timeout(nextScheduledTime - nowTime) { - loop(nextScheduledTime) - } } } - loop(System.nanoTime / 1000000L + period) + } + + task.timeout(delay) { () => + loop(System.nanoTime() / 1000000L + period) } } diff --git a/javalib/src/main/scala/java/util/TimerTask.scala b/javalib/src/main/scala/java/util/TimerTask.scala index 959d206f53..439add8f55 100644 --- a/javalib/src/main/scala/java/util/TimerTask.scala +++ b/javalib/src/main/scala/java/util/TimerTask.scala @@ -12,7 +12,8 @@ package java.util -import scala.scalajs.js.timers._ +import scala.scalajs.js +import scala.scalajs.js.timers.RawTimers._ import scala.scalajs.js.timers.SetTimeoutHandle abstract class TimerTask { @@ -40,9 +41,9 @@ abstract class TimerTask { def scheduledExecutionTime(): Long = lastScheduled - private[util] def timeout(delay: Long)(body: => Unit): Unit = { + private[util] def timeout(delay: Long)(body: js.Function0[Any]): Unit = { if (!canceled) { - handle = setTimeout(delay.toDouble)(body) + handle = setTimeout(body, delay.toDouble) } } diff --git a/javalib/src/main/scala/java/util/TreeMap.scala b/javalib/src/main/scala/java/util/TreeMap.scala new file mode 100644 index 0000000000..ca0789f9c4 --- /dev/null +++ b/javalib/src/main/scala/java/util/TreeMap.scala @@ -0,0 +1,606 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util + +import java.lang.Cloneable +import java.util.{RedBlackTree => RB} +import java.util.function.{Function, BiFunction} + +class TreeMap[K, V] private (tree: RB.Tree[K, V])( + implicit comp: Comparator[_ >: K]) + extends AbstractMap[K, V] with NavigableMap[K, V] with Cloneable with Serializable { + + def this() = this(RB.Tree.empty[K, V])(NaturalComparator) + + def this(comparator: Comparator[_ >: K]) = + this(RB.Tree.empty[K, V])(NaturalComparator.select(comparator)) + + def this(m: Map[K, V]) = { + this() + putAll(m) + } + + def this(m: SortedMap[K, V]) = { + this(RB.fromOrderedEntries(m.entrySet().iterator(), m.size()))( + NaturalComparator.select(m.comparator())) + } + + override def size(): Int = RB.size(tree) + + override def containsKey(key: Any): Boolean = RB.contains(tree, key) + + override def containsValue(value: Any): Boolean = { + // scalastyle:off return + val iter = RB.valuesIterator(tree) + while (iter.hasNext()) { + if (Objects.equals(value, iter.next())) + return true + } + false + // scalastyle:on return + } + + override def get(key: Any): V = RB.get(tree, key) + + def comparator(): Comparator[_ >: K] = + NaturalComparator.unselect(comp) + + def firstKey(): K = { + if (isEmpty()) + throw new NoSuchElementException() + RB.minKey(tree) + } + + def lastKey(): K = { + if (isEmpty()) + throw new NoSuchElementException() + RB.maxKey(tree) + } + + override def putAll(map: Map[_ <: K, _ <: V]): Unit = + map.forEach((k, v) => put(k, v)) + + override def put(key: K, value: V): V = + RB.insert(tree, key, value) + + override def computeIfAbsent(key: K, mappingFunction: Function[_ >: K, _ <: V]): V = { + val node = RB.getNode(tree, key) + + if (node eq null) { + val newValue = mappingFunction(key) + if (newValue != null) + put(key, newValue) + newValue + } else if (node.getValue() == null) { + val newValue = mappingFunction(key) + if (newValue != null) + updateNodeValue(node, newValue) + newValue + } else { + node.getValue() + } + } + + override def computeIfPresent(key: K, remappingFunction: BiFunction[_ >: K, _ >: V, _ <: V]): V = { + val node = RB.getNode(tree, key) + if ((node ne null) && node.getValue() != null) + updateNodeValue(node, remappingFunction(key, node.getValue())) + else + null.asInstanceOf[V] + } + + override def compute(key: K, remappingFunction: BiFunction[_ >: K, _ >: V, _ <: V]): V = { + val node = RB.getNode(tree, key) + if (node eq null) { + val newValue = remappingFunction(key, null.asInstanceOf[V]) + if (newValue != null) + put(key, newValue) + newValue + } else { + updateNodeValue(node, remappingFunction(key, node.getValue())) + } + } + + override def merge(key: K, value: V, remappingFunction: BiFunction[_ >: V, _ >: V, _ <: V]): V = { + value.getClass() // null check + + val node = RB.getNode(tree, key) + if (node eq null) { + put(key, value) + value + } else { + val oldValue = node.getValue() + val newValue = + if (oldValue == null) value + else remappingFunction(oldValue, value) + + updateNodeValue(node, newValue) + } + } + + /** Common code for functions above. + * + * - Sets value to newValue if it is non-null + * - deletes the node if newValue is null. + * + * @returns newValue + */ + private def updateNodeValue(node: RB.Node[K, V], newValue: V): V = { + if (newValue == null) + RB.deleteNode(tree, node) + else + node.setValue(newValue) + newValue + } + + override def remove(key: Any): V = + RB.nullableNodeValue(RB.delete(tree, key)) + + override def clear(): Unit = RB.clear(tree) + + override def clone(): Object = new TreeMap(tree.treeCopy())(comp) + + def firstEntry(): Map.Entry[K, V] = RB.minNode(tree) + + def lastEntry(): Map.Entry[K, V] = RB.maxNode(tree) + + def pollFirstEntry(): Map.Entry[K, V] = { + val node = RB.minNode(tree) + if (node ne null) + RB.deleteNode(tree, node) + node + } + + def pollLastEntry(): Map.Entry[K, V] = { + val node = RB.maxNode(tree) + if (node ne null) + RB.deleteNode(tree, node) + node + } + + def lowerEntry(key: K): Map.Entry[K, V] = + RB.maxNodeBefore(tree, key, RB.ExclusiveBound) + + def lowerKey(key: K): K = + RB.maxKeyBefore(tree, key, RB.ExclusiveBound) + + def floorEntry(key: K): Map.Entry[K, V] = + RB.maxNodeBefore(tree, key, RB.InclusiveBound) + + def floorKey(key: K): K = + RB.maxKeyBefore(tree, key, RB.InclusiveBound) + + def ceilingEntry(key: K): Map.Entry[K, V] = + RB.minNodeAfter(tree, key, RB.InclusiveBound) + + def ceilingKey(key: K): K = + RB.minKeyAfter(tree, key, RB.InclusiveBound) + + def higherEntry(key: K): Map.Entry[K, V] = + RB.minNodeAfter(tree, key, RB.ExclusiveBound) + + def higherKey(key: K): K = + RB.minKeyAfter(tree, key, RB.ExclusiveBound) + + override def keySet(): Set[K] = navigableKeySet() + + def navigableKeySet(): NavigableSet[K] = { + new TreeSet.Projection(tree, null.asInstanceOf[K], RB.NoBound, + null.asInstanceOf[K], RB.NoBound, null.asInstanceOf[V]) + } + + def descendingKeySet(): NavigableSet[K] = { + new TreeSet.DescendingProjection(tree, null.asInstanceOf[K], RB.NoBound, + null.asInstanceOf[K], RB.NoBound, null.asInstanceOf[V]) + } + + override def values(): Collection[V] = new AbstractCollection[V] { + def iterator(): Iterator[V] = RB.valuesIterator(tree) + + def size(): Int = RB.size(tree) + + override def contains(o: Any): Boolean = containsValue(o) + + override def clear(): Unit = RB.clear(tree) + } + + def entrySet(): Set[Map.Entry[K, V]] = { + new TreeMap.ProjectedEntrySet(tree, null.asInstanceOf[K], RB.NoBound, + null.asInstanceOf[K], RB.NoBound) + } + + def descendingMap(): NavigableMap[K, V] = { + new TreeMap.DescendingProjection(tree, null.asInstanceOf[K], RB.NoBound, + null.asInstanceOf[K], RB.NoBound) + } + + def subMap(fromKey: K, fromInclusive: Boolean, toKey: K, toInclusive: Boolean): NavigableMap[K, V] = { + new TreeMap.Projection(tree, + fromKey, RB.boundKindFromIsInclusive(fromInclusive), + toKey, RB.boundKindFromIsInclusive(toInclusive)) + } + + def headMap(toKey: K, inclusive: Boolean): NavigableMap[K, V] = { + new TreeMap.Projection(tree, + null.asInstanceOf[K], RB.NoBound, + toKey, RB.boundKindFromIsInclusive(inclusive)) + } + + def tailMap(fromKey: K, inclusive: Boolean): NavigableMap[K, V] = { + new TreeMap.Projection(tree, + fromKey, RB.boundKindFromIsInclusive(inclusive), + null.asInstanceOf[K], RB.NoBound) + } + + def subMap(fromKey: K, toKey: K): SortedMap[K, V] = + subMap(fromKey, true, toKey, false) + + def headMap(toKey: K): SortedMap[K, V] = + headMap(toKey, false) + + def tailMap(fromKey: K): SortedMap[K, V] = + tailMap(fromKey, true) +} + +private object TreeMap { + private class ProjectedEntrySet[K, V](tree: RB.Tree[K, V], + lowerBound: K, lowerKind: RB.BoundKind, upperBound: K, upperKind: RB.BoundKind)( + implicit protected val comp: Comparator[_ >: K]) + extends AbstractSet[Map.Entry[K, V]] { + + def iterator(): Iterator[Map.Entry[K, V]] = + RB.projectionIterator(tree, lowerBound, lowerKind, upperBound, upperKind) + + def size(): Int = + RB.projectionSize(tree, lowerBound, lowerKind, upperBound, upperKind) + + override def contains(o: Any): Boolean = o match { + case o: Map.Entry[_, _] if isWithinBounds(o.getKey()) => + val node = RB.getNode(tree, o.getKey()) + (node ne null) && Objects.equals(node.getValue(), o.getValue()) + case _ => + false + } + + override def remove(o: Any): Boolean = o match { + case o: Map.Entry[_, _] if isWithinBounds(o.getKey()) => + val node = RB.getNode(tree, o.getKey()) + if ((node ne null) && Objects.equals(node.getValue(), o.getValue())) { + RB.deleteNode(tree, node) + true + } else { + false + } + case _ => + false + } + + private def isWithinBounds(key: Any): Boolean = + RB.isWithinLowerBound(key, lowerBound, lowerKind) && RB.isWithinUpperBound(key, upperBound, upperKind) + } + + private abstract class AbstractProjection[K, V]( + protected val tree: RB.Tree[K, V], + protected val lowerBound: K, protected val lowerKind: RB.BoundKind, + protected val upperBound: K, protected val upperKind: RB.BoundKind + )( + implicit protected val comp: Comparator[_ >: K]) + extends AbstractMap[K, V] with NavigableMap[K, V] { + + // To be implemented by the two concrete subclasses, depending on the order + + protected def nextNode(key: K, boundKind: RB.BoundKind): RB.Node[K, V] + protected def previousNode(key: K, boundKind: RB.BoundKind): RB.Node[K, V] + + protected def subMapGeneric(newFromKey: K = null.asInstanceOf[K], + newFromBoundKind: RB.BoundKind = RB.NoBound, + newToKey: K = null.asInstanceOf[K], + newToBoundKind: RB.BoundKind = RB.NoBound): NavigableMap[K, V] + + // Implementation of most of the NavigableMap API + + override def size(): Int = + RB.projectionSize(tree, lowerBound, lowerKind, upperBound, upperKind) + + override def isEmpty(): Boolean = + RB.projectionIsEmpty(tree, lowerBound, lowerKind, upperBound, upperKind) + + override def containsKey(key: Any): Boolean = + isWithinBounds(key) && RB.contains(tree, key) + + override def get(key: Any): V = { + if (!isWithinBounds(key)) + null.asInstanceOf[V] + else + RB.get(tree, key) + } + + override def put(key: K, value: V): V = { + if (!isWithinBounds(key)) + throw new IllegalArgumentException + RB.insert(tree, key, value) + } + + override def remove(key: Any): V = { + val oldNode = + if (isWithinBounds(key)) RB.delete(tree, key) + else null + RB.nullableNodeValue(oldNode) + } + + def entrySet(): Set[Map.Entry[K, V]] = + new ProjectedEntrySet(tree, lowerBound, lowerKind, upperBound, upperKind) + + def lowerEntry(key: K): Map.Entry[K, V] = + previousNode(key, RB.ExclusiveBound) + + def lowerKey(key: K): K = + RB.nullableNodeKey(previousNode(key, RB.ExclusiveBound)) + + def floorEntry(key: K): Map.Entry[K, V] = + previousNode(key, RB.InclusiveBound) + + def floorKey(key: K): K = + RB.nullableNodeKey(previousNode(key, RB.InclusiveBound)) + + def ceilingEntry(key: K): Map.Entry[K, V] = + nextNode(key, RB.InclusiveBound) + + def ceilingKey(key: K): K = + RB.nullableNodeKey(nextNode(key, RB.InclusiveBound)) + + def higherEntry(key: K): Map.Entry[K, V] = + nextNode(key, RB.ExclusiveBound) + + def higherKey(key: K): K = + RB.nullableNodeKey(nextNode(key, RB.ExclusiveBound)) + + def firstKey(): K = { + val e = firstEntry() + if (e eq null) + throw new NoSuchElementException + e.getKey() + } + + def lastKey(): K = { + val e = lastEntry() + if (e eq null) + throw new NoSuchElementException + e.getKey() + } + + def subMap(fromKey: K, fromInclusive: Boolean, toKey: K, + toInclusive: Boolean): NavigableMap[K, V] = { + subMapGeneric( + fromKey, RB.boundKindFromIsInclusive(fromInclusive), + toKey, RB.boundKindFromIsInclusive(toInclusive)) + } + + def headMap(toKey: K, inclusive: Boolean): NavigableMap[K, V] = { + subMapGeneric(newToKey = toKey, + newToBoundKind = RB.boundKindFromIsInclusive(inclusive)) + } + + def tailMap(fromKey: K, inclusive: Boolean): NavigableMap[K, V] = { + subMapGeneric(newFromKey = fromKey, + newFromBoundKind = RB.boundKindFromIsInclusive(inclusive)) + } + + def subMap(fromKey: K, toKey: K): SortedMap[K, V] = + subMap(fromKey, true, toKey, false) + + def headMap(toKey: K): SortedMap[K, V] = + headMap(toKey, false) + + def tailMap(fromKey: K): SortedMap[K, V] = + tailMap(fromKey, true) + + // Common implementation of pollFirstEntry() and pollLastEntry() + + @inline + protected final def pollLowerEntry(): Map.Entry[K, V] = { + val node = RB.minNodeAfter(tree, lowerBound, lowerKind) + if (node ne null) { + if (isWithinUpperBound(node.key)) { + RB.deleteNode(tree, node) + node + } else { + null + } + } else { + null + } + } + + @inline + protected final def pollUpperEntry(): Map.Entry[K, V] = { + val node = RB.maxNodeBefore(tree, upperBound, upperKind) + if (node ne null) { + if (isWithinLowerBound(node.key)) { + RB.deleteNode(tree, node) + node + } else { + null + } + } else { + null + } + } + + // Helpers + + protected final def isWithinBounds(key: Any): Boolean = + isWithinLowerBound(key) && isWithinUpperBound(key) + + protected final def isWithinLowerBound(key: Any): Boolean = + RB.isWithinLowerBound(key, lowerBound, lowerKind) + + protected final def isWithinUpperBound(key: Any): Boolean = + RB.isWithinUpperBound(key, upperBound, upperKind) + + protected final def ifWithinLowerBound(node: RB.Node[K, V]): RB.Node[K, V] = + if (node != null && isWithinLowerBound(node.key)) node + else null + + protected final def ifWithinUpperBound(node: RB.Node[K, V]): RB.Node[K, V] = + if (node != null && isWithinUpperBound(node.key)) node + else null + } + + private final class Projection[K, V]( + tree0: RB.Tree[K, V], fromKey0: K, fromBoundKind0: RB.BoundKind, + toKey0: K, toBoundKind0: RB.BoundKind)( + implicit comp: Comparator[_ >: K]) + extends AbstractProjection[K, V](tree0, fromKey0, fromBoundKind0, + toKey0, toBoundKind0) { + + // Access fields under a different name, more appropriate for some uses + + @inline private def fromKey: K = lowerBound + @inline private def fromBoundKind: RB.BoundKind = lowerKind + @inline private def toKey: K = upperBound + @inline private def toBoundKind: RB.BoundKind = upperKind + + /* Implementation of the abstract methods from AbstractProjection + * Some are marked `@inline` for the likely case where + * `DescendingProjection` is not reachable at all and hence + * dead-code-eliminated. + */ + + @inline + protected def nextNode(key: K, boundKind: RB.BoundKind): RB.Node[K, V] = + ifWithinUpperBound(RB.minNodeAfter(tree, key, boundKind)) + + @inline + protected def previousNode(key: K, boundKind: RB.BoundKind): RB.Node[K, V] = + ifWithinLowerBound(RB.maxNodeBefore(tree, key, boundKind)) + + protected def subMapGeneric( + newFromKey: K, newFromBoundKind: RB.BoundKind, + newToKey: K, newToBoundKind: RB.BoundKind): NavigableMap[K, V] = { + val intersectedFromBound = RB.intersectLowerBounds( + new RB.Bound(fromKey, fromBoundKind), + new RB.Bound(newFromKey, newFromBoundKind)) + val intersectedToBound = RB.intersectUpperBounds( + new RB.Bound(toKey, toBoundKind), + new RB.Bound(newToKey, newToBoundKind)) + new Projection(tree, + intersectedFromBound.bound, intersectedFromBound.kind, + intersectedToBound.bound, intersectedToBound.kind) + } + + // Methods of the NavigableMap API that are not implemented in AbstractProjection + + def comparator(): Comparator[_ >: K] = + NaturalComparator.unselect(comp) + + def firstEntry(): Map.Entry[K, V] = + nextNode(fromKey, fromBoundKind) + + def lastEntry(): Map.Entry[K, V] = + previousNode(toKey, toBoundKind) + + @noinline + def pollFirstEntry(): Map.Entry[K, V] = + pollLowerEntry() + + @noinline + def pollLastEntry(): Map.Entry[K, V] = + pollUpperEntry() + + def navigableKeySet(): NavigableSet[K] = { + new TreeSet.Projection(tree, fromKey, fromBoundKind, + toKey, toBoundKind, null.asInstanceOf[V]) + } + + def descendingKeySet(): NavigableSet[K] = { + new TreeSet.DescendingProjection(tree, toKey, toBoundKind, + fromKey, fromBoundKind, null.asInstanceOf[V]) + } + + def descendingMap(): NavigableMap[K, V] = { + new DescendingProjection(tree, toKey, toBoundKind, + fromKey, fromBoundKind) + } + } + + private final class DescendingProjection[K, V]( + tree0: RB.Tree[K, V], fromKey0: K, fromBoundKind0: RB.BoundKind, + toKey0: K, toBoundKind0: RB.BoundKind)( + implicit comp: Comparator[_ >: K]) + extends AbstractProjection[K, V](tree0, toKey0, toBoundKind0, + fromKey0, fromBoundKind0) { + + // Access fields under a different name, more appropriate for some uses + + @inline private def fromKey: K = upperBound + @inline private def fromBoundKind: RB.BoundKind = upperKind + @inline private def toKey: K = lowerBound + @inline private def toBoundKind: RB.BoundKind = lowerKind + + // Implementation of the abstract methods from AbstractProjection + + protected def nextNode(key: K, boundKind: RB.BoundKind): RB.Node[K, V] = + ifWithinLowerBound(RB.maxNodeBefore(tree, key, boundKind)) + + protected def previousNode(key: K, boundKind: RB.BoundKind): RB.Node[K, V] = + ifWithinUpperBound(RB.minNodeAfter(tree, key, boundKind)) + + protected def subMapGeneric( + newFromKey: K, newFromBoundKind: RB.BoundKind, + newToKey: K, newToBoundKind: RB.BoundKind): NavigableMap[K, V] = { + val intersectedFromBound = RB.intersectUpperBounds( + new RB.Bound(fromKey, fromBoundKind), + new RB.Bound(newFromKey, newFromBoundKind)) + val intersectedToBound = RB.intersectLowerBounds( + new RB.Bound(toKey, toBoundKind), + new RB.Bound(newToKey, newToBoundKind)) + new Projection(tree, + intersectedFromBound.bound, intersectedFromBound.kind, + intersectedToBound.bound, intersectedToBound.kind) + } + + // Methods of the NavigableMap API that are not implemented in AbstractProjection + + def comparator(): Comparator[_ >: K] = + Collections.reverseOrder(NaturalComparator.unselect(comp)) + + def firstEntry(): Map.Entry[K, V] = + nextNode(fromKey, fromBoundKind) + + def lastEntry(): Map.Entry[K, V] = + previousNode(toKey, toBoundKind) + + @noinline + def pollFirstEntry(): Map.Entry[K, V] = + pollUpperEntry() + + @noinline + def pollLastEntry(): Map.Entry[K, V] = + pollLowerEntry() + + def navigableKeySet(): NavigableSet[K] = { + new TreeSet.DescendingProjection(tree, fromKey, fromBoundKind, + toKey, toBoundKind, null.asInstanceOf[V]) + } + + def descendingKeySet(): NavigableSet[K] = { + new TreeSet.Projection(tree, toKey, toBoundKind, + fromKey, fromBoundKind, null.asInstanceOf[V]) + } + + def descendingMap(): NavigableMap[K, V] = { + new Projection(tree, toKey, toBoundKind, fromKey, fromBoundKind) + } + } +} diff --git a/javalib/src/main/scala/java/util/TreeSet.scala b/javalib/src/main/scala/java/util/TreeSet.scala index 723e8ed2d4..eec1f08aad 100644 --- a/javalib/src/main/scala/java/util/TreeSet.scala +++ b/javalib/src/main/scala/java/util/TreeSet.scala @@ -12,6 +12,7 @@ package java.util +import java.lang.Cloneable import java.util.{RedBlackTree => RB} class TreeSet[E] private (tree: RB.Tree[E, Any])( @@ -49,7 +50,7 @@ class TreeSet[E] private (tree: RB.Tree[E, Any])( def descendingSet(): NavigableSet[E] = { new DescendingProjection(tree, null.asInstanceOf[E], RB.NoBound, - null.asInstanceOf[E], RB.NoBound) + null.asInstanceOf[E], RB.NoBound, ()) } def size(): Int = @@ -74,19 +75,19 @@ class TreeSet[E] private (tree: RB.Tree[E, Any])( toInclusive: Boolean): NavigableSet[E] = { new Projection(tree, fromElement, RB.boundKindFromIsInclusive(fromInclusive), - toElement, RB.boundKindFromIsInclusive(toInclusive)) + toElement, RB.boundKindFromIsInclusive(toInclusive), ()) } def headSet(toElement: E, inclusive: Boolean): NavigableSet[E] = { new Projection(tree, null.asInstanceOf[E], RB.NoBound, - toElement, RB.boundKindFromIsInclusive(inclusive)) + toElement, RB.boundKindFromIsInclusive(inclusive), ()) } def tailSet(fromElement: E, inclusive: Boolean): NavigableSet[E] = { new Projection(tree, fromElement, RB.boundKindFromIsInclusive(inclusive), - null.asInstanceOf[E], RB.NoBound) + null.asInstanceOf[E], RB.NoBound, ()) } def subSet(fromElement: E, toElement: E): SortedSet[E] = @@ -149,11 +150,13 @@ class TreeSet[E] private (tree: RB.Tree[E, Any])( new TreeSet(tree.treeCopy())(comp) } -private object TreeSet { - private abstract class AbstractProjection[E]( - protected val tree: RB.Tree[E, Any], +private[util] object TreeSet { + private[util] abstract class AbstractProjection[E, V]( + protected val tree: RB.Tree[E, V], protected val lowerBound: E, protected val lowerKind: RB.BoundKind, - protected val upperBound: E, protected val upperKind: RB.BoundKind)( + protected val upperBound: E, protected val upperKind: RB.BoundKind, + private val valueForAdd: V + )( implicit protected val comp: Comparator[_ >: E]) extends AbstractSet[E] with NavigableSet[E] { @@ -179,9 +182,11 @@ private object TreeSet { isWithinBounds(o) && RB.contains(tree, o) override def add(e: E): Boolean = { + if (valueForAdd == null) + throw new UnsupportedOperationException if (!isWithinBounds(e)) throw new IllegalArgumentException - RB.insert(tree, e, ()) == null + RB.insert(tree, e, valueForAdd) == null } override def remove(o: Any): Boolean = @@ -279,12 +284,12 @@ private object TreeSet { else null.asInstanceOf[E] } - private final class Projection[E]( - tree0: RB.Tree[E, Any], fromElement0: E, fromBoundKind0: RB.BoundKind, - toElement0: E, toBoundKind0: RB.BoundKind)( + private[util] final class Projection[E, V]( + tree0: RB.Tree[E, V], fromElement0: E, fromBoundKind0: RB.BoundKind, + toElement0: E, toBoundKind0: RB.BoundKind, valueForAdd: V)( implicit comp: Comparator[_ >: E]) - extends AbstractProjection[E](tree0, fromElement0, fromBoundKind0, - toElement0, toBoundKind0) { + extends AbstractProjection[E, V](tree0, fromElement0, fromBoundKind0, + toElement0, toBoundKind0, valueForAdd) { // Access fields under a different name, more appropriate for some uses @@ -318,7 +323,7 @@ private object TreeSet { new RB.Bound(newToElement, newToBoundKind)) new Projection(tree, intersectedFromBound.bound, intersectedFromBound.kind, - intersectedToBound.bound, intersectedToBound.kind) + intersectedToBound.bound, intersectedToBound.kind, valueForAdd) } // Methods of the NavigableSet API that are not implemented in AbstractProjection @@ -352,18 +357,18 @@ private object TreeSet { pollUpper() def descendingSet(): NavigableSet[E] = - new DescendingProjection(tree, toElement, toBoundKind, fromElement, fromBoundKind) + new DescendingProjection(tree, toElement, toBoundKind, fromElement, fromBoundKind, valueForAdd) def descendingIterator(): Iterator[E] = RB.descendingKeysIterator(tree, toElement, toBoundKind, fromElement, fromBoundKind) } - private final class DescendingProjection[E]( - tree0: RB.Tree[E, Any], fromElement0: E, fromBoundKind0: RB.BoundKind, - toElement0: E, toBoundKind0: RB.BoundKind)( + private[util] final class DescendingProjection[E, V]( + tree0: RB.Tree[E, V], fromElement0: E, fromBoundKind0: RB.BoundKind, + toElement0: E, toBoundKind0: RB.BoundKind, valueForAdd: V)( implicit comp: Comparator[_ >: E]) - extends AbstractProjection[E](tree0, toElement0, toBoundKind0, - fromElement0, fromBoundKind0) { + extends AbstractProjection[E, V](tree0, toElement0, toBoundKind0, + fromElement0, fromBoundKind0, valueForAdd) { // Access fields under a different name, more appropriate for some uses @@ -391,7 +396,7 @@ private object TreeSet { new RB.Bound(newToElement, newToBoundKind)) new Projection(tree, intersectedFromBound.bound, intersectedFromBound.kind, - intersectedToBound.bound, intersectedToBound.kind) + intersectedToBound.bound, intersectedToBound.kind, valueForAdd) } // Methods of the NavigableSet API that are not implemented in AbstractProjection @@ -425,7 +430,7 @@ private object TreeSet { pollLower() def descendingSet(): NavigableSet[E] = - new Projection(tree, toElement, toBoundKind, fromElement, fromBoundKind) + new Projection(tree, toElement, toBoundKind, fromElement, fromBoundKind, valueForAdd) def descendingIterator(): Iterator[E] = RB.projectionKeysIterator(tree, toElement, toBoundKind, fromElement, fromBoundKind) diff --git a/javalib/src/main/scala/java/util/UUID.scala b/javalib/src/main/scala/java/util/UUID.scala index ab08fa3fea..9525cb4955 100644 --- a/javalib/src/main/scala/java/util/UUID.scala +++ b/javalib/src/main/scala/java/util/UUID.scala @@ -12,14 +12,11 @@ package java.util -import java.lang.{Long => JLong} - import scala.scalajs.js final class UUID private ( private val i1: Int, private val i2: Int, - private val i3: Int, private val i4: Int, - private[this] var l1: JLong, private[this] var l2: JLong) + private val i3: Int, private val i4: Int) extends AnyRef with java.io.Serializable with Comparable[UUID] { import UUID._ @@ -40,21 +37,16 @@ final class UUID private ( def this(mostSigBits: Long, leastSigBits: Long) = { this((mostSigBits >>> 32).toInt, mostSigBits.toInt, - (leastSigBits >>> 32).toInt, leastSigBits.toInt, - JLong.valueOf(mostSigBits), JLong.valueOf(leastSigBits)) + (leastSigBits >>> 32).toInt, leastSigBits.toInt) } - def getLeastSignificantBits(): Long = { - if (l2 eq null) - l2 = JLong.valueOf((i3.toLong << 32) | (i4.toLong & 0xffffffffL)) - l2.longValue - } + @inline + def getLeastSignificantBits(): Long = + (i3.toLong << 32) | (i4.toLong & 0xffffffffL) - def getMostSignificantBits(): Long = { - if (l1 eq null) - l1 = JLong.valueOf((i1.toLong << 32) | (i2.toLong & 0xffffffffL)) - l1.longValue - } + @inline + def getMostSignificantBits(): Long = + (i1.toLong << 32) | (i2.toLong & 0xffffffffL) def version(): Int = (i2 & 0xf000) >> 12 @@ -116,16 +108,21 @@ final class UUID private ( } def compareTo(that: UUID): Int = { - if (this.i1 != that.i1) { - if (this.i1 > that.i1) 1 else -1 - } else if (this.i2 != that.i2) { - if (this.i2 > that.i2) 1 else -1 - } else if (this.i3 != that.i3) { - if (this.i3 > that.i3) 1 else -1 - } else if (this.i4 != that.i4) { - if (this.i4 > that.i4) 1 else -1 + // See #4882 and the test `UUIDTest.compareTo()` for context + val thisHi = this.getMostSignificantBits() + val thatHi = that.getMostSignificantBits() + if (thisHi != thatHi) { + if (thisHi < thatHi) -1 + else 1 } else { - 0 + val thisLo = this.getLeastSignificantBits() + val thatLo = that.getLeastSignificantBits() + if (thisLo != thatLo) { + if (thisLo < thatLo) -1 + else 1 + } else { + 0 + } } } } @@ -156,7 +153,7 @@ object UUID { val i2 = (intFromBuffer(4) & ~0x0000f000) | 0x00004000 val i3 = (intFromBuffer(8) & ~0xc0000000) | 0x80000000 val i4 = intFromBuffer(12) - new UUID(i1, i2, i3, i4, null, null) + new UUID(i1, i2, i3, i4) } // Not implemented (requires messing with MD5 or SHA-1): @@ -180,7 +177,7 @@ object UUID { val i2 = parseHex8(name.substring(9, 13), name.substring(14, 18)) val i3 = parseHex8(name.substring(19, 23), name.substring(24, 28)) val i4 = parseHex8(name.substring(28, 32), name.substring(32, 36)) - new UUID(i1, i2, i3, i4, null, null) + new UUID(i1, i2, i3, i4) } catch { case _: NumberFormatException => fail() } diff --git a/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala b/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala index 29847a2acb..49c5ac683e 100644 --- a/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala +++ b/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala @@ -12,6 +12,8 @@ package java.util.concurrent +import java.util.function.{BiConsumer, Consumer} + import java.io.Serializable import java.util._ @@ -72,6 +74,22 @@ class ConcurrentHashMap[K, V] private (initialCapacity: Int, loadFactor: Float) new ConcurrentHashMap.KeySetView[K, V](this.inner, mappedValue) } + def forEach(parallelismThreshold: Long, action: BiConsumer[_ >: K, _ >: V]): Unit = { + // Note: It is tempting to simply call inner.forEach here: + // However, this will not have the correct snapshotting behavior. + val i = inner.nodeIterator() + while (i.hasNext()) { + val n = i.next() + action.accept(n.key, n.value) + } + } + + def forEachKey(parallelismThreshold: Long, action: Consumer[_ >: K]): Unit = + inner.keyIterator().forEachRemaining(action) + + def forEachValue(parallelismThreshold: Long, action: Consumer[_ >: V]): Unit = + inner.valueIterator().forEachRemaining(action) + override def values(): Collection[V] = inner.values() diff --git a/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala b/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala index 434b555385..5aee358012 100644 --- a/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala +++ b/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala @@ -12,6 +12,7 @@ package java.util.concurrent +import java.lang.Cloneable import java.util._ class ConcurrentSkipListSet[E] private (inner: TreeSet[E]) diff --git a/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala b/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala index e807eef46c..fb8cb030a5 100644 --- a/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala +++ b/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala @@ -12,6 +12,8 @@ package java.util.concurrent +import java.lang.Cloneable +import java.lang.Utils._ import java.lang.{reflect => jlr} import java.util._ import java.util.function.{Predicate, UnaryOperator} @@ -46,13 +48,13 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) } def size(): Int = - inner.size + inner.length def isEmpty(): Boolean = size() == 0 def contains(o: scala.Any): Boolean = - iterator.scalaOps.exists(Objects.equals(o, _)) + iterator().scalaOps.exists(Objects.equals(o, _)) def indexOf(o: scala.Any): Int = indexOf(o.asInstanceOf[E], 0) @@ -81,13 +83,13 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) def toArray(): Array[AnyRef] = toArray(new Array[AnyRef](size())) - def toArray[T](a: Array[T]): Array[T] = { - val componentType = a.getClass.getComponentType + def toArray[T <: AnyRef](a: Array[T]): Array[T] = { + val componentType = a.getClass().getComponentType() val toFill: Array[T] = if (a.length >= size()) a else jlr.Array.newInstance(componentType, size()).asInstanceOf[Array[T]] - val iter = iterator + val iter = iterator() for (i <- 0 until size()) toFill(i) = iter.next().asInstanceOf[T] if (toFill.length > size()) @@ -143,7 +145,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) } def containsAll(c: Collection[_]): Boolean = - c.iterator.scalaOps.forall(this.contains(_)) + c.iterator().scalaOps.forall(this.contains(_)) def removeAll(c: Collection[_]): Boolean = { copyIfNeeded() @@ -290,7 +292,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) } protected def innerRemove(index: Int): E = - inner.splice(index, 1)(0) + arrayRemoveAndGet(inner, index) protected def innerRemoveMany(index: Int, count: Int): Unit = inner.splice(index, count) diff --git a/javalib/src/main/scala/java/util/concurrent/Flow.scala b/javalib/src/main/scala/java/util/concurrent/Flow.scala new file mode 100644 index 0000000000..77bca32d80 --- /dev/null +++ b/javalib/src/main/scala/java/util/concurrent/Flow.scala @@ -0,0 +1,38 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util.concurrent + +object Flow { + + @inline def defaultBufferSize(): Int = 256 + + trait Processor[T, R] extends Subscriber[T] with Publisher[R] + + @FunctionalInterface + trait Publisher[T] { + def subscribe(subscriber: Subscriber[_ >: T]): Unit + } + + trait Subscriber[T] { + def onSubscribe(subscription: Subscription): Unit + def onNext(item: T): Unit + def onError(throwable: Throwable): Unit + def onComplete(): Unit + } + + trait Subscription { + def request(n: Long): Unit + def cancel(): Unit + } + +} diff --git a/javalib/src/main/scala/java/util/concurrent/ThreadLocalRandom.scala b/javalib/src/main/scala/java/util/concurrent/ThreadLocalRandom.scala index ce6ab96c6e..ed5ce571a1 100644 --- a/javalib/src/main/scala/java/util/concurrent/ThreadLocalRandom.scala +++ b/javalib/src/main/scala/java/util/concurrent/ThreadLocalRandom.scala @@ -9,7 +9,6 @@ package java.util.concurrent import java.util.Random -import scala.annotation.tailrec class ThreadLocalRandom extends Random { @@ -22,98 +21,6 @@ class ThreadLocalRandom extends Random { super.setSeed(seed) } - - def nextInt(least: Int, bound: Int): Int = { - if (least >= bound) - throw new IllegalArgumentException() - - val difference = bound - least - if (difference > 0) { - nextInt(difference) + least - } else { - /* The interval size here is greater than Int.MaxValue, - * so the loop will exit with a probability of at least 1/2. - */ - @tailrec - def loop(): Int = { - val n = nextInt() - if (n >= least && n < bound) n - else loop() - } - - loop() - } - } - - def nextLong(_n: Long): Long = { - if (_n <= 0) - throw new IllegalArgumentException("n must be positive") - - /* - * Divide n by two until small enough for nextInt. On each - * iteration (at most 31 of them but usually much less), - * randomly choose both whether to include high bit in result - * (offset) and whether to continue with the lower vs upper - * half (which makes a difference only if odd). - */ - - var offset = 0L - var n = _n - - while (n >= Integer.MAX_VALUE) { - val bits = next(2) - val halfn = n >>> 1 - val nextn = - if ((bits & 2) == 0) halfn - else n - halfn - if ((bits & 1) == 0) - offset += n - nextn - n = nextn - } - offset + nextInt(n.toInt) - } - - def nextLong(least: Long, bound: Long): Long = { - if (least >= bound) - throw new IllegalArgumentException() - - val difference = bound - least - if (difference > 0) { - nextLong(difference) + least - } else { - /* The interval size here is greater than Long.MaxValue, - * so the loop will exit with a probability of at least 1/2. - */ - @tailrec - def loop(): Long = { - val n = nextLong() - if (n >= least && n < bound) n - else loop() - } - - loop() - } - } - - def nextDouble(n: Double): Double = { - if (n <= 0) - throw new IllegalArgumentException("n must be positive") - - nextDouble() * n - } - - def nextDouble(least: Double, bound: Double): Double = { - if (least >= bound) - throw new IllegalArgumentException() - - /* Based on documentation for Random.doubles to avoid issue #2144 and other - * possible rounding up issues: - * https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#doubles-double-double- - */ - val next = nextDouble() * (bound - least) + least - if (next < bound) next - else Math.nextAfter(bound, Double.NegativeInfinity) - } } object ThreadLocalRandom { diff --git a/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala b/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala index e1054d84a1..c308bf1b97 100644 --- a/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala +++ b/javalib/src/main/scala/java/util/concurrent/TimeUnit.scala @@ -134,7 +134,7 @@ object TimeUnit { var i = 0 while (i != len) { val value = values(i) - if (value.name == name) + if (value.name() == name) return value i += 1 } diff --git a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala index 0622ba8e10..9aaca49c6b 100644 --- a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala +++ b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala @@ -12,6 +12,9 @@ package java.util.concurrent.atomic +import java.util.function.IntBinaryOperator +import java.util.function.IntUnaryOperator + class AtomicInteger(private[this] var value: Int) extends Number with Serializable { @@ -65,6 +68,30 @@ class AtomicInteger(private[this] var value: Int) newValue } + final def getAndUpdate(updateFunction: IntUnaryOperator): Int = { + val old = value + value = updateFunction.applyAsInt(old) + old + } + + final def updateAndGet(updateFunction: IntUnaryOperator): Int = { + val old = value + value = updateFunction.applyAsInt(old) + value + } + + final def getAndAccumulate(x: Int, accumulatorFunction: IntBinaryOperator): Int = { + val old = value + value = accumulatorFunction.applyAsInt(old, x) + old + } + + final def accumulateAndGet(x: Int, accumulatorFunction: IntBinaryOperator): Int = { + val old = value + value = accumulatorFunction.applyAsInt(old, x) + value + } + override def toString(): String = value.toString() diff --git a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala index 01c4c0b98b..d58dcb4d26 100644 --- a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala +++ b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala @@ -12,6 +12,9 @@ package java.util.concurrent.atomic +import java.util.function.LongBinaryOperator +import java.util.function.LongUnaryOperator + class AtomicLong(private[this] var value: Long) extends Number with Serializable { def this() = this(0L) @@ -63,6 +66,30 @@ class AtomicLong(private[this] var value: Long) extends Number with Serializable newValue } + final def getAndUpdate(updateFunction: LongUnaryOperator): Long = { + val old = value + value = updateFunction.applyAsLong(old) + old + } + + final def updateAndGet(updateFunction: LongUnaryOperator): Long = { + val old = value + value = updateFunction.applyAsLong(old) + value + } + + final def getAndAccumulate(x: Long, accumulatorFunction: LongBinaryOperator): Long = { + val old = value + value = accumulatorFunction.applyAsLong(old, x) + old + } + + final def accumulateAndGet(x: Long, accumulatorFunction: LongBinaryOperator): Long = { + val old = value + value = accumulatorFunction.applyAsLong(old, x) + value + } + override def toString(): String = value.toString() diff --git a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala index 9a025f4cd5..b3cb37ddfe 100644 --- a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala +++ b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicReference.scala @@ -12,6 +12,9 @@ package java.util.concurrent.atomic +import java.util.function.BinaryOperator +import java.util.function.UnaryOperator + class AtomicReference[T <: AnyRef]( private[this] var value: T) extends Serializable { @@ -41,6 +44,30 @@ class AtomicReference[T <: AnyRef]( old } + final def getAndUpdate(updateFunction: UnaryOperator[T]): T = { + val old = value + value = updateFunction.apply(old) + old + } + + final def updateAndGet(updateFunction: UnaryOperator[T]): T = { + val old = value + value = updateFunction.apply(old) + value + } + + final def getAndAccumulate(x: T, accumulatorFunction: BinaryOperator[T]): T = { + val old = value + value = accumulatorFunction.apply(old, x) + old + } + + final def accumulateAndGet(x: T, accumulatorFunction: BinaryOperator[T]): T = { + val old = value + value = accumulatorFunction.apply(old, x) + value + } + override def toString(): String = String.valueOf(value) } diff --git a/javalib/src/main/scala/java/util/function/BiConsumer.scala b/javalib/src/main/scala/java/util/function/BiConsumer.scala index d39a9a3222..ce30ef7046 100644 --- a/javalib/src/main/scala/java/util/function/BiConsumer.scala +++ b/javalib/src/main/scala/java/util/function/BiConsumer.scala @@ -12,12 +12,9 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - trait BiConsumer[T, U] { def accept(t: T, u: U): Unit - @JavaDefaultMethod def andThen(after: BiConsumer[T, U]): BiConsumer[T, U] = { (t: T, u: U) => accept(t, u) after.accept(t, u) diff --git a/javalib/src/main/scala/java/util/function/BiFunction.scala b/javalib/src/main/scala/java/util/function/BiFunction.scala index c6f1f75a8a..95dcda75bf 100644 --- a/javalib/src/main/scala/java/util/function/BiFunction.scala +++ b/javalib/src/main/scala/java/util/function/BiFunction.scala @@ -12,12 +12,9 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - trait BiFunction[T, U, R] { def apply(t: T, u: U): R - @JavaDefaultMethod def andThen[V](after: Function[_ >: R, _ <: V]): BiFunction[T, U, V] = { (t: T, u: U) => after.apply(this.apply(t, u)) } diff --git a/javalib/src/main/scala/java/util/function/BiPredicate.scala b/javalib/src/main/scala/java/util/function/BiPredicate.scala index 24e9610343..2e34d6617d 100644 --- a/javalib/src/main/scala/java/util/function/BiPredicate.scala +++ b/javalib/src/main/scala/java/util/function/BiPredicate.scala @@ -12,20 +12,15 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - trait BiPredicate[T, U] { def test(t: T, u: U): Boolean - @JavaDefaultMethod def and(other: BiPredicate[_ >: T, _ >: U]): BiPredicate[T, U] = { (t: T, u: U) => test(t, u) && other.test(t, u) } - @JavaDefaultMethod def negate(): BiPredicate[T, U] = (t: T, u: U) => !test(t, u) - @JavaDefaultMethod def or(other: BiPredicate[_ >: T, _ >: U]): BiPredicate[T, U] = { (t: T, u: U) => test(t, u) || other.test(t, u) } diff --git a/javalib/src/main/scala/java/util/function/Consumer.scala b/javalib/src/main/scala/java/util/function/Consumer.scala index e978992b91..2df930c639 100644 --- a/javalib/src/main/scala/java/util/function/Consumer.scala +++ b/javalib/src/main/scala/java/util/function/Consumer.scala @@ -12,13 +12,10 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - @FunctionalInterface trait Consumer[T] { self => def accept(t: T): Unit - @JavaDefaultMethod def andThen(after: Consumer[_ >: T]): Consumer[T] = { new Consumer[T] { def accept(t: T): Unit = { diff --git a/javalib/src/main/scala/java/util/function/DoubleConsumer.scala b/javalib/src/main/scala/java/util/function/DoubleConsumer.scala index 32efd4d086..8184c13119 100644 --- a/javalib/src/main/scala/java/util/function/DoubleConsumer.scala +++ b/javalib/src/main/scala/java/util/function/DoubleConsumer.scala @@ -12,13 +12,10 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - @FunctionalInterface trait DoubleConsumer { def accept(value: Double): Unit - @JavaDefaultMethod def andThen(after: DoubleConsumer): DoubleConsumer = { (value: Double) => this.accept(value) after.accept(value) diff --git a/javalib/src/main/scala/java/util/function/DoublePredicate.scala b/javalib/src/main/scala/java/util/function/DoublePredicate.scala index b327f1ce2d..fb4c986cf9 100755 --- a/javalib/src/main/scala/java/util/function/DoublePredicate.scala +++ b/javalib/src/main/scala/java/util/function/DoublePredicate.scala @@ -12,13 +12,10 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - @FunctionalInterface trait DoublePredicate { self => def test(t: Double): Boolean - @JavaDefaultMethod def and(other: DoublePredicate): DoublePredicate = { new DoublePredicate { def test(value: Double): Boolean = @@ -26,7 +23,6 @@ trait DoublePredicate { self => } } - @JavaDefaultMethod def negate(): DoublePredicate = { new DoublePredicate { def test(value: Double): Boolean = @@ -34,7 +30,6 @@ trait DoublePredicate { self => } } - @JavaDefaultMethod def or(other: DoublePredicate): DoublePredicate = { new DoublePredicate { def test(value: Double): Boolean = diff --git a/javalib/src/main/scala/java/util/function/DoubleUnaryOperator.scala b/javalib/src/main/scala/java/util/function/DoubleUnaryOperator.scala index 53efb491f4..038c40a1e3 100644 --- a/javalib/src/main/scala/java/util/function/DoubleUnaryOperator.scala +++ b/javalib/src/main/scala/java/util/function/DoubleUnaryOperator.scala @@ -12,18 +12,14 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - @FunctionalInterface trait DoubleUnaryOperator { def applyAsDouble(operand: Double): Double - @JavaDefaultMethod def andThen(after: DoubleUnaryOperator): DoubleUnaryOperator = { (d: Double) => after.applyAsDouble(applyAsDouble(d)) } - @JavaDefaultMethod def compose(before: DoubleUnaryOperator): DoubleUnaryOperator = { (d: Double) => applyAsDouble(before.applyAsDouble(d)) } diff --git a/javalib/src/main/scala/java/util/function/Function.scala b/javalib/src/main/scala/java/util/function/Function.scala index f01c853527..6058a971dc 100644 --- a/javalib/src/main/scala/java/util/function/Function.scala +++ b/javalib/src/main/scala/java/util/function/Function.scala @@ -12,17 +12,13 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - trait Function[T, R] { def apply(t: T): R - @JavaDefaultMethod def andThen[V](after: Function[_ >: R, _ <: V]): Function[T, V] = { (t: T) => after.apply(apply(t)) } - @JavaDefaultMethod def compose[V](before: Function[_ >: V, _ <: T]): Function[V, R] = { (v: V) => apply(before.apply(v)) } diff --git a/javalib/src/main/scala/java/util/function/IntConsumer.scala b/javalib/src/main/scala/java/util/function/IntConsumer.scala index 5e54e7a101..023c191f6b 100644 --- a/javalib/src/main/scala/java/util/function/IntConsumer.scala +++ b/javalib/src/main/scala/java/util/function/IntConsumer.scala @@ -12,13 +12,10 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - @FunctionalInterface trait IntConsumer { def accept(value: Int): Unit - @JavaDefaultMethod def andThen(after: IntConsumer): IntConsumer = { (value: Int) => this.accept(value) after.accept(value) diff --git a/javalib/src/main/scala/java/util/function/IntPredicate.scala b/javalib/src/main/scala/java/util/function/IntPredicate.scala index c6cfc6c7fc..ed29e78459 100755 --- a/javalib/src/main/scala/java/util/function/IntPredicate.scala +++ b/javalib/src/main/scala/java/util/function/IntPredicate.scala @@ -12,13 +12,10 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - @FunctionalInterface trait IntPredicate { self => def test(t: Int): Boolean - @JavaDefaultMethod def and(other: IntPredicate): IntPredicate = { new IntPredicate { def test(value: Int): Boolean = @@ -26,7 +23,6 @@ trait IntPredicate { self => } } - @JavaDefaultMethod def negate(): IntPredicate = { new IntPredicate { def test(value: Int): Boolean = @@ -34,7 +30,6 @@ trait IntPredicate { self => } } - @JavaDefaultMethod def or(other: IntPredicate): IntPredicate = { new IntPredicate { def test(value: Int): Boolean = diff --git a/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala b/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala index 826ab9fc37..89297429c7 100644 --- a/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala +++ b/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala @@ -12,18 +12,14 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - @FunctionalInterface trait IntUnaryOperator { def applyAsInt(operand: Int): Int - @JavaDefaultMethod def andThen(after: IntUnaryOperator): IntUnaryOperator = { (i: Int) => after.applyAsInt(applyAsInt(i)) } - @JavaDefaultMethod def compose(before: IntUnaryOperator): IntUnaryOperator = { (i: Int) => applyAsInt(before.applyAsInt(i)) } diff --git a/javalib/src/main/scala/java/util/function/LongConsumer.scala b/javalib/src/main/scala/java/util/function/LongConsumer.scala index 4603d80376..a8a904246b 100644 --- a/javalib/src/main/scala/java/util/function/LongConsumer.scala +++ b/javalib/src/main/scala/java/util/function/LongConsumer.scala @@ -12,13 +12,10 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - @FunctionalInterface trait LongConsumer { def accept(value: Long): Unit - @JavaDefaultMethod def andThen(after: LongConsumer): LongConsumer = { (value: Long) => this.accept(value) after.accept(value) diff --git a/javalib/src/main/scala/java/util/function/LongPredicate.scala b/javalib/src/main/scala/java/util/function/LongPredicate.scala index 0b3693e6fc..a2de7a58ba 100755 --- a/javalib/src/main/scala/java/util/function/LongPredicate.scala +++ b/javalib/src/main/scala/java/util/function/LongPredicate.scala @@ -12,13 +12,10 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - @FunctionalInterface trait LongPredicate { self => def test(t: Long): Boolean - @JavaDefaultMethod def and(other: LongPredicate): LongPredicate = { new LongPredicate { def test(value: Long): Boolean = @@ -26,7 +23,6 @@ trait LongPredicate { self => } } - @JavaDefaultMethod def negate(): LongPredicate = { new LongPredicate { def test(value: Long): Boolean = @@ -34,7 +30,6 @@ trait LongPredicate { self => } } - @JavaDefaultMethod def or(other: LongPredicate): LongPredicate = { new LongPredicate { def test(value: Long): Boolean = diff --git a/javalib/src/main/scala/java/util/function/LongUnaryOperator.scala b/javalib/src/main/scala/java/util/function/LongUnaryOperator.scala index 0b84f242d9..c326b872a8 100644 --- a/javalib/src/main/scala/java/util/function/LongUnaryOperator.scala +++ b/javalib/src/main/scala/java/util/function/LongUnaryOperator.scala @@ -12,18 +12,14 @@ package java.util.function -import scala.scalajs.js.annotation.JavaDefaultMethod - @FunctionalInterface trait LongUnaryOperator { def applyAsLong(operand: Long): Long - @JavaDefaultMethod def andThen(after: LongUnaryOperator): LongUnaryOperator = { (l: Long) => after.applyAsLong(applyAsLong(l)) } - @JavaDefaultMethod def compose(before: LongUnaryOperator): LongUnaryOperator = { (l: Long) => applyAsLong(before.applyAsLong(l)) } diff --git a/javalib/src/main/scala/java/util/function/Predicate.scala b/javalib/src/main/scala/java/util/function/Predicate.scala index 4862e87927..70a2c9404f 100644 --- a/javalib/src/main/scala/java/util/function/Predicate.scala +++ b/javalib/src/main/scala/java/util/function/Predicate.scala @@ -14,13 +14,10 @@ package java.util.function import java.{util => ju} -import scala.scalajs.js.annotation.JavaDefaultMethod - @FunctionalInterface trait Predicate[T] { self => def test(t: T): Boolean - @JavaDefaultMethod def and(other: Predicate[_ >: T]): Predicate[T] = { new Predicate[T] { def test(t: T): Boolean = @@ -28,7 +25,6 @@ trait Predicate[T] { self => } } - @JavaDefaultMethod def negate(): Predicate[T] = { new Predicate[T] { def test(t: T): Boolean = @@ -36,7 +32,6 @@ trait Predicate[T] { self => } } - @JavaDefaultMethod def or(other: Predicate[_ >: T]): Predicate[T] = { new Predicate[T] { def test(t: T): Boolean = diff --git a/javalib/src/main/scala/java/util/internal/GenericArrayOps.scala b/javalib/src/main/scala/java/util/internal/GenericArrayOps.scala new file mode 100644 index 0000000000..e25c786845 --- /dev/null +++ b/javalib/src/main/scala/java/util/internal/GenericArrayOps.scala @@ -0,0 +1,146 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util.internal + +import java.lang.{reflect => jlr} +import java.util.Comparator + +/** Typeclasses for generic operations on `Array`s. */ +object GenericArrayOps { + + /** A typeclass for operations to manipulate existing arrays. */ + sealed trait ArrayOps[A] { + def length(a: Array[A]): Int + def get(a: Array[A], i: Int): A + def set(a: Array[A], i: Int, v: A): Unit + } + + /** A typeclass for the ability to create arrays of a given type. */ + sealed trait ArrayCreateOps[A] { + def create(length: Int): Array[A] + } + + // ArrayOps and ArrayCreateOps instances for reference types + + private object ReusableAnyRefArrayOps extends ArrayOps[AnyRef] { + @inline def length(a: Array[AnyRef]): Int = a.length + @inline def get(a: Array[AnyRef], i: Int): AnyRef = a(i) + @inline def set(a: Array[AnyRef], i: Int, v: AnyRef): Unit = a(i) = v + } + + @inline + implicit def specificAnyRefArrayOps[A <: AnyRef]: ArrayOps[A] = + ReusableAnyRefArrayOps.asInstanceOf[ArrayOps[A]] + + @inline + final class ClassArrayOps[A <: AnyRef](clazz: Class[_ <: Array[A]]) + extends ArrayCreateOps[A] { + @inline def create(length: Int): Array[A] = + createArrayOfClass(clazz, length) + } + + @inline + final class TemplateArrayOps[A <: AnyRef](template: Array[A]) + extends ArrayCreateOps[A] { + @inline def create(length: Int): Array[A] = + createArrayOfClass(template.getClass(), length) + } + + @inline + def createArrayOfClass[A <: AnyRef](clazz: Class[_ <: Array[A]], length: Int): Array[A] = + jlr.Array.newInstance(clazz.getComponentType(), length).asInstanceOf[Array[A]] + + implicit object AnyRefArrayCreateOps extends ArrayCreateOps[AnyRef] { + @inline def create(length: Int): Array[AnyRef] = new Array[AnyRef](length) + } + + /* ArrayOps and ArrayCreateOps instances for primitive types. + * + * With the exception of the one for Boolean, they also implement + * `java.util.Comparator` for the same element type. In a perfect design, we + * would define separate objects for that, but it would result in more + * generated code for no good reason. + */ + + implicit object BooleanArrayOps + extends ArrayOps[Boolean] with ArrayCreateOps[Boolean] { + @inline def length(a: Array[Boolean]): Int = a.length + @inline def get(a: Array[Boolean], i: Int): Boolean = a(i) + @inline def set(a: Array[Boolean], i: Int, v: Boolean): Unit = a(i) = v + @inline def create(length: Int): Array[Boolean] = new Array[Boolean](length) + } + + implicit object CharArrayOps + extends ArrayOps[Char] with ArrayCreateOps[Char] with Comparator[Char] { + @inline def length(a: Array[Char]): Int = a.length + @inline def get(a: Array[Char], i: Int): Char = a(i) + @inline def set(a: Array[Char], i: Int, v: Char): Unit = a(i) = v + @inline def create(length: Int): Array[Char] = new Array[Char](length) + @inline def compare(x: Char, y: Char): Int = java.lang.Character.compare(x, y) + } + + implicit object ByteArrayOps + extends ArrayOps[Byte] with ArrayCreateOps[Byte] with Comparator[Byte] { + @inline def length(a: Array[Byte]): Int = a.length + @inline def get(a: Array[Byte], i: Int): Byte = a(i) + @inline def set(a: Array[Byte], i: Int, v: Byte): Unit = a(i) = v + @inline def create(length: Int): Array[Byte] = new Array[Byte](length) + @inline def compare(x: Byte, y: Byte): Int = java.lang.Byte.compare(x, y) + } + + implicit object ShortArrayOps + extends ArrayOps[Short] with ArrayCreateOps[Short] with Comparator[Short] { + @inline def length(a: Array[Short]): Int = a.length + @inline def get(a: Array[Short], i: Int): Short = a(i) + @inline def set(a: Array[Short], i: Int, v: Short): Unit = a(i) = v + @inline def create(length: Int): Array[Short] = new Array[Short](length) + @inline def compare(x: Short, y: Short): Int = java.lang.Short.compare(x, y) + } + + implicit object IntArrayOps + extends ArrayOps[Int] with ArrayCreateOps[Int] with Comparator[Int] { + @inline def length(a: Array[Int]): Int = a.length + @inline def get(a: Array[Int], i: Int): Int = a(i) + @inline def set(a: Array[Int], i: Int, v: Int): Unit = a(i) = v + @inline def create(length: Int): Array[Int] = new Array[Int](length) + @inline def compare(x: Int, y: Int): Int = java.lang.Integer.compare(x, y) + } + + implicit object LongArrayOps + extends ArrayOps[Long] with ArrayCreateOps[Long] with Comparator[Long] { + @inline def length(a: Array[Long]): Int = a.length + @inline def get(a: Array[Long], i: Int): Long = a(i) + @inline def set(a: Array[Long], i: Int, v: Long): Unit = a(i) = v + @inline def create(length: Int): Array[Long] = new Array[Long](length) + @inline def compare(x: Long, y: Long): Int = java.lang.Long.compare(x, y) + } + + implicit object FloatArrayOps + extends ArrayOps[Float] with ArrayCreateOps[Float] with Comparator[Float] { + @inline def length(a: Array[Float]): Int = a.length + @inline def get(a: Array[Float], i: Int): Float = a(i) + @inline def set(a: Array[Float], i: Int, v: Float): Unit = a(i) = v + @inline def create(length: Int): Array[Float] = new Array[Float](length) + @inline def compare(x: Float, y: Float): Int = java.lang.Float.compare(x, y) + } + + implicit object DoubleArrayOps + extends ArrayOps[Double] with ArrayCreateOps[Double] with Comparator[Double] { + @inline def length(a: Array[Double]): Int = a.length + @inline def get(a: Array[Double], i: Int): Double = a(i) + @inline def set(a: Array[Double], i: Int, v: Double): Unit = a(i) = v + @inline def create(length: Int): Array[Double] = new Array[Double](length) + @inline def compare(x: Double, y: Double): Int = java.lang.Double.compare(x, y) + } + +} diff --git a/javalib/src/main/scala/java/util/internal/MurmurHash3.scala b/javalib/src/main/scala/java/util/internal/MurmurHash3.scala new file mode 100644 index 0000000000..bcf438f131 --- /dev/null +++ b/javalib/src/main/scala/java/util/internal/MurmurHash3.scala @@ -0,0 +1,61 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util.internal + +import java.lang.Integer.{rotateLeft => rotl} + +/** Primitives to implement MurmurHash3 hashes in data structures. + * + * This is copy of parts of `scala.util.hashing.MurmurHash3`. + */ +private[java] object MurmurHash3 { + /** Mix in a block of data into an intermediate hash value. */ + final def mix(hash: Int, data: Int): Int = { + var h = mixLast(hash, data) + h = rotl(h, 13) + h * 5 + 0xe6546b64 + } + + /** May optionally be used as the last mixing step. + * + * Is a little bit faster than mix, as it does no further mixing of the + * resulting hash. For the last element this is not necessary as the hash is + * thoroughly mixed during finalization anyway. + */ + final def mixLast(hash: Int, data: Int): Int = { + var k = data + + k *= 0xcc9e2d51 + k = rotl(k, 15) + k *= 0x1b873593 + + hash ^ k + } + + /** Finalize a hash to incorporate the length and make sure all bits avalanche. */ + @noinline final def finalizeHash(hash: Int, length: Int): Int = + avalanche(hash ^ length) + + /** Force all bits of the hash to avalanche. Used for finalizing the hash. */ + @inline private final def avalanche(hash: Int): Int = { + var h = hash + + h ^= h >>> 16 + h *= 0x85ebca6b + h ^= h >>> 13 + h *= 0xc2b2ae35 + h ^= h >>> 16 + + h + } +} diff --git a/javalib/src/main/scala/java/util/internal/RefTypes.scala b/javalib/src/main/scala/java/util/internal/RefTypes.scala new file mode 100644 index 0000000000..d02cf33d8d --- /dev/null +++ b/javalib/src/main/scala/java/util/internal/RefTypes.scala @@ -0,0 +1,94 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util.internal + +@inline +private[java] class BooleanRef(var elem: Boolean) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object BooleanRef { + def create(elem: Boolean): BooleanRef = new BooleanRef(elem) + def zero(): BooleanRef = new BooleanRef(false) +} + +@inline +private[java] class CharRef(var elem: Char) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object CharRef { + def create(elem: Char): CharRef = new CharRef(elem) + def zero(): CharRef = new CharRef(0.toChar) +} + +@inline +private[java] class ByteRef(var elem: Byte) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object ByteRef { + def create(elem: Byte): ByteRef = new ByteRef(elem) + def zero(): ByteRef = new ByteRef(0) +} + +@inline +private[java] class ShortRef(var elem: Short) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object ShortRef { + def create(elem: Short): ShortRef = new ShortRef(elem) + def zero(): ShortRef = new ShortRef(0) +} + +@inline +private[java] class IntRef(var elem: Int) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object IntRef { + def create(elem: Int): IntRef = new IntRef(elem) + def zero(): IntRef = new IntRef(0) +} + +@inline +private[java] class LongRef(var elem: Long) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object LongRef { + def create(elem: Long): LongRef = new LongRef(elem) + def zero(): LongRef = new LongRef(0) +} + +@inline +private[java] class FloatRef(var elem: Float) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object FloatRef { + def create(elem: Float): FloatRef = new FloatRef(elem) + def zero(): FloatRef = new FloatRef(0) +} + +@inline +private[java] class DoubleRef(var elem: Double) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object DoubleRef { + def create(elem: Double): DoubleRef = new DoubleRef(elem) + def zero(): DoubleRef = new DoubleRef(0) +} + +@inline +private[java] class ObjectRef[A](var elem: A) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object ObjectRef { + def create[A](elem: A): ObjectRef[A] = new ObjectRef(elem) + def zero(): ObjectRef[Object] = new ObjectRef(null) +} diff --git a/javalib/src/main/scala/java/util/internal/Tuples.scala b/javalib/src/main/scala/java/util/internal/Tuples.scala new file mode 100644 index 0000000000..d476cd74a9 --- /dev/null +++ b/javalib/src/main/scala/java/util/internal/Tuples.scala @@ -0,0 +1,26 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util.internal + +@inline +final class Tuple2[+T1, +T2](val _1: T1, val _2: T2) + +@inline +final class Tuple3[+T1, +T2, +T3](val _1: T1, val _2: T2, val _3: T3) + +@inline +final class Tuple4[+T1, +T2, +T3, +T4](val _1: T1, val _2: T2, val _3: T3, val _4: T4) + +@inline +final class Tuple8[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8]( + val _1: T1, val _2: T2, val _3: T3, val _4: T4, val _5: T5, val _6: T6, val _7: T7, val _8: T8) diff --git a/javalib/src/main/scala/java/util/random/RandomGenerator.scala b/javalib/src/main/scala/java/util/random/RandomGenerator.scala new file mode 100644 index 0000000000..ddb38b0469 --- /dev/null +++ b/javalib/src/main/scala/java/util/random/RandomGenerator.scala @@ -0,0 +1,335 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util.random + +import scala.annotation.tailrec + +import java.util.ScalaOps._ + +trait RandomGenerator { + // Comments starting with `// >` are cited from the JavaDoc. + + // Not implemented: all the methods using Streams + + // Not implemented, because + // > The default implementation checks for the @Deprecated annotation. + // def isDeprecated(): Boolean = ??? + + def nextBoolean(): Boolean = + nextInt() < 0 // is the sign bit 1? + + def nextBytes(bytes: Array[Byte]): Unit = { + val len = bytes.length // implicit NPE + var i = 0 + + for (_ <- 0 until (len >> 3)) { + var rnd = nextLong() + for (_ <- 0 until 8) { + bytes(i) = rnd.toByte + rnd >>>= 8 + i += 1 + } + } + + if (i != len) { + var rnd = nextLong() + while (i != len) { + bytes(i) = rnd.toByte + rnd >>>= 8 + i += 1 + } + } + } + + def nextFloat(): Float = { + // > Uses the 24 high-order bits from a call to nextInt() + val bits = nextInt() >>> (32 - 24) + bits.toFloat * (1.0f / (1 << 24)) // lossless multiplication + } + + def nextFloat(bound: Float): Float = { + // false for NaN + if (bound > 0 && bound != Float.PositiveInfinity) + ensureBelowBound(nextFloatBoundedInternal(bound), bound) + else + throw new IllegalArgumentException(s"Illegal bound: $bound") + } + + def nextFloat(origin: Float, bound: Float): Float = { + // `origin < bound` is false if either input is NaN + if (origin != Float.NegativeInfinity && origin < bound && bound != Float.PositiveInfinity) { + val difference = bound - origin + val result = if (difference != Float.PositiveInfinity) { + // Easy case + origin + nextFloatBoundedInternal(difference) + } else { + // Overflow: scale everything down by 0.5 then scale it back up by 2.0 + val halfOrigin = origin * 0.5f + val halfBound = bound * 0.5f + (halfOrigin + nextFloatBoundedInternal(halfBound - halfOrigin)) * 2.0f + } + + ensureBelowBound(result, bound) + } else { + throw new IllegalArgumentException(s"Illegal bounds: [$origin, $bound)") + } + } + + @inline + private def nextFloatBoundedInternal(bound: Float): Float = + nextFloat() * bound + + @inline + private def ensureBelowBound(value: Float, bound: Float): Float = { + /* Based on documentation for Random.doubles to avoid issue #2144 and other + * possible rounding up issues: + * https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#doubles-double-double- + */ + if (value < bound) value + else Math.nextDown(value) + } + + def nextDouble(): Double = { + // > Uses the 53 high-order bits from a call to nextLong() + val bits = nextLong() >>> (64 - 53) + bits.toDouble * (1.0 / (1L << 53)) // lossless multiplication + } + + def nextDouble(bound: Double): Double = { + // false for NaN + if (bound > 0 && bound != Double.PositiveInfinity) + ensureBelowBound(nextDoubleBoundedInternal(bound), bound) + else + throw new IllegalArgumentException(s"Illegal bound: $bound") + } + + def nextDouble(origin: Double, bound: Double): Double = { + // `origin < bound` is false if either input is NaN + if (origin != Double.NegativeInfinity && origin < bound && bound != Double.PositiveInfinity) { + val difference = bound - origin + val result = if (difference != Double.PositiveInfinity) { + // Easy case + origin + nextDoubleBoundedInternal(difference) + } else { + // Overflow: scale everything down by 0.5 then scale it back up by 2.0 + val halfOrigin = origin * 0.5 + val halfBound = bound * 0.5 + (halfOrigin + nextDoubleBoundedInternal(halfBound - halfOrigin)) * 2.0 + } + + ensureBelowBound(result, bound) + } else { + throw new IllegalArgumentException(s"Illegal bounds: [$origin, $bound)") + } + } + + @inline + private def nextDoubleBoundedInternal(bound: Double): Double = + nextDouble() * bound + + @inline + private def ensureBelowBound(value: Double, bound: Double): Double = { + /* Based on documentation for Random.doubles to avoid issue #2144 and other + * possible rounding up issues: + * https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#doubles-double-double- + */ + if (value < bound) value + else Math.nextDown(value) + } + + def nextInt(): Int = { + // > Uses the 32 high-order bits from a call to nextLong() + (nextLong() >>> 32).toInt + } + + /* The algorithms used in nextInt() with bounds were initially part of + * ThreadLocalRandom. That implementation had been written by Doug Lea with + * assistance from members of JCP JSR-166 Expert Group and released to the + * public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + + def nextInt(bound: Int): Int = { + if (bound <= 0) + throw new IllegalArgumentException(s"Illegal bound: $bound") + + nextIntBoundedInternal(bound) + } + + def nextInt(origin: Int, bound: Int): Int = { + if (bound <= origin) + throw new IllegalArgumentException(s"Illegal bounds: [$origin, $bound)") + + val difference = bound - origin + if (difference > 0 || difference == Int.MinValue) { + /* Either the difference did not overflow, or it is the only power of 2 + * that overflows. In both cases, use the straightforward algorithm. + * It works for `MinValue` because the code path for powers of 2 + * basically interprets the bound as unsigned. + */ + origin + nextIntBoundedInternal(difference) + } else { + /* The interval size here is greater than Int.MaxValue, + * so the loop will exit with a probability of at least 1/2. + */ + @tailrec + def loop(): Int = { + val rnd = nextInt() + if (rnd >= origin && rnd < bound) + rnd + else + loop() + } + + loop() + } + } + + private def nextIntBoundedInternal(bound: Int): Int = { + // bound > 0 || bound == Int.MinValue + + if ((bound & -bound) == bound) { // i.e., bound is a power of 2 + // > If bound is a power of two then limiting is a simple masking operation. + nextInt() & (bound - 1) + } else { + /* > Otherwise, the result is re-calculated by invoking nextInt() until + * > the result is greater than or equal zero and less than bound. + */ + + /* Taken literally, that spec would lead to huge rejection rates for + * small bounds. + * Instead, we start from a random 31-bit (non-negative) int `rnd`, and + * we compute `rnd % bound`. + * In order to get a uniform distribution, we must reject and retry if + * we get an `rnd` that is >= the largest int multiple of `bound`. + */ + + @tailrec + def loop(): Int = { + val rnd = nextInt() >>> 1 + val value = rnd % bound // candidate result + + // largest multiple of bound that is <= rnd + val multiple = rnd - value + + // if multiple + bound overflows + if (multiple + bound < 0) { + /* then `multiple` is the largest multiple of bound, and + * `rnd >= multiple`, so we must retry. + */ + loop() + } else { + value + } + } + + loop() + } + } + + // The only abstract method of RandomGenerator + def nextLong(): Long + + /* The algorithms for nextLong() with bounds are copy-pasted from the ones + * for nextInt(), mutatis mutandis. + */ + + def nextLong(bound: Long): Long = { + if (bound <= 0) + throw new IllegalArgumentException(s"Illegal bound: $bound") + + nextLongBoundedInternal(bound) + } + + def nextLong(origin: Long, bound: Long): Long = { + if (bound <= origin) + throw new IllegalArgumentException(s"Illegal bounds: [$origin, $bound)") + + val difference = bound - origin + if (difference > 0 || difference == Long.MinValue) { + /* Either the difference did not overflow, or it is the only power of 2 + * that overflows. In both cases, use the straightforward algorithm. + * It works for `MinValue` because the code path for powers of 2 + * basically interprets the bound as unsigned. + */ + origin + nextLongBoundedInternal(difference) + } else { + /* The interval size here is greater than Long.MaxValue, + * so the loop will exit with a probability of at least 1/2. + */ + @tailrec + def loop(): Long = { + val rnd = nextLong() + if (rnd >= origin && rnd < bound) + rnd + else + loop() + } + + loop() + } + } + + private def nextLongBoundedInternal(bound: Long): Long = { + // bound > 0 || bound == Long.MinValue + + if ((bound & -bound) == bound) { // i.e., bound is a power of 2 + // > If bound is a power of two then limiting is a simple masking operation. + nextLong() & (bound - 1L) + } else { + /* > Otherwise, the result is re-calculated by invoking nextLong() until + * > the result is greater than or equal zero and less than bound. + */ + + /* Taken literally, that spec would lead to huge rejection rates for + * small bounds. + * Instead, we start from a random 63-bit (non-negative) int `rnd`, and + * we compute `rnd % bound`. + * In order to get a uniform distribution, we must reject and retry if + * we get an `rnd` that is >= the largest int multiple of `bound`. + */ + + @tailrec + def loop(): Long = { + val rnd = nextLong() >>> 1 + val value = rnd % bound // candidate result + + // largest multiple of bound that is <= rnd + val multiple = rnd - value + + // if multiple + bound overflows + if (multiple + bound < 0L) { + /* then `multiple` is the largest multiple of bound, and + * `rnd >= multiple`, so we must retry. + */ + loop() + } else { + value + } + } + + loop() + } + } + + // Not implemented + // def nextGaussian(): Double = ??? + // def nextGaussian(mean: Double, stddev: Double): Double = ??? + // def nextExponential(): Double = ??? +} + +object RandomGenerator { // scalastyle:ignore + // Not implemented + // def of(name: String): RandomGenerator = ??? + // def getDefault(): RandomGenerator = ??? +} diff --git a/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala b/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala index 4b866920b0..3d2b480a94 100644 --- a/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala +++ b/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala @@ -14,7 +14,10 @@ package java.util.regex import scala.annotation.{tailrec, switch} +import java.lang.Utils._ + import scala.scalajs.js +import scala.scalajs.js.JSStringOps._ import Pattern.IndicesArray @@ -79,7 +82,7 @@ private[regex] class IndicesBuilder private (pattern: String, flags: String, } val start = index // by definition - val end = start + allMatchResult(0).get.length() + val end = start + undefOrForceGet(allMatchResult(0)).length() /* Initialize the `indices` array with: * - `[start, end]` at index 0, which represents the whole match, and @@ -91,10 +94,10 @@ private[regex] class IndicesBuilder private (pattern: String, flags: String, */ val len = groupCount + 1 val indices = new IndicesArray(len) - indices(0) = js.Tuple2(start, end) + indices(0) = js.Array(start, end).asInstanceOf[js.Tuple2[Int, Int]] var i = 1 while (i != len) { - indices(i) = js.undefined + indices(i) = undefined i += 1 } @@ -179,7 +182,7 @@ private[regex] object IndicesBuilder { final def propagateFromEnd(matchResult: js.RegExp.ExecResult, indices: IndicesArray, end: Int): Unit = { - val start = matchResult(newGroup).fold(-1)(matched => end - matched.length) + val start = undefOrFold(matchResult(newGroup))(() => -1)(matched => end - matched.length) propagate(matchResult, indices, start, end) } @@ -191,7 +194,7 @@ private[regex] object IndicesBuilder { final def propagateFromStart(matchResult: js.RegExp.ExecResult, indices: IndicesArray, start: Int): Int = { - val end = matchResult(newGroup).fold(-1)(matched => start + matched.length) + val end = undefOrFold(matchResult(newGroup))(() => -1)(matched => start + matched.length) propagate(matchResult, indices, start, end) end } @@ -212,8 +215,8 @@ private[regex] object IndicesBuilder { * always keep the default `-1` if this group node does not match * anything. */ - if (matchResult(newGroup).isDefined) - indices(number) = js.Tuple2(start, end) + if (undefOrIsDefined(matchResult(newGroup))) + indices(number) = js.Array(start, end).asInstanceOf[js.Tuple2[Int, Int]] inner.propagate(matchResult, indices, start, end) } } @@ -417,7 +420,7 @@ private[regex] object IndicesBuilder { } case '(' => - val indicator = pattern.substring(pIndex + 1, pIndex + 3) + val indicator = pattern.jsSubstring(pIndex + 1, pIndex + 3) if (indicator == "?=" || indicator == "?!") { // Look-ahead group pIndex += 3 @@ -425,7 +428,7 @@ private[regex] object IndicesBuilder { new LookAroundNode(isLookBehind = false, indicator, inner) } else if (indicator == "?<") { // Look-behind group, which must be ?<= or ? @@ -485,13 +488,13 @@ private[regex] object IndicesBuilder { val startIndex = pIndex pIndex = loop(startIndex + 1) - val regex = pattern.substring(startIndex, pIndex) + val regex = pattern.jsSubstring(startIndex, pIndex) new LeafRegexNode(regex) case _ => val start = pIndex pIndex += Character.charCount(dispatchCP) - new LeafRegexNode(pattern.substring(start, pIndex)) + new LeafRegexNode(pattern.jsSubstring(start, pIndex)) } if (baseNode ne null) { // null if we just completed an alternative @@ -503,7 +506,7 @@ private[regex] object IndicesBuilder { else pIndex += 1 - val repeater = pattern.substring(startIndex, pIndex) + val repeater = pattern.jsSubstring(startIndex, pIndex) sequence.push(new RepeatedNode(baseNode, repeater)) case '{' => @@ -512,7 +515,7 @@ private[regex] object IndicesBuilder { pIndex = pattern.indexOf("}", startIndex + 1) + 1 if (pattern.charAt(pIndex) == '?') // non-greedy mark pIndex += 1 - val repeater = pattern.substring(startIndex, pIndex) + val repeater = pattern.jsSubstring(startIndex, pIndex) sequence.push(new RepeatedNode(baseNode, repeater)) case _ => diff --git a/javalib/src/main/scala/java/util/regex/Matcher.scala b/javalib/src/main/scala/java/util/regex/Matcher.scala index 4effe7de81..5acda2c9bb 100644 --- a/javalib/src/main/scala/java/util/regex/Matcher.scala +++ b/javalib/src/main/scala/java/util/regex/Matcher.scala @@ -12,6 +12,8 @@ package java.util.regex +import java.lang.Utils._ + import scala.annotation.switch import scala.scalajs.js @@ -182,13 +184,13 @@ final class Matcher private[regex] ( def start(): Int = ensureLastMatch.index + regionStart() def end(): Int = start() + group().length - def group(): String = ensureLastMatch(0).get + def group(): String = undefOrForceGet(ensureLastMatch(0)) private def indices: IndicesArray = pattern().getIndices(ensureLastMatch, lastMatchIsForMatches) private def startInternal(compiledGroup: Int): Int = - indices(compiledGroup).fold(-1)(_._1 + regionStart()) + undefOrFold(indices(compiledGroup))(() => -1)(_._1 + regionStart()) def start(group: Int): Int = startInternal(pattern().numberedGroup(group)) @@ -197,7 +199,7 @@ final class Matcher private[regex] ( startInternal(pattern().namedGroup(name)) private def endInternal(compiledGroup: Int): Int = - indices(compiledGroup).fold(-1)(_._2 + regionStart()) + undefOrFold(indices(compiledGroup))(() => -1)(_._2 + regionStart()) def end(group: Int): Int = endInternal(pattern().numberedGroup(group)) @@ -206,10 +208,10 @@ final class Matcher private[regex] ( endInternal(pattern().namedGroup(name)) def group(group: Int): String = - ensureLastMatch(pattern().numberedGroup(group)).orNull + undefOrGetOrNull(ensureLastMatch(pattern().numberedGroup(group))) def group(name: String): String = - ensureLastMatch(pattern().namedGroup(name)).orNull + undefOrGetOrNull(ensureLastMatch(pattern().namedGroup(name))) // Seal the state @@ -266,7 +268,7 @@ object Matcher { def start(): Int = ensureLastMatch.index + regionStart def end(): Int = start() + group().length - def group(): String = ensureLastMatch(0).get + def group(): String = undefOrForceGet(ensureLastMatch(0)) private def indices: IndicesArray = pattern.getIndices(ensureLastMatch, lastMatchIsForMatches) @@ -276,13 +278,13 @@ object Matcher { */ def start(group: Int): Int = - indices(pattern.numberedGroup(group)).fold(-1)(_._1 + regionStart) + undefOrFold(indices(pattern.numberedGroup(group)))(() => -1)(_._1 + regionStart) def end(group: Int): Int = - indices(pattern.numberedGroup(group)).fold(-1)(_._2 + regionStart) + undefOrFold(indices(pattern.numberedGroup(group)))(() => -1)(_._2 + regionStart) def group(group: Int): String = - ensureLastMatch(pattern.numberedGroup(group)).orNull + undefOrGetOrNull(ensureLastMatch(pattern.numberedGroup(group))) private def ensureLastMatch: js.RegExp.ExecResult = { if (lastMatch == null) diff --git a/javalib/src/main/scala/java/util/regex/Pattern.scala b/javalib/src/main/scala/java/util/regex/Pattern.scala index 75089d46d5..52dcc3e8f0 100644 --- a/javalib/src/main/scala/java/util/regex/Pattern.scala +++ b/javalib/src/main/scala/java/util/regex/Pattern.scala @@ -14,6 +14,9 @@ package java.util.regex import scala.annotation.tailrec +import java.lang.Utils._ +import java.util.ScalaOps._ + import scala.scalajs.js import PatternCompiler.Support._ @@ -130,14 +133,14 @@ final class Pattern private[regex] ( } private[regex] def namedGroup(name: String): Int = { - groupNumberMap(namedGroups.getOrElse(name, { + groupNumberMap(dictGetOrElse(namedGroups, name) { () => throw new IllegalArgumentException(s"No group with name <$name>") - })) + }) } private[regex] def getIndices(lastMatch: js.RegExp.ExecResult, forMatches: Boolean): IndicesArray = { val lastMatchDyn = lastMatch.asInstanceOf[js.Dynamic] - if (js.isUndefined(lastMatchDyn.indices)) { + if (isUndefined(lastMatchDyn.indices)) { if (supportsIndices) { if (!enabledNativeIndices) { jsRegExpForFind = new js.RegExp(jsPattern, jsFlagsForFind + "d") @@ -181,40 +184,33 @@ final class Pattern private[regex] ( // Actually split original string val lim = if (limit > 0) limit else Int.MaxValue val matcher = this.matcher(inputStr) - val builder = Array.newBuilder[String] + val result = js.Array[String]() var prevEnd = 0 - var size = 0 - while ((size < lim-1) && matcher.find()) { + while ((result.length < lim - 1) && matcher.find()) { if (matcher.end() == 0) { /* If there is a zero-width match at the beginning of the string, * ignore it, i.e., omit the resulting empty string at the beginning * of the array. */ } else { - builder += inputStr.substring(prevEnd, matcher.start()) - size += 1 + result.push(inputStr.substring(prevEnd, matcher.start())) } prevEnd = matcher.end() } - builder += inputStr.substring(prevEnd) - val result = builder.result() + result.push(inputStr.substring(prevEnd)) // With `limit == 0`, remove trailing empty strings. - if (limit != 0) { - result - } else { - var actualLength = result.length + var actualLength = result.length + if (limit == 0) { while (actualLength != 0 && result(actualLength - 1) == "") actualLength -= 1 - - if (actualLength == result.length) { - result - } else { - val actualResult = new Array[String](actualLength) - System.arraycopy(result, 0, actualResult, 0, actualLength) - actualResult - } } + + // Build result array + val r = new Array[String](actualLength) + for (i <- 0 until actualLength) + r(i) = result(i) + r } } } diff --git a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala index d5ea0b3c23..751f2e8f78 100644 --- a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala +++ b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala @@ -25,10 +25,13 @@ import java.lang.Character.{ MAX_LOW_SURROGATE } +import java.lang.Utils._ import java.util.ScalaOps._ import scala.scalajs.js -import scala.scalajs.LinkingInfo.{ESVersion, esVersion} +import scala.scalajs.js.JSStringOps.enableJSStringOps +import scala.scalajs.LinkingInfo +import scala.scalajs.LinkingInfo.ESVersion /** Compiler from Java regular expressions to JavaScript regular expressions. * @@ -73,22 +76,22 @@ private[regex] object PatternCompiler { new js.RegExp("", flags) true } catch { - case _: js.JavaScriptException => + case _: Throwable => false } } /** Cache for `Support.supportsUnicode`. */ private val _supportsUnicode = - (esVersion >= ESVersion.ES2015) || featureTest("u") + (LinkingInfo.esVersion >= ESVersion.ES2015) || featureTest("u") /** Cache for `Support.supportsSticky`. */ private val _supportsSticky = - (esVersion >= ESVersion.ES2015) || featureTest("y") + (LinkingInfo.esVersion >= ESVersion.ES2015) || featureTest("y") /** Cache for `Support.supportsDotAll`. */ private val _supportsDotAll = - (esVersion >= ESVersion.ES2018) || featureTest("us") + (LinkingInfo.esVersion >= ESVersion.ES2018) || featureTest("us") /** Cache for `Support.supportsIndices`. */ private val _supportsIndices = @@ -104,17 +107,17 @@ private[regex] object PatternCompiler { /** Tests whether the underlying JS RegExp supports the 'u' flag. */ @inline def supportsUnicode: Boolean = - (esVersion >= ESVersion.ES2015) || _supportsUnicode + (LinkingInfo.esVersion >= ESVersion.ES2015) || _supportsUnicode /** Tests whether the underlying JS RegExp supports the 'y' flag. */ @inline def supportsSticky: Boolean = - (esVersion >= ESVersion.ES2015) || _supportsSticky + (LinkingInfo.esVersion >= ESVersion.ES2015) || _supportsSticky /** Tests whether the underlying JS RegExp supports the 's' flag. */ @inline def supportsDotAll: Boolean = - (esVersion >= ESVersion.ES2018) || _supportsDotAll + (LinkingInfo.esVersion >= ESVersion.ES2018) || _supportsDotAll /** Tests whether the underlying JS RegExp supports the 'd' flag. */ @inline @@ -128,7 +131,7 @@ private[regex] object PatternCompiler { */ @inline def enableUnicodeCaseInsensitive: Boolean = - esVersion >= ESVersion.ES2015 + LinkingInfo.esVersion >= ESVersion.ES2015 /** Tests whether features requiring \p{} and/or look-behind assertions are enabled. * @@ -137,7 +140,7 @@ private[regex] object PatternCompiler { */ @inline def enableUnicodeCharacterClassesAndLookBehinds: Boolean = - esVersion >= ESVersion.ES2018 + LinkingInfo.esVersion >= ESVersion.ES2018 } import Support._ @@ -212,7 +215,7 @@ private[regex] object PatternCompiler { import InlinedHelpers._ private def codePointToString(codePoint: Int): String = { - if (esVersion >= ESVersion.ES2015) { + if (LinkingInfo.esVersion >= ESVersion.ES2015) { js.Dynamic.global.String.fromCodePoint(codePoint).asInstanceOf[String] } else { if (isBmpCodePoint(codePoint)) { @@ -286,24 +289,24 @@ private[regex] object PatternCompiler { * This is a `js.Dictionary` because it can be used even when compiling to * ECMAScript 5.1. */ - private val asciiPOSIXCharacterClasses = { + private val asciiPOSIXCharacterClasses: js.Dictionary[CompiledCharClass] = { import CompiledCharClass._ - js.Dictionary( - ("Lower", posClass("a-z")), - ("Upper", posClass("A-Z")), - ("ASCII", posClass("\u0000-\u007f")), - ("Alpha", posClass("A-Za-z")), // [\p{Lower}\p{Upper}] - ("Digit", posClass("0-9")), - ("Alnum", posClass("0-9A-Za-z")), // [\p{Alpha}\p{Digit}] - ("Punct", posClass("!-/:-@[-`{-~")), // One of !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ - ("Graph", posClass("!-~")), // [\p{Alnum}\p{Punct}] - ("Print", posClass(" -~")), // [\p{Graph}\x20] - ("Blank", posClass("\t ")), - ("Cntrl", posClass("\u0000-\u001f\u007f")), - ("XDigit", posClass("0-9A-Fa-f")), - ("Space", posClass("\t-\r ")) // [ \t\n\x0B\f\r] - ) + val r = dictEmpty[CompiledCharClass]() + dictSet(r, "Lower", posClass("a-z")) + dictSet(r, "Upper", posClass("A-Z")) + dictSet(r, "ASCII", posClass("\u0000-\u007f")) + dictSet(r, "Alpha", posClass("A-Za-z")) // [\p{Lower}\p{Upper}] + dictSet(r, "Digit", posClass("0-9")) + dictSet(r, "Alnum", posClass("0-9A-Za-z")) // [\p{Alpha}\p{Digit}] + dictSet(r, "Punct", posClass("!-/:-@[-`{-~")) // One of !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ + dictSet(r, "Graph", posClass("!-~")) // [\p{Alnum}\p{Punct}] + dictSet(r, "Print", posClass(" -~")) // [\p{Graph}\x20] + dictSet(r, "Blank", posClass("\t ")) + dictSet(r, "Cntrl", posClass("\u0000-\u001f\u007f")) + dictSet(r, "XDigit", posClass("0-9A-Fa-f")) + dictSet(r, "Space", posClass("\t-\r ")) // [ \t\n\x0B\f\r] + r } /** Mapping of predefined character classes to the corresponding character @@ -333,70 +336,70 @@ private[regex] object PatternCompiler { "Cc", "Cf", "Cs", "Co", "Cn", "C" ) - for (gc <- generalCategories) { + forArrayElems(generalCategories) { gc => val compiled = posP(gc) - result(gc) = compiled - result("Is" + gc) = compiled - result("general_category=" + gc) = compiled - result("gc=" + gc) = compiled + mapSet(result, gc, compiled) + mapSet(result, "Is" + gc, compiled) + mapSet(result, "general_category=" + gc, compiled) + mapSet(result, "gc=" + gc, compiled) } // Binary properties - result("IsAlphabetic") = posP("Alphabetic") - result("IsIdeographic") = posP("Ideographic") - result("IsLetter") = posP("Letter") - result("IsLowercase") = posP("Lowercase") - result("IsUppercase") = posP("Uppercase") - result("IsTitlecase") = posP("Lt") - result("IsPunctuation") = posP("Punctuation") - result("IsControl") = posP("Control") - result("IsWhite_Space") = posP("White_Space") - result("IsDigit") = posP("Nd") - result("IsHex_Digit") = posP("Hex_Digit") - result("IsJoin_Control") = posP("Join_Control") - result("IsNoncharacter_Code_Point") = posP("Noncharacter_Code_Point") - result("IsAssigned") = posP("Assigned") + mapSet(result, "IsAlphabetic", posP("Alphabetic")) + mapSet(result, "IsIdeographic", posP("Ideographic")) + mapSet(result, "IsLetter", posP("Letter")) + mapSet(result, "IsLowercase", posP("Lowercase")) + mapSet(result, "IsUppercase", posP("Uppercase")) + mapSet(result, "IsTitlecase", posP("Lt")) + mapSet(result, "IsPunctuation", posP("Punctuation")) + mapSet(result, "IsControl", posP("Control")) + mapSet(result, "IsWhite_Space", posP("White_Space")) + mapSet(result, "IsDigit", posP("Nd")) + mapSet(result, "IsHex_Digit", posP("Hex_Digit")) + mapSet(result, "IsJoin_Control", posP("Join_Control")) + mapSet(result, "IsNoncharacter_Code_Point", posP("Noncharacter_Code_Point")) + mapSet(result, "IsAssigned", posP("Assigned")) // java.lang.Character classes - result("javaAlphabetic") = posP("Alphabetic") - result("javaDefined") = negP("Cn") - result("javaDigit") = posP("Nd") - result("javaIdentifierIgnorable") = posClass("\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}") - result("javaIdeographic") = posP("Ideographic") - result("javaISOControl") = posClass("\u0000-\u001F\u007F-\u009F") - result("javaJavaIdentifierPart") = - posClass("\\p{L}\\p{Sc}\\p{Pc}\\p{Nd}\\p{Nl}\\p{Mn}\\p{Mc}\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}") - result("javaJavaIdentifierStart") = posClass("\\p{L}\\p{Sc}\\p{Pc}\\p{Nl}") - result("javaLetterOrDigit") = posClass("\\p{L}\\p{Nd}") - result("javaLowerCase") = posP("Lowercase") - result("javaMirrored") = posP("Bidi_Mirrored") - result("javaSpaceChar") = posP("Z") - result("javaTitleCase") = posP("Lt") - result("javaUnicodeIdentifierPart") = - posClass("\\p{ID_Continue}\u2E2F\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}") - result("javaUnicodeIdentifierStart") = posClass("\\p{ID_Start}\u2E2F") - result("javaUpperCase") = posP("Uppercase") + mapSet(result, "javaAlphabetic", posP("Alphabetic")) + mapSet(result, "javaDefined", negP("Cn")) + mapSet(result, "javaDigit", posP("Nd")) + mapSet(result, "javaIdentifierIgnorable", posClass("\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}")) + mapSet(result, "javaIdeographic", posP("Ideographic")) + mapSet(result, "javaISOControl", posClass("\u0000-\u001F\u007F-\u009F")) + mapSet(result, "javaJavaIdentifierPart", + posClass("\\p{L}\\p{Sc}\\p{Pc}\\p{Nd}\\p{Nl}\\p{Mn}\\p{Mc}\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}")) + mapSet(result, "javaJavaIdentifierStart", posClass("\\p{L}\\p{Sc}\\p{Pc}\\p{Nl}")) + mapSet(result, "javaLetterOrDigit", posClass("\\p{L}\\p{Nd}")) + mapSet(result, "javaLowerCase", posP("Lowercase")) + mapSet(result, "javaMirrored", posP("Bidi_Mirrored")) + mapSet(result, "javaSpaceChar", posP("Z")) + mapSet(result, "javaTitleCase", posP("Lt")) + mapSet(result, "javaUnicodeIdentifierPart", + posClass("\\p{ID_Continue}\u2E2F\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}")) + mapSet(result, "javaUnicodeIdentifierStart", posClass("\\p{ID_Start}\u2E2F")) + mapSet(result, "javaUpperCase", posP("Uppercase")) // [\t-\r\u001C-\u001F\\p{Z}&&[^\u00A0\u2007\u202F]] - result("javaWhitespace") = - posClass("\t-\r\u001C-\u001F \u1680\u2000-\u2006\u2008-\u200A\u205F\u3000\\p{Zl}\\p{Zp}") + mapSet(result, "javaWhitespace", + posClass("\t-\r\u001C-\u001F \u1680\u2000-\u2006\u2008-\u200A\u205F\u3000\\p{Zl}\\p{Zp}")) /* POSIX character classes with Unicode compatibility * (resolved from the original definitions, which are in comments) */ - result("Lower") = posP("Lower") // \p{IsLowercase} - result("Upper") = posP("Upper") // \p{IsUppercase} - result("ASCII") = posClass("\u0000-\u007f") - result("Alpha") = posP("Alpha") // \p{IsAlphabetic} - result("Digit") = posP("Nd") // \p{IsDigit} - result("Alnum") = posClass("\\p{Alpha}\\p{Nd}") // [\p{IsAlphabetic}\p{IsDigit}] - result("Punct") = posP("P") // \p{IsPunctuation} + mapSet(result, "Lower", posP("Lower")) // \p{IsLowercase} + mapSet(result, "Upper", posP("Upper")) // \p{IsUppercase} + mapSet(result, "ASCII", posClass("\u0000-\u007f")) + mapSet(result, "Alpha", posP("Alpha")) // \p{IsAlphabetic} + mapSet(result, "Digit", posP("Nd")) // \p{IsDigit} + mapSet(result, "Alnum", posClass("\\p{Alpha}\\p{Nd}")) // [\p{IsAlphabetic}\p{IsDigit}] + mapSet(result, "Punct", posP("P")) // \p{IsPunctuation} // [^\p{IsWhite_Space}\p{gc=Cc}\p{gc=Cs}\p{gc=Cn}] - result("Graph") = negClass("\\p{White_Space}\\p{Cc}\\p{Cs}\\p{Cn}") + mapSet(result, "Graph", negClass("\\p{White_Space}\\p{Cc}\\p{Cs}\\p{Cn}")) /* [\p{Graph}\p{Blank}&&[^\p{Cntrl}]] * === (by definition of Cntrl) @@ -416,7 +419,7 @@ private[regex] object PatternCompiler { * === (because \x09-\x0d and \x85 are all in the Cc category) * [^\p{Zl}\p{Zp}\p{Cc}\p{Cs}\p{Cn}] */ - result("Print") = negClass("\\p{Zl}\\p{Zp}\\p{Cc}\\p{Cs}\\p{Cn}") + mapSet(result, "Print", negClass("\\p{Zl}\\p{Zp}\\p{Cc}\\p{Cs}\\p{Cn}")) /* [\p{IsWhite_Space}&&[^\p{gc=Zl}\p{gc=Zp}\x0a\x0b\x0c\x0d\x85]] * === (see the excerpt from PropList.txt below) @@ -424,11 +427,11 @@ private[regex] object PatternCompiler { * === (by simplification) * [\x09\p{gc=Zs}] */ - result("Blank") = posClass("\t\\p{Zs}") + mapSet(result, "Blank", posClass("\t\\p{Zs}")) - result("Cntrl") = posP("Cc") // \p{gc=Cc} - result("XDigit") = posClass("\\p{Nd}\\p{Hex}") // [\p{gc=Nd}\p{IsHex_Digit}] - result("Space") = posP("White_Space") // \p{IsWhite_Space} + mapSet(result, "Cntrl", posP("Cc")) // \p{gc=Cc} + mapSet(result, "XDigit", posClass("\\p{Nd}\\p{Hex}")) // [\p{gc=Nd}\p{IsHex_Digit}] + mapSet(result, "Space", posP("White_Space")) // \p{IsWhite_Space} result } @@ -473,7 +476,7 @@ private[regex] object PatternCompiler { /* SignWriting is an exception. It has an uppercase 'W' even though it is * not after '_'. We add the exception to the map immediately. */ - result("signwriting") = "SignWriting" + mapSet(result, "signwriting", "SignWriting") result } @@ -741,7 +744,7 @@ private final class PatternCompiler(private val pattern: String, private var fla * We store *original* group numbers, rather than compiled group numbers, * in order to make the renumbering caused by possessive quantifiers easier. */ - private val namedGroups = js.Dictionary.empty[Int] + private val namedGroups = dictEmpty[Int]() @inline private def hasFlag(flag: Int): Boolean = (flags & flag) != 0 @@ -805,7 +808,7 @@ private final class PatternCompiler(private val pattern: String, private var fla val jsPattern = if (isLiteral) { literal(pattern) } else { - if (pattern.substring(pIndex, pIndex + 2) == "\\G") { + if (pattern.jsSubstring(pIndex, pIndex + 2) == "\\G") { sticky = true pIndex += 2 } @@ -850,7 +853,7 @@ private final class PatternCompiler(private val pattern: String, private var fla private def processLeadingEmbeddedFlags(): Unit = { val m = leadingEmbeddedFlagSpecifierRegExp.exec(pattern) if (m != null) { - for (chars <- m(1)) { + undefOrForeach(m(1)) { chars => for (i <- 0 until chars.length()) flags |= charToFlag(chars.charAt(i)) } @@ -859,7 +862,7 @@ private final class PatternCompiler(private val pattern: String, private var fla if (hasFlag(UNICODE_CHARACTER_CLASS)) flags |= UNICODE_CASE - for (chars <- m(2)) { + undefOrForeach(m(2)) { chars => for (i <- 0 until chars.length()) flags &= ~charToFlag(chars.charAt(i)) } @@ -872,7 +875,7 @@ private final class PatternCompiler(private val pattern: String, private var fla */ // Advance past the embedded flags - pIndex += m(0).get.length() + pIndex += undefOrForceGet(m(0)).length() } } @@ -1080,6 +1083,33 @@ private final class PatternCompiler(private val pattern: String, private var fla if (hasRepeater) { // There is a repeater + + /* #4784 Wrap tokens that are Assertions in ES' pattern syntax, since + * it is not syntactically valid to directly quantify them. It is valid + * to quantify a group containing an Assertion, however. + * + * There is no index-out-of-bounds in the following code because + * `compiledToken` is known to be a syntactically valid, non-empty regex. + */ + val isTokenAnAssertion = (compiledToken.charAt(0): @switch) match { + case '^' | '$' => + true + case '(' => + /* This expression would also match named capturing groups, but we + * never emit those. Anyway, even if we did, we would uselessly wrap + * a group that does not need to be, but it would still be correct. + */ + compiledToken.charAt(1) == '?' && compiledToken.charAt(2) != ':' + case '\\' => + val c = compiledToken.charAt(1) + c == 'b' || c == 'B' + case _ => + false + } + val wrappedToken = + if (isTokenAnAssertion) "(?:" + compiledToken + ")" + else compiledToken + val baseRepeater = parseBaseRepeater(repeaterDispatchChar) if (pIndex != len) { @@ -1087,18 +1117,18 @@ private final class PatternCompiler(private val pattern: String, private var fla case '+' => // Possessive quantifier pIndex += 1 - buildPossessiveQuantifier(compiledGroupCountBeforeThisToken, compiledToken, baseRepeater) + buildPossessiveQuantifier(compiledGroupCountBeforeThisToken, wrappedToken, baseRepeater) case '?' => // Lazy quantifier pIndex += 1 - compiledToken + baseRepeater + "?" + wrappedToken + baseRepeater + "?" case _ => // Greedy quantifier - compiledToken + baseRepeater + wrappedToken + baseRepeater } } else { // Greedy quantifier - compiledToken + baseRepeater + wrappedToken + baseRepeater } } else { // No repeater @@ -1131,7 +1161,7 @@ private final class PatternCompiler(private val pattern: String, private var fla pIndex += 1 } - pattern.substring(startOfRepeater, pIndex) + pattern.jsSubstring(startOfRepeater, pIndex) } /** Builds a possessive quantifier, which is sugar for an atomic group over @@ -1189,13 +1219,7 @@ private final class PatternCompiler(private val pattern: String, private var fla else "(?<=^|\r(?!\n)|[\n\u0085\u2028\u2029])" } else { - /* Wrap as (?:^) in case it ends up being repeated, for example `^+` - * becomes `(?:^)+`. This is necessary because `^+` is not syntactically - * valid in JS, although it is valid once wrapped in a group. - * (Not that repeating ^ has any useful purpose, but the spec does not - * prevent it.) - */ - "(?:^)" + "^" } } @@ -1211,8 +1235,7 @@ private final class PatternCompiler(private val pattern: String, private var fla else "(?=$|(? - if (pattern.substring(pIndex, pIndex + 4) == "b{g}") { + if (pattern.jsSubstring(pIndex, pIndex + 4) == "b{g}") { parseError("\\b{g} is not supported") } else { /* Compile as is if both `UNICODE_CASE` and `UNICODE_CHARACTER_CLASS` are false. @@ -1300,7 +1323,7 @@ private final class PatternCompiler(private val pattern: String, private var fla case 'A' => // We can always use ^ for start-of-text because we never use the 'm' flag in the JS RegExp pIndex += 1 - "(?:^)" // wrap in case it is quantified (see compilation of '^') + "^" case 'G' => parseError("\\G in the middle of a pattern is not supported") case 'Z' => @@ -1313,7 +1336,7 @@ private final class PatternCompiler(private val pattern: String, private var fla case 'z' => // We can always use $ for end-of-text because we never use the 'm' flag in the JS RegExp pIndex += 1 - "(?:$)" // wrap in case it is quantified (see compilation of '$') + "$" // Linebreak matcher @@ -1343,11 +1366,11 @@ private final class PatternCompiler(private val pattern: String, private var fla // In most cases, one of the first two conditions is immediately false while (end != len && isDigit(pattern.charAt(end)) && - parseInt(pattern.substring(start, end + 1), 10) <= originalGroupCount) { + parseInt(pattern.jsSubstring(start, end + 1), 10) <= originalGroupCount) { end += 1 } - val groupString = pattern.substring(start, end) + val groupString = pattern.jsSubstring(start, end) val groupNumber = parseInt(groupString, 10) if (groupNumber > originalGroupCount) parseError(s"numbered capturing group <$groupNumber> does not exist") @@ -1362,9 +1385,9 @@ private final class PatternCompiler(private val pattern: String, private var fla parseError("\\k is not followed by '<' for named capturing group") pIndex += 1 val groupName = parseGroupName() - val groupNumber = namedGroups.getOrElse(groupName, { + val groupNumber = dictGetOrElse(namedGroups, groupName) { () => parseError(s"named capturing group <$groupName> does not exit") - }) + } val compiledGroupNumber = groupNumberMap(groupNumber) pIndex += 1 // Wrap in a non-capturing group in case it's followed by a (de-escaped) digit @@ -1377,10 +1400,10 @@ private final class PatternCompiler(private val pattern: String, private var fla val end = pattern.indexOf("\\E", start) if (end < 0) { pIndex = pattern.length() - literal(pattern.substring(start)) + literal(pattern.jsSubstring(start)) } else { pIndex = end + 2 - literal(pattern.substring(start, end)) + literal(pattern.jsSubstring(start, end)) } // Other @@ -1525,7 +1548,7 @@ private final class PatternCompiler(private val pattern: String, private var fla val lowStart = end + 2 val lowEnd = lowStart + 4 - if (isHighSurrogateCP(codeUnit) && pattern.substring(end, lowStart) == "\\u") { + if (isHighSurrogateCP(codeUnit) && pattern.jsSubstring(end, lowStart) == "\\u") { val low = parseHexCodePoint(lowStart, lowEnd, "Unicode") if (isLowSurrogateCP(low)) { pIndex = lowEnd @@ -1552,7 +1575,7 @@ private final class PatternCompiler(private val pattern: String, private var fla val cp = if (end - start > 6) Character.MAX_CODE_POINT + 1 - else parseInt(pattern.substring(start, end), 16) + else parseInt(pattern.jsSubstring(start, end), 16) if (cp > Character.MAX_CODE_POINT) parseError("Hexadecimal codepoint is too big") @@ -1602,21 +1625,21 @@ private final class PatternCompiler(private val pattern: String, private var fla if (innerEnd < 0) parseError("Unclosed character family") pIndex = innerEnd - pattern.substring(innerStart, innerEnd) + pattern.jsSubstring(innerStart, innerEnd) } else { - pattern.substring(start, start + 1) + pattern.jsSubstring(start, start + 1) } - val result = if (!unicodeCharacterClass && asciiPOSIXCharacterClasses.contains(property)) { + val result = if (!unicodeCharacterClass && dictContains(asciiPOSIXCharacterClasses, property)) { val property2 = if (asciiCaseInsensitive && (property == "Lower" || property == "Upper")) "Alpha" else property - asciiPOSIXCharacterClasses(property2) + dictRawApply(asciiPOSIXCharacterClasses, property2) } else { // For anything else, we need built-in support for \p requireES2018Features("Unicode character family") - predefinedPCharacterClasses.getOrElse(property, { + mapGetOrElse(predefinedPCharacterClasses, property) { () => val scriptPrefixLen = if (property.startsWith("Is")) { 2 } else if (property.startsWith("sc=")) { @@ -1629,8 +1652,8 @@ private final class PatternCompiler(private val pattern: String, private var fla // Error parseError(s"Unknown Unicode character class '$property'") } - CompiledCharClass.posP("sc=" + canonicalizeScriptName(property.substring(scriptPrefixLen))) - }) + CompiledCharClass.posP("sc=" + canonicalizeScriptName(property.jsSubstring(scriptPrefixLen))) + } } pIndex += 1 @@ -1651,19 +1674,19 @@ private final class PatternCompiler(private val pattern: String, private var fla val lowercase = scriptName.toLowerCase() - canonicalizedScriptNameCache.getOrElseUpdate(lowercase, { + mapGetOrElseUpdate(canonicalizedScriptNameCache, lowercase) { () => val canonical = lowercase.jsReplace(scriptCanonicalizeRegExp, ((s: String) => s.toUpperCase()): js.Function1[String, String]) try { new js.RegExp(s"\\p{sc=$canonical}", "u") } catch { - case _: js.JavaScriptException => + case _: Throwable => parseError(s"Unknown character script name {$scriptName}") } canonical - }) + } } private def compileCharacterClass(): String = { @@ -1794,7 +1817,7 @@ private final class PatternCompiler(private val pattern: String, private var fla if (c1 == ':' || c1 == '=' || c1 == '!') { // Non-capturing group or look-ahead pIndex = start + 3 - pattern.substring(start, start + 3) + compileInsideGroup() + ")" + pattern.jsSubstring(start, start + 3) + compileInsideGroup() + ")" } else if (c1 == '<') { if (start + 3 == len) parseError("Unclosed group") @@ -1805,11 +1828,11 @@ private final class PatternCompiler(private val pattern: String, private var fla // Named capturing group pIndex = start + 3 val name = parseGroupName() - if (namedGroups.contains(name)) + if (dictContains(namedGroups, name)) parseError(s"named capturing group <$name> is already defined") compiledGroupCount += 1 groupNumberMap.push(compiledGroupCount) // this changes originalGroupCount - namedGroups(name) = originalGroupCount + dictSet(namedGroups, name, originalGroupCount) pIndex += 1 "(" + compileInsideGroup() + ")" } else { @@ -1818,7 +1841,7 @@ private final class PatternCompiler(private val pattern: String, private var fla parseError("Unknown look-behind group") requireES2018Features("Look-behind group") pIndex = start + 4 - pattern.substring(start, start + 4) + compileInsideGroup() + ")" + pattern.jsSubstring(start, start + 4) + compileInsideGroup() + ")" } } else if (c1 == '>') { // Atomic group @@ -1846,6 +1869,6 @@ private final class PatternCompiler(private val pattern: String, private var fla pIndex += 1 if (pIndex == len || pattern.charAt(pIndex) != '>') parseError("named capturing group is missing trailing '>'") - pattern.substring(start, pIndex) + pattern.jsSubstring(start, pIndex) } } diff --git a/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala b/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala index 0faf78fafc..e0bd4e1223 100644 --- a/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala +++ b/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala @@ -33,24 +33,8 @@ class PatternSyntaxException(desc: String, regex: String, index: Int) val base = desc + indexHint + "\n" + re if (idx >= 0 && re != null && idx < re.length()) - base + "\n" + repeat(" ", idx) + "^" + base + "\n" + " ".asInstanceOf[java.lang._String].repeat(idx) + "^" else base } - - @inline - private def repeat(s: String, count: Int): String = { - // TODO Use java.lang.String.repeat() once we can (JDK 11+ method) - if (LinkingInfo.esVersion >= LinkingInfo.ESVersion.ES2015) { - s.asInstanceOf[js.Dynamic].repeat(count).asInstanceOf[String] - } else { - var result = "" - var i = 0 - while (i != count) { - result += s - i += 1 - } - result - } - } } diff --git a/javalib/src/main/scala/org/scalajs/javalibintf/StackTraceElement.scala b/javalib/src/main/scala/org/scalajs/javalibintf/StackTraceElement.scala new file mode 100644 index 0000000000..5237986051 --- /dev/null +++ b/javalib/src/main/scala/org/scalajs/javalibintf/StackTraceElement.scala @@ -0,0 +1,26 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.javalibintf + +import java.{lang => jl} + +object StackTraceElement { + def createWithColumnNumber(declaringClass: String, methodName: String, + fileName: String, lineNumber: Int, columnNumber: Int): jl.StackTraceElement = { + new jl.StackTraceElement(declaringClass, methodName, fileName, + lineNumber, columnNumber) + } + + def getColumnNumber(stackTraceElement: jl.StackTraceElement): Int = + stackTraceElement.getColumnNumber() +} diff --git a/javalib/src/main/scala/org/scalajs/javalibintf/TypedArrayBuffer.scala b/javalib/src/main/scala/org/scalajs/javalibintf/TypedArrayBuffer.scala new file mode 100644 index 0000000000..7fbe4d23c6 --- /dev/null +++ b/javalib/src/main/scala/org/scalajs/javalibintf/TypedArrayBuffer.scala @@ -0,0 +1,56 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.javalibintf + +import java.nio._ + +import scala.scalajs.js.typedarray._ + +object TypedArrayBuffer { + + def wrapInt8Array(array: Any): ByteBuffer = + ByteBuffer.wrapInt8Array(array.asInstanceOf[Int8Array]) + + def wrapUint16Array(array: Any): CharBuffer = + CharBuffer.wrapUint16Array(array.asInstanceOf[Uint16Array]) + + def wrapInt16Array(array: Any): ShortBuffer = + ShortBuffer.wrapInt16Array(array.asInstanceOf[Int16Array]) + + def wrapInt32Array(array: Any): IntBuffer = + IntBuffer.wrapInt32Array(array.asInstanceOf[Int32Array]) + + def wrapFloat32Array(array: Any): FloatBuffer = + FloatBuffer.wrapFloat32Array(array.asInstanceOf[Float32Array]) + + def wrapFloat64Array(array: Any): DoubleBuffer = + DoubleBuffer.wrapFloat64Array(array.asInstanceOf[Float64Array]) + + def hasArrayBuffer(buffer: Buffer): Boolean = + buffer.hasArrayBuffer() + + def arrayBuffer(buffer: Buffer): Any = + buffer.arrayBuffer() + + def arrayBufferOffset(buffer: Buffer): Int = + buffer.arrayBufferOffset() + + def dataView(buffer: Buffer): Any = + buffer.dataView() + + def hasTypedArray(buffer: Buffer): Boolean = + buffer.hasTypedArray() + + def typedArray(buffer: Buffer): Any = + buffer.typedArray() +} diff --git a/javalib/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferBridge.scala b/javalib/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferBridge.scala deleted file mode 100644 index c692870f9a..0000000000 --- a/javalib/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferBridge.scala +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Scala.js (https://www.scala-js.org/) - * - * Copyright EPFL. - * - * Licensed under Apache License 2.0 - * (https://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -/* !!!!! - * THIS FILE IS ALMOST COPY-PASTED IN javalib/ AND library/. - * THEY MUST BE KEPT IN SYNC. - * - * This file acts as bridge for scala.scalajs.js.typedarray to be able to - * access the additional public API provided by java.nio, but which is not - * part of the JDK API. Because javalib/ does not export its .class files, - * we cannot call this additional API directly from library/, even though the - * members are public. - * - * In library/, this file has only the signatures, with stub implementations. - * In javalib/, it has the proper the proper implementations. - * The build keeps the .class coming from library/ and the .sjsir file from - * javalib/. This way, we bridge the library and javalib. But that means the - * binary interface of TypedArrayBufferBridge must be strictly equivalent in - * the two copies. - * - * (Yes, this is a hack.) - * !!!!! - */ - -package scala.scalajs.js.typedarray - -import java.nio._ - -private[typedarray] object TypedArrayBufferBridge { - def wrap(array: ArrayBuffer): ByteBuffer = - ByteBuffer.wrap(array) - - def wrap(array: ArrayBuffer, byteOffset: Int, length: Int): ByteBuffer = - ByteBuffer.wrap(array, byteOffset, length) - - def wrap(array: Int8Array): ByteBuffer = - ByteBuffer.wrap(array) - - def wrap(array: Uint16Array): CharBuffer = - CharBuffer.wrap(array) - - def wrap(array: Int16Array): ShortBuffer = - ShortBuffer.wrap(array) - - def wrap(array: Int32Array): IntBuffer = - IntBuffer.wrap(array) - - def wrap(array: Float32Array): FloatBuffer = - FloatBuffer.wrap(array) - - def wrap(array: Float64Array): DoubleBuffer = - DoubleBuffer.wrap(array) - - def Buffer_hasArrayBuffer(buffer: Buffer): Boolean = - buffer.hasArrayBuffer() - - def Buffer_arrayBuffer(buffer: Buffer): ArrayBuffer = - buffer.arrayBuffer() - - def Buffer_arrayBufferOffset(buffer: Buffer): Int = - buffer.arrayBufferOffset() - - def Buffer_dataView(buffer: Buffer): DataView = - buffer.dataView() - - def Buffer_hasTypedArray(buffer: Buffer): Boolean = - buffer.hasTypedArray() - - def Buffer_typedArray(buffer: Buffer): TypedArray[_, _] = - buffer.typedArray() -} diff --git a/javalibintf/src/main/java/org/scalajs/javalibintf/StackTraceElement.java b/javalibintf/src/main/java/org/scalajs/javalibintf/StackTraceElement.java new file mode 100644 index 0000000000..b2cdb29b62 --- /dev/null +++ b/javalibintf/src/main/java/org/scalajs/javalibintf/StackTraceElement.java @@ -0,0 +1,74 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.javalibintf; + +/** + * Scala.js-specific extensions for {@link java.lang.StackTraceElement}. + * + *

In the JavaScript ecosystem, it is common practice for stack traces to + * mention column numbers in addition to line numbers. The official API of + * {@link java.lang.StackTraceElement} does not allow for representing column + * numbers, but Scala.js supports them. + * + *

This class offers methods to manipulate the extended information of + * {@link java.lang.StackTraceElement} for Scala.js. + * + *

This class only contains static methods. It cannot be instantiated. + * + * @see java.lang.StackTraceElement + */ +public final class StackTraceElement { + private StackTraceElement() {} + + /** + * Creates a {@link java.lang.StackTraceElement} that includes a column number. + * + * @param declaringClass + * the fully qualified name of the class containing the execution point + * represented by the stack trace element + * @param methodName + * the name of the method containing the execution point represented by the + * stack trace element + * @param fileName + * the name of the file containing the execution point represented by the + * stack trace element, or null if this information is unavailable + * @param lineNumber + * the line number of the source line containing the execution point + * represented by this stack trace element, or a negative number if this + * information is unavailable + * @param columnNumber + * the column number within the source line containing the execution point + * represented by this stack trace element, or a negative number if this + * information is unavailable + * + * @return + * a new {@link java.lang.StackTraceElement} containing the provided information + */ + public static final java.lang.StackTraceElement createWithColumnNumber( + String declaringClass, String methodName, String fileName, + int lineNumber, int columnNumber) { + throw new AssertionError("stub"); + } + + /** + * Returns the column number of the provided {@link java.lang.StackTraceElement}. + * + * @return + * the column number of the provided stackTraceElement, or a negative + * number if this information is unavailable + */ + public static final int getColumnNumber( + java.lang.StackTraceElement stackTraceElement) { + throw new AssertionError("stub"); + } +} diff --git a/javalibintf/src/main/java/org/scalajs/javalibintf/TypedArrayBuffer.java b/javalibintf/src/main/java/org/scalajs/javalibintf/TypedArrayBuffer.java new file mode 100644 index 0000000000..436b6d7fec --- /dev/null +++ b/javalibintf/src/main/java/org/scalajs/javalibintf/TypedArrayBuffer.java @@ -0,0 +1,315 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.javalibintf; + +import java.nio.*; + +/** + * Utilities to interface {@link java.nio.Buffer}s and JavaScript TypedArrays. + * + *

{@link java.nio.Buffer}s can be direct buffers or + * indirect buffers. Indirect buffers use an underlying array (like + * {@code int[]} in Java or {@code Array[Int]} in Scala). Direct buffers are + * supposed to use off-heap memory. + * + *

In a JavaScript environment, the equivalent of off-heap memory for + * buffers of primitive numeric types are TypedArrays. + * + *

This class provides methods to wrap TypedArrays as direct Buffers, and + * extract references to TypedArrays from direct Buffers. + */ +public final class TypedArrayBuffer { + private TypedArrayBuffer() {} + + /** + * Wraps a JavaScript {@code Int8Array} as a direct + * {@link java.nio.ByteBuffer}. + * + *

The provided {@code array} parameter must be a valid JavaScript + * {@code Int8Array}, otherwise the behavior of this method is not + * specified. + * + *

The returned {@link java.nio.ByteBuffer} has the following properties: + * + *

    + *
  • It has a {@code capacity()} equal to the {@code array.length}.
  • + *
  • Its initial {@code position()} is 0 and its {@code limit()} is its capacity.
  • + *
  • It is a direct buffer backed by the provided {@code Int8Array}: + * changes to one are reflected on the other.
  • + *
+ * + * @param array a JavaScript {@code Int8Array} + */ + public static final ByteBuffer wrapInt8Array(Object array) { + throw new AssertionError("stub"); + } + + /** + * Wraps a JavaScript {@code Uint16Array} as a direct + * {@link java.nio.CharBuffer}. + * + *

The provided {@code array} parameter must be a valid JavaScript + * {@code Uint16Array}, otherwise the behavior of this method is not + * specified. + * + *

The returned {@link java.nio.CharBuffer} has the following properties: + * + *

    + *
  • It has a {@code capacity()} equal to the {@code array.length}.
  • + *
  • Its initial {@code position()} is 0 and its {@code limit()} is its capacity.
  • + *
  • It is a direct buffer backed by the provided {@code Uint16Array}: + * changes to one are reflected on the other.
  • + *
+ * + * @param array a JavaScript {@code Uint16Array} + */ + public static final CharBuffer wrapUint16Array(Object array) { + throw new AssertionError("stub"); + } + + /** + * Wraps a JavaScript {@code Int16Array} as a direct + * {@link java.nio.ShortBuffer}. + * + *

The provided {@code array} parameter must be a valid JavaScript + * {@code Int16Array}, otherwise the behavior of this method is not + * specified. + * + *

The returned {@link java.nio.ShortBuffer} has the following properties: + * + *

    + *
  • It has a {@code capacity()} equal to the {@code array.length}.
  • + *
  • Its initial {@code position()} is 0 and its {@code limit()} is its capacity.
  • + *
  • It is a direct buffer backed by the provided {@code Int16Array}: + * changes to one are reflected on the other.
  • + *
+ * + * @param array a JavaScript {@code Int16Array} + */ + public static final ShortBuffer wrapInt16Array(Object array) { + throw new AssertionError("stub"); + } + + /** + * Wraps a JavaScript {@code Int32Array} as a direct + * {@link java.nio.IntBuffer}. + * + *

The provided {@code array} parameter must be a valid JavaScript + * {@code Int32Array}, otherwise the behavior of this method is not + * specified. + * + *

The returned {@link java.nio.IntBuffer} has the following properties: + * + *

    + *
  • It has a {@code capacity()} equal to the {@code array.length}.
  • + *
  • Its initial {@code position()} is 0 and its {@code limit()} is its capacity.
  • + *
  • It is a direct buffer backed by the provided {@code Int32Array}: + * changes to one are reflected on the other.
  • + *
+ * + * @param array a JavaScript {@code Int32Array} + */ + public static final IntBuffer wrapInt32Array(Object array) { + throw new AssertionError("stub"); + } + + /** + * Wraps a JavaScript {@code Float32Array} as a direct + * {@link java.nio.FloatBuffer}. + * + *

The provided {@code array} parameter must be a valid JavaScript + * {@code Float32Array}, otherwise the behavior of this method is not + * specified. + * + *

The returned {@link java.nio.FloatBuffer} has the following properties: + * + *

    + *
  • It has a {@code capacity()} equal to the {@code array.length}.
  • + *
  • Its initial {@code position()} is 0 and its {@code limit()} is its capacity.
  • + *
  • It is a direct buffer backed by the provided {@code Float32Array}: + * changes to one are reflected on the other.
  • + *
+ * + * @param array a JavaScript {@code Float32Array} + */ + public static final FloatBuffer wrapFloat32Array(Object array) { + throw new AssertionError("stub"); + } + + /** + * Wraps a JavaScript {@code Float64Array} as a direct + * {@link java.nio.DoubleBuffer}. + * + *

The provided {@code array} parameter must be a valid JavaScript + * {@code Float64Array}, otherwise the behavior of this method is not + * specified. + * + *

The returned {@link java.nio.DoubleBuffer} has the following properties: + * + *

    + *
  • It has a {@code capacity()} equal to the {@code array.length}.
  • + *
  • Its initial {@code position()} is 0 and its {@code limit()} is its capacity.
  • + *
  • It is a direct buffer backed by the provided {@code Float64Array}: + * changes to one are reflected on the other.
  • + *
+ * + * @param array a JavaScript {@code Float64Array} + */ + public static final DoubleBuffer wrapFloat64Array(Object array) { + throw new AssertionError("stub"); + } + + /** + * Tests whether the given {@link java.nio.Buffer} is backed by an accessible + * JavaScript {@code ArrayBuffer}. + * + *

This is true for all read-write direct buffers, in particular for those + * created with any of the {@code wrapX} methods of this class. + * + *

If this method returns {@code true}, then {@code arrayBuffer(buffer)}, + * {@code arrayBufferOffset(buffer)} and {@code dataView(buffer)} do not + * throw any {@link UnsupportedOperationException}. + * + * @return + * true if and only if the provided {@code buffer} is backed by an + * accessible JavaScript {@code ArrayBuffer} + * + * @see TypedArrayBuffer#arrayBuffer(Buffer) + * @see TypedArrayBuffer#arrayBufferOffset(Buffer) + * @see TypedArrayBuffer#dataView(Buffer) + */ + public static final boolean hasArrayBuffer(Buffer buffer) { + throw new AssertionError("stub"); + } + + /** + * Returns the JavaScript {@code ArrayBuffer} backing the provided + * {@link java.nio.Buffer}. + * + *

The {@code buffer} may represent a view of the returned + * {@code ArrayBuffer} that does not start at index 0. Use the method + * {@link TypedArrayBuffer#arrayBufferOffset(Buffer)} to retrieve the offset + * within the {@code ArrayBuffer}. + * + * @return + * the JavaScript {@code ArrayBuffer} backing the provided {@code buffer} + * + * @throws UnsupportedOperationException + * if the provided {@code buffer} is read-only or is not backed by a + * JavaScript {@code ArrayBuffer}, i.e., if {@code hasArrayBuffer(buffer)} + * returns {@code false} + * + * @see TypedArrayBuffer#hasArrayBuffer(Buffer) + * @see TypedArrayBuffer#arrayBufferOffset(Buffer) + */ + public static final Object arrayBuffer(Buffer buffer) throws UnsupportedOperationException { + throw new AssertionError("stub"); + } + + /** + * Returns the offset within the JavaScript {@code ArrayBuffer} backing the + * provided {@link java.nio.Buffer}. + * + * @return + * the offset within the JavaScript {@code ArrayBuffer} backing the + * provided {@code buffer} where the latter starts + * + * @throws UnsupportedOperationException + * if the provided {@code buffer} is read-only or is not backed by a + * JavaScript {@code ArrayBuffer}, i.e., if {@code hasArrayBuffer(buffer)} + * returns {@code false} + * + * @see TypedArrayBuffer#hasArrayBuffer(Buffer) + * @see TypedArrayBuffer#arrayBuffer(Buffer) + */ + public static final int arrayBufferOffset(Buffer buffer) throws UnsupportedOperationException { + throw new AssertionError("stub"); + } + + /** + * Returns a JavaScript {@code DataView} of the provided + * {@link java.nio.Buffer}. + * + * @return + * a JavaScript {@code DataView} of the provided {@code buffer} + * + * @throws UnsupportedOperationException + * if the provided {@code buffer} is read-only or is not backed by a + * JavaScript {@code ArrayBuffer}, i.e., if {@code hasArrayBuffer(buffer)} + * returns {@code false} + * + * @see TypedArrayBuffer#hasArrayBuffer(Buffer) + */ + public static final Object dataView(Buffer buffer) throws UnsupportedOperationException { + throw new AssertionError("stub"); + } + + /** + * Tests whether the given {@link java.nio.Buffer} is backed by an accessible + * JavaScript {@code TypedArray}. + * + *

This is true when all of the following conditions apply: + * + *

    + *
  • the buffer is a direct buffer,
  • + *
  • it is not read-only,
  • + *
  • its byte order corresponds to the native byte order of JavaScript + * {@code TypedArray}s, and
  • + *
  • it is not a {@link java.nio.LongBuffer}.
  • + *
+ * + *

In particular, it is true for all {@link java.nio.Buffer}s created with + * any of the {@code wrapXArray} methods of this class. + * + *

If this method returns {@code true}, then {@code typedArray(buffer)} + * does not throw any {@link UnsupportedOperationException}. + * + * @return + * true if and only if the provided {@code buffer} is backed by an + * accessible JavaScript {@code TypedArray} + * + * @see TypedArrayBuffer#typedArray(Buffer) + */ + public static final boolean hasTypedArray(Buffer buffer) { + throw new AssertionError("stub"); + } + + /** + * Returns a JavaScript {@code TypedArray} view of the provided + * {@link java.nio.Buffer}. + * + *

The particular type of {@code TypedArray} depends on the type of buffer: + * + *

    + *
  • an {@code Int8Array} for a {@link java.nio.ByteBuffer}
  • + *
  • a {@code Uint16Array} for a {@link java.nio.CharBuffer}
  • + *
  • an {@code Int16Array} for a {@link java.nio.ShortBuffer}
  • + *
  • an {@code Int32Array} for a {@link java.nio.IntBuffer}
  • + *
  • an {@code Float32Array} for a {@link java.nio.FloatBuffer}
  • + *
  • an {@code Float64Array} for a {@link java.nio.DoubleBuffer}
  • + *
+ * + * @return + * a JavaScript {@code TypedArray} view of the provided {@code buffer} + * + * @throws UnsupportedOperationException + * if the provided {@code buffer} is read-only or is not backed by a + * JavaScript {@code TypedArray}, i.e., if {@code hasTypedArray(buffer)} + * returns {@code false} + * + * @see TypedArrayBuffer#hasTypedArray(Buffer) + */ + public static final Object typedArray(Buffer buffer) throws UnsupportedOperationException { + throw new AssertionError("stub"); + } +} diff --git a/junit-runtime/src/main/scala/org/junit/Assume.scala b/junit-runtime/src/main/scala/org/junit/Assume.scala index ba9bdf8011..3d44f8be5d 100644 --- a/junit-runtime/src/main/scala/org/junit/Assume.scala +++ b/junit-runtime/src/main/scala/org/junit/Assume.scala @@ -33,13 +33,13 @@ object Assume { @noinline def assumeThat[T](actual: T, matcher: Matcher[T]): Unit = { if (!matcher.matches(actual.asInstanceOf[AnyRef])) - throw new AssumptionViolatedException(actual, matcher) + throw new AssumptionViolatedException(null, matcher, actual) } @noinline def assumeThat[T](message: String, actual: T, matcher: Matcher[T]): Unit = { if (!matcher.matches(actual.asInstanceOf[AnyRef])) - throw new AssumptionViolatedException(message, actual, matcher) + throw new AssumptionViolatedException(message, matcher, actual) } @noinline diff --git a/junit-runtime/src/main/scala/org/junit/AssumptionViolatedException.scala b/junit-runtime/src/main/scala/org/junit/AssumptionViolatedException.scala index a2adc0db01..315bcfa0e3 100644 --- a/junit-runtime/src/main/scala/org/junit/AssumptionViolatedException.scala +++ b/junit-runtime/src/main/scala/org/junit/AssumptionViolatedException.scala @@ -19,6 +19,10 @@ class AssumptionViolatedException protected (fAssumption: String, def this(message: String, expected: Any, matcher: Matcher[_]) = this(message, true, fMatcher = matcher, fValue = expected.asInstanceOf[AnyRef]) + // Non-deprecated access to the full constructor for use in `Assume.scala` + private[junit] def this(message: String, matcher: Matcher[_], actual: Any) = + this(message, true, fMatcher = matcher, fValue = actual.asInstanceOf[AnyRef]) + def this(message: String) = this(message, false, null, null) diff --git a/junit-runtime/src/main/scala/org/scalajs/junit/Reporter.scala b/junit-runtime/src/main/scala/org/scalajs/junit/Reporter.scala index 4673a4cf9e..015c328818 100644 --- a/junit-runtime/src/main/scala/org/scalajs/junit/Reporter.scala +++ b/junit-runtime/src/main/scala/org/scalajs/junit/Reporter.scala @@ -37,7 +37,7 @@ private[junit] final class Reporter(eventHandler: EventHandler, def reportIgnored(method: Option[String]): Unit = { logTestInfo(_.info, method, "ignored") - emitEvent(method, Status.Skipped) + emitEvent(method, Status.Skipped, 0, None) } def reportTestStarted(method: String): Unit = @@ -47,7 +47,7 @@ private[junit] final class Reporter(eventHandler: EventHandler, logTestInfo(_.debug, Some(method), s"finished, took $timeInSeconds sec") if (succeeded) - emitEvent(Some(method), Status.Success) + emitEvent(Some(method), Status.Success, timeInSeconds, None) } def reportErrors(prefix: String, method: Option[String], @@ -59,7 +59,7 @@ private[junit] final class Reporter(eventHandler: EventHandler, if (errors.nonEmpty) { emit(errors.head) - emitEvent(method, Status.Failure) + emitEvent(method, Status.Failure, timeInSeconds, Some(errors.head)) errors.tail.foreach(emit) } } @@ -67,7 +67,7 @@ private[junit] final class Reporter(eventHandler: EventHandler, def reportAssumptionViolation(method: Option[String], timeInSeconds: Double, e: Throwable): Unit = { logTestException(_.warn, "Test assumption in test ", method, e, timeInSeconds) - emitEvent(method, Status.Skipped) + emitEvent(method, Status.Skipped, timeInSeconds, Some(e)) } private def logTestInfo(level: Reporter.Level, method: Option[String], msg: String): Unit = @@ -114,11 +114,18 @@ private[junit] final class Reporter(eventHandler: EventHandler, prefix + Ansi.c(name, color) } - private def emitEvent(method: Option[String], status: Status): Unit = { + private def emitEvent( + method: Option[String], + status: Status, + timeInSeconds: Double, + throwable: Option[Throwable] + ): Unit = { val testName = method.fold(taskDef.fullyQualifiedName())(method => taskDef.fullyQualifiedName() + "." + settings.decodeName(method)) val selector = new TestSelector(testName) - eventHandler.handle(new JUnitEvent(taskDef, status, selector)) + val optionalThrowable: OptionalThrowable = new OptionalThrowable(throwable.orNull) + val duration: Long = (timeInSeconds*1000).toLong + eventHandler.handle(new JUnitEvent(taskDef, status, selector, optionalThrowable, duration)) } def log(level: Reporter.Level, s: String): Unit = { diff --git a/junit-test/outputs/org/scalajs/junit/AssertEquals2TestAssertions_.txt b/junit-test/outputs/org/scalajs/junit/AssertEquals2TestAssertions_.txt index 2a161db9ae..1890d11297 100644 --- a/junit-test/outputs/org/scalajs/junit/AssertEquals2TestAssertions_.txt +++ b/junit-test/outputs/org/scalajs/junit/AssertEquals2TestAssertions_.txt @@ -1,7 +1,7 @@ ldTest run started ldTest org.scalajs.junit.AssertEquals2Test.test started leTest org.scalajs.junit.AssertEquals2Test.test failed: This is the message expected: but was:, took