diff --git a/Jenkinsfile b/Jenkinsfile index 165dec8254..c1a4c70069 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -263,6 +263,11 @@ def Tasks = [ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ $testSuite$v/test && + sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallestModules))' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ + 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withOptimizer(false))' \ + $testSuite$v/test && sbtretry ++$scala 'set Global/enableMinifyEverywhere := $testMinify' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleSplitStyle(ModuleSplitStyle.SmallModulesFor(List("org.scalajs.testsuite"))))' \ 'set scalaJSLinkerConfig in $testSuite.v$v ~= (_.withModuleKind(ModuleKind.ESModule))' \ diff --git a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala index 7ad9ee3876..4de34d7f0b 100644 --- a/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala +++ b/ir/shared/src/main/scala/org/scalajs/ir/ScalaJSVersions.scala @@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.util.matching.Regex object ScalaJSVersions extends VersionChecks( - current = "1.19.0", + current = "1.19.1-SNAPSHOT", binaryEmitted = "1.19" ) diff --git a/javalib/src/main/scala/java/util/ArrayList.scala b/javalib/src/main/scala/java/util/ArrayList.scala index 68b9705f62..1c67de682b 100644 --- a/javalib/src/main/scala/java/util/ArrayList.scala +++ b/javalib/src/main/scala/java/util/ArrayList.scala @@ -14,80 +14,181 @@ package java.util import java.lang.Cloneable import java.lang.Utils._ +import java.util.ScalaOps._ import scala.scalajs._ +import scala.scalajs.LinkingInfo.isWebAssembly -class ArrayList[E] private (private[ArrayList] val inner: js.Array[E]) +class ArrayList[E] private (innerInit: AnyRef, private var _size: Int) extends AbstractList[E] with RandomAccess with Cloneable with Serializable { self => + /* This class has two different implementations for handling the + * internal data storage, depending on whether we are on Wasm or JS. + * On JS, we utilize `js.Array`. On Wasm, for performance reasons, + * we avoid JS interop and use a scala.Array. + * The `_size` field (unused in JS) keeps track of the effective size + * of the underlying Array for the Wasm implementation. + */ + + private val innerJS: js.Array[E] = + if (isWebAssembly) null + else innerInit.asInstanceOf[js.Array[E]] + + private var innerWasm: Array[AnyRef] = + if (!isWebAssembly) null + else innerInit.asInstanceOf[Array[AnyRef]] + def this(initialCapacity: Int) = { - this(new js.Array[E]) - if (initialCapacity < 0) - throw new IllegalArgumentException + this( + { + if (initialCapacity < 0) + throw new IllegalArgumentException + if (isWebAssembly) new Array[AnyRef](initialCapacity) + else new js.Array[E] + }, + 0 + ) } - def this() = - this(new js.Array[E]) + def this() = this(16) def this(c: Collection[_ <: E]) = { - this() + this(c.size()) addAll(c) } def trimToSize(): Unit = { - // We ignore this as js.Array doesn't support explicit pre-allocation + if (isWebAssembly) + resizeTo(size()) + // We ignore this in JS as js.Array doesn't support explicit pre-allocation } def ensureCapacity(minCapacity: Int): Unit = { - // We ignore this as js.Array doesn't support explicit pre-allocation + if (isWebAssembly) { + if (innerWasm.length < minCapacity) { + if (minCapacity > (1 << 30)) + resizeTo(minCapacity) + else + resizeTo(((1 << 31) >>> (Integer.numberOfLeadingZeros(minCapacity - 1)) - 1)) + } + } + // We ignore this in JS as js.Array doesn't support explicit pre-allocation } def size(): Int = - inner.length - - override def clone(): AnyRef = - new ArrayList(inner.jsSlice(0)) + if (isWebAssembly) _size + else innerJS.length + + override def clone(): AnyRef = { + if (isWebAssembly) + new ArrayList(innerWasm.clone(), size()) + else + new ArrayList(innerJS.jsSlice(0), 0) + } def get(index: Int): E = { checkIndexInBounds(index) - inner(index) + if (isWebAssembly) + innerWasm(index).asInstanceOf[E] + else + innerJS(index) } override def set(index: Int, element: E): E = { val e = get(index) - inner(index) = element + if (isWebAssembly) + innerWasm(index) = element.asInstanceOf[AnyRef] + else + innerJS(index) = element e } override def add(e: E): Boolean = { - inner.push(e) + if (isWebAssembly) { + if (size() >= innerWasm.length) + expand() + innerWasm(size()) = e.asInstanceOf[AnyRef] + _size += 1 + } else { + innerJS.push(e) + } true } override def add(index: Int, element: E): Unit = { checkIndexOnBounds(index) - inner.splice(index, 0, element) + if (isWebAssembly) { + if (size() >= innerWasm.length) + expand() + System.arraycopy(innerWasm, index, innerWasm, index + 1, size() - index) + innerWasm(index) = element.asInstanceOf[AnyRef] + _size += 1 + } else { + innerJS.splice(index, 0, element) + } } override def remove(index: Int): E = { checkIndexInBounds(index) - arrayRemoveAndGet(inner, index) + if (isWebAssembly) { + val removed = innerWasm(index).asInstanceOf[E] + System.arraycopy(innerWasm, index + 1, innerWasm, index, size() - index - 1) + innerWasm(size - 1) = null // free reference for GC + _size -= 1 + removed + } else { + arrayRemoveAndGet(innerJS, index) + } } override def clear(): Unit = - inner.length = 0 + if (isWebAssembly) { + Arrays.fill(innerWasm, null) // free references for GC + _size = 0 + } else { + innerJS.length = 0 + } override def addAll(index: Int, c: Collection[_ <: E]): Boolean = { c match { case other: ArrayList[_] => - inner.splice(index, 0, other.inner.toSeq: _*) + checkIndexOnBounds(index) + if (isWebAssembly) { + ensureCapacity(size() + other.size()) + System.arraycopy(innerWasm, index, innerWasm, index + other.size(), size() - index) + System.arraycopy(other.innerWasm, 0, innerWasm, index, other.size()) + _size += c.size() + } else { + innerJS.splice(index, 0, other.innerJS.toSeq: _*) + } other.size() > 0 case _ => super.addAll(index, c) } } - override protected def removeRange(fromIndex: Int, toIndex: Int): Unit = - inner.splice(fromIndex, toIndex - fromIndex) + override protected def removeRange(fromIndex: Int, toIndex: Int): Unit = { + if (fromIndex < 0 || toIndex > size() || toIndex < fromIndex) + throw new IndexOutOfBoundsException() + if (isWebAssembly) { + if (fromIndex != toIndex) { + System.arraycopy(innerWasm, toIndex, innerWasm, fromIndex, size() - toIndex) + val newSize = size() - toIndex + fromIndex + Arrays.fill(innerWasm, newSize, size(), null) // free references for GC + _size = newSize + } + } else { + innerJS.splice(fromIndex, toIndex - fromIndex) + } + } + // Wasm only + private def expand(): Unit = { + resizeTo(Math.max(innerWasm.length * 2, 16)) + } + + // Wasm only + private def resizeTo(newCapacity: Int): Unit = { + innerWasm = Arrays.copyOf(innerWasm, newCapacity) + } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala b/linker/shared/src/main/scala/org/scalajs/linker/analyzer/Analyzer.scala index 9a80ac96a2..22d3752fd4 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 @@ -31,7 +31,7 @@ import org.scalajs.ir.WellKnownNames._ import org.scalajs.linker._ import org.scalajs.linker.checker.CheckingPhase -import org.scalajs.linker.frontend.{IRLoader, LambdaSynthesizer, SyntheticClassKind} +import org.scalajs.linker.frontend.{IRLoader, LambdaSynthesizer, LinkTimeProperties, SyntheticClassKind} import org.scalajs.linker.interface._ import org.scalajs.linker.interface.unstable.ModuleInitializerImpl import org.scalajs.linker.standard._ @@ -47,6 +47,8 @@ import Infos.{NamespacedMethodName, ReachabilityInfo, ReachabilityInfoInClass} final class Analyzer(config: CommonPhaseConfig, initial: Boolean, checkIRFor: Option[CheckingPhase], failOnError: Boolean, irLoader: IRLoader) { + private val linkTimeProperties = LinkTimeProperties.fromCoreSpec(config.coreSpec) + private val infoLoader: InfoLoader = new InfoLoader(irLoader, checkIRFor) @@ -55,7 +57,7 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean, infoLoader.update(logger) - val run = new AnalyzerRun(config, initial, infoLoader)( + val run = new AnalyzerRun(config, initial, infoLoader, linkTimeProperties)( adjustExecutionContextForParallelism(ec, config.parallel)) run @@ -99,7 +101,10 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean, } private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, - infoLoader: InfoLoader)(implicit ec: ExecutionContext) extends Analysis { + infoLoader: InfoLoader, linkTimeProperties: LinkTimeProperties)( + implicit ec: ExecutionContext) + extends Analysis { + import AnalyzerRun._ private val allowAddingSyntheticMethods = initial @@ -1488,6 +1493,7 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, lookupOrSynthesizeClass(className, SyntheticClassKind.Lambda(descriptor)) { lambdaClassInfo => lambdaClassInfo.instantiated() lambdaClassInfo.callMethodStatically(MemberNamespace.Constructor, ctorName) + moduleUnit.addStaticDependency(lambdaClassInfo.className) } } } @@ -1538,7 +1544,7 @@ private class AnalyzerRun(config: CommonPhaseConfig, initial: Boolean, if (data.referencedLinkTimeProperties.nonEmpty) { for ((name, tpe) <- data.referencedLinkTimeProperties) { - if (!config.coreSpec.linkTimeProperties.validate(name, tpe)) { + if (!linkTimeProperties.get(name).exists(_.tpe == tpe)) { _errors ::= InvalidLinkTimeProperty(name, tpe, from) } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Desugarer.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Desugarer.scala index 44e2f66d09..57f8eeb366 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/frontend/Desugarer.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/Desugarer.scala @@ -28,7 +28,9 @@ import org.scalajs.ir.{Position, Version} final class Desugarer(config: CommonPhaseConfig, checkIR: Boolean) { import Desugarer._ - private val desugarTransformer = new DesugarTransformer(config.coreSpec) + private val linkTimeProperties = LinkTimeProperties.fromCoreSpec(config.coreSpec) + + private val desugarTransformer = new DesugarTransformer(linkTimeProperties) def desugar(unit: LinkingUnit, logger: Logger): LinkingUnit = { val result = logger.time("Desugarer: Desugar") { @@ -118,7 +120,7 @@ final class Desugarer(config: CommonPhaseConfig, checkIR: Boolean) { private[linker] object Desugarer { - private final class DesugarTransformer(coreSpec: CoreSpec) + private final class DesugarTransformer(linkTimeProperties: LinkTimeProperties) extends ClassTransformer { /* Cache the names generated for lambda classes because computing their @@ -135,8 +137,17 @@ private[linker] object Desugarer { override def transform(tree: Tree): Tree = { tree match { - case prop: LinkTimeProperty => - coreSpec.linkTimeProperties.transformLinkTimeProperty(prop) + case LinkTimeProperty(name) => + implicit val pos = tree.pos + val value = linkTimeProperties.get(name).getOrElse { + throw new IllegalArgumentException( + s"link time property not found: '$name' of type ${tree.tpe}") + } + value match { + case LinkTimeProperties.LinkTimeBoolean(value) => BooleanLiteral(value) + case LinkTimeProperties.LinkTimeInt(value) => IntLiteral(value) + case LinkTimeProperties.LinkTimeString(value) => StringLiteral(value) + } case NewLambda(descriptor, fun) => implicit val pos = tree.pos diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkTimeProperties.scala b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkTimeProperties.scala similarity index 50% rename from linker/shared/src/main/scala/org/scalajs/linker/standard/LinkTimeProperties.scala rename to linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkTimeProperties.scala index 875196c736..d2c12c67d0 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/LinkTimeProperties.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/frontend/LinkTimeProperties.scala @@ -10,15 +10,16 @@ * additional information regarding copyright ownership. */ -package org.scalajs.linker.standard +package org.scalajs.linker.frontend -import org.scalajs.ir.{Types => jstpe, Trees => js} import org.scalajs.ir.Trees.LinkTimeProperty._ +import org.scalajs.ir.Types._ import org.scalajs.ir.ScalaJSVersions -import org.scalajs.ir.Position.NoPosition -import org.scalajs.linker.interface.{Semantics, ESFeatures} -private[linker] final class LinkTimeProperties ( +import org.scalajs.linker.interface.{ESVersion => _, _} +import org.scalajs.linker.standard.CoreSpec + +final class LinkTimeProperties private ( semantics: Semantics, esFeatures: ESFeatures, targetIsWebAssembly: Boolean @@ -38,31 +39,24 @@ private[linker] final class LinkTimeProperties ( LinkTimeString(ScalaJSVersions.current) ) - def validate(name: String, tpe: jstpe.Type): Boolean = { - linkTimeProperties.get(name).exists { - case _: LinkTimeBoolean => tpe == jstpe.BooleanType - case _: LinkTimeInt => tpe == jstpe.IntType - case _: LinkTimeString => tpe == jstpe.StringType - } - } + def get(name: String): Option[LinkTimeValue] = + linkTimeProperties.get(name) +} + +object LinkTimeProperties { + sealed abstract class LinkTimeValue(val tpe: Type) - def transformLinkTimeProperty(prop: js.LinkTimeProperty): js.Literal = { - val value = linkTimeProperties.getOrElse(prop.name, - throw new IllegalArgumentException(s"link time property not found: '${prop.name}' of type ${prop.tpe}")) - value match { - case LinkTimeBoolean(value) => - js.BooleanLiteral(value)(prop.pos) - case LinkTimeInt(value) => - js.IntLiteral(value)(prop.pos) - case LinkTimeString(value) => - js.StringLiteral(value)(prop.pos) - } + final case class LinkTimeInt(value: Int) extends LinkTimeValue(IntType) + + final case class LinkTimeBoolean(value: Boolean) extends LinkTimeValue(BooleanType) + + final case class LinkTimeString(value: String) extends LinkTimeValue(StringType) { + // Being extra careful + require(value != null, "LinkTimeString requires a non-null value.") } -} -private[linker] object LinkTimeProperties { - sealed abstract class LinkTimeValue - final case class LinkTimeInt(value: Int) extends LinkTimeValue - final case class LinkTimeBoolean(value: Boolean) extends LinkTimeValue - final case class LinkTimeString(value: String) extends LinkTimeValue + def fromCoreSpec(coreSpec: CoreSpec): LinkTimeProperties = { + new LinkTimeProperties(coreSpec.semantics, coreSpec.esFeatures, + coreSpec.targetIsWebAssembly) + } } diff --git a/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala b/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala index e5e285268f..3c4c979adc 100644 --- a/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala +++ b/linker/shared/src/main/scala/org/scalajs/linker/standard/CoreSpec.scala @@ -96,9 +96,6 @@ final class CoreSpec private ( targetIsWebAssembly ) } - - private[linker] lazy val linkTimeProperties = new LinkTimeProperties( - semantics, esFeatures, targetIsWebAssembly) } private[linker] object CoreSpec { diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 5435860a02..2e94162e72 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -5,56 +5,15 @@ import com.typesafe.tools.mima.core.ProblemFilters._ object BinaryIncompatibilities { val IR = Seq( - // !!! Breaking, OK in minor release - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Names.*Class"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Names.ClassInitializerName"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Names.DefaultModuleID"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Names.HijackedClasses"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Names.NoArgConstructorName"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Names.ObjectArgConstructorName"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Names.StaticInitializerName"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types.BoxedClassToPrimType"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types.PrimTypeToBoxedClass"), - - // !!! Breaking, OK in minor release - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.InvalidIRException.tree"), - ProblemFilters.exclude[Problem]("org.scalajs.ir.Trees#Closure.*"), - - // !!! Breaking, PrimRef is not a case class anymore - ProblemFilters.exclude[MissingTypesProblem]("org.scalajs.ir.Types$PrimRef"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimRef.canEqual"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimRef.productArity"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimRef.productElement"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimRef.productElementName"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimRef.productElementNames"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimRef.productIterator"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimRef.productPrefix"), - ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.scalajs.ir.Types#PrimRef.unapply"), - - // !!! Breaking I guess ... we used to leak public things out of a `case class` with a private[ir] constructor - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimRef.this"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimRef.apply"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimRef.copy"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimRef.copy$default$1"), - ProblemFilters.exclude[MissingTypesProblem]("org.scalajs.ir.Types$PrimRef$"), - - // constructor of a sealed abstract class, not an issue - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Types#PrimTypeWithRef.this"), - - // private, not an issue - ProblemFilters.exclude[MissingClassProblem]("org.scalajs.ir.Serializers$Deserializer$BodyHack5Transformer$"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.ir.Serializers#Hacks.use*"), ) val Linker = Seq( - // !!! Breaking, OK in minor release - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.LinkedClass.this"), - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.LinkedTopLevelExport.this"), + // private[linker], not an issue + ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.standard.CoreSpec.linkTimeProperties"), + ProblemFilters.exclude[MissingClassProblem]("org.scalajs.linker.standard.LinkTimeProperties*"), ) val LinkerInterface = Seq( - // private, not an issue - ProblemFilters.exclude[DirectMissingMethodProblem]("org.scalajs.linker.interface.Semantics.this"), ) val SbtPlugin = Seq( @@ -64,10 +23,6 @@ object BinaryIncompatibilities { ) val Library = Seq( - // Changes covered by a deserialization hack (and the code cannot be used on the JVM, such as in macros) - ProblemFilters.exclude[AbstractClassProblem]("scala.scalajs.runtime.AnonFunction*"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.scalajs.runtime.AnonFunction*.this"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.scalajs.runtime.AnonFunction*.apply"), ) val TestInterface = Seq( diff --git a/project/Build.scala b/project/Build.scala index eb8b6b9f2f..80bfe9792c 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -396,7 +396,7 @@ object Build { "1.3.0", "1.3.1", "1.4.0", "1.5.0", "1.5.1", "1.6.0", "1.7.0", "1.7.1", "1.8.0", "1.9.0", "1.10.0", "1.10.1", "1.11.0", "1.12.0", "1.13.0", "1.13.1", "1.13.2", "1.14.0", "1.15.0", "1.16.0", "1.17.0", "1.18.0", - "1.18.1", "1.18.2") + "1.18.1", "1.18.2", "1.19.0") val previousVersion = previousVersions.last val previousBinaryCrossVersion = CrossVersion.binaryWith("sjs1_", "") diff --git a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayListTest.scala b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayListTest.scala index 9b9812f93c..400da32882 100644 --- a/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayListTest.scala +++ b/test-suite/shared/src/test/scala/org/scalajs/testsuite/javalib/util/ArrayListTest.scala @@ -13,6 +13,11 @@ package org.scalajs.testsuite.javalib.util import org.junit.Test +import org.junit.Assert._ +import org.junit.Assume._ + +import org.scalajs.testsuite.utils.AssertThrows.assertThrows +import org.scalajs.testsuite.utils.Platform import java.{util => ju} @@ -20,7 +25,7 @@ import scala.reflect.ClassTag class ArrayListTest extends AbstractListTest { - override def factory: AbstractListFactory = new ArrayListFactory + override def factory: ArrayListFactory = new ArrayListFactory @Test def ensureCapacity(): Unit = { // note that these methods become no ops in js @@ -29,6 +34,86 @@ class ArrayListTest extends AbstractListTest { al.ensureCapacity(34) al.trimToSize() } + + @Test def constructorInitialCapacity(): Unit = { + val al1 = new ju.ArrayList(0) + assertTrue(al1.size() == 0) + assertTrue(al1.isEmpty()) + + val al2 = new ju.ArrayList(2) + assertTrue(al2.size() == 0) + assertTrue(al2.isEmpty()) + + assertThrows(classOf[IllegalArgumentException], new ju.ArrayList(-1)) + } + + @Test def constructorNullThrowsNullPointerException(): Unit = { + assumeTrue("assumed compliant NPEs", Platform.hasCompliantNullPointers) + assertThrows(classOf[NullPointerException], new ju.ArrayList(null)) + } + + @Test def testClone(): Unit = { + val al1 = factory.fromElements[Int](1, 2) + val al2 = al1.clone().asInstanceOf[ju.ArrayList[Int]] + al1.add(100) + al2.add(200) + assertTrue(Array[Int](1, 2, 100).sameElements(al1.toArray())) + assertTrue(Array[Int](1, 2, 200).sameElements(al2.toArray())) + } + + @Test def removeRangeFromIdenticalIndices(): Unit = { + val al = new ArrayListRangeRemovable[Int]( + TrivialImmutableCollection(-175, 24, 7, 44)) + val expected = Array[Int](-175, 24, 7, 44) + al.removeRangeList(0, 0) + assertTrue(al.toArray().sameElements(expected)) + al.removeRangeList(1, 1) + assertTrue(al.toArray().sameElements(expected)) + al.removeRangeList(al.size, al.size) // no op + assertTrue(al.toArray().sameElements(expected)) + } + + @Test def removeRangeFromToInvalidIndices(): Unit = { + val al = new ArrayListRangeRemovable[Int]( + TrivialImmutableCollection(175, -24, -7, -44)) + + assertThrows( + classOf[java.lang.IndexOutOfBoundsException], + al.removeRangeList(-1, 2) + ) // fromIndex < 0 + assertThrows( + classOf[java.lang.IndexOutOfBoundsException], + al.removeRangeList(0, al.size + 1) + ) // toIndex > size + assertThrows( + classOf[java.lang.IndexOutOfBoundsException], + al.removeRangeList(2, -1) + ) // toIndex < fromIndex + } + + @Test def removeRangeFromToFirstTwoElements(): Unit = { + val al = new ArrayListRangeRemovable[Int]( + TrivialImmutableCollection(284, -27, 995, 500, 267, 904)) + val expected = Array[Int](995, 500, 267, 904) + al.removeRangeList(0, 2) + assertTrue(al.toArray().sameElements(expected)) + } + + @Test def removeRangeFromToTwoElementsFromMiddle(): Unit = { + val al = new ArrayListRangeRemovable[Int]( + TrivialImmutableCollection(7, 9, -1, 20)) + val expected = Array[Int](7, 20) + al.removeRangeList(1, 3) + assertTrue(al.toArray().sameElements(expected)) + } + + @Test def removeRangeFromToLastTwoElementsAtTail(): Unit = { + val al = new ArrayListRangeRemovable[Int]( + TrivialImmutableCollection(50, 72, 650, 12, 7, 28, 3)) + val expected = Array[Int](50, 72, 650, 12, 7) + al.removeRangeList(al.size - 2, al.size) + assertTrue(al.toArray().sameElements(expected)) + } } class ArrayListFactory extends AbstractListFactory { @@ -37,4 +122,13 @@ class ArrayListFactory extends AbstractListFactory { override def empty[E: ClassTag]: ju.ArrayList[E] = new ju.ArrayList[E] + + override def fromElements[E: ClassTag](coll: E*): ju.ArrayList[E] = + new ju.ArrayList[E](TrivialImmutableCollection(coll: _*)) +} + +class ArrayListRangeRemovable[E](c: ju.Collection[_ <: E]) extends ju.ArrayList[E](c) { + def removeRangeList(fromIndex: Int, toIndex: Int): Unit = { + removeRange(fromIndex, toIndex) + } } 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 787d88a4c3..c73e6acccd 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 @@ -117,6 +117,16 @@ trait CollectionTest extends IterableTest { assertFalse(coll.contains(TestObj(200))) } + @Test def isEmpty(): Unit = { + val coll = factory.empty[Int] + assertTrue(coll.size() == 0) + assertTrue(coll.isEmpty()) + + val nonEmpty = factory.fromElements[Int](1) + assertTrue(nonEmpty.size() == 1) + assertFalse(nonEmpty.isEmpty()) + } + @Test def removeString(): 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 8835696b00..98773fef7a 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 @@ -96,6 +96,19 @@ trait ListTest extends CollectionTest with CollectionsTestBase { assertThrows(classOf[IndexOutOfBoundsException], lst.get(lst.size)) } + @Test def addAllIndexBounds(): Unit = { + val al = factory.fromElements[String]("one", "two", "three") + + val coll = factory.fromElements[String]("foo") + assertThrows(classOf[IndexOutOfBoundsException], al.addAll(-1, coll)) + assertThrows(classOf[IndexOutOfBoundsException], al.addAll(al.size + 1, coll)) + + assertThrows(classOf[IndexOutOfBoundsException], + al.addAll(-1, TrivialImmutableCollection("foo"))) + assertThrows(classOf[IndexOutOfBoundsException], + al.addAll(al.size + 1, TrivialImmutableCollection("foo"))) + } + @Test def removeStringRemoveIndex(): Unit = { val lst = factory.empty[String]