From d5021d2d0d8281169df49790b8a8c7d7dc406fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 18 Jun 2019 08:52:45 +0200 Subject: [PATCH 0001/1606] [no-master] Fix 4 erroneous comparisons of `RefinedType` with `Type`. Discovered by new warnings emitted by the 2.13.0 compiler, yeah! --- .../tools/linker/frontend/optimizer/OptimizerCore.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/OptimizerCore.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/OptimizerCore.scala index 55a6375ace..bcff93ed96 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/OptimizerCore.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/OptimizerCore.scala @@ -2679,7 +2679,7 @@ private[optimizer] abstract class OptimizerCore( } case DoubleToInt => arg match { - case _ if arg.tpe == IntType => + case _ if arg.tpe.base == IntType => arg case PreTransLit(NumberLiteral(v)) => PreTransLit(IntLiteral(v.toInt)) @@ -2688,7 +2688,7 @@ private[optimizer] abstract class OptimizerCore( } case DoubleToFloat => arg match { - case _ if arg.tpe == FloatType => + case _ if arg.tpe.base == FloatType => arg case PreTransLit(NumberLiteral(v)) => PreTransLit(FloatLiteral(v.toFloat)) @@ -2697,7 +2697,7 @@ private[optimizer] abstract class OptimizerCore( } case DoubleToLong => arg match { - case _ if arg.tpe == IntType => + case _ if arg.tpe.base == IntType => foldUnaryOp(IntToLong, arg) case PreTransLit(NumberLiteral(v)) => PreTransLit(LongLiteral(v.toLong)) @@ -2923,7 +2923,7 @@ private[optimizer] abstract class OptimizerCore( PreTransLit(StringLiteral(s1 + s2)) case (_, PreTransLit(StringLiteral(""))) => foldBinaryOp(op, rhs1, lhs1) - case (PreTransLit(StringLiteral("")), _) if rhs1.tpe == StringType => + case (PreTransLit(StringLiteral("")), _) if rhs1.tpe.base == StringType => rhs1 case (_, PreTransBinaryOp(String_+, rl, rr)) => foldBinaryOp(String_+, PreTransBinaryOp(String_+, lhs1, rl), rr) From 45cf1cff7e52fef6e280e066a11d60df7295b3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 17 Jun 2019 17:46:45 +0200 Subject: [PATCH 0002/1606] [no-master] Remove `@deprecatedName`s that were introduced in 0.6.16. It is not possible to cross-compile `@deprecatedName`s without warnings between Scala 2.12- and 2.13+, because: * In 2.12, they require symbol literals as arguments * In 2.13, symbol literals are deprecated Since they were deprecated more than two years ago, and are part of an API that is not used much anyway (JS envs), I believe it is fair to drop the source compatibility in this case. --- js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala | 2 -- .../scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala | 3 --- .../main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala | 3 --- .../src/main/scala/org/scalajs/jsenv/nodejs/NodeJSEnv.scala | 3 --- .../main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala | 3 --- 5 files changed, 14 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala index d23742ed44..d5c846f8d7 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala @@ -24,9 +24,7 @@ import scala.concurrent.{Future, Promise} import scala.util.Try abstract class ExternalJSEnv( - @deprecatedName('additionalArgs) final protected val args: Seq[String], - @deprecatedName('additionalEnv) final protected val env: Map[String, String]) extends AsyncJSEnv { diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala index e1e63e9bad..2a37b2a3db 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala @@ -29,11 +29,8 @@ import scala.concurrent.TimeoutException import scala.concurrent.duration._ abstract class AbstractNodeJSEnv( - @deprecatedName('nodejsPath) protected val executable: String, - @deprecatedName('addArgs) args: Seq[String], - @deprecatedName('addEnv) env: Map[String, String], val sourceMap: Boolean) extends ExternalJSEnv(args, env) with ComJSEnv { diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala index 5e334102c0..a363ab1263 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala @@ -29,11 +29,8 @@ class JSDOMNodeJSEnv private[jsenv] ( @deprecated("Use org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv.", "0.6.18") def this( - @deprecatedName('nodejsPath) executable: String = "node", - @deprecatedName('addArgs) args: Seq[String] = Seq.empty, - @deprecatedName('addEnv) env: Map[String, String] = Map.empty ) = { this(executable, args, env, internal = ()) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/NodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/NodeJSEnv.scala index 99e163ef14..77a50de03b 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/NodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/NodeJSEnv.scala @@ -30,11 +30,8 @@ class NodeJSEnv(config: NodeJSEnv.Config) @deprecated("Use the overload with a NodeJSEnv.Config.", "0.6.18") def this( - @deprecatedName('nodejsPath) executable: String = "node", - @deprecatedName('addArgs) args: Seq[String] = Seq.empty, - @deprecatedName('addEnv) env: Map[String, String] = Map.empty) = { this(NodeJSEnv.Config().withExecutable(executable).withArgs(args.toList).withEnv(env)) } diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index 18d69c13f4..b2f55c5342 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -40,11 +40,8 @@ class PhantomJSEnv(config: PhantomJSEnv.Config) @deprecated("Use the overload with a PhantomJSEnv.Config.", "0.6.18") def this( - @deprecatedName('phantomjsPath) executable: String = "phantomjs", - @deprecatedName('addArgs) args: Seq[String] = Seq.empty, - @deprecatedName('addEnv) env: Map[String, String] = Map.empty, autoExit: Boolean = true, jettyClassLoader: ClassLoader = null From c4d50703bc53ea69b855afcf6df2277a3ab50bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 18 Jun 2019 13:49:59 +0200 Subject: [PATCH 0003/1606] [no-master] Upgrade to scopt 3.7.1. This is the first version that supports Scala 2.13.0 (and also the latest stable version as of this writing). --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 9aa2f2b1de..02d2428607 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1255,7 +1255,7 @@ object Build { ) ++ Seq( name := "Scala.js CLI", libraryDependencies ++= Seq( - "com.github.scopt" %% "scopt" % "3.5.0" + "com.github.scopt" %% "scopt" % "3.7.1" ), previousArtifactSetting, From fd2343eb9ca5f9e8655d567255a805292d4f5087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 18 Jun 2019 14:54:02 +0200 Subject: [PATCH 0004/1606] [no-master] Enable Scala 2.13.0 in the tools and all the other artifacts. Partest is not yet enabled in this commit, because a dependency of `partest` 2.13.0, namely `testkit`, was not published. See https://github.com/scala/bug/issues/11529 upstream. --- Jenkinsfile | 8 +- .../scala/org/scalajs/core/ir/Infos.scala | 8 +- .../org/scalajs/jsenv/test/TimeoutTests.scala | 2 +- .../org/scalajs/jsenv/ExternalJSEnv.scala | 14 ++- .../jsenv/VirtualFileMaterializer.scala | 2 +- .../jsenv/nodejs/AbstractNodeJSEnv.scala | 16 +++- .../scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala | 2 +- .../jsenv/phantomjs/PhantomJSEnv.scala | 2 +- .../scala/tools/nsc/MainGenericRunner.scala | 2 +- project/BinaryIncompatibilities.scala | 12 +++ project/Build.scala | 16 +++- project/build.sbt | 1 + .../resources/2.13.0-M3/WhitelistedTests.txt | 45 ---------- .../BlacklistedTests.txt | 84 ++++++++++++++--- .../resources/2.13.0/WhitelistedTests.txt | 90 +++++++++++++++++++ scripts/assemble-cli.sh | 4 + scripts/publish.sh | 15 +--- .../core/tools/test/js/QuickLinker.scala | 5 +- .../io/IRContainerPlatformExtensions.scala | 6 +- .../org/scalajs/core/tools/json/Impl.scala | 40 +++++++-- .../backend/closure/ClosureAstBuilder.scala | 7 +- .../closure/ClosureLinkerBackend.scala | 6 +- .../frontend/optimizer/ParIncOptimizer.scala | 18 +++- .../core/tools/jsdep/CollectionsCompat.scala | 17 ++++ .../core/tools/jsdep/CollectionsCompat.scala | 17 ++++ .../tools/jsdep/ComplianceRequirement.scala | 9 +- .../core/tools/jsdep/DependencyResolver.scala | 45 +++++----- .../core/tools/jsdep/ManifestFilters.scala | 4 +- .../core/tools/json/JSONDeserializer.scala | 2 +- .../core/tools/json/JSONSerializer.scala | 2 +- .../core/tools/linker/CollectionsCompat.scala | 32 +++++++ .../core/tools/linker/analyzer/Analysis.scala | 14 +-- .../core/tools/linker/analyzer/Analyzer.scala | 2 +- .../linker/analyzer/SymbolRequirement.scala | 26 +++++- .../linker/backend/emitter/Emitter.scala | 13 +-- .../backend/emitter/KnowledgeGuardian.scala | 3 +- .../core/tools/linker/checker/IRChecker.scala | 8 +- .../tools/linker/checker/InfoChecker.scala | 14 ++- .../frontend/optimizer/GenIncOptimizer.scala | 70 ++++++++------- .../frontend/optimizer/IncOptimizer.scala | 21 +++-- .../scalajs/core/tools/sem/Semantics.scala | 10 ++- 41 files changed, 508 insertions(+), 206 deletions(-) delete mode 100644 scala-test-suite/src/test/resources/2.13.0-M3/WhitelistedTests.txt rename scala-test-suite/src/test/resources/{2.13.0-M3 => 2.13.0}/BlacklistedTests.txt (68%) create mode 100644 scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt create mode 100644 tools/shared/src/main/scala-new-collections/org/scalajs/core/tools/jsdep/CollectionsCompat.scala create mode 100644 tools/shared/src/main/scala-old-collections/org/scalajs/core/tools/jsdep/CollectionsCompat.scala create mode 100644 tools/shared/src/main/scala/org/scalajs/core/tools/linker/CollectionsCompat.scala diff --git a/Jenkinsfile b/Jenkinsfile index c558ecdb77..4556d847b4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -434,7 +434,7 @@ def allJavaVersions = otherJavaVersions.clone() allJavaVersions << mainJavaVersion def mainScalaVersion = "2.12.8" -def mainScalaVersions = ["2.10.2", "2.11.12", "2.12.8"] +def mainScalaVersions = ["2.10.2", "2.11.12", "2.12.8", "2.13.0"] def otherScalaVersions = [ "2.10.3", "2.10.4", @@ -460,7 +460,6 @@ def otherScalaVersions = [ "2.12.6", "2.12.7" ] -def noToolsScalaVersions = ["2.13.0"] // The 'quick' matrix def quickMatrix = [] @@ -479,11 +478,6 @@ mainScalaVersions.each { scalaVersion -> quickMatrix.add([task: "partest-fastopt", scala: scalaVersion, java: mainJavaVersion]) } } -noToolsScalaVersions.each { scalaVersion -> - quickMatrix.add([task: "main", scala: scalaVersion, java: mainJavaVersion]) - quickMatrix.add([task: "test-suite-ecma-script5", scala: scalaVersion, java: mainJavaVersion, testSuite: "testSuite"]) - quickMatrix.add([task: "test-suite-ecma-script6", scala: scalaVersion, java: mainJavaVersion, testSuite: "testSuite"]) -} allJavaVersions.each { javaVersion -> quickMatrix.add([task: "tools-cli-stubs-sbtplugin", scala: "2.12.8", sbt_version_override: "", java: javaVersion]) } diff --git a/ir/src/main/scala/org/scalajs/core/ir/Infos.scala b/ir/src/main/scala/org/scalajs/core/ir/Infos.scala index fcb517c608..73683ede41 100644 --- a/ir/src/main/scala/org/scalajs/core/ir/Infos.scala +++ b/ir/src/main/scala/org/scalajs/core/ir/Infos.scala @@ -138,6 +138,12 @@ object Infos { this } + def addInterfaces(interfaces: List[String]): this.type = { + this.interfaces ++= interfaces + this + } + + @deprecated("Use the overload with a List.", "0.6.29") def addInterfaces(interfaces: TraversableOnce[String]): this.type = { this.interfaces ++= interfaces this @@ -276,7 +282,7 @@ object Infos { def result(): MethodInfo = { def toMapOfLists( m: mutable.Map[String, mutable.Set[String]]): Map[String, List[String]] = { - m.mapValues(_.toList).toMap + m.map(kv => kv._1 -> kv._2.toList).toMap } MethodInfo( diff --git a/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/TimeoutTests.scala b/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/TimeoutTests.scala index cbe6dc9d63..83c8b26241 100644 --- a/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/TimeoutTests.scala +++ b/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/TimeoutTests.scala @@ -140,7 +140,7 @@ trait TimeoutTests extends JSEnvTest { if (c >= 10) clearInterval(i); }, 10); - """ hasOutput (1 to 10).map(_ + "\n").mkString + """ hasOutput (1 to 10).mkString("", "\n", "\n") assertTrue("Execution took too little time", deadline.isOverdue()) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala index d5c846f8d7..bef59e9208 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala @@ -81,8 +81,18 @@ abstract class ExternalJSEnv( * The default value in `ExternalJSEnv` is * `System.getenv().asScala.toMap ++ env`. */ - protected def getVMEnv(): Map[String, String] = - System.getenv().asScala.toMap ++ env + protected def getVMEnv(): Map[String, String] = { + /* We use Java's `forEach` not to depend on Scala's JavaConverters, which + * are very difficult to cross-compile across 2.12- and 2.13+. + */ + val builder = Map.newBuilder[String, String] + System.getenv().forEach(new java.util.function.BiConsumer[String, String] { + def accept(key: String, value: String): Unit = + builder += key -> value + }) + builder ++= env + builder.result() + } /** Get files that are a library (i.e. that do not run anything) */ protected def getLibJSFiles(): Seq[VirtualJSFile] = diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/VirtualFileMaterializer.scala b/js-envs/src/main/scala/org/scalajs/jsenv/VirtualFileMaterializer.scala index de386666a3..455c0d9cc9 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/VirtualFileMaterializer.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/VirtualFileMaterializer.scala @@ -77,7 +77,7 @@ final class VirtualFileMaterializer(singleDir: Boolean = false) { // scalastyle:on line.size.limit private def createTempDir(): File = { val baseDir = new File(System.getProperty("java.io.tmpdir")) - val baseName = System.currentTimeMillis() + "-" + val baseName = "" + System.currentTimeMillis() + "-" @tailrec def loop(tries: Int): File = { diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala index 2a37b2a3db..74eaa5c49a 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala @@ -168,10 +168,18 @@ abstract class AbstractNodeJSEnv( val nodePath = libCache.cacheDir.getAbsolutePath + baseNodePath.fold("")(p => File.pathSeparator + p) - System.getenv().asScala.toMap ++ Seq( - "NODE_MODULE_CONTEXTS" -> "0", - "NODE_PATH" -> nodePath - ) ++ env + /* We use Java's `forEach` not to depend on Scala's JavaConverters, which + * are very difficult to cross-compile across 2.12- and 2.13+. + */ + val builder = Map.newBuilder[String, String] + System.getenv().forEach(new java.util.function.BiConsumer[String, String] { + def accept(key: String, value: String): Unit = + builder += key -> value + }) + builder += "NODE_MODULE_CONTEXTS" -> "0" + builder += "NODE_PATH" -> nodePath + builder ++= env + builder.result() } } diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala index a363ab1263..09e0c3bb86 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala @@ -70,7 +70,7 @@ class JSDOMNodeJSEnv private[jsenv] ( case file => libCache.materialize(file) } val scriptsURIsAsJSStrings = scriptsFiles.map { file => - '"' + escapeJS(file.toURI.toASCIIString) + '"' + "\"" + escapeJS(file.toURI.toASCIIString) + "\"" } val scriptsURIsJSArray = scriptsURIsAsJSStrings.mkString("[", ", ", "]") diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala index b2f55c5342..dbf88f9656 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/phantomjs/PhantomJSEnv.scala @@ -524,7 +524,7 @@ class PhantomJSEnv(config: PhantomJSEnv.Config) case '>' => ">" case '"' => """ case '&' => "&" - case c => c :: Nil + case c => c.toString() } } diff --git a/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala b/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala index b456d40760..2a5d8a9822 100644 --- a/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala +++ b/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala @@ -53,7 +53,7 @@ class MainGenericRunner { val opt = Option(System.getProperty("scalajs.partest.compliantSems")) opt.fold(Semantics.Defaults) { str => val sems = str.split(',') - Semantics.compliantTo(sems.toList) + Semantics.compliantTo(sems.toSet) } } diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 4b09ac6158..0b8f2e8459 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -8,6 +8,18 @@ object BinaryIncompatibilities { ) val Tools = Seq( + // private[optimizer], not an issue + ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.core.tools.linker.frontend.optimizer.GenIncOptimizer#Class.subclasses_="), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.core.tools.linker.frontend.optimizer.GenIncOptimizer#Class.subclasses"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.core.tools.linker.frontend.optimizer.GenIncOptimizer#AbsCollOps.*"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.core.tools.linker.frontend.optimizer.GenIncOptimizer#AbsCollOps.*"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("org.scalajs.core.tools.linker.frontend.optimizer.GenIncOptimizer#AbsCollOps.*"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.core.tools.linker.frontend.optimizer.IncOptimizer#CollOps.*"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.core.tools.linker.frontend.optimizer.ParIncOptimizer#CollOps.*"), + + // private, not an issue + ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.core.tools.linker.checker.InfoChecker.this"), + ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.core.tools.linker.checker.IRChecker#CheckedClass.this") ) val JSEnvs = Seq( diff --git a/project/Build.scala b/project/Build.scala index 02d2428607..3e9f4d2232 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -405,7 +405,7 @@ object Build { scalaVersion: String): Seq[ModuleID] = { CrossVersion.partialVersion(scalaVersion) match { case Some((2, n)) if n >= 13 => - Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "0.1.2") + Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "0.2.0") case _ => Nil } @@ -605,6 +605,8 @@ object Build { unmanagedSourceDirectories in Compile += baseDirectory.value.getParentFile / "shared/src/main/scala", + unmanagedSourceDirectories in Compile += + collectionsEraDependentDirectory(scalaVersion.value, baseDirectory.value.getParentFile / "shared/src/main"), unmanagedSourceDirectories in Test += baseDirectory.value.getParentFile / "shared/src/test/scala", @@ -2051,6 +2053,18 @@ object Build { testOptions += Tests.Argument(TestFrameworks.JUnit, "-a", "-s"), + unmanagedSources in Compile ++= { + val scalaV = scalaVersion.value + val upstreamSrcDir = (fetchScalaSource in partest).value + + if (scalaV.startsWith("2.10.") || scalaV.startsWith("2.11.") || + scalaV.startsWith("2.12.")) { + Nil + } else { + List(upstreamSrcDir / "src/testkit/scala/tools/testkit/AssertUtil.scala") + } + }, + unmanagedSources in Test ++= { assert(scalaBinaryVersion.value != "2.10", "scalaTestSuite is not supported on Scala 2.10") diff --git a/project/build.sbt b/project/build.sbt index a28032cd1b..d58bb6645a 100644 --- a/project/build.sbt +++ b/project/build.sbt @@ -28,6 +28,7 @@ unmanagedSourceDirectories in Compile ++= { Seq( root / "ir/src/main/scala", root / "tools/shared/src/main/scala", + root / "tools/shared/src/main/scala-old-collections", root / "tools/jvm/src/main/scala", root / "js-envs/src/main/scala", root / "test-adapter/src/main/scala", diff --git a/scala-test-suite/src/test/resources/2.13.0-M3/WhitelistedTests.txt b/scala-test-suite/src/test/resources/2.13.0-M3/WhitelistedTests.txt deleted file mode 100644 index ff1cde1442..0000000000 --- a/scala-test-suite/src/test/resources/2.13.0-M3/WhitelistedTests.txt +++ /dev/null @@ -1,45 +0,0 @@ -scala/CharSequenceImplicitsTests.scala -scala/collection/CollectionConversionsTest.scala -scala/collection/IndexedSeqOptimizedTest.scala -scala/collection/IndexedSeqTest.scala -scala/collection/IterableViewLikeTest.scala -scala/collection/LinearSeqOptimizedTest.scala -scala/collection/ReusableBuildersTest.scala -scala/collection/SearchingTest.scala -scala/collection/SeqLikeTest.scala -scala/collection/TraversableLikeTest.scala -scala/collection/TraversableOnceTest.scala -scala/collection/convert/NullSafetyToJavaTest.scala -scala/collection/convert/NullSafetyToScalaTest.scala -scala/collection/immutable/HashMapTest.scala -scala/collection/immutable/ListMapTest.scala -scala/collection/immutable/QueueTest.scala -scala/collection/immutable/RangeConsistencyTest.scala -scala/collection/immutable/SetTest.scala -scala/collection/immutable/TreeMapTest.scala -scala/collection/immutable/TreeSetTest.scala -scala/collection/immutable/VectorTest.scala -scala/collection/mutable/AnyRefMapTest.scala -scala/collection/mutable/ArraySortingTest.scala -scala/collection/mutable/BitSetTest.scala -scala/collection/mutable/HashMapTest.scala -scala/collection/mutable/HashSetTest.scala -scala/collection/mutable/LinkedHashMapTest.scala -scala/collection/mutable/LinkedHashSetTest.scala -scala/collection/mutable/SetLikeTest.scala -scala/collection/mutable/TreeMapTest.scala -scala/collection/mutable/TreeSetTest.scala -scala/collection/mutable/UnrolledBufferTest.scala -scala/collection/mutable/VectorTest.scala -scala/lang/primitives/PredefAutoboxingTest.scala -scala/math/BigIntTest.scala -scala/math/NumericTest.scala -scala/math/OrderingTest.scala -scala/runtime/ZippedTest.scala -scala/tools/nsc/SampleTest.scala -scala/tools/testing/AssertUtil.scala -scala/tools/testing/TempDir.scala -scala/util/RandomTest.scala -scala/util/RandomUtilTest.scala -scala/util/TryTest.scala -scala/util/control/ExceptionTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.0-M3/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt similarity index 68% rename from scala-test-suite/src/test/resources/2.13.0-M3/BlacklistedTests.txt rename to scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt index d426215689..4c19847829 100644 --- a/scala-test-suite/src/test/resources/2.13.0-M3/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt @@ -1,25 +1,39 @@ ## Do not compile +scala/ExtractorTest.scala +scala/OptionTest.scala scala/SerializationStabilityTest.scala +scala/StringTest.scala +scala/collection/FactoriesTest.scala +scala/collection/LazyZipOpsTest.scala +scala/collection/immutable/IndexedSeqTest.scala scala/lang/annotations/BytecodeTest.scala scala/lang/annotations/RunTest.scala scala/lang/traits/BytecodeTest.scala scala/lang/traits/RunTest.scala scala/lang/primitives/NaNTest.scala -scala/lang/primitives/BoxUnboxTest.scala scala/reflect/ClassOfTest.scala +scala/reflect/FieldAccessTest.scala scala/reflect/QTest.scala scala/reflect/io/ZipArchiveTest.scala -scala/reflect/internal/util/AbstractFileClassLoaderTest.scala -scala/reflect/internal/util/SourceFileTest.scala -scala/reflect/internal/util/StringOpsTest.scala scala/reflect/internal/Infer.scala +scala/reflect/internal/LongNamesTest.scala +scala/reflect/internal/MirrorsTest.scala +scala/reflect/internal/NamesTest.scala +scala/reflect/internal/PositionsTest.scala scala/reflect/internal/PrintersTest.scala scala/reflect/internal/ScopeTest.scala scala/reflect/internal/TreeGenTest.scala scala/reflect/internal/TypesTest.scala +scala/reflect/internal/util/AbstractFileClassLoaderTest.scala +scala/reflect/internal/util/FileUtilsTest.scala +scala/reflect/internal/util/SourceFileTest.scala +scala/reflect/internal/util/StringOpsTest.scala scala/reflect/internal/util/WeakHashSetTest.scala -scala/reflect/internal/MirrorsTest.scala -scala/reflect/internal/NamesTest.scala +scala/reflect/io/AbstractFileTest.scala +scala/tools/cmd/CommandLineParserTest.scala +scala/tools/nsc/DeterminismTest.scala +scala/tools/nsc/GlobalCustomizeClassloaderTest.scala +scala/tools/nsc/ScriptRunnerTest.scala scala/tools/nsc/backend/jvm/BTypesTest.scala scala/tools/nsc/backend/jvm/BytecodeTest.scala scala/tools/nsc/backend/jvm/DefaultMethodTest.scala @@ -34,6 +48,7 @@ scala/tools/nsc/backend/jvm/PerRunInitTest.scala scala/tools/nsc/backend/jvm/StringConcatTest.scala scala/tools/nsc/backend/jvm/analysis/NullnessAnalyzerTest.scala scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala +scala/tools/nsc/backend/jvm/analysis/TypeFlowAnalyzerTest.scala scala/tools/nsc/backend/jvm/opt/AnalyzerTest.scala scala/tools/nsc/backend/jvm/opt/BTypesFromClassfileTest.scala scala/tools/nsc/backend/jvm/opt/BoxUnboxTest.scala @@ -53,17 +68,20 @@ scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala scala/tools/nsc/backend/jvm/opt/SimplifyJumpsTest.scala scala/tools/nsc/backend/jvm/opt/UnreachableCodeTest.scala scala/tools/nsc/backend/jvm/opt/UnusedLocalVariablesTest.scala -scala/tools/nsc/ScriptRunnerTest.scala scala/tools/nsc/classpath/AggregateClassPathTest.scala scala/tools/nsc/classpath/JrtClassPathTest.scala +scala/tools/nsc/classpath/MultiReleaseJarTest.scala scala/tools/nsc/classpath/PathResolverBaseTest.scala scala/tools/nsc/classpath/VirtualDirectoryClassPathTest.scala scala/tools/nsc/classpath/ZipAndJarFileLookupFactoryTest.scala scala/tools/nsc/doc/html/HtmlDocletTest.scala +scala/tools/nsc/doc/html/StringLiteralTest.scala scala/tools/nsc/interpreter/CompletionTest.scala scala/tools/nsc/interpreter/ScriptedTest.scala scala/tools/nsc/interpreter/TabulatorTest.scala +scala/tools/nsc/parser/ParserTest.scala scala/tools/nsc/reporters/ConsoleReporterTest.scala +scala/tools/nsc/reporters/PositionFilterTest.scala scala/tools/nsc/settings/ScalaVersionTest.scala scala/tools/nsc/settings/SettingsTest.scala scala/tools/nsc/symtab/CannotHaveAttrsTest.scala @@ -72,41 +90,73 @@ scala/tools/nsc/symtab/FreshNameExtractorTest.scala scala/tools/nsc/symtab/StdNamesTest.scala scala/tools/nsc/symtab/SymbolTableForUnitTesting.scala scala/tools/nsc/symtab/SymbolTableTest.scala +scala/tools/nsc/symtab/classfile/PicklerTest.scala +scala/tools/nsc/transform/ErasureTest.scala +scala/tools/nsc/transform/MixinTest.scala +scala/tools/nsc/transform/ReleaseFenceTest.scala +scala/tools/nsc/transform/SpecializationTest.scala scala/tools/nsc/transform/delambdafy/DelambdafyTest.scala scala/tools/nsc/transform/patmat/SolvingTest.scala scala/tools/nsc/transform/patmat/PatmatBytecodeTest.scala scala/tools/nsc/typechecker/Implicits.scala +scala/tools/nsc/typechecker/Infer.scala +scala/tools/nsc/typechecker/NamerTest.scala +scala/tools/nsc/typechecker/OverridingPairsTest.scala +scala/tools/nsc/typechecker/ParamAliasTest.scala +scala/tools/nsc/typechecker/TypedTreeTest.scala scala/tools/nsc/util/StackTraceTest.scala scala/tools/testing/BytecodeTesting.scala scala/tools/testing/RunTesting.scala +scala/tools/testing/VirtualCompilerTesting.scala +scala/util/ChainingOpsTest.scala ## Do not link scala/CollectTest.scala scala/MatchErrorSerializationTest.scala scala/PartialFunctionSerializationTest.scala scala/lang/stringinterpol/StringContextTest.scala +scala/collection/IterableTest.scala scala/collection/IteratorTest.scala scala/collection/NewBuilderTest.scala scala/collection/SeqViewTest.scala scala/collection/SetMapConsistencyTest.scala +scala/collection/SetMapRulesTest.scala scala/collection/concurrent/TrieMapTest.scala scala/collection/convert/WrapperSerializationTest.scala +scala/collection/immutable/ChampMapSmokeTest.scala +scala/collection/immutable/ChampSetSmokeTest.scala +scala/collection/immutable/LazyListGCTest.scala +scala/collection/immutable/LazyListLazinessTest.scala scala/collection/immutable/ListTest.scala +scala/collection/immutable/SerializationTest.scala scala/collection/immutable/StreamTest.scala scala/collection/immutable/StringLikeTest.scala +scala/collection/immutable/VectorTest.scala scala/collection/mutable/ArrayBufferTest.scala -scala/collection/mutable/MutableListTest.scala scala/collection/mutable/OpenHashMapTest.scala scala/collection/mutable/PriorityQueueTest.scala +scala/collection/mutable/SerializationTest.scala scala/concurrent/FutureTest.scala scala/concurrent/duration/SerializationTest.scala scala/concurrent/impl/DefaultPromiseTest.scala scala/io/SourceTest.scala +scala/jdk/AccumulatorTest.scala +scala/jdk/DurationConvertersTest.scala +scala/jdk/FunctionConvertersTest.scala +scala/jdk/OptionConvertersTest.scala +scala/jdk/StepperConversionTest.scala +scala/jdk/StepperTest.scala +scala/jdk/StreamConvertersTest.scala +scala/jdk/StreamConvertersTypingTest.scala +scala/math/OrderingTest.scala scala/runtime/ScalaRunTimeTest.scala +scala/sys/process/ParserTest.scala scala/sys/process/PipedProcessTest.scala +scala/sys/process/ProcessBuilderTest.scala scala/sys/process/ProcessTest.scala scala/tools/testing/AssertUtilTest.scala scala/tools/testing/AssertThrowsTest.scala +scala/util/PropertiesTest.scala scala/util/SpecVersionTest.scala scala/util/SystemPropertiesTest.scala @@ -122,12 +172,20 @@ scala/util/matching/RegexTest.scala # Require strict-floats scala/math/BigDecimalTest.scala -# Difference of getClass() on primitive values -scala/collection/immutable/RangeTest.scala - -# Bugs -scala/collection/convert/MapWrapperTest.scala +# Fails for a BigDecimal range with augmented precision (might be an actual bug) +scala/collection/immutable/NumericRangeTest.scala # Tests passed but are too slow (timeouts) scala/collection/immutable/ListSetTest.scala scala/util/SortingTest.scala + +# Relies on undefined behavior +scala/collection/StringOpsTest.scala +scala/collection/StringParsersTest.scala +scala/collection/convert/MapWrapperTest.scala + +## Buglisted +# 3704 +scala/collection/convert/JSetWrapperTest.scala +# 3705 +scala/collection/ArrayOpsTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt b/scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt new file mode 100644 index 0000000000..1f0d30265b --- /dev/null +++ b/scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt @@ -0,0 +1,90 @@ +scala/ArrayTest.scala +scala/CharSequenceImplicitsTests.scala +scala/EnumerationTest.scala +scala/PartialFunctionCompositionTest.scala +scala/PredefTest.scala +scala/collection/BuildFromTest.scala +scala/collection/CatTest.scala +scala/collection/CollectionConversionsTest.scala +scala/collection/EqualityTest.scala +scala/collection/GenericTest.scala +scala/collection/IndexedSeqOptimizedTest.scala +scala/collection/IndexedSeqTest.scala +scala/collection/IndexedSeqViewTest.scala +scala/collection/IterableViewLikeTest.scala +scala/collection/LinearSeqOptimizedTest.scala +scala/collection/LinearSeqTest.scala +scala/collection/MapTest.scala +scala/collection/MapViewTest.scala +scala/collection/MinByMaxByTest.scala +scala/collection/ReusableBuildersTest.scala +scala/collection/SearchingTest.scala +scala/collection/SeqTest.scala +scala/collection/SeqTests.scala +scala/collection/SortedSetTest.scala +scala/collection/StrictOptimizedSeqTest.scala +scala/collection/TraversableLikeTest.scala +scala/collection/TraversableOnceTest.scala +scala/collection/UnsortedTest.scala +scala/collection/ViewTest.scala +scala/collection/WithFilterTest.scala +scala/collection/convert/BinaryTreeStepperTest.scala +scala/collection/convert/JCollectionWrapperTest.scala +scala/collection/convert/JIterableWrapperTest.scala +scala/collection/convert/JListWrapperTest.scala +scala/collection/convert/NullSafetyToJavaTest.scala +scala/collection/convert/NullSafetyToScalaTest.scala +scala/collection/generic/DecoratorsTest.scala +scala/collection/immutable/ArraySeqTest.scala +scala/collection/immutable/CustomHashInt.scala +scala/collection/immutable/HashMapTest.scala +scala/collection/immutable/HashSetTest.scala +scala/collection/immutable/LazyListTest.scala +scala/collection/immutable/ListMapTest.scala +scala/collection/immutable/QueueTest.scala +scala/collection/immutable/RangeConsistencyTest.scala +scala/collection/immutable/RangeTest.scala +scala/collection/immutable/SetTest.scala +scala/collection/immutable/SortedMapTest.scala +scala/collection/immutable/TreeMapTest.scala +scala/collection/immutable/TreeSeqMapTest.scala +scala/collection/immutable/TreeSetTest.scala +scala/collection/immutable/VectorMapTest.scala +scala/collection/immutable/WrappedStringTest.scala +scala/collection/mutable/AnyRefMapTest.scala +scala/collection/mutable/ArrayDequeTest.scala +scala/collection/mutable/ArraySeqTest.scala +scala/collection/mutable/ArraySortingTest.scala +scala/collection/mutable/BitSetTest.scala +scala/collection/mutable/CollisionProofHashMapTest.scala +scala/collection/mutable/HashMapTest.scala +scala/collection/mutable/HashSetTest.scala +scala/collection/mutable/LinkedHashMapTest.scala +scala/collection/mutable/LinkedHashSetTest.scala +scala/collection/mutable/ListBufferTest.scala +scala/collection/mutable/QueueTest.scala +scala/collection/mutable/SetLikeTest.scala +scala/collection/mutable/SetTest.scala +scala/collection/mutable/SortedMapTest.scala +scala/collection/mutable/StackTest.scala +scala/collection/mutable/StringBuilderTest.scala +scala/collection/mutable/TreeMapTest.scala +scala/collection/mutable/TreeSetTest.scala +scala/collection/mutable/UnrolledBufferTest.scala +scala/collection/mutable/VectorTest.scala +scala/concurrent/duration/SpecialDurationsTest.scala +scala/lang/primitives/PredefAutoboxingTest.scala +scala/math/BigIntTest.scala +scala/math/DoubleTest.scala +scala/math/EquivTest.scala +scala/math/NumericTest.scala +scala/math/PartialOrderingTest.scala +scala/runtime/ZippedTest.scala +scala/util/EitherTest.scala +scala/util/RandomTest.scala +scala/util/RandomUtilTest.scala +scala/util/TryTest.scala +scala/util/UsingTest.scala +scala/util/control/ControlThrowableTest.scala +scala/util/control/ExceptionTest.scala +scala/util/hashing/MurmurHash3Test.scala diff --git a/scripts/assemble-cli.sh b/scripts/assemble-cli.sh index 46ac96e6a5..aa37adad66 100755 --- a/scripts/assemble-cli.sh +++ b/scripts/assemble-cli.sh @@ -23,6 +23,10 @@ case $BINVER in FULLVERS="2.12.0 2.12.1 2.12.2 2.12.3 2.12.4 2.12.5 2.12.6 2.12.7 2.12.8" BASEVER="2.12.8" ;; + 2.13) + FULLVERS="2.13.0" + BASEVER="2.13.0" + ;; *) echo "Invalid Scala version $BINVER" >&2 exit 2 diff --git a/scripts/publish.sh b/scripts/publish.sh index 243bb5959e..bcff059989 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -8,16 +8,14 @@ else fi COMPILER_VERSIONS="2.10.2 2.10.3 2.10.4 2.10.5 2.10.6 2.10.7 2.11.0 2.11.1 2.11.2 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.11 2.11.12 2.12.0 2.12.1 2.12.2 2.12.3 2.12.4 2.12.5 2.12.6 2.12.7 2.12.8 2.13.0" -BIN_VERSIONS="2.10.7 2.11.12 2.12.8" -NO_TOOLS_BIN_VERSIONS="2.13.0" -CLI_VERSIONS="2.10.7 2.11.12 2.12.8" +BIN_VERSIONS="2.10.7 2.11.12 2.12.8 2.13.0" +CLI_VERSIONS="2.10.7 2.11.12 2.12.8 2.13.0" SBT_VERSION="2.10.7" SBT1_VERSION="2.12.8" SBT1_SBTVERSION="1.0.0" COMPILER="compiler jUnitPlugin" LIBS="library javalibEx ir irJS tools toolsJS jsEnvs jsEnvsTestKit testAdapter stubs testInterface testBridge jUnitRuntime" -NO_TOOLS_LIBS="library javalibEx stubs testInterface testBridge jUnitRuntime" # Publish compiler for v in $COMPILER_VERSIONS; do @@ -37,15 +35,6 @@ for v in $BIN_VERSIONS; do $CMD $ARGS done -# Publish limited versions -for v in $NO_TOOLS_BIN_VERSIONS; do - ARGS="++$v" - for p in $NO_TOOLS_LIBS; do - ARGS="$ARGS $p/publishSigned" - done - $CMD $ARGS -done - # Publish the CLI for v in $CLI_VERSIONS; do $CMD "++$v" "cli/publishSigned" diff --git a/tools/js/src/test/scala/org/scalajs/core/tools/test/js/QuickLinker.scala b/tools/js/src/test/scala/org/scalajs/core/tools/test/js/QuickLinker.scala index d0ee2d619a..26ba396994 100644 --- a/tools/js/src/test/scala/org/scalajs/core/tools/test/js/QuickLinker.scala +++ b/tools/js/src/test/scala/org/scalajs/core/tools/test/js/QuickLinker.scala @@ -27,7 +27,8 @@ object QuickLinker { @JSExport def linkNode(irFilesAndJars: js.Array[String], moduleInitializers: js.Array[String]): String = { - linkNodeInternal(Semantics.Defaults, irFilesAndJars, moduleInitializers) + linkNodeInternal(Semantics.Defaults, irFilesAndJars.toSeq, + moduleInitializers.toSeq) } /** Link the Scala.js test suite on Node.js */ @@ -53,7 +54,7 @@ object QuickLinker { ) ) - linkNodeInternal(semantics, irFilesAndJars, moduleInitializers) + linkNodeInternal(semantics, irFilesAndJars.toSeq, moduleInitializers.toSeq) } /** Link a Scala.js application on Node.js */ diff --git a/tools/jvm/src/main/scala/org/scalajs/core/tools/io/IRContainerPlatformExtensions.scala b/tools/jvm/src/main/scala/org/scalajs/core/tools/io/IRContainerPlatformExtensions.scala index 6355657e51..1f7decb39c 100644 --- a/tools/jvm/src/main/scala/org/scalajs/core/tools/io/IRContainerPlatformExtensions.scala +++ b/tools/jvm/src/main/scala/org/scalajs/core/tools/io/IRContainerPlatformExtensions.scala @@ -41,9 +41,9 @@ trait IRContainerPlatformExtensions { this: IRContainer.type => val baseDir = dir.getAbsoluteFile - def walkForIR(dir: JFile): Seq[JFile] = { - val (subdirs, files) = dir.listFiles().partition(_.isDirectory) - subdirs.flatMap(walkForIR) ++ files.filter(_.getName.endsWith(".sjsir")) + def walkForIR(dir: JFile): List[JFile] = { + val (subdirs, files) = dir.listFiles().toList.partition(_.isDirectory) + subdirs.flatMap(walkForIR) ::: files.filter(_.getName.endsWith(".sjsir")) } for (ir <- walkForIR(baseDir)) yield { diff --git a/tools/jvm/src/main/scala/org/scalajs/core/tools/json/Impl.scala b/tools/jvm/src/main/scala/org/scalajs/core/tools/json/Impl.scala index f2ecd02fa5..3ec7b4ccee 100644 --- a/tools/jvm/src/main/scala/org/scalajs/core/tools/json/Impl.scala +++ b/tools/jvm/src/main/scala/org/scalajs/core/tools/json/Impl.scala @@ -14,10 +14,10 @@ package org.scalajs.core.tools.json import org.json.simple.JSONValue -import scala.collection.JavaConverters._ - import java.io.{Writer, Reader} +import java.util.function.{BiConsumer, Consumer} + private[json] object Impl extends AbstractJSONImpl { type Repr = Object @@ -25,17 +25,41 @@ private[json] object Impl extends AbstractJSONImpl { def fromString(x: String): Repr = x def fromNumber(x: Number): Repr = x def fromBoolean(x: Boolean): Repr = java.lang.Boolean.valueOf(x) - def fromList(x: List[Repr]): Repr = x.asJava - def fromMap(x: Map[String, Repr]): Repr = x.asJava + + def fromList(x: List[Repr]): Repr = { + val result = new java.util.LinkedList[Repr] + x.foreach(result.add(_)) + result + } + + def fromMap(x: Map[String, Repr]): Repr = { + val result = new java.util.HashMap[String, Repr] + x.foreach(kv => result.put(kv._1, kv._2)) + result + } def toString(x: Repr): String = x.asInstanceOf[String] def toNumber(x: Repr): Number = x.asInstanceOf[Number] def toBoolean(x: Repr): Boolean = x.asInstanceOf[java.lang.Boolean].booleanValue() - def toList(x: Repr): List[Repr] = - x.asInstanceOf[java.util.List[Repr]].asScala.toList - def toMap(x: Repr): Map[String, Repr] = - x.asInstanceOf[java.util.Map[String, Repr]].asScala.toMap + + def toList(x: Repr): List[Repr] = { + val builder = List.newBuilder[Repr] + x.asInstanceOf[java.util.List[Repr]].forEach(new Consumer[Repr] { + def accept(elem: Repr): Unit = + builder += elem + }) + builder.result() + } + + def toMap(x: Repr): Map[String, Repr] = { + val builder = Map.newBuilder[String, Repr] + x.asInstanceOf[java.util.Map[String, Repr]].forEach(new BiConsumer[String, Repr] { + def accept(key: String, value: Repr): Unit = + builder += key -> value + }) + builder.result() + } def serialize(x: Repr): String = JSONValue.toJSONString(x) diff --git a/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/backend/closure/ClosureAstBuilder.scala b/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/backend/closure/ClosureAstBuilder.scala index f51dc4017a..6347751f7a 100644 --- a/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/backend/closure/ClosureAstBuilder.scala +++ b/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/backend/closure/ClosureAstBuilder.scala @@ -30,7 +30,7 @@ private[closure] class ClosureAstBuilder( relativizeBaseURI: Option[URI] = None) extends JSTreeBuilder { private val transformer = new ClosureAstTransformer(relativizeBaseURI) - private val treeBuf = mutable.ListBuffer.empty[Node] + private val treeBuf = List.newBuilder[Node] def addJSTree(tree: Tree): Unit = { /* Top-level `js.Block`s must be explicitly flattened here. @@ -50,10 +50,9 @@ private[closure] class ClosureAstBuilder( } lazy val closureAST: SourceAst = { - val root = transformer.setNodePosition(IR.script(treeBuf: _*), NoPosition) - + val root = + transformer.setNodePosition(IR.script(treeBuf.result(): _*), NoPosition) treeBuf.clear() - new ClosureAstBuilder.ScalaJSSourceAst(root) } diff --git a/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/backend/closure/ClosureLinkerBackend.scala b/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/backend/closure/ClosureLinkerBackend.scala index 166d8f412c..e0c164923f 100644 --- a/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/backend/closure/ClosureLinkerBackend.scala +++ b/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/backend/closure/ClosureLinkerBackend.scala @@ -12,8 +12,6 @@ package org.scalajs.core.tools.linker.backend.closure -import scala.collection.JavaConverters._ - import com.google.javascript.jscomp.{ SourceFile => ClosureSource, Compiler => ClosureCompiler, @@ -99,7 +97,7 @@ final class ClosureLinkerBackend( module.add(new CompilerInput(ast, ast.getInputId(), false)) // Compile the module - val closureExterns = List( + val closureExterns = java.util.Arrays.asList( toClosureSource(ClosureLinkerBackend.ScalaJSExternsFile), toClosureSource(makeExternsForExports(unit))) val options = closureOptions(output.name) @@ -107,7 +105,7 @@ final class ClosureLinkerBackend( val result = logger.time("Closure: Compiler pass") { compiler.compileModules( - closureExterns.asJava, List(module).asJava, options) + closureExterns, java.util.Arrays.asList(module), options) } if (!result.success) { diff --git a/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/ParIncOptimizer.scala b/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/ParIncOptimizer.scala index 4a01075462..a04740408e 100644 --- a/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/ParIncOptimizer.scala +++ b/tools/jvm/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/ParIncOptimizer.scala @@ -41,6 +41,9 @@ final class ParIncOptimizer(semantics: Semantics, esLevel: ESLevel, def emptyParIterable[V]: ParIterable[V] = ParArray.empty // Operations on ParMap + def isEmpty[K, V](map: ParMap[K, V]): Boolean = map.isEmpty + def forceGet[K, V](map: ParMap[K, V], k: K): V = map(k) + def get[K, V](map: ParMap[K, V], k: K): Option[V] = map.get(k) def put[K, V](map: ParMap[K, V], k: K, v: V): Unit = map.put(k, v) def remove[K, V](map: ParMap[K, V], k: K): Option[V] = map.remove(k) @@ -51,16 +54,19 @@ final class ParIncOptimizer(semantics: Semantics, esLevel: ESLevel, } } + def valuesForeach[K, V, U](map: ParMap[K, V])(f: V => U): Unit = + map.values.foreach(f) + // Operations on AccMap def acc[K, V](map: AccMap[K, V], k: K, v: V): Unit = map.getOrPut(k, AtomicAcc.empty) += v - def getAcc[K, V](map: AccMap[K, V], k: K): GenIterable[V] = + def getAcc[K, V](map: AccMap[K, V], k: K): ParIterable[V] = map.get(k).fold[Iterable[V]](Nil)(_.removeAll()).toParArray def parFlatMapKeys[A, B](map: AccMap[A, _])( - f: A => GenTraversableOnce[B]): GenIterable[B] = - map.keys.flatMap(f).toParArray + f: A => Option[B]): ParIterable[B] = + map.keys.flatMap(f(_)).toParArray // Operations on ParIterable def prepAdd[V](it: ParIterable[V]): Addable[V] = @@ -71,6 +77,12 @@ final class ParIncOptimizer(semantics: Semantics, esLevel: ESLevel, def finishAdd[V](addable: Addable[V]): ParIterable[V] = addable.removeAll().toParArray + + def foreach[V, U](it: ParIterable[V])(f: V => U): Unit = + it.foreach(f) + + def filter[V](it: ParIterable[V])(f: V => Boolean): ParIterable[V] = + it.filter(f) } private val _interfaces = TrieMap.empty[String, InterfaceType] diff --git a/tools/shared/src/main/scala-new-collections/org/scalajs/core/tools/jsdep/CollectionsCompat.scala b/tools/shared/src/main/scala-new-collections/org/scalajs/core/tools/jsdep/CollectionsCompat.scala new file mode 100644 index 0000000000..92627f37c4 --- /dev/null +++ b/tools/shared/src/main/scala-new-collections/org/scalajs/core/tools/jsdep/CollectionsCompat.scala @@ -0,0 +1,17 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.core.tools.jsdep + +object CollectionsCompat { + type TraversableCompat[+A] = Iterable[A] +} diff --git a/tools/shared/src/main/scala-old-collections/org/scalajs/core/tools/jsdep/CollectionsCompat.scala b/tools/shared/src/main/scala-old-collections/org/scalajs/core/tools/jsdep/CollectionsCompat.scala new file mode 100644 index 0000000000..09c24f6dfd --- /dev/null +++ b/tools/shared/src/main/scala-old-collections/org/scalajs/core/tools/jsdep/CollectionsCompat.scala @@ -0,0 +1,17 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.core.tools.jsdep + +object CollectionsCompat { + type TraversableCompat[+A] = Traversable[A] +} diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/ComplianceRequirement.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/ComplianceRequirement.scala index 0202f704de..335dc95397 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/ComplianceRequirement.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/ComplianceRequirement.scala @@ -14,6 +14,8 @@ package org.scalajs.core.tools.jsdep import org.scalajs.core.tools.sem.Semantics +import CollectionsCompat.TraversableCompat + /** Expresses a requirement for a given semantic to be compliant */ final class ComplianceRequirement( val semantics: String, val origins: List[Origin]) { @@ -45,7 +47,8 @@ object ComplianceRequirement { * requirements. * @throws BadComplianceException if the semantics are not compliant. */ - final def checkCompliance(requirements: Traversable[ComplianceRequirement], + final def checkCompliance( + requirements: TraversableCompat[ComplianceRequirement], semantics: Semantics): Unit = { val unmet = requirements.filterNot(compliance => semantics.isCompliant(compliance.semantics)) @@ -55,8 +58,8 @@ object ComplianceRequirement { } def mergeFromManifests( - manifests: Traversable[JSDependencyManifest] - ): Traversable[ComplianceRequirement] = { + manifests: TraversableCompat[JSDependencyManifest] + ): TraversableCompat[ComplianceRequirement] = { val flatTups = for { manifest <- manifests diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/DependencyResolver.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/DependencyResolver.scala index 48d040ae76..9e4a39da31 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/DependencyResolver.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/DependencyResolver.scala @@ -17,10 +17,12 @@ import scala.collection.mutable import org.scalajs.core.tools.io.VirtualJSFile import JSLibResolveException.Problem +import CollectionsCompat.TraversableCompat + object DependencyResolver { type DependencyFilter = - Traversable[FlatJSDependency] => Traversable[FlatJSDependency] + TraversableCompat[FlatJSDependency] => TraversableCompat[FlatJSDependency] /** Constructs an ordered list of JS libraries to include. Fails if: * - Resource names do not identify a unique resource on the classpath @@ -28,11 +30,11 @@ object DependencyResolver { * - Not all dependencies are available */ def resolveDependencies( - manifests: Traversable[JSDependencyManifest], + manifests: TraversableCompat[JSDependencyManifest], availableLibs: Map[String, VirtualJSFile], dependencyFilter: DependencyFilter): List[ResolvedJSDependency] = { - val resourceNames = collectAllResourceNames(manifests) + val resourceNames = collectAllResourceNames(manifests.toList) val resolvedJSLibs = resolveAllResourceNames(resourceNames, availableLibs.keys) @@ -48,7 +50,7 @@ object DependencyResolver { dep.minifiedResourceName.map(resolvedJSLibs)) } - val flatDeps = dependencyFilter(allFlatDeps) + val flatDeps = dependencyFilter(allFlatDeps).toList val includeList = createIncludeList(flatDeps) for (info <- includeList) yield { @@ -62,7 +64,7 @@ object DependencyResolver { * @return Map from resource name to the list of origins mentioning them */ private def collectAllResourceNames( - manifests: Traversable[JSDependencyManifest]): Map[String, List[Origin]] = { + manifests: List[JSDependencyManifest]): Map[String, List[Origin]] = { def allResources(dep: JSDependency) = dep.resourceName :: dep.dependencies ::: dep.minifiedResourceName.toList @@ -73,7 +75,7 @@ object DependencyResolver { resourceName <- allResources(dep) } yield (resourceName, manifest.origin) - nameOriginPairs.groupBy(_._1).mapValues(_.map(_._2)) + nameOriginPairs.groupBy(_._1).map(kv => kv._1 -> kv._2.map(_._2)) } /** Resolves all the resource names wrt to the current classpath. @@ -82,7 +84,7 @@ object DependencyResolver { */ private def resolveAllResourceNames( allResourceNames: Map[String, List[Origin]], - relPaths: Traversable[String]): Map[String, String] = { + relPaths: Iterable[String]): Map[String, String] = { val problems = mutable.ListBuffer.empty[Problem] val resolvedLibs = Map.newBuilder[String, String] @@ -100,7 +102,7 @@ object DependencyResolver { /** Resolves one resource name wrt to the current classpath. */ private def resolveResourceName(resourceName: String, origins: List[Origin], - relPaths: Traversable[String]): Either[Problem, String] = { + relPaths: Iterable[String]): Either[Problem, String] = { val candidates = (relPaths collect { case relPath if ("/" + relPath).endsWith("/" + resourceName) => relPath @@ -116,7 +118,7 @@ object DependencyResolver { /** Create a sorted include list for js libs */ private def createIncludeList( - flatDeps: Traversable[FlatJSDependency]): List[ResolutionInfo] = { + flatDeps: List[FlatJSDependency]): List[ResolutionInfo] = { val jsDeps = mergeManifests(flatDeps) // Verify all dependencies are met @@ -154,25 +156,28 @@ object DependencyResolver { /** Merges multiple JSDependencyManifests into a map of map: * resourceName -> ResolutionInfo */ - private def mergeManifests(flatDeps: Traversable[FlatJSDependency]) = { + private def mergeManifests( + flatDeps: List[FlatJSDependency]): Map[String, ResolutionInfo] = { checkCommonJSNameConflicts(flatDeps) val byRelPath = flatDeps.groupBy(_.relPath) checkMinifiedJSConflicts(byRelPath) - byRelPath.mapValues { sameName => - new ResolutionInfo( - relPath = sameName.head.relPath, - dependencies = sameName.flatMap(_.dependencies).toSet, - origins = sameName.map(_.origin).toList, - commonJSName = sameName.flatMap(_.commonJSName).headOption, - relPathMinified = sameName.flatMap(_.relPathMinified).headOption + byRelPath.map { case (relPath, sameName) => + relPath -> new ResolutionInfo( + relPath = relPath, + dependencies = sameName.flatMap(_.dependencies).toSet, + origins = sameName.map(_.origin), + commonJSName = sameName.flatMap(_.commonJSName).headOption, + relPathMinified = sameName.flatMap(_.relPathMinified).headOption ) } } - private def checkCommonJSNameConflicts(flatDeps: Traversable[FlatJSDependency]) = { + private def checkCommonJSNameConflicts( + flatDeps: List[FlatJSDependency]): Unit = { + @inline def hasConflict(x: FlatJSDependency, y: FlatJSDependency) = ( x.commonJSName.isDefined && @@ -186,11 +191,11 @@ object DependencyResolver { } yield dep if (conflicts.nonEmpty) - throw new ConflictingNameException(conflicts.toList) + throw new ConflictingNameException(conflicts) } private def checkMinifiedJSConflicts( - byRelPath: Map[String, Traversable[FlatJSDependency]]) = { + byRelPath: Map[String, List[FlatJSDependency]]): Unit = { @inline def hasConflict(x: FlatJSDependency, y: FlatJSDependency) = ( diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/ManifestFilters.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/ManifestFilters.scala index 80b361a0f7..92b12d4914 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/ManifestFilters.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/jsdep/ManifestFilters.scala @@ -12,11 +12,13 @@ package org.scalajs.core.tools.jsdep +import CollectionsCompat.TraversableCompat + /** Holds useful JSDependencyManifest filters */ object ManifestFilters { type ManifestFilter = - Traversable[JSDependencyManifest] => Traversable[JSDependencyManifest] + TraversableCompat[JSDependencyManifest] => TraversableCompat[JSDependencyManifest] /** Creates a manifest filter that maps resource names of a certain * origin as if they were written differently diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/json/JSONDeserializer.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/json/JSONDeserializer.scala index 960f9ad178..90b30cbed0 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/json/JSONDeserializer.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/json/JSONDeserializer.scala @@ -39,7 +39,7 @@ object JSONDeserializer { implicit def mapJSON[V: JSONDeserializer]: JSONDeserializer[Map[String, V]] = { new JSONDeserializer[Map[String, V]] { def deserialize(x: JSON): Map[String, V] = - Impl.toMap(x).mapValues(fromJSON[V] _) + Impl.toMap(x).map(kv => kv._1 -> fromJSON[V](kv._2)) } } diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/json/JSONSerializer.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/json/JSONSerializer.scala index 791485e975..eacf8dcf58 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/json/JSONSerializer.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/json/JSONSerializer.scala @@ -39,7 +39,7 @@ object JSONSerializer { implicit def mapJSON[V: JSONSerializer]:JSONSerializer[Map[String, V]] = { new JSONSerializer[Map[String, V]] { def serialize(x: Map[String, V]): JSON = - Impl.fromMap(x.mapValues(_.toJSON)) + Impl.fromMap(x.map(kv => kv._1 -> kv._2.toJSON)) } } diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/CollectionsCompat.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/CollectionsCompat.scala new file mode 100644 index 0000000000..e8d5dbb0c6 --- /dev/null +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/CollectionsCompat.scala @@ -0,0 +1,32 @@ +/* + * 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.core.tools.linker + +import scala.collection.mutable + +private[linker] object CollectionsCompat { + implicit class MutableMapCompatOps[K, V](val __self: mutable.Map[K, V]) + extends AnyVal { + + // filterInPlace replaces retain + def filterInPlace(p: (K, V) => Boolean): Unit = { + // Believe it or not, this is the implementation of `retain` in 2.12.x: + + // scala/bug#7269 toList avoids ConcurrentModificationException + for ((k, v) <- __self.toList) { + if (!p(k, v)) + __self -= k + } + } + } +} diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/Analysis.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/Analysis.scala index c692e43375..77ae47264d 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/Analysis.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/Analysis.scala @@ -34,7 +34,7 @@ trait Analysis { def allAvailable: Boolean def classInfos: scala.collection.Map[String, ClassInfo] - def errors: Seq[Error] + def errors: scala.collection.Seq[Error] } object Analysis { @@ -50,11 +50,11 @@ object Analysis { def kind: ClassKind def isExported: Boolean def superClass: ClassInfo - def ancestors: Seq[ClassInfo] - def descendants: Seq[ClassInfo] + def ancestors: scala.collection.Seq[ClassInfo] + def descendants: scala.collection.Seq[ClassInfo] def nonExistent: Boolean def ancestorCount: Int - def descendentClasses: Seq[ClassInfo] + def descendentClasses: scala.collection.Seq[ClassInfo] /** For a Scala class, it is instantiated with a `New`; for a JS class, * its constructor is accessed with a `JSLoadConstructor` or because it * is needed for a subclass. @@ -64,7 +64,7 @@ object Analysis { def isModuleAccessed: Boolean def areInstanceTestsUsed: Boolean def isDataAccessed: Boolean - def instantiatedFrom: Seq[From] + def instantiatedFrom: scala.collection.Seq[From] def isNeededAtAll: Boolean def isAnyStaticMethodReachable: Boolean def methodInfos: scala.collection.Map[String, MethodInfo] @@ -87,8 +87,8 @@ object Analysis { def isExported: Boolean def isReflProxy: Boolean def isReachable: Boolean - def calledFrom: Seq[From] - def instantiatedSubclasses: Seq[ClassInfo] + def calledFrom: scala.collection.Seq[From] + def instantiatedSubclasses: scala.collection.Seq[ClassInfo] def nonExistent: Boolean def syntheticKind: MethodSyntheticKind diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/Analyzer.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/Analyzer.scala index 479a01b39c..38b29126c6 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/Analyzer.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/Analyzer.scala @@ -36,7 +36,7 @@ private final class Analyzer(semantics: Semantics, def allAvailable: Boolean = _allAvailable def classInfos: scala.collection.Map[String, Analysis.ClassInfo] = _classInfos - def errors: Seq[Error] = _errors + def errors: scala.collection.Seq[Error] = _errors private def lookupClass(encodedName: String): ClassInfo = { _classInfos.get(encodedName) match { diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/SymbolRequirement.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/SymbolRequirement.scala index 9ba404d009..c05056bad3 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/SymbolRequirement.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/analyzer/SymbolRequirement.scala @@ -31,19 +31,31 @@ object SymbolRequirement { multiple(accessModule(moduleName), callMethod(moduleName, methodName)) def callOnModule(moduleName: String, - methodName: Traversable[String]): SymbolRequirement = { - val methodCalls = methodName.map(callMethod(moduleName, _)).toList + methodName: List[String]): SymbolRequirement = { + val methodCalls = methodName.map(callMethod(moduleName, _)) multipleInternal(accessModule(moduleName) :: methodCalls) } + @deprecated("Use the overload with a List instead.", "0.6.29") + def callOnModule(moduleName: String, + methodName: Traversable[String]): SymbolRequirement = { + callOnModule(moduleName, methodName.toList) + } + def instantiateClass(className: String, constructor: String): SymbolRequirement = { InstantiateClass(origin, className, constructor) } + def instantiateClass(className: String, + constructors: List[String]): SymbolRequirement = { + multipleInternal(constructors.map(instantiateClass(className, _))) + } + + @deprecated("Use the overload with a List instead.", "0.6.29") def instantiateClass(className: String, constructors: Traversable[String]): SymbolRequirement = { - multipleInternal(constructors.toList.map(instantiateClass(className, _))) + instantiateClass(className, constructors.toList) } def instanceTests(className: String): SymbolRequirement = @@ -55,9 +67,15 @@ object SymbolRequirement { def callMethod(className: String, methodName: String): SymbolRequirement = CallMethod(origin, className, methodName, statically = false) + def callMethods(className: String, + methodNames: List[String]): SymbolRequirement = { + multipleInternal(methodNames.map(callMethod(className, _))) + } + + @deprecated("Use the overload with a List instead.", "0.6.29") def callMethods(className: String, methodNames: Traversable[String]): SymbolRequirement = { - multipleInternal(methodNames.toList.map(callMethod(className, _))) + callMethods(className, methodNames.toList) } def callMethodStatically(className: String, methodName: String): SymbolRequirement = diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/Emitter.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/Emitter.scala index 455090b351..a48450fef2 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/Emitter.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/Emitter.scala @@ -27,6 +27,7 @@ import org.scalajs.core.tools.javascript.{Trees => js, _} import org.scalajs.core.tools.linker._ import org.scalajs.core.tools.linker.analyzer.SymbolRequirement import org.scalajs.core.tools.linker.backend.{OutputMode, ModuleKind} +import org.scalajs.core.tools.linker.CollectionsCompat.MutableMapCompatOps /** Emits a desugared JS tree to a builder */ final class Emitter private (semantics: Semantics, outputMode: OutputMode, @@ -291,7 +292,7 @@ final class Emitter private (semantics: Semantics, outputMode: OutputMode, logger.debug( s"Emitter: Method tree cache stats: resued: $statsMethodsReused -- "+ s"invalidated: $statsMethodsInvalidated") - classCaches.retain((_, c) => c.cleanAfterRun()) + classCaches.filterInPlace((_, c) => c.cleanAfterRun()) } private def emitLinkedClass( @@ -508,8 +509,8 @@ final class Emitter private (semantics: Semantics, outputMode: OutputMode, _methodCaches.getOrElseUpdate(encodedName, new MethodCache) def cleanAfterRun(): Boolean = { - _staticCaches.retain((_, c) => c.cleanAfterRun()) - _methodCaches.retain((_, c) => c.cleanAfterRun()) + _staticCaches.filterInPlace((_, c) => c.cleanAfterRun()) + _methodCaches.filterInPlace((_, c) => c.cleanAfterRun()) if (!_cacheUsed) invalidate() @@ -615,10 +616,10 @@ private[scalajs] object Emitter { callOnModule("sjsr_RuntimeString$", "hashCode__T__I"), instanceTests(LongImpl.RuntimeLongClass), - instantiateClass(LongImpl.RuntimeLongClass, LongImpl.AllConstructors), - callMethods(LongImpl.RuntimeLongClass, LongImpl.AllMethods), + instantiateClass(LongImpl.RuntimeLongClass, LongImpl.AllConstructors.toList), + callMethods(LongImpl.RuntimeLongClass, LongImpl.AllMethods.toList), - callOnModule(LongImpl.RuntimeLongModuleClass, LongImpl.AllModuleMethods), + callOnModule(LongImpl.RuntimeLongModuleClass, LongImpl.AllModuleMethods.toList), cond(semantics.strictFloats && esLevel == ESLevel.ES5) { callOnModule("sjsr_package$", "froundPolyfill__D__D") diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/KnowledgeGuardian.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/KnowledgeGuardian.scala index a2a6a83570..0164a797de 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/KnowledgeGuardian.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/KnowledgeGuardian.scala @@ -18,6 +18,7 @@ import org.scalajs.core.ir.{ClassKind, Definitions} import org.scalajs.core.ir.Trees.{FieldDef, JSNativeLoadSpec} import org.scalajs.core.tools.linker._ +import org.scalajs.core.tools.linker.CollectionsCompat.MutableMapCompatOps private[emitter] final class KnowledgeGuardian { import KnowledgeGuardian._ @@ -69,7 +70,7 @@ private[emitter] final class KnowledgeGuardian { } // Garbage collection - classes.retain((_, cls) => cls.testAndResetIsAlive()) + classes.filterInPlace((_, cls) => cls.testAndResetIsAlive()) val invalidateAll = !firstRun && { newIsParentDataAccessed != isParentDataAccessed || diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/checker/IRChecker.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/checker/IRChecker.scala index 707534c287..65a26c72ff 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/checker/IRChecker.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/checker/IRChecker.scala @@ -390,7 +390,7 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { val initialEnv = Env.fromSignature(NoType, params, NoType, isConstructor = true) - val preparedEnv = (initialEnv /: prepStats) { (prevEnv, stat) => + val preparedEnv = prepStats.foldLeft(initialEnv) { (prevEnv, stat) => typecheckStat(stat, prevEnv) } @@ -569,7 +569,7 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { env case Block(stats) => - (env /: stats) { (prevEnv, stat) => + stats.foldLeft(env) { (prevEnv, stat) => typecheckStat(stat, prevEnv) } env @@ -683,7 +683,7 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { case Block(statsAndExpr) => val stats :+ expr = statsAndExpr - val envAfterStats = (env /: stats) { (prevEnv, stat) => + val envAfterStats = stats.foldLeft(env) { (prevEnv, stat) => typecheckStat(stat, prevEnv) } typecheckExpr(expr, envAfterStats) @@ -1257,7 +1257,7 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { val ancestors: Set[String], val hasInstances: Boolean, val jsNativeLoadSpec: Option[JSNativeLoadSpec], - _fields: TraversableOnce[CheckedField])( + _fields: List[CheckedField])( implicit ctx: ErrorContext) { val fields = _fields.filter(!_.static).map(f => f.name -> f).toMap diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/checker/InfoChecker.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/checker/InfoChecker.scala index f1e00d205a..9d75b8b1f8 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/checker/InfoChecker.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/checker/InfoChecker.scala @@ -31,7 +31,7 @@ import org.scalajs.core.tools.logging._ /** Checker for the validity of the IR. */ private final class InfoChecker( - infoAndTrees: Traversable[(ClassInfo, ClassDef)], logger: Logger) { + infoAndTrees: List[(ClassInfo, ClassDef)], logger: Logger) { private var errorCount: Int = 0 @@ -167,8 +167,18 @@ object InfoChecker { * * @return Count of info checking errors (0 in case of success) */ - def check(infoAndTrees: Traversable[(ClassInfo, ClassDef)], + def check(infoAndTrees: List[(ClassInfo, ClassDef)], logger: Logger): Int = { new InfoChecker(infoAndTrees, logger).check() } + + /** Checks that the `ClassInfo`s associated with `ClassDef`s are correct. + * + * @return Count of info checking errors (0 in case of success) + */ + @deprecated("Use the overload with a List instead.", "0.6.29") + def check(infoAndTrees: Traversable[(ClassInfo, ClassDef)], + logger: Logger): Int = { + check(infoAndTrees.toList, logger) + } } diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/GenIncOptimizer.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/GenIncOptimizer.scala index 663f41cb77..73409cf576 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/GenIncOptimizer.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/GenIncOptimizer.scala @@ -16,7 +16,6 @@ import language.higherKinds import scala.annotation.{switch, tailrec} -import scala.collection.{GenMap, GenTraversableOnce, GenIterable, GenIterableLike} import scala.collection.mutable import org.scalajs.core.ir._ @@ -31,6 +30,7 @@ import org.scalajs.core.tools.logging._ import org.scalajs.core.tools.linker._ import org.scalajs.core.tools.linker.analyzer.SymbolRequirement import org.scalajs.core.tools.linker.backend.emitter.LongImpl +import org.scalajs.core.tools.linker.CollectionsCompat.MutableMapCompatOps /** Incremental optimizer. * An incremental optimizer optimizes a [[LinkingUnit]] in an incremental way. @@ -53,13 +53,13 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, val factory = SymbolRequirement.factory("optimizer") import factory._ - callMethods(LongImpl.RuntimeLongClass, LongImpl.AllIntrinsicMethods) ++ - optional(callMethods(LongImpl.RuntimeLongClass, LongImpl.OptionalIntrinsicMethods)) ++ + callMethods(LongImpl.RuntimeLongClass, LongImpl.AllIntrinsicMethods.toList) ++ + optional(callMethods(LongImpl.RuntimeLongClass, LongImpl.OptionalIntrinsicMethods.toList)) ++ /* #2184 + #2780: we need to keep all methods of j.l.Integer, in case * the corresponding methods are called on j.l.Byte or j.l.Short, and * through optimizations become calls on j.l.Integer. */ - callMethods(Definitions.BoxedIntegerClass, Seq( + callMethods(Definitions.BoxedIntegerClass, List( "byteValue__B", "shortValue__S", "intValue__I", @@ -101,18 +101,18 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, encodedName: String): MethodImpl private def findStaticsNamespace(encodedName: String): StaticsNamespace = - statics(encodedName) + CollOps.forceGet(statics, encodedName) private def findClass(encodedName: String): Class = classes(encodedName) private def findDefaults(encodedName: String): Defaults = - defaults(encodedName) + CollOps.forceGet(defaults, encodedName) private def getStaticsNamespace(encodedName: String): Option[StaticsNamespace] = - statics.get(encodedName) + CollOps.get(statics, encodedName) private def getClass(encodedName: String): Option[Class] = classes.get(encodedName) private def getDefaults(encodedName: String): Option[Defaults] = - defaults.get(encodedName) + CollOps.get(defaults, encodedName) private def withLogger[A](logger: Logger)(body: => A): A = { assert(this.logger == null) @@ -189,7 +189,8 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, * * Non-batch mode only. */ - assert(!batchMode || (statics.isEmpty && defaults.isEmpty)) + assert(!batchMode || + (CollOps.isEmpty(statics) && CollOps.isEmpty(defaults))) if (!batchMode) { for { (containerMap, neededLinkedClasses) <- @@ -225,7 +226,7 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, /* Add new statics. * Easy, we don't have to notify anyone. */ - for (linkedClass <- neededStatics.values) { + CollOps.valuesForeach(neededStatics) { linkedClass => val staticsNS = new StaticsNamespace(linkedClass.encodedName) CollOps.put(statics, staticsNS.encodedName, staticsNS) staticsNS.updateWith(linkedClass) @@ -234,7 +235,7 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, /* Add new defaults. * Easy, we don't have to notify anyone. */ - for (linkedClass <- neededDefaults.values) { + CollOps.valuesForeach(neededDefaults) { linkedClass => val defaultsNS = new Defaults(linkedClass.encodedName) CollOps.put(defaults, defaultsNS.encodedName, defaultsNS) defaultsNS.updateWith(linkedClass) @@ -250,7 +251,7 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, * Non-batch mode only. */ val objectClassStillExists = - objectClass.walkClassesForDeletions(neededClasses.get(_)) + objectClass.walkClassesForDeletions(CollOps.get(neededClasses, _)) assert(objectClassStillExists, "Uh oh, java.lang.Object was deleted!") /* Class changes: @@ -272,7 +273,7 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, // Group children by (immediate) parent val newChildrenByParent = CollOps.emptyAccMap[String, LinkedClass] - for (linkedClass <- neededClasses.values) { + CollOps.valuesForeach(neededClasses) { linkedClass => linkedClass.superClass.fold { assert(batchMode, "Trying to add java.lang.Object in incremental mode") objectClass = new Class(None, linkedClass.encodedName) @@ -292,8 +293,9 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, } else { val existingParents = CollOps.parFlatMapKeys(newChildrenByParent)(classes.get) - for (parent <- existingParents) + CollOps.foreach(existingParents) { parent => parent.walkForAdditions(getNewChildren) + } } } @@ -342,7 +344,7 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, val methodSetChanged = methods.keySet != newMethodNames if (methodSetChanged) { // Remove deleted methods - methods retain { (methodName, method) => + methods.filterInPlace { (methodName, method) => if (newMethodNames.contains(methodName)) { true } else { @@ -434,7 +436,7 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, getLinkedClassIfNeeded(encodedName) match { case Some(linkedClass) if sameSuperClass(linkedClass) => // Class still exists. Recurse. - subclasses = subclasses.filter( + subclasses = CollOps.filter(subclasses)( _.walkClassesForDeletions(getLinkedClassIfNeeded)) if (isInstantiated && !linkedClass.hasInstances) notInstantiatedAnymore() @@ -449,8 +451,7 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, /** Delete this class and all its subclasses. UPDATE PASS ONLY. */ def deleteSubtree(): Unit = { delete() - for (subclass <- subclasses) - subclass.deleteSubtree() + CollOps.foreach(subclasses)(_.deleteSubtree()) } /** UPDATE PASS ONLY. */ @@ -544,17 +545,18 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, } // Recurse in subclasses - for (cls <- subclasses) + CollOps.foreach(subclasses) { cls => cls.walkForChanges(getLinkedClass, methodAttributeChanges) + } } /** UPDATE PASS ONLY. */ def walkForAdditions( - getNewChildren: String => GenIterable[LinkedClass]): Unit = { + getNewChildren: String => CollOps.ParIterable[LinkedClass]): Unit = { val subclassAcc = CollOps.prepAdd(subclasses) - for (linkedClass <- getNewChildren(encodedName)) { + CollOps.foreach(getNewChildren(encodedName)) { linkedClass => val cls = new Class(Some(this), linkedClass.encodedName) CollOps.add(subclassAcc, cls) classes += linkedClass.encodedName -> cls @@ -647,7 +649,7 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, // Mixin constructor, 2.10/2.11 case ApplyStatic(ClassType(cls), methodName, List(This())) => - statics(cls).methods(methodName.name).originalDef.body.exists { + CollOps.forceGet(statics, cls).methods(methodName.name).originalDef.body.exists { case Skip() => true case _ => false } @@ -656,7 +658,7 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, case ApplyStatically(This(), ClassType(cls), methodName, Nil) if !classes.contains(cls) => // Since cls is not in classes, it must be a default method call. - defaults(cls).methods.get(methodName.name) exists { methodDef => + CollOps.forceGet(defaults, cls).methods.get(methodName.name) exists { methodDef => methodDef.originalDef.body exists { case Skip() => true case _ => false @@ -985,7 +987,7 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, methodName: String): Option[MethodID] = { classes.get(className).fold { // If it's not a class, it must be a call to a default intf method - val defaultsNS = defaults(className) + val defaultsNS = CollOps.forceGet(defaults, className) MethodImpl.this.registerStaticCall(defaultsNS.myInterface, methodName) defaultsNS.methods.get(methodName) } { clazz => @@ -997,7 +999,7 @@ abstract class GenIncOptimizer private[optimizer] (semantics: Semantics, /** Look up the target of a call to a static method. */ protected def callStatic(className: String, methodName: String): Option[MethodID] = { - val staticsNS = statics(className) + val staticsNS = CollOps.forceGet(statics, className) registerCallStatic(staticsNS.myInterface, methodName) staticsNS.methods.get(methodName) } @@ -1027,10 +1029,10 @@ object GenIncOptimizer { private[optimizer] trait AbsCollOps { type Map[K, V] <: mutable.Map[K, V] - type ParMap[K, V] <: GenMap[K, V] - type AccMap[K, V] - type ParIterable[V] <: GenIterableLike[V, ParIterable[V]] - type Addable[V] + type ParMap[K, V] <: AnyRef + type AccMap[K, V] <: AnyRef + type ParIterable[V] <: AnyRef + type Addable[V] <: AnyRef def emptyAccMap[K, V]: AccMap[K, V] def emptyMap[K, V]: Map[K, V] @@ -1038,20 +1040,26 @@ object GenIncOptimizer { def emptyParIterable[V]: ParIterable[V] // Operations on ParMap + def isEmpty[K, V](map: ParMap[K, V]): Boolean + def forceGet[K, V](map: ParMap[K, V], k: K): V + def get[K, V](map: ParMap[K, V], k: K): Option[V] def put[K, V](map: ParMap[K, V], k: K, v: V): Unit def remove[K, V](map: ParMap[K, V], k: K): Option[V] def retain[K, V](map: ParMap[K, V])(p: (K, V) => Boolean): Unit + def valuesForeach[K, V, U](map: ParMap[K, V])(f: V => U): Unit // Operations on AccMap def acc[K, V](map: AccMap[K, V], k: K, v: V): Unit - def getAcc[K, V](map: AccMap[K, V], k: K): GenIterable[V] + def getAcc[K, V](map: AccMap[K, V], k: K): ParIterable[V] def parFlatMapKeys[A, B](map: AccMap[A, _])( - f: A => GenTraversableOnce[B]): GenIterable[B] + f: A => Option[B]): ParIterable[B] // Operations on ParIterable def prepAdd[V](it: ParIterable[V]): Addable[V] def add[V](addable: Addable[V], v: V): Unit def finishAdd[V](addable: Addable[V]): ParIterable[V] + def foreach[V, U](it: ParIterable[V])(f: V => U): Unit + def filter[V](it: ParIterable[V])(f: V => Boolean): ParIterable[V] } diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/IncOptimizer.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/IncOptimizer.scala index b620d69a33..372f35ac51 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/IncOptimizer.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/frontend/optimizer/IncOptimizer.scala @@ -17,6 +17,7 @@ import scala.collection.mutable import org.scalajs.core.tools.sem.Semantics import org.scalajs.core.tools.javascript.ESLevel +import org.scalajs.core.tools.linker.CollectionsCompat.MutableMapCompatOps final class IncOptimizer(semantics: Semantics, esLevel: ESLevel, considerPositions: Boolean) @@ -35,27 +36,37 @@ final class IncOptimizer(semantics: Semantics, esLevel: ESLevel, def emptyParIterable[V]: ParIterable[V] = mutable.ListBuffer.empty // Operations on ParMap + def isEmpty[K, V](map: ParMap[K, V]): Boolean = map.isEmpty + def forceGet[K, V](map: ParMap[K, V], k: K): V = map(k) + def get[K, V](map: ParMap[K, V], k: K): Option[V] = map.get(k) def put[K, V](map: ParMap[K, V], k: K, v: V): Unit = map.put(k, v) def remove[K, V](map: ParMap[K, V], k: K): Option[V] = map.remove(k) def retain[K, V](map: ParMap[K, V])(p: (K, V) => Boolean): Unit = - map.retain(p) + map.filterInPlace(p) + + def valuesForeach[K, V, U](map: ParMap[K, V])(f: V => U): Unit = + map.values.foreach(f) // Operations on AccMap def acc[K, V](map: AccMap[K, V], k: K, v: V): Unit = map.getOrElseUpdate(k, mutable.ListBuffer.empty) += v - def getAcc[K, V](map: AccMap[K, V], k: K): GenIterable[V] = - map.getOrElse(k, Nil) + def getAcc[K, V](map: AccMap[K, V], k: K): ParIterable[V] = + map.getOrElse(k, emptyParIterable) def parFlatMapKeys[A, B](map: AccMap[A, _])( - f: A => GenTraversableOnce[B]): GenIterable[B] = - map.keys.flatMap(f).toList + f: A => Option[B]): ParIterable[B] = + emptyParIterable[B] ++= map.keys.flatMap(f(_)) // Operations on ParIterable def prepAdd[V](it: ParIterable[V]): Addable[V] = it def add[V](addable: Addable[V], v: V): Unit = addable += v def finishAdd[V](addable: Addable[V]): ParIterable[V] = addable + def foreach[V, U](it: ParIterable[V])(f: V => U): Unit = it.foreach(f) + + def filter[V](it: ParIterable[V])(f: V => Boolean): ParIterable[V] = + it.filter(f) } private val _interfaces = mutable.Map.empty[String, InterfaceType] diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/sem/Semantics.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/sem/Semantics.scala index dd26e7fd5e..dbb54266d6 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/sem/Semantics.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/sem/Semantics.scala @@ -250,13 +250,11 @@ object Semantics { productionMode = false, runtimeClassNameMapper = RuntimeClassNameMapper.keepAll()) - def compliantTo(semantics: Traversable[String]): Semantics = { + def compliantTo(semantics: Set[String]): Semantics = { import Defaults._ - val semsSet = semantics.toSet - def sw[T](name: String, compliant: T, default: T): T = - if (semsSet.contains(name)) compliant else default + if (semantics.contains(name)) compliant else default new Semantics( asInstanceOfs = sw("asInstanceOfs", Compliant, asInstanceOfs), @@ -267,4 +265,8 @@ object Semantics { productionMode = false, runtimeClassNameMapper = RuntimeClassNameMapper.keepAll()) } + + @deprecated("Use the overload with a Set instead.", "0.6.29") + def compliantTo(semantics: Traversable[String]): Semantics = + compliantTo(semantics.toSet) } From 7e497d4c9fe6637c47366fafb4f42dba2f04ff25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 21 Jun 2019 11:33:13 +0200 Subject: [PATCH 0005/1606] Recognize Metals as an IDE for the special treatment of the build. The same accommodations that were done for Eclipse are also valuable for Metals. Since the environment variable `METALS_ENABLED` is automatically added by Metals when it imports a build, this change gives a better out-of-the-box experience with Metals. We also now mention Metals-based IDEs as the recommended tools to develop Scala.js in the Developing guide. --- DEVELOPING.md | 5 +++++ project/Build.scala | 22 ++++++++++++---------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/DEVELOPING.md b/DEVELOPING.md index 7dc0ddc61a..cc2a880538 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -74,6 +74,11 @@ or, more typically, > partestSuite/testOnly -- --fastOpt +## Metals-based IDEs + +We recommend [Metals](https://scalameta.org/metals/)-based IDEs such as VS Code +to develop Scala.js itself. It can import the Scala.js build out-of-the-box. + ## Eclipse If you want to develop in Eclipse, use diff --git a/project/Build.scala b/project/Build.scala index 3e9f4d2232..f8b525688b 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -47,8 +47,10 @@ object ExposedValues extends AutoPlugin { object Build { - val isGeneratingEclipse = - Properties.envOrElse("GENERATING_ECLIPSE", "false").toBoolean + val isGeneratingForIDE = { + Properties.envOrElse("GENERATING_ECLIPSE", "false").toBoolean || + Properties.envOrElse("METALS_ENABLED", "false").toBoolean + } val bintrayProjectName = settingKey[String]( "Project name on Bintray") @@ -303,7 +305,7 @@ object Build { val noClassFilesSettings: Setting[_] = ( scalacOptions in (Compile, compile) ++= { - if (isGeneratingEclipse) Seq() + if (isGeneratingForIDE) Seq() else Seq("-Yskip:cleanup,icode,jvm") } ) @@ -388,7 +390,7 @@ object Build { // Link source maps scalacOptions ++= { - if (isGeneratingEclipse) Seq() + if (isGeneratingForIDE) Seq() else if (scalaJSIsSnapshotVersion) Seq() else Seq( // Link source maps to github sources @@ -414,13 +416,13 @@ object Build { implicit class ProjectOps(val project: Project) extends AnyVal { /** Uses the Scala.js compiler plugin. */ def withScalaJSCompiler: Project = - if (isGeneratingEclipse) project + if (isGeneratingForIDE) project else project.dependsOn(compiler % "plugin") def withScalaJSJUnitPlugin: Project = { project.settings( scalacOptions in Test ++= { - if (isGeneratingEclipse) { + if (isGeneratingForIDE) { Seq.empty } else { val jar = (packageBin in (jUnitPlugin, Compile)).value @@ -432,7 +434,7 @@ object Build { /** Depends on library as if (exportJars in library) was set to false. */ def dependsOnLibraryNoJar: Project = { - if (isGeneratingEclipse) { + if (isGeneratingForIDE) { project.dependsOn(library) } else { project.settings( @@ -447,7 +449,7 @@ object Build { /** Depends on the sources of another project. */ def dependsOnSource(dependency: Project): Project = { - if (isGeneratingEclipse) { + if (isGeneratingForIDE) { project.dependsOn(dependency) } else { project.settings( @@ -858,7 +860,7 @@ object Build { lazy val delambdafySetting = { scalacOptions ++= ( - if (isGeneratingEclipse) Seq() + if (isGeneratingForIDE) Seq() else if (scalaBinaryVersion.value == "2.10") Seq() else Seq("-Ydelambdafy:method")) } @@ -1108,7 +1110,7 @@ object Build { ) ++ Seq( name := "Scala.js library", delambdafySetting, - exportJars := !isGeneratingEclipse, + exportJars := !isGeneratingForIDE, previousArtifactSetting, mimaBinaryIssueFilters ++= BinaryIncompatibilities.Library, libraryDependencies += From 879d3c1b9be2362f14e4e5e89ca9db935990ac7d Mon Sep 17 00:00:00 2001 From: exoego Date: Mon, 29 Apr 2019 23:27:53 +0900 Subject: [PATCH 0006/1606] Fix #2870: Add warnings to duplicate export annotation. --- .../scalajs/core/compiler/PrepJSExports.scala | 11 +++ .../core/compiler/test/JSExportTest.scala | 80 +++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/compiler/src/main/scala/org/scalajs/core/compiler/PrepJSExports.scala b/compiler/src/main/scala/org/scalajs/core/compiler/PrepJSExports.scala index 199d0c5523..a147ea2759 100644 --- a/compiler/src/main/scala/org/scalajs/core/compiler/PrepJSExports.scala +++ b/compiler/src/main/scala/org/scalajs/core/compiler/PrepJSExports.scala @@ -466,6 +466,17 @@ trait PrepJSExports[G <: Global with Singleton] { this: PrepJSInterop[G] => ignoreInvalid = false) } + allExportInfos.filter(_.destination == ExportDestination.Normal) + .groupBy(_.jsName) + .filter { case (jsName, group) => + if (jsName == "apply" && group.size == 2) + // @JSExportAll and single @JSExport("apply") should not be warned. + !unitAnnots.exists(_.symbol == JSExportAllAnnotation) + else + group.size > 1 + } + .foreach(_ => reporter.warning(sym.pos, s"Found duplicate @JSExport")) + /* Filter out static exports of accessors (as they are not actually * exported, their fields are). The above is only used to uniformly perform * checks. diff --git a/compiler/src/test/scala/org/scalajs/core/compiler/test/JSExportTest.scala b/compiler/src/test/scala/org/scalajs/core/compiler/test/JSExportTest.scala index 6cc15e1cb7..e0e963b996 100644 --- a/compiler/src/test/scala/org/scalajs/core/compiler/test/JSExportTest.scala +++ b/compiler/src/test/scala/org/scalajs/core/compiler/test/JSExportTest.scala @@ -28,6 +28,86 @@ class JSExportTest extends DirectTest with TestHelpers { """import scala.scalajs.js, js.annotation._ """ + @Test + def warnOnDuplicateExport: Unit = { + """ + class A { + @JSExport + @JSExport + def a = 1 + } + """ hasWarns + """ + |newSource1.scala:6: warning: Found duplicate @JSExport + | def a = 1 + | ^ + """ + + """ + class A { + @JSExport + @JSExport("a") + def a = 1 + } + """ hasWarns + """ + |newSource1.scala:6: warning: Found duplicate @JSExport + | def a = 1 + | ^ + """ + + """ + class A { + @JSExport("a") + @JSExport("a") + def a = 1 + } + """ hasWarns + """ + |newSource1.scala:6: warning: Found duplicate @JSExport + | def a = 1 + | ^ + """ + + // special case for @JSExportAll and 2 or more @JSExport("apply") + // since @JSExportAll and single @JSExport("apply") should not be warned (see other tests) + """ + @JSExportAll + class A { + @JSExport("apply") + @JSExport("apply") + def apply(): Int = 1 + } + """ hasWarns + """ + |newSource1.scala:7: warning: Found duplicate @JSExport + | def apply(): Int = 1 + | ^ + """ + + """ + @JSExportAll + class A { + @JSExport + def a = 1 + } + """ hasWarns + """ + |newSource1.scala:6: warning: Found duplicate @JSExport + | def a = 1 + | ^ + """ + } + @Test + def noWarnOnUniqueExplicitName: Unit = { + """ + class A { + @JSExport("a") + @JSExport("b") + def c = 1 + } + """.hasNoWarns + } @Test def noDoubleUnderscoreExport: Unit = { // Normal exports From 52ac12ca00576cb6574b61de923d71de29c697c6 Mon Sep 17 00:00:00 2001 From: exoego Date: Wed, 5 Jun 2019 20:14:18 +0900 Subject: [PATCH 0007/1606] Throw UnsupportedEncodingException on bad charset. According to its spec (javadoc), OutputStreamWriter(OutputStream,String) should throw java.io.UnsupportedEncodingException if the named encoding is not supported. But current implementation throws java.nio.charset.UnsupportedCharsetException instead. --- .../src/main/scala/java/io/OutputStreamWriter.scala | 10 ++++++++-- .../testsuite/javalib/io/OutputStreamWriterTest.scala | 7 +++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/javalib/src/main/scala/java/io/OutputStreamWriter.scala b/javalib/src/main/scala/java/io/OutputStreamWriter.scala index 9762f05d44..1c7de50711 100644 --- a/javalib/src/main/scala/java/io/OutputStreamWriter.scala +++ b/javalib/src/main/scala/java/io/OutputStreamWriter.scala @@ -44,8 +44,14 @@ class OutputStreamWriter(private[this] var out: OutputStream, def this(out: OutputStream) = this(out, Charset.defaultCharset) - def this(out: OutputStream, charsetName: String) = - this(out, Charset.forName(charsetName)) + def this(out: OutputStream, charsetName: String) = { + this(out, try { + Charset.forName(charsetName) + } catch { + case _: UnsupportedCharsetException => + throw new UnsupportedEncodingException(charsetName) + }) + } def getEncoding(): String = if (closed) null else enc.charset.name diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/OutputStreamWriterTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/OutputStreamWriterTest.scala index 8d7d25248c..92ebd7bdb4 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/OutputStreamWriterTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/OutputStreamWriterTest.scala @@ -134,4 +134,11 @@ class OutputStreamWriterTest { testW({ osw => osw.write("ab\ud83d"); osw.close() }, Array('a', 'b', '?'), alreadyFlushed = true) } + + @Test def constructor_throw_UnsupportedEncodingException_if_unsupported_charset_name_given(): Unit = { + val ex = expectThrows(classOf[UnsupportedEncodingException], + new OutputStreamWriter(new ByteArrayOutputStream(), "UNSUPPORTED-CHARSET")) + assertTrue("Cause should be null since constructor does not accept cause", + ex.getCause == null) + } } From 48deae3416b3a3f6644c8fa3fb1b60ceb849d38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 3 Jul 2019 16:09:23 +0200 Subject: [PATCH 0008/1606] Remove obsolete scalalib overrides for 2.13.0-M5. They were forgotten when dropping support for 2.13.0-M5 in 2b6edc3bf613df7ebecb5b445daebbaa4501efd2. --- .../overrides-2.13.0-M5/scala/Array.scala | 650 ------------------ .../overrides-2.13.0-M5/scala/Console.scala | 280 -------- .../scala/Enumeration.scala | 327 --------- .../collection/immutable/NumericRange.scala | 411 ----------- .../scala/collection/immutable/Range.scala | 529 -------------- .../collection/mutable/ArrayBuilder.scala | 596 ---------------- .../scala/collection/mutable/Buffer.scala | 216 ------ .../scala/compat/Platform.scala | 132 ---- .../scala/concurrent/ExecutionContext.scala | 200 ------ .../overrides-2.13.0-M5/scala/package.scala | 140 ---- .../scala/reflect/ClassTag.scala | 127 ---- .../scala/reflect/Manifest.scala | 365 ---------- .../scala/reflect/NameTransformer.scala | 163 ----- .../scala/runtime/ScalaRunTime.scala | 271 -------- 14 files changed, 4407 deletions(-) delete mode 100644 scalalib/overrides-2.13.0-M5/scala/Array.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/Console.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/Enumeration.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/collection/immutable/NumericRange.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/collection/immutable/Range.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/collection/mutable/ArrayBuilder.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/collection/mutable/Buffer.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/compat/Platform.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/concurrent/ExecutionContext.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/package.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/reflect/ClassTag.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/reflect/Manifest.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/reflect/NameTransformer.scala delete mode 100644 scalalib/overrides-2.13.0-M5/scala/runtime/ScalaRunTime.scala diff --git a/scalalib/overrides-2.13.0-M5/scala/Array.scala b/scalalib/overrides-2.13.0-M5/scala/Array.scala deleted file mode 100644 index 723d1015c1..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/Array.scala +++ /dev/null @@ -1,650 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala - -//import scala.collection.generic._ -import scala.collection.{Factory, immutable, mutable} -import mutable.ArrayBuilder -import immutable.ArraySeq -import scala.language.implicitConversions -import scala.reflect.ClassTag -import scala.runtime.{BoxedUnit, ScalaRunTime} -import scala.runtime.ScalaRunTime.{array_apply, array_update} - -/** Utility methods for operating on arrays. - * For example: - * {{{ - * val a = Array(1, 2) - * val b = Array.ofDim[Int](2) - * val c = Array.concat(a, b) - * }}} - * where the array objects `a`, `b` and `c` have respectively the values - * `Array(1, 2)`, `Array(0, 0)` and `Array(1, 2, 0, 0)`. - * - * @author Martin Odersky - * @since 1.0 - */ -object Array { - def emptyBooleanArray = EmptyArrays.emptyBooleanArray - def emptyByteArray = EmptyArrays.emptyByteArray - def emptyCharArray = EmptyArrays.emptyCharArray - def emptyDoubleArray = EmptyArrays.emptyDoubleArray - def emptyFloatArray = EmptyArrays.emptyFloatArray - def emptyIntArray = EmptyArrays.emptyIntArray - def emptyLongArray = EmptyArrays.emptyLongArray - def emptyShortArray = EmptyArrays.emptyShortArray - def emptyObjectArray = EmptyArrays.emptyObjectArray - - private object EmptyArrays { - val emptyBooleanArray = new Array[Boolean](0) - val emptyByteArray = new Array[Byte](0) - val emptyCharArray = new Array[Char](0) - val emptyDoubleArray = new Array[Double](0) - val emptyFloatArray = new Array[Float](0) - val emptyIntArray = new Array[Int](0) - val emptyLongArray = new Array[Long](0) - val emptyShortArray = new Array[Short](0) - val emptyObjectArray = new Array[Object](0) - } - - /** Provides an implicit conversion from the Array object to a collection Factory */ - implicit def toFactory[A : ClassTag](dummy: Array.type): Factory[A, Array[A]] = new ArrayFactory(dummy) - @SerialVersionUID(3L) - private class ArrayFactory[A : ClassTag](dummy: Array.type) extends Factory[A, Array[A]] with Serializable { - def fromSpecific(it: IterableOnce[A]): Array[A] = Array.from[A](it) - def newBuilder: mutable.Builder[A, Array[A]] = Array.newBuilder[A] - } - - /** - * Returns a new [[scala.collection.mutable.ArrayBuilder]]. - */ - def newBuilder[T](implicit t: ClassTag[T]): ArrayBuilder[T] = ArrayBuilder.make[T](t) - - def from[A : ClassTag](it: IterableOnce[A]): Array[A] = { - val n = it.knownSize - if (n > -1) { - val elements = new Array[A](n) - val iterator = it.iterator - var i = 0 - while (i < n) { - ScalaRunTime.array_update(elements, i, iterator.next()) - i = i + 1 - } - elements - } else { - val b = ArrayBuilder.make[A] - val iterator = it.iterator - while (iterator.hasNext) - b += iterator.next() - b.result() - } - } - - private def slowcopy(src : AnyRef, - srcPos : Int, - dest : AnyRef, - destPos : Int, - length : Int): Unit = { - var i = srcPos - var j = destPos - val srcUntil = srcPos + length - while (i < srcUntil) { - array_update(dest, j, array_apply(src, i)) - i += 1 - j += 1 - } - } - - /** Copy one array to another. - * Equivalent to Java's - * `System.arraycopy(src, srcPos, dest, destPos, length)`, - * except that this also works for polymorphic and boxed arrays. - * - * Note that the passed-in `dest` array will be modified by this call. - * - * @param src the source array. - * @param srcPos starting position in the source array. - * @param dest destination array. - * @param destPos starting position in the destination array. - * @param length the number of array elements to be copied. - * - * @see `java.lang.System#arraycopy` - */ - def copy(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int): Unit = { - val srcClass = src.getClass - if (srcClass.isArray && dest.getClass.isAssignableFrom(srcClass)) - java.lang.System.arraycopy(src, srcPos, dest, destPos, length) - else - slowcopy(src, srcPos, dest, destPos, length) - } - - /** Copy one array to another, truncating or padding with default values (if - * necessary) so the copy has the specified length. - * - * Equivalent to Java's - * `java.util.Arrays.copyOf(original, newLength)`, - * except that this works for primitive and object arrays in a single method. - * - * @see `java.util.Arrays#copyOf` - */ - def copyOf[A](original: Array[A], newLength: Int): Array[A] = (original match { - case x: Array[BoxedUnit] => newUnitArray(newLength).asInstanceOf[Array[A]] - case x: Array[AnyRef] => java.util.Arrays.copyOf(x, newLength) - case x: Array[Int] => java.util.Arrays.copyOf(x, newLength) - case x: Array[Double] => java.util.Arrays.copyOf(x, newLength) - case x: Array[Long] => java.util.Arrays.copyOf(x, newLength) - case x: Array[Float] => java.util.Arrays.copyOf(x, newLength) - case x: Array[Char] => java.util.Arrays.copyOf(x, newLength) - case x: Array[Byte] => java.util.Arrays.copyOf(x, newLength) - case x: Array[Short] => java.util.Arrays.copyOf(x, newLength) - case x: Array[Boolean] => java.util.Arrays.copyOf(x, newLength) - }).asInstanceOf[Array[A]] - - /** Copy one array to another, truncating or padding with default values (if - * necessary) so the copy has the specified length. The new array can have - * a different type than the original one as long as the values are - * assignment-compatible. When copying between primitive and object arrays, - * boxing and unboxing are supported. - * - * Equivalent to Java's - * `java.util.Arrays.copyOf(original, newLength, newType)`, - * except that this works for all combinations of primitive and object arrays - * in a single method. - * - * @see `java.util.Arrays#copyOf` - */ - def copyAs[A](original: Array[_], newLength: Int)(implicit ct: ClassTag[A]): Array[A] = { - val runtimeClass = ct.runtimeClass - if (runtimeClass == Void.TYPE) newUnitArray(newLength).asInstanceOf[Array[A]] - else { - val destClass = runtimeClass.asInstanceOf[Class[A]] - if (destClass.isAssignableFrom(original.getClass.getComponentType)) { - if (destClass.isPrimitive) copyOf[A](original.asInstanceOf[Array[A]], newLength) - else { - val destArrayClass = java.lang.reflect.Array.newInstance(destClass, 0).getClass.asInstanceOf[Class[Array[AnyRef]]] - java.util.Arrays.copyOf(original.asInstanceOf[Array[AnyRef]], newLength, destArrayClass).asInstanceOf[Array[A]] - } - } else { - val dest = new Array[A](newLength) - Array.copy(original, 0, dest, 0, original.length) - dest - } - } - } - - private def newUnitArray(len: Int): Array[Unit] = { - val result = new Array[Unit](len) - java.util.Arrays.fill(result.asInstanceOf[Array[AnyRef]], ()) - result - } - - /** Returns an array of length 0 */ - def empty[T: ClassTag]: Array[T] = new Array[T](0) - - /** Creates an array with given elements. - * - * @param xs the elements to put in the array - * @return an array containing all elements from xs. - */ - // Subject to a compiler optimization in Cleanup. - // Array(e0, ..., en) is translated to { val a = new Array(3); a(i) = ei; a } - def apply[T: ClassTag](xs: T*): Array[T] = { - val array = new Array[T](xs.length) - var i = 0 - for (x <- xs.iterator) { array(i) = x; i += 1 } - array - } - - /** Creates an array of `Boolean` objects */ - // Subject to a compiler optimization in Cleanup, see above. - def apply(x: Boolean, xs: Boolean*): Array[Boolean] = { - val array = new Array[Boolean](xs.length + 1) - array(0) = x - var i = 1 - for (x <- xs.iterator) { array(i) = x; i += 1 } - array - } - - /** Creates an array of `Byte` objects */ - // Subject to a compiler optimization in Cleanup, see above. - def apply(x: Byte, xs: Byte*): Array[Byte] = { - val array = new Array[Byte](xs.length + 1) - array(0) = x - var i = 1 - for (x <- xs.iterator) { array(i) = x; i += 1 } - array - } - - /** Creates an array of `Short` objects */ - // Subject to a compiler optimization in Cleanup, see above. - def apply(x: Short, xs: Short*): Array[Short] = { - val array = new Array[Short](xs.length + 1) - array(0) = x - var i = 1 - for (x <- xs.iterator) { array(i) = x; i += 1 } - array - } - - /** Creates an array of `Char` objects */ - // Subject to a compiler optimization in Cleanup, see above. - def apply(x: Char, xs: Char*): Array[Char] = { - val array = new Array[Char](xs.length + 1) - array(0) = x - var i = 1 - for (x <- xs.iterator) { array(i) = x; i += 1 } - array - } - - /** Creates an array of `Int` objects */ - // Subject to a compiler optimization in Cleanup, see above. - def apply(x: Int, xs: Int*): Array[Int] = { - val array = new Array[Int](xs.length + 1) - array(0) = x - var i = 1 - for (x <- xs.iterator) { array(i) = x; i += 1 } - array - } - - /** Creates an array of `Long` objects */ - // Subject to a compiler optimization in Cleanup, see above. - def apply(x: Long, xs: Long*): Array[Long] = { - val array = new Array[Long](xs.length + 1) - array(0) = x - var i = 1 - for (x <- xs.iterator) { array(i) = x; i += 1 } - array - } - - /** Creates an array of `Float` objects */ - // Subject to a compiler optimization in Cleanup, see above. - def apply(x: Float, xs: Float*): Array[Float] = { - val array = new Array[Float](xs.length + 1) - array(0) = x - var i = 1 - for (x <- xs.iterator) { array(i) = x; i += 1 } - array - } - - /** Creates an array of `Double` objects */ - // Subject to a compiler optimization in Cleanup, see above. - def apply(x: Double, xs: Double*): Array[Double] = { - val array = new Array[Double](xs.length + 1) - array(0) = x - var i = 1 - for (x <- xs.iterator) { array(i) = x; i += 1 } - array - } - - /** Creates an array of `Unit` objects */ - def apply(x: Unit, xs: Unit*): Array[Unit] = { - val array = new Array[Unit](xs.length + 1) - array(0) = x - var i = 1 - for (x <- xs.iterator) { array(i) = x; i += 1 } - array - } - - /** Creates array with given dimensions */ - def ofDim[T: ClassTag](n1: Int): Array[T] = - new Array[T](n1) - /** Creates a 2-dimensional array */ - def ofDim[T: ClassTag](n1: Int, n2: Int): Array[Array[T]] = { - val arr: Array[Array[T]] = (new Array[Array[T]](n1): Array[Array[T]]) - for (i <- 0 until n1) arr(i) = new Array[T](n2) - arr - // tabulate(n1)(_ => ofDim[T](n2)) - } - /** Creates a 3-dimensional array */ - def ofDim[T: ClassTag](n1: Int, n2: Int, n3: Int): Array[Array[Array[T]]] = - tabulate(n1)(_ => ofDim[T](n2, n3)) - /** Creates a 4-dimensional array */ - def ofDim[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int): Array[Array[Array[Array[T]]]] = - tabulate(n1)(_ => ofDim[T](n2, n3, n4)) - /** Creates a 5-dimensional array */ - def ofDim[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int): Array[Array[Array[Array[Array[T]]]]] = - tabulate(n1)(_ => ofDim[T](n2, n3, n4, n5)) - - /** Concatenates all arrays into a single array. - * - * @param xss the given arrays - * @return the array created from concatenating `xss` - */ - def concat[T: ClassTag](xss: Array[T]*): Array[T] = { - val b = newBuilder[T] - b.sizeHint(xss.map(_.length).sum) - for (xs <- xss) b ++= xs - b.result() - } - - /** Returns an array that contains the results of some element computation a number - * of times. - * - * Note that this means that `elem` is computed a total of n times: - * {{{ - * scala> Array.fill(3){ math.random } - * res3: Array[Double] = Array(0.365461167592537, 1.550395944913685E-4, 0.7907242137333306) - * }}} - * - * @param n the number of elements desired - * @param elem the element computation - * @return an Array of size n, where each element contains the result of computing - * `elem`. - */ - def fill[T: ClassTag](n: Int)(elem: => T): Array[T] = { - val b = newBuilder[T] - b.sizeHint(n) - var i = 0 - while (i < n) { - b += elem - i += 1 - } - b.result() - } - - /** Returns a two-dimensional array that contains the results of some element - * computation a number of times. - * - * @param n1 the number of elements in the 1st dimension - * @param n2 the number of elements in the 2nd dimension - * @param elem the element computation - */ - def fill[T: ClassTag](n1: Int, n2: Int)(elem: => T): Array[Array[T]] = - tabulate(n1)(_ => fill(n2)(elem)) - - /** Returns a three-dimensional array that contains the results of some element - * computation a number of times. - * - * @param n1 the number of elements in the 1st dimension - * @param n2 the number of elements in the 2nd dimension - * @param n3 the number of elements in the 3rd dimension - * @param elem the element computation - */ - def fill[T: ClassTag](n1: Int, n2: Int, n3: Int)(elem: => T): Array[Array[Array[T]]] = - tabulate(n1)(_ => fill(n2, n3)(elem)) - - /** Returns a four-dimensional array that contains the results of some element - * computation a number of times. - * - * @param n1 the number of elements in the 1st dimension - * @param n2 the number of elements in the 2nd dimension - * @param n3 the number of elements in the 3rd dimension - * @param n4 the number of elements in the 4th dimension - * @param elem the element computation - */ - def fill[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => T): Array[Array[Array[Array[T]]]] = - tabulate(n1)(_ => fill(n2, n3, n4)(elem)) - - /** Returns a five-dimensional array that contains the results of some element - * computation a number of times. - * - * @param n1 the number of elements in the 1st dimension - * @param n2 the number of elements in the 2nd dimension - * @param n3 the number of elements in the 3rd dimension - * @param n4 the number of elements in the 4th dimension - * @param n5 the number of elements in the 5th dimension - * @param elem the element computation - */ - def fill[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => T): Array[Array[Array[Array[Array[T]]]]] = - tabulate(n1)(_ => fill(n2, n3, n4, n5)(elem)) - - /** Returns an array containing values of a given function over a range of integer - * values starting from 0. - * - * @param n The number of elements in the array - * @param f The function computing element values - * @return A traversable consisting of elements `f(0),f(1), ..., f(n - 1)` - */ - def tabulate[T: ClassTag](n: Int)(f: Int => T): Array[T] = { - val b = newBuilder[T] - b.sizeHint(n) - var i = 0 - while (i < n) { - b += f(i) - i += 1 - } - b.result() - } - - /** Returns a two-dimensional array containing values of a given function - * over ranges of integer values starting from `0`. - * - * @param n1 the number of elements in the 1st dimension - * @param n2 the number of elements in the 2nd dimension - * @param f The function computing element values - */ - def tabulate[T: ClassTag](n1: Int, n2: Int)(f: (Int, Int) => T): Array[Array[T]] = - tabulate(n1)(i1 => tabulate(n2)(f(i1, _))) - - /** Returns a three-dimensional array containing values of a given function - * over ranges of integer values starting from `0`. - * - * @param n1 the number of elements in the 1st dimension - * @param n2 the number of elements in the 2nd dimension - * @param n3 the number of elements in the 3rd dimension - * @param f The function computing element values - */ - def tabulate[T: ClassTag](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => T): Array[Array[Array[T]]] = - tabulate(n1)(i1 => tabulate(n2, n3)(f(i1, _, _))) - - /** Returns a four-dimensional array containing values of a given function - * over ranges of integer values starting from `0`. - * - * @param n1 the number of elements in the 1st dimension - * @param n2 the number of elements in the 2nd dimension - * @param n3 the number of elements in the 3rd dimension - * @param n4 the number of elements in the 4th dimension - * @param f The function computing element values - */ - def tabulate[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => T): Array[Array[Array[Array[T]]]] = - tabulate(n1)(i1 => tabulate(n2, n3, n4)(f(i1, _, _, _))) - - /** Returns a five-dimensional array containing values of a given function - * over ranges of integer values starting from `0`. - * - * @param n1 the number of elements in the 1st dimension - * @param n2 the number of elements in the 2nd dimension - * @param n3 the number of elements in the 3rd dimension - * @param n4 the number of elements in the 4th dimension - * @param n5 the number of elements in the 5th dimension - * @param f The function computing element values - */ - def tabulate[T: ClassTag](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => T): Array[Array[Array[Array[Array[T]]]]] = - tabulate(n1)(i1 => tabulate(n2, n3, n4, n5)(f(i1, _, _, _, _))) - - /** Returns an array containing a sequence of increasing integers in a range. - * - * @param start the start value of the array - * @param end the end value of the array, exclusive (in other words, this is the first value '''not''' returned) - * @return the array with values in range `start, start + 1, ..., end - 1` - * up to, but excluding, `end`. - */ - def range(start: Int, end: Int): Array[Int] = range(start, end, 1) - - /** Returns an array containing equally spaced values in some integer interval. - * - * @param start the start value of the array - * @param end the end value of the array, exclusive (in other words, this is the first value '''not''' returned) - * @param step the increment value of the array (may not be zero) - * @return the array with values in `start, start + step, ...` up to, but excluding `end` - */ - def range(start: Int, end: Int, step: Int): Array[Int] = { - if (step == 0) throw new IllegalArgumentException("zero step") - val b = newBuilder[Int] - b.sizeHint(immutable.Range.count(start, end, step, isInclusive = false)) - - var i = start - while (if (step < 0) end < i else i < end) { - b += i - i += step - } - b.result() - } - - /** Returns an array containing repeated applications of a function to a start value. - * - * @param start the start value of the array - * @param len the number of elements returned by the array - * @param f the function that is repeatedly applied - * @return the array returning `len` values in the sequence `start, f(start), f(f(start)), ...` - */ - def iterate[T: ClassTag](start: T, len: Int)(f: T => T): Array[T] = { - val b = newBuilder[T] - - if (len > 0) { - b.sizeHint(len) - var acc = start - var i = 1 - b += acc - - while (i < len) { - acc = f(acc) - i += 1 - b += acc - } - } - b.result() - } - - def equals(xs: Array[AnyRef], ys: Array[AnyRef]): Boolean = { - if (xs eq ys) - return true - if (xs.length != ys.length) - return false - - val len = xs.length - var i = 0 - while (i < len) { - if (xs(i) != ys(i)) - return false - i += 1 - } - true - } - - /** Called in a pattern match like `{ case Array(x,y,z) => println('3 elements')}`. - * - * @param x the selector value - * @return sequence wrapped in a [[scala.Some]], if `x` is an Array, otherwise `None` - */ - def unapplySeq[T](x: Array[T]): UnapplySeqWrapper[T] = new UnapplySeqWrapper(x) - - final class UnapplySeqWrapper[T](private val a: Array[T]) extends AnyVal { - def isEmpty: Boolean = false - def get: UnapplySeqWrapper[T] = this - def lengthCompare(len: Int): Int = a.lengthCompare(len) - def apply(i: Int): T = a(i) - def drop(n: Int): scala.Seq[T] = ArraySeq.unsafeWrapArray(a.drop(n)) // clones the array, also if n == 0 - def toSeq: scala.Seq[T] = a.toSeq // clones the array - } -} - -/** Arrays are mutable, indexed collections of values. `Array[T]` is Scala's representation - * for Java's `T[]`. - * - * {{{ - * val numbers = Array(1, 2, 3, 4) - * val first = numbers(0) // read the first element - * numbers(3) = 100 // replace the 4th array element with 100 - * val biggerNumbers = numbers.map(_ * 2) // multiply all numbers by two - * }}} - * - * Arrays make use of two common pieces of Scala syntactic sugar, shown on lines 2 and 3 of the above - * example code. - * Line 2 is translated into a call to `apply(Int)`, while line 3 is translated into a call to - * `update(Int, T)`. - * - * Two implicit conversions exist in [[scala.Predef]] that are frequently applied to arrays: a conversion - * to [[scala.collection.ArrayOps]] (shown on line 4 of the example above) and a conversion - * to [[scala.collection.mutable.ArraySeq]] (a subtype of [[scala.collection.Seq]]). - * Both types make available many of the standard operations found in the Scala collections API. - * The conversion to `ArrayOps` is temporary, as all operations defined on `ArrayOps` return an `Array`, - * while the conversion to `ArraySeq` is permanent as all operations return a `ArraySeq`. - * - * The conversion to `ArrayOps` takes priority over the conversion to `ArraySeq`. For instance, - * consider the following code: - * - * {{{ - * val arr = Array(1, 2, 3) - * val arrReversed = arr.reverse - * val seqReversed : collection.Seq[Int] = arr.reverse - * }}} - * - * Value `arrReversed` will be of type `Array[Int]`, with an implicit conversion to `ArrayOps` occurring - * to perform the `reverse` operation. The value of `seqReversed`, on the other hand, will be computed - * by converting to `ArraySeq` first and invoking the variant of `reverse` that returns another - * `ArraySeq`. - * - * @author Martin Odersky - * @since 1.0 - * @see [[http://www.scala-lang.org/files/archive/spec/2.13/ Scala Language Specification]], for in-depth information on the transformations the Scala compiler makes on Arrays (Sections 6.6 and 6.15 respectively.) - * @see [[http://docs.scala-lang.org/sips/completed/scala-2-8-arrays.html "Scala 2.8 Arrays"]] the Scala Improvement Document detailing arrays since Scala 2.8. - * @see [[http://docs.scala-lang.org/overviews/collections/arrays.html "The Scala 2.8 Collections' API"]] section on `Array` by Martin Odersky for more information. - * @hideImplicitConversion scala.Predef.booleanArrayOps - * @hideImplicitConversion scala.Predef.byteArrayOps - * @hideImplicitConversion scala.Predef.charArrayOps - * @hideImplicitConversion scala.Predef.doubleArrayOps - * @hideImplicitConversion scala.Predef.floatArrayOps - * @hideImplicitConversion scala.Predef.intArrayOps - * @hideImplicitConversion scala.Predef.longArrayOps - * @hideImplicitConversion scala.Predef.refArrayOps - * @hideImplicitConversion scala.Predef.shortArrayOps - * @hideImplicitConversion scala.Predef.unitArrayOps - * @hideImplicitConversion scala.LowPriorityImplicits.wrapRefArray - * @hideImplicitConversion scala.LowPriorityImplicits.wrapIntArray - * @hideImplicitConversion scala.LowPriorityImplicits.wrapDoubleArray - * @hideImplicitConversion scala.LowPriorityImplicits.wrapLongArray - * @hideImplicitConversion scala.LowPriorityImplicits.wrapFloatArray - * @hideImplicitConversion scala.LowPriorityImplicits.wrapCharArray - * @hideImplicitConversion scala.LowPriorityImplicits.wrapByteArray - * @hideImplicitConversion scala.LowPriorityImplicits.wrapShortArray - * @hideImplicitConversion scala.LowPriorityImplicits.wrapBooleanArray - * @hideImplicitConversion scala.LowPriorityImplicits.wrapUnitArray - * @hideImplicitConversion scala.LowPriorityImplicits.genericWrapArray - * @define coll array - * @define Coll `Array` - * @define orderDependent - * @define orderDependentFold - * @define mayNotTerminateInf - * @define willNotTerminateInf - * @define collectExample - * @define undefinedorder - */ -final class Array[T](_length: Int) extends java.io.Serializable with java.lang.Cloneable { - - /** The length of the array */ - def length: Int = throw new Error() - - /** The element at given index. - * - * Indices start at `0`; `xs.apply(0)` is the first element of array `xs`. - * Note the indexing syntax `xs(i)` is a shorthand for `xs.apply(i)`. - * - * @param i the index - * @return the element at the given index - * @throws ArrayIndexOutOfBoundsException if `i < 0` or `length <= i` - */ - def apply(i: Int): T = throw new Error() - - /** Update the element at given index. - * - * Indices start at `0`; `xs.update(i, x)` replaces the i^th^ element in the array. - * Note the syntax `xs(i) = x` is a shorthand for `xs.update(i, x)`. - * - * @param i the index - * @param x the value to be written at index `i` - * @throws ArrayIndexOutOfBoundsException if `i < 0` or `length <= i` - */ - def update(i: Int, x: T): Unit = { throw new Error() } - - /** Clone the Array. - * - * @return A clone of the Array. - */ - override def clone(): Array[T] = throw new Error() -} diff --git a/scalalib/overrides-2.13.0-M5/scala/Console.scala b/scalalib/overrides-2.13.0-M5/scala/Console.scala deleted file mode 100644 index 68394a91f7..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/Console.scala +++ /dev/null @@ -1,280 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2016, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala - -import java.io.{ BufferedReader, InputStream, InputStreamReader, OutputStream, PrintStream, Reader } -import scala.io.AnsiColor -import scala.util.DynamicVariable - -/** Implements functionality for printing Scala values on the terminal. For reading values - * use [[scala.io.StdIn$ StdIn]]. - * Also defines constants for marking up text on ANSI terminals. - * - * == Console Output == - * - * Use the print methods to output text. - * {{{ - * scala> Console.printf( - * "Today the outside temperature is a balmy %.1f°C. %<.1f°C beats the previous record of %.1f°C.\n", - * -137.0, - * -135.05) - * Today the outside temperature is a balmy -137.0°C. -137.0°C beats the previous record of -135.1°C. - * }}} - * - * == ANSI escape codes == - * Use the ANSI escape codes for colorizing console output either to STDOUT or STDERR. - * {{{ - * import Console.{GREEN, RED, RESET, YELLOW_B, UNDERLINED} - * - * object PrimeTest { - * - * def isPrime(): Unit = { - * - * val candidate = io.StdIn.readInt().ensuring(_ > 1) - * - * val prime = (2 to candidate - 1).forall(candidate % _ != 0) - * - * if (prime) - * Console.println(s"${RESET}${GREEN}yes${RESET}") - * else - * Console.err.println(s"${RESET}${YELLOW_B}${RED}${UNDERLINED}NO!${RESET}") - * } - * - * def main(args: Array[String]): Unit = isPrime() - * - * } - * }}} - * - * - * - * - * - * - * - * - *
$ scala PrimeTest
1234567891
yes
$ scala PrimeTest
56474
NO!
- * - * == IO redefinition == - * - * Use IO redefinition to temporarily swap in a different set of input and/or output streams. In this example the stream based - * method above is wrapped into a function. - * - * {{{ - * import java.io.{ByteArrayOutputStream, StringReader} - * - * object FunctionalPrimeTest { - * - * def isPrime(candidate: Int): Boolean = { - * - * val input = new StringReader(s"$candidate\n") - * val outCapture = new ByteArrayOutputStream - * val errCapture = new ByteArrayOutputStream - * - * Console.withIn(input) { - * Console.withOut(outCapture) { - * Console.withErr(errCapture) { - * PrimeTest.isPrime() - * } - * } - * } - * - * if (outCapture.toByteArray.nonEmpty) // "yes" - * true - * else if (errCapture.toByteArray.nonEmpty) // "NO!" - * false - * else throw new IllegalArgumentException(candidate.toString) - * } - * - * def main(args: Array[String]): Unit = { - * val primes = (2 to 50) filter (isPrime) - * println(s"First primes: $primes") - * } - * - * } - * }}} - * - * - * - * - * - *
$ scala FunctionalPrimeTest
First primes: Vector(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47)
- * - * @author Matthias Zenger - * @since 1.0 - * - * @groupname console-output Console Output - * @groupprio console-output 30 - * @groupdesc console-output These methods provide output via the console. - * - * @groupname io-default IO Defaults - * @groupprio io-default 50 - * @groupdesc io-default These values provide direct access to the standard IO channels - * - * @groupname io-redefinition IO Redefinition - * @groupprio io-redefinition 60 - * @groupdesc io-redefinition These methods allow substituting alternative streams for the duration of - * a body of code. Threadsafe by virtue of [[scala.util.DynamicVariable]]. - * - */ -object Console extends AnsiColor { - private[this] val outVar = new DynamicVariable[PrintStream](java.lang.System.out) - private[this] val errVar = new DynamicVariable[PrintStream](java.lang.System.err) - private[this] val inVar = new DynamicVariable[BufferedReader](null) - //new BufferedReader(new InputStreamReader(java.lang.System.in))) - - protected def setOutDirect(out: PrintStream): Unit = outVar.value = out - protected def setErrDirect(err: PrintStream): Unit = errVar.value = err - protected def setInDirect(in: BufferedReader): Unit = inVar.value = in - - /** The default output, can be overridden by `withOut` - * @group io-default - */ - def out = outVar.value - /** The default error, can be overridden by `withErr` - * @group io-default - */ - def err = errVar.value - /** The default input, can be overridden by `withIn` - * @group io-default - */ - def in = inVar.value - - /** Sets the default output stream for the duration - * of execution of one thunk. - * - * @example {{{ - * withOut(Console.err) { println("This goes to default _error_") } - * }}} - * - * @param out the new output stream. - * @param thunk the code to execute with - * the new output stream active - * @return the results of `thunk` - * @see `withOut[T](out:OutputStream)(thunk: => T)` - * @group io-redefinition - */ - def withOut[T](out: PrintStream)(thunk: =>T): T = - outVar.withValue(out)(thunk) - - /** Sets the default output stream for the duration - * of execution of one thunk. - * - * @param out the new output stream. - * @param thunk the code to execute with - * the new output stream active - * @return the results of `thunk` - * @see `withOut[T](out:PrintStream)(thunk: => T)` - * @group io-redefinition - */ - def withOut[T](out: OutputStream)(thunk: =>T): T = - withOut(new PrintStream(out))(thunk) - - /** Set the default error stream for the duration - * of execution of one thunk. - * @example {{{ - * withErr(Console.out) { err.println("This goes to default _out_") } - * }}} - * - * @param err the new error stream. - * @param thunk the code to execute with - * the new error stream active - * @return the results of `thunk` - * @see `withErr[T](err:OutputStream)(thunk: =>T)` - * @group io-redefinition - */ - def withErr[T](err: PrintStream)(thunk: =>T): T = - errVar.withValue(err)(thunk) - - /** Sets the default error stream for the duration - * of execution of one thunk. - * - * @param err the new error stream. - * @param thunk the code to execute with - * the new error stream active - * @return the results of `thunk` - * @see `withErr[T](err:PrintStream)(thunk: =>T)` - * @group io-redefinition - */ - def withErr[T](err: OutputStream)(thunk: =>T): T = - withErr(new PrintStream(err))(thunk) - - /** Sets the default input stream for the duration - * of execution of one thunk. - * - * @example {{{ - * val someFile:Reader = openFile("file.txt") - * withIn(someFile) { - * // Reads a line from file.txt instead of default input - * println(readLine) - * } - * }}} - * - * @param thunk the code to execute with - * the new input stream active - * - * @return the results of `thunk` - * @see `withIn[T](in:InputStream)(thunk: =>T)` - * @group io-redefinition - */ - def withIn[T](reader: Reader)(thunk: =>T): T = - inVar.withValue(new BufferedReader(reader))(thunk) - - /** Sets the default input stream for the duration - * of execution of one thunk. - * - * @param in the new input stream. - * @param thunk the code to execute with - * the new input stream active - * @return the results of `thunk` - * @see `withIn[T](reader:Reader)(thunk: =>T)` - * @group io-redefinition - */ - def withIn[T](in: InputStream)(thunk: =>T): T = - withIn(new InputStreamReader(in))(thunk) - - /** Prints an object to `out` using its `toString` method. - * - * @param obj the object to print; may be null. - * @group console-output - */ - def print(obj: Any): Unit = { - out.print(if (null == obj) "null" else obj.toString()) - } - - /** Flushes the output stream. This function is required when partial - * output (i.e. output not terminated by a newline character) has - * to be made visible on the terminal. - * @group console-output - */ - def flush(): Unit = { out.flush() } - - /** Prints a newline character on the default output. - * @group console-output - */ - def println(): Unit = { out.println() } - - /** Prints out an object to the default output, followed by a newline character. - * - * @param x the object to print. - * @group console-output - */ - def println(x: Any): Unit = { out.println(x) } - - /** Prints its arguments as a formatted string to the default output, - * based on a string pattern (in a fashion similar to printf in C). - * - * The interpretation of the formatting patterns is described in [[java.util.Formatter]]. - * - * @param text the pattern for formatting the arguments. - * @param args the arguments used to instantiating the pattern. - * @throws java.lang.IllegalArgumentException if there was a problem with the format string or arguments - * @group console-output - */ - def printf(text: String, args: Any*): Unit = { out.print(text format (args : _*)) } -} diff --git a/scalalib/overrides-2.13.0-M5/scala/Enumeration.scala b/scalalib/overrides-2.13.0-M5/scala/Enumeration.scala deleted file mode 100644 index c3eb4526c0..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/Enumeration.scala +++ /dev/null @@ -1,327 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala - -import scala.collection.{SpecificIterableFactory, StrictOptimizedIterableOps, View, immutable, mutable} -import java.lang.reflect.{Field => JField, Method => JMethod} - -import scala.annotation.implicitNotFound -import scala.reflect.NameTransformer._ -import scala.util.matching.Regex - -/** Defines a finite set of values specific to the enumeration. Typically - * these values enumerate all possible forms something can take and provide - * a lightweight alternative to case classes. - * - * Each call to a `Value` method adds a new unique value to the enumeration. - * To be accessible, these values are usually defined as `val` members of - * the enumeration. - * - * All values in an enumeration share a common, unique type defined as the - * `Value` type member of the enumeration (`Value` selected on the stable - * identifier path of the enumeration instance). - * - * Values SHOULD NOT be added to an enumeration after its construction; - * doing so makes the enumeration thread-unsafe. If values are added to an - * enumeration from multiple threads (in a non-synchronized fashion) after - * construction, the behavior of the enumeration is undefined. - * - * @example {{{ - * // Define a new enumeration with a type alias and work with the full set of enumerated values - * object WeekDay extends Enumeration { - * type WeekDay = Value - * val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value - * } - * import WeekDay._ - * - * def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun) - * - * WeekDay.values filter isWorkingDay foreach println - * // output: - * // Mon - * // Tue - * // Wed - * // Thu - * // Fri - * }}} - * - * @example {{{ - * // Example of adding attributes to an enumeration by extending the Enumeration.Val class - * object Planet extends Enumeration { - * protected case class Val(mass: Double, radius: Double) extends super.Val { - * def surfaceGravity: Double = Planet.G * mass / (radius * radius) - * def surfaceWeight(otherMass: Double): Double = otherMass * surfaceGravity - * } - * implicit def valueToPlanetVal(x: Value): Val = x.asInstanceOf[Val] - * - * val G: Double = 6.67300E-11 - * val Mercury = Val(3.303e+23, 2.4397e6) - * val Venus = Val(4.869e+24, 6.0518e6) - * val Earth = Val(5.976e+24, 6.37814e6) - * val Mars = Val(6.421e+23, 3.3972e6) - * val Jupiter = Val(1.9e+27, 7.1492e7) - * val Saturn = Val(5.688e+26, 6.0268e7) - * val Uranus = Val(8.686e+25, 2.5559e7) - * val Neptune = Val(1.024e+26, 2.4746e7) - * } - * - * println(Planet.values.filter(_.radius > 7.0e6)) - * // output: - * // Planet.ValueSet(Jupiter, Saturn, Uranus, Neptune) - * }}} - * - * @param initial The initial value from which to count the integers that - * identifies values at run-time. - * @author Matthias Zenger - */ -@SerialVersionUID(8476000850333817230L) -abstract class Enumeration (initial: Int) extends Serializable { - thisenum => - - def this() = this(0) - - /* Note that `readResolve` cannot be private, since otherwise - the JVM does not invoke it when deserializing subclasses. */ - protected def readResolve(): AnyRef = ??? - - /** The name of this enumeration. - */ - override def toString = - (getClass.getName.stripSuffix("$").split('.')).last.split('$').last - - /** The mapping from the integer used to identify values to the actual - * values. */ - private val vmap: mutable.Map[Int, Value] = new mutable.HashMap - - /** The cache listing all values of this enumeration. */ - @transient private var vset: ValueSet = null - @transient @volatile private var vsetDefined = false - - /** The mapping from the integer used to identify values to their - * names. */ - private[this] val nmap: mutable.Map[Int, String] = new mutable.HashMap - - /** The values of this enumeration as a set. - */ - def values: ValueSet = { - if (!vsetDefined) { - vset = (ValueSet.newBuilder ++= vmap.values).result() - vsetDefined = true - } - vset - } - - /** The integer to use to identify the next created value. */ - protected var nextId: Int = initial - - /** The string to use to name the next created value. */ - protected var nextName: Iterator[String] = _ - - private def nextNameOrNull = - if (nextName != null && nextName.hasNext) nextName.next() else null - - /** The highest integer amongst those used to identify values in this - * enumeration. */ - private[this] var topId = initial - - /** The lowest integer amongst those used to identify values in this - * enumeration, but no higher than 0. */ - private[this] var bottomId = if(initial < 0) initial else 0 - - /** The one higher than the highest integer amongst those used to identify - * values in this enumeration. */ - final def maxId = topId - - /** The value of this enumeration with given id `x` - */ - final def apply(x: Int): Value = vmap(x) - - /** Return a `Value` from this `Enumeration` whose name matches - * the argument `s`. The names are determined automatically via reflection. - * - * @param s an `Enumeration` name - * @return the `Value` of this `Enumeration` if its name matches `s` - * @throws NoSuchElementException if no `Value` with a matching - * name is in this `Enumeration` - */ - final def withName(s: String): Value = { - val (unnamed, named) = values partition { - _.toString().startsWith(" v - // If we have unnamed values, we issue a detailed error message - case None if unnamed.nonEmpty => - throw new NoSuchElementException( - s"""Couldn't find enum field with name $s. - |However, there were the following unnamed fields: - |${unnamed.mkString(" ","\n ","")}""".stripMargin) - // Normal case (no unnamed Values) - case _ => None.get - } - } - - /** Creates a fresh value, part of this enumeration. */ - protected final def Value: Value = Value(nextId) - - /** Creates a fresh value, part of this enumeration, identified by the - * integer `i`. - * - * @param i An integer that identifies this value at run-time. It must be - * unique amongst all values of the enumeration. - * @return Fresh value identified by `i`. - */ - protected final def Value(i: Int): Value = Value(i, nextNameOrNull) - - /** Creates a fresh value, part of this enumeration, called `name`. - * - * @param name A human-readable name for that value. - * @return Fresh value called `name`. - */ - protected final def Value(name: String): Value = Value(nextId, name) - - /** Creates a fresh value, part of this enumeration, called `name` - * and identified by the integer `i`. - * - * @param i An integer that identifies this value at run-time. It must be - * unique amongst all values of the enumeration. - * @param name A human-readable name for that value. - * @return Fresh value with the provided identifier `i` and name `name`. - */ - protected final def Value(i: Int, name: String): Value = new Val(i, name) - - /** The type of the enumerated values. */ - @SerialVersionUID(7091335633555234129L) - abstract class Value extends Ordered[Value] with Serializable { - /** the id and bit location of this enumeration value */ - def id: Int - /** a marker so we can tell whose values belong to whom come reflective-naming time */ - private[Enumeration] val outerEnum = thisenum - - override def compare(that: Value): Int = - if (this.id < that.id) -1 - else if (this.id == that.id) 0 - else 1 - override def equals(other: Any) = other match { - case that: Enumeration#Value => (outerEnum eq that.outerEnum) && (id == that.id) - case _ => false - } - override def hashCode: Int = id.## - - /** Create a ValueSet which contains this value and another one */ - def + (v: Value) = ValueSet(this, v) - } - - /** A class implementing the [[scala.Enumeration.Value]] type. This class - * can be overridden to change the enumeration's naming and integer - * identification behaviour. - */ - @SerialVersionUID(0 - 3501153230598116017L) - protected class Val(i: Int, name: String) extends Value with Serializable { - def this(i: Int) = this(i, nextNameOrNull) - def this(name: String) = this(nextId, name) - def this() = this(nextId) - - assert(!vmap.isDefinedAt(i), "Duplicate id: " + i) - vmap(i) = this - vsetDefined = false - nextId = i + 1 - if (nextId > topId) topId = nextId - if (i < bottomId) bottomId = i - def id = i - override def toString() = - if (name != null) name - // Scala.js specific - else s"" - - protected def readResolve(): AnyRef = { - val enumeration = thisenum.readResolve().asInstanceOf[Enumeration] - if (enumeration.vmap == null) this - else enumeration.vmap(i) - } - } - - /** An ordering by id for values of this set */ - object ValueOrdering extends Ordering[Value] { - def compare(x: Value, y: Value): Int = x compare y - } - - /** A class for sets of values. - * Iterating through this set will yield values in increasing order of their ids. - * - * @param nnIds The set of ids of values (adjusted so that the lowest value does - * not fall below zero), organized as a `BitSet`. - * @define Coll `collection.immutable.SortedSet` - */ - class ValueSet private[ValueSet] (private[this] var nnIds: immutable.BitSet) - extends immutable.AbstractSet[Value] - with immutable.SortedSet[Value] - with immutable.SortedSetOps[Value, immutable.SortedSet, ValueSet] - with StrictOptimizedIterableOps[Value, immutable.Set, ValueSet] - with Serializable { - - implicit def ordering: Ordering[Value] = ValueOrdering - def rangeImpl(from: Option[Value], until: Option[Value]): ValueSet = - new ValueSet(nnIds.rangeImpl(from.map(_.id - bottomId), until.map(_.id - bottomId))) - - override def empty = ValueSet.empty - override def knownSize: Int = nnIds.size - override def isEmpty: Boolean = nnIds.isEmpty - def contains(v: Value) = nnIds contains (v.id - bottomId) - def incl (value: Value) = new ValueSet(nnIds + (value.id - bottomId)) - def excl (value: Value) = new ValueSet(nnIds - (value.id - bottomId)) - def iterator = nnIds.iterator map (id => thisenum.apply(bottomId + id)) - override def iteratorFrom(start: Value) = nnIds iteratorFrom start.id map (id => thisenum.apply(bottomId + id)) - override def className = thisenum + ".ValueSet" - /** Creates a bit mask for the zero-adjusted ids in this set as a - * new array of longs */ - def toBitMask: Array[Long] = nnIds.toBitMask - - override protected def fromSpecific(coll: IterableOnce[Value]) = ValueSet.fromSpecific(coll) - override protected def newSpecificBuilder = ValueSet.newBuilder - - def map(f: Value => Value): ValueSet = fromSpecific(new View.Map(toIterable, f)) - def flatMap(f: Value => IterableOnce[Value]): ValueSet = fromSpecific(new View.FlatMap(toIterable, f)) - - // necessary for disambiguation: - override def map[B](f: Value => B)(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): SortedIterableCC[B] = - super[SortedSet].map[B](f) - override def flatMap[B](f: Value => IterableOnce[B])(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): SortedIterableCC[B] = - super[SortedSet].flatMap[B](f) - override def zip[B](that: IterableOnce[B])(implicit @implicitNotFound(ValueSet.zipOrdMsg) ev: Ordering[(Value, B)]): SortedIterableCC[(Value, B)] = - super[SortedSet].zip[B](that) - override def collect[B](pf: PartialFunction[Value, B])(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): SortedIterableCC[B] = - super[SortedSet].collect[B](pf) - - override protected[this] def writeReplace(): AnyRef = this - } - - /** A factory object for value sets */ - @SerialVersionUID(3L) - object ValueSet extends SpecificIterableFactory[Value, ValueSet] { - private final val ordMsg = "No implicit Ordering[${B}] found to build a SortedSet[${B}]. You may want to upcast to a Set[Value] first by calling `unsorted`." - private final val zipOrdMsg = "No implicit Ordering[${B}] found to build a SortedSet[(Value, ${B})]. You may want to upcast to a Set[Value] first by calling `unsorted`." - - /** The empty value set */ - val empty = new ValueSet(immutable.BitSet.empty) - /** A value set containing all the values for the zero-adjusted ids - * corresponding to the bits in an array */ - def fromBitMask(elems: Array[Long]): ValueSet = new ValueSet(immutable.BitSet.fromBitMask(elems)) - /** A builder object for value sets */ - def newBuilder: mutable.Builder[Value, ValueSet] = new mutable.Builder[Value, ValueSet] { - private[this] val b = new mutable.BitSet - def addOne (x: Value) = { b += (x.id - bottomId); this } - def clear() = b.clear() - def result() = new ValueSet(b.toImmutable) - } - def fromSpecific(it: IterableOnce[Value]): ValueSet = - newBuilder.addAll(it).result() - } -} diff --git a/scalalib/overrides-2.13.0-M5/scala/collection/immutable/NumericRange.scala b/scalalib/overrides-2.13.0-M5/scala/collection/immutable/NumericRange.scala deleted file mode 100644 index ecea6fd020..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/collection/immutable/NumericRange.scala +++ /dev/null @@ -1,411 +0,0 @@ -package scala.collection.immutable - -import scala.collection.{AbstractIterator, Iterator} - -import java.lang.String - -/** `NumericRange` is a more generic version of the - * `Range` class which works with arbitrary types. - * It must be supplied with an `Integral` implementation of the - * range type. - * - * Factories for likely types include `Range.BigInt`, `Range.Long`, - * and `Range.BigDecimal`. `Range.Int` exists for completeness, but - * the `Int`-based `scala.Range` should be more performant. - * - * {{{ - * val r1 = Range(0, 100, 1) - * val veryBig = Int.MaxValue.toLong + 1 - * val r2 = Range.Long(veryBig, veryBig + 100, 1) - * assert(r1 sameElements r2.map(_ - veryBig)) - * }}} - * - * @define Coll `NumericRange` - * @define coll numeric range - * @define mayNotTerminateInf - * @define willNotTerminateInf - */ -@SerialVersionUID(3L) -sealed class NumericRange[T]( - val start: T, - val end: T, - val step: T, - val isInclusive: Boolean -)(implicit - num: Integral[T] -) - extends AbstractSeq[T] - with IndexedSeq[T] - with IndexedSeqOps[T, IndexedSeq, IndexedSeq[T]] - with StrictOptimizedSeqOps[T, IndexedSeq, IndexedSeq[T]] { self => - - override def iterator: Iterator[T] = new NumericRange.NumericRangeIterator(this, num) - - /** Note that NumericRange must be invariant so that constructs - * such as "1L to 10 by 5" do not infer the range type as AnyVal. - */ - import num._ - - // See comment in Range for why this must be lazy. - override lazy val length: Int = NumericRange.count(start, end, step, isInclusive) - override def isEmpty = length == 0 - override def last: T = - if (length == 0) Nil.head - else locationAfterN(length - 1) - override def init: NumericRange[T] = - if (isEmpty) Nil.init - else new NumericRange(start, end - step, step, isInclusive) - - override def head: T = if (isEmpty) Nil.head else start - override def tail: NumericRange[T] = - if (isEmpty) Nil.tail - else if(isInclusive) new NumericRange.Inclusive(start + step, end, step) - else new NumericRange.Exclusive(start + step, end, step) - - /** Create a new range with the start and end values of this range and - * a new `step`. - */ - def by(newStep: T): NumericRange[T] = copy(start, end, newStep) - - - /** Create a copy of this range. - */ - def copy(start: T, end: T, step: T): NumericRange[T] = - new NumericRange(start, end, step, isInclusive) - - @throws[IndexOutOfBoundsException] - def apply(idx: Int): T = { - if (idx < 0 || idx >= length) throw new IndexOutOfBoundsException(idx.toString) - else locationAfterN(idx) - } - - override def foreach[@specialized(Unit) U](f: T => U): Unit = { - var count = 0 - var current = start - while (count < length) { - f(current) - current += step - count += 1 - } - } - - // TODO: these private methods are straight copies from Range, duplicated - // to guard against any (most likely illusory) performance drop. They should - // be eliminated one way or another. - - // Tests whether a number is within the endpoints, without testing - // whether it is a member of the sequence (i.e. when step > 1.) - private def isWithinBoundaries(elem: T) = !isEmpty && ( - (step > zero && start <= elem && elem <= last ) || - (step < zero && last <= elem && elem <= start) - ) - // Methods like apply throw exceptions on invalid n, but methods like take/drop - // are forgiving: therefore the checks are with the methods. - private def locationAfterN(n: Int): T = start + (step * fromInt(n)) - - // When one drops everything. Can't ever have unchecked operations - // like "end + 1" or "end - 1" because ranges involving Int.{ MinValue, MaxValue } - // will overflow. This creates an exclusive range where start == end - // based on the given value. - private def newEmptyRange(value: T) = NumericRange(value, value, step) - - override def take(n: Int): NumericRange[T] = { - if (n <= 0 || length == 0) newEmptyRange(start) - else if (n >= length) this - else new NumericRange.Inclusive(start, locationAfterN(n - 1), step) - } - - override def drop(n: Int): NumericRange[T] = { - if (n <= 0 || length == 0) this - else if (n >= length) newEmptyRange(end) - else copy(locationAfterN(n), end, step) - } - - override def splitAt(n: Int): (NumericRange[T], NumericRange[T]) = (take(n), drop(n)) - - override def reverse: NumericRange[T] = - if (isEmpty) this else new NumericRange.Inclusive(last, start, -step) - - import NumericRange.defaultOrdering - - override def min[T1 >: T](implicit ord: Ordering[T1]): T = - // We can take the fast path: - // - If the Integral of this NumericRange is also the requested Ordering - // (Integral <: Ordering). This can happen for custom Integral types. - // - The Ordering is the default Ordering of a well-known Integral type. - if ((ord eq num) || defaultOrdering.get(num).exists(ord eq _)) { - if (num.signum(step) > 0) head - else last - } else super.min(ord) - - override def max[T1 >: T](implicit ord: Ordering[T1]): T = - // See comment for fast path in min(). - if ((ord eq num) || defaultOrdering.get(num).exists(ord eq _)) { - if (num.signum(step) > 0) last - else head - } else super.max(ord) - - // Motivated by the desire for Double ranges with BigDecimal precision, - // we need some way to map a Range and get another Range. This can't be - // done in any fully general way because Ranges are not arbitrary - // sequences but step-valued, so we have a custom method only we can call - // which we promise to use responsibly. - // - // The point of it all is that - // - // 0.0 to 1.0 by 0.1 - // - // should result in - // - // NumericRange[Double](0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0) - // - // and not - // - // NumericRange[Double](0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9) - // - // or perhaps more importantly, - // - // (0.1 to 0.3 by 0.1 contains 0.3) == true - // - private[immutable] def mapRange[A](fm: T => A)(implicit unum: Integral[A]): NumericRange[A] = { - val self = this - - // XXX This may be incomplete. - new NumericRange[A](fm(start), fm(end), fm(step), isInclusive) { - - private[this] lazy val underlyingRange: NumericRange[T] = self - override def foreach[@specialized(Unit) U](f: A => U): Unit = { underlyingRange foreach (x => f(fm(x))) } - override def isEmpty = underlyingRange.isEmpty - override def apply(idx: Int): A = fm(underlyingRange(idx)) - override def containsTyped(el: A) = underlyingRange exists (x => fm(x) == el) - - override def toString = { - def simpleOf(x: Any): String = x.getClass.getName.split("\\.").last - val stepped = simpleOf(underlyingRange.step) - s"${super.toString} (using $underlyingRange of $stepped)" - } - } - } - - // a well-typed contains method. - def containsTyped(x: T): Boolean = - isWithinBoundaries(x) && (((x - start) % step) == zero) - - override def contains[A1 >: T](x: A1): Boolean = - try containsTyped(x.asInstanceOf[T]) - catch { case _: ClassCastException => false } - - override def sum[B >: T](implicit num: Numeric[B]): B = { - if (isEmpty) num.zero - else if (size == 1) head - else { - // If there is no overflow, use arithmetic series formula - // a + ... (n terms total) ... + b = n*(a+b)/2 - if ((num eq scala.math.Numeric.IntIsIntegral)|| - (num eq scala.math.Numeric.ShortIsIntegral)|| - (num eq scala.math.Numeric.ByteIsIntegral)|| - (num eq scala.math.Numeric.CharIsIntegral)) { - // We can do math with no overflow in a Long--easy - val exact = (size * ((num toLong head) + (num toInt last))) / 2 - num fromInt exact.toInt - } - else if (num eq scala.math.Numeric.LongIsIntegral) { - // Uh-oh, might be overflow, so we have to divide before we overflow. - // Either numRangeElements or (head + last) must be even, so divide the even one before multiplying - val a = head.toLong - val b = last.toLong - val ans = - if ((size & 1) == 0) (size / 2) * (a + b) - else size * { - // Sum is even, but we might overflow it, so divide in pieces and add back remainder - val ha = a/2 - val hb = b/2 - ha + hb + ((a - 2*ha) + (b - 2*hb)) / 2 - } - ans.asInstanceOf[B] - } - else { - // User provided custom Numeric, so we cannot rely on arithmetic series formula (e.g. won't work on something like Z_6) - if (isEmpty) num.zero - else { - var acc = num.zero - var i = head - var idx = 0 - while(idx < length) { - acc = num.plus(acc, i) - i = i + step - idx = idx + 1 - } - acc - } - } - } - } - - override lazy val hashCode: Int = super.hashCode() - override def equals(other: Any): Boolean = other match { - case x: NumericRange[_] => - (x canEqual this) && (length == x.length) && ( - (length == 0) || // all empty sequences are equal - (start == x.start && last == x.last) // same length and same endpoints implies equality - ) - case _ => - super.equals(other) - } - - override def toString: String = { - val empty = if (isEmpty) "empty " else "" - val preposition = if (isInclusive) "to" else "until" - val stepped = if (step == 1) "" else s" by $step" - s"${empty}NumericRange $start $preposition $end$stepped" - } - - override protected[this] def writeReplace(): AnyRef = this - - override protected[this] def className = "NumericRange" -} - -/** A companion object for numeric ranges. - * @define Coll `NumericRange` - * @define coll numeric range - */ -object NumericRange { - - /** Calculates the number of elements in a range given start, end, step, and - * whether or not it is inclusive. Throws an exception if step == 0 or - * the number of elements exceeds the maximum Int. - */ - def count[T](start: T, end: T, step: T, isInclusive: Boolean)(implicit num: Integral[T]): Int = { - val zero = num.zero - val upward = num.lt(start, end) - val posStep = num.gt(step, zero) - - if (step == zero) throw new IllegalArgumentException("step cannot be 0.") - else if (start == end) if (isInclusive) 1 else 0 - else if (upward != posStep) 0 - else { - /* We have to be frightfully paranoid about running out of range. - * We also can't assume that the numbers will fit in a Long. - * We will assume that if a > 0, -a can be represented, and if - * a < 0, -a+1 can be represented. We also assume that if we - * can't fit in Int, we can represent 2*Int.MaxValue+3 (at least). - * And we assume that numbers wrap rather than cap when they overflow. - */ - // Check whether we can short-circuit by deferring to Int range. - val startint = num.toInt(start) - if (start == num.fromInt(startint)) { - val endint = num.toInt(end) - if (end == num.fromInt(endint)) { - val stepint = num.toInt(step) - if (step == num.fromInt(stepint)) { - return { - if (isInclusive) Range.inclusive(startint, endint, stepint).length - else Range (startint, endint, stepint).length - } - } - } - } - // If we reach this point, deferring to Int failed. - // Numbers may be big. - val one = num.one - val limit = num.fromInt(Int.MaxValue) - def check(t: T): T = - if (num.gt(t, limit)) throw new IllegalArgumentException("More than Int.MaxValue elements.") - else t - // If the range crosses zero, it might overflow when subtracted - val startside = num.signum(start) - val endside = num.signum(end) - num.toInt{ - if (startside*endside >= 0) { - // We're sure we can subtract these numbers. - // Note that we do not use .rem because of different conventions for Long and BigInt - val diff = num.minus(end, start) - val quotient = check(num.quot(diff, step)) - val remainder = num.minus(diff, num.times(quotient, step)) - if (!isInclusive && zero == remainder) quotient else check(num.plus(quotient, one)) - } - else { - // We might not even be able to subtract these numbers. - // Jump in three pieces: - // * start to -1 or 1, whichever is closer (waypointA) - // * one step, which will take us at least to 0 (ends at waypointB) - // * there to the end - val negone = num.fromInt(-1) - val startlim = if (posStep) negone else one - val startdiff = num.minus(startlim, start) - val startq = check(num.quot(startdiff, step)) - val waypointA = if (startq == zero) start else num.plus(start, num.times(startq, step)) - val waypointB = num.plus(waypointA, step) - check { - if (num.lt(waypointB, end) != upward) { - // No last piece - if (isInclusive && waypointB == end) num.plus(startq, num.fromInt(2)) - else num.plus(startq, one) - } - else { - // There is a last piece - val enddiff = num.minus(end,waypointB) - val endq = check(num.quot(enddiff, step)) - val last = if (endq == zero) waypointB else num.plus(waypointB, num.times(endq, step)) - // Now we have to tally up all the pieces - // 1 for the initial value - // startq steps to waypointA - // 1 step to waypointB - // endq steps to the end (one less if !isInclusive and last==end) - num.plus(startq, num.plus(endq, if (!isInclusive && last==end) one else num.fromInt(2))) - } - } - } - } - } - } - - @SerialVersionUID(3L) - class Inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) - extends NumericRange(start, end, step, true) { - override def copy(start: T, end: T, step: T): Inclusive[T] = - NumericRange.inclusive(start, end, step) - - def exclusive: Exclusive[T] = NumericRange(start, end, step) - } - - @SerialVersionUID(3L) - class Exclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) - extends NumericRange(start, end, step, false) { - override def copy(start: T, end: T, step: T): Exclusive[T] = - NumericRange(start, end, step) - - def inclusive: Inclusive[T] = NumericRange.inclusive(start, end, step) - } - - def apply[T](start: T, end: T, step: T)(implicit num: Integral[T]): Exclusive[T] = - new Exclusive(start, end, step) - def inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]): Inclusive[T] = - new Inclusive(start, end, step) - - private[collection] val defaultOrdering = Map[Numeric[_], Ordering[_]]( - Numeric.IntIsIntegral -> Ordering.Int, - Numeric.ShortIsIntegral -> Ordering.Short, - Numeric.ByteIsIntegral -> Ordering.Byte, - Numeric.CharIsIntegral -> Ordering.Char, - Numeric.LongIsIntegral -> Ordering.Long - ) - - @SerialVersionUID(3L) - private final class NumericRangeIterator[T](self: NumericRange[T], num: Integral[T]) extends AbstractIterator[T] with Serializable { - import num.mkNumericOps - - private[this] var _hasNext = !self.isEmpty - private[this] var _next: T = self.start - private[this] val lastElement: T = if (_hasNext) self.last else self.start - override def knownSize: Int = if (_hasNext) num.toInt((lastElement - _next) / self.step) + 1 else 0 - def hasNext: Boolean = _hasNext - def next(): T = { - if (!_hasNext) Iterator.empty.next() - val value = _next - _hasNext = value != lastElement - _next = num.plus(value, self.step) - value - } - } -} diff --git a/scalalib/overrides-2.13.0-M5/scala/collection/immutable/Range.scala b/scalalib/overrides-2.13.0-M5/scala/collection/immutable/Range.scala deleted file mode 100644 index 47ac921e49..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/collection/immutable/Range.scala +++ /dev/null @@ -1,529 +0,0 @@ -package scala -package collection.immutable - -import collection.{AbstractIterator, Iterator} -import java.lang.String - -/** The `Range` class represents integer values in range - * ''[start;end)'' with non-zero step value `step`. - * It's a special case of an indexed sequence. - * For example: - * - * {{{ - * val r1 = 0 until 10 - * val r2 = r1.start until r1.end by r1.step + 1 - * println(r2.length) // = 5 - * }}} - * - * Ranges that contain more than `Int.MaxValue` elements can be created, but - * these overfull ranges have only limited capabilities. Any method that - * could require a collection of over `Int.MaxValue` length to be created, or - * could be asked to index beyond `Int.MaxValue` elements will throw an - * exception. Overfull ranges can safely be reduced in size by changing - * the step size (e.g. `by 3`) or taking/dropping elements. `contains`, - * `equals`, and access to the ends of the range (`head`, `last`, `tail`, - * `init`) are also permitted on overfull ranges. - * - * @param start the start of this range. - * @param end the end of the range. For exclusive ranges, e.g. - * `Range(0,3)` or `(0 until 3)`, this is one - * step past the last one in the range. For inclusive - * ranges, e.g. `Range.inclusive(0,3)` or `(0 to 3)`, - * it may be in the range if it is not skipped by the step size. - * To find the last element inside a non-empty range, - * use `last` instead. - * @param step the step for the range. - * - * @define coll range - * @define mayNotTerminateInf - * @define willNotTerminateInf - * @define doesNotUseBuilders - * '''Note:''' this method does not use builders to construct a new range, - * and its complexity is O(1). - */ -@SerialVersionUID(3L) -sealed abstract class Range( - val start: Int, - val end: Int, - val step: Int -) - extends AbstractSeq[Int] - with IndexedSeq[Int] - with IndexedSeqOps[Int, IndexedSeq, IndexedSeq[Int]] - with StrictOptimizedSeqOps[Int, IndexedSeq, IndexedSeq[Int]] { range => - - final override def iterator: Iterator[Int] = new RangeIterator(start, step, lastElement, isEmpty) - - private[this] def gap = end.toLong - start.toLong - private[this] def isExact = gap % step == 0 - private[this] def hasStub = isInclusive || !isExact - private[this] def longLength = gap / step + ( if (hasStub) 1 else 0 ) - - def isInclusive: Boolean - - final override val isEmpty: Boolean = ( - (start > end && step > 0) - || (start < end && step < 0) - || (start == end && !isInclusive) - ) - - private[this] val numRangeElements: Int = { - if (step == 0) throw new IllegalArgumentException("step cannot be 0.") - else if (isEmpty) 0 - else { - val len = longLength - if (len > scala.Int.MaxValue) -1 - else len.toInt - } - } - - final def length = if (numRangeElements < 0) fail() else numRangeElements - - // This field has a sensible value only for non-empty ranges - private[this] val lastElement = step match { - case 1 => if (isInclusive) end else end-1 - case -1 => if (isInclusive) end else end+1 - case _ => - val remainder = (gap % step).toInt - if (remainder != 0) end - remainder - else if (isInclusive) end - else end - step - } - - /** The last element of this range. This method will return the correct value - * even if there are too many elements to iterate over. - */ - final override def last: Int = if (isEmpty) Nil.head else lastElement - final override def head: Int = if (isEmpty) Nil.head else start - - /** Creates a new range containing all the elements of this range except the last one. - * - * $doesNotUseBuilders - * - * @return a new range consisting of all the elements of this range except the last one. - */ - final override def init: Range = { - if (isEmpty) - Nil.init - - dropRight(1) - } - - /** Creates a new range containing all the elements of this range except the first one. - * - * $doesNotUseBuilders - * - * @return a new range consisting of all the elements of this range except the first one. - */ - final override def tail: Range = { - if (isEmpty) - Nil.tail - if (numRangeElements == 1) newEmptyRange(end) - else if(isInclusive) new Range.Inclusive(start + step, end, step) - else new Range.Exclusive(start + step, end, step) - } - - final protected def copy(start: Int = start, end: Int = end, step: Int = step, isInclusive: Boolean = isInclusive): Range = - if(isInclusive) new Range.Inclusive(start, end, step) else new Range.Exclusive(start, end, step) - - /** Create a new range with the `start` and `end` values of this range and - * a new `step`. - * - * @return a new range with a different step - */ - final def by(step: Int): Range = copy(start, end, step) - - // Check cannot be evaluated eagerly because we have a pattern where - // ranges are constructed like: "x to y by z" The "x to y" piece - // should not trigger an exception. So the calculation is delayed, - // which means it will not fail fast for those cases where failing was - // correct. - private[this] def validateMaxLength(): Unit = { - if (numRangeElements < 0) - fail() - } - private[this] def fail() = Range.fail(start, end, step, isInclusive) - - @throws[IndexOutOfBoundsException] - final def apply(idx: Int): Int = { - validateMaxLength() - if (idx < 0 || idx >= numRangeElements) throw new IndexOutOfBoundsException(idx.toString) - else start + (step * idx) - } - - /*@`inline`*/ final override def foreach[@specialized(Unit) U](f: Int => U): Unit = { - // Implementation chosen on the basis of favorable microbenchmarks - // Note--initialization catches step == 0 so we don't need to here - if (!isEmpty) { - var i = start - while (true) { - f(i) - if (i == lastElement) return - i += step - } - } - } - - /** Creates a new range containing the first `n` elements of this range. - * - * @param n the number of elements to take. - * @return a new range consisting of `n` first elements. - */ - final override def take(n: Int): Range = - if (n <= 0 || isEmpty) newEmptyRange(start) - else if (n >= numRangeElements && numRangeElements >= 0) this - else { - // May have more than Int.MaxValue elements in range (numRangeElements < 0) - // but the logic is the same either way: take the first n - new Range.Inclusive(start, locationAfterN(n - 1), step) - } - - /** Creates a new range containing all the elements of this range except the first `n` elements. - * - * @param n the number of elements to drop. - * @return a new range consisting of all the elements of this range except `n` first elements. - */ - final override def drop(n: Int): Range = - if (n <= 0 || isEmpty) this - else if (n >= numRangeElements && numRangeElements >= 0) newEmptyRange(end) - else { - // May have more than Int.MaxValue elements (numRangeElements < 0) - // but the logic is the same either way: go forwards n steps, keep the rest - copy(locationAfterN(n), end, step) - } - - /** Creates a new range consisting of the last `n` elements of the range. - * - * $doesNotUseBuilders - */ - final override def takeRight(n: Int): Range = { - if (n <= 0) newEmptyRange(start) - else if (numRangeElements >= 0) drop(numRangeElements - n) - else { - // Need to handle over-full range separately - val y = last - val x = y - step.toLong*(n-1) - if ((step > 0 && x < start) || (step < 0 && x > start)) this - else Range.inclusive(x.toInt, y, step) - } - } - - /** Creates a new range consisting of the initial `length - n` elements of the range. - * - * $doesNotUseBuilders - */ - final override def dropRight(n: Int): Range = { - if (n <= 0) this - else if (numRangeElements >= 0) take(numRangeElements - n) - else { - // Need to handle over-full range separately - val y = last - step.toInt*n - if ((step > 0 && y < start) || (step < 0 && y > start)) newEmptyRange(start) - else Range.inclusive(start, y.toInt, step) - } - } - - // Advance from the start while we meet the given test - private[this] def argTakeWhile(p: Int => Boolean): Long = { - if (isEmpty) start - else { - var current = start - val stop = last - while (current != stop && p(current)) current += step - if (current != stop || !p(current)) current - else current.toLong + step - } - } - - final override def takeWhile(p: Int => Boolean): Range = { - val stop = argTakeWhile(p) - if (stop==start) newEmptyRange(start) - else { - val x = (stop - step).toInt - if (x == last) this - else Range.inclusive(start, x, step) - } - } - - final override def dropWhile(p: Int => Boolean): Range = { - val stop = argTakeWhile(p) - if (stop == start) this - else { - val x = (stop - step).toInt - if (x == last) newEmptyRange(last) - else Range.inclusive(x + step, last, step) - } - } - - final override def span(p: Int => Boolean): (Range, Range) = { - val border = argTakeWhile(p) - if (border == start) (newEmptyRange(start), this) - else { - val x = (border - step).toInt - if (x == last) (this, newEmptyRange(last)) - else (Range.inclusive(start, x, step), Range.inclusive(x+step, last, step)) - } - } - - /** Creates a new range containing the elements starting at `from` up to but not including `until`. - * - * $doesNotUseBuilders - * - * @param from the element at which to start - * @param until the element at which to end (not included in the range) - * @return a new range consisting of a contiguous interval of values in the old range - */ - final override def slice(from: Int, until: Int): Range = - if (from <= 0) take(until) - else if (until >= numRangeElements && numRangeElements >= 0) drop(from) - else { - val fromValue = locationAfterN(from) - if (from >= until) newEmptyRange(fromValue) - else Range.inclusive(fromValue, locationAfterN(until-1), step) - } - - // Overridden only to refine the return type - final override def splitAt(n: Int): (Range, Range) = (take(n), drop(n)) - - // Methods like apply throw exceptions on invalid n, but methods like take/drop - // are forgiving: therefore the checks are with the methods. - private[this] def locationAfterN(n: Int) = start + (step * n) - - // When one drops everything. Can't ever have unchecked operations - // like "end + 1" or "end - 1" because ranges involving Int.{ MinValue, MaxValue } - // will overflow. This creates an exclusive range where start == end - // based on the given value. - private[this] def newEmptyRange(value: Int) = new Range.Exclusive(value, value, step) - - /** Returns the reverse of this range. - */ - final override def reverse: Range = - if (isEmpty) this - else new Range.Inclusive(last, start, -step) - - /** Make range inclusive. - */ - final def inclusive: Range = - if (isInclusive) this - else new Range.Inclusive(start, end, step) - - final def contains(x: Int) = { - if (x == end && !isInclusive) false - else if (step > 0) { - if (x < start || x > end) false - else (step == 1) || (((x - start) % step) == 0) - } - else { - if (x < end || x > start) false - else (step == -1) || (((x - start) % step) == 0) - } - } - - final override def sum[B >: Int](implicit num: Numeric[B]): Int = { - if (num eq scala.math.Numeric.IntIsIntegral) { - // this is normal integer range with usual addition. arithmetic series formula can be used - if (isEmpty) 0 - else if (size == 1) head - else ((size * (head.toLong + last)) / 2).toInt - } else { - // user provided custom Numeric, we cannot rely on arithmetic series formula - if (isEmpty) num.toInt(num.zero) - else { - var acc = num.zero - var i = head - while (true) { - acc = num.plus(acc, i) - if (i == lastElement) return num.toInt(acc) - i = i + step - } - 0 // Never hit this--just to satisfy compiler since it doesn't know while(true) has type Nothing - } - } - } - - final override def min[A1 >: Int](implicit ord: Ordering[A1]): Int = - if (ord eq Ordering.Int) { - if (step > 0) head - else last - } else super.min(ord) - - final override def max[A1 >: Int](implicit ord: Ordering[A1]): Int = - if (ord eq Ordering.Int) { - if (step > 0) last - else head - } else super.max(ord) - - - final override def equals(other: Any) = other match { - case x: Range => - // Note: this must succeed for overfull ranges (length > Int.MaxValue) - if (isEmpty) x.isEmpty // empty sequences are equal - else // this is non-empty... - x.nonEmpty && start == x.start && { // ...so other must contain something and have same start - val l0 = last - (l0 == x.last && ( // And same end - start == l0 || step == x.step // And either the same step, or not take any steps - )) - } - case _ => - super.equals(other) - } - - /* Note: hashCode can't be overridden without breaking Seq's equals contract. */ - - final override def toString: String = { - val preposition = if (isInclusive) "to" else "until" - val stepped = if (step == 1) "" else s" by $step" - val prefix = if (isEmpty) "empty " else if (!isExact) "inexact " else "" - s"${prefix}Range $start $preposition $end$stepped" - } - - final override protected[this] def writeReplace(): AnyRef = this - - override protected[this] def className = "Range" - - override def distinct: Range = this -} - -/** - * Companion object for ranges. - * @define Coll `Range` - * @define coll range - */ -object Range { - - private def description(start: Int, end: Int, step: Int, isInclusive: Boolean) = - start + (if (isInclusive) " to " else " until ") + end + " by " + step - - private def fail(start: Int, end: Int, step: Int, isInclusive: Boolean) = - throw new IllegalArgumentException(description(start, end, step, isInclusive) + - ": seqs cannot contain more than Int.MaxValue elements.") - - /** Counts the number of range elements. - * precondition: step != 0 - * If the size of the range exceeds Int.MaxValue, the - * result will be negative. - */ - def count(start: Int, end: Int, step: Int, isInclusive: Boolean): Int = { - if (step == 0) - throw new IllegalArgumentException("step cannot be 0.") - - val isEmpty = - if (start == end) !isInclusive - else if (start < end) step < 0 - else step > 0 - - if (isEmpty) 0 - else { - // Counts with Longs so we can recognize too-large ranges. - val gap: Long = end.toLong - start.toLong - val jumps: Long = gap / step - // Whether the size of this range is one larger than the - // number of full-sized jumps. - val hasStub = isInclusive || (gap % step != 0) - val result: Long = jumps + ( if (hasStub) 1 else 0 ) - - if (result > scala.Int.MaxValue) -1 - else result.toInt - } - } - def count(start: Int, end: Int, step: Int): Int = - count(start, end, step, isInclusive = false) - - /** Make a range from `start` until `end` (exclusive) with given step value. - * @note step != 0 - */ - def apply(start: Int, end: Int, step: Int): Range.Exclusive = new Range.Exclusive(start, end, step) - - /** Make a range from `start` until `end` (exclusive) with step value 1. - */ - def apply(start: Int, end: Int): Range.Exclusive = new Range.Exclusive(start, end, 1) - - /** Make an inclusive range from `start` to `end` with given step value. - * @note step != 0 - */ - def inclusive(start: Int, end: Int, step: Int): Range.Inclusive = new Range.Inclusive(start, end, step) - - /** Make an inclusive range from `start` to `end` with step value 1. - */ - def inclusive(start: Int, end: Int): Range.Inclusive = new Range.Inclusive(start, end, 1) - - @SerialVersionUID(3L) - @inline - final class Inclusive(start: Int, end: Int, step: Int) extends Range(start, end, step) { - def isInclusive = true - } - - @SerialVersionUID(3L) - @inline - final class Exclusive(start: Int, end: Int, step: Int) extends Range(start, end, step) { - def isInclusive = false - } - - // BigInt and Long are straightforward generic ranges. - object BigInt { - def apply(start: BigInt, end: BigInt, step: BigInt) = NumericRange(start, end, step) - def inclusive(start: BigInt, end: BigInt, step: BigInt) = NumericRange.inclusive(start, end, step) - } - - object Long { - def apply(start: Long, end: Long, step: Long) = NumericRange(start, end, step) - def inclusive(start: Long, end: Long, step: Long) = NumericRange.inclusive(start, end, step) - } - - // BigDecimal uses an alternative implementation of Numeric in which - // it pretends to be Integral[T] instead of Fractional[T]. See Numeric for - // details. The intention is for it to throw an exception anytime - // imprecision or surprises might result from anything, although this may - // not yet be fully implemented. - object BigDecimal { - implicit val bigDecAsIntegral: Numeric.BigDecimalAsIfIntegral = Numeric.BigDecimalAsIfIntegral - - def apply(start: BigDecimal, end: BigDecimal, step: BigDecimal) = - NumericRange(start, end, step) - def inclusive(start: BigDecimal, end: BigDecimal, step: BigDecimal) = - NumericRange.inclusive(start, end, step) - } - - // As there is no appealing default step size for not-really-integral ranges, - // we offer a partially constructed object. - class Partial[T, U](private val f: T => U) extends AnyVal { - def by(x: T): U = f(x) - override def toString = "Range requires step" - } - - // Illustrating genericity with Int Range, which should have the same behavior - // as the original Range class. However we leave the original Range - // indefinitely, for performance and because the compiler seems to bootstrap - // off it and won't do so with our parameterized version without modifications. - object Int { - def apply(start: Int, end: Int, step: Int) = NumericRange(start, end, step) - def inclusive(start: Int, end: Int, step: Int) = NumericRange.inclusive(start, end, step) - } - -} - -/** - * @param lastElement The last element included in the Range - * @param initiallyEmpty Whether the Range was initially empty or not - */ -@SerialVersionUID(3L) -private class RangeIterator( - start: Int, - step: Int, - lastElement: Int, - initiallyEmpty: Boolean -) extends AbstractIterator[Int] with Serializable { - private[this] var _hasNext: Boolean = !initiallyEmpty - private[this] var _next: Int = start - override def knownSize: Int = if (_hasNext) (lastElement - _next) / step + 1 else 0 - def hasNext: Boolean = _hasNext - @throws[NoSuchElementException] - def next(): Int = { - if (!_hasNext) Iterator.empty.next() - val value = _next - _hasNext = value != lastElement - _next = value + step - value - } -} diff --git a/scalalib/overrides-2.13.0-M5/scala/collection/mutable/ArrayBuilder.scala b/scalalib/overrides-2.13.0-M5/scala/collection/mutable/ArrayBuilder.scala deleted file mode 100644 index fcdf4f4c30..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/collection/mutable/ArrayBuilder.scala +++ /dev/null @@ -1,596 +0,0 @@ -package scala.collection -package mutable - -import scala.reflect.ClassTag -import scala.runtime.BoxedUnit - -import scala.scalajs.js - -/** A builder class for arrays. - * - * @since 2.8 - * - * @tparam T the type of the elements for the builder. - */ -@SerialVersionUID(3L) -sealed abstract class ArrayBuilder[T] - extends ReusableBuilder[T, Array[T]] - with Serializable { - protected[this] var capacity: Int = 0 - protected[this] def elems: Array[T] - protected var size: Int = 0 - - def length: Int = size - - protected[this] final def ensureSize(size: Int): Unit = { - if (capacity < size || capacity == 0) { - var newsize = if (capacity == 0) 16 else capacity * 2 - while (newsize < size) newsize *= 2 - resize(newsize) - } - } - - override final def sizeHint(size: Int): Unit = - if (capacity < size) resize(size) - - def clear(): Unit = size = 0 - - protected[this] def resize(size: Int): Unit - - /** Add all elements of an array */ - def addAll(xs: Array[_ <: T]): this.type = addAll(xs, 0, xs.length) - - /** Add a slice of an array */ - def addAll(xs: Array[_ <: T], offset: Int, length: Int): this.type = { - ensureSize(this.size + length) - Array.copy(xs, offset, elems, this.size, length) - size += length - this - } - - override def addAll(xs: IterableOnce[T]): this.type = { - val k = xs.knownSize - if(k > 0) { - ensureSize(this.size + k) - xs match { - case xs: Iterable[T] => xs.copyToArray(elems, this.size) - case _ => xs.iterator.copyToArray(elems, this.size) - } - size += k - } else if(k < 0) super.addAll(xs) - this - } -} - -/** A companion object for array builders. - * - * @since 2.8 - */ -object ArrayBuilder { - - /** Creates a new arraybuilder of type `T`. - * - * @tparam T type of the elements for the array builder, with a `ClassTag` context bound. - * @return a new empty array builder. - */ - @inline - def make[T: ClassTag]: ArrayBuilder[T] = - new ArrayBuilder.generic[T](implicitly[ClassTag[T]].runtimeClass) - - /** A generic ArrayBuilder optimized for Scala.js. - * - * @tparam T type of elements for the array builder. - * @param elementClass runtime class of the elements in the array. - */ - @inline - private final class generic[T](elementClass: Class[_]) extends ArrayBuilder[T] { - - private val isCharArrayBuilder = classOf[Char] == elementClass - protected[this] def elems: Array[T] = throw new Error("unreachable") - private var jsElems: js.Array[Any] = js.Array() - - def addOne(elem: T): this.type = { - val unboxedElem = - if (isCharArrayBuilder) elem.asInstanceOf[Char].toInt - else if (elem == null) zeroOf(elementClass) - else elem - jsElems.push(unboxedElem) - this - } - - /** Add a slice of an array */ - override def addAll(xs: Array[_ <: T], offset: Int, length: Int): this.type = { - ensureSize(this.size + length) - addAll(xs.view.slice(offset, length)) - this - } - - override def addAll(xs: IterableOnce[T]): this.type = { - val it = xs.iterator - while (it.hasNext) { - this += it.next() - } - this - } - - override def clear(): Unit = - jsElems = js.Array() - - protected[this] def resize(size: Int): Unit = () - - def result(): Array[T] = { - val elemRuntimeClass = - if (classOf[Unit] == elementClass) classOf[BoxedUnit] - else if (classOf[Null] == elementClass || classOf[Nothing] == elementClass) classOf[Object] - else elementClass - genericArrayBuilderResult(elemRuntimeClass, jsElems) - } - - override def toString(): String = "ArrayBuilder.generic" - } - - // Intrinsic - private def zeroOf(runtimeClass: Class[_]): Any = runtimeClass match { - case java.lang.Byte.TYPE => 0.toByte - case java.lang.Short.TYPE => 0.toShort - case java.lang.Character.TYPE => 0 // yes, as an Int - case java.lang.Integer.TYPE => 0 - case java.lang.Long.TYPE => 0L - case java.lang.Float.TYPE => 0.0f - case java.lang.Double.TYPE => 0.0 - case java.lang.Boolean.TYPE => false - case java.lang.Void.TYPE => () - case _ => null - } - - // Intrinsic - private def genericArrayBuilderResult[T](runtimeClass: Class[_], - a: js.Array[Any]): Array[T] = { - val len = a.length - - if (classOf[Char] == runtimeClass) { - val result = new Array[Char](len) - var i = 0 - while (i != len) { - result(i) = a(i).asInstanceOf[Int].toChar - i += 1 - } - result.asInstanceOf[Array[T]] - } else { - val result: Array[T] = java.lang.reflect.Array.newInstance( - runtimeClass, len).asInstanceOf[Array[T]] - var i = 0 - while (i != len) { - result(i) = a(i).asInstanceOf[T] - i += 1 - } - result - } - } - - /** A class for array builders for arrays of reference types. - * - * This builder can be reused. - * - * @tparam T type of elements for the array builder, subtype of `AnyRef` with a `ClassTag` context bound. - */ - @SerialVersionUID(3L) - final class ofRef[T <: AnyRef](implicit ct: ClassTag[T]) extends ArrayBuilder[T] { - - protected var elems: Array[T] = _ - - private def mkArray(size: Int): Array[T] = { - if (capacity == size && capacity > 0) elems - else if (elems eq null) new Array[T](size) - else java.util.Arrays.copyOf[T](elems, size) - } - - protected[this] def resize(size: Int): Unit = { - elems = mkArray(size) - capacity = size - } - - def addOne(elem: T): this.type = { - ensureSize(size + 1) - elems(size) = elem - size += 1 - this - } - - def result() = { - if (capacity != 0 && capacity == size) { - capacity = 0 - val res = elems - elems = null - res - } - else mkArray(size) - } - - override def clear(): Unit = { - super.clear() - if(elems ne null) java.util.Arrays.fill(elems.asInstanceOf[Array[AnyRef]], null) - } - - override def equals(other: Any): Boolean = other match { - case x: ofRef[_] => (size == x.size) && (elems == x.elems) - case _ => false - } - - override def toString = "ArrayBuilder.ofRef" - } - - /** A class for array builders for arrays of `byte`s. It can be reused. */ - @SerialVersionUID(3L) - final class ofByte extends ArrayBuilder[Byte] { - - protected var elems: Array[Byte] = _ - - private def mkArray(size: Int): Array[Byte] = { - val newelems = new Array[Byte](size) - if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) - newelems - } - - protected[this] def resize(size: Int): Unit = { - elems = mkArray(size) - capacity = size - } - - def addOne(elem: Byte): this.type = { - ensureSize(size + 1) - elems(size) = elem - size += 1 - this - } - - def result() = { - if (capacity != 0 && capacity == size) { - capacity = 0 - val res = elems - elems = null - res - } - else mkArray(size) - } - - override def equals(other: Any): Boolean = other match { - case x: ofByte => (size == x.size) && (elems == x.elems) - case _ => false - } - - override def toString = "ArrayBuilder.ofByte" - } - - /** A class for array builders for arrays of `short`s. It can be reused. */ - @SerialVersionUID(3L) - final class ofShort extends ArrayBuilder[Short] { - - protected var elems: Array[Short] = _ - - private def mkArray(size: Int): Array[Short] = { - val newelems = new Array[Short](size) - if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) - newelems - } - - protected[this] def resize(size: Int): Unit = { - elems = mkArray(size) - capacity = size - } - - def addOne(elem: Short): this.type = { - ensureSize(size + 1) - elems(size) = elem - size += 1 - this - } - - def result() = { - if (capacity != 0 && capacity == size) { - capacity = 0 - val res = elems - elems = null - res - } - else mkArray(size) - } - - override def equals(other: Any): Boolean = other match { - case x: ofShort => (size == x.size) && (elems == x.elems) - case _ => false - } - - override def toString = "ArrayBuilder.ofShort" - } - - /** A class for array builders for arrays of `char`s. It can be reused. */ - @SerialVersionUID(3L) - final class ofChar extends ArrayBuilder[Char] { - - protected var elems: Array[Char] = _ - - private def mkArray(size: Int): Array[Char] = { - val newelems = new Array[Char](size) - if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) - newelems - } - - protected[this] def resize(size: Int): Unit = { - elems = mkArray(size) - capacity = size - } - - def addOne(elem: Char): this.type = { - ensureSize(size + 1) - elems(size) = elem - size += 1 - this - } - - def result() = { - if (capacity != 0 && capacity == size) { - capacity = 0 - val res = elems - elems = null - res - } - else mkArray(size) - } - - override def equals(other: Any): Boolean = other match { - case x: ofChar => (size == x.size) && (elems == x.elems) - case _ => false - } - - override def toString = "ArrayBuilder.ofChar" - } - - /** A class for array builders for arrays of `int`s. It can be reused. */ - @SerialVersionUID(3L) - final class ofInt extends ArrayBuilder[Int] { - - protected var elems: Array[Int] = _ - - private def mkArray(size: Int): Array[Int] = { - val newelems = new Array[Int](size) - if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) - newelems - } - - protected[this] def resize(size: Int): Unit = { - elems = mkArray(size) - capacity = size - } - - def addOne(elem: Int): this.type = { - ensureSize(size + 1) - elems(size) = elem - size += 1 - this - } - - def result() = { - if (capacity != 0 && capacity == size) { - capacity = 0 - val res = elems - elems = null - res - } - else mkArray(size) - } - - override def equals(other: Any): Boolean = other match { - case x: ofInt => (size == x.size) && (elems == x.elems) - case _ => false - } - - override def toString = "ArrayBuilder.ofInt" - } - - /** A class for array builders for arrays of `long`s. It can be reused. */ - @SerialVersionUID(3L) - final class ofLong extends ArrayBuilder[Long] { - - protected var elems: Array[Long] = _ - - private def mkArray(size: Int): Array[Long] = { - val newelems = new Array[Long](size) - if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) - newelems - } - - protected[this] def resize(size: Int): Unit = { - elems = mkArray(size) - capacity = size - } - - def addOne(elem: Long): this.type = { - ensureSize(size + 1) - elems(size) = elem - size += 1 - this - } - - def result() = { - if (capacity != 0 && capacity == size) { - capacity = 0 - val res = elems - elems = null - res - } - else mkArray(size) - } - - override def equals(other: Any): Boolean = other match { - case x: ofLong => (size == x.size) && (elems == x.elems) - case _ => false - } - - override def toString = "ArrayBuilder.ofLong" - } - - /** A class for array builders for arrays of `float`s. It can be reused. */ - @SerialVersionUID(3L) - final class ofFloat extends ArrayBuilder[Float] { - - protected var elems: Array[Float] = _ - - private def mkArray(size: Int): Array[Float] = { - val newelems = new Array[Float](size) - if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) - newelems - } - - protected[this] def resize(size: Int): Unit = { - elems = mkArray(size) - capacity = size - } - - def addOne(elem: Float): this.type = { - ensureSize(size + 1) - elems(size) = elem - size += 1 - this - } - - def result() = { - if (capacity != 0 && capacity == size) { - capacity = 0 - val res = elems - elems = null - res - } - else mkArray(size) - } - - override def equals(other: Any): Boolean = other match { - case x: ofFloat => (size == x.size) && (elems == x.elems) - case _ => false - } - - override def toString = "ArrayBuilder.ofFloat" - } - - /** A class for array builders for arrays of `double`s. It can be reused. */ - @SerialVersionUID(3L) - final class ofDouble extends ArrayBuilder[Double] { - - protected var elems: Array[Double] = _ - - private def mkArray(size: Int): Array[Double] = { - val newelems = new Array[Double](size) - if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) - newelems - } - - protected[this] def resize(size: Int): Unit = { - elems = mkArray(size) - capacity = size - } - - def addOne(elem: Double): this.type = { - ensureSize(size + 1) - elems(size) = elem - size += 1 - this - } - - def result() = { - if (capacity != 0 && capacity == size) { - capacity = 0 - val res = elems - elems = null - res - } - else mkArray(size) - } - - override def equals(other: Any): Boolean = other match { - case x: ofDouble => (size == x.size) && (elems == x.elems) - case _ => false - } - - override def toString = "ArrayBuilder.ofDouble" - } - - /** A class for array builders for arrays of `boolean`s. It can be reused. */ - @SerialVersionUID(3L) - class ofBoolean extends ArrayBuilder[Boolean] { - - protected var elems: Array[Boolean] = _ - - private def mkArray(size: Int): Array[Boolean] = { - val newelems = new Array[Boolean](size) - if (this.size > 0) Array.copy(elems, 0, newelems, 0, this.size) - newelems - } - - protected[this] def resize(size: Int): Unit = { - elems = mkArray(size) - capacity = size - } - - def addOne(elem: Boolean): this.type = { - ensureSize(size + 1) - elems(size) = elem - size += 1 - this - } - - def result() = { - if (capacity != 0 && capacity == size) { - capacity = 0 - val res = elems - elems = null - res - } - else mkArray(size) - } - - override def equals(other: Any): Boolean = other match { - case x: ofBoolean => (size == x.size) && (elems == x.elems) - case _ => false - } - - override def toString = "ArrayBuilder.ofBoolean" - } - - /** A class for array builders for arrays of `Unit` type. It can be reused. */ - @SerialVersionUID(3L) - final class ofUnit extends ArrayBuilder[Unit] { - - protected def elems: Array[Unit] = throw new UnsupportedOperationException() - - def addOne(elem: Unit): this.type = { - size += 1 - this - } - - override def addAll(xs: IterableOnce[Unit]): this.type = { - size += xs.iterator.size - this - } - - override def addAll(xs: Array[_ <: Unit], offset: Int, length: Int): this.type = { - size += length - this - } - - def result() = { - val ans = new Array[Unit](size) - var i = 0 - while (i < size) { ans(i) = (); i += 1 } - ans - } - - override def equals(other: Any): Boolean = other match { - case x: ofUnit => (size == x.size) - case _ => false - } - - protected[this] def resize(size: Int): Unit = () - - override def toString = "ArrayBuilder.ofUnit" - } -} diff --git a/scalalib/overrides-2.13.0-M5/scala/collection/mutable/Buffer.scala b/scalalib/overrides-2.13.0-M5/scala/collection/mutable/Buffer.scala deleted file mode 100644 index 005ceb2125..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/collection/mutable/Buffer.scala +++ /dev/null @@ -1,216 +0,0 @@ -package scala.collection -package mutable - -import scala.scalajs.js - -/** A `Buffer` is a growable and shrinkable `Seq`. */ -trait Buffer[A] - extends Seq[A] - with SeqOps[A, Buffer, Buffer[A]] - with Growable[A] - with Shrinkable[A] { - - override def iterableFactory: SeqFactory[Buffer] = Buffer - - //TODO Prepend is a logical choice for a readable name of `+=:` but it conflicts with the renaming of `append` to `add` - /** Prepends a single element at the front of this $coll. - * - * @param elem the element to $add. - * @return the $coll itself - */ - def prepend(elem: A): this.type - - /** Appends the given elements to this buffer. - * - * @param elem the element to append. - */ - @`inline` final def append(elem: A): this.type = addOne(elem) - - @deprecated("Use appendAll instead", "2.13.0") - @`inline` final def append(elems: A*): this.type = addAll(elems) - - /** Appends the elements contained in a iterable object to this buffer. - * @param xs the iterable object containing the elements to append. - */ - @`inline` final def appendAll(xs: IterableOnce[A]): this.type = addAll(xs) - - - /** Alias for `prepend` */ - @`inline` final def +=: (elem: A): this.type = prepend(elem) - - def prependAll(elems: IterableOnce[A]): this.type = { insertAll(0, elems); this } - - @deprecated("Use prependAll instead", "2.13.0") - @`inline` final def prepend(elems: A*): this.type = prependAll(elems) - - /** Alias for `prependAll` */ - @inline final def ++=:(elems: IterableOnce[A]): this.type = prependAll(elems) - - /** Inserts a new element at a given index into this buffer. - * - * @param idx the index where the new elements is inserted. - * @param elem the element to insert. - * @throws IndexOutOfBoundsException if the index `idx` is not in the valid range - * `0 <= idx <= length`. - */ - @throws[IndexOutOfBoundsException] - def insert(idx: Int, elem: A): Unit - - /** Inserts new elements at the index `idx`. Opposed to method - * `update`, this method will not replace an element with a new - * one. Instead, it will insert a new element at index `idx`. - * - * @param idx the index where a new element will be inserted. - * @param elems the iterable object providing all elements to insert. - * @throws IndexOutOfBoundsException if `idx` is out of bounds. - */ - @throws[IndexOutOfBoundsException] - def insertAll(idx: Int, elems: IterableOnce[A]): Unit - - /** Removes the element at a given index position. - * - * @param idx the index which refers to the element to delete. - * @return the element that was formerly at index `idx`. - */ - @throws[IndexOutOfBoundsException] - def remove(idx: Int): A - - /** Removes the element on a given index position. It takes time linear in - * the buffer size. - * - * @param idx the index which refers to the first element to remove. - * @param count the number of elements to remove. - * @throws IndexOutOfBoundsException if the index `idx` is not in the valid range - * `0 <= idx <= length - count` (with `count > 0`). - * @throws IllegalArgumentException if `count < 0`. - */ - @throws[IndexOutOfBoundsException] - @throws[IllegalArgumentException] - def remove(idx: Int, count: Int): Unit - - /** Removes a single element from this buffer, at its first occurrence. - * If the buffer does not contain that element, it is unchanged. - * - * @param x the element to remove. - * @return the buffer itself - */ - def subtractOne (x: A): this.type = { - val i = indexOf(x) - if (i != -1) remove(i) - this - } - - /** Removes the first ''n'' elements of this buffer. - * - * @param n the number of elements to remove from the beginning - * of this buffer. - */ - def trimStart(n: Int): Unit = remove(0, normalized(n)) - - /** Removes the last ''n'' elements of this buffer. - * - * @param n the number of elements to remove from the end - * of this buffer. - */ - def trimEnd(n: Int): Unit = { - val norm = normalized(n) - remove(length - norm, norm) - } - - def patchInPlace(from: Int, patch: scala.collection.Seq[A], replaced: Int): this.type - - // +=, ++=, clear inherited from Growable - // Per remark of @ichoran, we should preferably not have these: - // - // def +=:(elem: A): this.type = { insert(0, elem); this } - // def +=:(elem1: A, elem2: A, elems: A*): this.type = elem1 +=: elem2 +=: elems ++=: this - // def ++=:(elems: IterableOnce[A]): this.type = { insertAll(0, elems); this } - - def dropInPlace(n: Int): this.type = { remove(0, normalized(n)); this } - def dropRightInPlace(n: Int): this.type = { - val norm = normalized(n) - remove(length - norm, norm) - this - } - def takeInPlace(n: Int): this.type = { - val norm = normalized(n) - remove(norm, length - norm) - this - } - def takeRightInPlace(n: Int): this.type = { remove(0, length - normalized(n)); this } - def sliceInPlace(start: Int, end: Int): this.type = takeInPlace(end).dropInPlace(start) - private def normalized(n: Int): Int = math.min(math.max(n, 0), length) - - def dropWhileInPlace(p: A => Boolean): this.type = { - val idx = indexWhere(!p(_)) - if (idx < 0) { clear(); this } else dropInPlace(idx) - } - def takeWhileInPlace(p: A => Boolean): this.type = { - val idx = indexWhere(!p(_)) - if (idx < 0) this else takeInPlace(idx) - } - def padToInPlace(len: Int, elem: A): this.type = { - while (length < len) +=(elem) - this - } - - override protected[this] def stringPrefix = "Buffer" -} - -trait IndexedBuffer[A] extends IndexedSeq[A] - with IndexedSeqOps[A, IndexedBuffer, IndexedBuffer[A]] - with Buffer[A] { - - override def iterableFactory: SeqFactory[IndexedBuffer] = IndexedBuffer - - def flatMapInPlace(f: A => IterableOnce[A]): this.type = { - // There's scope for a better implementation which copies elements in place. - var i = 0 - val s = size - val newElems = new Array[IterableOnce[A]](s) - while (i < s) { newElems(i) = f(this(i)); i += 1 } - clear() - i = 0 - while (i < s) { ++=(newElems(i)); i += 1 } - this - } - - def filterInPlace(p: A => Boolean): this.type = { - var i, j = 0 - while (i < size) { - if (p(apply(i))) { - if (i != j) { - this(j) = this(i) - } - j += 1 - } - i += 1 - } - - if (i == j) this else takeInPlace(j) - } - - def patchInPlace(from: Int, patch: scala.collection.Seq[A], replaced: Int): this.type = { - val replaced0 = math.min(math.max(replaced, 0), length) - val i = math.min(math.max(from, 0), length) - var j = 0 - val n = math.min(patch.length, replaced0) - while (j < n && i + j < length) { - update(i + j, patch(j)) - j += 1 - } - if (j < patch.length) insertAll(i + j, patch.iterator.drop(j)) - else if (j < replaced0) remove(i + j, replaced0 - j) - this - } -} - -@SerialVersionUID(3L) -object Buffer extends SeqFactory.Delegate[Buffer](js.WrappedArray) - -@SerialVersionUID(3L) -object IndexedBuffer extends SeqFactory.Delegate[IndexedBuffer](js.WrappedArray) - -/** Explicit instantiation of the `Buffer` trait to reduce class file size in subclasses. */ -@SerialVersionUID(3L) -abstract class AbstractBuffer[A] extends AbstractSeq[A] with Buffer[A] diff --git a/scalalib/overrides-2.13.0-M5/scala/compat/Platform.scala b/scalalib/overrides-2.13.0-M5/scala/compat/Platform.scala deleted file mode 100644 index be1e259ba5..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/compat/Platform.scala +++ /dev/null @@ -1,132 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala -package compat - -import java.lang.System - -object Platform { - - /** Thrown when a stack overflow occurs because a method or function recurses too deeply. - * - * On the JVM, this is a type alias for `java.lang.StackOverflowError`, which itself extends `java.lang.Error`. - * The same rules apply to catching a `java.lang.Error` as for Java, that it indicates a serious problem that a reasonable application should not try and catch. - */ - type StackOverflowError = java.lang.StackOverflowError - - /** This is a type alias for `java.util.ConcurrentModificationException`, - * which may be thrown by methods that detect an invalid modification of an object. - * For example, many common collection types do not allow modifying a collection - * while it is being iterated over. - */ - type ConcurrentModificationException = java.util.ConcurrentModificationException - - /** Copies `length` elements of array `src` starting at position `srcPos` to the - * array `dest` starting at position `destPos`. If `src`==`dest`, the copying will - * behave as if the elements copied from `src` were first copied to a temporary - * array before being copied back into the array at the destination positions. - * - * @param src A non-null array as source for the copy. - * @param srcPos The starting index in the source array. - * @param dest A non-null array as destination for the copy. - * @param destPos The starting index in the destination array. - * @param length The number of elements to be copied. - * @throws java.lang.NullPointerException If either `src` or `dest` are `null`. - * @throws java.lang.ArrayStoreException If either `src` or `dest` are not of type - * [java.lang.Array]; or if the element type of `src` is not - * compatible with that of `dest`. - * @throws java.lang.IndexOutOfBoundsException If either `srcPos` or `destPos` are - * outside of the bounds of their respective arrays; or if `length` - * is negative; or if there are less than `length` elements available - * after `srcPos` or `destPos` in `src` and `dest` respectively. - */ - @inline - def arraycopy(src: AnyRef, srcPos: Int, dest: AnyRef, destPos: Int, length: Int): Unit = { - System.arraycopy(src, srcPos, dest, destPos, length) - } - - /** Creates a new array of the specified type and given length. - * - * Note that if `elemClass` is a subclass of [[scala.AnyVal]] then the returned value is an Array of the corresponding java primitive type. - * For example, the following code `scala.compat.Platform.createArray(classOf[Int], 4)` returns an array of the java primitive type `int`. - * - * For a [[scala.AnyVal]] array, the values of the array are set to 0 for ''numeric value types'' ([[scala.Double]], [[scala.Float]], [[scala.Long]], [[scala.Int]], [[scala.Char]], - * [[scala.Short]], and [[scala.Byte]]), and `false` for [[scala.Boolean]]. Creation of an array of type [[scala.Unit]] is not possible. - * - * For subclasses of [[scala.AnyRef]], the values of the array are set to `null`. - * - * The caller must cast the returned value to the correct type. - * - * @example {{{ - * val a = scala.compat.Platform.createArray(classOf[Int], 4).asInstanceOf[Array[Int]] // returns Array[Int](0, 0, 0, 0) - * }}} - * - * @param elemClass the `Class` object of the component type of the array - * @param length the length of the new array. - * @return an array of the given component type as an `AnyRef`. - * @throws java.lang.NullPointerException If `elemClass` is `null`. - * @throws java.lang.IllegalArgumentException if componentType is [[scala.Unit]] or `java.lang.Void.TYPE` - * @throws java.lang.NegativeArraySizeException if the specified length is negative - */ - @inline - def createArray(elemClass: Class[_], length: Int): AnyRef = - java.lang.reflect.Array.newInstance(elemClass, length) - - /** Assigns the value of 0 to each element in the array. - * @param arr A non-null Array[Int]. - * @throws java.lang.NullPointerException If `arr` is `null`. - */ - @inline - def arrayclear(arr: Array[Int]): Unit = { java.util.Arrays.fill(arr, 0) } - - /** Returns the `Class` object associated with the class or interface with the given string name using the current `ClassLoader`. - * On the JVM, invoking this method is equivalent to: `java.lang.Class.forName(name)` - * - * For more information, please see the Java documentation for [[java.lang.Class]]. - * - * @param name the fully qualified name of the desired class. - * @return the `Class` object for the class with the specified name. - * @throws java.lang.LinkageError if the linkage fails - * @throws java.lang.ExceptionInInitializerError if the initialization provoked by this method fails - * @throws java.lang.ClassNotFoundException if the class cannot be located - * @example {{{ - * val a = scala.compat.Platform.getClassForName("java.lang.Integer") // returns the Class[_] for java.lang.Integer - * }}} - */ - @inline - def getClassForName(name: String): Class[_] = java.lang.Class.forName(name) - - /** The default line separator. - * - * On the JavaScript backend, this is always "\n". - */ - val EOL = "\n" - - /** The current time in milliseconds. The time is counted since 1 January 1970 - * UTC. - * - * Note that the operating system timer used to obtain this value may be less - * precise than a millisecond. - */ - @inline - def currentTime: Long = System.currentTimeMillis() - - /** Runs the garbage collector. - * - * This is a request that the underlying JVM runs the garbage collector. - * The results of this call depends heavily on the JVM used. - * The underlying JVM is free to ignore this request. - */ - @inline - def collectGarbage(): Unit = System.gc() - - /** The name of the default character set encoding as a string */ - @inline - def defaultCharsetName: String = java.nio.charset.Charset.defaultCharset.name -} diff --git a/scalalib/overrides-2.13.0-M5/scala/concurrent/ExecutionContext.scala b/scalalib/overrides-2.13.0-M5/scala/concurrent/ExecutionContext.scala deleted file mode 100644 index d82e6cf9da..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/concurrent/ExecutionContext.scala +++ /dev/null @@ -1,200 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.concurrent - - -import java.util.concurrent.{ ExecutorService, Executor } -import scala.annotation.implicitNotFound - -/** - * An `ExecutionContext` can execute program logic asynchronously, - * typically but not necessarily on a thread pool. - * - * A general purpose `ExecutionContext` must be asynchronous in executing - * any `Runnable` that is passed into its `execute`-method. A special purpose - * `ExecutionContext` may be synchronous but must only be passed to code that - * is explicitly safe to be run using a synchronously executing `ExecutionContext`. - * - * APIs such as `Future.onComplete` require you to provide a callback - * and an implicit `ExecutionContext`. The implicit `ExecutionContext` - * will be used to execute the callback. - * - * While it is possible to simply import - * `scala.concurrent.ExecutionContext.Implicits.global` to obtain an - * implicit `ExecutionContext`, application developers should carefully - * consider where they want to set execution policy; - * ideally, one place per application—or per logically related section of code— - * will make a decision about which `ExecutionContext` to use. - * That is, you will mostly want to avoid hardcoding, especially via an import, - * `scala.concurrent.ExecutionContext.Implicits.global`. - * The recommended approach is to add `(implicit ec: ExecutionContext)` to methods, - * or class constructor parameters, which need an `ExecutionContext`. - * - * Then locally import a specific `ExecutionContext` in one place for the entire - * application or module, passing it implicitly to individual methods. - * Alternatively define a local implicit val with the required `ExecutionContext`. - * - * A custom `ExecutionContext` may be appropriate to execute code - * which blocks on IO or performs long-running computations. - * `ExecutionContext.fromExecutorService` and `ExecutionContext.fromExecutor` - * are good ways to create a custom `ExecutionContext`. - * - * The intent of `ExecutionContext` is to lexically scope code execution. - * That is, each method, class, file, package, or application determines - * how to run its own code. This avoids issues such as running - * application callbacks on a thread pool belonging to a networking library. - * The size of a networking library's thread pool can be safely configured, - * knowing that only that library's network operations will be affected. - * Application callback execution can be configured separately. - */ -@implicitNotFound("""Cannot find an implicit ExecutionContext. You might pass -an (implicit ec: ExecutionContext) parameter to your method. - -The ExecutionContext is used to configure how and on which -thread pools Futures will run, so the specific ExecutionContext -that is selected is important. - -If your application does not define an ExecutionContext elsewhere, -consider using Scala's global ExecutionContext by defining -the following: - -implicit val ec = ExecutionContext.global""") -trait ExecutionContext { - - /** Runs a block of code on this execution context. - * - * @param runnable the task to execute - */ - def execute(runnable: Runnable): Unit - - /** Reports that an asynchronous computation failed. - * - * @param cause the cause of the failure - */ - def reportFailure(@deprecatedName("t") cause: Throwable): Unit - - /** Prepares for the execution of a task. Returns the prepared - * execution context. The recommended implementation of - * `prepare` is to return `this`. - * - * This method should no longer be overridden or called. It was - * originally expected that `prepare` would be called by - * all libraries that consume ExecutionContexts, in order to - * capture thread local context. However, this usage has proven - * difficult to implement in practice and instead it is - * now better to avoid using `prepare` entirely. - * - * Instead, if an `ExecutionContext` needs to capture thread - * local context, it should capture that context when it is - * constructed, so that it doesn't need any additional - * preparation later. - */ - @deprecated("preparation of ExecutionContexts will be removed", "2.12.0") - // This cannot be removed until there is a suitable replacement - def prepare(): ExecutionContext = this -} - -/** - * An [[ExecutionContext]] that is also a - * Java [[http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html Executor]]. - */ -trait ExecutionContextExecutor extends ExecutionContext with Executor - -/** - * An [[ExecutionContext]] that is also a - * Java [[http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html ExecutorService]]. - */ -trait ExecutionContextExecutorService extends ExecutionContextExecutor with ExecutorService - - -/** Contains factory methods for creating execution contexts. - */ -object ExecutionContext { - /** - * The explicit global `ExecutionContext`. Invoke `global` when you want to provide the global - * `ExecutionContext` explicitly. - * - * The default `ExecutionContext` implementation is backed by a work-stealing thread pool. - * It can be configured via the following [[scala.sys.SystemProperties]]: - * - * `scala.concurrent.context.minThreads` = defaults to "1" - * `scala.concurrent.context.numThreads` = defaults to "x1" (i.e. the current number of available processors * 1) - * `scala.concurrent.context.maxThreads` = defaults to "x1" (i.e. the current number of available processors * 1) - * `scala.concurrent.context.maxExtraThreads` = defaults to "256" - * - * The pool size of threads is then `numThreads` bounded by `minThreads` on the lower end and `maxThreads` on the high end. - * - * The `maxExtraThreads` is the maximum number of extra threads to have at any given time to evade deadlock, - * see [[scala.concurrent.BlockContext]]. - * - * @return the global `ExecutionContext` - */ - def global: ExecutionContextExecutor = Implicits.global.asInstanceOf[ExecutionContextExecutor] - - object Implicits { - /** - * The implicit global `ExecutionContext`. Import `global` when you want to provide the global - * `ExecutionContext` implicitly. - * - * The default `ExecutionContext` implementation is backed by a work-stealing thread pool. By default, - * the thread pool uses a target number of worker threads equal to the number of - * [[https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#availableProcessors-- available processors]]. - */ - implicit lazy val global: ExecutionContext = - scala.scalajs.concurrent.JSExecutionContext.queue - } - - /** Creates an `ExecutionContext` from the given `ExecutorService`. - * - * @param e the `ExecutorService` to use. If `null`, a new `ExecutorService` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. - * @param reporter a function for error reporting - * @return the `ExecutionContext` using the given `ExecutorService` - */ - def fromExecutorService(e: ExecutorService, reporter: Throwable => Unit): ExecutionContextExecutorService = - impl.ExecutionContextImpl.fromExecutorService(e, reporter) - - /** Creates an `ExecutionContext` from the given `ExecutorService` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. - * - * If it is guaranteed that none of the executed tasks are blocking, a single-threaded `ExecutorService` - * can be used to create an `ExecutionContext` as follows: - * - * {{{ - * import java.util.concurrent.Executors - * val ec = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor()) - * }}} - * - * @param e the `ExecutorService` to use. If `null`, a new `ExecutorService` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. - * @return the `ExecutionContext` using the given `ExecutorService` - */ - def fromExecutorService(e: ExecutorService): ExecutionContextExecutorService = fromExecutorService(e, defaultReporter) - - /** Creates an `ExecutionContext` from the given `Executor`. - * - * @param e the `Executor` to use. If `null`, a new `Executor` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. - * @param reporter a function for error reporting - * @return the `ExecutionContext` using the given `Executor` - */ - def fromExecutor(e: Executor, reporter: Throwable => Unit): ExecutionContextExecutor = - impl.ExecutionContextImpl.fromExecutor(e, reporter) - - /** Creates an `ExecutionContext` from the given `Executor` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. - * - * @param e the `Executor` to use. If `null`, a new `Executor` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. - * @return the `ExecutionContext` using the given `Executor` - */ - def fromExecutor(e: Executor): ExecutionContextExecutor = fromExecutor(e, defaultReporter) - - /** The default reporter simply prints the stack trace of the `Throwable` to [[http://docs.oracle.com/javase/8/docs/api/java/lang/System.html#err System.err]]. - * - * @return the function for error reporting - */ - def defaultReporter: Throwable => Unit = _.printStackTrace() -} - - diff --git a/scalalib/overrides-2.13.0-M5/scala/package.scala b/scalalib/overrides-2.13.0-M5/scala/package.scala deleted file mode 100644 index 39b2190e47..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/package.scala +++ /dev/null @@ -1,140 +0,0 @@ -import scala.annotation.migration - -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - - -/** - * Core Scala types. They are always available without an explicit import. - * @contentDiagram hideNodes "scala.Serializable" - */ -package object scala { - type Throwable = java.lang.Throwable - type Exception = java.lang.Exception - type Error = java.lang.Error - - type RuntimeException = java.lang.RuntimeException - type NullPointerException = java.lang.NullPointerException - type ClassCastException = java.lang.ClassCastException - type IndexOutOfBoundsException = java.lang.IndexOutOfBoundsException - type ArrayIndexOutOfBoundsException = java.lang.ArrayIndexOutOfBoundsException - type StringIndexOutOfBoundsException = java.lang.StringIndexOutOfBoundsException - type UnsupportedOperationException = java.lang.UnsupportedOperationException - type IllegalArgumentException = java.lang.IllegalArgumentException - type NoSuchElementException = java.util.NoSuchElementException - type NumberFormatException = java.lang.NumberFormatException - type AbstractMethodError = java.lang.AbstractMethodError - type InterruptedException = java.lang.InterruptedException - - // A dummy used by the specialization annotation. - val AnyRef = new Specializable { - override def toString = "object AnyRef" - } - - @deprecated("Use IterableOnce instead of TraversableOnce", "2.13.0") - type TraversableOnce[+A] = scala.collection.IterableOnce[A] - - type IterableOnce[+A] = scala.collection.IterableOnce[A] - - @deprecated("Use Iterable instead of Traversable", "2.13.0") - type Traversable[+A] = scala.collection.Iterable[A] - @deprecated("Use Iterable instead of Traversable", "2.13.0") - val Traversable = scala.collection.Iterable - - type Iterable[+A] = scala.collection.Iterable[A] - val Iterable = scala.collection.Iterable - - @migration("scala.Seq is now scala.collection.immutable.Seq instead of scala.collection.Seq", "2.13.0") - type Seq[+A] = scala.collection.immutable.Seq[A] - val Seq = scala.collection.immutable.Seq - - @migration("scala.IndexedSeq is now scala.collection.immutable.IndexedSeq instead of scala.collection.IndexedSeq", "2.13.0") - type IndexedSeq[+A] = scala.collection.immutable.IndexedSeq[A] - val IndexedSeq = scala.collection.immutable.IndexedSeq - - type Iterator[+A] = scala.collection.Iterator[A] - val Iterator = scala.collection.Iterator - - @deprecated("Use scala.collection.BufferedIterator instead of scala.BufferedIterator", "2.13.0") - type BufferedIterator[+A] = scala.collection.BufferedIterator[A] - - type List[+A] = scala.collection.immutable.List[A] - val List = scala.collection.immutable.List - - val Nil = scala.collection.immutable.Nil - - type ::[A] = scala.collection.immutable.::[A] - val :: = scala.collection.immutable.:: - - val +: = scala.collection.+: - val :+ = scala.collection.:+ - - @deprecated("Use LazyList instead of Stream", "2.13.0") - type Stream[+A] = scala.collection.immutable.Stream[A] - @deprecated("Use LazyList instead of Stream", "2.13.0") - val Stream = scala.collection.immutable.Stream - - type LazyList[+A] = scala.collection.immutable.LazyList[A] - val LazyList = scala.collection.immutable.LazyList - // This should be an alias to LazyList.#:: but we need to support Stream, too - //val #:: = scala.collection.immutable.LazyList.#:: - object #:: { - def unapply[A](s: LazyList[A]): Option[(A, LazyList[A])] = - if (s.nonEmpty) Some((s.head, s.tail)) else None - def unapply[A](s: Stream[A]): Option[(A, Stream[A])] = - if (s.nonEmpty) Some((s.head, s.tail)) else None - } - - type Vector[+A] = scala.collection.immutable.Vector[A] - val Vector = scala.collection.immutable.Vector - - type StringBuilder = scala.collection.mutable.StringBuilder - val StringBuilder = scala.collection.mutable.StringBuilder - - type Range = scala.collection.immutable.Range - val Range = scala.collection.immutable.Range - - // Numeric types which were moved into scala.math.* - - type BigDecimal = scala.math.BigDecimal - lazy val BigDecimal = scala.math.BigDecimal - - type BigInt = scala.math.BigInt - lazy val BigInt = scala.math.BigInt - - type Equiv[T] = scala.math.Equiv[T] - val Equiv = scala.math.Equiv - - type Fractional[T] = scala.math.Fractional[T] - val Fractional = scala.math.Fractional - - type Integral[T] = scala.math.Integral[T] - val Integral = scala.math.Integral - - type Numeric[T] = scala.math.Numeric[T] - val Numeric = scala.math.Numeric - - type Ordered[T] = scala.math.Ordered[T] - val Ordered = scala.math.Ordered - - type Ordering[T] = scala.math.Ordering[T] - val Ordering = scala.math.Ordering - - type PartialOrdering[T] = scala.math.PartialOrdering[T] - type PartiallyOrdered[T] = scala.math.PartiallyOrdered[T] - - type Either[+A, +B] = scala.util.Either[A, B] - val Either = scala.util.Either - - type Left[+A, +B] = scala.util.Left[A, B] - val Left = scala.util.Left - - type Right[+A, +B] = scala.util.Right[A, B] - val Right = scala.util.Right - -} diff --git a/scalalib/overrides-2.13.0-M5/scala/reflect/ClassTag.scala b/scalalib/overrides-2.13.0-M5/scala/reflect/ClassTag.scala deleted file mode 100644 index 64861a6f96..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/reflect/ClassTag.scala +++ /dev/null @@ -1,127 +0,0 @@ -package scala -package reflect - -import java.lang.{ Class => jClass } - -/** - * - * A `ClassTag[T]` stores the erased class of a given type `T`, accessible via the `runtimeClass` - * field. This is particularly useful for instantiating `Array`s whose element types are unknown - * at compile time. - * - * `ClassTag`s are a weaker special case of [[scala.reflect.api.TypeTags#TypeTag]]s, in that they - * wrap only the runtime class of a given type, whereas a `TypeTag` contains all static type - * information. That is, `ClassTag`s are constructed from knowing only the top-level class of a - * type, without necessarily knowing all of its argument types. This runtime information is enough - * for runtime `Array` creation. - * - * For example: - * {{{ - * scala> def mkArray[T : ClassTag](elems: T*) = Array[T](elems: _*) - * mkArray: [T](elems: T*)(implicit evidence$1: scala.reflect.ClassTag[T])Array[T] - * - * scala> mkArray(42, 13) - * res0: Array[Int] = Array(42, 13) - * - * scala> mkArray("Japan","Brazil","Germany") - * res1: Array[String] = Array(Japan, Brazil, Germany) - * }}} - * - * See [[scala.reflect.api.TypeTags]] for more examples, or the - * [[http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html Reflection Guide: TypeTags]] - * for more details. - * - */ -@scala.annotation.implicitNotFound(msg = "No ClassTag available for ${T}") -trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serializable { - // please, don't add any APIs here, like it was with `newWrappedArray` and `newArrayBuilder` - // class tags, and all tags in general, should be as minimalistic as possible - - /** A class representing the type `U` to which `T` would be erased. - * Note that there is no subtyping relationship between `T` and `U`. - */ - def runtimeClass: jClass[_] - - /** Produces a `ClassTag` that knows how to instantiate an `Array[Array[T]]` */ - def wrap: ClassTag[Array[T]] = ClassTag[Array[T]](arrayClass(runtimeClass)) - - /** Produces a new array with element type `T` and length `len` */ - def newArray(len: Int): Array[T] = - java.lang.reflect.Array.newInstance(runtimeClass, len).asInstanceOf[Array[T]] - - /** A ClassTag[T] can serve as an extractor that matches only objects of type T. - * - * The compiler tries to turn unchecked type tests in pattern matches into checked ones - * by wrapping a `(_: T)` type pattern as `ct(_: T)`, where `ct` is the `ClassTag[T]` instance. - * Type tests necessary before calling other extractors are treated similarly. - * `SomeExtractor(...)` is turned into `ct(SomeExtractor(...))` if `T` in `SomeExtractor.unapply(x: T)` - * is uncheckable, but we have an instance of `ClassTag[T]`. - */ - def unapply(x: Any): Option[T] = - if (runtimeClass.isInstance(x)) Some(x.asInstanceOf[T]) - else None - - // case class accessories - override def canEqual(x: Any) = x.isInstanceOf[ClassTag[_]] - override def equals(x: Any) = x.isInstanceOf[ClassTag[_]] && this.runtimeClass == x.asInstanceOf[ClassTag[_]].runtimeClass - override def hashCode = runtimeClass.## - override def toString = { - def prettyprint(clazz: jClass[_]): String = - if (clazz.isArray) s"Array[${prettyprint(clazz.getComponentType)}]" else - clazz.getName - prettyprint(runtimeClass) - } -} - -/** - * Class tags corresponding to primitive types and constructor/extractor for ClassTags. - */ -object ClassTag { - def Byte : ClassTag[scala.Byte] = ManifestFactory.Byte - def Short : ClassTag[scala.Short] = ManifestFactory.Short - def Char : ClassTag[scala.Char] = ManifestFactory.Char - def Int : ClassTag[scala.Int] = ManifestFactory.Int - def Long : ClassTag[scala.Long] = ManifestFactory.Long - def Float : ClassTag[scala.Float] = ManifestFactory.Float - def Double : ClassTag[scala.Double] = ManifestFactory.Double - def Boolean : ClassTag[scala.Boolean] = ManifestFactory.Boolean - def Unit : ClassTag[scala.Unit] = ManifestFactory.Unit - def Any : ClassTag[scala.Any] = ManifestFactory.Any - def Object : ClassTag[java.lang.Object] = ManifestFactory.Object - def AnyVal : ClassTag[scala.AnyVal] = ManifestFactory.AnyVal - def AnyRef : ClassTag[scala.AnyRef] = ManifestFactory.AnyRef - def Nothing : ClassTag[scala.Nothing] = ManifestFactory.Nothing - def Null : ClassTag[scala.Null] = ManifestFactory.Null - - @inline - @SerialVersionUID(1L) - private class GenericClassTag[T](val runtimeClass: jClass[_]) extends ClassTag[T] { - override def newArray(len: Int): Array[T] = { - java.lang.reflect.Array.newInstance(runtimeClass, len).asInstanceOf[Array[T]] - } - } - - def apply[T](runtimeClass1: jClass[_]): ClassTag[T] = - runtimeClass1 match { - case java.lang.Byte.TYPE => ClassTag.Byte.asInstanceOf[ClassTag[T]] - case java.lang.Short.TYPE => ClassTag.Short.asInstanceOf[ClassTag[T]] - case java.lang.Character.TYPE => ClassTag.Char.asInstanceOf[ClassTag[T]] - case java.lang.Integer.TYPE => ClassTag.Int.asInstanceOf[ClassTag[T]] - case java.lang.Long.TYPE => ClassTag.Long.asInstanceOf[ClassTag[T]] - case java.lang.Float.TYPE => ClassTag.Float.asInstanceOf[ClassTag[T]] - case java.lang.Double.TYPE => ClassTag.Double.asInstanceOf[ClassTag[T]] - case java.lang.Boolean.TYPE => ClassTag.Boolean.asInstanceOf[ClassTag[T]] - case java.lang.Void.TYPE => ClassTag.Unit.asInstanceOf[ClassTag[T]] - case _ => - if (classOf[java.lang.Object] == runtimeClass1) - ClassTag.Object.asInstanceOf[ClassTag[T]] - else if (classOf[scala.runtime.Nothing$] == runtimeClass1) - ClassTag.Nothing.asInstanceOf[ClassTag[T]] - else if (classOf[scala.runtime.Null$] == runtimeClass1) - ClassTag.Null.asInstanceOf[ClassTag[T]] - else - new GenericClassTag[T](runtimeClass1) - } - - def unapply[T](ctag: ClassTag[T]): Option[Class[_]] = Some(ctag.runtimeClass) -} diff --git a/scalalib/overrides-2.13.0-M5/scala/reflect/Manifest.scala b/scalalib/overrides-2.13.0-M5/scala/reflect/Manifest.scala deleted file mode 100644 index fd352e2c2f..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/reflect/Manifest.scala +++ /dev/null @@ -1,365 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2007-2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala -package reflect - -import scala.collection.mutable.{ArrayBuilder, ArraySeq} - -/** A `Manifest[T]` is an opaque descriptor for type T. Its supported use - * is to give access to the erasure of the type as a `Class` instance, as - * is necessary for the creation of native `Arrays` if the class is not - * known at compile time. - * - * The type-relation operators `<:<` and `=:=` should be considered - * approximations only, as there are numerous aspects of type conformance - * which are not yet adequately represented in manifests. - * - * Example usages: - * {{{ - * def arr[T] = new Array[T](0) // does not compile - * def arr[T](implicit m: Manifest[T]) = new Array[T](0) // compiles - * def arr[T: Manifest] = new Array[T](0) // shorthand for the preceding - * - * // Methods manifest and optManifest are in [[scala.Predef]]. - * def isApproxSubType[T: Manifest, U: Manifest] = manifest[T] <:< manifest[U] - * isApproxSubType[List[String], List[AnyRef]] // true - * isApproxSubType[List[String], List[Int]] // false - * - * def methods[T: Manifest] = manifest[T].runtimeClass.getMethods - * def retType[T: Manifest](name: String) = - * methods[T] find (_.getName == name) map (_.getGenericReturnType) - * - * retType[Map[_, _]]("values") // Some(scala.collection.Iterable) - * }}} - */ -@scala.annotation.implicitNotFound(msg = "No Manifest available for ${T}.") -// TODO undeprecated until Scala reflection becomes non-experimental -// @deprecated("use scala.reflect.ClassTag (to capture erasures) or scala.reflect.runtime.universe.TypeTag (to capture types) or both instead", "2.10.0") -trait Manifest[T] extends ClassManifest[T] with Equals { - override def typeArguments: List[Manifest[_]] = Nil - - override def arrayManifest: Manifest[Array[T]] = - Manifest.classType[Array[T]](arrayClass[T](runtimeClass), this) - - override def canEqual(that: Any): Boolean = that match { - case _: Manifest[_] => true - case _ => false - } - /** Note: testing for erasure here is important, as it is many times - * faster than <:< and rules out most comparisons. - */ - override def equals(that: Any): Boolean = that match { - case m: Manifest[_] => (m canEqual this) && (this.runtimeClass == m.runtimeClass) && (this <:< m) && (m <:< this) - case _ => false - } - override def hashCode = this.runtimeClass.## -} - -// TODO undeprecated until Scala reflection becomes non-experimental -// @deprecated("use type tags and manually check the corresponding class or type instead", "2.10.0") -@SerialVersionUID(1L) -abstract class AnyValManifest[T <: AnyVal](override val toString: String) extends Manifest[T] with Equals { - override def <:<(that: ClassManifest[_]): Boolean = - (that eq this) || (that eq Manifest.Any) || (that eq Manifest.AnyVal) - override def canEqual(other: Any) = other match { - case _: AnyValManifest[_] => true - case _ => false - } - override def equals(that: Any): Boolean = this eq that.asInstanceOf[AnyRef] - @transient - override val hashCode = System.identityHashCode(this) -} - -/** `ManifestFactory` defines factory methods for manifests. - * It is intended for use by the compiler and should not be used in client code. - * - * Unlike `Manifest`, this factory isn't annotated with a deprecation warning. - * This is done to prevent avalanches of deprecation warnings in the code that calls methods with manifests. - * Why so complicated? Read up the comments for `ClassManifestFactory`. - */ -object ManifestFactory { - def valueManifests: List[AnyValManifest[_]] = - List(Byte, Short, Char, Int, Long, Float, Double, Boolean, Unit) - - private object ByteManifest extends AnyValManifest[scala.Byte]("Byte") { - def runtimeClass = java.lang.Byte.TYPE - override def newArray(len: Int): Array[Byte] = new Array[Byte](len) - override def newWrappedArray(len: Int): ArraySeq[Byte] = new ArraySeq.ofByte(new Array[Byte](len)) - override def newArrayBuilder(): ArrayBuilder[Byte] = new ArrayBuilder.ofByte() - override def unapply(x: Any): Option[Byte] = { - x match { - case d: Byte => Some(d) - case _ => None - } - } - private def readResolve(): Any = Manifest.Byte - } - def Byte: AnyValManifest[Byte] = ByteManifest - - private object ShortManifest extends AnyValManifest[scala.Short]("Short") { - def runtimeClass = java.lang.Short.TYPE - override def newArray(len: Int): Array[Short] = new Array[Short](len) - override def newWrappedArray(len: Int): ArraySeq[Short] = new ArraySeq.ofShort(new Array[Short](len)) - override def newArrayBuilder(): ArrayBuilder[Short] = new ArrayBuilder.ofShort() - override def unapply(x: Any): Option[Short] = { - x match { - case d: Short => Some(d) - case _ => None - } - } - private def readResolve(): Any = Manifest.Short - } - def Short: AnyValManifest[Short] = ShortManifest - - private object CharManifest extends AnyValManifest[scala.Char]("Char") { - def runtimeClass = java.lang.Character.TYPE - override def newArray(len: Int): Array[Char] = new Array[Char](len) - override def newWrappedArray(len: Int): ArraySeq[Char] = new ArraySeq.ofChar(new Array[Char](len)) - override def newArrayBuilder(): ArrayBuilder[Char] = new ArrayBuilder.ofChar() - override def unapply(x: Any): Option[Char] = { - x match { - case d: Char => Some(d) - case _ => None - } - } - private def readResolve(): Any = Manifest.Char - } - def Char: AnyValManifest[Char] = CharManifest - - private object IntManifest extends AnyValManifest[scala.Int]("Int") { - def runtimeClass = java.lang.Integer.TYPE - override def newArray(len: Int): Array[Int] = new Array[Int](len) - override def newWrappedArray(len: Int): ArraySeq[Int] = new ArraySeq.ofInt(new Array[Int](len)) - override def newArrayBuilder(): ArrayBuilder[Int] = new ArrayBuilder.ofInt() - override def unapply(x: Any): Option[Int] = { - x match { - case d: Int => Some(d) - case _ => None - } - } - private def readResolve(): Any = Manifest.Int - } - def Int: AnyValManifest[Int] = IntManifest - - private object LongManifest extends AnyValManifest[scala.Long]("Long") { - def runtimeClass = java.lang.Long.TYPE - override def newArray(len: Int): Array[Long] = new Array[Long](len) - override def newWrappedArray(len: Int): ArraySeq[Long] = new ArraySeq.ofLong(new Array[Long](len)) - override def newArrayBuilder(): ArrayBuilder[Long] = new ArrayBuilder.ofLong() - override def unapply(x: Any): Option[Long] = { - x match { - case d: Long => Some(d) - case _ => None - } - } - private def readResolve(): Any = Manifest.Long - } - def Long: AnyValManifest[Long] = LongManifest - - private object FloatManifest extends AnyValManifest[scala.Float]("Float") { - def runtimeClass = java.lang.Float.TYPE - override def newArray(len: Int): Array[Float] = new Array[Float](len) - override def newWrappedArray(len: Int): ArraySeq[Float] = new ArraySeq.ofFloat(new Array[Float](len)) - override def newArrayBuilder(): ArrayBuilder[Float] = new ArrayBuilder.ofFloat() - override def unapply(x: Any): Option[Float] = { - x match { - case d: Float => Some(d) - case _ => None - } - } - private def readResolve(): Any = Manifest.Float - } - def Float: AnyValManifest[Float] = FloatManifest - - private object DoubleManifest extends AnyValManifest[scala.Double]("Double") { - def runtimeClass = java.lang.Double.TYPE - override def newArray(len: Int): Array[Double] = new Array[Double](len) - override def newWrappedArray(len: Int): ArraySeq[Double] = new ArraySeq.ofDouble(new Array[Double](len)) - override def newArrayBuilder(): ArrayBuilder[Double] = new ArrayBuilder.ofDouble() - - override def unapply(x: Any): Option[Double] = { - x match { - case d: Double => Some(d) - case _ => None - } - } - private def readResolve(): Any = Manifest.Double - } - def Double: AnyValManifest[Double] = DoubleManifest - - private object BooleanManifest extends AnyValManifest[scala.Boolean]("Boolean") { - def runtimeClass = java.lang.Boolean.TYPE - override def newArray(len: Int): Array[Boolean] = new Array[Boolean](len) - override def newWrappedArray(len: Int): ArraySeq[Boolean] = new ArraySeq.ofBoolean(new Array[Boolean](len)) - override def newArrayBuilder(): ArrayBuilder[Boolean] = new ArrayBuilder.ofBoolean() - override def unapply(x: Any): Option[Boolean] = { - x match { - case d: Boolean => Some(d) - case _ => None - } - } - private def readResolve(): Any = Manifest.Boolean - } - def Boolean: AnyValManifest[Boolean] = BooleanManifest - - private object UnitManifest extends AnyValManifest[scala.Unit]("Unit") { - def runtimeClass = java.lang.Void.TYPE - override def newArray(len: Int): Array[Unit] = new Array[Unit](len) - override def newWrappedArray(len: Int): ArraySeq[Unit] = new ArraySeq.ofUnit(new Array[Unit](len)) - override def newArrayBuilder(): ArrayBuilder[Unit] = new ArrayBuilder.ofUnit() - override protected def arrayClass[T](tp: Class[_]): Class[Array[T]] = - if (tp eq runtimeClass) classOf[Array[scala.runtime.BoxedUnit]].asInstanceOf[Class[Array[T]]] - else super.arrayClass(tp) - override def unapply(x: Any): Option[Unit] = { - x match { - case d: Unit => Some(d) - case _ => None - } - } - private def readResolve(): Any = Manifest.Unit - } - def Unit: AnyValManifest[Unit] = UnitManifest - - private object AnyManifest extends PhantomManifest[scala.Any](classOf[java.lang.Object], "Any") { - override def runtimeClass = classOf[java.lang.Object] - override def newArray(len: Int) = new Array[scala.Any](len) - override def <:<(that: ClassManifest[_]): Boolean = (that eq this) - private def readResolve(): Any = Manifest.Any - } - def Any: Manifest[scala.Any] = AnyManifest - - private object ObjectManifest extends PhantomManifest[java.lang.Object](classOf[java.lang.Object], "Object") { - override def runtimeClass = classOf[java.lang.Object] - override def newArray(len: Int) = new Array[java.lang.Object](len) - override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) - private def readResolve(): Any = Manifest.Object - } - def Object: Manifest[java.lang.Object] = ObjectManifest - - def AnyRef: Manifest[scala.AnyRef] = Object.asInstanceOf[Manifest[scala.AnyRef]] - - private object AnyValManifest extends PhantomManifest[scala.AnyVal](classOf[java.lang.Object], "AnyVal") { - override def runtimeClass = classOf[java.lang.Object] - override def newArray(len: Int) = new Array[scala.AnyVal](len) - override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) - private def readResolve(): Any = Manifest.AnyVal - } - def AnyVal: Manifest[scala.AnyVal] = AnyValManifest - - private object NullManifest extends PhantomManifest[scala.Null](classOf[scala.runtime.Null$], "Null") { - override def runtimeClass = classOf[scala.runtime.Null$] - override def newArray(len: Int) = new Array[scala.Null](len) - override def <:<(that: ClassManifest[_]): Boolean = - (that ne null) && (that ne Nothing) && !(that <:< AnyVal) - private def readResolve(): Any = Manifest.Null - } - def Null: Manifest[scala.Null] = NullManifest - - private object NothingManifest extends PhantomManifest[scala.Nothing](classOf[scala.runtime.Nothing$], "Nothing") { - override def runtimeClass = classOf[scala.runtime.Nothing$] - override def newArray(len: Int) = new Array[scala.Nothing](len) - override def <:<(that: ClassManifest[_]): Boolean = (that ne null) - private def readResolve(): Any = Manifest.Nothing - } - def Nothing: Manifest[scala.Nothing] = NothingManifest - - @SerialVersionUID(1L) - private class SingletonTypeManifest[T <: AnyRef](value: AnyRef) extends Manifest[T] { - lazy val runtimeClass = value.getClass - override lazy val toString = value.toString + ".type" - } - - /** Manifest for the singleton type `value.type`. */ - def singleType[T <: AnyRef](value: AnyRef): Manifest[T] = - new SingletonTypeManifest[T](value) - - /** Manifest for the class type `clazz[args]`, where `clazz` is - * a top-level or static class. - * @note This no-prefix, no-arguments case is separate because we - * it's called from ScalaRunTime.boxArray itself. If we - * pass varargs as arrays into this, we get an infinitely recursive call - * to boxArray. (Besides, having a separate case is more efficient) - */ - def classType[T](clazz: Predef.Class[_]): Manifest[T] = - new ClassTypeManifest[T](None, clazz, Nil) - - /** Manifest for the class type `clazz`, where `clazz` is - * a top-level or static class and args are its type arguments. */ - def classType[T](clazz: Predef.Class[T], arg1: Manifest[_], args: Manifest[_]*): Manifest[T] = - new ClassTypeManifest[T](None, clazz, arg1 :: args.toList) - - /** Manifest for the class type `clazz[args]`, where `clazz` is - * a class with non-package prefix type `prefix` and type arguments `args`. - */ - def classType[T](prefix: Manifest[_], clazz: Predef.Class[_], args: Manifest[_]*): Manifest[T] = - new ClassTypeManifest[T](Some(prefix), clazz, args.toList) - - @SerialVersionUID(1L) - private abstract class PhantomManifest[T](_runtimeClass: Predef.Class[_], - override val toString: String) extends ClassTypeManifest[T](None, _runtimeClass, Nil) { - override def equals(that: Any): Boolean = this eq that.asInstanceOf[AnyRef] - @transient - override val hashCode = System.identityHashCode(this) - } - - /** Manifest for the class type `clazz[args]`, where `clazz` is - * a top-level or static class. */ - @SerialVersionUID(1L) - private class ClassTypeManifest[T](prefix: Option[Manifest[_]], - runtimeClass1: Predef.Class[_], - override val typeArguments: List[Manifest[_]]) extends Manifest[T] { - def runtimeClass: Predef.Class[_] = runtimeClass1 - override def toString = - (if (prefix.isEmpty) "" else prefix.get.toString+"#") + - (if (runtimeClass.isArray) "Array" else runtimeClass.getName) + - argString - } - - def arrayType[T](arg: Manifest[_]): Manifest[Array[T]] = - arg.asInstanceOf[Manifest[T]].arrayManifest - - @SerialVersionUID(1L) - private class AbstractTypeManifest[T](prefix: Manifest[_], name: String, upperBound: Predef.Class[_], args: scala.collection.Seq[Manifest[_]]) extends Manifest[T] { - def runtimeClass = upperBound - override val typeArguments = args.toList - override def toString = prefix.toString+"#"+name+argString - } - - /** Manifest for the abstract type `prefix # name`. `upperBound` is not - * strictly necessary as it could be obtained by reflection. It was - * added so that erasure can be calculated without reflection. */ - def abstractType[T](prefix: Manifest[_], name: String, upperBound: Predef.Class[_], args: Manifest[_]*): Manifest[T] = - new AbstractTypeManifest[T](prefix, name, upperBound, args) - - @SerialVersionUID(1L) - private class WildcardManifest[T](lowerBound: Manifest[_], upperBound: Manifest[_]) extends Manifest[T] { - def runtimeClass = upperBound.runtimeClass - override def toString = - "_" + - (if (lowerBound eq Nothing) "" else " >: "+lowerBound) + - (if (upperBound eq Nothing) "" else " <: "+upperBound) - } - - /** Manifest for the unknown type `_ >: L <: U` in an existential. - */ - def wildcardType[T](lowerBound: Manifest[_], upperBound: Manifest[_]): Manifest[T] = - new WildcardManifest[T](lowerBound, upperBound) - - @SerialVersionUID(1L) - private class IntersectionTypeManifest[T](parents: Array[Manifest[_]]) extends Manifest[T] { - // We use an `Array` instead of a `Seq` for `parents` to avoid cyclic dependencies during deserialization - // which can cause serialization proxies to leak and cause a ClassCastException. - def runtimeClass = parents(0).runtimeClass - override def toString = parents.mkString(" with ") - } - - /** Manifest for the intersection type `parents_0 with ... with parents_n`. */ - def intersectionType[T](parents: Manifest[_]*): Manifest[T] = - new IntersectionTypeManifest[T](parents.toArray) -} diff --git a/scalalib/overrides-2.13.0-M5/scala/reflect/NameTransformer.scala b/scalalib/overrides-2.13.0-M5/scala/reflect/NameTransformer.scala deleted file mode 100644 index 8c2f072dd3..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/reflect/NameTransformer.scala +++ /dev/null @@ -1,163 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala -package reflect - -/** Provides functions to encode and decode Scala symbolic names. - * Also provides some constants. - */ -object NameTransformer { - // TODO: reduce duplication with and in StdNames - // I made these constants because we cannot change them without bumping our major version anyway. - final val NAME_JOIN_STRING = "$" - final val MODULE_SUFFIX_STRING = "$" - final val MODULE_INSTANCE_NAME = "MODULE$" - final val LOCAL_SUFFIX_STRING = " " - final val LAZY_LOCAL_SUFFIX_STRING = "$lzy" - final val MODULE_VAR_SUFFIX_STRING = "$module" - final val SETTER_SUFFIX_STRING = "_$eq" - final val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$" - - private[this] val nops = 128 - private[this] val ncodes = 26 * 26 - - private class OpCodes(val op: Char, val code: String, val next: OpCodes) - - private[this] val op2code = new Array[String](nops) - private[this] val code2op = new Array[OpCodes](ncodes) - private def enterOp(op: Char, code: String) = { - op2code(op.toInt) = code - val c = (code.charAt(1) - 'a') * 26 + code.charAt(2) - 'a' - code2op(c.toInt) = new OpCodes(op, code, code2op(c)) - } - - /* Note: decoding assumes opcodes are only ever lowercase. */ - enterOp('~', "$tilde") - enterOp('=', "$eq") - enterOp('<', "$less") - enterOp('>', "$greater") - enterOp('!', "$bang") - enterOp('#', "$hash") - enterOp('%', "$percent") - enterOp('^', "$up") - enterOp('&', "$amp") - enterOp('|', "$bar") - enterOp('*', "$times") - enterOp('/', "$div") - enterOp('+', "$plus") - enterOp('-', "$minus") - enterOp(':', "$colon") - enterOp('\\', "$bslash") - enterOp('?', "$qmark") - enterOp('@', "$at") - - /** Replace operator symbols by corresponding `\$opname`. - * - * @param name the string to encode - * @return the string with all recognized opchars replaced with their encoding - */ - def encode(name: String): String = { - var buf: StringBuilder = null - val len = name.length() - var i = 0 - while (i < len) { - val c = name charAt i - if (c < nops && (op2code(c.toInt) ne null)) { - if (buf eq null) { - buf = new StringBuilder() - buf.append(name.substring(0, i)) - } - buf.append(op2code(c.toInt)) - /* Handle glyphs that are not valid Java/JVM identifiers */ - } - else if (!Character.isJavaIdentifierPart(c)) { - if (buf eq null) { - buf = new StringBuilder() - buf.append(name.substring(0, i)) - } - buf.append("$u%04X".format(c.toInt)) - } - else if (buf ne null) { - buf.append(c) - } - i += 1 - } - if (buf eq null) name else buf.toString() - } - - /** Replace `\$opname` by corresponding operator symbol. - * - * @param name0 the string to decode - * @return the string with all recognized operator symbol encodings replaced with their name - */ - def decode(name0: String): String = { - //System.out.println("decode: " + name);//DEBUG - val name = if (name0.endsWith("")) name0.stripSuffix("") + "this" - else name0 - var buf: StringBuilder = null - val len = name.length() - var i = 0 - while (i < len) { - var ops: OpCodes = null - var unicode = false - val c = name charAt i - if (c == '$' && i + 2 < len) { - val ch1 = name.charAt(i+1) - if ('a' <= ch1 && ch1 <= 'z') { - val ch2 = name.charAt(i+2) - if ('a' <= ch2 && ch2 <= 'z') { - ops = code2op((ch1 - 'a') * 26 + ch2 - 'a') - while ((ops ne null) && !name.startsWith(ops.code, i)) ops = ops.next - if (ops ne null) { - if (buf eq null) { - buf = new StringBuilder() - buf.append(name.substring(0, i)) - } - buf.append(ops.op) - i += ops.code.length() - } - /* Handle the decoding of Unicode glyphs that are - * not valid Java/JVM identifiers */ - } else if ((len - i) >= 6 && // Check that there are enough characters left - ch1 == 'u' && - ((Character.isDigit(ch2)) || - ('A' <= ch2 && ch2 <= 'F'))) { - /* Skip past "$u", next four should be hexadecimal */ - val hex = name.substring(i+2, i+6) - try { - val str = Integer.parseInt(hex, 16).toChar - if (buf eq null) { - buf = new StringBuilder() - buf.append(name.substring(0, i)) - } - buf.append(str) - /* 2 for "$u", 4 for hexadecimal number */ - i += 6 - unicode = true - } catch { - case _:NumberFormatException => - /* `hex` did not decode to a hexadecimal number, so - * do nothing. */ - } - } - } - } - /* If we didn't see an opcode or encoded Unicode glyph, and the - buffer is non-empty, write the current character and advance - one */ - if ((ops eq null) && !unicode) { - if (buf ne null) - buf.append(c) - i += 1 - } - } - //System.out.println("= " + (if (buf == null) name else buf.toString()));//DEBUG - if (buf eq null) name else buf.toString() - } -} diff --git a/scalalib/overrides-2.13.0-M5/scala/runtime/ScalaRunTime.scala b/scalalib/overrides-2.13.0-M5/scala/runtime/ScalaRunTime.scala deleted file mode 100644 index ebff8ca11a..0000000000 --- a/scalalib/overrides-2.13.0-M5/scala/runtime/ScalaRunTime.scala +++ /dev/null @@ -1,271 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala -package runtime - -import scala.collection.{AbstractIterator, AnyConstr, SortedOps, StrictOptimizedIterableOps, StringOps, StringView, View} -import scala.collection.immutable.{ArraySeq, NumericRange} -import scala.collection.mutable.StringBuilder -import scala.reflect.{ClassTag, classTag} -import java.lang.{Class => jClass} -import java.lang.reflect.{Method => JMethod} - -import scala.collection.generic.IsIterable - -/** The object ScalaRunTime provides support methods required by - * the scala runtime. All these methods should be considered - * outside the API and subject to change or removal without notice. - */ -object ScalaRunTime { - def isArray(x: Any, atLevel: Int = 1): Boolean = - x != null && isArrayClass(x.getClass, atLevel) - - private def isArrayClass(clazz: jClass[_], atLevel: Int): Boolean = - clazz != null && clazz.isArray && (atLevel == 1 || isArrayClass(clazz.getComponentType, atLevel - 1)) - - // A helper method to make my life in the pattern matcher a lot easier. - def drop[Repr](coll: Repr, num: Int)(implicit iterable: IsIterable[Repr] { type C <: Repr }): Repr = - iterable(coll) drop num - - /** Return the class object representing an array with element class `clazz`. - */ - def arrayClass(clazz: jClass[_]): jClass[_] = { - // newInstance throws an exception if the erasure is Void.TYPE. see scala/bug#5680 - if (clazz == java.lang.Void.TYPE) classOf[Array[Unit]] - else java.lang.reflect.Array.newInstance(clazz, 0).getClass - } - - /** Return the class object representing an unboxed value type, - * e.g., classOf[int], not classOf[java.lang.Integer]. The compiler - * rewrites expressions like 5.getClass to come here. - */ - def anyValClass[T <: AnyVal : ClassTag](value: T): jClass[T] = - classTag[T].runtimeClass.asInstanceOf[jClass[T]] - - /** Retrieve generic array element */ - def array_apply(xs: AnyRef, idx: Int): Any = { - xs match { - case x: Array[AnyRef] => x(idx).asInstanceOf[Any] - case x: Array[Int] => x(idx).asInstanceOf[Any] - case x: Array[Double] => x(idx).asInstanceOf[Any] - case x: Array[Long] => x(idx).asInstanceOf[Any] - case x: Array[Float] => x(idx).asInstanceOf[Any] - case x: Array[Char] => x(idx).asInstanceOf[Any] - case x: Array[Byte] => x(idx).asInstanceOf[Any] - case x: Array[Short] => x(idx).asInstanceOf[Any] - case x: Array[Boolean] => x(idx).asInstanceOf[Any] - case x: Array[Unit] => x(idx).asInstanceOf[Any] - case null => throw new NullPointerException - } - } - - /** update generic array element */ - def array_update(xs: AnyRef, idx: Int, value: Any): Unit = { - xs match { - case x: Array[AnyRef] => x(idx) = value.asInstanceOf[AnyRef] - case x: Array[Int] => x(idx) = value.asInstanceOf[Int] - case x: Array[Double] => x(idx) = value.asInstanceOf[Double] - case x: Array[Long] => x(idx) = value.asInstanceOf[Long] - case x: Array[Float] => x(idx) = value.asInstanceOf[Float] - case x: Array[Char] => x(idx) = value.asInstanceOf[Char] - case x: Array[Byte] => x(idx) = value.asInstanceOf[Byte] - case x: Array[Short] => x(idx) = value.asInstanceOf[Short] - case x: Array[Boolean] => x(idx) = value.asInstanceOf[Boolean] - case x: Array[Unit] => x(idx) = value.asInstanceOf[Unit] - case null => throw new NullPointerException - } - } - - /** Get generic array length */ - def array_length(xs: AnyRef): Int = java.lang.reflect.Array.getLength(xs) - - // TODO: bytecode Object.clone() will in fact work here and avoids - // the type switch. See Array_clone comment in BCodeBodyBuilder. - def array_clone(xs: AnyRef): AnyRef = xs match { - case x: Array[AnyRef] => x.clone() - case x: Array[Int] => x.clone() - case x: Array[Double] => x.clone() - case x: Array[Long] => x.clone() - case x: Array[Float] => x.clone() - case x: Array[Char] => x.clone() - case x: Array[Byte] => x.clone() - case x: Array[Short] => x.clone() - case x: Array[Boolean] => x.clone() - case null => throw new NullPointerException - } - - /** Convert an array to an object array. - * Needed to deal with vararg arguments of primitive types that are passed - * to a generic Java vararg parameter T ... - */ - def toObjectArray(src: AnyRef): Array[Object] = src match { - case x: Array[AnyRef] => x - case _ => - val length = array_length(src) - val dest = new Array[Object](length) - for (i <- 0 until length) - array_update(dest, i, array_apply(src, i)) - dest - } - - def toArray[T](xs: scala.collection.Seq[T]) = { - val arr = new Array[AnyRef](xs.length) - var i = 0 - for (x <- xs) { - arr(i) = x.asInstanceOf[AnyRef] - i += 1 - } - arr - } - - // Java bug: https://bugs.java.com/view_bug.do?bug_id=4071957 - // More background at ticket #2318. - def ensureAccessible(m: JMethod): JMethod = scala.reflect.ensureAccessible(m) - - def _toString(x: Product): String = - x.productIterator.mkString(x.productPrefix + "(", ",", ")") - - def _hashCode(x: Product): Int = scala.util.hashing.MurmurHash3.productHash(x) - - /** A helper for case classes. */ - def typedProductIterator[T](x: Product): Iterator[T] = { - new AbstractIterator[T] { - private[this] var c: Int = 0 - private[this] val cmax = x.productArity - def hasNext = c < cmax - def next() = { - val result = x.productElement(c) - c += 1 - result.asInstanceOf[T] - } - } - } - - /** Given any Scala value, convert it to a String. - * - * The primary motivation for this method is to provide a means for - * correctly obtaining a String representation of a value, while - * avoiding the pitfalls of naively calling toString on said value. - * In particular, it addresses the fact that (a) toString cannot be - * called on null and (b) depending on the apparent type of an - * array, toString may or may not print it in a human-readable form. - * - * @param arg the value to stringify - * @return a string representation of arg. - */ - def stringOf(arg: Any): String = stringOf(arg, scala.Int.MaxValue) - def stringOf(arg: Any, maxElements: Int): String = { - def packageOf(x: AnyRef) = x.getClass.getPackage match { - case null => "" - case p => p.getName - } - def isScalaClass(x: AnyRef) = packageOf(x) startsWith "scala." - def isScalaCompilerClass(x: AnyRef) = packageOf(x) startsWith "scala.tools.nsc." - - // includes specialized subclasses and future proofed against hypothetical TupleN (for N > 22) - def isTuple(x: Any) = x != null && x.getClass.getName.startsWith("scala.Tuple") - - // We use reflection because the scala.xml package might not be available - def isSubClassOf(potentialSubClass: Class[_], ofClass: String) = - try { - val classLoader = potentialSubClass.getClassLoader - val clazz = Class.forName(ofClass, /*initialize =*/ false, classLoader) - clazz.isAssignableFrom(potentialSubClass) - } catch { - case cnfe: ClassNotFoundException => false - } - def isXmlNode(potentialSubClass: Class[_]) = isSubClassOf(potentialSubClass, "scala.xml.Node") - def isXmlMetaData(potentialSubClass: Class[_]) = isSubClassOf(potentialSubClass, "scala.xml.MetaData") - - // When doing our own iteration is dangerous - def useOwnToString(x: Any) = x match { - // Range/NumericRange have a custom toString to avoid walking a gazillion elements - case _: Range | _: NumericRange[_] => true - // Sorted collections to the wrong thing (for us) on iteration - ticket #3493 - case _: SortedOps[_, _] => true - // StringBuilder(a, b, c) and similar not so attractive - case _: StringView | _: StringOps | _: StringBuilder => true - // Don't want to evaluate any elements in a view - case _: View[_] => true - // Node extends NodeSeq extends Seq[Node] and MetaData extends Iterable[MetaData] - // -> catch those by isXmlNode and isXmlMetaData. - // Don't want to a) traverse infinity or b) be overly helpful with peoples' custom - // collections which may have useful toString methods - ticket #3710 - // or c) print AbstractFiles which are somehow also Iterable[AbstractFile]s. - case x: Iterable[_] => (!x.isInstanceOf[StrictOptimizedIterableOps[_, AnyConstr, _]]) || !isScalaClass(x) || isScalaCompilerClass(x) || isXmlNode(x.getClass) || isXmlMetaData(x.getClass) - // Otherwise, nothing could possibly go wrong - case _ => false - } - - // A variation on inner for maps so they print -> instead of bare tuples - def mapInner(arg: Any): String = arg match { - case (k, v) => inner(k) + " -> " + inner(v) - case _ => inner(arg) - } - - // Special casing Unit arrays, the value class which uses a reference array type. - def arrayToString(x: AnyRef) = { - if (x.getClass.getComponentType == classOf[BoxedUnit]) - 0 until (array_length(x) min maxElements) map (_ => "()") mkString ("Array(", ", ", ")") - else - x.asInstanceOf[Array[_]].iterator.take(maxElements).map(inner).mkString("Array(", ", ", ")") - } - - // The recursively applied attempt to prettify Array printing. - // Note that iterator is used if possible and foreach is used as a - // last resort, because the parallel collections "foreach" in a - // random order even on sequences. - def inner(arg: Any): String = arg match { - case null => "null" - case "" => "\"\"" - case x: String => if (x.head.isWhitespace || x.last.isWhitespace) "\"" + x + "\"" else x - case x if useOwnToString(x) => x.toString - case x: AnyRef if isArray(x) => arrayToString(x) - case x: scala.collection.Map[_, _] => x.iterator take maxElements map mapInner mkString (x.collectionClassName + "(", ", ", ")") - case x: Iterable[_] => x.iterator take maxElements map inner mkString (x.collectionClassName + "(", ", ", ")") - case x: Product1[_] if isTuple(x) => "(" + inner(x._1) + ",)" // that special trailing comma - case x: Product if isTuple(x) => x.productIterator map inner mkString ("(", ",", ")") - case x => x.toString - } - - // The try/catch is defense against iterables which aren't actually designed - // to be iterated, such as some scala.tools.nsc.io.AbstractFile derived classes. - try inner(arg) - catch { - case _: UnsupportedOperationException | _: AssertionError => "" + arg - } - } - - /** stringOf formatted for use in a repl result. */ - def replStringOf(arg: Any, maxElements: Int): String = - stringOf(arg, maxElements) match { - case null => "null toString" - case s if s.indexOf('\n') >= 0 => "\n" + s + "\n" - case s => s + "\n" - } - - // Convert arrays to immutable.ArraySeq for use with Java varargs: - def genericWrapArray[T](xs: Array[T]): ArraySeq[T] = - if (xs eq null) null - else ArraySeq.unsafeWrapArray(xs) - def wrapRefArray[T <: AnyRef](xs: Array[T]): ArraySeq[T] = { - if (xs eq null) null - else if (xs.length == 0) ArraySeq.empty[AnyRef].asInstanceOf[ArraySeq[T]] - else new ArraySeq.ofRef[T](xs) - } - def wrapIntArray(xs: Array[Int]): ArraySeq[Int] = if (xs ne null) new ArraySeq.ofInt(xs) else null - def wrapDoubleArray(xs: Array[Double]): ArraySeq[Double] = if (xs ne null) new ArraySeq.ofDouble(xs) else null - def wrapLongArray(xs: Array[Long]): ArraySeq[Long] = if (xs ne null) new ArraySeq.ofLong(xs) else null - def wrapFloatArray(xs: Array[Float]): ArraySeq[Float] = if (xs ne null) new ArraySeq.ofFloat(xs) else null - def wrapCharArray(xs: Array[Char]): ArraySeq[Char] = if (xs ne null) new ArraySeq.ofChar(xs) else null - def wrapByteArray(xs: Array[Byte]): ArraySeq[Byte] = if (xs ne null) new ArraySeq.ofByte(xs) else null - def wrapShortArray(xs: Array[Short]): ArraySeq[Short] = if (xs ne null) new ArraySeq.ofShort(xs) else null - def wrapBooleanArray(xs: Array[Boolean]): ArraySeq[Boolean] = if (xs ne null) new ArraySeq.ofBoolean(xs) else null - def wrapUnitArray(xs: Array[Unit]): ArraySeq[Unit] = if (xs ne null) new ArraySeq.ofUnit(xs) else null -} From 3f7b83b19176aac85a936801d7e57b9176ede0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 2 Jul 2019 18:01:30 +0200 Subject: [PATCH 0009/1606] Fix #3705: Correctly reimplement `ArrayBuilder.generic.addAll()`. --- .../resources/2.13.0/BlacklistedTests.txt | 2 -- .../resources/2.13.0/WhitelistedTests.txt | 1 + .../collection/mutable/ArrayBuilder.scala | 8 ++++++-- .../testsuite/scalalib/ArrayBuilderTest.scala | 20 +++++++++++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt index 4c19847829..6571436825 100644 --- a/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt @@ -187,5 +187,3 @@ scala/collection/convert/MapWrapperTest.scala ## Buglisted # 3704 scala/collection/convert/JSetWrapperTest.scala -# 3705 -scala/collection/ArrayOpsTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt b/scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt index 1f0d30265b..647cc8c0fe 100644 --- a/scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt @@ -3,6 +3,7 @@ scala/CharSequenceImplicitsTests.scala scala/EnumerationTest.scala scala/PartialFunctionCompositionTest.scala scala/PredefTest.scala +scala/collection/ArrayOpsTest.scala scala/collection/BuildFromTest.scala scala/collection/CatTest.scala scala/collection/CollectionConversionsTest.scala diff --git a/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala b/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala index f8b8bca258..912cfa84fa 100644 --- a/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala +++ b/scalalib/overrides-2.13/scala/collection/mutable/ArrayBuilder.scala @@ -114,8 +114,12 @@ object ArrayBuilder { /** Add a slice of an array */ override def addAll(xs: Array[_ <: T], offset: Int, length: Int): this.type = { - ensureSize(this.size + length) - addAll(xs.view.slice(offset, length)) + val end = offset + length + var i = offset + while (i < end) { + this += xs(i) + i += 1 + } this } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala index 3190f32803..ea570dc0a1 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala @@ -24,6 +24,7 @@ import org.junit.Assume._ import org.scalajs.testsuite.utils.Platform._ class ArrayBuilderTest { + import ArrayBuilderTest._ @noinline def erase(x: Any): Any = x @@ -279,4 +280,23 @@ class ArrayBuilderTest { assertSame(classOf[Array[Nothing]], makeNoInline[Nothing].result().getClass) assertSame(classOf[Array[Null]], makeNoInline[Null].result().getClass) } + + @Test def addAll(): Unit = { + assumeFalse("Needs at least Scala 2.13", + scalaVersion.startsWith("2.10.") || + scalaVersion.startsWith("2.11.") || + scalaVersion.startsWith("2.12.")) + + val b = ArrayBuilder.make[Int] + val arr = Array[Int](1, 2, 3, 4, 5) + b.addAll(arr, 3, 2) + assertArrayEquals(Array[Int](4, 5), b.result()) + } +} + +object ArrayBuilderTest { + implicit class ArrayBuilderCompat[A](val __sef: ArrayBuilder[A]) extends AnyVal { + def addAll(xs: Array[_ <: A], offset: Int, length: Int): Unit = + throw new AssertionError("unreachable code") + } } From b643cc45d58310c8179ef7104c9e1fe41552f9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 3 Jul 2019 11:49:34 +0200 Subject: [PATCH 0010/1606] Widen the parameters of `ju.Objects.*` methods to `Any`. This is how Scala interprets parameters of type `Object` coming from Java. The widening has the same binary API, but the source API to call `Objects` methods from the `javalib` itself is improved. --- javalib/src/main/scala/java/util/Objects.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/javalib/src/main/scala/java/util/Objects.scala b/javalib/src/main/scala/java/util/Objects.scala index 2c0b636288..f0b204a303 100644 --- a/javalib/src/main/scala/java/util/Objects.scala +++ b/javalib/src/main/scala/java/util/Objects.scala @@ -17,13 +17,13 @@ import scala.reflect.ClassTag object Objects { @inline - def equals(a: AnyRef, b: AnyRef): Boolean = + def equals(a: Any, b: Any): Boolean = if (a == null) b == null else a.equals(b) @inline - def deepEquals(a: AnyRef, b: AnyRef): Boolean = { - if (a eq b) true + def deepEquals(a: Any, b: Any): Boolean = { + if (a.asInstanceOf[AnyRef] eq b.asInstanceOf[AnyRef]) true else if (a == null || b == null) false else { (a, b) match { @@ -42,7 +42,7 @@ object Objects { } @inline - def hashCode(o: AnyRef): Int = + def hashCode(o: Any): Int = if (o == null) 0 else o.hashCode() @@ -51,11 +51,11 @@ object Objects { Arrays.hashCode(values) @inline - def toString(o: AnyRef): String = + def toString(o: Any): String = String.valueOf(o) @inline - def toString(o: AnyRef, nullDefault: String): String = + def toString(o: Any, nullDefault: String): String = if (o == null) nullDefault else o.toString @@ -75,11 +75,11 @@ object Objects { else obj @inline - def isNull(obj: AnyRef): Boolean = + def isNull(obj: Any): Boolean = obj == null @inline - def nonNull(obj: AnyRef): Boolean = + def nonNull(obj: Any): Boolean = obj != null // Requires the implementation of java.util.function From 137c11df0d12084a5f023162da0946c81e1ab216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 3 Jul 2019 12:50:52 +0200 Subject: [PATCH 0011/1606] Implement `java.util.function.Predicate`. We need it to implement `j.u.Collection.removeIf()` in the next commit. --- .../scala/java/util/function/Predicate.scala | 55 +++++++++++ project/Build.scala | 19 ++++ .../javalib/util/function/PredicateTest.scala | 93 +++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 javalib/src/main/scala/java/util/function/Predicate.scala create mode 100644 test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/function/PredicateTest.scala diff --git a/javalib/src/main/scala/java/util/function/Predicate.scala b/javalib/src/main/scala/java/util/function/Predicate.scala new file mode 100644 index 0000000000..4862e87927 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/Predicate.scala @@ -0,0 +1,55 @@ +/* + * 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.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 = + self.test(t) && other.test(t) // the order and short-circuit are by-spec + } + } + + @JavaDefaultMethod + def negate(): Predicate[T] = { + new Predicate[T] { + def test(t: T): Boolean = + !self.test(t) + } + } + + @JavaDefaultMethod + def or(other: Predicate[_ >: T]): Predicate[T] = { + new Predicate[T] { + def test(t: T): Boolean = + self.test(t) || other.test(t) // the order and short-circuit are by-spec + } + } +} + +object Predicate { + def isEqual[T](targetRef: Any): Predicate[T] = { + new Predicate[T] { + def test(t: T): Boolean = + ju.Objects.equals(targetRef, t) + } + } +} diff --git a/project/Build.scala b/project/Build.scala index f8b525688b..91cfa3c165 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1549,6 +1549,25 @@ object Build { publishArtifact in Compile := false, scalacOptions ~= (_.filter(_ != "-deprecation")), + // To support calls to static methods in interfaces + scalacOptions in Test ++= { + /* Starting from 2.10.7 and 2.11.12, scalac refuses to emit calls to + * static methods in interfaces unless the -target:jvm-1.8 flag is given. + * scalac 2.12+ emits JVM 8 bytecode by default, of course, so it is not + * needed for later versions. + */ + val PartialVersion = """(\d+)\.(\d+)\.(\d+)(?:-.+)?""".r + val needsTargetFlag = scalaVersion.value match { + case PartialVersion("2", "10", n) => n.toInt >= 7 + case PartialVersion("2", "11", n) => n.toInt >= 12 + case _ => false + } + if (needsTargetFlag) + Seq("-target:jvm-1.8") + else + Nil + }, + // Need reflect for typechecking macros libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/function/PredicateTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/function/PredicateTest.scala new file mode 100644 index 0000000000..237fd03ab4 --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/function/PredicateTest.scala @@ -0,0 +1,93 @@ +/* + * 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.testsuite.javalib.util.function + +import java.util.function.Predicate + +import org.junit.Assert._ +import org.junit.Test + +import org.scalajs.testsuite.utils.AssertThrows._ + +class PredicateTest { + import PredicateTest._ + + private val largerThan10 = makePredicate[Int](_ > 10) + private val even = makePredicate[Int](_ % 2 == 0) + + private val throwingPredicate = + makePredicate[Any](x => throw new ThrowingPredicateException(x)) + + private val dontCallPredicate = + makePredicate[Any](x => throw new AssertionError(s"dontCallPredicate.test($x)")) + + @Test def and(): Unit = { + // Truth table + val evenAndLargerThan10 = largerThan10.and(even) + assertTrue(evenAndLargerThan10.test(22)) + assertFalse(evenAndLargerThan10.test(21)) + assertFalse(evenAndLargerThan10.test(6)) + assertFalse(evenAndLargerThan10.test(5)) + + // Short-circuit + assertFalse(largerThan10.and(dontCallPredicate).test(5)) + assertThrows(classOf[ThrowingPredicateException], + throwingPredicate.and(dontCallPredicate).test(5)) + } + + @Test def negate(): Unit = { + // Truth table + val notLargerThan10 = largerThan10.negate() + assertTrue(notLargerThan10.test(5)) + assertFalse(notLargerThan10.test(15)) + } + + @Test def or(): Unit = { + // Truth table + val evenOrLargerThan10 = largerThan10.or(even) + assertTrue(evenOrLargerThan10.test(22)) + assertTrue(evenOrLargerThan10.test(21)) + assertTrue(evenOrLargerThan10.test(6)) + assertFalse(evenOrLargerThan10.test(5)) + + // Short-circuit + assertTrue(largerThan10.or(dontCallPredicate).test(15)) + assertThrows(classOf[ThrowingPredicateException], + throwingPredicate.or(dontCallPredicate).test(15)) + } + + @Test def isEqual(): Unit = { + val isEqualToPair = Predicate.isEqual[Any]((3, 4)) + assertTrue(isEqualToPair.test((3, 4))) + assertFalse(isEqualToPair.test((5, 6))) + assertFalse(isEqualToPair.test("foo")) + assertFalse(isEqualToPair.test(null)) + + val isEqualToNull = Predicate.isEqual[Any](null) + assertFalse(isEqualToNull.test((3, 4))) + assertFalse(isEqualToNull.test((5, 6))) + assertFalse(isEqualToNull.test("foo")) + assertTrue(isEqualToNull.test(null)) + } +} + +object PredicateTest { + final class ThrowingPredicateException(x: Any) + extends Exception(s"throwing predicate called with $x") + + def makePredicate[T](f: T => Boolean): Predicate[T] = { + new Predicate[T] { + def test(t: T): Boolean = f(t) + } + } +} From 3e0bebd524211b77852de42b53759e7f851008b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 2 Jul 2019 16:34:43 +0200 Subject: [PATCH 0012/1606] Further improve the default import of the build in Metals. When generating for IDEs, now we: * Disable source- and resource-generators. * Use stub definitions for sources that are normally generated (in particular, the `ScalaJSEnvHolder`). * Do not add the `-P:scalajs:*` options (which is important because we already disable the Scala.js compiler plugin). * Skip the JVM back-end phases like we do for the normal build. --- DEVELOPING.md | 4 + project/Build.scala | 90 ++++++++++++------- .../backend/emitter/ScalaJSEnvHolder.scala | 21 +++++ 3 files changed, 82 insertions(+), 33 deletions(-) create mode 100644 tools/shared/src/main/scala-ide-stubs/org/scalajs/core/tools/linker/backend/emitter/ScalaJSEnvHolder.scala diff --git a/DEVELOPING.md b/DEVELOPING.md index cc2a880538..c23a4fa117 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -79,6 +79,10 @@ or, more typically, We recommend [Metals](https://scalameta.org/metals/)-based IDEs such as VS Code to develop Scala.js itself. It can import the Scala.js build out-of-the-box. +After importing the build in Metals, you will need to run `clean` in sbt before +normal sbt commands can correctly work. Metals will continue to provide all its +features after cleaning. + ## Eclipse If you want to develop in Eclipse, use diff --git a/project/Build.scala b/project/Build.scala index f8b525688b..f815afe4fc 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -110,6 +110,25 @@ object Build { if (condition) List(testDir) else Nil + private def addScalaJSCompilerOption(option: String): Setting[_] = + addScalaJSCompilerOption(Def.setting(option)) + + private def addScalaJSCompilerOption(option: Def.Initialize[String]): Setting[_] = + addScalaJSCompilerOption(None, option) + + private def addScalaJSCompilerOptionInConfig(config: Configuration, + option: String): Setting[_] = { + addScalaJSCompilerOption(Some(config), Def.setting(option)) + } + + private def addScalaJSCompilerOption(config: Option[Configuration], + option: Def.Initialize[String]): Setting[_] = { + config.fold(scalacOptions)(scalacOptions in _) ++= { + if (isGeneratingForIDE) Nil + else Seq(s"-P:scalajs:${option.value}") + } + } + val previousArtifactSetting: Setting[_] = { mimaPreviousArtifacts ++= { val scalaV = scalaVersion.value @@ -303,12 +322,8 @@ object Build { } ) - val noClassFilesSettings: Setting[_] = ( - scalacOptions in (Compile, compile) ++= { - if (isGeneratingForIDE) Seq() - else Seq("-Yskip:cleanup,icode,jvm") - } - ) + val noClassFilesSettings: Setting[_] = + scalacOptions in (Compile, compile) += "-Yskip:cleanup,icode,jvm" val publishSettings = Seq( publishMavenStyle := true, @@ -400,7 +415,7 @@ object Build { ) }, - scalacOptions += "-P:scalajs:sjsDefinedByDefault" + addScalaJSCompilerOption("sjsDefinedByDefault") ) private def parallelCollectionsDependencies( @@ -612,11 +627,16 @@ object Build { unmanagedSourceDirectories in Test += baseDirectory.value.getParentFile / "shared/src/test/scala", - sourceGenerators in Compile += Def.task { - ScalaJSEnvGenerator.generateEnvHolder( - baseDirectory.value.getParentFile, - (sourceManaged in Compile).value) - }.taskValue, + if (isGeneratingForIDE) { + unmanagedSourceDirectories in Compile += + baseDirectory.value.getParentFile / "shared/src/main/scala-ide-stubs" + } else { + sourceGenerators in Compile += Def.task { + ScalaJSEnvGenerator.generateEnvHolder( + baseDirectory.value.getParentFile, + (sourceManaged in Compile).value) + }.taskValue + }, previousArtifactSetting, mimaBinaryIssueFilters ++= BinaryIncompatibilities.Tools, @@ -648,20 +668,24 @@ object Build { */ scalacOptions in Test -= "-Xfatal-warnings", - resourceGenerators in Test += Def.task { - val base = (resourceManaged in Compile).value - IO.createDirectory(base) - val outFile = base / "js-test-definitions.js" - - val testDefinitions = { - org.scalajs.build.HTMLRunnerTemplateAccess.renderTestDefinitions( - (loadedTestFrameworks in testSuite in Test).value, - (definedTests in testSuite in Test).value) - } + if (isGeneratingForIDE) { + resourceGenerators in Test ++= Nil + } else { + resourceGenerators in Test += Def.task { + val base = (resourceManaged in Compile).value + IO.createDirectory(base) + val outFile = base / "js-test-definitions.js" + + val testDefinitions = { + org.scalajs.build.HTMLRunnerTemplateAccess.renderTestDefinitions( + (loadedTestFrameworks in testSuite in Test).value, + (definedTests in testSuite in Test).value) + } - IO.write(outFile, testDefinitions) - Seq(outFile) - }.taskValue, + IO.write(outFile, testDefinitions) + Seq(outFile) + }.taskValue + }, // Give more memory to Node.js, and deactivate source maps jsEnv := { @@ -937,12 +961,12 @@ object Build { * #2195 This must come *before* the option added by myScalaJSSettings * because mapSourceURI works on a first-match basis. */ - scalacOptions += { - "-P:scalajs:mapSourceURI:" + + addScalaJSCompilerOption(Def.setting { + "mapSourceURI:" + (artifactPath in fetchScalaSource).value.toURI + "->https://raw.githubusercontent.com/scala/scala/v" + scalaVersion.value + "/src/library/" - } + }) ) ++ myScalaJSSettings ++ Seq( name := "Scala library for Scala.js", publishArtifact in Compile := false, @@ -954,7 +978,7 @@ object Build { Set("-deprecation", "-unchecked", "-feature") contains _)), // Tell the plugin to hack-fix bad classOf trees - scalacOptions += "-P:scalajs:fixClassOf", + addScalaJSCompilerOption("fixClassOf"), libraryDependencies += "org.scala-lang" % "scala-library" % scalaVersion.value classifier "sources", @@ -1117,7 +1141,7 @@ object Build { "org.scala-lang" % "scala-reflect" % scalaVersion.value % "provided", // js.JSApp is annotated with @JSExportDescendentObjects - scalacOptions += "-P:scalajs:suppressExportDeprecations" + addScalaJSCompilerOption("suppressExportDeprecations") ) ++ ( scalaJSExternalCompileSettings ) ++ inConfig(Compile)(Seq( @@ -1310,7 +1334,7 @@ object Build { /* The test bridge uses namespaced top-level exports that we cannot * get rid of in 0.6.x. */ - scalacOptions += "-P:scalajs:suppressExportDeprecations" + addScalaJSCompilerOption("suppressExportDeprecations") ) ).withScalaJSCompiler.dependsOn(library, testInterface) @@ -1642,8 +1666,8 @@ object Build { * @JSName/missing @JSGlobal. Don't drown the test:compile output under * useless warnings. */ - scalacOptions in Test += "-P:scalajs:suppressExportDeprecations", - scalacOptions in Test += "-P:scalajs:suppressMissingJSGlobalDeprecations", + addScalaJSCompilerOptionInConfig(Test, "suppressExportDeprecations"), + addScalaJSCompilerOptionInConfig(Test, "suppressMissingJSGlobalDeprecations"), unmanagedSourceDirectories in Test ++= { val testDir = (sourceDirectory in Test).value diff --git a/tools/shared/src/main/scala-ide-stubs/org/scalajs/core/tools/linker/backend/emitter/ScalaJSEnvHolder.scala b/tools/shared/src/main/scala-ide-stubs/org/scalajs/core/tools/linker/backend/emitter/ScalaJSEnvHolder.scala new file mode 100644 index 0000000000..e003e280de --- /dev/null +++ b/tools/shared/src/main/scala-ide-stubs/org/scalajs/core/tools/linker/backend/emitter/ScalaJSEnvHolder.scala @@ -0,0 +1,21 @@ +/* + * 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.core.tools.linker.backend.emitter + +/* This file is a stub used only by IDEs. The file used for actual compilation + * is generated by the build. + */ + +private[emitter] object ScalaJSEnvHolder { + final val scalajsenv = "" +} From cb013d07d9fc9059e361003e67d739c45e367465 Mon Sep 17 00:00:00 2001 From: exoego Date: Mon, 1 Jul 2019 18:14:51 +0900 Subject: [PATCH 0013/1606] Fix #3709: Add TypedArray factory methods. The addition of type parameter to TypedArrayStatic is fine for the 0.6.x series, since it is binary compatible. The additions of methods in the open trait is also binary compatible, but only because this is a native JS trait. Therefore, suppressed MiMa for new methods in TypedArray. --- .../scalajs/js/typedarray/Float32Array.scala | 2 +- .../scalajs/js/typedarray/Float64Array.scala | 2 +- .../scalajs/js/typedarray/Int16Array.scala | 2 +- .../scalajs/js/typedarray/Int32Array.scala | 2 +- .../scalajs/js/typedarray/Int8Array.scala | 2 +- .../scalajs/js/typedarray/TypedArray.scala | 16 +++- .../scalajs/js/typedarray/Uint16Array.scala | 2 +- .../scalajs/js/typedarray/Uint32Array.scala | 2 +- .../scalajs/js/typedarray/Uint8Array.scala | 2 +- .../js/typedarray/Uint8ClampedArray.scala | 2 +- project/BinaryIncompatibilities.scala | 20 ++--- .../testsuite/typedarray/TypedArrayTest.scala | 76 +++++++++++++++++++ 12 files changed, 111 insertions(+), 19 deletions(-) diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/Float32Array.scala b/library/src/main/scala/scala/scalajs/js/typedarray/Float32Array.scala index 910806df03..6312752824 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/Float32Array.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/Float32Array.scala @@ -44,4 +44,4 @@ class Float32Array private[this] () extends TypedArray[Float, Float32Array] { */ @js.native @JSGlobal -object Float32Array extends TypedArrayStatic +object Float32Array extends TypedArrayStatic[Float, Float32Array] diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/Float64Array.scala b/library/src/main/scala/scala/scalajs/js/typedarray/Float64Array.scala index f4514a8cfe..393243abaf 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/Float64Array.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/Float64Array.scala @@ -44,4 +44,4 @@ class Float64Array private[this] () extends TypedArray[Double, Float64Array] { */ @js.native @JSGlobal -object Float64Array extends TypedArrayStatic +object Float64Array extends TypedArrayStatic[Double, Float64Array] diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/Int16Array.scala b/library/src/main/scala/scala/scalajs/js/typedarray/Int16Array.scala index 2db0796390..5a7fbe7cdf 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/Int16Array.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/Int16Array.scala @@ -44,4 +44,4 @@ class Int16Array private[this] () extends TypedArray[Short, Int16Array] { */ @js.native @JSGlobal -object Int16Array extends TypedArrayStatic +object Int16Array extends TypedArrayStatic[Short, Int16Array] diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/Int32Array.scala b/library/src/main/scala/scala/scalajs/js/typedarray/Int32Array.scala index 7eed4c63b8..b9c35c32b6 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/Int32Array.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/Int32Array.scala @@ -44,4 +44,4 @@ class Int32Array private[this] () extends TypedArray[Int, Int32Array] { */ @js.native @JSGlobal -object Int32Array extends TypedArrayStatic +object Int32Array extends TypedArrayStatic[Int, Int32Array] diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/Int8Array.scala b/library/src/main/scala/scala/scalajs/js/typedarray/Int8Array.scala index 4d7a3b79dd..ac26df29f7 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/Int8Array.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/Int8Array.scala @@ -44,4 +44,4 @@ class Int8Array private[this] () extends TypedArray[Byte, Int8Array] { */ @js.native @JSGlobal -object Int8Array extends TypedArrayStatic +object Int8Array extends TypedArrayStatic[Byte, Int8Array] diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/TypedArray.scala b/library/src/main/scala/scala/scalajs/js/typedarray/TypedArray.scala index f5f041f6c0..0a49782307 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/TypedArray.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/TypedArray.scala @@ -65,6 +65,20 @@ trait TypedArray[T, Repr] extends ArrayBufferView with js.Iterable[T] { * Static information that exists for any concrete TypedArray */ @js.native -trait TypedArrayStatic extends js.Object { +trait TypedArrayStatic[T, Repr] extends js.Object { val BYTES_PER_ELEMENT: Int = js.native + + /** Returns a new array from a set of elements. */ + def of(items: T*): Repr = js.native + + /** Creates an array from an `iterable` object. */ + def from(iterable: js.Iterable[T]): Repr = js.native + + /** Creates an array from an `iterable` object. */ + def from[E](iterable: js.Iterable[E], + mapFn: js.Function1[E, T]): Repr = js.native + + /** Creates an array from an `iterable` object. */ + def from[D, E](iterable: js.Iterable[E], mapFn: js.ThisFunction1[D, E, T], + thisArg: D): Repr = js.native } diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/Uint16Array.scala b/library/src/main/scala/scala/scalajs/js/typedarray/Uint16Array.scala index b14b92007a..3586c5008f 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/Uint16Array.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/Uint16Array.scala @@ -44,4 +44,4 @@ class Uint16Array private[this] () extends TypedArray[Int, Uint16Array] { */ @js.native @JSGlobal -object Uint16Array extends TypedArrayStatic +object Uint16Array extends TypedArrayStatic[Int, Uint16Array] diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/Uint32Array.scala b/library/src/main/scala/scala/scalajs/js/typedarray/Uint32Array.scala index accf72c806..49f30f2fbd 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/Uint32Array.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/Uint32Array.scala @@ -44,4 +44,4 @@ class Uint32Array private[this] () extends TypedArray[Double, Uint32Array] { */ @js.native @JSGlobal -object Uint32Array extends TypedArrayStatic +object Uint32Array extends TypedArrayStatic[Double, Uint32Array] diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/Uint8Array.scala b/library/src/main/scala/scala/scalajs/js/typedarray/Uint8Array.scala index 0551fa2334..851cf8b7df 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/Uint8Array.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/Uint8Array.scala @@ -44,4 +44,4 @@ class Uint8Array private[this] () extends TypedArray[Short, Uint8Array] { */ @js.native @JSGlobal -object Uint8Array extends TypedArrayStatic +object Uint8Array extends TypedArrayStatic[Short, Uint8Array] diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/Uint8ClampedArray.scala b/library/src/main/scala/scala/scalajs/js/typedarray/Uint8ClampedArray.scala index f54a5d442f..2fa1ff0948 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/Uint8ClampedArray.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/Uint8ClampedArray.scala @@ -46,4 +46,4 @@ class Uint8ClampedArray private[this] () */ @js.native @JSGlobal -object Uint8ClampedArray extends TypedArrayStatic +object Uint8ClampedArray extends TypedArrayStatic[Int, Uint8ClampedArray] diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 0b8f2e8459..2b1d742864 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -41,17 +41,19 @@ object BinaryIncompatibilities { ) val Library = Seq( + // New methods in a native JS trait, not an issue + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.scalajs.js.typedarray.TypedArrayStatic.*") ) val TestInterface = Seq( - /* Things that moved to scalajs-test-bridge. - * They were private[scalajs] or stricter, so not an issue. - */ - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testcommon.*"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testinterface.HTMLRunner"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testinterface.HTMLRunner$*"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testinterface.TestDetector"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testinterface.TestDetector$*"), - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testinterface.internal.*") + /* Things that moved to scalajs-test-bridge. + * They were private[scalajs] or stricter, so not an issue. + */ + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testcommon.*"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testinterface.HTMLRunner"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testinterface.HTMLRunner$*"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testinterface.TestDetector"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testinterface.TestDetector$*"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.testinterface.internal.*") ) } diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/typedarray/TypedArrayTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/typedarray/TypedArrayTest.scala index a004d48e14..bab7eb4617 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/typedarray/TypedArrayTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/typedarray/TypedArrayTest.scala @@ -27,6 +27,10 @@ import js.typedarray._ trait TypedArrayTest[V, T <: TypedArray[V, T]] { def bytesPerElement: Int + def ofFn(items: V*): T + def fromFn(iterable: js.Iterable[V]): T + def fromFn[E](iterable: js.Iterable[E])(mapper: js.Function1[E, V]): T + def fromFn[D, E](iterable: js.Iterable[E], thisObj: D)(mapper: js.ThisFunction1[D, E, V]): T def lenCtor(len: Int): T def tarrCtor(tarr: TypedArray[_, _]): T def itCtor(arr: js.Iterable[_]): T @@ -36,6 +40,32 @@ trait TypedArrayTest[V, T <: TypedArray[V, T]] { def hasType(obj: Any): Boolean def intToV(n: Int): V + @Test def should_provide_a_factory_method_of(): Unit = { + val x = ofFn(intToV(0), intToV(1), intToV(2)) + assertEquals(3, x.length) + assertEquals(2, x(2)) + } + + @Test def should_provide_a_factory_method_from(): Unit = { + val x = fromFn(js.Array(intToV(0), intToV(1), intToV(2))) + assertEquals(3, x.length) + assertEquals(2, x(2)) + } + + @Test def should_provide_a_factory_method_from_with_mapping_function(): Unit = { + val src = js.Array("", "a", "bc") + val x = fromFn(src)((s: String) => intToV(s.length)) + assertEquals(3, x.length) + assertEquals(2, x(2)) + } + + @Test def should_provide_a_factory_method_from_with_mapping_function_and_thisArg(): Unit = { + val src = js.Array("", "a", "bc") + val x = fromFn(src, 10)((thisArg: Int, s: String) => intToV(s.length * thisArg)) + assertEquals(3, x.length) + assertEquals(20, x(2)) + } + @Test def should_allow_constructing_a_new_name_with_length(): Unit = { val x = lenCtor(10) assertTrue(hasType(x)) @@ -251,6 +281,11 @@ trait TypedArrayTest[V, T <: TypedArray[V, T]] { object Int8ArrayTest extends Requires.TypedArray class Int8ArrayTest extends TypedArrayTest[Byte, Int8Array] { + def ofFn(items: Byte*): Int8Array = Int8Array.of(items: _*) + def fromFn(iterable: js.Iterable[Byte]): Int8Array = Int8Array.from(iterable) + def fromFn[E](iterable: js.Iterable[E])(fn: js.Function1[E, Byte]): Int8Array = Int8Array.from(iterable, fn) + def fromFn[D, E](iterable: js.Iterable[E], thisObj: D)(fn: js.ThisFunction1[D, E, Byte]): Int8Array = + Int8Array.from(iterable, fn, thisObj) def bytesPerElement: Int = Int8Array.BYTES_PER_ELEMENT def lenCtor(len: Int): Int8Array = new Int8Array(len) def tarrCtor(tarr: TypedArray[_, _]): Int8Array = new Int8Array(tarr) @@ -265,6 +300,11 @@ class Int8ArrayTest extends TypedArrayTest[Byte, Int8Array] { object Uint8ArrayTest extends Requires.TypedArray class Uint8ArrayTest extends TypedArrayTest[Short, Uint8Array] { + def ofFn(items: Short*): Uint8Array = Uint8Array.of(items: _*) + def fromFn(iterable: js.Iterable[Short]): Uint8Array = Uint8Array.from(iterable) + def fromFn[E](iterable: js.Iterable[E])(fn: js.Function1[E, Short]): Uint8Array = Uint8Array.from(iterable, fn) + def fromFn[D, E](iterable: js.Iterable[E], thisObj: D)(fn: js.ThisFunction1[D, E, Short]): Uint8Array = + Uint8Array.from(iterable, fn, thisObj) def bytesPerElement: Int = Uint8Array.BYTES_PER_ELEMENT def lenCtor(len: Int): Uint8Array = new Uint8Array(len) def tarrCtor(tarr: TypedArray[_, _]): Uint8Array = new Uint8Array(tarr) @@ -279,6 +319,12 @@ class Uint8ArrayTest extends TypedArrayTest[Short, Uint8Array] { object Uint8ClampedArrayTest extends Requires.TypedArray class Uint8ClampedArrayTest extends TypedArrayTest[Int, Uint8ClampedArray] { + def ofFn(items: Int*): Uint8ClampedArray = Uint8ClampedArray.of(items: _*) + def fromFn(iterable: js.Iterable[Int]): Uint8ClampedArray = Uint8ClampedArray.from(iterable) + def fromFn[E](iterable: js.Iterable[E])(fn: js.Function1[E, Int]): Uint8ClampedArray = + Uint8ClampedArray.from(iterable, fn) + def fromFn[D, E](iterable: js.Iterable[E], thisObj: D)(fn: js.ThisFunction1[D, E, Int]): Uint8ClampedArray = + Uint8ClampedArray.from(iterable, fn, thisObj) def bytesPerElement: Int = Uint8ClampedArray.BYTES_PER_ELEMENT def lenCtor(len: Int): Uint8ClampedArray = new Uint8ClampedArray(len) def tarrCtor(tarr: TypedArray[_, _]): Uint8ClampedArray = new Uint8ClampedArray(tarr) @@ -293,6 +339,11 @@ class Uint8ClampedArrayTest extends TypedArrayTest[Int, Uint8ClampedArray] { object Int16ArrayTest extends Requires.TypedArray class Int16ArrayTest extends TypedArrayTest[Short, Int16Array] { + def ofFn(items: Short*): Int16Array = Int16Array.of(items: _*) + def fromFn(iterable: js.Iterable[Short]): Int16Array = Int16Array.from(iterable) + def fromFn[E](iterable: js.Iterable[E])(fn: js.Function1[E, Short]): Int16Array = Int16Array.from(iterable, fn) + def fromFn[D, E](iterable: js.Iterable[E], thisObj: D)(fn: js.ThisFunction1[D, E, Short]): Int16Array = + Int16Array.from(iterable, fn, thisObj) def bytesPerElement: Int = Int16Array.BYTES_PER_ELEMENT def lenCtor(len: Int): Int16Array = new Int16Array(len) def tarrCtor(tarr: TypedArray[_, _]): Int16Array = new Int16Array(tarr) @@ -307,6 +358,11 @@ class Int16ArrayTest extends TypedArrayTest[Short, Int16Array] { object Uint16ArrayTest extends Requires.TypedArray class Uint16ArrayTest extends TypedArrayTest[Int, Uint16Array] { + def ofFn(items: Int*): Uint16Array = Uint16Array.of(items: _*) + def fromFn(iterable: js.Iterable[Int]): Uint16Array = Uint16Array.from(iterable) + def fromFn[E](iterable: js.Iterable[E])(fn: js.Function1[E, Int]): Uint16Array = Uint16Array.from(iterable, fn) + def fromFn[D, E](iterable: js.Iterable[E], thisObj: D)(fn: js.ThisFunction1[D, E, Int]): Uint16Array = + Uint16Array.from(iterable, fn, thisObj) def bytesPerElement: Int = Uint16Array.BYTES_PER_ELEMENT def lenCtor(len: Int): Uint16Array = new Uint16Array(len) def tarrCtor(tarr: TypedArray[_, _]): Uint16Array = new Uint16Array(tarr) @@ -321,6 +377,11 @@ class Uint16ArrayTest extends TypedArrayTest[Int, Uint16Array] { object Int32ArrayTest extends Requires.TypedArray class Int32ArrayTest extends TypedArrayTest[Int, Int32Array] { + def ofFn(items: Int*): Int32Array = Int32Array.of(items: _*) + def fromFn(iterable: js.Iterable[Int]): Int32Array = Int32Array.from(iterable) + def fromFn[E](iterable: js.Iterable[E])(fn: js.Function1[E, Int]): Int32Array = Int32Array.from(iterable, fn) + def fromFn[D, E](iterable: js.Iterable[E], thisObj: D)(fn: js.ThisFunction1[D, E, Int]): Int32Array = + Int32Array.from(iterable, fn, thisObj) def bytesPerElement: Int = Int32Array.BYTES_PER_ELEMENT def lenCtor(len: Int): Int32Array = new Int32Array(len) def tarrCtor(tarr: TypedArray[_, _]): Int32Array = new Int32Array(tarr) @@ -335,6 +396,11 @@ class Int32ArrayTest extends TypedArrayTest[Int, Int32Array] { object Uint32ArrayTest extends Requires.TypedArray class Uint32ArrayTest extends TypedArrayTest[Double, Uint32Array] { + def ofFn(items: Double*): Uint32Array = Uint32Array.of(items: _*) + def fromFn(iterable: js.Iterable[Double]): Uint32Array = Uint32Array.from(iterable) + def fromFn[E](iterable: js.Iterable[E])(fn: js.Function1[E, Double]): Uint32Array = Uint32Array.from(iterable, fn) + def fromFn[D, E](iterable: js.Iterable[E], thisObj: D)(fn: js.ThisFunction1[D, E, Double]): Uint32Array = + Uint32Array.from(iterable, fn, thisObj) def bytesPerElement: Int = Uint32Array.BYTES_PER_ELEMENT def lenCtor(len: Int): Uint32Array = new Uint32Array(len) def tarrCtor(tarr: TypedArray[_, _]): Uint32Array = new Uint32Array(tarr) @@ -349,6 +415,11 @@ class Uint32ArrayTest extends TypedArrayTest[Double, Uint32Array] { object Float32ArrayTest extends Requires.TypedArray class Float32ArrayTest extends TypedArrayTest[Float, Float32Array] { + def ofFn(items: Float*): Float32Array = Float32Array.of(items: _*) + def fromFn(iterable: js.Iterable[Float]): Float32Array = Float32Array.from(iterable) + def fromFn[E](iterable: js.Iterable[E])(fn: js.Function1[E, Float]): Float32Array = Float32Array.from(iterable, fn) + def fromFn[D, E](iterable: js.Iterable[E], thisObj: D)(fn: js.ThisFunction1[D, E, Float]): Float32Array = + Float32Array.from(iterable, fn, thisObj) def bytesPerElement: Int = Float32Array.BYTES_PER_ELEMENT def lenCtor(len: Int): Float32Array = new Float32Array(len) def tarrCtor(tarr: TypedArray[_, _]): Float32Array = new Float32Array(tarr) @@ -363,6 +434,11 @@ class Float32ArrayTest extends TypedArrayTest[Float, Float32Array] { object Float64ArrayTest extends Requires.TypedArray class Float64ArrayTest extends TypedArrayTest[Double, Float64Array] { + def ofFn(items: Double*): Float64Array = Float64Array.of(items: _*) + def fromFn(iterable: js.Iterable[Double]): Float64Array = Float64Array.from(iterable) + def fromFn[E](iterable: js.Iterable[E])(fn: js.Function1[E, Double]): Float64Array = Float64Array.from(iterable, fn) + def fromFn[D, E](iterable: js.Iterable[E], thisObj: D)(fn: js.ThisFunction1[D, E, Double]): Float64Array = + Float64Array.from(iterable, fn, thisObj) def bytesPerElement: Int = Float64Array.BYTES_PER_ELEMENT def lenCtor(len: Int): Float64Array = new Float64Array(len) def tarrCtor(tarr: TypedArray[_, _]): Float64Array = new Float64Array(tarr) From 8061f61f33cd12ee8e435be37f3c22aa33175bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 2 Jul 2019 17:17:24 +0200 Subject: [PATCH 0014/1606] Use `-Ystop-after` instead of `-Yskip` to disable the JVM back-end. This makes the code independent of what phases still exist after the JS back-end. In particular, it gets rid of the annoying warnings (seen as if they were errors) about `icode` not existing in 2.11+. --- project/Build.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index f815afe4fc..7aae07348e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -322,8 +322,12 @@ object Build { } ) - val noClassFilesSettings: Setting[_] = - scalacOptions in (Compile, compile) += "-Yskip:cleanup,icode,jvm" + val noClassFilesSettings: Setting[_] = { + scalacOptions in (Compile, compile) += { + if (isGeneratingForIDE) "-Yskip:jvm" + else "-Ystop-after:jscode" + } + } val publishSettings = Seq( publishMavenStyle := true, From f122aa56d0d8aaeaff904f53c781891823307f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 3 Jul 2019 13:29:23 +0200 Subject: [PATCH 0015/1606] Fix #3704: Implement java.util.Collection.removeIf. --- .../src/main/scala/java/util/Collection.scala | 18 ++++++++++ .../concurrent/CopyOnWriteArrayList.scala | 34 +++++++++++++++++++ .../resources/2.13.0/BlacklistedTests.txt | 4 --- .../resources/2.13.0/WhitelistedTests.txt | 1 + .../javalib/util/CollectionTest.scala | 23 +++++++++++++ 5 files changed, 76 insertions(+), 4 deletions(-) diff --git a/javalib/src/main/scala/java/util/Collection.scala b/javalib/src/main/scala/java/util/Collection.scala index f8a0b7a556..34af7828ea 100644 --- a/javalib/src/main/scala/java/util/Collection.scala +++ b/javalib/src/main/scala/java/util/Collection.scala @@ -12,6 +12,10 @@ 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 @@ -24,6 +28,20 @@ trait Collection[E] extends java.lang.Iterable[E] { def containsAll(c: Collection[_]): Boolean def addAll(c: Collection[_ <: E]): Boolean def removeAll(c: Collection[_]): Boolean + + @JavaDefaultMethod + def removeIf(filter: Predicate[_ >: E]): Boolean = { + var result = false + val iter = iterator() + while (iter.hasNext()) { + if (filter.test(iter.next())) { + iter.remove() + result = true + } + } + result + } + def retainAll(c: Collection[_]): Boolean def clear(): Unit def equals(o: Any): Boolean diff --git a/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala b/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala index 243bd3b36a..adca7c315c 100644 --- a/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala +++ b/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala @@ -14,6 +14,7 @@ package java.util.concurrent import java.lang.{reflect => jlr} import java.util._ +import java.util.function.Predicate import scala.annotation.tailrec @@ -185,6 +186,39 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) !c.isEmpty } + /* Override Collection.removeIf() because our iterators do not support + * the `remove()` method. + */ + override def removeIf(filter: Predicate[_ >: E]): Boolean = { + // scalastyle:off return + /* The outer loop iterates as long as no element passes the filter (and + * hence no modification is required). + */ + val iter = iterator() + var index = 0 + while (iter.hasNext()) { + if (filter.test(iter.next())) { + /* We found the first element that needs to be removed: copy and + * truncate at the current index. + */ + copyIfNeeded() + innerSplice(index, size() - index) + /* Now keep iterating, but push elements that do not pass the test. + * `index` is useless from now on, so do not keep updating it. + */ + while (iter.hasNext()) { + val elem = iter.next() + if (!filter.test(elem)) + innerPush(elem) + } + return true + } + index += 1 + } + false // the outer loop finished without entering the inner one + // scalastyle:on return + } + override def toString: String = iterator().scalaOps.mkString("[", ",", "]") diff --git a/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt b/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt index 6571436825..8d2a2b543e 100644 --- a/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.0/BlacklistedTests.txt @@ -183,7 +183,3 @@ scala/util/SortingTest.scala scala/collection/StringOpsTest.scala scala/collection/StringParsersTest.scala scala/collection/convert/MapWrapperTest.scala - -## Buglisted -# 3704 -scala/collection/convert/JSetWrapperTest.scala diff --git a/scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt b/scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt index 647cc8c0fe..531758b2fc 100644 --- a/scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt +++ b/scala-test-suite/src/test/resources/2.13.0/WhitelistedTests.txt @@ -33,6 +33,7 @@ scala/collection/convert/BinaryTreeStepperTest.scala scala/collection/convert/JCollectionWrapperTest.scala scala/collection/convert/JIterableWrapperTest.scala scala/collection/convert/JListWrapperTest.scala +scala/collection/convert/JSetWrapperTest.scala scala/collection/convert/NullSafetyToJavaTest.scala scala/collection/convert/NullSafetyToScalaTest.scala scala/collection/generic/DecoratorsTest.scala diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala index 05d15aa2c1..8f40d3e734 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala @@ -221,6 +221,23 @@ trait CollectionTest { assertEquals(coll.iterator().asScala.toSet, Set("one", "two", "three")) } + + @Test def removeIf(): Unit = { + val coll = factory.fromElements[Int](42, 50, 12, 0, -45, 102, 32, 75) + assertEquals(8, coll.size()) + + assertTrue(coll.removeIf(new java.util.function.Predicate[Int] { + def test(x: Int): Boolean = x >= 50 + })) + assertEquals(5, coll.size()) + assertEquals(coll.iterator().asScala.toSet, Set(-45, 0, 12, 32, 42)) + + assertFalse(coll.removeIf(new java.util.function.Predicate[Int] { + def test(x: Int): Boolean = x >= 45 + })) + assertEquals(5, coll.size()) + assertEquals(coll.iterator().asScala.toSet, Set(-45, 0, 12, 32, 42)) + } } object CollectionFactory { @@ -233,4 +250,10 @@ trait CollectionFactory { def empty[E: ClassTag]: ju.Collection[E] def allowsMutationThroughIterator: Boolean = true def allowsNullElementQuery: Boolean = true + + def fromElements[E: ClassTag](elems: E*): ju.Collection[E] = { + val coll = empty[E] + coll.addAll(elems.asJavaCollection) + coll + } } From 9d8b86a252de71d3fab6a8a1ab9acfeef53f5b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 9 Jul 2019 13:43:38 +0200 Subject: [PATCH 0016/1606] Compile the javalanglib with -Yno-predef. When writing code in the java.lang package, references to things like `Boolean` or `Double` refer to `j.l.Boolean` or `j.l.Double`. Usually this is not what we want (we want the primitive types instead), but the implicits available in `Predef` hide mistakes by introducing boxing and unboxing where required. The `-Yno-predef` flag prevents these mistakes from happening. --- .../src/main/scala/java/lang/Boolean.scala | 2 +- javalanglib/src/main/scala/java/lang/Byte.scala | 2 +- .../src/main/scala/java/lang/Character.scala | 16 +++++++++++----- javalanglib/src/main/scala/java/lang/Class.scala | 7 +++++-- .../src/main/scala/java/lang/Double.scala | 4 ++-- javalanglib/src/main/scala/java/lang/Enum.scala | 2 +- javalanglib/src/main/scala/java/lang/Float.scala | 2 +- .../src/main/scala/java/lang/Integer.scala | 2 +- javalanglib/src/main/scala/java/lang/Long.scala | 12 ++++++++---- javalanglib/src/main/scala/java/lang/Math.scala | 12 ++++++------ javalanglib/src/main/scala/java/lang/Short.scala | 2 +- .../src/main/scala/java/lang/System.scala | 2 +- .../src/main/scala/java/lang/ThreadLocal.scala | 2 +- javalanglib/src/main/scala/java/lang/Void.scala | 2 +- project/Build.scala | 9 +++++++++ 15 files changed, 50 insertions(+), 28 deletions(-) diff --git a/javalanglib/src/main/scala/java/lang/Boolean.scala b/javalanglib/src/main/scala/java/lang/Boolean.scala index 680752bf93..3c6deb828f 100644 --- a/javalanglib/src/main/scala/java/lang/Boolean.scala +++ b/javalanglib/src/main/scala/java/lang/Boolean.scala @@ -41,7 +41,7 @@ final class Boolean private () } object Boolean { - final val TYPE = classOf[scala.Boolean] + final val TYPE = scala.Predef.classOf[scala.Boolean] /* TRUE and FALSE are supposed to be vals. However, they are better * optimized as defs, because they end up being just the constant true and diff --git a/javalanglib/src/main/scala/java/lang/Byte.scala b/javalanglib/src/main/scala/java/lang/Byte.scala index f1be4954e8..36e457dbf8 100644 --- a/javalanglib/src/main/scala/java/lang/Byte.scala +++ b/javalanglib/src/main/scala/java/lang/Byte.scala @@ -45,7 +45,7 @@ final class Byte private () extends Number with Comparable[Byte] { } object Byte { - final val TYPE = classOf[scala.Byte] + final val TYPE = scala.Predef.classOf[scala.Byte] final val SIZE = 8 final val BYTES = 1 diff --git a/javalanglib/src/main/scala/java/lang/Character.scala b/javalanglib/src/main/scala/java/lang/Character.scala index 7ae4e551d1..546fca6b46 100644 --- a/javalanglib/src/main/scala/java/lang/Character.scala +++ b/javalanglib/src/main/scala/java/lang/Character.scala @@ -172,7 +172,7 @@ class Character(private val value: scala.Char) } object Character { - final val TYPE = classOf[scala.Char] + final val TYPE = scala.Predef.classOf[scala.Char] final val MIN_VALUE = '\u0000' final val MAX_VALUE = '\uffff' final val SIZE = 16 @@ -571,8 +571,8 @@ object Character { //def getDirectionality(c: scala.Char): scala.Byte /* Conversions */ - def toUpperCase(c: scala.Char): scala.Char = c.toString.toUpperCase()(0) - def toLowerCase(c: scala.Char): scala.Char = c.toString.toLowerCase()(0) + def toUpperCase(c: scala.Char): scala.Char = c.toString.toUpperCase().charAt(0) + def toLowerCase(c: scala.Char): scala.Char = c.toString.toLowerCase().charAt(0) //def toTitleCase(c: scala.Char): scala.Char //def getNumericValue(c: scala.Char): Int @@ -995,8 +995,14 @@ object Character { } private[this] def uncompressDeltas(deltas: Array[Int]): Array[Int] = { - for (i <- 1 until deltas.length) - deltas(i) += deltas(i - 1) + var acc = deltas(0) + var i = 1 + val len = deltas.length + while (i != len) { + acc += deltas(i) + deltas(i) = acc + i += 1 + } deltas } diff --git a/javalanglib/src/main/scala/java/lang/Class.scala b/javalanglib/src/main/scala/java/lang/Class.scala index 34c1d30c47..3886b3190f 100644 --- a/javalanglib/src/main/scala/java/lang/Class.scala +++ b/javalanglib/src/main/scala/java/lang/Class.scala @@ -41,7 +41,9 @@ final class Class[A] private (data: ScalaJSClassData[A]) extends Object { def isInstance(obj: Object): scala.Boolean = data.isInstance(obj) - def isAssignableFrom(that: Class[_]): scala.Boolean = + def isAssignableFrom(that: Class[_]): scala.Boolean = { + import scala.Predef.classOf + if (this.isPrimitive || that.isPrimitive) { /* This differs from the JVM specification to mimic the behavior of * runtime type tests of primitive numeric types. @@ -63,6 +65,7 @@ final class Class[A] private (data: ScalaJSClassData[A]) extends Object { } else { this.isInstance(that.getFakeInstance()) } + } private def getFakeInstance(): Object = data.getFakeInstance() @@ -106,7 +109,7 @@ final class Class[A] private (data: ScalaJSClassData[A]) extends Object { @inline // optimize for the Unchecked case, where this becomes identity() def cast(obj: Object): A = { scala.scalajs.runtime.SemanticsUtils.asInstanceOfCheck( - (this eq classOf[Nothing]) || + (this eq scala.Predef.classOf[Nothing]) || (obj != null && !isRawJSType && !isInstance(obj)), new ClassCastException("" + obj + " is not an instance of " + getName)) obj.asInstanceOf[A] diff --git a/javalanglib/src/main/scala/java/lang/Double.scala b/javalanglib/src/main/scala/java/lang/Double.scala index 79ac693dac..0b7b2a0933 100644 --- a/javalanglib/src/main/scala/java/lang/Double.scala +++ b/javalanglib/src/main/scala/java/lang/Double.scala @@ -58,7 +58,7 @@ final class Double private () extends Number with Comparable[Double] { } object Double { - final val TYPE = classOf[scala.Double] + final val TYPE = scala.Predef.classOf[scala.Double] final val POSITIVE_INFINITY = 1.0 / 0.0 final val NEGATIVE_INFINITY = 1.0 / -0.0 final val NaN = 0.0 / 0.0 @@ -175,7 +175,7 @@ object Double { val mantissa = js.Dynamic.global.parseInt(truncatedMantissaStr, 16).asInstanceOf[scala.Double] - assert(mantissa != 0.0 && mantissa != scala.Double.PositiveInfinity) + // Assert: mantissa != 0.0 && mantissa != scala.Double.PositiveInfinity val binaryExpDouble = js.Dynamic.global.parseInt(binaryExpStr, 10).asInstanceOf[scala.Double] diff --git a/javalanglib/src/main/scala/java/lang/Enum.scala b/javalanglib/src/main/scala/java/lang/Enum.scala index d10947e5b0..8f4dfb50b1 100644 --- a/javalanglib/src/main/scala/java/lang/Enum.scala +++ b/javalanglib/src/main/scala/java/lang/Enum.scala @@ -30,7 +30,7 @@ abstract class Enum[E <: Enum[E]] protected (_name: String, _ordinal: Int) override protected final def clone(): AnyRef = throw new CloneNotSupportedException("Enums are not cloneable") - final def compareTo(o: E): Int = _ordinal.compareTo(o.ordinal) + final def compareTo(o: E): Int = Integer.compare(_ordinal, o.ordinal) // Not implemented: // final def getDeclaringClass(): Class[E] diff --git a/javalanglib/src/main/scala/java/lang/Float.scala b/javalanglib/src/main/scala/java/lang/Float.scala index a64620e091..91abeb7071 100644 --- a/javalanglib/src/main/scala/java/lang/Float.scala +++ b/javalanglib/src/main/scala/java/lang/Float.scala @@ -56,7 +56,7 @@ final class Float private () extends Number with Comparable[Float] { } object Float { - final val TYPE = classOf[scala.Float] + final val TYPE = scala.Predef.classOf[scala.Float] final val POSITIVE_INFINITY = 1.0f / 0.0f final val NEGATIVE_INFINITY = 1.0f / -0.0f final val NaN = 0.0f / 0.0f diff --git a/javalanglib/src/main/scala/java/lang/Integer.scala b/javalanglib/src/main/scala/java/lang/Integer.scala index b2f538b4c7..1cc590f09d 100644 --- a/javalanglib/src/main/scala/java/lang/Integer.scala +++ b/javalanglib/src/main/scala/java/lang/Integer.scala @@ -61,7 +61,7 @@ final class Integer private () extends Number with Comparable[Integer] { } object Integer { - final val TYPE = classOf[scala.Int] + final val TYPE = scala.Predef.classOf[scala.Int] final val MIN_VALUE = -2147483648 final val MAX_VALUE = 2147483647 final val SIZE = 32 diff --git a/javalanglib/src/main/scala/java/lang/Long.scala b/javalanglib/src/main/scala/java/lang/Long.scala index 66804953e4..0c7c5ca695 100644 --- a/javalanglib/src/main/scala/java/lang/Long.scala +++ b/javalanglib/src/main/scala/java/lang/Long.scala @@ -51,7 +51,7 @@ final class Long private () extends Number with Comparable[Long] { object Long { import scala.scalajs.runtime.RuntimeLong - final val TYPE = classOf[scala.Long] + final val TYPE = scala.Predef.classOf[scala.Long] final val MIN_VALUE = -9223372036854775808L final val MAX_VALUE = 9223372036854775807L final val SIZE = 64 @@ -68,11 +68,14 @@ object Long { */ private lazy val StringRadixInfos: js.Array[StringRadixInfo] = { val r = new js.Array[StringRadixInfo]() + var radix = 0 - for (_ <- 0 until Character.MIN_RADIX) + while (radix < Character.MIN_RADIX) { r += null + radix += 1 + } - for (radix <- Character.MIN_RADIX to Character.MAX_RADIX) { + while (radix <= Character.MAX_RADIX) { /* Find the biggest chunk size we can use. * * - radixPowLength should be the biggest signed int32 value that is an @@ -95,6 +98,7 @@ object Long { val overflowBarrier = Long.divideUnsigned(-1L, radixPowLengthLong) r += new StringRadixInfo(chunkLength, radixPowLengthLong, paddingZeros, overflowBarrier) + radix += 1 } r @@ -291,7 +295,7 @@ object Long { secondResult } else { // Third and final chunk. This one can overflow - assert(secondChunkEnd + chunkLen == length) + // Assert: secondChunkEnd + chunkLen == length val overflowBarrier = radixInfo.overflowBarrier val thirdChunk = parseChunk(secondChunkEnd, length) diff --git a/javalanglib/src/main/scala/java/lang/Math.scala b/javalanglib/src/main/scala/java/lang/Math.scala index 3f42a01ab1..df510cee73 100644 --- a/javalanglib/src/main/scala/java/lang/Math.scala +++ b/javalanglib/src/main/scala/java/lang/Math.scala @@ -105,7 +105,7 @@ object Math { if (assumingES6 || !js.isUndefined(g.Math.cbrt)) { js.Math.cbrt(a) } else { - if (a == 0 || a.isNaN || a.isPosInfinity || a.isNegInfinity) { + if (a == 0 || Double.isNaN(a) || Double.isInfinite(a)) { a } else { val sign = if (a < 0.0) -1.0 else 1.0 @@ -213,7 +213,7 @@ object Math { // http://en.wikipedia.org/wiki/Hypot#Implementation if (abs(a) == scala.Double.PositiveInfinity || abs(b) == scala.Double.PositiveInfinity) scala.Double.PositiveInfinity - else if (a.isNaN || b.isNaN) + else if (Double.isNaN(a) || Double.isNaN(b)) scala.Double.NaN else if (a == 0 && b == 0) 0.0 @@ -234,7 +234,7 @@ object Math { js.Math.expm1(a) } else { // https://github.com/ghewgill/picomath/blob/master/javascript/expm1.js - if (a == 0 || a.isNaN) + if (a == 0 || Double.isNaN(a)) a // Power Series http://en.wikipedia.org/wiki/Power_series // for small values of a, exp(a) = 1 + a + (a*a)/2 @@ -249,7 +249,7 @@ object Math { if (assumingES6 || !js.isUndefined(g.Math.sinh)) { js.Math.sinh(a) } else { - if (a.isNaN || a == 0.0 || abs(a) == scala.Double.PositiveInfinity) a + if (Double.isNaN(a) || a == 0.0 || abs(a) == scala.Double.PositiveInfinity) a else (exp(a) - exp(-a)) / 2.0 } } @@ -258,7 +258,7 @@ object Math { if (assumingES6 || !js.isUndefined(g.Math.cosh)) { js.Math.cosh(a) } else { - if (a.isNaN) + if (Double.isNaN(a)) a else if (a == 0.0) 1.0 @@ -273,7 +273,7 @@ object Math { if (assumingES6 || !js.isUndefined(g.Math.tanh)) { js.Math.tanh(a) } else { - if (a.isNaN || a == 0.0) + if (Double.isNaN(a) || a == 0.0) a else if (abs(a) == scala.Double.PositiveInfinity) signum(a) diff --git a/javalanglib/src/main/scala/java/lang/Short.scala b/javalanglib/src/main/scala/java/lang/Short.scala index 54c312f0ba..275784e3e8 100644 --- a/javalanglib/src/main/scala/java/lang/Short.scala +++ b/javalanglib/src/main/scala/java/lang/Short.scala @@ -44,7 +44,7 @@ final class Short private () extends Number with Comparable[Short] { } object Short { - final val TYPE = classOf[scala.Short] + final val TYPE = scala.Predef.classOf[scala.Short] final val SIZE = 16 final val BYTES = 2 diff --git a/javalanglib/src/main/scala/java/lang/System.scala b/javalanglib/src/main/scala/java/lang/System.scala index 24697f49ba..b0c53a8b68 100644 --- a/javalanglib/src/main/scala/java/lang/System.scala +++ b/javalanglib/src/main/scala/java/lang/System.scala @@ -291,7 +291,7 @@ object System { def gc(): Unit = Runtime.getRuntime().gc() } -private[lang] final class JSConsoleBasedPrintStream(isErr: Boolean) +private[lang] final class JSConsoleBasedPrintStream(isErr: scala.Boolean) extends PrintStream(new JSConsoleBasedPrintStream.DummyOutputStream) { import JSConsoleBasedPrintStream._ diff --git a/javalanglib/src/main/scala/java/lang/ThreadLocal.scala b/javalanglib/src/main/scala/java/lang/ThreadLocal.scala index 0582006a66..11cbaeb627 100644 --- a/javalanglib/src/main/scala/java/lang/ThreadLocal.scala +++ b/javalanglib/src/main/scala/java/lang/ThreadLocal.scala @@ -13,7 +13,7 @@ package java.lang class ThreadLocal[T] { - private var hasValue: Boolean = false + private var hasValue: scala.Boolean = false private var v: T = _ protected def initialValue(): T = null.asInstanceOf[T] diff --git a/javalanglib/src/main/scala/java/lang/Void.scala b/javalanglib/src/main/scala/java/lang/Void.scala index 6caa81accb..b4de582730 100644 --- a/javalanglib/src/main/scala/java/lang/Void.scala +++ b/javalanglib/src/main/scala/java/lang/Void.scala @@ -15,5 +15,5 @@ package java.lang final class Void private () object Void { - final val TYPE = classOf[scala.Unit] + final val TYPE = scala.Predef.classOf[scala.Unit] } diff --git a/project/Build.scala b/project/Build.scala index 49f62bf5fa..5395da4aa7 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -923,6 +923,15 @@ object Build { delambdafySetting, noClassFilesSettings, + /* When writing code in the java.lang package, references to things + * like `Boolean` or `Double` refer to `j.l.Boolean` or `j.l.Double`. + * Usually this is not what we want (we want the primitive types + * instead), but the implicits available in `Predef` hide mistakes by + * introducing boxing and unboxing where required. The `-Yno-predef` + * flag prevents these mistakes from happening. + */ + scalacOptions += "-Yno-predef", + resourceGenerators in Compile += Def.task { val base = (resourceManaged in Compile).value Seq( From cde963b2638120f07304ccad6e870dd6bb9f28ea Mon Sep 17 00:00:00 2001 From: exoego Date: Tue, 2 Jul 2019 18:51:48 +0900 Subject: [PATCH 0017/1606] Skip n bytes if possible in the default Reader.skip(n). --- javalib/src/main/scala/java/io/Reader.scala | 22 +++++++++++++++++-- .../testsuite/javalib/io/ReadersTest.scala | 18 +++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/javalib/src/main/scala/java/io/Reader.scala b/javalib/src/main/scala/java/io/Reader.scala index df45a80c00..68bea4dc81 100644 --- a/javalib/src/main/scala/java/io/Reader.scala +++ b/javalib/src/main/scala/java/io/Reader.scala @@ -14,6 +14,8 @@ package java.io import java.nio.CharBuffer +import scala.annotation.tailrec + abstract class Reader private[this] (_lock: Option[Object]) extends Readable with Closeable { @@ -53,8 +55,24 @@ abstract class Reader private[this] (_lock: Option[Object]) def skip(n: Long): Long = { if (n < 0) throw new IllegalArgumentException("Cannot skip negative amount") - else if (read() == -1) 0 - else 1 + + val buffer = new Array[Char](8192) + @tailrec + def loop(m: Long, lastSkipped: Long): Long = { + if (m <= 0) { + lastSkipped + } else { + val mMin = Math.min(m, 8192).toInt + val skipped = read(buffer, 0, mMin) + if (skipped < 0) { + lastSkipped + } else { + val totalSkipped = lastSkipped + skipped + loop(m - mMin, totalSkipped) + } + } + } + loop(n, 0) } def ready(): Boolean = false diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala index 32bcf92cee..b5bd9494fb 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala @@ -18,10 +18,28 @@ import java.io._ import org.junit.Test import org.junit.Assert._ +import org.junit.Assume._ import org.scalajs.testsuite.utils.AssertThrows._ +import org.scalajs.testsuite.utils.Platform._ /** Tests for our implementation of java.io._ reader classes */ +class ReaderTest { + object MyReader extends java.io.Reader { + def read(dbuf: Array[Char], off: Int, len: Int): Int = { + java.util.Arrays.fill(dbuf, off, off + len, 'A') + len + } + def close(): Unit = () + } + + @Test def skip_should_always_skip_n_if_possible(): Unit = { + assumeFalse("Too slow in Rhino", executingInRhino) + assertEquals(42, MyReader.skip(42)) + assertEquals(10000, MyReader.skip(10000)) // more than the 8192 batch size + } +} + class StringReaderTest { val str = "asdf" def newReader: StringReader = new StringReader(str) From 5180897c2f9d8c5200a5b41bfd9892a20d6b755e Mon Sep 17 00:00:00 2001 From: exoego Date: Tue, 2 Jul 2019 18:53:26 +0900 Subject: [PATCH 0018/1606] Do not allow negative skips in StringReader.skip(n) when EOF has been reached. --- .../src/main/scala/java/io/StringReader.scala | 13 +++-- .../testsuite/javalib/io/ReadersTest.scala | 47 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/javalib/src/main/scala/java/io/StringReader.scala b/javalib/src/main/scala/java/io/StringReader.scala index 6aa8755418..0643390139 100644 --- a/javalib/src/main/scala/java/io/StringReader.scala +++ b/javalib/src/main/scala/java/io/StringReader.scala @@ -70,10 +70,15 @@ class StringReader(s: String) extends Reader { } override def skip(ns: Long): Long = { - // Apparently, StringReader.skip allows negative skips - val count = Math.max(Math.min(ns, s.length - pos).toInt, -pos) - pos += count - count.toLong + if (pos >= s.length) { + // Always return 0 if the entire string has been read or skipped + 0 + } else { + // Apparently, StringReader.skip allows negative skips + val count = Math.max(Math.min(ns, s.length - pos).toInt, -pos) + pos += count + count.toLong + } } private def ensureOpen(): Unit = { diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala index b5bd9494fb..bea92d47b5 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala @@ -130,6 +130,29 @@ class StringReaderTest { @Test def should_support_marking(): Unit = { assertTrue(newReader.markSupported) } + + @Test def skip_should_accept_negative_lookahead_as_lookback(): Unit = { + // StringReader.skip accepts negative lookahead + val r = newReader + assertEquals("already head", 0, r.skip(-1)) + assertEquals('a', r.read()) + + assertEquals(1, r.skip(1)) + assertEquals('d', r.read()) + + assertEquals(-2, r.skip(-2)) + assertEquals('s', r.read()) + } + + @Test def skip_should_always_return_0_after_reaching_end(): Unit = { + val r = newReader + assertEquals(4, r.skip(100)) + assertEquals(-1, r.read()) + + assertEquals(0, r.skip(-100)) + assertEquals(-1, r.read()) + } + } class BufferedReaderTest { @@ -260,6 +283,15 @@ class BufferedReaderTest { assertEquals(null, r.readLine()) } + @Test def skip_should_always_return_0_after_reaching_end(): Unit = { + val r = newReader + assertEquals(25, r.skip(100)) + assertEquals(-1, r.read()) + + assertEquals(0, r.skip(100)) + assertEquals(-1, r.read()) + } + @Test def should_support_marking(): Unit = { assertTrue(newReader.markSupported) } @@ -311,4 +343,19 @@ class InputStreamReaderTest { assertEquals(0, streamReader.read(new Array[Char](0))) } + @Test def skip_should_always_return_0_after_reaching_end(): Unit = { + val data = "Lorem ipsum".getBytes() + val r = new InputStreamReader(new ByteArrayInputStream(data)) + assertTrue(r.skip(100) > 0) + assertEquals(-1, r.read()) + + assertEquals(0, r.skip(100)) + assertEquals(-1, r.read()) + } + + @Test def should_throw_IOException_since_mark_is_not_supported(): Unit = { + val data = "Lorem ipsum".getBytes() + val r = new InputStreamReader(new ByteArrayInputStream(data)) + expectThrows(classOf[IOException], r.mark(0)) + } } From ed5d5f4334303b7db1e12eabc9f590e8506feae7 Mon Sep 17 00:00:00 2001 From: exoego Date: Tue, 2 Jul 2019 18:54:09 +0900 Subject: [PATCH 0019/1606] Throw when Reader.mark() is given a negative read-ahead limit. --- javalib/src/main/scala/java/io/BufferedReader.scala | 2 ++ javalib/src/main/scala/java/io/StringReader.scala | 2 ++ .../org/scalajs/testsuite/javalib/io/ReadersTest.scala | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/javalib/src/main/scala/java/io/BufferedReader.scala b/javalib/src/main/scala/java/io/BufferedReader.scala index a880e7611d..0e18b89eef 100644 --- a/javalib/src/main/scala/java/io/BufferedReader.scala +++ b/javalib/src/main/scala/java/io/BufferedReader.scala @@ -36,6 +36,8 @@ class BufferedReader(in: Reader, sz: Int) extends Reader { } override def mark(readAheadLimit: Int): Unit = { + if (readAheadLimit < 0) + throw new IllegalArgumentException("Read-ahead limit < 0") ensureOpen() val srcBuf = buf diff --git a/javalib/src/main/scala/java/io/StringReader.scala b/javalib/src/main/scala/java/io/StringReader.scala index 0643390139..51f343f9f5 100644 --- a/javalib/src/main/scala/java/io/StringReader.scala +++ b/javalib/src/main/scala/java/io/StringReader.scala @@ -23,6 +23,8 @@ class StringReader(s: String) extends Reader { } override def mark(readAheadLimit: Int): Unit = { + if (readAheadLimit < 0) + throw new IllegalArgumentException("Read-ahead limit < 0") ensureOpen() mark = pos diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala index bea92d47b5..6d2063e5d8 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/io/ReadersTest.scala @@ -131,6 +131,10 @@ class StringReaderTest { assertTrue(newReader.markSupported) } + @Test def mark_should_throw_with_negative_lookahead(): Unit = { + expectThrows(classOf[IllegalArgumentException], newReader.mark(-10)) + } + @Test def skip_should_accept_negative_lookahead_as_lookback(): Unit = { // StringReader.skip accepts negative lookahead val r = newReader @@ -295,6 +299,10 @@ class BufferedReaderTest { @Test def should_support_marking(): Unit = { assertTrue(newReader.markSupported) } + + @Test def mark_should_throw_with_negative_lookahead(): Unit = { + expectThrows(classOf[IllegalArgumentException], newReader.mark(-10)) + } } class InputStreamReaderTest { From bfda917836f0b540fe172b8091afbaffe4415ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 9 Jul 2019 11:32:58 +0200 Subject: [PATCH 0020/1606] Do not eagerly throw for `Nothing` in `Class.cast()`. There is no need to, as the compiler will insert an explicit cast at call site to adapt the erased `A` (`Object`) to `Nothing` (assuming the call is not in statement position). The test was a bit too restrictive, and has been relaxed not to expect a specific error message, and to only expect an exception when used in expression position. The new behavior is consistent with Scala/JVM, since of course, on the JVM, `Class.cast()` does not know about `scala.runtime.Nothing$`. --- javalanglib/src/main/scala/java/lang/Class.scala | 1 - .../scalajs/testsuite/compiler/RuntimeTypesTest.scala | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/javalanglib/src/main/scala/java/lang/Class.scala b/javalanglib/src/main/scala/java/lang/Class.scala index 3886b3190f..b1937f2a42 100644 --- a/javalanglib/src/main/scala/java/lang/Class.scala +++ b/javalanglib/src/main/scala/java/lang/Class.scala @@ -109,7 +109,6 @@ final class Class[A] private (data: ScalaJSClassData[A]) extends Object { @inline // optimize for the Unchecked case, where this becomes identity() def cast(obj: Object): A = { scala.scalajs.runtime.SemanticsUtils.asInstanceOfCheck( - (this eq scala.Predef.classOf[Nothing]) || (obj != null && !isRawJSType && !isInstance(obj)), new ClassCastException("" + obj + " is not an instance of " + getName)) obj.asInstanceOf[A] diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/RuntimeTypesTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/RuntimeTypesTest.scala index 9ac732cdd6..9682473d5b 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/RuntimeTypesTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/RuntimeTypesTest.scala @@ -64,13 +64,12 @@ class RuntimeTypesTest { assumeTrue("Assumed compliant asInstanceOf", hasCompliantAsInstanceOfs) def test(x: Any): Unit = { try { - classOf[Nothing].cast(x) + val result = classOf[Nothing].cast(x) fail("casting " + x + " to Nothing did not fail") + identity(result) // discard `result` without warning } catch { - case th: Throwable => - assertTrue(th.isInstanceOf[ClassCastException]) - assertEquals(x + " is not an instance of scala.runtime.Nothing$", - th.getMessage) + case th: ClassCastException => + // ok } } test("a") From 054a1fb714e3ae2b6f38d91be72458e28d47ebea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 6 Jul 2019 16:19:55 +0200 Subject: [PATCH 0021/1606] Optimize calls to `Array.apply()` overloads with literal varargs. In Scala/JVM, this optimization is performed by the `cleanup` phase, but it must come after our back-end because the other things it does are not good for us, so we have to reimplement it ourselves. --- .../org/scalajs/core/compiler/GenJSCode.scala | 30 +++++++++++--- .../core/compiler/test/OptimizationTest.scala | 39 +++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/core/compiler/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/core/compiler/GenJSCode.scala index a22f8a35a3..fa35e529e4 100644 --- a/compiler/src/main/scala/org/scalajs/core/compiler/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/core/compiler/GenJSCode.scala @@ -54,7 +54,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) import jsInterop.{jsNameOf, compat068FullJSNameOf, jsNativeLoadSpecOf, JSName} import JSTreeExtractors._ - import treeInfo.hasSynthCaseSymbol + import treeInfo.{hasSynthCaseSymbol, StripCast} import platform.isMaybeBoxed @@ -1905,6 +1905,23 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } } + /* !!! Copy-pasted from `CleanUp.scala` upstream and simplified with + * our `WrapArray` extractor. + * + * Replaces `Array(Predef.wrapArray(ArrayValue(...).$asInstanceOf[...]), )` + * with just `ArrayValue(...).$asInstanceOf[...]` + * + * See scala/bug#6611; we must *only* do this for literal vararg arrays. + * + * This is normally done by `cleanup` but it comes later than this phase. + */ + case Apply(appMeth, Apply(wrapRefArrayMeth, (arg @ StripCast(ArrayValue(_, _))) :: Nil) :: _ :: Nil) + if wrapRefArrayMeth.symbol == WrapArray.wrapRefArrayMethod && appMeth.symbol == ArrayModule_genericApply => + genStatOrExpr(arg, isStat) + case Apply(appMeth, elem0 :: WrapArray(rest @ ArrayValue(elemtpt, _)) :: Nil) + if appMeth.symbol == ArrayModule_apply(elemtpt.tpe) => + genStatOrExpr(treeCopy.ArrayValue(rest, rest.elemtpt, elem0 :: rest.elems), isStat) + case app: Apply => genApply(app, isStat) @@ -4983,11 +5000,14 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } object WrapArray { - private val isWrapArray: Set[Symbol] = { - val wrapArrayModule = - if (hasNewCollections) ScalaRunTimeModule - else PredefModule + private val wrapArrayModule = + if (hasNewCollections) ScalaRunTimeModule + else PredefModule + val wrapRefArrayMethod: Symbol = + getMemberMethod(wrapArrayModule, nme.wrapRefArray) + + private val isWrapArray: Set[Symbol] = { Seq( nme.wrapRefArray, nme.wrapByteArray, diff --git a/compiler/src/test/scala/org/scalajs/core/compiler/test/OptimizationTest.scala b/compiler/src/test/scala/org/scalajs/core/compiler/test/OptimizationTest.scala index d16f02d9fa..4ec963f215 100644 --- a/compiler/src/test/scala/org/scalajs/core/compiler/test/OptimizationTest.scala +++ b/compiler/src/test/scala/org/scalajs/core/compiler/test/OptimizationTest.scala @@ -49,6 +49,45 @@ class OptimizationTest extends JSASTTest { } } + @Test + def testArrayApplyOptimization: Unit = { + /* Make sure Array(...) is optimized away completely for several kinds + * of data types, with both the generic overload and the ones specialized + * for primitives. + */ + """ + class A { + val a = Array(5, 7, 9, -3) + val b = Array("hello", "world") + val c = Array('a', 'b') + val d = Array(Nil) + val e = Array(5.toByte, 7.toByte, 9.toByte, -3.toByte) + } + """. + hasNot("any LoadModule of the scala.Array companion") { + case js.LoadModule(jstpe.ClassType("s_Array$")) => + } + + /* Using [] with primitives produces suboptimal trees, which cannot be + * optimized. We should improve this in the future, if possible. This is + * particularly annoying for Byte and Short, as it means that we need to + * write `.toByte` for every single element if we want the optimization to + * kick in. + * + * Scala/JVM has the same limitation. + */ + """ + class A { + val a = Array[Int](5, 7, 9, -3) + val b = Array[Byte](5, 7, 9, -3) + } + """. + hasExactly(2, "calls to Array.apply methods") { + case js.Apply(js.LoadModule(jstpe.ClassType("s_Array$")), js.Ident(methodName, _), _) + if methodName.startsWith("apply__") => + } + } + @Test def testJSArrayApplyOptimization: Unit = { /* Make sure js.Array(...) is optimized away completely for several kinds From 9ac9780b6c20c6998ba9779aed91b2238eec77c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 8 Jul 2019 17:18:34 +0200 Subject: [PATCH 0022/1606] Allow the Array.apply opt to apply to constant arrays of `j.l.Character`. We remove the type parameters of `Array[Int](...)` so that the *overload* for `Int` is selected, rather than the generic one, which cannot be optimized for primitives. Two arrays were `Array[Byte]`s, and this change turns them into `Array[Int]`s, because `Array[Byte](...)` cannot be optimized unless all elements are real bytes (not ints adaptable to bytes). This is fine, though, because those two arrays were only used in places were `Int`s are equally good, if not better. --- .../src/main/scala/java/lang/Character.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/javalanglib/src/main/scala/java/lang/Character.scala b/javalanglib/src/main/scala/java/lang/Character.scala index 546fca6b46..0db6c71acb 100644 --- a/javalanglib/src/main/scala/java/lang/Character.scala +++ b/javalanglib/src/main/scala/java/lang/Character.scala @@ -237,10 +237,10 @@ object Character { } @inline - private[this] def getTypeLT256(codePoint: Int): scala.Byte = + private[this] def getTypeLT256(codePoint: Int): Int = charTypesFirst256(codePoint) - private[this] def getTypeGE256(codePoint: Int): scala.Byte = { + private[this] def getTypeGE256(codePoint: Int): Int = { // the idx is increased by 1 due to the differences in indexing // between charTypeIndices and charType val idx = Arrays.binarySearch(charTypeIndices, codePoint) + 1 @@ -628,7 +628,7 @@ object Character { // Based on Unicode 7.0.0 // Types of characters from 0 to 255 - private[this] lazy val charTypesFirst256 = Array[scala.Byte](15, 15, 15, 15, + private[this] lazy val charTypesFirst256: Array[Int] = Array(15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 12, 24, 24, 24, 26, 24, 24, 24, 21, 22, 24, 25, 24, 20, 24, 24, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 24, 24, 25, @@ -661,11 +661,11 @@ object Character { // .map(tup => tup._1 - tup._2) // val charTypes = indicesAndTypes.map(_._2) // println(charTypeIndicesDeltas.mkString( - // "charTypeIndices: val deltas = Array[Int](", ", ", ")")) - // println(charTypes.mkString("val charTypes = Array[scala.Byte](", ", ", ")")) + // "charTypeIndices: val deltas = Array(", ", ", ")")) + // println(charTypes.mkString("val charTypes = Array(", ", ", ")")) // - private[this] lazy val charTypeIndices = { - val deltas = Array[Int](257, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + private[this] lazy val charTypeIndices: Array[Int] = { + val deltas = Array(257, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -815,7 +815,7 @@ object Character { uncompressDeltas(deltas) } - private[this] lazy val charTypes = Array[scala.Byte](1, 2, 1, 2, 1, 2, + private[this] lazy val charTypes: Array[Int] = Array(1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, @@ -979,8 +979,8 @@ object Character { // 0 :: isMirroredIndices.init).map(tup => tup._1 - tup._2) // println(isMirroredIndicesDeltas.mkString( // "isMirroredIndices: val deltas = Array[Int](", ", ", ")")) - private[this] lazy val isMirroredIndices = { - val deltas = Array[Int](40, 2, 18, 1, 1, 1, 28, 1, 1, 1, 29, 1, 1, 1, + private[this] lazy val isMirroredIndices: Array[Int] = { + val deltas = Array(40, 2, 18, 1, 1, 1, 28, 1, 1, 1, 29, 1, 1, 1, 45, 1, 15, 1, 3710, 4, 1885, 2, 2460, 2, 10, 2, 54, 2, 14, 2, 177, 1, 192, 4, 3, 6, 3, 1, 3, 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, @@ -1013,7 +1013,7 @@ object Character { * point mapping to digits from 0 to 9. */ private[this] lazy val nonASCIIZeroDigitCodePoints: Array[Int] = { - Array[Int](0x660, 0x6f0, 0x7c0, 0x966, 0x9e6, 0xa66, 0xae6, 0xb66, 0xbe6, + Array(0x660, 0x6f0, 0x7c0, 0x966, 0x9e6, 0xa66, 0xae6, 0xb66, 0xbe6, 0xc66, 0xce6, 0xd66, 0xe50, 0xed0, 0xf20, 0x1040, 0x1090, 0x17e0, 0x1810, 0x1946, 0x19d0, 0x1a80, 0x1a90, 0x1b50, 0x1bb0, 0x1c40, 0x1c50, 0xa620, 0xa8d0, 0xa900, 0xa9d0, 0xaa50, 0xabf0, 0xff10, 0x104a0, From 9839acc2d140a7e58e71d68bcb4662331acc3f70 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 14 Jul 2019 12:14:28 +0200 Subject: [PATCH 0023/1606] Fix caching behavior of java.lang.Object generator Instead of simply assuming that the IR hasn't changed if the file exists, we actually check this. --- project/Build.scala | 30 +++++++++--------------------- project/JavaLangObject.scala | 11 +++++++++-- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index b01cda903f..ccb0889877 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -10,10 +10,7 @@ import Keys._ import com.typesafe.tools.mima.plugin.MimaPlugin.autoImport._ import de.heikoseeberger.sbtheader.HeaderPlugin.autoImport._ -import java.io.{ - BufferedOutputStream, - FileOutputStream -} +import java.util.Arrays import scala.collection.mutable import scala.concurrent.Await @@ -818,21 +815,6 @@ object Build { } } - private def serializeHardcodedIR(base: File, - classDef: ir.Trees.ClassDef): File = { - // We assume that there are no weird characters in the full name - val fullName = ir.Definitions.decodeClassName(classDef.name.name) - val output = base / (fullName.replace('.', '/') + ".sjsir") - - if (!output.exists()) { - IO.createDirectory(output.getParentFile) - val stream = new BufferedOutputStream(new FileOutputStream(output)) - try ir.Serializers.serialize(stream, classDef) - finally stream.close() - } - output - } - lazy val javalanglib: Project = project.enablePlugins( MyScalaJSPlugin ).settings( @@ -845,8 +827,14 @@ object Build { noClassFilesSettings, resourceGenerators in Compile += Def.task { - val base = (resourceManaged in Compile).value - Seq(serializeHardcodedIR(base, JavaLangObject.TheClassDef)) + val output = (resourceManaged in Compile).value / "java/lang/Object.sjsir" + val data = JavaLangObject.irBytes + + if (!output.exists || !Arrays.equals(data, IO.readBytes(output))) { + IO.write(output, data) + } + + Seq(output) }.taskValue, scalaJSExternalCompileSettings ).withScalaJSCompiler.dependsOnLibraryNoJar diff --git a/project/JavaLangObject.scala b/project/JavaLangObject.scala index 52dbe1a1bf..c0fde75188 100644 --- a/project/JavaLangObject.scala +++ b/project/JavaLangObject.scala @@ -4,6 +4,8 @@ package build * Hard-coded IR for java.lang.Object. */ +import java.io.ByteArrayOutputStream + import org.scalajs.ir import ir._ import ir.Definitions._ @@ -18,8 +20,7 @@ import ir.Position.NoPosition * compiler to define java.lang.Object. */ object JavaLangObject { - - val TheClassDef = { + private val TheClassDef = { implicit val DummyPos = NoPosition // ClassType(Object) is normally invalid, but not in this class def @@ -175,4 +176,10 @@ object JavaLangObject { Hashers.hashClassDef(classDef) } + val irBytes: Array[Byte] = { + val stream = new ByteArrayOutputStream + try ir.Serializers.serialize(stream, TheClassDef) + finally stream.close() + stream.toByteArray + } } From c37f7b235312e2ff0457dc1e2fe6426170535bd9 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 14 Jul 2019 15:08:51 +0200 Subject: [PATCH 0024/1606] Avoid constructors of primitive boxes They are deprecated in Java 9. --- .../src/main/scala/java/lang/reflect/Array.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/javalanglib/src/main/scala/java/lang/reflect/Array.scala b/javalanglib/src/main/scala/java/lang/reflect/Array.scala index e394e4f912..0f80c62261 100644 --- a/javalanglib/src/main/scala/java/lang/reflect/Array.scala +++ b/javalanglib/src/main/scala/java/lang/reflect/Array.scala @@ -41,14 +41,14 @@ object Array { def get(array: AnyRef, index: Int): AnyRef = array match { case array: Array[Object] => array(index) - case array: Array[Boolean] => new java.lang.Boolean(array(index)) - case array: Array[Char] => new java.lang.Character(array(index)) - case array: Array[Byte] => new java.lang.Byte(array(index)) - case array: Array[Short] => new java.lang.Short(array(index)) - case array: Array[Int] => new java.lang.Integer(array(index)) - case array: Array[Long] => new java.lang.Long(array(index)) - case array: Array[Float] => new java.lang.Float(array(index)) - case array: Array[Double] => new java.lang.Double(array(index)) + case array: Array[Boolean] => java.lang.Boolean.valueOf(array(index)) + case array: Array[Char] => java.lang.Character.valueOf(array(index)) + case array: Array[Byte] => java.lang.Byte.valueOf(array(index)) + case array: Array[Short] => java.lang.Short.valueOf(array(index)) + case array: Array[Int] => java.lang.Integer.valueOf(array(index)) + 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") } From 720d456431c8afabf908e6f581c46c6b8bf4dc7d Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sun, 14 Jul 2019 21:03:53 +0200 Subject: [PATCH 0025/1606] Do not assume full control over classesDirectory in ExternalCompile In fact, sbt's copyResources also writes into that directory. So removing it entirely is problematic. Instead we compile to a cached directory and sync the files. This ensures stale files will get removed. --- project/ExternalCompile.scala | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/project/ExternalCompile.scala b/project/ExternalCompile.scala index b23d6b9619..872d6ae3f1 100644 --- a/project/ExternalCompile.scala +++ b/project/ExternalCompile.scala @@ -50,16 +50,17 @@ object ExternalCompile { // Compile - val cachedCompile = FileFunction.cached(cacheDir / "compile", + val outputDirectory = cacheDir / "compile-out" + val cachedCompile = FileFunction.cached(cacheDir / "compile-cache", FilesInfo.lastModified, FilesInfo.exists) { dependencies => logger.info( "Compiling %d Scala sources to %s..." format ( sources.size, classesDirectory)) - if (classesDirectory.exists) - IO.delete(classesDirectory) - IO.createDirectory(classesDirectory) + if (outputDirectory.exists) + IO.delete(outputDirectory) + IO.createDirectory(outputDirectory) val sourcesArgs = sources.map(_.getAbsolutePath()).toList @@ -83,7 +84,7 @@ object ExternalCompile { val run = (runner in compile).value val optErrorMsg = run.run("scala.tools.nsc.Main", compilerCp, "-cp" :: cpStr :: - "-d" :: classesDirectory.getAbsolutePath() :: + "-d" :: outputDirectory.getAbsolutePath() :: options ++: sourcesArgs, patchedLogger) @@ -103,8 +104,12 @@ object ExternalCompile { doCompile(sourcesArgs) } - // Output is all files in classesDirectory - (classesDirectory ** AllPassFilter).get.toSet + // Copy to classes directory. + val mappings = (outputDirectory ** AllPassFilter) + .pair(Path.rebase(outputDirectory, classesDirectory)) + Sync.sync(s.cacheStoreFactory.make("compile-copy"))(mappings) + + mappings.unzip._2.toSet } cachedCompile((sources ++ allMyDependencies).toSet) From 5d2b2f1a63ef7f7fb2968c6cd02fff24e03dc4b6 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 23 Jul 2019 20:31:14 +0200 Subject: [PATCH 0026/1606] Make partest depend on test-interface only Otherwise we pull in dependencies that are not binary compatible. --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index ccb0889877..9ef5dda2e0 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1750,7 +1750,7 @@ object Build { libraryDependencies ++= { if (shouldPartest.value) { Seq( - "org.scala-sbt" % "sbt" % sbtVersion.value, + "org.scala-sbt" % "test-interface" % "1.0", { val v = scalaVersion.value if (v == "2.11.0" || v == "2.11.1" || v == "2.11.2") From 6360501b95cbaaa4e7e310311a72b2b752f6083a Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 13 Jul 2019 12:49:11 +0200 Subject: [PATCH 0027/1606] Switch our build to sbt-1.2.8 --- project/Build.scala | 106 +++++++++++++++++----------------- project/ExternalCompile.scala | 17 +++--- project/build.properties | 2 +- project/build.sbt | 2 +- 4 files changed, 65 insertions(+), 62 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 9ef5dda2e0..6fb0c96509 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -76,8 +76,9 @@ object MyScalaJSPlugin extends AutoPlugin { def addScalaJSCompilerOption(config: Option[Configuration], option: Def.Initialize[String]): Setting[_] = { config.fold(scalacOptions)(scalacOptions in _) ++= { + val o = option.value if (isGeneratingForIDE) Nil - else Seq(s"-P:scalajs:${option.value}") + else Seq(s"-P:scalajs:$o") } } @@ -205,6 +206,15 @@ object Build { organization := "org.scala-js", version := scalaJSVersion, + crossScalaVersions := Seq( + "2.10.7", + "2.11.0", "2.11.1", "2.11.2", "2.11.4", "2.11.5", "2.11.6", "2.11.7", + "2.11.8", "2.11.11", "2.11.12", + "2.12.1", "2.12.2", "2.12.3", "2.12.4", "2.12.5", "2.12.6", "2.12.7", + "2.12.8", + "2.13.0", + ), + normalizedName ~= { _.replace("scala.js", "scalajs").replace("scala-js", "scalajs") }, @@ -462,12 +472,9 @@ object Build { def withScalaJSJUnitPlugin: Project = { project.settings( scalacOptions in Test ++= { - if (isGeneratingForIDE) { - Seq.empty - } else { - val jar = (packageBin in (jUnitPlugin, Compile)).value - Seq(s"-Xplugin:$jar") - } + val jar = (packageBin in (jUnitPlugin, Compile)).value + if (isGeneratingForIDE) Seq.empty + else Seq(s"-Xplugin:$jar") } ) } @@ -599,36 +606,41 @@ object Build { "com.novocode" % "junit-interface" % "0.9" % "test" ), testOptions += Tests.Argument(TestFrameworks.JUnit, "-a"), - testOptions += Tests.Setup { () => - val testOutDir = (streams.value.cacheDirectory / "scalajs-compiler-test") - IO.createDirectory(testOutDir) - System.setProperty("scala.scalajs.compiler.test.output", - testOutDir.getAbsolutePath) - System.setProperty("scala.scalajs.compiler.test.scalajslib", - (packageBin in (LocalProject("library"), Compile)).value.getAbsolutePath) - - def scalaArtifact(name: String): String = { - def isTarget(att: Attributed[File]) = { - att.metadata.get(moduleID.key).exists { mId => - mId.organization == "org.scala-lang" && - mId.name == name && - mId.revision == scalaVersion.value + testOptions += { + val s = streams.value + val sjslib = (packageBin in (LocalProject("library"), Compile)).value + + Tests.Setup { () => + val testOutDir = (s.cacheDirectory / "scalajs-compiler-test") + IO.createDirectory(testOutDir) + System.setProperty("scala.scalajs.compiler.test.output", + testOutDir.getAbsolutePath) + System.setProperty("scala.scalajs.compiler.test.scalajslib", + sjslib.getAbsolutePath) + + def scalaArtifact(name: String): String = { + def isTarget(att: Attributed[File]) = { + att.metadata.get(moduleID.key).exists { mId => + mId.organization == "org.scala-lang" && + mId.name == name && + mId.revision == scalaVersion.value + } } - } - (managedClasspath in Test).value.find(isTarget).fold { - streams.value.log.error(s"Couldn't find $name on the classpath") - "" - } { lib => - lib.data.getAbsolutePath + (managedClasspath in Test).value.find(isTarget).fold { + s.log.error(s"Couldn't find $name on the classpath") + "" + } { lib => + lib.data.getAbsolutePath + } } - } - System.setProperty("scala.scalajs.compiler.test.scalalib", - scalaArtifact("scala-library")) + System.setProperty("scala.scalajs.compiler.test.scalalib", + scalaArtifact("scala-library")) - System.setProperty("scala.scalajs.compiler.test.scalareflect", - scalaArtifact("scala-reflect")) + System.setProperty("scala.scalajs.compiler.test.scalareflect", + scalaArtifact("scala-reflect")) + } }, exportJars := true ).dependsOnSource(irProject) @@ -779,6 +791,7 @@ object Build { case _ => "1.0.0" } }, + crossScalaVersions := Seq("2.10.7", "2.12.8"), scalaBinaryVersion := CrossVersion.binaryScalaVersion(scalaVersion.value), previousArtifactSetting, @@ -892,28 +905,15 @@ object Build { artifactPath in fetchScalaSource := target.value / "scalaSources" / scalaVersion.value, - /* Work around for #2649. We would like to always use `update`, but - * that fails if the scalaVersion we're looking for happens to be the - * version of Scala used by sbt itself. This is clearly a bug in sbt, - * which we work around here by using `updateClassifiers` instead in - * that case. - */ - update in fetchScalaSource := Def.taskDyn { - if (scalaVersion.value == scala.util.Properties.versionNumberString) - updateClassifiers - else - update - }.value, - fetchScalaSource := { val s = streams.value val cacheDir = s.cacheDirectory val ver = scalaVersion.value val trgDir = (artifactPath in fetchScalaSource).value - val report = (update in fetchScalaSource).value + val report = update.value val scalaLibSourcesJar = report.select( - configuration = Set("compile"), + configuration = configurationFilter("compile"), module = moduleFilter(name = "scala-library"), artifact = artifactFilter(classifier = "sources")).headOption.getOrElse { throw new Exception( @@ -973,6 +973,8 @@ object Build { val sources = mutable.ListBuffer.empty[File] val paths = mutable.Set.empty[String] + val s = streams.value + for { srcDir <- sourceDirectories normSrcDir = normPath(srcDir) @@ -987,7 +989,7 @@ object Build { if (paths.add(path)) sources += src else - streams.value.log.debug(s"not including $src") + s.log.debug(s"not including $src") } } @@ -1078,7 +1080,7 @@ object Build { -- "*.nodoc.scala" ) - (sources in doc).value.filter(filter.accept) + prev.filter(filter.accept) } else { Nil } @@ -1776,10 +1778,8 @@ object Build { }, sources in Compile := { - if (shouldPartest.value) - (sources in Compile).value - else - Nil + val s = (sources in Compile).value + if (shouldPartest.value) s else Nil } ).dependsOn(compiler, linker, nodeJSEnv) diff --git a/project/ExternalCompile.scala b/project/ExternalCompile.scala index 872d6ae3f1..13fd69218a 100644 --- a/project/ExternalCompile.scala +++ b/project/ExternalCompile.scala @@ -27,7 +27,12 @@ object ExternalCompile { compile := { val inputs = (compileInputs in compile).value - import inputs.config._ + val run = (runner in compile).value + + val classpath = inputs.options.classpath + val classesDirectory = inputs.options.classesDirectory + val sources = inputs.options.sources + val options = inputs.options.scalacOptions val s = streams.value val logger = s.log @@ -73,7 +78,7 @@ object ExternalCompile { def log(level: Level.Value, message: => String) = { val msg = message if (level != Level.Info || - !msg.startsWith("Running scala.tools.nsc.Main")) + !msg.startsWith("Running (fork) scala.tools.nsc.Main")) logger.log(level, msg) } def success(message: => String) = logger.success(message) @@ -81,14 +86,12 @@ object ExternalCompile { } def doCompile(sourcesArgs: List[String]): Unit = { - val run = (runner in compile).value - val optErrorMsg = run.run("scala.tools.nsc.Main", compilerCp, + run.run("scala.tools.nsc.Main", compilerCp, "-cp" :: cpStr :: "-d" :: outputDirectory.getAbsolutePath() :: options ++: sourcesArgs, - patchedLogger) - optErrorMsg.foreach(errorMsg => throw new Exception(errorMsg)) + patchedLogger).get } /* Crude way of overcoming the Windows limitation on command line @@ -115,7 +118,7 @@ object ExternalCompile { cachedCompile((sources ++ allMyDependencies).toSet) // We do not have dependency analysis when compiling externally - sbt.inc.Analysis.Empty + sbt.internal.inc.Analysis.Empty } ) diff --git a/project/build.properties b/project/build.properties index 133a8f197e..c0bab04941 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.17 +sbt.version=1.2.8 diff --git a/project/build.sbt b/project/build.sbt index c741be357f..0b43234e86 100644 --- a/project/build.sbt +++ b/project/build.sbt @@ -25,7 +25,7 @@ unmanagedSourceDirectories in Compile ++= { root / "test-adapter/src/main/scala", root / "test-common/src/main/scala", root / "sbt-plugin/src/main/scala", - root / "sbt-plugin/src/main/scala-sbt-0.13" + root / "sbt-plugin/src/main/scala-sbt-1.0" ) } From fe176165a6f21a782ab5792d8482a75f539e03ed Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 13 Jul 2019 14:57:22 +0200 Subject: [PATCH 0028/1606] Drop sbt 0.13.x support --- DEVELOPING.md | 2 +- Jenkinsfile | 9 +-- project/Build.scala | 9 +-- project/build.sbt | 1 - sbt-plugin-test/build.sbt | 7 +- .../org/scalajs/sbtplugin/SBTCompat.scala | 64 ----------------- .../org/scalajs/sbtplugin/SBTCompat.scala | 71 ------------------- .../sbtplugin/ScalaJSCrossVersion.scala | 14 ++-- .../org/scalajs/sbtplugin/ScalaJSPlugin.scala | 5 +- .../sbtplugin/ScalaJSPluginInternal.scala | 63 +++++++++------- scripts/publish.sh | 7 +- 11 files changed, 58 insertions(+), 194 deletions(-) delete mode 100644 sbt-plugin/src/main/scala-sbt-0.13/org/scalajs/sbtplugin/SBTCompat.scala delete mode 100644 sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SBTCompat.scala diff --git a/DEVELOPING.md b/DEVELOPING.md index 8f495a2bd0..697865c368 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -184,5 +184,5 @@ following incantations. > ++SCALA_VERSION > ;compiler/publishLocal;library/publishLocal;testInterface/publishLocal;testBridge/publishLocal;jUnitRuntime/publishLocal;jUnitPlugin/publishLocal - > ++2.10.7 + > ++2.12.8 > ;ir/publishLocal;io/publishLocal;logging/publishLocal;linker/publishLocal;jsEnvs/publishLocal;jsEnvsTestKit/publishLocal;nodeJSEnv/publishLocal;testAdapter/publishLocal;sbtPlugin/publishLocal diff --git a/Jenkinsfile b/Jenkinsfile index 0c16bff949..5c38d62fe9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -370,21 +370,19 @@ def Tasks = [ "sbtplugin-test": ''' setJavaVersion 1.8 - SBT_VER_OVERRIDE=$sbt_version_override # Publish Scala.js artifacts locally # Then go into standalone project and test npm install && sbt ++2.11.12 compiler/publishLocal library/publishLocal \ testInterface/publishLocal testBridge/publishLocal \ jUnitPlugin/publishLocal jUnitRuntime/publishLocal && - sbt ++$toolsscala ${SBT_VER_OVERRIDE:+^^$SBT_VER_OVERRIDE} \ + sbt ++$toolsscala \ ir/publishLocal logging/publishLocal \ linker/publishLocal jsEnvs/publishLocal \ nodeJSEnv/publishLocal testAdapter/publishLocal \ sbtPlugin/publishLocal && cd sbt-plugin-test && setJavaVersion $java && - if [ -n "$SBT_VER_OVERRIDE" ]; then echo "sbt.version=$SBT_VER_OVERRIDE" > ./project/build.properties; fi && sbt noDOM/run \ noDOM/testHtml multiTestJS/testHtml \ test \ @@ -456,13 +454,12 @@ mainScalaVersions.each { scalaVersion -> } quickMatrix.add([task: "test-suite-ecma-script5-force-polyfills", scala: mainScalaVersion, java: mainJavaVersion, testSuite: "testSuite"]) allJavaVersions.each { javaVersion -> - quickMatrix.add([task: "tools-sbtplugin", scala: "2.12.8", sbt_version_override: "", java: javaVersion]) + quickMatrix.add([task: "tools-sbtplugin", scala: "2.12.8", java: javaVersion]) quickMatrix.add([task: "tools", scala: "2.10.7", java: javaVersion]) quickMatrix.add([task: "tools", scala: "2.11.12", java: javaVersion]) } quickMatrix.add([task: "partestc", scala: "2.11.0", java: mainJavaVersion]) -quickMatrix.add([task: "sbtplugin-test", toolsscala: "2.10.7", sbt_version_override: "0.13.17", java: mainJavaVersion]) -quickMatrix.add([task: "sbtplugin-test", toolsscala: "2.12.8", sbt_version_override: "", java: mainJavaVersion]) +quickMatrix.add([task: "sbtplugin-test", toolsscala: "2.12.8", java: mainJavaVersion]) // The 'full' matrix def fullMatrix = quickMatrix.clone() diff --git a/project/Build.scala b/project/Build.scala index 6fb0c96509..cd2e1d9c06 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -785,13 +785,8 @@ object Build { normalizedName := "sbt-scalajs", bintrayProjectName := "sbt-scalajs-plugin", // "sbt-scalajs" was taken sbtPlugin := true, - sbtVersion in pluginCrossBuild := { - scalaVersion.value match { - case v if v.startsWith("2.10.") => "0.13.17" - case _ => "1.0.0" - } - }, - crossScalaVersions := Seq("2.10.7", "2.12.8"), + crossScalaVersions := Seq("2.12.8"), + sbtVersion := "1.0.0", scalaBinaryVersion := CrossVersion.binaryScalaVersion(scalaVersion.value), previousArtifactSetting, diff --git a/project/build.sbt b/project/build.sbt index 0b43234e86..d2f25ee6f3 100644 --- a/project/build.sbt +++ b/project/build.sbt @@ -25,7 +25,6 @@ unmanagedSourceDirectories in Compile ++= { root / "test-adapter/src/main/scala", root / "test-common/src/main/scala", root / "sbt-plugin/src/main/scala", - root / "sbt-plugin/src/main/scala-sbt-1.0" ) } diff --git a/sbt-plugin-test/build.sbt b/sbt-plugin-test/build.sbt index 0862d22ed9..fda4ddee08 100644 --- a/sbt-plugin-test/build.sbt +++ b/sbt-plugin-test/build.sbt @@ -164,11 +164,8 @@ lazy val multiTestJVM = project.in(file("multiTest/jvm")). // Test platformDepsCrossVersion (as a setting, it's evaluated when loading the build) platformDepsCrossVersion := { val value = platformDepsCrossVersion.value - if (!sbtVersion.value.startsWith("0.")) { - // In 0.13, CrossVersions do not have a meaningful ==, but they do in 1.0 - assert(value == CrossVersion.binary, - "platformDepsCrossVersion should be CrossVersion.binary in multiTestJVM") - } + assert(value == CrossVersion.binary, + "platformDepsCrossVersion should be CrossVersion.binary in multiTestJVM") value } ). diff --git a/sbt-plugin/src/main/scala-sbt-0.13/org/scalajs/sbtplugin/SBTCompat.scala b/sbt-plugin/src/main/scala-sbt-0.13/org/scalajs/sbtplugin/SBTCompat.scala deleted file mode 100644 index 200e700174..0000000000 --- a/sbt-plugin/src/main/scala-sbt-0.13/org/scalajs/sbtplugin/SBTCompat.scala +++ /dev/null @@ -1,64 +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. - */ - -package org.scalajs.sbtplugin - -import sbt._ - -private[sbtplugin] object SBTCompat { - type IncOptions = sbt.inc.IncOptions - - val formatImplicits: sbt.Cache.type = sbt.Cache - - def crossVersionAddScalaJSPart(cross: CrossVersion, - part: String): CrossVersion = { - cross match { - case CrossVersion.Disabled => - CrossVersion.binaryMapped(_ => part) - case cross: CrossVersion.Binary => - CrossVersion.binaryMapped( - cross.remapVersion.andThen(part + "_" + _)) - case cross: CrossVersion.Full => - CrossVersion.fullMapped( - cross.remapVersion.andThen(part + "_" + _)) - } - } - - /** Patches the IncOptions so that .sjsir files are pruned as needed. - * - * This complicated logic patches the ClassfileManager factory of the given - * IncOptions with one that is aware of .sjsir files emitted by the Scala.js - * compiler. This makes sure that, when a .class file must be deleted, the - * corresponding .sjsir file are also deleted. - */ - def scalaJSPatchIncOptions(incOptions: IncOptions): IncOptions = { - val inheritedNewClassfileManager = incOptions.newClassfileManager - val newClassfileManager = () => new sbt.inc.ClassfileManager { - private[this] val inherited = inheritedNewClassfileManager() - - def delete(classes: Iterable[File]): Unit = { - inherited.delete(classes flatMap { classFile => - val scalaJSFiles = if (classFile.getPath endsWith ".class") { - val f = new File(classFile.getPath.stripSuffix(".class") + ".sjsir") - if (f.exists) List(f) - else Nil - } else Nil - classFile :: scalaJSFiles - }) - } - - def generated(classes: Iterable[File]): Unit = inherited.generated(classes) - def complete(success: Boolean): Unit = inherited.complete(success) - } - incOptions.withNewClassfileManager(newClassfileManager) - } -} diff --git a/sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SBTCompat.scala b/sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SBTCompat.scala deleted file mode 100644 index 1182d71b45..0000000000 --- a/sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SBTCompat.scala +++ /dev/null @@ -1,71 +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. - */ - -package org.scalajs.sbtplugin - -import sbt._ - -private[sbtplugin] object SBTCompat { - type IncOptions = xsbti.compile.IncOptions - - val formatImplicits: sjsonnew.BasicJsonProtocol.type = - sjsonnew.BasicJsonProtocol - - def crossVersionAddScalaJSPart(cross: CrossVersion, - part: String): CrossVersion = { - cross match { - case CrossVersion.Disabled => - CrossVersion.constant(part) - case cross: sbt.librarymanagement.Constant => - cross.withValue(part + "_" + cross.value) - case cross: CrossVersion.Binary => - cross.withPrefix(part + "_" + cross.prefix) - case cross: CrossVersion.Full => - cross.withPrefix(part + "_" + cross.prefix) - } - } - - /** Patches the IncOptions so that .sjsir files are pruned as needed. - * - * This complicated logic patches the ClassfileManager factory of the given - * IncOptions with one that is aware of .sjsir files emitted by the Scala.js - * compiler. This makes sure that, when a .class file must be deleted, the - * corresponding .sjsir file are also deleted. - */ - def scalaJSPatchIncOptions(incOptions: IncOptions): IncOptions = { - import xsbti.compile.{ClassFileManager, ClassFileManagerUtil} - - val sjsirFileManager = new ClassFileManager { - private[this] val inherited = - ClassFileManagerUtil.getDefaultClassFileManager(incOptions) - - def delete(classes: Array[File]): Unit = { - inherited.delete(classes.flatMap { classFile => - if (classFile.getPath.endsWith(".class")) { - val f = new File(classFile.getPath.stripSuffix(".class") + ".sjsir") - if (f.exists) List(f) - else Nil - } else { - Nil - } - }) - } - - def generated(classes: Array[File]): Unit = {} - def complete(success: Boolean): Unit = {} - } - - val newExternalHooks = - incOptions.externalHooks.withExternalClassFileManager(sjsirFileManager) - incOptions.withExternalHooks(newExternalHooks) - } -} diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSCrossVersion.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSCrossVersion.scala index 83a709c68a..6c3de4fa34 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSCrossVersion.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSCrossVersion.scala @@ -16,8 +16,6 @@ import sbt._ import org.scalajs.ir.ScalaJSVersions -import SBTCompat._ - object ScalaJSCrossVersion { private final val ReleaseVersion = raw"""(\d+)\.(\d+)\.(\d+)""".r @@ -32,8 +30,16 @@ object ScalaJSCrossVersion { case _ => full } - def scalaJSMapped(cross: CrossVersion): CrossVersion = - crossVersionAddScalaJSPart(cross, "sjs" + currentBinaryVersion) + def scalaJSMapped(cross: CrossVersion): CrossVersion = cross match { + case CrossVersion.Disabled => + CrossVersion.constant("sjs" + currentBinaryVersion) + case cross: sbt.librarymanagement.Constant => + cross.withValue("sjs" + currentBinaryVersion + "_" + cross.value) + case cross: CrossVersion.Binary => + cross.withPrefix("sjs" + currentBinaryVersion + "_" + cross.prefix) + case cross: CrossVersion.Full => + cross.withPrefix("sjs" + currentBinaryVersion + "_" + cross.prefix) + } val binary: CrossVersion = scalaJSMapped(CrossVersion.binary) diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala index b42a7a7a0d..ca1036a146 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala @@ -181,10 +181,7 @@ object ScalaJSPlugin extends AutoPlugin { Seq( scalaJSStage := Stage.FastOpt, - scalaJSLinkerConfig := { - StandardLinker.Config() - .withParallel(ScalaJSPluginInternal.DefaultParallelLinker) - }, + scalaJSLinkerConfig := StandardLinker.Config(), jsEnv := new NodeJSEnv(), diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala index f6e6210337..b9b97db5a3 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala @@ -39,9 +39,9 @@ import org.scalajs.ir.Printers.IRTreePrinter import org.scalajs.testing.adapter.{TestAdapter, HTMLRunnerBuilder, TestAdapterInitializer} import Loggers._ -import SBTCompat._ -import SBTCompat.formatImplicits._ -import SBTCompat.formatImplicits.seqFormat + +import sjsonnew.BasicJsonProtocol._ +import sjsonnew.BasicJsonProtocol.seqFormat /** Implementation details of `ScalaJSPlugin`. */ private[sbtplugin] object ScalaJSPluginInternal { @@ -85,26 +85,6 @@ private[sbtplugin] object ScalaJSPluginInternal { private[sbtplugin] def closeAllTestAdapters(): Unit = createdTestAdapters.getAndSet(Nil).foreach(_.close()) - /* #2798 -- On Java 9+, the parallel collections on 2.10 die with a - * `NumberFormatException` and prevent the linker from working. - * - * By default, we therefore pre-emptively disable the parallel optimizer in - * case the parallel collections cannot deal with the current version of - * Java. - * - * TODO This will automatically "fix itself" once we upgrade to sbt 1.x, - * which uses Scala 2.12. We should get rid of that workaround at that point - * for tidiness, though. - */ - val DefaultParallelLinker: Boolean = { - try { - scala.util.Properties.isJavaAtLeast("1.8") - true - } catch { - case _: NumberFormatException => false - } - } - private def enhanceIRVersionNotSupportedException[A](body: => A): A = { try { body @@ -131,9 +111,40 @@ private[sbtplugin] object ScalaJSPluginInternal { } } - /** Patches the IncOptions so that .sjsir files are pruned as needed. */ - def scalaJSPatchIncOptions(incOptions: IncOptions): IncOptions = - SBTCompat.scalaJSPatchIncOptions(incOptions) + /** Patches the IncOptions so that .sjsir files are pruned as needed. + * + * This complicated logic patches the ClassfileManager factory of the given + * IncOptions with one that is aware of .sjsir files emitted by the Scala.js + * compiler. This makes sure that, when a .class file must be deleted, the + * corresponding .sjsir file are also deleted. + */ + def scalaJSPatchIncOptions(incOptions: IncOptions): IncOptions = { + import xsbti.compile.{ClassFileManager, ClassFileManagerUtil} + + val sjsirFileManager = new ClassFileManager { + private[this] val inherited = + ClassFileManagerUtil.getDefaultClassFileManager(incOptions) + + def delete(classes: Array[File]): Unit = { + inherited.delete(classes.flatMap { classFile => + if (classFile.getPath.endsWith(".class")) { + val f = new File(classFile.getPath.stripSuffix(".class") + ".sjsir") + if (f.exists) List(f) + else Nil + } else { + Nil + } + }) + } + + def generated(classes: Array[File]): Unit = {} + def complete(success: Boolean): Unit = {} + } + + val newExternalHooks = + incOptions.externalHooks.withExternalClassFileManager(sjsirFileManager) + incOptions.withExternalHooks(newExternalHooks) + } /** Settings for the production key (e.g. fastOptJS) of a given stage */ private def scalaJSStageSettings(stage: Stage, diff --git a/scripts/publish.sh b/scripts/publish.sh index 73a5b41e40..510a909118 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -10,9 +10,7 @@ fi COMPILER_VERSIONS="2.11.0 2.11.1 2.11.2 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.11 2.11.12 2.12.1 2.12.2 2.12.3 2.12.4 2.12.5 2.12.6 2.12.7 2.12.8 2.13.0" BIN_VERSIONS="2.11.12 2.12.8 2.13.0" JVM_BIN_VERSIONS="2.10.7 2.11.12 2.12.8" -SBT_VERSION="2.10.7" -SBT1_VERSION="2.12.8" -SBT1_SBTVERSION="1.0.0" +SBT_VERSION="2.12.8" COMPILER="compiler jUnitPlugin" LIBS="library irJS loggingJS linkerJS testInterface testBridge jUnitRuntime" @@ -46,5 +44,4 @@ for v in $JVM_BIN_VERSIONS; do done # Publish sbt-plugin -$CMD "++$SBT_VERSION" "sbtPlugin/publishSigned" -$CMD "++$SBT1_VERSION" "^^$SBT1_SBTVERSION" "sbtPlugin/publishSigned" +$CMD "++$SBT_VERSION" sbtPlugin/publishSigned" From 2a99ed0fd0c23362e502acecdc38f43f9cf051be Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 13 Jul 2019 15:13:27 +0200 Subject: [PATCH 0029/1606] Drop Scala 2.10 support --- Jenkinsfile | 1 - .../org/scalajs/nscplugin/GenJSCode.scala | 1 - .../nscplugin/test/JSInteropTest.scala | 3 +- .../nscplugin/test/OptimizationTest.scala | 1 - .../frontend/optimizer/ParIncOptimizer.scala | 8 ++--- .../scalajs/linker/analyzer/Analysis.scala | 14 +-------- .../scalajs/linker/analyzer/Analyzer.scala | 1 - .../frontend/optimizer/GenIncOptimizer.scala | 17 +++++------ .../frontend/optimizer/IncOptimizer.scala | 8 ++--- project/Build.scala | 29 +++++-------------- scripts/publish.sh | 2 +- .../testing/adapter/RunnerAdapter.scala | 1 - .../scalajs/testing/common/FutureUtil.scala | 25 ---------------- .../org/scalajs/testing/common/RPCCore.scala | 5 ++-- .../scalajs/testing/common/RunMuxRPC.scala | 4 +-- .../testsuite/library/ArrayOpsTest.scala | 7 ----- .../scalajs/testsuite/compiler/LongTest.scala | 2 +- .../testsuite/compiler/RegressionTest.scala | 1 - .../testsuite/scalalib/ArrayBuilderTest.scala | 1 - 19 files changed, 29 insertions(+), 102 deletions(-) delete mode 100644 test-common/src/main/scala/org/scalajs/testing/common/FutureUtil.scala diff --git a/Jenkinsfile b/Jenkinsfile index 5c38d62fe9..351ac14540 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -455,7 +455,6 @@ mainScalaVersions.each { scalaVersion -> quickMatrix.add([task: "test-suite-ecma-script5-force-polyfills", scala: mainScalaVersion, java: mainJavaVersion, testSuite: "testSuite"]) allJavaVersions.each { javaVersion -> quickMatrix.add([task: "tools-sbtplugin", scala: "2.12.8", java: javaVersion]) - quickMatrix.add([task: "tools", scala: "2.10.7", java: javaVersion]) quickMatrix.add([task: "tools", scala: "2.11.12", java: javaVersion]) } quickMatrix.add([task: "partestc", scala: "2.11.0", java: mainJavaVersion]) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 45ad02344c..1bc3eed677 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -5907,7 +5907,6 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) private lazy val hasNewCollections = { val v = scala.util.Properties.versionNumberString - !v.startsWith("2.10.") && !v.startsWith("2.11.") && !v.startsWith("2.12.") } diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala index 3a4ccad599..441e7e97cd 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/JSInteropTest.scala @@ -37,8 +37,7 @@ class JSInteropTest extends DirectTest with TestHelpers { private def ifHasNewRefChecks(msg: String): String = { val version = scala.util.Properties.versionNumberString - if (version.startsWith("2.10.") || - version.startsWith("2.11.") || + if (version.startsWith("2.11.") || version.startsWith("2.12.")) { "" } else { diff --git a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala index 0775b420f4..5e8dc51163 100644 --- a/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala +++ b/compiler/src/test/scala/org/scalajs/nscplugin/test/OptimizationTest.scala @@ -297,7 +297,6 @@ object OptimizationTest { private val hasOldCollections = { val version = scala.util.Properties.versionNumberString - version.startsWith("2.10.") || version.startsWith("2.11.") || version.startsWith("2.12.") } diff --git a/linker/jvm/src/main/scala/org/scalajs/linker/frontend/optimizer/ParIncOptimizer.scala b/linker/jvm/src/main/scala/org/scalajs/linker/frontend/optimizer/ParIncOptimizer.scala index aadf792d39..9cc75f159a 100644 --- a/linker/jvm/src/main/scala/org/scalajs/linker/frontend/optimizer/ParIncOptimizer.scala +++ b/linker/jvm/src/main/scala/org/scalajs/linker/frontend/optimizer/ParIncOptimizer.scala @@ -153,9 +153,9 @@ final class ParIncOptimizer(config: CommonPhaseConfig) dynamicCallers.getOrPut(methodName, TrieSet.empty) += caller /** PROCESS PASS ONLY. */ - def registerStaticCaller(namespaceOrdinal: Int, methodName: String, + def registerStaticCaller(namespace: MemberNamespace, methodName: String, caller: MethodImpl): Unit = { - staticCallers(namespaceOrdinal) + staticCallers(namespace.ordinal) .getOrPut(methodName, TrieSet.empty) += caller } @@ -171,9 +171,9 @@ final class ParIncOptimizer(config: CommonPhaseConfig) dynamicCallers.remove(methodName).foreach(_.keysIterator.foreach(_.tag())) /** UPDATE PASS ONLY. */ - def tagStaticCallersOf(namespaceOrdinal: Int, + def tagStaticCallersOf(namespace: MemberNamespace, methodName: String): Unit = { - staticCallers(namespaceOrdinal) + staticCallers(namespace.ordinal) .remove(methodName) .foreach(_.keysIterator.foreach(_.tag())) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala index efe14ca48f..8e578d2c8b 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analysis.scala @@ -91,8 +91,7 @@ object Analysis { trait MethodInfo { def owner: ClassInfo def encodedName: String - // Using the ordinal works around a bug of Scala 2.10 - protected def namespaceOrdinal: Int + def namespace: MemberNamespace def isAbstract: Boolean def isExported: Boolean def isReflProxy: Boolean @@ -128,17 +127,6 @@ object Analysis { this.namespace.prefixString + owner.displayName + "." + displayName } - object MethodInfo { - implicit class MethodInfoOps private[MethodInfo] ( - val __private_self: MethodInfo) extends AnyVal { - - @inline private def self: MethodInfo = __private_self - - def namespace: MemberNamespace = - MemberNamespace.fromOrdinal(self.namespaceOrdinal) - } - } - sealed trait MethodSyntheticKind object MethodSyntheticKind { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index 5bbd0a053b..32aac37f25 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala @@ -975,7 +975,6 @@ private final class Analyzer(config: CommonPhaseConfig, val encodedName = data.encodedName val namespace = data.namespace - protected val namespaceOrdinal = namespace.ordinal val isAbstract = data.isAbstract val isExported = data.isExported val isReflProxy = isReflProxyName(encodedName) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/GenIncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/GenIncOptimizer.scala index 23348db7ab..8b5dd9bd35 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/GenIncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/GenIncOptimizer.scala @@ -173,7 +173,7 @@ abstract class GenIncOptimizer private[optimizer] (config: CommonPhaseConfig) { val (_, changed, _) = staticLikeNamespace.updateWith(linkedClass) for (method <- changed) { staticLikeNamespace.myInterface.tagStaticCallersOf( - staticLikeNamespace.namespace.ordinal, method) + staticLikeNamespace.namespace, method) } } true @@ -503,7 +503,7 @@ abstract class GenIncOptimizer private[optimizer] (config: CommonPhaseConfig) { // Tag callers with static calls for (methodName <- methodAttributeChanges) - myInterface.tagStaticCallersOf(namespace.ordinal, methodName) + myInterface.tagStaticCallersOf(namespace, methodName) // Module class specifics updateHasElidableModuleAccessor() @@ -511,7 +511,7 @@ abstract class GenIncOptimizer private[optimizer] (config: CommonPhaseConfig) { // Inlineable class if (updateIsInlineable(linkedClass)) { for (method <- methods.values; if isConstructorName(method.encodedName)) - myInterface.tagStaticCallersOf(namespace.ordinal, method.encodedName) + myInterface.tagStaticCallersOf(namespace, method.encodedName) } // Recurse in subclasses @@ -608,7 +608,7 @@ abstract class GenIncOptimizer private[optimizer] (config: CommonPhaseConfig) { * not just added. */ for (methodName <- allMethodNames) - myInterface.tagStaticCallersOf(namespace.ordinal, methodName) + myInterface.tagStaticCallersOf(namespace, methodName) } updateHasElidableModuleAccessor() @@ -755,8 +755,7 @@ abstract class GenIncOptimizer private[optimizer] (config: CommonPhaseConfig) { /** Register a static-caller of an instance method. * PROCESS PASS ONLY. */ - // Using the ordinal works around a bug of Scala 2.10 - def registerStaticCaller(namespaceOrdinal: Int, methodName: String, + def registerStaticCaller(namespace: MemberNamespace, methodName: String, caller: MethodImpl): Unit /** Tag the dynamic-callers of an instance method. @@ -767,9 +766,7 @@ abstract class GenIncOptimizer private[optimizer] (config: CommonPhaseConfig) { /** Tag the static-callers of an instance method. * UPDATE PASS ONLY. */ - // Using the ordinal works around a bug of Scala 2.10 - def tagStaticCallersOf(namespaceOrdinal: Int, - methodName: String): Unit + def tagStaticCallersOf(namespace: MemberNamespace, methodName: String): Unit } /** A method implementation. @@ -826,7 +823,7 @@ abstract class GenIncOptimizer private[optimizer] (config: CommonPhaseConfig) { */ private def registerStaticCall(intf: InterfaceType, namespace: MemberNamespace, methodName: String): Unit = { - intf.registerStaticCaller(namespace.ordinal, methodName, this) + intf.registerStaticCaller(namespace, methodName, this) registeredTo(intf) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala index 6b7dc1b8e5..600f64c581 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/IncOptimizer.scala @@ -122,9 +122,9 @@ final class IncOptimizer(config: CommonPhaseConfig) def registerDynamicCaller(methodName: String, caller: MethodImpl): Unit = dynamicCallers.getOrElseUpdate(methodName, mutable.Set.empty) += caller - def registerStaticCaller(namespaceOrdinal: Int, methodName: String, + def registerStaticCaller(namespace: MemberNamespace, methodName: String, caller: MethodImpl): Unit = { - staticCallers(namespaceOrdinal) + staticCallers(namespace.ordinal) .getOrElseUpdate(methodName, mutable.Set.empty) += caller } @@ -137,9 +137,9 @@ final class IncOptimizer(config: CommonPhaseConfig) def tagDynamicCallersOf(methodName: String): Unit = dynamicCallers.remove(methodName).foreach(_.foreach(_.tag())) - def tagStaticCallersOf(namespaceOrdinal: Int, + def tagStaticCallersOf(namespace: MemberNamespace, methodName: String): Unit = { - staticCallers(namespaceOrdinal) + staticCallers(namespace.ordinal) .remove(methodName) .foreach(_.foreach(_.tag())) } diff --git a/project/Build.scala b/project/Build.scala index cd2e1d9c06..12c39b12e6 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -137,13 +137,12 @@ object Build { CrossVersion.binaryMapped(v => s"sjs${previousSJSBinaryVersion}_$v") val scalaVersionsUsedForPublishing: Set[String] = - Set("2.10.7", "2.11.12", "2.12.8") + Set("2.11.12", "2.12.8") val newScalaBinaryVersionsInThisRelease: Set[String] = Set() */ def hasNewCollections(version: String): Boolean = { - !version.startsWith("2.10.") && !version.startsWith("2.11.") && !version.startsWith("2.12.") } @@ -207,7 +206,6 @@ object Build { version := scalaJSVersion, crossScalaVersions := Seq( - "2.10.7", "2.11.0", "2.11.1", "2.11.2", "2.11.4", "2.11.5", "2.11.6", "2.11.7", "2.11.8", "2.11.11", "2.11.12", "2.12.1", "2.12.2", "2.12.3", "2.12.4", "2.12.5", "2.12.6", "2.12.7", @@ -415,22 +413,11 @@ object Build { // The pattern matcher used to exceed its analysis budget before 2.11.5 scalacOptions ++= { scalaVersion.value.split('.') match { - case Array("2", "10", _) => Nil case Array("2", "11", x) if x.takeWhile(_.isDigit).toInt <= 4 => Nil case _ => Seq("-Xfatal-warnings") } }, - - scalacOptions in (Compile, doc) := { - val baseOptions = (scalacOptions in (Compile, doc)).value - - // in Scala 2.10, some ScalaDoc links fail - val fatalInDoc = scalaBinaryVersion.value != "2.10" - - if (fatalInDoc) baseOptions - else baseOptions.filterNot(_ == "-Xfatal-warnings") - } ) private def publishToBintraySettings = Def.settings( @@ -1053,7 +1040,6 @@ object Build { */ val mustAvoidJavaDoc = { javaV >= 9 && { - scalaV.startsWith("2.10.") || scalaV.startsWith("2.11.") || scalaV == "2.12.0" || scalaV == "2.12.1" @@ -1297,14 +1283,13 @@ object Build { // To support calls to static methods in interfaces scalacOptions in Test ++= { - /* Starting from 2.10.7 and 2.11.12, scalac refuses to emit calls to - * static methods in interfaces unless the -target:jvm-1.8 flag is given. - * scalac 2.12+ emits JVM 8 bytecode by default, of course, so it is not - * needed for later versions. - */ + /* Starting from 2.11.12, scalac refuses to emit calls to static methods + * in interfaces unless the -target:jvm-1.8 flag is given. + * scalac 2.12+ emits JVM 8 bytecode by default, of course, so it is not + * needed for later versions. + */ val PartialVersion = """(\d+)\.(\d+)\.(\d+)(?:-.+)?""".r val needsTargetFlag = scalaVersion.value match { - case PartialVersion("2", "10", n) => n.toInt >= 7 case PartialVersion("2", "11", n) => n.toInt >= 12 case _ => false } @@ -1827,7 +1812,7 @@ object Build { val scalaV = scalaVersion.value val upstreamSrcDir = (fetchScalaSource in partest).value - if (scalaV.startsWith("2.10.") || scalaV.startsWith("2.11.") || + if (scalaV.startsWith("2.11.") || scalaV.startsWith("2.12.")) { Nil } else { diff --git a/scripts/publish.sh b/scripts/publish.sh index 510a909118..e2c750d83b 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -9,7 +9,7 @@ fi COMPILER_VERSIONS="2.11.0 2.11.1 2.11.2 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.11 2.11.12 2.12.1 2.12.2 2.12.3 2.12.4 2.12.5 2.12.6 2.12.7 2.12.8 2.13.0" BIN_VERSIONS="2.11.12 2.12.8 2.13.0" -JVM_BIN_VERSIONS="2.10.7 2.11.12 2.12.8" +JVM_BIN_VERSIONS="2.11.12 2.12.8" SBT_VERSION="2.12.8" COMPILER="compiler jUnitPlugin" diff --git a/test-adapter/src/main/scala/org/scalajs/testing/adapter/RunnerAdapter.scala b/test-adapter/src/main/scala/org/scalajs/testing/adapter/RunnerAdapter.scala index 05eb85ecbb..a98c4fbf17 100644 --- a/test-adapter/src/main/scala/org/scalajs/testing/adapter/RunnerAdapter.scala +++ b/test-adapter/src/main/scala/org/scalajs/testing/adapter/RunnerAdapter.scala @@ -23,7 +23,6 @@ import org.scalajs.testing.common._ import sbt.testing._ -import FutureUtil._ import TestAdapter.ManagedRunner private final class RunnerAdapter private (runnerArgs: RunnerArgs, diff --git a/test-common/src/main/scala/org/scalajs/testing/common/FutureUtil.scala b/test-common/src/main/scala/org/scalajs/testing/common/FutureUtil.scala deleted file mode 100644 index 0564c03e08..0000000000 --- a/test-common/src/main/scala/org/scalajs/testing/common/FutureUtil.scala +++ /dev/null @@ -1,25 +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. - */ - -package org.scalajs.testing.common - -import scala.util._ -import scala.concurrent._ - -private[testing] object FutureUtil { - /** Same as Future.fromTry(x) but works in 2.10 */ - def futureFromTry[T](x: Try[T]): Future[T] = { - val promise = Promise[T] - promise.complete(x) - promise.future - } -} diff --git a/test-common/src/main/scala/org/scalajs/testing/common/RPCCore.scala b/test-common/src/main/scala/org/scalajs/testing/common/RPCCore.scala index 0cedf34060..2468e15253 100644 --- a/test-common/src/main/scala/org/scalajs/testing/common/RPCCore.scala +++ b/test-common/src/main/scala/org/scalajs/testing/common/RPCCore.scala @@ -22,7 +22,6 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicLong import Serializer.{serialize, deserialize} -import FutureUtil._ /** Core RPC dispatcher. * @@ -121,7 +120,7 @@ private[testing] abstract class RPCCore()(implicit ec: ExecutionContext) { val ep: bep.endpoint.type = bep.endpoint import ep._ - futureFromTry(Try(deserialize[Req](in))) + Future.fromTry(Try(deserialize[Req](in))) .flatMap(bep.exec) .onComplete(repl => send(makeReply(callID, repl))) } @@ -180,7 +179,7 @@ private[testing] abstract class RPCCore()(implicit ec: ExecutionContext) { /** Attaches the given method to the given (local) endpoint. */ final def attach(ep: RPCEndpoint)(ex: ep.Req => ep.Resp): Unit = { - attachAsync(ep)(x => futureFromTry(Try(ex(x)))) + attachAsync(ep)(x => Future.fromTry(Try(ex(x)))) } /** Attaches the given method to the given (local) endpoint. */ diff --git a/test-common/src/main/scala/org/scalajs/testing/common/RunMuxRPC.scala b/test-common/src/main/scala/org/scalajs/testing/common/RunMuxRPC.scala index bdd1c98869..f0189e6f55 100644 --- a/test-common/src/main/scala/org/scalajs/testing/common/RunMuxRPC.scala +++ b/test-common/src/main/scala/org/scalajs/testing/common/RunMuxRPC.scala @@ -20,8 +20,6 @@ import scala.util.Try import java.util.concurrent.ConcurrentHashMap -import FutureUtil.futureFromTry - /** Helper above an [[RPCCore]] that allows to multiplex between runs. * * Instead of registering/calling a single endpoint, it supports @@ -51,7 +49,7 @@ private[testing] final class RunMuxRPC(rpc: RPCCore) { attachMux(ep.opCode, runId, ex)(rpc.attach(ep)) def attach[Req](ep: MuxRPCEndpoint[Req], runId: RunID)(ex: Req => ep.Resp): Unit = - attachAsync(ep, runId)(x => futureFromTry(Try(ex(x)))) + attachAsync(ep, runId)(x => Future.fromTry(Try(ex(x)))) def attachAsync[Req](ep: MuxRPCEndpoint[Req], runId: RunID)( ex: Req => Future[ep.Resp]): Unit = { diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/ArrayOpsTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/ArrayOpsTest.scala index 7f24932815..fdd8307bbb 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/library/ArrayOpsTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/library/ArrayOpsTest.scala @@ -114,7 +114,6 @@ class ArrayOpsTest { @Test def sizeCompare(): Unit = { assumeFalse("sizeCompare was added in 2.13", - scalaVersion.startsWith("2.10.") || scalaVersion.startsWith("2.11.") || scalaVersion.startsWith("2.12.")) @@ -136,7 +135,6 @@ class ArrayOpsTest { @Test def sizeIs(): Unit = { assumeFalse("sizeIs was added in 2.13", - scalaVersion.startsWith("2.10.") || scalaVersion.startsWith("2.11.") || scalaVersion.startsWith("2.12.")) @@ -151,7 +149,6 @@ class ArrayOpsTest { @Test def lengthIs(): Unit = { assumeFalse("lengthIs was added in 2.13", - scalaVersion.startsWith("2.10.") || scalaVersion.startsWith("2.11.") || scalaVersion.startsWith("2.12.")) @@ -305,7 +302,6 @@ class ArrayOpsTest { @Test def partitionMap(): Unit = { assumeFalse("partitionMap was added in 2.13", - scalaVersion.startsWith("2.10.") || scalaVersion.startsWith("2.11.") || scalaVersion.startsWith("2.12.")) @@ -669,7 +665,6 @@ class ArrayOpsTest { val array = js.Array(1, 5, 7, 2, 54, 2, 78, 0, 3) val supportsNegativeStart = { - !scalaVersion.startsWith("2.10.") && !scalaVersion.startsWith("2.11.") && !scalaVersion.startsWith("2.12.") } @@ -920,7 +915,6 @@ class ArrayOpsTest { array.trimStart(4) assumeFalse("the safe behavior was introduced in 2.13", - scalaVersion.startsWith("2.10.") || scalaVersion.startsWith("2.11.") || scalaVersion.startsWith("2.12.")) assertJSArrayEquals(js.Array(42, 53, 5, 54, 23, 44, 78), array) @@ -935,7 +929,6 @@ class ArrayOpsTest { array.trimEnd(4) assumeFalse("the safe behavior was introduced in 2.13", - scalaVersion.startsWith("2.10.") || scalaVersion.startsWith("2.11.") || scalaVersion.startsWith("2.12.")) assertJSArrayEquals(js.Array(33, 11, 2, 3, 42, 53, 5), array) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala index eaa77e58fd..f6f81cbca4 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/LongTest.scala @@ -179,7 +179,7 @@ class LongTest { } @Test def `should_have_correct_hash_in_case_classes`(): Unit = { - if (scalaVersion.startsWith("2.10.") || scalaVersion.startsWith("2.11.") || + if (scalaVersion.startsWith("2.11.") || scalaVersion.startsWith("2.12.")) { assertEquals(-1669410282, HashTestBox(0L).##) assertEquals(-1561146018, HashTestBox(55L).##) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala index 268edd7a8f..d3f4ebccb6 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala @@ -171,7 +171,6 @@ class RegressionTest { assumeFalse("Affected by https://github.com/scala/bug/issues/10551", Platform.executingInJVM && { - scalaVersion.startsWith("2.10.") || scalaVersion.startsWith("2.11.") || scalaVersion == "2.12.0" || scalaVersion == "2.12.1" || scalaVersion == "2.12.2" || scalaVersion == "2.12.3" || diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala index ea570dc0a1..57d95ded1e 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/scalalib/ArrayBuilderTest.scala @@ -283,7 +283,6 @@ class ArrayBuilderTest { @Test def addAll(): Unit = { assumeFalse("Needs at least Scala 2.13", - scalaVersion.startsWith("2.10.") || scalaVersion.startsWith("2.11.") || scalaVersion.startsWith("2.12.")) From 1ee28891a584181f8140d0ac11a76eba5678258c Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Sat, 27 Jul 2019 18:28:50 +0200 Subject: [PATCH 0030/1606] Re-Introduce workaround to fetch scala source This is currently failing in the CI (with Scala 2.12.7). 6360501b95cbaaa4e7e310311a72b2b752f6083a erroneously removed this workaround: My local tests were incorrect. I have manually checked that `sbt ++2.12.7 scalalib/fetchScalaSource` works again after this commit. --- project/Build.scala | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 12c39b12e6..5de518fbe1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -887,13 +887,26 @@ object Build { artifactPath in fetchScalaSource := target.value / "scalaSources" / scalaVersion.value, + /* Work around for #2649. We would like to always use `update`, but + * that fails if the scalaVersion we're looking for happens to be the + * version of Scala used by sbt itself. This is clearly a bug in sbt, + * which we work around here by using `updateClassifiers` instead in + * that case. + */ + update in fetchScalaSource := Def.taskDyn { + if (scalaVersion.value == scala.util.Properties.versionNumberString) + updateClassifiers + else + update + }.value, + fetchScalaSource := { val s = streams.value val cacheDir = s.cacheDirectory val ver = scalaVersion.value val trgDir = (artifactPath in fetchScalaSource).value - val report = update.value + val report = (update in fetchScalaSource).value val scalaLibSourcesJar = report.select( configuration = configurationFilter("compile"), module = moduleFilter(name = "scala-library"), From 417c3ed30a87f3a96c3147ad81c2cd042cecb4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 19 Jun 2019 18:14:39 +0200 Subject: [PATCH 0031/1606] Implement j.u.Hashtable on top of j.u.HashMap. Instead of Scala's `HashMap`, which simplifies it a lot. `Hashtable` is a legacy API that was effectively replaced by `HashMap` even in Java. --- .../src/main/scala/java/util/Hashtable.scala | 167 ++++-------------- 1 file changed, 38 insertions(+), 129 deletions(-) diff --git a/javalib/src/main/scala/java/util/Hashtable.scala b/javalib/src/main/scala/java/util/Hashtable.scala index ec863c7226..9b353b1866 100644 --- a/javalib/src/main/scala/java/util/Hashtable.scala +++ b/javalib/src/main/scala/java/util/Hashtable.scala @@ -14,69 +14,62 @@ package java.util import java.{util => ju} -import scala.collection.mutable - -import ScalaOps._ - -class Hashtable[K, V] private (inner: mutable.HashMap[Box[Any], V]) +/* This implementation allows `null` keys and values, although the JavaDoc + * specifies that operations should throw `NullPointerException`s if `null` + * keys or values are used. This makes the implementation easier, notably by + * allowing to reuse the implementation of `j.u.HashMap`, and is acceptable + * given that NPEs are undefined behavior in Scala.js. + */ +class Hashtable[K, V] private (inner: ju.HashMap[K, V]) extends ju.Dictionary[K,V] with ju.Map[K, V] with Cloneable with Serializable { def this() = - this(mutable.HashMap.empty[Box[Any], V]) + this(new ju.HashMap[K, V]()) - def this(initialCapacity: Int) = this() + def this(initialCapacity: Int) = + this(new ju.HashMap[K, V](initialCapacity)) - def this(initialCapacity: Int, loadFactor: Float) = this() + def this(initialCapacity: Int, loadFactor: Float) = + this(new ju.HashMap[K, V](initialCapacity, loadFactor)) - def this(t: ju.Map[_ <: K, _ <: V]) = { - this() - putAll(t) - } + def this(t: ju.Map[_ <: K, _ <: V]) = + this(new ju.HashMap[K, V](t)) def size(): Int = - inner.size + inner.size() - def isEmpty: Boolean = - inner.isEmpty + def isEmpty(): Boolean = + inner.isEmpty() def keys(): ju.Enumeration[K] = - inner.keysIterator.map(_.inner.asInstanceOf[K]).asJavaEnumeration + Collections.enumeration(keySet()) def elements(): ju.Enumeration[V] = - inner.valuesIterator.asJavaEnumeration + Collections.enumeration(values()) def contains(value: Any): Boolean = containsValue(value) def containsValue(value: Any): Boolean = - inner.valuesIterator.contains(value) + inner.containsValue(value) def containsKey(key: Any): Boolean = - inner.contains(Box(key)) + inner.containsKey(key) - def get(key: Any): V = { - if (key == null) - throw new NullPointerException - inner.getOrElse(Box(key), null.asInstanceOf[V]) - } + def get(key: Any): V = + inner.get(key) // Not implemented // protected def rehash(): Unit def put(key: K, value: V): V = - inner.put(Box(key.asInstanceOf[AnyRef]), value).getOrElse(null.asInstanceOf[V]) + inner.put(key, value) - def remove(key: Any): V = { - if (key == null) - throw new NullPointerException - inner.remove(Box(key)).getOrElse(null.asInstanceOf[V]) - } + def remove(key: Any): V = + inner.remove(key) - def putAll(m: ju.Map[_ <: K, _ <: V]): Unit = { - m.entrySet.scalaOps.foreach { - kv => inner.put(Box(kv.getKey.asInstanceOf[AnyRef]), kv.getValue) - } - } + def putAll(m: ju.Map[_ <: K, _ <: V]): Unit = + inner.putAll(m) def clear(): Unit = inner.clear() @@ -85,98 +78,14 @@ class Hashtable[K, V] private (inner: mutable.HashMap[Box[Any], V]) new ju.Hashtable[K, V](this) override def toString(): String = - inner.iterator.map(kv => "" + kv._1.inner + "=" + kv._2).mkString("{", ", ", "}") - - def keySet(): ju.Set[K] = { - new AbstractSet[K] { - def iterator(): Iterator[K] = - new EntrySetIterator().scalaOps.map(_.getKey()) - - def size(): Int = inner.size - } - } - - def entrySet(): ju.Set[ju.Map.Entry[K, V]] = { - new AbstractSet[Map.Entry[K, V]] { - def iterator(): Iterator[Map.Entry[K, V]] = - new EntrySetIterator - - def size(): Int = inner.size - } - } - - /* Inspired by the implementation of - * scala.collection.convert.JavaCollectionWrappers.MapWrapper.entrySet - * as found in version 2.13.0, with two changes: - * - * - accommodate the fact that our keys are boxed, and - * - explicitly snapshot the underlying contents right before any mutation of - * the underlying Map, as we do not have any guarantee that mutations - * preserve the state of existing iterators. - */ - private class EntrySetIterator extends Iterator[Map.Entry[K, V]] { - private var underlying: scala.collection.Iterator[(Box[Any], V)] = - Hashtable.this.inner.iterator - - private var isSnapshot: Boolean = false - - private var prev: Box[Any] = null - - private def ensureSnapshot(): Unit = { - if (!isSnapshot) { - underlying = underlying.toList.iterator - isSnapshot = true - } - } - - def hasNext(): Boolean = underlying.hasNext - - def next(): Map.Entry[K, V] = { - val (boxedKey, initialValue) = underlying.next() - prev = boxedKey - - new Map.Entry[K, V] { - private var value = initialValue - - def getKey(): K = boxedKey.inner.asInstanceOf[K] - - def getValue(): V = value - - def setValue(v: V): V = { - ensureSnapshot() - val oldValue = value - inner.put(boxedKey, v) - value = v - oldValue - } - - override def equals(that: Any): Boolean = that match { - case that: Map.Entry[_, _] => - getKey() === that.getKey() && getValue() === that.getValue() - case _ => - false - } - - override def hashCode(): Int = - boxedKey.hashCode ^ (if (value == null) 0 else value.hashCode()) - } - } - - def remove(): Unit = { - if (prev == null) - throw new IllegalStateException("next must be called at least once before remove") - ensureSnapshot() - inner -= prev - prev = null - } - } - - def values(): ju.Collection[V] = { - new AbstractCollection[V] { - def iterator(): Iterator[V] = - new EntrySetIterator().scalaOps.map(_.getValue()) - - def size(): Int = inner.size - } - } + inner.toString() + + def keySet(): ju.Set[K] = + inner.keySet() + + def entrySet(): ju.Set[ju.Map.Entry[K, V]] = + inner.entrySet() + + def values(): ju.Collection[V] = + inner.values() } From 2f519823eec1f8d23f6852ebb6e4e19be8573569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 24 Jun 2019 13:14:39 +0200 Subject: [PATCH 0032/1606] Implement java.util.{Linked,}HashMap from scratch. Previously, they were implemented on top of `s.c.m.HashMap` and `s.c.m.LinkedHashMap`, with various hacks to implement Java features and behaviors: * Non-cooperative equality * `Iterator.remove()` * Access-order for `LinkedHashMap` * `removeEldestEntry()` for `LinkedHashMap`. * Mutable `Map.Entry` All those hacks resulted in significantly more memory allocation (at least two objects for each `get`, and one `Map.Entry` for each element during iterations). The new implementation, done from scratch, has direct internal support for all these operations: * It never allocates during lookup and removal * Iteration never allocates besides the instance of `Iterator` * It allocates at most one object during `put` * It guarantees strict O(1) `Iterator.remove()` * It directly calls `Object.equals()` rather than using `==` The implementation was initially inspired by the implementation of `s.c.m.HashMap` in Scala 2.13.0 (in particular the core data structure and algorithms), but was heavily changed to accommodate the requirements of the Java API. The test suite is slightly enhanced with whitebox tests that exercise the new algorithms, in particular table growth. We also replace `j.l.Integer`s used as keys by `String`s in several tests, because `Integer`s have hash codes that are *too* predictable, and do not exercise the insertion order of `LinkedHashMap`s. --- .../src/main/scala/java/util/HashMap.scala | 538 +++++++++++++++--- .../main/scala/java/util/LinkedHashMap.scala | 156 +++-- .../javalib/util/LinkedHashMapTest.scala | 54 +- .../testsuite/javalib/util/MapTest.scala | 24 + 4 files changed, 616 insertions(+), 156 deletions(-) diff --git a/javalib/src/main/scala/java/util/HashMap.scala b/javalib/src/main/scala/java/util/HashMap.scala index 3c5be1b7ac..34ee6e485d 100644 --- a/javalib/src/main/scala/java/util/HashMap.scala +++ b/javalib/src/main/scala/java/util/HashMap.scala @@ -12,153 +12,509 @@ package java.util -import scala.collection.mutable +import scala.annotation.tailrec -class HashMap[K, V] protected (inner: mutable.Map[Box[K], V]) +import java.{util => ju} + +import ScalaOps._ + +class HashMap[K, V](initialCapacity: Int, loadFactor: Double) extends AbstractMap[K, V] with Serializable with Cloneable { self => - def this() = - this(mutable.HashMap.empty[Box[K], V]) + import HashMap._ - def this(initialCapacity: Int, loadFactor: Float) = { - this() - if (initialCapacity < 0) - throw new IllegalArgumentException("initialCapacity < 0") - else if (loadFactor < 0.0) - throw new IllegalArgumentException("loadFactor <= 0.0") - } + if (initialCapacity < 0) + throw new IllegalArgumentException("initialCapacity < 0") + if (loadFactor <= 0.0) + throw new IllegalArgumentException("loadFactor <= 0.0") + + def this() = + this(HashMap.DEFAULT_INITIAL_CAPACITY, HashMap.DEFAULT_LOAD_FACTOR) def this(initialCapacity: Int) = this(initialCapacity, HashMap.DEFAULT_LOAD_FACTOR) def this(m: Map[_ <: K, _ <: V]) = { - this() + this(m.size()) putAll(m) } - override def clear(): Unit = - inner.clear() + /** The actual hash table. + * + * In each bucket, nodes are sorted by increasing value of `hash`. + * + * Deviation from the JavaDoc: we do not use `initialCapacity` as is for the + * number of buckets. Instead we round it up to the next power of 2. This + * allows some algorithms to be more efficient, notably `index()` and + * `growTable()`. Since the number of buckets is not observable from the + * outside, this deviation does not change any semantics. + */ + private[this] var table = new Array[Node[K, V]](tableSizeFor(initialCapacity)) + + /** The next size value at which to resize (capacity * load factor). */ + private[this] var threshold: Int = newThreshold(table.length) + + private[this] var contentSize: Int = 0 + + /* Internal API for LinkedHashMap: these methods are overridden in + * LinkedHashMap to implement its insertion- or access-order. + */ + + private[util] def newNode(key: K, hash: Int, value: V, + previous: Node[K, V], next: Node[K, V]): Node[K, V] = { + new Node(key, hash, value, previous, next) + } + + private[util] def nodeWasAccessed(node: Node[K, V]): Unit = () + + private[util] def nodeWasAdded(node: Node[K, V]): Unit = () + + private[util] def nodeWasRemoved(node: Node[K, V]): Unit = () + + // Public API - override def clone(): AnyRef = { - new HashMap(inner.clone()) + override def size(): Int = + contentSize + + override def isEmpty(): Boolean = + contentSize == 0 + + override def get(key: Any): V = { + val node = findNode(key) + if (node eq null) { + null.asInstanceOf[V] + } else { + nodeWasAccessed(node) + node.value + } } override def containsKey(key: Any): Boolean = - inner.contains(Box(key.asInstanceOf[K])) + findNode(key) ne null + + override def put(key: K, value: V): V = + put0(key, value) + + override def putAll(m: Map[_ <: K, _ <: V]): Unit = { + m match { + case m: ju.HashMap[_, _] => + val iter = m.nodeIterator() + while (iter.hasNext()) { + val next = iter.next() + put0(next.key, next.value, next.hash) + } + case _ => + super.putAll(m) + } + } + + override def remove(key: Any): V = { + val node = remove0(key) + if (node eq null) null.asInstanceOf[V] + else node.value + } + + override def clear(): Unit = { + ju.Arrays.fill(table.asInstanceOf[Array[AnyRef]], null) + contentSize = 0 + } override def containsValue(value: Any): Boolean = - inner.valuesIterator.contains(value.asInstanceOf[V]) + valueIterator().scalaOps.exists(Objects.equals(value, _)) + + override def keySet(): ju.Set[K] = + new KeySet + + override def values(): ju.Collection[V] = + new Values - override def entrySet(): Set[Map.Entry[K, V]] = + def entrySet(): ju.Set[ju.Map.Entry[K, V]] = new EntrySet - override def get(key: Any): V = - inner.get(Box(key.asInstanceOf[K])).getOrElse(null.asInstanceOf[V]) + override def clone(): AnyRef = + new HashMap[K, V](this) - override def isEmpty(): Boolean = - inner.isEmpty + // Elementary operations - override def keySet(): Set[K] = - new KeySet + @inline private def index(hash: Int): Int = + hash & (table.length - 1) - override def put(key: K, value: V): V = - inner.put(Box(key), value).getOrElse(null.asInstanceOf[V]) + @inline + private def findNode(key: Any): Node[K, V] = { + val hash = computeHash(key) + findNode0(key, hash, index(hash)) + } - override def remove(key: Any): V = { - val boxedKey = Box(key.asInstanceOf[K]) - inner.get(boxedKey).fold(null.asInstanceOf[V]) { value => - inner -= boxedKey - value + @inline + private def findNodeAndIndexForRemoval(key: Any): (Node[K, V], Int) = { + val hash = computeHash(key) + val idx = index(hash) + val node = findNode0(key, hash, idx) + (node, idx) + } + + private def findNode0(key: Any, hash: Int, idx: Int): Node[K, V] = { + @inline + @tailrec + def loop(node: Node[K, V]): Node[K, V] = { + if (node eq null) null + else if (hash == node.hash && Objects.equals(key, node.key)) node + else if (hash < node.hash) null + else loop(node.next) } + loop(table(idx)) } - override def size(): Int = - inner.size - - override def values(): Collection[V] = - new ValuesView - - private class EntrySet extends AbstractSet[Map.Entry[K, V]] - with AbstractMapView[Map.Entry[K, V]] { - override def iterator(): Iterator[Map.Entry[K, V]] = { - new AbstractMapViewIterator[Map.Entry[K, V]] { - override protected def getNextForm(key: Box[K]): Map.Entry[K, V] = { - new AbstractMap.SimpleEntry(key.inner, inner(key)) { - override def setValue(value: V): V = { - inner.update(key, value) - super.setValue(value) - } + // Heavy lifting: modifications + + /** Adds a key-value pair to this map + * + * @param key the key to add + * @param value the value to add + * @return the old value associated with `key`, or `null` if there was none + */ + @inline + private[this] def put0(key: K, value: V): V = + put0(key, value, computeHash(key)) + + /** Adds a key-value pair to this map + * + * @param key the key to add + * @param value the value to add + * @param hash the **improved** hashcode of `key` (see computeHash) + * @return the old value associated with `key`, or `null` if there was none + */ + private[this] def put0(key: K, value: V, hash: Int): V = { + if (contentSize + 1 >= threshold) + growTable() + val idx = index(hash) + put0(key, value, hash, idx) + } + + /** Adds a key-value pair to this map + * + * @param key the key to add + * @param value the value to add + * @param hash the **improved** hashcode of `key` (see computeHash) + * @param idx the index in the `table` corresponding to the `hash` + * @return the old value associated with `key`, or `null` if there was none + */ + private[this] def put0(key: K, value: V, hash: Int, idx: Int): V = { + // scalastyle:off return + val newNode = table(idx) match { + case null => + val newNode = this.newNode(key, hash, value, null, null) + table(idx) = newNode + newNode + case first => + var prev: Node[K, V] = null + var n = first + while ((n ne null) && n.hash <= hash) { + if (n.hash == hash && Objects.equals(key, n.key)) { + nodeWasAccessed(n) + val old = n.value + n.value = value + return old } + prev = n + n = n.next } - } + val newNode = this.newNode(key, hash, value, prev, n) + if (prev eq null) + table(idx) = newNode + else + prev.next = newNode + if (n ne null) + n.previous = newNode + newNode } + contentSize += 1 + nodeWasAdded(newNode) + null.asInstanceOf[V] + // scalastyle:on return } - private class KeySet extends AbstractSet[K] with AbstractMapView[K] { - override def remove(o: Any): Boolean = { - val boxedKey = Box(o.asInstanceOf[K]) - val contains = inner.contains(boxedKey) - if (contains) - inner -= boxedKey - contains - } + /** Removes a key from this map if it exists. + * + * @param key the key to remove + * @return the node that contained `key` if it was present, otherwise null + */ + private def remove0(key: Any): Node[K, V] = { + val (node, idx) = findNodeAndIndexForRemoval(key) + if (node ne null) + remove0(node, idx) + node + } - override def iterator(): Iterator[K] = { - new AbstractMapViewIterator[K] { - protected def getNextForm(key: Box[K]): K = - key.inner + private[util] final def removeNode(node: Node[K, V]): Unit = + remove0(node, index(node.hash)) + + private def remove0(node: Node[K, V], idx: Int): Unit = { + val previous = node.previous + val next = node.next + if (previous eq null) + table(idx) = next + else + previous.next = next + if (next ne null) + next.previous = previous + contentSize -= 1 + nodeWasRemoved(node) + } + + /** Grow the size of the table (always times 2). */ + private[this] def growTable(): Unit = { + val oldTable = table + val oldlen = oldTable.length + val newlen = oldlen * 2 + val newTable = new Array[Node[K, V]](newlen) + table = newTable + threshold = newThreshold(newlen) + + /* Split the nodes of each bucket from the old table into the "low" and + * "high" indices of the new table. Since the new table contains exactly + * twice as many buckets as the old table, every index `i` from the old + * table is split into indices `i` and `oldlen + i` in the new table. + */ + var i = 0 + while (i < oldlen) { + var lastLow: Node[K, V] = null + var lastHigh: Node[K, V] = null + var node = oldTable(i) + while (node ne null) { + if ((node.hash & oldlen) == 0) { + // go to low + node.previous = lastLow + if (lastLow eq null) + newTable(i) = node + else + lastLow.next = node + lastLow = node + } else { + // go to high + node.previous = lastHigh + if (lastHigh eq null) + newTable(oldlen + i) = node + else + lastHigh.next = node + lastHigh = node + } + node = node.next } + if (lastLow ne null) + lastLow.next = null + if (lastHigh ne null) + lastHigh.next = null + i += 1 } } - private class ValuesView extends AbstractMapView[V] { - override def size(): Int = - inner.size + /** Rounds up `capacity` to a power of 2, with a maximum of 2^30. */ + @inline private[this] def tableSizeFor(capacity: Int): Int = + Math.min(Integer.highestOneBit(Math.max(capacity - 1, 4)) * 2, 1 << 30) - override def iterator(): Iterator[V] = { - new AbstractMapViewIterator[V] { - protected def getNextForm(key: Box[K]): V = inner(key) + @inline private[this] def newThreshold(size: Int): Int = + (size.toDouble * loadFactor).toInt + + // Iterators + + private[util] def nodeIterator(): ju.Iterator[Node[K, V]] = + new NodeIterator + + private[util] def keyIterator(): ju.Iterator[K] = + new KeyIterator + + private[util] def valueIterator(): ju.Iterator[V] = + new ValueIterator + + // The cast works around the lack of definition-site variance + private[util] final def entrySetIterator(): ju.Iterator[Map.Entry[K, V]] = + nodeIterator().asInstanceOf[ju.Iterator[Map.Entry[K, V]]] + + private final class NodeIterator extends AbstractHashMapIterator[Node[K, V]] { + protected[this] def extract(node: Node[K, V]): Node[K, V] = node + } + + private final class KeyIterator extends AbstractHashMapIterator[K] { + protected[this] def extract(node: Node[K, V]): K = node.key + } + + private final class ValueIterator extends AbstractHashMapIterator[V] { + protected[this] def extract(node: Node[K, V]): V = node.value + } + + private abstract class AbstractHashMapIterator[A] extends ju.Iterator[A] { + private[this] val len = table.length + private[this] var nextIdx: Int = _ // 0 + private[this] var nextNode: Node[K, V] = _ // null + private[this] var lastNode: Node[K, V] = _ // null + + protected[this] def extract(node: Node[K, V]): A + + /* Movements of `nextNode` and `nextIdx` are spread over `hasNext()` to + * simplify initial conditions, and preserving as much performance as + * possible while guaranteeing that constructing the iterator remains O(1) + * (the first linear behavior can happen when calling `hasNext()`, not + * before). + */ + + def hasNext(): Boolean = { + // scalastyle:off return + if (nextNode ne null) { + true + } else { + while (nextIdx < len) { + val node = table(nextIdx) + nextIdx += 1 + if (node ne null) { + nextNode = node + return true + } + } + false } + // scalastyle:on return + } + + def next(): A = { + if (!hasNext()) + throw new NoSuchElementException("next on empty iterator") + val node = nextNode + lastNode = node + nextNode = node.next + extract(node) + } + + def remove(): Unit = { + val last = lastNode + if (last eq null) + throw new IllegalStateException("next must be called at least once before remove") + removeNode(last) + lastNode = null } } - private trait AbstractMapView[E] extends AbstractCollection[E] { - override def size(): Int = - inner.size + // Views + + private final class KeySet extends AbstractSet[K] { + def iterator(): Iterator[K] = + keyIterator() + + def size(): Int = + self.size() + + override def contains(o: Any): Boolean = + containsKey(o) + + override def remove(o: Any): Boolean = + self.remove0(o) ne null override def clear(): Unit = - inner.clear() + self.clear() } - private abstract class AbstractMapViewIterator[E] extends Iterator[E] { - protected val innerIterator = inner.keySet.iterator + private final class Values extends AbstractCollection[V] { + def iterator(): ju.Iterator[V] = + valueIterator() - protected var lastKey: Option[Box[K]] = None + def size(): Int = + self.size() - protected def getNextForm(key: Box[K]): E + override def clear(): Unit = + self.clear() + } - final override def next: E = { - lastKey = Some(innerIterator.next()) - getNextForm(lastKey.get) - } + private final class EntrySet extends AbstractSet[Map.Entry[K, V]] { + def iterator(): Iterator[Map.Entry[K, V]] = + entrySetIterator() - final override def hasNext: Boolean = - innerIterator.hasNext + def size(): Int = + self.size() - final override def remove(): Unit = { - lastKey match { - case Some(key) => - inner.remove(key) - lastKey = None - case None => - throw new IllegalStateException - } + override def contains(o: Any): Boolean = o match { + case o: Map.Entry[_, _] => + val node = findNode(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[_, _] => + val key = o.getKey() + val (node, idx) = findNodeAndIndexForRemoval(key) + if ((node ne null) && Objects.equals(node.getValue(), o.getValue())) { + remove0(node, idx) + true + } else { + false + } + case _ => + false + } + + override def clear(): Unit = + self.clear() } } object HashMap { - private[HashMap] final val DEFAULT_INITIAL_CAPACITY = 16 - private[HashMap] final val DEFAULT_LOAD_FACTOR = 0.75f + private[util] final val DEFAULT_INITIAL_CAPACITY = 16 + private[util] final val DEFAULT_LOAD_FACTOR = 0.75f + + /** Computes the improved hash of an original (`any.hashCode()`) hash. */ + @inline private def improveHash(originalHash: Int): Int = { + /* Improve the hash by xoring the high 16 bits into the low 16 bits just in + * case entropy is skewed towards the high-value bits. We only use the + * lowest bits to determine the hash bucket. + * + * This function is also its own inverse. That is, for all ints i, + * improveHash(improveHash(i)) = i + * this allows us to retrieve the original hash when we need it, and that + * is why unimproveHash simply forwards to this method. + */ + originalHash ^ (originalHash >>> 16) + } + + /** Performs the inverse operation of improveHash. + * + * In this case, it happens to be identical to improveHash. + */ + @inline private def unimproveHash(improvedHash: Int): Int = + improveHash(improvedHash) + + /** Computes the improved hash of this key */ + @inline private def computeHash(k: Any): Int = + if (k == null) 0 + else improveHash(k.hashCode()) + + private[util] class Node[K, V](val key: K, val hash: Int, var value: V, + var previous: Node[K, V], var next: Node[K, V]) + extends Map.Entry[K, V] { + + def getKey(): K = key + + def getValue(): V = value + + def setValue(v: V): V = { + val oldValue = value + value = v + oldValue + } + + override def equals(that: Any): Boolean = that match { + case that: Map.Entry[_, _] => + Objects.equals(getKey(), that.getKey()) && + Objects.equals(getValue(), that.getValue()) + case _ => + false + } + + override def hashCode(): Int = + unimproveHash(hash) ^ Objects.hashCode(value) + + override def toString(): String = + "" + getKey + "=" + getValue + } } diff --git a/javalib/src/main/scala/java/util/LinkedHashMap.scala b/javalib/src/main/scala/java/util/LinkedHashMap.scala index 7a85e069de..6faebf55f1 100644 --- a/javalib/src/main/scala/java/util/LinkedHashMap.scala +++ b/javalib/src/main/scala/java/util/LinkedHashMap.scala @@ -12,72 +12,150 @@ package java.util -import scala.collection.mutable +import java.{util => ju} -class LinkedHashMap[K, V] private (inner: mutable.LinkedHashMap[Box[K], V], - accessOrder: Boolean) extends HashMap[K, V](inner) { +class LinkedHashMap[K, V](initialCapacity: Int, loadFactor: Float, + accessOrder: Boolean) + extends HashMap[K, V](initialCapacity, loadFactor) { self => - def this() = - this(mutable.LinkedHashMap.empty[Box[K], V], false) - - def this(initialCapacity: Int, loadFactor: Float, accessOrder: Boolean) = { - this(mutable.LinkedHashMap.empty[Box[K], V], accessOrder) - if (initialCapacity < 0) - throw new IllegalArgumentException("initialCapacity < 0") - else if (loadFactor < 0.0) - throw new IllegalArgumentException("loadFactor <= 0.0") - } + import LinkedHashMap._ + + /** Node that was least recently created (or accessed under access-order). */ + private var eldest: Node[K, V] = _ + + /** Node that was most recently created (or accessed under access-order). */ + private var youngest: Node[K, V] = _ def this(initialCapacity: Int, loadFactor: Float) = this(initialCapacity, loadFactor, false) def this(initialCapacity: Int) = - this(initialCapacity, LinkedHashMap.DEFAULT_LOAD_FACTOR) + this(initialCapacity, HashMap.DEFAULT_LOAD_FACTOR) + + def this() = + this(HashMap.DEFAULT_INITIAL_CAPACITY) def this(m: Map[_ <: K, _ <: V]) = { - this() + this(m.size()) putAll(m) } - override def get(key: scala.Any): V = { - val value = super.get(key) + private def asMyNode(node: HashMap.Node[K, V]): Node[K, V] = + node.asInstanceOf[Node[K, V]] + + private[util] override def newNode(key: K, hash: Int, value: V, + previous: HashMap.Node[K, V], + next: HashMap.Node[K, V]): HashMap.Node[K, V] = { + new Node(key, hash, value, previous, next, null, null) + } + + private[util] override def nodeWasAccessed(node: HashMap.Node[K, V]): Unit = { if (accessOrder) { - val boxedKey = Box(key.asInstanceOf[K]) - if (value != null || containsKey(boxedKey)) { - inner.remove(boxedKey) - inner(boxedKey) = value + val myNode = asMyNode(node) + if (myNode.younger ne null) { + removeFromOrderedList(myNode) + appendToOrderedList(myNode) } } - value } - override def put(key: K, value: V): V = { - val oldValue = { - if (accessOrder) { - val old = remove(key) - super.put(key, value) - old - } else { - super.put(key, value) - } - } - val iter = entrySet().iterator() - if (iter.hasNext && removeEldestEntry(iter.next())) - iter.remove() - oldValue + private[util] override def nodeWasAdded(node: HashMap.Node[K, V]): Unit = { + appendToOrderedList(asMyNode(node)) + if (removeEldestEntry(eldest)) + removeNode(eldest) + } + + private[util] override def nodeWasRemoved(node: HashMap.Node[K, V]): Unit = + removeFromOrderedList(asMyNode(node)) + + private def appendToOrderedList(node: Node[K, V]): Unit = { + val older = youngest + if (older ne null) + older.younger = node + else + eldest = node + node.older = older + node.younger = null + youngest = node + } + + private def removeFromOrderedList(node: Node[K, V]): Unit = { + val older = node.older + val younger = node.younger + if (older eq null) + eldest = younger + else + older.younger = younger + if (younger eq null) + youngest = older + else + younger.older = older } protected def removeEldestEntry(eldest: Map.Entry[K, V]): Boolean = false + private[util] override def nodeIterator(): ju.Iterator[HashMap.Node[K, V]] = + new NodeIterator + + private[util] override def keyIterator(): ju.Iterator[K] = + new KeyIterator + + private[util] override def valueIterator(): ju.Iterator[V] = + new ValueIterator + + private final class NodeIterator + extends AbstractLinkedHashMapIterator[HashMap.Node[K, V]] { + protected[this] def extract(node: Node[K, V]): Node[K, V] = node + } + + private final class KeyIterator extends AbstractLinkedHashMapIterator[K] { + protected[this] def extract(node: Node[K, V]): K = node.key + } + + private final class ValueIterator extends AbstractLinkedHashMapIterator[V] { + protected[this] def extract(node: Node[K, V]): V = node.value + } + + private abstract class AbstractLinkedHashMapIterator[A] extends ju.Iterator[A] { + private[this] var nextNode: Node[K, V] = eldest + private[this] var lastNode: Node[K, V] = _ + + protected[this] def extract(node: Node[K, V]): A + + def hasNext(): Boolean = + nextNode ne null + + def next(): A = { + if (!hasNext()) + throw new NoSuchElementException("next on empty iterator") + val node = nextNode + lastNode = node + nextNode = node.younger + extract(node) + } + + def remove(): Unit = { + val last = lastNode + if (last eq null) + throw new IllegalStateException("next must be called at least once before remove") + removeNode(last) + lastNode = null + } + } + override def clone(): AnyRef = { - new LinkedHashMap(inner.clone(), accessOrder) + val result = new LinkedHashMap[K, V](size(), loadFactor, accessOrder) + result.putAll(this) + result } } object LinkedHashMap { - private[LinkedHashMap] final val DEFAULT_INITIAL_CAPACITY = 16 - private[LinkedHashMap] final val DEFAULT_LOAD_FACTOR = 0.75f + private final class Node[K, V](key: K, hash: Int, value: V, + previous: HashMap.Node[K, V], next: HashMap.Node[K, V], + var older: Node[K, V], var younger: Node[K, V]) + extends HashMap.Node[K, V](key, hash, value, previous, next) } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala index 4b6697046f..67c184b858 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala @@ -47,11 +47,11 @@ abstract class LinkedHashMapTest extends HashMapTest { val withSizeLimit = factory.withSizeLimit @Test def should_iterate_in_insertion_order_after_building(): Unit = { - val lhm = factory.empty[jl.Integer, String] - (0 until 100).foreach(key => lhm.put(key, s"elem $key")) + val lhm = factory.empty[String, String] + (0 until 100).foreach(key => lhm.put(key.toString(), s"elem $key")) - def expectedKey(index: Int): Int = - withSizeLimit.getOrElse(0) + index + def expectedKey(index: Int): String = + (withSizeLimit.getOrElse(0) + index).toString() def expectedValue(index: Int): String = s"elem ${expectedKey(index)}" @@ -74,13 +74,13 @@ abstract class LinkedHashMapTest extends HashMapTest { } @Test def should_iterate_in_the_same_order_after_removal_of_elements(): Unit = { - val lhm = factory.empty[jl.Integer, String] - (0 until 100).foreach(key => lhm.put(key, s"elem $key")) + val lhm = factory.empty[String, String] + (0 until 100).foreach(key => lhm.put(key.toString(), s"elem $key")) - (0 until 100 by 3).foreach(key => lhm.remove(key)) + (0 until 100 by 3).foreach(key => lhm.remove(key.toString())) val expectedKey = - ((100 - withSizeLimit.getOrElse(100)) to 100).filter(_ % 3 != 0).toArray + ((100 - withSizeLimit.getOrElse(100)) to 100).filter(_ % 3 != 0).map(_.toString()).toArray def expectedValue(index: Int): String = s"elem ${expectedKey(index)}" @@ -103,15 +103,15 @@ abstract class LinkedHashMapTest extends HashMapTest { } @Test def should_iterate_in_order_after_adding_elements(): Unit = { - val lhm = factory.empty[jl.Integer, String] - (0 until 100).foreach(key => lhm.put(key, s"elem $key")) + val lhm = factory.empty[String, String] + (0 until 100).foreach(key => lhm.put(key.toString(), s"elem $key")) - lhm.put(0, "new 0") - lhm.put(100, "elem 100") - lhm.put(42, "new 42") - lhm.put(52, "new 52") - lhm.put(1, "new 1") - lhm.put(98, "new 98") + lhm.put("0", "new 0") + lhm.put("100", "elem 100") + lhm.put("42", "new 42") + lhm.put("52", "new 52") + lhm.put("1", "new 1") + lhm.put("98", "new 98") val expectedKey = { if (factory.accessOrder) { @@ -122,11 +122,11 @@ abstract class LinkedHashMapTest extends HashMapTest { if (withSizeLimit.isDefined) (55 until 100) ++ List(0, 100, 42, 52, 1) else 0 to 100 } - }.toArray + }.map(_.toString()).toArray def expectedElem(index: Int): String = { val key = expectedKey(index) - if (key == 0 || key == 1 || key == 42 || key == 52 || key == 98) + if (key == "0" || key == "1" || key == "42" || key == "52" || key == "98") s"new $key" else s"elem $key" @@ -151,15 +151,15 @@ abstract class LinkedHashMapTest extends HashMapTest { } @Test def should_iterate_in__after_accessing_elements(): Unit = { - val lhm = factory.empty[jl.Integer, String] - (0 until 100).foreach(key => lhm.put(key, s"elem $key")) + val lhm = factory.empty[String, String] + (0 until 100).foreach(key => lhm.put(key.toString(), s"elem $key")) - lhm.get(42) - lhm.get(52) - lhm.get(5) + lhm.get("42") + lhm.get("52") + lhm.get("5") - def expectedKey(index: Int): Int = { - if (accessOrder) { + def expectedKey(index: Int): String = { + val intKey = if (accessOrder) { // elements ordered by insertion order except for those accessed if (withSizeLimit.isEmpty) { if (index < 5) index // no elements removed in this range @@ -182,6 +182,7 @@ abstract class LinkedHashMapTest extends HashMapTest { // accesses shouldn't modify the order withSizeLimit.getOrElse(0) + index } + intKey.toString() } def expectedValue(index: Int): String = @@ -213,7 +214,8 @@ object LinkedHashMapFactory { } } -class LinkedHashMapFactory(val accessOrder: Boolean, val withSizeLimit: Option[Int]) +class LinkedHashMapFactory(val accessOrder: Boolean, + override val withSizeLimit: Option[Int]) extends HashMapFactory { def orderName: String = if (accessOrder) "access-order" diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala index 206e4f00fd..86b8cfe6dd 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala @@ -63,6 +63,17 @@ trait MapTest { assertEquals("two", mp.get("TWO")) } + @Test def should_store_strings_large_map(): Unit = { + val largeMap = factory.empty[String, Int] + for (i <- 0 until 1000) + largeMap.put(i.toString(), i) + val expectedSize = factory.withSizeLimit.fold(1000)(Math.min(_, 1000)) + assertEquals(expectedSize, largeMap.size()) + for (i <- (1000 - expectedSize) until 1000) + assertEquals(i, largeMap.get(i.toString())) + assertNull(largeMap.get("1000")) + } + @Test def should_store_integers(): Unit = { val mp = factory.empty[Int, Int] @@ -72,6 +83,17 @@ trait MapTest { assertEquals(12345, one) } + @Test def should_store_integers_large_map(): Unit = { + val largeMap = factory.empty[Int, Int] + for (i <- 0 until 1000) + largeMap.put(i, i * 2) + val expectedSize = factory.withSizeLimit.fold(1000)(Math.min(_, 1000)) + assertEquals(expectedSize, largeMap.size()) + for (i <- (1000 - expectedSize) until 1000) + assertEquals(i * 2, largeMap.get(i)) + assertNull(largeMap.get(1000)) + } + @Test def should_store_doubles_also_in_corner_cases(): Unit = { val mp = factory.empty[Double, Double] @@ -624,4 +646,6 @@ trait MapFactory { def allowsNullKeysQueries: Boolean = true def allowsNullValuesQueries: Boolean = true + + def withSizeLimit: Option[Int] = None } From dc54bf15fd820462f044752d82b68951ae7000aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 24 Jun 2019 14:47:03 +0200 Subject: [PATCH 0033/1606] Implement `j.u.{Linked,}HashSet` on top of `j.u.{Linked,}HashMap`. Previously, they were implemented on top of `s.c.m.{Linked,}HashSet`, with hacks to support Java features and behaviors, notably: * Non-cooperative equality * `Iterator.remove()` Now that `java.util.{Linked,}HashMap`s are implemented from scratch, it makes more sense to implement the hash sets on top of them, since they already provide all these features in a native way. An even better way to implement them would be to do it from scratch as well, with a copy of the algorithms for the hash maps, but the only real advantage would be to claim a bit of memory usage. The added complexity does not seem to be worth it. --- .../src/main/scala/java/util/HashSet.scala | 79 ++++++------------- .../main/scala/java/util/LinkedHashSet.scala | 18 ++--- 2 files changed, 32 insertions(+), 65 deletions(-) diff --git a/javalib/src/main/scala/java/util/HashSet.scala b/javalib/src/main/scala/java/util/HashSet.scala index be20d27430..c05ea35794 100644 --- a/javalib/src/main/scala/java/util/HashSet.scala +++ b/javalib/src/main/scala/java/util/HashSet.scala @@ -12,57 +12,47 @@ package java.util -import scala.collection.mutable +class HashSet[E] private[util] (inner: HashMap[E, Any]) + extends AbstractSet[E] with Set[E] with Cloneable with Serializable { -import ScalaOps._ + /* Note: in practice, the values of `inner` are always `()` (aka `undefined`). + * We use `Any` because we need to deal with `null`s, and referencing + * `scala.runtime.BoxedUnit` in this code would be really ugly. + */ -class HashSet[E] extends AbstractSet[E] with Set[E] - with Cloneable - with Serializable { self => def this(initialCapacity: Int, loadFactor: Float) = - this() + this(new HashMap[E, Any](initialCapacity, loadFactor)) def this(initialCapacity: Int) = - this() + this(new HashMap[E, Any](initialCapacity)) + + def this() = + this(new HashMap[E, Any]()) def this(c: Collection[_ <: E]) = { - this() + this(c.size()) addAll(c) } - protected val inner: mutable.Set[Box[E]] = - new mutable.HashSet[Box[E]]() + private val innerKeySet = inner.keySet() override def contains(o: Any): Boolean = - inner.contains(Box(o.asInstanceOf[E])) + inner.containsKey(o) override def remove(o: Any): Boolean = - inner.remove(Box(o.asInstanceOf[E])) + inner.remove(o) != null override def containsAll(c: Collection[_]): Boolean = - c.scalaOps.forall(e => contains(e)) + innerKeySet.containsAll(c) - override def removeAll(c: Collection[_]): Boolean = { - val iter = c.iterator - var changed = false - while (iter.hasNext) - changed = remove(iter.next()) || changed - changed - } + override def removeAll(c: Collection[_]): Boolean = + innerKeySet.removeAll(c) - override def retainAll(c: Collection[_]): Boolean = { - val iter = iterator - var changed = false - while (iter.hasNext) { - val value = iter.next - if (!c.contains(value)) - changed = remove(value) || changed - } - changed - } + override def retainAll(c: Collection[_]): Boolean = + innerKeySet.retainAll(c) override def add(e: E): Boolean = - inner.add(Box(e)) + inner.put(e, ()) == null override def addAll(c: Collection[_ <: E]): Boolean = { val iter = c.iterator() @@ -74,30 +64,9 @@ class HashSet[E] extends AbstractSet[E] with Set[E] override def clear(): Unit = inner.clear() - override def size(): Int = inner.size - - def iterator(): Iterator[E] = { - new Iterator[E] { - private val iter = inner.clone.iterator - - private var last: Option[E] = None + override def size(): Int = inner.size() - def hasNext(): Boolean = iter.hasNext - - def next(): E = { - last = Some(iter.next().inner) - last.get - } - - def remove(): Unit = { - if (last.isEmpty) { - throw new IllegalStateException() - } else { - last.foreach(self.remove(_)) - last = None - } - } - } - } + def iterator(): Iterator[E] = + innerKeySet.iterator() } diff --git a/javalib/src/main/scala/java/util/LinkedHashSet.scala b/javalib/src/main/scala/java/util/LinkedHashSet.scala index a305b9f0d9..b67e126d8d 100644 --- a/javalib/src/main/scala/java/util/LinkedHashSet.scala +++ b/javalib/src/main/scala/java/util/LinkedHashSet.scala @@ -12,23 +12,21 @@ package java.util -import scala.collection.mutable +class LinkedHashSet[E] private[util] (inner: LinkedHashMap[E, Any]) + extends HashSet[E](inner) with Set[E] with Cloneable with Serializable { -class LinkedHashSet[E] extends HashSet[E] with Set[E] - with Cloneable - with Serializable { def this(initialCapacity: Int, loadFactor: Float) = - this() + this(new LinkedHashMap[E, Any](initialCapacity, loadFactor)) def this(initialCapacity: Int) = - this() + this(new LinkedHashMap[E, Any](initialCapacity)) + + def this() = + this(new LinkedHashMap[E, Any]()) def this(c: java.util.Collection[_ <: E]) = { - this() + this(c.size()) addAll(c) } - override protected val inner: mutable.Set[Box[E]] = - new mutable.LinkedHashSet[Box[E]]() - } From 8e6c79394cc65254f2abff55c1e7f13c10838ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 9 Jul 2019 15:43:56 +0200 Subject: [PATCH 0034/1606] Do not check formal arguments of method calls to non-instantiated types. In a method call of the form `x.m__sig(...args)`, if the type of `x` does not have instances, the base linker might not have linked the classes referenced in `sig` at all. Therefore, we must not try to infer the type signature, as it can crash. --- .../scalajs/linker/checker/IRChecker.scala | 28 ++++- .../org/scalajs/linker/AnalyzerTest.scala | 2 - .../org/scalajs/linker/IRCheckerTest.scala | 106 ++++++++++++++++++ .../scala/org/scalajs/linker/LinkerTest.scala | 16 +-- .../linker/testutils/MemClassDefIRFile.scala | 36 ++++++ .../linker/testutils/TestIRBuilder.scala | 43 ++++--- .../testsuite/compiler/RegressionTest.scala | 20 ++++ 7 files changed, 215 insertions(+), 36 deletions(-) create mode 100644 linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala create mode 100644 linker/shared/src/test/scala/org/scalajs/linker/testutils/MemClassDefIRFile.scala diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 7025b5edce..1787a6fb9a 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -834,8 +834,32 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { if (flags.isPrivate) reportError(s"Illegal flag for Apply: Private") val receiverType = typecheckExpr(receiver, env) - checkApplyGeneric(method, s"$receiverType.$method", args, tree.tpe, - isStatic = false) + val fullCheck = receiverType match { + case ClassType(cls) => + /* For class types, we only perform full checks if the class has + * instances. This is necessary because the BaseLinker can + * completely get rid of all the method *definitions* for the call + * method. In that case, the classes references in the *signature* + * of the method might not have been made reachable, and hence + * inferring the type signature might fail. Obviously in such cases, + * the only value that `receiver` can assume is `null`, and the + * `Apply` will fail with an NPE, so the types of the arguments are + * irreleant. + */ + lookupClass(cls).hasInstances + case NullType | NothingType => + // By a similar argument, we must not perform full checks here + false + case _ => + true + } + if (fullCheck) { + checkApplyGeneric(method, s"$receiverType.$method", args, tree.tpe, + isStatic = false) + } else { + for (arg <- args) + typecheckExpr(arg, env) + } case ApplyStatically(_, receiver, cls, Ident(method, _), args) => typecheckExpect(receiver, env, ClassType(cls.className)) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala index 5d00dc50be..36e9457cb0 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/AnalyzerTest.scala @@ -39,8 +39,6 @@ class AnalyzerTest { import scala.concurrent.ExecutionContext.Implicits.global import AnalyzerTest._ - private val EAF = ApplyFlags.empty - @Test def trivialOK(): AsyncResult = await { val analysis = computeAnalysis(Nil) diff --git a/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala new file mode 100644 index 0000000000..2d9fa086b0 --- /dev/null +++ b/linker/shared/src/test/scala/org/scalajs/linker/IRCheckerTest.scala @@ -0,0 +1,106 @@ +/* + * 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.linker + +import scala.concurrent._ + +import org.junit.Test +import org.junit.Assert._ + +import org.scalajs.ir.ClassKind +import org.scalajs.ir.Definitions._ +import org.scalajs.ir.Trees._ +import org.scalajs.ir.Types._ + +import org.scalajs.logging._ + +import org.scalajs.junit.async._ + +import org.scalajs.linker._ +import org.scalajs.linker.standard._ + +import org.scalajs.linker.testutils._ +import org.scalajs.linker.testutils.TestIRBuilder._ + +class IRCheckerTest { + import scala.concurrent.ExecutionContext.Implicits.global + + import IRCheckerTest._ + + @Test + def testMethodCallOnClassWithNoInstances(): AsyncResult = await { + def callMethOn(receiver: Tree): Tree = + Apply(EAF, receiver, Ident("meth__LFoo__V"), List(Null()))(NoType) + + val classDefs = Seq( + // LFoo will be dropped by base linking + classDef("LFoo", superClass = Some(ObjectClass)), + + classDef("LBar", + superClass = Some(ObjectClass), + memberDefs = List( + trivialCtor("LBar"), + + /* This method is called, but unreachable because there are no + * instances of `Bar`. It will therefore not make `Foo` reachable. + */ + MethodDef(MemberFlags.empty, Ident("meth__LFoo__V"), + List(paramDef("foo", ClassType("LFoo"))), NoType, + Some(Skip()))( + emptyOptHints, None) + ) + ), + + classDef("LTest$", kind = ClassKind.ModuleClass, + superClass = Some(ObjectClass), + memberDefs = List( + trivialCtor("LTest$"), + MethodDef(MemberFlags.empty, Ident("nullBar__LBar"), Nil, ClassType("LBar"), + Some(Null()))( + emptyOptHints, None), + mainMethodDef(Block( + callMethOn(Apply(EAF, This()(ClassType("LTest$")), + Ident("nullBar__LBar"), Nil)(ClassType("LBar"))), + callMethOn(Null()), + callMethOn(Throw(Null())) + )) + ) + ) + ) + + testLinkNoIRError(classDefs, mainModuleInitializers("Test")) + } + +} + +object IRCheckerTest { + def testLinkNoIRError(classDefs: Seq[ClassDef], + moduleInitializers: List[ModuleInitializer])( + implicit ec: ExecutionContext): Future[Unit] = { + + val config = StandardLinker.Config() + .withCheckIR(true) + .withOptimizer(false) + val linkerFrontend = StandardLinkerFrontend(config) + val symbolRequirements = StandardLinkerBackend(config).symbolRequirements + + val classDefsFiles = classDefs.map(MemClassDefIRFile(_)) + + val result = TestIRRepo.minilib.stdlibIRFiles.flatMap { stdLibFiles => + linkerFrontend.link(stdLibFiles ++ classDefsFiles, moduleInitializers, + symbolRequirements, new ScalaConsoleLogger(Level.Error)) + } + + result.map(_ => ()) + } +} diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LinkerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LinkerTest.scala index d6803124ed..a08e1b9700 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LinkerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LinkerTest.scala @@ -56,10 +56,7 @@ class LinkerTest { ) ) ) - val moduleInitializers = List( - ModuleInitializer.mainMethodWithArgs("HelloWorld", "main") - ) - testLink(classDefs, moduleInitializers) + testLink(classDefs, mainModuleInitializers("HelloWorld")) } /** This test exposes a problem where a linker in error state is called @@ -110,16 +107,7 @@ object LinkerTest { implicit ec: ExecutionContext): Future[Unit] = { val linker = StandardLinker(StandardLinker.Config()) - - val classDefsFiles = classDefs.map { classDef => - new IRFileImpl("mem://" + classDef.name.name + ".sjsir", None) { - def tree(implicit ec: ExecutionContext): Future[ClassDef] = Future(classDef) - - def entryPointsInfo(implicit ec: ExecutionContext): Future[EntryPointsInfo] = - tree.map(EntryPointsInfo.forClassDef) - } - } - + val classDefsFiles = classDefs.map(MemClassDefIRFile(_)) val output = LinkerOutput(LinkerOutput.newMemFile()) TestIRRepo.minilib.stdlibIRFiles.flatMap { stdLibFiles => diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/MemClassDefIRFile.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/MemClassDefIRFile.scala new file mode 100644 index 0000000000..184e25cfc5 --- /dev/null +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/MemClassDefIRFile.scala @@ -0,0 +1,36 @@ +/* + * 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.linker.testutils + +import scala.concurrent._ + +import org.scalajs.ir.EntryPointsInfo +import org.scalajs.ir.Trees.ClassDef + +import org.scalajs.linker.IRFile +import org.scalajs.linker.standard.IRFileImpl + +private final class MemClassDefIRFile(classDef: ClassDef) + extends IRFileImpl("mem://" + classDef.name.name + ".sjsir", None) { + + def tree(implicit ec: ExecutionContext): Future[ClassDef] = + Future(classDef) + + def entryPointsInfo(implicit ec: ExecutionContext): Future[EntryPointsInfo] = + tree.map(EntryPointsInfo.forClassDef) +} + +object MemClassDefIRFile { + def apply(classDef: ClassDef): IRFile = + new MemClassDefIRFile(classDef) +} diff --git a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala index d62e336fea..a2dde949e2 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/testutils/TestIRBuilder.scala @@ -1,15 +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. - */ - +/* + * 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.linker.testutils import org.scalajs.ir @@ -18,10 +18,12 @@ import org.scalajs.ir.Definitions._ import org.scalajs.ir.Trees._ import org.scalajs.ir.Types._ +import org.scalajs.linker.ModuleInitializer + object TestIRBuilder { implicit val noPosition: ir.Position = ir.Position.NoPosition - private val EAF = ApplyFlags.empty + val EAF = ApplyFlags.empty val emptyOptHints: OptimizerHints = OptimizerHints.empty @@ -41,10 +43,10 @@ object TestIRBuilder { emptyOptHints) } - def trivialCtor(enclosingClassName: String): MethodDef = { + def trivialCtor(enclosingClassName: String): MethodDef = { val flags = MemberFlags.empty.withNamespace(MemberNamespace.Constructor) MethodDef(flags, Ident("init___"), Nil, NoType, - Some(ApplyStatically(EAF.withConstructor(true), + Some(ApplyStatically(EAF.withConstructor(true), This()(ClassType(enclosingClassName)), ClassRef(ObjectClass), Ident("init___"), Nil)(NoType)))( emptyOptHints, None) @@ -52,9 +54,14 @@ object TestIRBuilder { def mainMethodDef(body: Tree): MethodDef = { val stringArrayType = ArrayType(ArrayTypeRef("T", 1)) - val argsParamDef = ParamDef(Ident("args", Some("args")), stringArrayType, - mutable = false, rest = false) + val argsParamDef = paramDef("args", stringArrayType) MethodDef(MemberFlags.empty, Ident("main__AT__V"), List(argsParamDef), NoType, Some(body))(emptyOptHints, None) } -} + + def paramDef(name: String, ptpe: Type): ParamDef = + ParamDef(Ident(name, Some(name)), ptpe, mutable = false, rest = false) + + def mainModuleInitializers(moduleClassName: String): List[ModuleInitializer] = + ModuleInitializer.mainMethodWithArgs(moduleClassName, "main") :: Nil +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala index d3f4ebccb6..ebdd2916c6 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/compiler/RegressionTest.scala @@ -331,6 +331,26 @@ class RegressionTest { assertEquals(3, test(null)) } + @Test def IR_checker_must_not_check_method_signatures_on_classes_with_no_instance(): Unit = { + assumeTrue("linking only", false) + + class Foo // this class will be dropped by base linking + + class Bar { + /* This method is called, but unreachable because there are no instances + * of `Bar`. It will therefore not make `Foo` reachable. + */ + def meth(foo: Foo): String = foo.toString() + } + + @noinline def nullBar(): Bar = null + + // the IR checker must not try to infer the signature of these calls + nullBar().meth(null) + (null: Bar).meth(null) + (??? : Bar).meth(null) // scalastyle:ignore + } + @Test def should_properly_order_ctor_statements_when_inlining_issue_1369(): Unit = { trait Bar { def x: Int From e07d99d3e9b5de42beab6b0de79cf156d2e17dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 30 Jul 2019 14:00:11 +0200 Subject: [PATCH 0035/1606] Use `Objects.equals()` and `Objects.hashCode()` where relevant. Instead of inline tests and the custom `===` operator. --- .../scala/java/util/AbstractCollection.scala | 4 +-- .../main/scala/java/util/AbstractList.scala | 8 +++--- .../main/scala/java/util/AbstractMap.scala | 25 +++++++------------ .../src/main/scala/java/util/ArrayDeque.scala | 6 ++--- javalib/src/main/scala/java/util/Arrays.scala | 8 +++--- .../main/scala/java/util/Collections.scala | 6 ++--- .../src/main/scala/java/util/LinkedList.scala | 4 +-- .../src/main/scala/java/util/Objects.scala | 2 +- .../main/scala/java/util/PriorityQueue.scala | 2 +- .../util/concurrent/ConcurrentHashMap.scala | 8 +++--- .../concurrent/CopyOnWriteArrayList.scala | 10 ++++---- .../src/main/scala/java/util/package.scala | 12 ++------- 12 files changed, 40 insertions(+), 55 deletions(-) diff --git a/javalib/src/main/scala/java/util/AbstractCollection.scala b/javalib/src/main/scala/java/util/AbstractCollection.scala index f5cadcb698..e8c1ec9372 100644 --- a/javalib/src/main/scala/java/util/AbstractCollection.scala +++ b/javalib/src/main/scala/java/util/AbstractCollection.scala @@ -25,7 +25,7 @@ abstract class AbstractCollection[E] protected () extends Collection[E] { def isEmpty(): Boolean = size == 0 def contains(o: Any): Boolean = - this.scalaOps.exists(o === _) + this.scalaOps.exists(Objects.equals(o, _)) def toArray(): Array[AnyRef] = toArray(new Array[AnyRef](size)) @@ -50,7 +50,7 @@ abstract class AbstractCollection[E] protected () extends Collection[E] { @tailrec def findAndRemove(iter: Iterator[E]): Boolean = { if (iter.hasNext) { - if (iter.next() === o) { + if (Objects.equals(iter.next(), o)) { iter.remove() true } else diff --git a/javalib/src/main/scala/java/util/AbstractList.scala b/javalib/src/main/scala/java/util/AbstractList.scala index 9d23dbad22..e28dc643d1 100644 --- a/javalib/src/main/scala/java/util/AbstractList.scala +++ b/javalib/src/main/scala/java/util/AbstractList.scala @@ -35,13 +35,13 @@ abstract class AbstractList[E] protected () extends AbstractCollection[E] throw new UnsupportedOperationException def indexOf(o: Any): Int = - this.scalaOps.indexWhere(_ === o) + this.scalaOps.indexWhere(Objects.equals(_, o)) def lastIndexOf(o: Any): Int = { @tailrec def findIndex(iter: ListIterator[E]): Int = { if (!iter.hasPrevious) -1 - else if (iter.previous() === o) iter.nextIndex + else if (Objects.equals(iter.previous(), o)) iter.nextIndex else findIndex(iter) } findIndex(listIterator(size)) @@ -111,7 +111,7 @@ abstract class AbstractList[E] protected () extends AbstractCollection[E] o match { case o: List[_] => val oIter = o.listIterator - this.scalaOps.forall(oIter.hasNext && _ === oIter.next()) && !oIter.hasNext + this.scalaOps.forall(oIter.hasNext && Objects.equals(_, oIter.next())) && !oIter.hasNext case _ => false } } @@ -119,7 +119,7 @@ abstract class AbstractList[E] protected () extends AbstractCollection[E] override def hashCode(): Int = { this.scalaOps.foldLeft(1) { - (prev, elem) => 31 * prev + (if (elem == null) 0 else elem.hashCode) + (prev, elem) => 31 * prev + Objects.hashCode(elem) } } diff --git a/javalib/src/main/scala/java/util/AbstractMap.scala b/javalib/src/main/scala/java/util/AbstractMap.scala index 9205d264e6..cd66351f08 100644 --- a/javalib/src/main/scala/java/util/AbstractMap.scala +++ b/javalib/src/main/scala/java/util/AbstractMap.scala @@ -21,21 +21,14 @@ object AbstractMap { private def entryEquals[K, V](entry: Map.Entry[K, V], other: Any): Boolean = { other match { case other: Map.Entry[_, _] => - entry.getKey === other.getKey && entry.getValue === other.getValue + Objects.equals(entry.getKey, other.getKey) && + Objects.equals(entry.getValue, other.getValue) case _ => false } } - private def entryHashCode[K, V](entry: Map.Entry[K, V]): Int = { - val keyHash = - if (entry.getKey == null) 0 - else entry.getKey.hashCode - val valueHash = - if (entry.getValue == null) 0 - else entry.getValue.hashCode - - keyHash ^ valueHash - } + private def entryHashCode[K, V](entry: Map.Entry[K, V]): Int = + Objects.hashCode(entry.getKey) ^ Objects.hashCode(entry.getValue) class SimpleEntry[K, V](private var key: K, private var value: V) extends Map.Entry[K, V] with Serializable { @@ -95,13 +88,13 @@ abstract class AbstractMap[K, V] protected () extends java.util.Map[K, V] { def isEmpty(): Boolean = size == 0 def containsValue(value: Any): Boolean = - entrySet.scalaOps.exists(value === _.getValue) + entrySet.scalaOps.exists(entry => Objects.equals(value, entry.getValue)) def containsKey(key: Any): Boolean = - entrySet.scalaOps.exists(entry => entry === key) + entrySet.scalaOps.exists(entry => Objects.equals(key, entry.getKey)) def get(key: Any): V = { - entrySet.scalaOps.find(_.getKey === key).fold[V] { + entrySet.scalaOps.find(entry => Objects.equals(key, entry.getKey)).fold[V] { null.asInstanceOf[V] } { entry => entry.getValue @@ -116,7 +109,7 @@ abstract class AbstractMap[K, V] protected () extends java.util.Map[K, V] { def findAndRemove(iter: Iterator[Map.Entry[K, V]]): V = { if (iter.hasNext) { val item = iter.next() - if (key === item.getKey) { + if (Objects.equals(key, item.getKey)) { iter.remove() item.getValue } else @@ -177,7 +170,7 @@ abstract class AbstractMap[K, V] protected () extends java.util.Map[K, V] { o match { case m: Map[_, _] => self.size == m.size && - entrySet.scalaOps.forall(item => m.get(item.getKey) === item.getValue) + entrySet.scalaOps.forall(item => Objects.equals(m.get(item.getKey), item.getValue)) case _ => false } } diff --git a/javalib/src/main/scala/java/util/ArrayDeque.scala b/javalib/src/main/scala/java/util/ArrayDeque.scala index 7564bce572..05c43d4b5e 100644 --- a/javalib/src/main/scala/java/util/ArrayDeque.scala +++ b/javalib/src/main/scala/java/util/ArrayDeque.scala @@ -114,7 +114,7 @@ class ArrayDeque[E] private (private var inner: js.Array[E]) } def removeFirstOccurrence(o: Any): Boolean = { - val index = inner.indexWhere(_ === o) + val index = inner.indexWhere(Objects.equals(_, o)) if (index >= 0) { inner.remove(index) status += 1 @@ -124,7 +124,7 @@ class ArrayDeque[E] private (private var inner: js.Array[E]) } def removeLastOccurrence(o: Any): Boolean = { - val index = inner.lastIndexWhere(_ === o) + val index = inner.lastIndexWhere(Objects.equals(_, o)) if (index >= 0) { inner.remove(index) status += 1 @@ -193,7 +193,7 @@ class ArrayDeque[E] private (private var inner: js.Array[E]) def descendingIterator(): Iterator[E] = failFastIterator(inner.size, x => (x - 1)) - override def contains(o: Any): Boolean = inner.exists(_ === o) + override def contains(o: Any): Boolean = inner.exists(Objects.equals(_, o)) override def remove(o: Any): Boolean = removeFirstOccurrence(o) diff --git a/javalib/src/main/scala/java/util/Arrays.scala b/javalib/src/main/scala/java/util/Arrays.scala index cc0b8caa49..57f69bd8fd 100644 --- a/javalib/src/main/scala/java/util/Arrays.scala +++ b/javalib/src/main/scala/java/util/Arrays.scala @@ -651,13 +651,13 @@ object Arrays { hashCodeImpl[Double](a) @noinline def hashCode(a: Array[AnyRef]): Int = - hashCodeImpl[AnyRef](a) + hashCodeImpl[AnyRef](a, Objects.hashCode(_)) @inline private def hashCodeImpl[T](a: Array[T], - elementHashCode: T => Int = (x: T) => x.asInstanceOf[AnyRef].hashCode): Int = { + elementHashCode: T => Int = (x: T) => x.hashCode): Int = { if (a == null) 0 - else a.foldLeft(1)((acc, x) => 31*acc + (if (x == null) 0 else elementHashCode(x))) + else a.foldLeft(1)((acc, x) => 31*acc + elementHashCode(x)) } @noinline def deepHashCode(a: Array[AnyRef]): Int = { @@ -673,7 +673,7 @@ object Arrays { case elem: Array[Boolean] => hashCode(elem) case elem: Array[Float] => hashCode(elem) case elem: Array[Double] => hashCode(elem) - case _ => elem.hashCode + case _ => Objects.hashCode(elem) } } hashCodeImpl(a, getHash) diff --git a/javalib/src/main/scala/java/util/Collections.scala b/javalib/src/main/scala/java/util/Collections.scala index a901b65e69..3ad292c7e4 100644 --- a/javalib/src/main/scala/java/util/Collections.scala +++ b/javalib/src/main/scala/java/util/Collections.scala @@ -305,7 +305,7 @@ object Collections { case _: RandomAccess => var modified = false for (i <- 0 until list.size) { - if (list.get(i) === oldVal) { + if (Objects.equals(list.get(i), oldVal)) { list.set(i, newVal) modified = true } @@ -316,7 +316,7 @@ object Collections { @tailrec def replaceAll(iter: ListIterator[T], mod: Boolean): Boolean = { if (iter.hasNext) { - val isEqual = iter.next() === oldVal + val isEqual = Objects.equals(iter.next(), oldVal) if (isEqual) iter.set(newVal) replaceAll(iter, mod || isEqual) @@ -541,7 +541,7 @@ object Collections { } def frequency(c: Collection[_], o: AnyRef): Int = - c.scalaOps.count(_ === o) + c.scalaOps.count(Objects.equals(_, o)) def disjoint(c1: Collection[_], c2: Collection[_]): Boolean = { if (c1.size < c2.size) diff --git a/javalib/src/main/scala/java/util/LinkedList.scala b/javalib/src/main/scala/java/util/LinkedList.scala index 895a04106d..f6699068b5 100644 --- a/javalib/src/main/scala/java/util/LinkedList.scala +++ b/javalib/src/main/scala/java/util/LinkedList.scala @@ -109,7 +109,7 @@ class LinkedList[E]() extends AbstractSequentialList[E] } override def contains(o: Any): Boolean = - this.scalaOps.exists(_ === o) + this.scalaOps.exists(Objects.equals(_, o)) override def size(): Int = _size.toInt @@ -253,7 +253,7 @@ class LinkedList[E]() extends AbstractSequentialList[E] private def _removeOccurrence(iter: Iterator[E], o: Any): Boolean = { var changed = false while (iter.hasNext() && !changed) { - if (iter.next() === o) { + if (Objects.equals(iter.next(), o)) { iter.remove() changed = true } diff --git a/javalib/src/main/scala/java/util/Objects.scala b/javalib/src/main/scala/java/util/Objects.scala index f0b204a303..ae960094e1 100644 --- a/javalib/src/main/scala/java/util/Objects.scala +++ b/javalib/src/main/scala/java/util/Objects.scala @@ -36,7 +36,7 @@ object Objects { case (a1: Array[Boolean], a2: Array[Boolean]) => Arrays.equals(a1, a2) case (a1: Array[Float], a2: Array[Float]) => Arrays.equals(a1, a2) case (a1: Array[Double], a2: Array[Double]) => Arrays.equals(a1, a2) - case _ => a === b + case _ => Objects.equals(a, b) } } } diff --git a/javalib/src/main/scala/java/util/PriorityQueue.scala b/javalib/src/main/scala/java/util/PriorityQueue.scala index 128be47372..8f0da14186 100644 --- a/javalib/src/main/scala/java/util/PriorityQueue.scala +++ b/javalib/src/main/scala/java/util/PriorityQueue.scala @@ -95,7 +95,7 @@ class PriorityQueue[E] protected (ordering: Ordering[_ >: E], _comparator: Compa } override def contains(o: Any): Boolean = - inner.exists(_ === Box(o)) + inner.exists(box => Objects.equals(o, box.inner)) def iterator(): Iterator[E] = { new Iterator[E] { diff --git a/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala b/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala index 41d50b9f66..bd232ce7f3 100644 --- a/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala +++ b/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala @@ -47,13 +47,13 @@ class ConcurrentHashMap[K >: Null, V >: Null] override def containsKey(key: Any): Boolean = { if (key == null) throw new NullPointerException() - inner.exists { case (ik, _) => key === ik() } + inner.exists { case (ik, _) => Objects.equals(key, ik()) } } override def containsValue(value: Any): Boolean = { if (value == null) throw new NullPointerException() - inner.exists { case (_, iv) => value === iv } + inner.exists { case (_, iv) => Objects.equals(value, iv) } } override def put(key: K, value: V): V = { @@ -84,7 +84,7 @@ class ConcurrentHashMap[K >: Null, V >: Null] override def remove(key: Any, value: Any): Boolean = { val old = inner(Box(key.asInstanceOf[K])) - if (value === old) + if (Objects.equals(value, old)) inner.remove(Box(key.asInstanceOf[K])).isDefined else false @@ -93,7 +93,7 @@ class ConcurrentHashMap[K >: Null, V >: Null] override def replace(key: K, oldValue: V, newValue: V): Boolean = { if (key != null && oldValue != null && newValue != null) { val old = inner(Box(key)) - if (oldValue === old) { + if (Objects.equals(oldValue, old)) { put(key, newValue) true } else { diff --git a/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala b/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala index adca7c315c..b8d77f88ac 100644 --- a/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala +++ b/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala @@ -51,14 +51,14 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) size == 0 def contains(o: scala.Any): Boolean = - iterator.scalaOps.exists(o === _) + iterator.scalaOps.exists(Objects.equals(o, _)) def indexOf(o: scala.Any): Int = indexOf(o.asInstanceOf[E], 0) def indexOf(e: E, index: Int): Int = { checkIndexInBounds(index) - index + listIterator(index).scalaOps.indexWhere(_ === e) + index + listIterator(index).scalaOps.indexWhere(Objects.equals(_, e)) } def lastIndexOf(o: scala.Any): Int = @@ -68,7 +68,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) @tailrec def findIndex(iter: ListIterator[E]): Int = { if (!iter.hasPrevious) -1 - else if (iter.previous() === e) iter.nextIndex + else if (Objects.equals(iter.previous(), e)) iter.nextIndex else findIndex(iter) } findIndex(listIterator(size)) @@ -229,7 +229,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) obj match { case obj: List[_] => val oIter = obj.listIterator - this.scalaOps.forall(oIter.hasNext && _ === oIter.next()) && !oIter.hasNext + this.scalaOps.forall(elem => oIter.hasNext && Objects.equals(elem, oIter.next())) && !oIter.hasNext case _ => false } } @@ -237,7 +237,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) override def hashCode(): Int = { iterator().scalaOps.foldLeft(1) { - (prev, elem) => 31 * prev + (if (elem == null) 0 else elem.hashCode) + (prev, elem) => 31 * prev + Objects.hashCode(elem) } } diff --git a/javalib/src/main/scala/java/util/package.scala b/javalib/src/main/scala/java/util/package.scala index 1e0f26fb56..4f87826ba9 100644 --- a/javalib/src/main/scala/java/util/package.scala +++ b/javalib/src/main/scala/java/util/package.scala @@ -14,26 +14,18 @@ package java package object util { - implicit private[util] class CompareNullablesOps(val self: Any) extends AnyVal { - @inline - def ===(that: Any): Boolean = - if (self.asInstanceOf[AnyRef] eq null) that.asInstanceOf[AnyRef] eq null - else self.equals(that) - } - private[util] final case class Box[+K](inner: K) { def apply(): K = inner override def equals(o: Any): Boolean = { o match { - case o: Box[_] => inner === o.inner + case o: Box[_] => Objects.equals(inner, o.inner) case _ => false } } override def hashCode(): Int = - if (inner == null) 0 - else inner.hashCode + Objects.hashCode(inner) } private[util] def defaultOrdering[E]: Ordering[E] = { From 60c94e28c3b0d04b8c78399a4e8c1e67804a7095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 1 Aug 2019 15:17:53 +0200 Subject: [PATCH 0036/1606] Implement `j.u.c.ConcurrentHashMap` on top of `j.u.HashMap`. This allows to get rid of the dependency on `s.c.m.HashMap`, and leverages the many recent improvements to `j.u.HashMap`. --- .../java/util/NullRejectingHashMap.scala | 104 ++++++ .../util/concurrent/ConcurrentHashMap.scala | 316 +++++++++--------- .../concurrent/ConcurrentHashMapTest.scala | 159 +++++++++ 3 files changed, 422 insertions(+), 157 deletions(-) create mode 100644 javalib/src/main/scala/java/util/NullRejectingHashMap.scala diff --git a/javalib/src/main/scala/java/util/NullRejectingHashMap.scala b/javalib/src/main/scala/java/util/NullRejectingHashMap.scala new file mode 100644 index 0000000000..786a44a7d3 --- /dev/null +++ b/javalib/src/main/scala/java/util/NullRejectingHashMap.scala @@ -0,0 +1,104 @@ +/* + * 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 + +/** A subclass of `HashMap` that systematically rejects `null` keys and values. + * + * This class is used as the implementation of some other hashtable-like data + * structures that require non-`null` keys and values to correctly implement + * their specifications. + */ +private[util] class NullRejectingHashMap[K, V]( + initialCapacity: Int, loadFactor: Double) + extends HashMap[K, V](initialCapacity, loadFactor) { + + def this() = + this(HashMap.DEFAULT_INITIAL_CAPACITY, HashMap.DEFAULT_LOAD_FACTOR) + + def this(initialCapacity: Int) = + this(initialCapacity, HashMap.DEFAULT_LOAD_FACTOR) + + def this(m: Map[_ <: K, _ <: V]) = { + this(m.size()) + putAll(m) + } + + // Use Nodes that will reject `null`s in `setValue()` + override private[util] def newNode(key: K, hash: Int, value: V, + previous: HashMap.Node[K, V], next: HashMap.Node[K, V]): HashMap.Node[K, V] = { + new NullRejectingHashMap.Node(key, hash, value, previous, next) + } + + override def get(key: Any): V = { + if (key == null) + throw new NullPointerException() + super.get(key) + } + + override def containsKey(key: Any): Boolean = { + if (key == null) + throw new NullPointerException() + super.containsKey(key) + } + + override def put(key: K, value: V): V = { + if (key == null || value == null) + throw new NullPointerException() + super.put(key, value) + } + + @noinline + override def putAll(m: Map[_ <: K, _ <: V]): Unit = { + /* The only purpose of `impl` is to capture the wildcards as named types, + * so that we prevent type inference from inferring deprecated existential + * types. + */ + @inline + def impl[K1 <: K, V1 <: V](m: Map[K1, V1]): Unit = { + val iter = m.entrySet().iterator() + while (iter.hasNext()) { + val entry = iter.next() + put(entry.getKey(), entry.getValue()) + } + } + impl(m) + } + + override def remove(key: Any): V = { + if (key == null) + throw new NullPointerException() + super.remove(key) + } + + override def containsValue(value: Any): Boolean = { + if (value == null) + throw new NullPointerException() + super.containsValue(value) + } + + override def clone(): AnyRef = + new NullRejectingHashMap[K, V](this) +} + +private object NullRejectingHashMap { + private final class Node[K, V](key: K, hash: Int, value: V, + previous: HashMap.Node[K, V], next: HashMap.Node[K, V]) + extends HashMap.Node[K, V](key, hash, value, previous, next) { + + override def setValue(v: V): V = { + if (v == null) + throw new NullPointerException() + super.setValue(v) + } + } +} diff --git a/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala b/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala index bd232ce7f3..787408406c 100644 --- a/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala +++ b/javalib/src/main/scala/java/util/concurrent/ConcurrentHashMap.scala @@ -12,225 +12,227 @@ package java.util.concurrent -import java.lang.{reflect => jlr} import java.io.Serializable import java.util._ -import ScalaOps._ +class ConcurrentHashMap[K, V] private (initialCapacity: Int, loadFactor: Float) + extends AbstractMap[K, V] with ConcurrentMap[K, V] with Serializable { -class ConcurrentHashMap[K >: Null, V >: Null] - extends AbstractMap[K, V] with ConcurrentMap[K, V] with Serializable { self => + import ConcurrentHashMap._ - def this(initialCapacity: Int) = - this() - - def this(initialCapacity: Int, loadFactor: Float) = - this() + def this() = + this(HashMap.DEFAULT_INITIAL_CAPACITY, HashMap.DEFAULT_LOAD_FACTOR) - def this(initialCapacity: Int, loadFactor: Float, concurrencyLevel: Int) = - this() + def this(initialCapacity: Int) = + this(initialCapacity, HashMap.DEFAULT_LOAD_FACTOR) def this(initialMap: java.util.Map[_ <: K, _ <: V]) = { - this() + this(initialMap.size()) putAll(initialMap) } - private val inner = new scala.collection.mutable.HashMap[Box[K], V]() + def this(initialCapacity: Int, loadFactor: Float, concurrencyLevel: Int) = + this(initialCapacity, loadFactor) // ignore concurrencyLevel + + private[this] val inner: InnerHashMap[K, V] = + new InnerHashMap[K, V](initialCapacity, loadFactor) - override def isEmpty(): Boolean = inner.isEmpty + override def size(): Int = + inner.size() - override def size(): Int = inner.size + override def isEmpty(): Boolean = + inner.isEmpty() override def get(key: Any): V = - inner.get(Box(key.asInstanceOf[K])).getOrElse(null) + inner.get(key) - override def containsKey(key: Any): Boolean = { - if (key == null) - throw new NullPointerException() - inner.exists { case (ik, _) => Objects.equals(key, ik()) } - } + override def containsKey(key: Any): Boolean = + inner.containsKey(key) - override def containsValue(value: Any): Boolean = { - if (value == null) - throw new NullPointerException() - inner.exists { case (_, iv) => Objects.equals(value, iv) } - } + override def containsValue(value: Any): Boolean = + inner.containsValue(value) - override def put(key: K, value: V): V = { - if (key != null && value != null) - inner.put(Box(key), value).getOrElse(null) - else - throw new NullPointerException() - } + override def put(key: K, value: V): V = + inner.put(key, value) - def putIfAbsent(key: K, value: V): V = { - if (key == null || value == null) - throw new NullPointerException() + override def remove(key: Any): V = + inner.remove(key) - val bKey = Box(key) - inner.get(bKey).getOrElse { - inner.put(bKey, value) - null - } - } + override def clear(): Unit = + inner.clear() - override def putAll(m: Map[_ <: K, _ <: V]): Unit = { - for (e <- m.entrySet().scalaOps) - put(e.getKey, e.getValue) - } + override def keySet(): ConcurrentHashMap.KeySetView[K, V] = + new ConcurrentHashMap.KeySetView[K, V](inner.keySet()) - override def remove(key: Any): V = - inner.remove(Box(key.asInstanceOf[K])).getOrElse(null) + override def values(): Collection[V] = + inner.values() + + override def entrySet(): Set[Map.Entry[K, V]] = + inner.entrySet() + + override def hashCode(): Int = + inner.hashCode() + + override def toString(): String = + inner.toString() + + override def equals(o: Any): Boolean = + inner.equals(o) + + def putIfAbsent(key: K, value: V): V = { + if (value == null) + throw new NullPointerException() + val old = inner.get(key) // throws if `key` is null + if (old == null) + inner.put(key, value) + old + } - override def remove(key: Any, value: Any): Boolean = { - val old = inner(Box(key.asInstanceOf[K])) - if (Objects.equals(value, old)) - inner.remove(Box(key.asInstanceOf[K])).isDefined - else + def remove(key: Any, value: Any): Boolean = { + if (value == null) + throw new NullPointerException() + val old = inner.get(key) // throws if `key` is null + if (value.equals(old)) { // false if `old` is null + inner.remove(key) + true + } else { false + } } override def replace(key: K, oldValue: V, newValue: V): Boolean = { - if (key != null && oldValue != null && newValue != null) { - val old = inner(Box(key)) - if (Objects.equals(oldValue, old)) { - put(key, newValue) - true - } else { - false - } - } else { + if (oldValue == null || newValue == null) throw new NullPointerException() + val old = inner.get(key) // throws if `key` is null + if (oldValue.equals(old)) { // false if `old` is null + inner.put(key, newValue) + true + } else { + false } } override def replace(key: K, value: V): V = { - if (key != null && value != null) { - if (inner(Box(key)) != null) put(key, value) - else null - } else { + if (value == null) throw new NullPointerException() - } + val old = inner.get(key) // throws if `key` is null + if (old != null) + inner.put(key, value) + old } - override def clear(): Unit = - inner.clear() - - override def keySet(): ConcurrentHashMap.KeySetView[K, V] = - new ConcurrentHashMap.KeySetView[K, V](this) - - def entrySet(): Set[Map.Entry[K, V]] = { - new AbstractSet[Map.Entry[K, V]] { - override def size(): Int = self.size - - def iterator(): Iterator[Map.Entry[K, V]] = { - new Iterator[Map.Entry[K, V]] { - private val keysCopy = inner.keysIterator - - private var lastKey: Box[K] = null - - def hasNext(): Boolean = keysCopy.hasNext - - def next(): Map.Entry[K, V] = { - val k = keysCopy.next() - val v = inner(k) - lastKey = k - new AbstractMap.SimpleImmutableEntry(k(), v) - } - - def remove(): Unit = { - if (lastKey != null) { - inner.remove(lastKey) - lastKey = null - } else { - throw new IllegalStateException() - } - } - } - } - } - } + def contains(value: Any): Boolean = + containsValue(value) def keys(): Enumeration[K] = - inner.keys.iterator.map(_.inner).asJavaEnumeration + Collections.enumeration(inner.keySet()) def elements(): Enumeration[V] = - inner.values.iterator.asJavaEnumeration + Collections.enumeration(values()) } object ConcurrentHashMap { + import HashMap.Node + + /** Inner HashMap that contains the real implementation of a + * ConcurrentHashMap. + * + * It is a null-rejecting hash map because some algorithms rely on the fact + * that `get(key) == null` means the key was not in the map. + * + * It also has snapshotting iterators to make sure they are *weakly + * consistent*. + */ + private final class InnerHashMap[K, V](initialCapacity: Int, loadFactor: Float) + extends NullRejectingHashMap[K, V](initialCapacity, loadFactor) { + + override private[util] def nodeIterator(): Iterator[HashMap.Node[K, V]] = + new NodeIterator + + override private[util] def keyIterator(): Iterator[K] = + new KeyIterator + + override private[util] def valueIterator(): Iterator[V] = + new ValueIterator + + private def makeSnapshot(): ArrayList[Node[K, V]] = { + val snapshot = new ArrayList[Node[K, V]](size()) + val iter = super.nodeIterator() + while (iter.hasNext()) + snapshot.add(iter.next()) + snapshot + } - class KeySetView[K >: Null, V >: Null] private[ConcurrentHashMap] ( - chm: ConcurrentHashMap[K, V]) extends Set[K] with Serializable { + private final class NodeIterator extends AbstractCHMIterator[Node[K, V]] { + protected[this] def extract(node: Node[K, V]): Node[K, V] = node + } - def size(): Int = chm.size + private final class KeyIterator extends AbstractCHMIterator[K] { + protected[this] def extract(node: Node[K, V]): K = node.key + } - def isEmpty(): Boolean = chm.isEmpty + private final class ValueIterator extends AbstractCHMIterator[V] { + protected[this] def extract(node: Node[K, V]): V = node.value + } - def contains(o: Any): Boolean = chm.containsKey(o) + private abstract class AbstractCHMIterator[A] extends Iterator[A] { + private[this] val innerIter = makeSnapshot().iterator() + private[this] var lastNode: Node[K, V] = _ // null - def iterator(): Iterator[K] = { - new Iterator[K] { - val iter = chm.entrySet.iterator() + protected[this] def extract(node: Node[K, V]): A - def hasNext(): Boolean = iter.hasNext() + def hasNext(): Boolean = + innerIter.hasNext() - def next(): K = iter.next().getKey() + def next(): A = { + val node = innerIter.next() + lastNode = node + extract(node) + } - def remove(): Unit = iter.remove() + def remove(): Unit = { + val last = lastNode + if (last eq null) + throw new IllegalStateException("next must be called at least once before remove") + removeNode(last) + lastNode = null } } + } - def toArray(): Array[AnyRef] = { - val result = new Array[AnyRef](size()) - for (keyAndIdx <- iterator().scalaOps.zipWithIndex.scalaOps) - result(keyAndIdx._2) = keyAndIdx._1.asInstanceOf[AnyRef] - result - } + /* `KeySetView` is a public class in the JDK API. The result of + * `ConcurrentHashMap.keySet()` must be statically typed as a `KeySetView`, + * hence the existence of this class, although it forwards all its operations + * to the inner key set. + */ + class KeySetView[K, V] private[ConcurrentHashMap] (inner: Set[K]) + extends Set[K] with Serializable { - def toArray[T <: AnyRef](a: Array[T]): Array[T] = { - val toFill: Array[T] = - if (a.size >= size) a - else jlr.Array.newInstance(a.getClass.getComponentType, size).asInstanceOf[Array[T]] - - val iter = iterator - for (i <- 0 until size) - toFill(i) = iter.next().asInstanceOf[T] - if (toFill.size > size) - toFill(size) = null.asInstanceOf[T] - toFill - } + def contains(o: Any): Boolean = inner.contains(o) - def add(e: K): Boolean = - throw new UnsupportedOperationException() + def remove(o: Any): Boolean = inner.remove(o) - def remove(o: Any): Boolean = chm.remove(o) != null + def iterator(): Iterator[K] = inner.iterator() - def containsAll(c: Collection[_]): Boolean = - c.scalaOps.forall(item => chm.containsKey(item.asInstanceOf[K])) + def size(): Int = inner.size() - def addAll(c: Collection[_ <: K]): Boolean = - throw new UnsupportedOperationException() + def isEmpty(): Boolean = inner.isEmpty() - def removeAll(c: Collection[_]): Boolean = - removeWhere(c.contains(_)) + def toArray(): Array[AnyRef] = inner.toArray() - def retainAll(c: Collection[_]): Boolean = - removeWhere(!c.contains(_)) + def toArray[T <: AnyRef](a: Array[T]): Array[T] = inner.toArray[T](a) - def clear(): Unit = chm.clear() + def add(e: K): Boolean = inner.add(e) - private def removeWhere(p: Any => Boolean): Boolean = { - val iter = chm.entrySet.iterator - var changed = false - while (iter.hasNext) { - if (p(iter.next().getKey())) { - iter.remove() - changed = true - } - } - changed - } + def containsAll(c: Collection[_]): Boolean = inner.containsAll(c) + + def addAll(c: Collection[_ <: K]): Boolean = inner.addAll(c) + + def removeAll(c: Collection[_]): Boolean = inner.removeAll(c) + + def retainAll(c: Collection[_]): Boolean = inner.retainAll(c) + + def clear(): Unit = inner.clear() } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala index f17cdab590..0cd7286720 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala @@ -14,9 +14,11 @@ package org.scalajs.testsuite.javalib.util.concurrent import scala.language.implicitConversions +import scala.collection.mutable import scala.reflect.ClassTag import java.{util => ju} +import java.util.concurrent.{ConcurrentHashMap => CHM} import org.junit.Assert._ import org.junit.Test @@ -67,6 +69,163 @@ class ConcurrentHashMapTest extends MapTest { assertEquals("456", chm.putIfAbsent("123", "789")) assertEquals("def", chm.putIfAbsent("abc", "jkl")) } + + @Test def testIteratorsAreWeaklyConsistent(): Unit = { + /* The Javadoc says the following about weakly consistent iterators: + * > they are guaranteed to traverse elements as they existed upon + * > construction exactly once, and may (but are not guaranteed to) reflect + * > any modifications subsequent to construction. + * + * The two subsentences seem to be contradictory, notably in terms of + * removal. Experimentation shows that iterators *can* reflect removals + * subsequent to construction. + * + * Therefore, we interpreted that the only actual guarantees are the + * following: + * + * - If a key existed when the iterator was created, and it is not removed, + * then eventually the iterator will yield it. + * - An iterator never yields the same key twice. + * - If an iterator yields a given key, then the associated value can be + * any value associated with the key at some point since the iterator was + * created (but not another, arbitrary value). + * + * This test aims at testing those guarantees, and only those guarantees, + * using random schedulings of concurrent `put` and `remove` operations + * while the iterator is used. + */ + + // Creates a new map with the state before creating the iterator + def createInitialMap(): CHM[String, String] = { + val m = factory.empty[String, String] + m.put("initial", "init") + m.put("there forever", "always") + m.put("mutated", "first value") + for (i <- 0 until 30) + m.put(i.toString(), s"value $i") + m + } + + /* A list of operations that will randomly scheduled concurrently with the + * iteration. + */ + val concurrentOperations = List[CHM[String, String] => Unit]( + _.put("foo", "bar"), + _.put("babar", "baz"), + _.put("babar", "bazbaz"), + _.put("hello", "world"), + _.put("mutated", "second value"), + _.remove("initial"), + _.remove("hello") + ) + + // Per key, the set of values that we can possibly observe + val possibleValuesFor: Map[String, Set[String]] = { + Map( + "initial" -> Set("init"), + "there forever" -> Set("always"), + "mutated" -> Set("first value", "second value"), + "foo" -> Set("bar"), + "babar" -> Set("baz", "bazbaz"), + "hello" -> Set("world") + ) ++ (0 until 30).map(i => i.toString() -> Set(s"value $i")) + } + + // The set of all the values that we can possibly observe (for values()) + val allPossibleValues: Set[String] = possibleValuesFor.flatMap(_._2).toSet + + /* The list of keys that we *must* observe at some point, because they + * exist before the iterator is created, and they are never removed. + */ + val mandatoryKeys: List[String] = + List("there forever", "mutated") ++ (0 until 30).map(_.toString()) + + /* The list of values that we *must* observe at some point, because they + * are uniquely associated with a key that we must observe at some point. + */ + val mandatoryValues: List[String] = + mandatoryKeys.map(possibleValuesFor(_)).filter(_.size == 1).map(_.head) + + // The initial seed was generated at random + val topLevelRandom = new java.util.Random(2972838770879131323L) + + // Test 30 different interleavings for entrySet() + for (_ <- 0 until 30) { + val random = new scala.util.Random(topLevelRandom.nextLong()) + var shuffledOps = random.shuffle(concurrentOperations) + val m = createInitialMap() + val encounteredKeys = mutable.Set.empty[String] + val entryIter = m.entrySet().iterator() + + while (entryIter.hasNext()) { + // Schedule 0-to-many concurrent operations before the next call to next() + while (shuffledOps.nonEmpty && random.nextBoolean()) { + shuffledOps.head(m) + shuffledOps = shuffledOps.tail + } + + val entry = entryIter.next() + val key = entry.getKey() + val value = entry.getValue() + assertTrue(s"duplicate iteration of key '$key'", encounteredKeys.add(key)) + assertTrue(s"unexpected key '$key'", possibleValuesFor.contains(key)) + assertTrue(s"unexpected value '$value' for key '$key'", + possibleValuesFor(key).contains(value)) + } + + for (key <- mandatoryKeys) + assertTrue(s"missing key '$key'", encounteredKeys.contains(key)) + } + + // Test 30 different interleavings for keySet() + for (_ <- 0 until 30) { + val random = new scala.util.Random(topLevelRandom.nextLong()) + var shuffledOps = random.shuffle(concurrentOperations) + val m = createInitialMap() + val encounteredKeys = mutable.Set.empty[String] + val keyIter = m.keySet().iterator() + + while (keyIter.hasNext()) { + // Schedule 0-to-many concurrent operations before the next call to next() + while (shuffledOps.nonEmpty && random.nextBoolean()) { + shuffledOps.head(m) + shuffledOps = shuffledOps.tail + } + + val key = keyIter.next() + assertTrue(s"duplicate iteration of key '$key'", encounteredKeys.add(key)) + assertTrue(s"unexpected key '$key'", possibleValuesFor.contains(key)) + } + + for (key <- mandatoryKeys) + assertTrue(s"missing key '$key'", encounteredKeys.contains(key)) + } + + // Test 30 different interleavings for values() + for (_ <- 0 until 30) { + val random = new scala.util.Random(topLevelRandom.nextLong()) + var shuffledOps = random.shuffle(concurrentOperations) + val m = createInitialMap() + val encounteredValues = mutable.Set.empty[String] + val valueIter = m.values().iterator() + + while (valueIter.hasNext()) { + // Schedule 0-to-many concurrent operations before the next call to next() + while (shuffledOps.nonEmpty && random.nextBoolean()) { + shuffledOps.head(m) + shuffledOps = shuffledOps.tail + } + + val value = valueIter.next() + encounteredValues.add(value) + assertTrue(s"unexpected value '$value'", + allPossibleValues.contains(value)) + } + + for (value <- mandatoryValues) + assertTrue(s"missing value '$value'", encounteredValues.contains(value)) + } + } } object ConcurrentHashMapFactory extends ConcurrentHashMapFactory { From e31c83a79d50a05ad982a418c48938594577ee85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 9 Aug 2019 13:23:05 +0200 Subject: [PATCH 0037/1606] Do not special-case `ArrayBufferInputStream` in `DataInputStream`. The special-case fundamentally requires a dependency from the javalib to the Scala.js standard library, which we want to avoid. --- .../main/scala/java/io/DataInputStream.scala | 76 +++---------------- 1 file changed, 12 insertions(+), 64 deletions(-) diff --git a/javalib/src/main/scala/java/io/DataInputStream.scala b/javalib/src/main/scala/java/io/DataInputStream.scala index 3296a8ef20..e257d46dee 100644 --- a/javalib/src/main/scala/java/io/DataInputStream.scala +++ b/javalib/src/main/scala/java/io/DataInputStream.scala @@ -24,34 +24,6 @@ class DataInputStream(in: InputStream) extends FilterInputStream(in) private var pushedBack: Int = -1 private var pushedBackMark: Int = -1 - /* ArrayBufferInputStream mode helpers - * - * These variables are used to special case on ArrayBufferInputStreams - * They allow directly accessing the underlying ArrayBuffer rather than - * creating byte arrays first. - */ - private val inArrayBufferStream = in match { - case in: ArrayBufferInputStream => in - case _ => null - } - private val hasArrayBuffer = inArrayBufferStream != null - private val bufDataView = { - if (hasArrayBuffer) { - val in = inArrayBufferStream - new DataView(in.buffer, in.offset, in.length) - } else { - null - } - } - - private def consumePos(n: Int) = { - val off = if (pushedBack != -1) 1 else 0 - val resultPos = inArrayBufferStream.pos - off - val toSkip = n - off - if (in.skip(toSkip) != toSkip) eof() - resultPos - } - // General Helpers private def eof() = throw new EOFException() private def pushBack(v: Int) = { pushedBack = v } @@ -65,26 +37,14 @@ class DataInputStream(in: InputStream) extends FilterInputStream(in) res.toByte } - def readChar(): Char = { - if (hasArrayBuffer) - bufDataView.getUint16(consumePos(2)).toChar - else - ((readByte() << 8) | readUnsignedByte()).toChar - } + def readChar(): Char = + ((readByte() << 8) | readUnsignedByte()).toChar - def readDouble(): Double = { - if (hasArrayBuffer) - bufDataView.getFloat64(consumePos(8)) - else - java.lang.Double.longBitsToDouble(readLong()) - } + def readDouble(): Double = + java.lang.Double.longBitsToDouble(readLong()) - def readFloat(): Float = { - if (hasArrayBuffer) - bufDataView.getFloat32(consumePos(4)) - else - java.lang.Float.intBitsToFloat(readInt()) - } + def readFloat(): Float = + java.lang.Float.intBitsToFloat(readInt()) def readFully(b: Array[Byte]): Unit = readFully(b, 0, b.length) @@ -103,12 +63,8 @@ class DataInputStream(in: InputStream) extends FilterInputStream(in) } def readInt(): Int = { - if (hasArrayBuffer) { - bufDataView.getInt32(consumePos(4)) - } else { - (readUnsignedByte() << 24) | (readUnsignedByte() << 16) | - (readUnsignedByte() << 8) | readUnsignedByte() - } + (readUnsignedByte() << 24) | (readUnsignedByte() << 16) | + (readUnsignedByte() << 8) | readUnsignedByte() } def readLine(): String = { @@ -135,12 +91,8 @@ class DataInputStream(in: InputStream) extends FilterInputStream(in) (hi << 32) | (lo & 0xFFFFFFFFL) } - def readShort(): Short = { - if (hasArrayBuffer) - bufDataView.getInt16(consumePos(2)) - else - ((readByte() << 8) | readUnsignedByte()).toShort - } + def readShort(): Short = + ((readByte() << 8) | readUnsignedByte()).toShort def readUnsignedByte(): Int = { val res = read() @@ -148,12 +100,8 @@ class DataInputStream(in: InputStream) extends FilterInputStream(in) res } - def readUnsignedShort(): Int = { - if (hasArrayBuffer) - bufDataView.getUint16(consumePos(2)) - else - (readUnsignedByte() << 8) | readUnsignedByte() - } + def readUnsignedShort(): Int = + (readUnsignedByte() << 8) | readUnsignedByte() def readUTF(): String = { val length = readUnsignedShort() From 634a360e5d9742005911df964f28186137849045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 9 Aug 2019 13:31:23 +0200 Subject: [PATCH 0038/1606] Remove `js.typedarray.ArrayBufferInputStream`. The only reasons this class existed in the core were that: * it could be special-cased in `DataInputStream`, and * it was used in the bootstrapped linker. Now, neither is true anymore, so the existence of `ArrayBufferInputStream` is questionable. Since it was part of the public API of the Scala.js library, its removal is a breaking change. Users of the class can still recover its functionality by copy-pasting the 0.6.x implementation into their own projects. I suspect this shouldn't be too much of an issue, because typed arrays are better suited to be treated as `java.nio.Buffer`s than `java.io.InputStream`s anyway. --- .../typedarray/ArrayBufferInputStream.scala | 101 --------- .../javalib/io/DataInputStreamJSTest.scala | 55 ----- .../ArrayBufferInputStreamTest.scala | 199 ------------------ 3 files changed, 355 deletions(-) delete mode 100644 library/src/main/scala/scala/scalajs/js/typedarray/ArrayBufferInputStream.scala delete mode 100644 test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/io/DataInputStreamJSTest.scala delete mode 100644 test-suite/js/src/test/scala/org/scalajs/testsuite/typedarray/ArrayBufferInputStreamTest.scala diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBufferInputStream.scala b/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBufferInputStream.scala deleted file mode 100644 index 9f930b4883..0000000000 --- a/library/src/main/scala/scala/scalajs/js/typedarray/ArrayBufferInputStream.scala +++ /dev/null @@ -1,101 +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. - */ - -package scala.scalajs.js.typedarray - -import java.io.InputStream - -/** ECMAScript 6 - * A java.io.InputStream wrapping a JavaScript ArrayBuffer - * - * This class is extremely similar to a ByteArrayInputStream, but - * uses ArrayBuffers as the underlying representation. Stream - * implementations may special case on this stream for better - * performance and access the underlying buffer directly. (They still - * need to make sure the internal pointers are properly aligned - * though). - * - * This stream has several public members (n.b. [[buffer]], [[offset]], - * [[length]] and [[pos]]) in order to allow JavaScript aware applications to - * special case on this kind of stream and access the underlying - * [[ArrayBuffer]] directly for efficiency. In this case it is the client's - * responsibility to synchronize [[pos]], as if the stream were read normally - * (if the context in which it is used requires this). - * - * @param buffer Underlying ArrayBuffer - * @param offset Offset in bytes in [[buffer]] - * @param length Length in bytes in [[buffer]] - */ -class ArrayBufferInputStream(val buffer: ArrayBuffer, val offset: Int, - val length: Int) extends InputStream { - - /** Convenience constructor. Strictly equivalent to - * {{new ArrayBufferInputStream(buffer, 0, buffer.byteLength)} - */ - def this(buffer: ArrayBuffer) = this(buffer, 0, buffer.byteLength) - - private val uintView = new Uint8Array(buffer, offset, length) - private val byteView = new Int8Array(buffer, offset, length) - - /** Used to persist [[pos]] when mark is called */ - protected var mark: Int = 0 - - /** Next byte to read in the buffer (after adding offset). - * - * Use [[skip]] to update (protects from overrun and moving backwards). - */ - @inline def pos: Int = _pos - @inline protected def pos_=(x: Int): Unit = _pos = x - private[this] var _pos: Int = 0 - - override def available(): Int = length - pos - override def mark(readlimit: Int): Unit = { mark = pos } - override def markSupported(): Boolean = true - def read(): Int = { - if (pos < length) { - val res = uintView(pos) - pos += 1 - res - } else -1 - } - - override def read(b: Array[Byte], off: Int, reqLen: Int): Int = { - if (off < 0 || reqLen < 0 || reqLen > b.length - off) - throw new IndexOutOfBoundsException - - val len = Math.min(reqLen, length - pos) - - if (reqLen == 0) - 0 // 0 requested, 0 returned - else if (len == 0) - -1 // nothing to read at all - else { - var i = 0 - while (i < len) { - b(i + off) = byteView(pos + i) - i += 1 - } - pos += len - len - } - } - - override def reset(): Unit = { pos = mark } - - /** Skips a given number of bytes. Always skips the maximum number possible */ - override def skip(n: Long): Long = { - val k = Math.max(0, Math.min(n, length - pos)).toInt - pos += k - k.toLong - } - -} diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/io/DataInputStreamJSTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/io/DataInputStreamJSTest.scala deleted file mode 100644 index b3577548f1..0000000000 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/javalib/io/DataInputStreamJSTest.scala +++ /dev/null @@ -1,55 +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. - */ - -package org.scalajs.testsuite.javalib.io - -import java.io.InputStream - -import org.scalajs.testsuite.utils.Platform._ - -import scala.scalajs.js.typedarray._ -import scala.scalajs.js.JSConverters._ - -import org.junit._ -import org.junit.Assume._ - -trait AssumeTypedArrays { - @BeforeClass def assumeTypedArrays(): Unit = { - assumeTrue("Assumed typed arrays", typedArrays) - } -} - -object DataInputStreamArrayBufferInputStreamTest extends AssumeTypedArrays - -class DataInputStreamArrayBufferInputStreamTest extends DataInputStreamTest { - protected def inFromBytes(bytes: Seq[Byte]): InputStream = - new ArrayBufferInputStream(new Int8Array(bytes.toJSArray).buffer) -} - -object DataInputStreamArrayPartiallyConsumedArrayBufferInputStreamTest - extends AssumeTypedArrays - -class DataInputStreamArrayPartiallyConsumedArrayBufferInputStreamTest - extends DataInputStreamTest { - - protected def inFromBytes(bytes: Seq[Byte]): InputStream = { - val addBytes = Seq[Byte](0, 0, 0, 0) - val allBytes = addBytes ++ bytes - val in = new ArrayBufferInputStream( - new Int8Array(allBytes.toJSArray).buffer) - - for (_ <- addBytes) - in.read() - - in - } -} diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/typedarray/ArrayBufferInputStreamTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/typedarray/ArrayBufferInputStreamTest.scala deleted file mode 100644 index 4f93d394f0..0000000000 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/typedarray/ArrayBufferInputStreamTest.scala +++ /dev/null @@ -1,199 +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. - */ - -package org.scalajs.testsuite.typedarray - -import org.scalajs.testsuite.utils.Requires - -import scala.language.implicitConversions - -import java.io.InputStream - -import scala.scalajs.js -import scala.scalajs.js.typedarray._ - -import org.junit.Assert._ -import org.junit.Test - -import org.scalajs.testsuite.utils.AssertThrows._ - -/** Tests for our implementation of java.io._ stream classes */ -trait ArrayBufferInputStreamTest { - - def byteArray(a: TraversableOnce[Int]): Array[Byte] = { - a.toArray.map(_.toByte) - } - - def mkStream(seq: Seq[Int]): InputStream - - private val length = 50 - - private def newStream: InputStream = mkStream(1 to length) - - @Test def read(): Unit = { - val stream = newStream - - for (i <- 1 to length) - assertEquals(i, stream.read()) - - for (_ <- 1 to 5) - assertEquals(-1, stream.read()) - } - - @Test def read_buf(): Unit = { - val stream = newStream - val buf = new Array[Byte](10) - - assertEquals(10, stream.read(buf)) - assertArrayEquals(byteArray(1 to 10), buf) - - assertEquals(35L, stream.skip(35)) - - assertEquals(5, stream.read(buf)) - assertArrayEquals(byteArray((46 to 50) ++ (6 to 10)), buf) - - assertEquals(-1, stream.read(buf)) - assertEquals(-1, stream.read()) - } - - @Test def read_full_argument(): Unit = { - val stream = newStream - val buf = new Array[Byte](20) - - assertEquals(5, stream.read(buf, 10, 5)) - assertArrayEquals(byteArray(Seq.fill(10)(0) ++ (1 to 5) ++ Seq.fill(5)(0)), buf) - - assertEquals(20, stream.read(buf, 0, 20)) - assertArrayEquals(byteArray(6 to 25), buf) - - assertEquals(0, stream.read(buf, 10, 0)) - assertArrayEquals(byteArray(6 to 25), buf) - - expectThrows(classOf[IndexOutOfBoundsException], stream.read(buf, -1, 0)) - expectThrows(classOf[IndexOutOfBoundsException], stream.read(buf, 0, -1)) - expectThrows(classOf[IndexOutOfBoundsException], stream.read(buf, 100, 0)) - expectThrows(classOf[IndexOutOfBoundsException], stream.read(buf, 10, 100)) - assertArrayEquals(byteArray(6 to 25), buf) - - assertEquals(20L, stream.skip(20)) - - assertEquals(5, stream.read(buf, 0, 10)) - assertArrayEquals(byteArray((46 to 50) ++ (11 to 25)), buf) - - assertEquals(-1, stream.read(buf, 0, 10)) - assertEquals(0, stream.read(buf, 0, 0)) - assertArrayEquals(byteArray((46 to 50) ++ (11 to 25)), buf) - } - - @Test def available(): Unit = { - val stream = newStream - - def mySkip(n: Int) = for (_ <- 1 to n) assertNotEquals(-1, stream.read()) - def check(n: Int) = assertEquals(n, stream.available) - - check(50) - mySkip(5) - check(45) - assertEquals(10L, stream.skip(10)) - check(35) - mySkip(30) - check(5) - assertEquals(5L, stream.skip(20)) - check(0) - } - - @Test def skip(): Unit = { - val stream = newStream - - assertEquals(7L, stream.skip(7)) - - for (i <- 8 to 32) - assertEquals(i, stream.read()) - - assertEquals(0L, stream.skip(0)) - assertEquals(33, stream.read()) - assertEquals(0L, stream.skip(-4)) - assertEquals(34, stream.read()) - - assertEquals(16L, stream.skip(30)) - assertEquals(0L, stream.skip(30)) - } - - @Test def markSupported(): Unit = { - assertTrue(newStream.markSupported) - } - - @Test def close(): Unit = { - val stream = newStream - - for (i <- 1 to length) { - stream.close() - assertEquals(i, stream.read()) - } - } - - @Test def mark_reset(): Unit = { - val stream = newStream - - def read(range: Range) = for (i <- range) assertEquals(i, stream.read()) - - read(1 to 10) - stream.reset() // mark must be 0 at creation - read(1 to 5) - stream.mark(length) - read(6 to 22) - stream.reset() - read(6 to 20) - stream.reset() - read(6 to 25) - stream.reset() - assertEquals(40L, stream.skip(40)) - stream.mark(length) - read(46 to 50) - stream.reset() - read(46 to 50) - stream.mark(length) - assertEquals(-1, stream.read()) - stream.reset() - assertEquals(-1, stream.read()) - } - - @Test def should_return_positive_integers_when_calling_read(): Unit = { - val stream = mkStream(Seq(-1, -2, -3)) - assertEquals(255, stream.read()) - assertEquals(254, stream.read()) - assertEquals(253, stream.read()) - assertEquals(-1, stream.read()) - } -} - -object ArrayBufferInputStreamWithoutOffsetTest extends Requires.TypedArray - -class ArrayBufferInputStreamWithoutOffsetTest extends ArrayBufferInputStreamTest { - def mkStream(seq: Seq[Int]): InputStream = { - import js.JSConverters._ - new ArrayBufferInputStream(new Int8Array(seq.toJSArray).buffer) - } -} - -object ArrayBufferInputStreamWithOffsetTest extends Requires.TypedArray - -class ArrayBufferInputStreamWithOffsetTest extends ArrayBufferInputStreamTest { - def mkStream(seq: Seq[Int]): InputStream = { - import js.JSConverters._ - val off = 100 - val data = new Int8Array(seq.size + off) - data.set(seq.toJSArray, off) - - new ArrayBufferInputStream(data.buffer, off, seq.size) - } -} From d0d04a5a8a785cc0dbe9186325a275413ca59f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 13 Aug 2019 10:55:46 +0200 Subject: [PATCH 0039/1606] Remove dead code in the javalib test suites. --- .../javalib/util/CollectionTest.scala | 13 ----------- .../testsuite/javalib/util/ListTest.scala | 21 ----------------- .../testsuite/javalib/util/MapTest.scala | 23 ------------------- .../testsuite/javalib/util/SetTest.scala | 14 ----------- .../javalib/util/SortedMapTest.scala | 10 -------- .../javalib/util/SortedSetTest.scala | 9 -------- 6 files changed, 90 deletions(-) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala index 8f40d3e734..78de1a1619 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala @@ -27,19 +27,6 @@ trait CollectionTest { def factory: CollectionFactory - def testCollectionApi(): Unit = { - shouldStoreStrings() - shouldStoreIntegers() - shouldStoreDoubles() - shouldStoreCustomObjects() - shouldRemoveStoredElements() - shouldRemoveStoredElementsOnDoubleCornerCases() - shouldBeClearedWithOneOperation() - shouldCheckContainedPresence() - shouldCheckContainedPresenceForDoubleCornerCases() - shouldGiveProperIteratorOverElements() - } - @Test def shouldStoreStrings(): Unit = { val coll = factory.empty[String] diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala index bd31245779..bb40a8764b 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala @@ -25,27 +25,6 @@ trait ListTest extends CollectionTest { def factory: ListFactory - def testListApi(): Unit = { - testCollectionApi() - shouldStoreStrings_List() - shouldStoreIntegers_List() - shouldStoreDoubles_List() - shouldStoreCustomObjects_List() - shouldRemoveStoredElements_List() - shouldRemoveStoredElementsOnDoubleCornerCases_List() - shouldBeClearedWithOneOperation_List() - shouldCheckContainedPresence_List() - shouldCheckContainedPresenceForDoubleCornerCases_List() - shouldGiveAProperSetOperation() - shouldGiveProperIteratorOverElements_List() - shouldGiveProperListIteratorOverElements() - shouldAddElementsAtAGivenIndex() - shouldGiveTheFirstIndexOfAnElement() - shouldGiveTheFirstOrLastIndexOfAnElementForDoubleCornerCases() - shouldGiveASublistBackedUpByTheOriginalList() - shouldIterateAndModifyElementsWithAListIteratorIfAllowed() - } - @Test def shouldStoreStrings_List(): Unit = { val lst = factory.empty[String] diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala index 86b8cfe6dd..856c0f91c8 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala @@ -28,29 +28,6 @@ trait MapTest { def factory: MapFactory - def testMapApi(): Unit = { - should_store_strings() - should_store_integers() - should_store_doubles_also_in_corner_cases() - should_store_custom_objects() - should_remove_stored_elements() - should_remove_stored_elements_on_double_corner_cases() - should_put_or_fail_on_null_keys() - should_put_or_fail_on_null_values() - should_be_cleared_with_one_operation() - should_check_contained_key_presence() - should_check_contained_value_presence() - should_give_proper_Collection_over_values() - should_give_proper_EntrySet_over_key_values_pairs() - should_put_a_whole_map_into() - values_should_mirror_the_related_map_size() - values_should_check_single_and_multiple_objects_presence() - values_should_side_effect_clear_remove_retain_on_the_related_map() - keySet_should_mirror_the_related_map_size() - keySet_should_check_single_and_multiple_objects_presence() - keySet_should_side_effect_clear_remove_retain_on_the_related_map() - } - @Test def should_store_strings(): Unit = { val mp = factory.empty[String, String] diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala index df0eb2053c..3210f42e35 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala @@ -28,20 +28,6 @@ trait SetTest extends CollectionTest { def factory: SetFactory - def testSetApi(): Unit = { - testCollectionApi() - shouldCheckSetSize() - shouldStoreIntegers_Set() - shouldStoreObjectsWithSameHashCodeButDifferentTypes() - shouldStoreDoublesAlsoInCornerCases() - shouldStoreCustomObjects_Set() - shouldRemoveStoredElements_Set() - shouldBeClearedWithOneOperation_Set() - shouldCheckContainedElemsPresence() - shouldPutAWholeCollectionInto() - shouldIterateOverElements() - } - @Test def shouldCheckSetSize(): Unit = { val hs = factory.empty[String] diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedMapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedMapTest.scala index c1dfeec498..7822eadb29 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedMapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedMapTest.scala @@ -23,16 +23,6 @@ trait SortedMapTest extends MapTest { def factory: SortedMapFactory - def testSortedMapApi(): Unit = { - testMapApi() - should_always_be_sorted() - should_return_the_firstKey() - should_return_the_lastKey() - should_return_a_proper_headMap() - should_return_a_proper_tailMap() - should_return_a_proper_subMap() - } - // TODO: implement tests (when we port the first SortedMap) @Test def should_always_be_sorted(): Unit = { diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala index fa55f4ae48..c3f646ff54 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala @@ -23,15 +23,6 @@ trait SortedSetTest extends SetTest { def factory: SortedSetFactory - def testSortedSetApi(): Unit = { - testSetApi() - shouldRetrieveTheFirstElement() - shouldRetrieveTheLastElement() - shouldReturnAProperHeadSet() - shouldReturnAProperTailSet() - shouldReturnAProperSubSet() - } - @Test def shouldRetrieveTheFirstElement(): Unit = { val ssInt = factory.empty[Int] From a5c1d2095df42166e75974a39232898e982e5836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 13 Aug 2019 15:19:16 +0200 Subject: [PATCH 0040/1606] Enhance the test suite for `java.util.Map`s. * Add more tests for custom objects, for which `eq` is not the same as `equals()`. * Add more tests for views, in particular for `entrySet()` views which were hardly covered. * Do not rely on Scala collections and their Java views for the tests. In the process, we also fix `m.values().contains(null)` for `NullRejectingHashMap`, which previously failed to throw a `NullPointerException`. --- .../src/main/scala/java/util/HashMap.scala | 3 + .../testsuite/javalib/util/MapTest.scala | 814 +++++++++++++----- .../javalib/util/TrivialImmutableMap.scala | 51 ++ 3 files changed, 665 insertions(+), 203 deletions(-) create mode 100644 test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TrivialImmutableMap.scala diff --git a/javalib/src/main/scala/java/util/HashMap.scala b/javalib/src/main/scala/java/util/HashMap.scala index 34ee6e485d..8968bbb738 100644 --- a/javalib/src/main/scala/java/util/HashMap.scala +++ b/javalib/src/main/scala/java/util/HashMap.scala @@ -421,6 +421,9 @@ class HashMap[K, V](initialCapacity: Int, loadFactor: Double) def size(): Int = self.size() + override def contains(o: Any): Boolean = + containsValue(o) + override def clear(): Unit = self.clear() } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala index 856c0f91c8..80c2d14b12 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala @@ -20,15 +20,14 @@ import org.junit.Assert._ import org.scalajs.testsuite.javalib.util.concurrent.ConcurrentMapFactory import org.scalajs.testsuite.utils.AssertThrows._ -import scala.collection.JavaConverters._ -import scala.collection.{mutable => mu} import scala.reflect.ClassTag trait MapTest { + import MapTest._ def factory: MapFactory - @Test def should_store_strings(): Unit = { + @Test def testSizeGetPutWithStrings(): Unit = { val mp = factory.empty[String, String] assertEquals(0, mp.size()) @@ -38,9 +37,18 @@ trait MapTest { mp.put("TWO", "two") assertEquals(2, mp.size()) assertEquals("two", mp.get("TWO")) + mp.put("ONE", "three") + assertEquals(2, mp.size()) + assertEquals("three", mp.get("ONE")) + + assertEquals(null, mp.get("THREE")) + assertEquals(null, mp.get(42)) + assertEquals(null, mp.get(TestObj(42))) + if (factory.allowsNullKeysQueries) + assertEquals(null, mp.get(null)) } - @Test def should_store_strings_large_map(): Unit = { + @Test def testSizeGetPutWithStringsLargeMap(): Unit = { val largeMap = factory.empty[String, Int] for (i <- 0 until 1000) largeMap.put(i.toString(), i) @@ -49,18 +57,35 @@ trait MapTest { for (i <- (1000 - expectedSize) until 1000) assertEquals(i, largeMap.get(i.toString())) assertNull(largeMap.get("1000")) + + assertEquals(null, largeMap.get("THREE")) + assertEquals(null, largeMap.get(42)) + assertEquals(null, largeMap.get(TestObj(42))) + if (factory.allowsNullKeysQueries) + assertEquals(null, largeMap.get(null)) } - @Test def should_store_integers(): Unit = { + @Test def testSizeGetPutWithInts(): Unit = { val mp = factory.empty[Int, Int] mp.put(100, 12345) assertEquals(1, mp.size()) - val one = mp.get(100) - assertEquals(12345, one) + assertEquals(12345, mp.get(100)) + mp.put(150, 54321) + assertEquals(2, mp.size()) + assertEquals(54321, mp.get(150)) + mp.put(100, 3) + assertEquals(2, mp.size()) + assertEquals(3, mp.get(100)) + + assertEquals(null, mp.get(42)) + assertEquals(null, mp.get("THREE")) + assertEquals(null, mp.get(TestObj(42))) + if (factory.allowsNullKeysQueries) + assertEquals(null, mp.get(null)) } - @Test def should_store_integers_large_map(): Unit = { + @Test def testSizeGetPutWithIntsLargeMap(): Unit = { val largeMap = factory.empty[Int, Int] for (i <- 0 until 1000) largeMap.put(i, i * 2) @@ -69,9 +94,52 @@ trait MapTest { for (i <- (1000 - expectedSize) until 1000) assertEquals(i * 2, largeMap.get(i)) assertNull(largeMap.get(1000)) + + assertEquals(null, largeMap.get(-42)) + assertEquals(null, largeMap.get("THREE")) + assertEquals(null, largeMap.get(TestObj(42))) + if (factory.allowsNullKeysQueries) + assertEquals(null, largeMap.get(null)) } - @Test def should_store_doubles_also_in_corner_cases(): Unit = { + @Test def testSizeGetPutWithCustomObjects(): Unit = { + val mp = factory.empty[TestObj, TestObj] + + mp.put(TestObj(100), TestObj(12345)) + assertEquals(1, mp.size()) + assertEquals(12345, mp.get(TestObj(100)).num) + mp.put(TestObj(150), TestObj(54321)) + assertEquals(2, mp.size()) + assertEquals(54321, mp.get(TestObj(150)).num) + mp.put(TestObj(100), TestObj(3)) + assertEquals(2, mp.size()) + assertEquals(3, mp.get(TestObj(100)).num) + + assertEquals(null, mp.get("THREE")) + assertEquals(null, mp.get(42)) + assertEquals(null, mp.get(TestObj(42))) + if (factory.allowsNullKeysQueries) + assertEquals(null, mp.get(null)) + } + + @Test def testSizeGetPutWithCustomObjectsLargeMap(): Unit = { + val largeMap = factory.empty[TestObj, Int] + for (i <- 0 until 1000) + largeMap.put(TestObj(i), i * 2) + val expectedSize = factory.withSizeLimit.fold(1000)(Math.min(_, 1000)) + assertEquals(expectedSize, largeMap.size()) + for (i <- (1000 - expectedSize) until 1000) + assertEquals(i * 2, largeMap.get(TestObj(i))) + assertNull(largeMap.get(1000)) + + assertEquals(null, largeMap.get(TestObj(-42))) + assertEquals(null, largeMap.get("THREE")) + assertEquals(null, largeMap.get(42)) + if (factory.allowsNullKeysQueries) + assertEquals(null, largeMap.get(null)) + } + + @Test def testSizeGetPutWithDoublesCornerCasesOfEquals(): Unit = { val mp = factory.empty[Double, Double] mp.put(1.2345, 11111.0) @@ -95,28 +163,61 @@ trait MapTest { assertEquals(44444.0, four, 0.0) } - @Test def should_store_custom_objects(): Unit = { - case class TestObj(num: Int) - - val mp = factory.empty[TestObj, TestObj] - - mp.put(TestObj(100), TestObj(12345)) - assertEquals(1, mp.size()) - val one = mp.get(TestObj(100)) - assertEquals(12345, one.num) - } - - @Test def should_remove_stored_elements(): Unit = { + @Test def testRemoveWithStrings(): Unit = { val mp = factory.empty[String, String] mp.put("ONE", "one") - assertEquals(1, mp.size()) + for (i <- 0 until 30) + mp.put(s"key $i", s"value $i") + assertEquals(31, mp.size()) assertEquals("one", mp.remove("ONE")) - val newOne = mp.get("ONE") assertNull(mp.get("ONE")) + assertNull(mp.remove("ONE")) + + assertNull(mp.remove("foobar")) + assertNull(mp.remove(42)) + assertNull(mp.remove(TestObj(42))) + if (factory.allowsNullKeys) + assertNull(mp.remove(null)) } - @Test def should_remove_stored_elements_on_double_corner_cases(): Unit = { + @Test def testRemoveWithInts(): Unit = { + val mp = factory.empty[Int, String] + + mp.put(543, "one") + for (i <- 0 until 30) + mp.put(i, s"value $i") + assertEquals(31, mp.size()) + assertEquals("one", mp.remove(543)) + assertNull(mp.get(543)) + assertNull(mp.remove(543)) + + assertNull(mp.remove("foobar")) + assertNull(mp.remove(42)) + assertNull(mp.remove(TestObj(42))) + if (factory.allowsNullKeys) + assertNull(mp.remove(null)) + } + + @Test def testRemoveWithCustomObjects(): Unit = { + val mp = factory.empty[TestObj, String] + + mp.put(TestObj(543), "one") + for (i <- 0 until 30) + mp.put(TestObj(i), s"value $i") + assertEquals(31, mp.size()) + assertEquals("one", mp.remove(TestObj(543))) + assertNull(mp.get(TestObj(543))) + assertNull(mp.remove(TestObj(543))) + + assertNull(mp.remove(TestObj(42))) + assertNull(mp.remove("foobar")) + assertNull(mp.remove(42)) + if (factory.allowsNullKeys) + assertNull(mp.remove(null)) + } + + @Test def testRemoveWithDoublesCornerCasesOfEquals(): Unit = { val mp = factory.empty[Double, String] mp.put(1.2345, "11111.0") @@ -149,29 +250,41 @@ trait MapTest { assertTrue(mp.isEmpty) } - @Test def should_put_or_fail_on_null_keys(): Unit = { + @Test def testGetPutRemoveNullKeys(): Unit = { + val mp = factory.empty[String, String] + for (i <- 0 until 30) + mp.put(s"key $i", s"value $i") + if (factory.allowsNullKeys) { - val mp = factory.empty[String, String] mp.put(null, "one") + assertEquals(31, mp.size()) assertEquals("one", mp.get(null)) + assertEquals("one", mp.remove(null)) + assertNull(mp.get(null)) + assertNull(mp.remove(null)) } else { - val mp = factory.empty[String, String] expectThrows(classOf[NullPointerException], mp.put(null, "one")) } } - @Test def should_put_or_fail_on_null_values(): Unit = { + @Test def testGetPutRemoveNullValues(): Unit = { + val mp = factory.empty[String, String] + for (i <- 0 until 30) + mp.put(s"key $i", s"value $i") + if (factory.allowsNullValues) { - val mp = factory.empty[String, String] mp.put("one", null) + assertEquals(31, mp.size()) + assertNull(mp.get("one")) + assertNull(mp.remove("one")) + assertEquals(30, mp.size()) assertNull(mp.get("one")) } else { - val mp = factory.empty[String, String] expectThrows(classOf[NullPointerException], mp.put("one", null)) } } - @Test def should_be_cleared_with_one_operation(): Unit = { + @Test def testClear(): Unit = { val mp = factory.empty[String, String] mp.put("ONE", "one") @@ -179,9 +292,17 @@ trait MapTest { assertEquals(2, mp.size()) mp.clear() assertEquals(0, mp.size()) + assertNull(mp.get("ONE")) + assertNull(mp.get("TWO")) + + // can be reused after clear() + mp.put("TWO", "value 2") + mp.put("THREE", "value 3") + assertEquals("value 2", mp.get("TWO")) + assertEquals("value 3", mp.get("THREE")) } - @Test def should_check_contained_key_presence(): Unit = { + @Test def testContainsKey(): Unit = { val mp = factory.empty[String, String] mp.put("ONE", "one") @@ -190,10 +311,10 @@ trait MapTest { if (factory.allowsNullKeysQueries) assertFalse(mp.containsKey(null)) else - expectThrows(classOf[Throwable], mp.containsKey(null)) + expectThrows(classOf[NullPointerException], mp.containsKey(null)) } - @Test def should_check_contained_value_presence(): Unit = { + @Test def testContainsValue(): Unit = { val mp = factory.empty[String, String] mp.put("ONE", "one") @@ -202,14 +323,37 @@ trait MapTest { if (factory.allowsNullValuesQueries) assertFalse(mp.containsValue(null)) else - expectThrows(classOf[Throwable], mp.containsValue(null)) + expectThrows(classOf[NullPointerException], mp.containsValue(null)) } - @Test def should_give_proper_Collection_over_values(): Unit = { + @Test def testPutAll(): Unit = { val mp = factory.empty[String, String] mp.put("ONE", "one") - val values = mp.values + + mp.putAll(TrivialImmutableMap("X" -> "y", "A" -> "b")) + assertEquals(3, mp.size()) + assertEquals("one", mp.get("ONE")) + assertEquals("y", mp.get("X")) + assertEquals("b", mp.get("A")) + + val nullMap = TrivialImmutableMap((null: String) -> "y", "X" -> "z") + if (factory.allowsNullKeys) { + mp.putAll(nullMap) + assertEquals("y", mp.get(null)) + assertEquals("z", mp.get("X")) + assertEquals("one", mp.get("ONE")) + assertEquals("b", mp.get("A")) + } else { + expectThrows(classOf[NullPointerException], mp.putAll(nullMap)) + } + } + + @Test def testValuesSizeIteratorBasic(): Unit = { + val mp = factory.empty[String, String] + mp.put("ONE", "one") + + val values = mp.values() assertEquals(1, values.size) val iter = values.iterator assertTrue(iter.hasNext) @@ -217,14 +361,12 @@ trait MapTest { assertFalse(iter.hasNext) } - @Test def should_give_proper_EntrySet_over_key_values_pairs(): Unit = { + @Test def testEntrySetSizeIteratorBasic(): Unit = { val mp = factory.empty[String, String] - mp.put("ONE", "one") - val entrySet = mp.entrySet + val entrySet = mp.entrySet() assertEquals(1, entrySet.size) - val iter = entrySet.iterator assertTrue(iter.hasNext) val next = iter.next @@ -233,126 +375,99 @@ trait MapTest { assertEquals("one", next.getValue) } - @Test def should_give_proper_KeySet_over_keys(): Unit = { + @Test def testKeySetSizeIteratorBasic(): Unit = { val mp = factory.empty[String, String] - mp.put("ONE", "one") - val keySet = mp.keySet + val keySet = mp.keySet() assertEquals(1, keySet.size) - val iter = keySet.iterator assertTrue(iter.hasNext) assertEquals("ONE", iter.next) assertFalse(iter.hasNext) } - @Test def should_put_a_whole_map_into(): Unit = { + @Test def testValuesIsViewForSize(): Unit = { val mp = factory.empty[String, String] - - val m = mu.Map[String, String]( - "X" -> "y") - mp.putAll(m.asJava) - assertEquals(1, mp.size) - assertEquals("y", mp.get("X")) - - val nullMap = mu.Map[String, String]( - (null: String) -> "y", - "X" -> "y") - - if (factory.allowsNullKeys) { - mp.putAll(nullMap.asJava) - assertEquals("y", mp.get(null)) - assertEquals("y", mp.get("X")) - } else { - expectThrows(classOf[NullPointerException], mp.putAll(nullMap.asJava)) - } - } - - class SimpleQueryableMap[K, V](inner: mu.HashMap[K, V]) - extends ju.AbstractMap[K, V] { - def entrySet(): java.util.Set[java.util.Map.Entry[K, V]] = { - inner.map { - case (k, v) => new ju.AbstractMap.SimpleImmutableEntry(k, v) - }.toSet[java.util.Map.Entry[K, V]].asJava - } - } - - @Test def values_should_mirror_the_related_map_size(): Unit = { - val mp = factory.empty[String, String] - mp.put("ONE", "one") mp.put("TWO", "two") + val values = mp.values() - val values = mp.values assertEquals(2, values.size) - mp.put("THREE", "three") - assertEquals(3, values.size) - mp.remove("ONE") - assertEquals(2, values.size) - assertFalse(values.isEmpty) - mp.clear() - assertEquals(0, values.size) - assertTrue(values.isEmpty) + } - val hm1 = mu.HashMap( - "ONE" -> "one", - "TWO" -> "two") - val hm2 = mu.HashMap( - "ONE" -> null, - "TWO" -> "two") - val hm3 = mu.HashMap( - (null: String) -> "one", - "TWO" -> "two") - val hm4 = mu.HashMap( - (null: String) -> null, - "TWO" -> "two") - - assertEquals(2, new SimpleQueryableMap(hm1).values.size) - assertEquals(2, new SimpleQueryableMap(hm2).values.size) - assertEquals(2, new SimpleQueryableMap(hm3).values.size) - assertEquals(2, new SimpleQueryableMap(hm4).values.size) - } - - @Test def values_should_check_single_and_multiple_objects_presence(): Unit = { + @Test def testValuesIsViewForQueriesWithStrings(): Unit = { val mp = factory.empty[String, String] - mp.put("ONE", "one") mp.put("TWO", "two") + val values = mp.values() - val values = mp.values assertTrue(values.contains("one")) assertTrue(values.contains("two")) assertFalse(values.contains("three")) if (factory.allowsNullValuesQueries) assertFalse(values.contains(null)) else - expectThrows(classOf[Throwable], mp.asScala.contains(null)) + expectThrows(classOf[NullPointerException], values.contains(null)) mp.put("THREE", "three") assertTrue(values.contains("three")) - val coll1 = Set("one", "two", "three").asJavaCollection + val coll1 = ju.Arrays.asList("one", "two", "three") assertTrue(values.containsAll(coll1)) - val coll2 = Set("one", "two", "three", "four").asJavaCollection + val coll2 = ju.Arrays.asList("one", "two", "three", "four") assertFalse(values.containsAll(coll2)) - val coll3 = Set("one", "two", "three", null).asJavaCollection + if (factory.allowsNullValuesQueries) { + val coll3 = ju.Arrays.asList("one", "two", "three", null) + assertFalse(values.containsAll(coll3)) + } + } + + @Test def testValuesIsViewForQueriesWithCustomObjects(): Unit = { + val mp = factory.empty[TestObj, TestObj] + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + val values = mp.values() + + assertTrue(values.contains(TestObj(11))) + assertTrue(values.contains(TestObj(22))) + assertFalse(values.contains(TestObj(33))) + if (factory.allowsNullValuesQueries) + assertFalse(values.contains(null)) + else + expectThrows(classOf[NullPointerException], values.contains(null)) + + mp.put(TestObj(3), TestObj(33)) + + assertTrue(values.contains(TestObj(33))) + + val coll1 = ju.Arrays.asList(TestObj(11), TestObj(22), TestObj(33)) + assertTrue(values.containsAll(coll1)) + + val coll2 = ju.Arrays.asList(TestObj(11), TestObj(22), TestObj(33), TestObj(44)) assertFalse(values.containsAll(coll2)) + if (factory.allowsNullValuesQueries) { + val coll3 = ju.Arrays.asList(TestObj(11), TestObj(22), TestObj(33), null) + assertFalse(values.containsAll(coll3)) + } + } + + @Test def testValuesIsViewForQueriesWithDoublesCornerCaseOfEquals(): Unit = { val nummp = factory.empty[Double, Double] + val numValues = nummp.values() - val numValues = nummp.values nummp.put(1, +0.0) assertTrue(numValues.contains(+0.0)) assertFalse(numValues.contains(-0.0)) @@ -367,33 +482,14 @@ trait MapTest { assertTrue(numValues.contains(+0.0)) assertTrue(numValues.contains(-0.0)) assertTrue(numValues.contains(Double.NaN)) - - val hm1 = mu.HashMap( - 1.0 -> null, - 2.0 -> 2.0) - val hm2 = mu.HashMap( - (null: Any) -> 1.0, - 2.0 -> 2.0) - val hm3 = mu.HashMap( - (null: Any) -> null, - 2.0 -> 2.0) - - assertFalse(new SimpleQueryableMap(hm1).values.contains(1.0)) - assertTrue(new SimpleQueryableMap(hm2).values.contains(1.0)) - assertFalse(new SimpleQueryableMap(hm3).values.contains(1.0)) - - assertTrue(new SimpleQueryableMap(hm1).values.contains(null)) - assertFalse(new SimpleQueryableMap(hm2).values.contains(null)) - assertTrue(new SimpleQueryableMap(hm3).values.contains(null)) } - @Test def values_should_side_effect_clear_remove_retain_on_the_related_map(): Unit = { + @Test def testValuesIsViewForRemoveWithStrings(): Unit = { val mp = factory.empty[String, String] - mp.put("ONE", "one") mp.put("TWO", "two") + val values = mp.values() - val values = mp.values assertFalse(values.isEmpty) assertFalse(mp.isEmpty) @@ -406,9 +502,7 @@ trait MapTest { mp.put("TWO", "two") assertTrue(mp.containsKey("ONE")) - values.remove("one") - assertFalse(mp.containsKey("ONE")) mp.put("ONE", "one") @@ -418,7 +512,7 @@ trait MapTest { assertTrue(mp.containsKey("TWO")) assertTrue(mp.containsKey("THREE")) - values.removeAll(List("one", "two").asJavaCollection) + values.removeAll(ju.Arrays.asList("one", "two")) assertFalse(mp.containsKey("ONE")) assertFalse(mp.containsKey("TWO")) @@ -432,91 +526,143 @@ trait MapTest { assertTrue(mp.containsKey("TWO")) assertTrue(mp.containsKey("THREE")) - values.retainAll(List("one", "two").asJavaCollection) + values.retainAll(ju.Arrays.asList("one", "two")) assertTrue(mp.containsKey("ONE")) assertTrue(mp.containsKey("TWO")) assertFalse(mp.containsKey("THREE")) } - @Test def keySet_should_mirror_the_related_map_size(): Unit = { - val mp = factory.empty[String, String] + @Test def testValuesIsViewForRemoveWithCustomObjects(): Unit = { + val mp = factory.empty[TestObj, TestObj] + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + val values = mp.values() + + assertFalse(values.isEmpty) + assertFalse(mp.isEmpty) + + values.clear() + + assertTrue(values.isEmpty) + assertTrue(mp.isEmpty) + + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + + assertTrue(mp.containsKey(TestObj(1))) + values.remove(TestObj(11)) + assertFalse(mp.containsKey(TestObj(1))) + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(3), TestObj(33)) + + assertTrue(mp.containsKey(TestObj(1))) + assertTrue(mp.containsKey(TestObj(2))) + assertTrue(mp.containsKey(TestObj(3))) + + values.removeAll(ju.Arrays.asList(TestObj(11), TestObj(22))) + + assertFalse(mp.containsKey(TestObj(1))) + assertFalse(mp.containsKey(TestObj(2))) + assertTrue(mp.containsKey(TestObj(3))) + + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + mp.put(TestObj(3), TestObj(33)) + + assertTrue(mp.containsKey(TestObj(1))) + assertTrue(mp.containsKey(TestObj(2))) + assertTrue(mp.containsKey(TestObj(3))) + + values.retainAll(ju.Arrays.asList(TestObj(11), TestObj(22))) + + assertTrue(mp.containsKey(TestObj(1))) + assertTrue(mp.containsKey(TestObj(2))) + assertFalse(mp.containsKey(TestObj(3))) + } + + @Test def testKeySetIsViewForSize(): Unit = { + val mp = factory.empty[String, String] mp.put("ONE", "one") mp.put("TWO", "two") + val keySet = mp.keySet() - val keySet = mp.keySet assertEquals(2, keySet.size) - mp.put("THREE", "three") - assertEquals(3, keySet.size) - mp.remove("ONE") - assertEquals(2, keySet.size) - assertFalse(keySet.isEmpty) - mp.clear() - assertEquals(0, keySet.size) - assertTrue(keySet.isEmpty) + } - val hm1 = mu.HashMap( - "ONE" -> "one", - "TWO" -> "two") - val hm2 = mu.HashMap( - "ONE" -> null, - "TWO" -> "two") - val hm3 = mu.HashMap( - (null: String) -> "one", - "TWO" -> "two") - val hm4 = mu.HashMap( - (null: String) -> null, - "TWO" -> "two") - - assertEquals(2, new SimpleQueryableMap(hm1).keySet.size) - assertEquals(2, new SimpleQueryableMap(hm2).keySet.size) - assertEquals(2, new SimpleQueryableMap(hm3).keySet.size) - assertEquals(2, new SimpleQueryableMap(hm4).keySet.size) - } - - @Test def keySet_should_check_single_and_multiple_objects_presence(): Unit = { + @Test def testKeySetIsViewForQueriesWithStrings(): Unit = { val mp = factory.empty[String, String] - mp.put("ONE", "one") mp.put("TWO", "two") + val keySet = mp.keySet() - val keySet = mp.keySet assertTrue(keySet.contains("ONE")) assertTrue(keySet.contains("TWO")) assertFalse(keySet.contains("THREE")) if (factory.allowsNullKeysQueries) assertFalse(keySet.contains(null)) else - expectThrows(classOf[Throwable], mp.asScala.contains(null)) + expectThrows(classOf[NullPointerException], keySet.contains(null)) mp.put("THREE", "three") assertTrue(keySet.contains("THREE")) - val coll1 = - Set("ONE", "TWO", "THREE").asJavaCollection + val coll1 = ju.Arrays.asList("ONE", "TWO", "THREE") assertTrue(keySet.containsAll(coll1)) - val coll2 = - Set("ONE", "TWO", "THREE", "FOUR").asJavaCollection + val coll2 = ju.Arrays.asList("ONE", "TWO", "THREE", "FOUR") assertFalse(keySet.containsAll(coll2)) - val coll3 = - Set("ONE", "TWO", "THREE", null).asJavaCollection + if (factory.allowsNullKeysQueries) { + val coll3 = ju.Arrays.asList("ONE", "TWO", "THREE", null) + assertFalse(keySet.containsAll(coll3)) + } + } + + @Test def testKeySetIsViewForQueriesWithCustomObjects(): Unit = { + val mp = factory.empty[TestObj, TestObj] + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + val keySet = mp.keySet() + + assertTrue(keySet.contains(TestObj(1))) + assertTrue(keySet.contains(TestObj(2))) + assertFalse(keySet.contains(TestObj(3))) + if (factory.allowsNullKeysQueries) + assertFalse(keySet.contains(null)) + else + expectThrows(classOf[NullPointerException], keySet.contains(null)) + + mp.put(TestObj(3), TestObj(33)) + + assertTrue(keySet.contains(TestObj(3))) + + val coll1 = ju.Arrays.asList(TestObj(1), TestObj(2), TestObj(3)) + assertTrue(keySet.containsAll(coll1)) + + val coll2 = ju.Arrays.asList(TestObj(1), TestObj(2), TestObj(4)) assertFalse(keySet.containsAll(coll2)) + if (factory.allowsNullKeysQueries) { + val coll3 = ju.Arrays.asList(TestObj(1), TestObj(2), null) + assertFalse(keySet.containsAll(coll3)) + } + } + + @Test def testKeySetIsViewForQueriesWithDoublesCornerCaseOfEquals(): Unit = { val nummp = factory.empty[Double, Double] + val numkeySet = nummp.keySet() - val numkeySet = nummp.keySet nummp.put(+0.0, 1) assertTrue(numkeySet.contains(+0.0)) assertFalse(numkeySet.contains(-0.0)) @@ -531,33 +677,64 @@ trait MapTest { assertTrue(numkeySet.contains(+0.0)) assertTrue(numkeySet.contains(-0.0)) assertTrue(numkeySet.contains(Double.NaN)) + } - val hm1 = mu.HashMap( - 1.0 -> null, - 2.0 -> 2.0) - val hm2 = mu.HashMap( - (null: Any) -> 1.0, - 2.0 -> 2.0) - val hm3 = mu.HashMap( - (null: Any) -> null, - 2.0 -> 2.0) + @Test def testKeySetIsViewForRemoveWithStrings(): Unit = { + val mp = factory.empty[String, String] + mp.put("ONE", "one") + mp.put("TWO", "two") + val keySet = mp.keySet() - assertTrue(new SimpleQueryableMap(hm1).keySet.contains(1.0)) - assertFalse(new SimpleQueryableMap(hm2).keySet.contains(1.0)) - assertFalse(new SimpleQueryableMap(hm3).keySet.contains(1.0)) + assertFalse(keySet.isEmpty) + assertFalse(mp.isEmpty) - assertFalse(new SimpleQueryableMap(hm1).keySet.contains(null)) - assertTrue(new SimpleQueryableMap(hm2).keySet.contains(null)) - assertTrue(new SimpleQueryableMap(hm3).keySet.contains(null)) - } + keySet.clear() - @Test def keySet_should_side_effect_clear_remove_retain_on_the_related_map(): Unit = { - val mp = factory.empty[String, String] + assertTrue(keySet.isEmpty) + + assertTrue(mp.isEmpty) mp.put("ONE", "one") mp.put("TWO", "two") - val keySet = mp.keySet + assertTrue(mp.containsKey("ONE")) + keySet.remove("ONE") + assertFalse(mp.containsKey("ONE")) + + mp.put("ONE", "one") + mp.put("THREE", "three") + + assertTrue(mp.containsKey("ONE")) + assertTrue(mp.containsKey("TWO")) + assertTrue(mp.containsKey("THREE")) + + keySet.removeAll(ju.Arrays.asList("ONE", "TWO", "FIVE")) + + assertFalse(mp.containsKey("ONE")) + assertFalse(mp.containsKey("TWO")) + assertTrue(mp.containsKey("THREE")) + + mp.put("ONE", "one") + mp.put("TWO", "two") + mp.put("THREE", "three") + + assertTrue(mp.containsKey("ONE")) + assertTrue(mp.containsKey("TWO")) + assertTrue(mp.containsKey("THREE")) + + keySet.retainAll(ju.Arrays.asList("ONE", "TWO", "FIVE")) + + assertTrue(mp.containsKey("ONE")) + assertTrue(mp.containsKey("TWO")) + assertFalse(mp.containsKey("THREE")) + } + + @Test def testKeySetIsViewForRemoveWithCustomObjects(): Unit = { + val mp = factory.empty[TestObj, TestObj] + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + val keySet = mp.keySet() + assertFalse(keySet.isEmpty) assertFalse(mp.isEmpty) @@ -567,14 +744,147 @@ trait MapTest { assertTrue(mp.isEmpty) + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + + assertTrue(mp.containsKey(TestObj(1))) + keySet.remove(TestObj(1)) + assertFalse(mp.containsKey(TestObj(1))) + + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(3), TestObj(33)) + + assertTrue(mp.containsKey(TestObj(1))) + assertTrue(mp.containsKey(TestObj(2))) + assertTrue(mp.containsKey(TestObj(3))) + + keySet.removeAll(ju.Arrays.asList(TestObj(1), TestObj(2), TestObj(5))) + + assertFalse(mp.containsKey(TestObj(1))) + assertFalse(mp.containsKey(TestObj(2))) + assertTrue(mp.containsKey(TestObj(3))) + + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + mp.put(TestObj(3), TestObj(33)) + + assertTrue(mp.containsKey(TestObj(1))) + assertTrue(mp.containsKey(TestObj(2))) + assertTrue(mp.containsKey(TestObj(3))) + + keySet.retainAll(ju.Arrays.asList(TestObj(1), TestObj(2), TestObj(5))) + + assertTrue(mp.containsKey(TestObj(1))) + assertTrue(mp.containsKey(TestObj(2))) + assertFalse(mp.containsKey(TestObj(3))) + } + + @Test def testEntrySetIsViewForSize(): Unit = { + val mp = factory.empty[String, String] mp.put("ONE", "one") mp.put("TWO", "two") + val entrySet = mp.entrySet() - assertTrue(mp.containsKey("ONE")) + assertEquals(2, entrySet.size) + mp.put("THREE", "three") + assertEquals(3, entrySet.size) + mp.remove("ONE") + assertEquals(2, entrySet.size) + assertFalse(entrySet.isEmpty) + mp.clear() + assertEquals(0, entrySet.size) + assertTrue(entrySet.isEmpty) + } - keySet.remove("ONE") + @Test def testEntrySetIsViewForQueriesWithStrings(): Unit = { + val mp = factory.empty[String, String] + mp.put("ONE", "one") + mp.put("TWO", "two") + val entrySet = mp.entrySet() + + assertTrue(entrySet.contains(SIE("ONE", "one"))) + assertTrue(entrySet.contains(SIE("TWO", "two"))) + assertFalse(entrySet.contains(SIE("THREE", "three"))) + assertFalse(entrySet.contains(SIE("ONE", "two"))) + assertFalse(entrySet.contains(SIE("THREE", "one"))) + + mp.put("THREE", "three") + + assertTrue(entrySet.contains(SIE("THREE", "three"))) + + val coll1 = ju.Arrays.asList(SIE("ONE", "one"), SIE("TWO", "two"), + SIE("THREE", "three")) + assertTrue(entrySet.containsAll(coll1)) + + val coll2 = ju.Arrays.asList(SIE("ONE", "one"), SIE("TWO", "two"), + SIE("THREE", "three"), SIE("FOUR", "four")) + assertFalse(entrySet.containsAll(coll2)) + + val coll3 = ju.Arrays.asList(SIE("ONE", "one"), SIE("TWO", "four"), + SIE("THREE", "three")) + assertFalse(entrySet.containsAll(coll3)) + + val coll4 = ju.Arrays.asList(SIE("ONE", "one"), SIE("four", "two"), + SIE("THREE", "three")) + assertFalse(entrySet.containsAll(coll4)) + } + + @Test def testEntrySetIsViewForQueriesWithCustomObjects(): Unit = { + val mp = factory.empty[TestObj, TestObj] + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + val entrySet = mp.entrySet() + + assertTrue(entrySet.contains(SIE(TestObj(1), TestObj(11)))) + assertTrue(entrySet.contains(SIE(TestObj(2), TestObj(22)))) + assertFalse(entrySet.contains(SIE(TestObj(3), TestObj(33)))) + assertFalse(entrySet.contains(SIE(TestObj(1), TestObj(22)))) + assertFalse(entrySet.contains(SIE(TestObj(3), TestObj(11)))) + + mp.put(TestObj(3), TestObj(33)) + + assertTrue(entrySet.contains(SIE(TestObj(3), TestObj(33)))) + + val coll1 = ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + SIE(TestObj(2), TestObj(22)), SIE(TestObj(3), TestObj(33))) + assertTrue(entrySet.containsAll(coll1)) + + val coll2 = ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + SIE(TestObj(2), TestObj(22)), SIE(TestObj(3), TestObj(33)), + SIE(TestObj(4), TestObj(44))) + assertFalse(entrySet.containsAll(coll2)) + + val coll3 = ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + SIE(TestObj(2), TestObj(44)), SIE(TestObj(3), TestObj(33))) + assertFalse(entrySet.containsAll(coll3)) + + val coll4 = ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + SIE(TestObj(4), TestObj(22)), SIE(TestObj(3), TestObj(33))) + assertFalse(entrySet.containsAll(coll4)) + } + + @Test def testEntrySetIsViewForRemoveWithStrings(): Unit = { + val mp = factory.empty[String, String] + mp.put("ONE", "one") + mp.put("TWO", "two") + val entrySet = mp.entrySet() + + assertFalse(entrySet.isEmpty) + assertFalse(mp.isEmpty) + + entrySet.clear() + assertTrue(entrySet.isEmpty) + assertTrue(mp.isEmpty) + mp.put("ONE", "one") + mp.put("TWO", "two") + + assertTrue(mp.containsKey("ONE")) + assertTrue(entrySet.remove(SIE("ONE", "one"))) + assertFalse(entrySet.remove(SIE("TWO", "four"))) + assertFalse(entrySet.remove("TWO")) assertFalse(mp.containsKey("ONE")) + assertTrue(mp.containsKey("TWO")) mp.put("ONE", "one") mp.put("THREE", "three") @@ -583,7 +893,8 @@ trait MapTest { assertTrue(mp.containsKey("TWO")) assertTrue(mp.containsKey("THREE")) - keySet.removeAll(List("ONE", "TWO").asJavaCollection) + entrySet.removeAll(ju.Arrays.asList(SIE("ONE", "one"), SIE("TWO", "two"), + SIE("THREE", "four"), "THREE", 42)) assertFalse(mp.containsKey("ONE")) assertFalse(mp.containsKey("TWO")) @@ -597,13 +908,110 @@ trait MapTest { assertTrue(mp.containsKey("TWO")) assertTrue(mp.containsKey("THREE")) - keySet.retainAll(List("ONE", "TWO").asJavaCollection) + entrySet.retainAll(ju.Arrays.asList(SIE("ONE", "one"), SIE("TWO", "two"), + SIE("THREE", "four"), "THREE", 42)) assertTrue(mp.containsKey("ONE")) assertTrue(mp.containsKey("TWO")) assertFalse(mp.containsKey("THREE")) } + @Test def testEntrySetIsViewForRemoveWithCustomObjects(): Unit = { + val mp = factory.empty[TestObj, TestObj] + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + val entrySet = mp.entrySet() + + assertFalse(entrySet.isEmpty) + assertFalse(mp.isEmpty) + + entrySet.clear() + assertTrue(entrySet.isEmpty) + assertTrue(mp.isEmpty) + + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + + assertTrue(mp.containsKey(TestObj(1))) + assertTrue(entrySet.remove(SIE(TestObj(1), TestObj(11)))) + assertFalse(entrySet.remove(SIE(TestObj(2), TestObj(44)))) + assertFalse(entrySet.remove(TestObj(2))) + assertFalse(mp.containsKey(TestObj(1))) + assertTrue(mp.containsKey(TestObj(2))) + + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(3), TestObj(33)) + + assertTrue(mp.containsKey(TestObj(1))) + assertTrue(mp.containsKey(TestObj(2))) + assertTrue(mp.containsKey(TestObj(3))) + + entrySet.removeAll(ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + SIE(TestObj(2), TestObj(22)), SIE(TestObj(3), TestObj(44)), + TestObj(3), 42)) + + assertFalse(mp.containsKey(TestObj(1))) + assertFalse(mp.containsKey(TestObj(2))) + assertTrue(mp.containsKey(TestObj(3))) + + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + mp.put(TestObj(3), TestObj(33)) + + assertTrue(mp.containsKey(TestObj(1))) + assertTrue(mp.containsKey(TestObj(2))) + assertTrue(mp.containsKey(TestObj(3))) + + entrySet.retainAll(ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + SIE(TestObj(2), TestObj(22)), SIE(TestObj(3), TestObj(44)), + TestObj(3), 42)) + + assertTrue(mp.containsKey(TestObj(1))) + assertTrue(mp.containsKey(TestObj(2))) + assertFalse(mp.containsKey(TestObj(3))) + } + + @Test def testEntrySetIsViewForSetValueWithStrings(): Unit = { + val mp = factory.empty[String, String] + mp.put("ONE", "one") + mp.put("TWO", "two") + val entrySet = mp.entrySet() + + val entry = entrySet.iterator().next() + val key = entry.getKey() + assertTrue(key == "ONE" || key == "TWO") + val expectedValue = if (key == "ONE") "one" else "two" + + assertEquals(expectedValue, entry.getValue()) + assertEquals(expectedValue, entry.setValue("new value")) + assertEquals("new value", entry.getValue()) + assertEquals("new value", mp.get(key)) + } + + @Test def testEntrySetIsViewForSetValueWithCustomObjects(): Unit = { + val mp = factory.empty[TestObj, TestObj] + mp.put(TestObj(1), TestObj(11)) + mp.put(TestObj(2), TestObj(22)) + val entrySet = mp.entrySet() + + val entry = entrySet.iterator().next() + val key = entry.getKey() + assertTrue(key.num == 1 || key.num == 2) + val expectedValue = TestObj(if (key.num == 1) 11 else 22) + + assertEquals(expectedValue, entry.getValue()) + assertEquals(expectedValue, entry.setValue(TestObj(56))) + assertEquals(TestObj(56), entry.getValue()) + assertEquals(TestObj(56), mp.get(key)) + } + +} + +object MapTest { + final case class TestObj(num: Int) + + def SIE[K, V](key: K, value: V): ju.Map.Entry[K, V] = + new ju.AbstractMap.SimpleImmutableEntry(key, value) } object MapFactory { diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TrivialImmutableMap.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TrivialImmutableMap.scala new file mode 100644 index 0000000000..e15198b6aa --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TrivialImmutableMap.scala @@ -0,0 +1,51 @@ +/* + * 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.testsuite.javalib.util + +import java.{util => ju} +import java.util.Map.Entry + +final class TrivialImmutableMap[K, V] private (contents: List[Entry[K, V]]) + extends ju.AbstractMap[K, V] { + + def entrySet(): ju.Set[Entry[K,V]] = { + new ju.AbstractSet[Entry[K, V]] { + def size(): Int = contents.size + + def iterator(): ju.Iterator[Entry[K,V]] = { + new ju.Iterator[Entry[K, V]] { + private var remaining: List[Entry[K, V]] = contents + + def hasNext(): Boolean = remaining.nonEmpty + + def next(): Entry[K,V] = { + val head = remaining.head + remaining = remaining.tail + head + } + + override def remove(): Unit = + throw new UnsupportedOperationException("Iterator.remove()") + } + } + } + } +} + +object TrivialImmutableMap { + def apply[K, V](contents: List[Entry[K, V]]): TrivialImmutableMap[K, V] = + new TrivialImmutableMap(contents) + + def apply[K, V](contents: (K, V)*): TrivialImmutableMap[K, V] = + apply(contents.toList.map(kv => new ju.AbstractMap.SimpleImmutableEntry(kv._1, kv._2))) +} From 03e21895f0da94639276ef3e097de9ee7fe30d4b Mon Sep 17 00:00:00 2001 From: exoego Date: Wed, 17 Jul 2019 13:46:24 +0900 Subject: [PATCH 0041/1606] Fix #3697: Improve error message when no main module initializer was found --- .../sbtplugin/ScalaJSPluginInternal.scala | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala index 5d6d2e5d39..4c94309e74 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala @@ -909,15 +909,23 @@ object ScalaJSPluginInternal { (scalaJSModuleInitializers in (This, Zero, This)).value, scalaJSModuleInitializers ++= { + val mainClasses = scalaJSDiscoveredMainClasses.value if (scalaJSUseMainModuleInitializer.value) { Seq(scalaJSMainModuleInitializer.value.getOrElse { - throw new MessageOnlyException( - "No main module initializer was specified (possibly because " + - "no or multiple main classes were found), but " + - "scalaJSUseMainModuleInitializer was set to true. " + - "You can explicitly specify it either with " + - "`mainClass := Some(...)` or with " + - "`scalaJSMainModuleInitializer := Some(...)`") + if (mainClasses.isEmpty) { + throw new MessageOnlyException( + "No main module initializer was specified, but " + + "scalaJSUseMainModuleInitializer was set to true. " + + "You can explicitly specify it either with " + + "`mainClass := Some(...)` or with " + + "`scalaJSMainModuleInitializer := Some(...)`") + } else { + throw new MessageOnlyException( + s"Multiple main classes (${mainClasses.keys}) were found. " + + "You can explicitly specify the one you want with " + + "`mainClass := Some(...)` or with " + + "`scalaJSMainModuleInitializer := Some(...)`") + } }) } else { Seq.empty From 783c6bec6ccd3320dc69af700edd63809b435144 Mon Sep 17 00:00:00 2001 From: exoego Date: Mon, 19 Aug 2019 06:24:03 +0900 Subject: [PATCH 0042/1606] Suppress stack trace of LinkingException's in sbt plugin since it is useless for users --- .../org/scalajs/sbtplugin/ScalaJSPluginInternal.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala index 4c94309e74..f4b24648f0 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala @@ -322,9 +322,14 @@ object ScalaJSPluginInternal { IO.createDirectory(output.getParentFile) - linker.link(ir, moduleInitializers, - AtomicWritableFileVirtualJSFile(output), - sbtLogger2ToolsLogger(log)) + try { + linker.link(ir, moduleInitializers, + AtomicWritableFileVirtualJSFile(output), + sbtLogger2ToolsLogger(log)) + } catch { + case e: LinkingException => + throw new MessageOnlyException(e.getMessage) + } logIRCacheStats(log) From 434b8ce12ce111831095a0abf8f347d0523a3d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 19 Aug 2019 22:32:40 +0200 Subject: [PATCH 0043/1606] Remove dead-code definitions of `allFactories` in the test suite. --- .../scalajs/testsuite/javalib/util/AbstractSetTest.scala | 5 ----- .../scalajs/testsuite/javalib/util/ArrayDequeTest.scala | 5 ----- .../scalajs/testsuite/javalib/util/CollectionTest.scala | 5 ----- .../org/scalajs/testsuite/javalib/util/DequeTest.scala | 5 ----- .../org/scalajs/testsuite/javalib/util/HashMapTest.scala | 5 ----- .../org/scalajs/testsuite/javalib/util/HashSetTest.scala | 5 ----- .../scalajs/testsuite/javalib/util/LinkedHashMapTest.scala | 7 ------- .../scalajs/testsuite/javalib/util/LinkedHashSetTest.scala | 5 ----- .../org/scalajs/testsuite/javalib/util/ListTest.scala | 5 ----- .../scala/org/scalajs/testsuite/javalib/util/MapTest.scala | 5 ----- .../scalajs/testsuite/javalib/util/NavigableSetTest.scala | 5 ----- .../scala/org/scalajs/testsuite/javalib/util/SetTest.scala | 5 ----- .../org/scalajs/testsuite/javalib/util/SortedMapTest.scala | 4 ---- .../org/scalajs/testsuite/javalib/util/SortedSetTest.scala | 5 ----- .../org/scalajs/testsuite/javalib/util/TreeSetTest.scala | 5 ----- .../javalib/util/concurrent/ConcurrentHashMapTest.scala | 5 ----- .../javalib/util/concurrent/ConcurrentMapTest.scala | 5 ----- .../util/concurrent/ConcurrentSkipListSetTest.scala | 5 ----- 18 files changed, 91 deletions(-) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/AbstractSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/AbstractSetTest.scala index 165c68fd4f..4401f1fa3c 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/AbstractSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/AbstractSetTest.scala @@ -20,11 +20,6 @@ abstract class AbstractSetTest extends SetTest { def factory: AbstractSetFactory } -object AbstractSetFactory { - def allFactories: Iterator[AbstractSetFactory] = - HashSetFactory.allFactories -} - trait AbstractSetFactory extends SetFactory { def empty[E: ClassTag]: ju.AbstractSet[E] } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayDequeTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayDequeTest.scala index 3c8bc344d3..72aab2b2f0 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayDequeTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayDequeTest.scala @@ -170,11 +170,6 @@ class ArrayDequeTest extends AbstractCollectionTest with DequeTest { } } -object ArrayDequeFactory { - def allFactories: Iterator[ArrayDequeFactory] = - Iterator(new ArrayDequeFactory()) -} - class ArrayDequeFactory extends AbstractCollectionFactory with DequeFactory { override def implementationName: String = "java.util.ArrayDeque" diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala index 78de1a1619..3f170c90b4 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala @@ -227,11 +227,6 @@ trait CollectionTest { } } -object CollectionFactory { - def allFactories: Iterator[CollectionFactory] = - ListFactory.allFactories ++ SetFactory.allFactories -} - trait CollectionFactory { def implementationName: String def empty[E: ClassTag]: ju.Collection[E] diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/DequeTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/DequeTest.scala index 5a841b751b..6c915cc998 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/DequeTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/DequeTest.scala @@ -20,11 +20,6 @@ trait DequeTest extends CollectionTest { def factory: DequeFactory } -object DequeFactory { - def allFactories: Iterator[DequeFactory] = - ArrayDequeFactory.allFactories -} - trait DequeFactory extends CollectionFactory { def empty[E: ClassTag]: ju.Deque[E] } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashMapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashMapTest.scala index f49c0b5e5a..0d14faaa7b 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashMapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashMapTest.scala @@ -20,11 +20,6 @@ class HashMapTest extends MapTest { def factory(): HashMapFactory = new HashMapFactory } -object HashMapFactory { - def allFactories: Iterator[MapFactory] = - Iterator(new HashMapFactory) ++ LinkedHashMapFactory.allFactories -} - class HashMapFactory extends AbstractMapFactory { override def implementationName: String = "java.util.HashMap" diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashSetTest.scala index 071ef2ac7d..9630911231 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashSetTest.scala @@ -22,11 +22,6 @@ class HashSetTest extends AbstractSetTest { def factory: HashSetFactory = new HashSetFactory } -object HashSetFactory { - def allFactories: Iterator[HashSetFactory] = - Iterator(new HashSetFactory) ++ LinkedHashSetFactory.allFactories -} - class HashSetFactory extends AbstractSetFactory { def implementationName: String = "java.util.HashSet" diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala index 67c184b858..d3f8ac2e3f 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala @@ -207,13 +207,6 @@ abstract class LinkedHashMapTest extends HashMapTest { } -object LinkedHashMapFactory { - def allFactories: Iterator[MapFactory] = { - Iterator(new LinkedHashMapFactory(true, Some(50)), new LinkedHashMapFactory(true, None), - new LinkedHashMapFactory(false, Some(50)), new LinkedHashMapFactory(false, None)) - } -} - class LinkedHashMapFactory(val accessOrder: Boolean, override val withSizeLimit: Option[Int]) extends HashMapFactory { diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashSetTest.scala index 61f631c298..cd283ba1e5 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashSetTest.scala @@ -61,11 +61,6 @@ class LinkedHashSetTest extends HashSetTest { } -object LinkedHashSetFactory extends HashSetFactory { - def allFactories: Iterator[LinkedHashSetFactory] = - Iterator(new LinkedHashSetFactory) -} - class LinkedHashSetFactory extends HashSetFactory { override def implementationName: String = "java.util.LinkedHashSet" diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala index bb40a8764b..68d5adaecc 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala @@ -438,11 +438,6 @@ trait ListTest extends CollectionTest { } } -object ListFactory { - def allFactories: Iterator[ListFactory] = - Iterator(new ArrayListFactory, new LinkedListFactory, new AbstractListFactory) -} - trait ListFactory extends CollectionFactory { def empty[E: ClassTag]: ju.List[E] diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala index 80c2d14b12..728780bf8b 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala @@ -1014,11 +1014,6 @@ object MapTest { new ju.AbstractMap.SimpleImmutableEntry(key, value) } -object MapFactory { - def allFactories: Iterator[MapFactory] = - HashMapFactory.allFactories ++ SortedMapFactory.allFactories ++ ConcurrentMapFactory.allFactories -} - trait MapFactory { def implementationName: String diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/NavigableSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/NavigableSetTest.scala index 897c0bafde..ca1da08fa0 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/NavigableSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/NavigableSetTest.scala @@ -135,11 +135,6 @@ trait NavigableSetTest extends SetTest { } } -object NavigableSetFactory { - def allFactories: Iterator[NavigableSetFactory] = - ConcurrentSkipListSetFactory.allFactories -} - trait NavigableSetFactory extends SetFactory { def empty[E: ClassTag]: ju.NavigableSet[E] } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala index 3210f42e35..5a2d88b802 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala @@ -214,11 +214,6 @@ trait SetTest extends CollectionTest { } } -object SetFactory { - def allFactories: Iterator[SetFactory] = - AbstractSetFactory.allFactories ++ SortedSetFactory.allFactories ++ NavigableSetFactory.allFactories -} - trait SetFactory extends CollectionFactory { def empty[E: ClassTag]: ju.Set[E] diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedMapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedMapTest.scala index 7822eadb29..aa57e8646e 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedMapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedMapTest.scala @@ -50,10 +50,6 @@ trait SortedMapTest extends MapTest { } } -object SortedMapFactory { - def allFactories: Iterator[SortedMapFactory] = Iterator.empty -} - trait SortedMapFactory extends MapFactory { def empty[K: ClassTag, V: ClassTag]: ju.SortedMap[K, V] } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala index c3f646ff54..daeda50a80 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala @@ -137,11 +137,6 @@ trait SortedSetTest extends SetTest { } } -object SortedSetFactory { - def allFactories: Iterator[SortedSetFactory] = - Iterator.empty -} - trait SortedSetFactory extends SetFactory { def empty[E: ClassTag]: ju.SortedSet[E] } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TreeSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TreeSetTest.scala index 3405797cd7..535f793855 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TreeSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TreeSetTest.scala @@ -343,11 +343,6 @@ abstract class TreeSetTest(val factory: TreeSetFactory) } } -object TreeSetFactory extends TreeSetFactory { - def allFactories: Iterator[TreeSetFactory] = - Iterator(new TreeSetFactory, new TreeSetWithNullFactory) -} - class TreeSetFactory extends AbstractSetFactory with NavigableSetFactory with SortedSetFactory { def implementationName: String = diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala index 0cd7286720..1f9eda63a4 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentHashMapTest.scala @@ -228,11 +228,6 @@ class ConcurrentHashMapTest extends MapTest { } } -object ConcurrentHashMapFactory extends ConcurrentHashMapFactory { - def allFactories: Iterator[ConcurrentHashMapFactory] = - Iterator(ConcurrentHashMapFactory) -} - class ConcurrentHashMapFactory extends ConcurrentMapFactory { def implementationName: String = "java.util.concurrent.ConcurrentHashMap" diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentMapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentMapTest.scala index acf35d2e6a..4e06f57795 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentMapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentMapTest.scala @@ -18,11 +18,6 @@ import org.scalajs.testsuite.javalib.util.MapFactory import scala.reflect.ClassTag -object ConcurrentMapFactory { - def allFactories: Iterator[ConcurrentMapFactory] = - ConcurrentHashMapFactory.allFactories -} - trait ConcurrentMapFactory extends MapFactory { def empty[K: ClassTag, V: ClassTag]: ju.concurrent.ConcurrentMap[K, V] diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala index d8ae7a3c52..22e7134ba4 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala @@ -410,11 +410,6 @@ class ConcurrentSkipListSetTest { } } -object ConcurrentSkipListSetFactory extends ConcurrentSkipListSetFactory { - def allFactories: Iterator[ConcurrentSkipListSetFactory] = - Iterator(new ConcurrentSkipListSetFactory) -} - class ConcurrentSkipListSetFactory extends NavigableSetFactory { def implementationName: String = "java.util.concurrent.ConcurrentSkipListSet" From 73680fa4e66ca07648b7a29d739010b39c1714c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 7 Mar 2019 17:47:43 +0100 Subject: [PATCH 0044/1606] Rename JS* IR nodes involving selections. * `JSDotSelect` -> `JSPrivateSelect` * `JSBracketSelect` -> `JSSelect` * `JSBracketMethodApply` -> `JSMethodApply` * `JSSuperBracketSelect` -> `JSSuperSelect` * `JSSuperBracketCall` -> `JSSuperCall` The new names are more semantically meaningful, and correspond to how those nodes are used. `JSDotMethodApply` was removed because it was unused. `JSDelete` was never used with `JSDotSelect`s, so it was specialized for the semantics with `JSBracketSelect`s (now `JSSelect`s) by directly integrating the qualifier and the item. --- .../org/scalajs/nscplugin/GenJSCode.scala | 30 +++--- .../org/scalajs/nscplugin/GenJSExports.scala | 17 ++- .../main/scala/org/scalajs/ir/Hashers.scala | 31 +++--- .../main/scala/org/scalajs/ir/Printers.scala | 29 +++-- .../scala/org/scalajs/ir/Serializers.scala | 44 ++++---- ir/src/main/scala/org/scalajs/ir/Tags.scala | 15 ++- .../scala/org/scalajs/ir/Transformers.scala | 28 +++-- .../scala/org/scalajs/ir/Traversers.scala | 19 ++-- ir/src/main/scala/org/scalajs/ir/Trees.scala | 29 ++--- .../scala/org/scalajs/ir/PrintersTest.scala | 44 ++++---- .../backend/emitter/FunctionEmitter.scala | 100 ++++++------------ .../scalajs/linker/checker/IRChecker.scala | 25 ++--- .../frontend/optimizer/OptimizerCore.scala | 52 ++++----- .../scala/org/scalajs/linker/LinkerTest.scala | 2 +- 14 files changed, 187 insertions(+), 278 deletions(-) diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala index 5114097c6c..8b942eaa16 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSCode.scala @@ -721,9 +721,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case fdef: js.FieldDef => implicit val pos = fdef.pos val select = fdef.name match { - case ident: js.Ident => js.JSDotSelect(selfRef, ident) - case lit: js.StringLiteral => js.JSBracketSelect(selfRef, lit) - case js.ComputedName(tree, _) => js.JSBracketSelect(selfRef, tree) + case ident: js.Ident => js.JSPrivateSelect(selfRef, ident) + case lit: js.StringLiteral => js.JSSelect(selfRef, lit) + case js.ComputedName(tree, _) => js.JSSelect(selfRef, tree) } js.Assign(select, jstpe.zeroOf(fdef.ftpe)) @@ -732,7 +732,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) val name = mdef.name.asInstanceOf[js.StringLiteral] val impl = memberLambda(mdef.args, mdef.body.getOrElse( throw new AssertionError("Got anon SJS class with abstract method"))) - js.Assign(js.JSBracketSelect(selfRef, name), impl) + js.Assign(js.JSSelect(selfRef, name), impl) case pdef: js.PropertyDef => implicit val pos = pdef.pos @@ -756,7 +756,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) List(js.StringLiteral("configurable") -> js.BooleanLiteral(true)) ) - js.JSBracketMethodApply(jsObject, js.StringLiteral("defineProperty"), + js.JSMethodApply(jsObject, js.StringLiteral("defineProperty"), List(selfRef, name, descriptor)) case tree => @@ -2114,9 +2114,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) } else if (isNonNativeJSClass(sym.owner)) { val genQual = genExpr(qualifier) val boxed = if (isExposed(sym)) - js.JSBracketSelect(genQual, genExpr(jsNameOf(sym))) + js.JSSelect(genQual, genExpr(jsNameOf(sym))) else - js.JSDotSelect(genQual, encodeFieldSym(sym)) + js.JSPrivateSelect(genQual, encodeFieldSym(sym)) unboxFieldValue(boxed) } else if (jsInterop.isFieldStatic(sym)) { unboxFieldValue(genSelectStaticFieldAsBoxed(sym)) @@ -2214,9 +2214,9 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) if (isNonNativeJSClass(sym.owner)) { val genLhs = if (isExposed(sym)) - js.JSBracketSelect(genQual, genExpr(jsNameOf(sym))) + js.JSSelect(genQual, genExpr(jsNameOf(sym))) else - js.JSDotSelect(genQual, encodeFieldSym(sym)) + js.JSPrivateSelect(genQual, encodeFieldSym(sym)) js.Assign(genLhs, genBoxedRhs) } else if (jsInterop.isFieldStatic(sym)) { js.Assign(genSelectStaticFieldAsBoxed(sym), genBoxedRhs) @@ -2288,7 +2288,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case ExportDestination.Static => val exportInfo = exportInfos.head val companionClass = patchedLinkedClassOfClass(sym.owner) - js.JSBracketSelect( + js.JSSelect( genPrimitiveJSClass(companionClass), js.StringLiteral(exportInfo.jsName)) } @@ -4559,7 +4559,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) case DELETE => // js.special.delete(arg1, arg2) val (arg1, arg2) = genArgs2 - js.JSDelete(js.JSBracketSelect(arg1, arg2)) + js.JSDelete(arg1, arg2) case FORIN => /* js.special.forin(arg1, arg2) @@ -4700,7 +4700,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) jsSuperClassValue.fold[js.Tree] { genJSBracketSelectOrGlobalRef(receiver, propName) } { superClassValue => - js.JSSuperBracketSelect(superClassValue, + js.JSSuperSelect(superClassValue, ruleOutGlobalScope(receiver), propName) } } @@ -4717,7 +4717,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) genJSBracketMethodApplyOrGlobalRefApply( receiver, methodName, args) } { superClassValue => - js.JSSuperBracketCall(superClassValue, + js.JSSuperMethodCall(superClassValue, ruleOutGlobalScope(receiver), methodName, args) } } @@ -5817,7 +5817,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) item: js.Tree)(implicit pos: Position): js.Tree = { qual match { case MaybeGlobalScope.NotGlobalScope(qualTree) => - js.JSBracketSelect(qualTree, item) + js.JSSelect(qualTree, item) case MaybeGlobalScope.GlobalScope(_) => item match { @@ -5864,7 +5864,7 @@ abstract class GenJSCode[G <: Global with Singleton](val global: G) implicit pos: Position): js.Tree = { receiver match { case MaybeGlobalScope.NotGlobalScope(receiverTree) => - js.JSBracketMethodApply(receiverTree, method, args) + js.JSMethodApply(receiverTree, method, args) case MaybeGlobalScope.GlobalScope(_) => method match { diff --git a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala index 140e82dd46..ebad66aafd 100644 --- a/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala +++ b/compiler/src/main/scala/org/scalajs/nscplugin/GenJSExports.scala @@ -513,10 +513,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { assert(needsRestParam, "Trying to read rest param length but needsRestParam is false") js.Match( - js.Unbox(js.JSBracketSelect( - genRestArgRef(), - js.StringLiteral("length")), - 'I'), + js.Unbox(js.JSSelect(genRestArgRef(), js.StringLiteral("length")), 'I'), cases.toList, defaultCase)(jstpe.AnyType) } } @@ -683,14 +680,14 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { if (jsInterop.isJSGetter(sym)) { assert(allArgs.isEmpty, s"getter symbol $sym does not have a getter signature") - js.JSSuperBracketSelect(superClass, receiver, nameString) + js.JSSuperSelect(superClass, receiver, nameString) } else if (jsInterop.isJSSetter(sym)) { assert(allArgs.size == 1 && allArgs.head.isInstanceOf[js.Tree], s"setter symbol $sym does not have a setter signature") - js.Assign(js.JSSuperBracketSelect(superClass, receiver, nameString), + js.Assign(js.JSSuperSelect(superClass, receiver, nameString), allArgs.head.asInstanceOf[js.Tree]) } else { - js.JSSuperBracketCall(superClass, receiver, nameString, allArgs) + js.JSSuperMethodCall(superClass, receiver, nameString, allArgs) } } @@ -1123,7 +1120,7 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { if (index <= minArgc) js.VarRef(js.Ident("arg$" + index))(jstpe.AnyType) else - js.JSBracketSelect(genRestArgRef(), js.IntLiteral(index - 1 - minArgc)) + js.JSSelect(genRestArgRef(), js.IntLiteral(index - 1 - minArgc)) } private def genVarargRef(fixedParamCount: Int, minArgc: Int)( @@ -1133,8 +1130,8 @@ trait GenJSExports[G <: Global with Singleton] extends SubComponent { s"genVarargRef($fixedParamCount, $minArgc) at $pos") if (fixedParamCount == minArgc) restParam else { - js.JSBracketMethodApply(restParam, js.StringLiteral("slice"), List( - js.IntLiteral(fixedParamCount - minArgc))) + js.JSMethodApply(restParam, js.StringLiteral("slice"), + List(js.IntLiteral(fixedParamCount - minArgc))) } } diff --git a/ir/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/src/main/scala/org/scalajs/ir/Hashers.scala index fc6fb8af8d..a65da7dea8 100644 --- a/ir/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/src/main/scala/org/scalajs/ir/Hashers.scala @@ -299,13 +299,13 @@ object Hashers { mixTree(ctor) mixTreeOrJSSpreads(args) - case JSDotSelect(qualifier, item) => - mixTag(TagJSDotSelect) + case JSPrivateSelect(qualifier, item) => + mixTag(TagJSPrivateSelect) mixTree(qualifier) mixIdent(item) - case JSBracketSelect(qualifier, item) => - mixTag(TagJSBracketSelect) + case JSSelect(qualifier, item) => + mixTag(TagJSSelect) mixTree(qualifier) mixTree(item) @@ -314,26 +314,20 @@ object Hashers { mixTree(fun) mixTreeOrJSSpreads(args) - case JSDotMethodApply(receiver, method, args) => - mixTag(TagJSDotMethodApply) - mixTree(receiver) - mixIdent(method) - mixTreeOrJSSpreads(args) - - case JSBracketMethodApply(receiver, method, args) => - mixTag(TagJSBracketMethodApply) + case JSMethodApply(receiver, method, args) => + mixTag(TagJSMethodApply) mixTree(receiver) mixTree(method) mixTreeOrJSSpreads(args) - case JSSuperBracketSelect(superClass, qualifier, item) => - mixTag(TagJSSuperBracketSelect) + case JSSuperSelect(superClass, qualifier, item) => + mixTag(TagJSSuperSelect) mixTree(superClass) mixTree(qualifier) mixTree(item) - case JSSuperBracketCall(superClass, receiver, method, args) => - mixTag(TagJSSuperBracketCall) + case JSSuperMethodCall(superClass, receiver, method, args) => + mixTag(TagJSSuperMethodCall) mixTree(superClass) mixTree(receiver) mixTree(method) @@ -355,9 +349,10 @@ object Hashers { mixTag(TagLoadJSModule) mixClassRef(cls) - case JSDelete(prop) => + case JSDelete(qualifier, item) => mixTag(TagJSDelete) - mixTree(prop) + mixTree(qualifier) + mixTree(item) case JSUnaryOp(op, lhs) => mixTag(TagJSUnaryOp) diff --git a/ir/src/main/scala/org/scalajs/ir/Printers.scala b/ir/src/main/scala/org/scalajs/ir/Printers.scala index b212f7b6e6..426cacbc39 100644 --- a/ir/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/src/main/scala/org/scalajs/ir/Printers.scala @@ -516,8 +516,8 @@ object Printers { case JSNew(ctor, args) => def containsOnlySelectsFromAtom(tree: Tree): Boolean = tree match { - case JSDotSelect(qual, _) => containsOnlySelectsFromAtom(qual) - case JSBracketSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case JSPrivateSelect(qual, _) => containsOnlySelectsFromAtom(qual) + case JSSelect(qual, _) => containsOnlySelectsFromAtom(qual) case VarRef(_) => true case This() => true case _ => false // in particular, Apply @@ -532,12 +532,12 @@ object Printers { } printArgs(args) - case JSDotSelect(qualifier, item) => + case JSPrivateSelect(qualifier, item) => print(qualifier) print(".") print(item) - case JSBracketSelect(qualifier, item) => + case JSSelect(qualifier, item) => print(qualifier) print('[') print(item) @@ -545,7 +545,7 @@ object Printers { case JSFunctionApply(fun, args) => fun match { - case _:JSDotSelect | _:JSBracketSelect | _:Select => + case _:JSPrivateSelect | _:JSSelect | _:Select => print("(0, ") print(fun) print(')') @@ -555,20 +555,14 @@ object Printers { } printArgs(args) - case JSDotMethodApply(receiver, method, args) => - print(receiver) - print(".") - print(method) - printArgs(args) - - case JSBracketMethodApply(receiver, method, args) => + case JSMethodApply(receiver, method, args) => print(receiver) print('[') print(method) print(']') printArgs(args) - case JSSuperBracketSelect(superClass, qualifier, item) => + case JSSuperSelect(superClass, qualifier, item) => print("super(") print(superClass) print(")::") @@ -577,7 +571,7 @@ object Printers { print(item) print(']') - case JSSuperBracketCall(superClass, receiver, method, args) => + case JSSuperMethodCall(superClass, receiver, method, args) => print("super(") print(superClass) print(")::") @@ -605,9 +599,12 @@ object Printers { print("mod:") print(cls) - case JSDelete(prop) => + case JSDelete(qualifier, item) => print("delete ") - print(prop) + print(qualifier) + print('[') + print(item) + print(']') case JSUnaryOp(op, lhs) => import JSUnaryOp._ diff --git a/ir/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/src/main/scala/org/scalajs/ir/Serializers.scala index a204dbc160..c4a144e268 100644 --- a/ir/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/src/main/scala/org/scalajs/ir/Serializers.scala @@ -309,32 +309,28 @@ object Serializers { writeByte(TagJSNew) writeTree(ctor); writeTreeOrJSSpreads(args) - case JSDotSelect(qualifier, item) => - writeByte(TagJSDotSelect) + case JSPrivateSelect(qualifier, item) => + writeByte(TagJSPrivateSelect) writeTree(qualifier); writeIdent(item) - case JSBracketSelect(qualifier, item) => - writeByte(TagJSBracketSelect) + case JSSelect(qualifier, item) => + writeByte(TagJSSelect) writeTree(qualifier); writeTree(item) case JSFunctionApply(fun, args) => writeByte(TagJSFunctionApply) writeTree(fun); writeTreeOrJSSpreads(args) - case JSDotMethodApply(receiver, method, args) => - writeByte(TagJSDotMethodApply) - writeTree(receiver); writeIdent(method); writeTreeOrJSSpreads(args) - - case JSBracketMethodApply(receiver, method, args) => - writeByte(TagJSBracketMethodApply) + case JSMethodApply(receiver, method, args) => + writeByte(TagJSMethodApply) writeTree(receiver); writeTree(method); writeTreeOrJSSpreads(args) - case JSSuperBracketSelect(superClass, qualifier, item) => - writeByte(TagJSSuperBracketSelect) + case JSSuperSelect(superClass, qualifier, item) => + writeByte(TagJSSuperSelect) writeTree(superClass); writeTree(qualifier); writeTree(item) - case JSSuperBracketCall(superClass, receiver, method, args) => - writeByte(TagJSSuperBracketCall) + case JSSuperMethodCall(superClass, receiver, method, args) => + writeByte(TagJSSuperMethodCall) writeTree(superClass); writeTree(receiver); writeTree(method); writeTreeOrJSSpreads(args) case JSSuperConstructorCall(args) => @@ -353,9 +349,10 @@ object Serializers { writeByte(TagLoadJSModule) writeClassRef(cls) - case JSDelete(prop) => + case JSDelete(qualifier, item) => writeByte(TagJSDelete) - writeTree(prop) + writeTree(qualifier) + writeTree(item) case JSUnaryOp(op, lhs) => writeByte(TagJSUnaryOp) @@ -907,19 +904,18 @@ object Serializers { case TagGetClass => GetClass(readTree()) case TagJSNew => JSNew(readTree(), readTreeOrJSSpreads()) - case TagJSDotSelect => JSDotSelect(readTree(), readIdent()) - case TagJSBracketSelect => JSBracketSelect(readTree(), readTree()) + case TagJSPrivateSelect => JSPrivateSelect(readTree(), readIdent()) + case TagJSSelect => JSSelect(readTree(), readTree()) case TagJSFunctionApply => JSFunctionApply(readTree(), readTreeOrJSSpreads()) - case TagJSDotMethodApply => JSDotMethodApply(readTree(), readIdent(), readTreeOrJSSpreads()) - case TagJSBracketMethodApply => JSBracketMethodApply(readTree(), readTree(), readTreeOrJSSpreads()) - case TagJSSuperBracketSelect => JSSuperBracketSelect(readTree(), readTree(), readTree()) - case TagJSSuperBracketCall => - JSSuperBracketCall(readTree(), readTree(), readTree(), readTreeOrJSSpreads()) + case TagJSMethodApply => JSMethodApply(readTree(), readTree(), readTreeOrJSSpreads()) + case TagJSSuperSelect => JSSuperSelect(readTree(), readTree(), readTree()) + case TagJSSuperMethodCall => + JSSuperMethodCall(readTree(), readTree(), readTree(), readTreeOrJSSpreads()) case TagJSSuperConstructorCall => JSSuperConstructorCall(readTreeOrJSSpreads()) case TagJSImportCall => JSImportCall(readTree()) case TagLoadJSConstructor => LoadJSConstructor(readClassRef()) case TagLoadJSModule => LoadJSModule(readClassRef()) - case TagJSDelete => JSDelete(readTree()) + case TagJSDelete => JSDelete(readTree(), readTree()) case TagJSUnaryOp => JSUnaryOp(readInt(), readTree()) case TagJSBinaryOp => JSBinaryOp(readInt(), readTree(), readTree()) case TagJSArrayConstr => JSArrayConstr(readTreeOrJSSpreads()) diff --git a/ir/src/main/scala/org/scalajs/ir/Tags.scala b/ir/src/main/scala/org/scalajs/ir/Tags.scala index 6b1483e070..c6df3ebeea 100644 --- a/ir/src/main/scala/org/scalajs/ir/Tags.scala +++ b/ir/src/main/scala/org/scalajs/ir/Tags.scala @@ -60,14 +60,13 @@ private[ir] object Tags { final val TagGetClass = TagUnbox + 1 final val TagJSNew = TagGetClass + 1 - final val TagJSDotSelect = TagJSNew + 1 - final val TagJSBracketSelect = TagJSDotSelect + 1 - final val TagJSFunctionApply = TagJSBracketSelect + 1 - final val TagJSDotMethodApply = TagJSFunctionApply + 1 - final val TagJSBracketMethodApply = TagJSDotMethodApply + 1 - final val TagJSSuperBracketSelect = TagJSBracketMethodApply + 1 - final val TagJSSuperBracketCall = TagJSSuperBracketSelect + 1 - final val TagJSSuperConstructorCall = TagJSSuperBracketCall + 1 + final val TagJSPrivateSelect = TagJSNew + 1 + final val TagJSSelect = TagJSPrivateSelect + 1 + final val TagJSFunctionApply = TagJSSelect + 1 + final val TagJSMethodApply = TagJSFunctionApply + 1 + final val TagJSSuperSelect = TagJSMethodApply + 1 + final val TagJSSuperMethodCall = TagJSSuperSelect + 1 + final val TagJSSuperConstructorCall = TagJSSuperMethodCall + 1 final val TagJSImportCall = TagJSSuperConstructorCall + 1 final val TagLoadJSConstructor = TagJSImportCall + 1 final val TagLoadJSModule = TagLoadJSConstructor + 1 diff --git a/ir/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/src/main/scala/org/scalajs/ir/Transformers.scala index 2348ccad62..bdfe900814 100644 --- a/ir/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/src/main/scala/org/scalajs/ir/Transformers.scala @@ -143,29 +143,25 @@ object Transformers { case JSNew(ctor, args) => JSNew(transformExpr(ctor), args.map(transformExprOrJSSpread)) - case JSDotSelect(qualifier, item) => - JSDotSelect(transformExpr(qualifier), item) + case JSPrivateSelect(qualifier, item) => + JSPrivateSelect(transformExpr(qualifier), item) - case JSBracketSelect(qualifier, item) => - JSBracketSelect(transformExpr(qualifier), transformExpr(item)) + case JSSelect(qualifier, item) => + JSSelect(transformExpr(qualifier), transformExpr(item)) case JSFunctionApply(fun, args) => JSFunctionApply(transformExpr(fun), args.map(transformExprOrJSSpread)) - case JSDotMethodApply(receiver, method, args) => - JSDotMethodApply(transformExpr(receiver), method, + case JSMethodApply(receiver, method, args) => + JSMethodApply(transformExpr(receiver), transformExpr(method), args.map(transformExprOrJSSpread)) - case JSBracketMethodApply(receiver, method, args) => - JSBracketMethodApply(transformExpr(receiver), transformExpr(method), - args.map(transformExprOrJSSpread)) - - case JSSuperBracketSelect(superClass, qualifier, item) => - JSSuperBracketSelect(superClass, transformExpr(qualifier), + case JSSuperSelect(superClass, qualifier, item) => + JSSuperSelect(superClass, transformExpr(qualifier), transformExpr(item)) - case JSSuperBracketCall(superClass, receiver, method, args) => - JSSuperBracketCall(superClass, transformExpr(receiver), + case JSSuperMethodCall(superClass, receiver, method, args) => + JSSuperMethodCall(superClass, transformExpr(receiver), transformExpr(method), args.map(transformExprOrJSSpread)) case JSSuperConstructorCall(args) => @@ -174,8 +170,8 @@ object Transformers { case JSImportCall(arg) => JSImportCall(transformExpr(arg)) - case JSDelete(prop) => - JSDelete(transformExpr(prop)) + case JSDelete(qualifier, item) => + JSDelete(transformExpr(qualifier), transformExpr(item)) case JSUnaryOp(op, lhs) => JSUnaryOp(op, transformExpr(lhs)) diff --git a/ir/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/src/main/scala/org/scalajs/ir/Traversers.scala index 578200f063..87c68a460d 100644 --- a/ir/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/src/main/scala/org/scalajs/ir/Traversers.scala @@ -139,10 +139,10 @@ object Traversers { traverse(ctor) args.foreach(traverseTreeOrJSSpread) - case JSDotSelect(qualifier, item) => + case JSPrivateSelect(qualifier, item) => traverse(qualifier) - case JSBracketSelect(qualifier, item) => + case JSSelect(qualifier, item) => traverse(qualifier) traverse(item) @@ -150,21 +150,17 @@ object Traversers { traverse(fun) args.foreach(traverseTreeOrJSSpread) - case JSDotMethodApply(receiver, method, args) => - traverse(receiver) - args.foreach(traverseTreeOrJSSpread) - - case JSBracketMethodApply(receiver, method, args) => + case JSMethodApply(receiver, method, args) => traverse(receiver) traverse(method) args.foreach(traverseTreeOrJSSpread) - case JSSuperBracketSelect(superClass, qualifier, item) => + case JSSuperSelect(superClass, qualifier, item) => traverse(superClass) traverse(qualifier) traverse(item) - case JSSuperBracketCall(superClass, receiver, method, args) => + case JSSuperMethodCall(superClass, receiver, method, args) => traverse(superClass) traverse(receiver) traverse(method) @@ -176,8 +172,9 @@ object Traversers { case JSImportCall(arg) => traverse(arg) - case JSDelete(prop) => - traverse(prop) + case JSDelete(qualifier, item) => + traverse(qualifier) + traverse(item) case JSUnaryOp(op, lhs) => traverse(lhs) diff --git a/ir/src/main/scala/org/scalajs/ir/Trees.scala b/ir/src/main/scala/org/scalajs/ir/Trees.scala index 25ca915ce7..cff92bad9d 100644 --- a/ir/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/src/main/scala/org/scalajs/ir/Trees.scala @@ -170,8 +170,7 @@ object Trees { implicit val pos: Position) extends Tree { require(lhs match { case _:VarRef | _:Select | _:SelectStatic | _:ArraySelect | - _:JSDotSelect | _:JSBracketSelect | _:JSSuperBracketSelect | - _:JSGlobalRef => + _:JSPrivateSelect | _:JSSelect | _:JSSuperSelect | _:JSGlobalRef => true case _ => false @@ -489,12 +488,12 @@ object Trees { val tpe = AnyType } - case class JSDotSelect(qualifier: Tree, item: Ident)( + case class JSPrivateSelect(qualifier: Tree, item: Ident)( implicit val pos: Position) extends Tree { val tpe = AnyType } - case class JSBracketSelect(qualifier: Tree, item: Tree)( + case class JSSelect(qualifier: Tree, item: Tree)( implicit val pos: Position) extends Tree { val tpe = AnyType } @@ -504,12 +503,7 @@ object Trees { val tpe = AnyType } - case class JSDotMethodApply(receiver: Tree, method: Ident, - args: List[TreeOrJSSpread])(implicit val pos: Position) extends Tree { - val tpe = AnyType - } - - case class JSBracketMethodApply(receiver: Tree, method: Tree, + case class JSMethodApply(receiver: Tree, method: Tree, args: List[TreeOrJSSpread])(implicit val pos: Position) extends Tree { val tpe = AnyType } @@ -544,7 +538,7 @@ object Trees { * as if it were in an instance method of `Foo` with `qualifier` as the * `this` value. */ - case class JSSuperBracketSelect(superClass: Tree, receiver: Tree, item: Tree)( + case class JSSuperSelect(superClass: Tree, receiver: Tree, item: Tree)( implicit val pos: Position) extends Tree { val tpe = AnyType } @@ -593,7 +587,7 @@ object Trees { * super[method](...args) * }}} */ - case class JSSuperBracketCall(superClass: Tree, receiver: Tree, method: Tree, + case class JSSuperMethodCall(superClass: Tree, receiver: Tree, method: Tree, args: List[TreeOrJSSpread])(implicit val pos: Position) extends Tree { val tpe = AnyType } @@ -693,20 +687,15 @@ object Trees { } /** `...items`, the "spread" operator of ECMAScript 6. - * - * It is only valid in the `args`/`items` of a [[JSNew]], [[JSFunctionApply]], - * [[JSDotMethodApply]], [[JSBracketMethodApply]], or [[JSArrayConstr]]. * * @param items An Array whose items will be spread (not an arbitrary iterable) */ case class JSSpread(items: Tree)(implicit val pos: Position) extends IRNode with TreeOrJSSpread - case class JSDelete(prop: Tree)(implicit val pos: Position) extends Tree { - require(prop match { - case _:JSDotSelect | _:JSBracketSelect => true - case _ => false - }, s"Invalid prop for JSDelete: $prop") + /** `delete qualifier[item]` */ + case class JSDelete(qualifier: Tree, item: Tree)(implicit val pos: Position) + extends Tree { val tpe = NoType // cannot be in expression position } diff --git a/ir/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/src/test/scala/org/scalajs/ir/PrintersTest.scala index fecb8dc7ef..b92d9d0207 100644 --- a/ir/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -582,25 +582,25 @@ class PrintersTest { 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.C(4, 5)", - JSNew(JSDotSelect(ref("x", AnyType), "C"), List(i(4), i(5)))) + JSNew(JSPrivateSelect(ref("x", AnyType), "C"), List(i(4), i(5)))) assertPrintEquals("""new x["C"]()""", - JSNew(JSBracketSelect(ref("x", AnyType), StringLiteral("C")), Nil)) + JSNew(JSSelect(ref("x", AnyType), StringLiteral("C")), Nil)) val fApplied = JSFunctionApply(ref("f", AnyType), Nil) assertPrintEquals("new (f())()", JSNew(fApplied, Nil)) assertPrintEquals("new (f().C)(4, 5)", - JSNew(JSDotSelect(fApplied, "C"), List(i(4), i(5)))) + JSNew(JSPrivateSelect(fApplied, "C"), List(i(4), i(5)))) assertPrintEquals("""new (f()["C"])()""", - JSNew(JSBracketSelect(fApplied, StringLiteral("C")), Nil)) + JSNew(JSSelect(fApplied, StringLiteral("C")), Nil)) } - @Test def printJSDotSelect(): Unit = { - assertPrintEquals("x.f", JSDotSelect(ref("x", AnyType), "f")) + @Test def printJSPrivateSelect(): Unit = { + assertPrintEquals("x.f", JSPrivateSelect(ref("x", AnyType), "f")) } - @Test def printJSBracketSelect(): Unit = { + @Test def printJSSelect(): Unit = { assertPrintEquals("""x["f"]""", - JSBracketSelect(ref("x", AnyType), StringLiteral("f"))) + JSSelect(ref("x", AnyType), StringLiteral("f"))) } @Test def printJSFunctionApply(): Unit = { @@ -609,36 +609,30 @@ class PrintersTest { JSFunctionApply(ref("f", AnyType), List(i(3), i(4)))) assertPrintEquals("(0, x.f)()", - JSFunctionApply(JSDotSelect(ref("x", AnyType), "f"), Nil)) + JSFunctionApply(JSPrivateSelect(ref("x", AnyType), "f"), Nil)) assertPrintEquals("""(0, x["f"])()""", - JSFunctionApply(JSBracketSelect(ref("x", AnyType), StringLiteral("f")), + JSFunctionApply(JSSelect(ref("x", AnyType), StringLiteral("f")), Nil)) assertPrintEquals("(0, x.f$1)()", JSFunctionApply(Select(ref("x", "Ltest_Test"), "f$1")(AnyType), Nil)) } - @Test def printJSDotMethodApply(): Unit = { - assertPrintEquals("x.m()", JSDotMethodApply(ref("x", AnyType), "m", Nil)) - assertPrintEquals("x.m(4, 5)", - JSDotMethodApply(ref("x", AnyType), "m", List(i(4), i(5)))) - } - - @Test def printJSBracketMethodApply(): Unit = { + @Test def printJSMethodApply(): Unit = { assertPrintEquals("""x["m"]()""", - JSBracketMethodApply(ref("x", AnyType), StringLiteral("m"), Nil)) + JSMethodApply(ref("x", AnyType), StringLiteral("m"), Nil)) assertPrintEquals("""x["m"](4, 5)""", - JSBracketMethodApply(ref("x", AnyType), StringLiteral("m"), + JSMethodApply(ref("x", AnyType), StringLiteral("m"), List(i(4), i(5)))) } - @Test def printJSSuperBracketSelect(): Unit = { + @Test def printJSSuperSelect(): Unit = { assertPrintEquals("""super(sc)::x["f"]""", - JSSuperBracketSelect(ref("sc", AnyType), ref("x", AnyType), StringLiteral("f"))) + JSSuperSelect(ref("sc", AnyType), ref("x", AnyType), StringLiteral("f"))) } - @Test def printJSSuperBracketCall(): Unit = { + @Test def printJSSuperMethodCall(): Unit = { assertPrintEquals("""super(sc)::x["f"]()""", - JSSuperBracketCall(ref("sc", AnyType), ref("x", AnyType), StringLiteral("f"), Nil)) + JSSuperMethodCall(ref("sc", AnyType), ref("x", AnyType), StringLiteral("f"), Nil)) } @Test def printJSSuperConstructorCall(): Unit = { @@ -663,8 +657,8 @@ class PrintersTest { } @Test def printJSDelete(): Unit = { - assertPrintEquals("delete x.f", - JSDelete(JSDotSelect(ref("x", AnyType), "f"))) + assertPrintEquals("""delete x["f"]""", + JSDelete(ref("x", AnyType), StringLiteral("f"))) } @Test def printJSUnaryOp(): Unit = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index e7eb17b07e..1594a20b2f 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -577,7 +577,7 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { } } - case Assign(select @ JSDotSelect(qualifier, item), rhs) => + case Assign(select @ JSPrivateSelect(qualifier, item), rhs) => unnest(qualifier, rhs) { (newQualifier, newRhs, env0) => implicit val env = env0 js.Assign( @@ -586,7 +586,7 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { transformExprNoChar(newRhs)) } - case Assign(select @ JSBracketSelect(qualifier, item), rhs) => + case Assign(select @ JSSelect(qualifier, item), rhs) => unnest(List(qualifier, item, rhs)) { case (List(newQualifier, newItem, newRhs), env0) => implicit val env = env0 @@ -596,7 +596,7 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { transformExprNoChar(newRhs)) } - case Assign(select @ JSSuperBracketSelect(superClass, qualifier, item), rhs) => + case Assign(select @ JSSuperSelect(superClass, qualifier, item), rhs) => unnest(List(superClass, qualifier, item, rhs)) { case (List(newSuperClass, newQualifier, newItem, newRhs), env0) => implicit val env = env0 @@ -830,18 +830,11 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { js.Block(superCtorCall :: fieldDefs) } - case JSDelete(JSDotSelect(obj, prop)) => - unnest(obj) { (newObj, env0) => - implicit val env = env0 - js.Delete(js.DotSelect(transformExprNoChar(newObj), - transformPropIdent(prop))) - } - - case JSDelete(JSBracketSelect(obj, prop)) => - unnest(obj, prop) { (newObj, newProp, env0) => + case JSDelete(qualifier, item) => + unnest(qualifier, item) { (newQual, newItem, env0) => implicit val env = env0 js.Delete(genBracketSelect( - transformExprNoChar(newObj), transformExprNoChar(newProp))) + transformExprNoChar(newQual), transformExprNoChar(newItem))) } // Treat 'return' as an LHS @@ -1263,17 +1256,15 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { // JavaScript expressions that can always have side-effects case JSNew(fun, args) => allowSideEffects && test(fun) && (args.forall(testJSArg)) - case JSDotSelect(qualifier, item) => + case JSPrivateSelect(qualifier, item) => allowSideEffects && test(qualifier) - case JSBracketSelect(qualifier, item) => + case JSSelect(qualifier, item) => allowSideEffects && test(qualifier) && test(item) case JSFunctionApply(fun, args) => allowSideEffects && test(fun) && (args.forall(testJSArg)) - case JSDotMethodApply(receiver, method, args) => - allowSideEffects && test(receiver) && (args.forall(testJSArg)) - case JSBracketMethodApply(receiver, method, args) => + case JSMethodApply(receiver, method, args) => allowSideEffects && test(receiver) && test(method) && (args.forall(testJSArg)) - case JSSuperBracketSelect(superClass, qualifier, item) => + case JSSuperSelect(superClass, qualifier, item) => allowSideEffects && test(superClass) && test(qualifier) && test(item) case JSImportCall(arg) => allowSideEffects && test(arg) @@ -1705,7 +1696,7 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { case JSFunctionApply(fun, args) => if (containsAnySpread(args)) { redo { - JSBracketMethodApply(fun, StringLiteral("apply"), + JSMethodApply(fun, StringLiteral("apply"), List(Undefined(), spreadToArgArray(args))) } } else { @@ -1715,31 +1706,13 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { } } - case JSDotMethodApply(receiver, method, args) => - if (containsAnySpread(args)) { - withTempVar(receiver) { (newReceiver, env0) => - implicit val env = env0 - redo { - JSBracketMethodApply( - JSDotSelect(newReceiver, method), - StringLiteral("apply"), - List(newReceiver, spreadToArgArray(args))) - } - } - } else { - unnest(receiver :: castNoSpread(args)) { (newReceiverAndArgs, env) => - val newReceiver :: newArgs = newReceiverAndArgs - redo(JSDotMethodApply(newReceiver, method, newArgs))(env) - } - } - - case JSBracketMethodApply(receiver, method, args) => + case JSMethodApply(receiver, method, args) => if (containsAnySpread(args)) { withTempVar(receiver) { (newReceiver, env0) => implicit val env = env0 redo { - JSBracketMethodApply( - JSBracketSelect(newReceiver, method), + JSMethodApply( + JSSelect(newReceiver, method), StringLiteral("apply"), List(newReceiver, spreadToArgArray(args))) } @@ -1747,21 +1720,21 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { } else { unnest(receiver :: method :: castNoSpread(args)) { (newReceiverAndArgs, env) => val newReceiver :: newMethod :: newArgs = newReceiverAndArgs - redo(JSBracketMethodApply(newReceiver, newMethod, newArgs))(env) + redo(JSMethodApply(newReceiver, newMethod, newArgs))(env) } } - case JSSuperBracketSelect(superClass, qualifier, item) => + case JSSuperSelect(superClass, qualifier, item) => unnest(List(superClass, qualifier, item)) { case (List(newSuperClass, newQualifier, newItem), env) => - redo(JSSuperBracketSelect(newSuperClass, newQualifier, newItem))(env) + redo(JSSuperSelect(newSuperClass, newQualifier, newItem))(env) } - case JSSuperBracketCall(superClass, receiver, method, args) => + case JSSuperMethodCall(superClass, receiver, method, args) => redo { - JSBracketMethodApply( - JSBracketSelect( - JSBracketSelect(superClass, StringLiteral("prototype")), + JSMethodApply( + JSSelect( + JSSelect(superClass, StringLiteral("prototype")), method), StringLiteral("call"), receiver :: args) @@ -1772,14 +1745,14 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { redo(JSImportCall(newArg))(env) } - case JSDotSelect(qualifier, item) => + case JSPrivateSelect(qualifier, item) => unnest(qualifier) { (newQualifier, env) => - redo(JSDotSelect(newQualifier, item))(env) + redo(JSPrivateSelect(newQualifier, item))(env) } - case JSBracketSelect(qualifier, item) => + case JSSelect(qualifier, item) => unnest(qualifier, item) { (newQualifier, newItem, env) => - redo(JSBracketSelect(newQualifier, newItem))(env) + redo(JSSelect(newQualifier, newItem))(env) } case JSUnaryOp(op, lhs) => @@ -1839,11 +1812,11 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { */ value case prop: Ident => - Assign(JSDotSelect(objVarDef.ref, prop), value) + Assign(JSPrivateSelect(objVarDef.ref, prop), value) case prop: StringLiteral => - Assign(JSBracketSelect(objVarDef.ref, prop), value) + Assign(JSSelect(objVarDef.ref, prop), value) case ComputedName(tree, _) => - Assign(JSBracketSelect(objVarDef.ref, tree), value) + Assign(JSSelect(objVarDef.ref, tree), value) } (namesSeen ++ nameForDupes, stat :: statsAcc) }._2 @@ -1931,8 +1904,7 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { case List(part) => part case _ => val partHead :: partTail = reversedParts.reverse - JSBracketMethodApply( - partHead, StringLiteral("concat"), partTail) + JSMethodApply(partHead, StringLiteral("concat"), partTail) } } @@ -2519,10 +2491,10 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { case JSNew(constr, args) => js.New(transformExprNoChar(constr), args.map(transformJSArg)) - case JSDotSelect(qualifier, item) => + case JSPrivateSelect(qualifier, item) => js.DotSelect(transformExprNoChar(qualifier), transformPropIdent(item)) - case JSBracketSelect(qualifier, item) => + case JSSelect(qualifier, item) => genBracketSelect(transformExprNoChar(qualifier), transformExprNoChar(item)) @@ -2551,17 +2523,11 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { } js.Apply(protectedFun, args.map(transformJSArg)) - case JSDotMethodApply(receiver, method, args) => - js.Apply( - js.DotSelect(transformExprNoChar(receiver), - transformPropIdent(method)), - args.map(transformJSArg)) - - case JSBracketMethodApply(receiver, method, args) => + case JSMethodApply(receiver, method, args) => js.Apply(genBracketSelect(transformExprNoChar(receiver), transformExprNoChar(method)), args.map(transformJSArg)) - case JSSuperBracketSelect(superClass, qualifier, item) => + case JSSuperSelect(superClass, qualifier, item) => genCallHelper("superGet", transformExprNoChar(superClass), transformExprNoChar(qualifier), transformExprNoChar(item)) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 1787a6fb9a..92b10207e3 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -657,13 +657,9 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { case Debugger() => env - case JSDelete(JSDotSelect(obj, prop)) => - typecheckExpr(obj, env) - env - - case JSDelete(JSBracketSelect(obj, prop)) => - typecheckExpr(obj, env) - typecheckExpr(prop, env) + case JSDelete(qualifier, item) => + typecheckExpr(qualifier, env) + typecheckExpr(item, env) env case _ => @@ -969,10 +965,10 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { for (arg <- args) typecheckExprOrSpread(arg, env) - case JSDotSelect(qualifier, item) => + case JSPrivateSelect(qualifier, item) => typecheckExpr(qualifier, env) - case JSBracketSelect(qualifier, item) => + case JSSelect(qualifier, item) => typecheckExpr(qualifier, env) typecheckExpr(item, env) @@ -981,23 +977,18 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { for (arg <- args) typecheckExprOrSpread(arg, env) - case JSDotMethodApply(receiver, method, args) => - typecheckExpr(receiver, env) - for (arg <- args) - typecheckExprOrSpread(arg, env) - - case JSBracketMethodApply(receiver, method, args) => + case JSMethodApply(receiver, method, args) => typecheckExpr(receiver, env) typecheckExpr(method, env) for (arg <- args) typecheckExprOrSpread(arg, env) - case JSSuperBracketSelect(superClass, qualifier, item) => + case JSSuperSelect(superClass, qualifier, item) => typecheckExpr(superClass, env) typecheckExpr(qualifier, env) typecheckExpr(item, env) - case JSSuperBracketCall(superClass, receiver, method, args) => + case JSSuperMethodCall(superClass, receiver, method, args) => typecheckExpr(superClass, env) typecheckExpr(receiver, env) typecheckExpr(method, env) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 8410ad9113..886aed7241 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -336,8 +336,8 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { lhs match { case lhs: Select => pretransformSelectCommon(lhs, isLhsOfAssign = true)(cont) - case lhs: JSBracketSelect => - pretransformJSBracketSelect(lhs, isLhsOfAssign = true)(cont) + case lhs: JSSelect => + pretransformJSSelect(lhs, isLhsOfAssign = true)(cont) case _ => pretransformExpr(lhs)(cont) } @@ -563,12 +563,12 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { case JSNew(ctor, args) => JSNew(transformExpr(ctor), transformExprsOrSpreads(args)) - case JSDotSelect(qualifier, item) => - JSDotSelect(transformExpr(qualifier), item) + case JSPrivateSelect(qualifier, item) => + JSPrivateSelect(transformExpr(qualifier), item) - case tree: JSBracketSelect => + case tree: JSSelect => trampoline { - pretransformJSBracketSelect(tree, isLhsOfAssign = false)( + pretransformJSSelect(tree, isLhsOfAssign = false)( finishTransform(isStat)) } @@ -578,20 +578,16 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { finishTransform(isStat)) } - case JSDotMethodApply(receiver, method, args) => - JSDotMethodApply(transformExpr(receiver), method, + case JSMethodApply(receiver, method, args) => + JSMethodApply(transformExpr(receiver), transformExpr(method), transformExprsOrSpreads(args)) - case JSBracketMethodApply(receiver, method, args) => - JSBracketMethodApply(transformExpr(receiver), transformExpr(method), - transformExprsOrSpreads(args)) - - case JSSuperBracketSelect(superClass, qualifier, item) => - JSSuperBracketSelect(transformExpr(superClass), transformExpr(qualifier), + case JSSuperSelect(superClass, qualifier, item) => + JSSuperSelect(transformExpr(superClass), transformExpr(qualifier), transformExpr(item)) - case JSSuperBracketCall(superClass, receiver, method, args) => - JSSuperBracketCall(transformExpr(superClass), transformExpr(receiver), + case JSSuperMethodCall(superClass, receiver, method, args) => + JSSuperMethodCall(transformExpr(superClass), transformExpr(receiver), transformExpr(method), transformExprsOrSpreads(args)) case JSSuperConstructorCall(args) => @@ -600,11 +596,8 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { case JSImportCall(arg) => JSImportCall(transformExpr(arg)) - case JSDelete(JSDotSelect(obj, prop)) => - JSDelete(JSDotSelect(transformExpr(obj), prop)) - - case JSDelete(JSBracketSelect(obj, prop)) => - JSDelete(JSBracketSelect(transformExpr(obj), transformExpr(prop))) + case JSDelete(qualifier, item) => + JSDelete(transformExpr(qualifier), transformExpr(item)) case JSUnaryOp(op, lhs) => JSUnaryOp(op, transformExpr(lhs)) @@ -828,8 +821,8 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { case tree: BinaryOp => pretransformBinaryOp(tree)(cont) - case tree: JSBracketSelect => - pretransformJSBracketSelect(tree, isLhsOfAssign = false)(cont) + case tree: JSSelect => + pretransformJSSelect(tree, isLhsOfAssign = false)(cont) case tree: JSFunctionApply => pretransformJSFunctionApply(tree, isStat = false, @@ -1655,19 +1648,18 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { } } - private def pretransformJSBracketSelect(tree: JSBracketSelect, - isLhsOfAssign: Boolean)( + private def pretransformJSSelect(tree: JSSelect, isLhsOfAssign: Boolean)( cont: PreTransCont)( implicit scope: Scope): TailRec[Tree] = { - val JSBracketSelect(qual, item) = tree + val JSSelect(qual, item) = tree implicit val pos = tree.pos pretransformExprs(qual, item) { (tqual, titem0) => val titem = optimizeJSBracketSelectItem(titem0) def default: TailRec[Tree] = { - cont(PreTransTree(foldJSBracketSelect(finishTransformExpr(tqual), + cont(PreTransTree(foldJSSelect(finishTransformExpr(tqual), finishTransformExpr(titem)))) } @@ -3883,17 +3875,17 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { } } - private def foldJSBracketSelect(qualifier: Tree, item: Tree)( + private def foldJSSelect(qualifier: Tree, item: Tree)( implicit pos: Position): Tree = { // !!! Must be in sync with scala.scalajs.runtime.LinkingInfo import config.coreSpec._ @inline def default = - JSBracketSelect(qualifier, item) + JSSelect(qualifier, item) (qualifier, item) match { - case (JSBracketSelect(JSLinkingInfo(), StringLiteral("semantics")), + case (JSSelect(JSLinkingInfo(), StringLiteral("semantics")), StringLiteral(semanticsStr)) => def behavior2IntLiteral(behavior: CheckedBehavior) = { IntLiteral(behavior match { diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LinkerTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LinkerTest.scala index a08e1b9700..9ac9ef9c2d 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LinkerTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LinkerTest.scala @@ -44,7 +44,7 @@ class LinkerTest { def linkHelloWorld(): AsyncResult = await { val name = "LHelloWorld$" val mainMethodBody = { - JSBracketMethodApply(JSGlobalRef(Ident("console")), StringLiteral("log"), + JSMethodApply(JSGlobalRef(Ident("console")), StringLiteral("log"), List(StringLiteral("Hello world!"))) } val classDefs = Seq( From 36341c4b9adbd788c231bcafe0a957fd8e873b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 8 Mar 2019 15:49:54 +0100 Subject: [PATCH 0045/1606] Do not allow Idents as keys in JSObjectConstrs anymore. And simply store computed names as trees, without a special case for string literals. The capability of using `Ident`s as keys was not used by the compiler, and it is not quite clear what the meaning of doing so was supposed to be. --- .../main/scala/org/scalajs/ir/Hashers.scala | 4 +- .../main/scala/org/scalajs/ir/Printers.scala | 10 +- .../scala/org/scalajs/ir/Serializers.scala | 6 +- .../scala/org/scalajs/ir/Transformers.scala | 11 +-- .../scala/org/scalajs/ir/Traversers.scala | 6 +- ir/src/main/scala/org/scalajs/ir/Trees.scala | 2 +- .../scala/org/scalajs/ir/PrintersTest.scala | 4 +- .../backend/emitter/FunctionEmitter.scala | 93 +++++++------------ .../scalajs/linker/checker/IRChecker.scala | 4 +- .../frontend/optimizer/OptimizerCore.scala | 28 +----- 10 files changed, 60 insertions(+), 108 deletions(-) diff --git a/ir/src/main/scala/org/scalajs/ir/Hashers.scala b/ir/src/main/scala/org/scalajs/ir/Hashers.scala index a65da7dea8..ab74455f77 100644 --- a/ir/src/main/scala/org/scalajs/ir/Hashers.scala +++ b/ir/src/main/scala/org/scalajs/ir/Hashers.scala @@ -371,8 +371,8 @@ object Hashers { case JSObjectConstr(fields) => mixTag(TagJSObjectConstr) - fields foreach { case (pn, value) => - mixPropertyName(pn) + fields.foreach { case (key, value) => + mixTree(key) mixTree(value) } diff --git a/ir/src/main/scala/org/scalajs/ir/Printers.scala b/ir/src/main/scala/org/scalajs/ir/Printers.scala index 426cacbc39..2d69d48c15 100644 --- a/ir/src/main/scala/org/scalajs/ir/Printers.scala +++ b/ir/src/main/scala/org/scalajs/ir/Printers.scala @@ -667,7 +667,15 @@ object Printers { print('{'); indent(); println() var rest = fields while (rest.nonEmpty) { - print(rest.head._1) + val elem = rest.head + elem._1 match { + case key: StringLiteral => + print(key: Tree) + case key => + print('[') + print(key) + print(']') + } print(": ") print(rest.head._2) rest = rest.tail diff --git a/ir/src/main/scala/org/scalajs/ir/Serializers.scala b/ir/src/main/scala/org/scalajs/ir/Serializers.scala index c4a144e268..4294f016f9 100644 --- a/ir/src/main/scala/org/scalajs/ir/Serializers.scala +++ b/ir/src/main/scala/org/scalajs/ir/Serializers.scala @@ -369,8 +369,8 @@ object Serializers { case JSObjectConstr(fields) => writeByte(TagJSObjectConstr) writeInt(fields.size) - fields foreach { field => - writePropertyName(field._1); writeTree(field._2) + fields.foreach { field => + writeTree(field._1); writeTree(field._2) } case JSGlobalRef(ident) => @@ -920,7 +920,7 @@ object Serializers { case TagJSBinaryOp => JSBinaryOp(readInt(), readTree(), readTree()) case TagJSArrayConstr => JSArrayConstr(readTreeOrJSSpreads()) case TagJSObjectConstr => - JSObjectConstr(List.fill(readInt())((readPropertyName(), readTree()))) + JSObjectConstr(List.fill(readInt())((readTree(), readTree()))) case TagJSGlobalRef => JSGlobalRef(readIdent()) case TagJSLinkingInfo => JSLinkingInfo() diff --git a/ir/src/main/scala/org/scalajs/ir/Transformers.scala b/ir/src/main/scala/org/scalajs/ir/Transformers.scala index bdfe900814..6a22ff6ef9 100644 --- a/ir/src/main/scala/org/scalajs/ir/Transformers.scala +++ b/ir/src/main/scala/org/scalajs/ir/Transformers.scala @@ -183,15 +183,8 @@ object Transformers { JSArrayConstr(items.map(transformExprOrJSSpread)) case JSObjectConstr(fields) => - JSObjectConstr(fields map { - case (name, value) => - val newName = name match { - case ComputedName(tree, logicalName) => - ComputedName(transformExpr(tree), logicalName) - case _ => - name - } - (newName, transformExpr(value)) + JSObjectConstr(fields.map { field => + (transformExpr(field._1), transformExpr(field._2)) }) // Atomic expressions diff --git a/ir/src/main/scala/org/scalajs/ir/Traversers.scala b/ir/src/main/scala/org/scalajs/ir/Traversers.scala index 87c68a460d..3ae509e38e 100644 --- a/ir/src/main/scala/org/scalajs/ir/Traversers.scala +++ b/ir/src/main/scala/org/scalajs/ir/Traversers.scala @@ -188,11 +188,7 @@ object Traversers { case JSObjectConstr(fields) => for ((key, value) <- fields) { - key match { - case ComputedName(tree, _) => - traverse(tree) - case _ => - } + traverse(key) traverse(value) } diff --git a/ir/src/main/scala/org/scalajs/ir/Trees.scala b/ir/src/main/scala/org/scalajs/ir/Trees.scala index cff92bad9d..f200a6c210 100644 --- a/ir/src/main/scala/org/scalajs/ir/Trees.scala +++ b/ir/src/main/scala/org/scalajs/ir/Trees.scala @@ -769,7 +769,7 @@ object Trees { val tpe = AnyType } - case class JSObjectConstr(fields: List[(PropertyName, Tree)])( + case class JSObjectConstr(fields: List[(Tree, Tree)])( implicit val pos: Position) extends Tree { val tpe = AnyType } diff --git a/ir/src/test/scala/org/scalajs/ir/PrintersTest.scala b/ir/src/test/scala/org/scalajs/ir/PrintersTest.scala index b92d9d0207..99979c6c22 100644 --- a/ir/src/test/scala/org/scalajs/ir/PrintersTest.scala +++ b/ir/src/test/scala/org/scalajs/ir/PrintersTest.scala @@ -731,11 +731,11 @@ class PrintersTest { assertPrintEquals( """ |{ - | f: 5, + | [x]: 5, | "g": 6 |} """, - JSObjectConstr(List(Ident("f") -> i(5), StringLiteral("g") -> i(6)))) + JSObjectConstr(List(ref("x", AnyType) -> i(5), StringLiteral("g") -> i(6)))) } @Test def printGlobalRef(): Unit = { diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index 1594a20b2f..c2e3785b79 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -1013,15 +1013,10 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { case arg @ JSObjectConstr(items) if !doesObjectConstrRequireDesugaring(arg) => // We need to properly interleave keys and values here - val newItems = items.foldRight[List[(PropertyName, Tree)]](Nil) { + val newItems = items.foldRight[List[(Tree, Tree)]](Nil) { case ((key, value), acc) => val newValue = rec(value) // value first! - val newKey = key match { - case _:Ident | _:StringLiteral => - key - case ComputedName(keyExpr, logicalName) => - ComputedName(rec(keyExpr), logicalName) - } + val newKey = rec(key) (newKey, newValue) :: acc } JSObjectConstr(newItems) @@ -1109,34 +1104,23 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { } /** Unnest for the fields of a `JSObjectConstr`. */ - def unnestJSObjectConstrFields(fields: List[(PropertyName, Tree)])( - makeStat: (List[(PropertyName, Tree)], Env) => js.Tree)( + def unnestJSObjectConstrFields(fields: List[(Tree, Tree)])( + makeStat: (List[(Tree, Tree)], Env) => js.Tree)( implicit env: Env): js.Tree = { // Collect all the trees that need unnesting, in evaluation order - val trees = fields.flatMap { - case (ComputedName(tree, _), value) => List(tree, value) - case (_, value) => List(value) + val trees = fields.flatMap { field => + List(field._1, field._2) } unnest(trees) { (newTrees, env) => + // Re-decompose all the trees into pairs of (key, value) val newTreesIterator = newTrees.iterator + val newFields = List.newBuilder[(Tree, Tree)] + while (newTreesIterator.hasNext) + newFields += ((newTreesIterator.next(), newTreesIterator.next())) - val newFields = fields.map { - case (propName, value) => - val newPropName = propName match { - case ComputedName(_, logicalName) => - val newTree = newTreesIterator.next() - ComputedName(newTree, logicalName) - case _:StringLiteral | _:Ident => - propName - } - val newValue = newTreesIterator.next() - (newPropName, newValue) - } - - assert(!newTreesIterator.hasNext) - makeStat(newFields, env) + makeStat(newFields.result(), env) } } @@ -1223,10 +1207,7 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { allowUnpure && !doesObjectConstrRequireDesugaring(tree) && items.forall { item => - test(item._2) && (item._1 match { - case ComputedName(tree, _) => test(tree) - case _ => true - }) + test(item._1) && test(item._2) } case Closure(arrow, captureParams, params, body, captureValues) => allowUnpure && (captureValues forall test) @@ -1799,24 +1780,19 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { val objVarDef = VarDef(newSyntheticVar(), AnyType, mutable = false, JSObjectConstr(Nil)) val assignFields = fields.foldRight((Set.empty[String], List.empty[Tree])) { - case ((prop, value), (namesSeen, statsAcc)) => + case ((key, value), (namesSeen, statsAcc)) => implicit val pos = value.pos - val nameForDupes = prop match { - case _:StringLiteral | _:Ident => Some(prop.encodedName) - case _: ComputedName => None + val nameForDupes = key match { + case StringLiteral(s) => Some(s) + case _ => None } - val stat = prop match { - case _ if nameForDupes.exists(namesSeen) => - /* Important: do not emit the assignment, otherwise - * Closure recreates a literal with the duplicate field! - */ - value - case prop: Ident => - Assign(JSPrivateSelect(objVarDef.ref, prop), value) - case prop: StringLiteral => - Assign(JSSelect(objVarDef.ref, prop), value) - case ComputedName(tree, _) => - Assign(JSSelect(objVarDef.ref, tree), value) + val stat = if (nameForDupes.exists(namesSeen)) { + /* Important: do not emit the assignment, otherwise + * Closure recreates a literal with the duplicate field! + */ + value + } else { + Assign(JSSelect(objVarDef.ref, key), value) } (namesSeen ++ nameForDupes, stat :: statsAcc) }._2 @@ -1915,12 +1891,11 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { esFeatures.useECMAScript2015 def hasComputedName: Boolean = - tree.fields.exists(_._1.isInstanceOf[ComputedName]) + tree.fields.exists(!_._1.isInstanceOf[StringLiteral]) def hasDuplicateNonComputedProp: Boolean = { val names = tree.fields.collect { case (StringLiteral(name), _) => name - case (Ident(name, _), _) => name } names.toSet.size != names.size } @@ -2559,8 +2534,14 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { js.ArrayConstr(items.map(transformJSArg)) case JSObjectConstr(fields) => - js.ObjectConstr(fields map { case (name, value) => - (transformPropertyName(name), transformExprNoChar(value)) + js.ObjectConstr(fields.map { field => + val key = field._1 + implicit val pos = key.pos + val newKey = key match { + case StringLiteral(s) => js.StringLiteral(s) + case _ => js.ComputedName(transformExprNoChar(key)) + } + (newKey, transformExprNoChar(field._2)) }) case JSGlobalRef(name) => @@ -2710,16 +2691,6 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { paramDef.pos) } - def transformPropertyName(pName: PropertyName)( - implicit env: Env): js.PropertyName = { - implicit val pos = pName.pos - pName match { - case name: Ident => transformPropIdent(name) - case StringLiteral(s) => js.StringLiteral(s) - case ComputedName(tree, _) => js.ComputedName(transformExprNoChar(tree)) - } - } - private def transformLabelIdent(ident: Ident): js.Ident = js.Ident(ident.name, ident.originalName)(ident.pos) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala index 92b10207e3..8f6934e6a9 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/checker/IRChecker.scala @@ -1033,8 +1033,10 @@ private final class IRChecker(unit: LinkingUnit, logger: Logger) { typecheckExprOrSpread(item, env) case JSObjectConstr(fields) => - for ((_, value) <- fields) + for ((key, value) <- fields) { + typecheckExpr(key, env) typecheckExpr(value, env) + } case JSGlobalRef(_) => diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala index 886aed7241..8f42f6032d 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/optimizer/OptimizerCore.scala @@ -609,20 +609,8 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { JSArrayConstr(transformExprsOrSpreads(items)) case JSObjectConstr(fields) => - JSObjectConstr(fields map { - case (name, value) => - val newName = name match { - case _:StringLiteral | _:Ident => - name - case ComputedName(nameExpr, logicalName) => - transformExpr(nameExpr) match { - case newName: StringLiteral => - newName - case newName => - ComputedName(newName, logicalName) - } - } - (newName, transformExpr(value)) + JSObjectConstr(fields.map { field => + (transformExpr(field._1), transformExpr(field._2)) }) // Atomic expressions @@ -2157,7 +2145,7 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { jsArray.replacement match { case InlineJSArrayReplacement(elemLocalDefs, _) if elemLocalDefs.forall(e => isSubtype(e.tpe.base, ClassType("T2"))) => - val fields: List[(PropertyName, Tree)] = for { + val fields: List[(Tree, Tree)] = for { (elemLocalDef, idx) <- elemLocalDefs.toList.zipWithIndex } yield { elemLocalDef match { @@ -2165,13 +2153,7 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { InlineClassInstanceReplacement(recType, tupleFields, _)) => val List(key, value) = recType.fields.map(f => tupleFields(f.name)) - val keyProp = key.newReplacement match { - case keyProp: StringLiteral => - keyProp - case keyTree => - ComputedName(keyTree, "local" + idx) - } - (keyProp, value.newReplacement) + (key.newReplacement, value.newReplacement) case _ => val flags = ApplyFlags.empty @@ -2179,7 +2161,7 @@ private[optimizer] abstract class OptimizerCore(config: CommonPhaseConfig) { "$$und1__O", Nil)(AnyType) val value = Apply(flags, elemLocalDef.newReplacement, "$$und2__O", Nil)(AnyType) - (ComputedName(key, "local" + idx), value) + (key, value) } } From ccd346ce7fdd2282bae4cf607d9c4f2ad3677ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 5 Aug 2019 13:29:23 +0200 Subject: [PATCH 0046/1606] Remove support for recognizing Rhino stack traces in `StackTrace`. It is very unlikely that Rhino or Nashorn support will ever be revived, so keeping the support is not worth it, especially since it uses a `try..catch`. --- .../src/main/scala/java/lang/StackTrace.scala | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/javalanglib/src/main/scala/java/lang/StackTrace.scala b/javalanglib/src/main/scala/java/lang/StackTrace.scala index 4e979609e4..c04ed98e51 100644 --- a/javalanglib/src/main/scala/java/lang/StackTrace.scala +++ b/javalanglib/src/main/scala/java/lang/StackTrace.scala @@ -86,22 +86,6 @@ private[lang] object StackTrace { @inline def captureState(throwable: Throwable, e: Any): Unit = throwable.setStackTraceStateInternal(e) - /** Tests whether we're running under Rhino (or Nashorn). - * - * Even though we do not support Rhino nor Nashorn in the core repository, - * we can always hope that someone will eventually pull off a third-party JS - * env that manages to use either without surgery in the Scala.js linker. - * So we keep support of stack trace detection for those engines. - */ - private lazy val isRhino: scala.Boolean = { - try { - js.Dynamic.global.Packages.org.mozilla.javascript.JavaScriptException - true - } catch { - case js.JavaScriptException(_) => false - } - } - /** 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 @@ -321,8 +305,6 @@ private[lang] object StackTrace { if (!e) { js.Array[String]() - } else if (isRhino) { - extractRhino(e) } else if (e.arguments && e.stack) { extractChrome(e) } else if (e.stack && e.sourceURL) { @@ -361,14 +343,6 @@ private[lang] object StackTrace { } } - private def extractRhino(e: js.Dynamic): js.Array[String] = { - (e.stack.asInstanceOf[js.UndefOr[String]]).getOrElse("") - .jsReplace("""^\s+at\s+""".re("gm"), "") // remove 'at' and indentation - .jsReplace("""^(.+?)(?: \((.+)\))?$""".re("gm"), "$2@$1") - .jsReplace("""\r\n?""".re("gm"), "\n") // Rhino has platform-dependent EOL's - .jsSplit("\n") - } - private def extractChrome(e: js.Dynamic): js.Array[String] = { (e.stack.asInstanceOf[String] + "\n") .jsReplace("""^[\s\S]+?\s+at\s+""".re, " at ") // remove message From 64121eab5f9e6cd9f08457669657f07e3d3a3b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 5 Aug 2019 13:36:59 +0200 Subject: [PATCH 0047/1606] Directly create `StackTraceElement`s in `normalizedLinesToStackTrace`. Previously, stack trace elements were first created as a JS-friendly structure before being converted to actual `StackTraceElement`. It used to be necessary to support `__ScalaJSEnv.sourceMapper`, but that feature has been removed. Therefore, we now directly create the resulting `StackTraceElement`s. --- .../src/main/scala/java/lang/StackTrace.scala | 52 +++++-------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/javalanglib/src/main/scala/java/lang/StackTrace.scala b/javalanglib/src/main/scala/java/lang/StackTrace.scala index c04ed98e51..83665a9588 100644 --- a/javalanglib/src/main/scala/java/lang/StackTrace.scala +++ b/javalanglib/src/main/scala/java/lang/StackTrace.scala @@ -121,7 +121,7 @@ private[lang] object StackTrace { val NormalizedFrameLine = """^([^\@]*)\@(.*):([0-9]+)$""".re val NormalizedFrameLineWithColumn = """^([^\@]*)\@(.*):([0-9]+):([0-9]+)$""".re - val trace = new js.Array[JSStackTraceElem] + val trace = js.Array[StackTraceElement]() var i = 0 while (i < lines.length) { val line = lines(i) @@ -129,34 +129,31 @@ private[lang] object StackTrace { val mtch1 = NormalizedFrameLineWithColumn.exec(line) if (mtch1 ne null) { val (className, methodName) = extractClassMethod(mtch1(1).get) - trace.push(JSStackTraceElem(className, methodName, mtch1(2).get, - parseInt(mtch1(3).get), parseInt(mtch1(4).get))) + val elem = new StackTraceElement(className, methodName, mtch1(2).get, + parseInt(mtch1(3).get)) + elem.setColumnNumber(parseInt(mtch1(4).get)) + trace.push(elem) } else { val mtch2 = NormalizedFrameLine.exec(line) if (mtch2 ne null) { val (className, methodName) = extractClassMethod(mtch2(1).get) - trace.push(JSStackTraceElem(className, + trace.push(new StackTraceElement(className, methodName, mtch2(2).get, parseInt(mtch2(3).get))) } else { // just in case - trace.push(JSStackTraceElem("", line, null, -1)) + trace.push(new StackTraceElement("", line, null, -1)) } } } i += 1 } - // Convert JS objects to java.lang.StackTraceElements - // While loop due to space concerns - val result = new Array[StackTraceElement](trace.length) - + // Convert the JS array into a Scala array + val len = trace.length + val result = new Array[StackTraceElement](len) i = 0 - while (i < trace.length) { - val jsSte = trace(i) - val ste = new StackTraceElement(jsSte.declaringClass, jsSte.methodName, - jsSte.fileName, jsSte.lineNumber) - jsSte.columnNumber.foreach(ste.setColumnNumber) - result(i) = ste + while (i < len) { + result(i) = trace(i) i += 1 } @@ -479,29 +476,4 @@ private[lang] object StackTrace { * --------------------------------------------------------------------------- */ - private trait JSStackTraceElem extends js.Object { - var declaringClass: String - var methodName: String - var fileName: String - /** 1-based line number */ - var lineNumber: Int - /** 1-based optional columnNumber */ - var columnNumber: js.UndefOr[Int] = js.undefined - } - - private object JSStackTraceElem { - @inline - def apply(declaringClass: String, methodName: String, - fileName: String, lineNumber: Int, - columnNumber: js.UndefOr[Int] = js.undefined): JSStackTraceElem = { - js.Dynamic.literal( - declaringClass = declaringClass, - methodName = methodName, - fileName = fileName, - lineNumber = lineNumber, - columnNumber = columnNumber - ).asInstanceOf[JSStackTraceElem] - } - } - } From 0df32dcd4929c35f52ca77c6c3c2d06b8f2661f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 26 Aug 2019 10:56:22 +0200 Subject: [PATCH 0048/1606] Add testSuiteLinker to `allProjects` in the build. Adding it there was forgotten when the project was introduced in f73592f0bad7faf06daf44449a93aab88ac0deee. --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index e934e2ae1a..e8c55b884c 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -520,7 +520,7 @@ object Build { testInterface, jUnitRuntime, testBridge, jUnitPlugin, jUnitAsyncJS, jUnitAsyncJVM, jUnitTestOutputsJS, jUnitTestOutputsJVM, helloworld, reversi, testingExample, testSuite, testSuiteJVM, - testSuiteEx, + testSuiteEx, testSuiteLinker, partest, partestSuite, scalaTestSuite ) From ad275d13d7878f280c359a562ef263f29221fe6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 26 Aug 2019 11:11:25 +0200 Subject: [PATCH 0049/1606] Fix two `logger.time` calls as `logger.timeFuture`. --- .../main/scala/org/scalajs/linker/backend/emitter/Emitter.scala | 2 +- .../scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala | 2 +- .../src/main/scala/org/scalajs/linker/frontend/Refiner.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala index 6b7e4bacb5..6a53cedeae 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala @@ -401,7 +401,7 @@ final class Emitter private (config: CommonPhaseConfig, s"Emitter: Class tree cache stats: reused: $statsClassesReused -- "+ s"invalidated: $statsClassesInvalidated") logger.debug( - s"Emitter: Method tree cache stats: resued: $statsMethodsReused -- "+ + s"Emitter: Method tree cache stats: reused: $statsMethodsReused -- "+ s"invalidated: $statsMethodsInvalidated") classCaches.filterInPlace((_, c) => c.cleanAfterRun()) } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala index cfc77cce39..c193565ec7 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkerFrontendImpl.scala @@ -73,7 +73,7 @@ final class LinkerFrontendImpl private (config: LinkerFrontendImpl.Config) optimizer.update(unit, logger) } - logger.time("Refiner") { + logger.timeFuture("Refiner") { refiner.refine(optimized, symbolRequirements, logger) } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala index bd225942e9..0a8e888911 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Refiner.scala @@ -39,7 +39,7 @@ final class Refiner(config: CommonPhaseConfig) { Map(unit.classDefs.map(c => c.encodedName -> c): _*) inputProvider.update(linkedClassesByName) - val analysis = logger.time("Refiner: Compute reachability") { + val analysis = logger.timeFuture("Refiner: Compute reachability") { val allSymbolRequirements = { symbolRequirements ++ ModuleInitializer.toSymbolRequirement(unit.moduleInitializers) From 3877d864cf0552b9ffe6b8d36123fdb9141fcb6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 5 Jul 2019 17:52:23 +0200 Subject: [PATCH 0050/1606] Make the IR of the javalanglib independent of the Scala library. We do this with a combination of * Changes in the sources in order not to refer to any non-native code from the Scala library. * A postprocessor for the IR to erase native JS types to `Object`. --- .../scala/org/scalajs/ir/Definitions.scala | 37 +- .../src/main/scala/java/lang/Byte.scala | 2 +- .../src/main/scala/java/lang/Class.scala | 8 +- .../src/main/scala/java/lang/Double.scala | 16 +- .../scala/java/lang/FloatingPointBits.scala | 39 +- .../src/main/scala/java/lang/Integer.scala | 8 +- .../src/main/scala/java/lang/Long.scala | 17 +- .../src/main/scala/java/lang/Math.scala | 21 +- .../main/scala/java/lang/ObjectClone.scala | 20 +- .../main/scala/java/lang/SemanticsUtils.scala | 58 +++ .../src/main/scala/java/lang/Short.scala | 2 +- .../src/main/scala/java/lang/StackTrace.scala | 127 ++--- .../scala/java/lang/StackTraceElement.scala | 6 +- .../src/main/scala/java/lang/System.scala | 127 +++-- .../src/main/scala/java/lang/Throwables.scala | 2 +- .../src/main/scala/java/lang/Utils.scala | 135 ++++++ .../src/main/scala/java/lang/_String.scala | 3 +- project/Build.scala | 32 +- project/JavalibIRCleaner.scala | 441 ++++++++++++++++++ 19 files changed, 901 insertions(+), 200 deletions(-) create mode 100644 javalanglib/src/main/scala/java/lang/SemanticsUtils.scala create mode 100644 javalanglib/src/main/scala/java/lang/Utils.scala create mode 100644 project/JavalibIRCleaner.scala diff --git a/ir/src/main/scala/org/scalajs/ir/Definitions.scala b/ir/src/main/scala/org/scalajs/ir/Definitions.scala index acdad8e96f..0624e6a3ba 100644 --- a/ir/src/main/scala/org/scalajs/ir/Definitions.scala +++ b/ir/src/main/scala/org/scalajs/ir/Definitions.scala @@ -138,6 +138,25 @@ object Definitions { private val decompressedPrefixes: Seq[(String, String)] = compressedPrefixes map { case (a, b) => (b, a) } + /** Encodes a method name from its full signature. */ + def encodeMethodName(baseName: String, paramTypes: List[TypeRef], + resultType: Option[TypeRef]): String = { + + val paramTypesString = paramTypes.map(encodeTypeRef).mkString("__") + + if (baseName == "") { + assert(paramTypes.isEmpty && resultType.isEmpty) + StaticInitializerName + } else if (baseName == "") { + assert(resultType.isEmpty) + paramTypes.map(encodeTypeRef).mkString("init___", "__", "") + } else { + val resultTypeString = resultType.fold("")(encodeTypeRef) + (paramTypes.map(encodeTypeRef) :+ resultTypeString).mkString( + baseName + "__", "__", "") + } + } + /** Decodes a method name into its full signature. */ def decodeMethodName( encodedName: String): (String, List[TypeRef], Option[TypeRef]) = { @@ -154,11 +173,7 @@ object Definitions { } // -1 preserves trailing empty strings - val parts = privateAndSigString.split("__", -1).toSeq - val paramsAndResultStrings = - if (parts.headOption.exists(_.startsWith("p"))) parts.tail - else parts - + val paramsAndResultStrings = privateAndSigString.split("__", -1).toSeq val paramStrings :+ resultString = paramsAndResultStrings val paramTypes = paramStrings.map(decodeTypeRef).toList @@ -169,6 +184,18 @@ object Definitions { (simpleName, paramTypes, resultType) } + /** Encodes a [[Types.TypeRef]] to be used for example in an encoded method + * signature. + */ + def encodeTypeRef(typeRef: TypeRef): String = { + typeRef match { + case ClassRef(className) => + className + case ArrayTypeRef(baseClassName, dimensions) => + "A" * dimensions + baseClassName + } + } + /** Decodes a [[Types.TypeRef]], such as in an encoded method signature. */ def decodeTypeRef(encodedName: String): TypeRef = { diff --git a/javalanglib/src/main/scala/java/lang/Byte.scala b/javalanglib/src/main/scala/java/lang/Byte.scala index 15f6bb8a0c..25510909dc 100644 --- a/javalanglib/src/main/scala/java/lang/Byte.scala +++ b/javalanglib/src/main/scala/java/lang/Byte.scala @@ -76,7 +76,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(s"""For input string: "$s"""") + throw new NumberFormatException("For input string: \"" + s + "\"") else r.toByte } diff --git a/javalanglib/src/main/scala/java/lang/Class.scala b/javalanglib/src/main/scala/java/lang/Class.scala index 3c1267e860..a02cd05e2d 100644 --- a/javalanglib/src/main/scala/java/lang/Class.scala +++ b/javalanglib/src/main/scala/java/lang/Class.scala @@ -84,9 +84,11 @@ final class Class[A] private (data0: Object) extends Object { @inline // optimize for the Unchecked case, where this becomes identity() def cast(obj: Object): A = { - scala.scalajs.runtime.SemanticsUtils.asInstanceOfCheck( - (obj != null && !isJSType && !isInstance(obj)), - new ClassCastException("" + obj + " is not an instance of " + getName)) + SemanticsUtils.asInstanceOfCheck({ () => + (obj != null && !isJSType && !isInstance(obj)) + }, { () => + new ClassCastException("" + obj + " is not an instance of " + getName) + }) obj.asInstanceOf[A] } diff --git a/javalanglib/src/main/scala/java/lang/Double.scala b/javalanglib/src/main/scala/java/lang/Double.scala index d47fadc085..646c129264 100644 --- a/javalanglib/src/main/scala/java/lang/Double.scala +++ b/javalanglib/src/main/scala/java/lang/Double.scala @@ -112,7 +112,7 @@ object Double { def parseDouble(s: String): scala.Double = { def fail(): Nothing = - throw new NumberFormatException(s"""For input string: "$s"""") + throw new NumberFormatException("For input string: \"" + s + "\"") // (Very) slow path for hexadecimal notation def parseHexDoubleImpl(match2: js.RegExp.ExecResult): scala.Double = { @@ -184,12 +184,16 @@ object Double { * `binaryExp` (see below) did not stray too far from that range itself. */ - val mantissa = - js.Dynamic.global.parseInt(truncatedMantissaStr, 16).asInstanceOf[scala.Double] + @inline def nativeParseInt(s: String, radix: Int): scala.Double = { + js.Dynamic.global + .parseInt(s.asInstanceOf[js.Any], radix.asInstanceOf[js.Any]) + .asInstanceOf[scala.Double] + } + + val mantissa = nativeParseInt(truncatedMantissaStr, 16) // Assert: mantissa != 0.0 && mantissa != scala.Double.PositiveInfinity - val binaryExpDouble = - js.Dynamic.global.parseInt(binaryExpStr, 10).asInstanceOf[scala.Double] + val binaryExpDouble = nativeParseInt(binaryExpStr, 10) val binaryExp = binaryExpDouble.toInt // caps to [MinValue, MaxValue] val binExpAndCorrection = binaryExp + fullCorrection @@ -221,7 +225,7 @@ object Double { val match1 = doubleStrPat.exec(s) if (match1 != null) { - js.Dynamic.global.parseFloat(match1(1)).asInstanceOf[scala.Double] + js.Dynamic.global.parseFloat(match1(1).asInstanceOf[js.Any]).asInstanceOf[scala.Double] } else { val match2 = doubleStrHexPat.exec(s) if (match2 != null) diff --git a/javalanglib/src/main/scala/java/lang/FloatingPointBits.scala b/javalanglib/src/main/scala/java/lang/FloatingPointBits.scala index d6a700db00..2c051c32ba 100644 --- a/javalanglib/src/main/scala/java/lang/FloatingPointBits.scala +++ b/javalanglib/src/main/scala/java/lang/FloatingPointBits.scala @@ -19,11 +19,11 @@ import js.typedarray /** Manipulating the bits of floating point numbers. */ private[lang] object FloatingPointBits { - import scala.scalajs.LinkingInfo.assumingES6 + import scala.scalajs.runtime.linkingInfo private[this] val _areTypedArraysSupported = { // Here we use `assumingES6` to dce the 4 subsequent tests - assumingES6 || { + linkingInfo.assumingES6 || { js.typeOf(global.ArrayBuffer) != "undefined" && js.typeOf(global.Int32Array) != "undefined" && js.typeOf(global.Float32Array) != "undefined" && @@ -41,7 +41,7 @@ private[lang] object FloatingPointBits { * * If we emit ES5, replace `areTypedArraysSupported` by * `_areTypedArraysSupported` so we do not calculate it multiple times. */ - assumingES6 || _areTypedArraysSupported + linkingInfo.assumingES6 || _areTypedArraysSupported } private val arrayBuffer = @@ -153,18 +153,16 @@ private[lang] object FloatingPointBits { private def floatToIntBitsPolyfill(value: scala.Double): Int = { val ebits = 8 val fbits = 23 - val (s, e, f) = encodeIEEE754(ebits, fbits, value) - (if (s) 0x80000000 else 0) | (e << fbits) | rawToInt(f) + val sef = encodeIEEE754(ebits, fbits, value) + (if (sef.s) 0x80000000 else 0) | (sef.e << fbits) | rawToInt(sef.f) } private def longBitsToDoublePolyfill(bits: scala.Long): scala.Double = { - import js.JSNumberOps._ - val ebits = 11 val fbits = 52 val hifbits = fbits-32 val hi = (bits >>> 32).toInt - val lo = bits.toInt.toUint + val lo = Utils.toUint(bits.toInt) val s = hi < 0 val e = (hi >> hifbits) & ((1 << ebits) - 1) val f = (hi & ((1 << hifbits) - 1)).toDouble * 0x100000000L.toDouble + lo @@ -175,10 +173,10 @@ private[lang] object FloatingPointBits { val ebits = 11 val fbits = 52 val hifbits = fbits-32 - val (s, e, f) = encodeIEEE754(ebits, fbits, value) - val hif = rawToInt(f / 0x100000000L.toDouble) - val hi = (if (s) 0x80000000 else 0) | (e << hifbits) | hif - val lo = rawToInt(f) + val sef = encodeIEEE754(ebits, fbits, value) + val hif = rawToInt(sef.f / 0x100000000L.toDouble) + val hi = (if (sef.s) 0x80000000 else 0) | (sef.e << hifbits) | hif + val lo = rawToInt(sef.f) (hi.toLong << 32) | (lo.toLong & 0xffffffffL) } @@ -209,7 +207,7 @@ private[lang] object FloatingPointBits { } @inline private def encodeIEEE754(ebits: Int, fbits: Int, - v: scala.Double): (scala.Boolean, Int, scala.Double) = { + v: scala.Double): EncodeIEEE754Result = { import Math._ @@ -217,11 +215,11 @@ private[lang] object FloatingPointBits { if (Double.isNaN(v)) { // http://dev.w3.org/2006/webapi/WebIDL/#es-type-mapping - (false, (1 << ebits) - 1, pow(2, fbits-1)) + new EncodeIEEE754Result(false, (1 << ebits) - 1, pow(2, fbits-1)) } else if (Double.isInfinite(v)) { - (v < 0, (1 << ebits) - 1, 0.0) + new EncodeIEEE754Result(v < 0, (1 << ebits) - 1, 0.0) } else if (v == 0.0) { - (1 / v == scala.Double.NegativeInfinity, 0, 0.0) + new EncodeIEEE754Result(1 / v == scala.Double.NegativeInfinity, 0, 0.0) } else { val LN2 = 0.6931471805599453 @@ -259,10 +257,10 @@ private[lang] object FloatingPointBits { e = e + bias f = f - twoPowFbits } - (s, e, f) + new EncodeIEEE754Result(s, e, f) } else { // Subnormal - (s, 0, roundToEven(av / pow(2, 1-bias-fbits))) + new EncodeIEEE754Result(s, 0, roundToEven(av / pow(2, 1-bias-fbits))) } } } @@ -279,4 +277,9 @@ private[lang] object FloatingPointBits { else w } + // Cannot use tuples in the javalanglib + @inline + private final class EncodeIEEE754Result(val s: scala.Boolean, val e: Int, + val f: scala.Double) + } diff --git a/javalanglib/src/main/scala/java/lang/Integer.scala b/javalanglib/src/main/scala/java/lang/Integer.scala index a9a7763fa0..7a1fdf1049 100644 --- a/javalanglib/src/main/scala/java/lang/Integer.scala +++ b/javalanglib/src/main/scala/java/lang/Integer.scala @@ -77,7 +77,7 @@ object Integer { signed: scala.Boolean): scala.Int = { def fail(): Nothing = - throw new NumberFormatException(s"""For input string: "$s"""") + throw new NumberFormatException("For input string: \"" + s + "\"") val len = if (s == null) 0 else s.length @@ -123,9 +123,9 @@ object Integer { if (x == y) 0 else if (x < y) -1 else 1 @inline def compareUnsigned(x: scala.Int, y: scala.Int): scala.Int = { - import js.JSNumberOps._ + import Utils.toUint if (x == y) 0 - else if (x.toUint > y.toUint) 1 + else if (toUint(x) > toUint(y)) 1 else -1 } @@ -235,7 +235,7 @@ object Integer { if (radix == 10 || radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { Integer.toString(i) } else { - import js.JSNumberOps.enableJSNumberOps + import Utils.Implicits.enableJSNumberOps i.toString(radix) } } diff --git a/javalanglib/src/main/scala/java/lang/Long.scala b/javalanglib/src/main/scala/java/lang/Long.scala index f0b8b042fd..449bcf860c 100644 --- a/javalanglib/src/main/scala/java/lang/Long.scala +++ b/javalanglib/src/main/scala/java/lang/Long.scala @@ -71,7 +71,7 @@ object Long { var radix = 0 while (radix < Character.MIN_RADIX) { - r += null + r.push(null) radix += 1 } @@ -96,8 +96,8 @@ object Long { } val radixPowLengthLong = radixPowLength.toLong val overflowBarrier = Long.divideUnsigned(-1L, radixPowLengthLong) - r += new StringRadixInfo(chunkLength, radixPowLengthLong, - paddingZeros, overflowBarrier) + r.push(new StringRadixInfo(chunkLength, radixPowLengthLong, + paddingZeros, overflowBarrier)) radix += 1 } @@ -138,7 +138,7 @@ object Long { val hi = (i >>> 32).toInt if (lo >> 31 == hi) { // It's a signed int32 - import js.JSNumberOps.enableJSNumberOps + import Utils.Implicits.enableJSNumberOps lo.toString(radix) } else if (hi < 0) { "-" + toUnsignedStringInternalLarge(-i, radix) @@ -151,8 +151,8 @@ object Long { private def toUnsignedStringImpl(i: scala.Long, radix: Int): String = { if ((i >>> 32).toInt == 0) { // It's an unsigned int32 - import js.JSNumberOps._ - i.toInt.toUint.toString(radix) + import Utils.Implicits.enableJSNumberOps + Utils.toUint(i.toInt).toString(radix) } else { toUnsignedStringInternalLarge(i, radix) } @@ -160,8 +160,7 @@ object Long { // Must be called only with valid radix private def toUnsignedStringInternalLarge(i: scala.Long, radix: Int): String = { - import js.JSNumberOps._ - import js.JSStringOps._ + import Utils.Implicits._ val radixInfo = StringRadixInfos(radix) val divisor = radixInfo.radixPowLength @@ -312,7 +311,7 @@ object Long { } private def parseLongError(s: String): Nothing = - throw new NumberFormatException(s"""For input string: "$s"""") + throw new NumberFormatException("For input string: \"" + s + "\"") @inline def `new`(value: scala.Long): Long = valueOf(value) diff --git a/javalanglib/src/main/scala/java/lang/Math.scala b/javalanglib/src/main/scala/java/lang/Math.scala index df510cee73..f9386b1210 100644 --- a/javalanglib/src/main/scala/java/lang/Math.scala +++ b/javalanglib/src/main/scala/java/lang/Math.scala @@ -16,12 +16,15 @@ package lang import scala.scalajs.js import js.Dynamic.{ global => g } -import scala.scalajs.LinkingInfo.assumingES6 +import scala.scalajs.runtime.linkingInfo object Math { final val E = 2.718281828459045 final val PI = 3.141592653589793 + @inline private def assumingES6: scala.Boolean = + linkingInfo.assumingES6 + @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 @inline def abs(a: scala.Float): scala.Float = js.Math.abs(a).toFloat @@ -63,14 +66,14 @@ object Math { @inline def log(a: scala.Double): scala.Double = js.Math.log(a) @inline def log10(a: scala.Double): scala.Double = { - if (assumingES6 || !js.isUndefined(g.Math.log10)) + if (assumingES6 || !Utils.isUndefined(g.Math.log10)) js.Math.log10(a) else log(a) / 2.302585092994046 } @inline def log1p(a: scala.Double): scala.Double = { - if (assumingES6 || !js.isUndefined(g.Math.log1p)) + if (assumingES6 || !Utils.isUndefined(g.Math.log1p)) js.Math.log1p(a) else if (a == 0.0) a else log(a + 1) @@ -102,7 +105,7 @@ object Math { } def cbrt(a: scala.Double): scala.Double = { - if (assumingES6 || !js.isUndefined(g.Math.cbrt)) { + if (assumingES6 || !Utils.isUndefined(g.Math.cbrt)) { js.Math.cbrt(a) } else { if (a == 0 || Double.isNaN(a) || Double.isInfinite(a)) { @@ -207,7 +210,7 @@ object Math { } def hypot(a: scala.Double, b: scala.Double): scala.Double = { - if (assumingES6 || !js.isUndefined(g.Math.hypot)) { + if (assumingES6 || !Utils.isUndefined(g.Math.hypot)) { js.Math.hypot(a, b) } else { // http://en.wikipedia.org/wiki/Hypot#Implementation @@ -230,7 +233,7 @@ object Math { } def expm1(a: scala.Double): scala.Double = { - if (assumingES6 || !js.isUndefined(g.Math.expm1)) { + if (assumingES6 || !Utils.isUndefined(g.Math.expm1)) { js.Math.expm1(a) } else { // https://github.com/ghewgill/picomath/blob/master/javascript/expm1.js @@ -246,7 +249,7 @@ object Math { } def sinh(a: scala.Double): scala.Double = { - if (assumingES6 || !js.isUndefined(g.Math.sinh)) { + if (assumingES6 || !Utils.isUndefined(g.Math.sinh)) { js.Math.sinh(a) } else { if (Double.isNaN(a) || a == 0.0 || abs(a) == scala.Double.PositiveInfinity) a @@ -255,7 +258,7 @@ object Math { } def cosh(a: scala.Double): scala.Double = { - if (assumingES6 || !js.isUndefined(g.Math.cosh)) { + if (assumingES6 || !Utils.isUndefined(g.Math.cosh)) { js.Math.cosh(a) } else { if (Double.isNaN(a)) @@ -270,7 +273,7 @@ object Math { } def tanh(a: scala.Double): scala.Double = { - if (assumingES6 || !js.isUndefined(g.Math.tanh)) { + if (assumingES6 || !Utils.isUndefined(g.Math.tanh)) { js.Math.tanh(a) } else { if (Double.isNaN(a) || a == 0.0) diff --git a/javalanglib/src/main/scala/java/lang/ObjectClone.scala b/javalanglib/src/main/scala/java/lang/ObjectClone.scala index aa19205614..1a40c78718 100644 --- a/javalanglib/src/main/scala/java/lang/ObjectClone.scala +++ b/javalanglib/src/main/scala/java/lang/ObjectClone.scala @@ -14,6 +14,8 @@ package java.lang import scala.scalajs.js +import Utils._ + /** Implementation of `java.lang.Object.clone()`. * * Called by the hard-coded IR of `java.lang.Object`. @@ -56,7 +58,9 @@ private[lang] object ObjectClone { // Polyfill for Reflect.ownKeys { (o: js.Object) => - getOwnPropertyNames(o) ++ getOwnPropertySymbols(o) + getOwnPropertyNames(o).asInstanceOf[js.Dynamic] + .concat(getOwnPropertySymbols(o)) + .asInstanceOf[js.Array[js.Any]] } } } @@ -65,18 +69,18 @@ private[lang] object ObjectClone { { (o: js.Object) => val ownKeys = ownKeysFun(o) val descriptors = new js.Object - for (key <- ownKeys) { + forArrayElems(ownKeys) { key => /* Almost equivalent to the JavaScript code * descriptors[key] = Object.getOwnPropertyDescriptor(descriptors, key); * except that `defineProperty` will by-pass any existing setter for * the property `key` on `descriptors` or in its prototype chain. */ - global.Object.defineProperty(descriptors, key, literal( - configurable = true, - enumerable = true, - writable = true, - value = global.Object.getOwnPropertyDescriptor(o, key) - )) + global.Object.defineProperty(descriptors, key, new js.Object { + val configurable = true + val enumerable = true + val writable = true + val value = global.Object.getOwnPropertyDescriptor(o, key) + }) } descriptors } diff --git a/javalanglib/src/main/scala/java/lang/SemanticsUtils.scala b/javalanglib/src/main/scala/java/lang/SemanticsUtils.scala new file mode 100644 index 0000000000..0250052188 --- /dev/null +++ b/javalanglib/src/main/scala/java/lang/SemanticsUtils.scala @@ -0,0 +1,58 @@ +/* + * 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 + +import scala.scalajs.js +import scala.scalajs.runtime.{linkingInfo, UndefinedBehaviorError} + +/** Utilities to test for erroneous conditions depending on the Semantics + * configuration. + */ +private[lang] object SemanticsUtils { + + private final val Compliant = 0 + private final val Fatal = 1 + private final val Unchecked = 2 + + /** Tests for an erroneous condition governed by the `asInstanceOfs` + * semantics. + */ + @inline + def asInstanceOfCheck(shouldThrow: js.Function0[scala.Boolean], + exception: js.Function0[ClassCastException]): Unit = { + genericCheck(linkingInfo.semantics.asInstanceOfs, shouldThrow, exception) + } + + /** Tests for an erroneous condition governed by the `arrayIndexOutOfBounds` + * semantics. + */ + @inline + def arrayIndexOutOfBoundsCheck(shouldThrow: js.Function0[scala.Boolean], + exception: js.Function0[ArrayIndexOutOfBoundsException]): Unit = { + genericCheck(linkingInfo.semantics.arrayIndexOutOfBounds, shouldThrow, + exception) + } + + @inline + private def genericCheck(complianceLevel: Int, + shouldThrow: js.Function0[scala.Boolean], + exception: js.Function0[Throwable]): Unit = { + if (complianceLevel != Unchecked && shouldThrow()) { + if (complianceLevel == Compliant) + throw exception() + else + throw new UndefinedBehaviorError(exception()) + } + } + +} diff --git a/javalanglib/src/main/scala/java/lang/Short.scala b/javalanglib/src/main/scala/java/lang/Short.scala index ef31e9a818..06f17aaa87 100644 --- a/javalanglib/src/main/scala/java/lang/Short.scala +++ b/javalanglib/src/main/scala/java/lang/Short.scala @@ -75,7 +75,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(s"""For input string: "$s"""") + throw new NumberFormatException("For input string: \"" + s + "\"") else r.toShort } diff --git a/javalanglib/src/main/scala/java/lang/StackTrace.scala b/javalanglib/src/main/scala/java/lang/StackTrace.scala index 83665a9588..e919390ac1 100644 --- a/javalanglib/src/main/scala/java/lang/StackTrace.scala +++ b/javalanglib/src/main/scala/java/lang/StackTrace.scala @@ -15,7 +15,9 @@ package java.lang import scala.annotation.tailrec import scala.scalajs.js -import js.JSStringOps._ + +import Utils._ +import Utils.Implicits.enableJSStringOps /** Conversions of JavaScript stack traces to Java stack traces. */ @@ -54,7 +56,7 @@ private[lang] object StackTrace { * prototypes. */ captureState(throwable, throwable) - } else if (js.isUndefined(js.constructorOf[js.Error].captureStackTrace)) { + } else if (Utils.isUndefined(js.constructorOf[js.Error].captureStackTrace)) { captureState(throwable, createException()) } else { /* V8-specific. @@ -70,14 +72,8 @@ private[lang] object StackTrace { } /** Creates a JS Error with the current stack trace state. */ - @inline private def createException(): Any = { - try { - // Intentionally throw a JavaScript error - new js.Object().asInstanceOf[js.Dynamic].undef() - } catch { - case js.JavaScriptException(e) => e - } - } + @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 @@ -116,11 +112,12 @@ private[lang] object StackTrace { private def normalizedLinesToStackTrace( lines: js.Array[String]): Array[StackTraceElement] = { - import Integer.parseInt - val NormalizedFrameLine = """^([^\@]*)\@(.*):([0-9]+)$""".re val NormalizedFrameLineWithColumn = """^([^\@]*)\@(.*):([0-9]+):([0-9]+)$""".re + @inline def parseInt(s: String): Int = + js.Dynamic.global.parseInt(s).asInstanceOf[Int] + val trace = js.Array[StackTraceElement]() var i = 0 while (i < lines.length) { @@ -128,17 +125,21 @@ private[lang] object StackTrace { if (!line.isEmpty) { val mtch1 = NormalizedFrameLineWithColumn.exec(line) if (mtch1 ne null) { - val (className, methodName) = extractClassMethod(mtch1(1).get) - val elem = new StackTraceElement(className, methodName, mtch1(2).get, - parseInt(mtch1(3).get)) - elem.setColumnNumber(parseInt(mtch1(4).get)) + 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) } else { val mtch2 = NormalizedFrameLine.exec(line) if (mtch2 ne null) { - val (className, methodName) = extractClassMethod(mtch2(1).get) - trace.push(new StackTraceElement(className, - methodName, mtch2(2).get, parseInt(mtch2(3).get))) + 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)) @@ -178,8 +179,14 @@ private[lang] object StackTrace { * `("", functionName)` * is returned, which will instruct [[StackTraceElement.toString()]] to only * display the function name. + * + * @return + * A 2-element array with the recovered class and method names, in that + * order. It is an array instead of a tuple because tuples have user code + * in the Scala.js standard library, which we cannot reference from the + * javalanglib. */ - private def extractClassMethod(functionName: String): (String, String) = { + private def extractClassMethod(functionName: String): js.Array[String] = { val PatC = """^(?:Object\.|\[object Object\]\.)?\$c_([^\.]+)(?:\.prototype)?\.([^\.]+)$""".re val PatS = """^(?:Object\.|\[object Object\]\.)?\$(?:ct|ps?|s|f)_((?:_[^_]|[^_])+)__([^\.]+)$""".re val PatN = """^new (?:Object\.|\[object Object\]\.)?\$c_([^\.]+)$""".re @@ -188,17 +195,18 @@ private[lang] object StackTrace { val matchC = PatC.exec(functionName) val matchCOrS = if (matchC ne null) matchC else PatS.exec(functionName) if (matchCOrS ne null) { - (decodeClassName(matchCOrS(1).get), decodeMethodName(matchCOrS(2).get)) + js.Array[String](decodeClassName(undefOrForceGet(matchCOrS(1))), + decodeMethodName(undefOrForceGet(matchCOrS(2)))) } else { val matchN = PatN.exec(functionName) if (matchN ne null) { - (decodeClassName(matchN(1).get), "") + js.Array[String](decodeClassName(undefOrForceGet(matchN(1))), "") } else { val matchM = PatM.exec(functionName) if (matchM ne null) { - (decodeClassName(matchM(1).get), "") + js.Array[String](decodeClassName(undefOrForceGet(matchM(1))), "") } else { - ("", functionName) + js.Array[String]("", functionName) } } } @@ -209,15 +217,15 @@ private[lang] object StackTrace { // !!! Duplicate logic: this code must be in sync with ir.Definitions private def decodeClassName(encodedName: String): String = { - val base = if (decompressedClasses.contains(encodedName)) { - decompressedClasses(encodedName) + val base = if (dictContains(decompressedClasses, encodedName)) { + dictRawApply(decompressedClasses, encodedName) } else { @tailrec def loop(i: Int): String = { if (i < compressedPrefixes.length) { val prefix = compressedPrefixes(i) if (encodedName.startsWith(prefix)) - decompressedPrefixes(prefix) + encodedName.substring(prefix.length) + dictRawApply(decompressedPrefixes, prefix) + encodedName.substring(prefix.length) else loop(i+1) } else { @@ -232,34 +240,35 @@ private[lang] object StackTrace { } private lazy val decompressedClasses: js.Dictionary[String] = { - val dict = js.Dynamic.literal( - O = "java_lang_Object", - T = "java_lang_String" - ).asInstanceOf[js.Dictionary[String]] + val dict = new js.Object().asInstanceOf[js.Dictionary[String]] + dictSet(dict, "O", "java_lang_Object") + dictSet(dict, "T", "java_lang_String") var index = 0 while (index <= 22) { if (index >= 2) - dict("T"+index) = "scala_Tuple"+index - dict("F"+index) = "scala_Function"+index + dictSet(dict, "T" + index, "scala_Tuple" + index) + dictSet(dict, "F" + index, "scala_Function" + index) index += 1 } dict } - private lazy val decompressedPrefixes = js.Dynamic.literal( - sjsr_ = "scala_scalajs_runtime_", - sjs_ = "scala_scalajs_", - sci_ = "scala_collection_immutable_", - scm_ = "scala_collection_mutable_", - scg_ = "scala_collection_generic_", - sc_ = "scala_collection_", - sr_ = "scala_runtime_", - s_ = "scala_", - jl_ = "java_lang_", - ju_ = "java_util_" - ).asInstanceOf[js.Dictionary[String]] + private lazy val decompressedPrefixes: js.Dictionary[String] = { + val dict = new js.Object().asInstanceOf[js.Dictionary[String]] + dictSet(dict, "sjsr_", "scala_scalajs_runtime_") + dictSet(dict, "sjs_", "scala_scalajs_") + dictSet(dict, "sci_", "scala_collection_immutable_") + dictSet(dict, "scm_", "scala_collection_mutable_") + dictSet(dict, "scg_", "scala_collection_generic_") + dictSet(dict, "sc_", "scala_collection_") + dictSet(dict, "sr_", "scala_runtime_") + dictSet(dict, "s_", "scala_") + dictSet(dict, "jl_", "java_lang_") + dictSet(dict, "ju_", "java_util_") + dict + } private lazy val compressedPrefixes = js.Object.keys(decompressedPrefixes.asInstanceOf[js.Object]) @@ -292,7 +301,7 @@ private[lang] object StackTrace { */ private def normalizeStackTraceLines(e: js.Dynamic): js.Array[String] = { - import js.DynamicImplicits.{truthValue, number2dynamic} + import Utils.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 @@ -387,11 +396,13 @@ private[lang] object StackTrace { val result = new js.Array[String] var i = 2 - val len = lines.length.toInt + val len = lines.length while (i < len) { val mtch = lineRE.exec(lines(i)) if (mtch ne null) { - result.push("{anonymous}()@" + mtch(2).get + ":" + mtch(1).get + result.push( + "{anonymous}()@" + undefOrForceGet(mtch(2)) + ":" + + undefOrForceGet(mtch(1)) /* + " -- " + lines(i+1).replace("""^\s+""".re, "") */) } i += 2 @@ -408,12 +419,14 @@ private[lang] object StackTrace { val result = new js.Array[String] var i = 0 - val len = lines.length.toInt + val len = lines.length while (i < len) { val mtch = lineRE.exec(lines(i)) if (mtch ne null) { - val fnName = mtch(3).getOrElse("{anonymous}") - result.push(fnName + "()@" + mtch(2).get + ":" + mtch(1).get + val fnName = undefOrGetOrElse(mtch(3), "{anonymous}") + result.push( + fnName + "()@" + undefOrForceGet(mtch(2)) + ":" + + undefOrForceGet(mtch(1)) /* + " -- " + lines(i+1).replace("""^\s+""".re, "")*/) } i += 2 @@ -431,12 +444,12 @@ private[lang] object StackTrace { val result = new js.Array[String] var i = 0 - val len = lines.length.toInt + val len = lines.length while (i < len) { val mtch = lineRE.exec(lines(i)) if (mtch ne null) { - val fnName = mtch(1).fold("global code")(_ + "()") - result.push(fnName + "@" + mtch(2).get + ":" + mtch(3).get) + val fnName = undefOrFold(mtch(1))("global code", _ + "()") + result.push(fnName + "@" + undefOrForceGet(mtch(2)) + ":" + undefOrForceGet(mtch(3))) } i += 1 } @@ -450,12 +463,12 @@ private[lang] object StackTrace { val result = new js.Array[String] var i = 0 - val len = lines.length.toInt + val len = lines.length while (i < len) { val mtch = lineRE.exec(lines(i)) if (mtch ne null) { - val location = mtch(4).get + ":" + mtch(1).get + ":" + mtch(2).get - val fnName0 = mtch(2).getOrElse("global code") + val location = undefOrForceGet(mtch(4)) + ":" + undefOrForceGet(mtch(1)) + ":" + undefOrForceGet(mtch(2)) + 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/javalanglib/src/main/scala/java/lang/StackTraceElement.scala index 3c10d44cd4..d314654826 100644 --- a/javalanglib/src/main/scala/java/lang/StackTraceElement.scala +++ b/javalanglib/src/main/scala/java/lang/StackTraceElement.scala @@ -58,11 +58,11 @@ final class StackTraceElement(declaringClass: String, methodName: String, else result += "(Unknown Source)" } else { - result += s"($fileName" + result += "(" + fileName if (lineNumber >= 0) { - result += s":$lineNumber" + result += ":" + lineNumber if (columnNumber >= 0) - result += s":$columnNumber" + result += ":" + columnNumber } result += ")" } diff --git a/javalanglib/src/main/scala/java/lang/System.scala b/javalanglib/src/main/scala/java/lang/System.scala index 792ebf517c..acd3d34bc3 100644 --- a/javalanglib/src/main/scala/java/lang/System.scala +++ b/javalanglib/src/main/scala/java/lang/System.scala @@ -16,11 +16,12 @@ import java.io._ import scala.scalajs.js import scala.scalajs.js.Dynamic.global -import scala.scalajs.LinkingInfo.assumingES6 -import scala.scalajs.runtime.{linkingInfo, SemanticsUtils} +import scala.scalajs.runtime.linkingInfo import java.{util => ju} +import Utils._ + object System { var out: PrintStream = new JSConsoleBasedPrintStream(isErr = false) var err: PrintStream = new JSConsoleBasedPrintStream(isErr = true) @@ -40,7 +41,7 @@ object System { } private[this] val getHighPrecisionTime: js.Function0[scala.Double] = { - import js.DynamicImplicits.truthValue + import Utils.DynamicImplicits.truthValue if (js.typeOf(global.performance) != "undefined") { if (global.performance.now) { @@ -63,50 +64,28 @@ object System { import scala.{Boolean, Char, Byte, Short, Int, Long, Float, Double} - @inline def checkIndices(srcLen: Int, destLen: Int): Unit = { - SemanticsUtils.arrayIndexOutOfBoundsCheck({ + def mismatch(): Nothing = + throw new ArrayStoreException("Incompatible array types") + + def impl(srcLen: Int, destLen: Int, f: js.Function2[Int, Int, Any]): Unit = { + SemanticsUtils.arrayIndexOutOfBoundsCheck({ () => srcPos < 0 || destPos < 0 || length < 0 || srcPos > srcLen - length || destPos > destLen - length - }, { + }, { () => new ArrayIndexOutOfBoundsException() }) - } - def mismatch(): Nothing = - throw new ArrayStoreException("Incompatible array types") - - val forward = (src ne dest) || destPos < srcPos || srcPos + length < destPos - - def copyPrim[@specialized T](src: Array[T], dest: Array[T]): Unit = { - checkIndices(src.length, dest.length) - if (forward) { + if ((src ne dest) || destPos < srcPos || srcPos + length < destPos) { var i = 0 while (i < length) { - dest(i+destPos) = src(i+srcPos) + f(i + destPos, i + srcPos) i += 1 } } else { - var i = length-1 + var i = length - 1 while (i >= 0) { - dest(i+destPos) = src(i+srcPos) - i -= 1 - } - } - } - - def copyRef(src: Array[AnyRef], dest: Array[AnyRef]): Unit = { - checkIndices(src.length, dest.length) - if (forward) { - var i = 0 - while (i < length) { - dest(i+destPos) = src(i+srcPos) - i += 1 - } - } else { - var i = length-1 - while (i >= 0) { - dest(i+destPos) = src(i+srcPos) + f(i + destPos, i + srcPos) i -= 1 } } @@ -117,47 +96,47 @@ object System { } else (src match { case src: Array[AnyRef] => dest match { - case dest: Array[AnyRef] => copyRef(src, dest) + case dest: Array[AnyRef] => impl(src.length, dest.length, (i, j) => dest(i) = src(j)) case _ => mismatch() } case src: Array[Boolean] => dest match { - case dest: Array[Boolean] => copyPrim(src, dest) + case dest: Array[Boolean] => impl(src.length, dest.length, (i, j) => dest(i) = src(j)) case _ => mismatch() } case src: Array[Char] => dest match { - case dest: Array[Char] => copyPrim(src, dest) + case dest: Array[Char] => impl(src.length, dest.length, (i, j) => dest(i) = src(j)) case _ => mismatch() } case src: Array[Byte] => dest match { - case dest: Array[Byte] => copyPrim(src, dest) + case dest: Array[Byte] => impl(src.length, dest.length, (i, j) => dest(i) = src(j)) case _ => mismatch() } case src: Array[Short] => dest match { - case dest: Array[Short] => copyPrim(src, dest) + case dest: Array[Short] => impl(src.length, dest.length, (i, j) => dest(i) = src(j)) case _ => mismatch() } case src: Array[Int] => dest match { - case dest: Array[Int] => copyPrim(src, dest) + case dest: Array[Int] => impl(src.length, dest.length, (i, j) => dest(i) = src(j)) case _ => mismatch() } case src: Array[Long] => dest match { - case dest: Array[Long] => copyPrim(src, dest) + case dest: Array[Long] => impl(src.length, dest.length, (i, j) => dest(i) = src(j)) case _ => mismatch() } case src: Array[Float] => dest match { - case dest: Array[Float] => copyPrim(src, dest) + case dest: Array[Float] => impl(src.length, dest.length, (i, j) => dest(i) = src(j)) case _ => mismatch() } case src: Array[Double] => dest match { - case dest: Array[Double] => copyPrim(src, dest) + case dest: Array[Double] => impl(src.length, dest.length, (i, j) => dest(i) = src(j)) case _ => mismatch() } case _ => @@ -183,19 +162,19 @@ object System { * through `System.identityHashCode(x)` than with `x.hashCode()`. */ x.hashCode() - } else if (assumingES6 || idHashCodeMap != null) { + } else if (linkingInfo.assumingES6 || idHashCodeMap != null) { // Use the global WeakMap of attributed id hash codes val hash = idHashCodeMap.get(x.asInstanceOf[js.Any]) - if (!js.isUndefined(hash)) { + if (!Utils.isUndefined(hash)) { hash.asInstanceOf[Int] } else { val newHash = nextIDHashCode() - idHashCodeMap.set(x.asInstanceOf[js.Any], newHash) + idHashCodeMap.set(x.asInstanceOf[js.Any], newHash.asInstanceOf[js.Any]) newHash } } else { val hash = x.asInstanceOf[js.Dynamic].selectDynamic("$idHashCode$0") - if (!js.isUndefined(hash)) { + if (!Utils.isUndefined(hash)) { /* Note that this can work even if x is sealed, if * identityHashCode() was called for the first time before x was * sealed. @@ -207,7 +186,7 @@ object System { * technically undefined behavior. */ val newHash = nextIDHashCode() - x.asInstanceOf[js.Dynamic].updateDynamic("$idHashCode$0")(newHash) + x.asInstanceOf[js.Dynamic].updateDynamic("$idHashCode$0")(newHash.asInstanceOf[js.Any]) newHash } else { // Otherwise, we unfortunately have to return a constant. @@ -221,7 +200,7 @@ object System { private var lastIDHashCode: Int = 0 val idHashCodeMap = - if (assumingES6 || js.typeOf(global.WeakMap) != "undefined") + if (linkingInfo.assumingES6 || js.typeOf(global.WeakMap) != "undefined") js.Dynamic.newInstance(global.WeakMap)() else null @@ -238,27 +217,29 @@ object System { var properties: ju.Properties = null private[System] def loadSystemProperties(): js.Dictionary[String] = { - js.Dictionary( - ("java.version", "1.8"), - ("java.vm.specification.version", "1.8"), - ("java.vm.specification.vendor", "Oracle Corporation"), - ("java.vm.specification.name", "Java Virtual Machine Specification"), - ("java.vm.name", "Scala.js"), - ("java.vm.version", linkingInfo.linkerVersion), - ("java.specification.version", "1.8"), - ("java.specification.vendor", "Oracle Corporation"), - ("java.specification.name", "Java Platform API Specification"), - ("file.separator", "/"), - ("path.separator", ":"), - ("line.separator", "\n") - ) + val result = new js.Object().asInstanceOf[js.Dictionary[String]] + dictSet(result, "java.version", "1.8") + dictSet(result, "java.vm.specification.version", "1.8") + 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.specification.version", "1.8") + dictSet(result, "java.specification.vendor", "Oracle Corporation") + dictSet(result, "java.specification.name", "Java Platform API Specification") + dictSet(result, "file.separator", "/") + dictSet(result, "path.separator", ":") + dictSet(result, "line.separator", "\n") + result } private[System] def forceProperties(): ju.Properties = { if (properties eq null) { properties = new ju.Properties - for ((key, value) <- dict) - properties.setProperty(key, value) + val keys = js.Object.keys(dict.asInstanceOf[js.Object]) + forArrayElems(keys) { key => + properties.setProperty(key, dictRawApply(dict, key)) + } dict = null } properties @@ -281,21 +262,21 @@ object System { } def getProperty(key: String): String = - if (SystemProperties.dict ne null) SystemProperties.dict.getOrElse(key, null) + if (SystemProperties.dict ne null) dictGetOrElse(SystemProperties.dict, key, null) else SystemProperties.properties.getProperty(key) def getProperty(key: String, default: String): String = - if (SystemProperties.dict ne null) SystemProperties.dict.getOrElse(key, default) + if (SystemProperties.dict ne null) dictGetOrElse(SystemProperties.dict, key, default) else SystemProperties.properties.getProperty(key, default) def clearProperty(key: String): String = - if (SystemProperties.dict ne null) SystemProperties.dict.remove(key).getOrElse(null) + if (SystemProperties.dict ne null) dictGetOrElseAndRemove(SystemProperties.dict, key, null) else SystemProperties.properties.remove(key).asInstanceOf[String] def setProperty(key: String, value: String): String = { if (SystemProperties.dict ne null) { val oldValue = getProperty(key) - SystemProperties.dict(key) = value + dictSet(SystemProperties.dict, key, value) oldValue } else { SystemProperties.properties.setProperty(key, value).asInstanceOf[String] @@ -394,13 +375,13 @@ private[lang] final class JSConsoleBasedPrintStream(isErr: scala.Boolean) override def close(): Unit = () private def doWriteLine(line: String): Unit = { - import js.DynamicImplicits.truthValue + import Utils.DynamicImplicits.truthValue if (js.typeOf(global.console) != "undefined") { if (isErr && global.console.error) - global.console.error(line) + global.console.error(line.asInstanceOf[js.Any]) else - global.console.log(line) + global.console.log(line.asInstanceOf[js.Any]) } } } diff --git a/javalanglib/src/main/scala/java/lang/Throwables.scala b/javalanglib/src/main/scala/java/lang/Throwables.scala index df20e62782..7436735f34 100644 --- a/javalanglib/src/main/scala/java/lang/Throwables.scala +++ b/javalanglib/src/main/scala/java/lang/Throwables.scala @@ -92,7 +92,7 @@ class Throwable protected (s: String, private var e: Throwable, def printStackTrace(s: java.io.PrintWriter): Unit = printStackTraceImpl(s.println(_)) - private[this] def printStackTraceImpl(sprintln: String => Unit): Unit = { + private[this] def printStackTraceImpl(sprintln: js.Function1[String, Unit]): Unit = { getStackTrace() // will init it if still null // Message diff --git a/javalanglib/src/main/scala/java/lang/Utils.scala b/javalanglib/src/main/scala/java/lang/Utils.scala new file mode 100644 index 0000000000..8db06a4bb1 --- /dev/null +++ b/javalanglib/src/main/scala/java/lang/Utils.scala @@ -0,0 +1,135 @@ +/* + * 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 + +import scala.language.implicitConversions + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSBracketAccess + +private[lang] object Utils { + @inline + def isUndefined(x: Any): scala.Boolean = + x.asInstanceOf[AnyRef] eq ().asInstanceOf[AnyRef] + + @inline + def undefOrIsDefined[A](x: js.UndefOr[A]): scala.Boolean = + x ne ().asInstanceOf[AnyRef] + + @inline + def undefOrForceGet[A](x: js.UndefOr[A]): A = + x.asInstanceOf[A] + + @inline + def undefOrGetOrElse[A](x: js.UndefOr[A], default: A): A = + if (undefOrIsDefined(x)) x.asInstanceOf[A] + else default + + @inline + def undefOrFold[A, B](x: js.UndefOr[A])(default: B, f: js.Function1[A, B]): B = + if (undefOrIsDefined(x)) f(undefOrForceGet(x)) + else default + + private object Cache { + val safeHasOwnProperty = + js.Dynamic.global.Object.prototype.hasOwnProperty + .asInstanceOf[js.ThisFunction1[js.Dictionary[_], String, scala.Boolean]] + } + + @inline + private def safeHasOwnProperty(dict: js.Dictionary[_], key: String): scala.Boolean = + Cache.safeHasOwnProperty(dict, key) + + @js.native + private trait DictionaryRawApply[A] extends js.Object { + /** Reads a field of this object by its name. + * + * This must not be called if the dictionary does not contain the key. + */ + @JSBracketAccess + def rawApply(key: String): A = js.native + + /** Writes a field of this object. */ + @JSBracketAccess + def rawUpdate(key: String, value: A): Unit = js.native + } + + def dictGetOrElse[A](dict: js.Dictionary[_ <: A], key: String, + default: A): A = { + if (dictContains(dict, key)) + dictRawApply(dict, key) + else + default + } + + def dictGetOrElseAndRemove[A](dict: js.Dictionary[_ <: A], key: String, + default: A): A = { + if (dictContains(dict, key)) { + val result = dictRawApply(dict, key) + js.special.delete(dict, key) + result + } else { + default + } + } + + @inline + def dictRawApply[A](dict: js.Dictionary[A], key: String): A = + dict.asInstanceOf[DictionaryRawApply[A]].rawApply(key) + + def dictContains[A](dict: js.Dictionary[A], key: String): scala.Boolean = { + /* We have to use a safe version of hasOwnProperty, because + * "hasOwnProperty" could be a key of this dictionary. + */ + safeHasOwnProperty(dict, key) + } + + @inline + def dictSet[A](dict: js.Dictionary[A], key: String, value: A): Unit = + dict.asInstanceOf[DictionaryRawApply[A]].rawUpdate(key, value) + + @inline + def forArrayElems[A](array: js.Array[A])(f: js.Function1[A, Any]): Unit = { + val len = array.length + var i = 0 + while (i != len) { + f(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] + + implicit def enableJSNumberOps(x: Int): js.JSNumberOps = + x.asInstanceOf[js.JSNumberOps] + + implicit def enableJSNumberOps(x: scala.Double): js.JSNumberOps = + x.asInstanceOf[js.JSNumberOps] + } + + 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] + } +} diff --git a/javalanglib/src/main/scala/java/lang/_String.scala b/javalanglib/src/main/scala/java/lang/_String.scala index fa3b1d679a..1eb39f3c6e 100644 --- a/javalanglib/src/main/scala/java/lang/_String.scala +++ b/javalanglib/src/main/scala/java/lang/_String.scala @@ -16,12 +16,13 @@ import java.util.Comparator import scala.scalajs.js import scala.scalajs.js.annotation._ -import js.JSStringOps._ import java.nio.ByteBuffer import java.nio.charset.Charset 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. * diff --git a/project/Build.scala b/project/Build.scala index e934e2ae1a..bcc98253c0 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -840,7 +840,37 @@ object Build { Seq(output) }.taskValue, - scalaJSExternalCompileSettings + scalaJSExternalCompileSettings, + + products in Compile := { + val s = streams.value + + val prev = (products in Compile).value + assert(prev.size == 1) + val javalibDir = prev.head + + val dependencyClasspath = + Attributed.data((internalDependencyClasspath in Compile).value) + + val inputsFinder = dependencyClasspath.foldLeft(javalibDir ** "*.sjsir") { + (prev, classpathItem) => prev +++ classpathItem ** "*.sjsir" + } + + val outputDir = crossTarget.value / "cleaned-classes" + + FileFunction.cached(s.cacheDirectory / "cleaned-sjsir", + FilesInfo.lastModified, FilesInfo.exists) { _ => + s.log.info(s"Patching sjsir files for javalanglib...") + + if (outputDir.exists) + IO.delete(outputDir) + IO.createDirectory(outputDir) + + JavalibIRCleaner.cleanIR(dependencyClasspath, javalibDir, outputDir, s.log) + } (inputsFinder.get.toSet) + + Seq(outputDir) + } ).withScalaJSCompiler.dependsOnLibraryNoJar lazy val javalib: Project = project.enablePlugins( diff --git a/project/JavalibIRCleaner.scala b/project/JavalibIRCleaner.scala new file mode 100644 index 0000000000..bce152d75b --- /dev/null +++ b/project/JavalibIRCleaner.scala @@ -0,0 +1,441 @@ +package build + +import org.scalajs.ir._ +import org.scalajs.ir.ClassKind +import org.scalajs.ir.Definitions._ +import org.scalajs.ir.Trees._ +import org.scalajs.ir.Types._ + +import java.io._ +import java.nio.file.{Files, Path} +import java.{util => ju} + +import scala.collection.mutable + +import sbt.{Logger, MessageOnlyException} + +/** Postprocessor for the IR of the javalanglib, to remove all references to + * JS types in the Scala library, and ensure that there remains no other + * reference to the Scala library. + * + * This ensures that the IR of the javalanglib is truly independent of Scala. + * + * The main task is to *completely* erase all references to JS types to + * `j.l.Object`. This includes: + * + * - Delete (or do not copy over) .sjsir files that define abstract and native + * JS types. + * - Erase references to JS types in method signatures and `TypeRef`s to + * `java.lang.Object`. + * - Eagerly dereference `LoadJSConstructor` and `LoadJSModule` by "inlining" + * the JS load spec of the mentioned class ref. + * + * Afterwards, we check that the IR does not contain any reference to classes + * undef the `scala.*` package, with the unfortunate exception of + * `scala.scalajs.runtime.UndefinedBehaviorError`. + */ +object JavalibIRCleaner { + def cleanIR(dependencyClasspath: Seq[File], javalibDir: File, outputDir: File, + logger: Logger): Set[File] = { + + val errorManager = new ErrorManager(logger) + + val javalibDirPath = javalibDir.toPath() + val outputDirPath = outputDir.toPath() + + val libraryIRFiles = dependencyClasspath.flatMap(f => listIRFiles(f.toPath())) + val javalibIRFiles = listIRFiles(javalibDirPath) + + val jsTypes = getJSTypes(libraryIRFiles ++ javalibIRFiles) + + val resultBuilder = Set.newBuilder[File] + + for (irFile <- javalibIRFiles) { + import ClassKind._ + + val tree = irFile.tree + tree.kind match { + case Class | ModuleClass | Interface | HijackedClass => + val cleanedTree = cleanTree(tree, jsTypes, errorManager) + val outputPath = + outputDirPath.resolve(javalibDirPath.relativize(irFile.path)) + writeIRFile(outputPath, cleanedTree) + resultBuilder += outputPath.toFile() + + case AbstractJSType | NativeJSClass | NativeJSModuleClass => + // discard + + case JSClass | JSModuleClass => + errorManager.reportError( + s"found non-native JS class ${tree.name.encodedName}")(tree.pos) + } + } + + if (errorManager.hasErrors) { + throw new MessageOnlyException( + s"There were ${errorManager.errorCount} errors while " + + "postprocessing the IR of the javalanglib. " + + "The javalanglib must be written in a style that does not leak any " + + "reference to the Scala library.") + } + + resultBuilder.result() + } + + private final class ErrorManager(logger: Logger) { + private var _errorCount: Int = 0 + + def reportError(msg: String)(implicit pos: Position): Unit = { + logger.error(s"$msg at $pos") + _errorCount += 1 + } + + def hasErrors: Boolean = _errorCount != 0 + + def errorCount: Int = _errorCount + } + + private final class IRFile(val path: Path, val tree: ClassDef) + + private def listIRFiles(path: Path): Seq[IRFile] = { + import java.nio.file._ + import java.nio.file.attribute.BasicFileAttributes + + val builder = Seq.newBuilder[IRFile] + + val dirVisitor = new SimpleFileVisitor[Path] { + override def visitFile(path: Path, attrs: BasicFileAttributes): FileVisitResult = { + if (path.getFileName().toString().endsWith(".sjsir")) + builder += new IRFile(path, readIRFile(path)) + super.visitFile(path, attrs) + } + } + + Files.walkFileTree(path, ju.EnumSet.of(FileVisitOption.FOLLOW_LINKS), + Int.MaxValue, dirVisitor) + builder.result() + } + + private def readIRFile(path: Path): ClassDef = { + import java.nio.ByteBuffer + import java.nio.channels.FileChannel + + val channel = FileChannel.open(path) + try { + val fileSize = channel.size() + if (fileSize > Int.MaxValue.toLong) + throw new IOException(s"IR file too large: $path") + val buffer = ByteBuffer.allocate(fileSize.toInt) + channel.read(buffer) + buffer.flip() + Serializers.deserialize(buffer) + } finally { + channel.close() + } + } + + private def writeIRFile(path: Path, tree: ClassDef): Unit = { + Files.createDirectories(path.getParent()) + val outputStream = + new BufferedOutputStream(new FileOutputStream(path.toFile())) + try { + Serializers.serialize(outputStream, tree) + } finally { + outputStream.close() + } + } + + private def getJSTypes(irFiles: Seq[IRFile]): Map[String, ClassDef] = { + (for { + irFile <- irFiles + if irFile.tree.kind.isJSType + } yield { + val tree = irFile.tree + tree.name.encodedName -> tree + }).toMap + } + + private def cleanTree(tree: ClassDef, jsTypes: Map[String, ClassDef], + errorManager: ErrorManager): ClassDef = { + new ClassDefCleaner(tree.name.name, jsTypes, errorManager) + .cleanClassDef(tree) + } + + private final class ClassDefCleaner(enclosingClassName: String, + jsTypes: Map[String, ClassDef], errorManager: ErrorManager) + extends Transformers.ClassTransformer { + + def cleanClassDef(tree: ClassDef): ClassDef = { + import tree._ + + val preprocessedTree = { + var changed = false + + // Preprocess the super interface list + val newInterfaces = transformInterfaceList(interfaces) + if (newInterfaces ne interfaces) + changed = true + + /* Remove the `private def writeReplace__O` generated by scalac 2.13+ + * in the companion of serializable classes. + */ + val newMemberDefs = memberDefs.filter { m => + if (m.name.encodedName == "writeReplace__O") { + changed = true + false + } else { + true + } + } + + if (changed) { + ClassDef(name, kind, jsClassCaptures, superClass, newInterfaces, + jsSuperClass, jsNativeLoadSpec, newMemberDefs, topLevelExportDefs)( + optimizerHints)(pos) + } else { + tree + } + } + + validateClassName(preprocessedTree.name.name) + for (superClass <- preprocessedTree.superClass) + validateClassName(superClass.name) + for (interface <- preprocessedTree.interfaces) + validateClassName(interface.name) + + val transformedClassDef = + Hashers.hashClassDef(this.transformClassDef(preprocessedTree)) + + postTransformChecks(transformedClassDef) + transformedClassDef + } + + private def transformInterfaceList(interfaces: List[Ident]): List[Ident] = { + /* Replace references to scala.Serializable by java.io.Serializable. + * This works around the fact that scalac adds scala.Serializable to the + * companion object of any class that extends java.io.Serializable. + */ + + val ScalaSerializable = "s_Serializable" + val JavaIOSerializable = "Ljava_io_Serializable" + + if (!interfaces.exists(_.name == ScalaSerializable)) { + interfaces + } else if (interfaces.exists(_.name == JavaIOSerializable)) { + interfaces.filter(_.name != "s_Serializable") + } else { + interfaces.map { ident => + if (ident.name == ScalaSerializable) + Ident(JavaIOSerializable, Some("java.io.Serializable"))(ident.pos) + else + ident + } + } + } + + override def transformMemberDef(memberDef: MemberDef): MemberDef = { + super.transformMemberDef(memberDef) match { + case m: MethodDef => + m.name match { + case name: Ident => + MethodDef(m.flags, transformMethodIdent(name), m.args, + m.resultType, m.body)(m.optimizerHints, m.hash)(m.pos) + case _ => + m + } + case m => + m + } + } + + override def transform(tree: Tree, isStat: Boolean): Tree = { + implicit val pos = tree.pos + + val preprocessedTree = tree match { + /* In SemanticsUtils, there is one generic `throw exception()` where + * `exception()` is one of the exceptions for undefined behavior. We + * as humans know that none of those can be a `JavaScriptException`, + * but the compiler doesn't, so it introduces a call to + * `sjs.runtime.unwrapJavaScriptExeption(arg)`. Here, we get rid of + * that call and rewrite `tree` to `throw arg`. + */ + case Throw(Apply(_, LoadModule(ClassRef("sjsr_package$")), + Ident("unwrapJavaScriptException__jl_Throwable__O", _), arg :: Nil)) + if enclosingClassName == "jl_SemanticsUtils$" => + Throw(arg) + + case _ => + tree + } + + val result = super.transform(preprocessedTree, isStat) match { + case New(cls, ctor, args) => + New(cls, transformMethodIdent(ctor), args) + + case t: Apply => + Apply(t.flags, t.receiver, transformMethodIdent(t.method), + t.args)(t.tpe) + case t: ApplyStatically => + validateNonJSClassRef(t.cls) + ApplyStatically(t.flags, t.receiver, t.cls, + transformMethodIdent(t.method), t.args)(t.tpe) + case t: ApplyStatic => + validateNonJSClassRef(t.cls) + ApplyStatic(t.flags, t.cls, + transformMethodIdent(t.method), t.args)(t.tpe) + + case NewArray(typeRef, lengths) => + NewArray(transformArrayTypeRef(typeRef), lengths) + case ArrayValue(typeRef, elems) => + ArrayValue(transformArrayTypeRef(typeRef), elems) + + case IsInstanceOf(expr, typeRef) => + IsInstanceOf(expr, transformTypeRef(typeRef)) + case AsInstanceOf(expr, typeRef) => + AsInstanceOf(expr, transformTypeRef(typeRef)) + + case LoadJSConstructor(cls) => + genLoadFromLoadSpecOf(cls) + case LoadJSModule(cls) => + genLoadFromLoadSpecOf(cls) + + case t: ClassOf => + if (transformTypeRef(t.typeRef) != t.typeRef) + reportError(s"illegal Class(${t.typeRef})") + t + + case t => + t + } + + validateType(result.tpe) + result + } + + private def genLoadFromLoadSpecOf(cls: ClassRef)( + implicit pos: Position): Tree = { + jsTypes.get(cls.className) match { + case Some(classDef) => + classDef.jsNativeLoadSpec match { + case Some(loadSpec) => + genLoadFromLoadSpec(loadSpec) + case None => + reportError( + s"$cls does not have a load spec " + + "(this shouldn't have happened at all; bug in the compiler?)") + JSGlobalRef(Ident("Object")) + } + case None => + reportError(s"$cls is not a JS type") + JSGlobalRef(Ident("Object")) + } + } + + private def genLoadFromLoadSpec(loadSpec: JSNativeLoadSpec)( + implicit pos: Position): Tree = { + loadSpec match { + case JSNativeLoadSpec.Global(globalRef, Nil) => + JSGlobalRef(Ident(globalRef)) + case _ => + reportError( + s"unsupported load spec $loadSpec; " + + "only @JSGlobal without `.` is supported") + JSGlobalRef(Ident("Object")) + } + } + + private def transformMethodIdent(ident: Ident): Ident = { + implicit val pos = ident.pos + val encodedName = ident.encodedName + val sig = decodeMethodName(encodedName) + val newParamTypes = sig._2.map(transformTypeRef) + val newResultType = sig._3.map(transformTypeRef) + if (newParamTypes == sig._2 && newResultType == sig._3) { + ident + } else { + Ident(encodeMethodName(sig._1, newParamTypes, newResultType), + ident.originalName)(ident.pos) + } + } + + private def transformClassRef(cls: ClassRef)( + implicit pos: Position): ClassRef = { + if (jsTypes.contains(cls.className)) { + ClassRef(ObjectClass) + } else { + validateClassName(cls.className) + cls + } + } + + private def transformArrayTypeRef(typeRef: ArrayTypeRef)( + implicit pos: Position): ArrayTypeRef = { + if (jsTypes.contains(typeRef.baseClassName)) { + ArrayTypeRef(ObjectClass, typeRef.dimensions) + } else { + validateClassName(typeRef.baseClassName) + typeRef + } + } + + private def transformTypeRef(typeRef: TypeRef)( + implicit pos: Position): TypeRef = typeRef match { + case typeRef: ClassRef => transformClassRef(typeRef) + case typeRef: ArrayTypeRef => transformArrayTypeRef(typeRef) + } + + private def postTransformChecks(classDef: ClassDef): Unit = { + // Check that no two methods have been erased to the same name + val seenMethodNames = mutable.Set.empty[(MemberNamespace, String)] + for (m <- classDef.memberDefs) { + if (m.isInstanceOf[MethodDef]) { + if (!seenMethodNames.add((m.flags.namespace, m.name.encodedName))) { + reportError( + s"duplicate method name ${m.name.encodedName} after erasure")( + m.pos) + } + } + } + } + + private def validateType(tpe: Type)(implicit pos: Position): Unit = { + tpe match { + case ClassType(cls) => + validateClassName(cls) + case ArrayType(ArrayTypeRef(cls, _)) => + validateClassName(cls) + case _ => + // ok + } + } + + private def validateClassName(cls: String)(implicit pos: Position): Unit = { + if (isScalaClassName(cls)) + reportError(s"Illegal reference to Scala class $cls") + } + + private def validateNonJSClassRef(cls: ClassRef)(implicit pos: Position): Unit = + validateNonJSClassName(cls.className) + + private def validateNonJSClassName(cls: String)(implicit pos: Position): Unit = { + if (jsTypes.contains(cls)) + reportError(s"Invalid reference to JS class $cls") + else + validateClassName(cls) + } + + private def isScalaClassName(cls: String): Boolean = { + { + cls.startsWith("s") || + cls.startsWith("Lscala_") || + (cls.length > 1 && (cls.charAt(0) == 'T' || cls.charAt(0) == 'F')) + } && { + cls != "sjsr_UndefinedBehaviorError" // TODO We need to get rid of this + } + } + + private def reportError(msg: String)(implicit pos: Position): Unit = { + errorManager.reportError(s"$msg in $enclosingClassName") + } + } +} From e7706c6bd6f8adb7c1a44f3d04e767e3194b3f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 2 Aug 2019 17:34:42 +0200 Subject: [PATCH 0051/1606] Do not override `j.l.Class` and `j.l.FloatingPointBits` in the minilib. With the new independent javalanglib, the standard ones can now be used in the minilib. Moreover, we remove `scala.Serializable` from the minilib, as our IR cleaner is able to remove its spurious addition by scalac. This requires to adapt `RuntimeLong` a bit not to have the spurious `scala.Serializable`, though (because it is not subject to the IR cleaner). We also adapt the standard `FloatPointBits` to directly use `js.Math` methods instead of `j.l.Math`, so that we don't have to add `Math` to the minilib. --- .../scala/java/lang/FloatingPointBits.scala | 8 ++-- .../scala/scalajs/runtime/RuntimeLong.scala | 5 ++- minilib/src/main/scala/java/lang/Class.scala | 38 ------------------- .../scala/java/lang/FloatingPointBits.scala | 31 --------------- minilib/src/main/scala/java/lang/System.scala | 2 +- .../src/main/scala/java/lang/Throwable.scala | 6 +-- project/MiniLib.scala | 17 ++------- 7 files changed, 17 insertions(+), 90 deletions(-) delete mode 100644 minilib/src/main/scala/java/lang/Class.scala delete mode 100644 minilib/src/main/scala/java/lang/FloatingPointBits.scala diff --git a/javalanglib/src/main/scala/java/lang/FloatingPointBits.scala b/javalanglib/src/main/scala/java/lang/FloatingPointBits.scala index 2c051c32ba..ea7f38d379 100644 --- a/javalanglib/src/main/scala/java/lang/FloatingPointBits.scala +++ b/javalanglib/src/main/scala/java/lang/FloatingPointBits.scala @@ -209,7 +209,7 @@ private[lang] object FloatingPointBits { @inline private def encodeIEEE754(ebits: Int, fbits: Int, v: scala.Double): EncodeIEEE754Result = { - import Math._ + import js.Math.{floor, log, pow} val bias = (1 << (ebits-1)) - 1 // constant @@ -229,7 +229,9 @@ private[lang] object FloatingPointBits { if (av >= pow(2, 1-bias)) { val twoPowFbits = pow(2, fbits) - var e = min(rawToInt(floor(log(av) / LN2)), 1023) + var e = rawToInt(floor(log(av) / LN2)) + if (e > 1023) + e = 1023 var twoPowE = pow(2, e) /* #2911: When av is very close under a power of 2 (e.g., @@ -269,7 +271,7 @@ private[lang] object FloatingPointBits { (x.asInstanceOf[js.Dynamic] | 0.asInstanceOf[js.Dynamic]).asInstanceOf[Int] @inline private def roundToEven(n: scala.Double): scala.Double = { - val w = Math.floor(n) + val w = js.Math.floor(n) val f = n - w if (f < 0.5) w else if (f > 0.5) w + 1 diff --git a/library/src/main/scala/scala/scalajs/runtime/RuntimeLong.scala b/library/src/main/scala/scala/scalajs/runtime/RuntimeLong.scala index a6ecd8237b..06ab3aaf82 100644 --- a/library/src/main/scala/scala/scalajs/runtime/RuntimeLong.scala +++ b/library/src/main/scala/scala/scalajs/runtime/RuntimeLong.scala @@ -536,7 +536,10 @@ final class RuntimeLong(val lo: Int, val hi: Int) } -object RuntimeLong { +/* `extends java.io.Serializable` prevents scalac from inserting + * `extends scala.Serializable`. + */ +object RuntimeLong extends java.io.Serializable { import Utils._ private final val TwoPow32 = 4294967296.0 diff --git a/minilib/src/main/scala/java/lang/Class.scala b/minilib/src/main/scala/java/lang/Class.scala deleted file mode 100644 index f9e8d72812..0000000000 --- a/minilib/src/main/scala/java/lang/Class.scala +++ /dev/null @@ -1,38 +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. - */ - -package java.lang - -import scala.scalajs.js - -/** Stripped down version of `java.lang.Class`, with just the bare minimum to - * support `toString()`. - * - * We cannot use the full `java.lang.Class` out of the box because it - * internally uses a static facade type for its `data`, and the minilib cannot - * contain any JS type. - */ -final class Class[A] private (private val data: Object) extends Object { - override def toString(): String = { - (if (isInterface()) "interface " else - if (isPrimitive()) "" else "class ") + getName() - } - - def isInterface(): scala.Boolean = - data.asInstanceOf[js.Dynamic].isInterface.asInstanceOf[scala.Boolean] - - def isPrimitive(): scala.Boolean = - data.asInstanceOf[js.Dynamic].isPrimitive.asInstanceOf[scala.Boolean] - - def getName(): String = - data.asInstanceOf[js.Dynamic].name.asInstanceOf[String] -} diff --git a/minilib/src/main/scala/java/lang/FloatingPointBits.scala b/minilib/src/main/scala/java/lang/FloatingPointBits.scala deleted file mode 100644 index 1e98dc1eb0..0000000000 --- a/minilib/src/main/scala/java/lang/FloatingPointBits.scala +++ /dev/null @@ -1,31 +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. - */ - -package java.lang - -import scala.scalajs.js - -/** Stripped down version of `java.lang.FloatingPointBits` with the bare - * minimum to support a correct `numberHashCode()`. - * - * We cannot use the full `java.lang.FloatingPointBits` out of the box, - * because it internally uses typed arrays (and static facade types thereof) - * to implement a better `numberHashCode()`. - */ -private[lang] object FloatingPointBits { - // Simpler version than the original, technically valid but of lesser quality - def numberHashCode(value: scala.Double): Int = - rawToInt(value) - - @inline private def rawToInt(x: scala.Double): Int = - (x.asInstanceOf[js.Dynamic] | 0.asInstanceOf[js.Dynamic]).asInstanceOf[Int] -} diff --git a/minilib/src/main/scala/java/lang/System.scala b/minilib/src/main/scala/java/lang/System.scala index 31907ec7ed..3033942a05 100644 --- a/minilib/src/main/scala/java/lang/System.scala +++ b/minilib/src/main/scala/java/lang/System.scala @@ -13,7 +13,7 @@ package java.lang /** Stripped down version of `java.lang.System` with the bare minimum to - * support a correct `numberHashCode()`. + * support a correct `Object.hashCode()`. * * We cannot use the full `java.lang.System` out of the box, because its * constructor initializes `out` and `err`, which require diff --git a/minilib/src/main/scala/java/lang/Throwable.scala b/minilib/src/main/scala/java/lang/Throwable.scala index 220af2a898..255385e839 100644 --- a/minilib/src/main/scala/java/lang/Throwable.scala +++ b/minilib/src/main/scala/java/lang/Throwable.scala @@ -17,9 +17,9 @@ package java.lang * * We cannot use the full `java.lang.Throwable` out of the box, because its * constructor calls `initStackTrace()`, which uses `StackTrace.scala` and - * therefore a bunch of JS stuff inside to recover stack traces. This stripped - * down `Throwable` does not offer any method to access the stack trace so - * that `initStackTrace()` is not necessary. + * therefore a bunch of utilities inside to recover stack traces. This + * stripped down `Throwable` does not offer any method to access the stack + * trace so that `initStackTrace()` is not necessary. */ class Throwable(s: String, e: Throwable) extends Object with java.io.Serializable { diff --git a/project/MiniLib.scala b/project/MiniLib.scala index 951c057e60..ab1c720c32 100644 --- a/project/MiniLib.scala +++ b/project/MiniLib.scala @@ -4,7 +4,7 @@ object MiniLib { val Whitelist = { val inJavaLang = List( "Object", - // "Class" is overridden in minilib/ + "Class", // "System" is overridden in minilib/ "CharSequence", @@ -23,7 +23,8 @@ object MiniLib { "Double", "String", - // "FloatingPointBits" is overridden in minilib/ + "FloatingPointBits", + "FloatingPointBits$EncodeIEEE754Result", // "Throwable" is overridden in minilib/ "Error", @@ -44,16 +45,6 @@ object MiniLib { "Serializable" ).map("java/io/" + _) - /* TODO Unfortunately, when a class extends java.io.Serializable, scalac - * forces its companion object to extend scala.Serializable (ugh!), which - * means that things like java.lang.Integer$ depend on scala.Serializable. - * However, as far as the linker is concerned, it shouldn't need, so it - * would be nice to get rid of this dependency. - */ - val inScala = List( - "Serializable" - ).map("scala/" + _) - /* TODO Could we put UndefinedBehaviorError in a neutral namespace? * RuntimeLong should probably be part of the linker itself, as a resource. */ @@ -64,7 +55,7 @@ object MiniLib { ).map("scala/scalajs/runtime/" + _) val allBaseNames = - inJavaLang ::: inJavaIO ::: inScala ::: inScalaJSRuntime + inJavaLang ::: inJavaIO ::: inScalaJSRuntime allBaseNames.flatMap(name => List(name + ".sjsir", name + "$.sjsir")).toSet } From e2703168e1eedaaa2b71a734cfed9d8c7cf3e0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 5 Aug 2019 11:49:04 +0200 Subject: [PATCH 0052/1606] Refactor `System` to allow better DCE and to use it in the minilib. --- .../src/main/scala/java/lang/System.scala | 289 +++++++++++------- minilib/src/main/scala/java/lang/System.scala | 39 --- project/MiniLib.scala | 3 +- 3 files changed, 186 insertions(+), 145 deletions(-) delete mode 100644 minilib/src/main/scala/java/lang/System.scala diff --git a/javalanglib/src/main/scala/java/lang/System.scala b/javalanglib/src/main/scala/java/lang/System.scala index acd3d34bc3..31d9f5b3bc 100644 --- a/javalanglib/src/main/scala/java/lang/System.scala +++ b/javalanglib/src/main/scala/java/lang/System.scala @@ -20,45 +20,80 @@ import scala.scalajs.runtime.linkingInfo import java.{util => ju} -import Utils._ - object System { - var out: PrintStream = new JSConsoleBasedPrintStream(isErr = false) - var err: PrintStream = new JSConsoleBasedPrintStream(isErr = true) - var in: InputStream = null + /* System contains a bag of unrelated features. If we naively implement + * everything inside System, reaching any of these features can reach + * unrelated code. For example, using `nanoTime()` would reach + * `JSConsoleBasedPrintStream` and therefore a bunch of `java.io` classes. + * + * Instead, every feature that requires its own fields is extracted in a + * separate private object, and corresponding methods of System delegate to + * methods of that private object. + * + * All non-intrinsic methods are marked `@inline` so that the module accessor + * of `System` can always be completely elided. + */ + + // Standard streams (out, err, in) ------------------------------------------ + + private object Streams { + var out: PrintStream = new JSConsoleBasedPrintStream(isErr = false) + var err: PrintStream = new JSConsoleBasedPrintStream(isErr = true) + var in: InputStream = null + } + @inline + def out: PrintStream = Streams.out + + @inline + def err: PrintStream = Streams.err + + @inline + def in: InputStream = Streams.in + + @inline def setIn(in: InputStream): Unit = - this.in = in + Streams.in = in + @inline def setOut(out: PrintStream): Unit = - this.out = out + Streams.out = out + @inline def setErr(err: PrintStream): Unit = - this.err = err + Streams.err = err + + // System time -------------------------------------------------------------- - def currentTimeMillis(): scala.Long = { + @inline + def currentTimeMillis(): scala.Long = (new js.Date).getTime().toLong - } - private[this] val getHighPrecisionTime: js.Function0[scala.Double] = { - import Utils.DynamicImplicits.truthValue + private object NanoTime { + val getHighPrecisionTime: js.Function0[scala.Double] = { + import Utils.DynamicImplicits.truthValue - if (js.typeOf(global.performance) != "undefined") { - if (global.performance.now) { - () => global.performance.now().asInstanceOf[scala.Double] - } else if (global.performance.webkitNow) { - () => global.performance.webkitNow().asInstanceOf[scala.Double] + if (js.typeOf(global.performance) != "undefined") { + if (global.performance.now) { + () => global.performance.now().asInstanceOf[scala.Double] + } else if (global.performance.webkitNow) { + () => global.performance.webkitNow().asInstanceOf[scala.Double] + } else { + () => new js.Date().getTime() + } } else { () => new js.Date().getTime() } - } else { - () => new js.Date().getTime() } } + @inline def nanoTime(): scala.Long = - (getHighPrecisionTime() * 1000000).toLong + (NanoTime.getHighPrecisionTime() * 1000000).toLong + + // arraycopy ---------------------------------------------------------------- + // Intrinsic def arraycopy(src: Object, srcPos: scala.Int, dest: Object, destPos: scala.Int, length: scala.Int): Unit = { @@ -144,79 +179,93 @@ object System { }) } - def identityHashCode(x: Object): scala.Int = { - (x: Any) match { - case null => 0 - case _:scala.Boolean | _:scala.Double | _:String | () => - x.hashCode() - case _ => - import IDHashCode._ - if (x.getClass == null) { - /* x is not a Scala.js object: we have delegate to x.hashCode(). - * This is very important, as we really need to go through - * `$objectHashCode()` in `CoreJSLib` instead of using our own - * `idHashCodeMap`. That's because `$objectHashCode()` uses the - * intrinsic `$systemIdentityHashCode` for JS objects, regardless of - * whether the optimizer is enabled or not. If we use our own - * `idHashCodeMap`, we will get different hash codes when obtained - * through `System.identityHashCode(x)` than with `x.hashCode()`. - */ - x.hashCode() - } else if (linkingInfo.assumingES6 || idHashCodeMap != null) { - // Use the global WeakMap of attributed id hash codes - val hash = idHashCodeMap.get(x.asInstanceOf[js.Any]) - if (!Utils.isUndefined(hash)) { - hash.asInstanceOf[Int] - } else { - val newHash = nextIDHashCode() - idHashCodeMap.set(x.asInstanceOf[js.Any], newHash.asInstanceOf[js.Any]) - newHash - } - } else { - val hash = x.asInstanceOf[js.Dynamic].selectDynamic("$idHashCode$0") - if (!Utils.isUndefined(hash)) { - /* Note that this can work even if x is sealed, if - * identityHashCode() was called for the first time before x was - * sealed. - */ - hash.asInstanceOf[Int] - } else if (!js.Object.isSealed(x.asInstanceOf[js.Object])) { - /* If x is not sealed, we can (almost) safely create an additional - * field with a bizarre and relatively long name, even though it is - * technically undefined behavior. - */ - val newHash = nextIDHashCode() - x.asInstanceOf[js.Dynamic].updateDynamic("$idHashCode$0")(newHash.asInstanceOf[js.Any]) - newHash - } else { - // Otherwise, we unfortunately have to return a constant. - 42 - } - } - } - } + // identityHashCode --------------------------------------------------------- private object IDHashCode { - private var lastIDHashCode: Int = 0 + private[this] var lastIDHashCode: Int = 0 - val idHashCodeMap = + private[this] val idHashCodeMap = { if (linkingInfo.assumingES6 || js.typeOf(global.WeakMap) != "undefined") js.Dynamic.newInstance(global.WeakMap)() else null + } - def nextIDHashCode(): Int = { + @inline + private def nextIDHashCode(): Int = { val r = lastIDHashCode + 1 lastIDHashCode = r r } + + def idHashCode(x: Object): scala.Int = { + (x: Any) match { + case null => + 0 + case _:scala.Boolean | _:scala.Double | _:String | () => + x.hashCode() + case _ => + if (x.getClass == null) { + /* x is not a Scala.js object: we have delegate to x.hashCode(). + * This is very important, as we really need to go through + * `$objectHashCode()` in `CoreJSLib` instead of using our own + * `idHashCodeMap`. That's because `$objectHashCode()` uses the + * intrinsic `$systemIdentityHashCode` for JS objects, regardless + * of whether the optimizer is enabled or not. If we use our own + * `idHashCodeMap`, we will get different hash codes when obtained + * through `System.identityHashCode(x)` than with `x.hashCode()`. + */ + x.hashCode() + } else if (linkingInfo.assumingES6 || idHashCodeMap != null) { + // Use the global WeakMap of attributed id hash codes + val hash = idHashCodeMap.get(x.asInstanceOf[js.Any]) + if (hash ne ().asInstanceOf[AnyRef]) { + hash.asInstanceOf[Int] + } else { + val newHash = nextIDHashCode() + idHashCodeMap.set(x.asInstanceOf[js.Any], + newHash.asInstanceOf[js.Any]) + newHash + } + } else { + val hash = x.asInstanceOf[js.Dynamic].selectDynamic("$idHashCode$0") + if (hash ne ().asInstanceOf[AnyRef]) { + /* Note that this can work even if x is sealed, if + * identityHashCode() was called for the first time before x was + * sealed. + */ + hash.asInstanceOf[Int] + } else if (!js.Object.isSealed(x.asInstanceOf[js.Object])) { + /* If x is not sealed, we can (almost) safely create an + * additional field with a bizarre and relatively long name, even + * though it is technically undefined behavior. + */ + val newHash = nextIDHashCode() + x.asInstanceOf[js.Dynamic].updateDynamic("$idHashCode$0")( + newHash.asInstanceOf[js.Any]) + newHash + } else { + // Otherwise, we unfortunately have to return a constant. + 42 + } + } + } + } } + // Intrinsic + def identityHashCode(x: Object): scala.Int = + IDHashCode.idHashCode(x) + + // System properties -------------------------------------------------------- + private object SystemProperties { - var dict: js.Dictionary[String] = loadSystemProperties() - var properties: ju.Properties = null + import Utils._ + + private[this] var dict: js.Dictionary[String] = loadSystemProperties() + private[this] var properties: ju.Properties = null - private[System] def loadSystemProperties(): js.Dictionary[String] = { + private def loadSystemProperties(): js.Dictionary[String] = { val result = new js.Object().asInstanceOf[js.Dictionary[String]] dictSet(result, "java.version", "1.8") dictSet(result, "java.vm.specification.version", "1.8") @@ -233,7 +282,7 @@ object System { result } - private[System] def forceProperties(): ju.Properties = { + def getProperties(): ju.Properties = { if (properties eq null) { properties = new ju.Properties val keys = js.Object.keys(dict.asInstanceOf[js.Object]) @@ -244,48 +293,74 @@ object System { } properties } + + def setProperties(properties: ju.Properties): Unit = { + if (properties eq null) { + dict = loadSystemProperties() + this.properties = null + } else { + dict = null + this.properties = properties + } + } + + def getProperty(key: String): String = + 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) + else properties.getProperty(key, default) + + def clearProperty(key: String): String = + if (dict ne null) dictGetOrElseAndRemove(dict, key, null) + else properties.remove(key).asInstanceOf[String] + + def setProperty(key: String, value: String): String = { + if (dict ne null) { + val oldValue = getProperty(key) + dictSet(dict, key, value) + oldValue + } else { + properties.setProperty(key, value).asInstanceOf[String] + } + } } + @inline def getProperties(): ju.Properties = - SystemProperties.forceProperties() + SystemProperties.getProperties() + @inline def lineSeparator(): String = "\n" - def setProperties(properties: ju.Properties): Unit = { - if (properties eq null) { - SystemProperties.dict = SystemProperties.loadSystemProperties() - SystemProperties.properties = null - } else { - SystemProperties.dict = null - SystemProperties.properties = properties - } - } + @inline + def setProperties(properties: ju.Properties): Unit = + SystemProperties.setProperties(properties) + @inline def getProperty(key: String): String = - if (SystemProperties.dict ne null) dictGetOrElse(SystemProperties.dict, key, null) - else SystemProperties.properties.getProperty(key) + SystemProperties.getProperty(key) + @inline def getProperty(key: String, default: String): String = - if (SystemProperties.dict ne null) dictGetOrElse(SystemProperties.dict, key, default) - else SystemProperties.properties.getProperty(key, default) + SystemProperties.getProperty(key, default) + @inline def clearProperty(key: String): String = - if (SystemProperties.dict ne null) dictGetOrElseAndRemove(SystemProperties.dict, key, null) - else SystemProperties.properties.remove(key).asInstanceOf[String] - - def setProperty(key: String, value: String): String = { - if (SystemProperties.dict ne null) { - val oldValue = getProperty(key) - dictSet(SystemProperties.dict, key, value) - oldValue - } else { - SystemProperties.properties.setProperty(key, value).asInstanceOf[String] - } - } + SystemProperties.clearProperty(key) + + @inline + def setProperty(key: String, value: String): String = + SystemProperties.setProperty(key, value) + // Environment variables ---------------------------------------------------- + + @inline def getenv(): ju.Map[String, String] = ju.Collections.emptyMap() + @inline def getenv(name: String): String = { if (name eq null) throw new NullPointerException @@ -293,11 +368,15 @@ object System { null } + // Runtime ------------------------------------------------------------------ + //def exit(status: scala.Int): Unit + + @inline def gc(): Unit = Runtime.getRuntime().gc() } -private[lang] final class JSConsoleBasedPrintStream(isErr: scala.Boolean) +private final class JSConsoleBasedPrintStream(isErr: scala.Boolean) extends PrintStream(new JSConsoleBasedPrintStream.DummyOutputStream) { import JSConsoleBasedPrintStream._ diff --git a/minilib/src/main/scala/java/lang/System.scala b/minilib/src/main/scala/java/lang/System.scala deleted file mode 100644 index 3033942a05..0000000000 --- a/minilib/src/main/scala/java/lang/System.scala +++ /dev/null @@ -1,39 +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. - */ - -package java.lang - -/** Stripped down version of `java.lang.System` with the bare minimum to - * support a correct `Object.hashCode()`. - * - * We cannot use the full `java.lang.System` out of the box, because its - * constructor initializes `out` and `err`, which require - * `JSConsoleBasedPrintStream` and therefore a large part of `java.io`. We do - * not simply reuse (copy-paste) `identityHashCode` either because it - * internally uses a `WeakMap` typed as `js.Dynamic`. - */ -object System { - // Simpler than the original, technically valid but of lesser quality - def identityHashCode(x: Object): scala.Int = { - (x: Any) match { - case null => 0 - case _:scala.Boolean | _:scala.Double | _:String | () => - x.hashCode() - case _ => - // See the original System.scala for the rationale of this test - if (x.getClass == null) - x.hashCode() - else - 42 - } - } -} diff --git a/project/MiniLib.scala b/project/MiniLib.scala index ab1c720c32..765812b49e 100644 --- a/project/MiniLib.scala +++ b/project/MiniLib.scala @@ -5,7 +5,8 @@ object MiniLib { val inJavaLang = List( "Object", "Class", - // "System" is overridden in minilib/ + "System", + "System$IDHashCode", "CharSequence", "Cloneable", From 03a9acdce9e01206db7902b54b0d44ad758a0d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 21 Aug 2019 16:59:32 +0200 Subject: [PATCH 0053/1606] Implement `java.lang.{Byte,Short,Integer.Long}.decode()`. --- .../src/main/scala/java/lang/Byte.scala | 3 + .../src/main/scala/java/lang/Integer.scala | 57 ++++++++++++++ .../src/main/scala/java/lang/Long.scala | 3 + .../src/main/scala/java/lang/Short.scala | 3 + .../testsuite/javalib/lang/ByteTest.scala | 59 ++++++++++++++- .../testsuite/javalib/lang/IntegerTest.scala | 75 ++++++++++++++++--- .../testsuite/javalib/lang/LongTest.scala | 41 ++++++++++ .../testsuite/javalib/lang/ShortTest.scala | 59 ++++++++++++++- 8 files changed, 286 insertions(+), 14 deletions(-) diff --git a/javalanglib/src/main/scala/java/lang/Byte.scala b/javalanglib/src/main/scala/java/lang/Byte.scala index 36e457dbf8..357e8ac230 100644 --- a/javalanglib/src/main/scala/java/lang/Byte.scala +++ b/javalanglib/src/main/scala/java/lang/Byte.scala @@ -79,6 +79,9 @@ object Byte { @inline def toString(b: scala.Byte): String = "" + b + @noinline def decode(nm: String): Byte = + Integer.decodeGeneric(nm, valueOf(_, _)) + @inline def compare(x: scala.Byte, y: scala.Byte): scala.Int = x - y } diff --git a/javalanglib/src/main/scala/java/lang/Integer.scala b/javalanglib/src/main/scala/java/lang/Integer.scala index 1cc590f09d..1d6996753c 100644 --- a/javalanglib/src/main/scala/java/lang/Integer.scala +++ b/javalanglib/src/main/scala/java/lang/Integer.scala @@ -130,6 +130,63 @@ object Integer { @inline def toUnsignedString(i: Int, radix: Int): String = toStringBase(i, radix) + @noinline def decode(nm: String): Integer = + decodeGeneric(nm, valueOf(_, _)) + + @inline private[lang] def decodeGeneric[A](nm: String, + parse: (String, Int) => A): A = { + + val len = nm.length() + var i = 0 + + val negative = if (i != len) { + nm.charAt(i) match { + case '+' => + i += 1 + false + case '-' => + i += 1 + true + case _ => + false + } + } else { + false + } + + val base = if (i != len) { + nm.charAt(i) match { + case '0' => + if (i == len - 1) { + 10 + } else { + i += 1 + nm.charAt(i) match { + case 'x' | 'X' => + i += 1 + 16 + case _ => + 8 + } + } + case '#' => + i += 1 + 16 + case _ => + 10 + } + } else { + 10 + } + + val remaining = nm.substring(i) + if (remaining.startsWith("+") || remaining.startsWith("-")) + throw new NumberFormatException("Sign character in wrong position") + + val s = if (negative) "-" + remaining else remaining + parse(s, base) + } + @inline def compare(x: scala.Int, y: scala.Int): scala.Int = if (x == y) 0 else if (x < y) -1 else 1 diff --git a/javalanglib/src/main/scala/java/lang/Long.scala b/javalanglib/src/main/scala/java/lang/Long.scala index 0c7c5ca695..4aca4b87f4 100644 --- a/javalanglib/src/main/scala/java/lang/Long.scala +++ b/javalanglib/src/main/scala/java/lang/Long.scala @@ -321,6 +321,9 @@ object Long { @inline def valueOf(s: String, radix: Int): Long = valueOf(parseLong(s, radix)) + @noinline def decode(nm: String): Long = + Integer.decodeGeneric(nm, valueOf(_, _)) + @inline def hashCode(value: scala.Long): Int = value.toInt ^ (value >>> 32).toInt diff --git a/javalanglib/src/main/scala/java/lang/Short.scala b/javalanglib/src/main/scala/java/lang/Short.scala index 275784e3e8..78f0ab7305 100644 --- a/javalanglib/src/main/scala/java/lang/Short.scala +++ b/javalanglib/src/main/scala/java/lang/Short.scala @@ -78,6 +78,9 @@ object Short { @inline def toString(s: scala.Short): String = "" + s + @noinline def decode(nm: String): Short = + Integer.decodeGeneric(nm, valueOf(_, _)) + @inline def compare(x: scala.Short, y: scala.Short): scala.Int = x - y diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ByteTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ByteTest.scala index 4a7c2de6ca..67326b665d 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ByteTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ByteTest.scala @@ -48,6 +48,7 @@ class ByteTest { assertEquals(v, JByte.parseByte(s)) assertEquals(v, JByte.valueOf(s).byteValue()) assertEquals(v, new JByte(s).byteValue()) + assertEquals(v, JByte.decode(s)) } test("0", 0) @@ -57,11 +58,67 @@ class ByteTest { } @Test def should_reject_invalid_strings_when_parsing(): Unit = { - def test(s: String): Unit = + def test(s: String): Unit = { expectThrows(classOf[NumberFormatException], JByte.parseByte(s)) + expectThrows(classOf[NumberFormatException], JByte.decode(s)) + } test("abc") test("") test("200") // out of range } + + @Test def should_parse_strings_in_base_16(): Unit = { + def test(s: String, v: Byte): Unit = { + assertEquals(v, JByte.parseByte(s, 16)) + assertEquals(v, JByte.valueOf(s, 16).intValue()) + assertEquals(v, JByte.decode(IntegerTest.insertAfterSign("0x", s))) + assertEquals(v, JByte.decode(IntegerTest.insertAfterSign("0X", s))) + assertEquals(v, JByte.decode(IntegerTest.insertAfterSign("#", s))) + } + + test("0", 0x0) + test("5", 0x5) + test("7f", 0x7f) + test("-24", -0x24) + test("30", 0x30) + test("-9", -0x9) + } + + @Test def testDecodeBase8(): Unit = { + def test(s: String, v: Byte): Unit = { + assertEquals(v, JByte.decode(s)) + } + + test("00", 0) + test("0123", 83) + test("-012", -10) + } + + @Test def testDecodeInvalid(): Unit = { + def test(s: String): Unit = + assertThrows(classOf[NumberFormatException], JByte.decode(s)) + + // sign after another sign or after a base prefix + test("++0") + test("--0") + test("0x+1") + test("0X-1") + test("#-1") + test("0-1") + + // empty string after sign or after base prefix + test("") + test("+") + test("-") + test("-0x") + test("+0X") + test("#") + + // integer too large + test("0x80") + test("-0x81") + test("0200") + test("-0201") + } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/IntegerTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/IntegerTest.scala index 4b32178395..369d823764 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/IntegerTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/IntegerTest.scala @@ -19,10 +19,7 @@ import org.scalajs.testsuite.utils.AssertThrows._ import org.scalajs.testsuite.utils.Platform._ class IntegerTest { - - // Explicitly define these as `var`'s to avoid any compile-time constant folding - val MaxValue: Int = Int.MaxValue - val MinValue: Int = Int.MinValue + import IntegerTest._ @Test def `reverseBytes`(): Unit = { assertEquals(0xefbeadde, Integer.reverseBytes(0xdeadbeef)) @@ -451,23 +448,23 @@ class IntegerTest { @Test def toBinaryString(): Unit = { assertEquals("11111111111111111111111111111111", Integer.toBinaryString(-1)) assertEquals("11111111111111111101100011101111", Integer.toBinaryString(-10001)) - assertEquals("10000000000000000000000000000000", Integer.toBinaryString(MinValue)) - assertEquals("1111111111111111111111111111111", Integer.toBinaryString(MaxValue)) + assertEquals("10000000000000000000000000000000", Integer.toBinaryString(Int.MinValue)) + assertEquals("1111111111111111111111111111111", Integer.toBinaryString(Int.MaxValue)) } @Test def toHexString(): Unit = { assertEquals("ffffffff", Integer.toHexString(-1)) assertEquals("ffffd8ef", Integer.toHexString(-10001)) - assertEquals("80000000", Integer.toHexString(MinValue)) + assertEquals("80000000", Integer.toHexString(Int.MinValue)) assertEquals("8007613e", Integer.toHexString(-2147000002)) - assertEquals("7fffffff", Integer.toHexString(MaxValue)) + assertEquals("7fffffff", Integer.toHexString(Int.MaxValue)) } @Test def toOctalString(): Unit = { assertEquals("37777777777", Integer.toOctalString(-1)) assertEquals("37777754357", Integer.toOctalString(-10001)) - assertEquals("20000000000", Integer.toOctalString(MinValue)) - assertEquals("17777777777", Integer.toOctalString(MaxValue)) + assertEquals("20000000000", Integer.toOctalString(Int.MinValue)) + assertEquals("17777777777", Integer.toOctalString(Int.MaxValue)) } @Test def compareTo(): Unit = { @@ -494,8 +491,10 @@ class IntegerTest { def test(s: String, v: Int, radix: Int = 10): Unit = { assertEquals(v, Integer.parseInt(s, radix)) assertEquals(v, Integer.valueOf(s, radix).intValue()) - if (radix == 10) + if (radix == 10) { assertEquals(v, new Integer(s).intValue()) + assertEquals(v, Integer.decode(s)) + } } test("0", 0) @@ -521,8 +520,11 @@ class IntegerTest { } @Test def should_reject_invalid_strings_when_parsing(): Unit = { - def test(s: String, radix: Int = 10): Unit = + def test(s: String, radix: Int = 10): Unit = { expectThrows(classOf[NumberFormatException], Integer.parseInt(s, radix)) + if (radix == 10 && s != null) + expectThrows(classOf[NumberFormatException], Integer.decode(s)) + } test("abc") test("5a") @@ -598,6 +600,9 @@ class IntegerTest { def test(s: String, v: Int): Unit = { assertEquals(v, Integer.parseInt(s, 16)) assertEquals(v, Integer.valueOf(s, 16).intValue()) + assertEquals(v, Integer.decode(insertAfterSign("0x", s))) + assertEquals(v, Integer.decode(insertAfterSign("0X", s))) + assertEquals(v, Integer.decode(insertAfterSign("#", s))) } test("0", 0x0) @@ -608,6 +613,43 @@ class IntegerTest { test("-90000", -0x90000) } + @Test def testDecodeBase8(): Unit = { + def test(s: String, v: Int): Unit = { + assertEquals(v, Integer.decode(s)) + } + + test("00", 0) + test("012345670", 2739128) + test("-012", -10) + } + + @Test def testDecodeInvalid(): Unit = { + def test(s: String): Unit = + assertThrows(classOf[NumberFormatException], Integer.decode(s)) + + // sign after another sign or after a base prefix + test("++0") + test("--0") + test("0x+1") + test("0X-1") + test("#-1") + test("0-1") + + // empty string after sign or after base prefix + test("") + test("+") + test("-") + test("-0x") + test("+0X") + test("#") + + // integer too large + test("0x80000000") + test("-0x800000001") + test("020000000000") + test("-020000000001") + } + @Test def highestOneBit(): Unit = { /* Spec ported from * https://github.com/gwtproject/gwt/blob/master/user/test/com/google/gwt/emultest/java/lang/IntegerTest.java @@ -662,3 +704,12 @@ class IntegerTest { assertEquals("-2147483648", Integer.toString(-2147483648, 10)) } } + +object IntegerTest { + def insertAfterSign(prefix: String, s: String): String = { + if (s.charAt(0) == '+' || s.charAt(0) == '-') + s.substring(0, 1) + prefix + s.substring(1) + else + prefix + s + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/LongTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/LongTest.scala index 91027c20a9..56ae36b3c0 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/LongTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/LongTest.scala @@ -130,6 +130,7 @@ class LongTest { assertEquals(v, JLong.parseLong(s)) assertEquals(v, JLong.valueOf(s).longValue()) assertEquals(v, new JLong(s).longValue()) + assertEquals(v, JLong.decode(s)) } test("0", 0L) @@ -162,6 +163,9 @@ class LongTest { def test(s: String, v: Long): Unit = { assertEquals(v, JLong.parseLong(s, 16)) assertEquals(v, JLong.valueOf(s, 16).longValue()) + assertEquals(v, JLong.decode(IntegerTest.insertAfterSign("0x", s))) + assertEquals(v, JLong.decode(IntegerTest.insertAfterSign("0X", s))) + assertEquals(v, JLong.decode(IntegerTest.insertAfterSign("#", s))) } test("0", 0x0L) @@ -207,6 +211,43 @@ class LongTest { List[Int](-10, -5, 0, 1, 37, 38, 50, 100).foreach(test("5", _)) } + @Test def testDecodeBase8(): Unit = { + def test(s: String, v: Long): Unit = { + assertEquals(v, JLong.decode(s)) + } + + test("00", 0L) + test("012345670", 2739128L) + test("-012", -10L) + } + + @Test def testDecodeInvalid(): Unit = { + def test(s: String): Unit = + assertThrows(classOf[NumberFormatException], JLong.decode(s)) + + // sign after another sign or after a base prefix + test("++0") + test("--0") + test("0x+1") + test("0X-1") + test("#-1") + test("0-1") + + // empty string after sign or after base prefix + test("") + test("+") + test("-") + test("-0x") + test("+0X") + test("#") + + // integer too large + test("0x8000000000000000") + test("-0x80000000000000001") + test("01000000000000000000000") + test("-01000000000000000000001") + } + @Test def toString_without_radix(): Unit = { assertEquals("2147483647", Int.MaxValue.toLong.toString) assertEquals("-50", (-50L).toString) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ShortTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ShortTest.scala index 82886c44a1..4b83101aba 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ShortTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/ShortTest.scala @@ -48,6 +48,7 @@ class ShortTest { assertEquals(v, JShort.parseShort(s)) assertEquals(v, JShort.valueOf(s).shortValue()) assertEquals(v, new JShort(s).shortValue()) + assertEquals(v, JShort.decode(s)) } test("0", 0) @@ -58,12 +59,68 @@ class ShortTest { } @Test def should_reject_invalid_strings_when_parsing(): Unit = { - def test(s: String): Unit = + def test(s: String): Unit = { expectThrows(classOf[NumberFormatException], JShort.parseShort(s)) + expectThrows(classOf[NumberFormatException], JShort.decode(s)) + } test("abc") test("") test("60000") // out of range test("-90000") // out of range } + + @Test def should_parse_strings_in_base_16(): Unit = { + def test(s: String, v: Short): Unit = { + assertEquals(v, JShort.parseShort(s, 16)) + assertEquals(v, JShort.valueOf(s, 16).intValue()) + assertEquals(v, JShort.decode(IntegerTest.insertAfterSign("0x", s))) + assertEquals(v, JShort.decode(IntegerTest.insertAfterSign("0X", s))) + assertEquals(v, JShort.decode(IntegerTest.insertAfterSign("#", s))) + } + + test("0", 0x0) + test("5", 0x5) + test("ff", 0xff) + test("-24", -0x24) + test("3000", 0x3000) + test("-900", -0x900) + } + + @Test def testDecodeBase8(): Unit = { + def test(s: String, v: Short): Unit = { + assertEquals(v, JShort.decode(s)) + } + + test("00", 0) + test("0123", 83) + test("-012", -10) + } + + @Test def testDecodeInvalid(): Unit = { + def test(s: String): Unit = + assertThrows(classOf[NumberFormatException], JShort.decode(s)) + + // sign after another sign or after a base prefix + test("++0") + test("--0") + test("0x+1") + test("0X-1") + test("#-1") + test("0-1") + + // empty string after sign or after base prefix + test("") + test("+") + test("-") + test("-0x") + test("+0X") + test("#") + + // integer too large + test("0x8000") + test("-0x8001") + test("0100000") + test("-0100001") + } } From b7203dda31366e8871f394fc623ad7c322d6e396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 28 Aug 2019 13:48:13 +0200 Subject: [PATCH 0054/1606] Update the Unicode database to match updates from JDK 8u211. The following characters were added: 00BB, 20BC through 20BF and 32FF. --- .../src/main/scala/java/lang/Character.scala | 720 +++++++++--------- .../javalib/lang/CharacterTest.scala | 10 +- 2 files changed, 385 insertions(+), 345 deletions(-) diff --git a/javalanglib/src/main/scala/java/lang/Character.scala b/javalanglib/src/main/scala/java/lang/Character.scala index 0db6c71acb..f31edea177 100644 --- a/javalanglib/src/main/scala/java/lang/Character.scala +++ b/javalanglib/src/main/scala/java/lang/Character.scala @@ -625,7 +625,8 @@ object Character { @inline def compare(x: scala.Char, y: scala.Char): Int = x - y - // Based on Unicode 7.0.0 + // Based on Unicode 6.2.0, extended with chars 00BB, 20BC-20BF and 32FF + // Generated with OpenJDK 1.8.0_222 // Types of characters from 0 to 255 private[this] lazy val charTypesFirst256: Array[Int] = Array(15, 15, 15, 15, @@ -643,354 +644,385 @@ object Character { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 25, 2, 2, 2, 2, 2, 2, 2, 2) - // Character type data by ranges of types - // charTypeIndices: contains the index where the range ends - // charType: contains the type of the carater in the range ends - // note that charTypeIndices.length + 1 = charType.length and that the - // range 0 to 255 is not included because it is contained in charTypesFirst256 - // They where generated with the following script: - // - // val indicesAndTypes = (256 to Character.MAX_CODE_POINT) - // .map(i => (i, Character.getType(i))) - // .foldLeft[List[(Int, Int)]](Nil) { - // case (x :: xs, elem) if x._2 == elem._2 => x :: xs - // case (prevs, elem) => elem :: prevs - // }.reverse - // val charTypeIndices = indicesAndTypes.map(_._1).tail - // val charTypeIndicesDeltas = charTypeIndices.zip(0 :: charTypeIndices.init) - // .map(tup => tup._1 - tup._2) - // val charTypes = indicesAndTypes.map(_._2) - // println(charTypeIndicesDeltas.mkString( - // "charTypeIndices: val deltas = Array(", ", ", ")")) - // println(charTypes.mkString("val charTypes = Array(", ", ", ")")) - // + /* Character type data by ranges of types + * charTypeIndices: contains the index where the range ends + * charType: contains the type of the carater in the range ends + * note that charTypeIndices.length + 1 = charType.length and that the + * range 0 to 255 is not included because it is contained in charTypesFirst256 + * + * They where generated with the following script, which can be pasted into + * a Scala REPL. + +def formatLargeArray(array: Array[Int], indent: String): String = { + val indentMinus1 = indent.substring(1) + val builder = new java.lang.StringBuilder + builder.append(indentMinus1) + var curLineLength = indentMinus1.length + for (i <- 0 until array.length) { + val toAdd = " " + array(i) + (if (i == array.length - 1) "" else ",") + if (curLineLength + toAdd.length >= 80) { + builder.append("\n") + builder.append(indentMinus1) + curLineLength = indentMinus1.length + } + builder.append(toAdd) + curLineLength += toAdd.length + } + builder.toString() +} + +val indicesAndTypes = (256 to Character.MAX_CODE_POINT) + .map(i => (i, Character.getType(i))) + .foldLeft[List[(Int, Int)]](Nil) { + case (x :: xs, elem) if x._2 == elem._2 => x :: xs + case (prevs, elem) => elem :: prevs + }.reverse +val charTypeIndices = indicesAndTypes.map(_._1).tail +val charTypeIndicesDeltas = charTypeIndices + .zip(0 :: charTypeIndices.init) + .map(tup => tup._1 - tup._2) +val charTypes = indicesAndTypes.map(_._2) +println("charTypeIndices, deltas:") +println(" Array(") +println(formatLargeArray(charTypeIndicesDeltas.toArray, " ")) +println(" )") +println("charTypes:") +println(" Array(") +println(formatLargeArray(charTypes.toArray, " ")) +println(" )") + + */ + private[this] lazy val charTypeIndices: Array[Int] = { - val deltas = Array(257, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 2, 1, 1, 1, 1, 3, 2, 1, 1, 1, 2, 1, 3, - 2, 4, 1, 2, 1, 3, 3, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, - 3, 1, 1, 1, 2, 2, 1, 1, 3, 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, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 2, 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, 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, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 27, 21, - 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 63, - 1, 256, 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, 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, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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) + val deltas = Array( + 257, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 2, + 1, 1, 1, 1, 3, 2, 1, 1, 1, 2, 1, 3, 2, 4, 1, 2, 1, 3, 3, 2, 1, 2, 1, 1, + 1, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 3, 1, 1, 1, 2, 2, 1, 1, 3, 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, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 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, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 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, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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 + ) uncompressDeltas(deltas) } - private[this] lazy val charTypes: Array[Int] = Array(1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 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, 1, 2, 5, 1, 3, 2, - 1, 3, 2, 1, 3, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 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, 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, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, - 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, - 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 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, 0, 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) - - // Indices representing the start of ranges of codePoint that have the same - // `isMirrored` result. It is true for the first range - // (i.e. isMirrored(40)==true, isMirrored(41)==true, isMirrored(42)==false) - // They where generated with the following script: - // - // val indicesAndRes = (0 to Character.MAX_CODE_POINT) - // .map(i => (i, Character.isMirrored(i))).foldLeft[List[(Int, Boolean)]](Nil) { - // case (x :: xs, elem) if x._2 == elem._2 => x :: xs - // case (prevs, elem) => elem :: prevs - // }.reverse - // val isMirroredIndices = indicesAndRes.map(_._1).tail - // val isMirroredIndicesDeltas = isMirroredIndices.zip( - // 0 :: isMirroredIndices.init).map(tup => tup._1 - tup._2) - // println(isMirroredIndicesDeltas.mkString( - // "isMirroredIndices: val deltas = Array[Int](", ", ", ")")) + private[this] lazy val charTypes: Array[Int] = Array( + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 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, 1, 2, 5, 1, 3, 2, 1, + 3, 2, 1, 3, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 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, + 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, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 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 + ) + + /* Indices representing the start of ranges of codePoint that have the same + * `isMirrored` result. It is true for the first range + * (i.e. isMirrored(40)==true, isMirrored(41)==true, isMirrored(42)==false) + * They where generated with the following script, which can be pasted into + * a Scala REPL. + +val indicesAndRes = (0 to Character.MAX_CODE_POINT) + .map(i => (i, Character.isMirrored(i))) + .foldLeft[List[(Int, Boolean)]](Nil) { + case (x :: xs, elem) if x._2 == elem._2 => x :: xs + case (prevs, elem) => elem :: prevs + }.reverse +val isMirroredIndices = indicesAndRes.map(_._1).tail +val isMirroredIndicesDeltas = isMirroredIndices + .zip(0 :: isMirroredIndices.init) + .map(tup => tup._1 - tup._2) +println("isMirroredIndices, deltas:") +println(" Array(") +println(formatLargeArray(isMirroredIndicesDeltas.toArray, " ")) +println(" )") + + */ private[this] lazy val isMirroredIndices: Array[Int] = { - val deltas = Array(40, 2, 18, 1, 1, 1, 28, 1, 1, 1, 29, 1, 1, 1, - 45, 1, 15, 1, 3710, 4, 1885, 2, 2460, 2, 10, 2, 54, 2, 14, 2, 177, 1, - 192, 4, 3, 6, 3, 1, 3, 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) + val deltas = Array( + 40, 2, 18, 1, 1, 1, 28, 1, 1, 1, 29, 1, 1, 1, 45, 1, 15, 1, 3710, 4, + 1885, 2, 2460, 2, 10, 2, 54, 2, 14, 2, 177, 1, 192, 4, 3, 6, 3, 1, 3, + 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 + ) uncompressDeltas(deltas) } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/CharacterTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/CharacterTest.scala index 5a01f5daa0..12c0183dbe 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/CharacterTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/lang/CharacterTest.scala @@ -2421,6 +2421,15 @@ class CharacterTest { assertTrue(Character.isDefined('\uFD39')) assertTrue(Character.isDefined('\uFF3A')) + if (!executingInJVM) { + assertTrue(Character.isDefined('\u00BB')) + assertTrue(Character.isDefined('\u20BC')) + assertTrue(Character.isDefined('\u20BD')) + assertTrue(Character.isDefined('\u20BE')) + assertTrue(Character.isDefined('\u20BF')) + assertTrue(Character.isDefined('\u32FF')) + } + // 100 randomly chosen characters that produce false assertFalse(Character.isDefined('\u0528')) assertFalse(Character.isDefined('\u052B')) @@ -2492,7 +2501,6 @@ class CharacterTest { assertFalse(Character.isDefined('\u2FE9')) assertFalse(Character.isDefined('\u2FEE')) assertFalse(Character.isDefined('\u31EE')) - assertFalse(Character.isDefined('\u32FF')) assertFalse(Character.isDefined('\u9FE9')) assertFalse(Character.isDefined('\uA639')) assertFalse(Character.isDefined('\uA7AE')) From fa808380af3766472057194252d8d4c162b5493f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 26 Aug 2019 11:53:42 +0200 Subject: [PATCH 0055/1606] Fix #2066: Emit `x.isInstanceOf[C]` as `x instanceof C` when possible. When the rhs of an `isInstanceOf` refers to a *class* `C` (as opposed to a trait), it is semantically correct to compile it as `x instanceof $c_C`. It turns out that this is much faster. In fastOpt mode, the optimization also affects `asInstanceOf`s. In fullOpt mode, most instance tests come from pattern matching, so effectively this optimization is very effective on pattern- matching-intensive workloads. Benchmarks of the bootstrapped Scala.js optimizer while optimizing the test suite show speedups of up to 47% in fastOpt and 40% in fullOpt, with the maximum for the optimizer core. --- ci/checksizes.sh | 12 +- .../linker/backend/emitter/ClassEmitter.scala | 128 ++++++++++-------- .../linker/backend/emitter/CoreJSLib.scala | 8 +- .../linker/backend/emitter/Emitter.scala | 8 ++ .../backend/emitter/EmitterDefinitions.scala | 50 +++++++ .../backend/emitter/FunctionEmitter.scala | 2 +- .../linker/backend/emitter/JSGen.scala | 33 ++++- ...nstanceTestsHijackedBoxedClassesTest.scala | 34 +++++ 8 files changed, 204 insertions(+), 71 deletions(-) create mode 100644 linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterDefinitions.scala diff --git a/ci/checksizes.sh b/ci/checksizes.sh index 5838408622..655f7bd4c1 100755 --- a/ci/checksizes.sh +++ b/ci/checksizes.sh @@ -34,20 +34,20 @@ REVERSI_OPT_GZ_SIZE=$(stat '-c%s' "$REVERSI_OPT.gz") case $FULLVER in 2.11.12) - REVERSI_PREOPT_EXPECTEDSIZE=415000 + REVERSI_PREOPT_EXPECTEDSIZE=414000 REVERSI_OPT_EXPECTEDSIZE=95000 REVERSI_PREOPT_GZ_EXPECTEDSIZE=58000 REVERSI_OPT_GZ_EXPECTEDSIZE=27000 ;; 2.12.8) - REVERSI_PREOPT_EXPECTEDSIZE=485000 - REVERSI_OPT_EXPECTEDSIZE=111000 - REVERSI_PREOPT_GZ_EXPECTEDSIZE=59000 + REVERSI_PREOPT_EXPECTEDSIZE=484000 + REVERSI_OPT_EXPECTEDSIZE=110000 + REVERSI_PREOPT_GZ_EXPECTEDSIZE=60000 REVERSI_OPT_GZ_EXPECTEDSIZE=27000 ;; 2.13.0) - REVERSI_PREOPT_EXPECTEDSIZE=526000 - REVERSI_OPT_EXPECTEDSIZE=123000 + REVERSI_PREOPT_EXPECTEDSIZE=523000 + REVERSI_OPT_EXPECTEDSIZE=121000 REVERSI_PREOPT_GZ_EXPECTEDSIZE=74000 REVERSI_OPT_GZ_EXPECTEDSIZE=34000 ;; diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala index db8d41ceaf..03cfd29373 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/ClassEmitter.scala @@ -24,6 +24,8 @@ import org.scalajs.linker.backend.javascript.{Trees => js} import CheckedBehavior.Unchecked +import EmitterDefinitions._ + /** Emitter for the skeleton of classes. */ private[emitter] final class ClassEmitter(jsGen: JSGen) { @@ -674,6 +676,25 @@ private[emitter] final class ClassEmitter(jsGen: JSGen) { } } + def genFakeClass(tree: LinkedClass): js.Tree = { + assert(tree.kind.isClass) + + implicit val pos = tree.pos + + val className = tree.encodedName + + if (esFeatures.useECMAScript2015) { + js.ClassDef(Some(encodeClassVar(className).ident), None, Nil) + } else { + js.Block( + js.DocComment("@constructor"), + envFieldDef("c", className, + js.Function(arrow = false, Nil, js.Skip()), + keepFunctionExpression = false) + ) + } + } + def genInstanceTests(tree: LinkedClass): js.Tree = { import Definitions._ import TreeDSL._ @@ -697,40 +718,56 @@ private[emitter] final class ClassEmitter(jsGen: JSGen) { val objParam = js.ParamDef(js.Ident("obj"), rest = false) val obj = objParam.ref - val createIsStat = { - envFunctionDef("is", className, List(objParam), js.Return { - className match { - case Definitions.ObjectClass => - js.BinaryOp(JSBinaryOp.!==, obj, js.Null()) + val isExpression = { + className match { + case Definitions.ObjectClass => + js.BinaryOp(JSBinaryOp.!==, obj, js.Null()) - case Definitions.BoxedStringClass => - js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral("string") + case Definitions.BoxedStringClass => + js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral("string") - case _ => - var test = { - genIsScalaJSObject(obj) && - genIsClassNameInAncestors(className, - obj DOT "$classData" DOT "ancestors") - } + case _ => + var test = if (tree.kind.isClass) { + obj instanceof encodeClassVar(className) + } else { + !(!( + genIsScalaJSObject(obj) && + genIsClassNameInAncestors(className, + obj DOT "$classData" DOT "ancestors") + )) + } - def typeOfTest(typeString: String): js.Tree = - js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral(typeString) + def typeOfTest(typeString: String): js.Tree = + js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral(typeString) - if (isAncestorOfString) - test = test || typeOfTest("string") - if (isAncestorOfHijackedNumberClass) { - test = test || typeOfTest("number") - if (useBigIntForLongs) - test = test || genCallHelper("isLong", obj) - } - if (isAncestorOfBoxedBooleanClass) - test = test || typeOfTest("boolean") - if (isAncestorOfBoxedCharacterClass) - test = test || (obj instanceof envField("Char")) + if (isAncestorOfString) + test = test || typeOfTest("string") + if (isAncestorOfHijackedNumberClass) { + test = test || typeOfTest("number") + if (useBigIntForLongs) + test = test || genCallHelper("isLong", obj) + } + if (isAncestorOfBoxedBooleanClass) + test = test || typeOfTest("boolean") + if (isAncestorOfBoxedCharacterClass) + test = test || (obj instanceof envField("Char")) - !(!test) - } - }) + test + } + } + + val needIsFunction = isExpression match { + case js.BinaryOp(JSBinaryOp.instanceof, _, _) => + // This is a simple `instanceof`. It will always be inlined at call site. + false + case _ => + true + } + + val createIsStat = if (needIsFunction) { + envFunctionDef("is", className, List(objParam), js.Return(isExpression)) + } else { + js.Skip() } val createAsStat = if (semantics.asInstanceOfs == Unchecked) { @@ -742,8 +779,11 @@ private[emitter] final class ClassEmitter(jsGen: JSGen) { obj case _ => - js.If(js.Apply(envField("is", className), List(obj)) || - (obj === js.Null()), { + val isCond = + if (needIsFunction) js.Apply(envField("is", className), List(obj)) + else isExpression + + js.If(isCond || (obj === js.Null()), { obj }, { genCallHelper("throwClassCastException", @@ -1229,35 +1269,9 @@ private[emitter] final class ClassEmitter(jsGen: JSGen) { } private[emitter] object ClassEmitter { - // TODO We should compute all of those from the Class Hierarchy - - private val CharSequenceClass = "jl_CharSequence" - private val SerializableClass = "Ljava_io_Serializable" - private val ComparableClass = "jl_Comparable" - private val NumberClass = "jl_Number" - - private val NonObjectAncestorsOfStringClass = - Set(CharSequenceClass, ComparableClass, SerializableClass) - private val NonObjectAncestorsOfBoxedCharacterClass = - Set(ComparableClass, SerializableClass) - private val NonObjectAncestorsOfHijackedNumberClasses = - Set(NumberClass, ComparableClass, SerializableClass) - private val NonObjectAncestorsOfBoxedBooleanClass = - Set(ComparableClass, SerializableClass) - - private[emitter] val AncestorsOfHijackedClasses = Set( - Definitions.ObjectClass, - CharSequenceClass, - SerializableClass, - ComparableClass, - NumberClass - ) - private val ClassesWhoseDataReferToTheirInstanceTests = AncestorsOfHijackedClasses + Definitions.BoxedStringClass - private final val ThrowableClass = "jl_Throwable" - def shouldExtendJSError(linkedClass: LinkedClass): Boolean = { linkedClass.name.name == ThrowableClass && linkedClass.superClass.exists(_.name == Definitions.ObjectClass) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala index 3d22c4996e..b6bbafa6c3 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/CoreJSLib.scala @@ -446,10 +446,10 @@ private[emitter] object CoreJSLib { If(instance === Null(), { Return(Apply(instance DOT "getClass__jl_Class", Nil)) }, { - If(genIsInstanceOf(instance, ClassRef(BoxedLongClass)), { + If(genIsInstanceOfHijackedClass(instance, ClassRef(BoxedLongClass)), { Return(genClassOf(BoxedLongClass)) }, { - If(genIsInstanceOf(instance, ClassRef(BoxedCharacterClass)), { + If(genIsInstanceOfHijackedClass(instance, ClassRef(BoxedCharacterClass)), { Return(genClassOf(BoxedCharacterClass)) }, { If(genIsScalaJSObject(instance), { @@ -535,7 +535,7 @@ private[emitter] object CoreJSLib { val defaultCall: Tree = Return(implementationInObject.getOrElse(normalCall)) val allButNormal = implementingHijackedClasses.foldRight(defaultCall) { (className, next) => - If(genIsInstanceOf(instance, ClassRef(className)), + If(genIsInstanceOfHijackedClass(instance, ClassRef(className)), Return(genHijackedMethodApply(className)), next) } @@ -962,7 +962,7 @@ private[emitter] object CoreJSLib { val fullName = decodeClassName(className) val v = varRef("v") buf += envFunctionDef(name, paramList(v), { - If(genIsInstanceOf(v, ClassRef(className)) || (v === Null()), { + If(genIsInstanceOfHijackedClass(v, ClassRef(className)) || (v === Null()), { Return(v) }, { genCallHelper("throwClassCastException", v, str(fullName)) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala index 6a53cedeae..5db6254843 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/Emitter.scala @@ -585,6 +585,14 @@ final class Emitter private (config: CommonPhaseConfig, } if (classEmitter.needInstanceTests(linkedClass)) { + if (!linkedClass.hasInstances && kind.isClass) { + /* The isInstanceOf implementation will generate + * `x instanceof $c_TheClass`, but `$c_TheClass` won't be declared at + * all. Define it as a fake class to avoid `ReferenceError`s. + */ + addToMainBase(classEmitter.genFakeClass(linkedClass)) + } + addToMainBase(classTreeCache.instanceTests.getOrElseUpdate(js.Block( classEmitter.genInstanceTests(linkedClass), classEmitter.genArrayInstanceTests(linkedClass) diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterDefinitions.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterDefinitions.scala new file mode 100644 index 0000000000..c2841093ea --- /dev/null +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/EmitterDefinitions.scala @@ -0,0 +1,50 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.linker.backend.emitter + +import org.scalajs.ir.Definitions._ + +private[emitter] object EmitterDefinitions { + /* In theory, some of the following could be computed from the Class + * Hierarchy. However, that would be require dealing with incremental runs, + * which would be overkill since these things are in fact known to be static. + */ + + final val CharSequenceClass = "jl_CharSequence" + final val SerializableClass = "Ljava_io_Serializable" + final val ComparableClass = "jl_Comparable" + final val NumberClass = "jl_Number" + + final val ThrowableClass = "jl_Throwable" + + val NonObjectAncestorsOfStringClass = + Set(CharSequenceClass, ComparableClass, SerializableClass) + val NonObjectAncestorsOfBoxedCharacterClass = + Set(ComparableClass, SerializableClass) + val NonObjectAncestorsOfHijackedNumberClasses = + Set(NumberClass, ComparableClass, SerializableClass) + val NonObjectAncestorsOfBoxedBooleanClass = + Set(ComparableClass, SerializableClass) + + val AncestorsOfHijackedClasses = Set( + ObjectClass, + CharSequenceClass, + SerializableClass, + ComparableClass, + NumberClass + ) + + val HijackedClassesAndTheirSuperClasses = + HijackedClasses ++ Set(ObjectClass, NumberClass) + +} diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala index c2e3785b79..0db285ef50 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/FunctionEmitter.scala @@ -2780,7 +2780,7 @@ private[emitter] class FunctionEmitter(jsGen: JSGen) { private object FunctionEmitter { private val MaybeHijackedClasses = { - (Definitions.HijackedClasses ++ ClassEmitter.AncestorsOfHijackedClasses) - + (Definitions.HijackedClasses ++ EmitterDefinitions.AncestorsOfHijackedClasses) - Definitions.ObjectClass } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala index 9f9dbf9243..6db12b76f9 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/backend/emitter/JSGen.scala @@ -18,12 +18,15 @@ import scala.annotation.tailrec import org.scalajs.ir import ir._ +import ir.Definitions._ import ir.Types._ import ir.{Trees => irt} import org.scalajs.linker._ import org.scalajs.linker.backend.javascript.Trees._ +import EmitterDefinitions._ + /** Collection of tree generators that are used accross the board. * This class is fully stateless. * @@ -104,8 +107,33 @@ private[emitter] final class JSGen(val semantics: Semantics, } def genIsInstanceOf(expr: Tree, typeRef: TypeRef)( - implicit pos: Position): Tree = - genIsAsInstanceOf(expr, typeRef, test = true) + implicit globalKnowledge: GlobalKnowledge, pos: Position): Tree = { + import TreeDSL._ + + typeRef match { + case ClassRef(className) => + if (!HijackedClassesAndTheirSuperClasses.contains(className) && + !globalKnowledge.isInterface(className)) { + expr instanceof encodeClassVar(className) + } else if (className == BoxedLongClass && !useBigIntForLongs) { + expr instanceof encodeClassVar(LongImpl.RuntimeLongClass) + } else { + genIsAsInstanceOf(expr, typeRef, test = true) + } + case ArrayTypeRef(_, _) => + genIsAsInstanceOf(expr, typeRef, test = true) + } + } + + def genIsInstanceOfHijackedClass(expr: Tree, classRef: ClassRef)( + implicit pos: Position): Tree = { + import TreeDSL._ + + if (classRef.className == BoxedLongClass && !useBigIntForLongs) + expr instanceof encodeClassVar(LongImpl.RuntimeLongClass) + else + genIsAsInstanceOf(expr, classRef, test = true) + } def genAsInstanceOf(expr: Tree, typeRef: TypeRef)( implicit pos: Position): Tree = @@ -113,7 +141,6 @@ private[emitter] final class JSGen(val semantics: Semantics, private def genIsAsInstanceOf(expr: Tree, typeRef: TypeRef, test: Boolean)( implicit pos: Position): Tree = { - import Definitions._ import TreeDSL._ typeRef match { diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InstanceTestsHijackedBoxedClassesTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InstanceTestsHijackedBoxedClassesTest.scala index a53b535a33..a5c0cbeae6 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InstanceTestsHijackedBoxedClassesTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InstanceTestsHijackedBoxedClassesTest.scala @@ -78,6 +78,40 @@ class InstanceTestsHijackedBoxedClassesTest { assertEquals("ok", test(0.2)) } + @Test def isInstanceOfJavaLangNumber(): Unit = { + @noinline def testNoinline(expected: Boolean, value: Any): Unit = { + assertEquals(expected, value.isInstanceOf[java.lang.Number]) + assertEquals(expected, classOf[java.lang.Number].isInstance(value)) + } + + @inline def test(expected: Boolean, value: Any): Unit = { + testNoinline(expected, value) + assertEquals(expected, value.isInstanceOf[java.lang.Number]) + assertEquals(expected, classOf[java.lang.Number].isInstance(value)) + } + + test(false, true) + test(false, 'A') + test(false, ()) + test(false, "hello") + + test(true, 1.toByte) + test(true, 0x100.toShort) + test(true, 0x10000) + test(true, 0x100000000L) + test(true, 1.5f) + test(true, 1.2) + + class CustomNumber extends java.lang.Number { + def intValue(): Int = 0 + def longValue(): Long = 0 + def floatValue(): Float = 0 + def doubleValue(): Double = 0 + } + + test(true, new CustomNumber) + } + @Test def should_support_asInstanceOf_positive(): Unit = { def swallow(x: Any): Unit = () swallow(((): Any).asInstanceOf[Unit]) From 108984703f64db40948938d6ce5226b6626a452e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 26 Aug 2019 17:53:39 +0200 Subject: [PATCH 0056/1606] [no-master] Fix #2066: Emit `x.isInstanceOf[C]` as `x instanceof C` when possible. Backport of fa808380af3766472057194252d8d4c162b5493f. When the rhs of an `isInstanceOf` refers to a *class* `C` (as opposed to a trait), it is semantically correct to compile it as `x instanceof $c_C`. It turns out that this is much faster. In fastOpt mode, the optimization also affects `asInstanceOf`s. In fullOpt mode, most instance tests come from pattern matching, so effectively this optimization is very effective on pattern- matching-intensive workloads. --- ci/checksizes.sh | 22 ++-- project/BinaryIncompatibilities.scala | 3 + ...nstanceTestsHijackedBoxedClassesTest.scala | 34 ++++++ tools/scalajsenv.js | 2 +- .../linker/backend/emitter/ClassEmitter.scala | 110 +++++++++++++----- .../linker/backend/emitter/Emitter.scala | 8 ++ .../backend/emitter/EmitterDefinitions.scala | 28 +++++ .../tools/linker/backend/emitter/JSGen.scala | 23 +++- .../linker/backend/emitter/TreeDSL.scala | 3 + 9 files changed, 186 insertions(+), 47 deletions(-) create mode 100644 tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/EmitterDefinitions.scala diff --git a/ci/checksizes.sh b/ci/checksizes.sh index 34f93b7959..fa1d4fcce2 100755 --- a/ci/checksizes.sh +++ b/ci/checksizes.sh @@ -37,28 +37,28 @@ REVERSI_OPT_GZ_SIZE=$(stat '-c%s' "$REVERSI_OPT.gz") case $FULLVER in 2.10.2) - REVERSI_PREOPT_EXPECTEDSIZE=533000 - REVERSI_OPT_EXPECTEDSIZE=122000 + REVERSI_PREOPT_EXPECTEDSIZE=532000 + REVERSI_OPT_EXPECTEDSIZE=120000 REVERSI_PREOPT_GZ_EXPECTEDSIZE=71000 REVERSI_OPT_GZ_EXPECTEDSIZE=31000 ;; 2.11.12) - REVERSI_PREOPT_EXPECTEDSIZE=530000 - REVERSI_OPT_EXPECTEDSIZE=124000 + REVERSI_PREOPT_EXPECTEDSIZE=528000 + REVERSI_OPT_EXPECTEDSIZE=121000 REVERSI_PREOPT_GZ_EXPECTEDSIZE=72000 - REVERSI_OPT_GZ_EXPECTEDSIZE=32000 + REVERSI_OPT_GZ_EXPECTEDSIZE=31000 ;; 2.12.8) - REVERSI_PREOPT_EXPECTEDSIZE=618000 - REVERSI_OPT_EXPECTEDSIZE=144000 + REVERSI_PREOPT_EXPECTEDSIZE=616000 + REVERSI_OPT_EXPECTEDSIZE=141000 REVERSI_PREOPT_GZ_EXPECTEDSIZE=74000 REVERSI_OPT_GZ_EXPECTEDSIZE=32000 ;; 2.13.0) - REVERSI_PREOPT_EXPECTEDSIZE=794000 - REVERSI_OPT_EXPECTEDSIZE=188000 - REVERSI_PREOPT_GZ_EXPECTEDSIZE=103000 - REVERSI_OPT_GZ_EXPECTEDSIZE=47000 + REVERSI_PREOPT_EXPECTEDSIZE=675000 + REVERSI_OPT_EXPECTEDSIZE=158000 + REVERSI_PREOPT_GZ_EXPECTEDSIZE=93000 + REVERSI_OPT_GZ_EXPECTEDSIZE=41000 ;; esac diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 2b1d742864..367ada5295 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -17,6 +17,9 @@ object BinaryIncompatibilities { ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.core.tools.linker.frontend.optimizer.IncOptimizer#CollOps.*"), ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.core.tools.linker.frontend.optimizer.ParIncOptimizer#CollOps.*"), + // private[emitter], not an issue + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.core.tools.linker.backend.emitter.JSGen.genIsInstanceOf"), + // private, not an issue ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.core.tools.linker.checker.InfoChecker.this"), ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.scalajs.core.tools.linker.checker.IRChecker#CheckedClass.this") diff --git a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InstanceTestsHijackedBoxedClassesTest.scala b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InstanceTestsHijackedBoxedClassesTest.scala index 8e8fff87c1..64fd6d2fa6 100644 --- a/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InstanceTestsHijackedBoxedClassesTest.scala +++ b/test-suite/js/src/test/scala/org/scalajs/testsuite/compiler/InstanceTestsHijackedBoxedClassesTest.scala @@ -74,6 +74,40 @@ class InstanceTestsHijackedBoxedClassesTest { assertEquals("ok", test(0.2)) } + @Test def isInstanceOfJavaLangNumber(): Unit = { + @noinline def testNoinline(expected: Boolean, value: Any): Unit = { + assertEquals(expected, value.isInstanceOf[java.lang.Number]) + assertEquals(expected, classOf[java.lang.Number].isInstance(value)) + } + + @inline def test(expected: Boolean, value: Any): Unit = { + testNoinline(expected, value) + assertEquals(expected, value.isInstanceOf[java.lang.Number]) + assertEquals(expected, classOf[java.lang.Number].isInstance(value)) + } + + test(false, true) + test(false, 'A') + test(false, ()) + test(false, "hello") + + test(true, 1.toByte) + test(true, 0x100.toShort) + test(true, 0x10000) + test(true, 0x100000000L) + test(true, 1.5f) + test(true, 1.2) + + class CustomNumber extends java.lang.Number { + def intValue(): Int = 0 + def longValue(): Long = 0 + def floatValue(): Float = 0 + def doubleValue(): Double = 0 + } + + test(true, new CustomNumber) + } + @Test def should_support_asInstanceOf_positive(): Unit = { def swallow(x: Any): Unit = () swallow(((): Any).asInstanceOf[Unit]) diff --git a/tools/scalajsenv.js b/tools/scalajsenv.js index 86f2280f59..51814aecf5 100644 --- a/tools/scalajsenv.js +++ b/tools/scalajsenv.js @@ -300,7 +300,7 @@ ScalaJS.objectGetClass = function(instance) { default: if (instance === null) return instance.getClass__jl_Class(); - else if (ScalaJS.is.sjsr_RuntimeLong(instance)) + else if (instance instanceof ScalaJS.c.sjsr_RuntimeLong) return ScalaJS.d.jl_Long.getClassOf(); else if (ScalaJS.isScalaJSObject(instance)) return instance.$classData.getClassOf(); diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/ClassEmitter.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/ClassEmitter.scala index af10a16307..967ed1d290 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/ClassEmitter.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/ClassEmitter.scala @@ -51,6 +51,8 @@ private[emitter] final class ClassEmitter(jsGen: JSGen) { if (kind.isAnyScalaJSDefinedClass && tree.hasInstances) reverseParts ::= genClassForRhino(tree) if (needInstanceTests(tree)) { + if (tree.kind.isClass && !tree.hasInstances) + reverseParts ::= genFakeClass(tree) reverseParts ::= genInstanceTests(tree) reverseParts ::= genArrayInstanceTests(tree) } @@ -558,6 +560,29 @@ private[emitter] final class ClassEmitter(jsGen: JSGen) { } } + def genFakeClass(tree: LinkedClass): js.Tree = { + assert(tree.kind.isClass) + + implicit val pos = tree.pos + + val className = tree.encodedName + + outputMode match { + case OutputMode.ECMAScript51Global | OutputMode.ECMAScript51Isolated => + js.Block( + js.DocComment("@constructor"), + envFieldDef("c", className, + js.Function(Nil, js.Skip()), + keepFunctionExpression = false) + ) + + case OutputMode.ECMAScript6 => + js.ClassDef( + Some(encodeClassVar(className).asInstanceOf[js.VarRef].ident), + None, Nil) + } + } + def genInstanceTests(tree: LinkedClass): js.Tree = { import Definitions._ import TreeDSL._ @@ -581,40 +606,58 @@ private[emitter] final class ClassEmitter(jsGen: JSGen) { val objParam = js.ParamDef(Ident("obj"), rest = false) val obj = objParam.ref - val createIsStat = { - envFieldDef("is", className, - js.Function(List(objParam), js.Return(className match { - case Definitions.ObjectClass => - js.BinaryOp(JSBinaryOp.!==, obj, js.Null()) + val isExpression = { + className match { + case Definitions.ObjectClass => + js.BinaryOp(JSBinaryOp.!==, obj, js.Null()) - case Definitions.StringClass => - js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral("string") + case Definitions.StringClass => + js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral("string") - case Definitions.RuntimeNothingClass => - // Even null is not an instance of Nothing - js.BooleanLiteral(false) + case Definitions.RuntimeNothingClass => + // Even null is not an instance of Nothing + js.BooleanLiteral(false) - case _ => - var test = { - genIsScalaJSObject(obj) && - genIsClassNameInAncestors(className, - obj DOT "$classData" DOT "ancestors") - } + case _ => + var test = if (tree.kind.isClass) { + obj instanceof encodeClassVar(className) + } else { + !(!( + genIsScalaJSObject(obj) && + genIsClassNameInAncestors(className, + obj DOT "$classData" DOT "ancestors") + )) + } + + if (isAncestorOfString) + test = test || ( + js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral("string")) + if (isAncestorOfHijackedNumberClass) + test = test || ( + js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral("number")) + if (isAncestorOfBoxedBooleanClass) + test = test || ( + js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral("boolean")) + if (isAncestorOfBoxedUnitClass) + test = test || (obj === js.Undefined()) + + test + } + } - if (isAncestorOfString) - test = test || ( - js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral("string")) - if (isAncestorOfHijackedNumberClass) - test = test || ( - js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral("number")) - if (isAncestorOfBoxedBooleanClass) - test = test || ( - js.UnaryOp(JSUnaryOp.typeof, obj) === js.StringLiteral("boolean")) - if (isAncestorOfBoxedUnitClass) - test = test || (obj === js.Undefined()) - - !(!test) - }))) + val needIsFunction = isExpression match { + case js.BinaryOp(JSBinaryOp.instanceof, _, _) => + // This is a simple `instanceof`. It will always be inlined at call site. + false + case _ => + true + } + + val createIsStat = if (needIsFunction) { + envFieldDef("is", className, + js.Function(List(objParam), js.Return(isExpression))) + } else { + js.Skip() } val createAsStat = if (semantics.asInstanceOfs == Unchecked) { @@ -634,8 +677,11 @@ private[emitter] final class ClassEmitter(jsGen: JSGen) { // Always throw for .asInstanceOf[Nothing], even for null throwError } else { - js.If(js.Apply(envField("is", className), List(obj)) || - (obj === js.Null()), { + val isCond = + if (needIsFunction) js.Apply(envField("is", className), List(obj)) + else isExpression + + js.If(isCond || (obj === js.Null()), { obj }, { throwError diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/Emitter.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/Emitter.scala index a48450fef2..fb03fc5210 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/Emitter.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/Emitter.scala @@ -341,6 +341,14 @@ final class Emitter private (semantics: Semantics, outputMode: OutputMode, } if (classEmitter.needInstanceTests(linkedClass)) { + if (!linkedClass.hasInstances && kind.isClass) { + /* The isInstanceOf implementation will generate + * `x instanceof $c_TheClass`, but `$c_TheClass` won't be declared at + * all. Define it as a fake class to avoid `ReferenceError`s. + */ + addTree(classEmitter.genFakeClass(linkedClass)) + } + addTree(classTreeCache.instanceTests.getOrElseUpdate(js.Block( classEmitter.genInstanceTests(linkedClass), classEmitter.genArrayInstanceTests(linkedClass) diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/EmitterDefinitions.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/EmitterDefinitions.scala new file mode 100644 index 0000000000..427187d633 --- /dev/null +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/EmitterDefinitions.scala @@ -0,0 +1,28 @@ +/* + * 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.core.tools.linker.backend.emitter + +import org.scalajs.core.ir.Definitions._ + +private[emitter] object EmitterDefinitions { + /* In theory, some of the following could be computed from the Class + * Hierarchy. However, that would be require dealing with incremental runs, + * which would be overkill since these things are in fact known to be static. + */ + + final val NumberClass = "jl_Number" + + val HijackedClassesAndTheirSuperClasses = + HijackedClasses ++ Set(ObjectClass, NumberClass) + +} diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/JSGen.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/JSGen.scala index 3986f50d36..fff84f0759 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/JSGen.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/JSGen.scala @@ -16,6 +16,7 @@ import scala.language.implicitConversions import org.scalajs.core.ir import ir._ +import ir.Definitions._ import ir.Types._ import ir.{Trees => irt} @@ -23,6 +24,8 @@ import org.scalajs.core.tools.sem._ import org.scalajs.core.tools.linker.backend.{ModuleKind, OutputMode} import org.scalajs.core.tools.javascript.Trees._ +import EmitterDefinitions._ + /** Collection of tree generators that are used accross the board. * This class is fully stateless. * @@ -86,8 +89,23 @@ private[emitter] final class JSGen(val semantics: Semantics, } def genIsInstanceOf(expr: Tree, cls: ReferenceType)( - implicit pos: Position): Tree = - genIsAsInstanceOf(expr, cls, test = true) + implicit globalKnowledge: GlobalKnowledge, pos: Position): Tree = { + import TreeDSL._ + + cls match { + case ClassType(className) => + if (!HijackedClassesAndTheirSuperClasses.contains(className) && + !globalKnowledge.isInterface(className)) { + expr instanceof encodeClassVar(className) + } else if (className == BoxedLongClass) { + expr instanceof encodeClassVar(LongImpl.RuntimeLongClass) + } else { + genIsAsInstanceOf(expr, cls, test = true) + } + case ArrayType(_, _) => + genIsAsInstanceOf(expr, cls, test = true) + } + } def genAsInstanceOf(expr: Tree, cls: ReferenceType)( implicit pos: Position): Tree = @@ -95,7 +113,6 @@ private[emitter] final class JSGen(val semantics: Semantics, private def genIsAsInstanceOf(expr: Tree, cls: ReferenceType, test: Boolean)( implicit pos: Position): Tree = { - import Definitions._ import TreeDSL._ cls match { diff --git a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/TreeDSL.scala b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/TreeDSL.scala index ef4608c640..a1781f6be8 100644 --- a/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/TreeDSL.scala +++ b/tools/shared/src/main/scala/org/scalajs/core/tools/linker/backend/emitter/TreeDSL.scala @@ -46,6 +46,9 @@ private[emitter] object TreeDSL { def ||(that: Tree)(implicit pos: Position): Tree = BinaryOp(ir.Trees.JSBinaryOp.||, self, that) + def instanceof(that: Tree)(implicit pos: Position): Tree = + BinaryOp(ir.Trees.JSBinaryOp.instanceof, self, that) + // Other constructs def :=(that: Tree)(implicit pos: Position): Tree = From 67d32b0056b608f95b1ff31099ed1297a3f8e618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 1 Aug 2019 17:08:48 +0200 Subject: [PATCH 0057/1606] Implement `java.util.PriorityQueue` from scratch. Previously, it was implemented on top of `s.c.m.PriorityQueue`. Unlike other collections implemented on top of Scala collections, `PriorityQueue` had no serious hacks. It did have overhead in terms of allocations of `Box`es, though. The new implementation, done from scratch, does not box elements, and offers direct support for all the methods of the JDK API. The test suite of `PriorityQueue` is enhanced in the process. Notably, it includes all the standard tests for `Collection`s in `CollectionTest`. --- .../scala/java/util/NaturalComparator.scala | 47 +++ .../main/scala/java/util/PriorityQueue.scala | 289 +++++++++++++----- .../javalib/util/PriorityQueueTest.scala | 242 ++++++++++++++- 3 files changed, 509 insertions(+), 69 deletions(-) create mode 100644 javalib/src/main/scala/java/util/NaturalComparator.scala diff --git a/javalib/src/main/scala/java/util/NaturalComparator.scala b/javalib/src/main/scala/java/util/NaturalComparator.scala new file mode 100644 index 0000000000..b0b72d3dc1 --- /dev/null +++ b/javalib/src/main/scala/java/util/NaturalComparator.scala @@ -0,0 +1,47 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util + +/** A universal `Comparator` using the *natural ordering* of the elements. + * + * A number of JDK APIs accept a possibly-null `Comparator`, and use the + * *natural ordering* of elements when it is `null`. This universal comparator + * can be used internally instead of systematically testing for `null`, + * simplifying code paths. + * + * The `compare()` method of this comparator throws `ClassCastException`s when + * used with values that cannot be compared using natural ordering, assuming + * Scala.js is configured with compliant `asInstanceOf`s. The behavior is + * otherwise undefined. + */ +private[util] object NaturalComparator extends Comparator[Any] { + def compare(o1: Any, o2: Any): Int = + o1.asInstanceOf[Comparable[Any]].compareTo(o2) + + /** Selects the given comparator if it is non-null, otherwise the natural + * comparator. + */ + def select[A](comparator: Comparator[A]): Comparator[_ >: A] = + if (comparator eq null) this + else comparator + + /** Unselects the given comparator, returning `null` if it was the natural + * comparator. + * + * This method is useful to re-expose to a public API an internal comparator + * that was obtained with `select()`. + */ + def unselect[A](comparator: Comparator[A]): Comparator[A] = + if (comparator eq this) null + else comparator +} diff --git a/javalib/src/main/scala/java/util/PriorityQueue.scala b/javalib/src/main/scala/java/util/PriorityQueue.scala index 8f0da14186..9de0a50ccc 100644 --- a/javalib/src/main/scala/java/util/PriorityQueue.scala +++ b/javalib/src/main/scala/java/util/PriorityQueue.scala @@ -12,136 +12,289 @@ package java.util -import java.lang.Comparable -import scala.math.Ordering -import scala.collection.mutable import scala.annotation.tailrec -import scala.language.existentials -class PriorityQueue[E] protected (ordering: Ordering[_ >: E], _comparator: Comparator[_ >: E]) - extends AbstractQueue[E] with Serializable { self => +import scala.scalajs.js + +class PriorityQueue[E] private ( + private val comp: Comparator[_ >: E], internal: Boolean) + extends AbstractQueue[E] with Serializable { + + def this() = + this(NaturalComparator, internal = true) def this(initialCapacity: Int) = { - this(defaultOrdering[E], null.asInstanceOf[Comparator[_ >: E]]) + this() if (initialCapacity < 1) throw new IllegalArgumentException() } - def this() = - this(11) + def this(comparator: Comparator[_ >: E]) = { + this(NaturalComparator.select(comparator), internal = true) + } def this(initialCapacity: Int, comparator: Comparator[_ >: E]) = { - this(PriorityQueue.safeGetOrdering[E](comparator), null.asInstanceOf[Comparator[E]]) + this(comparator) if (initialCapacity < 1) throw new IllegalArgumentException() } def this(c: Collection[_ <: E]) = { - this(defaultOrdering[E], null.asInstanceOf[Comparator[E]]) + this(c match { + case c: PriorityQueue[_] => + c.comp.asInstanceOf[Comparator[_ >: E]] + case c: SortedSet[_] => + NaturalComparator.select(c.comparator().asInstanceOf[Comparator[_ >: E]]) + case _ => + NaturalComparator + }, internal = true) addAll(c) } def this(c: PriorityQueue[_ <: E]) = { - this(PriorityQueue.safeGetOrdering[E](c.comparator()), - c.comparator().asInstanceOf[Comparator[E]]) + this(c.comp.asInstanceOf[Comparator[_ >: E]], internal = true) addAll(c) } def this(sortedSet: SortedSet[_ <: E]) = { - this(PriorityQueue.safeGetOrdering[E](sortedSet.comparator()), - sortedSet.comparator().asInstanceOf[Comparator[E]]) + this(NaturalComparator.select( + sortedSet.comparator().asInstanceOf[Comparator[_ >: E]]), + internal = true) addAll(sortedSet) } - private implicit object BoxOrdering extends Ordering[Box[E]] { - def compare(a: Box[E], b:Box[E]): Int = ordering.compare(b.inner, a.inner) - } - - private val inner: mutable.PriorityQueue[Box[E]] = new mutable.PriorityQueue[Box[E]]() + // The index 0 is not used; the root is at index 1. + // This is standard practice in binary heaps, to simplify arithmetics. + private[this] val inner = js.Array[E](null.asInstanceOf[E]) override def add(e: E): Boolean = { - if (e == null) throw new NullPointerException() - else { - inner += Box(e) - true - } + if (e == null) + throw new NullPointerException() + inner.push(e) + fixUp(inner.length - 1) + true } def offer(e: E): Boolean = add(e) def peek(): E = - inner.headOption.fold(null.asInstanceOf[E])(_.inner) + if (inner.length > 1) inner(1) + else null.asInstanceOf[E] override def remove(o: Any): Boolean = { - val boxed = Box(o.asInstanceOf[E]) - val initialSize = inner.size - - @tailrec - def takeLeft(part: mutable.PriorityQueue[Box[E]]): mutable.PriorityQueue[Box[E]] = { - if (inner.isEmpty) part - else { - val next = inner.dequeue - if (boxed == next) part - else if (BoxOrdering.compare(boxed, next) > 0) - part += next - else takeLeft(part += next) + if (o == null) { + false + } else { + val len = inner.length + var i = 1 + while (i != len && !o.equals(inner(i))) { + i += 1 + } + + if (i != len) { + removeAt(i) + true + } else { + false } } + } - val left = takeLeft(new mutable.PriorityQueue[Box[E]]()) - inner ++= left + private def removeExact(o: Any): Unit = { + val len = inner.length + var i = 1 + + /* This is tricky. We must use reference equality to find the exact object + * to remove, but if `o` is a positive or negative 0.0 or NaN, we must use + * `equals` to delete the correct element (i.e., not confuse +0.0 and -0.0, + * and considering `NaN` equal to itself). + */ + o match { + case o: Double if o == 0.0 || java.lang.Double.isNaN(o) => + while (i != len && !o.equals(inner(i))) { + i += 1 + } + case _ => + while (i != len && (o.asInstanceOf[AnyRef] ne inner(i).asInstanceOf[AnyRef])) { + i += 1 + } + } - (inner.size != initialSize) + if (i == len) + throw new ConcurrentModificationException() + removeAt(i) } - override def contains(o: Any): Boolean = - inner.exists(box => Objects.equals(o, box.inner)) + private def removeAt(i: Int): Unit = { + val newLength = inner.length - 1 + if (i == newLength) { + inner.length = newLength + } else { + inner(i) = inner(newLength) + inner.length = newLength + fixUpOrDown(i) + } + } + + override def contains(o: Any): Boolean = { + if (o == null) { + false + } else { + val len = inner.length + var i = 1 + while (i != len && !o.equals(inner(i))) { + i += 1 + } + i != len + } + } def iterator(): Iterator[E] = { new Iterator[E] { - private val iter = inner.clone.iterator - - private var last: Option[E] = None + private[this] var inner: js.Array[E] = PriorityQueue.this.inner + private[this] var nextIdx: Int = 1 + private[this] var last: E = _ // null - def hasNext(): Boolean = iter.hasNext + def hasNext(): Boolean = nextIdx < inner.length def next(): E = { - last = Some(iter.next().inner) - last.get + if (!hasNext()) + throw new NoSuchElementException("empty iterator") + last = inner(nextIdx) + nextIdx += 1 + last } def remove(): Unit = { - if (last.isEmpty) { + /* Once we start removing elements, the inner array of the enclosing + * PriorityQueue will be modified in arbitrary ways. In particular, + * entries yet to be iterated can be moved before `nextIdx` if the + * removal requires a `fixUp()`. + * + * Therefore, at the first removal, we take a snapshot of the remainder + * of the inner array yet to be iterated, and continue iterating over + * the snapshot. + * + * We use a linear lookup based on reference equality to precisely + * remove the entries that we are still iterating over (in + * `removeExact()`). + * + * This means that this method is O(n), contrary to typical + * expectations for `Iterator.remove()`. I could not come up with a + * better algorithm. + */ + + if (last == null) throw new IllegalStateException() - } else { - last.foreach(self.remove(_)) - last = None + if (inner eq PriorityQueue.this.inner) { + inner = inner.jsSlice(nextIdx) + nextIdx = 0 } + removeExact(last) + last = null.asInstanceOf[E] } } } - def size(): Int = inner.size + def size(): Int = inner.length - 1 override def clear(): Unit = - inner.dequeueAll + inner.length = 1 + + def poll(): E = { + val inner = this.inner // local copy + if (inner.length > 1) { + val newSize = inner.length - 1 + val result = inner(1) + inner(1) = inner(newSize) + inner.length = newSize + fixDown(1) + result + } else { + null.asInstanceOf[E] + } + } - def poll(): E = - if (inner.isEmpty) null.asInstanceOf[E] - else inner.dequeue().inner + def comparator(): Comparator[_ >: E] = + NaturalComparator.unselect(comp) + + // Heavy lifting: heap fixup + + /** Fixes the heap property around the child at index `m`, either up the + * tree or down the tree, depending on which side is found to violate the + * heap property. + */ + private[this] def fixUpOrDown(m: Int): Unit = { + val inner = this.inner // local copy + if (m > 1 && comp.compare(inner(m >> 1), inner(m)) > 0) + fixUp(m) + else + fixDown(m) + } - def comparator(): Comparator[_ >: E] = _comparator -} + /** Fixes the heap property from the child at index `m` up the tree, towards + * the root. + */ + private[this] def fixUp(m: Int): Unit = { + val inner = this.inner // local copy + + /* At each step, even though `m` changes, the element moves with it, and + * hence inner(m) is always the same initial `innerAtM`. + */ + val innerAtM = inner(m) + + @inline @tailrec + def loop(m: Int): Unit = { + if (m > 1) { + val parent = m >> 1 + val innerAtParent = inner(parent) + if (comp.compare(innerAtParent, innerAtM) > 0) { + inner(parent) = innerAtM + inner(m) = innerAtParent + loop(parent) + } + } + } -object PriorityQueue { + loop(m) + } - def safeGetOrdering[E](_comp: => Comparator[_]): Ordering[E] = { - val comp: Comparator[_] = _comp - val ord = - if (comp == null) defaultOrdering[E] - else Ordering.comparatorToOrdering(comp) + /** Fixes the heap property from the child at index `m` down the tree, + * towards the leaves. + */ + private[this] def fixDown(m: Int): Unit = { + val inner = this.inner // local copy + val size = inner.length - 1 + + /* At each step, even though `m` changes, the element moves with it, and + * hence inner(m) is always the same initial `innerAtM`. + */ + val innerAtM = inner(m) + + @inline @tailrec + def loop(m: Int): Unit = { + var j = 2 * m // left child of `m` + if (j <= size) { + var innerAtJ = inner(j) + + // if the left child is greater than the right child, switch to the right child + if (j < size) { + val innerAtJPlus1 = inner(j + 1) + if (comp.compare(innerAtJ, innerAtJPlus1) > 0) { + j += 1 + innerAtJ = innerAtJPlus1 + } + } - ord.asInstanceOf[Ordering[E]] - } + // if the node `m` is greater than the selected child, swap and recurse + if (comp.compare(innerAtM, innerAtJ) > 0) { + inner(m) = innerAtJ + inner(j) = innerAtM + loop(j) + } + } + } + loop(m) + } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PriorityQueueTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PriorityQueueTest.scala index 5577e46d56..281cd095ad 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PriorityQueueTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PriorityQueueTest.scala @@ -15,14 +15,19 @@ package org.scalajs.testsuite.javalib.util import scala.language.implicitConversions import scala.collection.JavaConverters._ +import scala.reflect.ClassTag import org.junit.Test import org.junit.Assert._ +import org.junit.Assume._ import java.util.PriorityQueue import java.util.Comparator -class PriorityQueueTest { +import org.scalajs.testsuite.utils.Platform.executingInJVM + +class PriorityQueueTest extends CollectionTest { + def factory: PriorityQueueFactory = new PriorityQueueFactory @Test def should_store_and_remove_ordered_integers(): Unit = { val pq = new PriorityQueue[Int]() @@ -236,4 +241,239 @@ class PriorityQueueTest { assertEquals(-0.987, pqDouble.poll(), 0.0) } + @Test def poll(): Unit = { + val pq = newPriorityQueueWith0Until100() + + var nextExpected = 0 + while (!pq.isEmpty()) { + assertEquals(nextExpected, pq.poll()) + nextExpected += 1 + } + assertEquals(100, nextExpected) + } + + @Test def removingAnArbitraryElementPreservesPriorities(): Unit = { + for (itemToRemove <- 0 until 100) { + val pq = newPriorityQueueWith0Until100() + + assertTrue(pq.remove(itemToRemove)) + + var nextExpected = 0 + while (!pq.isEmpty()) { + if (nextExpected == itemToRemove) + nextExpected += 1 + assertEquals(s"after removing $itemToRemove", nextExpected, pq.poll()) + nextExpected += 1 + } + + if (itemToRemove == 99) + assertEquals(99, nextExpected) + else + assertEquals(100, nextExpected) + } + } + + @Test def removingAnArbitraryElementWithAnIteratorPreservesPriorities(): Unit = { + for (itemToRemove <- 0 until 100) { + val pq = newPriorityQueueWith0Until100() + + val iter = pq.iterator() + while (iter.next() != itemToRemove) {} + iter.remove() + + var nextExpected = 0 + while (!pq.isEmpty()) { + if (nextExpected == itemToRemove) + nextExpected += 1 + assertEquals(s"after removing $itemToRemove", nextExpected, pq.poll()) + nextExpected += 1 + } + + if (itemToRemove == 99) + assertEquals(99, nextExpected) + else + assertEquals(100, nextExpected) + } + } + + // --- Begin Whitebox Tests --- + + /* The following tests are meant to test the behavior of `Iterator.remove()` + * and subsequent iteration in very specific scenarios that are corner cases + * of the current implementation. + * + * We could write the same tests as blackbox, but then we would not be able + * to verify that they are indeed testing the scenarios they are supposed to + * test. + * + * Therefore, these tests are whitebox, and rely on details of our own + * implementation. They verify that elements are iterated in a very specific + * order, and that the internal representation of the heap evolves in the way + * we expect. + * + * Since the following tests are whitebox, they may have to be changed if the + * internal data structure and/or algorithms of PriorityQueue are modified in + * the future. + */ + + @Test def removingAnArbitraryElementWithAnIteratorPreservesPrioritiesCornerCase(): Unit = { + /* This tests the specific scenario where `Iterator.remove()` causes the + * array to be reordered in such a way that a) elements yet to be iterated + * are moved before the iteration cursor, and b) elements already iteratoed + * are moved after the cursor. + * + * Due to the nature of a binary heap, triggering this scenario is quite + * difficult, and does not easily happen by chance. The arrangement of + * nodes has to be engineered in a specific way for the test to be + * meaningful. + */ + + assumeFalse("whitebox test of our own implementation", executingInJVM) + + val pq = new java.util.PriorityQueue[Int]() + for (x <- List(1, 2, 30, 4, 3, 40, 35, 10)) + pq.add(x) + + assertEquals("[1,2,30,4,3,40,35,10]", pq.toString()) + + val iter = pq.iterator() + assertEquals(1, iter.next()) + assertEquals(2, iter.next()) + assertEquals(30, iter.next()) + assertEquals(4, iter.next()) + assertEquals(3, iter.next()) + assertEquals(40, iter.next()) + + iter.remove() + assertEquals("[1,2,10,4,3,30,35]", pq.toString()) + + assertEquals(35, iter.next()) + assertEquals(10, iter.next()) + + iter.remove() + assertEquals("[1,2,30,4,3,35]", pq.toString()) + + assertFalse(iter.hasNext()) + } + + @Test def removingAnArbitraryElementWithAnIteratorDoubleCornerCase(): Unit = { + /* This tests that when `Iterator.remove()` is supposed to remove a zero, + * it does not accidentally remove a zero of the opposite sign. + * + * To be meaningful, this tests requires that the zero we are trying to + * remove be positioned further in the internal array than the other one. + */ + + assumeFalse("whitebox test of our own implementation", executingInJVM) + + val pq = new java.util.PriorityQueue[Double]() + for (x <- List(-1.0, -0.0, 0.0, 3.5, Double.NaN)) + pq.add(x) + + assertEquals("[-1,0,0,3.5,NaN]", pq.toString()) + + val iter = pq.iterator() + assertEquals(-1.0: Any, iter.next()) + assertEquals(-0.0: Any, iter.next()) + assertEquals(0.0: Any, iter.next()) + + iter.remove() + assertEquals("+0.0 must have been removed, not -0.0", + "[-1,0,NaN,3.5]", pq.toString()) + assertTrue("+0.0 must have been removed, not -0.0", + pq.contains(-0.0) && !pq.contains(0.0)) + + assertEquals(3.5: Any, iter.next()) + assertEquals(Double.NaN: Any, iter.next()) + + iter.remove() + assertEquals("NaN must have been removed", "[-1,0,3.5]", pq.toString()) + + assertFalse(iter.hasNext()) + } + + @Test def removingAnArbitraryElementWithAnIteratorReferenceCornerCase(): Unit = { + /* This tests that when `Iterator.remove()` is supposed to remove an + * object, it does not accidentally remove an other object that happens to + * be `equals` to it (but with a different identity). + * + * To be meaningful, this tests requires that the object we are trying to + * remove be positioned further in the internal array than the other one. + */ + + assumeFalse("whitebox test of our own implementation", executingInJVM) + + final case class TestObj(i: Int)(val id: Int) extends Comparable[TestObj] { + def compareTo(o: TestObj): Int = Integer.compare(this.i, o.i) + + override def toString(): String = s"TestObj@$id" + } + + val first = TestObj(1)(10) + val second = TestObj(2)(20) + val third = TestObj(2)(21) + val fourth = TestObj(3)(30) + val fifth = TestObj(4)(40) + + val pq = new java.util.PriorityQueue[TestObj]() + for (x <- List(first, second, third, fourth, fifth)) + pq.add(x) + + assertEquals("[TestObj@10,TestObj@20,TestObj@21,TestObj@30,TestObj@40]", + pq.toString()) + + val iter = pq.iterator() + assertSame(first, iter.next()) + assertSame(second, iter.next()) + assertSame(third, iter.next()) + + iter.remove() + assertEquals("third must have been removed, not second", + "[TestObj@10,TestObj@20,TestObj@40,TestObj@30]", pq.toString()) + + assertSame(fourth, iter.next()) + assertSame(fifth, iter.next()) + + iter.remove() + assertEquals("[TestObj@10,TestObj@20,TestObj@30]", pq.toString()) + + assertFalse(iter.hasNext()) + } + + // --- End Whitebox Tests --- + + /** Built with `scala.util.Random.shuffle((0 until 100).toList)`. */ + private val listOfShuffled0Until100 = List( + 89, 26, 23, 9, 96, 81, 34, 79, 37, 90, 45, 66, 16, 49, 70, 77, 5, 19, 39, + 98, 44, 15, 1, 6, 43, 27, 40, 3, 68, 91, 76, 20, 54, 87, 85, 12, 86, 31, + 67, 24, 95, 0, 38, 22, 97, 28, 59, 2, 94, 7, 51, 30, 72, 56, 18, 13, 14, + 75, 53, 64, 47, 46, 58, 93, 74, 32, 57, 83, 60, 73, 11, 88, 69, 65, 33, + 52, 29, 80, 50, 63, 10, 62, 48, 55, 41, 35, 21, 42, 61, 36, 99, 78, 82, + 8, 4, 71, 25, 84, 92, 17 + ) + + /** Creates a new priority queue in which the integers 0 until 100 have been + * added in a random order. + * + * The random order is important to avoid creating a degenerate heap where + * the array is in strict increasing order. Such degenerate heaps tend to + * pass tests too easily, even with broken implementations. + */ + private def newPriorityQueueWith0Until100(): PriorityQueue[Int] = { + val pq = new PriorityQueue[Int]() + for (i <- listOfShuffled0Until100) + pq.add(i) + pq + } + +} + +class PriorityQueueFactory extends CollectionFactory { + + override def implementationName: String = + "java.util.PriorityQueue" + + override def empty[E: ClassTag]: PriorityQueue[E] = + new PriorityQueue() + } From eaa8d543c221ec556a82565d9946c01f8f7ffbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 2 Sep 2019 13:44:33 +0200 Subject: [PATCH 0058/1606] [no-master] Disable slow scalaTestSuite tests when running in Rhino. Because under Rhino, they are *too* slow. --- project/Build.scala | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/project/Build.scala b/project/Build.scala index 5395da4aa7..c64e6e5ad5 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -2189,6 +2189,39 @@ object Build { scalaScalaJUnitSources.collect { case fTup if whitelist(fTup._1) => fTup._2 } + }, + + /* Disable slow tests when running with Rhino, because they become *too* + * slow with Rhino. + * + * The following tests are targeted: + * + * - immutable.RangeConsistencyTest.rangeChurnTest + * - immutable.SetTest.t7326 + * - immutable.TreeSeqMapTest.testAppend + * - mutable.ArrayDequeTest.apply + * - mutable.ArrayDequeTest.sliding + * - mutable.QueueTest.sliding + * - mutable.StackTest.sliding + * + * Unfortunately we cannot disable test by test, so we have to disable + * the whole class instead. + */ + testOptions in Test := { + val prev = (testOptions in Test).value + val isRhino = (resolvedJSEnv in Test).value.isInstanceOf[RhinoJSEnv] + if (isRhino) { + prev :+ Tests.Filter { name => + !name.endsWith(".immutable.RangeConsistencyTest") && + !name.endsWith(".immutable.SetTest") && + !name.endsWith(".immutable.TreeSeqMapTest") && + !name.endsWith(".mutable.ArrayDequeTest") && + !name.endsWith(".mutable.QueueTest") && + !name.endsWith(".mutable.StackTest") + } + } else { + prev + } } ) ).withScalaJSCompiler.withScalaJSJUnitPlugin.dependsOn(jUnitRuntime, testBridge % "test") From 649bf36aea6c0edf97098b9a62cf792a30062891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 30 Aug 2019 11:22:55 +0200 Subject: [PATCH 0059/1606] Upgrade the sbt plugin to sbt 1.2.1. And test the sbt-plugin-test with sbt 1.2.8, which is the latest stable release of sbt as of this writing. The upstream fix https://github.com/sbt/zinc/pull/562, which made its way into sbt 1.2.1, is necessary for incremental compilation to even have a chance to work, as explained in #3419. However, it is not enough, so an additional fix is still needed in our sbt plugin. The upgrade is not technically necessary for our build to succeed, or even for the upcoming fix to build, but projects using Scala.js will not benefit from the fix unless they upgrade to sbt 1.2.1+. Depending on it in our build at least records the required version in our published artifact. --- project/Build.scala | 2 +- sbt-plugin-test/project/build.properties | 2 +- scripts/publish.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index c64e6e5ad5..40419ca224 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -854,7 +854,7 @@ object Build { sbtVersion in pluginCrossBuild := { scalaVersion.value match { case v if v.startsWith("2.10.") => "0.13.17" - case _ => "1.0.0" + case _ => "1.2.1" } }, scalaBinaryVersion := diff --git a/sbt-plugin-test/project/build.properties b/sbt-plugin-test/project/build.properties index 94005e587c..c0bab04941 100644 --- a/sbt-plugin-test/project/build.properties +++ b/sbt-plugin-test/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.0 +sbt.version=1.2.8 diff --git a/scripts/publish.sh b/scripts/publish.sh index bcff059989..2b075bea83 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -12,7 +12,7 @@ BIN_VERSIONS="2.10.7 2.11.12 2.12.8 2.13.0" CLI_VERSIONS="2.10.7 2.11.12 2.12.8 2.13.0" SBT_VERSION="2.10.7" SBT1_VERSION="2.12.8" -SBT1_SBTVERSION="1.0.0" +SBT1_SBTVERSION="1.2.1" COMPILER="compiler jUnitPlugin" LIBS="library javalibEx ir irJS tools toolsJS jsEnvs jsEnvsTestKit testAdapter stubs testInterface testBridge jUnitRuntime" From 78682118e626ec729836ecffa19f55f6e07ed369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 30 Aug 2019 11:58:17 +0200 Subject: [PATCH 0060/1606] Fix #3419 and #3596: Backup and restore .sjsir files ourselves. Rather than relying on zinc to do it based on the arguments to `delete()`, which it hasn't done since sbt 1.x. --- .../org/scalajs/sbtplugin/SBTCompat.scala | 33 +---- .../scalajs/sbtplugin/SJSIRFileManager.scala | 117 ++++++++++++++++++ 2 files changed, 120 insertions(+), 30 deletions(-) create mode 100644 sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SJSIRFileManager.scala diff --git a/sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SBTCompat.scala b/sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SBTCompat.scala index fa0de90fa7..5d209f33eb 100644 --- a/sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SBTCompat.scala +++ b/sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SBTCompat.scala @@ -14,8 +14,6 @@ package org.scalajs.sbtplugin import sbt._ -import org.scalajs.core.tools.io.FileVirtualFile - private[sbtplugin] object SBTCompat { type IncOptions = xsbti.compile.IncOptions @@ -43,36 +41,11 @@ private[sbtplugin] object SBTCompat { } } - /** Patches the IncOptions so that .sjsir files are pruned as needed. - * - * This complicated logic patches the ClassfileManager factory of the given - * IncOptions with one that is aware of .sjsir files emitted by the Scala.js - * compiler. This makes sure that, when a .class file must be deleted, the - * corresponding .sjsir file are also deleted. + /** Patches the IncOptions so that .sjsir files are pruned, backed up and + * restored as needed. */ def scalaJSPatchIncOptions(incOptions: IncOptions): IncOptions = { - import xsbti.compile.{ClassFileManager, ClassFileManagerUtil} - - val sjsirFileManager = new ClassFileManager { - private[this] val inherited = - ClassFileManagerUtil.getDefaultClassFileManager(incOptions) - - def delete(classes: Array[File]): Unit = { - inherited.delete(classes.flatMap { classFile => - if (classFile.getPath.endsWith(".class")) { - val f = FileVirtualFile.withExtension(classFile, ".class", ".sjsir") - if (f.exists) List(f) - else Nil - } else { - Nil - } - }) - } - - def generated(classes: Array[File]): Unit = {} - def complete(success: Boolean): Unit = {} - } - + val sjsirFileManager = new SJSIRFileManager val newExternalHooks = incOptions.externalHooks.withExternalClassFileManager(sjsirFileManager) incOptions.withExternalHooks(newExternalHooks) diff --git a/sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SJSIRFileManager.scala b/sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SJSIRFileManager.scala new file mode 100644 index 0000000000..49117da118 --- /dev/null +++ b/sbt-plugin/src/main/scala-sbt-1.0/org/scalajs/sbtplugin/SJSIRFileManager.scala @@ -0,0 +1,117 @@ +/* + * 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.sbtplugin + +import java.io.File +import java.nio.file.Files + +import scala.collection.mutable + +import sbt._ + +import xsbti.compile.ClassFileManager + +/** A class file manager that prunes .sjsir files as needed. + * + * This makes sure that, when a .class file must be deleted, the + * corresponding .sjsir file is also deleted, as well as maintaining the + * transactional aspect of incremental compilation in the presence of + * compilation errors in an intermediate run (as product files need to be + * backed up and later restored). + * + * This code is adapted from Zinc `TransactionalClassFileManager`. + * We need to duplicate the logic since forwarding to the default class + * file manager doesn't work: we need to backup sjsir files in a different + * temporary directory as class files. + * + * We stole this from `TastyFileManager` in dotty, at + * https://github.com/lampepfl/dotty/blob/0.17.0-RC1/sbt-dotty/src/dotty/tools/sbtplugin/TastyFileManager.scala + * + * There are plans to upstream a generic version of this code in zinc in the + * future: https://github.com/sbt/zinc/issues/579 + */ +private[sbtplugin] final class SJSIRFileManager extends ClassFileManager { + private[this] var _tempDir: File = null + + private[this] def tempDir: File = { + if (_tempDir == null) + _tempDir = Files.createTempDirectory("backup").toFile + _tempDir + } + + private[this] val generatedSJSIRFiles = new mutable.HashSet[File] + private[this] val movedSJSIRFiles = new mutable.HashMap[File, File] + + /* The incremental compiler works along the following general protocol: + * 1. it identifies the set of files that have been changed/added/removed + * 2. it compiles that set of files + * a) it first `delete`s the products of changed and removed files + * b) then it compiles the files + * c) it registers produced files as `generated` + * 3. it detects whether a new round of compilation is necessary, and with + * which files; if necessary, it loops back to 2. + * 4. when no new round is necessary, it `complete`s with `success = true` if + * the whole cycle is successful, or `success = false` otherwise. + * + * Consequently, it is quite possible that, within one cycle, a given file is + * `generated` during one iteration, and `delete`d in a subsequent one. In + * this case, we would need to revert back to the file not being present in + * the final transactional output on `success = false`. This is why we have + * the `!generatedSJSIRFiles(t)` test in `delete()` below. + */ + + override def delete(classes: Array[File]): Unit = { + val files = sjsirFiles(classes) + val toBeBackedUp = files + .filter(t => !movedSJSIRFiles.contains(t) && !generatedSJSIRFiles(t)) + for (c <- toBeBackedUp) + movedSJSIRFiles.put(c, backupFile(c)) + IO.deleteFilesEmptyDirs(files) + } + + override def generated(classes: Array[File]): Unit = + generatedSJSIRFiles ++= sjsirFiles(classes) + + override def complete(success: Boolean): Unit = { + if (!success) { + IO.deleteFilesEmptyDirs(generatedSJSIRFiles) + for ((orig, tmp) <- movedSJSIRFiles) + IO.move(tmp, orig) + } + + generatedSJSIRFiles.clear() + movedSJSIRFiles.clear() + if (_tempDir != null) { + IO.delete(_tempDir) + _tempDir = null + } + } + + private def sjsirFiles(classes: Array[File]): Array[File] = { + classes.flatMap { classFile => + if (classFile.getPath.endsWith(".class")) { + val f = new File(classFile.getPath.stripSuffix(".class") + ".sjsir") + if (f.exists) List(f) + else Nil + } else { + Nil + } + } + } + + private def backupFile(c: File): File = { + val target = File.createTempFile("sbt", ".sjsir", tempDir) + IO.move(c, target) + target + } +} From f0a0228ebf0f50234411157777e30fba22ce4741 Mon Sep 17 00:00:00 2001 From: exoego Date: Thu, 6 Jun 2019 14:20:22 +0900 Subject: [PATCH 0061/1606] Various fixes in URI 1) IPv4 should not accept the digits greater than 255 2) Normalize host in URI.hashCode. This is necessary so that `foo.com` and `FoO.CoM` (which compare equal) produce the same hashCode. 3) Properly compare URIs. 1. Apply case insensitivity when comparing URI parts 2. Compare paths, query and fragments when comparing URI parts. This is necessary so that `foo.com/foo` and `FoO.CoM/foo` equals, but `foo.com/foo` and `FoO.CoM/FoO` does not. 3. Return 0 if both scheme is undefined --- javalib/src/main/scala/java/net/URI.scala | 128 +++++++++++------- .../testsuite/javalib/net/URITest.scala | 77 ++++++++--- 2 files changed, 138 insertions(+), 67 deletions(-) diff --git a/javalib/src/main/scala/java/net/URI.scala b/javalib/src/main/scala/java/net/URI.scala index bed3118a5d..7352ccdc92 100644 --- a/javalib/src/main/scala/java/net/URI.scala +++ b/javalib/src/main/scala/java/net/URI.scala @@ -54,7 +54,7 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { private val _authority = fld(AbsAuthority, RelAuthority).filter(_ != "") private val _userInfo = fld(AbsUserInfo, RelUserInfo) private val _host = fld(AbsHost, RelHost) - private val _port = fld(AbsPort, RelPort).fold(-1)(_.toInt) + private val _port = fld(AbsPort, RelPort).map(_.toInt) private val _path = { val useNetPath = fld(AbsAuthority, RelAuthority).isDefined @@ -101,41 +101,56 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { */ @inline private def internalCompare(that: URI)(cmp: (String, String) => Int): Int = { - @inline def cmpOpt(x: js.UndefOr[String], y: js.UndefOr[String]): Int = { + @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 => cmp(s1, s2))) + else x.fold(-1)(s1 => y.fold(1)(s2 => comparator(s1, s2))) + } + def comparePathQueryFragement(): Int = { + val cmpPath = cmpOpt(this._path, that._path)(cmp) + if (cmpPath != 0) { + cmpPath + } else { + val cmpQuery = cmpOpt(this._query, that._query)(cmp) + if (cmpQuery != 0) cmpQuery + else cmpOpt(this._fragment, that._fragment)(cmp) + } } - if (this._scheme != that._scheme) - this._scheme.fold(-1)(s1 => that._scheme.fold(1)(s1.compareToIgnoreCase)) - else if (this._isOpaque != that._isOpaque) - // A hierarchical URI is less than an opaque URI - if (this._isOpaque) 1 else -1 - else if (_isOpaque) { - val ssp = cmp(this._schemeSpecificPart, that._schemeSpecificPart) - if (ssp != 0) ssp - else cmpOpt(this._fragment, that._fragment) - } else if (this._authority != that._authority) { - if (this._host.isDefined && that._host.isDefined) { - val ui = cmpOpt(this._userInfo, that._userInfo) - if (ui != 0) ui - else { - val hst = this._host.get.compareToIgnoreCase(that._host.get) - if (hst != 0) hst - else if (this._port == that._port) 0 - else if (this._port == -1) -1 - else if (that._port == -1) 1 - else this._port - that._port + val cmpScheme = cmpOpt(this._scheme, that._scheme)(_.compareToIgnoreCase(_)) + if (cmpScheme != 0) { + cmpScheme + } else { + val cmpIsOpaque = this.isOpaque.compareTo(that.isOpaque) // A hierarchical URI is less than an opaque URI + if (cmpIsOpaque != 0) { + cmpIsOpaque + } else { + if (this.isOpaque()) { + 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) + if (cmpUserInfo != 0) { + cmpUserInfo + } else { + val cmpHost = cmpOpt(this._host, that._host)(_.compareToIgnoreCase(_)) + if (cmpHost != 0) { + cmpHost + } else { + val cmpPort = cmpOpt(this._port, that._port)(_ - _) + if (cmpPort != 0) cmpPort + else comparePathQueryFragement() + } + } + } else { + val cmpAuthority = cmpOpt(this._authority, that._authority)(cmp) + if (cmpAuthority != 0) cmpAuthority + else comparePathQueryFragement() } - } else - cmpOpt(this._authority, that._authority) - } else if (this._path != that._path) - cmpOpt(this._path, that._path) - else if (this._query != that._query) - cmpOpt(this._query, that._query) - else - cmpOpt(this._fragment, that._fragment) + } + } } def compareTo(that: URI): Int = internalCompare(that)(_.compareTo(_)) @@ -149,7 +164,7 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { def getFragment(): String = _fragment.map(decodeComponent).orNull def getHost(): String = _host.orNull def getPath(): String = _path.map(decodeComponent).orNull - def getPort(): Int = _port + def getPort(): Int = _port.getOrElse(-1) def getQuery(): String = _query.map(decodeComponent).orNull def getRawAuthority(): String = _authority.orNull def getRawFragment(): String = _fragment.orNull @@ -166,10 +181,19 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { import URI.normalizeEscapes var acc = URI.uriSeed - acc = mix(acc, _scheme.##) // scheme may not contain escapes - acc = mix(acc, normalizeEscapes(_schemeSpecificPart).##) - acc = mixLast(acc, _fragment.map(normalizeEscapes).##) - + acc = mix(acc, _scheme.map(_.toLowerCase).##) // 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.##) + } else { + acc = mix(acc, normalizeEscapes(this._authority).##) + } + acc = mix(acc, normalizeEscapes(this._path).##) + acc = mix(acc, normalizeEscapes(this._query).##) + acc = mixLast(acc, normalizeEscapes(this._fragment).##) finalizeHash(acc, 3) } @@ -334,7 +358,10 @@ object URI { } // IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit - private final val ipv4address = "[0-9]{1,3}(?:\\.[0-9]{1,3}){3}" + private final val ipv4address = { + val digit = "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + s"(?:$digit\\.){3}$digit" + } private final val ipv6address = { // http://stackoverflow.com/a/17871737/1149944 @@ -800,21 +827,22 @@ object URI { } /** Upper-cases all URI escape sequences in `str`. Used for hashing */ - private def normalizeEscapes(str: String): String = { - var i = 0 - var res = "" - while (i < str.length) { - if (str.charAt(i) == '%') { - assert(str.length > i + 2, "Invalid escape in URI") - res += str.substring(i, i+3).toUpperCase() - i += 3 - } else { - res += str.substring(i, i+1) - i += 1 + private def normalizeEscapes(maybeStr: js.UndefOr[String]): js.UndefOr[String] = { + maybeStr.map { str => + var i = 0 + var res = "" + while (i < str.length) { + if (str.charAt(i) == '%') { + assert(str.length > i + 2, "Invalid escape in URI") + res += str.substring(i, i+3).toUpperCase() + i += 3 + } else { + res += str.substring(i, i+1) + i += 1 + } } + res } - - res } private final val uriSeed = 53722356 diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala index cf69f5653c..a8a455f9c6 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/net/URITest.scala @@ -18,7 +18,6 @@ import org.junit.Assert._ import org.junit.Test import org.scalajs.testsuite.utils.AssertThrows._ -import org.scalajs.testsuite.utils.Platform.executingInJVM class URITest { @@ -166,6 +165,8 @@ class URITest { val y = new URI("http://example.com/asdf%6A") val z = new URI("http://example.com/asdfj") val rel = new URI("/foo/bar") + val rel2 = new URI("/foo/aaa") + val rel3 = new URI("/foo/ccc") assertTrue(x.compareTo(y) > 0) assertTrue(x.compareTo(z) < 0) @@ -177,6 +178,8 @@ class URITest { assertTrue(y.compareTo(rel) > 0) assertTrue(z.compareTo(rel) > 0) assertEquals(0, rel.compareTo(rel)) + assertTrue(rel.compareTo(rel2) > 0) + assertTrue(rel.compareTo(rel3) < 0) } @Test def should_provide_equals(): Unit = { @@ -190,8 +193,33 @@ class URITest { assertTrue(x == x) assertTrue(y == y) assertTrue(z == z) + } + + @Test def equals_and_hashCode_should_produces_same_result(): Unit = { + val equalsPairs: Seq[(URI, URI)] = Seq( + (new URI("http://example.com"), new URI("http://Example.CoM")), + (new URI("http://Example.Com@example.com"), new URI("http://Example.Com@Example.Com")), + (new URI("http://example.com/foo"), new URI("http://ExaMple.CoM/foo")), + (new URI("http://example.com/asdf%6a"), new URI("http://example.com/asdf%6A")), + (new URI("MAILTO:john"), new URI("mailto:john")) + ) + equalsPairs.foreach { case (a, b) => + assertEquals(a, b) + assertEquals(b, a) + assertEquals(a.hashCode(), b.hashCode()) + } - assertNotEquals(new URI("foo:helloWorld%6b%6C"), new URI("foo:helloWorld%6C%6b")) + val nonEqualPairs: Seq[(URI,URI)] = Seq( + (new URI("http://example.com/example-com"), new URI("http://Example.CoM/eXAMplE-cOm")), + (new URI("http://example.com@example.com"), new URI("http://EXAMPLE.COM@EXAMPLE.Com")), + (new URI("foo:helloWorld%6b%6C"), new URI("foo:helloWorld%6C%6b")) + ) + nonEqualPairs.foreach { case (a, b) => + assertNotEquals(a, b) + // Note: hashCode is not restricted to produce same result even if a.equals(b) is false. + assertNotEquals("a does not equal to b, but produces same hashCode. Pick different test data", + a.hashCode(), b.hashCode()) + } } @Test def should_provide_normalize(): Unit = { @@ -337,10 +365,12 @@ class URITest { } @Test def should_provide_hashCode(): Unit = { - if (!executingInJVM) { // Fails on JDK6 and JDK7 - assertEquals(new URI("http://example.com/asdf%6a").hashCode, - new URI("http://example.com/asdf%6A").hashCode) - } + assertEquals(new URI("http://example.com/asdf%6a").hashCode, + new URI("http://example.com/asdf%6A").hashCode) + assertEquals(new URI("http://example.com").hashCode(), + new URI("http://Example.CoM").hashCode()) + assertNotEquals(new URI("http://example.com/example-com").hashCode(), + new URI("http://Example.CoM/eXAMplE-cOm").hashCode()) } @Test def should_allow_non_ASCII_characters(): Unit = { @@ -411,21 +441,34 @@ class URITest { rawPath = "/%E3%81a", rawSchemeSpecificPart = "//booh/%E3%81a") - if (!executingInJVM) { // Fails on JDK6 and JDK7 - // %E3%E3 is considered as 2 malformed - expectURI(new URI("http://booh/%E3%E3a"), true, false)( - scheme = "http", - host = "booh", - path = "/��a", - authority = "booh", - schemeSpecificPart = "//booh/��a")( - rawPath = "/%E3%E3a", - rawSchemeSpecificPart = "//booh/%E3%E3a") - } + // %E3%E3 is considered as 2 malformed + expectURI(new URI("http://booh/%E3%E3a"), true, false)( + scheme = "http", + host = "booh", + path = "/��a", + authority = "booh", + schemeSpecificPart = "//booh/��a")( + rawPath = "/%E3%E3a", + rawSchemeSpecificPart = "//booh/%E3%E3a") } @Test def should_throw_on_bad_escape_sequences(): Unit = { expectThrows(classOf[URISyntaxException], new URI("http://booh/%E")) expectThrows(classOf[URISyntaxException], new URI("http://booh/%Ep")) } + + @Test def should_accept_valid_ipv4(): Unit = { + assertEquals(new URI("http","000.001.01.0", "", "").getHost, "000.001.01.0") + } + + @Test def should_throw_on_ipv4_out_of_range(): Unit = { + expectThrows(classOf[URISyntaxException], new URI("http","256.1.1.1", "", "")) + expectThrows(classOf[URISyntaxException], new URI("http","123.45.67.890", "", "")) + } + + @Test def opaque_url_should_consider_ssp_on_equality(): Unit = { + assertTrue("scheme case-insensitive", new URI("MAILTO:john") == new URI("mailto:john")) + assertTrue("SSP case-sensitive", new URI("mailto:john") != new URI("mailto:JOHN")) + assertTrue(new URI("mailto:john") != new URI("MAILTO:jim")) + } } From 6fbe7b2bab6b45b8cb63a2ebd837ef3d03b8360c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 14 Aug 2019 13:45:42 +0200 Subject: [PATCH 0062/1606] Do not rely on JavaConverters in the test suite. It is awkward to test our JDK collections using `JavaConverters`, as the latter rely on JDK collections themselves, to some extent. Using a trivial custom implementation of an immutable `j.u.Collection`, we can easily avoid most existing uses of `JavaConverters`. The remaining uses can be replaced by a few utilities, added in `Utils.scala`. --- .../javalib/util/ArrayDequeTest.scala | 23 ++-- .../javalib/util/CollectionTest.scala | 31 ++--- .../CollectionsOnCheckedCollectionTest.scala | 6 +- .../util/CollectionsOnCheckedListTest.scala | 6 +- .../util/CollectionsOnCheckedMapTest.scala | 10 +- .../util/CollectionsOnCollectionsTest.scala | 27 ++-- .../javalib/util/CollectionsOnListsTest.scala | 72 +++++------ .../javalib/util/CollectionsOnSetsTest.scala | 5 +- .../javalib/util/CollectionsTest.scala | 99 +++++++++----- .../javalib/util/HashtableTest.scala | 44 +++---- .../javalib/util/LinkedHashMapTest.scala | 122 ++++++++---------- .../javalib/util/LinkedHashSetTest.scala | 29 ++--- .../javalib/util/LinkedListTest.scala | 27 ++-- .../testsuite/javalib/util/ListTest.scala | 7 + .../testsuite/javalib/util/MapTest.scala | 86 ++++++------ .../javalib/util/NavigableSetTest.scala | 19 ++- .../javalib/util/PriorityQueueTest.scala | 11 +- .../javalib/util/PropertiesTest.scala | 29 +++-- .../testsuite/javalib/util/SetTest.scala | 36 ++---- .../javalib/util/SortedSetTest.scala | 26 ++-- .../testsuite/javalib/util/TreeSetTest.scala | 18 +-- .../util/TrivialImmutableCollection.scala | 108 ++++++++++++++++ .../testsuite/javalib/util/Utils.scala | 98 ++++++++++++++ .../ConcurrentLinkedQueueTest.scala | 12 +- .../ConcurrentSkipListSetTest.scala | 52 ++++---- .../concurrent/CopyOnWriteArrayListTest.scala | 17 ++- .../testsuite/niocharset/CharsetTest.scala | 6 +- .../testsuite/utils/CollectionsTestBase.scala | 20 +-- 28 files changed, 613 insertions(+), 433 deletions(-) create mode 100644 test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TrivialImmutableCollection.scala create mode 100644 test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/Utils.scala diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayDequeTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayDequeTest.scala index 72aab2b2f0..bbc84d555d 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayDequeTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayDequeTest.scala @@ -17,8 +17,6 @@ import java.util import org.junit.Test import org.junit.Assert._ -import scala.collection.JavaConverters._ - import java.{util => ju} import scala.reflect.ClassTag @@ -44,24 +42,22 @@ class ArrayDequeTest extends AbstractCollectionTest with DequeTest { } @Test def could_be_instantiated_with_a_prepopulated_Collection(): Unit = { - val s = Seq(1, 5, 2, 3, 4) - val l = s.asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val ad = factory.from[Int](l) assertEquals(ad.size(), 5) - for (i <- 0 until s.size) - assertEquals(ad.poll(), s(i)) + for (i <- 0 until l.size()) + assertEquals(ad.poll(), l(i)) assertTrue(ad.isEmpty) } @Test def should_add_multiple_element_in_one_operation(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection val ad = factory.empty[Int] assertEquals(ad.size(), 0) - ad.addAll(l) + ad.addAll(TrivialImmutableCollection(1, 5, 2, 3, 4)) assertEquals(ad.size(), 5) ad.add(6) assertEquals(ad.size(), 6) @@ -137,8 +133,8 @@ class ArrayDequeTest extends AbstractCollectionTest with DequeTest { } @Test def should_remove_occurrences_of_provided_elements(): Unit = { - val l = Seq("one", "two", "three", "two", "one").asJavaCollection - val ad = factory.from[String](l) + val ad = factory.from[String]( + TrivialImmutableCollection("one", "two", "three", "two", "one")) assertTrue(ad.removeFirstOccurrence("one")) assertTrue(ad.removeLastOccurrence("two")) @@ -150,21 +146,20 @@ class ArrayDequeTest extends AbstractCollectionTest with DequeTest { } @Test def should_iterate_over_elements_in_both_directions(): Unit = { - val s = Seq("one", "two", "three") - val l = s.asJavaCollection + val l = TrivialImmutableCollection("one", "two", "three") val ad = factory.from[String](l) val iter = ad.iterator() for (i <- 0 until l.size()) { assertTrue(iter.hasNext()) - assertEquals(iter.next(), s(i)) + assertEquals(iter.next(), l(i)) } assertFalse(iter.hasNext()) val diter = ad.descendingIterator() for (i <- (0 until l.size()).reverse) { assertTrue(diter.hasNext()) - assertEquals(diter.next(), s(i)) + assertEquals(diter.next(), l(i)) } assertFalse(diter.hasNext()) } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala index 3f170c90b4..bcdc6d1116 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionTest.scala @@ -17,12 +17,12 @@ import java.{util => ju, lang => jl} import org.junit.Test import org.junit.Assert._ -import scala.collection.JavaConverters._ - import org.scalajs.testsuite.utils.AssertThrows._ import scala.reflect.ClassTag +import Utils._ + trait CollectionTest { def factory: CollectionFactory @@ -36,14 +36,14 @@ trait CollectionTest { coll.clear() assertEquals(0, coll.size()) - assertFalse(coll.addAll(Seq.empty[String].asJava)) + assertFalse(coll.addAll(TrivialImmutableCollection[String]())) assertEquals(0, coll.size()) - assertTrue(coll.addAll(Seq("one").asJava)) + assertTrue(coll.addAll(TrivialImmutableCollection("one"))) assertEquals(1, coll.size()) coll.clear() - assertTrue(coll.addAll(Seq("one", "two", "one").asJava)) + assertTrue(coll.addAll(TrivialImmutableCollection("one", "two", "one"))) assertTrue(coll.size() >= 1) } @@ -56,14 +56,14 @@ trait CollectionTest { coll.clear() assertEquals(0, coll.size()) - assertFalse(coll.addAll(Seq.empty[Int].asJava)) + assertFalse(coll.addAll(TrivialImmutableCollection[Int]())) assertEquals(0, coll.size()) - assertTrue(coll.addAll(Seq(1).asJava)) + assertTrue(coll.addAll(TrivialImmutableCollection(1))) assertEquals(1, coll.size()) coll.clear() - assertTrue(coll.addAll(Seq(1, 2, 1).asJava)) + assertTrue(coll.addAll(TrivialImmutableCollection(1, 2, 1))) assertTrue(coll.size() >= 1) } @@ -76,14 +76,14 @@ trait CollectionTest { coll.clear() assertEquals(0, coll.size()) - assertFalse(coll.addAll(Seq.empty[Double].asJava)) + assertFalse(coll.addAll(TrivialImmutableCollection[Double]())) assertEquals(0, coll.size()) - assertTrue(coll.addAll(Seq(1.234).asJava)) + assertTrue(coll.addAll(TrivialImmutableCollection(1.234))) assertEquals(1, coll.size()) coll.clear() - assertTrue(coll.addAll(Seq(1.234, 2.345, 1.234).asJava)) + assertTrue(coll.addAll(TrivialImmutableCollection(1.234, 2.345, 1.234))) assertTrue(coll.size() >= 1) coll.clear() @@ -206,7 +206,8 @@ trait CollectionTest { coll.add("three") coll.add("three") - assertEquals(coll.iterator().asScala.toSet, Set("one", "two", "three")) + assertIteratorSameElementsAsSetDupesAllowed("one", "two", "three")( + coll.iterator()) } @Test def removeIf(): Unit = { @@ -217,13 +218,13 @@ trait CollectionTest { def test(x: Int): Boolean = x >= 50 })) assertEquals(5, coll.size()) - assertEquals(coll.iterator().asScala.toSet, Set(-45, 0, 12, 32, 42)) + assertIteratorSameElementsAsSet(-45, 0, 12, 32, 42)(coll.iterator()) assertFalse(coll.removeIf(new java.util.function.Predicate[Int] { def test(x: Int): Boolean = x >= 45 })) assertEquals(5, coll.size()) - assertEquals(coll.iterator().asScala.toSet, Set(-45, 0, 12, 32, 42)) + assertIteratorSameElementsAsSet(-45, 0, 12, 32, 42)(coll.iterator()) } } @@ -235,7 +236,7 @@ trait CollectionFactory { def fromElements[E: ClassTag](elems: E*): ju.Collection[E] = { val coll = empty[E] - coll.addAll(elems.asJavaCollection) + coll.addAll(TrivialImmutableCollection(elems: _*)) coll } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedCollectionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedCollectionTest.scala index 5af993f6e0..d21dffa10e 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedCollectionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedCollectionTest.scala @@ -20,8 +20,6 @@ import org.junit.Test import org.scalajs.testsuite.javalib.util.concurrent.CopyOnWriteArrayListFactory -import scala.collection.JavaConverters._ - import org.scalajs.testsuite.utils.AssertThrows._ import org.scalajs.testsuite.utils.Platform._ @@ -46,7 +44,7 @@ trait CollectionsCheckedCollectionTest @Test def testCheckedCollection(): Unit = { assertTrue(superColl().add(new C)) - assertTrue(superColl().addAll(Seq(new C).asJava)) + assertTrue(superColl().addAll(TrivialImmutableCollection(new C))) } @Test def testCheckedCollectionBadInputs(): Unit = { @@ -54,7 +52,7 @@ trait CollectionsCheckedCollectionTest expectThrows(classOf[ClassCastException], superColl().add(new A)) expectThrows(classOf[ClassCastException], - superColl().addAll(Seq(new A).asJava)) + superColl().addAll(TrivialImmutableCollection(new A))) } protected def superColl(): ju.Collection[A] = diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedListTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedListTest.scala index 94ce72d9d8..e63182ebf0 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedListTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedListTest.scala @@ -22,8 +22,6 @@ import org.scalajs.testsuite.javalib.util.concurrent.CopyOnWriteArrayListFactory import org.scalajs.testsuite.utils.AssertThrows._ import org.scalajs.testsuite.utils.Platform._ -import scala.collection.JavaConverters._ - import scala.reflect.ClassTag trait CollectionsCheckedListTest @@ -51,7 +49,7 @@ trait CollectionsCheckedListTest @Test def testCheckedList(): Unit = { superList().add(0, new C) - assertTrue(superList().addAll(0, Seq(new C).asJava)) + assertTrue(superList().addAll(0, TrivialImmutableCollection(new C))) testOnFirstPositionOfIterator[ju.ListIterator[A]](superList().listIterator _, _.add(new C), None) testOnFirstPositionOfIterator[ju.ListIterator[A]](superList().listIterator _, @@ -63,7 +61,7 @@ trait CollectionsCheckedListTest expectThrows(classOf[ClassCastException], superList().add(0, new A)) expectThrows(classOf[ClassCastException], - superList().addAll(0, Seq(new A).asJava)) + superList().addAll(0, TrivialImmutableCollection(new A))) testOnFirstPositionOfIterator[ju.ListIterator[A]]( superList().listIterator _, _.add(new A), Some(classOf[ClassCastException])) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedMapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedMapTest.scala index 1705ae1eb8..bc3e2ebcad 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedMapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCheckedMapTest.scala @@ -21,8 +21,6 @@ import org.junit.Test import org.scalajs.testsuite.utils.AssertThrows._ import org.scalajs.testsuite.utils.Platform._ -import scala.collection.JavaConverters._ - import scala.reflect.ClassTag trait CollectionsOnCheckedMapTest extends CollectionsOnMapsTest { @@ -64,8 +62,8 @@ trait CollectionsOnCheckedMapTest extends CollectionsOnMapsTest { m.put(new C, new C) m.asInstanceOf[ju.Map[A, A]] } - expectThrows(classOf[ClassCastException], - singletonMap().entrySet().asScala.head.setValue(new A)) + val firstEntry = singletonMap().entrySet().iterator().next() + expectThrows(classOf[ClassCastException], firstEntry.setValue(new A)) } private def superMap(): ju.Map[A, A] = @@ -111,8 +109,8 @@ trait CollectionsOnCheckedSortedMapTest extends CollectionsOnSortedMapsTest { m.put(new C, new C) m.asInstanceOf[ju.Map[A, A]] } - expectThrows(classOf[ClassCastException], - singletonMap().entrySet().asScala.head.setValue(new A)) + val firstEntry = singletonMap().entrySet().iterator().next() + expectThrows(classOf[ClassCastException], firstEntry.setValue(new A)) } private def superMap(): ju.Map[A, A] = diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCollectionsTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCollectionsTest.scala index 14dc022b6f..de63f9a781 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCollectionsTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnCollectionsTest.scala @@ -20,7 +20,6 @@ import org.junit.Test import org.scalajs.testsuite.utils.CollectionsTestBase -import scala.collection.JavaConverters._ import scala.reflect.ClassTag trait CollectionsOnCollectionsTest extends CollectionsTestBase { @@ -30,7 +29,7 @@ trait CollectionsOnCollectionsTest extends CollectionsTestBase { def testMinMax1[T <: AnyRef with Comparable[T]: ClassTag]( factory: CollectionFactory, toElem: Int => T, isMin: Boolean): Unit = { val coll = factory.empty[T] - coll.addAll(range.map(toElem).asJava) + coll.addAll(rangeOfElems(toElem)) val minMax = if (isMin) range.head else range.last def getMinMax(): T = @@ -40,10 +39,10 @@ trait CollectionsOnCollectionsTest extends CollectionsTestBase { assertEquals(0, getMinMax().compareTo(toElem(minMax))) coll match { - case list: List[_] => - ju.Collections.shuffle(list.asJava, new ju.Random(42)) + case list: ju.List[_] => + ju.Collections.shuffle(list, new ju.Random(42)) assertEquals(0, getMinMax().compareTo(toElem(minMax))) - ju.Collections.shuffle(list.asJava, new ju.Random(100000)) + ju.Collections.shuffle(list, new ju.Random(100000)) assertEquals(0, getMinMax().compareTo(toElem(minMax))) case _ => } @@ -52,7 +51,7 @@ trait CollectionsOnCollectionsTest extends CollectionsTestBase { def testMinMax2[T: ClassTag](factory: CollectionFactory, toElem: Int => T, isMin: Boolean, cmp: ju.Comparator[T]): Unit = { val coll = factory.empty[T] - coll.addAll(range.map(toElem).asJava) + coll.addAll(rangeOfElems(toElem)) val minMax = if (isMin) range.head else range.last def getMinMax: T = @@ -62,10 +61,10 @@ trait CollectionsOnCollectionsTest extends CollectionsTestBase { assertEquals(0, cmp.compare(getMinMax, toElem(minMax))) coll match { - case list: List[_] => - ju.Collections.shuffle(list.asJava, new ju.Random(42)) + case list: ju.List[_] => + ju.Collections.shuffle(list, new ju.Random(42)) assertEquals(0, cmp.compare(getMinMax, toElem(minMax))) - ju.Collections.shuffle(list.asJava, new ju.Random(100000)) + ju.Collections.shuffle(list, new ju.Random(100000)) assertEquals(0, cmp.compare(getMinMax, toElem(minMax))) case _ => } @@ -123,9 +122,9 @@ trait CollectionsOnCollectionsTest extends CollectionsTestBase { } expectAllFrequenciesToBe(0) - coll.addAll(range.map(toElem).asJava) + coll.addAll(rangeOfElems(toElem)) expectAllFrequenciesToBe(1) - coll.addAll(range.map(toElem).asJava) + coll.addAll(rangeOfElems(toElem)) coll match { case _: ju.Set[_] => expectAllFrequenciesToBe(1) case _: ju.List[_] => expectAllFrequenciesToBe(2) @@ -158,10 +157,10 @@ trait CollectionsOnCollectionsTest extends CollectionsTestBase { def test[E: ClassTag](toElem: Int => E): Unit = { val coll = factory.empty[E] testCollectionUnmodifiability(ju.Collections.unmodifiableCollection(coll), - toElem(0)) - coll.addAll(range.map(toElem).asJava) + toElem(0)) + coll.addAll(rangeOfElems(toElem)) testCollectionUnmodifiability(ju.Collections.unmodifiableCollection(coll), - toElem(0)) + toElem(0)) } test[jl.Integer](_.toInt) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnListsTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnListsTest.scala index a4c235551e..2a2e85bd0f 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnListsTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnListsTest.scala @@ -21,7 +21,6 @@ import org.scalajs.testsuite.javalib.util.concurrent.CopyOnWriteArrayListFactory import org.scalajs.testsuite.utils.AssertThrows._ import org.scalajs.testsuite.utils.CollectionsTestBase -import scala.collection.JavaConverters._ import scala.reflect.ClassTag object CollectionsOnListTest extends CollectionsTestBase { @@ -65,19 +64,20 @@ object CollectionsOnListTest extends CollectionsTestBase { } } - list.addAll(range.map(toElem).asJava) + list.addAll(rangeOfElems(toElem)) ju.Collections.sort(list) testIfSorted(true) list.clear() - list.addAll(range.reverse.map(toElem).asJava) + list.addAll(TrivialImmutableCollection(range.reverse.map(toElem): _*)) ju.Collections.sort(list) testIfSorted(true) for (seed <- List(0, 1, 42, -5432, 2341242)) { val rnd = new scala.util.Random(seed) list.clear() - list.addAll(range.map(_ => toElem(rnd.nextInt())).asJava) + list.addAll( + TrivialImmutableCollection(range.map(_ => toElem(rnd.nextInt())): _*)) ju.Collections.sort(list) testIfSorted(false) } @@ -101,19 +101,20 @@ object CollectionsOnListTest extends CollectionsTestBase { override def compare(o1: T, o2: T): Int = cmpFun(o1, o2) } - list.addAll(range.map(toElem).asJava) + list.addAll(rangeOfElems(toElem)) ju.Collections.sort(list, cmp) testIfSorted(true) list.clear() - list.addAll(range.reverse.map(toElem).asJava) + list.addAll(TrivialImmutableCollection(range.reverse.map(toElem): _*)) ju.Collections.sort(list, cmp) testIfSorted(true) for (seed <- List(0, 1, 42, -5432, 2341242)) { val rnd = new scala.util.Random(seed) list.clear() - list.addAll(range.map(_ => toElem(rnd.nextInt())).asJava) + list.addAll( + TrivialImmutableCollection(range.map(_ => toElem(rnd.nextInt())): _*)) ju.Collections.sort(list, cmp) testIfSorted(false) } @@ -133,9 +134,7 @@ trait CollectionsOnListTest extends CollectionsOnCollectionsTest { @Test def binarySearch_on_comparables(): Unit = { // Test: binarySearch[T](list: List[Comparable[T]], T) def test[T <: AnyRef with Comparable[T]: ClassTag](toElem: Int => T): Unit = { - val list = factory.empty[T] - - list.addAll(range.map(toElem).sorted.asJava) + val list = factory.fromElements[T](range.map(toElem).sorted: _*) for (i <- Seq(range.head, range.last, range(range.size/3), range(range.size/2), range(3*range.size/5))) { @@ -162,12 +161,12 @@ trait CollectionsOnListTest extends CollectionsOnCollectionsTest { @Test def binarySearch_with_comparator(): Unit = { // Test: binarySearch[T](List[T], key: T, Comparator[T])) def test[T: ClassTag](toElem: Int => T, cmpFun: (T, T) => Int): Unit = { - val list = factory.empty[T] val cmp = new ju.Comparator[T] { override def compare(o1: T, o2: T): Int = cmpFun(o1, o2) } - list.addAll(range.map(toElem).sortWith(cmpFun(_, _) < 0).asJava) + val list = factory.fromElements[T]( + range.map(toElem).sortWith(cmpFun(_, _) < 0): _*) for (i <- Seq(range.head, range.last, range(range.size/3), range(range.size/2), range(3*range.size/5))) { @@ -194,8 +193,7 @@ trait CollectionsOnListTest extends CollectionsOnCollectionsTest { @Test def reverse(): Unit = { // Test: reverse(list: List[_]) def test[T: ClassTag](toElem: Int => T): Unit = { - val list = factory.empty[T] - list.addAll(range.map(toElem).asJava) + val list = factory.fromElements[T](range.map(toElem): _*) def testIfInOrder(reversed: Boolean): Unit = { for (i <- range) { @@ -225,10 +223,10 @@ trait CollectionsOnListTest extends CollectionsOnCollectionsTest { val list = factory.empty[E] ju.Collections.shuffle(list) assertEquals(0, list.size) - list.addAll(range.map(toElem).asJava) + list.addAll(rangeOfElems(toElem)) shuffle(list) assertEquals(range.size, list.size) - assertTrue(list.containsAll(range.map(toElem).asJava)) + assertTrue(list.containsAll(rangeOfElems(toElem))) } test[jl.Integer](_.toInt) test[jl.Long](_.toLong) @@ -250,8 +248,7 @@ trait CollectionsOnListTest extends CollectionsOnCollectionsTest { @Test def swap(): Unit = { // Test: swap(List[_], Int, Int) def test[E: ClassTag](toElem: Int => E): Unit = { - val list = factory.empty[E] - list.addAll(range.map(toElem(_)).asJava) + val list = factory.fromElements[E](range.map(toElem): _*) ju.Collections.swap(list, 0, 1) assertEquals(toElem(1), list.get(0)) @@ -282,8 +279,7 @@ trait CollectionsOnListTest extends CollectionsOnCollectionsTest { @Test def fill(): Unit = { // Test: fill[E](List[E], E) def test[E: ClassTag](toElem: Int => E): Unit = { - val list = factory.empty[E] - list.addAll(range.map(toElem(_)).asJava) + val list = factory.fromElements[E](range.map(toElem): _*) ju.Collections.fill(list, toElem(0)) for (i <- range) @@ -341,8 +337,7 @@ trait CollectionsOnListTest extends CollectionsOnCollectionsTest { @Test def rotate(): Unit = { def modulo(a: Int, b: Int): Int = ((a % b) + b) % b def test[E: ClassTag](toElem: Int => E): Unit = { - val list = factory.empty[E] - list.addAll(range.map(toElem).asJava) + val list = factory.fromElements[E](range.map(toElem): _*) ju.Collections.rotate(list, 0) for (i <- range) @@ -365,7 +360,7 @@ trait CollectionsOnListTest extends CollectionsOnCollectionsTest { assertEquals(toElem(modulo(i + 3, range.size)), list.get(i)) list.clear() - list.addAll((0 until 6).map(toElem).asJava) + list.addAll(TrivialImmutableCollection((0 until 6).map(toElem): _*)) ju.Collections.rotate(list, 2) for (i <- 0 until 6) assertEquals(toElem(modulo(i - 2, 6)), list.get(i)) @@ -379,26 +374,25 @@ trait CollectionsOnListTest extends CollectionsOnCollectionsTest { @Test def replaceAll(): Unit = { def test[E: ClassTag](toElem: Int => E): Unit = { - val list = factory.empty[E] - list.addAll(range.map(toElem).asJava) + val list = factory.fromElements[E](range.map(toElem): _*) ju.Collections.replaceAll(list, toElem(range.last), toElem(0)) for (i <- range.init) assertEquals(toElem(i), list.get(i)) - assertEquals(toElem(0), list.asScala.last) + assertEquals(toElem(0), list.get(list.size() - 1)) ju.Collections.replaceAll(list, toElem(range(range.size - 2)), toElem(0)) for (i <- range.dropRight(2)) assertEquals(toElem(i), list.get(i)) - assertEquals(toElem(0), list.asScala.dropRight(1).last) - assertEquals(toElem(0), list.asScala.last) + assertEquals(toElem(0), list.get(list.size() - 2)) + assertEquals(toElem(0), list.get(list.size() - 1)) ju.Collections.replaceAll(list, toElem(0), toElem(-1)) for (i <- range.tail.dropRight(2)) assertEquals(toElem(i), list.get(i)) - assertEquals(toElem(-1), list.asScala.head) - assertEquals(toElem(-1), list.asScala.dropRight(1).last) - assertEquals(toElem(-1), list.asScala.last) + assertEquals(toElem(-1), list.get(0)) + assertEquals(toElem(-1), list.get(list.size() - 2)) + assertEquals(toElem(-1), list.get(list.size() - 1)) } test[jl.Integer](_.toInt) @@ -414,16 +408,16 @@ trait CollectionsOnListTest extends CollectionsOnCollectionsTest { assertEquals(0, ju.Collections.indexOfSubList(source, target)) - source.addAll(range.map(toElem).asJava) + source.addAll(rangeOfElems(toElem)) assertEquals(0, ju.Collections.indexOfSubList(source, target)) - target.addAll(range.map(toElem).asJava) + target.addAll(rangeOfElems(toElem)) assertEquals(0, ju.Collections.indexOfSubList(source, target)) - source.addAll(range.map(toElem).asJava) + source.addAll(rangeOfElems(toElem)) assertEquals(0, ju.Collections.indexOfSubList(source, target)) - source.addAll(range.map(toElem).asJava) + source.addAll(rangeOfElems(toElem)) assertEquals(0, ju.Collections.indexOfSubList(source, target)) source.remove(0) @@ -446,16 +440,16 @@ trait CollectionsOnListTest extends CollectionsOnCollectionsTest { assertEquals(0, ju.Collections.lastIndexOfSubList(source, target)) - source.addAll(range.map(toElem).asJava) + source.addAll(rangeOfElems(toElem)) assertEquals(range.size, ju.Collections.lastIndexOfSubList(source, target)) - target.addAll(range.map(toElem).asJava) + target.addAll(rangeOfElems(toElem)) assertEquals(0, ju.Collections.lastIndexOfSubList(source, target)) - source.addAll(range.map(toElem).asJava) + source.addAll(rangeOfElems(toElem)) assertEquals(range.size, ju.Collections.lastIndexOfSubList(source, target)) - source.addAll(range.map(toElem).asJava) + source.addAll(rangeOfElems(toElem)) assertEquals(2 * range.size, ju.Collections.lastIndexOfSubList(source, target)) source.remove(source.size - 1) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnSetsTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnSetsTest.scala index b7e1087ff5..bd3f19fc98 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnSetsTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsOnSetsTest.scala @@ -16,7 +16,6 @@ import java.{util => ju, lang => jl} import org.junit.Test -import scala.collection.JavaConverters._ import scala.reflect.ClassTag trait CollectionsOnSetsTest extends CollectionsOnCollectionsTest { @@ -26,7 +25,7 @@ trait CollectionsOnSetsTest extends CollectionsOnCollectionsTest { def test[E: ClassTag](toElem: Int => E): Unit = { val set = factory.empty[E] testSetUnmodifiability(ju.Collections.unmodifiableSet(set), toElem(0)) - set.addAll(range.map(toElem).asJava) + set.addAll(rangeOfElems(toElem)) testSetUnmodifiability(ju.Collections.unmodifiableSet(set), toElem(0)) } @@ -45,7 +44,7 @@ trait CollectionsOnSortedSetsTest extends CollectionsOnSetsTest { val sortedSet = factory.empty[E] testSortedSetUnmodifiability(ju.Collections.unmodifiableSortedSet(sortedSet), toElem(0)) - sortedSet.addAll(range.map(toElem).asJava) + sortedSet.addAll(rangeOfElems(toElem)) testSortedSetUnmodifiability(ju.Collections.unmodifiableSortedSet(sortedSet), toElem(0)) } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsTest.scala index 1f0646b6d3..c9343f52d5 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsTest.scala @@ -20,32 +20,34 @@ import org.junit.Test import org.scalajs.testsuite.utils.AssertThrows._ import org.scalajs.testsuite.utils.CollectionsTestBase -import scala.collection.JavaConverters._ import scala.reflect.ClassTag +import Utils._ + class CollectionsTest extends CollectionsTestBase { private def checkImmutablilityOfCollectionApi[E](coll: ju.Collection[E], elem: E): Unit = { expectThrows(classOf[UnsupportedOperationException], coll.add(elem)) - expectThrows(classOf[UnsupportedOperationException], coll.addAll(List(elem).asJava)) - assertFalse(coll.addAll(List.empty[E].asJava)) + expectThrows(classOf[UnsupportedOperationException], + coll.addAll(TrivialImmutableCollection(elem))) + assertFalse(coll.addAll(TrivialImmutableCollection[E]())) - if (coll.asScala.count(_ == elem) != coll.size) - expectThrows(classOf[Exception], coll.retainAll(List(elem).asJava)) + if (ju.Collections.frequency(coll, elem) != coll.size) + expectThrows(classOf[Exception], coll.retainAll(TrivialImmutableCollection(elem))) else - assertFalse(coll.retainAll(List(elem).asJava)) + assertFalse(coll.retainAll(TrivialImmutableCollection(elem))) if (coll.contains(elem)) { expectThrows(classOf[Exception], coll.remove(elem)) - expectThrows(classOf[Exception], coll.removeAll(List(elem).asJava)) + expectThrows(classOf[Exception], coll.removeAll(TrivialImmutableCollection(elem))) } else { assertFalse(coll.remove(elem)) - assertFalse(coll.removeAll(List(elem).asJava)) + assertFalse(coll.removeAll(TrivialImmutableCollection(elem))) } - assertFalse(coll.removeAll(List.empty[E].asJava)) + assertFalse(coll.removeAll(TrivialImmutableCollection[E]())) - if (coll.asScala.nonEmpty) { + if (!coll.isEmpty()) { expectThrows(classOf[Throwable], coll.clear()) } else { coll.clear() // Should not throw @@ -58,23 +60,25 @@ class CollectionsTest extends CollectionsTestBase { private def checkImmutablilityOfListApi[E](list: ju.List[E], elem: E): Unit = { checkImmutablilityOfCollectionApi(list, elem) expectThrows(classOf[UnsupportedOperationException], list.add(0, elem)) - assertFalse(list.addAll(0, List.empty[E].asJava)) - expectThrows(classOf[UnsupportedOperationException], list.addAll(0, List(elem).asJava)) + assertFalse(list.addAll(0, TrivialImmutableCollection[E]())) + expectThrows(classOf[UnsupportedOperationException], + list.addAll(0, TrivialImmutableCollection(elem))) expectThrows(classOf[UnsupportedOperationException], list.remove(0)) } private def checkImmutablilityOfMapApi[K, V](map: ju.Map[K, V], k: K, v: V): Unit = { expectThrows(classOf[UnsupportedOperationException], map.put(k, v)) - expectThrows(classOf[UnsupportedOperationException], map.putAll(Map(k ->v).asJava)) - map.putAll(Map.empty[K, V].asJava) // Should not throw + expectThrows(classOf[UnsupportedOperationException], + map.putAll(TrivialImmutableMap(k -> v))) + map.putAll(TrivialImmutableMap[K, V]()) // Should not throw if (map.containsKey(k)) expectThrows(classOf[Throwable], map.remove(k)) else assertNull(map.remove(k).asInstanceOf[AnyRef]) - if (map.asScala.nonEmpty) + if (!map.isEmpty()) expectThrows(classOf[Throwable], map.clear()) else map.clear() // Should not throw @@ -85,7 +89,7 @@ class CollectionsTest extends CollectionsTestBase { val emptySet = ju.Collections.emptySet[E] assertTrue(emptySet.isEmpty) assertEquals(0, emptySet.size) - assertEquals(0, emptySet.iterator.asScala.size) + assertTrue(iteratorIsEmpty(emptySet.iterator())) checkImmutablilityOfSetApi(emptySet, toElem(0)) } @@ -99,7 +103,7 @@ class CollectionsTest extends CollectionsTestBase { val emptyList = ju.Collections.emptyList[E] assertTrue(emptyList.isEmpty) assertEquals(0, emptyList.size) - assertEquals(0, emptyList.iterator.asScala.size) + assertTrue(iteratorIsEmpty(emptyList.iterator())) checkImmutablilityOfListApi(emptyList, toElem(0)) } @@ -129,7 +133,7 @@ class CollectionsTest extends CollectionsTestBase { val singletonSet = ju.Collections.singleton[E](toElem(0)) assertTrue(singletonSet.contains(toElem(0))) assertEquals(1, singletonSet.size) - assertEquals(1, singletonSet.iterator.asScala.size) + assertEquals(1, iteratorSize(singletonSet.iterator())) checkImmutablilityOfSetApi(singletonSet, toElem(0)) checkImmutablilityOfSetApi(singletonSet, toElem(1)) } @@ -144,7 +148,7 @@ class CollectionsTest extends CollectionsTestBase { val singletonList = ju.Collections.singletonList[E](toElem(0)) assertTrue(singletonList.contains(toElem(0))) assertEquals(1, singletonList.size) - assertEquals(1, singletonList.iterator.asScala.size) + assertEquals(1, iteratorSize(singletonList.iterator())) checkImmutablilityOfListApi(singletonList, toElem(0)) checkImmutablilityOfListApi(singletonList, toElem(1)) } @@ -159,7 +163,9 @@ class CollectionsTest extends CollectionsTestBase { val singletonMap = ju.Collections.singletonMap[K, V](toKey(0), toValue(1)) assertEquals(toValue(1), singletonMap.get(toKey(0))) assertEquals(1, singletonMap.size) - assertEquals(1, singletonMap.asScala.iterator.size) + assertEquals(1, iteratorSize(singletonMap.entrySet().iterator())) + assertEquals(1, iteratorSize(singletonMap.keySet().iterator())) + assertEquals(1, iteratorSize(singletonMap.values().iterator())) checkImmutablilityOfMapApi(singletonMap, toKey(0), toValue(0)) checkImmutablilityOfMapApi(singletonMap, toKey(1), toValue(1)) } @@ -174,9 +180,9 @@ class CollectionsTest extends CollectionsTestBase { for (n <- Seq(1, 4, 543)) { val nCopies = ju.Collections.nCopies(n, toElem(0)) assertTrue(nCopies.contains(toElem(0))) - nCopies.asScala.forall(_ == toElem(0)) + assertEquals(n, ju.Collections.frequency(nCopies, toElem(0))) assertEquals(n, nCopies.size) - assertEquals(n, nCopies.iterator.asScala.size) + assertEquals(n, iteratorSize(nCopies.iterator())) checkImmutablilityOfListApi(nCopies, toElem(0)) checkImmutablilityOfListApi(nCopies, toElem(1)) } @@ -184,7 +190,7 @@ class CollectionsTest extends CollectionsTestBase { val zeroCopies = ju.Collections.nCopies(0, toElem(0)) assertFalse(zeroCopies.contains(toElem(0))) assertEquals(0, zeroCopies.size) - assertEquals(0, zeroCopies.iterator.asScala.size) + assertTrue(iteratorIsEmpty(zeroCopies.iterator())) checkImmutablilityOfListApi(zeroCopies, toElem(0)) for (n <- Seq(-1, -4, -543)) { @@ -263,9 +269,9 @@ class CollectionsTest extends CollectionsTestBase { } @Test def enumeration(): Unit = { - val coll = range.asJavaCollection + val coll = TrivialImmutableCollection(range: _*) val enum = ju.Collections.enumeration(coll) - for (elem <- coll.asScala) { + for (elem <- range) { assertTrue(enum.hasMoreElements) assertEquals(elem, enum.nextElement()) } @@ -273,19 +279,44 @@ class CollectionsTest extends CollectionsTestBase { } @Test def list(): Unit = { - val enum = range.iterator.asJavaEnumeration + val elementCount = 30 + + val enum = new ju.Enumeration[Int] { + private var next: Int = 0 + def hasMoreElements(): Boolean = next != elementCount + def nextElement(): Int = { + next += 1 + next - 1 + } + } + val list = ju.Collections.list(enum) - assertEquals(range.size, list.size) - for (i <- range) + assertEquals(elementCount, list.size) + for (i <- 0 until elementCount) assertEquals(i, list.get(i)) } + @Test def frequency(): Unit = { + val coll = TrivialImmutableCollection(5, 68, 12, 5, 5, 3, 12, 40, 56) + + assertEquals(0, ju.Collections.frequency(coll, 1)) + assertEquals(1, ju.Collections.frequency(coll, 3)) + assertEquals(3, ju.Collections.frequency(coll, 5)) + assertEquals(2, ju.Collections.frequency(coll, 12)) + assertEquals(1, ju.Collections.frequency(coll, 40)) + assertEquals(1, ju.Collections.frequency(coll, 56)) + assertEquals(1, ju.Collections.frequency(coll, 68)) + } + @Test def disjoint(): Unit = { - assertFalse(ju.Collections.disjoint((0 to 3).asJava, (0 to 3).asJava)) - assertFalse(ju.Collections.disjoint((0 to 3).asJava, (3 to 5).asJava)) - assertTrue(ju.Collections.disjoint((0 to 3).asJava, (6 to 9).asJava)) - assertTrue(ju.Collections.disjoint((0 to -1).asJava, (0 to 3).asJava)) - assertTrue(ju.Collections.disjoint((0 to 3).asJava, (0 to -1).asJava)) - assertTrue(ju.Collections.disjoint((0 to -1).asJava, (0 to -1).asJava)) + def coll(range: Range): ju.Collection[Int] = + TrivialImmutableCollection(range: _*) + + assertFalse(ju.Collections.disjoint(coll(0 to 3), coll(0 to 3))) + assertFalse(ju.Collections.disjoint(coll(0 to 3), coll(3 to 5))) + assertTrue(ju.Collections.disjoint(coll(0 to 3), coll(6 to 9))) + assertTrue(ju.Collections.disjoint(coll(0 to -1), coll(0 to 3))) + assertTrue(ju.Collections.disjoint(coll(0 to 3), coll(0 to -1))) + assertTrue(ju.Collections.disjoint(coll(0 to -1), coll(0 to -1))) } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashtableTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashtableTest.scala index b1eba2680e..7fdb8e21e0 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashtableTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/HashtableTest.scala @@ -17,7 +17,7 @@ import org.junit.Assert._ import java.{util => ju} -import scala.collection.JavaConverters._ +import Utils._ class HashtableTest { @@ -48,22 +48,22 @@ class HashtableTest { @Test def keys(): Unit = { val ht = new ju.Hashtable[Int, Int] - assertEquals(Set.empty[Int], ht.keys().asScala.toSet) + assertEnumSameElementsAsSet()(ht.keys()) ht.put(1, 4) - assertEquals(Set(1), ht.keys().asScala.toSet) + assertEnumSameElementsAsSet(1)(ht.keys()) ht.put(2, 5) ht.put(3, 6) - assertEquals(Set(1, 2, 3), ht.keys().asScala.toSet) + assertEnumSameElementsAsSet(1, 2, 3)(ht.keys()) } @Test def elements(): Unit = { val ht = new ju.Hashtable[Int, Int] - assertEquals(Set.empty[Int], ht.elements().asScala.toSet) + assertEnumSameElementsAsSet()(ht.elements()) ht.put(1, 4) - assertEquals(Set(4), ht.elements().asScala.toSet) + assertEnumSameElementsAsSet(4)(ht.elements()) ht.put(2, 5) ht.put(3, 6) - assertEquals(Set(4, 5, 6), ht.elements().asScala.toSet) + assertEnumSameElementsAsSet(4, 5, 6)(ht.elements()) } @Test def contains(): Unit = { @@ -192,13 +192,13 @@ class HashtableTest { @Test def keySet(): Unit = { val ht = new ju.Hashtable[Int, Int] - assertEquals(Set.empty[Int], ht.keySet().asScala.toSet) + assertCollSameElementsAsSet()(ht.keySet()) ht.put(1, 4) - assertEquals(Set(1), ht.keySet().asScala.toSet) + assertCollSameElementsAsSet(1)(ht.keySet()) ht.put(2, 5) - assertEquals(Set(1, 2), ht.keySet().asScala.toSet) + assertCollSameElementsAsSet(1, 2)(ht.keySet()) ht.put(3, 6) - assertEquals(Set(1, 2, 3), ht.keySet().asScala.toSet) + assertCollSameElementsAsSet(1, 2, 3)(ht.keySet()) } @Test def entrySet(): Unit = { @@ -207,14 +207,13 @@ class HashtableTest { assertTrue(entrySet.isEmpty) ht.put(1, 4) - assertEquals(Set(1), entrySet.asScala.map(_.getKey)) - assertEquals(Set(4), entrySet.asScala.map(_.getValue)) + assertCollSameElementsAsSet[ju.Map.Entry[Int, Int]](SIE(1, 4))(entrySet) ht.put(2, 5) - assertEquals(Set(1, 2), entrySet.asScala.map(_.getKey)) - assertEquals(Set(4, 5), entrySet.asScala.map(_.getValue)) + assertCollSameElementsAsSet[ju.Map.Entry[Int, Int]](SIE(1, 4), SIE(2, 5))( + entrySet) ht.put(3, 6) - assertEquals(Set(1, 2, 3), entrySet.asScala.map(_.getKey)) - assertEquals(Set(4, 5, 6), entrySet.asScala.map(_.getValue)) + assertCollSameElementsAsSet[ju.Map.Entry[Int, Int]](SIE(1, 4), SIE(2, 5), + SIE(3, 6))(entrySet) // Directly test the iterator, including its mutation capabilities @@ -244,19 +243,20 @@ class HashtableTest { assertEquals(ht.get(thirdKey), thirdEntry.getValue()) assertFalse(iter.hasNext()) - assertEquals(allKeys - secondKey, entrySet.asScala.map(_.getKey)) + assertIteratorSameElementsAsSet((allKeys - secondKey).toSeq: _*)( + iteratorMap(entrySet.iterator())(_.getKey())) assertTrue(ht.containsKey(firstKey) && ht.containsKey(thirdKey)) assertFalse(ht.containsKey(secondKey)) } @Test def values(): Unit = { val ht = new ju.Hashtable[Int, Int] - assertEquals(Set.empty[Int], ht.values().asScala.toSet) + assertCollSameElementsAsSet()(ht.values()) ht.put(1, 4) - assertEquals(Set(4), ht.values().asScala.toSet) + assertCollSameElementsAsSet(4)(ht.values()) ht.put(2, 5) - assertEquals(Set(4, 5), ht.values().asScala.toSet) + assertCollSameElementsAsSet(4, 5)(ht.values()) ht.put(3, 6) - assertEquals(Set(4, 5, 6), ht.values().asScala.toSet) + assertCollSameElementsAsSet(4, 5, 6)(ht.values()) } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala index d3f8ac2e3f..5646eb8291 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashMapTest.scala @@ -15,8 +15,6 @@ package org.scalajs.testsuite.javalib.util import org.junit.Test import org.junit.Assert._ -import scala.collection.JavaConverters._ - import java.{util => ju, lang => jl} import scala.reflect.ClassTag @@ -53,24 +51,12 @@ abstract class LinkedHashMapTest extends HashMapTest { def expectedKey(index: Int): String = (withSizeLimit.getOrElse(0) + index).toString() - def expectedValue(index: Int): String = - s"elem ${expectedKey(index)}" - - val expectedSize = withSizeLimit.getOrElse(100) - - assertEquals(expectedSize, lhm.entrySet.size) - for ((entry, index) <- lhm.entrySet.asScala.zipWithIndex) { - assertEquals(expectedKey(index), entry.getKey) - assertEquals(expectedValue(index), entry.getValue) + val expected = (0 until withSizeLimit.getOrElse(100)).map { i => + val key = expectedKey(i) + key -> s"elem $key" } - assertEquals(expectedSize, lhm.keySet.size) - for ((key, index) <- lhm.keySet.asScala.zipWithIndex) - assertEquals(expectedKey(index), key) - - assertEquals(expectedSize, lhm.entrySet.size) - for ((value, index) <- lhm.values.asScala.zipWithIndex) - assertEquals(expectedValue(index), value) + assertSameEntriesOrdered(expected: _*)(lhm) } @Test def should_iterate_in_the_same_order_after_removal_of_elements(): Unit = { @@ -79,27 +65,12 @@ abstract class LinkedHashMapTest extends HashMapTest { (0 until 100 by 3).foreach(key => lhm.remove(key.toString())) - val expectedKey = - ((100 - withSizeLimit.getOrElse(100)) to 100).filter(_ % 3 != 0).map(_.toString()).toArray - - def expectedValue(index: Int): String = - s"elem ${expectedKey(index)}" + val expectedKeys = + ((100 - withSizeLimit.getOrElse(100)) until 100).filter(_ % 3 != 0).map(_.toString()) - val expectedSize = if (withSizeLimit.isDefined) 33 else 66 + val expected = expectedKeys.map(key => key -> s"elem $key") - assertEquals(expectedSize, lhm.entrySet.size) - for ((entry, index) <- lhm.entrySet.asScala.zipWithIndex) { - assertEquals(expectedKey(index), entry.getKey) - assertEquals(expectedValue(index), entry.getValue) - } - - assertEquals(expectedSize, lhm.keySet.size) - for ((key, index) <- lhm.keySet.asScala.zipWithIndex) - assertEquals(expectedKey(index), key) - - assertEquals(expectedSize, lhm.entrySet.size) - for ((value, index) <- lhm.values.asScala.zipWithIndex) - assertEquals(expectedValue(index), value) + assertSameEntriesOrdered(expected: _*)(lhm) } @Test def should_iterate_in_order_after_adding_elements(): Unit = { @@ -113,7 +84,7 @@ abstract class LinkedHashMapTest extends HashMapTest { lhm.put("1", "new 1") lhm.put("98", "new 98") - val expectedKey = { + val expectedKeys = { if (factory.accessOrder) { val keys = (2 until 42) ++ (43 until 52) ++ (53 until 98) ++ List(99, 0, 100, 42, 52, 1, 98) @@ -122,32 +93,18 @@ abstract class LinkedHashMapTest extends HashMapTest { if (withSizeLimit.isDefined) (55 until 100) ++ List(0, 100, 42, 52, 1) else 0 to 100 } - }.map(_.toString()).toArray + }.map(_.toString()) - def expectedElem(index: Int): String = { - val key = expectedKey(index) + def expectedElem(key: String): String = { if (key == "0" || key == "1" || key == "42" || key == "52" || key == "98") s"new $key" else s"elem $key" } - val expectedSize = withSizeLimit.getOrElse(101) - - assertEquals(expectedSize, lhm.entrySet.size) - - for ((entry, index) <- lhm.entrySet.asScala.zipWithIndex) { - assertEquals(expectedKey(index), entry.getKey) - assertEquals(expectedElem(index), entry.getValue) - } - - assertEquals(expectedSize, lhm.keySet.size) - for ((key, index) <- lhm.keySet.asScala.zipWithIndex) - assertEquals(expectedKey(index), key) + val expected = expectedKeys.map(key => key -> expectedElem(key)) - assertEquals(expectedSize, lhm.entrySet.size) - for ((value, index) <- lhm.values.asScala.zipWithIndex) - assertEquals(expectedElem(index), value) + assertSameEntriesOrdered(expected: _*)(lhm) } @Test def should_iterate_in__after_accessing_elements(): Unit = { @@ -185,24 +142,49 @@ abstract class LinkedHashMapTest extends HashMapTest { intKey.toString() } - def expectedValue(index: Int): String = - s"elem ${expectedKey(index)}" + val expected = (0 until withSizeLimit.getOrElse(100)).map { i => + val key = expectedKey(i) + key -> s"elem $key" + } + + assertSameEntriesOrdered(expected: _*)(lhm) + } - val expectedSize = withSizeLimit.getOrElse(100) + private def assertSameEntriesOrdered[A, B](expected: (A, B)*)( + map: ju.Map[A, B]): Unit = { - assertEquals(expectedSize, lhm.entrySet.size) - for ((entry, index) <- lhm.entrySet.asScala.zipWithIndex) { - assertEquals(expectedKey(index), entry.getKey) - assertEquals(expectedValue(index), entry.getValue) - } + val expectedSize = expected.size + + val entrySet = map.entrySet() + assertEquals(expectedSize, entrySet.size()) + val keySet = map.keySet() + assertEquals(expectedSize, keySet.size()) + val values = map.values() + assertEquals(expectedSize, values.size()) - assertEquals(expectedSize, lhm.keySet.size) - for ((key, index) <- lhm.keySet.asScala.zipWithIndex) - assertEquals(expectedKey(index), key) + val expectedIter = expected.iterator + val entryIter = entrySet.iterator() + val keyIter = keySet.iterator() + val valueIter = values.iterator() + + for (_ <- 0 until expectedSize) { + val (key, value) = expectedIter.next() + + assertTrue(entryIter.hasNext()) + val entry = entryIter.next() + assertEquals(key, entry.getKey()) + assertEquals(value, entry.getValue()) + + assertTrue(keyIter.hasNext()) + assertEquals(key, keyIter.next()) + + assertTrue(valueIter.hasNext()) + assertEquals(value, valueIter.next()) + } - assertEquals(expectedSize, lhm.entrySet.size) - for ((value, index) <- lhm.values.asScala.zipWithIndex) - assertEquals(expectedValue(index), value) + assertFalse(entryIter.hasNext()) + assertFalse(keyIter.hasNext()) + assertFalse(valueIter.hasNext()) } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashSetTest.scala index cd283ba1e5..5a528e6b18 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedHashSetTest.scala @@ -17,7 +17,6 @@ import java.{util => ju} import org.junit.Test import org.junit.Assert._ -import scala.collection.JavaConverters._ import scala.reflect.ClassTag class LinkedHashSetTest extends HashSetTest { @@ -27,36 +26,26 @@ class LinkedHashSetTest extends HashSetTest { @Test def should_iterate_over_elements_in_an_ordered_manner(): Unit = { val hs = factory.empty[String] - val l1 = List[String]("ONE", "TWO", (null: String)) - assertTrue(hs.addAll(l1.asJavaCollection)) + val l1 = TrivialImmutableCollection("ONE", "TWO", null) + assertTrue(hs.addAll(l1)) assertEquals(3, hs.size) val iter1 = hs.iterator() - val result1 = { - for (i <- 0 until 3) yield { - assertTrue(iter1.hasNext()) - val value = iter1.next() - assertEquals(l1(i), value) - value - } + for (i <- 0 until 3) { + assertTrue(iter1.hasNext()) + assertEquals(l1(i), iter1.next()) } assertFalse(iter1.hasNext()) - assertEquals(l1, result1) - val l2 = l1 :+ "THREE" + val l2 = TrivialImmutableCollection("ONE", "TWO", null, "THREE") assertTrue(hs.add(l2(3))) val iter2 = hs.iterator() - val result2 = { - for (i <- 0 until 4) yield { - assertTrue(iter2.hasNext()) - val value = iter2.next() - assertEquals(l2(i), value) - value - } + for (i <- 0 until 4) { + assertTrue(iter2.hasNext()) + assertEquals(l2(i), iter2.next()) } assertFalse(iter2.hasNext()) - assertTrue(result2.equals(l2)) } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedListTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedListTest.scala index 4efaa420f1..7df366c956 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedListTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/LinkedListTest.scala @@ -17,8 +17,6 @@ import scala.language.implicitConversions import org.junit.Test import org.junit.Assert._ -import scala.collection.JavaConverters._ - import java.util.LinkedList import scala.reflect.ClassTag @@ -44,20 +42,19 @@ class LinkedListTest extends AbstractListTest { } @Test def could_be_instantiated_with_a_prepopulated_Collection(): Unit = { - val s = Seq(1, 5, 2, 3, 4) - val l = s.asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val ll = new LinkedList[Int](l) assertEquals(5, ll.size()) - for (i <- 0 until s.size) - assertEquals(s(i), ll.poll()) + for (i <- 0 until l.size()) + assertEquals(l(i), ll.poll()) assertTrue(ll.isEmpty) } @Test def should_add_multiple_element_in_one_operation(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val ll = new LinkedList[Int]() assertEquals(0, ll.size()) @@ -68,14 +65,13 @@ class LinkedListTest extends AbstractListTest { } @Test def `could_be_instantiated_with_a_prepopulated_Collection_-_LinkedListTest`(): Unit = { - val s = Seq(1, 5, 2, 3, 4) - val l = s.asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val ll = new LinkedList[Int](l) assertEquals(5, ll.size()) - for (i <- 0 until s.size) - assertEquals(s(i), ll.poll()) + for (i <- 0 until l.size()) + assertEquals(l(i), ll.poll()) assertTrue(ll.isEmpty) } @@ -150,7 +146,7 @@ class LinkedListTest extends AbstractListTest { } @Test def should_remove_occurrences_of_provided_elements(): Unit = { - val l = Seq("one", "two", "three", "two", "one").asJavaCollection + val l = TrivialImmutableCollection("one", "two", "three", "two", "one") val ll = new LinkedList[String](l) assertTrue(ll.removeFirstOccurrence("one")) @@ -165,21 +161,20 @@ class LinkedListTest extends AbstractListTest { } @Test def should_iterate_over_elements_in_both_directions(): Unit = { - val s = Seq("one", "two", "three") - val l = s.asJavaCollection + val l = TrivialImmutableCollection("one", "two", "three") val ll = new LinkedList[String](l) val iter = ll.iterator() for (i <- 0 until l.size()) { assertTrue(iter.hasNext()) - assertEquals(s(i), iter.next()) + assertEquals(l(i), iter.next()) } assertFalse(iter.hasNext()) val diter = ll.descendingIterator() for (i <- (0 until l.size()).reverse) { assertTrue(diter.hasNext()) - assertEquals(s(i), diter.next()) + assertEquals(l(i), diter.next()) } assertFalse(diter.hasNext()) } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala index 68d5adaecc..35ea2e12dc 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ListTest.scala @@ -441,6 +441,13 @@ trait ListTest extends CollectionTest { trait ListFactory extends CollectionFactory { def empty[E: ClassTag]: ju.List[E] + // Refines the result type of CollectionFactory.fromElements + override def fromElements[E: ClassTag](elems: E*): ju.List[E] = { + val coll = empty[E] + coll.addAll(TrivialImmutableCollection(elems: _*)) + coll + } + /** Sortable using java.util.Collections.sort */ def sortableUsingCollections: Boolean = true diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala index 728780bf8b..924208c3a1 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/MapTest.scala @@ -22,6 +22,8 @@ import org.scalajs.testsuite.utils.AssertThrows._ import scala.reflect.ClassTag +import Utils._ + trait MapTest { import MapTest._ @@ -422,14 +424,14 @@ trait MapTest { assertTrue(values.contains("three")) - val coll1 = ju.Arrays.asList("one", "two", "three") + val coll1 = TrivialImmutableCollection("one", "two", "three") assertTrue(values.containsAll(coll1)) - val coll2 = ju.Arrays.asList("one", "two", "three", "four") + val coll2 = TrivialImmutableCollection("one", "two", "three", "four") assertFalse(values.containsAll(coll2)) if (factory.allowsNullValuesQueries) { - val coll3 = ju.Arrays.asList("one", "two", "three", null) + val coll3 = TrivialImmutableCollection("one", "two", "three", null) assertFalse(values.containsAll(coll3)) } } @@ -452,14 +454,17 @@ trait MapTest { assertTrue(values.contains(TestObj(33))) - val coll1 = ju.Arrays.asList(TestObj(11), TestObj(22), TestObj(33)) + val coll1 = TrivialImmutableCollection(TestObj(11), TestObj(22), + TestObj(33)) assertTrue(values.containsAll(coll1)) - val coll2 = ju.Arrays.asList(TestObj(11), TestObj(22), TestObj(33), TestObj(44)) + val coll2 = TrivialImmutableCollection(TestObj(11), TestObj(22), + TestObj(33), TestObj(44)) assertFalse(values.containsAll(coll2)) if (factory.allowsNullValuesQueries) { - val coll3 = ju.Arrays.asList(TestObj(11), TestObj(22), TestObj(33), null) + val coll3 = TrivialImmutableCollection(TestObj(11), TestObj(22), + TestObj(33), null) assertFalse(values.containsAll(coll3)) } } @@ -512,7 +517,7 @@ trait MapTest { assertTrue(mp.containsKey("TWO")) assertTrue(mp.containsKey("THREE")) - values.removeAll(ju.Arrays.asList("one", "two")) + values.removeAll(TrivialImmutableCollection("one", "two")) assertFalse(mp.containsKey("ONE")) assertFalse(mp.containsKey("TWO")) @@ -526,7 +531,7 @@ trait MapTest { assertTrue(mp.containsKey("TWO")) assertTrue(mp.containsKey("THREE")) - values.retainAll(ju.Arrays.asList("one", "two")) + values.retainAll(TrivialImmutableCollection("one", "two")) assertTrue(mp.containsKey("ONE")) assertTrue(mp.containsKey("TWO")) @@ -561,7 +566,7 @@ trait MapTest { assertTrue(mp.containsKey(TestObj(2))) assertTrue(mp.containsKey(TestObj(3))) - values.removeAll(ju.Arrays.asList(TestObj(11), TestObj(22))) + values.removeAll(TrivialImmutableCollection(TestObj(11), TestObj(22))) assertFalse(mp.containsKey(TestObj(1))) assertFalse(mp.containsKey(TestObj(2))) @@ -575,7 +580,7 @@ trait MapTest { assertTrue(mp.containsKey(TestObj(2))) assertTrue(mp.containsKey(TestObj(3))) - values.retainAll(ju.Arrays.asList(TestObj(11), TestObj(22))) + values.retainAll(TrivialImmutableCollection(TestObj(11), TestObj(22))) assertTrue(mp.containsKey(TestObj(1))) assertTrue(mp.containsKey(TestObj(2))) @@ -617,14 +622,14 @@ trait MapTest { assertTrue(keySet.contains("THREE")) - val coll1 = ju.Arrays.asList("ONE", "TWO", "THREE") + val coll1 = TrivialImmutableCollection("ONE", "TWO", "THREE") assertTrue(keySet.containsAll(coll1)) - val coll2 = ju.Arrays.asList("ONE", "TWO", "THREE", "FOUR") + val coll2 = TrivialImmutableCollection("ONE", "TWO", "THREE", "FOUR") assertFalse(keySet.containsAll(coll2)) if (factory.allowsNullKeysQueries) { - val coll3 = ju.Arrays.asList("ONE", "TWO", "THREE", null) + val coll3 = TrivialImmutableCollection("ONE", "TWO", "THREE", null) assertFalse(keySet.containsAll(coll3)) } } @@ -647,14 +652,14 @@ trait MapTest { assertTrue(keySet.contains(TestObj(3))) - val coll1 = ju.Arrays.asList(TestObj(1), TestObj(2), TestObj(3)) + val coll1 = TrivialImmutableCollection(TestObj(1), TestObj(2), TestObj(3)) assertTrue(keySet.containsAll(coll1)) - val coll2 = ju.Arrays.asList(TestObj(1), TestObj(2), TestObj(4)) + val coll2 = TrivialImmutableCollection(TestObj(1), TestObj(2), TestObj(4)) assertFalse(keySet.containsAll(coll2)) if (factory.allowsNullKeysQueries) { - val coll3 = ju.Arrays.asList(TestObj(1), TestObj(2), null) + val coll3 = TrivialImmutableCollection(TestObj(1), TestObj(2), null) assertFalse(keySet.containsAll(coll3)) } } @@ -708,7 +713,7 @@ trait MapTest { assertTrue(mp.containsKey("TWO")) assertTrue(mp.containsKey("THREE")) - keySet.removeAll(ju.Arrays.asList("ONE", "TWO", "FIVE")) + keySet.removeAll(TrivialImmutableCollection("ONE", "TWO", "FIVE")) assertFalse(mp.containsKey("ONE")) assertFalse(mp.containsKey("TWO")) @@ -722,7 +727,7 @@ trait MapTest { assertTrue(mp.containsKey("TWO")) assertTrue(mp.containsKey("THREE")) - keySet.retainAll(ju.Arrays.asList("ONE", "TWO", "FIVE")) + keySet.retainAll(TrivialImmutableCollection("ONE", "TWO", "FIVE")) assertTrue(mp.containsKey("ONE")) assertTrue(mp.containsKey("TWO")) @@ -758,7 +763,8 @@ trait MapTest { assertTrue(mp.containsKey(TestObj(2))) assertTrue(mp.containsKey(TestObj(3))) - keySet.removeAll(ju.Arrays.asList(TestObj(1), TestObj(2), TestObj(5))) + keySet.removeAll(TrivialImmutableCollection(TestObj(1), TestObj(2), + TestObj(5))) assertFalse(mp.containsKey(TestObj(1))) assertFalse(mp.containsKey(TestObj(2))) @@ -772,7 +778,8 @@ trait MapTest { assertTrue(mp.containsKey(TestObj(2))) assertTrue(mp.containsKey(TestObj(3))) - keySet.retainAll(ju.Arrays.asList(TestObj(1), TestObj(2), TestObj(5))) + keySet.retainAll(TrivialImmutableCollection(TestObj(1), TestObj(2), + TestObj(5))) assertTrue(mp.containsKey(TestObj(1))) assertTrue(mp.containsKey(TestObj(2))) @@ -812,20 +819,20 @@ trait MapTest { assertTrue(entrySet.contains(SIE("THREE", "three"))) - val coll1 = ju.Arrays.asList(SIE("ONE", "one"), SIE("TWO", "two"), - SIE("THREE", "three")) + val coll1 = TrivialImmutableCollection(SIE("ONE", "one"), + SIE("TWO", "two"), SIE("THREE", "three")) assertTrue(entrySet.containsAll(coll1)) - val coll2 = ju.Arrays.asList(SIE("ONE", "one"), SIE("TWO", "two"), - SIE("THREE", "three"), SIE("FOUR", "four")) + val coll2 = TrivialImmutableCollection(SIE("ONE", "one"), + SIE("TWO", "two"), SIE("THREE", "three"), SIE("FOUR", "four")) assertFalse(entrySet.containsAll(coll2)) - val coll3 = ju.Arrays.asList(SIE("ONE", "one"), SIE("TWO", "four"), - SIE("THREE", "three")) + val coll3 = TrivialImmutableCollection(SIE("ONE", "one"), + SIE("TWO", "four"), SIE("THREE", "three")) assertFalse(entrySet.containsAll(coll3)) - val coll4 = ju.Arrays.asList(SIE("ONE", "one"), SIE("four", "two"), - SIE("THREE", "three")) + val coll4 = TrivialImmutableCollection(SIE("ONE", "one"), + SIE("four", "two"), SIE("THREE", "three")) assertFalse(entrySet.containsAll(coll4)) } @@ -845,20 +852,20 @@ trait MapTest { assertTrue(entrySet.contains(SIE(TestObj(3), TestObj(33)))) - val coll1 = ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + val coll1 = TrivialImmutableCollection(SIE(TestObj(1), TestObj(11)), SIE(TestObj(2), TestObj(22)), SIE(TestObj(3), TestObj(33))) assertTrue(entrySet.containsAll(coll1)) - val coll2 = ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + val coll2 = TrivialImmutableCollection(SIE(TestObj(1), TestObj(11)), SIE(TestObj(2), TestObj(22)), SIE(TestObj(3), TestObj(33)), SIE(TestObj(4), TestObj(44))) assertFalse(entrySet.containsAll(coll2)) - val coll3 = ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + val coll3 = TrivialImmutableCollection(SIE(TestObj(1), TestObj(11)), SIE(TestObj(2), TestObj(44)), SIE(TestObj(3), TestObj(33))) assertFalse(entrySet.containsAll(coll3)) - val coll4 = ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + val coll4 = TrivialImmutableCollection(SIE(TestObj(1), TestObj(11)), SIE(TestObj(4), TestObj(22)), SIE(TestObj(3), TestObj(33))) assertFalse(entrySet.containsAll(coll4)) } @@ -893,8 +900,8 @@ trait MapTest { assertTrue(mp.containsKey("TWO")) assertTrue(mp.containsKey("THREE")) - entrySet.removeAll(ju.Arrays.asList(SIE("ONE", "one"), SIE("TWO", "two"), - SIE("THREE", "four"), "THREE", 42)) + entrySet.removeAll(TrivialImmutableCollection(SIE("ONE", "one"), + SIE("TWO", "two"), SIE("THREE", "four"), "THREE", 42)) assertFalse(mp.containsKey("ONE")) assertFalse(mp.containsKey("TWO")) @@ -908,8 +915,8 @@ trait MapTest { assertTrue(mp.containsKey("TWO")) assertTrue(mp.containsKey("THREE")) - entrySet.retainAll(ju.Arrays.asList(SIE("ONE", "one"), SIE("TWO", "two"), - SIE("THREE", "four"), "THREE", 42)) + entrySet.retainAll(TrivialImmutableCollection(SIE("ONE", "one"), + SIE("TWO", "two"), SIE("THREE", "four"), "THREE", 42)) assertTrue(mp.containsKey("ONE")) assertTrue(mp.containsKey("TWO")) @@ -946,7 +953,7 @@ trait MapTest { assertTrue(mp.containsKey(TestObj(2))) assertTrue(mp.containsKey(TestObj(3))) - entrySet.removeAll(ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + entrySet.removeAll(TrivialImmutableCollection(SIE(TestObj(1), TestObj(11)), SIE(TestObj(2), TestObj(22)), SIE(TestObj(3), TestObj(44)), TestObj(3), 42)) @@ -962,7 +969,7 @@ trait MapTest { assertTrue(mp.containsKey(TestObj(2))) assertTrue(mp.containsKey(TestObj(3))) - entrySet.retainAll(ju.Arrays.asList(SIE(TestObj(1), TestObj(11)), + entrySet.retainAll(TrivialImmutableCollection(SIE(TestObj(1), TestObj(11)), SIE(TestObj(2), TestObj(22)), SIE(TestObj(3), TestObj(44)), TestObj(3), 42)) @@ -1009,9 +1016,6 @@ trait MapTest { object MapTest { final case class TestObj(num: Int) - - def SIE[K, V](key: K, value: V): ju.Map.Entry[K, V] = - new ju.AbstractMap.SimpleImmutableEntry(key, value) } trait MapFactory { diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/NavigableSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/NavigableSetTest.scala index ca1da08fa0..08a292569d 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/NavigableSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/NavigableSetTest.scala @@ -19,7 +19,6 @@ import java.{util => ju} import org.scalajs.testsuite.javalib.util.concurrent.ConcurrentSkipListSetFactory -import scala.collection.JavaConverters._ import scala.reflect.ClassTag trait NavigableSetTest extends SetTest { @@ -27,7 +26,7 @@ trait NavigableSetTest extends SetTest { def factory: NavigableSetFactory @Test def `should_retrieve_ceiling(ordered)_elements`(): Unit = { - val lInt = Set(1, 5, 2, 3, 4).asJavaCollection + val lInt = TrivialImmutableCollection(1, 5, 2, 3, 4) val nsInt = factory.empty[Int] nsInt.addAll(lInt) @@ -37,7 +36,7 @@ trait NavigableSetTest extends SetTest { assertEquals(1, nsInt.ceiling(1)) assertEquals(5, nsInt.ceiling(5)) - val lString = Set("a", "e", "b", "c", "d").asJavaCollection + val lString = TrivialImmutableCollection("a", "e", "b", "c", "d") val nsString = factory.empty[String] nsString.addAll(lString) @@ -50,7 +49,7 @@ trait NavigableSetTest extends SetTest { } @Test def `should_retrieve_floor(ordered)_elements`(): Unit = { - val lInt = Set(1, 5, 2, 3, 4).asJavaCollection + val lInt = TrivialImmutableCollection(1, 5, 2, 3, 4) val nsInt = factory.empty[Int] nsInt.addAll(lInt) @@ -60,7 +59,7 @@ trait NavigableSetTest extends SetTest { assertEquals(3, nsInt.floor(3)) assertEquals(1, nsInt.floor(1)) - val lString = Set("a", "e", "b", "c", "d").asJavaCollection + val lString = TrivialImmutableCollection("a", "e", "b", "c", "d") val nsString = factory.empty[String] nsString.addAll(lString) @@ -73,7 +72,7 @@ trait NavigableSetTest extends SetTest { } @Test def `should_retrieve_higher(ordered)_elements`(): Unit = { - val lInt = Set(1, 5, 2, 3, 4).asJavaCollection + val lInt = TrivialImmutableCollection(1, 5, 2, 3, 4) val nsInt = factory.empty[Int] nsInt.addAll(lInt) @@ -83,7 +82,7 @@ trait NavigableSetTest extends SetTest { assertEquals(2, nsInt.higher(1)) assertEquals(1, nsInt.higher(-10)) - val lString = Set("a", "e", "b", "c", "d").asJavaCollection + val lString = TrivialImmutableCollection("a", "e", "b", "c", "d") val nsString = factory.empty[String] nsString.addAll(lString) @@ -96,7 +95,7 @@ trait NavigableSetTest extends SetTest { } @Test def `should_retrieve_lower(ordered)_elements`(): Unit = { - val lInt = Set(1, 5, 2, 3, 4).asJavaCollection + val lInt = TrivialImmutableCollection(1, 5, 2, 3, 4) val nsInt = factory.empty[Int] nsInt.addAll(lInt) @@ -106,7 +105,7 @@ trait NavigableSetTest extends SetTest { assertEquals(2, nsInt.lower(3)) assertEquals(5, nsInt.lower(10)) - val lString = Set("a", "e", "b", "c", "d").asJavaCollection + val lString = TrivialImmutableCollection("a", "e", "b", "c", "d") val nsString = factory.empty[String] nsString.addAll(lString) @@ -119,7 +118,7 @@ trait NavigableSetTest extends SetTest { } @Test def should_poll_first_and_last_elements(): Unit = { - val lInt = Set(1, 5, 2, 3, 4).asJavaCollection + val lInt = TrivialImmutableCollection(1, 5, 2, 3, 4) val ns = factory.empty[Int] ns.addAll(lInt) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PriorityQueueTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PriorityQueueTest.scala index 281cd095ad..83b046089c 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PriorityQueueTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PriorityQueueTest.scala @@ -14,7 +14,6 @@ package org.scalajs.testsuite.javalib.util import scala.language.implicitConversions -import scala.collection.JavaConverters._ import scala.reflect.ClassTag import org.junit.Test @@ -127,7 +126,7 @@ class PriorityQueueTest extends CollectionTest { } @Test def could_be_instantiated_with_a_prepopulated_Collection(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val pq = new PriorityQueue[Int](l) assertEquals(5, pq.size()) @@ -138,7 +137,7 @@ class PriorityQueueTest extends CollectionTest { } @Test def could_be_instantiated_with_a_prepopulated_PriorityQueue(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val pq1 = new PriorityQueue[Int](l) val pq2 = new PriorityQueue[Int](pq1) @@ -152,7 +151,7 @@ class PriorityQueueTest extends CollectionTest { } @Test def could_be_instantiated_with_a_prepopulated_SortedSet(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val ss = new java.util.concurrent.ConcurrentSkipListSet[Int](l) val pq1 = new PriorityQueue[Int](l) val pq2 = new PriorityQueue[Int](ss) @@ -167,7 +166,7 @@ class PriorityQueueTest extends CollectionTest { } @Test def should_be_cleared_in_a_single_operation(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val pq = new PriorityQueue[Int](l) assertEquals(5, pq.size()) @@ -176,7 +175,7 @@ class PriorityQueueTest extends CollectionTest { } @Test def should_add_multiple_elemnt_in_one_operation(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val pq = new PriorityQueue[Int]() assertEquals(0, pq.size()) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PropertiesTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PropertiesTest.scala index 33382e3f5b..a1e1754bef 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PropertiesTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PropertiesTest.scala @@ -12,6 +12,7 @@ package org.scalajs.testsuite.javalib.util +import java.{util => ju} import java.util.Properties import org.junit.Test @@ -21,7 +22,7 @@ import org.junit.Assume._ import org.scalajs.testsuite.utils.AssertThrows._ import org.scalajs.testsuite.utils.Platform._ -import scala.collection.JavaConverters._ +import Utils._ class PropertiesTest { @@ -63,18 +64,18 @@ class PropertiesTest { @Test def propertyNames(): Unit = { val prop = new Properties() - assertEquals(0, prop.propertyNames().asScala.size) + assertTrue(enumerationIsEmpty(prop.propertyNames())) prop.setProperty("a", "A") prop.setProperty("b", "B") prop.setProperty("c", "C") - assertEquals(3, prop.propertyNames().asScala.size) - assertEquals(Set("a", "b", "c"), prop.propertyNames().asScala.toSet) + assertEquals(3, enumerationSize(prop.propertyNames())) + assertEnumSameElementsAsSet[Any]("a", "b", "c")(prop.propertyNames()) val prop2 = new Properties(prop) prop.setProperty("c", "CC") prop.setProperty("d", "D") - assertEquals(4, prop2.propertyNames().asScala.size) - assertEquals(Set("a", "b", "c", "d"), prop2.propertyNames().asScala.toSet) + assertEquals(4, enumerationSize(prop2.propertyNames())) + assertEnumSameElementsAsSet[Any]("a", "b", "c", "d")(prop2.propertyNames()) } @Test def propertyNamesWithBadContents(): Unit = { @@ -90,7 +91,7 @@ class PropertiesTest { prop.remove(1.asInstanceOf[AnyRef]) prop.put("1", 1.asInstanceOf[AnyRef]) - assertEquals(Set("a", "b", "c", "1"), prop.propertyNames().asScala.toSet) + assertEnumSameElementsAsSet[Any]("a", "b", "c", "1")(prop.propertyNames()) prop.remove("1") val prop2 = new Properties(prop) @@ -102,7 +103,7 @@ class PropertiesTest { prop2.remove(1.asInstanceOf[AnyRef]) prop2.put("1", 1.asInstanceOf[AnyRef]) - assertEquals(Set("a", "b", "c", "d", "1"), prop2.propertyNames().asScala.toSet) + assertEnumSameElementsAsSet[Any]("a", "b", "c", "d", "1")(prop2.propertyNames()) } @Test def stringPropertyNames(): Unit = { @@ -112,13 +113,13 @@ class PropertiesTest { prop.setProperty("b", "B") prop.setProperty("c", "C") assertEquals(3, prop.stringPropertyNames().size) - assertEquals(Set("a", "b", "c"), prop.stringPropertyNames().asScala.toSet) + assertCollSameElementsAsSet("a", "b", "c")(prop.stringPropertyNames()) val prop2 = new Properties(prop) prop.setProperty("c", "CC") prop.setProperty("d", "D") assertEquals(4, prop2.stringPropertyNames().size) - assertEquals(Set("a", "b", "c", "d"), prop2.stringPropertyNames().asScala.toSet) + assertCollSameElementsAsSet("a", "b", "c", "d")(prop2.stringPropertyNames()) } @Test def stringPropertyNamesWithBadContents(): Unit = { @@ -130,11 +131,11 @@ class PropertiesTest { prop.setProperty("c", "C") prop.put(1.asInstanceOf[AnyRef], "2") - assertEquals(Set("a", "b", "c"), prop.stringPropertyNames().asScala.toSet) + assertCollSameElementsAsSet("a", "b", "c")(prop.stringPropertyNames()) prop.remove(1.asInstanceOf[AnyRef]) prop.put("1", 1.asInstanceOf[AnyRef]) - assertEquals(Set("a", "b", "c"), prop.stringPropertyNames().asScala.toSet) + assertCollSameElementsAsSet("a", "b", "c")(prop.stringPropertyNames()) prop.remove("1") val prop2 = new Properties(prop) @@ -142,10 +143,10 @@ class PropertiesTest { prop.setProperty("d", "D") prop2.put(1.asInstanceOf[AnyRef], "2") - assertEquals(Set("a", "b", "c", "d"), prop2.stringPropertyNames().asScala.toSet) + assertCollSameElementsAsSet("a", "b", "c", "d")(prop2.stringPropertyNames()) prop2.remove(1.asInstanceOf[AnyRef]) prop2.put("1", 1.asInstanceOf[AnyRef]) - assertEquals(Set("a", "b", "c", "d"), prop2.stringPropertyNames().asScala.toSet) + assertCollSameElementsAsSet("a", "b", "c", "d")(prop2.stringPropertyNames()) } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala index 5a2d88b802..ad032f923c 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SetTest.scala @@ -21,9 +21,10 @@ import org.scalajs.testsuite.utils.AssertThrows._ import java.{util => ju, lang => jl} -import scala.collection.JavaConverters._ import scala.reflect.ClassTag +import Utils._ + trait SetTest extends CollectionTest { def factory: SetFactory @@ -130,15 +131,13 @@ trait SetTest extends CollectionTest { assertTrue(hs.add("ONE")) assertTrue(hs.add("TWO")) assertEquals(2, hs.size()) - val l1 = List[String]("ONE", "TWO") - assertTrue(hs.removeAll(l1.asJavaCollection)) + assertTrue(hs.removeAll(TrivialImmutableCollection("ONE", "TWO"))) assertEquals(0, hs.size()) assertTrue(hs.add("ONE")) assertTrue(hs.add("TWO")) assertEquals(2, hs.size()) - val l2 = List[String]("ONE", "THREE") - assertTrue(hs.retainAll(l2.asJavaCollection)) + assertTrue(hs.retainAll(TrivialImmutableCollection("ONE", "THREE"))) assertEquals(1, hs.size()) assertTrue(hs.contains("ONE")) assertFalse(hs.contains("TWO")) @@ -174,18 +173,16 @@ trait SetTest extends CollectionTest { @Test def shouldPutAWholeCollectionInto(): Unit = { val hs = factory.empty[String] + val l = TrivialImmutableCollection("ONE", "TWO", null) + if (factory.allowsNullElement) { - val l = List[String]("ONE", "TWO", (null: String)) - assertTrue(hs.addAll(l.asJavaCollection)) + assertTrue(hs.addAll(l)) assertEquals(3, hs.size) assertTrue(hs.contains("ONE")) assertTrue(hs.contains("TWO")) assertTrue(hs.contains(null)) } else { - expectThrows(classOf[Exception], { - val l = List[String]("ONE", "TWO", (null: String)) - hs.addAll(l.asJavaCollection) - }) + expectThrows(classOf[Exception], hs.addAll(l)) } } @@ -194,23 +191,14 @@ trait SetTest extends CollectionTest { val l = { if (factory.allowsNullElement) - List[String]("ONE", "TWO", (null: String)) + List("ONE", "TWO", null) else - List[String]("ONE", "TWO", "THREE") + List("ONE", "TWO", "THREE") } - assertTrue(hs.addAll(l.asJavaCollection)) + assertTrue(hs.addAll(TrivialImmutableCollection(l: _*))) assertEquals(3, hs.size) - val iter = hs.iterator() - val result = { - for (i <- 0 until 3) yield { - assertTrue(iter.hasNext()) - iter.next() - } - } - assertFalse(iter.hasNext()) - assertTrue(result.asJava.containsAll(l.asJava)) - assertTrue(l.asJava.containsAll(result.asJava)) + assertIteratorSameElementsAsSet(l: _*)(hs.iterator()) } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala index daeda50a80..5538ad2507 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/SortedSetTest.scala @@ -16,7 +16,7 @@ import org.junit.Test import org.junit.Assert._ import java.{util => ju} -import scala.collection.JavaConverters._ + import scala.reflect.ClassTag trait SortedSetTest extends SetTest { @@ -63,7 +63,7 @@ trait SortedSetTest extends SetTest { assertEquals(10000.987, ssDouble.last, 0.0) } - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) @Test def shouldReturnAProperHeadSet(): Unit = { val ss = factory.empty[Int] @@ -71,22 +71,22 @@ trait SortedSetTest extends SetTest { ss.addAll(l) val hs1 = ss.headSet(3) - val l1 = Set(1,2).asJavaCollection + val l1 = TrivialImmutableCollection(1, 2) assertTrue(hs1.containsAll(l1)) assertTrue(hs1.removeAll(l1)) assertTrue(hs1.isEmpty) assertEquals(3, ss.size) - assertTrue(ss.containsAll(Set(3,4,5).asJavaCollection)) + assertTrue(ss.containsAll(TrivialImmutableCollection(3, 4, 5))) ss.addAll(l) val hs2 = ss.headSet(4) - val l2 = Set(1,2,3).asJavaCollection + val l2 = TrivialImmutableCollection(1, 2, 3) assertTrue(hs2.containsAll(l2)) assertTrue(hs2.removeAll(l2)) assertTrue(hs2.isEmpty) assertEquals(2, ss.size) - assertTrue(ss.containsAll(Set(4,5).asJavaCollection)) + assertTrue(ss.containsAll(TrivialImmutableCollection(4, 5))) } @Test def shouldReturnAProperTailSet(): Unit = { @@ -95,22 +95,22 @@ trait SortedSetTest extends SetTest { ss.addAll(l) val ts1 = ss.tailSet(3) - val l3 = Set(3,4,5).asJavaCollection + val l3 = TrivialImmutableCollection(3, 4, 5) assertTrue(ts1.containsAll(l3)) assertTrue(ts1.removeAll(l3)) assertTrue(ts1.isEmpty) assertEquals(2, ss.size) - assertTrue(ss.containsAll(Set(1,2).asJavaCollection)) + assertTrue(ss.containsAll(TrivialImmutableCollection(1, 2))) ss.addAll(l) val ts2 = ss.tailSet(4) - val l4 = Set(4,5).asJavaCollection + val l4 = TrivialImmutableCollection(4, 5) assertTrue(ts2.containsAll(l4)) assertTrue(ts2.removeAll(l4)) assertTrue(ts2.isEmpty) assertEquals(3, ss.size) - assertTrue(ss.containsAll(Set(1,2,3).asJavaCollection)) + assertTrue(ss.containsAll(TrivialImmutableCollection(1, 2, 3))) } @Test def shouldReturnAProperSubSet(): Unit = { @@ -119,12 +119,12 @@ trait SortedSetTest extends SetTest { ss.addAll(l) val ss1 = ss.subSet(2, 4) - val l5 = Set(2,3).asJavaCollection + val l5 = TrivialImmutableCollection(2, 3) assertTrue(ss1.containsAll(l5)) assertTrue(ss1.removeAll(l5)) assertTrue(ss1.isEmpty) assertEquals(3, ss.size) - assertTrue(ss.containsAll(Set(1,4,5).asJavaCollection)) + assertTrue(ss.containsAll(TrivialImmutableCollection(1, 4, 5))) ss.addAll(l) @@ -133,7 +133,7 @@ trait SortedSetTest extends SetTest { assertTrue(ss2.removeAll(l5)) assertFalse(ss2.isEmpty) assertEquals(3, ss.size) - assertTrue(ss.containsAll(Set(1,4,5).asJavaCollection)) + assertTrue(ss.containsAll(TrivialImmutableCollection(1, 4, 5))) } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TreeSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TreeSetTest.scala index 535f793855..f264dfbb72 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TreeSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TreeSetTest.scala @@ -16,8 +16,6 @@ import org.junit.Assert._ import scala.language.implicitConversions -import scala.collection.JavaConverters._ - import org.junit.Test import org.junit.Assert._ import org.junit.Assume._ @@ -179,7 +177,7 @@ abstract class TreeSetTest(val factory: TreeSetFactory) } @Test def could_be_instantiated_with_a_prepopulated_Collection(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val ts = factory.newFrom(l) assertEquals(5, ts.size()) @@ -191,7 +189,7 @@ abstract class TreeSetTest(val factory: TreeSetFactory) } @Test def should_be_cleared_in_a_single_operation(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val ts = factory.empty[Int] ts.addAll(l) @@ -202,7 +200,7 @@ abstract class TreeSetTest(val factory: TreeSetFactory) } @Test def should_add_multiple_element_in_one_operation(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val ts = factory.empty[Int] assertEquals(0, ts.size()) @@ -263,18 +261,16 @@ abstract class TreeSetTest(val factory: TreeSetFactory) } @Test def should_not_put_a_whole_Collection_with_null_elements_into(): Unit = { - val l = List[String]("ONE", "TWO", (null: String)) + val l = TrivialImmutableCollection("ONE", "TWO", (null: String)) val ts1 = factory.empty[String] if (factory.allowsNullElement) { - assertTrue(ts1.addAll(l.asJava)) + assertTrue(ts1.addAll(l)) assertTrue(ts1.contains(null)) assertTrue(ts1.contains("ONE")) assertFalse(ts1.contains("THREE")) } else { - expectThrows(classOf[Exception], { - ts1.addAll(l.asJavaCollection) - }) + expectThrows(classOf[Exception], ts1.addAll(l)) } } @@ -292,7 +288,7 @@ abstract class TreeSetTest(val factory: TreeSetFactory) @Test def should_throw_exceptions_on_access_outside_bound_on_views(): Unit = { assumeTrue("Assumed compliant asInstanceOf", hasCompliantAsInstanceOfs) - val l = Set(2, 3, 6).asJavaCollection + val l = TrivialImmutableCollection(2, 3, 6) val ts = factory.empty[Int] ts.addAll(l) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TrivialImmutableCollection.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TrivialImmutableCollection.scala new file mode 100644 index 0000000000..01bb829ffb --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/TrivialImmutableCollection.scala @@ -0,0 +1,108 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package org.scalajs.testsuite.javalib.util + +import java.{util => ju} + +/** A trivial, "obviously correct" implementation of an immutable + * `java.util.Collection[A]`. + * + * It can be used as an argument to test other collections, notably for their + * bulk operations such as `addAll()`, `removeAll()`, etc. + */ +final class TrivialImmutableCollection[A] private (contents: Array[AnyRef]) + extends ju.Collection[A] { + + def size(): Int = contents.length + + def isEmpty(): Boolean = size() == 0 + + def contains(o: Any): Boolean = { + // scalastyle:off return + var i = 0 + while (i != contents.length) { + if (ju.Objects.equals(o, contents(i))) + return true + i += 1 + } + false + // scalastyle:on return + } + + def iterator(): ju.Iterator[A] = { + new ju.Iterator[A] { + private var nextIndex: Int = 0 + + def hasNext(): Boolean = nextIndex != contents.length + + def next(): A = { + if (!hasNext()) + throw new ju.NoSuchElementException() + val result = contents(nextIndex).asInstanceOf[A] + nextIndex += 1 + result + } + + override def remove(): Unit = + throw new UnsupportedOperationException("Iterator.remove()") + } + } + + def toArray(): Array[AnyRef] = + contents.clone() + + def toArray[T](a: Array[T with AnyRef]): Array[T with AnyRef] = + ju.Arrays.copyOf[T, AnyRef](contents, contents.length, a.getClass()) + + def add(e: A): Boolean = + throw new UnsupportedOperationException("TrivialImmutableCollection.add()") + + def remove(o: Any): Boolean = + throw new UnsupportedOperationException("TrivialImmutableCollection.remove()") + + def containsAll(c: ju.Collection[_]): Boolean = { + // scalastyle:off return + val iter = c.iterator() + while (iter.hasNext()) { + if (!contains(iter.next())) + return false + } + true + // scalastyle:on return + } + + def addAll(c: ju.Collection[_ <: A]): Boolean = + throw new UnsupportedOperationException("TrivialImmutableCollection.addAll()") + + def removeAll(c: ju.Collection[_]): Boolean = + throw new UnsupportedOperationException("TrivialImmutableCollection.removeAll()") + + def retainAll(c: ju.Collection[_]): Boolean = + throw new UnsupportedOperationException("TrivialImmutableCollection.retainAll()") + + def clear(): Unit = + throw new UnsupportedOperationException("TrivialImmutableCollection.clear()") + + /** Returns the `i`th element of this collection. + * + * This method is not part of the API of `java.util.Collection`. It is made + * publicly available to users of `TrivialImmutableCollection` as a + * convenience for tests. + */ + def apply(i: Int): A = contents(i).asInstanceOf[A] +} + +object TrivialImmutableCollection { + def apply[A](elems: A*): TrivialImmutableCollection[A] = + new TrivialImmutableCollection(elems.asInstanceOf[Seq[AnyRef]].toArray) +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/Utils.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/Utils.scala new file mode 100644 index 0000000000..6482e9cd75 --- /dev/null +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/Utils.scala @@ -0,0 +1,98 @@ +/* + * 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.testsuite.javalib.util + +import java.{util => ju} + +import org.junit.Assert._ + +object Utils { + def SIE[K, V](key: K, value: V): ju.Map.Entry[K, V] = + new ju.AbstractMap.SimpleImmutableEntry(key, value) + + def iteratorIsEmpty(iter: ju.Iterator[_]): Boolean = + !iter.hasNext() + + def iteratorSize(iter: ju.Iterator[_]): Int = { + var result = 0 + while (iter.hasNext()) { + iter.next() + result += 1 + } + result + } + + def iteratorMap[A, B](iter: ju.Iterator[A])(f: A => B): ju.Iterator[B] = { + new ju.Iterator[B] { + def hasNext(): Boolean = iter.hasNext() + + def next(): B = f(iter.next()) + + override def remove(): Unit = + throw new UnsupportedOperationException("Iterator.remove()") + } + } + + def enumerationIsEmpty(enum: ju.Enumeration[_]): Boolean = + !enum.hasMoreElements() + + def enumerationSize(enum: ju.Enumeration[_]): Int = { + var result = 0 + while (enum.hasMoreElements()) { + enum.nextElement() + result += 1 + } + result + } + + def assertEnumSameElementsAsSet[A](expected: A*)( + enum: ju.Enumeration[_ <: A]): Unit = { + assertIteratorSameElementsAsSet(expected: _*)(new ju.Iterator[A] { + def hasNext(): Boolean = enum.hasMoreElements() + def next(): A = enum.nextElement() + override def remove(): Unit = + throw new UnsupportedOperationException("Iterator.remove()") + }) + } + + def assertCollSameElementsAsSet[A](expected: A*)( + coll: ju.Collection[A]): Unit = { + assertIteratorSameElementsAsSet(expected: _*)(coll.iterator()) + } + + def assertIteratorSameElementsAsSet[A](expected: A*)( + iter: ju.Iterator[A]): Unit = { + val expectedSet = expected.toSet + var size = 0 + while (iter.hasNext()) { + val elem = iter.next() + assertTrue(s"unexpected element $elem", expectedSet.contains(elem)) + size += 1 + } + assertEquals(expectedSet.size, size) + } + + def assertIteratorSameElementsAsSetDupesAllowed[A](expected: A*)( + iter: ju.Iterator[A]): Unit = { + val expectedSet = expected.toSet + val notSeen = scala.collection.mutable.HashSet[A](expected: _*) + while (iter.hasNext()) { + val value = iter.next() + assertTrue(s"iterator yieled unexpected value $value", + expectedSet.contains(value)) + notSeen -= value + } + assertTrue(s"iterator did not yield expected values $notSeen", + notSeen.isEmpty) + } +} diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentLinkedQueueTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentLinkedQueueTest.scala index 57790fc102..131e030c9b 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentLinkedQueueTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentLinkedQueueTest.scala @@ -17,11 +17,11 @@ import java.{util => ju} import org.junit.Assert._ import org.junit.Test + import org.scalajs.testsuite.javalib.util.{AbstractCollectionFactory, AbstractCollectionTest} -import scala.collection.JavaConverters._ -import scala.language.implicitConversions import scala.reflect.ClassTag +import org.scalajs.testsuite.javalib.util.TrivialImmutableCollection class ConcurrentLinkedQueueTest extends AbstractCollectionTest { @@ -86,18 +86,18 @@ class ConcurrentLinkedQueueTest extends AbstractCollectionTest { } @Test def could_be_instantiated_with_a_prepopulated_Collection(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val pq = factory.newFrom(l) assertEquals(5, pq.size()) - for (i <- l.asScala) { + for (i <- List(1, 5, 2, 3, 4)) { assertEquals(i, pq.poll()) } assertTrue(pq.isEmpty) } @Test def should_be_cleared_in_a_single_operation(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val pq = factory.newFrom(l) assertEquals(5, pq.size()) @@ -106,7 +106,7 @@ class ConcurrentLinkedQueueTest extends AbstractCollectionTest { } @Test def should_add_multiple_elemnt_in_one_operation(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val pq = factory.empty[Int] assertEquals(0, pq.size()) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala index 22e7134ba4..2181d4a0a3 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala @@ -20,11 +20,11 @@ import org.junit.Assume._ import org.junit.Test import org.scalajs.testsuite.javalib.util.NavigableSetFactory +import org.scalajs.testsuite.javalib.util.TrivialImmutableCollection + import org.scalajs.testsuite.utils.AssertThrows._ import org.scalajs.testsuite.utils.Platform._ -import scala.collection.JavaConverters._ -import scala.language.implicitConversions import scala.reflect.ClassTag // TODO extends AbstractSetTest with NavigableSetTest @@ -136,7 +136,7 @@ class ConcurrentSkipListSetTest { } @Test def could_be_instantiated_with_a_prepopulated_Collection(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val csls = factory.newFrom(l) assertEquals(5, csls.size()) @@ -148,7 +148,7 @@ class ConcurrentSkipListSetTest { } @Test def should_be_cleared_in_a_single_operation(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val csls = factory.newFrom(l) assertEquals(5, csls.size()) @@ -157,7 +157,7 @@ class ConcurrentSkipListSetTest { } @Test def should_add_multiple_elemnt_in_one_operation(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val csls = factory.empty[Int] assertEquals(0, csls.size()) @@ -243,7 +243,7 @@ class ConcurrentSkipListSetTest { } @Test def `should_retrieve_ceiling(ordered)_elements`(): Unit = { - val lInt = Set(1, 5, 2, 3, 4).asJavaCollection + val lInt = TrivialImmutableCollection(1, 5, 2, 3, 4) val cslsInt = new ConcurrentSkipListSet[Int](lInt) assertEquals(1, cslsInt.ceiling(-10)) @@ -251,7 +251,7 @@ class ConcurrentSkipListSetTest { assertEquals(1, cslsInt.ceiling(1)) assertEquals(5, cslsInt.ceiling(5)) - val lString = Set("a", "e", "b", "c", "d").asJavaCollection + val lString = TrivialImmutableCollection("a", "e", "b", "c", "d") val cslsString = new ConcurrentSkipListSet[String](lString) assertEquals("a", cslsString.ceiling("00000")) @@ -262,7 +262,7 @@ class ConcurrentSkipListSetTest { } @Test def `should_retrieve_floor(ordered)_elements`(): Unit = { - val lInt = Set(1, 5, 2, 3, 4).asJavaCollection + val lInt = TrivialImmutableCollection(1, 5, 2, 3, 4) val cslsInt = new ConcurrentSkipListSet[Int](lInt) assertEquals(5, cslsInt.floor(10)) @@ -270,7 +270,7 @@ class ConcurrentSkipListSetTest { assertEquals(3, cslsInt.floor(3)) assertEquals(1, cslsInt.floor(1)) - val lString = Set("a", "e", "b", "c", "d").asJavaCollection + val lString = TrivialImmutableCollection("a", "e", "b", "c", "d") val cslsString = new ConcurrentSkipListSet[String](lString) assertEquals("e", cslsString.floor("zzzzz")) @@ -281,7 +281,7 @@ class ConcurrentSkipListSetTest { } @Test def `should_retrieve_higher(ordered)_elements`(): Unit = { - val lInt = Set(1, 5, 2, 3, 4).asJavaCollection + val lInt = TrivialImmutableCollection(1, 5, 2, 3, 4) val cslsInt = new ConcurrentSkipListSet[Int](lInt) assertEquals(5, cslsInt.higher(4)) @@ -289,7 +289,7 @@ class ConcurrentSkipListSetTest { assertEquals(2, cslsInt.higher(1)) assertEquals(1, cslsInt.higher(-10)) - val lString = Set("a", "e", "b", "c", "d").asJavaCollection + val lString = TrivialImmutableCollection("a", "e", "b", "c", "d") val cslsString = new ConcurrentSkipListSet[String](lString) assertNull(cslsString.higher("zzzzz")) @@ -300,7 +300,7 @@ class ConcurrentSkipListSetTest { } @Test def `should_retrieve_lower(ordered)_elements`(): Unit = { - val lInt = Set(1, 5, 2, 3, 4).asJavaCollection + val lInt = TrivialImmutableCollection(1, 5, 2, 3, 4) val cslsInt = new ConcurrentSkipListSet[Int](lInt) assertEquals(4, cslsInt.lower(5)) @@ -308,7 +308,7 @@ class ConcurrentSkipListSetTest { assertEquals(2, cslsInt.lower(3)) assertEquals(5, cslsInt.lower(10)) - val lString = Set("a", "e", "b", "c", "d").asJavaCollection + val lString = TrivialImmutableCollection("a", "e", "b", "c", "d") val cslsString = new ConcurrentSkipListSet[String](lString) assertEquals("e", cslsString.lower("zzzzz")) @@ -319,7 +319,7 @@ class ConcurrentSkipListSetTest { } @Test def should_poll_first_and_last_elements(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val csls = new ConcurrentSkipListSet[Int](l) assertTrue(csls.contains(1)) @@ -333,56 +333,56 @@ class ConcurrentSkipListSetTest { } @Test def should_get_partial_views_that_are_backed_on_the_original_list(): Unit = { - val l = Set(1, 5, 2, 3, 4).asJavaCollection + val l = TrivialImmutableCollection(1, 5, 2, 3, 4) val csls = new ConcurrentSkipListSet[Int](l) val hs1 = csls.headSet(3) - val l1 = Set(1,2).asJavaCollection + val l1 = TrivialImmutableCollection(1, 2) assertTrue(hs1.containsAll(l1)) assertTrue(hs1.removeAll(l1)) assertTrue(hs1.isEmpty) assertEquals(3, csls.size) - assertTrue(csls.containsAll(Set(3,4,5).asJavaCollection)) + assertTrue(csls.containsAll(TrivialImmutableCollection(3, 4, 5))) csls.addAll(l) val hs2 = csls.headSet(3, true) - val l2 = Set(1,2,3).asJavaCollection + val l2 = TrivialImmutableCollection(1, 2, 3) assertTrue(hs2.containsAll(l2)) assertTrue(hs2.removeAll(l2)) assertTrue(hs2.isEmpty) assertEquals(2, csls.size) - assertTrue(csls.containsAll(Set(4,5).asJavaCollection)) + assertTrue(csls.containsAll(TrivialImmutableCollection(4, 5))) csls.addAll(l) val ts1 = csls.tailSet(3) - val l3 = Set(3,4,5).asJavaCollection + val l3 = TrivialImmutableCollection(3, 4, 5) assertTrue(ts1.containsAll(l3)) assertTrue(ts1.removeAll(l3)) assertTrue(ts1.isEmpty) assertEquals(2, csls.size) - assertTrue(csls.containsAll(Set(1,2).asJavaCollection)) + assertTrue(csls.containsAll(TrivialImmutableCollection(1, 2))) csls.addAll(l) val ts2 = csls.tailSet(3, false) - val l4 = Set(4,5).asJavaCollection + val l4 = TrivialImmutableCollection(4, 5) assertTrue(ts2.containsAll(l4)) assertTrue(ts2.removeAll(l4)) assertTrue(ts2.isEmpty) assertEquals(3, csls.size) - assertTrue(csls.containsAll(Set(1,2,3).asJavaCollection)) + assertTrue(csls.containsAll(TrivialImmutableCollection(1, 2, 3))) csls.addAll(l) val ss1 = csls.subSet(2, true, 3, true) - val l5 = Set(2,3).asJavaCollection + val l5 = TrivialImmutableCollection(2, 3) assertTrue(ss1.containsAll(l5)) assertTrue(ss1.removeAll(l5)) assertTrue(ss1.isEmpty) assertEquals(3, csls.size) - assertTrue(csls.containsAll(Set(1,4,5).asJavaCollection)) + assertTrue(csls.containsAll(TrivialImmutableCollection(1, 4, 5))) csls.addAll(l) @@ -391,7 +391,7 @@ class ConcurrentSkipListSetTest { assertTrue(ss2.removeAll(l5)) assertTrue(ss2.isEmpty) assertEquals(3, csls.size) - assertTrue(csls.containsAll(Set(1,4,5).asJavaCollection)) + assertTrue(csls.containsAll(TrivialImmutableCollection(1, 4, 5))) } @Test def should_throw_exception_on_non_comparable_objects(): Unit = { diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/CopyOnWriteArrayListTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/CopyOnWriteArrayListTest.scala index c2a7b5b053..9256100141 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/CopyOnWriteArrayListTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/CopyOnWriteArrayListTest.scala @@ -18,8 +18,7 @@ import org.junit.Assert._ import org.junit.Test import org.scalajs.testsuite.javalib.util.{ListFactory, ListTest} - -import scala.collection.JavaConverters._ +import org.scalajs.testsuite.javalib.util.TrivialImmutableCollection import scala.reflect.ClassTag @@ -47,27 +46,27 @@ class CopyOnWriteArrayListTest extends ListTest { @Test def should_implement_addAllAbsent(): Unit = { val list = factory.empty[Int] - assertEquals(3, list.addAllAbsent((0 until 3).asJava)) + assertEquals(3, list.addAllAbsent(TrivialImmutableCollection((0 until 3): _*))) assertEquals(3, list.size) for (i <- 0 until 3) assertEquals(i, list.get(i)) - assertEquals(0, list.addAllAbsent((0 until 2).asJava)) + assertEquals(0, list.addAllAbsent(TrivialImmutableCollection((0 until 2): _*))) assertEquals(3, list.size) for (i <- 0 until 3) assertEquals(i, list.get(i)) - assertEquals(3, list.addAllAbsent((3 until 6).asJava)) + assertEquals(3, list.addAllAbsent(TrivialImmutableCollection((3 until 6): _*))) assertEquals(6, list.size) for (i <- 0 until 6) assertEquals(i, list.get(i)) - assertEquals(4, list.addAllAbsent((0 until 10).asJava)) + assertEquals(4, list.addAllAbsent(TrivialImmutableCollection((0 until 10): _*))) assertEquals(10, list.size) for (i <- 0 until 10) assertEquals(i, list.get(i)) - assertEquals(1, list.addAllAbsent(Seq(42, 42, 42).asJava)) + assertEquals(1, list.addAllAbsent(TrivialImmutableCollection(42, 42, 42))) assertEquals(11, list.size) for (i <- 0 until 10) assertEquals(i, list.get(i)) @@ -76,12 +75,12 @@ class CopyOnWriteArrayListTest extends ListTest { @Test def should_implement_a_snapshot_iterator(): Unit = { val list = factory.empty[Int] - list.addAll((0 to 10).asJava) + list.addAll(TrivialImmutableCollection((0 to 10): _*)) val iter = list.iterator() list.clear() val iter2 = list.iterator() - list.addAll((0 to 5).asJava) + list.addAll(TrivialImmutableCollection((0 to 5): _*)) for (i <- 0 to 10) { assertTrue(iter.hasNext) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niocharset/CharsetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niocharset/CharsetTest.scala index 10dd36c3d9..0fb13632ee 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/niocharset/CharsetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/niocharset/CharsetTest.scala @@ -12,19 +12,19 @@ package org.scalajs.testsuite.niocharset -import scala.collection.JavaConverters._ - import java.nio.charset._ import java.nio.charset.StandardCharsets._ import org.junit.Test import org.junit.Assert._ +import org.scalajs.testsuite.javalib.util.TrivialImmutableCollection import org.scalajs.testsuite.utils.AssertThrows._ import org.scalajs.testsuite.utils.Platform.executingInJVM class CharsetTest { - def javaSet[A](elems: A*): java.util.Set[A] = Set(elems: _*).asJava + def javaSet[A](elems: A*): java.util.Set[A] = + new java.util.HashSet(TrivialImmutableCollection(elems: _*)) @Test def defaultCharset(): Unit = { assertSame(UTF_8, Charset.defaultCharset()) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/CollectionsTestBase.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/CollectionsTestBase.scala index 8c23de37f2..a61c4bb7d6 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/CollectionsTestBase.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/utils/CollectionsTestBase.scala @@ -16,12 +16,16 @@ import java.{lang => jl, util => ju} import org.scalajs.testsuite.utils.AssertThrows._ -import scala.collection.JavaConverters._ +import org.scalajs.testsuite.javalib.util.TrivialImmutableCollection +import org.scalajs.testsuite.javalib.util.TrivialImmutableMap trait CollectionsTestBase { val range: Range = 0 to 30 + def rangeOfElems[A](toElem: Int => A): TrivialImmutableCollection[A] = + TrivialImmutableCollection(range.map(toElem): _*) + class A extends jl.Comparable[A] { def compareTo(o: A): Int = this.##.compareTo(o.##) } @@ -39,15 +43,13 @@ trait CollectionsTestBase { } def testCollectionUnmodifiability[E](coll: ju.Collection[E], elem: E): Unit = { + val empty = TrivialImmutableCollection[E]() expectThrows(classOf[UnsupportedOperationException], coll.add(elem)) - expectThrows(classOf[UnsupportedOperationException], - coll.addAll(Seq.empty[E].asJava)) + expectThrows(classOf[UnsupportedOperationException], coll.addAll(empty)) expectThrows(classOf[UnsupportedOperationException], coll.clear()) expectThrows(classOf[UnsupportedOperationException], coll.remove(elem)) - expectThrows(classOf[UnsupportedOperationException], - coll.removeAll(Seq.empty[E].asJava)) - expectThrows(classOf[UnsupportedOperationException], - coll.retainAll(Seq.empty[E].asJava)) + expectThrows(classOf[UnsupportedOperationException], coll.removeAll(empty)) + expectThrows(classOf[UnsupportedOperationException], coll.retainAll(empty)) testIteratorsUnmodifiability(() => coll.iterator()) } @@ -71,7 +73,7 @@ trait CollectionsTestBase { testCollectionUnmodifiability(list, elem) expectThrows(classOf[UnsupportedOperationException], list.add(0, elem)) expectThrows(classOf[UnsupportedOperationException], - list.addAll(0, Seq.empty[E].asJava)) + list.addAll(0, TrivialImmutableCollection[E]())) expectThrows(classOf[UnsupportedOperationException], list.remove(0)) expectThrows(classOf[UnsupportedOperationException], list.set(0, elem)) def testSublist(sl: ju.List[E]): Unit = { @@ -100,7 +102,7 @@ trait CollectionsTestBase { expectThrows(classOf[UnsupportedOperationException], map.clear()) expectThrows(classOf[UnsupportedOperationException], map.put(key, value)) expectThrows(classOf[UnsupportedOperationException], - map.putAll(Map.empty[K, V].asJava)) + map.putAll(TrivialImmutableMap[K, V]())) testSetUnmodifiability(map.entrySet(), new ju.AbstractMap.SimpleImmutableEntry(key, value)) testSetUnmodifiability(map.keySet(), key) From 61972891e76912d7f51d55e497b914b4f8d41f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 14 Aug 2019 13:49:02 +0200 Subject: [PATCH 0063/1606] Remove two unused imports of `JavaConverters`. This removes the last references to `JavaConverters` in our codebase, excluding scalalib overrides and comments. --- js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala | 1 - .../main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala | 1 - 2 files changed, 2 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala index bef59e9208..0c2d73fe76 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/ExternalJSEnv.scala @@ -19,7 +19,6 @@ import org.scalajs.core.tools.jsdep.ResolvedJSDependency import java.io.{ Console => _, _ } import scala.io.Source -import scala.collection.JavaConverters._ import scala.concurrent.{Future, Promise} import scala.util.Try diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala index 74eaa5c49a..08b52870cd 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala @@ -24,7 +24,6 @@ import org.scalajs.core.tools.logging.NullLogger import org.scalajs.jsenv._ import org.scalajs.jsenv.Utils.OptDeadline -import scala.collection.JavaConverters._ import scala.concurrent.TimeoutException import scala.concurrent.duration._ From 934ba738b0251ecab602ebdd387edbe4285164df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 14 Aug 2019 14:50:06 +0200 Subject: [PATCH 0064/1606] Rewrite the collection behind `AbstractCollectionTest` not to use `Box`. This removes the last use of `Box` and `===` in the test suite of JDK collections, allowing to get rid of those. --- .../javalib/util/AbstractCollectionTest.scala | 104 +++++++++++------- .../testsuite/javalib/util/package.scala | 39 ------- 2 files changed, 66 insertions(+), 77 deletions(-) delete mode 100644 test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/package.scala diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/AbstractCollectionTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/AbstractCollectionTest.scala index c0b0729875..c383dd7abd 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/AbstractCollectionTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/AbstractCollectionTest.scala @@ -21,54 +21,82 @@ class AbstractCollectionTest extends CollectionTest { } class AbstractCollectionFactory extends CollectionFactory { + import AbstractCollectionFactory._ override def implementationName: String = "java.util.AbstractCollection" - override def empty[E: ClassTag]: ju.AbstractCollection[E] = { - // inefficient but simple for debugging implementation of AbstractCollection - new ju.AbstractCollection[E] { + override def empty[E: ClassTag]: ju.AbstractCollection[E] = + new AbstractCollectionImpl[E] - private val inner = scala.collection.mutable.Set.empty[Box[E]] +} + +object AbstractCollectionFactory { + /* A mutable implementation of `java.util.Collection[E]` that relies on all + * the default behaviors implemented in `j.u.AbstractCollection`. + * + * Every modification allocates a new internal `Array`. This property is used + * to reliably detect concurrent modifications. + */ + private final class AbstractCollectionImpl[E] extends ju.AbstractCollection[E] { + private var inner = new Array[AnyRef](0) + + override def add(elem: E): Boolean = { + inner = ju.Arrays.copyOf(inner, inner.length + 1) + inner(inner.length - 1) = elem.asInstanceOf[AnyRef] + true + } + + def size(): Int = + inner.length + + def iterator(): ju.Iterator[E] = + new AbstractCollectionImplIterator(inner) + + private final class AbstractCollectionImplIterator[E]( + private var iterInner: Array[AnyRef]) + extends ju.Iterator[E] { + + private[this] var nextIndex: Int = 0 + private[this] var canRemove: Boolean = false - override def add(elem: E): Boolean = { - val canAdd = !inner(Box(elem)) - if (canAdd) - inner += Box(elem) - canAdd + def hasNext(): Boolean = { + checkConcurrentModification() + nextIndex != inner.length } - def size(): Int = - inner.size - - override def iterator(): ju.Iterator[E] = { - new ju.Iterator[E] { - val innerIter = inner.iterator - - var last: Option[E] = None - - def next(): E = { - val elem = innerIter.next().inner - last = Option(elem) - elem - } - - override def remove(): Unit = { - last match { - case Some(elem) => - inner -= Box(elem) - last = None - case None => - throw new IllegalStateException() - } - } - - def hasNext: Boolean = { - innerIter.hasNext - } + def next(): E = { + checkConcurrentModification() + if (nextIndex == inner.length) + throw new ju.NoSuchElementException() + + val elem = inner(nextIndex).asInstanceOf[E] + nextIndex += 1 + canRemove = true + elem + } + + override def remove(): Unit = { + checkConcurrentModification() + if (!canRemove) + throw new IllegalStateException("remove() called before next()") + + nextIndex -= 1 + val newInner = ju.Arrays.copyOf(iterInner, iterInner.length - 1) + var i = nextIndex + while (i != newInner.length) { + newInner(i) = iterInner(i + 1) + i += 1 } + inner = newInner + iterInner = newInner + canRemove = false + } + + private def checkConcurrentModification(): Unit = { + if (inner ne iterInner) + throw new ju.ConcurrentModificationException() } } } - } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/package.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/package.scala deleted file mode 100644 index ae30e1d439..0000000000 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/package.scala +++ /dev/null @@ -1,39 +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. - */ - -package org.scalajs.testsuite.javalib - -package object util { - - implicit private[util] class CompareNullablesOps(val self: Any) extends AnyVal { - @inline - def ===(that: Any): Boolean = - if (self.asInstanceOf[AnyRef] eq null) that.asInstanceOf[AnyRef] eq null - else self.equals(that) - } - - private[util] final case class Box[+K](inner: K) { - def apply(): K = inner - - override def equals(o: Any): Boolean = { - o match { - case o: Box[_] => inner === o.inner - case _ => false - } - } - - override def hashCode(): Int = - if (inner == null) 0 - else inner.hashCode - } - -} From 8c4f4488924f7ff769f9d7c11de150ec3c3795ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 31 Jul 2019 13:07:45 +0200 Subject: [PATCH 0065/1606] Make `ConcurrentSkipListSet` independent of the internals of `TreeSet`. In the JDK, `TreeSet.add()` specifically throws when adding the first element, if it is not comparable to itself. This behavior was introduced on purpose in JDK 7, so our `TreeSet` mimics that. `ConcurrentSkipListSet`, however, allows the first element to be added even if it is not comparable to itself. Since our implementation of `ConcurrentSkipListSet` uses an inner `TreeSet`, it would normally throw in that case. We had some code to customize the inner `TreeSet` to cancel that throwing, but it was relying on internal implementation details of `TreeSet`. In this commit, we remove that specific code. This allows to make `TreeSet.inner` private, hiding the fact that it uses an `s.c.m.TreeSet` inside. However, this means that we relax a bit the requirements of `ConcurrentSkipListSet`, as it will now throw when trying to insert the first element if it is not comparable to itself. The change in the tests highlights this change of behavior. --- javalib/src/main/scala/java/util/TreeSet.scala | 2 +- .../java/util/concurrent/ConcurrentSkipListSet.scala | 10 +++------- .../util/concurrent/ConcurrentSkipListSetTest.scala | 7 +++++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/javalib/src/main/scala/java/util/TreeSet.scala b/javalib/src/main/scala/java/util/TreeSet.scala index 59889e3482..85e42a099e 100644 --- a/javalib/src/main/scala/java/util/TreeSet.scala +++ b/javalib/src/main/scala/java/util/TreeSet.scala @@ -51,7 +51,7 @@ class TreeSet[E] (_comparator: Comparator[_ >: E]) } - protected val inner: mutable.TreeSet[Box[E]] = new mutable.TreeSet[Box[E]]() + private val inner: mutable.TreeSet[Box[E]] = new mutable.TreeSet[Box[E]]() def iterator(): Iterator[E] = { new Iterator[E] { diff --git a/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala b/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala index e007f472eb..c668e577c4 100644 --- a/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala +++ b/javalib/src/main/scala/java/util/concurrent/ConcurrentSkipListSet.scala @@ -20,15 +20,11 @@ class ConcurrentSkipListSet[E] private (inner: TreeSet[E]) with Cloneable with Serializable { - def this(collection: Collection[_ <: E]) = { - this(new TreeSet[E](collection) { - override def add(e: E): Boolean = - inner.add(Box(e)) - }) - } + def this(collection: Collection[_ <: E]) = + this(new TreeSet[E](collection)) def this() = - this(Collections.emptySet[E]: Collection[E]) + this(new TreeSet[E]()) def this(comparator: Comparator[_ >: E]) = this(new TreeSet[E](comparator)) diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala index 2181d4a0a3..e6de7376be 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/concurrent/ConcurrentSkipListSetTest.scala @@ -404,8 +404,11 @@ class ConcurrentSkipListSetTest { val csls = new ConcurrentSkipListSet[TestObj]() assertEquals(0, csls.size()) - csls.add(new TestObj(111)) - expectThrows(classOf[ClassCastException], csls.add(new TestObj(222))) + expectThrows(classOf[ClassCastException], { + // Throw either when the first or second element is added + csls.add(new TestObj(111)) + csls.add(new TestObj(222)) + }) assertNull(csls.comparator) } } From bd5683ac5d4bfae9fa4268cb44f8d15bb41d8eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 1 Aug 2019 11:16:43 +0200 Subject: [PATCH 0066/1606] Support a `null` argument in `Collections.reserveOrder(Comparator)`. --- .../main/scala/java/util/Collections.scala | 8 +++-- .../javalib/util/CollectionsTest.scala | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/javalib/src/main/scala/java/util/Collections.scala b/javalib/src/main/scala/java/util/Collections.scala index 3ad292c7e4..2732559402 100644 --- a/javalib/src/main/scala/java/util/Collections.scala +++ b/javalib/src/main/scala/java/util/Collections.scala @@ -518,8 +518,12 @@ object Collections { } def reverseOrder[T](cmp: Comparator[T]): Comparator[T] = { - new Comparator[T] with Serializable { - override def compare(o1: T, o2: T): Int = cmp.compare(o2, o1) + if (cmp eq null) { + reverseOrder() + } else { + new Comparator[T] with Serializable { + override def compare(o1: T, o2: T): Int = cmp.compare(o2, o1) + } } } diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsTest.scala index c9343f52d5..845ba5de38 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/CollectionsTest.scala @@ -268,6 +268,40 @@ class CollectionsTest extends CollectionsTestBase { } } + @Test def reverseOrder_with_null_comparator(): Unit = { + // Essentially equivalent to reverseOrder_on_comparables + + def testNumerical[E](toElem: Int => E): Unit = { + val rCmp = ju.Collections.reverseOrder[E](null) + for (i <- range) { + assertEquals(0, rCmp.compare(toElem(i), toElem(i))) + assertTrue(rCmp.compare(toElem(i), toElem(i - 1)) < 0) + assertTrue(rCmp.compare(toElem(i), toElem(i + 1)) > 0) + } + } + + testNumerical[Int](_.toInt) + testNumerical[Long](_.toLong) + testNumerical[Double](_.toDouble) + + val rCmp = ju.Collections.reverseOrder[String](null) + + assertEquals(0, rCmp.compare("", "")) + assertEquals(0, rCmp.compare("a", "a")) + assertEquals(0, rCmp.compare("123", "123")) + assertEquals(0, rCmp.compare("hello world", "hello world")) + + assertTrue(rCmp.compare("a", "b") > 0) + assertTrue(rCmp.compare("a", "ba") > 0) + assertTrue(rCmp.compare("a", "aa") > 0) + assertTrue(rCmp.compare("aa", "aaa") > 0) + + assertTrue(rCmp.compare("b", "a") < 0) + assertTrue(rCmp.compare("ba", "a") < 0) + assertTrue(rCmp.compare("aa", "a") < 0) + assertTrue(rCmp.compare("aaa", "aa") < 0) + } + @Test def enumeration(): Unit = { val coll = TrivialImmutableCollection(range: _*) val enum = ju.Collections.enumeration(coll) From 0fd4ad9d707ec24762ed40bed87af8181c4be0ed Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Tue, 6 Aug 2019 20:54:46 +0200 Subject: [PATCH 0067/1606] Fix #3616: Do not force all JSEnv inputs to be of the same type --- .../org/scalajs/jsenv/test/RunTests.scala | 6 +- .../org/scalajs/jsenv/test/kit/TestKit.scala | 20 +-- .../main/scala/org/scalajs/jsenv/Input.scala | 22 ++- .../main/scala/org/scalajs/jsenv/JSEnv.scala | 10 +- .../org/scalajs/jsenv/nodejs/NodeJSEnv.scala | 155 +++++++++--------- .../scala/tools/nsc/MainGenericRunner.scala | 2 +- project/Build.scala | 26 +-- project/NodeJSEnvForcePolyfills.scala | 18 +- .../scala/org/scalajs/sbtplugin/Run.scala | 2 +- .../org/scalajs/sbtplugin/ScalaJSPlugin.scala | 4 +- .../sbtplugin/ScalaJSPluginInternal.scala | 14 +- .../testing/adapter/HTMLRunnerBuilder.scala | 8 +- .../scalajs/testing/adapter/JSEnvRPC.scala | 2 +- .../scalajs/testing/adapter/TestAdapter.scala | 2 +- 14 files changed, 130 insertions(+), 161 deletions(-) diff --git a/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/RunTests.scala b/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/RunTests.scala index 63d8824d65..feee0fabb8 100644 --- a/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/RunTests.scala +++ b/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/RunTests.scala @@ -27,7 +27,7 @@ import org.scalajs.jsenv.test.kit.{TestKit, Run} private[test] class RunTests(config: JSEnvSuiteConfig, withCom: Boolean) { private val kit = new TestKit(config.jsEnv, config.awaitTimeout) - private def withRun(input: Input)(body: Run => Unit) = { + private def withRun(input: Seq[Input])(body: Run => Unit) = { if (withCom) kit.withComRun(input)(body) else kit.withRun(input)(body) } @@ -141,7 +141,7 @@ private[test] class RunTests(config: JSEnvSuiteConfig, withCom: Boolean) { val badFile = Jimfs.newFileSystem().getPath("nonexistent") // `start` may not throw but must fail asynchronously - withRun(Input.ScriptsToLoad(badFile :: Nil)) { + withRun(Input.Script(badFile) :: Nil) { _.fails() } } @@ -155,7 +155,7 @@ private[test] class RunTests(config: JSEnvSuiteConfig, withCom: Boolean) { val tmpPath = tmpFile.toPath Files.write(tmpPath, "console.log(\"test\");".getBytes(StandardCharsets.UTF_8)) - withRun(Input.ScriptsToLoad(tmpPath :: Nil)) { + withRun(Input.Script(tmpPath) :: Nil) { _.expectOut("test\n") .closeRun() } diff --git a/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/kit/TestKit.scala b/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/kit/TestKit.scala index 889c3e5583..7049dad73e 100644 --- a/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/kit/TestKit.scala +++ b/js-envs-test-kit/src/main/scala/org/scalajs/jsenv/test/kit/TestKit.scala @@ -56,7 +56,7 @@ final class TestKit(jsEnv: JSEnv, timeout: FiniteDuration) { start(codeToInput(code)) /** Starts a [[Run]] for testing. */ - def start(input: Input): Run = + def start(input: Seq[Input]): Run = start(input, RunConfig()) /** Starts a [[Run]] for testing. */ @@ -64,7 +64,7 @@ final class TestKit(jsEnv: JSEnv, timeout: FiniteDuration) { start(codeToInput(code), config) /** Starts a [[Run]] for testing. */ - def start(input: Input, config: RunConfig): Run = { + def start(input: Seq[Input], config: RunConfig): Run = { val (run, out, err) = io(config)(jsEnv.start(input, _)) new Run(run, out, err, timeout) } @@ -74,7 +74,7 @@ final class TestKit(jsEnv: JSEnv, timeout: FiniteDuration) { startWithCom(codeToInput(code)) /** Starts a [[ComRun]] for testing. */ - def startWithCom(input: Input): ComRun = + def startWithCom(input: Seq[Input]): ComRun = startWithCom(input, RunConfig()) /** Starts a [[ComRun]] for testing. */ @@ -82,7 +82,7 @@ final class TestKit(jsEnv: JSEnv, timeout: FiniteDuration) { startWithCom(codeToInput(code), config) /** Starts a [[ComRun]] for testing. */ - def startWithCom(input: Input, config: RunConfig): ComRun = { + def startWithCom(input: Seq[Input], config: RunConfig): ComRun = { val msg = new MsgHandler val (run, out, err) = io(config)(jsEnv.startWithCom(input, _, msg.onMessage _)) run.future.onComplete(msg.onRunComplete _)(TestKit.completer) @@ -95,7 +95,7 @@ final class TestKit(jsEnv: JSEnv, timeout: FiniteDuration) { withRun(codeToInput(code))(body) /** Convenience method to start a [[Run]] and close it after usage. */ - def withRun[T](input: Input)(body: Run => T): T = + def withRun[T](input: Seq[Input])(body: Run => T): T = withRun(input, RunConfig())(body) /** Convenience method to start a [[Run]] and close it after usage. */ @@ -103,7 +103,7 @@ final class TestKit(jsEnv: JSEnv, timeout: FiniteDuration) { withRun(codeToInput(code), config)(body) /** Convenience method to start a [[Run]] and close it after usage. */ - def withRun[T](input: Input, config: RunConfig)(body: Run => T): T = { + def withRun[T](input: Seq[Input], config: RunConfig)(body: Run => T): T = { val run = start(input, config) try body(run) finally run.close() @@ -113,14 +113,14 @@ final class TestKit(jsEnv: JSEnv, timeout: FiniteDuration) { def withComRun[T](code: String)(body: ComRun => T): T = withComRun(codeToInput(code))(body) /** Convenience method to start a [[ComRun]] and close it after usage. */ - def withComRun[T](input: Input)(body: ComRun => T): T = withComRun(input, RunConfig())(body) + def withComRun[T](input: Seq[Input])(body: ComRun => T): T = withComRun(input, RunConfig())(body) /** Convenience method to start a [[ComRun]] and close it after usage. */ def withComRun[T](code: String, config: RunConfig)(body: ComRun => T): T = withComRun(codeToInput(code), config)(body) /** Convenience method to start a [[ComRun]] and close it after usage. */ - def withComRun[T](input: Input, config: RunConfig)(body: ComRun => T): T = { + def withComRun[T](input: Seq[Input], config: RunConfig)(body: ComRun => T): T = { val run = startWithCom(input, config) try body(run) finally run.close() @@ -154,10 +154,10 @@ private object TestKit { private val completer = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor()) - private def codeToInput(code: String): Input = { + private def codeToInput(code: String): Seq[Input] = { val p = Files.write( Jimfs.newFileSystem().getPath("testScript.js"), code.getBytes(StandardCharsets.UTF_8)) - Input.ScriptsToLoad(List(p)) + List(Input.Script(p)) } } diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/Input.scala b/js-envs/src/main/scala/org/scalajs/jsenv/Input.scala index 3ad4a82344..a3440e97cf 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/Input.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/Input.scala @@ -26,26 +26,24 @@ import java.nio.file.Path abstract class Input private () object Input { - /** All files are to be loaded as scripts into the global scope in the order given. */ - final case class ScriptsToLoad(scripts: List[Path]) extends Input + /** The file is to be loaded as a script into the global scope. */ + final case class Script(script: Path) extends Input - /** All files are to be loaded as ES modules, in the given order. + /** The file is to be loaded as an ES module. * - * Some environments may not be able to execute several ES modules in a + * Some environments may not be able to load several ES modules in a * deterministic order. If that is the case, they must reject an - * `ESModulesToLoad` input if the `modules` argument has more than one - * element. + * `ESModule` input if it appears with other Inputs such that loading + * in a deterministic order is not possible. */ - final case class ESModulesToLoad(modules: List[Path]) - extends Input + final case class ESModule(module: Path) extends Input - /** All files are to be loaded as CommonJS modules, in the given order. */ - final case class CommonJSModulesToLoad(modules: List[Path]) - extends Input + /** The file is to be loaded as a CommonJS module. */ + final case class CommonJSModule(module: Path) extends Input } class UnsupportedInputException(msg: String, cause: Throwable) extends IllegalArgumentException(msg, cause) { def this(msg: String) = this(msg, null) - def this(input: Input) = this(s"Unsupported input: $input") + def this(input: Seq[Input]) = this(s"Unsupported input: $input") } diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/JSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/JSEnv.scala index 091afeb168..1e7b400fbe 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/JSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/JSEnv.scala @@ -33,10 +33,12 @@ trait JSEnv { * with the input's content (e.g. file does not exist, syntax error, etc.). * In this case, [[JSRun#future]] should be failed instead. * - * @throws java.lang.IllegalArgumentException if the value of `input` or - * `config` cannot be supported. + * @throws UnsupportedInputException if the value of `input` cannot be + * supported. + * @throws java.lang.IllegalArgumentException if the value of `config` cannot + * be supported. */ - def start(input: Input, config: RunConfig): JSRun + def start(input: Seq[Input], config: RunConfig): JSRun /** Like [[start]], but initializes a communication channel. * @@ -83,6 +85,6 @@ trait JSEnv { * [[JSRun#future]] of the returned [[JSComRun]] is completed. Further, * [[JSRun#future]] may only complete with no callback in-flight. */ - def startWithCom(input: Input, config: RunConfig, + def startWithCom(input: Seq[Input], config: RunConfig, onMessage: String => Unit): JSComRun } diff --git a/nodejs-env/src/main/scala/org/scalajs/jsenv/nodejs/NodeJSEnv.scala b/nodejs-env/src/main/scala/org/scalajs/jsenv/nodejs/NodeJSEnv.scala index 26ab9fee5d..c50fee5783 100644 --- a/nodejs-env/src/main/scala/org/scalajs/jsenv/nodejs/NodeJSEnv.scala +++ b/nodejs-env/src/main/scala/org/scalajs/jsenv/nodejs/NodeJSEnv.scala @@ -33,49 +33,40 @@ final class NodeJSEnv(config: NodeJSEnv.Config) extends JSEnv { val name: String = "Node.js" - def start(input: Input, runConfig: RunConfig): JSRun = { + def start(input: Seq[Input], runConfig: RunConfig): JSRun = { NodeJSEnv.validator.validate(runConfig) validateInput(input) - internalStart(initFiles, input, runConfig) + internalStart(initFiles ++ input, runConfig) } - def startWithCom(input: Input, runConfig: RunConfig, + def startWithCom(input: Seq[Input], runConfig: RunConfig, onMessage: String => Unit): JSComRun = { NodeJSEnv.validator.validate(runConfig) validateInput(input) ComRun.start(runConfig, onMessage) { comLoader => - internalStart(initFiles :+ comLoader, input, runConfig) + internalStart(initFiles ++ (Input.Script(comLoader) +: input), runConfig) } } - private def validateInput(input: Input): Unit = { - input match { - case _:Input.ScriptsToLoad | _:Input.ESModulesToLoad | - _:Input.CommonJSModulesToLoad => - // ok - case _ => - throw new UnsupportedInputException(input) - } + private def validateInput(input: Seq[Input]): Unit = input.foreach { + case _:Input.Script | _:Input.ESModule | _:Input.CommonJSModule => + // ok + case _ => + throw new UnsupportedInputException(input) } - private def internalStart(initFiles: List[Path], input: Input, runConfig: RunConfig): JSRun = { + private def internalStart(input: Seq[Input], runConfig: RunConfig): JSRun = { val command = config.executable :: config.args val externalConfig = ExternalJSRun.Config() .withEnv(env) .withRunConfig(runConfig) - ExternalJSRun.start(command, externalConfig)( - NodeJSEnv.write(initFiles, input)) + ExternalJSRun.start(command, externalConfig)(NodeJSEnv.write(input)) } - private def initFiles: List[Path] = config.sourceMap match { + private def initFiles: Seq[Input] = config.sourceMap match { case SourceMap.Disable => Nil - case SourceMap.EnableIfAvailable => installSourceMapIfAvailable :: Nil - case SourceMap.Enable => installSourceMap :: Nil - } - - private def inputFiles(input: Input) = input match { - case Input.ScriptsToLoad(scripts) => scripts - case _ => throw new UnsupportedInputException(input) + case SourceMap.EnableIfAvailable => Input.Script(installSourceMapIfAvailable) :: Nil + case SourceMap.Enable => Input.Script(installSourceMap) :: Nil } private def env: Map[String, String] = @@ -104,68 +95,70 @@ object NodeJSEnv { "require('source-map-support').install();".getBytes(StandardCharsets.UTF_8)) } - private def write(initFiles: List[Path], input: Input)(out: OutputStream): Unit = { - val p = new PrintStream(out, false, "UTF8") - try { - def writeRunScript(path: Path): Unit = { - try { - val f = path.toFile - val pathJS = "\"" + escapeJS(f.getAbsolutePath) + "\"" - p.println(s""" + private def write(input: Seq[Input])(out: OutputStream): Unit = { + def runScript(path: Path): String = { + try { + val f = path.toFile + val pathJS = "\"" + escapeJS(f.getAbsolutePath) + "\"" + s""" + require('vm').runInThisContext( + require('fs').readFileSync($pathJS, { encoding: "utf-8" }), + { filename: $pathJS, displayErrors: true } + ) + """ + } catch { + case _: UnsupportedOperationException => + val code = new String(Files.readAllBytes(path), StandardCharsets.UTF_8) + val codeJS = "\"" + escapeJS(code) + "\"" + val pathJS = "\"" + escapeJS(path.toString) + "\"" + s""" require('vm').runInThisContext( - require('fs').readFileSync($pathJS, { encoding: "utf-8" }), + $codeJS, { filename: $pathJS, displayErrors: true } - ); - """) - } catch { - case _: UnsupportedOperationException => - val code = new String(Files.readAllBytes(path), StandardCharsets.UTF_8) - val codeJS = "\"" + escapeJS(code) + "\"" - val pathJS = "\"" + escapeJS(path.toString) + "\"" - p.println(s""" - require('vm').runInThisContext( - $codeJS, - { filename: $pathJS, displayErrors: true } - ); - """) - } + ) + """ } + } - for (initFile <- initFiles) - writeRunScript(initFile) - - input match { - case Input.ScriptsToLoad(scripts) => - for (script <- scripts) - writeRunScript(script) - - case Input.CommonJSModulesToLoad(modules) => - for (module <- modules) - p.println(s"""require("${escapeJS(toFile(module).getAbsolutePath)}")""") - - case Input.ESModulesToLoad(modules) => - if (modules.nonEmpty) { - val uris = modules.map(m => toFile(m).toURI) - - val imports = uris.map { uri => - s"""import("${escapeJS(uri.toASCIIString)}")""" - } - val importChain = imports.reduceLeft { (prev, imprt) => - s"""$prev.then(_ => $imprt)""" - } - - val importerFileContent = { - s""" - |$importChain.catch(e => { - | console.error(e); - | process.exit(1); - |}); - """.stripMargin - } - val f = createTmpFile("importer.js") - Files.write(f.toPath, importerFileContent.getBytes(StandardCharsets.UTF_8)) - p.println(s"""require("${escapeJS(f.getAbsolutePath)}");""") - } + def requireCommonJSModule(module: Path): String = + s"""require("${escapeJS(toFile(module).getAbsolutePath)}")""" + + def importESModule(module: Path): String = + s"""import("${escapeJS(toFile(module).toURI.toASCIIString)}")""" + + def execInputExpr(input: Input): String = input match { + case Input.Script(script) => runScript(script) + case Input.CommonJSModule(module) => requireCommonJSModule(module) + case Input.ESModule(module) => importESModule(module) + } + + val p = new PrintStream(out, false, "UTF8") + try { + if (!input.exists(_.isInstanceOf[Input.ESModule])) { + /* If there is no ES module in the input, we can do everything + * synchronously, and directly on the standard input. + */ + for (item <- input) + p.println(execInputExpr(item) + ";") + } else { + /* If there is at least one ES module, we must asynchronous chain things, + * and we must use an actual file to feed code to Node.js (because + * `import()` cannot be used from the standard input). + */ + val importChain = input.foldLeft("Promise.resolve()") { (prev, item) => + s"$prev.\n then(${execInputExpr(item)})" + } + val importerFileContent = { + s""" + |$importChain.catch(e => { + | console.error(e); + | process.exit(1); + |}); + """.stripMargin + } + val f = createTmpFile("importer.js") + Files.write(f.toPath, importerFileContent.getBytes(StandardCharsets.UTF_8)) + p.println(s"""require("${escapeJS(f.getAbsolutePath)}");""") } } finally { p.close() diff --git a/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala b/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala index bb852086c0..f321ff456b 100644 --- a/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala +++ b/partest/src/main/scala/scala/tools/nsc/MainGenericRunner.scala @@ -109,7 +109,7 @@ class MainGenericRunner { file } - val input = Input.ScriptsToLoad(sjsCode :: Nil) + val input = Input.Script(sjsCode) :: Nil val config = RunConfig().withLogger(logger) val run = new NodeJSEnv().start(input, config) diff --git a/project/Build.scala b/project/Build.scala index e23a7a3b82..ff4e458554 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1450,7 +1450,7 @@ object Build { IO.write(launcherFile, code) val config = RunConfig().withLogger(sbtLogger2ToolsLogger(s.log)) - val input = Input.ScriptsToLoad(List(launcherFile.toPath)) + val input = List(Input.Script(launcherFile.toPath)) s.log.info(s"Linking test suite with JS linker") @@ -1476,15 +1476,7 @@ object Build { jsEnvInput := { val resourceDir = (resourceDirectory in Test).value val f = (resourceDir / "NonNativeJSTypeTestNatives.js").toPath - - jsEnvInput.value match { - case Input.ScriptsToLoad(prevFiles) => - Input.ScriptsToLoad(f :: prevFiles) - case Input.ESModulesToLoad(prevFiles) => - Input.ESModulesToLoad(f :: prevFiles) - case Input.CommonJSModulesToLoad(prevFiles) => - Input.CommonJSModulesToLoad(f :: prevFiles) - } + Input.Script(f) +: jsEnvInput.value } } @@ -1559,18 +1551,8 @@ object Build { } }.value, - jsEnvInput in Test := { - val prev = (jsEnvInput in Test).value - val loopbackScript = (setModuleLoopbackScript in Test).value - - loopbackScript match { - case None => - prev - case Some(script) => - val Input.ESModulesToLoad(modules) = prev - Input.ESModulesToLoad(modules :+ script) - } - }, + jsEnvInput in Test ++= + (setModuleLoopbackScript in Test).value.toList.map(Input.ESModule(_)), if (isGeneratingForIDE) { unmanagedSourceDirectories in Compile += diff --git a/project/NodeJSEnvForcePolyfills.scala b/project/NodeJSEnvForcePolyfills.scala index 042a6b0e29..025fdf248e 100644 --- a/project/NodeJSEnvForcePolyfills.scala +++ b/project/NodeJSEnvForcePolyfills.scala @@ -15,24 +15,19 @@ final class NodeJSEnvForcePolyfills(config: NodeJSEnv.Config) extends JSEnv { private val nodeJSEnv = new NodeJSEnv(config) - def start(input: Input, runConfig: RunConfig): JSRun = - nodeJSEnv.start(patchInput(input), runConfig) + def start(input: Seq[Input], runConfig: RunConfig): JSRun = + nodeJSEnv.start(forcePolyfills +: input, runConfig) - def startWithCom(input: Input, runConfig: RunConfig, + def startWithCom(input: Seq[Input], runConfig: RunConfig, onMessage: String => Unit): JSComRun = { - nodeJSEnv.startWithCom(patchInput(input), runConfig, onMessage) - } - - private def patchInput(input: Input): Input = input match { - case Input.ScriptsToLoad(scripts) => Input.ScriptsToLoad(forcePolyfills +: scripts) - case _ => throw new UnsupportedInputException(input) + nodeJSEnv.startWithCom(forcePolyfills +: input, runConfig, onMessage) } /** File to force all our ES 2015 polyfills to be used, by deleting the * native functions. */ - private def forcePolyfills(): Path = { - Files.write( + private def forcePolyfills(): Input = { + val p = Files.write( Jimfs.newFileSystem().getPath("scalaJSEnvInfo.js"), """ |delete Math.fround; @@ -58,5 +53,6 @@ final class NodeJSEnvForcePolyfills(config: NodeJSEnv.Config) extends JSEnv { |delete global.Float32Array; |delete global.Float64Array; """.stripMargin.getBytes(StandardCharsets.UTF_8)) + Input.Script(p) } } diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/Run.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/Run.scala index a9ff301f12..242d8a77de 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/Run.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/Run.scala @@ -23,7 +23,7 @@ private[sbtplugin] object Run { * * Interruption can be triggered by typing anything into stdin. */ - def runInterruptible(jsEnv: JSEnv, input: Input, config: RunConfig): Unit = { + def runInterruptible(jsEnv: JSEnv, input: Seq[Input], config: RunConfig): Unit = { val readPromise = Promise[Unit]() val readThread = new Thread { override def run(): Unit = { diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala index ca1036a146..e4113d5012 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPlugin.scala @@ -155,9 +155,9 @@ object ScalaJSPlugin extends AutoPlugin { "Prints the content of a .sjsir file in human readable form.", CTask) - val jsEnvInput = TaskKey[Input]( + val jsEnvInput = TaskKey[Seq[Input]]( "jsEnvInput", - "The JSEnv.Input to give to the jsEnv for tasks such as `run` and `test`", + "The JSEnv.Inputs to give to the jsEnv for tasks such as `run` and `test`", BTask) val scalaJSSourceFiles = AttributeKey[Seq[File]]("scalaJSSourceFiles", diff --git a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala index 999c8ded41..ec30559a78 100644 --- a/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala +++ b/sbt-plugin/src/main/scala/org/scalajs/sbtplugin/ScalaJSPluginInternal.scala @@ -77,7 +77,7 @@ private[sbtplugin] object ScalaJSPluginInternal { private val createdTestAdapters = new AtomicReference[List[TestAdapter]](Nil) - private def newTestAdapter(jsEnv: JSEnv, input: Input, + private def newTestAdapter(jsEnv: JSEnv, input: Seq[Input], config: TestAdapter.Config): TestAdapter = { registerResource(createdTestAdapters, new TestAdapter(jsEnv, input, config)) } @@ -343,14 +343,12 @@ private[sbtplugin] object ScalaJSPluginInternal { // Use the Scala.js linked file as the default Input for the JSEnv jsEnvInput := { val linkedFile = scalaJSLinkedFile.value.data.toPath - scalaJSLinkerConfig.value.moduleKind match { - case ModuleKind.NoModule => - Input.ScriptsToLoad(List(linkedFile)) - case ModuleKind.ESModule => - Input.ESModulesToLoad(List(linkedFile)) - case ModuleKind.CommonJSModule => - Input.CommonJSModulesToLoad(List(linkedFile)) + val input = scalaJSLinkerConfig.value.moduleKind match { + case ModuleKind.NoModule => Input.Script(linkedFile) + case ModuleKind.ESModule => Input.ESModule(linkedFile) + case ModuleKind.CommonJSModule => Input.CommonJSModule(linkedFile) } + List(input) }, scalaJSMainModuleInitializer := { diff --git a/test-adapter/src/main/scala/org/scalajs/testing/adapter/HTMLRunnerBuilder.scala b/test-adapter/src/main/scala/org/scalajs/testing/adapter/HTMLRunnerBuilder.scala index 31195c5fc8..67180cca0d 100644 --- a/test-adapter/src/main/scala/org/scalajs/testing/adapter/HTMLRunnerBuilder.scala +++ b/test-adapter/src/main/scala/org/scalajs/testing/adapter/HTMLRunnerBuilder.scala @@ -46,13 +46,13 @@ object HTMLRunnerBuilder { } } - def writeToFile(output: File, title: String, input: Input, + def writeToFile(output: File, title: String, input: Seq[Input], frameworkImplClassNames: List[List[String]], taskDefs: List[TaskDef]): Unit = { - val jsFiles = input match { - case Input.ScriptsToLoad(jsFiles) => - jsFiles + val jsFiles = input.map { + case Input.Script(script) => script + case _ => throw new UnsupportedInputException( s"Unsupported input for the generation of an HTML runner: $input") diff --git a/test-adapter/src/main/scala/org/scalajs/testing/adapter/JSEnvRPC.scala b/test-adapter/src/main/scala/org/scalajs/testing/adapter/JSEnvRPC.scala index 010e101295..cc2ce09fa4 100644 --- a/test-adapter/src/main/scala/org/scalajs/testing/adapter/JSEnvRPC.scala +++ b/test-adapter/src/main/scala/org/scalajs/testing/adapter/JSEnvRPC.scala @@ -19,7 +19,7 @@ import org.scalajs.testing.common._ /** RPC Core for use with a [[JSEnv]]. */ private[adapter] final class JSEnvRPC( - jsenv: JSEnv, input: Input, config: RunConfig)( + jsenv: JSEnv, input: Seq[Input], config: RunConfig)( implicit ec: ExecutionContext) extends RPCCore { private val run = jsenv.startWithCom(input, config, handleMessage) diff --git a/test-adapter/src/main/scala/org/scalajs/testing/adapter/TestAdapter.scala b/test-adapter/src/main/scala/org/scalajs/testing/adapter/TestAdapter.scala index a4add9f6dd..0d240e6427 100644 --- a/test-adapter/src/main/scala/org/scalajs/testing/adapter/TestAdapter.scala +++ b/test-adapter/src/main/scala/org/scalajs/testing/adapter/TestAdapter.scala @@ -25,7 +25,7 @@ import org.scalajs.testing.common._ import sbt.testing.Framework -final class TestAdapter(jsEnv: JSEnv, input: Input, config: TestAdapter.Config) { +final class TestAdapter(jsEnv: JSEnv, input: Seq[Input], config: TestAdapter.Config) { import TestAdapter._ From 1757273cbe0b5cec28ad726c0deb5448f4dafd42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 1 Aug 2019 11:18:23 +0200 Subject: [PATCH 0068/1606] Implement `java.util.TreeSet` from scratch. Previously, it was implemented on top of `s.c.m.TreeSet`, with various hacks to implement JDK features: * `Iterator.remove()` * Descending iterators and views * Projections with exclusive start and/or inclusive end All those hacks resulted in significantly more memory allocation (at least one `Box` for most elementary operations) as well as unreasonable time complexity for views. The new implementation, done from scratch, has direct internal support for all these operations: * It never allocates during lookup and removal * Iteration never allocates besides the instance of `Iterator` * It allocates at most one object during `add` The implementation was initially inspired by the implementation of `s.c.m.TreeMap` in Scala 2.13.0 (in particular the core data structure and algorithms in `RedBlackTree.scala`), but was heavily changed to accommodate the requirements of the JDK API. The red-black tree implementation supports values associated to keys, although `TreeSet` does not use that feature. It will be useful when we eventually implement `TreeMap`. The original red-black tree in Scala 2.13.0 supported it, so it was easier to port it than not. --- javalib/src/main/scala/java/util/Compat.scala | 59 - .../main/scala/java/util/NavigableView.scala | 208 ---- .../main/scala/java/util/RedBlackTree.scala | 1013 +++++++++++++++++ .../src/main/scala/java/util/TreeSet.scala | 525 ++++++--- .../src/main/scala/java/util/package.scala | 37 - 5 files changed, 1364 insertions(+), 478 deletions(-) delete mode 100644 javalib/src/main/scala/java/util/Compat.scala delete mode 100644 javalib/src/main/scala/java/util/NavigableView.scala create mode 100644 javalib/src/main/scala/java/util/RedBlackTree.scala delete mode 100644 javalib/src/main/scala/java/util/package.scala diff --git a/javalib/src/main/scala/java/util/Compat.scala b/javalib/src/main/scala/java/util/Compat.scala deleted file mode 100644 index dec28971b0..0000000000 --- a/javalib/src/main/scala/java/util/Compat.scala +++ /dev/null @@ -1,59 +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. - */ - -package java.util - -import scala.collection.mutable - -/** Make some Scala 2.13 APIs available in older Scala versions. */ -private[util] object Compat { - - /** Adds methods from 2.13 to `SortedSet`. - * - * The `to` operation has been renamed to `rangeTo` in 2.13, to not - * conflict with the `to` operation in `Iterable`. - */ - implicit class SortedSetCompat[A](val __private_self: mutable.SortedSet[A]) - extends AnyVal { - - @inline private def self: mutable.SortedSet[A] = __private_self - - /* Note: the double implicit conversion trick does not work here because - * there *is* a `to` method in 2.13 (but it takes a `Factory` as parameter) - * so the second implicit conversion is never triggered. - */ - def rangeTo(to: A): mutable.SortedSet[A] = { - // Implementation copied from 2.12's implementation - val i = self.rangeFrom(to).iterator - if (i.isEmpty) { - self - } else { - val next = i.next() - if (self.ordering.compare(next, to) == 0) { - if (i.isEmpty) self - else self.rangeUntil(i.next()) - } else { - self.rangeUntil(next) - } - } - } - - /* Note: the double implicit conversion trick does not work here either - * because the `from` and `until` methods still exist on 2.13 but they - * are deprecated. - */ - def rangeFrom(a: A): mutable.SortedSet[A] = self.rangeImpl(Some(a), None) - def rangeUntil(a: A): mutable.SortedSet[A] = self.rangeImpl(None, Some(a)) - - } - -} diff --git a/javalib/src/main/scala/java/util/NavigableView.scala b/javalib/src/main/scala/java/util/NavigableView.scala deleted file mode 100644 index eef1314a65..0000000000 --- a/javalib/src/main/scala/java/util/NavigableView.scala +++ /dev/null @@ -1,208 +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. - */ - -package java.util - -import scala.math.Ordering - -import scala.collection.mutable - -import Compat.SortedSetCompat -import ScalaOps._ - -private[util] class NavigableView[E](original: NavigableSet[E], - inner: () => mutable.SortedSet[Box[E]], - lowerBound: Option[E], lowerInclusive: Boolean, - upperBound: Option[E], upperInclusive: Boolean) - extends AbstractCollection[E] with NavigableSet[E] with SortedSet[E] { - - def size(): Int = - iterator.scalaOps.count(_ => true) - - override def contains(o: Any): Boolean = - inner().contains(Box(o.asInstanceOf[E])) - - override def add(e: E): Boolean = { - val comp = comparator() - lowerBound.foreach { bound => - val cmp = comp.compare(e, bound) - if (cmp < 0 || (!lowerInclusive && cmp==0)) - throw new IllegalArgumentException() - } - upperBound.foreach { bound => - val cmp = comp.compare(e, bound) - if (cmp > 0 || (!upperInclusive && cmp==0)) - throw new IllegalArgumentException() - } - original.add(e) - } - - override def remove(o: Any): Boolean = - original.remove(o) - - private def _iterator(iter: scala.collection.Iterator[E]): Iterator[E] = { - new Iterator[E] { - private var last: Option[E] = None - - def hasNext(): Boolean = iter.hasNext - - def next(): E = { - last = Some(iter.next()) - last.get - } - - def remove(): Unit = { - if (last.isEmpty) { - throw new IllegalStateException() - } else { - last.foreach(original.remove(_)) - last = None - } - } - } - } - - def iterator(): Iterator[E] = - _iterator(inner().iterator.map(_.inner)) - - def descendingIterator(): Iterator[E] = - _iterator(iterator.scalaOps.toList.reverse.iterator) - - override def removeAll(c: Collection[_]): Boolean = { - val iter = c.iterator() - var changed = false - while (iter.hasNext) - changed = remove(iter.next) || changed - changed - } - - override def addAll(c: Collection[_ <: E]): Boolean = - original.addAll(c) - - def lower(e: E): E = - headSet(e, false).last() - - def floor(e: E): E = - headSet(e, true).last() - - def ceiling(e: E): E = - tailSet(e, true).first() - - def higher(e: E): E = - tailSet(e, false).first() - - def pollFirst(): E = { - val polled = inner().headOption - if (polled.isDefined) { - val elem = polled.get.inner - remove(elem) - elem - } else null.asInstanceOf[E] - } - - def pollLast(): E = { - val polled = inner().lastOption - if (polled.isDefined) { - val elem = polled.get.inner - remove(elem) - elem - } else null.asInstanceOf[E] - } - - def comparator(): Comparator[E] = { - new Comparator[E] { - val ordering = inner().ordering - - def compare(a: E, b: E): Int = - ordering.compare(Box(a), Box(b)) - } - } - - def first(): E = { - val iter = iterator() - if (iter.hasNext) iter.next - else null.asInstanceOf[E] - } - - def last(): E = { - val iter = iterator() - var result = null.asInstanceOf[E] - while (iter.hasNext) - result = iter.next() - result - } - - def subSet(fromElement: E, fromInclusive: Boolean, toElement: E, - toInclusive: Boolean): NavigableSet[E] = { - val innerNow = inner() - val boxedFrom = Box(fromElement) - val boxedTo = Box(toElement) - - val subSetFun = { () => - val toTs = - if (toInclusive) innerNow.rangeTo(boxedTo) - else innerNow.rangeUntil(boxedTo) - if (fromInclusive) toTs.rangeFrom(boxedFrom) - else toTs.rangeFrom(boxedFrom).clone() -= boxedFrom - } - - new NavigableView(this, subSetFun, - Some(fromElement), fromInclusive, - Some(toElement), toInclusive) - } - - def headSet(toElement: E, inclusive: Boolean): NavigableSet[E] = { - val innerNow = inner() - val boxed = Box(toElement) - - val headSetFun = - if (inclusive) () => innerNow.rangeTo(boxed) - else () => innerNow.rangeUntil(boxed) - - new NavigableView(this, headSetFun, - None, true, - Some(toElement), inclusive) - } - - def tailSet(fromElement: E, inclusive: Boolean): NavigableSet[E] = { - val innerNow = inner() - val boxed = Box(fromElement) - - val tailSetFun = - if (inclusive) () => innerNow.rangeFrom(boxed) - else () => innerNow.rangeFrom(boxed).clone() -= boxed - - new NavigableView(this, tailSetFun, - Some(fromElement), inclusive, - None, true) - } - - def subSet(fromElement: E, toElement: E): NavigableSet[E] = - subSet(fromElement, true, toElement, false) - - def headSet(toElement: E): NavigableSet[E] = - headSet(toElement, false) - - def tailSet(fromElement: E): NavigableSet[E] = - tailSet(fromElement, true) - - def descendingSet(): NavigableSet[E] = { - val descSetFun = { () => - val innerNow = inner() - val retSet = new mutable.TreeSet[Box[E]]()(innerNow.ordering.reverse) - retSet ++= innerNow - retSet - } - - new NavigableView(this, descSetFun, None, true, None, true) - } -} diff --git a/javalib/src/main/scala/java/util/RedBlackTree.scala b/javalib/src/main/scala/java/util/RedBlackTree.scala new file mode 100644 index 0000000000..ba8d614235 --- /dev/null +++ b/javalib/src/main/scala/java/util/RedBlackTree.scala @@ -0,0 +1,1013 @@ +/* + * 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 scala.annotation.tailrec + +import scala.scalajs.js + +/** The red-black tree implementation used by `TreeSet`s. + * + * It could also be used by `TreeMap`s in the future. + * + * This implementation was copied and adapted from + * `scala.collection.mutable.RedBlackTree` as found in Scala 2.13.0. + */ +private[util] object RedBlackTree { + + // ---- bounds helpers ---- + + type BoundKind = Int + + /* The values of `InclusiveBound` and `ExclusiveBound` must be 0 and 1, + * respectively. Some of the algorithms in this implementation rely on them + * having those specific values, as they do arithmetics with them. + */ + final val InclusiveBound = 0 + final val ExclusiveBound = 1 + final val NoBound = 2 + + @inline + def boundKindFromIsInclusive(isInclusive: Boolean): BoundKind = + if (isInclusive) InclusiveBound + else ExclusiveBound + + @inline + final class Bound[A](val bound: A, val kind: BoundKind) + + def isWithinLowerBound[A](key: Any, bound: A, boundKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Boolean = { + boundKind == NoBound || compare(key, bound) >= boundKind + } + + def isWithinUpperBound[A](key: Any, bound: A, boundKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Boolean = { + boundKind == NoBound || compare(key, bound) <= -boundKind + } + + def intersectLowerBounds[A](bound1: Bound[A], bound2: Bound[A])( + implicit comp: Comparator[_ >: A]): Bound[A] = { + if (bound1.kind == NoBound) { + bound2 + } else if (bound2.kind == NoBound) { + bound1 + } else { + val cmp = compare(bound1.bound, bound2.bound) + if (cmp > 0 || (cmp == 0 && bound1.kind == ExclusiveBound)) + bound1 + else + bound2 + } + } + + def intersectUpperBounds[A](bound1: Bound[A], bound2: Bound[A])( + implicit comp: Comparator[_ >: A]): Bound[A] = { + if (bound1.kind == NoBound) { + bound2 + } else if (bound2.kind == NoBound) { + bound1 + } else { + val cmp = compare(bound1.bound, bound2.bound) + if (cmp < 0 || (cmp == 0 && bound1.kind == ExclusiveBound)) + bound1 + else + bound2 + } + } + + // ---- class structure ---- + + /* For performance reasons, this implementation uses `null` references to + * represent leaves instead of a sentinel node. + * + * Currently, the internal nodes do not store their subtree size - only the + * tree object keeps track of its size. Therefore, while obtaining the size + * of the whole tree is O(1), knowing the number of entries inside a range is + * O(n) on the size of the range. + */ + + final class Tree[A, B](var root: Node[A, B], var size: Int) { + def treeCopy(): Tree[A, B] = new Tree(copyTree(root), size) + } + + object Tree { + def empty[A, B]: Tree[A, B] = new Tree(null, 0) + } + + final class Node[A, B]( + val key: A, + private[RedBlackTree] var value: B, + private[RedBlackTree] var red: Boolean, + private[RedBlackTree] var left: Node[A, B], + private[RedBlackTree] var right: Node[A, B], + private[RedBlackTree] var parent: Node[A, B] + ) extends Map.Entry[A, B] { + def getKey(): A = key + + def getValue(): B = value + + def setValue(v: B): B = { + val oldValue = value + value = v + oldValue + } + + override def equals(that: Any): Boolean = that match { + case that: Map.Entry[_, _] => + Objects.equals(getKey(), that.getKey()) && + Objects.equals(getValue(), that.getValue()) + case _ => + false + } + + override def hashCode(): Int = + Objects.hashCode(key) ^ Objects.hashCode(value) + + override def toString(): String = + "" + getKey + "=" + getValue + + @inline private[RedBlackTree] def isRoot: Boolean = + parent eq null + + @inline private[RedBlackTree] def isLeftChild: Boolean = + (parent ne null) && (parent.left eq this) + + @inline private[RedBlackTree] def isRightChild: Boolean = + (parent ne null) && (parent.right eq this) + } + + object Node { + @inline + def apply[A, B](key: A, value: B, red: Boolean, left: Node[A, B], + right: Node[A, B], parent: Node[A, B]): Node[A, B] = { + new Node(key, value, red, left, right, parent) + } + + @inline + def leaf[A, B](key: A, value: B, red: Boolean, parent: Node[A, B]): Node[A, B] = + new Node(key, value, red, null, null, parent) + } + + // ---- comparator helper ---- + + @inline + private[this] def compare[A](key1: Any, key2: A)( + implicit comp: Comparator[_ >: A]): Int = { + /* The implementation of `compare` and/or its generic bridge may perform + * monomorphic casts that can fail. This is according to spec for TreeSet + * and TreeMap. + */ + comp.asInstanceOf[Comparator[Any]].compare(key1, key2) + } + + // ---- getters ---- + + private def isRed(node: Node[_, _]): Boolean = (node ne null) && node.red + + private def isBlack(node: Node[_, _]): Boolean = (node eq null) || !node.red + + // ---- size ---- + + private def size(node: Node[_, _]): Int = + if (node eq null) 0 + else 1 + size(node.left) + size(node.right) + + def size(tree: Tree[_, _]): Int = tree.size + + def projectionSize[A, B](tree: Tree[A, B], lowerBound: A, + lowerKind: BoundKind, upperBound: A, upperKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Int = { + if (lowerKind == NoBound && upperKind == NoBound) { + size(tree) + } else { + var result = 0 + val iter = + projectionIterator(tree, lowerBound, lowerKind, upperBound, upperKind) + while (iter.hasNext()) { + iter.next() + result += 1 + } + result + } + } + + def isEmpty(tree: Tree[_, _]): Boolean = tree.root eq null + + def projectionIsEmpty[A](tree: Tree[A, _], lowerBound: A, + lowerKind: BoundKind, upperBound: A, upperKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Boolean = { + if (lowerKind == NoBound && upperKind == NoBound) { + isEmpty(tree) + } else { + val node = minNodeAfter(tree, lowerBound, lowerKind) + (node eq null) || !isWithinUpperBound(node.key, upperBound, upperKind) + } + } + + def clear(tree: Tree[_, _]): Unit = { + tree.root = null + tree.size = 0 + } + + // ---- search ---- + + def get[A, B](tree: Tree[A, B], key: Any)( + implicit comp: Comparator[_ >: A]): B = { + nullableNodeFlatMap(getNode(tree.root, key))(_.value) + } + + @tailrec + private[this] def getNode[A, B](node: Node[A, B], key: Any)( + implicit comp: Comparator[_ >: A]): Node[A, B] = { + if (node eq null) { + null + } else { + val cmp = compare(key, node.key) + if (cmp < 0) getNode(node.left, key) + else if (cmp > 0) getNode(node.right, key) + else node + } + } + + def contains[A](tree: Tree[A, _], key: Any)( + implicit comp: Comparator[_ >: A]): Boolean = { + getNode(tree.root, key) ne null + } + + def minNode[A, B](tree: Tree[A, B]): Node[A, B] = + minNode(tree.root) + + def minKey[A](tree: Tree[A, _]): A = + nullableNodeKey(minNode(tree.root)) + + private def minNode[A, B](node: Node[A, B]): Node[A, B] = + nullableNodeFlatMap(node)(minNodeNonNull(_)) + + @tailrec + private def minNodeNonNull[A, B](node: Node[A, B]): Node[A, B] = + if (node.left eq null) node + else minNodeNonNull(node.left) + + def maxNode[A, B](tree: Tree[A, B]): Node[A, B] = + maxNode(tree.root) + + def maxKey[A](tree: Tree[A, _]): A = + nullableNodeKey(maxNode(tree.root)) + + private def maxNode[A, B](node: Node[A, B]): Node[A, B] = + nullableNodeFlatMap(node)(maxNodeNonNull(_)) + + @tailrec + private def maxNodeNonNull[A, B](node: Node[A, B]): Node[A, B] = + if (node.right eq null) node + else maxNodeNonNull(node.right) + + /** Returns the first (lowest) map entry with a key (equal or) greater than + * `key`. + * + * Returns `null` if there is no such node. + */ + def minNodeAfter[A, B](tree: Tree[A, B], key: A, boundKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Node[A, B] = { + minNodeAfter(tree.root, key, boundKind) + } + + def minKeyAfter[A](tree: Tree[A, _], key: A, boundKind: BoundKind)( + implicit comp: Comparator[_ >: A]): A = { + nullableNodeKey(minNodeAfter(tree.root, key, boundKind)) + } + + private def minNodeAfter[A, B](node: Node[A, B], key: A, boundKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Node[A, B] = { + if (node eq null) { + null + } else if (boundKind == NoBound) { + minNodeNonNull(node) + } else { + @tailrec + def minNodeAfterNonNull(node: Node[A, B]): Node[A, B] = { + val cmp = compare(key, node.key) + if (cmp == 0) { + if (boundKind == InclusiveBound) + node + else + successor(node) + } else if (cmp < 0) { + val child = node.left + if (child eq null) + node + else + minNodeAfterNonNull(child) + } else { + val child = node.right + if (child eq null) + successor(node) + else + minNodeAfterNonNull(child) + } + } + + minNodeAfterNonNull(node) + } + } + + /** Returns the last (highest) map entry with a key (equal or) smaller than + * `key`. + * + * Returns `null` if there is no such node. + */ + def maxNodeBefore[A, B](tree: Tree[A, B], key: A, boundKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Node[A, B] = { + maxNodeBefore(tree.root, key, boundKind) + } + + def maxKeyBefore[A](tree: Tree[A, _], key: A, boundKind: BoundKind)( + implicit comp: Comparator[_ >: A]): A = { + nullableNodeKey(maxNodeBefore(tree.root, key, boundKind)) + } + + private def maxNodeBefore[A, B](node: Node[A, B], key: A, boundKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Node[A, B] = { + if (node eq null) { + null + } else if (boundKind == NoBound) { + maxNodeNonNull(node) + } else { + @tailrec + def maxNodeBeforeNonNull(node: Node[A, B]): Node[A, B] = { + val cmp = compare(key, node.key) + if (cmp == 0) { + if (boundKind == InclusiveBound) + node + else + predecessor(node) + } else if (cmp < 0) { + val child = node.left + if (child eq null) + predecessor(node) + else + maxNodeBeforeNonNull(child) + } else { + val child = node.right + if (child eq null) + node + else + maxNodeBeforeNonNull(child) + } + } + + maxNodeBeforeNonNull(node) + } + } + + // ---- insertion ---- + + def insert[A, B](tree: Tree[A, B], key: A, value: B)( + implicit comp: Comparator[_ >: A]): B = { + /* The target node is the node with `key` if it exists, or the node under + * which we need to insert the new `key`. + * We use a loop here instead of a tailrec def because we need 2 results + * from this lookup: `targetNode` and `cmp`. + */ + var targetNode: Node[A, B] = null + var nextNode: Node[A, B] = tree.root + var cmp: Int = 1 + while ((nextNode ne null) && cmp != 0) { + targetNode = nextNode + cmp = compare(key, nextNode.key) + nextNode = if (cmp < 0) nextNode.left else nextNode.right + } + + if (cmp == 0) { + // Found existing node: just update its value + targetNode.setValue(value) + } else { + // Insert a new node under targetNode, then fix the RB tree + val newNode = Node.leaf(key, value, red = true, targetNode) + + if (targetNode eq null) { + /* Here, the key was never compared to anything. Compare it with itself + * so that we eagerly cause the comparator to throw if it cannot handle + * the key at all, before we put the key in the map. + */ + compare(key, key) + tree.root = newNode + } else if (cmp < 0) { + targetNode.left = newNode + } else { + targetNode.right = newNode + } + + fixAfterInsert(tree, newNode) + tree.size += 1 + + null.asInstanceOf[B] + } + } + + @tailrec + private[this] def fixAfterInsert[A, B](tree: Tree[A, B], + node: Node[A, B]): Unit = { + val parent = node.parent + if (parent eq null) { + // The inserted node is the root; mark it black and we're done + node.red = false + } else if (isBlack(parent)) { + // The parent is black and the node is red; we're done + } else if (parent.isLeftChild) { + val grandParent = parent.parent + val uncle = grandParent.right + if (isRed(uncle)) { + parent.red = false + uncle.red = false + grandParent.red = true + fixAfterInsert(tree, grandParent) + } else { + if (node.isRightChild) { + rotateLeft(tree, parent) + // Now `parent` is the child of `node`, which is the child of `grandParent` + node.red = false + } else { + parent.red = false + } + grandParent.red = true + rotateRight(tree, grandParent) + // Now the node which took the place of `grandParent` is black, so we're done + } + } else { + // Symmetric cases + val grandParent = parent.parent + val uncle = grandParent.left + if (isRed(uncle)) { + parent.red = false + uncle.red = false + grandParent.red = true + fixAfterInsert(tree, grandParent) + } else { + if (node.isLeftChild) { + rotateRight(tree, parent) + // Now `parent` is the child of `node`, which is the child of `grandParent` + node.red = false + } else { + parent.red = false + } + grandParent.red = true + rotateLeft(tree, grandParent) + // Now the node which took the place of `grandParent` is black, so we're done + } + } + } + + // ---- deletion ---- + + def delete[A, B](tree: Tree[A, B], key: Any)( + implicit comp: Comparator[_ >: A]): B = { + nullableNodeFlatMap(getNode(tree.root, key)) { node => + deleteNode(tree, node) + node.value + } + } + + def deleteNode[A, B](tree: Tree[A, B], node: Node[A, B]): Unit = { + if (node.left eq null) { + val onlyChild = node.right // can be null + transplant(tree, node, onlyChild) + if (!node.red) + fixAfterDelete(tree, onlyChild, node.parent) + } else if (node.right eq null) { + val onlyChild = node.left + transplant(tree, node, onlyChild) + if (!node.red) + fixAfterDelete(tree, onlyChild, node.parent) + } else { + /* We don't know how to delete a node with 2 children, so we're going to + * find the successor `succ` of `node`, then (conceptually) swap it with + * `node` before deleting `node`. We can do this because we know that + * `succ` has a null `left` child. + * + * In fact we transplant the `onlyChildOfSucc` (orignally at + * `succ.right`) in place of `succ`, then transplant `succ` in place of + * `node` (also acquiring its color), and finally fixing up the tree + * around `onlyChildOfSucc`. + * + * Textbook red-black trees simply set the `key` and `value` of `node` to + * be those of `succ`, then delete `succ`. We cannot do this because our + * `key` is immutable (it *has* to be for `Node` to comply with the + * contract of `Map.Entry`). + */ + val succ = minNodeNonNull(node.right) + // Assert: succ.left eq null + val succWasRed = succ.red // conceptually, the color of `node` after the swap + val onlyChildOfSucc = succ.right + val newParentOfTheChild = if (succ.parent eq node) { + succ + } else { + val theParent = succ.parent + transplant(tree, succ, onlyChildOfSucc) + succ.right = node.right + succ.right.parent = succ + theParent + } + transplant(tree, node, succ) + succ.left = node.left + succ.left.parent = succ + succ.red = node.red + // Assert: if (onlyChildOfSucc ne null) then (newParentOfTheChild eq onlyChildOfSucc.parent) + if (!succWasRed) + fixAfterDelete(tree, onlyChildOfSucc, newParentOfTheChild) + } + + tree.size -= 1 + } + + /* `node` can be `null` (in which case it is black), so we have to pass + * `parent` explicitly from above. + */ + @tailrec + private[this] def fixAfterDelete[A, B](tree: Tree[A, B], node: Node[A, B], + parent: Node[A, B]): Unit = { + if ((node ne tree.root) && isBlack(node)) { + // `node` can *still* be null here; we cannot use `node.isLeftChild` + if (node eq parent.left) { + // From now on, we don't use `node` anymore; we just fix up at `parent` + + var rightChild = parent.right + // assert(rightChild ne null) + + if (rightChild.red) { + rightChild.red = false + parent.red = true + rotateLeft(tree, parent) + rightChild = parent.right // shape changed; update `rightChild` + } + if (isBlack(rightChild.left) && isBlack(rightChild.right)) { + rightChild.red = true + fixAfterDelete(tree, parent, parent.parent) + } else { + if (isBlack(rightChild.right)) { + rightChild.left.red = false + rightChild.red = true + rotateRight(tree, rightChild) + rightChild = parent.right // shape changed; update `rightChild` + } + rightChild.red = parent.red + parent.red = false + rightChild.right.red = false + rotateLeft(tree, parent) + // we're done here + } + } else { // symmetric cases + // From now on, we don't use `node` anymore; we just fix up at `parent` + + var leftChild = parent.left + // assert(leftChild ne null) + + if (leftChild.red) { + leftChild.red = false + parent.red = true + rotateRight(tree, parent) + leftChild = parent.left // shape changed; update `leftChild` + } + if (isBlack(leftChild.right) && isBlack(leftChild.left)) { + leftChild.red = true + fixAfterDelete(tree, parent, parent.parent) + } else { + if (isBlack(leftChild.left)) { + leftChild.right.red = false + leftChild.red = true + rotateLeft(tree, leftChild) + leftChild = parent.left // shape changed; update `leftChild` + } + leftChild.red = parent.red + parent.red = false + leftChild.left.red = false + rotateRight(tree, parent) + // we're done here + } + } + } else { + // We found a red node or the root; mark it black and we're done + if (node ne null) + node.red = false + } + } + + // ---- helpers ---- + + /** 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 = + 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 = + if (node eq null) null.asInstanceOf[A] + else node.key + + /** 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 + * method returns `null`. + */ + private[this] def successor[A, B](node: Node[A, B]): Node[A, B] = { + if (node.right ne null) { + minNodeNonNull(node.right) + } else { + @inline @tailrec + def closestAncestorOnTheRight(node: Node[A, B]): Node[A, B] = { + val parent = node.parent + if ((parent eq null) || (node eq parent.left)) parent + else closestAncestorOnTheRight(parent) + } + closestAncestorOnTheRight(node) + } + } + + /** Returns the node that precedes `node` in an in-order tree traversal. + * + * If `node` has the minimum key (and is, therefore, the first node), this + * method returns `null`. + */ + private[this] def predecessor[A, B](node: Node[A, B]): Node[A, B] = { + if (node.left ne null) { + maxNodeNonNull(node.left) + } else { + @inline @tailrec + def closestAncestorOnTheLeft(node: Node[A, B]): Node[A, B] = { + val parent = node.parent + if ((parent eq null) || (node eq parent.right)) parent + else closestAncestorOnTheLeft(parent) + } + closestAncestorOnTheLeft(node) + } + } + + private[this] def rotateLeft[A, B](tree: Tree[A, B], x: Node[A, B]): Unit = { + if (x ne null) { + // assert(x.right ne null) + val y = x.right + x.right = y.left + + if (y.left ne null) + y.left.parent = x + y.parent = x.parent + + if (x.isRoot) + tree.root = y + else if (x.isLeftChild) + x.parent.left = y + else + x.parent.right = y + + y.left = x + x.parent = y + } + } + + private[this] def rotateRight[A, B](tree: Tree[A, B], x: Node[A, B]): Unit = { + if (x ne null) { + // assert(x.left ne null) + val y = x.left + x.left = y.right + + if (y.right ne null) + y.right.parent = x + y.parent = x.parent + + if (x.isRoot) + tree.root = y + else if (x.isRightChild) + x.parent.right = y + else + x.parent.left = y + + y.right = x + x.parent = y + } + } + + /** Transplant the node `from` to the place of node `to`. + * + * This is done by setting `from` as a child of `to`'s previous parent and + * setting `from`'s parent to the `to`'s previous parent. The children of + * `from` are left unchanged. + */ + private[this] def transplant[A, B](tree: Tree[A, B], to: Node[A, B], + from: Node[A, B]): Unit = { + if (to.isRoot) + tree.root = from + else if (to.isLeftChild) + to.parent.left = from + else + to.parent.right = from + + if (from ne null) + from.parent = to.parent + } + + // ---- iterators ---- + + def iterator[A, B](tree: Tree[A, B]): Iterator[Map.Entry[A, B]] = + new EntriesIterator(tree) + + def keysIterator[A, B](tree: Tree[A, B]): Iterator[A] = + new KeysIterator(tree) + + def valuesIterator[A, B](tree: Tree[A, B]): Iterator[B] = + new ValuesIterator(tree) + + private[this] abstract class AbstractTreeIterator[A, B, R](tree: Tree[A, B], + private[this] var nextNode: Node[A, B]) + extends Iterator[R] { + + private[this] var lastNode: Node[A, B] = _ // null + + protected def advance(node: Node[A, B]): Node[A, B] + protected def nextResult(node: Node[A, B]): R + + def hasNext(): Boolean = nextNode ne null + + def next(): R = { + val node = nextNode + if (node eq null) + throw new NoSuchElementException("next on empty iterator") + lastNode = node + nextNode = advance(node) + nextResult(node) + } + + def remove(): Unit = { + val node = lastNode + if (node eq null) + throw new IllegalStateException() + deleteNode(tree, node) + lastNode = null + } + } + + private[this] abstract class TreeIterator[A, B, R](tree: Tree[A, B]) + extends AbstractTreeIterator[A, B, R](tree, minNode(tree)) { + + protected final def advance(node: Node[A, B]): Node[A, B] = + successor(node) + } + + private[this] final class EntriesIterator[A, B](tree: Tree[A, B]) + extends TreeIterator[A, B, Map.Entry[A, B]](tree) { + + protected def nextResult(node: Node[A, B]): Map.Entry[A, B] = node + } + + private[this] final class KeysIterator[A, B](tree: Tree[A, B]) + extends TreeIterator[A, B, A](tree) { + + protected def nextResult(node: Node[A, B]): A = node.key + } + + private[this] final class ValuesIterator[A, B](tree: Tree[A, B]) + extends TreeIterator[A, B, B](tree) { + + protected def nextResult(node: Node[A, B]): B = node.value + } + + // ---- projection iterators ---- + + def projectionIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Iterator[Map.Entry[A, B]] = { + new ProjectionEntriesIterator(tree, start, startKind, end, endKind) + } + + def projectionKeysIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Iterator[A] = { + new ProjectionKeysIterator(tree, start, startKind, end, endKind) + } + + def projectionValuesIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Iterator[B] = { + new ProjectionValuesIterator(tree, start, startKind, end, endKind) + } + + private[this] abstract class ProjectionIterator[A, B, R](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]) + extends AbstractTreeIterator[A, B, R]( + tree, + ProjectionIterator.nullIfAfterEnd( + minNodeAfter(tree.root, start, startKind), end, endKind)) { + + protected final def advance(node: Node[A, B]): Node[A, B] = + ProjectionIterator.nullIfAfterEnd(successor(node), end, endKind) + } + + private[this] object ProjectionIterator { + @inline + private def nullIfAfterEnd[A, B](node: Node[A, B], end: A, + endKind: BoundKind)(implicit comp: Comparator[_ >: A]): Node[A, B] = { + if (endKind != NoBound && (node ne null) && + !isWithinUpperBound(node.key, end, endKind)) { + null + } else { + node + } + } + } + + private[this] final class ProjectionEntriesIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]) + extends ProjectionIterator[A, B, Map.Entry[A, B]](tree, start, startKind, end, endKind) { + + def nextResult(node: Node[A, B]): Map.Entry[A, B] = node + } + + private[this] final class ProjectionKeysIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]) + extends ProjectionIterator[A, B, A](tree, start, startKind, end, endKind) { + + def nextResult(node: Node[A, B]): A = node.key + } + + private[this] final class ProjectionValuesIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]) + extends ProjectionIterator[A, B, B](tree, start, startKind, end, endKind) { + + def nextResult(node: Node[A, B]): B = node.value + } + + // ---- descending iterators ---- + + /* We do not have optimized iterators for descending order on + * non-projections, as they would be of questionable value. Instead, we use + * descending project iterators instead. + * + * Since we know that both bounds do not exist, we know that the comparator + * will never be used by the algorithms in DescendingTreeIterator, so we do + * not require one and instead push a `null` internally. + */ + + def descendingIterator[A, B](tree: Tree[A, B]): Iterator[Map.Entry[A, B]] = { + descendingIterator(tree, null.asInstanceOf[A], NoBound, + null.asInstanceOf[A], NoBound)(null) + } + + def descendingKeysIterator[A, B](tree: Tree[A, B]): Iterator[A] = { + descendingKeysIterator(tree, null.asInstanceOf[A], NoBound, + null.asInstanceOf[A], NoBound)(null) + } + + def descendingValuesIterator[A, B](tree: Tree[A, B]): Iterator[B] = { + descendingValuesIterator(tree, null.asInstanceOf[A], NoBound, + null.asInstanceOf[A], NoBound)(null) + } + + // ---- descending projection iterators ---- + + def descendingIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Iterator[Map.Entry[A, B]] = { + new DescendingEntriesIterator(tree, start, startKind, end, endKind) + } + + def descendingKeysIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Iterator[A] = { + new DescendingKeysIterator(tree, start, startKind, end, endKind) + } + + def descendingValuesIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]): Iterator[B] = { + new DescendingValuesIterator(tree, start, startKind, end, endKind) + } + + private[this] abstract class DescendingTreeIterator[A, B, R](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]) + extends AbstractTreeIterator[A, B, R]( + tree, + DescendingTreeIterator.nullIfBeforeEnd( + maxNodeBefore(tree.root, start, startKind), end, endKind)) { + + protected final def advance(node: Node[A, B]): Node[A, B] = + DescendingTreeIterator.nullIfBeforeEnd(predecessor(node), end, endKind) + } + + private[this] object DescendingTreeIterator { + @inline + private def nullIfBeforeEnd[A, B](node: Node[A, B], end: A, + endKind: BoundKind)(implicit comp: Comparator[_ >: A]): Node[A, B] = { + if (endKind != NoBound && (node ne null) && + !isWithinLowerBound(node.key, end, endKind)) { + null + } else { + node + } + } + } + + private[this] final class DescendingEntriesIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]) + extends DescendingTreeIterator[A, B, Map.Entry[A, B]](tree, start, startKind, end, endKind) { + + def nextResult(node: Node[A, B]): Map.Entry[A, B] = node + } + + private[this] final class DescendingKeysIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]) + extends DescendingTreeIterator[A, B, A](tree, start, startKind, end, endKind) { + + def nextResult(node: Node[A, B]): A = node.key + } + + private[this] final class DescendingValuesIterator[A, B](tree: Tree[A, B], + start: A, startKind: BoundKind, end: A, endKind: BoundKind)( + implicit comp: Comparator[_ >: A]) + extends DescendingTreeIterator[A, B, B](tree, start, startKind, end, endKind) { + + def nextResult(node: Node[A, B]): B = node.value + } + + // building + + /** Common implementation of `fromOrderedKeys` and `fromOrderedEntries`. */ + @noinline + def fromOrdered[A, B, C](xs: Iterator[A], size: Int, + keyOf: js.Function1[A, B], valueOf: js.Function1[A, C]): Tree[B, C] = { + // maximum depth of non-leaf nodes == floor(log2(size)) + val maxUsedDepth = 32 - Integer.numberOfLeadingZeros(size) + + @noinline + def createSubTree(level: Int, size: Int): Node[B, C] = size match { + case 0 => + null + case 1 => + /* Nodes on the last level must be red, because the last level might + * not be full. If they were black, then the paths from the root to the + * last level would have 1 more black node than those ending at the + * second-to-last level. + * Exception: when the only node is the root, because the root must be + * black. + */ + val item = xs.next() + val red = level == maxUsedDepth && level != 1 + Node.leaf(keyOf(item), valueOf(item), red, null) + case _ => + val leftSize = (size - 1) >> 1 // opt: (size - 1) / 2 with size > 0 + val left = createSubTree(level + 1, leftSize) + val item = xs.next() + val right = createSubTree(level + 1, size - 1 - leftSize) + val node = Node(keyOf(item), valueOf(item), false, left, right, null) + if (left ne null) + left.parent = node + right.parent = node + node + } + + new Tree(createSubTree(1, size), size) + } + + /** Build a Tree suitable for a TreeSet from an ordered sequence of keys. + * + * All values will be `()`. + */ + def fromOrderedKeys[A](xs: Iterator[A], size: Int): Tree[A, Any] = + fromOrdered(xs, size, (x: A) => x, (_: A) => ()) + + /** Build a Tree suitable for a TreeMap from an ordered sequence of key/value + * pairs. + */ + def fromOrderedEntries[A, B](xs: Iterator[Map.Entry[A, B]], size: Int): Tree[A, B] = + fromOrdered(xs, size, (x: Map.Entry[A, B]) => x.getKey(), (x: Map.Entry[A, B]) => x.getValue()) + + private def copyTree[A, B](n: Node[A, B]): Node[A, B] = { + if (n eq null) { + null + } else { + val c = Node(n.key, n.value, n.red, copyTree(n.left), copyTree(n.right), null) + if (c.left != null) + c.left.parent = c + if (c.right != null) + c.right.parent = c + c + } + } +} diff --git a/javalib/src/main/scala/java/util/TreeSet.scala b/javalib/src/main/scala/java/util/TreeSet.scala index 85e42a099e..723e8ed2d4 100644 --- a/javalib/src/main/scala/java/util/TreeSet.scala +++ b/javalib/src/main/scala/java/util/TreeSet.scala @@ -12,245 +12,422 @@ package java.util -import java.lang.Comparable +import java.util.{RedBlackTree => RB} -import scala.math.Ordering +class TreeSet[E] private (tree: RB.Tree[E, Any])( + implicit comp: Comparator[_ >: E]) + extends AbstractSet[E] with NavigableSet[E] with Cloneable with Serializable { -import scala.collection.mutable + import TreeSet._ -import Compat.SortedSetCompat -import ScalaOps._ - -class TreeSet[E] (_comparator: Comparator[_ >: E]) - extends AbstractSet[E] - with NavigableSet[E] - with Cloneable - with Serializable { self => + /* Note: in practice, the values of `tree` are always `()` (aka `undefined`). + * We use `Any` because we need to deal with `null`s, and referencing + * `scala.runtime.BoxedUnit` in this code would be really ugly. + */ def this() = - this(null.asInstanceOf[Comparator[_ >: E]]) + this(RB.Tree.empty[E, Any])(NaturalComparator) + + def this(comparator: Comparator[_ >: E]) = + this(RB.Tree.empty[E, Any])(NaturalComparator.select(comparator)) def this(collection: Collection[_ <: E]) = { - this(null.asInstanceOf[Comparator[E]]) + this() addAll(collection) } def this(sortedSet: SortedSet[E]) = { - this(sortedSet.comparator()) - addAll(sortedSet) + this(RB.fromOrderedKeys(sortedSet.iterator(), sortedSet.size()))( + NaturalComparator.select(sortedSet.comparator())) } - private implicit object BoxOrdering extends Ordering[Box[E]] { + def iterator(): Iterator[E] = + RB.keysIterator(tree) - val cmp = { - if (_comparator ne null) _comparator - else defaultOrdering[E] - } + def descendingIterator(): Iterator[E] = + RB.descendingKeysIterator(tree) + + def descendingSet(): NavigableSet[E] = { + new DescendingProjection(tree, null.asInstanceOf[E], RB.NoBound, + null.asInstanceOf[E], RB.NoBound) + } - def compare(a: Box[E], b: Box[E]): Int = cmp.compare(a.inner, b.inner) + def size(): Int = + RB.size(tree) + override def isEmpty(): Boolean = + RB.isEmpty(tree) + + override def contains(o: Any): Boolean = + RB.contains(tree, o) + + override def add(e: E): Boolean = + RB.insert(tree, e, ()) == null + + override def remove(o: Any): Boolean = + RB.delete(tree, o) != null + + override def clear(): Unit = + RB.clear(tree) + + def subSet(fromElement: E, fromInclusive: Boolean, toElement: E, + toInclusive: Boolean): NavigableSet[E] = { + new Projection(tree, + fromElement, RB.boundKindFromIsInclusive(fromInclusive), + toElement, RB.boundKindFromIsInclusive(toInclusive)) } - private val inner: mutable.TreeSet[Box[E]] = new mutable.TreeSet[Box[E]]() + def headSet(toElement: E, inclusive: Boolean): NavigableSet[E] = { + new Projection(tree, + null.asInstanceOf[E], RB.NoBound, + toElement, RB.boundKindFromIsInclusive(inclusive)) + } - def iterator(): Iterator[E] = { - new Iterator[E] { - private val iter = inner.clone.iterator + def tailSet(fromElement: E, inclusive: Boolean): NavigableSet[E] = { + new Projection(tree, + fromElement, RB.boundKindFromIsInclusive(inclusive), + null.asInstanceOf[E], RB.NoBound) + } - private var last: Option[E] = None + def subSet(fromElement: E, toElement: E): SortedSet[E] = + subSet(fromElement, true, toElement, false) - def hasNext(): Boolean = iter.hasNext + def headSet(toElement: E): SortedSet[E] = + headSet(toElement, false) - def next(): E = { - last = Some(iter.next().inner) - last.get - } + def tailSet(fromElement: E): SortedSet[E] = + tailSet(fromElement, true) - def remove(): Unit = { - if (last.isEmpty) { - throw new IllegalStateException() - } else { - last.foreach(self.remove(_)) - last = None - } - } - } + def comparator(): Comparator[_ >: E] = + NaturalComparator.unselect(comp) + + def first(): E = { + if (isEmpty()) + throw new NoSuchElementException() + RB.minKey(tree) } - def descendingIterator(): Iterator[E] = { - new Iterator[E] { - private val iter = inner.iterator.toList.reverse.iterator + def last(): E = { + if (isEmpty()) + throw new NoSuchElementException() + RB.maxKey(tree) + } - private var last: Option[E] = None + def lower(e: E): E = + RB.maxKeyBefore(tree, e, RB.ExclusiveBound) - def hasNext(): Boolean = iter.hasNext + def floor(e: E): E = + RB.maxKeyBefore(tree, e, RB.InclusiveBound) - def next(): E = { - val nxt = iter.next().inner - last = Some(nxt) - nxt - } + def ceiling(e: E): E = + RB.minKeyAfter(tree, e, RB.InclusiveBound) - def remove(): Unit = { - if (last.isEmpty) { - throw new IllegalStateException() - } else { - last.foreach(self.remove(_)) - last = None - } - } + def higher(e: E): E = + RB.minKeyAfter(tree, e, RB.ExclusiveBound) + + def pollFirst(): E = { + val node = RB.minNode(tree) + if (node ne null) { + RB.deleteNode(tree, node) + node.key + } else { + null.asInstanceOf[E] } } - def descendingSet(): NavigableSet[E] = { - val descSetFun = { () => - val retSet = new mutable.TreeSet[Box[E]]()(BoxOrdering.reverse) - retSet ++= inner - retSet + def pollLast(): E = { + val node = RB.maxNode(tree) + if (node ne null) { + RB.deleteNode(tree, node) + node.key + } else { + null.asInstanceOf[E] } - new NavigableView(this, descSetFun, None, true, None, true) } - def size(): Int = - inner.size + override def clone(): TreeSet[E] = + new TreeSet(tree.treeCopy())(comp) +} - override def isEmpty(): Boolean = - inner.headOption.isEmpty +private object TreeSet { + private abstract class AbstractProjection[E]( + protected val tree: RB.Tree[E, Any], + protected val lowerBound: E, protected val lowerKind: RB.BoundKind, + protected val upperBound: E, protected val upperKind: RB.BoundKind)( + implicit protected val comp: Comparator[_ >: E]) + extends AbstractSet[E] with NavigableSet[E] { - override def contains(o: Any): Boolean = - inner.contains(Box(o.asInstanceOf[E])) + // To be implemented by the two concrete subclasses, depending on the order - override def add(e: E): Boolean = { - val boxed = Box(e) + protected def nextKey(key: E, boundKind: RB.BoundKind): E + protected def previousKey(key: E, boundKind: RB.BoundKind): E - if (isEmpty) - BoxOrdering.compare(boxed, boxed) + protected def subSetGeneric(newFromElement: E = null.asInstanceOf[E], + newFromBoundKind: RB.BoundKind = RB.NoBound, + newToElement: E = null.asInstanceOf[E], + newToBoundKind: RB.BoundKind = RB.NoBound): NavigableSet[E] - inner.add(boxed) - } + // Implementation of most of the NavigableSet API - override def remove(o: Any): Boolean = - inner.remove(Box(o.asInstanceOf[E])) + def size(): Int = + RB.projectionSize(tree, lowerBound, lowerKind, upperBound, upperKind) - override def clear(): Unit = - inner.clear() - - override def addAll(c: Collection[_ <: E]): Boolean = { - val iter = c.iterator() - var changed = false - while (iter.hasNext) - changed = add(iter.next()) || changed - changed - } + override def isEmpty(): Boolean = + RB.projectionIsEmpty(tree, lowerBound, lowerKind, upperBound, upperKind) - override def removeAll(c: Collection[_]): Boolean = { - val iter = c.iterator() - var changed = false - while (iter.hasNext) - changed = inner.remove(Box(iter.next).asInstanceOf[Box[E]]) || changed - changed - } + override def contains(o: Any): Boolean = + isWithinBounds(o) && RB.contains(tree, o) - def subSet(fromElement: E, fromInclusive: Boolean, toElement: E, - toInclusive: Boolean): NavigableSet[E] = { - val boxedFrom = Box(fromElement) - val boxedTo = Box(toElement) - - val subSetFun = { () => - val base = new mutable.TreeSet[Box[E]] - base ++= inner.range(boxedFrom, boxedTo) - if (!fromInclusive) - base -= boxedFrom - if (toInclusive && inner.contains(boxedTo)) - base += boxedTo - base + override def add(e: E): Boolean = { + if (!isWithinBounds(e)) + throw new IllegalArgumentException + RB.insert(tree, e, ()) == null } - new NavigableView(this, subSetFun, - Some(fromElement), fromInclusive, - Some(toElement), toInclusive) - } + override def remove(o: Any): Boolean = + isWithinBounds(o) && RB.delete(tree, o) != null - def headSet(toElement: E, inclusive: Boolean): NavigableSet[E] = { - val boxed = Box(toElement) - - val headSetFun = { () => - val base = new mutable.TreeSet[Box[E]] - if (inclusive) - base ++= inner.rangeTo(boxed) - else - base ++= inner.rangeUntil(boxed) - base + def lower(e: E): E = + previousKey(e, RB.ExclusiveBound) + + def floor(e: E): E = + previousKey(e, RB.InclusiveBound) + + def ceiling(e: E): E = + nextKey(e, RB.InclusiveBound) + + def higher(e: E): E = + nextKey(e, RB.ExclusiveBound) + + def subSet(fromElement: E, fromInclusive: Boolean, toElement: E, + toInclusive: Boolean): NavigableSet[E] = { + subSetGeneric( + fromElement, RB.boundKindFromIsInclusive(fromInclusive), + toElement, RB.boundKindFromIsInclusive(toInclusive)) } - new NavigableView(this, headSetFun, - None, true, - Some(toElement), inclusive) - } + def headSet(toElement: E, inclusive: Boolean): NavigableSet[E] = { + subSetGeneric(newToElement = toElement, + newToBoundKind = RB.boundKindFromIsInclusive(inclusive)) + } - def tailSet(fromElement: E, inclusive: Boolean): NavigableSet[E] = { - val boxed = Box(fromElement) - - val tailSetFun = { () => - val base = new mutable.TreeSet[Box[E]] - base ++= inner.rangeFrom(boxed) - if (!inclusive) - base -= boxed - base + def tailSet(fromElement: E, inclusive: Boolean): NavigableSet[E] = { + subSetGeneric(newFromElement = fromElement, + newFromBoundKind = RB.boundKindFromIsInclusive(inclusive)) } - new NavigableView(this, tailSetFun, - Some(fromElement), inclusive, - None, true) - } + def subSet(fromElement: E, toElement: E): SortedSet[E] = + subSet(fromElement, true, toElement, false) - def subSet(fromElement: E, toElement: E): SortedSet[E] = - subSet(fromElement, true, toElement, false) + def headSet(toElement: E): SortedSet[E] = + headSet(toElement, false) - def headSet(toElement: E): SortedSet[E] = - headSet(toElement, false) + def tailSet(fromElement: E): SortedSet[E] = + tailSet(fromElement, true) - def tailSet(fromElement: E): SortedSet[E] = - tailSet(fromElement, true) + // Common implementation of pollFirst() and pollLast() - def comparator(): Comparator[_ >: E] = _comparator + @inline + protected final def pollLower(): E = { + val node = RB.minNodeAfter(tree, lowerBound, lowerKind) + if (node ne null) { + val key = node.key + if (isWithinUpperBound(key)) { + RB.deleteNode(tree, node) + key + } else { + null.asInstanceOf[E] + } + } else { + null.asInstanceOf[E] + } + } - def first(): E = - inner.head.inner + @inline + protected final def pollUpper(): E = { + val node = RB.maxNodeBefore(tree, upperBound, upperKind) + if (node ne null) { + val key = node.key + if (isWithinLowerBound(key)) { + RB.deleteNode(tree, node) + key + } else { + null.asInstanceOf[E] + } + } else { + null.asInstanceOf[E] + } + } - def last(): E = - inner.last.inner + // Helpers - def lower(e: E): E = - headSet(e, false).last() + protected final def isWithinBounds(key: Any): Boolean = + isWithinLowerBound(key) && isWithinUpperBound(key) - def floor(e: E): E = - headSet(e, true).last() + protected final def isWithinLowerBound(key: Any): Boolean = + RB.isWithinLowerBound(key, lowerBound, lowerKind) - def ceiling(e: E): E = - tailSet(e, true).first() + protected final def isWithinUpperBound(key: Any): Boolean = + RB.isWithinUpperBound(key, upperBound, upperKind) - def higher(e: E): E = - tailSet(e, false).first() + protected final def ifWithinLowerBound(e: E): E = + if (e != null && isWithinLowerBound(e)) e + else null.asInstanceOf[E] - def pollFirst(): E = { - val polled = inner.headOption - if (polled.isDefined) { - val elem = polled.get.inner - remove(elem) - elem - } else null.asInstanceOf[E] + protected final def ifWithinUpperBound(e: E): E = + if (e != null && isWithinUpperBound(e)) e + else null.asInstanceOf[E] } - def pollLast(): E = { - val polled = inner.lastOption - if (polled.isDefined) { - val elem = polled.get.inner - remove(elem) - elem - } else null.asInstanceOf[E] + private final class Projection[E]( + tree0: RB.Tree[E, Any], fromElement0: E, fromBoundKind0: RB.BoundKind, + toElement0: E, toBoundKind0: RB.BoundKind)( + implicit comp: Comparator[_ >: E]) + extends AbstractProjection[E](tree0, fromElement0, fromBoundKind0, + toElement0, toBoundKind0) { + + // Access fields under a different name, more appropriate for some uses + + @inline private def fromElement: E = lowerBound + @inline private def fromBoundKind: RB.BoundKind = lowerKind + @inline private def toElement: E = 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 nextKey(key: E, boundKind: RB.BoundKind): E = + ifWithinUpperBound(RB.minKeyAfter(tree, key, boundKind)) + + @inline + protected def previousKey(key: E, boundKind: RB.BoundKind): E = + ifWithinLowerBound(RB.maxKeyBefore(tree, key, boundKind)) + + protected def subSetGeneric( + newFromElement: E, newFromBoundKind: RB.BoundKind, + newToElement: E, newToBoundKind: RB.BoundKind): NavigableSet[E] = { + val intersectedFromBound = RB.intersectLowerBounds( + new RB.Bound(fromElement, fromBoundKind), + new RB.Bound(newFromElement, newFromBoundKind)) + val intersectedToBound = RB.intersectUpperBounds( + new RB.Bound(toElement, toBoundKind), + new RB.Bound(newToElement, newToBoundKind)) + new Projection(tree, + intersectedFromBound.bound, intersectedFromBound.kind, + intersectedToBound.bound, intersectedToBound.kind) + } + + // Methods of the NavigableSet API that are not implemented in AbstractProjection + + def iterator(): Iterator[E] = + RB.projectionKeysIterator(tree, fromElement, fromBoundKind, toElement, toBoundKind) + + def comparator(): Comparator[_ >: E] = + NaturalComparator.unselect(comp) + + def first(): E = { + val key = nextKey(fromElement, fromBoundKind) + if (key == null) + throw new NoSuchElementException() + key + } + + def last(): E = { + val key = previousKey(toElement, toBoundKind) + if (key == null) + throw new NoSuchElementException() + key + } + + @noinline + def pollFirst(): E = + pollLower() + + @noinline + def pollLast(): E = + pollUpper() + + def descendingSet(): NavigableSet[E] = + new DescendingProjection(tree, toElement, toBoundKind, fromElement, fromBoundKind) + + def descendingIterator(): Iterator[E] = + RB.descendingKeysIterator(tree, toElement, toBoundKind, fromElement, fromBoundKind) } - override def clone(): TreeSet[E] = - new TreeSet(this) + private final class DescendingProjection[E]( + tree0: RB.Tree[E, Any], fromElement0: E, fromBoundKind0: RB.BoundKind, + toElement0: E, toBoundKind0: RB.BoundKind)( + implicit comp: Comparator[_ >: E]) + extends AbstractProjection[E](tree0, toElement0, toBoundKind0, + fromElement0, fromBoundKind0) { + + // Access fields under a different name, more appropriate for some uses + + @inline private def fromElement: E = upperBound + @inline private def fromBoundKind: RB.BoundKind = upperKind + @inline private def toElement: E = lowerBound + @inline private def toBoundKind: RB.BoundKind = lowerKind + + // Implementation of the abstract methods from AbstractProjection + + protected def nextKey(key: E, boundKind: RB.BoundKind): E = + ifWithinLowerBound(RB.maxKeyBefore(tree, key, boundKind)) + + protected def previousKey(key: E, boundKind: RB.BoundKind): E = + ifWithinUpperBound(RB.minKeyAfter(tree, key, boundKind)) + + protected def subSetGeneric( + newFromElement: E, newFromBoundKind: RB.BoundKind, + newToElement: E, newToBoundKind: RB.BoundKind): NavigableSet[E] = { + val intersectedFromBound = RB.intersectUpperBounds( + new RB.Bound(fromElement, fromBoundKind), + new RB.Bound(newFromElement, newFromBoundKind)) + val intersectedToBound = RB.intersectLowerBounds( + new RB.Bound(toElement, toBoundKind), + new RB.Bound(newToElement, newToBoundKind)) + new Projection(tree, + intersectedFromBound.bound, intersectedFromBound.kind, + intersectedToBound.bound, intersectedToBound.kind) + } + + // Methods of the NavigableSet API that are not implemented in AbstractProjection + + def iterator(): Iterator[E] = + RB.descendingKeysIterator(tree, fromElement, fromBoundKind, toElement, toBoundKind) + + def comparator(): Comparator[_ >: E] = + Collections.reverseOrder(NaturalComparator.unselect(comp)) + + def first(): E = { + val key = nextKey(fromElement, fromBoundKind) + if (key == null) + throw new NoSuchElementException() + key + } + + def last(): E = { + val key = previousKey(toElement, toBoundKind) + if (key == null) + throw new NoSuchElementException() + key + } + + @noinline + def pollFirst(): E = + pollUpper() + + @noinline + def pollLast(): E = + pollLower() + + def descendingSet(): NavigableSet[E] = + new Projection(tree, toElement, toBoundKind, fromElement, fromBoundKind) + + def descendingIterator(): Iterator[E] = + RB.projectionKeysIterator(tree, toElement, toBoundKind, fromElement, fromBoundKind) + } } diff --git a/javalib/src/main/scala/java/util/package.scala b/javalib/src/main/scala/java/util/package.scala deleted file mode 100644 index 4f87826ba9..0000000000 --- a/javalib/src/main/scala/java/util/package.scala +++ /dev/null @@ -1,37 +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. - */ - -package java - -package object util { - - private[util] final case class Box[+K](inner: K) { - def apply(): K = inner - - override def equals(o: Any): Boolean = { - o match { - case o: Box[_] => Objects.equals(inner, o.inner) - case _ => false - } - } - - override def hashCode(): Int = - Objects.hashCode(inner) - } - - private[util] def defaultOrdering[E]: Ordering[E] = { - new Ordering[E] { - def compare(a: E, b: E): Int = - a.asInstanceOf[Comparable[E]].compareTo(b) - } - } -} From 4c4509017550fc719ea495896c53af502beace26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 3 Sep 2019 17:14:42 +0200 Subject: [PATCH 0069/1606] Remove explicit uses of Scala collections in the javalib. Use JavaScript or Java collections instead. There is a change of behavior in `Properties.propertyNames()`, which now disregards any overriden `propertyNames()` in the default property map. The new behavior matches that of OpenJDK, and we add relevant tests. --- .../scala/java/nio/charset/CoderResult.scala | 32 ++++++-- javalib/src/main/scala/java/util/Arrays.scala | 79 +++++++++++-------- .../main/scala/java/util/Collections.scala | 74 +++++++++++------ .../src/main/scala/java/util/Properties.scala | 37 ++++++--- .../src/main/scala/java/util/ScalaOps.scala | 59 -------------- javalib/src/main/scala/java/util/Timer.scala | 1 - .../concurrent/CopyOnWriteArrayList.scala | 59 +++++++++----- .../javalib/util/PropertiesTest.scala | 24 ++++++ 8 files changed, 206 insertions(+), 159 deletions(-) diff --git a/javalib/src/main/scala/java/nio/charset/CoderResult.scala b/javalib/src/main/scala/java/nio/charset/CoderResult.scala index 8cfe677a1b..0721da1988 100644 --- a/javalib/src/main/scala/java/nio/charset/CoderResult.scala +++ b/javalib/src/main/scala/java/nio/charset/CoderResult.scala @@ -14,10 +14,10 @@ package java.nio.charset import scala.annotation.switch -import scala.collection.mutable - import java.nio._ +import scala.scalajs.js + class CoderResult private (kind: Int, _length: Int) { import CoderResult._ @@ -57,14 +57,16 @@ object CoderResult { private val Malformed3 = new CoderResult(Malformed, 3) private val Malformed4 = new CoderResult(Malformed, 4) - private val uniqueMalformed = mutable.Map.empty[Int, CoderResult] + // This is a sparse array + private val uniqueMalformed = js.Array[js.UndefOr[CoderResult]]() private val Unmappable1 = new CoderResult(Unmappable, 1) private val Unmappable2 = new CoderResult(Unmappable, 2) private val Unmappable3 = new CoderResult(Unmappable, 3) private val Unmappable4 = new CoderResult(Unmappable, 4) - private val uniqueUnmappable = mutable.Map.empty[Int, CoderResult] + // This is a sparse array + private val uniqueUnmappable = js.Array[js.UndefOr[CoderResult]]() @inline def malformedForLength(length: Int): CoderResult = (length: @switch) match { case 1 => Malformed1 @@ -74,8 +76,15 @@ object CoderResult { case _ => malformedForLengthImpl(length) } - private def malformedForLengthImpl(length: Int): CoderResult = - uniqueMalformed.getOrElseUpdate(length, new CoderResult(Malformed, length)) + private def malformedForLengthImpl(length: Int): CoderResult = { + uniqueMalformed(length).fold { + val result = new CoderResult(Malformed, length) + uniqueMalformed(length) = result + result + } { result => + result + } + } @inline def unmappableForLength(length: Int): CoderResult = (length: @switch) match { case 1 => Unmappable1 @@ -85,6 +94,13 @@ object CoderResult { case _ => unmappableForLengthImpl(length) } - private def unmappableForLengthImpl(length: Int): CoderResult = - uniqueUnmappable.getOrElseUpdate(length, new CoderResult(Unmappable, length)) + private def unmappableForLengthImpl(length: Int): CoderResult = { + uniqueUnmappable(length).fold { + val result = new CoderResult(Unmappable, length) + uniqueUnmappable(length) = result + result + } { result => + result + } + } } diff --git a/javalib/src/main/scala/java/util/Arrays.scala b/javalib/src/main/scala/java/util/Arrays.scala index 57f69bd8fd..582e5c43f6 100644 --- a/javalib/src/main/scala/java/util/Arrays.scala +++ b/javalib/src/main/scala/java/util/Arrays.scala @@ -19,8 +19,6 @@ import scala.annotation.tailrec import scala.reflect.ClassTag -import scala.collection.immutable - object Arrays { @inline @@ -718,31 +716,56 @@ object Arrays { else a.mkString("[", ", ", "]") } - @noinline def deepToString(a: Array[AnyRef]): String = - deepToStringImpl(a, immutable.HashSet.empty[AsRef]) + def deepToString(a: Array[AnyRef]): String = { + /* The following array represents a set of the `Array[AnyRef]` that have + * already been seen in the current recursion. We use a JS array instead of + * a full-blown `HashSet` because it will likely stay very short (its size + * is O(h) where h is the height of the tree of non-cyclical paths starting + * at `a`), so the cost of using `System.identityHashCode` will probably + * outweigh the benefits of the time complexity guarantees provided by a + * hash-set. + */ + val seen = js.Array[Array[AnyRef]]() + + @inline def wasSeen(a: Array[AnyRef]): Boolean = { + // JavaScript's indexOf uses `===` + seen.asInstanceOf[js.Dynamic].indexOf(a.asInstanceOf[js.Any]).asInstanceOf[Int] >= 0 + } - private def deepToStringImpl(a: Array[AnyRef], branch: immutable.Set[AsRef]): String = { - @inline - def valueToString(e: AnyRef): String = { - if (e == null) "null" - else { - e match { - case e: Array[AnyRef] => deepToStringImpl(e, branch + new AsRef(a)) - case e: Array[Long] => toString(e) - case e: Array[Int] => toString(e) - case e: Array[Short] => toString(e) - case e: Array[Byte] => toString(e) - case e: Array[Char] => toString(e) - case e: Array[Boolean] => toString(e) - case e: Array[Float] => toString(e) - case e: Array[Double] => toString(e) - case _ => String.valueOf(e) + def rec(a: Array[AnyRef]): String = { + var result = "[" + val len = a.length + var i = 0 + while (i != len) { + if (i != 0) + result += ", " + a(i) match { + case e: Array[AnyRef] => + if ((e eq a) || wasSeen(e)) { + result += "[...]" + } else { + seen.push(a) + result += rec(e) + seen.pop() + } + + case e: Array[Long] => result += toString(e) + case e: Array[Int] => result += toString(e) + case e: Array[Short] => result += toString(e) + case e: Array[Byte] => result += toString(e) + case e: Array[Char] => result += toString(e) + case e: Array[Boolean] => result += toString(e) + case e: Array[Float] => result += toString(e) + case e: Array[Double] => result += toString(e) + case e => result += e // handles null } + i += 1 } + result + "]" } + if (a == null) "null" - else if (branch.contains(new AsRef(a))) "[...]" - else a.iterator.map(valueToString).mkString("[", ", ", "]") + else rec(a) } @inline @@ -761,16 +784,4 @@ object Arrays { def compare(x: T, y: T): Int = cmp.compare(x, y) } } - - private final class AsRef(val inner: AnyRef) { - override def hashCode(): Int = - System.identityHashCode(inner) - - override def equals(obj: Any): Boolean = { - obj match { - case obj: AsRef => obj.inner eq inner - case _ => false - } - } - } } diff --git a/javalib/src/main/scala/java/util/Collections.scala b/javalib/src/main/scala/java/util/Collections.scala index 2732559402..550abe2c72 100644 --- a/javalib/src/main/scala/java/util/Collections.scala +++ b/javalib/src/main/scala/java/util/Collections.scala @@ -19,8 +19,6 @@ import scala.language.implicitConversions import scala.annotation.tailrec -import scala.collection.mutable - import ScalaOps._ object Collections { @@ -72,10 +70,15 @@ object Collections { sort(list, naturalComparator[T]) def sort[T](list: List[T], c: Comparator[_ >: T]): Unit = { - val sortedListIter = list.scalaOps.toSeq.sorted(c).javaIterator() + val arrayBuf = list.toArray() + Arrays.sort[AnyRef with T](arrayBuf.asInstanceOf[Array[AnyRef with T]], c) + + // The spec of `Arrays.asList()` guarantees that its result implements RandomAccess + val sortedList = Arrays.asList(arrayBuf).asInstanceOf[List[T] with RandomAccess] + list match { - case list: RandomAccess => copyImpl(sortedListIter, list) - case _ => copyImpl(sortedListIter, list.listIterator) + case list: RandomAccess => copyImpl(sortedList, list) + case _ => copyImpl(sortedList, list.listIterator) } } @@ -149,16 +152,35 @@ object Collections { def shuffle(list: List[_]): Unit = shuffle(list, new Random) + @noinline def shuffle(list: List[_], rnd: Random): Unit = shuffleImpl(list, rnd) @inline - def shuffleImpl[T](list: List[T], rnd: Random): Unit = { - val scalaRnd = scala.util.Random.javaRandomToRandom(rnd) - val shuffledListIter = scalaRnd.shuffle(list.scalaOps.toSeq).javaIterator() + private def shuffleImpl[T](list: List[T], rnd: Random): Unit = { + def shuffleInPlace(list: List[T] with RandomAccess): Unit = { + @inline + def swap(i1: Int, i2: Int): Unit = { + val tmp = list.get(i1) + list.set(i1, list.get(i2)) + list.set(i2, tmp) + } + + var n = list.size() + while (n > 1) { + val k = rnd.nextInt(n) + swap(n - 1, k) + n -= 1 + } + } + list match { - case list: RandomAccess => copyImpl(shuffledListIter, list) - case _ => copyImpl(shuffledListIter, list.listIterator) + case list: RandomAccess => + shuffleInPlace(list) + case _ => + val buffer = new ArrayList[T](list) + shuffleInPlace(buffer) + copyImpl(buffer, list.listIterator) } } @@ -555,13 +577,15 @@ object Collections { } def addAll[T](c: Collection[_ >: T], elements: Array[AnyRef]): Boolean = { - val elementsColl = new AbstractCollection[T] { - def size(): Int = elements.length - - def iterator(): Iterator[T] = - (elements: mutable.Seq[AnyRef]).javaIterator().asInstanceOf[Iterator[T]] + var added = false + val len = elements.length + var i = 0 + while (i != len) { + if (c.add(elements(i).asInstanceOf[T])) + added = true + i += 1 } - c.addAll(elementsColl) + added } def newSetFromMap[E](map: Map[E, java.lang.Boolean]): Set[E] = { @@ -845,12 +869,11 @@ object Collections { if (eagerThrow) { throw new UnsupportedOperationException } else { - val cSet = c.asInstanceOf[Collection[AnyRef]].scalaOps.toSet - if (this.scalaOps.exists(e => cSet(e.asInstanceOf[AnyRef]))) { - throw new UnsupportedOperationException - } else { - false + this.scalaOps.foreach { item => + if (c.contains(item)) + throw new UnsupportedOperationException() } + false } } @@ -858,12 +881,11 @@ object Collections { if (eagerThrow) { throw new UnsupportedOperationException } else { - val cSet = c.asInstanceOf[Collection[AnyRef]].scalaOps.toSet - if (this.scalaOps.exists(e => !cSet(e.asInstanceOf[AnyRef]))) { - throw new UnsupportedOperationException - } else { - false + this.scalaOps.foreach { item => + if (!c.contains(item)) + throw new UnsupportedOperationException() } + false } } } diff --git a/javalib/src/main/scala/java/util/Properties.scala b/javalib/src/main/scala/java/util/Properties.scala index 6a43e2e017..baf1b8f11d 100644 --- a/javalib/src/main/scala/java/util/Properties.scala +++ b/javalib/src/main/scala/java/util/Properties.scala @@ -12,9 +12,11 @@ package java.util +import scala.annotation.tailrec + import java.{util => ju} -import scala.collection.mutable +import scala.scalajs.js import ScalaOps._ @@ -49,27 +51,36 @@ class Properties(protected val defaults: Properties) } def propertyNames(): ju.Enumeration[_] = { - val propNames = mutable.Set.empty[Any] - // Explicitly use asInstanceOf, to trigger the ClassCastException mandated by the spec - keySet().scalaOps.foreach(propNames += _.asInstanceOf[String]) - if (defaults != null) - defaults.propertyNames().scalaOps.foreach(propNames += _) - propNames.iterator.asJavaEnumeration + val propNames = new ju.HashSet[String] + foreachAncestor { ancestor => + ancestor.keySet().scalaOps.foreach { key => + // Explicitly use asInstanceOf, to trigger the ClassCastException mandated by the spec + propNames.add(key.asInstanceOf[String]) + } + } + Collections.enumeration(propNames) } def stringPropertyNames(): ju.Set[String] = { val set = new ju.HashSet[String] - entrySet().scalaOps.foreach { entry => - (entry.getKey, entry.getValue) match { - case (key: String, _: String) => set.add(key) - case _ => // Ignore key + foreachAncestor { ancestor => + ancestor.entrySet().scalaOps.foreach { entry => + (entry.getKey, entry.getValue) match { + case (key: String, _: String) => set.add(key) + case _ => // Ignore key + } } } - if (defaults != null) - set.addAll(defaults.stringPropertyNames()) set } + @inline @tailrec + private final def foreachAncestor(f: Properties => Unit): Unit = { + f(this) + if (defaults ne null) + defaults.foreachAncestor(f) + } + // def list(out: PrintStream): Unit // def list(out: PrintWriter): Unit } diff --git a/javalib/src/main/scala/java/util/ScalaOps.scala b/javalib/src/main/scala/java/util/ScalaOps.scala index f9f3e22f29..fd32d8b453 100644 --- a/javalib/src/main/scala/java/util/ScalaOps.scala +++ b/javalib/src/main/scala/java/util/ScalaOps.scala @@ -12,45 +12,9 @@ package java.util -import scala.collection.immutable -import scala.collection.mutable - /** Make some Scala collection APIs available on Java collections. */ private[util] object ScalaOps { - implicit class ToScalaIterableOps[A] private[ScalaOps] ( - val __self: scala.collection.Iterable[A]) - extends AnyVal { - def javaIterator(): Iterator[A] = - new JavaIteratorAdapter(__self.iterator) - } - - private class JavaIteratorAdapter[A](scalaIterator: scala.collection.Iterator[A]) - extends Iterator[A] { - def hasNext(): Boolean = scalaIterator.hasNext - def next(): A = scalaIterator.next() - - def remove(): Unit = - throw new UnsupportedOperationException("remove") - } - - implicit class ScalaIteratorOps[A] private[ScalaOps] ( - val __self: scala.collection.Iterator[A]) - extends AnyVal { - - def asJavaEnumeration(): Enumeration[A] = - new JavaEnumerationAdapter(__self) - } - - private class JavaEnumerationAdapter[A] private[ScalaOps] ( - val __self: scala.collection.Iterator[A]) - extends Enumeration[A] { - - def hasMoreElements(): Boolean = __self.hasNext - - def nextElement(): A = __self.next() - } - implicit class ToJavaIterableOps[A] private[ScalaOps] ( val __self: java.lang.Iterable[A]) extends AnyVal { @@ -85,15 +49,6 @@ private[util] object ScalaOps { @inline def reduceLeft[B >: A](f: (B, A) => B): B = __self.iterator().scalaOps.reduceLeft(f) - @inline def toList: immutable.List[A] = - __self.iterator().scalaOps.toList - - @inline def toSeq: immutable.Seq[A] = - __self.iterator().scalaOps.toSeq - - @inline def toSet: immutable.Set[A] = - __self.iterator().scalaOps.toSet - @inline def mkString(start: String, sep: String, end: String): String = __self.iterator().scalaOps.mkString(start, sep, end) } @@ -164,20 +119,6 @@ private[util] object ScalaOps { foldLeft[B](__self.next())(f) } - @inline def toList: immutable.List[A] = { - val builder = immutable.List.newBuilder[A] - foreach(builder += _) - builder.result() - } - - @inline def toSeq: immutable.Seq[A] = toList - - @inline def toSet: immutable.Set[A] = { - val builder = immutable.Set.newBuilder[A] - foreach(builder += _) - builder.result() - } - @inline def mkString(start: String, sep: String, end: String): String = { var result: String = start var first = true diff --git a/javalib/src/main/scala/java/util/Timer.scala b/javalib/src/main/scala/java/util/Timer.scala index 2c79059ac5..fd28fe69d2 100644 --- a/javalib/src/main/scala/java/util/Timer.scala +++ b/javalib/src/main/scala/java/util/Timer.scala @@ -12,7 +12,6 @@ package java.util -import scala.collection._ import scala.concurrent.duration._ class Timer() { diff --git a/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala b/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala index b8d77f88ac..c7bc527c91 100644 --- a/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala +++ b/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala @@ -116,13 +116,13 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) def add(index: Int, element: E): Unit = { checkIndexOnBounds(index) copyIfNeeded() - innerSplice(index, 0, element) + innerInsert(index, element) } def remove(index: Int): E = { checkIndexInBounds(index) copyIfNeeded() - innerSplice(index, 1)(0) + innerRemove(index) } def remove(o: scala.Any): Boolean = { @@ -182,7 +182,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) def addAll(index: Int, c: Collection[_ <: E]): Boolean = { checkIndexOnBounds(index) copyIfNeeded() - innerSplice(index, 0, c.asInstanceOf[Collection[E]].scalaOps.toSeq: _*) + innerInsertMany(index, c) !c.isEmpty } @@ -202,7 +202,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) * truncate at the current index. */ copyIfNeeded() - innerSplice(index, size() - index) + innerRemoveMany(index, size() - index) /* Now keep iterating, but push elements that do not pass the test. * `index` is useless from now on, so do not keep updating it. */ @@ -267,8 +267,20 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) protected def innerPush(elem: E): Unit = inner.push(elem) - protected def innerSplice(index: Int, deleteCount: Int, items: E*): js.Array[E] = - inner.splice(index, deleteCount, items: _*) + protected def innerInsert(index: Int, elem: E): Unit = + inner.splice(index, 0, elem) + + protected def innerInsertMany(index: Int, items: Collection[_ <: E]): Unit = { + val itemsArray = js.Array[E]() + items.scalaOps.foreach(itemsArray.push(_)) + inner.splice(index, 0, itemsArray.toSeq: _*) + } + + protected def innerRemove(index: Int): E = + inner.splice(index, 1)(0) + + protected def innerRemoveMany(index: Int, count: Int): Unit = + inner.splice(index, count) protected def copyIfNeeded(): Unit = { if (requiresCopyOnWrite) { @@ -291,7 +303,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) override def clear(): Unit = { copyIfNeeded() - self.innerSplice(fromIndex, size) + self.innerRemoveMany(fromIndex, size) changeSize(-size) } @@ -325,19 +337,30 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) override protected def innerSet(index: Int, elem: E): Unit = self.innerSet(fromIndex + index, elem) - override protected def innerSplice(index: Int, deleteCount: Int, - items: E*): js.Array[E] = { - changeSize(items.size - deleteCount) - self.innerSplice(fromIndex + index, deleteCount, items: _*) + override protected def innerPush(elem: E): Unit = { + changeSize(1) + self.innerInsert(toIndex - 1, elem) } - override protected def innerPush(elem: E): Unit = { - if (toIndex < self.size) { - innerSplice(size, 0, elem) - } else { - changeSize(1) - self.innerPush(elem) - } + override protected def innerInsert(index: Int, elem: E): Unit = { + changeSize(1) + self.innerInsert(fromIndex + index, elem) + } + + override protected def innerInsertMany(index: Int, + items: Collection[_ <: E]): Unit = { + changeSize(items.size()) + self.innerInsertMany(fromIndex + index, items) + } + + override protected def innerRemove(index: Int): E = { + changeSize(-1) + self.innerRemove(fromIndex + index) + } + + override protected def innerRemoveMany(index: Int, count: Int): Unit = { + changeSize(-count) + self.innerRemoveMany(index, count) } override protected def copyIfNeeded(): Unit = diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PropertiesTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PropertiesTest.scala index a1e1754bef..045de3b35e 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PropertiesTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/PropertiesTest.scala @@ -78,6 +78,18 @@ class PropertiesTest { assertEnumSameElementsAsSet[Any]("a", "b", "c", "d")(prop2.propertyNames()) } + @Test def propertyNamesIsNotAffectedByOverriddenPropertyNamesInDefaults(): Unit = { + val defaults = new java.util.Properties { + override def propertyNames(): ju.Enumeration[_] = + ju.Collections.emptyEnumeration[String]() + } + defaults.setProperty("foo", "bar") + + val props = new Properties(defaults) + props.setProperty("foobar", "babar") + assertEnumSameElementsAsSet[Any]("foo", "foobar")(props.propertyNames()) + } + @Test def propertyNamesWithBadContents(): Unit = { assumeTrue("Assumed compliant asInstanceOf", hasCompliantAsInstanceOfs) @@ -122,6 +134,18 @@ class PropertiesTest { assertCollSameElementsAsSet("a", "b", "c", "d")(prop2.stringPropertyNames()) } + @Test def stringPropertyNamesIsNotAffectedByOverriddenStringPropertyNamesInDefaults(): Unit = { + val defaults = new java.util.Properties { + override def stringPropertyNames(): ju.Set[String] = + ju.Collections.emptySet[String]() + } + defaults.setProperty("foo", "bar") + + val props = new Properties(defaults) + props.setProperty("foobar", "babar") + assertCollSameElementsAsSet("foo", "foobar")(props.stringPropertyNames()) + } + @Test def stringPropertyNamesWithBadContents(): Unit = { assumeTrue("Assumed compliant asInstanceOf", hasCompliantAsInstanceOfs) From e304a3ad2667771b569af874b824ac643960bb5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 2 Aug 2019 14:30:54 +0200 Subject: [PATCH 0070/1606] Avoid Scala Lists in `URI.normalize()`. Use a `js.Array` and an imperative algorithm instead. --- javalib/src/main/scala/java/net/URI.scala | 106 +++++++++++++--------- 1 file changed, 61 insertions(+), 45 deletions(-) diff --git a/javalib/src/main/scala/java/net/URI.scala b/javalib/src/main/scala/java/net/URI.scala index 7352ccdc92..8bfa123da3 100644 --- a/javalib/src/main/scala/java/net/URI.scala +++ b/javalib/src/main/scala/java/net/URI.scala @@ -201,63 +201,79 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { def isOpaque(): Boolean = _isOpaque def normalize(): URI = if (_isOpaque || _path.isEmpty) this else { + import js.JSStringOps._ + val origPath = _path.get + val segments = origPath.jsSplit("/") + // Step 1: Remove all "." segments // Step 2: Remove ".." segments preceeded by non ".." segment until no // longer applicable - /** Checks whether a successive ".." may drop the head of a - * reversed segment list. - */ - def okToDropFrom(resRev: List[String]) = - resRev.nonEmpty && resRev.head != ".." && resRev.head != "" - - @tailrec - def loop(in: List[String], resRev: List[String]): List[String] = in match { - case "." :: Nil => - // convert "." segments at end to an empty segment - // (consider: /a/b/. => /a/b/, not /a/b) - loop(Nil, "" :: resRev) - case ".." :: Nil if okToDropFrom(resRev) => - // prevent a ".." segment at end to change a "dir" into a "file" - // (consider: /a/b/.. => /a/, not /a) - loop(Nil, "" :: resRev.tail) - case "." :: xs => - // remove "." segments - loop(xs, resRev) - case "" :: xs if xs.nonEmpty => + val inLen = segments.length + val isAbsPath = inLen != 0 && segments(0) == "" + + // Do not inject the first empty segment into the normalization loop, + // so that we don't need to special-case it inside. + val startIdx = if (isAbsPath) 1 else 0 + var inIdx = startIdx + var outIdx = startIdx + + while (inIdx != inLen) { + val segment = segments(inIdx) + inIdx += 1 // do this before the rest of the loop + + if (segment == ".") { + if (inIdx == inLen) { + // convert "." segments at end to an empty segment + // (consider: /a/b/. => /a/b/, not /a/b) + segments(outIdx) = "" + outIdx += 1 + } else { + // remove "." segments, so do not increment outIdx + } + } else if (segment == "..") { + val okToDrop = outIdx != startIdx && { + val lastSegment = segments(outIdx - 1) + lastSegment != ".." && lastSegment != "" + } + if (okToDrop) { + if (inIdx == inLen) { // did we reach the end? + // prevent a ".." segment at end to change a "dir" into a "file" + // (consider: /a/b/.. => /a/, not /a) + segments(outIdx - 1) = "" + // do not increment outIdx + } else { + // remove preceding segment (it is not "..") + outIdx -= 1 + } + } else { + // cannot drop + segments(outIdx) = ".." + outIdx += 1 + } + } else if (segment == "" && inIdx != inLen) { // remove empty segments not at end of path - loop(xs, resRev) - case ".." :: xs if okToDropFrom(resRev) => - // Remove preceeding non-".." segment - loop(xs, resRev.tail) - case x :: xs => - loop(xs, x :: resRev) - case Nil => - resRev.reverse + // do not increment outIdx + } else { + // keep the segment + segments(outIdx) = segment + outIdx += 1 + } } - // Split into segments. -1 since we want empty trailing ones - val segments0 = origPath.split("/", -1).toList - val isAbsPath = segments0.nonEmpty && segments0.head == "" - // Don't inject first empty segment into normalization loop, so we - // won't need to special case it. - val segments1 = if (isAbsPath) segments0.tail else segments0 - val segments2 = loop(segments1, Nil) + // Truncate `segments` at `outIdx` + segments.length = outIdx // Step 3: If path is relative and first segment contains ":", prepend "." - // segment (according to JavaDoc). If it is absolute, add empty - // segment again to have leading "/". - val segments3 = { - if (isAbsPath) - "" :: segments2 - else if (segments2.nonEmpty && segments2.head.contains(':')) - "." :: segments2 - else segments2 - } + // segment (according to JavaDoc). If the path is absolute, the first + // segment is "" so the `contains(':')` returns false. + if (outIdx != 0 && segments(0).contains(":")) + segments.unshift(".") - val newPath = segments3.mkString("/") + // Now add all the segments from step 1, 2 and 3 + val newPath = segments.join("/") // Only create new instance if anything changed if (newPath == origPath) From fbbf7435f0ee46a3e09cc49e74185625a3ad06c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 2 Aug 2019 15:23:17 +0200 Subject: [PATCH 0071/1606] Avoid using Seqs to create the tables in `Base64`. --- javalib/src/main/scala/java/util/Base64.scala | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/javalib/src/main/scala/java/util/Base64.scala b/javalib/src/main/scala/java/util/Base64.scala index 77190b8313..68aaaa0b36 100644 --- a/javalib/src/main/scala/java/util/Base64.scala +++ b/javalib/src/main/scala/java/util/Base64.scala @@ -20,18 +20,37 @@ import java.nio.ByteBuffer object Base64 { - private val chars = ('A' to 'Z') ++ ('a' to 'z') ++ ('0' to '9') - - private val basicEncodeTable: Array[Byte] = - (chars ++ Seq('+', '/')).map(_.toByte).toArray + private val basicEncodeTable: Array[Byte] = { + val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + val table = new Array[Byte](64) + var i = 0 + while (i != 64) { + table(i) = chars.charAt(i).toByte + i += 1 + } + table + } - private val urlSafeEncodeTable: Array[Byte] = - (chars ++ Seq('-', '_')).map(_.toByte).toArray + private val urlSafeEncodeTable: Array[Byte] = { + val table = basicEncodeTable.clone() + table(62) = '-'.toByte + table(63) = '_'.toByte + table + } private def decodeTable(encode: Array[Byte]): Array[Int] = { - val decode = Array.fill[Int](256)(-1) - for ((b, i) <- encode.zipWithIndex) - decode(b) = i + val decode = new Array[Int](256) + var i = 0 + while (i != 256) { + decode(i) = -1 + i += 1 + } + val len = encode.length + var j = 0 + while (j != len) { + decode(encode(j)) = j + j += 1 + } decode('=') = -2 decode } @@ -39,7 +58,7 @@ object Base64 { private val basicDecodeTable = decodeTable(basicEncodeTable) private val urlSafeDecodeTable = decodeTable(urlSafeEncodeTable) - private val mimeLineSeparators = Array[Byte]('\r', '\n') + private val mimeLineSeparators = Array('\r'.toByte, '\n'.toByte) private final val mimeLineLength = 76 private val basicEncoder = From 456298df8a99b1297acbaedb445acf7bb3e02448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 3 Sep 2019 17:46:49 +0200 Subject: [PATCH 0072/1606] Avoid unoptimizable uses of Ranges in the javalib. As well as unoptimizable default parameters (if their rhs is a lambda, it is not small enough to be inlined at call site, and therefore the lambda cannot be eliminated). --- .../src/main/scala/java/math/BigInteger.scala | 13 +++- javalib/src/main/scala/java/util/Arrays.scala | 66 ++++++++++++++----- .../main/scala/java/util/Collections.scala | 45 +++++++------ 3 files changed, 86 insertions(+), 38 deletions(-) diff --git a/javalib/src/main/scala/java/math/BigInteger.scala b/javalib/src/main/scala/java/math/BigInteger.scala index 399b3add46..faaa681a1d 100644 --- a/javalib/src/main/scala/java/math/BigInteger.scala +++ b/javalib/src/main/scala/java/math/BigInteger.scala @@ -771,8 +771,17 @@ class BigInteger extends Number with Comparable[BigInteger] { numberLength += 1 } - private[math] def equalsArrays(b: Array[Int]): Boolean = - (0 until numberLength).forall(i => digits(i) == b(i)) + private[math] def equalsArrays(b: Array[Int]): Boolean = { + // scalastyle:off return + var i = 0 + while (i != numberLength) { + if (digits(i) != b(i)) + return false + i += 1 + } + true + // scalastyle:on return + } private[math] def getFirstNonzeroDigit(): Int = { if (firstNonzeroDigit == firstNonzeroDigitNotSet) { diff --git a/javalib/src/main/scala/java/util/Arrays.scala b/javalib/src/main/scala/java/util/Arrays.scala index 582e5c43f6..3524e1874e 100644 --- a/javalib/src/main/scala/java/util/Arrays.scala +++ b/javalib/src/main/scala/java/util/Arrays.scala @@ -436,8 +436,22 @@ object Arrays { @inline private def equalsImpl[T](a: Array[T], b: Array[T]): Boolean = { - (a eq b) || (a != null && b != null && a.length == b.length && - a.indices.forall(i => a(i) == b(i))) + // scalastyle:off return + if (a eq b) + return true + if (a == null || b == null) + return false + val len = a.length + if (b.length != len) + return false + var i = 0 + while (i != len) { + if (a(i) != b(i)) + return false + i += 1 + } + true + // scalastyle:on return } @noinline def fill(a: Array[Long], value: Long): Unit = @@ -625,37 +639,42 @@ object Arrays { } @noinline def hashCode(a: Array[Long]): Int = - hashCodeImpl[Long](a) + hashCodeImpl[Long](a, _.hashCode()) @noinline def hashCode(a: Array[Int]): Int = - hashCodeImpl[Int](a) + hashCodeImpl[Int](a, _.hashCode()) @noinline def hashCode(a: Array[Short]): Int = - hashCodeImpl[Short](a) + hashCodeImpl[Short](a, _.hashCode()) @noinline def hashCode(a: Array[Char]): Int = - hashCodeImpl[Char](a) + hashCodeImpl[Char](a, _.hashCode()) @noinline def hashCode(a: Array[Byte]): Int = - hashCodeImpl[Byte](a) + hashCodeImpl[Byte](a, _.hashCode()) @noinline def hashCode(a: Array[Boolean]): Int = - hashCodeImpl[Boolean](a) + hashCodeImpl[Boolean](a, _.hashCode()) @noinline def hashCode(a: Array[Float]): Int = - hashCodeImpl[Float](a) + hashCodeImpl[Float](a, _.hashCode()) @noinline def hashCode(a: Array[Double]): Int = - hashCodeImpl[Double](a) + hashCodeImpl[Double](a, _.hashCode()) @noinline def hashCode(a: Array[AnyRef]): Int = hashCodeImpl[AnyRef](a, Objects.hashCode(_)) @inline - private def hashCodeImpl[T](a: Array[T], - elementHashCode: T => Int = (x: T) => x.hashCode): Int = { - if (a == null) 0 - else a.foldLeft(1)((acc, x) => 31*acc + elementHashCode(x)) + private def hashCodeImpl[T](a: Array[T], elementHashCode: T => Int): Int = { + if (a == null) { + 0 + } else { + var acc = 1 + for (i <- 0 until a.length) + acc = 31 * acc + elementHashCode(a(i)) + acc + } } @noinline def deepHashCode(a: Array[AnyRef]): Int = { @@ -678,9 +697,22 @@ object Arrays { } @noinline def deepEquals(a1: Array[AnyRef], a2: Array[AnyRef]): Boolean = { - if (a1 eq a2) true - else if (a1 == null || a2 == null || a1.length != a2.length) false - else a1.indices.forall(i => Objects.deepEquals(a1(i), a2(i))) + // scalastyle:off return + if (a1 eq a2) + return true + if (a1 == null || a2 == null) + return false + val len = a1.length + if (a2.length != len) + return false + var i = 0 + while (i != len) { + if (!Objects.deepEquals(a1(i), a2(i))) + return false + i += 1 + } + true + // scalastyle:on return } @noinline def toString(a: Array[Long]): String = diff --git a/javalib/src/main/scala/java/util/Collections.scala b/javalib/src/main/scala/java/util/Collections.scala index 550abe2c72..e68e224392 100644 --- a/javalib/src/main/scala/java/util/Collections.scala +++ b/javalib/src/main/scala/java/util/Collections.scala @@ -350,26 +350,33 @@ object Collections { } } - def indexOfSubList(source: List[_], target: List[_]): Int = - indexOfSubListImpl(source, target, fromStart = true) - - def lastIndexOfSubList(source: List[_], target: List[_]): Int = - indexOfSubListImpl(source, target, fromStart = false) - - @inline - private def indexOfSubListImpl(source: List[_], target: List[_], - fromStart: Boolean): Int = { - val targetSize = target.size - if (targetSize == 0) { - if (fromStart) 0 - else source.size - } else { - val indices = 0 to source.size - targetSize - val indicesInOrder = if (fromStart) indices else indices.reverse - indicesInOrder.find { i => - source.subList(i, i + target.size).equals(target) - }.getOrElse(-1) + def indexOfSubList(source: List[_], target: List[_]): Int = { + // scalastyle:off return + val sourceSize = source.size() + val targetSize = target.size() + val end = sourceSize - targetSize + var i = 0 + while (i <= end) { + if (source.subList(i, i + targetSize).equals(target)) + return i + i += 1 + } + -1 + // scalastyle:on return + } + + def lastIndexOfSubList(source: List[_], target: List[_]): Int = { + // scalastyle:off return + val sourceSize = source.size() + val targetSize = target.size() + var i = sourceSize - targetSize + while (i >= 0) { + if (source.subList(i, i + targetSize).equals(target)) + return i + i -= 1 } + -1 + // scalastyle:on return } def unmodifiableCollection[T](c: Collection[_ <: T]): Collection[T] = From cbcb287f453c6528269862f0d77fc81b7926b30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 3 Sep 2019 18:17:39 +0200 Subject: [PATCH 0073/1606] Manually inline the only two uses of transient iterators. --- .../main/scala/java/util/AbstractList.scala | 8 ++- .../main/scala/java/util/AbstractMap.scala | 15 ++++-- .../src/main/scala/java/util/ScalaOps.scala | 52 ------------------- 3 files changed, 18 insertions(+), 57 deletions(-) diff --git a/javalib/src/main/scala/java/util/AbstractList.scala b/javalib/src/main/scala/java/util/AbstractList.scala index e28dc643d1..848a8d441c 100644 --- a/javalib/src/main/scala/java/util/AbstractList.scala +++ b/javalib/src/main/scala/java/util/AbstractList.scala @@ -52,8 +52,12 @@ abstract class AbstractList[E] protected () extends AbstractCollection[E] def addAll(index: Int, c: Collection[_ <: E]): Boolean = { checkIndexOnBounds(index) - for ((elem, i) <- c.iterator().scalaOps.zipWithIndex.scalaOps) - add(index + i, elem) + var i = index + val iter = c.iterator() + while (iter.hasNext()) { + add(i, iter.next()) + i += 1 + } !c.isEmpty } diff --git a/javalib/src/main/scala/java/util/AbstractMap.scala b/javalib/src/main/scala/java/util/AbstractMap.scala index cd66351f08..5ffff72791 100644 --- a/javalib/src/main/scala/java/util/AbstractMap.scala +++ b/javalib/src/main/scala/java/util/AbstractMap.scala @@ -180,8 +180,17 @@ abstract class AbstractMap[K, V] protected () extends java.util.Map[K, V] { entrySet.scalaOps.foldLeft(0)((prev, item) => item.hashCode + prev) override def toString(): String = { - entrySet.iterator.scalaOps.map { - e => s"${e.getKey}=${e.getValue}" - }.scalaOps.mkString("{", ", ", "}") + var result = "{" + var first = true + val iter = entrySet().iterator() + while (iter.hasNext()) { + val entry = iter.next() + if (first) + first = false + else + result += ", " + result = result + entry.getKey() + "=" + entry.getValue() + } + result + "}" } } diff --git a/javalib/src/main/scala/java/util/ScalaOps.scala b/javalib/src/main/scala/java/util/ScalaOps.scala index fd32d8b453..4f80c581b7 100644 --- a/javalib/src/main/scala/java/util/ScalaOps.scala +++ b/javalib/src/main/scala/java/util/ScalaOps.scala @@ -131,58 +131,6 @@ private[util] object ScalaOps { } result + end } - - @inline def map[B](f: A => B): Iterator[B] = - new MappedIterator(__self, f) - - @inline def withFilter(f: A => Boolean): IteratorWithFilter[A] = - new IteratorWithFilter(__self, f) - - @inline def zipWithIndex: Iterator[(A, Int)] = - new IteratorWithIndex(__self) - } - - @inline - private class MappedIterator[A, B](iter: Iterator[A], f: A => B) - extends Iterator[B] { - - def hasNext(): Boolean = iter.hasNext() - - def next(): B = f(iter.next()) - - def remove(): Unit = iter.remove() - } - - @inline - final class IteratorWithFilter[A] private[ScalaOps] ( - iter: Iterator[A], pred: A => Boolean) { - - def foreach[U](f: A => U): Unit = { - for (x <- iter.scalaOps) { - if (pred(x)) - f(x) - } - } - - def withFilter(f: A => Boolean): IteratorWithFilter[A] = - new IteratorWithFilter(iter, x => pred(x) && f(x)) - } - - @inline - private class IteratorWithIndex[A](iter: Iterator[A]) - extends Iterator[(A, Int)] { - - private var lastIndex = -1 - - def hasNext(): Boolean = iter.hasNext() - - def next(): (A, Int) = { - val index = lastIndex + 1 - lastIndex = index - (iter.next(), index) - } - - def remove(): Unit = iter.remove() } implicit class ToJavaEnumerationOps[A] private[ScalaOps] ( From 77bf72fa657f3a1077d56f156b7334dfef9963c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 5 Sep 2019 10:27:16 +0200 Subject: [PATCH 0074/1606] Allow the `Array.apply` opt to kick in for arrays in the javalib. --- javalib/src/main/scala/java/math/BigInteger.scala | 2 +- javalib/src/main/scala/java/math/Conversion.scala | 4 ++-- javalib/src/main/scala/java/math/Primality.scala | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/javalib/src/main/scala/java/math/BigInteger.scala b/javalib/src/main/scala/java/math/BigInteger.scala index faaa681a1d..327bb825d5 100644 --- a/javalib/src/main/scala/java/math/BigInteger.scala +++ b/javalib/src/main/scala/java/math/BigInteger.scala @@ -678,7 +678,7 @@ class BigInteger extends Number with Comparable[BigInteger] { def toByteArray(): Array[Byte] = { if (this.sign == 0) - return Array[Byte](0) // scalastyle:ignore + return Array(0.toByte) // scalastyle:ignore val temp: BigInteger = this val bitLen = bitLength() diff --git a/javalib/src/main/scala/java/math/Conversion.scala b/javalib/src/main/scala/java/math/Conversion.scala index ec50f56576..a6408eff20 100644 --- a/javalib/src/main/scala/java/math/Conversion.scala +++ b/javalib/src/main/scala/java/math/Conversion.scala @@ -37,7 +37,7 @@ private[math] object Conversion { * Holds the maximal exponent for each radix, so that * radixdigitFitInInt[radix] fit in an {@code int} (32 bits). */ - final val DigitFitInInt = Array[Int]( + final val DigitFitInInt = Array( -1, -1, 31, 19, 15, 13, 11, 11, 10, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5) @@ -47,7 +47,7 @@ private[math] object Conversion { * numbers from 2 to 36) that fit into unsigned int (32 bits). bigRadices[0] = * 2 ^ 31, bigRadices[8] = 10 ^ 9, etc. */ - final val BigRadices = Array[Int]( + final val BigRadices = Array( -2147483648, 1162261467, 1073741824, 1220703125, 362797056, 1977326743, 1073741824, 387420489, 1000000000, 214358881, 429981696, 815730721, 1475789056, 170859375, 268435456, 410338673, 612220032, 893871739, diff --git a/javalib/src/main/scala/java/math/Primality.scala b/javalib/src/main/scala/java/math/Primality.scala index d0393daf98..b28fcbc838 100644 --- a/javalib/src/main/scala/java/math/Primality.scala +++ b/javalib/src/main/scala/java/math/Primality.scala @@ -52,7 +52,7 @@ private[math] object Primality { 59, 54, 49, 44, 38, 32, 26, 1) /** All prime numbers with bit length lesser than 10 bits. */ - private val Primes = Array[Int](2, 3, 5, 7, 11, 13, 17, 19, 23, 29, + private val Primes = Array(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, From f700b9fad774b43e4f42c292dd54ca1fc7bdb044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 12 Sep 2019 15:05:32 +0200 Subject: [PATCH 0075/1606] Compile the javalib with -Yno-predef. This helps to discover unwanted uses of the Scala library features early. --- .../main/scala/java/io/BufferedReader.scala | 2 +- .../main/scala/java/io/DataInputStream.scala | 20 +++---- .../main/scala/java/io/DataOutputStream.scala | 13 +++-- .../scala/java/io/InputStreamReader.scala | 13 +++-- .../scala/java/io/OutputStreamWriter.scala | 10 ++-- .../src/main/scala/java/math/BigDecimal.scala | 48 ++++++++++------- .../src/main/scala/java/math/BigInteger.scala | 12 +++-- .../src/main/scala/java/math/BitLevel.scala | 2 + .../src/main/scala/java/math/Conversion.scala | 12 ++++- .../src/main/scala/java/math/Division.scala | 3 ++ .../main/scala/java/math/Multiplication.scala | 2 + .../src/main/scala/java/math/Primality.scala | 1 + .../main/scala/java/math/RoundingMode.scala | 14 +++-- javalib/src/main/scala/java/net/URI.scala | 37 +++++++++---- .../src/main/scala/java/nio/ByteBuffer.scala | 2 +- .../src/main/scala/java/nio/CharBuffer.scala | 2 +- .../main/scala/java/nio/DoubleBuffer.scala | 2 +- .../src/main/scala/java/nio/FloatBuffer.scala | 2 +- .../src/main/scala/java/nio/GenBuffer.scala | 2 +- .../src/main/scala/java/nio/IntBuffer.scala | 2 +- .../src/main/scala/java/nio/LongBuffer.scala | 2 +- .../src/main/scala/java/nio/ShortBuffer.scala | 2 +- .../main/scala/java/nio/charset/Charset.scala | 6 ++- .../java/nio/charset/CharsetDecoder.scala | 3 +- .../java/nio/charset/CharsetEncoder.scala | 3 +- .../scala/java/util/AbstractCollection.scala | 4 +- javalib/src/main/scala/java/util/Arrays.scala | 18 ++++++- javalib/src/main/scala/java/util/Base64.scala | 20 ++++--- .../main/scala/java/util/Collections.scala | 4 +- javalib/src/main/scala/java/util/Date.scala | 2 +- .../src/main/scala/java/util/Formatter.scala | 5 +- .../src/main/scala/java/util/ScalaOps.scala | 19 ++++++- javalib/src/main/scala/java/util/UUID.scala | 6 +-- .../concurrent/ConcurrentLinkedQueue.scala | 1 + .../concurrent/CopyOnWriteArrayList.scala | 7 +-- .../scala/java/util/concurrent/TimeUnit.scala | 13 ++++- .../concurrent/atomic/AtomicLongArray.scala | 4 +- .../atomic/AtomicReferenceArray.scala | 4 +- .../scala/java/util/regex/GroupStartMap.scala | 52 +++++++++++++------ .../main/scala/java/util/regex/Pattern.scala | 15 ++++-- project/Build.scala | 5 ++ 41 files changed, 270 insertions(+), 126 deletions(-) diff --git a/javalib/src/main/scala/java/io/BufferedReader.scala b/javalib/src/main/scala/java/io/BufferedReader.scala index 0e18b89eef..3257c5b9d1 100644 --- a/javalib/src/main/scala/java/io/BufferedReader.scala +++ b/javalib/src/main/scala/java/io/BufferedReader.scala @@ -41,7 +41,7 @@ class BufferedReader(in: Reader, sz: Int) extends Reader { ensureOpen() val srcBuf = buf - if (buf.size < readAheadLimit) + if (buf.length < readAheadLimit) buf = new Array[Char](readAheadLimit) // Move data to beginning of buffer diff --git a/javalib/src/main/scala/java/io/DataInputStream.scala b/javalib/src/main/scala/java/io/DataInputStream.scala index 3296a8ef20..81412ebf85 100644 --- a/javalib/src/main/scala/java/io/DataInputStream.scala +++ b/javalib/src/main/scala/java/io/DataInputStream.scala @@ -160,13 +160,16 @@ class DataInputStream(in: InputStream) extends FilterInputStream(in) var res = "" var i = 0 + def hex(x: Int): String = + (if (x < 0x10) "0" else "") + Integer.toHexString(x) + def badFormat(msg: String) = throw new UTFDataFormatException(msg) while (i < length) { val a = read() if (a == -1) - badFormat(s"Unexpected EOF: ${length - i} bytes to go") + badFormat("Unexpected EOF: " + (length - i) + " bytes to go") i += 1 @@ -178,9 +181,9 @@ class DataInputStream(in: InputStream) extends FilterInputStream(in) i += 1 if (b == -1) - badFormat(f"Expected 2 bytes, found: EOF (init: $a%#02x)") + badFormat("Expected 2 bytes, found: EOF (init: " + hex(a) + ")") if ((b & 0xC0) != 0x80) // 10xxxxxx - badFormat(f"Expected 2 bytes, found: $b%#02x (init: $a%#02x)") + badFormat("Expected 2 bytes, found: " + hex(b) + " (init: " + hex(a) + ")") (((a & 0x1F) << 6) | (b & 0x3F)).toChar } else if ((a & 0xF0) == 0xE0 && i < length - 1) { // 1110xxxx @@ -189,22 +192,21 @@ class DataInputStream(in: InputStream) extends FilterInputStream(in) i += 2 if (b == -1) - badFormat(f"Expected 3 bytes, found: EOF (init: $a%#02x)") + badFormat("Expected 3 bytes, found: EOF (init: " + hex(a) + ")") if ((b & 0xC0) != 0x80) // 10xxxxxx - badFormat(f"Expected 3 bytes, found: $b%#02x (init: $a%#02x)") + badFormat("Expected 3 bytes, found: " + hex(b) + " (init: " + hex(a) + ")") if (c == -1) - badFormat(f"Expected 3 bytes, found: $b%#02x, EOF (init: $a%#02x)") + badFormat("Expected 3 bytes, found: " + hex(b) + ", EOF (init: " + hex(a) + ")") if ((c & 0xC0) != 0x80) // 10xxxxxx - badFormat( - f"Expected 3 bytes, found: $b%#02x, $c%#02x (init: $a%#02x)") + badFormat("Expected 3 bytes, found: " + hex(b) + ", " + hex(c) + " (init: " + hex(a) + ")") (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)).toChar } else { val rem = length - i - badFormat(f"Unexpected start of char: $a%#02x ($rem%d bytes to go)") + badFormat("Unexpected start of char: " + hex(a) + " (" + rem + " bytes to go)") } } diff --git a/javalib/src/main/scala/java/io/DataOutputStream.scala b/javalib/src/main/scala/java/io/DataOutputStream.scala index 7490ce53ab..4b2f2ddda1 100644 --- a/javalib/src/main/scala/java/io/DataOutputStream.scala +++ b/javalib/src/main/scala/java/io/DataOutputStream.scala @@ -12,6 +12,8 @@ package java.io +import java.util.ScalaOps._ + class DataOutputStream(out: OutputStream) extends FilterOutputStream(out) with DataOutput { @@ -66,20 +68,21 @@ class DataOutputStream(out: OutputStream) writeLong(java.lang.Double.doubleToLongBits(v)) final def writeBytes(s: String): Unit = { - for (c <- s) - write(c.toInt) + for (i <- 0 until s.length()) + write(s.charAt(i).toInt) } final def writeChars(s: String): Unit = { - for (c <- s) - writeChar(c) + for (i <- 0 until s.length()) + writeChar(s.charAt(i)) } final def writeUTF(s: String): Unit = { val buffer = new Array[Byte](2 + 3*s.length) var idx = 2 - for (c <- s) { + for (i <- 0 until s.length()) { + val c = s.charAt(i) if (c <= 0x7f && c >= 0x01) { buffer(idx) = c.toByte idx += 1 diff --git a/javalib/src/main/scala/java/io/InputStreamReader.scala b/javalib/src/main/scala/java/io/InputStreamReader.scala index 65f1f0b821..b794edf05c 100644 --- a/javalib/src/main/scala/java/io/InputStreamReader.scala +++ b/javalib/src/main/scala/java/io/InputStreamReader.scala @@ -126,7 +126,8 @@ class InputStreamReader(private[this] var in: InputStream, } val charsRead = loopWithOutBuf(2*len) - assert(charsRead != 0) // can be -1, though + if (charsRead == 0) + throw new AssertionError() // can be -1, though outBuf.flip() if (charsRead == -1) -1 @@ -154,10 +155,12 @@ class InputStreamReader(private[this] var in: InputStream, out.position() - initPos } else if (result.isUnderflow) { if (endOfInput) { - assert(!inBuf.hasRemaining, - "CharsetDecoder.decode() should not have returned UNDERFLOW when "+ - "both endOfInput and inBuf.hasRemaining are true. It should have "+ - "returned a MalformedInput error instead.") + if (inBuf.hasRemaining) { + throw new AssertionError( + "CharsetDecoder.decode() should not have returned UNDERFLOW " + + "when both endOfInput and inBuf.hasRemaining are true. It " + + "should have returned a MalformedInput error instead.") + } // Flush if (decoder.flush(out).isOverflow) { InputStreamReader.Overflow diff --git a/javalib/src/main/scala/java/io/OutputStreamWriter.scala b/javalib/src/main/scala/java/io/OutputStreamWriter.scala index 1c7de50711..0a61f4f7c9 100644 --- a/javalib/src/main/scala/java/io/OutputStreamWriter.scala +++ b/javalib/src/main/scala/java/io/OutputStreamWriter.scala @@ -107,10 +107,12 @@ class OutputStreamWriter(private[this] var out: OutputStream, val cbuf = CharBuffer.wrap(inBuf) val result = enc.encode(cbuf, outBuf, true) if (result.isUnderflow) { - assert(!cbuf.hasRemaining, - "CharsetEncoder.encode() should not have returned UNDERFLOW when "+ - "both endOfInput and inBuf.hasRemaining are true. It should have "+ - "returned a MalformedInput error instead.") + if (cbuf.hasRemaining) { + throw new AssertionError( + "CharsetEncoder.encode() should not have returned UNDERFLOW " + + "when both endOfInput and inBuf.hasRemaining are true. It " + + "should have returned a MalformedInput error instead.") + } } else if (result.isOverflow) { makeRoomInOutBuf() loopEncode() diff --git a/javalib/src/main/scala/java/math/BigDecimal.scala b/javalib/src/main/scala/java/math/BigDecimal.scala index d77a101836..11123965f3 100644 --- a/javalib/src/main/scala/java/math/BigDecimal.scala +++ b/javalib/src/main/scala/java/math/BigDecimal.scala @@ -23,9 +23,12 @@ package java.math -import java.util.Arrays import scala.annotation.tailrec +import java.lang.{Double => JDouble} +import java.util.Arrays +import java.util.ScalaOps._ + object BigDecimal { final val ZERO = new BigDecimal(0, 0) @@ -84,8 +87,16 @@ object BigDecimal { private final val ZeroScaledBy = Array.tabulate[BigDecimal](BigIntScaledByZeroLength)(new BigDecimal(0, _)) - /** An array filled with characters '0'. */ - private final val CharZeros = Array.fill[Char](100)('0') + /** 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. + */ + private val CharZeros: String = { + "00000000000000000000000000000000000000000000000000" + + "00000000000000000000000000000000000000000000000000" + } + + /** `CharZeros.length`. */ + final val CharZerosLength = 100 def valueOf(unscaledVal: Long, scale: Int): BigDecimal = { if (scale == 0) @@ -104,7 +115,7 @@ object BigDecimal { } def valueOf(d: Double): BigDecimal = { - if (d.isInfinite || d.isNaN) + if (JDouble.isInfinite(d) || JDouble.isNaN(d)) throw new NumberFormatException("Infinity or NaN: " + d) new BigDecimal(d.toString) @@ -386,7 +397,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { } val (unscaled, bufLength) = { - val u = in.subSequence(begin, index).toString + val u = String.valueOf(in, begin, index - begin) val b = index - begin // A decimal point was found if ((index <= last) && (in(index) == '.')) { @@ -401,10 +412,10 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { index += 1 } _scale = index - begin - (u + in.subSequence(begin, begin + _scale).toString, b + _scale) + (u + String.valueOf(in, begin, _scale), b + _scale) } else { _scale = 0 - (u,b) + (u, b) } } @@ -458,7 +469,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { def this(dVal: Double) = { this() - if (dVal.isInfinite || dVal.isNaN) + if (JDouble.isInfinite(dVal) || JDouble.isNaN(dVal)) throw new NumberFormatException("Infinity or NaN: " + dVal) val bits = java.lang.Double.doubleToLongBits(dVal) @@ -1301,7 +1312,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { intString.insert(end - _scale, ".") } else { intString.insert(begin - 1, "0.").insert( - begin + 1, CharZeros.mkString, 0, -exponent.toInt - 1) + begin + 1, CharZeros, 0, -exponent.toInt - 1) } } else { val r0 = @@ -1332,7 +1343,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { intString.insert(end - _scale, ".") } else { intString.insert(begin - 1, "0.").insert(begin + 1, - CharZeros.mkString, 0, -exponent0.toInt - 1) + CharZeros, 0, -exponent0.toInt - 1) } } else { val delta = end - begin @@ -1384,18 +1395,17 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { var delta = _scale // We take space for all digits, plus a possible decimal point, plus 'scale' var result = if (begin == 1) "-" else "" - val charZerosStr = CharZeros.mkString if (_scale > 0) { delta -= intStr.length - begin if (delta >= 0) { result += "0." // To append zeros after the decimal point - while (delta > CharZeros.length) { - result += charZerosStr - delta -= CharZeros.length + while (delta > CharZerosLength) { + result += CharZeros + delta -= CharZerosLength } - result += charZerosStr.substring(0, delta) + intStr.substring(begin) + result += CharZeros.substring(0, delta) + intStr.substring(begin) } else { delta = begin - delta result += intStr.substring(begin, delta) + "." + intStr.substring(delta) @@ -1403,11 +1413,11 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { } else { // (scale <= 0) result += intStr.substring(begin) // To append trailing zeros - while (delta < -CharZeros.length) { - result += charZerosStr - delta += CharZeros.length + while (delta < -CharZerosLength) { + result += CharZeros + delta += CharZerosLength } - result += charZerosStr.substring(0, -delta) + result += CharZeros.substring(0, -delta) } result } diff --git a/javalib/src/main/scala/java/math/BigInteger.scala b/javalib/src/main/scala/java/math/BigInteger.scala index 327bb825d5..a9a0855736 100644 --- a/javalib/src/main/scala/java/math/BigInteger.scala +++ b/javalib/src/main/scala/java/math/BigInteger.scala @@ -41,9 +41,11 @@ package java.math -import java.util.Random import scala.annotation.tailrec +import java.util.Random +import java.util.ScalaOps._ + object BigInteger { final val ONE = new BigInteger(1, 1) @@ -178,8 +180,12 @@ class BigInteger extends Number with Comparable[BigInteger] { checkNotNull(magnitude) if ((signum < -1) || (signum > 1)) throw new NumberFormatException("Invalid signum value") - if (signum == 0 && magnitude.exists(_ != 0)) - throw new NumberFormatException("signum-magnitude mismatch") + if (signum == 0) { + for (i <- 0 until magnitude.length) { + if (magnitude(i) != 0) + throw new NumberFormatException("signum-magnitude mismatch") + } + } if (magnitude.length == 0) { sign = 0 diff --git a/javalib/src/main/scala/java/math/BitLevel.scala b/javalib/src/main/scala/java/math/BitLevel.scala index 2fc2f4e9d2..e1e3dae417 100644 --- a/javalib/src/main/scala/java/math/BitLevel.scala +++ b/javalib/src/main/scala/java/math/BitLevel.scala @@ -41,6 +41,8 @@ package java.math +import java.util.ScalaOps._ + /** Object that provides all the bit level operations for {@link BigInteger}. * * The operations are: