diff --git a/javalib/src/main/scala/java/io/BufferedReader.scala b/javalib/src/main/scala/java/io/BufferedReader.scala index 3257c5b9d1..4cb0afdd32 100644 --- a/javalib/src/main/scala/java/io/BufferedReader.scala +++ b/javalib/src/main/scala/java/io/BufferedReader.scala @@ -16,7 +16,9 @@ class BufferedReader(in: Reader, sz: Int) extends Reader { def this(in: Reader) = this(in, 4096) - private[this] var buf = new Array[Char](sz) + // Workaround 2.11 with no-specialization ; buf should be initialized on the same line + private[this] var buf: Array[Char] = null + buf = new Array[Char](sz) /** Last valid value in the buffer (exclusive) */ private[this] var end = 0 diff --git a/javalib/src/main/scala/java/math/BigDecimal.scala b/javalib/src/main/scala/java/math/BigDecimal.scala index c3ffdba757..6ee2f36636 100644 --- a/javalib/src/main/scala/java/math/BigDecimal.scala +++ b/javalib/src/main/scala/java/math/BigDecimal.scala @@ -58,8 +58,13 @@ object BigDecimal { private final val LongFivePows = newArrayOfPows(28, 5) - private final val LongFivePowsBitLength = - Array.tabulate[Int](LongFivePows.length)(i => bitLength(LongFivePows(i))) + private final val LongFivePowsBitLength = { + val len = LongFivePows.length + val result = new Array[Int](len) + for (i <- 0 until len) + result(i) = bitLength(LongFivePows(i)) + result + } /** An array of longs with powers of ten. * @@ -68,8 +73,13 @@ object BigDecimal { */ private[math] final val LongTenPows = newArrayOfPows(19, 10) - private final val LongTenPowsBitLength = - Array.tabulate[Int](LongTenPows.length)(i => bitLength(LongTenPows(i))) + private final val LongTenPowsBitLength = { + val len = LongTenPows.length + val result = new Array[Int](len) + for (i <- 0 until len) + result(i) = bitLength(LongTenPows(i)) + result + } private final val BigIntScaledByZeroLength = 11 @@ -77,15 +87,23 @@ object BigDecimal { * * ([0,0],[1,0],...,[10,0]). */ - private final val BigIntScaledByZero = - Array.tabulate[BigDecimal](BigIntScaledByZeroLength)(new BigDecimal(_, 0)) + private final val BigIntScaledByZero = { + val result = new Array[BigDecimal](BigIntScaledByZeroLength) + for (i <- 0 until BigIntScaledByZeroLength) + result(i) = new BigDecimal(i, 0) + result + } /** An array with the zero number scaled by the first positive scales. * * (0*10^0, 0*10^1, ..., 0*10^10). */ - private final val ZeroScaledBy = - Array.tabulate[BigDecimal](BigIntScaledByZeroLength)(new BigDecimal(0, _)) + private final val ZeroScaledBy = { + val result = new Array[BigDecimal](BigIntScaledByZeroLength) + for (i <- 0 until BigIntScaledByZeroLength) + result(i) = new BigDecimal(0, i) + result + } /** A string filled with 100 times the character `'0'`. * It is not a `final` val so that it isn't copied at every call site. @@ -205,8 +223,13 @@ object BigDecimal { else 0 } - private[math] def newArrayOfPows(len: Int, pow: Int): Array[Long] = - Array.iterate(1L, len)(_ * pow) + private[math] def newArrayOfPows(len: Int, pow: Int): Array[Long] = { + val result = new Array[Long](len) + result(0) = 1L + for (i <- 1 until len) + result(i) = result(i - 1) * pow + result + } /** Return an increment that can be -1,0 or 1, depending on {@code roundingMode}. * @@ -276,11 +299,20 @@ object BigDecimal { 32 - java.lang.Integer.numberOfLeadingZeros(smallValue) } - @inline - private def charNotEqualTo(c: Char, cs: Char*): Boolean = !cs.contains(c) + private def charNotEqualTo(c: Char, cs: Array[Char]): Boolean = !charEqualTo(c, cs) - @inline - private def charEqualTo(c: Char, cs: Char*): Boolean = cs.contains(c) + private def charEqualTo(c: Char, cs: Array[Char]): Boolean = { + // scalastyle:off return + val len = cs.length + var i = 0 + while (i != len) { + if (cs(i) == c) + return true + i += 1 + } + false + // scalastyle:on return + } @inline private def insertString(s: String, pos: Int, s2: String): String = @@ -374,12 +406,12 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { if (offset <= last && in(offset) == '+') { index += 1 // Fail if the next character is another sign. - if (index < last && charEqualTo(in(index), '+', '-')) + if (index < last && charEqualTo(in(index), Array('+', '-'))) throw new NumberFormatException("For input string: " + in.toString) } else { // check that '-' is not followed by another sign val isMinus = index <= last && in(index) == '-' - val nextIsSign = index + 1 < last && charEqualTo(in(index + 1), '+', '-') + val nextIsSign = index + 1 < last && charEqualTo(in(index + 1), Array('+', '-')) if (isMinus && nextIsSign) throw new NumberFormatException("For input string: " + in.toString) } @@ -388,7 +420,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { var counter = 0 var wasNonZero = false // Accumulating all digits until a possible decimal point - while (index <= last && charNotEqualTo(in(index), '.', 'e', 'E')) { + while (index <= last && charNotEqualTo(in(index), Array('.', 'e', 'E'))) { if (!wasNonZero) { if (in(index) == '0') counter += 1 else wasNonZero = true @@ -404,7 +436,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { index += 1 // Accumulating all digits until a possible exponent val begin = index - while (index <= last && charNotEqualTo(in(index), 'e', 'E')) { + while (index <= last && charNotEqualTo(in(index), Array('e', 'E'))) { if (!wasNonZero) { if (in(index) == '0') counter += 1 else wasNonZero = true @@ -420,7 +452,7 @@ class BigDecimal() extends Number with Comparable[BigDecimal] { } // An exponent was found - if ((index <= last) && charEqualTo(in(index), 'e', 'E')) { + if ((index <= last) && charEqualTo(in(index), Array('e', 'E'))) { index += 1 // Checking for a possible sign of scale val indexIsPlus = index <= last && in(index) == '+' diff --git a/javalib/src/main/scala/java/math/BigInteger.scala b/javalib/src/main/scala/java/math/BigInteger.scala index 2de2c425cb..59ce0d1c49 100644 --- a/javalib/src/main/scala/java/math/BigInteger.scala +++ b/javalib/src/main/scala/java/math/BigInteger.scala @@ -74,7 +74,12 @@ object BigInteger { new BigInteger(1, 4), new BigInteger(1, 5), new BigInteger(1, 6), new BigInteger(1, 7), new BigInteger(1, 8), new BigInteger(1, 9), TEN) - private final val TWO_POWS = Array.tabulate[BigInteger](32)(i => BigInteger.valueOf(1L << i)) + private final val TWO_POWS = { + val result = new Array[BigInteger](32) + for (i <- 0 until 32) + result(i) = BigInteger.valueOf(1L << i) + result + } /** The first non zero digit is either -1 if sign is zero, otherwise it is >= 0. * diff --git a/javalib/src/main/scala/java/math/Division.scala b/javalib/src/main/scala/java/math/Division.scala index f895fc5fe1..a69382135b 100644 --- a/javalib/src/main/scala/java/math/Division.scala +++ b/javalib/src/main/scala/java/math/Division.scala @@ -884,11 +884,14 @@ private[math] object Division { for (i <- 0 until modulusLen) { var innnerCarry: Int = 0 // unsigned val m = Multiplication.unsignedMultAddAdd(res(i), n2, 0, 0).toInt - for (j <- 0 until modulusLen) { + // Work around Scala 2.11 limitation with the IR cleaner ; should be for (j <- 0 until modulusLen) + var j = 0 + while (j < modulusLen) { val nextInnnerCarry = unsignedMultAddAdd(m, modulusDigits(j), res(i + j), innnerCarry) res(i + j) = nextInnnerCarry.toInt innnerCarry = (nextInnnerCarry >> 32).toInt + j += 1 } val nextOuterCarry = (outerCarry & UINT_MAX) + (res(i + modulusLen) & UINT_MAX) + (innnerCarry & UINT_MAX) diff --git a/javalib/src/main/scala/java/math/Multiplication.scala b/javalib/src/main/scala/java/math/Multiplication.scala index 9f0ca4188e..10ecb738cc 100644 --- a/javalib/src/main/scala/java/math/Multiplication.scala +++ b/javalib/src/main/scala/java/math/Multiplication.scala @@ -124,10 +124,13 @@ private[math] object Multiplication { for (i <- 0 until aLen) { carry = 0 - for (j <- i + 1 until aLen) { + // Work around Scala 2.11 limitation with the IR cleaner ; should be for (j <- i + 1 until aLen) + var j = i + 1 + while (j < aLen) { val t = unsignedMultAddAdd(a(i), a(j), res(i + j), carry) res(i + j) = t.toInt carry = (t >>> 32).toInt + j += 1 } res(i + aLen) = carry } @@ -439,16 +442,24 @@ private[math] object Multiplication { for (i <- 0 until aLen) { var carry = 0 val aI = a(i) - for (j <- 0 until bLen) { + // Work around Scala 2.11 limitation with the IR cleaner ; should be for (j <- 0 until bLen) + var j = 0 + while (j < bLen) { val added = unsignedMultAddAdd(aI, b(j), t(i + j), carry) t(i + j) = added.toInt carry = (added >>> 32).toInt + j += 1 } t(i + bLen) = carry } } } - private def newArrayOfPows(len: Int, pow: Int): Array[Int] = - Array.iterate(1, len)(_ * pow) + private def newArrayOfPows(len: Int, pow: Int): Array[Int] = { + val result = new Array[Int](len) + result(0) = 1 + for (i <- 1 until len) + result(i) = result(i - 1) * pow + result + } } diff --git a/javalib/src/main/scala/java/math/Primality.scala b/javalib/src/main/scala/java/math/Primality.scala index 354b6607c9..b7fd19101b 100644 --- a/javalib/src/main/scala/java/math/Primality.scala +++ b/javalib/src/main/scala/java/math/Primality.scala @@ -79,8 +79,13 @@ private[math] object Primality { (18, 13), (31, 23), (54, 43), (97, 75)) /** All {@code BigInteger} prime numbers with bit length lesser than 8 bits. */ - private val BiPrimes = - Array.tabulate[BigInteger](Primes.length)(i => BigInteger.valueOf(Primes(i))) + private val BiPrimes = { + val len = Primes.length + val result = new Array[BigInteger](len) + for (i <- 0 until len) + result(i) = BigInteger.valueOf(Primes(i)) + result + } /** A random number is generated until a probable prime number is found. * diff --git a/javalib/src/main/scala/java/net/URI.scala b/javalib/src/main/scala/java/net/URI.scala index 58dda9f946..e114fe6ac7 100644 --- a/javalib/src/main/scala/java/net/URI.scala +++ b/javalib/src/main/scala/java/net/URI.scala @@ -19,6 +19,7 @@ import scala.annotation.tailrec import java.nio._ import java.nio.charset.{CodingErrorAction, StandardCharsets} +import java.util.JSUtils._ final class URI(origStr: String) extends Serializable with Comparable[URI] { @@ -35,10 +36,10 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { if (_fld == null) throw new URISyntaxException(origStr, "Malformed URI") - private val _isAbsolute = _fld(AbsScheme).isDefined - private val _isOpaque = _fld(AbsOpaquePart).isDefined + private val _isAbsolute = undefOrIsDefined(_fld(AbsScheme)) + private val _isOpaque = undefOrIsDefined(_fld(AbsOpaquePart)) - @inline private def fld(idx: Int): String = _fld(idx).orNull + @inline private def fld(idx: Int): String = undefOrGetOrNull(_fld(idx)) @inline private def fld(absIdx: Int, relIdx: Int): String = if (_isAbsolute) fld(absIdx) else fld(relIdx) @@ -187,7 +188,7 @@ final class URI(origStr: String) extends Serializable with Comparable[URI] { def getUserInfo(): String = decodeComponent(_userInfo) override def hashCode(): Int = { - import scala.util.hashing.MurmurHash3._ + import java.util.internal.MurmurHash3._ import URI.normalizeEscapes def normalizeEscapesHash(str: String): Int = diff --git a/javalib/src/main/scala/java/nio/ByteBuffer.scala b/javalib/src/main/scala/java/nio/ByteBuffer.scala index 8b100204f8..92cb2a8ea0 100644 --- a/javalib/src/main/scala/java/nio/ByteBuffer.scala +++ b/javalib/src/main/scala/java/nio/ByteBuffer.scala @@ -31,14 +31,14 @@ object ByteBuffer { // Extended API - def wrap(array: ArrayBuffer): ByteBuffer = - TypedArrayByteBuffer.wrap(array) + def wrapArrayBuffer(array: ArrayBuffer): ByteBuffer = + TypedArrayByteBuffer.wrapArrayBuffer(array) - def wrap(array: ArrayBuffer, byteOffset: Int, length: Int): ByteBuffer = - TypedArrayByteBuffer.wrap(array, byteOffset, length) + def wrapArrayBuffer(array: ArrayBuffer, byteOffset: Int, length: Int): ByteBuffer = + TypedArrayByteBuffer.wrapArrayBuffer(array, byteOffset, length) - def wrap(array: Int8Array): ByteBuffer = - TypedArrayByteBuffer.wrap(array) + def wrapInt8Array(array: Int8Array): ByteBuffer = + TypedArrayByteBuffer.wrapInt8Array(array) } abstract class ByteBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/CharBuffer.scala b/javalib/src/main/scala/java/nio/CharBuffer.scala index 79443286ec..8501f7a01c 100644 --- a/javalib/src/main/scala/java/nio/CharBuffer.scala +++ b/javalib/src/main/scala/java/nio/CharBuffer.scala @@ -34,8 +34,8 @@ object CharBuffer { // Extended API - def wrap(array: Uint16Array): CharBuffer = - TypedArrayCharBuffer.wrap(array) + def wrapUint16Array(array: Uint16Array): CharBuffer = + TypedArrayCharBuffer.wrapUint16Array(array) } abstract class CharBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/DataViewExt.scala b/javalib/src/main/scala/java/nio/DataViewExt.scala new file mode 100644 index 0000000000..f034f2f915 --- /dev/null +++ b/javalib/src/main/scala/java/nio/DataViewExt.scala @@ -0,0 +1,46 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.nio + +import scala.scalajs.js.typedarray.DataView + +/** Copy of features in `scala.scalajs.js.typedarray.DateViewExt`. + * + * Defined as functions instead of extension methods, because the AnyVal over + * a JS type generates an `equals` method that references `BoxesRunTime`. + */ +private[nio] object DataViewExt { + /** Reads a 2's complement signed 64-bit integers from the data view. + * @param index Starting index + * @param littleEndian Whether the number is stored in little endian + */ + @inline + def dataViewGetInt64(dataView: DataView, index: Int, littleEndian: Boolean): Long = { + val high = dataView.getInt32(index + (if (littleEndian) 4 else 0), littleEndian) + val low = dataView.getInt32(index + (if (littleEndian) 0 else 4), littleEndian) + (high.toLong << 32) | (low.toLong & 0xffffffffL) + } + + /** Writes a 2's complement signed 64-bit integers to the data view. + * @param index Starting index + * @param value Value to be written + * @param littleEndian Whether to store the number in little endian + */ + @inline + def dataViewSetInt64(dataView: DataView, index: Int, value: Long, littleEndian: Boolean): Unit = { + val high = (value >>> 32).toInt + val low = value.toInt + dataView.setInt32(index + (if (littleEndian) 4 else 0), high, littleEndian) + dataView.setInt32(index + (if (littleEndian) 0 else 4), low, littleEndian) + } +} diff --git a/javalib/src/main/scala/java/nio/DataViewLongBuffer.scala b/javalib/src/main/scala/java/nio/DataViewLongBuffer.scala index 3ee08fee13..3d083001cb 100644 --- a/javalib/src/main/scala/java/nio/DataViewLongBuffer.scala +++ b/javalib/src/main/scala/java/nio/DataViewLongBuffer.scala @@ -12,8 +12,9 @@ package java.nio +import java.nio.DataViewExt._ + import scala.scalajs.js.typedarray._ -import DataViewExt._ private[nio] final class DataViewLongBuffer private ( override private[nio] val _dataView: DataView, @@ -86,11 +87,11 @@ private[nio] final class DataViewLongBuffer private ( @inline private[nio] def load(index: Int): Long = - _dataView.getInt64(8 * index, !isBigEndian) + dataViewGetInt64(_dataView, 8 * index, !isBigEndian) @inline private[nio] def store(index: Int, elem: Long): Unit = - _dataView.setInt64(8 * index, elem, !isBigEndian) + dataViewSetInt64(_dataView, 8 * index, elem, !isBigEndian) @inline override private[nio] def load(startIndex: Int, diff --git a/javalib/src/main/scala/java/nio/DoubleBuffer.scala b/javalib/src/main/scala/java/nio/DoubleBuffer.scala index 34c77ba0c5..4a4fcda944 100644 --- a/javalib/src/main/scala/java/nio/DoubleBuffer.scala +++ b/javalib/src/main/scala/java/nio/DoubleBuffer.scala @@ -28,8 +28,8 @@ object DoubleBuffer { // Extended API - def wrap(array: Float64Array): DoubleBuffer = - TypedArrayDoubleBuffer.wrap(array) + def wrapFloat64Array(array: Float64Array): DoubleBuffer = + TypedArrayDoubleBuffer.wrapFloat64Array(array) } abstract class DoubleBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/FloatBuffer.scala b/javalib/src/main/scala/java/nio/FloatBuffer.scala index dc816242c6..9f9a8021de 100644 --- a/javalib/src/main/scala/java/nio/FloatBuffer.scala +++ b/javalib/src/main/scala/java/nio/FloatBuffer.scala @@ -28,8 +28,8 @@ object FloatBuffer { // Extended API - def wrap(array: Float32Array): FloatBuffer = - TypedArrayFloatBuffer.wrap(array) + def wrapFloat32Array(array: Float32Array): FloatBuffer = + TypedArrayFloatBuffer.wrapFloat32Array(array) } abstract class FloatBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/GenBuffer.scala b/javalib/src/main/scala/java/nio/GenBuffer.scala index 9489f579a3..514a0daec9 100644 --- a/javalib/src/main/scala/java/nio/GenBuffer.scala +++ b/javalib/src/main/scala/java/nio/GenBuffer.scala @@ -118,7 +118,7 @@ private[nio] final class GenBuffer[B <: Buffer] private (val self: B) @inline def generic_hashCode(hashSeed: Int): Int = { - import scala.util.hashing.MurmurHash3._ + import java.util.internal.MurmurHash3._ val start = position() val end = limit() var h = hashSeed diff --git a/javalib/src/main/scala/java/nio/IntBuffer.scala b/javalib/src/main/scala/java/nio/IntBuffer.scala index 09cfa88515..5e31304b4f 100644 --- a/javalib/src/main/scala/java/nio/IntBuffer.scala +++ b/javalib/src/main/scala/java/nio/IntBuffer.scala @@ -28,8 +28,8 @@ object IntBuffer { // Extended API - def wrap(array: Int32Array): IntBuffer = - TypedArrayIntBuffer.wrap(array) + def wrapInt32Array(array: Int32Array): IntBuffer = + TypedArrayIntBuffer.wrapInt32Array(array) } abstract class IntBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/ShortBuffer.scala b/javalib/src/main/scala/java/nio/ShortBuffer.scala index d31b13fec8..b3d3b9b0b5 100644 --- a/javalib/src/main/scala/java/nio/ShortBuffer.scala +++ b/javalib/src/main/scala/java/nio/ShortBuffer.scala @@ -28,8 +28,8 @@ object ShortBuffer { // Extended API - def wrap(array: Int16Array): ShortBuffer = - TypedArrayShortBuffer.wrap(array) + def wrapInt16Array(array: Int16Array): ShortBuffer = + TypedArrayShortBuffer.wrapInt16Array(array) } abstract class ShortBuffer private[nio] ( diff --git a/javalib/src/main/scala/java/nio/TypedArrayByteBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayByteBuffer.scala index 26f93b0012..2371a887e5 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayByteBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayByteBuffer.scala @@ -12,8 +12,9 @@ package java.nio +import java.nio.DataViewExt._ + import scala.scalajs.js.typedarray._ -import DataViewExt._ private[nio] final class TypedArrayByteBuffer private ( override private[nio] val _typedArray: Int8Array, @@ -128,13 +129,13 @@ private[nio] final class TypedArrayByteBuffer private ( } @noinline def getLong(): Long = - _dataView.getInt64(getPosAndAdvanceRead(8), !isBigEndian) + dataViewGetInt64(_dataView, getPosAndAdvanceRead(8), !isBigEndian) @noinline def putLong(value: Long): ByteBuffer = - { ensureNotReadOnly(); _dataView.setInt64(getPosAndAdvanceWrite(8), value, !isBigEndian); this } + { ensureNotReadOnly(); dataViewSetInt64(_dataView, getPosAndAdvanceWrite(8), value, !isBigEndian); this } @noinline def getLong(index: Int): Long = - _dataView.getInt64(validateIndex(index, 8), !isBigEndian) + dataViewGetInt64(_dataView, validateIndex(index, 8), !isBigEndian) @noinline def putLong(index: Int, value: Long): ByteBuffer = - { ensureNotReadOnly(); _dataView.setInt64(validateIndex(index, 8), value, !isBigEndian); this } + { ensureNotReadOnly(); dataViewSetInt64(_dataView, validateIndex(index, 8), value, !isBigEndian); this } def asLongBuffer(): LongBuffer = DataViewLongBuffer.fromTypedArrayByteBuffer(this) @@ -225,13 +226,13 @@ private[nio] object TypedArrayByteBuffer { new TypedArrayByteBuffer(new Int8Array(capacity), 0, capacity, false) } - def wrap(array: ArrayBuffer): ByteBuffer = - wrap(new Int8Array(array)) + def wrapArrayBuffer(array: ArrayBuffer): ByteBuffer = + wrapInt8Array(new Int8Array(array)) - def wrap(array: ArrayBuffer, byteOffset: Int, length: Int): ByteBuffer = - wrap(new Int8Array(array, byteOffset, length)) + def wrapArrayBuffer(array: ArrayBuffer, byteOffset: Int, length: Int): ByteBuffer = + wrapInt8Array(new Int8Array(array, byteOffset, length)) - def wrap(typedArray: Int8Array): ByteBuffer = { + def wrapInt8Array(typedArray: Int8Array): ByteBuffer = { val buf = new TypedArrayByteBuffer(typedArray, 0, typedArray.length, false) buf._isBigEndian = ByteOrder.areTypedArraysBigEndian buf diff --git a/javalib/src/main/scala/java/nio/TypedArrayCharBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayCharBuffer.scala index 96a8d82056..71a51057d2 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayCharBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayCharBuffer.scala @@ -135,6 +135,6 @@ private[nio] object TypedArrayCharBuffer { def fromTypedArrayByteBuffer(byteBuffer: TypedArrayByteBuffer): CharBuffer = GenTypedArrayBuffer.generic_fromTypedArrayByteBuffer(byteBuffer) - def wrap(array: Uint16Array): CharBuffer = + def wrapUint16Array(array: Uint16Array): CharBuffer = new TypedArrayCharBuffer(array, 0, array.length, false) } diff --git a/javalib/src/main/scala/java/nio/TypedArrayDoubleBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayDoubleBuffer.scala index 5cb48beace..4211fb143b 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayDoubleBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayDoubleBuffer.scala @@ -128,6 +128,6 @@ private[nio] object TypedArrayDoubleBuffer { def fromTypedArrayByteBuffer(byteBuffer: TypedArrayByteBuffer): DoubleBuffer = GenTypedArrayBuffer.generic_fromTypedArrayByteBuffer(byteBuffer) - def wrap(array: Float64Array): DoubleBuffer = + def wrapFloat64Array(array: Float64Array): DoubleBuffer = new TypedArrayDoubleBuffer(array, 0, array.length, false) } diff --git a/javalib/src/main/scala/java/nio/TypedArrayFloatBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayFloatBuffer.scala index d485e87054..cab3cbc756 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayFloatBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayFloatBuffer.scala @@ -128,6 +128,6 @@ private[nio] object TypedArrayFloatBuffer { def fromTypedArrayByteBuffer(byteBuffer: TypedArrayByteBuffer): FloatBuffer = GenTypedArrayBuffer.generic_fromTypedArrayByteBuffer(byteBuffer) - def wrap(array: Float32Array): FloatBuffer = + def wrapFloat32Array(array: Float32Array): FloatBuffer = new TypedArrayFloatBuffer(array, 0, array.length, false) } diff --git a/javalib/src/main/scala/java/nio/TypedArrayIntBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayIntBuffer.scala index 2d73e5025e..8beab4ac58 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayIntBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayIntBuffer.scala @@ -128,6 +128,6 @@ private[nio] object TypedArrayIntBuffer { def fromTypedArrayByteBuffer(byteBuffer: TypedArrayByteBuffer): IntBuffer = GenTypedArrayBuffer.generic_fromTypedArrayByteBuffer(byteBuffer) - def wrap(array: Int32Array): IntBuffer = + def wrapInt32Array(array: Int32Array): IntBuffer = new TypedArrayIntBuffer(array, 0, array.length, false) } diff --git a/javalib/src/main/scala/java/nio/TypedArrayShortBuffer.scala b/javalib/src/main/scala/java/nio/TypedArrayShortBuffer.scala index 0c77246b34..09a9ca38dc 100644 --- a/javalib/src/main/scala/java/nio/TypedArrayShortBuffer.scala +++ b/javalib/src/main/scala/java/nio/TypedArrayShortBuffer.scala @@ -128,6 +128,6 @@ private[nio] object TypedArrayShortBuffer { def fromTypedArrayByteBuffer(byteBuffer: TypedArrayByteBuffer): ShortBuffer = GenTypedArrayBuffer.generic_fromTypedArrayByteBuffer(byteBuffer) - def wrap(array: Int16Array): ShortBuffer = + def wrapInt16Array(array: Int16Array): ShortBuffer = new TypedArrayShortBuffer(array, 0, array.length, false) } diff --git a/javalib/src/main/scala/java/nio/charset/Charset.scala b/javalib/src/main/scala/java/nio/charset/Charset.scala index 2edce1ae00..c053e242ba 100644 --- a/javalib/src/main/scala/java/nio/charset/Charset.scala +++ b/javalib/src/main/scala/java/nio/charset/Charset.scala @@ -15,6 +15,7 @@ package java.nio.charset import java.nio.{ByteBuffer, CharBuffer} import java.util.{Collections, HashSet, Arrays} import java.util.ScalaOps._ +import java.util.JSUtils._ import scala.scalajs.js @@ -78,20 +79,22 @@ object Charset { def defaultCharset(): Charset = UTF_8 - def forName(charsetName: String): Charset = - CharsetMap.getOrElse(charsetName.toLowerCase, - throw new UnsupportedCharsetException(charsetName)) + def forName(charsetName: String): Charset = { + dictGetOrElse(CharsetMap, charsetName.toLowerCase()) { + throw new UnsupportedCharsetException(charsetName) + } + } def isSupported(charsetName: String): Boolean = - CharsetMap.contains(charsetName.toLowerCase) + dictContains(CharsetMap, charsetName.toLowerCase()) private lazy val CharsetMap = { - val m = js.Dictionary.empty[Charset] - for (c <- js.Array(US_ASCII, ISO_8859_1, UTF_8, UTF_16BE, UTF_16LE, UTF_16)) { - m(c.name().toLowerCase) = c + val m = dictEmpty[Charset]() + forArrayElems(js.Array(US_ASCII, ISO_8859_1, UTF_8, UTF_16BE, UTF_16LE, UTF_16)) { c => + dictSet(m, c.name().toLowerCase(), c) val aliases = c._aliases for (i <- 0 until aliases.length) - m(aliases(i).toLowerCase) = c + dictSet(m, aliases(i).toLowerCase(), c) } m } diff --git a/javalib/src/main/scala/java/nio/charset/CoderResult.scala b/javalib/src/main/scala/java/nio/charset/CoderResult.scala index f7f73967f3..257dd0904b 100644 --- a/javalib/src/main/scala/java/nio/charset/CoderResult.scala +++ b/javalib/src/main/scala/java/nio/charset/CoderResult.scala @@ -15,6 +15,7 @@ package java.nio.charset import scala.annotation.switch import java.nio._ +import java.util.JSUtils._ import scala.scalajs.js @@ -77,7 +78,7 @@ object CoderResult { } private def malformedForLengthImpl(length: Int): CoderResult = { - uniqueMalformed(length).fold { + undefOrFold(uniqueMalformed(length)) { val result = new CoderResult(Malformed, length) uniqueMalformed(length) = result result @@ -95,7 +96,7 @@ object CoderResult { } private def unmappableForLengthImpl(length: Int): CoderResult = { - uniqueUnmappable(length).fold { + undefOrFold(uniqueUnmappable(length)) { val result = new CoderResult(Unmappable, length) uniqueUnmappable(length) = result result diff --git a/javalib/src/main/scala/java/util/AbstractMap.scala b/javalib/src/main/scala/java/util/AbstractMap.scala index d2ea01b065..df75f5ba67 100644 --- a/javalib/src/main/scala/java/util/AbstractMap.scala +++ b/javalib/src/main/scala/java/util/AbstractMap.scala @@ -94,7 +94,7 @@ abstract class AbstractMap[K, V] protected () extends java.util.Map[K, V] { entrySet().scalaOps.exists(entry => Objects.equals(key, entry.getKey())) def get(key: Any): V = { - entrySet().scalaOps.find(entry => Objects.equals(key, entry.getKey())).fold[V] { + entrySet().scalaOps.findFold(entry => Objects.equals(key, entry.getKey())) { null.asInstanceOf[V] } { entry => entry.getValue() diff --git a/javalib/src/main/scala/java/util/ArrayDeque.scala b/javalib/src/main/scala/java/util/ArrayDeque.scala index 9abd87a7b9..46ef2388a8 100644 --- a/javalib/src/main/scala/java/util/ArrayDeque.scala +++ b/javalib/src/main/scala/java/util/ArrayDeque.scala @@ -13,6 +13,7 @@ package java.util import java.lang.Cloneable +import java.util.JSUtils._ import scala.scalajs.js @@ -37,6 +38,9 @@ class ArrayDeque[E] private (private val inner: js.Array[E]) addAll(c) } + @inline + override def isEmpty(): Boolean = inner.length == 0 + def addFirst(e: E): Unit = offerFirst(e) @@ -64,21 +68,21 @@ class ArrayDeque[E] private (private val inner: js.Array[E]) } def removeFirst(): E = { - if (inner.isEmpty) + if (isEmpty()) throw new NoSuchElementException() else pollFirst() } def removeLast(): E = { - if (inner.isEmpty) + if (isEmpty()) throw new NoSuchElementException() else pollLast() } def pollFirst(): E = { - if (inner.isEmpty) null.asInstanceOf[E] + if (isEmpty()) null.asInstanceOf[E] else { val res = inner.shift() status += 1 @@ -87,52 +91,65 @@ class ArrayDeque[E] private (private val inner: js.Array[E]) } def pollLast(): E = { - if (inner.isEmpty) null.asInstanceOf[E] + if (isEmpty()) null.asInstanceOf[E] else inner.pop() } def getFirst(): E = { - if (inner.isEmpty) + if (isEmpty()) throw new NoSuchElementException() else peekFirst() } def getLast(): E = { - if (inner.isEmpty) + if (isEmpty()) throw new NoSuchElementException() else peekLast() } def peekFirst(): E = { - if (inner.isEmpty) null.asInstanceOf[E] - else inner.head + if (isEmpty()) null.asInstanceOf[E] + else inner(0) } def peekLast(): E = { - if (inner.isEmpty) null.asInstanceOf[E] - else inner.last + if (isEmpty()) null.asInstanceOf[E] + else inner(inner.length - 1) } def removeFirstOccurrence(o: Any): Boolean = { - val index = inner.indexWhere(Objects.equals(_, o)) - if (index >= 0) { - inner.remove(index) - status += 1 - true - } else - false + // scalastyle:off return + val inner = this.inner // local copy + val len = inner.length + var i = 0 + while (i != len) { + if (Objects.equals(inner(i), o)) { + arrayRemove(inner, i) + status += 1 + return true + } + i += 1 + } + false + // scalastyle:on return } def removeLastOccurrence(o: Any): Boolean = { - val index = inner.lastIndexWhere(Objects.equals(_, o)) - if (index >= 0) { - inner.remove(index) - status += 1 - true - } else - false + // scalastyle:off return + val inner = this.inner // local copy + var i = inner.length - 1 + while (i >= 0) { + if (Objects.equals(inner(i), o)) { + arrayRemove(inner, i) + status += 1 + return true + } + i -= 1 + } + false + // scalastyle:on return } override def add(e: E): Boolean = { @@ -154,7 +171,7 @@ class ArrayDeque[E] private (private val inner: js.Array[E]) def pop(): E = removeFirst() - def size(): Int = inner.size + def size(): Int = inner.length private def failFastIterator(startIndex: Int, nex: (Int) => Int) = { new Iterator[E] { @@ -169,7 +186,7 @@ class ArrayDeque[E] private (private val inner: js.Array[E]) def hasNext(): Boolean = { checkStatus() val n = nex(index) - (n >= 0) && (n < inner.size) + (n >= 0) && (n < inner.length) } def next(): E = { @@ -180,10 +197,10 @@ class ArrayDeque[E] private (private val inner: js.Array[E]) override def remove(): Unit = { checkStatus() - if (index < 0 || index >= inner.size) { + if (index < 0 || index >= inner.length) { throw new IllegalStateException() } else { - inner.remove(index) + arrayRemove(inner, index) } } } @@ -193,14 +210,16 @@ class ArrayDeque[E] private (private val inner: js.Array[E]) failFastIterator(-1, x => (x + 1)) def descendingIterator(): Iterator[E] = - failFastIterator(inner.size, x => (x - 1)) + failFastIterator(inner.length, x => (x - 1)) - override def contains(o: Any): Boolean = inner.exists(Objects.equals(_, o)) + override def contains(o: Any): Boolean = + arrayExists(inner)(Objects.equals(_, o)) override def remove(o: Any): Boolean = removeFirstOccurrence(o) override def clear(): Unit = { - if (!inner.isEmpty) status += 1 - inner.clear() + if (!isEmpty()) + status += 1 + inner.length = 0 } } diff --git a/javalib/src/main/scala/java/util/ArrayList.scala b/javalib/src/main/scala/java/util/ArrayList.scala index 3f573bc526..ad0e9b2d19 100644 --- a/javalib/src/main/scala/java/util/ArrayList.scala +++ b/javalib/src/main/scala/java/util/ArrayList.scala @@ -13,6 +13,7 @@ package java.util import java.lang.Cloneable +import java.util.JSUtils._ import scala.scalajs._ @@ -60,22 +61,22 @@ class ArrayList[E] private (private[ArrayList] val inner: js.Array[E]) } override def add(e: E): Boolean = { - inner += e + inner.push(e) true } override def add(index: Int, element: E): Unit = { checkIndexOnBounds(index) - inner.insert(index, element) + inner.splice(index, 0, element) } override def remove(index: Int): E = { checkIndexInBounds(index) - inner.remove(index) + arrayRemoveAndGet(inner, index) } override def clear(): Unit = - inner.clear() + inner.length = 0 override def addAll(index: Int, c: Collection[_ <: E]): Boolean = { c match { diff --git a/javalib/src/main/scala/java/util/BitSet.scala b/javalib/src/main/scala/java/util/BitSet.scala index 5e2c4bd61f..171ed1a629 100644 --- a/javalib/src/main/scala/java/util/BitSet.scala +++ b/javalib/src/main/scala/java/util/BitSet.scala @@ -637,16 +637,20 @@ class BitSet private (private var bits: Array[Int]) extends Serializable with Cl var result: String = "{" var comma: Boolean = false + // Work around Scala 2.11 limitation with the IR cleaner ; should be double-for over i and j for { i <- 0 until getActualArrayLength() - j <- 0 until ElementSize } { - if ((bits(i) & (1 << j)) != 0) { - if (comma) - result += ", " - else - comma = true - result += (i << AddressBitsPerWord) + j + var j = 0 + while (j < ElementSize) { + if ((bits(i) & (1 << j)) != 0) { + if (comma) + result += ", " + else + comma = true + result += (i << AddressBitsPerWord) + j + } + j += 1 } } diff --git a/javalib/src/main/scala/java/util/Formatter.scala b/javalib/src/main/scala/java/util/Formatter.scala index 5535fda2cc..5807a2ddcf 100644 --- a/javalib/src/main/scala/java/util/Formatter.scala +++ b/javalib/src/main/scala/java/util/Formatter.scala @@ -18,6 +18,7 @@ import scala.scalajs.js import java.lang.{Double => JDouble} import java.io._ import java.math.{BigDecimal, BigInteger} +import java.util.JSUtils._ final class Formatter private (private[this] var dest: Appendable, formatterLocaleInfo: Formatter.LocaleInfo) @@ -82,8 +83,12 @@ final class Formatter private (private[this] var dest: Appendable, @noinline private def sendToDestSlowPath(ss: js.Array[String]): Unit = { + // Workaround Scala 2.11 limitation: cannot nest anonymous functions for the IR cleaner + @inline def body(): Unit = + forArrayElems(ss)(dest.append(_)) + trapIOExceptions { - ss.foreach(dest.append(_)) + body() } } @@ -334,7 +339,7 @@ final class Formatter private (private[this] var dest: Appendable, * Int range. */ private def parsePositiveInt(capture: js.UndefOr[String]): Int = { - capture.fold { + undefOrFold(capture) { -1 } { s => val x = js.Dynamic.global.parseInt(s, 10).asInstanceOf[Double] diff --git a/javalib/src/main/scala/java/util/JSUtils.scala b/javalib/src/main/scala/java/util/JSUtils.scala new file mode 100644 index 0000000000..0f7d3ab22f --- /dev/null +++ b/javalib/src/main/scala/java/util/JSUtils.scala @@ -0,0 +1,182 @@ +/* + * 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.language.implicitConversions + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSBracketAccess + +private[java] object JSUtils { + @inline + def undefined: js.UndefOr[Nothing] = ().asInstanceOf[js.UndefOr[Nothing]] + + @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 undefOrGetOrNull[A >: Null](x: js.UndefOr[A]): A = + if (undefOrIsDefined(x)) x.asInstanceOf[A] + else null + + @inline + def undefOrForeach[A](x: js.UndefOr[A])(f: A => Any): Unit = { + if (undefOrIsDefined(x)) + f(undefOrForceGet(x)) + } + + @inline + def undefOrFold[A, B](x: js.UndefOr[A])(default: => B)(f: 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 + } + + @inline + def dictEmpty[A](): js.Dictionary[A] = + new js.Object().asInstanceOf[js.Dictionary[A]] + + @inline + def dictGetOrElse[A](dict: js.Dictionary[_ <: A], key: String)( + default: => 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: A => Any): Unit = { + val len = array.length + var i = 0 + while (i != len) { + f(array(i)) + i += 1 + } + } + + @inline + def arrayRemove[A](array: js.Array[A], index: Int): Unit = + array.splice(index, 1) + + @inline + def arrayRemoveAndGet[A](array: js.Array[A], index: Int): A = + array.splice(index, 1)(0) + + @inline + def arrayExists[A](array: js.Array[A])(f: A => Boolean): Boolean = { + // scalastyle:off return + val len = array.length + var i = 0 + while (i != len) { + if (f(array(i))) + return true + i += 1 + } + false + // scalastyle:on return + } + + @js.native + private trait RawMap[K, V] extends js.Object { + def has(key: K): Boolean = js.native + def keys(): js.Iterator[K] = js.native + def set(key: K, value: V): js.Map[K, V] = js.native + def get(key: K): V = js.native + } + + @inline def mapHas[K, V](m: js.Map[K, V], key: K): Boolean = + m.asInstanceOf[RawMap[K, V]].has(key) + + @inline def mapGet[K, V](m: js.Map[K, V], key: K): V = + m.asInstanceOf[RawMap[K, V]].get(key) + + @inline def mapSet[K, V](m: js.Map[K, V], key: K, value: V): Unit = + m.asInstanceOf[RawMap[K, V]].set(key, value) + + @inline def mapGetOrElse[K, V](m: js.Map[K, V], key: K)(default: => V): V = + if (mapHas(m, key)) mapGet(m, key) + else default + + @inline def mapGetOrElseUpdate[K, V](m: js.Map[K, V], key: K)(default: => V): V = { + if (mapHas(m, key)) { + mapGet(m, key) + } else { + val value = default + mapSet(m, key, value) + value + } + } +} diff --git a/javalib/src/main/scala/java/util/ScalaOps.scala b/javalib/src/main/scala/java/util/ScalaOps.scala index 4362f77fa8..a3c64920b3 100644 --- a/javalib/src/main/scala/java/util/ScalaOps.scala +++ b/javalib/src/main/scala/java/util/ScalaOps.scala @@ -57,8 +57,8 @@ private[java] object ScalaOps { @inline def indexWhere(f: A => Boolean): Int = __self.iterator().scalaOps.indexWhere(f) - @inline def find(f: A => Boolean): Option[A] = - __self.iterator().scalaOps.find(f) + @inline def findFold[B](f: A => Boolean)(default: => B)(g: A => B): B = + __self.iterator().scalaOps.findFold(f)(default)(g) @inline def foldLeft[B](z: B)(f: (B, A) => B): B = __self.iterator().scalaOps.foldLeft(z)(f) @@ -112,14 +112,14 @@ private[java] object ScalaOps { // scalastyle:on return } - @inline def find(f: A => Boolean): Option[A] = { + @inline def findFold[B](f: A => Boolean)(default: => B)(g: A => B): B = { // scalastyle:off return while (__self.hasNext()) { val x = __self.next() if (f(x)) - return Some(x) + return g(x) } - None + default // scalastyle:on return } diff --git a/javalib/src/main/scala/java/util/Timer.scala b/javalib/src/main/scala/java/util/Timer.scala index ac75a1e61d..4be9d67d43 100644 --- a/javalib/src/main/scala/java/util/Timer.scala +++ b/javalib/src/main/scala/java/util/Timer.scala @@ -70,16 +70,18 @@ class Timer() { private def schedulePeriodically( task: TimerTask, delay: Long, period: Long): Unit = { acquire(task) - task.timeout(delay) { - def loop(): Unit = { - val startTime = System.nanoTime() - task.doRun() - val endTime = System.nanoTime() - val duration = (endTime - startTime) / 1000000 - task.timeout(period - duration) { - loop() - } + + def loop(): Unit = { + val startTime = System.nanoTime() + task.doRun() + val endTime = System.nanoTime() + val duration = (endTime - startTime) / 1000000 + task.timeout(period - duration) { + loop() } + } + + task.timeout(delay) { loop() } } @@ -100,21 +102,23 @@ class Timer() { private def scheduleFixed( task: TimerTask, delay: Long, period: Long): Unit = { acquire(task) - task.timeout(delay) { - def loop(scheduledTime: Long): Unit = { - task.doRun() - val nextScheduledTime = scheduledTime + period - val nowTime = System.nanoTime / 1000000L - if (nowTime >= nextScheduledTime) { - // Re-run immediately. + + def loop(scheduledTime: Long): Unit = { + task.doRun() + val nextScheduledTime = scheduledTime + period + val nowTime = System.nanoTime / 1000000L + if (nowTime >= nextScheduledTime) { + // Re-run immediately. + loop(nextScheduledTime) + } else { + // Re-run after a timeout. + task.timeout(nextScheduledTime - nowTime) { loop(nextScheduledTime) - } else { - // Re-run after a timeout. - task.timeout(nextScheduledTime - nowTime) { - loop(nextScheduledTime) - } } } + } + + task.timeout(delay) { loop(System.nanoTime / 1000000L + period) } } diff --git a/javalib/src/main/scala/java/util/TimerTask.scala b/javalib/src/main/scala/java/util/TimerTask.scala index 959d206f53..4299157e89 100644 --- a/javalib/src/main/scala/java/util/TimerTask.scala +++ b/javalib/src/main/scala/java/util/TimerTask.scala @@ -12,7 +12,8 @@ package java.util -import scala.scalajs.js.timers._ +import scala.scalajs.js +import scala.scalajs.js.timers.RawTimers._ import scala.scalajs.js.timers.SetTimeoutHandle abstract class TimerTask { @@ -42,7 +43,7 @@ abstract class TimerTask { private[util] def timeout(delay: Long)(body: => Unit): Unit = { if (!canceled) { - handle = setTimeout(delay.toDouble)(body) + handle = setTimeout(() => body, delay.toDouble) } } diff --git a/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala b/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala index f4fd855cec..16d35937fd 100644 --- a/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala +++ b/javalib/src/main/scala/java/util/concurrent/CopyOnWriteArrayList.scala @@ -16,6 +16,7 @@ import java.lang.Cloneable import java.lang.{reflect => jlr} import java.util._ import java.util.function.{Predicate, UnaryOperator} +import java.util.JSUtils._ import scala.annotation.tailrec @@ -47,7 +48,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) } def size(): Int = - inner.size + inner.length def isEmpty(): Boolean = size() == 0 @@ -291,7 +292,7 @@ class CopyOnWriteArrayList[E <: AnyRef] private (private var inner: js.Array[E]) } protected def innerRemove(index: Int): E = - inner.splice(index, 1)(0) + arrayRemoveAndGet(inner, index) protected def innerRemoveMany(index: Int, count: Int): Unit = inner.splice(index, count) diff --git a/javalib/src/main/scala/java/util/internal/MurmurHash3.scala b/javalib/src/main/scala/java/util/internal/MurmurHash3.scala new file mode 100644 index 0000000000..bcf438f131 --- /dev/null +++ b/javalib/src/main/scala/java/util/internal/MurmurHash3.scala @@ -0,0 +1,61 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util.internal + +import java.lang.Integer.{rotateLeft => rotl} + +/** Primitives to implement MurmurHash3 hashes in data structures. + * + * This is copy of parts of `scala.util.hashing.MurmurHash3`. + */ +private[java] object MurmurHash3 { + /** Mix in a block of data into an intermediate hash value. */ + final def mix(hash: Int, data: Int): Int = { + var h = mixLast(hash, data) + h = rotl(h, 13) + h * 5 + 0xe6546b64 + } + + /** May optionally be used as the last mixing step. + * + * Is a little bit faster than mix, as it does no further mixing of the + * resulting hash. For the last element this is not necessary as the hash is + * thoroughly mixed during finalization anyway. + */ + final def mixLast(hash: Int, data: Int): Int = { + var k = data + + k *= 0xcc9e2d51 + k = rotl(k, 15) + k *= 0x1b873593 + + hash ^ k + } + + /** Finalize a hash to incorporate the length and make sure all bits avalanche. */ + @noinline final def finalizeHash(hash: Int, length: Int): Int = + avalanche(hash ^ length) + + /** Force all bits of the hash to avalanche. Used for finalizing the hash. */ + @inline private final def avalanche(hash: Int): Int = { + var h = hash + + h ^= h >>> 16 + h *= 0x85ebca6b + h ^= h >>> 13 + h *= 0xc2b2ae35 + h ^= h >>> 16 + + h + } +} diff --git a/javalib/src/main/scala/java/util/internal/RefTypes.scala b/javalib/src/main/scala/java/util/internal/RefTypes.scala new file mode 100644 index 0000000000..d02cf33d8d --- /dev/null +++ b/javalib/src/main/scala/java/util/internal/RefTypes.scala @@ -0,0 +1,94 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util.internal + +@inline +private[java] class BooleanRef(var elem: Boolean) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object BooleanRef { + def create(elem: Boolean): BooleanRef = new BooleanRef(elem) + def zero(): BooleanRef = new BooleanRef(false) +} + +@inline +private[java] class CharRef(var elem: Char) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object CharRef { + def create(elem: Char): CharRef = new CharRef(elem) + def zero(): CharRef = new CharRef(0.toChar) +} + +@inline +private[java] class ByteRef(var elem: Byte) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object ByteRef { + def create(elem: Byte): ByteRef = new ByteRef(elem) + def zero(): ByteRef = new ByteRef(0) +} + +@inline +private[java] class ShortRef(var elem: Short) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object ShortRef { + def create(elem: Short): ShortRef = new ShortRef(elem) + def zero(): ShortRef = new ShortRef(0) +} + +@inline +private[java] class IntRef(var elem: Int) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object IntRef { + def create(elem: Int): IntRef = new IntRef(elem) + def zero(): IntRef = new IntRef(0) +} + +@inline +private[java] class LongRef(var elem: Long) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object LongRef { + def create(elem: Long): LongRef = new LongRef(elem) + def zero(): LongRef = new LongRef(0) +} + +@inline +private[java] class FloatRef(var elem: Float) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object FloatRef { + def create(elem: Float): FloatRef = new FloatRef(elem) + def zero(): FloatRef = new FloatRef(0) +} + +@inline +private[java] class DoubleRef(var elem: Double) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object DoubleRef { + def create(elem: Double): DoubleRef = new DoubleRef(elem) + def zero(): DoubleRef = new DoubleRef(0) +} + +@inline +private[java] class ObjectRef[A](var elem: A) extends Serializable { + override def toString(): String = String.valueOf(elem) +} +private[java] object ObjectRef { + def create[A](elem: A): ObjectRef[A] = new ObjectRef(elem) + def zero(): ObjectRef[Object] = new ObjectRef(null) +} diff --git a/javalib/src/main/scala/java/util/internal/Tuples.scala b/javalib/src/main/scala/java/util/internal/Tuples.scala new file mode 100644 index 0000000000..d476cd74a9 --- /dev/null +++ b/javalib/src/main/scala/java/util/internal/Tuples.scala @@ -0,0 +1,26 @@ +/* + * Scala.js (https://www.scala-js.org/) + * + * Copyright EPFL. + * + * Licensed under Apache License 2.0 + * (https://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package java.util.internal + +@inline +final class Tuple2[+T1, +T2](val _1: T1, val _2: T2) + +@inline +final class Tuple3[+T1, +T2, +T3](val _1: T1, val _2: T2, val _3: T3) + +@inline +final class Tuple4[+T1, +T2, +T3, +T4](val _1: T1, val _2: T2, val _3: T3, val _4: T4) + +@inline +final class Tuple8[+T1, +T2, +T3, +T4, +T5, +T6, +T7, +T8]( + val _1: T1, val _2: T2, val _3: T3, val _4: T4, val _5: T5, val _6: T6, val _7: T7, val _8: T8) diff --git a/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala b/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala index 4b866920b0..257e399807 100644 --- a/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala +++ b/javalib/src/main/scala/java/util/regex/IndicesBuilder.scala @@ -14,6 +14,8 @@ package java.util.regex import scala.annotation.{tailrec, switch} +import java.util.JSUtils._ + import scala.scalajs.js import Pattern.IndicesArray @@ -79,7 +81,7 @@ private[regex] class IndicesBuilder private (pattern: String, flags: String, } val start = index // by definition - val end = start + allMatchResult(0).get.length() + val end = start + undefOrForceGet(allMatchResult(0)).length() /* Initialize the `indices` array with: * - `[start, end]` at index 0, which represents the whole match, and @@ -91,10 +93,10 @@ private[regex] class IndicesBuilder private (pattern: String, flags: String, */ val len = groupCount + 1 val indices = new IndicesArray(len) - indices(0) = js.Tuple2(start, end) + indices(0) = js.Array(start, end).asInstanceOf[js.Tuple2[Int, Int]] var i = 1 while (i != len) { - indices(i) = js.undefined + indices(i) = undefined i += 1 } @@ -179,7 +181,7 @@ private[regex] object IndicesBuilder { final def propagateFromEnd(matchResult: js.RegExp.ExecResult, indices: IndicesArray, end: Int): Unit = { - val start = matchResult(newGroup).fold(-1)(matched => end - matched.length) + val start = undefOrFold(matchResult(newGroup))(-1)(matched => end - matched.length) propagate(matchResult, indices, start, end) } @@ -191,7 +193,7 @@ private[regex] object IndicesBuilder { final def propagateFromStart(matchResult: js.RegExp.ExecResult, indices: IndicesArray, start: Int): Int = { - val end = matchResult(newGroup).fold(-1)(matched => start + matched.length) + val end = undefOrFold(matchResult(newGroup))(-1)(matched => start + matched.length) propagate(matchResult, indices, start, end) end } @@ -212,8 +214,8 @@ private[regex] object IndicesBuilder { * always keep the default `-1` if this group node does not match * anything. */ - if (matchResult(newGroup).isDefined) - indices(number) = js.Tuple2(start, end) + if (undefOrIsDefined(matchResult(newGroup))) + indices(number) = js.Array(start, end).asInstanceOf[js.Tuple2[Int, Int]] inner.propagate(matchResult, indices, start, end) } } diff --git a/javalib/src/main/scala/java/util/regex/Matcher.scala b/javalib/src/main/scala/java/util/regex/Matcher.scala index 4effe7de81..6385dbd96a 100644 --- a/javalib/src/main/scala/java/util/regex/Matcher.scala +++ b/javalib/src/main/scala/java/util/regex/Matcher.scala @@ -12,6 +12,8 @@ package java.util.regex +import java.util.JSUtils._ + import scala.annotation.switch import scala.scalajs.js @@ -182,13 +184,13 @@ final class Matcher private[regex] ( def start(): Int = ensureLastMatch.index + regionStart() def end(): Int = start() + group().length - def group(): String = ensureLastMatch(0).get + def group(): String = undefOrForceGet(ensureLastMatch(0)) private def indices: IndicesArray = pattern().getIndices(ensureLastMatch, lastMatchIsForMatches) private def startInternal(compiledGroup: Int): Int = - indices(compiledGroup).fold(-1)(_._1 + regionStart()) + undefOrFold(indices(compiledGroup))(-1)(_._1 + regionStart()) def start(group: Int): Int = startInternal(pattern().numberedGroup(group)) @@ -197,7 +199,7 @@ final class Matcher private[regex] ( startInternal(pattern().namedGroup(name)) private def endInternal(compiledGroup: Int): Int = - indices(compiledGroup).fold(-1)(_._2 + regionStart()) + undefOrFold(indices(compiledGroup))(-1)(_._2 + regionStart()) def end(group: Int): Int = endInternal(pattern().numberedGroup(group)) @@ -206,10 +208,10 @@ final class Matcher private[regex] ( endInternal(pattern().namedGroup(name)) def group(group: Int): String = - ensureLastMatch(pattern().numberedGroup(group)).orNull + undefOrGetOrNull(ensureLastMatch(pattern().numberedGroup(group))) def group(name: String): String = - ensureLastMatch(pattern().namedGroup(name)).orNull + undefOrGetOrNull(ensureLastMatch(pattern().namedGroup(name))) // Seal the state @@ -266,7 +268,7 @@ object Matcher { def start(): Int = ensureLastMatch.index + regionStart def end(): Int = start() + group().length - def group(): String = ensureLastMatch(0).get + def group(): String = undefOrForceGet(ensureLastMatch(0)) private def indices: IndicesArray = pattern.getIndices(ensureLastMatch, lastMatchIsForMatches) @@ -276,13 +278,13 @@ object Matcher { */ def start(group: Int): Int = - indices(pattern.numberedGroup(group)).fold(-1)(_._1 + regionStart) + undefOrFold(indices(pattern.numberedGroup(group)))(-1)(_._1 + regionStart) def end(group: Int): Int = - indices(pattern.numberedGroup(group)).fold(-1)(_._2 + regionStart) + undefOrFold(indices(pattern.numberedGroup(group)))(-1)(_._2 + regionStart) def group(group: Int): String = - ensureLastMatch(pattern.numberedGroup(group)).orNull + undefOrGetOrNull(ensureLastMatch(pattern.numberedGroup(group))) private def ensureLastMatch: js.RegExp.ExecResult = { if (lastMatch == null) diff --git a/javalib/src/main/scala/java/util/regex/Pattern.scala b/javalib/src/main/scala/java/util/regex/Pattern.scala index fc747f0eba..a26bff33d0 100644 --- a/javalib/src/main/scala/java/util/regex/Pattern.scala +++ b/javalib/src/main/scala/java/util/regex/Pattern.scala @@ -14,6 +14,7 @@ package java.util.regex import scala.annotation.tailrec +import java.util.JSUtils._ import java.util.ScalaOps._ import scala.scalajs.js @@ -132,14 +133,14 @@ final class Pattern private[regex] ( } private[regex] def namedGroup(name: String): Int = { - groupNumberMap(namedGroups.getOrElse(name, { + groupNumberMap(dictGetOrElse(namedGroups, name) { throw new IllegalArgumentException(s"No group with name <$name>") - })) + }) } private[regex] def getIndices(lastMatch: js.RegExp.ExecResult, forMatches: Boolean): IndicesArray = { val lastMatchDyn = lastMatch.asInstanceOf[js.Dynamic] - if (js.isUndefined(lastMatchDyn.indices)) { + if (isUndefined(lastMatchDyn.indices)) { if (supportsIndices) { if (!enabledNativeIndices) { jsRegExpForFind = new js.RegExp(jsPattern, jsFlagsForFind + "d") diff --git a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala index bdc9593238..5011bab65a 100644 --- a/javalib/src/main/scala/java/util/regex/PatternCompiler.scala +++ b/javalib/src/main/scala/java/util/regex/PatternCompiler.scala @@ -25,10 +25,12 @@ import java.lang.Character.{ MAX_LOW_SURROGATE } +import java.util.JSUtils._ import java.util.ScalaOps._ import scala.scalajs.js -import scala.scalajs.LinkingInfo.{ESVersion, esVersion} +import scala.scalajs.runtime.linkingInfo +import scala.scalajs.LinkingInfo.ESVersion /** Compiler from Java regular expressions to JavaScript regular expressions. * @@ -80,15 +82,15 @@ private[regex] object PatternCompiler { /** Cache for `Support.supportsUnicode`. */ private val _supportsUnicode = - (esVersion >= ESVersion.ES2015) || featureTest("u") + (linkingInfo.esVersion >= ESVersion.ES2015) || featureTest("u") /** Cache for `Support.supportsSticky`. */ private val _supportsSticky = - (esVersion >= ESVersion.ES2015) || featureTest("y") + (linkingInfo.esVersion >= ESVersion.ES2015) || featureTest("y") /** Cache for `Support.supportsDotAll`. */ private val _supportsDotAll = - (esVersion >= ESVersion.ES2018) || featureTest("us") + (linkingInfo.esVersion >= ESVersion.ES2018) || featureTest("us") /** Cache for `Support.supportsIndices`. */ private val _supportsIndices = @@ -104,17 +106,17 @@ private[regex] object PatternCompiler { /** Tests whether the underlying JS RegExp supports the 'u' flag. */ @inline def supportsUnicode: Boolean = - (esVersion >= ESVersion.ES2015) || _supportsUnicode + (linkingInfo.esVersion >= ESVersion.ES2015) || _supportsUnicode /** Tests whether the underlying JS RegExp supports the 'y' flag. */ @inline def supportsSticky: Boolean = - (esVersion >= ESVersion.ES2015) || _supportsSticky + (linkingInfo.esVersion >= ESVersion.ES2015) || _supportsSticky /** Tests whether the underlying JS RegExp supports the 's' flag. */ @inline def supportsDotAll: Boolean = - (esVersion >= ESVersion.ES2018) || _supportsDotAll + (linkingInfo.esVersion >= ESVersion.ES2018) || _supportsDotAll /** Tests whether the underlying JS RegExp supports the 'd' flag. */ @inline @@ -128,7 +130,7 @@ private[regex] object PatternCompiler { */ @inline def enableUnicodeCaseInsensitive: Boolean = - esVersion >= ESVersion.ES2015 + linkingInfo.esVersion >= ESVersion.ES2015 /** Tests whether features requiring \p{} and/or look-behind assertions are enabled. * @@ -137,7 +139,7 @@ private[regex] object PatternCompiler { */ @inline def enableUnicodeCharacterClassesAndLookBehinds: Boolean = - esVersion >= ESVersion.ES2018 + linkingInfo.esVersion >= ESVersion.ES2018 } import Support._ @@ -212,7 +214,7 @@ private[regex] object PatternCompiler { import InlinedHelpers._ private def codePointToString(codePoint: Int): String = { - if (esVersion >= ESVersion.ES2015) { + if (linkingInfo.esVersion >= ESVersion.ES2015) { js.Dynamic.global.String.fromCodePoint(codePoint).asInstanceOf[String] } else { if (isBmpCodePoint(codePoint)) { @@ -286,24 +288,24 @@ private[regex] object PatternCompiler { * This is a `js.Dictionary` because it can be used even when compiling to * ECMAScript 5.1. */ - private val asciiPOSIXCharacterClasses = { + private val asciiPOSIXCharacterClasses: js.Dictionary[CompiledCharClass] = { import CompiledCharClass._ - js.Dictionary( - ("Lower", posClass("a-z")), - ("Upper", posClass("A-Z")), - ("ASCII", posClass("\u0000-\u007f")), - ("Alpha", posClass("A-Za-z")), // [\p{Lower}\p{Upper}] - ("Digit", posClass("0-9")), - ("Alnum", posClass("0-9A-Za-z")), // [\p{Alpha}\p{Digit}] - ("Punct", posClass("!-/:-@[-`{-~")), // One of !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ - ("Graph", posClass("!-~")), // [\p{Alnum}\p{Punct}] - ("Print", posClass(" -~")), // [\p{Graph}\x20] - ("Blank", posClass("\t ")), - ("Cntrl", posClass("\u0000-\u001f\u007f")), - ("XDigit", posClass("0-9A-Fa-f")), - ("Space", posClass("\t-\r ")) // [ \t\n\x0B\f\r] - ) + val r = dictEmpty[CompiledCharClass]() + dictSet(r, "Lower", posClass("a-z")) + dictSet(r, "Upper", posClass("A-Z")) + dictSet(r, "ASCII", posClass("\u0000-\u007f")) + dictSet(r, "Alpha", posClass("A-Za-z")) // [\p{Lower}\p{Upper}] + dictSet(r, "Digit", posClass("0-9")) + dictSet(r, "Alnum", posClass("0-9A-Za-z")) // [\p{Alpha}\p{Digit}] + dictSet(r, "Punct", posClass("!-/:-@[-`{-~")) // One of !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ + dictSet(r, "Graph", posClass("!-~")) // [\p{Alnum}\p{Punct}] + dictSet(r, "Print", posClass(" -~")) // [\p{Graph}\x20] + dictSet(r, "Blank", posClass("\t ")) + dictSet(r, "Cntrl", posClass("\u0000-\u001f\u007f")) + dictSet(r, "XDigit", posClass("0-9A-Fa-f")) + dictSet(r, "Space", posClass("\t-\r ")) // [ \t\n\x0B\f\r] + r } /** Mapping of predefined character classes to the corresponding character @@ -333,70 +335,70 @@ private[regex] object PatternCompiler { "Cc", "Cf", "Cs", "Co", "Cn", "C" ) - for (gc <- generalCategories) { + forArrayElems(generalCategories) { gc => val compiled = posP(gc) - result(gc) = compiled - result("Is" + gc) = compiled - result("general_category=" + gc) = compiled - result("gc=" + gc) = compiled + mapSet(result, gc, compiled) + mapSet(result, "Is" + gc, compiled) + mapSet(result, "general_category=" + gc, compiled) + mapSet(result, "gc=" + gc, compiled) } // Binary properties - result("IsAlphabetic") = posP("Alphabetic") - result("IsIdeographic") = posP("Ideographic") - result("IsLetter") = posP("Letter") - result("IsLowercase") = posP("Lowercase") - result("IsUppercase") = posP("Uppercase") - result("IsTitlecase") = posP("Lt") - result("IsPunctuation") = posP("Punctuation") - result("IsControl") = posP("Control") - result("IsWhite_Space") = posP("White_Space") - result("IsDigit") = posP("Nd") - result("IsHex_Digit") = posP("Hex_Digit") - result("IsJoin_Control") = posP("Join_Control") - result("IsNoncharacter_Code_Point") = posP("Noncharacter_Code_Point") - result("IsAssigned") = posP("Assigned") + mapSet(result, "IsAlphabetic", posP("Alphabetic")) + mapSet(result, "IsIdeographic", posP("Ideographic")) + mapSet(result, "IsLetter", posP("Letter")) + mapSet(result, "IsLowercase", posP("Lowercase")) + mapSet(result, "IsUppercase", posP("Uppercase")) + mapSet(result, "IsTitlecase", posP("Lt")) + mapSet(result, "IsPunctuation", posP("Punctuation")) + mapSet(result, "IsControl", posP("Control")) + mapSet(result, "IsWhite_Space", posP("White_Space")) + mapSet(result, "IsDigit", posP("Nd")) + mapSet(result, "IsHex_Digit", posP("Hex_Digit")) + mapSet(result, "IsJoin_Control", posP("Join_Control")) + mapSet(result, "IsNoncharacter_Code_Point", posP("Noncharacter_Code_Point")) + mapSet(result, "IsAssigned", posP("Assigned")) // java.lang.Character classes - result("javaAlphabetic") = posP("Alphabetic") - result("javaDefined") = negP("Cn") - result("javaDigit") = posP("Nd") - result("javaIdentifierIgnorable") = posClass("\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}") - result("javaIdeographic") = posP("Ideographic") - result("javaISOControl") = posClass("\u0000-\u001F\u007F-\u009F") - result("javaJavaIdentifierPart") = - posClass("\\p{L}\\p{Sc}\\p{Pc}\\p{Nd}\\p{Nl}\\p{Mn}\\p{Mc}\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}") - result("javaJavaIdentifierStart") = posClass("\\p{L}\\p{Sc}\\p{Pc}\\p{Nl}") - result("javaLetterOrDigit") = posClass("\\p{L}\\p{Nd}") - result("javaLowerCase") = posP("Lowercase") - result("javaMirrored") = posP("Bidi_Mirrored") - result("javaSpaceChar") = posP("Z") - result("javaTitleCase") = posP("Lt") - result("javaUnicodeIdentifierPart") = - posClass("\\p{ID_Continue}\u2E2F\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}") - result("javaUnicodeIdentifierStart") = posClass("\\p{ID_Start}\u2E2F") - result("javaUpperCase") = posP("Uppercase") + mapSet(result, "javaAlphabetic", posP("Alphabetic")) + mapSet(result, "javaDefined", negP("Cn")) + mapSet(result, "javaDigit", posP("Nd")) + mapSet(result, "javaIdentifierIgnorable", posClass("\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}")) + mapSet(result, "javaIdeographic", posP("Ideographic")) + mapSet(result, "javaISOControl", posClass("\u0000-\u001F\u007F-\u009F")) + mapSet(result, "javaJavaIdentifierPart", + posClass("\\p{L}\\p{Sc}\\p{Pc}\\p{Nd}\\p{Nl}\\p{Mn}\\p{Mc}\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}")) + mapSet(result, "javaJavaIdentifierStart", posClass("\\p{L}\\p{Sc}\\p{Pc}\\p{Nl}")) + mapSet(result, "javaLetterOrDigit", posClass("\\p{L}\\p{Nd}")) + mapSet(result, "javaLowerCase", posP("Lowercase")) + mapSet(result, "javaMirrored", posP("Bidi_Mirrored")) + mapSet(result, "javaSpaceChar", posP("Z")) + mapSet(result, "javaTitleCase", posP("Lt")) + mapSet(result, "javaUnicodeIdentifierPart", + posClass("\\p{ID_Continue}\u2E2F\u0000-\u0008\u000E-\u001B\u007F-\u009F\\p{Cf}")) + mapSet(result, "javaUnicodeIdentifierStart", posClass("\\p{ID_Start}\u2E2F")) + mapSet(result, "javaUpperCase", posP("Uppercase")) // [\t-\r\u001C-\u001F\\p{Z}&&[^\u00A0\u2007\u202F]] - result("javaWhitespace") = - posClass("\t-\r\u001C-\u001F \u1680\u2000-\u2006\u2008-\u200A\u205F\u3000\\p{Zl}\\p{Zp}") + mapSet(result, "javaWhitespace", + posClass("\t-\r\u001C-\u001F \u1680\u2000-\u2006\u2008-\u200A\u205F\u3000\\p{Zl}\\p{Zp}")) /* POSIX character classes with Unicode compatibility * (resolved from the original definitions, which are in comments) */ - result("Lower") = posP("Lower") // \p{IsLowercase} - result("Upper") = posP("Upper") // \p{IsUppercase} - result("ASCII") = posClass("\u0000-\u007f") - result("Alpha") = posP("Alpha") // \p{IsAlphabetic} - result("Digit") = posP("Nd") // \p{IsDigit} - result("Alnum") = posClass("\\p{Alpha}\\p{Nd}") // [\p{IsAlphabetic}\p{IsDigit}] - result("Punct") = posP("P") // \p{IsPunctuation} + mapSet(result, "Lower", posP("Lower")) // \p{IsLowercase} + mapSet(result, "Upper", posP("Upper")) // \p{IsUppercase} + mapSet(result, "ASCII", posClass("\u0000-\u007f")) + mapSet(result, "Alpha", posP("Alpha")) // \p{IsAlphabetic} + mapSet(result, "Digit", posP("Nd")) // \p{IsDigit} + mapSet(result, "Alnum", posClass("\\p{Alpha}\\p{Nd}")) // [\p{IsAlphabetic}\p{IsDigit}] + mapSet(result, "Punct", posP("P")) // \p{IsPunctuation} // [^\p{IsWhite_Space}\p{gc=Cc}\p{gc=Cs}\p{gc=Cn}] - result("Graph") = negClass("\\p{White_Space}\\p{Cc}\\p{Cs}\\p{Cn}") + mapSet(result, "Graph", negClass("\\p{White_Space}\\p{Cc}\\p{Cs}\\p{Cn}")) /* [\p{Graph}\p{Blank}&&[^\p{Cntrl}]] * === (by definition of Cntrl) @@ -416,7 +418,7 @@ private[regex] object PatternCompiler { * === (because \x09-\x0d and \x85 are all in the Cc category) * [^\p{Zl}\p{Zp}\p{Cc}\p{Cs}\p{Cn}] */ - result("Print") = negClass("\\p{Zl}\\p{Zp}\\p{Cc}\\p{Cs}\\p{Cn}") + mapSet(result, "Print", negClass("\\p{Zl}\\p{Zp}\\p{Cc}\\p{Cs}\\p{Cn}")) /* [\p{IsWhite_Space}&&[^\p{gc=Zl}\p{gc=Zp}\x0a\x0b\x0c\x0d\x85]] * === (see the excerpt from PropList.txt below) @@ -424,11 +426,11 @@ private[regex] object PatternCompiler { * === (by simplification) * [\x09\p{gc=Zs}] */ - result("Blank") = posClass("\t\\p{Zs}") + mapSet(result, "Blank", posClass("\t\\p{Zs}")) - result("Cntrl") = posP("Cc") // \p{gc=Cc} - result("XDigit") = posClass("\\p{Nd}\\p{Hex}") // [\p{gc=Nd}\p{IsHex_Digit}] - result("Space") = posP("White_Space") // \p{IsWhite_Space} + mapSet(result, "Cntrl", posP("Cc")) // \p{gc=Cc} + mapSet(result, "XDigit", posClass("\\p{Nd}\\p{Hex}")) // [\p{gc=Nd}\p{IsHex_Digit}] + mapSet(result, "Space", posP("White_Space")) // \p{IsWhite_Space} result } @@ -473,7 +475,7 @@ private[regex] object PatternCompiler { /* SignWriting is an exception. It has an uppercase 'W' even though it is * not after '_'. We add the exception to the map immediately. */ - result("signwriting") = "SignWriting" + mapSet(result, "signwriting", "SignWriting") result } @@ -741,7 +743,7 @@ private final class PatternCompiler(private val pattern: String, private var fla * We store *original* group numbers, rather than compiled group numbers, * in order to make the renumbering caused by possessive quantifiers easier. */ - private val namedGroups = js.Dictionary.empty[Int] + private val namedGroups = dictEmpty[Int]() @inline private def hasFlag(flag: Int): Boolean = (flags & flag) != 0 @@ -850,7 +852,7 @@ private final class PatternCompiler(private val pattern: String, private var fla private def processLeadingEmbeddedFlags(): Unit = { val m = leadingEmbeddedFlagSpecifierRegExp.exec(pattern) if (m != null) { - for (chars <- m(1)) { + undefOrForeach(m(1)) { chars => for (i <- 0 until chars.length()) flags |= charToFlag(chars.charAt(i)) } @@ -859,7 +861,7 @@ private final class PatternCompiler(private val pattern: String, private var fla if (hasFlag(UNICODE_CHARACTER_CLASS)) flags |= UNICODE_CASE - for (chars <- m(2)) { + undefOrForeach(m(2)) { chars => for (i <- 0 until chars.length()) flags &= ~charToFlag(chars.charAt(i)) } @@ -872,7 +874,7 @@ private final class PatternCompiler(private val pattern: String, private var fla */ // Advance past the embedded flags - pIndex += m(0).get.length() + pIndex += undefOrForceGet(m(0)).length() } } @@ -1362,9 +1364,9 @@ private final class PatternCompiler(private val pattern: String, private var fla parseError("\\k is not followed by '<' for named capturing group") pIndex += 1 val groupName = parseGroupName() - val groupNumber = namedGroups.getOrElse(groupName, { + val groupNumber = dictGetOrElse(namedGroups, groupName) { parseError(s"named capturing group <$groupName> does not exit") - }) + } val compiledGroupNumber = groupNumberMap(groupNumber) pIndex += 1 // Wrap in a non-capturing group in case it's followed by a (de-escaped) digit @@ -1607,16 +1609,16 @@ private final class PatternCompiler(private val pattern: String, private var fla pattern.substring(start, start + 1) } - val result = if (!unicodeCharacterClass && asciiPOSIXCharacterClasses.contains(property)) { + val result = if (!unicodeCharacterClass && dictContains(asciiPOSIXCharacterClasses, property)) { val property2 = if (asciiCaseInsensitive && (property == "Lower" || property == "Upper")) "Alpha" else property - asciiPOSIXCharacterClasses(property2) + dictRawApply(asciiPOSIXCharacterClasses, property2) } else { // For anything else, we need built-in support for \p requireES2018Features("Unicode character family") - predefinedPCharacterClasses.getOrElse(property, { + mapGetOrElse(predefinedPCharacterClasses, property) { val scriptPrefixLen = if (property.startsWith("Is")) { 2 } else if (property.startsWith("sc=")) { @@ -1630,7 +1632,7 @@ private final class PatternCompiler(private val pattern: String, private var fla parseError(s"Unknown Unicode character class '$property'") } CompiledCharClass.posP("sc=" + canonicalizeScriptName(property.substring(scriptPrefixLen))) - }) + } } pIndex += 1 @@ -1651,7 +1653,7 @@ private final class PatternCompiler(private val pattern: String, private var fla val lowercase = scriptName.toLowerCase() - canonicalizedScriptNameCache.getOrElseUpdate(lowercase, { + mapGetOrElseUpdate(canonicalizedScriptNameCache, lowercase) { val canonical = lowercase.jsReplace(scriptCanonicalizeRegExp, ((s: String) => s.toUpperCase()): js.Function1[String, String]) @@ -1663,7 +1665,7 @@ private final class PatternCompiler(private val pattern: String, private var fla } canonical - }) + } } private def compileCharacterClass(): String = { @@ -1805,11 +1807,11 @@ private final class PatternCompiler(private val pattern: String, private var fla // Named capturing group pIndex = start + 3 val name = parseGroupName() - if (namedGroups.contains(name)) + if (dictContains(namedGroups, name)) parseError(s"named capturing group <$name> is already defined") compiledGroupCount += 1 groupNumberMap.push(compiledGroupCount) // this changes originalGroupCount - namedGroups(name) = originalGroupCount + dictSet(namedGroups, name, originalGroupCount) pIndex += 1 "(" + compileInsideGroup() + ")" } else { diff --git a/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala b/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala index 0faf78fafc..99937b1550 100644 --- a/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala +++ b/javalib/src/main/scala/java/util/regex/PatternSyntaxException.scala @@ -13,6 +13,7 @@ package java.util.regex import scala.scalajs.js +import scala.scalajs.runtime.linkingInfo import scala.scalajs.LinkingInfo class PatternSyntaxException(desc: String, regex: String, index: Int) @@ -41,7 +42,7 @@ class PatternSyntaxException(desc: String, regex: String, index: Int) @inline private def repeat(s: String, count: Int): String = { // TODO Use java.lang.String.repeat() once we can (JDK 11+ method) - if (LinkingInfo.esVersion >= LinkingInfo.ESVersion.ES2015) { + if (linkingInfo.esVersion >= LinkingInfo.ESVersion.ES2015) { s.asInstanceOf[js.Dynamic].repeat(count).asInstanceOf[String] } else { var result = "" diff --git a/javalib/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferBridge.scala b/javalib/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferBridge.scala index c692870f9a..3468a9f7e9 100644 --- a/javalib/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferBridge.scala +++ b/javalib/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferBridge.scala @@ -21,12 +21,17 @@ * members are public. * * In library/, this file has only the signatures, with stub implementations. - * In javalib/, it has the proper the proper implementations. + * In javalib/, it has the proper implementations. * The build keeps the .class coming from library/ and the .sjsir file from * javalib/. This way, we bridge the library and javalib. But that means the * binary interface of TypedArrayBufferBridge must be strictly equivalent in * the two copies. * + * Because of these copies, we must also explicitly use `Any` instead of all + * JS types in the method signatures. The IR cleaner would replace any JS type + * by `Any` in the javalib, so if we don't write them like that in the library + * as well, there will be mismatches. + * * (Yes, this is a hack.) * !!!!! */ @@ -36,45 +41,45 @@ package scala.scalajs.js.typedarray import java.nio._ private[typedarray] object TypedArrayBufferBridge { - def wrap(array: ArrayBuffer): ByteBuffer = - ByteBuffer.wrap(array) + def wrapArrayBuffer(array: Any): ByteBuffer = + ByteBuffer.wrapArrayBuffer(array.asInstanceOf[ArrayBuffer]) - def wrap(array: ArrayBuffer, byteOffset: Int, length: Int): ByteBuffer = - ByteBuffer.wrap(array, byteOffset, length) + def wrapArrayBuffer(array: Any, byteOffset: Int, length: Int): ByteBuffer = + ByteBuffer.wrapArrayBuffer(array.asInstanceOf[ArrayBuffer], byteOffset, length) - def wrap(array: Int8Array): ByteBuffer = - ByteBuffer.wrap(array) + def wrapInt8Array(array: Any): ByteBuffer = + ByteBuffer.wrapInt8Array(array.asInstanceOf[Int8Array]) - def wrap(array: Uint16Array): CharBuffer = - CharBuffer.wrap(array) + def wrapUint16Array(array: Any): CharBuffer = + CharBuffer.wrapUint16Array(array.asInstanceOf[Uint16Array]) - def wrap(array: Int16Array): ShortBuffer = - ShortBuffer.wrap(array) + def wrapInt16Array(array: Any): ShortBuffer = + ShortBuffer.wrapInt16Array(array.asInstanceOf[Int16Array]) - def wrap(array: Int32Array): IntBuffer = - IntBuffer.wrap(array) + def wrapInt32Array(array: Any): IntBuffer = + IntBuffer.wrapInt32Array(array.asInstanceOf[Int32Array]) - def wrap(array: Float32Array): FloatBuffer = - FloatBuffer.wrap(array) + def wrapFloat32Array(array: Any): FloatBuffer = + FloatBuffer.wrapFloat32Array(array.asInstanceOf[Float32Array]) - def wrap(array: Float64Array): DoubleBuffer = - DoubleBuffer.wrap(array) + def wrapFloat64Array(array: Any): DoubleBuffer = + DoubleBuffer.wrapFloat64Array(array.asInstanceOf[Float64Array]) def Buffer_hasArrayBuffer(buffer: Buffer): Boolean = buffer.hasArrayBuffer() - def Buffer_arrayBuffer(buffer: Buffer): ArrayBuffer = + def Buffer_arrayBuffer(buffer: Buffer): Any = buffer.arrayBuffer() def Buffer_arrayBufferOffset(buffer: Buffer): Int = buffer.arrayBufferOffset() - def Buffer_dataView(buffer: Buffer): DataView = + def Buffer_dataView(buffer: Buffer): Any = buffer.dataView() def Buffer_hasTypedArray(buffer: Buffer): Boolean = buffer.hasTypedArray() - def Buffer_typedArray(buffer: Buffer): TypedArray[_, _] = + def Buffer_typedArray(buffer: Buffer): Any = buffer.typedArray() } diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBuffer.scala b/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBuffer.scala index 8d5319bc20..42e561d39f 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBuffer.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBuffer.scala @@ -24,33 +24,33 @@ import java.nio._ object TypedArrayBuffer { /** Wraps an [[ArrayBuffer]] in a direct [[java.nio.ByteBuffer ByteBuffer]]. */ def wrap(array: ArrayBuffer): ByteBuffer = - TypedArrayBufferBridge.wrap(array) + TypedArrayBufferBridge.wrapArrayBuffer(array) /** Wraps an [[ArrayBuffer]] in a direct [[java.nio.ByteBuffer ByteBuffer]]. */ def wrap(array: ArrayBuffer, byteOffset: Int, length: Int): ByteBuffer = - TypedArrayBufferBridge.wrap(array, byteOffset, length) + TypedArrayBufferBridge.wrapArrayBuffer(array, byteOffset, length) /** Wraps an [[Int8Array]] in a direct [[java.nio.ByteBuffer ByteBuffer]]. */ def wrap(array: Int8Array): ByteBuffer = - TypedArrayBufferBridge.wrap(array) + TypedArrayBufferBridge.wrapInt8Array(array) /** Wraps a [[Uint16Array]] in a direct [[java.nio.CharBuffer CharBuffer]]. */ def wrap(array: Uint16Array): CharBuffer = - TypedArrayBufferBridge.wrap(array) + TypedArrayBufferBridge.wrapUint16Array(array) /** Wraps an [[Int16Array]] in a direct [[java.nio.ShortBuffer ShortBuffer]]. */ def wrap(array: Int16Array): ShortBuffer = - TypedArrayBufferBridge.wrap(array) + TypedArrayBufferBridge.wrapInt16Array(array) /** Wraps an [[Int32Array]] in a direct [[java.nio.IntBuffer IntBuffer]]. */ def wrap(array: Int32Array): IntBuffer = - TypedArrayBufferBridge.wrap(array) + TypedArrayBufferBridge.wrapInt32Array(array) /** Wraps a [[Float32Array]] in a direct [[java.nio.FloatBuffer FloatBuffer]]. */ def wrap(array: Float32Array): FloatBuffer = - TypedArrayBufferBridge.wrap(array) + TypedArrayBufferBridge.wrapFloat32Array(array) /** Wraps a [[Float64Array]] in a direct [[java.nio.DoubleBuffer DoubleBuffer]]. */ def wrap(array: Float64Array): DoubleBuffer = - TypedArrayBufferBridge.wrap(array) + TypedArrayBufferBridge.wrapFloat64Array(array) } diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferBridge.scala b/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferBridge.scala index 0f436fda7e..857abcadc4 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferBridge.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferBridge.scala @@ -21,12 +21,17 @@ * members are public. * * In library/, this file has only the signatures, with stub implementations. - * In javalib/, it has the proper the proper implementations. + * In javalib/, it has the proper implementations. * The build keeps the .class coming from library/ and the .sjsir file from * javalib/. This way, we bridge the library and javalib. But that means the * binary interface of TypedArrayBufferBridge must be strictly equivalent in * the two copies. * + * Because of these copies, we must also explicitly use `Any` instead of all + * JS types in the method signatures. The IR cleaner would replace any JS type + * by `Any` in the javalib, so if we don't write them like that in the library + * as well, there will be mismatches. + * * (Yes, this is a hack.) * !!!!! */ @@ -36,33 +41,33 @@ package scala.scalajs.js.typedarray import java.nio._ private[typedarray] object TypedArrayBufferBridge { - def wrap(array: ArrayBuffer): ByteBuffer = stub() + def wrapArrayBuffer(array: Any): ByteBuffer = stub() - def wrap(array: ArrayBuffer, byteOffset: Int, length: Int): ByteBuffer = stub() + def wrapArrayBuffer(array: Any, byteOffset: Int, length: Int): ByteBuffer = stub() - def wrap(array: Int8Array): ByteBuffer = stub() + def wrapInt8Array(array: Any): ByteBuffer = stub() - def wrap(array: Uint16Array): CharBuffer = stub() + def wrapUint16Array(array: Any): CharBuffer = stub() - def wrap(array: Int16Array): ShortBuffer = stub() + def wrapInt16Array(array: Any): ShortBuffer = stub() - def wrap(array: Int32Array): IntBuffer = stub() + def wrapInt32Array(array: Any): IntBuffer = stub() - def wrap(array: Float32Array): FloatBuffer = stub() + def wrapFloat32Array(array: Any): FloatBuffer = stub() - def wrap(array: Float64Array): DoubleBuffer = stub() + def wrapFloat64Array(array: Any): DoubleBuffer = stub() def Buffer_hasArrayBuffer(buffer: Buffer): Boolean = stub() - def Buffer_arrayBuffer(buffer: Buffer): ArrayBuffer = stub() + def Buffer_arrayBuffer(buffer: Buffer): Any = stub() def Buffer_arrayBufferOffset(buffer: Buffer): Int = stub() - def Buffer_dataView(buffer: Buffer): DataView = stub() + def Buffer_dataView(buffer: Buffer): Any = stub() def Buffer_hasTypedArray(buffer: Buffer): Boolean = stub() - def Buffer_typedArray(buffer: Buffer): TypedArray[_, _] = stub() + def Buffer_typedArray(buffer: Buffer): Any = stub() private def stub(): Nothing = throw new Error("stub") diff --git a/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferOps.scala b/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferOps.scala index 9572727d9e..b0c1911a0c 100644 --- a/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferOps.scala +++ b/library/src/main/scala/scala/scalajs/js/typedarray/TypedArrayBufferOps.scala @@ -41,7 +41,7 @@ final class TypedArrayBufferOps[ // scalastyle:ignore * If this buffer has no backing [[ArrayBuffer]], i.e., !hasArrayBuffer() */ def arrayBuffer(): ArrayBuffer = - TypedArrayBufferBridge.Buffer_arrayBuffer(buffer) + TypedArrayBufferBridge.Buffer_arrayBuffer(buffer).asInstanceOf[ArrayBuffer] /** Byte offset in the associated [[ArrayBuffer]] _(optional operation)_. * @@ -60,7 +60,7 @@ final class TypedArrayBufferOps[ // scalastyle:ignore * If this buffer has no backing [[ArrayBuffer]], i.e., !hasArrayBuffer() */ def dataView(): DataView = - TypedArrayBufferBridge.Buffer_dataView(buffer) + TypedArrayBufferBridge.Buffer_dataView(buffer).asInstanceOf[DataView] /** Tests whether this direct buffer has a valid associated [[TypedArray]]. * diff --git a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala index 8e955e5b8d..571e1f0656 100644 --- a/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala +++ b/linker/shared/src/test/scala/org/scalajs/linker/LibrarySizeTest.scala @@ -70,9 +70,9 @@ class LibrarySizeTest { ) testLinkedSizes( - expectedFastLinkSize = 146336, - expectedFullLinkSizeWithoutClosure = 135798, - expectedFullLinkSizeWithClosure = 22074, + expectedFastLinkSize = 144675, + expectedFullLinkSizeWithoutClosure = 133818, + expectedFullLinkSizeWithClosure = 21620, classDefs, moduleInitializers = MainTestModuleInitializers ) diff --git a/project/Build.scala b/project/Build.scala index d31c011828..2a61950768 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -572,7 +572,7 @@ object Build { ) val cleanIRSettings = Def.settings( - // In order to rewrite anonymous functions, the code must not be specialized + // In order to rewrite anonymous functions and tuples, the code must not be specialized scalacOptions += "-no-specialization", products in Compile := { @@ -1271,6 +1271,8 @@ object Build { Nil }, + cleanIRSettings, + headerSources in Compile ~= { srcs => srcs.filter { src => val path = src.getPath.replace('\\', '/') diff --git a/project/JavalibIRCleaner.scala b/project/JavalibIRCleaner.scala index 8b784e8f0e..8214b6ee71 100644 --- a/project/JavalibIRCleaner.scala +++ b/project/JavalibIRCleaner.scala @@ -168,7 +168,7 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { validateClassName(interface.name) val transformedClassDef = - Hashers.hashClassDef(this.transformClassDef(preprocessedTree)) + Hashers.hashClassDef(eliminateRedundantBridges(this.transformClassDef(preprocessedTree))) postTransformChecks(transformedClassDef) transformedClassDef @@ -210,6 +210,89 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { } } + /** Eliminate bridges that have become redundant because of our additional erasure. */ + private def eliminateRedundantBridges(classDef: ClassDef): ClassDef = { + import MemberNamespace._ + + def argsCorrespond(args: List[Tree], paramDefs: List[ParamDef]): Boolean = { + (args.size == paramDefs.size) && args.zip(paramDefs).forall { + case (VarRef(LocalIdent(argName)), ParamDef(LocalIdent(paramName), _, _, _)) => + argName == paramName + case _ => + false + } + } + + val memberDefs = classDef.memberDefs + + // Instance bridges, which call "themselves" (another version of themselves with the same name) + + def isRedundantBridge(memberDef: MemberDef): Boolean = memberDef match { + case MethodDef(flags, MethodIdent(name), _, paramDefs, _, Some(body)) if flags.namespace == Public => + body match { + case Apply(ApplyFlags.empty, This(), MethodIdent(`name`), args) => + argsCorrespond(args, paramDefs) + case _ => + false + } + case _ => + false + } + + val newMemberDefs1 = memberDefs.filterNot(isRedundantBridge(_)) + + // Make sure that we did not remove *all* overloads for any method name + + def publicMethodNames(memberDefs: List[MemberDef]): Set[MethodName] = { + memberDefs.collect { + case MethodDef(flags, name, _, _, _, _) if flags.namespace == Public => name.name + }.toSet + } + + val lostMethodNames = publicMethodNames(memberDefs) -- publicMethodNames(newMemberDefs1) + if (lostMethodNames.nonEmpty) { + for (lostMethodName <- lostMethodNames) + reportError(s"eliminateRedundantBridges removed all overloads of ${lostMethodName.nameString}")(classDef.pos) + } + + // Static forwarders to redundant bridges -- these are duplicate public static methods + + def isStaticForwarder(memberDef: MethodDef): Boolean = memberDef match { + case MethodDef(flags, MethodIdent(name), _, paramDefs, _, Some(body)) if flags.namespace == PublicStatic => + body match { + case Apply(ApplyFlags.empty, LoadModule(_), MethodIdent(`name`), args) => + argsCorrespond(args, paramDefs) + case _ => + false + } + case _ => + false + } + + val seenStaticForwarderNames = mutable.Set.empty[MethodName] + val newMemberDefs2 = newMemberDefs1.filter { memberDef => + memberDef match { + case m: MethodDef if isStaticForwarder(m) => + seenStaticForwarderNames.add(m.name.name) // keep if it is the first one + case _ => + true // always keep + } + } + + new ClassDef( + classDef.name, + classDef.originalName, + classDef.kind, + classDef.jsClassCaptures, + classDef.superClass, + classDef.interfaces, + classDef.jsSuperClass, + classDef.jsNativeLoadSpec, + newMemberDefs2, + classDef.topLevelExportDefs + )(classDef.optimizerHints)(classDef.pos) + } + private def transformParamDefs(paramDefs: List[ParamDef]): List[ParamDef] = { for (paramDef <- paramDefs) yield { implicit val pos = paramDef.pos @@ -244,10 +327,27 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { if isFunctionNType(n, fun.tpe) => JSFunctionApply(fun, args) + // <= 2.12 : toJSVarArgs(jsArrayOps(jsArray).toSeq) -> jsArray + case IntrinsicCall(ScalaJSRuntimeMod, `toJSVarArgsReadOnlyMethodName`, + List(Apply( + ApplyFlags.empty, + IntrinsicCall(JSAnyMod, `jsArrayOpsToArrayOpsMethodName`, List(jsArray)), + MethodIdent(`toReadOnlySeqMethodName`), + Nil) + )) => + jsArray + + // >= 2.13 : toJSVarArgs(toSeq$extension(jsArray)) -> jsArray + case IntrinsicCall(ScalaJSRuntimeMod, `toJSVarArgsImmutableMethodName`, + List(IntrinsicCall(JSArrayOpsMod, `toImmutableSeqExtensionMethodName`, List(jsArray)))) => + jsArray + case IntrinsicCall(JSAnyMod, `jsAnyFromIntMethodName`, List(arg)) => arg case IntrinsicCall(JSAnyMod, `jsAnyFromStringMethodName`, List(arg)) => arg + case IntrinsicCall(JSAnyMod, `jsArrayOpsToArrayMethodName`, List(arg)) => + arg case IntrinsicCall(JSDynamicImplicitsMod, `number2dynamicMethodName`, List(arg)) => arg case IntrinsicCall(JSNumberOpsMod, `enableJSNumberOpsDoubleMethodName`, List(arg)) => @@ -256,6 +356,8 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { arg case IntrinsicCall(JSStringOpsMod, `enableJSStringOpsMethodName`, List(arg)) => arg + case IntrinsicCall(UnionTypeMod, `unionTypeFromMethodName`, List(arg, _)) => + arg case IntrinsicCall(JSDynamicImplicitsMod, `truthValueMethodName`, List(arg)) => AsInstanceOf( @@ -491,7 +593,15 @@ final class JavalibIRCleaner(baseDirectoryURI: URI) { def isJavaScriptExceptionWithinItself = cls == JavaScriptExceptionClass && enclosingClassName == JavaScriptExceptionClass - if (cls.nameString.startsWith("scala.") && !isJavaScriptExceptionWithinItself) + def isTypedArrayBufferBridgeWithinItself = { + (cls == TypedArrayBufferBridge || cls == TypedArrayBufferBridgeMod) && + (enclosingClassName == TypedArrayBufferBridge || enclosingClassName == TypedArrayBufferBridgeMod) + } + + def isAnException: Boolean = + isJavaScriptExceptionWithinItself || isTypedArrayBufferBridgeWithinItself + + if (cls.nameString.startsWith("scala.") && !isAnException) reportError(s"Illegal reference to Scala class ${cls.nameString}") } @@ -521,10 +631,17 @@ object JavalibIRCleaner { // Within js.JavaScriptException, which is part of the linker private lib, we can refer to itself private val JavaScriptExceptionClass = ClassName("scala.scalajs.js.JavaScriptException") + // Within TypedArrayBufferBridge, which is actually part of the library, we can refer to itself + private val TypedArrayBufferBridge = ClassName("scala.scalajs.js.typedarray.TypedArrayBufferBridge") + private val TypedArrayBufferBridgeMod = ClassName("scala.scalajs.js.typedarray.TypedArrayBufferBridge$") + + private val ImmutableSeq = ClassName("scala.collection.immutable.Seq") private val JavaIOSerializable = ClassName("java.io.Serializable") private val JSAny = ClassName("scala.scalajs.js.Any") private val JSAnyMod = ClassName("scala.scalajs.js.Any$") private val JSArray = ClassName("scala.scalajs.js.Array") + private val JSArrayOps = ClassName("scala.scalajs.js.ArrayOps") + private val JSArrayOpsMod = ClassName("scala.scalajs.js.ArrayOps$") private val JSDynamic = ClassName("scala.scalajs.js.Dynamic") private val JSDynamicImplicitsMod = ClassName("scala.scalajs.js.DynamicImplicits$") private val JSNumberOps = ClassName("scala.scalajs.js.JSNumberOps") @@ -535,6 +652,9 @@ object JavalibIRCleaner { private val ScalaSerializable = ClassName("scala.Serializable") private val ScalaJSRuntimeMod = ClassName("scala.scalajs.runtime.package$") private val StringContextClass = ClassName("scala.StringContext") + private val UnionType = ClassName("scala.scalajs.js.$bar") + private val UnionTypeMod = ClassName("scala.scalajs.js.$bar$") + private val UnionTypeEvidence = ClassName("scala.scalajs.js.$bar$Evidence") private val FunctionNClasses: IndexedSeq[ClassName] = (0 to MaxFunctionArity).map(n => ClassName(s"scala.Function$n")) @@ -552,16 +672,30 @@ object JavalibIRCleaner { MethodName("fromInt", List(IntRef), ClassRef(JSAny)) private val jsAnyFromStringMethodName = MethodName("fromString", List(ClassRef(BoxedStringClass)), ClassRef(JSAny)) + private val jsArrayOpsToArrayMethodName = + MethodName("jsArrayOps", List(ClassRef(JSArray)), ClassRef(JSArray)) + private val jsArrayOpsToArrayOpsMethodName = + MethodName("jsArrayOps", List(ClassRef(JSArray)), ClassRef(JSArrayOps)) private val number2dynamicMethodName = MethodName("number2dynamic", List(DoubleRef), ClassRef(JSDynamic)) private val sMethodName = MethodName("s", List(ClassRef(ReadOnlySeq)), ClassRef(BoxedStringClass)) private val stringContextCtorMethodName = MethodName.constructor(List(ClassRef(ReadOnlySeq))) + private val toImmutableSeqExtensionMethodName = + MethodName("toSeq$extension", List(ClassRef(JSArray)), ClassRef(ImmutableSeq)) + private val toJSVarArgsImmutableMethodName = + MethodName("toJSVarArgs", List(ClassRef(ImmutableSeq)), ClassRef(JSArray)) + private val toJSVarArgsReadOnlyMethodName = + MethodName("toJSVarArgs", List(ClassRef(ReadOnlySeq)), ClassRef(JSArray)) private val toScalaVarArgsReadOnlyMethodName = MethodName("toScalaVarArgs", List(ClassRef(JSArray)), ClassRef(ReadOnlySeq)) + private val toReadOnlySeqMethodName = + MethodName("toSeq", Nil, ClassRef(ReadOnlySeq)) private val truthValueMethodName = MethodName("truthValue", List(ClassRef(JSDynamic)), BooleanRef) + private val unionTypeFromMethodName = + MethodName("from", List(ClassRef(ObjectClass), ClassRef(UnionTypeEvidence)), ClassRef(UnionType)) private val writeReplaceMethodName = MethodName("writeReplace", Nil, ClassRef(ObjectClass)) @@ -603,6 +737,30 @@ object JavalibIRCleaner { funClass -> ObjectClass } - functionTypePairs.toMap + val refBaseNames = + List("Boolean", "Char", "Byte", "Short", "Int", "Long", "Float", "Double", "Object") + val refPairs = for { + refBaseName <- refBaseNames + } yield { + val simpleName = refBaseName + "Ref" + ClassName("scala.runtime." + simpleName) -> ClassName("java.util.internal." + simpleName) + } + + val tuplePairs = for { + n <- (2 to 22).toList + } yield { + ClassName("scala.Tuple" + n) -> ClassName("java.util.internal.Tuple" + n) + } + + val otherPairs = List( + /* AssertionError conveniently features a constructor taking an Object. + * Since any MatchError in the javalib would be a bug, it is fine to + * rewrite them to AssertionErrors. + */ + ClassName("scala.MatchError") -> ClassName("java.lang.AssertionError"), + ) + + val allPairs = functionTypePairs ++ refPairs ++ tuplePairs ++ otherPairs + allPairs.toMap } }