From 38121c09008d2b06c55dbeccac9f0210c8d602ad Mon Sep 17 00:00:00 2001 From: Bill Venners Date: Wed, 9 Sep 2026 15:01:46 -0700 Subject: [PATCH] Add Scaladoc for undocumented collection.convert APIs and the Scala.js library --- library-js/src/scala/Array.scala | 73 ++++ library-js/src/scala/Console.scala | 15 + library-js/src/scala/Enumeration.scala | 139 +++++++ library-js/src/scala/MatchError.scala | 7 + library-js/src/scala/Symbol.scala | 24 ++ .../collection/immutable/NumericRange.scala | 225 +++++++++++ .../scala/collection/immutable/Range.scala | 361 ++++++++++++++++++ .../collection/mutable/ArrayBuilder.scala | 312 +++++++++++++++ .../src/scala/collection/mutable/Buffer.scala | 99 +++++ .../scala/concurrent/ExecutionContext.scala | 14 + library-js/src/scala/math/ScalaNumber.scala | 4 + library-js/src/scala/package.scala | 39 ++ library-js/src/scala/reflect/ClassTag.scala | 47 +++ library-js/src/scala/reflect/Manifest.scala | 95 +++++ .../src/scala/runtime/BoxesRunTime.scala | 312 +++++++++++++++ .../src/scala/runtime/ScalaRunTime.scala | 126 ++++++ .../src/scala/runtime/VarArgsBuilder.scala | 157 ++++++++ .../scala/scalajs/js/internal/UnitOps.scala | 8 + .../scalajs/runtime/AnonFunctionXXL.scala | 8 + .../src/scala/util/DynamicVariable.scala | 1 + .../src/scala/util/control/NoStackTrace.scala | 11 + .../collection/convert/AsJavaExtensions.scala | 100 +++++ .../convert/AsScalaExtensions.scala | 107 ++++++ .../collection/convert/StreamExtensions.scala | 208 ++++++++++ 24 files changed, 2492 insertions(+) diff --git a/library-js/src/scala/Array.scala b/library-js/src/scala/Array.scala index 69bdbdd1b969..a3ab4ca5e44b 100644 --- a/library-js/src/scala/Array.scala +++ b/library-js/src/scala/Array.scala @@ -39,14 +39,23 @@ import scala.runtime.ScalaRunTime.{array_apply, array_update} * @since 1.0 */ object Array { + /** An empty `Array[Boolean]`, shared to avoid allocation; a zero-length array cannot be mutated. */ def emptyBooleanArray = EmptyArrays.emptyBooleanArray + /** An empty `Array[Byte]`, shared to avoid allocation; a zero-length array cannot be mutated. */ def emptyByteArray = EmptyArrays.emptyByteArray + /** An empty `Array[Char]`, shared to avoid allocation; a zero-length array cannot be mutated. */ def emptyCharArray = EmptyArrays.emptyCharArray + /** An empty `Array[Double]`, shared to avoid allocation; a zero-length array cannot be mutated. */ def emptyDoubleArray = EmptyArrays.emptyDoubleArray + /** An empty `Array[Float]`, shared to avoid allocation; a zero-length array cannot be mutated. */ def emptyFloatArray = EmptyArrays.emptyFloatArray + /** An empty `Array[Int]`, shared to avoid allocation; a zero-length array cannot be mutated. */ def emptyIntArray = EmptyArrays.emptyIntArray + /** An empty `Array[Long]`, shared to avoid allocation; a zero-length array cannot be mutated. */ def emptyLongArray = EmptyArrays.emptyLongArray + /** An empty `Array[Short]`, shared to avoid allocation; a zero-length array cannot be mutated. */ def emptyShortArray = EmptyArrays.emptyShortArray + /** An empty `Array[Object]`, shared to avoid allocation; a zero-length array cannot be mutated. */ def emptyObjectArray = EmptyArrays.emptyObjectArray private object EmptyArrays { @@ -82,6 +91,20 @@ object Array { */ def newBuilder[T](implicit t: ClassTag[T]): ArrayBuilder[T] = ArrayBuilder.make[T](using t) + /** Builds an array from the iterable collection. + * + * ``` + * scala> val a = Array.from(Seq(1, 5)) + * val a: Array[Int] = Array(1, 5) + * + * scala> val b = Array.from(Range(1, 5)) + * val b: Array[Int] = Array(1, 2, 3, 4) + * ``` + * + * @tparam A the element type of the array + * @param it the iterable collection + * @return an array consisting of elements of the iterable collection + */ def from[A: ClassTag](it: IterableOnce[A]^): Array[A] = { val n = it.knownSize if (n > -1) { @@ -678,6 +701,21 @@ object Array { } } + /** Compares two arrays per element. + * + * A more efficient version of `xs.sameElements(ys)`. + * + * Note that arrays are invariant in Scala, but it may + * be sound to cast an array of arbitrary reference type + * to `Array[AnyRef]`. Arrays are covariant in their + * element type at run time. + * + * `Array.equals(xs.asInstanceOf[Array[AnyRef]], ys.asInstanceOf[Array[AnyRef]])` + * + * @param xs an array of AnyRef + * @param ys an array of AnyRef + * @return true if corresponding elements are equal + */ def equals(xs: Array[AnyRef], ys: Array[AnyRef]): Boolean = { if (xs eq ys) return true @@ -702,12 +740,47 @@ object Array { */ def unapplySeq[T](x: Array[T]): UnapplySeqWrapper[T] = new UnapplySeqWrapper(x) + /** A wrapper that lets an array be destructured by a sequence pattern such as + * `case Array(x, y, z) =>`. + * + * The members below implement the name-based extractor protocol directly against + * the wrapped array, so a fixed-arity pattern matches without building an + * intermediate sequence. + * + * @tparam T the element type of the wrapped array + * @param a the array to be destructured + */ final class UnapplySeqWrapper[T](private val a: Array[T]) extends AnyVal { + /** Returns `false`, since this extractor never yields an empty result. + * + * The extraction itself cannot fail, so [[get]] is always available. Whether a + * particular sequence pattern matches is decided separately, by [[lengthCompare]] + * and the element accessors. + */ def isEmpty: Boolean = false + /** Returns this wrapper, whose sequence-like operations supply the elements of the pattern. */ def get: UnapplySeqWrapper[T] = this + /** Compares the length of the wrapped array to a test value. + * + * @param len the test value that gets compared with the length + * @return a value less than, equal to, or greater than `0` as the length of the array is less than, equal to, or greater than `len` + */ def lengthCompare(len: Int): Int = a.lengthCompare(len) + /** Returns the element of the wrapped array at the given index. + * + * @param i the index, which must be in the range from `0` until the length of the array + * @return the element at index `i` + * @throws ArrayIndexOutOfBoundsException if `i` is negative or not less than the length of the array + */ def apply(i: Int): T = a(i) + /** Returns all elements of the wrapped array except the first `n`, as needed to bind the + * variable-length part of a pattern such as `case Array(x, rest*) =>`. + * + * @param n the number of leading elements to skip + * @return a sequence backed by a fresh copy of the remaining elements, even when `n` is `0` + */ def drop(n: Int): scala.Seq[T] = ArraySeq.unsafeWrapArray(a.drop(n)) // clones the array, also if n == 0 + /** Returns the elements of the wrapped array as a sequence backed by a copy of the array. */ def toSeq: scala.Seq[T] = a.toSeq // clones the array } } diff --git a/library-js/src/scala/Console.scala b/library-js/src/scala/Console.scala index fa82c7b753ce..8e754e89eef2 100644 --- a/library-js/src/scala/Console.scala +++ b/library-js/src/scala/Console.scala @@ -133,8 +133,23 @@ object Console extends AnsiColor { private[this] val inVar = new DynamicVariable[BufferedReader](null.asInstanceOf[BufferedReader]) //new BufferedReader(new InputStreamReader(java.lang.System.in))) + /** Redefines the default output stream, replacing the current binding directly + * rather than scoping the change to a thunk as `withOut` does. + * + * @param out the new default output stream + */ protected def setOutDirect(out: PrintStream): Unit = outVar.value = out + /** Redefines the default error stream, replacing the current binding directly + * rather than scoping the change to a thunk as `withErr` does. + * + * @param err the new default error stream + */ protected def setErrDirect(err: PrintStream): Unit = errVar.value = err + /** Redefines the default input, replacing the current binding directly + * rather than scoping the change to a thunk as `withIn` does. + * + * @param in the new default input reader + */ protected def setInDirect(in: BufferedReader): Unit = inVar.value = in /** The default output, can be overridden by `withOut`. diff --git a/library-js/src/scala/Enumeration.scala b/library-js/src/scala/Enumeration.scala index ca367be3ba33..6c50d2014a15 100644 --- a/library-js/src/scala/Enumeration.scala +++ b/library-js/src/scala/Enumeration.scala @@ -91,10 +91,16 @@ import scala.util.matching.Regex abstract class Enumeration (initial: Int) extends Serializable { thisenum => + /** Creates an enumeration whose value ids are counted from `0`. */ def this() = this(0) /* Note that `readResolve` cannot be private, since otherwise the JVM does not invoke it when deserializing subclasses. */ + /** Serialization hook that on the JVM resolves a deserialized enumeration to + * its singleton module instance; not implemented on Scala.js. + * + * @throws NotImplementedError always + */ protected def readResolve(): AnyRef = ??? /** The name of this enumeration. */ @@ -217,14 +223,26 @@ abstract class Enumeration (initial: Int) extends Serializable { /** A marker so we can tell whose values belong to whom come reflective-naming time. */ private[Enumeration] val outerEnum = thisenum + /** Compares this value with another value of this enumeration by id. + * + * @param that the value to compare with + * @return `-1`, `0`, or `1` as this value's id is less than, equal to, or + * greater than the id of `that` + */ override def compare(that: Value): Int = if (this.id < that.id) -1 else if (this.id == that.id) 0 else 1 + /** Returns `true` if `other` is a value of the same enumeration instance + * with the same id as this value. + * + * @param other the object to compare with + */ override def equals(other: Any) = other match { case that: Enumeration#Value => (outerEnum eq that.outerEnum) && (id == that.id) case _ => false } + /** Returns the hash code of this value, computed from its id. */ override def hashCode: Int = id.## /** Creates a ValueSet which contains this value and another one. @@ -240,8 +258,22 @@ abstract class Enumeration (initial: Int) extends Serializable { */ @SerialVersionUID(0 - 3501153230598116017L) protected class Val(i: Int, name: String | Null) extends Value with Serializable { + /** Creates a fresh value identified by `i`, named by the next name in + * `nextName` if defined, or unnamed otherwise. + * + * @param i the integer that identifies the value at run-time; must be + * unique amongst all values of the enumeration + */ def this(i: Int) = this(i, nextNameOrNull) + /** Creates a fresh value identified by `nextId`, called `name`. + * + * @param name a human-readable name for the value, or `null` for an + * unnamed value + */ def this(name: String | Null) = this(nextId, name) + /** Creates a fresh value identified by `nextId`, named by the next name in + * `nextName` if defined, or unnamed otherwise. + */ def this() = this(nextId) assert(!vmap.isDefinedAt(i), "Duplicate id: " + i) @@ -250,12 +282,23 @@ abstract class Enumeration (initial: Int) extends Serializable { nextId = i + 1 if (nextId > topId) topId = nextId if (i < bottomId) bottomId = i + /** The integer that identifies this value within its enumeration. */ def id = i + /** Returns the name of this value if one was provided or drawn from + * `nextName`, otherwise a placeholder of the form + * `` (on Scala.js, names of + * unnamed values cannot be recovered by reflection). + */ override def toString() = if (name != null) name // Scala.js specific else s"" + /** During deserialization, replaces this value with the equivalent value + * registered in the owning enumeration, as resolved by + * `Enumeration.readResolve` (which is unimplemented on Scala.js and throws + * `NotImplementedError` unless overridden). + */ protected def readResolve(): AnyRef = { val enumeration = thisenum.readResolve().asInstanceOf[Enumeration] if (enumeration.vmap == null) this @@ -265,6 +308,13 @@ abstract class Enumeration (initial: Int) extends Serializable { /** An ordering by id for values of this set. */ implicit object ValueOrdering extends Ordering[Value] { + /** Compares two values of this enumeration by id. + * + * @param x the first value to compare + * @param y the second value to compare + * @return `-1`, `0`, or `1` as the id of `x` is less than, equal to, or + * greater than the id of `y` + */ def compare(x: Value, y: Value): Int = x compare y } @@ -282,37 +332,121 @@ abstract class Enumeration (initial: Int) extends Serializable { with StrictOptimizedIterableOps[Value, immutable.Set, ValueSet] with Serializable { + /** Returns [[ValueOrdering]], the ordering of values by their ids. */ implicit def ordering: Ordering[Value] = ValueOrdering + /** Returns the values of this set whose ids lie within given bounds. + * + * @param from the value whose id is the inclusive lower bound, or `None` + * for no lower bound + * @param until the value whose id is the exclusive upper bound, or `None` + * for no upper bound + * @return a `ValueSet` containing the values of this set within the bounds + */ def rangeImpl(from: Option[Value], until: Option[Value]): ValueSet = new ValueSet(nnIds.rangeImpl(from.map(_.id - bottomId), until.map(_.id - bottomId))) + /** Returns the empty set of values of this enumeration. */ override def empty = ValueSet.empty + /** Returns the number of values in this set; always known, since the + * underlying bit set is finite. + */ override def knownSize: Int = nnIds.size + /** Returns `true` if this set contains no values. */ override def isEmpty: Boolean = nnIds.isEmpty + /** Tests whether this set contains a given value. + * + * @param v the value to test for membership + */ def contains(v: Value) = nnIds contains (v.id - bottomId) + /** Returns a new `ValueSet` containing the values of this set and the given value. + * + * @param value the value to add + */ def incl (value: Value) = new ValueSet(nnIds + (value.id - bottomId)) + /** Returns a new `ValueSet` containing the values of this set except the given value. + * + * @param value the value to remove + */ def excl (value: Value) = new ValueSet(nnIds - (value.id - bottomId)) + /** Returns an iterator over the values in this set, in increasing order of their ids. */ def iterator = nnIds.iterator map (id => thisenum.apply(bottomId + id)) + /** Returns an iterator over the values in this set whose ids are greater + * than or equal to that of `start`, in increasing order of their ids. + * + * @param start the inclusive lower bound for the values to return + */ override def iteratorFrom(start: Value) = nnIds iteratorFrom start.id map (id => thisenum.apply(bottomId + id)) + /** Returns the name used to prefix the string representation of this set: + * the enumeration's name followed by `.ValueSet`. + */ override def className = s"$thisenum.ValueSet" /** Creates a bit mask for the zero-adjusted ids in this set as a * new array of longs */ def toBitMask: Array[Long] = nnIds.toBitMask + /** Builds a `ValueSet` containing the values of the given collection. + * + * @param coll the source of values for the new set + */ override protected def fromSpecific(coll: IterableOnce[Value]) = ValueSet.fromSpecific(coll) + /** Returns a new builder that accumulates values into a `ValueSet`. */ override protected def newSpecificBuilder = ValueSet.newBuilder + /** Builds a new `ValueSet` by applying a function to each value of this set. + * + * @param f the function to apply to each value + * @return a `ValueSet` of the results, ordered by their ids + */ def map(f: Value => Value): ValueSet = fromSpecific(new View.Map(this, f)) + /** Builds a new `ValueSet` by applying a function to each value of this set + * and collecting all values in the results. + * + * @param f the function to apply to each value + * @return a `ValueSet` of all values produced by `f`, ordered by their ids + */ def flatMap(f: Value => IterableOnce[Value]): ValueSet = fromSpecific(new View.FlatMap(this, f)) // necessary for disambiguation: + /** Builds a new sorted set by applying a function to each value of this set. + * + * @tparam B the element type of the returned set + * @param f the function to apply to each value + * @param ev the ordering for the elements of the returned set + * @return a sorted set of the results of applying `f` to each value + */ override def map[B](f: Value => B)(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] = super[SortedSet].map[B](f) + /** Builds a new sorted set by applying a function to each value of this set + * and collecting all elements in the results. + * + * @tparam B the element type of the returned set + * @param f the function to apply to each value + * @param ev the ordering for the elements of the returned set + * @return a sorted set of all elements produced by `f` + */ override def flatMap[B](f: Value => IterableOnce[B])(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] = super[SortedSet].flatMap[B](f) + /** Builds a new sorted set of pairs formed from the values of this set and + * the corresponding elements of another collection, dropping whatever + * remains of the longer of the two. + * + * @tparam B the type of the second element of each pair + * @param that the collection providing the second element of each pair + * @param ev the ordering for the pairs in the returned set + * @return a sorted set of corresponding pairs + */ override def zip[B](that: IterableOnce[B])(implicit @implicitNotFound(ValueSet.zipOrdMsg) ev: Ordering[(Value, B)]): immutable.SortedSet[(Value, B)] = super[SortedSet].zip[B](that) + /** Builds a new sorted set by applying a partial function to each value of + * this set on which it is defined. + * + * @tparam B the element type of the returned set + * @param pf the partial function to apply to each value + * @param ev the ordering for the elements of the returned set + * @return a sorted set of the results of applying `pf` to each value on + * which it is defined + */ override def collect[B](pf: PartialFunction[Value, B])(implicit @implicitNotFound(ValueSet.ordMsg) ev: Ordering[B]): immutable.SortedSet[B] = super[SortedSet].collect[B](pf) } @@ -338,6 +472,11 @@ abstract class Enumeration (initial: Int) extends Serializable { def clear() = b.clear() def result() = new ValueSet(b.toImmutable) } + /** Builds a `ValueSet` containing the values of the given collection. + * + * @param it the source of values for the new set + * @return a `ValueSet` containing the values of `it` + */ def fromSpecific(it: IterableOnce[Value]): ValueSet = newBuilder.addAll(it).result() } diff --git a/library-js/src/scala/MatchError.scala b/library-js/src/scala/MatchError.scala index 0af77b48ca76..c274df303e8f 100644 --- a/library-js/src/scala/MatchError.scala +++ b/library-js/src/scala/MatchError.scala @@ -42,5 +42,12 @@ final class MatchError(@transient obj: Any) extends RuntimeException { this } + /** Returns a message describing the object that failed to match. + * + * The message contains the object's string representation and its class name, + * falling back to the class name alone if its `toString` throws, and naming + * the class "a JS class" if the object has no Java class. The message is + * computed at most once. + */ override def getMessage() = objString } diff --git a/library-js/src/scala/Symbol.scala b/library-js/src/scala/Symbol.scala index a5bbd615ef36..c897c09de3a2 100644 --- a/library-js/src/scala/Symbol.scala +++ b/library-js/src/scala/Symbol.scala @@ -35,13 +35,37 @@ final class Symbol private (val name: String) extends Serializable { @throws(classOf[java.io.ObjectStreamException]) private def readResolve(): Any = Symbol.apply(name) + /** Returns the hash code of this symbol, computed from its name. */ override def hashCode = name.hashCode() + /** Returns `true` if `other` is the same object as this symbol. + * + * Since symbols are interned, two symbols with the same name are the same + * object, so reference equality suffices. + * + * @param other the object to compare with + */ override def equals(other: Any) = this eq other.asInstanceOf[AnyRef] } object Symbol extends UniquenessCache[String, Symbol] { + /** Returns the unique symbol with the given name, creating and caching it on + * first use. + * + * @param name the name of the symbol + * @return the interned `Symbol` for `name` + */ override def apply(name: String): Symbol = super.apply(name) + /** Creates a fresh symbol with the given name; called by the cache on a miss. + * + * @param name the name of the symbol + * @return a new `Symbol` for `name` + */ protected def valueFromKey(name: String): Symbol = new Symbol(name) + /** Returns the name of the given symbol, the key under which it is cached. + * + * @param sym the symbol whose name to return + * @return the name of `sym`, always wrapped in `Some` + */ protected def keyFromValue(sym: Symbol): Option[String] = Some(sym.name) } diff --git a/library-js/src/scala/collection/immutable/NumericRange.scala b/library-js/src/scala/collection/immutable/NumericRange.scala index 8286a062d084..8315470dcb7f 100644 --- a/library-js/src/scala/collection/immutable/NumericRange.scala +++ b/library-js/src/scala/collection/immutable/NumericRange.scala @@ -40,9 +40,15 @@ import scala.collection.{AbstractIterator, AnyStepper, IterableFactoryDefaults, */ @SerialVersionUID(3L) sealed class NumericRange[T]( + /** The start value of the range; its first element when the range is non-empty. */ val start: T, + /** The end value of the range, a bound that is not necessarily an element itself. */ val end: T, + /** The increment between successive elements; may be negative, must not be zero. */ val step: T, + /** Whether `end` may be an element: an inclusive range contains `end` when it + * is a whole number of steps from `start`; an exclusive range never contains it. + */ val isInclusive: Boolean )(implicit num: Integral[T] @@ -54,8 +60,19 @@ sealed class NumericRange[T]( with IterableFactoryDefaults[T, IndexedSeq] with Serializable { self => + /** Returns a new iterator over all elements of this range in order. */ override def iterator: Iterator[T] = new NumericRange.NumericRangeIterator(this, num) + /** Returns a stepper for the elements of this range. + * + * Uses a stepper specialized for `Int` or `Long` elements when `shape` has + * one of those shapes, and a generic stepper otherwise. + * + * @tparam S the type of the returned stepper, determined by `shape` + * @param shape the shape of the stepper for elements of type `T` + * @return a stepper over the elements of this range, supporting efficient + * splitting for parallel processing + */ override def stepper[S <: Stepper[_]](implicit shape: StepperShape[T, S]): S with EfficientSplit = { import scala.collection.convert._ import impl._ @@ -74,20 +91,49 @@ sealed class NumericRange[T]( import num._ // See comment in Range for why this must be lazy. + /** The number of elements in this range, computed on first access and cached. + * + * @throws IllegalArgumentException if `step` is zero, if the range has more than + * `Int.MaxValue` elements, or, for a `BigDecimal` range, if the endpoints + * cannot represent the step accurately enough to count the elements + */ override lazy val length: Int = NumericRange.count(start, end, step, isInclusive) + /** Whether this range has no elements. + * + * Determined from `start`, `end`, the sign of `step`, and `isInclusive` + * alone, so this is safe even on ranges whose `length` would overflow an + * `Int`. + */ override lazy val isEmpty: Boolean = ( (num.gt(start, end) && num.gt(step, num.zero)) || (num.lt(start, end) && num.lt(step, num.zero)) || (num.equiv(start, end) && !isInclusive) ) + /** Returns the last element of this range: the element nearest `end` that + * the range contains, which is not necessarily `end` itself. + * + * @throws NoSuchElementException if this range is empty + */ override def last: T = if (isEmpty) Nil.head else locationAfterN(length - 1) + /** Returns a new range with all elements of this range except the last. + * + * @throws UnsupportedOperationException if this range is empty + */ override def init: NumericRange[T] = if (isEmpty) Nil.init else new NumericRange(start, end - step, step, isInclusive) + /** Returns the first element of this range, which is `start`. + * + * @throws NoSuchElementException if this range is empty + */ override def head: T = if (isEmpty) Nil.head else start + /** Returns a new range with all elements of this range except the first. + * + * @throws UnsupportedOperationException if this range is empty + */ override def tail: NumericRange[T] = if (isEmpty) Nil.tail else if(isInclusive) new NumericRange.Inclusive(start + step, end, step) @@ -112,12 +158,22 @@ sealed class NumericRange[T]( def copy(start: T, end: T, step: T): NumericRange[T] = new NumericRange(start, end, step, isInclusive) + /** Returns the element at the given index, computed as `start + idx * step`. + * + * @param idx the zero-based index of the element + * @return the element at index `idx` + * @throws IndexOutOfBoundsException if `idx` is negative or not less than `length` + */ @throws[IndexOutOfBoundsException] def apply(idx: Int): T = { if (idx < 0 || idx >= length) throw new IndexOutOfBoundsException(s"$idx is out of bounds (min 0, max ${length - 1})") else locationAfterN(idx) } + /** Applies `f` to every element of this range in order. + * + * @param f the function applied to each element; its result is discarded + */ override def foreach[@specialized(Specializable.Unit) U](f: T => U): Unit = { var count = 0 var current = start @@ -217,20 +273,47 @@ sealed class NumericRange[T]( // based on the given value. private def newEmptyRange(value: T) = NumericRange(value, value, step) + /** Returns a range with the first `n` elements of this range. + * + * @param n the number of elements to take + * @return a range of the first `n` elements of this range, this whole + * range if `n` is greater than or equal to `length`, or an empty + * range if `n <= 0` + */ override def take(n: Int): NumericRange[T] = { if (n <= 0 || isEmpty) newEmptyRange(start) else if (crossesTheEndAfterN(n)) this else new NumericRange.Inclusive(start, locationAfterN(n - 1), step) } + /** Returns a range with all elements of this range except the first `n`. + * + * @param n the number of elements to drop + * @return a range of the elements of this range after the first `n`, this + * whole range if `n <= 0`, or an empty range if `n` is greater + * than or equal to `length` + */ override def drop(n: Int): NumericRange[T] = { if (n <= 0 || isEmpty) this else if (crossesTheEndAfterN(n)) newEmptyRange(end) else copy(locationAfterN(n), end, step) } + /** Splits this range at a given index. + * + * @param n the index at which to split + * @return a pair of ranges `(take(n), drop(n))` + */ override def splitAt(n: Int): (NumericRange[T], NumericRange[T]) = (take(n), drop(n)) + /** Returns a new range with the same elements as this range in reverse + * order: an inclusive range from `last` to `start` with step `-step`. + * Returns this range itself if it is empty. + * + * @throws ArithmeticException if negating the step cannot change its sign, either + * because the element type is unsigned or because the step is the minimum + * value of a signed fixed-width type + */ override def reverse: NumericRange[T] = if (isEmpty) this else { @@ -242,6 +325,22 @@ sealed class NumericRange[T]( import NumericRange.defaultOrdering + /** Returns the smallest element of this range under the ordering `ord`. + * + * When `ord` is this range's own `Integral` instance, or the default + * ordering of one of the standard fixed-width integral types (`Byte`, + * `Short`, `Char`, `Int`, or `Long`), the result is taken directly from an + * endpoint: the first element for a positive step, the last element + * otherwise. Any other ordering falls back to a linear scan. + * + * @tparam T1 the type on which `ord` orders values, a supertype of `T` + * @param ord the ordering used to compare elements + * @return the smallest element of this range according to `ord` + * @throws NoSuchElementException if this range is empty and the endpoint + * shortcut applies + * @throws UnsupportedOperationException if this range is empty and the + * linear scan applies + */ override def min[T1 >: T](implicit ord: Ordering[T1]): T = // We can take the fast path: // - If the Integral of this NumericRange is also the requested Ordering @@ -252,6 +351,22 @@ sealed class NumericRange[T]( else last } else super.min(ord) + /** Returns the largest element of this range under the ordering `ord`. + * + * When `ord` is this range's own `Integral` instance, or the default + * ordering of one of the standard fixed-width integral types (`Byte`, + * `Short`, `Char`, `Int`, or `Long`), the result is taken directly from an + * endpoint: the last element for a positive step, the first element + * otherwise. Any other ordering falls back to a linear scan. + * + * @tparam T1 the type on which `ord` orders values, a supertype of `T` + * @param ord the ordering used to compare elements + * @return the largest element of this range according to `ord` + * @throws NoSuchElementException if this range is empty and the endpoint + * shortcut applies + * @throws UnsupportedOperationException if this range is empty and the + * linear scan applies + */ override def max[T1 >: T](implicit ord: Ordering[T1]): T = // See comment for fast path in min(). if ((ord eq num) || defaultOrdering.get(num).exists(ord eq _)) { @@ -260,13 +375,42 @@ sealed class NumericRange[T]( } else super.max(ord) // a well-typed contains method. + /** Returns `true` if `x` is an element of this range, `false` otherwise. + * + * Computed arithmetically, without examining elements: `x` must lie + * between `start` and `last` and be a whole number of steps from `start`. + * + * @param x the value to test + */ def containsTyped(x: T): Boolean = isWithinBoundaries(x) && (((x - start) % step) == zero) + /** Returns `true` if `x` is an element of this range, `false` otherwise. + * + * Returns `false` if `x` is not a value of this range's element type; + * otherwise the answer is computed arithmetically as in `containsTyped`, + * without examining elements. + * + * @tparam A1 the type of the value to test, a supertype of `T` + * @param x the value to test + */ override def contains[A1 >: T](x: A1): Boolean = try containsTyped(x.asInstanceOf[T]) catch { case _: ClassCastException => false } + /** Returns the sum of all elements of this range. + * + * For the standard fixed-width integral types (`Byte`, `Short`, `Char`, + * `Int`, and `Long`) the sum is computed in constant time using the + * arithmetic series formula `n * (head + last) / 2`, with care taken not + * to overflow intermediate results. For any other `Numeric` instance the + * elements are added one by one. + * + * @tparam B the type in which the sum is computed, a supertype of `T` + * @param num the `Numeric` instance used to add elements; its identity + * selects the constant-time specializations + * @return the sum of all elements, or `num.zero` if this range is empty + */ override def sum[B >: T](implicit num: Numeric[B]): B = { if (isEmpty) num.zero else if (size == 1) head @@ -314,9 +458,29 @@ sealed class NumericRange[T]( } } + /** The hash code of this range, computed on first access and cached. + * Consistent with `equals`: equal to the hash code of any sequence with + * the same elements. + */ override lazy val hashCode: Int = super.hashCode() + /** Returns `Int.MaxValue`: indexed access via `apply` is cheap at every + * length, so element scans (as in `sameElements`) never need to switch to + * an iterator. + */ override protected final def applyPreferredMaxLength: Int = Int.MaxValue + /** Compares this range to `other` for equality. + * + * Two numeric ranges are equal if they have the same length and, when + * non-empty, the same `start` and the same last element; the elements in + * between are then necessarily the same. Comparison with any other kind + * of sequence falls back to the element-by-element comparison of the + * superclass. + * + * @param other the value to compare this range with + * @return `true` if `other` is a sequence with the same elements in the + * same order as this range + */ override def equals(other: Any): Boolean = other match { case x: NumericRange[_] => (x canEqual this) && (length == x.length) && ( @@ -327,6 +491,11 @@ sealed class NumericRange[T]( super.equals(other) } + /** Returns a string representation of this range, such as + * `NumericRange 1 until 10 by 2`: `to` for an inclusive range, `until` + * for an exclusive one, prefixed with `empty ` if the range is empty. + * The `by` clause is omitted when `step` is 1. + */ override def toString: String = { val empty = if (isEmpty) "empty " else "" val preposition = if (isInclusive) "to" else "until" @@ -464,26 +633,82 @@ object NumericRange { } } + /** A numeric range that includes its `end` value, when `end` is a whole + * number of steps from `start`. + * + * @tparam T the element type of the range + * @param start the start value of the range + * @param end the end value, an element of the range when it is a whole + * number of steps from `start` + * @param step the increment between successive elements; must not be zero + * @param num the `Integral` instance providing arithmetic on `T` + */ @SerialVersionUID(3L) class Inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) extends NumericRange(start, end, step, true) { + /** Creates an inclusive range with the given start, end, and step. + * + * @param start the start value of the new range + * @param end the end value of the new range, included when it is a whole + * number of steps from `start` + * @param step the step value of the new range + * @return a new inclusive range with the given `start`, `end`, and `step` + */ override def copy(start: T, end: T, step: T): Inclusive[T] = NumericRange.inclusive(start, end, step) + /** Returns an exclusive range with the same `start`, `end`, and `step` as this range. */ def exclusive: Exclusive[T] = NumericRange(start, end, step) } + /** A numeric range that excludes its `end` value. + * + * @tparam T the element type of the range + * @param start the start value of the range + * @param end the end value, never an element of the range itself + * @param step the increment between successive elements; must not be zero + * @param num the `Integral` instance providing arithmetic on `T` + */ @SerialVersionUID(3L) class Exclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) extends NumericRange(start, end, step, false) { + /** Creates an exclusive range with the given start, end, and step. + * + * @param start the start value of the new range + * @param end the end value of the new range, excluded from it + * @param step the step value of the new range + * @return a new exclusive range with the given `start`, `end`, and `step` + */ override def copy(start: T, end: T, step: T): Exclusive[T] = NumericRange(start, end, step) + /** Returns an inclusive range with the same `start`, `end`, and `step` as this range. */ def inclusive: Inclusive[T] = NumericRange.inclusive(start, end, step) } + /** Creates an exclusive numeric range, from `start` until `end` (excluded) + * in increments of `step`. + * + * @tparam T the element type of the range + * @param start the start value of the range + * @param end the end value, excluded from the range + * @param step the increment between successive elements; must not be zero + * @param num the `Integral` instance providing arithmetic on `T` + * @return a new exclusive range with the given `start`, `end`, and `step` + */ def apply[T](start: T, end: T, step: T)(implicit num: Integral[T]): Exclusive[T] = new Exclusive(start, end, step) + /** Creates an inclusive numeric range, from `start` to `end` in increments + * of `step`. + * + * @tparam T the element type of the range + * @param start the start value of the range + * @param end the end value, an element of the range when it is a whole + * number of steps from `start` + * @param step the increment between successive elements; must not be zero + * @param num the `Integral` instance providing arithmetic on `T` + * @return a new inclusive range with the given `start`, `end`, and `step` + */ def inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]): Inclusive[T] = new Inclusive(start, end, step) diff --git a/library-js/src/scala/collection/immutable/Range.scala b/library-js/src/scala/collection/immutable/Range.scala index 62d078d83998..a5dc8319d91e 100644 --- a/library-js/src/scala/collection/immutable/Range.scala +++ b/library-js/src/scala/collection/immutable/Range.scala @@ -59,8 +59,11 @@ import scala.util.hashing.MurmurHash3 */ @SerialVersionUID(3L) sealed abstract class Range( + /** The start value of this range; its first element when this range is non-empty. */ val start: Int, + /** The end value of this range; not necessarily an element of it (see `last` for the last actual element). */ val end: Int, + /** The difference between successive elements of this range; never zero. */ val step: Int ) extends AbstractSeq[Int] @@ -70,8 +73,19 @@ sealed abstract class Range( with IterableFactoryDefaults[Int, IndexedSeq] with Serializable { range => + /** Returns a new iterator over all elements of this range, from first to last. */ final override def iterator: Iterator[Int] = new RangeIterator(start, step, lastElement, isEmpty) + /** Returns a [[scala.collection.Stepper]] for the elements of this range. + * + * @tparam S the type of the stepper, determined by `shape` + * @param shape an implicit value determining the type of stepper to create: + * an `IntStepper` for the primitive `Int` shape, or a boxing + * `AnyStepper` for the reference shape + * @return a stepper over the elements of this range; it supports efficient splitting + * @throws IllegalArgumentException if this range contains more than + * `Int.MaxValue` elements + */ override final def stepper[S <: Stepper[_]](implicit shape: StepperShape[Int, S]): S with EfficientSplit = { val st = new RangeStepper(start, step, 0, length) val r = @@ -88,8 +102,21 @@ sealed abstract class Range( private[this] def hasStub = isInclusive || !isExact private[this] def longLength = gap / step + ( if (hasStub) 1 else 0 ) + /** Returns `true` if this range is inclusive (built with `to` or + * `Range.inclusive`), `false` if it is exclusive (built with `until` or + * `Range.apply`). + * + * Even in an inclusive range, `end` is an element only if it is reachable + * from `start` in `step` increments. + */ def isInclusive: Boolean + /** Whether this range contains no elements. + * + * A range is empty when `step` leads from `start` away from `end`, or when + * it is exclusive with `start == end`. This value is computed once at + * construction. + */ final override val isEmpty: Boolean = ( (start > end && step > 0) || (start < end && step < 0) @@ -106,6 +133,11 @@ sealed abstract class Range( } } + /** Returns the number of elements in this range, in constant time. + * + * @throws IllegalArgumentException if this range contains more than + * `Int.MaxValue` elements + */ final def length = if (numRangeElements < 0) fail() else numRangeElements // This field has a sensible value only for non-empty ranges @@ -124,6 +156,10 @@ sealed abstract class Range( */ final override def last: Int = if (isEmpty) throw Range.emptyRangeError("last") else lastElement + /** Returns the first element of this range, which is always `start`. + * + * @throws NoSuchElementException if this range is empty + */ final override def head: Int = if (isEmpty) throw Range.emptyRangeError("head") else start @@ -149,11 +185,31 @@ sealed abstract class Range( else new Range.Exclusive(start + step, end, step) } + /** Builds a new indexed sequence by applying a function to all elements of + * this range. + * + * @tparam B the element type of the returned collection + * @param f the function to apply to each element + * @return a new indexed sequence containing the results of applying `f` to + * each element of this range, in order + * @throws IllegalArgumentException if this range contains more than + * `Int.MaxValue` elements + */ override def map[B](f: Int => B): IndexedSeq[B] = { validateMaxLength() super.map(f) } + /** Creates a new range from the given values, each of which defaults to the + * corresponding value of this range. + * + * @param start the start value of the new range + * @param end the end value of the new range + * @param step the step of the new range; must be non-zero + * @param isInclusive whether the new range includes its `end` value + * @return a new `Range.Inclusive` if `isInclusive` is `true`, otherwise + * a new `Range.Exclusive` + */ final protected def copy(start: Int = start, end: Int = end, step: Int = step, isInclusive: Boolean = isInclusive): Range = if(isInclusive) new Range.Inclusive(start, end, step) else new Range.Exclusive(start, end, step) @@ -176,6 +232,14 @@ sealed abstract class Range( } private[this] def fail() = Range.fail(start, end, step, isInclusive) + /** Returns the element at index `idx`, that is, `start + step * idx`, in + * constant time. + * + * @param idx the index of the element to return + * @throws IndexOutOfBoundsException if `idx` is negative or not less than `length` + * @throws IllegalArgumentException if this range contains more than + * `Int.MaxValue` elements + */ @throws[IndexOutOfBoundsException] final def apply(idx: Int): Int = { validateMaxLength() @@ -196,6 +260,16 @@ sealed abstract class Range( } } + /** Returns the index of the first occurrence of `elem` at or after index + * `from`, or `-1` if `elem` does not occur at or after that index. + * + * When `elem` is an `Int`, its position is computed arithmetically in + * constant time instead of by searching; this is possible because each + * value occurs at most once in a range. + * + * @param elem the element to search for + * @param from the start index for the search + */ override final def indexOf[@specialized(Int) B >: Int](elem: B, from: Int = 0): Int = elem match { case i: Int => @@ -204,6 +278,18 @@ sealed abstract class Range( case _ => super.indexOf(elem, from) } + /** Returns the index of the last occurrence of `elem` at or before index + * `end`, or `-1` if `elem` does not occur at or before that index. + * + * When `elem` is an `Int`, its position is computed arithmetically in + * constant time instead of by searching; this is possible because each + * value occurs at most once in a range. + * + * @param elem the element to search for + * @param end the end index for the search + * @throws IllegalArgumentException if this range contains more than + * `Int.MaxValue` elements + */ override final def lastIndexOf[@specialized(Int) B >: Int](elem: B, end: Int = length - 1): Int = elem match { case i: Int => @@ -215,6 +301,17 @@ sealed abstract class Range( private[this] def posOf(i: Int): Int = if (contains(i)) (i - start) / step else -1 + /** Returns `true` if this range contains the same elements as `that`, in + * the same order. + * + * When `that` is also a `Range`, the answer is computed in constant time + * from the two ranges' lengths, starts, and steps, without iterating. + * + * @tparam B the element type of `that` + * @param that the collection to compare with + * @throws IllegalArgumentException if this range contains more than + * `Int.MaxValue` elements + */ override def sameElements[B >: Int](that: IterableOnce[B]): Boolean = that match { case other: Range => (this.length : @annotation.switch) match { @@ -305,6 +402,13 @@ sealed abstract class Range( } } + /** Returns the longest prefix of this range whose elements all satisfy `p`. + * + * The result is itself a range; the predicate is evaluated on successive + * elements until it first fails. + * + * @param p the predicate used to test elements + */ final override def takeWhile(p: Int => Boolean): Range = { val stop = argTakeWhile(p) if (stop==start) newEmptyRange(start) @@ -315,6 +419,14 @@ sealed abstract class Range( } } + /** Returns the remainder of this range after the longest prefix whose + * elements all satisfy `p`. + * + * The result is itself a range; the predicate is evaluated on successive + * elements until it first fails. + * + * @param p the predicate used to test elements + */ final override def dropWhile(p: Int => Boolean): Range = { val stop = argTakeWhile(p) if (stop == start) this @@ -325,6 +437,15 @@ sealed abstract class Range( } } + /** Splits this range into the longest prefix whose elements all satisfy `p` + * and the remainder. + * + * Equivalent to `(takeWhile(p), dropWhile(p))`, but more efficient: the + * predicate is evaluated at most once per element. + * + * @param p the predicate used to test elements + * @return a pair of ranges `(this.takeWhile(p), this.dropWhile(p))` + */ final override def span(p: Int => Boolean): (Range, Range) = { val border = argTakeWhile(p) if (border == start) (newEmptyRange(start), this) @@ -353,6 +474,13 @@ sealed abstract class Range( } // Overridden only to refine the return type + /** Splits this range into a prefix/suffix pair at a given index. + * + * $doesNotUseBuilders + * + * @param n the index at which to split + * @return a pair of ranges `(this.take(n), this.drop(n))` + */ final override def splitAt(n: Int): (Range, Range) = (take(n), drop(n)) // Methods like apply throw exceptions on invalid n, but methods like take/drop @@ -375,6 +503,13 @@ sealed abstract class Range( if (isInclusive) this else new Range.Inclusive(start, end, step) + /** Returns `true` if `x` is an element of this range. + * + * This is a constant-time operation: membership is decided with a bounds + * check and a divisibility test rather than a search. + * + * @param x the value to test for membership + */ final def contains(x: Int) = { if (x == end && !isInclusive) false else if (step > 0) { @@ -387,11 +522,33 @@ sealed abstract class Range( } } /* Seq#contains has a type parameter so the optimised contains above doesn't override it */ + /** Returns `true` if `elem` is an element of this range. + * + * When `elem` is an `Int`, this delegates to the constant-time + * `contains(x: Int)` overload; otherwise it falls back to a linear search. + * + * @tparam B the type of `elem` + * @param elem the value to test for membership + */ override final def contains[B >: Int](elem: B): Boolean = elem match { case i: Int => this.contains(i) case _ => super.contains(elem) } + /** Returns the sum of the elements of this range. + * + * For the default `Int` numeric, the sum is computed in constant time with + * the arithmetic-series formula, and the result wraps around on overflow + * exactly as repeated `Int` addition would. For any other `Numeric`, the + * elements are added one by one and the result is converted back to `Int` + * with `num.toInt`. + * + * @tparam B a supertype of `Int` for which the addition is defined + * @param num the numeric instance used to add elements + * @return the sum of all elements, or zero if this range is empty + * @throws IllegalArgumentException if this range contains more than + * `Int.MaxValue` elements + */ final override def sum[B >: Int](implicit num: Numeric[B]): Int = { if (num eq scala.math.Numeric.IntIsIntegral) { // this is normal integer range with usual addition. arithmetic series formula can be used @@ -414,6 +571,20 @@ sealed abstract class Range( } } + /** Returns the smallest element of this range. + * + * For the standard `Int` ordering or its reverse, the result is `head` or + * `last`, depending on the ordering and the sign of `step`, found in + * constant time; for any other ordering, the elements are scanned. + * + * @tparam A1 a supertype of `Int` over which `ord` compares + * @param ord the ordering used to compare elements + * @return the smallest element of this range with respect to `ord` + * @throws NoSuchElementException if this range is empty and `ord` is the + * `Int` ordering or its reverse + * @throws UnsupportedOperationException if this range is empty and `ord` is + * any other ordering + */ final override def min[A1 >: Int](implicit ord: Ordering[A1]): Int = if (ord eq Ordering.Int) { if (step > 0) head @@ -423,6 +594,20 @@ sealed abstract class Range( else head } else super.min(ord) + /** Returns the largest element of this range. + * + * For the standard `Int` ordering or its reverse, the result is `head` or + * `last`, depending on the ordering and the sign of `step`, found in + * constant time; for any other ordering, the elements are scanned. + * + * @tparam A1 a supertype of `Int` over which `ord` compares + * @param ord the ordering used to compare elements + * @return the largest element of this range with respect to `ord` + * @throws NoSuchElementException if this range is empty and `ord` is the + * `Int` ordering or its reverse + * @throws UnsupportedOperationException if this range is empty and `ord` is + * any other ordering + */ final override def max[A1 >: Int](implicit ord: Ordering[A1]): Int = if (ord eq Ordering.Int) { if (step > 0) last @@ -432,6 +617,13 @@ sealed abstract class Range( else last } else super.max(ord) + /** Returns an iterator over all the tails of this range, starting with this + * range itself and ending with an empty range. + * + * Each tail is itself a range, produced in constant time by `drop`. + * @throws IllegalArgumentException if this range contains more than + * `Int.MaxValue` elements + */ override def tails: Iterator[Range] = new AbstractIterator[Range] { private[this] var i = 0 @@ -447,6 +639,13 @@ sealed abstract class Range( } } + /** Returns an iterator over all the inits of this range, starting with this + * range itself and ending with an empty range. + * + * Each init is itself a range, produced in constant time by `dropRight`. + * @throws IllegalArgumentException if this range contains more than + * `Int.MaxValue` elements + */ override def inits: Iterator[Range] = new AbstractIterator[Range] { private[this] var i = 0 @@ -461,8 +660,22 @@ sealed abstract class Range( } } } + /** The maximum length below which indexed access via `apply` is preferred + * over `iterator` when scanning elements; always `Int.MaxValue` because + * `apply` is constant-time for ranges. + */ override protected final def applyPreferredMaxLength: Int = Int.MaxValue + /** Returns `true` if `other` is equal to this range. + * + * Another `Range` is equal to this one if both are empty, or if both have + * the same `start` and last element and, unless they contain a single + * element, the same `step`; this is decided in constant time and works even + * for ranges of more than `Int.MaxValue` elements. Any other object is + * compared with generic sequence equality. + * + * @param other the object to compare with + */ final override def equals(other: Any) = other match { case x: Range => // Note: this must succeed for overfull ranges (length > Int.MaxValue) @@ -478,10 +691,26 @@ sealed abstract class Range( super.equals(other) } + /** Returns a hash code value consistent with `equals`. + * + * For ranges of two or more elements, the hash is computed directly from + * `start`, `step`, and the last element; smaller ranges use the generic + * sequence hash. + * + * @throws IllegalArgumentException if this range contains more than + * `Int.MaxValue` elements + */ final override def hashCode: Int = if(length >= 2) MurmurHash3.rangeHash(start, step, lastElement) else super.hashCode + /** Returns a string representation of this range, such as + * `Range 0 until 10` or `Range 1 to 9 by 2`. + * + * The `by` clause is omitted when `step` is `1`. The prefix `empty ` marks + * an empty range, and the prefix `inexact ` marks a non-empty range whose + * `end` is not an exact multiple of `step` away from `start`. + */ final override def toString: String = { val preposition = if (isInclusive) "to" else "until" val stepped = if (step == 1) "" else s" by $step" @@ -491,8 +720,20 @@ sealed abstract class Range( override protected[this] def className = "Range" + /** Returns this range: the elements of a range are always pairwise distinct. */ override def distinct: Range = this + /** Partitions the elements of this range into fixed-size groups. + * + * Each group is itself a range, produced in constant time by `slice`. + * + * @param size the number of elements per group + * @return an iterator over the groups; every group has `size` elements, + * except possibly the last, which may have fewer + * @throws IllegalArgumentException if `size` is less than `1` + * @throws IllegalArgumentException if this range contains more than + * `Int.MaxValue` elements + */ override def grouped(size: Int): Iterator[Range] = { require(size >= 1, f"size=$size%d, but size must be positive") if (isEmpty) { @@ -514,6 +755,17 @@ sealed abstract class Range( } } + /** Returns the elements of this range in sorted order according to an + * ordering. + * + * For the standard `Int` ordering, the result is this range itself when + * `step` is positive, or its `reverse` when `step` is negative, computed in + * constant time; for any other ordering, a generic sort is performed. + * + * @tparam B a supertype of `Int` over which `ord` compares + * @param ord the ordering used to compare elements + * @return an indexed sequence containing the elements of this range, sorted + */ override def sorted[B >: Int](implicit ord: Ordering[B]): IndexedSeq[Int] = if (ord eq Ordering.Int) { if (step > 0) { @@ -573,6 +825,18 @@ object Range { else result.toInt } } + /** Counts the number of elements of an exclusive range with the given + * start, end, and step. + * + * Equivalent to `count(start, end, step, isInclusive = false)`. + * + * @param start the first element of the range + * @param end the exclusive upper bound of the range + * @param step the increment between successive elements; must be non-zero + * @return the number of elements in the range, `0` if the range is empty, + * or `-1` if the count exceeds `Int.MaxValue` + * @throws IllegalArgumentException if `step` is `0` + */ def count(start: Int, end: Int, step: Int): Int = count(start, end, step, isInclusive = false) @@ -612,26 +876,73 @@ object Range { */ def inclusive(start: Int, end: Int): Range.Inclusive = new Range.Inclusive(start, end, 1) + /** A `Range` that includes its `end` value, as built by `to` or + * `Range.inclusive`. + * + * `end` is an element of the range only if it is reachable from `start` in + * `step` increments. + * + * @param start the start value of the range + * @param end the inclusive end value of the range + * @param step the step value between consecutive elements, must be non-zero + */ @SerialVersionUID(3L) @inline final class Inclusive(start: Int, end: Int, step: Int) extends Range(start, end, step) { + /** Returns `true`: this range includes its `end` value. */ def isInclusive = true } + /** A `Range` that excludes its `end` value, as built by `until` or + * `Range.apply`. + * + * @param start the start value of the range + * @param end the exclusive end value of the range + * @param step the step value between consecutive elements, must be non-zero + */ @SerialVersionUID(3L) @inline final class Exclusive(start: Int, end: Int, step: Int) extends Range(start, end, step) { + /** Returns `false`: this range excludes its `end` value. */ def isInclusive = false } // BigInt and Long are straightforward generic ranges. object BigInt { + /** Creates an exclusive range of `BigInt` values. + * + * @param start the start value of the range + * @param end the exclusive end value of the range + * @param step the step value between consecutive elements, must be non-zero + * @return an exclusive `NumericRange` from `start` until `end` in increments of `step` + */ def apply(start: BigInt, end: BigInt, step: BigInt) = NumericRange(start, end, step) + /** Creates an inclusive range of `BigInt` values. + * + * @param start the start value of the range + * @param end the inclusive end value of the range + * @param step the step value between consecutive elements, must be non-zero + * @return an inclusive `NumericRange` from `start` to `end` in increments of `step` + */ def inclusive(start: BigInt, end: BigInt, step: BigInt) = NumericRange.inclusive(start, end, step) } object Long { + /** Creates an exclusive range of `Long` values. + * + * @param start the start value of the range + * @param end the exclusive end value of the range + * @param step the step value between consecutive elements, must be non-zero + * @return an exclusive `NumericRange` from `start` until `end` in increments of `step` + */ def apply(start: Long, end: Long, step: Long) = NumericRange(start, end, step) + /** Creates an inclusive range of `Long` values. + * + * @param start the start value of the range + * @param end the inclusive end value of the range + * @param step the step value between consecutive elements, must be non-zero + * @return an inclusive `NumericRange` from `start` to `end` in increments of `step` + */ def inclusive(start: Long, end: Long, step: Long) = NumericRange.inclusive(start, end, step) } @@ -641,18 +952,52 @@ object Range { // imprecision or surprises might result from anything, although this may // not yet be fully implemented. object BigDecimal { + /** The implicit numeric instance used to build `BigDecimal` ranges; it + * treats `BigDecimal` as if it were an integral type (see + * `scala.math.Numeric.BigDecimalAsIfIntegral`). + */ implicit val bigDecAsIntegral: Numeric.BigDecimalAsIfIntegral = Numeric.BigDecimalAsIfIntegral + /** Creates an exclusive range of `BigDecimal` values. + * + * @param start the start value of the range + * @param end the exclusive end value of the range + * @param step the step value between consecutive elements, must be non-zero + * @return an exclusive `NumericRange` from `start` until `end` in increments of `step` + */ def apply(start: BigDecimal, end: BigDecimal, step: BigDecimal) = NumericRange(start, end, step) + /** Creates an inclusive range of `BigDecimal` values. + * + * @param start the start value of the range + * @param end the inclusive end value of the range + * @param step the step value between consecutive elements, must be non-zero + * @return an inclusive `NumericRange` from `start` to `end` in increments of `step` + */ def inclusive(start: BigDecimal, end: BigDecimal, step: BigDecimal) = NumericRange.inclusive(start, end, step) } // As there is no appealing default step size for not-really-integral ranges, // we offer a partially constructed object. + /** A partially constructed range that still lacks its step value. + * + * Range constructions with no sensible default step, such as `until` and + * `to` on `BigDecimal`, return a `Partial`; calling `by` supplies the step + * and yields the finished range. + * + * @tparam T the type of the step + * @tparam U the type of the completed range + * @param f the function that builds the completed range from a step value + */ class Partial[T, U](private val f: T => U) extends AnyVal { + /** Completes this partially constructed range with the given step value. + * + * @param x the step value + * @return the completed range, `f(x)` + */ def by(x: T): U = f(x) + /** Returns the string `"Range requires step"`, indicating that this is not yet a complete range. */ override def toString = "Range requires step" } @@ -661,7 +1006,23 @@ object Range { // indefinitely, for performance and because the compiler seems to bootstrap // off it and won't do so with our parameterized version without modifications. object Int { + /** Creates an exclusive `NumericRange` of `Int` values, a generic + * alternative to `Range` with the same behavior. + * + * @param start the start value of the range + * @param end the exclusive end value of the range + * @param step the step value between consecutive elements, must be non-zero + * @return an exclusive `NumericRange` from `start` until `end` in increments of `step` + */ def apply(start: Int, end: Int, step: Int) = NumericRange(start, end, step) + /** Creates an inclusive `NumericRange` of `Int` values, a generic + * alternative to `Range` with the same behavior. + * + * @param start the start value of the range + * @param end the inclusive end value of the range + * @param step the step value between consecutive elements, must be non-zero + * @return an inclusive `NumericRange` from `start` to `end` in increments of `step` + */ def inclusive(start: Int, end: Int, step: Int) = NumericRange.inclusive(start, end, step) } diff --git a/library-js/src/scala/collection/mutable/ArrayBuilder.scala b/library-js/src/scala/collection/mutable/ArrayBuilder.scala index 847d79ceb420..d4bda011b1dd 100644 --- a/library-js/src/scala/collection/mutable/ArrayBuilder.scala +++ b/library-js/src/scala/collection/mutable/ArrayBuilder.scala @@ -34,10 +34,13 @@ sealed abstract class ArrayBuilder[T] with Serializable { protected[this] var capacity: Int = 0 protected[this] def elems: Array[T] | Null // may not be allocated at size = capacity = 0 + /** The number of elements added to this builder so far. */ protected var size: Int = 0 + /** Current number of elements. */ def length: Int = size + /** Current number of elements. */ override def knownSize: Int = size protected[this] final def ensureSize(size: Int): Unit = { @@ -48,9 +51,21 @@ sealed abstract class ArrayBuilder[T] } } + /** Grows the backing array to hold at least `size` elements. + * + * Does nothing if the current capacity suffices; otherwise resizes to + * exactly `size`, avoiding the over-allocation of the doubling strategy + * when the final number of elements is known in advance. + * + * @param size the expected number of elements + */ override final def sizeHint(size: Int): Unit = if (capacity < size) resize(size) + /** Discards all elements added so far, leaving this builder empty. + * + * Any backing array is kept at its current capacity and reused. + */ def clear(): Unit = size = 0 protected[this] def resize(size: Int): Unit @@ -76,6 +91,15 @@ sealed abstract class ArrayBuilder[T] this } + /** Adds all elements of an iterable collection. + * + * If `xs` has a known size, the backing array is grown once up front and + * the elements are copied in bulk; otherwise the elements are added one by + * one. + * + * @param xs the collection whose elements are added + * @return this builder with the elements of `xs` appended + */ override def addAll(xs: IterableOnce[T]): this.type = { val k = xs.knownSize if(k > 0) { @@ -257,6 +281,9 @@ object ArrayBuilder { @SerialVersionUID(3L) final class ofRef[T <: AnyRef | Null](implicit ct: ClassTag[T]) extends ArrayBuilder[T] { + /** The backing array; `null` until storage is first allocated, and reset + * to `null` when `result()` hands the array off without copying. + */ protected var elems: Array[T] | Null = null private def mkArray(size: Int): Array[T] = { @@ -270,6 +297,11 @@ object ArrayBuilder { capacity = size } + /** Adds a single element to this builder. + * + * @param elem the element to add + * @return this builder with `elem` appended + */ def addOne(elem: T): this.type = { ensureSize(size + 1) elems.nn(size) = elem @@ -277,6 +309,14 @@ object ArrayBuilder { this } + /** Returns an array containing all elements added to this builder. + * + * If the elements added exactly fill the backing array, that array is + * returned directly, without copying, and this builder gives it up; + * otherwise the elements are copied into a new array of exactly the + * right length. After this call, `clear()` must be called before this + * builder is used again. + */ def result() = { if (capacity != 0 && capacity == size) { capacity = 0 @@ -287,16 +327,32 @@ object ArrayBuilder { else mkArray(size) } + /** Discards all elements added so far, leaving this builder empty. + * + * The backing array is kept at its current capacity, but its cells are + * nulled out so that the discarded elements can be garbage collected. + */ override def clear(): Unit = { super.clear() if(elems ne null) java.util.Arrays.fill(elems.asInstanceOf[Array[AnyRef]], null) } + /** Tests this builder for equality with `other`. + * + * True only if `other` is an `ofRef` builder with the same number of + * elements and the same backing array instance (or with neither builder + * having allocated one); elements are not compared individually. + * + * @param other the value to compare with + * @return `true` if `other` is an `ofRef` builder equal to this one, + * `false` otherwise + */ override def equals(other: Any): Boolean = other match { case x: ofRef[_] => (size == x.size) && (elems == x.elems) case _ => false } + /** Returns the string `"ArrayBuilder.ofRef"`. */ override def toString = "ArrayBuilder.ofRef" } @@ -304,6 +360,9 @@ object ArrayBuilder { @SerialVersionUID(3L) final class ofByte extends ArrayBuilder[Byte] { + /** The backing array; `null` until storage is first allocated, and reset + * to `null` when `result()` hands the array off without copying. + */ protected var elems: Array[Byte] | Null = null private def mkArray(size: Int): Array[Byte] = { @@ -317,6 +376,11 @@ object ArrayBuilder { capacity = size } + /** Adds a single element to this builder. + * + * @param elem the element to add + * @return this builder with `elem` appended + */ def addOne(elem: Byte): this.type = { ensureSize(size + 1) elems.nn(size) = elem @@ -324,6 +388,14 @@ object ArrayBuilder { this } + /** Returns an array containing all elements added to this builder. + * + * If the elements added exactly fill the backing array, that array is + * returned directly, without copying, and this builder gives it up; + * otherwise the elements are copied into a new array of exactly the + * right length. After this call, `clear()` must be called before this + * builder is used again. + */ def result() = { if (capacity != 0 && capacity == size) { capacity = 0 @@ -334,11 +406,22 @@ object ArrayBuilder { else mkArray(size) } + /** Tests this builder for equality with `other`. + * + * True only if `other` is a builder of the same class with the same + * number of elements and the same backing array instance (or with + * neither builder having allocated one); elements are not compared + * individually. + * + * @param other the value to compare with + * @return `true` if `other` is equal to this builder, `false` otherwise + */ override def equals(other: Any): Boolean = other match { case x: ofByte => (size == x.size) && (elems == x.elems) case _ => false } + /** Returns the string `"ArrayBuilder.ofByte"`. */ override def toString = "ArrayBuilder.ofByte" } @@ -346,6 +429,9 @@ object ArrayBuilder { @SerialVersionUID(3L) final class ofShort extends ArrayBuilder[Short] { + /** The backing array; `null` until storage is first allocated, and reset + * to `null` when `result()` hands the array off without copying. + */ protected var elems: Array[Short] | Null = null private def mkArray(size: Int): Array[Short] = { @@ -359,6 +445,11 @@ object ArrayBuilder { capacity = size } + /** Adds a single element to this builder. + * + * @param elem the element to add + * @return this builder with `elem` appended + */ def addOne(elem: Short): this.type = { ensureSize(size + 1) elems.nn(size) = elem @@ -366,6 +457,14 @@ object ArrayBuilder { this } + /** Returns an array containing all elements added to this builder. + * + * If the elements added exactly fill the backing array, that array is + * returned directly, without copying, and this builder gives it up; + * otherwise the elements are copied into a new array of exactly the + * right length. After this call, `clear()` must be called before this + * builder is used again. + */ def result() = { if (capacity != 0 && capacity == size) { capacity = 0 @@ -376,11 +475,22 @@ object ArrayBuilder { else mkArray(size) } + /** Tests this builder for equality with `other`. + * + * True only if `other` is a builder of the same class with the same + * number of elements and the same backing array instance (or with + * neither builder having allocated one); elements are not compared + * individually. + * + * @param other the value to compare with + * @return `true` if `other` is equal to this builder, `false` otherwise + */ override def equals(other: Any): Boolean = other match { case x: ofShort => (size == x.size) && (elems == x.elems) case _ => false } + /** Returns the string `"ArrayBuilder.ofShort"`. */ override def toString = "ArrayBuilder.ofShort" } @@ -388,6 +498,9 @@ object ArrayBuilder { @SerialVersionUID(3L) final class ofChar extends ArrayBuilder[Char] { + /** The backing array; `null` until storage is first allocated, and reset + * to `null` when `result()` hands the array off without copying. + */ protected var elems: Array[Char] | Null = null private def mkArray(size: Int): Array[Char] = { @@ -401,6 +514,11 @@ object ArrayBuilder { capacity = size } + /** Adds a single element to this builder. + * + * @param elem the element to add + * @return this builder with `elem` appended + */ def addOne(elem: Char): this.type = { ensureSize(size + 1) elems.nn(size) = elem @@ -408,6 +526,14 @@ object ArrayBuilder { this } + /** Returns an array containing all elements added to this builder. + * + * If the elements added exactly fill the backing array, that array is + * returned directly, without copying, and this builder gives it up; + * otherwise the elements are copied into a new array of exactly the + * right length. After this call, `clear()` must be called before this + * builder is used again. + */ def result() = { if (capacity != 0 && capacity == size) { capacity = 0 @@ -418,11 +544,22 @@ object ArrayBuilder { else mkArray(size) } + /** Tests this builder for equality with `other`. + * + * True only if `other` is a builder of the same class with the same + * number of elements and the same backing array instance (or with + * neither builder having allocated one); elements are not compared + * individually. + * + * @param other the value to compare with + * @return `true` if `other` is equal to this builder, `false` otherwise + */ override def equals(other: Any): Boolean = other match { case x: ofChar => (size == x.size) && (elems == x.elems) case _ => false } + /** Returns the string `"ArrayBuilder.ofChar"`. */ override def toString = "ArrayBuilder.ofChar" } @@ -430,6 +567,9 @@ object ArrayBuilder { @SerialVersionUID(3L) final class ofInt extends ArrayBuilder[Int] { + /** The backing array; `null` until storage is first allocated, and reset + * to `null` when `result()` hands the array off without copying. + */ protected var elems: Array[Int] | Null = null private def mkArray(size: Int): Array[Int] = { @@ -443,6 +583,11 @@ object ArrayBuilder { capacity = size } + /** Adds a single element to this builder. + * + * @param elem the element to add + * @return this builder with `elem` appended + */ def addOne(elem: Int): this.type = { ensureSize(size + 1) elems.nn(size) = elem @@ -450,6 +595,14 @@ object ArrayBuilder { this } + /** Returns an array containing all elements added to this builder. + * + * If the elements added exactly fill the backing array, that array is + * returned directly, without copying, and this builder gives it up; + * otherwise the elements are copied into a new array of exactly the + * right length. After this call, `clear()` must be called before this + * builder is used again. + */ def result() = { if (capacity != 0 && capacity == size) { capacity = 0 @@ -460,11 +613,22 @@ object ArrayBuilder { else mkArray(size) } + /** Tests this builder for equality with `other`. + * + * True only if `other` is a builder of the same class with the same + * number of elements and the same backing array instance (or with + * neither builder having allocated one); elements are not compared + * individually. + * + * @param other the value to compare with + * @return `true` if `other` is equal to this builder, `false` otherwise + */ override def equals(other: Any): Boolean = other match { case x: ofInt => (size == x.size) && (elems == x.elems) case _ => false } + /** Returns the string `"ArrayBuilder.ofInt"`. */ override def toString = "ArrayBuilder.ofInt" } @@ -472,6 +636,9 @@ object ArrayBuilder { @SerialVersionUID(3L) final class ofLong extends ArrayBuilder[Long] { + /** The backing array; `null` until storage is first allocated, and reset + * to `null` when `result()` hands the array off without copying. + */ protected var elems: Array[Long] | Null = null private def mkArray(size: Int): Array[Long] = { @@ -485,6 +652,11 @@ object ArrayBuilder { capacity = size } + /** Adds a single element to this builder. + * + * @param elem the element to add + * @return this builder with `elem` appended + */ def addOne(elem: Long): this.type = { ensureSize(size + 1) elems.nn(size) = elem @@ -492,6 +664,14 @@ object ArrayBuilder { this } + /** Returns an array containing all elements added to this builder. + * + * If the elements added exactly fill the backing array, that array is + * returned directly, without copying, and this builder gives it up; + * otherwise the elements are copied into a new array of exactly the + * right length. After this call, `clear()` must be called before this + * builder is used again. + */ def result() = { if (capacity != 0 && capacity == size) { capacity = 0 @@ -502,11 +682,22 @@ object ArrayBuilder { else mkArray(size) } + /** Tests this builder for equality with `other`. + * + * True only if `other` is a builder of the same class with the same + * number of elements and the same backing array instance (or with + * neither builder having allocated one); elements are not compared + * individually. + * + * @param other the value to compare with + * @return `true` if `other` is equal to this builder, `false` otherwise + */ override def equals(other: Any): Boolean = other match { case x: ofLong => (size == x.size) && (elems == x.elems) case _ => false } + /** Returns the string `"ArrayBuilder.ofLong"`. */ override def toString = "ArrayBuilder.ofLong" } @@ -514,6 +705,9 @@ object ArrayBuilder { @SerialVersionUID(3L) final class ofFloat extends ArrayBuilder[Float] { + /** The backing array; `null` until storage is first allocated, and reset + * to `null` when `result()` hands the array off without copying. + */ protected var elems: Array[Float] | Null = null private def mkArray(size: Int): Array[Float] = { @@ -527,6 +721,11 @@ object ArrayBuilder { capacity = size } + /** Adds a single element to this builder. + * + * @param elem the element to add + * @return this builder with `elem` appended + */ def addOne(elem: Float): this.type = { ensureSize(size + 1) elems.nn(size) = elem @@ -534,6 +733,14 @@ object ArrayBuilder { this } + /** Returns an array containing all elements added to this builder. + * + * If the elements added exactly fill the backing array, that array is + * returned directly, without copying, and this builder gives it up; + * otherwise the elements are copied into a new array of exactly the + * right length. After this call, `clear()` must be called before this + * builder is used again. + */ def result() = { if (capacity != 0 && capacity == size) { capacity = 0 @@ -544,11 +751,22 @@ object ArrayBuilder { else mkArray(size) } + /** Tests this builder for equality with `other`. + * + * True only if `other` is a builder of the same class with the same + * number of elements and the same backing array instance (or with + * neither builder having allocated one); elements are not compared + * individually. + * + * @param other the value to compare with + * @return `true` if `other` is equal to this builder, `false` otherwise + */ override def equals(other: Any): Boolean = other match { case x: ofFloat => (size == x.size) && (elems == x.elems) case _ => false } + /** Returns the string `"ArrayBuilder.ofFloat"`. */ override def toString = "ArrayBuilder.ofFloat" } @@ -556,6 +774,9 @@ object ArrayBuilder { @SerialVersionUID(3L) final class ofDouble extends ArrayBuilder[Double] { + /** The backing array; `null` until storage is first allocated, and reset + * to `null` when `result()` hands the array off without copying. + */ protected var elems: Array[Double] | Null = null private def mkArray(size: Int): Array[Double] = { @@ -569,6 +790,11 @@ object ArrayBuilder { capacity = size } + /** Adds a single element to this builder. + * + * @param elem the element to add + * @return this builder with `elem` appended + */ def addOne(elem: Double): this.type = { ensureSize(size + 1) elems.nn(size) = elem @@ -576,6 +802,14 @@ object ArrayBuilder { this } + /** Returns an array containing all elements added to this builder. + * + * If the elements added exactly fill the backing array, that array is + * returned directly, without copying, and this builder gives it up; + * otherwise the elements are copied into a new array of exactly the + * right length. After this call, `clear()` must be called before this + * builder is used again. + */ def result() = { if (capacity != 0 && capacity == size) { capacity = 0 @@ -586,11 +820,22 @@ object ArrayBuilder { else mkArray(size) } + /** Tests this builder for equality with `other`. + * + * True only if `other` is a builder of the same class with the same + * number of elements and the same backing array instance (or with + * neither builder having allocated one); elements are not compared + * individually. + * + * @param other the value to compare with + * @return `true` if `other` is equal to this builder, `false` otherwise + */ override def equals(other: Any): Boolean = other match { case x: ofDouble => (size == x.size) && (elems == x.elems) case _ => false } + /** Returns the string `"ArrayBuilder.ofDouble"`. */ override def toString = "ArrayBuilder.ofDouble" } @@ -598,6 +843,9 @@ object ArrayBuilder { @SerialVersionUID(3L) class ofBoolean extends ArrayBuilder[Boolean] { + /** The backing array; `null` until storage is first allocated, and reset + * to `null` when `result()` hands the array off without copying. + */ protected var elems: Array[Boolean] | Null = null private def mkArray(size: Int): Array[Boolean] = { @@ -611,6 +859,11 @@ object ArrayBuilder { capacity = size } + /** Adds a single element to this builder. + * + * @param elem the element to add + * @return this builder with `elem` appended + */ def addOne(elem: Boolean): this.type = { ensureSize(size + 1) elems.nn(size) = elem @@ -618,6 +871,14 @@ object ArrayBuilder { this } + /** Returns an array containing all elements added to this builder. + * + * If the elements added exactly fill the backing array, that array is + * returned directly, without copying, and this builder gives it up; + * otherwise the elements are copied into a new array of exactly the + * right length. After this call, `clear()` must be called before this + * builder is used again. + */ def result() = { if (capacity != 0 && capacity == size) { capacity = 0 @@ -628,11 +889,22 @@ object ArrayBuilder { else mkArray(size) } + /** Tests this builder for equality with `other`. + * + * True only if `other` is a builder of the same class with the same + * number of elements and the same backing array instance (or with + * neither builder having allocated one); elements are not compared + * individually. + * + * @param other the value to compare with + * @return `true` if `other` is equal to this builder, `false` otherwise + */ override def equals(other: Any): Boolean = other match { case x: ofBoolean => (size == x.size) && (elems == x.elems) case _ => false } + /** Returns the string `"ArrayBuilder.ofBoolean"`. */ override def toString = "ArrayBuilder.ofBoolean" } @@ -640,23 +912,53 @@ object ArrayBuilder { @SerialVersionUID(3L) final class ofUnit extends ArrayBuilder[Unit] { + /** Not supported: this builder stores no elements, only their count. + * + * @throws UnsupportedOperationException always + */ protected def elems: Array[Unit] | Null = throw new UnsupportedOperationException() + /** Adds a single unit value by incrementing the element count. + * + * @param elem never used, as all unit values are identical + * @return this builder with its size increased by 1 + */ def addOne(elem: Unit): this.type = { size += 1 this } + /** Adds all elements of a collection by increasing the element count by + * its size. + * + * @param xs the collection whose elements are counted; it is iterated + * fully to compute its size, but its elements are not stored + * @return this builder with its size increased by the size of `xs` + */ override def addAll(xs: IterableOnce[Unit]): this.type = { size += xs.iterator.size this } + /** Adds `length` unit values by increasing the element count, without + * inspecting the array. + * + * @param xs the array whose elements are counted; never used + * @param offset the start index of the slice; never used + * @param length the number of elements to add, applied as is and without + * bounds checks; a negative value decreases the count + * @return this builder with its size increased by `length` + */ override def addAll(xs: Array[_ <: Unit], offset: Int, length: Int): this.type = { size += length this } + /** Returns a new array of `size` unit values. + * + * A fresh array is allocated and filled on each call; this builder's + * count is left unchanged. + */ def result() = { val ans = new Array[Unit](size) var i = 0 @@ -664,6 +966,15 @@ object ArrayBuilder { ans } + /** Tests this builder for equality with `other`. + * + * True only if `other` is an `ofUnit` builder with the same number of + * elements. + * + * @param other the value to compare with + * @return `true` if `other` is an `ofUnit` builder of the same size, + * `false` otherwise + */ override def equals(other: Any): Boolean = other match { case x: ofUnit => (size == x.size) case _ => false @@ -671,6 +982,7 @@ object ArrayBuilder { protected[this] def resize(size: Int): Unit = () + /** Returns the string `"ArrayBuilder.ofUnit"`. */ override def toString = "ArrayBuilder.ofUnit" } } diff --git a/library-js/src/scala/collection/mutable/Buffer.scala b/library-js/src/scala/collection/mutable/Buffer.scala index 95ba2237439e..b0938dcaf1fb 100644 --- a/library-js/src/scala/collection/mutable/Buffer.scala +++ b/library-js/src/scala/collection/mutable/Buffer.scala @@ -30,8 +30,14 @@ trait Buffer[A] with Shrinkable[A] with IterableFactoryDefaults[A, Buffer] { + /** The companion object `Buffer`, which creates `js.WrappedArray` instances. */ override def iterableFactory: SeqFactory[Buffer] = Buffer + /** The number of elements in this buffer, if it can be cheaply computed, -1 otherwise. + * + * Overridden to select `Seq`'s implementation over the conflicting one inherited + * from [[Growable]]. + */ override def knownSize: Int = super[Seq].knownSize //TODO Prepend is a logical choice for a readable name of `+=:` but it conflicts with the renaming of `append` to `add` @@ -63,6 +69,11 @@ trait Buffer[A] /** Alias for `prepend`. */ @`inline` final def +=: (elem: A): this.type = prepend(elem) + /** Prepends the elements contained in an iterable object to this buffer. + * + * @param elems the iterable object containing the elements to prepend. + * @return this buffer + */ def prependAll(elems: IterableOnce[A]): this.type = { insertAll(0, elems); this } @deprecated("Use prependAll instead", "2.13.0") @@ -142,6 +153,17 @@ trait Buffer[A] remove(length - norm, norm) } + /** Replaces a slice of elements in this buffer by another sequence of elements. + * + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original buffer appends the patch to the end. + * If the `replaced` count would exceed the available elements, the difference in excess is ignored. + * + * @param from the index of the first replaced element + * @param patch the replacement sequence + * @param replaced the number of elements to drop in the original buffer + * @return this buffer + */ def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A], replaced: Int): this.type // +=, ++=, clear inherited from Growable @@ -151,29 +173,73 @@ trait Buffer[A] // def +=:(elem1: A, elem2: A, elems: A*): this.type = elem1 +=: elem2 +=: elems ++=: this // def ++=:(elems: IterableOnce[A]): this.type = { insertAll(0, elems); this } + /** Removes the first `n` elements from this buffer. + * + * @param n the number of elements to remove + * @return this buffer + */ def dropInPlace(n: Int): this.type = { remove(0, normalized(n)); this } + /** Removes the last `n` elements from this buffer. + * + * @param n the number of elements to remove + * @return this buffer + */ def dropRightInPlace(n: Int): this.type = { val norm = normalized(n) remove(length - norm, norm) this } + /** Retains the first `n` elements from this buffer and removes the rest. + * + * @param n the number of elements to retain + * @return this buffer + */ def takeInPlace(n: Int): this.type = { val norm = normalized(n) remove(norm, length - norm) this } + /** Retains the last `n` elements from this buffer and removes the rest. + * + * @param n the number of elements to retain + * @return this buffer + */ def takeRightInPlace(n: Int): this.type = { remove(0, length - normalized(n)); this } + /** Retains the specified slice from this buffer and removes the rest. + * + * @param start the lowest index to include + * @param end the lowest index to exclude + * @return this buffer + */ def sliceInPlace(start: Int, end: Int): this.type = takeInPlace(end).dropInPlace(start) private def normalized(n: Int): Int = math.min(math.max(n, 0), length) + /** Drops the longest prefix of elements that satisfy a predicate. + * + * @param p The predicate used to test elements. + * @return this buffer + * @see [[dropWhile]] + */ def dropWhileInPlace(p: A => Boolean): this.type = { val idx = indexWhere(!p(_)) if (idx < 0) { clear(); this } else dropInPlace(idx) } + /** Retains the longest prefix of elements that satisfy a predicate. + * + * @param p The predicate used to test elements. + * @return this buffer + * @see [[takeWhile]] + */ def takeWhileInPlace(p: A => Boolean): this.type = { val idx = indexWhere(!p(_)) if (idx < 0) this else takeInPlace(idx) } + /** Appends the given element to this buffer until a target length is reached. + * + * @param len the target length + * @param elem the padding value + * @return this buffer + */ def padToInPlace(len: Int, elem: A): this.type = { while (length < len) +=(elem) this @@ -183,13 +249,27 @@ trait Buffer[A] override protected[this] def stringPrefix = "Buffer" } +/** A `Buffer` that is also an `IndexedSeq`, so its elements can be accessed and + * updated efficiently by index. + * + * Adds `flatMapInPlace` and `filterInPlace`, and implements `patchInPlace` in + * terms of indexed `update`. + * + * @tparam A the element type of the buffer + */ trait IndexedBuffer[A] extends IndexedSeq[A] with IndexedSeqOps[A, IndexedBuffer, IndexedBuffer[A]] with Buffer[A] with IterableFactoryDefaults[A, IndexedBuffer] { + /** The companion object `IndexedBuffer`, which creates `js.WrappedArray` instances. */ override def iterableFactory: SeqFactory[IndexedBuffer] = IndexedBuffer + /** Replaces the contents of this buffer with the flatmapped result. + * + * @param f the mapping function + * @return this buffer + */ def flatMapInPlace(f: A => IterableOnce[A]): this.type = { // There's scope for a better implementation which copies elements in place. var i = 0 @@ -202,6 +282,11 @@ trait IndexedBuffer[A] extends IndexedSeq[A] this } + /** Replaces the contents of this buffer with the filtered result. + * + * @param p the filtering function + * @return this buffer + */ def filterInPlace(p: A => Boolean): this.type = { var i, j = 0 while (i < size) { @@ -217,6 +302,20 @@ trait IndexedBuffer[A] extends IndexedSeq[A] if (i == j) this else takeInPlace(j) } + /** Replaces a slice of elements in this buffer by another sequence of elements. + * + * `from` and `replaced` are clamped to the range `[0, length]`: patching at negative + * indices is the same as patching starting at 0, patching at indices at or larger + * than the length appends the patch to the end, and an excessive `replaced` count is + * reduced to the available elements. Implemented by overwriting replaced elements in + * place via `update` while the patch lasts, then inserting any remaining patch + * elements or removing any remaining replaced elements. + * + * @param from the index of the first replaced element + * @param patch the replacement sequence + * @param replaced the number of elements to drop in the original buffer + * @return this buffer + */ def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A], replaced: Int): this.type = { val replaced0 = math.min(math.max(replaced, 0), length) val i = math.min(math.max(from, 0), length) diff --git a/library-js/src/scala/concurrent/ExecutionContext.scala b/library-js/src/scala/concurrent/ExecutionContext.scala index 017b302a8169..e19eb71b18b7 100644 --- a/library-js/src/scala/concurrent/ExecutionContext.scala +++ b/library-js/src/scala/concurrent/ExecutionContext.scala @@ -157,8 +157,22 @@ object ExecutionContext { * Any `NonFatal` or `InterruptedException`s will be reported to the `defaultReporter`. */ object parasitic extends ExecutionContextExecutor with BatchingExecutor { + /** Executes the given `Runnable` immediately on the current thread. + * + * @param runnable the task to execute + */ override final def submitForExecution(runnable: Runnable): Unit = runnable.run() + /** Runs the given `Runnable` on the calling thread. Nested calls are not run eagerly: + * past a small nesting depth they are queued in a batch and trampolined, to bound + * stack growth. + * + * @param runnable the task to execute + */ override final def execute(runnable: Runnable): Unit = submitSyncBatched(runnable) + /** Reports the given `Throwable` to the default reporter. + * + * @param t the failure to report + */ override final def reportFailure(t: Throwable): Unit = defaultReporter(t) } diff --git a/library-js/src/scala/math/ScalaNumber.scala b/library-js/src/scala/math/ScalaNumber.scala index e16467e42cb7..05c8647fbb2a 100644 --- a/library-js/src/scala/math/ScalaNumber.scala +++ b/library-js/src/scala/math/ScalaNumber.scala @@ -18,6 +18,10 @@ import scala.language.`2.13` * @since 2.8 */ abstract class ScalaNumber extends java.lang.Number { + /** Returns `true` if this number has no fractional part, i.e. is a whole number. */ protected def isWhole(): Boolean + /** Returns the value underlying this wrapper, e.g. the `java.math.BigInteger` + * underlying a [[scala.math.BigInt]]. + */ def underlying(): Object } diff --git a/library-js/src/scala/package.scala b/library-js/src/scala/package.scala index 9ced6a30f81c..5a6f17a33f12 100644 --- a/library-js/src/scala/package.scala +++ b/library-js/src/scala/package.scala @@ -40,6 +40,7 @@ package object scala { type InterruptedException = java.lang.InterruptedException // A dummy used by the specialization annotation. + /** A dummy [[Specializable]] instance standing for `AnyRef` in arguments to the `@specialized` annotation. */ val AnyRef = new Specializable { override def toString = "object AnyRef" } @@ -51,29 +52,36 @@ package object scala { @deprecated("Use Iterable instead of Traversable", "2.13.0") type Traversable[+A] = scala.collection.Iterable[A] + /** The [[scala.collection.Iterable]] companion object, under its old name `Traversable`. */ @deprecated("Use Iterable instead of Traversable", "2.13.0") val Traversable = scala.collection.Iterable type Iterable[+A] = scala.collection.Iterable[A] + /** The [[scala.collection.Iterable]] companion object. */ val Iterable = scala.collection.Iterable @migration("scala.Seq is now scala.collection.immutable.Seq instead of scala.collection.Seq", "2.13.0") type Seq[+A] = scala.collection.immutable.Seq[A] + /** The [[scala.collection.immutable.Seq]] companion object. */ val Seq = scala.collection.immutable.Seq @migration("scala.IndexedSeq is now scala.collection.immutable.IndexedSeq instead of scala.collection.IndexedSeq", "2.13.0") type IndexedSeq[+A] = scala.collection.immutable.IndexedSeq[A] + /** The [[scala.collection.immutable.IndexedSeq]] companion object. */ val IndexedSeq = scala.collection.immutable.IndexedSeq type Iterator[+A] = scala.collection.Iterator[A] + /** The [[scala.collection.Iterator]] companion object. */ val Iterator = scala.collection.Iterator @deprecated("Use scala.collection.BufferedIterator instead of scala.BufferedIterator", "2.13.0") type BufferedIterator[+A] = scala.collection.BufferedIterator[A] type List[+A] = scala.collection.immutable.List[A] + /** The [[scala.collection.immutable.List]] companion object. */ val List = scala.collection.immutable.List + /** The empty list, [[scala.collection.immutable.Nil]]. */ val Nil = scala.collection.immutable.Nil type ::[A] = scala.collection.immutable.::[A] @@ -84,65 +92,96 @@ package object scala { @deprecated("Use LazyList instead of Stream", "2.13.0") type Stream[+A] = scala.collection.immutable.Stream[A] + /** The [[scala.collection.immutable.Stream]] companion object. */ @deprecated("Use LazyList instead of Stream", "2.13.0") val Stream = scala.collection.immutable.Stream type LazyList[+A] = scala.collection.immutable.LazyList[A] + /** The [[scala.collection.immutable.LazyList]] companion object. */ val LazyList = scala.collection.immutable.LazyList // This should be an alias to LazyList.#:: but we need to support Stream, too //val #:: = scala.collection.immutable.LazyList.#:: object #:: { + /** Decomposes a non-empty lazy list into its head, which is evaluated, and + * its tail, enabling patterns of the form `case head #:: tail =>`. + * + * @tparam A the element type of the lazy list + * @param s the lazy list to decompose + * @return `Some` of the head and tail of `s` if it is non-empty, `None` otherwise; + * obtaining the tail evaluates it, so matching forces one element beyond the head + */ def unapply[A](s: LazyList[A]): Option[(A, LazyList[A])] = if (s.nonEmpty) Some((s.head, s.tail)) else None + /** Decomposes a non-empty stream into its head, which is evaluated, and + * its tail, enabling patterns of the form `case head #:: tail =>`. + * + * @tparam A the element type of the stream + * @param s the stream to decompose + * @return `Some` of the head and tail of `s` if it is non-empty, `None` otherwise + */ def unapply[A](s: Stream[A]): Option[(A, Stream[A])] = if (s.nonEmpty) Some((s.head, s.tail)) else None } type Vector[+A] = scala.collection.immutable.Vector[A] + /** The [[scala.collection.immutable.Vector]] companion object. */ val Vector = scala.collection.immutable.Vector type StringBuilder = scala.collection.mutable.StringBuilder + /** The [[scala.collection.mutable.StringBuilder]] companion object. */ val StringBuilder = scala.collection.mutable.StringBuilder type Range = scala.collection.immutable.Range + /** The [[scala.collection.immutable.Range]] companion object. */ val Range = scala.collection.immutable.Range // Numeric types which were moved into scala.math.* type BigDecimal = scala.math.BigDecimal + /** The [[scala.math.BigDecimal]] companion object. */ lazy val BigDecimal = scala.math.BigDecimal type BigInt = scala.math.BigInt + /** The [[scala.math.BigInt]] companion object. */ lazy val BigInt = scala.math.BigInt type Equiv[T] = scala.math.Equiv[T] + /** The [[scala.math.Equiv]] companion object. */ val Equiv = scala.math.Equiv type Fractional[T] = scala.math.Fractional[T] + /** The [[scala.math.Fractional]] companion object. */ val Fractional = scala.math.Fractional type Integral[T] = scala.math.Integral[T] + /** The [[scala.math.Integral]] companion object. */ val Integral = scala.math.Integral type Numeric[T] = scala.math.Numeric[T] + /** The [[scala.math.Numeric]] companion object. */ val Numeric = scala.math.Numeric type Ordered[T] = scala.math.Ordered[T] + /** The [[scala.math.Ordered]] companion object. */ val Ordered = scala.math.Ordered type Ordering[T] = scala.math.Ordering[T] + /** The [[scala.math.Ordering]] companion object. */ val Ordering = scala.math.Ordering type PartialOrdering[T] = scala.math.PartialOrdering[T] type PartiallyOrdered[T] = scala.math.PartiallyOrdered[T] type Either[+A, +B] = scala.util.Either[A, B] + /** The [[scala.util.Either]] companion object. */ val Either = scala.util.Either type Left[+A, +B] = scala.util.Left[A, B] + /** The [[scala.util.Left]] companion object. */ val Left = scala.util.Left type Right[+A, +B] = scala.util.Right[A, B] + /** The [[scala.util.Right]] companion object. */ val Right = scala.util.Right } diff --git a/library-js/src/scala/reflect/ClassTag.scala b/library-js/src/scala/reflect/ClassTag.scala index 46f5bd4e1330..c5f830b26cb0 100644 --- a/library-js/src/scala/reflect/ClassTag.scala +++ b/library-js/src/scala/reflect/ClassTag.scala @@ -84,9 +84,23 @@ trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serial else None // case class accessories + /** Tests whether `x` can possibly equal this class tag: only `ClassTag` instances can. + * + * @param x the value to test + */ override def canEqual(x: Any) = x.isInstanceOf[ClassTag[_]] + /** Tests whether `x` is a `ClassTag` whose `runtimeClass` equals this one's. + * + * @param x the value to compare against + */ override def equals(x: Any) = x.isInstanceOf[ClassTag[_]] && this.runtimeClass == x.asInstanceOf[ClassTag[_]].runtimeClass + /** Returns the hash code of `runtimeClass`, so that class tags that compare equal + * hash alike. + */ override def hashCode = runtimeClass.## + /** Returns the name of `runtimeClass`, rendering array classes as `Array[...]` of + * their component type, recursively. + */ override def toString = { def prettyprint(clazz: jClass[_]): String = if (clazz.isArray) s"Array[${prettyprint(clazz.getComponentType)}]" else @@ -99,20 +113,35 @@ trait ClassTag[T] extends ClassManifestDeprecatedApis[T] with Equals with Serial object ClassTag { import ManifestFactory._ + /** The class tag for the value type `Byte`, whose `runtimeClass` is `java.lang.Byte.TYPE`. */ val Byte : ByteManifest = ManifestFactory.Byte + /** The class tag for the value type `Short`, whose `runtimeClass` is `java.lang.Short.TYPE`. */ val Short : ShortManifest = ManifestFactory.Short + /** The class tag for the value type `Char`, whose `runtimeClass` is `java.lang.Character.TYPE`. */ val Char : CharManifest = ManifestFactory.Char + /** The class tag for the value type `Int`, whose `runtimeClass` is `java.lang.Integer.TYPE`. */ val Int : IntManifest = ManifestFactory.Int + /** The class tag for the value type `Long`, whose `runtimeClass` is `java.lang.Long.TYPE`. */ val Long : LongManifest = ManifestFactory.Long + /** The class tag for the value type `Float`, whose `runtimeClass` is `java.lang.Float.TYPE`. */ val Float : FloatManifest = ManifestFactory.Float + /** The class tag for the value type `Double`, whose `runtimeClass` is `java.lang.Double.TYPE`. */ val Double : DoubleManifest = ManifestFactory.Double + /** The class tag for the value type `Boolean`, whose `runtimeClass` is `java.lang.Boolean.TYPE`. */ val Boolean : BooleanManifest = ManifestFactory.Boolean + /** The class tag for the value type `Unit`, whose `runtimeClass` is `java.lang.Void.TYPE`. */ val Unit : UnitManifest = ManifestFactory.Unit + /** The class tag for the type `Any`, whose `runtimeClass` is `classOf[java.lang.Object]`. */ val Any : ClassTag[scala.Any] = ManifestFactory.Any + /** The class tag for the type `Object`, whose `runtimeClass` is `classOf[java.lang.Object]`. */ val Object : ClassTag[java.lang.Object] = ManifestFactory.Object + /** The class tag for the type `AnyVal`, whose `runtimeClass` is `classOf[java.lang.Object]`. */ val AnyVal : ClassTag[scala.AnyVal] = ManifestFactory.AnyVal + /** The class tag for the type `AnyRef`, the same instance as `Object`. */ val AnyRef : ClassTag[scala.AnyRef] = ManifestFactory.AnyRef + /** The class tag for the type `Nothing`, whose `runtimeClass` is `classOf[scala.runtime.Nothing$]`. */ val Nothing : ClassTag[scala.Nothing] = ManifestFactory.Nothing + /** The class tag for the type `Null`, whose `runtimeClass` is `classOf[scala.runtime.Null$]`. */ val Null : ClassTag[scala.Null] = ManifestFactory.Null @inline @@ -123,6 +152,18 @@ object ClassTag { } } + /** Returns a `ClassTag[T]` for the given runtime class. + * + * For the primitive classes (`java.lang.Byte.TYPE`, ..., `java.lang.Void.TYPE`) and + * for `classOf[java.lang.Object]`, `classOf[scala.runtime.Nothing$]`, and + * `classOf[scala.runtime.Null$]`, returns the corresponding shared tag + * (`ClassTag.Byte`, ..., `ClassTag.Unit`, `ClassTag.Object`, `ClassTag.Nothing`, + * `ClassTag.Null`); otherwise returns a new `ClassTag` wrapping `runtimeClass1`. + * + * @tparam T the type the tag is for; it is not checked against `runtimeClass1`, so a + * mismatched pair yields a tag whose `runtimeClass` does not erase `T` + * @param runtimeClass1 the runtime class the tag reports + */ def apply[T](runtimeClass1: jClass[_]): ClassTag[T] = runtimeClass1 match { case java.lang.Byte.TYPE => ClassTag.Byte.asInstanceOf[ClassTag[T]] @@ -145,5 +186,11 @@ object ClassTag { new GenericClassTag[T](runtimeClass1) } + /** Extractor that yields the runtime class of a class tag. + * + * @tparam T the type represented by `ctag` + * @param ctag the class tag to extract from + * @return `Some` of `ctag`'s `runtimeClass` + */ def unapply[T](ctag: ClassTag[T]): Option[Class[_]] = Some(ctag.runtimeClass) } diff --git a/library-js/src/scala/reflect/Manifest.scala b/library-js/src/scala/reflect/Manifest.scala index 5d7d0024a6e5..05ae17d7e3a8 100644 --- a/library-js/src/scala/reflect/Manifest.scala +++ b/library-js/src/scala/reflect/Manifest.scala @@ -48,11 +48,24 @@ import scala.collection.mutable.{ArrayBuilder, ArraySeq} // TODO undeprecated until Scala reflection becomes non-experimental // @deprecated("use scala.reflect.ClassTag (to capture erasures) or scala.reflect.runtime.universe.TypeTag (to capture types) or both instead", "2.10.0") trait Manifest[T] extends ClassManifest[T] with Equals { + /** Returns the manifests for the type arguments of the type represented by this + * manifest. This default implementation returns `Nil`; manifests built with type + * arguments override it. + */ override def typeArguments: List[Manifest[_]] = Nil + /** Returns a manifest for the array type `Array[T]`, whose runtime class is the + * array class with this manifest's runtime class as component, and whose only + * type argument is this manifest. + */ override def arrayManifest: Manifest[Array[T]] = Manifest.classType[Array[T]](arrayClass[T](runtimeClass), this) + /** Tests whether `that` can possibly equal this manifest. + * + * @param that the value to test + * @return `true` if `that` is a `Manifest`, `false` otherwise + */ override def canEqual(that: Any): Boolean = that match { case _: Manifest[_] => true case _ => false @@ -66,6 +79,9 @@ trait Manifest[T] extends ClassManifest[T] with Equals { case m: Manifest[_] => (m canEqual this) && (this.runtimeClass == m.runtimeClass) && (this <:< m) && (m <:< this) case _ => false } + /** Returns the hash code of `runtimeClass`, so that manifests that compare equal + * hash alike. + */ override def hashCode = this.runtimeClass.## } @@ -81,24 +97,42 @@ object Manifest { * defined above. */ + /** Returns the manifests for the value types `Byte`, `Short`, `Char`, `Int`, + * `Long`, `Float`, `Double`, `Boolean`, and `Unit`, in that order. + */ def valueManifests: List[AnyValManifest[_]] = ManifestFactory.valueManifests + /** The manifest for the value type `Byte`, whose `runtimeClass` is `java.lang.Byte.TYPE`. */ val Byte: ManifestFactory.ByteManifest = ManifestFactory.Byte + /** The manifest for the value type `Short`, whose `runtimeClass` is `java.lang.Short.TYPE`. */ val Short: ManifestFactory.ShortManifest = ManifestFactory.Short + /** The manifest for the value type `Char`, whose `runtimeClass` is `java.lang.Character.TYPE`. */ val Char: ManifestFactory.CharManifest = ManifestFactory.Char + /** The manifest for the value type `Int`, whose `runtimeClass` is `java.lang.Integer.TYPE`. */ val Int: ManifestFactory.IntManifest = ManifestFactory.Int + /** The manifest for the value type `Long`, whose `runtimeClass` is `java.lang.Long.TYPE`. */ val Long: ManifestFactory.LongManifest = ManifestFactory.Long + /** The manifest for the value type `Float`, whose `runtimeClass` is `java.lang.Float.TYPE`. */ val Float: ManifestFactory.FloatManifest = ManifestFactory.Float + /** The manifest for the value type `Double`, whose `runtimeClass` is `java.lang.Double.TYPE`. */ val Double: ManifestFactory.DoubleManifest = ManifestFactory.Double + /** The manifest for the value type `Boolean`, whose `runtimeClass` is `java.lang.Boolean.TYPE`. */ val Boolean: ManifestFactory.BooleanManifest = ManifestFactory.Boolean + /** The manifest for the value type `Unit`, whose `runtimeClass` is `java.lang.Void.TYPE`. */ val Unit: ManifestFactory.UnitManifest = ManifestFactory.Unit + /** The manifest for the type `Any`, whose `runtimeClass` is `classOf[java.lang.Object]`. */ val Any: Manifest[scala.Any] = ManifestFactory.Any + /** The manifest for the type `Object`, whose `runtimeClass` is `classOf[java.lang.Object]`. */ val Object: Manifest[java.lang.Object] = ManifestFactory.Object + /** The manifest for the type `AnyRef`, the same instance as `Object`. */ val AnyRef: Manifest[scala.AnyRef] = ManifestFactory.AnyRef + /** The manifest for the type `AnyVal`, whose `runtimeClass` is `classOf[java.lang.Object]`. */ val AnyVal: Manifest[scala.AnyVal] = ManifestFactory.AnyVal + /** The manifest for the type `Null`, whose `runtimeClass` is `classOf[scala.runtime.Null$]`. */ val Null: Manifest[scala.Null] = ManifestFactory.Null + /** The manifest for the type `Nothing`, whose `runtimeClass` is `classOf[scala.runtime.Nothing$]`. */ val Nothing: Manifest[scala.Nothing] = ManifestFactory.Nothing /** Manifest for the singleton type `value.type`. @@ -144,6 +178,14 @@ object Manifest { def classType[T](prefix: Manifest[_], clazz: Predef.Class[_], args: Manifest[_]*): Manifest[T] = ManifestFactory.classType[T](prefix, clazz, args: _*) + /** Manifest for the array type `Array[T]`, where `arg` manifests the element type `T`. + * + * @tparam T the element type of the resulting array manifest; it is not checked against + * `arg`, so a mismatched pair yields a manifest whose represented element type + * differs from its static one + * @param arg the manifest for the element type + * @return the array manifest of `arg` + */ def arrayType[T](arg: Manifest[_]): Manifest[Array[T]] = ManifestFactory.arrayType[T](arg) @@ -181,15 +223,44 @@ object Manifest { // TODO undeprecated until Scala reflection becomes non-experimental // @deprecated("use type tags and manually check the corresponding class or type instead", "2.10.0") +/** The base manifest for Scala's value types. + * + * Value manifests compare with reference identity: a value manifest equals only + * itself, and represents a subtype (`<:<`) only of itself, `Any`, and `AnyVal`. + * + * @tparam T the value type represented by this manifest + * @param toString the name of the value type, used as the string representation of this manifest + */ @SerialVersionUID(1L) abstract class AnyValManifest[T <: AnyVal](override val toString: String) extends Manifest[T] with Equals { + /** Tests whether the type represented by this manifest is a subtype of the type + * represented by `that`. + * + * @param that the manifest to compare against + * @return `true` if `that` is this manifest, `Manifest.Any`, or `Manifest.AnyVal`, + * `false` otherwise + */ override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Manifest.Any) || (that eq Manifest.AnyVal) + /** Tests whether `other` can possibly equal this manifest: only `AnyValManifest` + * instances can. + * + * @param other the value to test + */ override def canEqual(other: Any) = other match { case _: AnyValManifest[_] => true case _ => false } + /** Tests whether `that` is the same instance as this manifest; equality of value + * manifests is reference identity. + * + * @param that the value to compare against + * @return `true` if `that` is this exact instance, `false` otherwise + */ override def equals(that: Any): Boolean = this eq that.asInstanceOf[AnyRef] + /** Returns the identity hash code of this manifest, consistent with its + * reference-identity `equals`. + */ override def hashCode = System.identityHashCode(this) } @@ -201,6 +272,9 @@ abstract class AnyValManifest[T <: AnyVal](override val toString: String) extend * Why so complicated? Read up the comments for `ClassManifestFactory`. */ object ManifestFactory { + /** Returns the manifests for the value types `Byte`, `Short`, `Char`, `Int`, + * `Long`, `Float`, `Double`, `Boolean`, and `Unit`, in that order. + */ def valueManifests: List[AnyValManifest[_]] = List(Byte, Short, Char, Int, Long, Float, Double, Boolean, Unit) @@ -219,6 +293,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Byte } private object ByteManifest extends ByteManifest + /** The manifest for the value type `Byte`, whose `runtimeClass` is `java.lang.Byte.TYPE`. */ val Byte: ByteManifest = ByteManifest @SerialVersionUID(1L) @@ -236,6 +311,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Short } private object ShortManifest extends ShortManifest + /** The manifest for the value type `Short`, whose `runtimeClass` is `java.lang.Short.TYPE`. */ val Short: ShortManifest = ShortManifest @SerialVersionUID(1L) @@ -253,6 +329,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Char } private object CharManifest extends CharManifest + /** The manifest for the value type `Char`, whose `runtimeClass` is `java.lang.Character.TYPE`. */ val Char: CharManifest = CharManifest @SerialVersionUID(1L) @@ -270,6 +347,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Int } private object IntManifest extends IntManifest + /** The manifest for the value type `Int`, whose `runtimeClass` is `java.lang.Integer.TYPE`. */ val Int: IntManifest = IntManifest @SerialVersionUID(1L) @@ -287,6 +365,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Long } private object LongManifest extends LongManifest + /** The manifest for the value type `Long`, whose `runtimeClass` is `java.lang.Long.TYPE`. */ val Long: LongManifest = LongManifest @SerialVersionUID(1L) @@ -304,6 +383,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Float } private object FloatManifest extends FloatManifest + /** The manifest for the value type `Float`, whose `runtimeClass` is `java.lang.Float.TYPE`. */ val Float: FloatManifest = FloatManifest @SerialVersionUID(1L) @@ -322,6 +402,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Double } private object DoubleManifest extends DoubleManifest + /** The manifest for the value type `Double`, whose `runtimeClass` is `java.lang.Double.TYPE`. */ val Double: DoubleManifest = DoubleManifest @SerialVersionUID(1L) @@ -339,6 +420,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Boolean } private object BooleanManifest extends BooleanManifest + /** The manifest for the value type `Boolean`, whose `runtimeClass` is `java.lang.Boolean.TYPE`. */ val Boolean: BooleanManifest = BooleanManifest @SerialVersionUID(1L) @@ -359,6 +441,7 @@ object ManifestFactory { private def readResolve(): Any = Manifest.Unit } private object UnitManifest extends UnitManifest + /** The manifest for the value type `Unit`, whose `runtimeClass` is `java.lang.Void.TYPE`. */ val Unit: UnitManifest = UnitManifest private object AnyManifest extends PhantomManifest[scala.Any](classOf[java.lang.Object], "Any") { @@ -367,6 +450,7 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that eq this) private def readResolve(): Any = Manifest.Any } + /** The manifest for the type `Any`, whose `runtimeClass` is `classOf[java.lang.Object]`. */ val Any: Manifest[scala.Any] = AnyManifest private object ObjectManifest extends PhantomManifest[java.lang.Object](classOf[java.lang.Object], "Object") { @@ -375,8 +459,10 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) private def readResolve(): Any = Manifest.Object } + /** The manifest for the type `Object`, whose `runtimeClass` is `classOf[java.lang.Object]`. */ val Object: Manifest[java.lang.Object] = ObjectManifest + /** The manifest for the type `AnyRef`, the same instance as `Object`. */ val AnyRef: Manifest[scala.AnyRef] = Object private object AnyValManifest extends PhantomManifest[scala.AnyVal](classOf[java.lang.Object], "AnyVal") { @@ -385,6 +471,7 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) private def readResolve(): Any = Manifest.AnyVal } + /** The manifest for the type `AnyVal`, whose `runtimeClass` is `classOf[java.lang.Object]`. */ val AnyVal: Manifest[scala.AnyVal] = AnyValManifest private object NullManifest extends PhantomManifest[scala.Null](classOf[scala.runtime.Null$], "Null") { @@ -394,6 +481,7 @@ object ManifestFactory { (that ne null) && (that ne Nothing) && !(that <:< AnyVal) private def readResolve(): Any = Manifest.Null } + /** The manifest for the type `Null`, whose `runtimeClass` is `classOf[scala.runtime.Null$]`. */ val Null: Manifest[scala.Null] = NullManifest private object NothingManifest extends PhantomManifest[scala.Nothing](classOf[scala.runtime.Nothing$], "Nothing") { @@ -402,6 +490,7 @@ object ManifestFactory { override def <:<(that: ClassManifest[_]): Boolean = (that ne null) private def readResolve(): Any = Manifest.Nothing } + /** The manifest for the type `Nothing`, whose `runtimeClass` is `classOf[scala.runtime.Nothing$]`. */ val Nothing: Manifest[scala.Nothing] = NothingManifest @SerialVersionUID(1L) @@ -474,6 +563,12 @@ object ManifestFactory { argString } + /** Manifest for the array type `Array[T]`, where `arg` manifests the element type `T`. + * + * @tparam T the element type of the array + * @param arg the manifest for the element type `T` + * @return the array manifest of `arg` + */ def arrayType[T](arg: Manifest[_]): Manifest[Array[T]] = arg.asInstanceOf[Manifest[T]].arrayManifest diff --git a/library-js/src/scala/runtime/BoxesRunTime.scala b/library-js/src/scala/runtime/BoxesRunTime.scala index feeb41dba623..d14e4931c42c 100644 --- a/library-js/src/scala/runtime/BoxesRunTime.scala +++ b/library-js/src/scala/runtime/BoxesRunTime.scala @@ -10,6 +10,9 @@ import scala.scalajs.LinkingInfo.{linkTimeIf, moduleKind} /* The declaration of the class is only to make the JVM back-end happy when * compiling the scalalib. */ +/** Never instantiated: this empty class exists only so that the JVM back-end accepts this file + * when compiling the Scala.js standard library. All operations live in the companion object. + */ final class BoxesRunTime object BoxesRunTime { @@ -17,46 +20,242 @@ object BoxesRunTime { // when we upgrade to Scala.js 1.23.0+. private final val WasmModule = 4 + /** Boxes a primitive `Boolean` into a `java.lang.Boolean`. + * + * On Scala.js a `Boolean` is a primitive JavaScript boolean, which is its + * own boxed representation, so the cast reinterprets the value without + * allocating a wrapper. + * + * @param b the primitive value to box + * @return `b` as a `java.lang.Boolean` + */ def boxToBoolean(b: Boolean): java.lang.Boolean = b.asInstanceOf[java.lang.Boolean] + /** Boxes a primitive `Char` into a `java.lang.Character`. + * + * On Scala.js `Char` is the one primitive type with a distinct box class, + * so unlike the other `boxTo` methods this cast creates an actual + * `Character` wrapper around the character value. + * + * @param c the primitive value to box + * @return `c` boxed as a `java.lang.Character` + */ def boxToCharacter(c: Char): java.lang.Character = c.asInstanceOf[java.lang.Character] + /** Boxes a primitive `Byte` by casting it to a boxed representation. + * + * On Scala.js a `Byte` is a primitive JavaScript number, which is its own + * boxed representation, so the cast reinterprets the value without + * allocating a wrapper. + * + * @param b the primitive value to box + * @return `b` in boxed form + */ def boxToByte(b: Byte): java.lang.Boolean = b.asInstanceOf[java.lang.Boolean] + /** Boxes a primitive `Short` into a `java.lang.Short`. + * + * On Scala.js a `Short` is a primitive JavaScript number, which is its + * own boxed representation, so the cast reinterprets the value without + * allocating a wrapper. + * + * @param s the primitive value to box + * @return `s` as a `java.lang.Short` + */ def boxToShort(s: Short): java.lang.Short = s.asInstanceOf[java.lang.Short] + /** Boxes a primitive `Int` into a `java.lang.Integer`. + * + * On Scala.js an `Int` is a primitive JavaScript number, which is its own + * boxed representation, so the cast reinterprets the value without + * allocating a wrapper. + * + * @param i the primitive value to box + * @return `i` as a `java.lang.Integer` + */ def boxToInteger(i: Int): java.lang.Integer = i.asInstanceOf[java.lang.Integer] + /** Boxes a primitive `Long` into a `java.lang.Long`. + * + * On Scala.js a `Long` is already represented by a heap object (a + * `RuntimeLong`), which is its own boxed representation, so the cast + * reinterprets the value without allocating a wrapper. + * + * @param l the primitive value to box + * @return `l` as a `java.lang.Long` + */ def boxToLong(l: Long): java.lang.Long = l.asInstanceOf[java.lang.Long] + /** Boxes a primitive `Float` into a `java.lang.Float`. + * + * On Scala.js a `Float` is a primitive JavaScript number, which is its + * own boxed representation, so the cast reinterprets the value without + * allocating a wrapper. + * + * @param f the primitive value to box + * @return `f` as a `java.lang.Float` + */ def boxToFloat(f: Float): java.lang.Float = f.asInstanceOf[java.lang.Float] + /** Boxes a primitive `Double` into a `java.lang.Double`. + * + * On Scala.js a `Double` is a primitive JavaScript number, which is its + * own boxed representation, so the cast reinterprets the value without + * allocating a wrapper. + * + * @param d the primitive value to box + * @return `d` as a `java.lang.Double` + */ def boxToDouble(d: Double): java.lang.Double = d.asInstanceOf[java.lang.Double] + /** Unboxes a boxed `Boolean` into a primitive `Boolean`. + * + * Implemented as a cast, so Scala.js cast semantics apply: `null` unboxes + * to `false`, matching the JVM runtime. + * + * @param b the boxed value to unbox + * @return the primitive value of `b`, or `false` if `b` is `null` + * @throws ClassCastException if `b` is neither `null` nor a boxed + * `Boolean` (when compliant `asInstanceOf`s are enabled, the + * default) + */ def unboxToBoolean(b: Any): Boolean = b.asInstanceOf[Boolean] + /** Unboxes a boxed `Character` into a primitive `Char`. + * + * Implemented as a cast, so Scala.js cast semantics apply: `null` unboxes + * to the null character (the `Char` with numeric value `0`), matching + * the JVM runtime. + * + * @param c the boxed value to unbox + * @return the primitive value of `c`, or the null character if `c` is + * `null` + * @throws ClassCastException if `c` is neither `null` nor a boxed `Char` + * (when compliant `asInstanceOf`s are enabled, the default) + */ def unboxToChar(c: Any): Char = c.asInstanceOf[Char] + /** Unboxes a boxed `Byte` into a primitive `Byte`. + * + * Implemented as a cast, so Scala.js cast semantics apply: `null` unboxes + * to `0`, and because boxed numbers are primitive JavaScript numbers, any + * boxed numeric value that is a whole number in the `Byte` range passes + * the cast (unlike on the JVM, where only a `java.lang.Byte` would). + * + * @param b the boxed value to unbox + * @return the primitive value of `b`, or `0` if `b` is `null` + * @throws ClassCastException if `b` is neither `null` nor a number in the + * `Byte` range (when compliant `asInstanceOf`s are enabled, the + * default) + */ def unboxToByte(b: Any): Byte = b.asInstanceOf[Byte] + /** Unboxes a boxed `Short` into a primitive `Short`. + * + * Implemented as a cast, so Scala.js cast semantics apply: `null` unboxes + * to `0`, and because boxed numbers are primitive JavaScript numbers, any + * boxed numeric value that is a whole number in the `Short` range passes + * the cast (unlike on the JVM, where only a `java.lang.Short` would). + * + * @param s the boxed value to unbox + * @return the primitive value of `s`, or `0` if `s` is `null` + * @throws ClassCastException if `s` is neither `null` nor a number in the + * `Short` range (when compliant `asInstanceOf`s are enabled, the + * default) + */ def unboxToShort(s: Any): Short = s.asInstanceOf[Short] + /** Unboxes a boxed `Int` into a primitive `Int`. + * + * Implemented as a cast, so Scala.js cast semantics apply: `null` unboxes + * to `0`, and because boxed numbers are primitive JavaScript numbers, any + * boxed numeric value that is a whole number in the `Int` range passes + * the cast (unlike on the JVM, where only a `java.lang.Integer` would). + * + * @param i the boxed value to unbox + * @return the primitive value of `i`, or `0` if `i` is `null` + * @throws ClassCastException if `i` is neither `null` nor a number in the + * `Int` range (when compliant `asInstanceOf`s are enabled, the + * default) + */ def unboxToInt(i: Any): Int = i.asInstanceOf[Int] + /** Unboxes a boxed `Long` into a primitive `Long`. + * + * Implemented as a cast, so Scala.js cast semantics apply: `null` unboxes + * to `0L`. Unlike the other numeric types, a `Long` is represented by a + * `RuntimeLong` instance rather than a JavaScript number, so only an + * actual boxed `Long` passes the cast. + * + * @param l the boxed value to unbox + * @return the primitive value of `l`, or `0L` if `l` is `null` + * @throws ClassCastException if `l` is neither `null` nor a boxed `Long` + * (when compliant `asInstanceOf`s are enabled, the default) + */ def unboxToLong(l: Any): Long = l.asInstanceOf[Long] + /** Unboxes a boxed `Float` into a primitive `Float`. + * + * Implemented as a cast, so Scala.js cast semantics apply: `null` unboxes + * to `0.0f`, and because boxed numbers are primitive JavaScript numbers, + * any boxed numeric value exactly representable as a `Float` passes the + * cast (unlike on the JVM, where only a `java.lang.Float` would). + * + * @param f the boxed value to unbox + * @return the primitive value of `f`, or `0.0f` if `f` is `null` + * @throws ClassCastException if `f` is neither `null` nor a number + * exactly representable as a `Float` (when compliant + * `asInstanceOf`s are enabled, the default) + */ def unboxToFloat(f: Any): Float = f.asInstanceOf[Float] + /** Unboxes a boxed `Double` into a primitive `Double`. + * + * Implemented as a cast, so Scala.js cast semantics apply: `null` unboxes + * to `0.0`, and because every boxed number other than `Long` is a + * primitive JavaScript number, any such boxed numeric value passes the + * cast (unlike on the JVM, where only a `java.lang.Double` would). + * + * @param d the boxed value to unbox + * @return the primitive value of `d`, or `0.0` if `d` is `null` + * @throws ClassCastException if `d` is neither `null` nor a JavaScript + * number (when compliant `asInstanceOf`s are enabled, the + * default) + */ def unboxToDouble(d: Any): Double = d.asInstanceOf[Double] + /** Implements the universal equality test `x == y` for boxed values. + * + * Compares for equality by first trying a cheap fast path and, if that + * does not settle the question, falling back to the full dispatch of + * [[equals2]], which handles `Long`s, characters, `ScalaNumber`s and + * user-defined `equals` methods. + * + * Which fast path is used is chosen at link time by module kind. For Wasm + * modules it is a reference comparison, with a `Double` additionally + * required to equal itself, which rejects `NaN`. Otherwise it is + * JavaScript's strict equality operator (`===`); because boxed numbers are + * primitive JavaScript numbers on Scala.js, that path also equates + * numerically equal values of different boxed types (a boxed `Int` `1` + * equals a boxed `Double` `1.0`), which the reference comparison instead + * leaves to [[equals2]]. Identical references and two `null`s are settled + * by either. The two paths are deliberately aligned and give the same + * answers; they differ only in how much they settle before [[equals2]] is + * consulted. `NaN` fails both, and is also unequal to itself in + * [[equals2]], preserving `Double.NaN != Double.NaN`. + * + * @param x the left operand, may be `null` + * @param y the right operand, may be `null` + * @return `true` if `x` and `y` are equal by universal equality + */ def equals(x: Object, y: Object): Boolean = { linkTimeIf(moduleKind == WasmModule) { if (x eq y) { @@ -73,6 +272,19 @@ object BoxesRunTime { } } + /** The slow path of [[equals]]: universal equality dispatched on the type of `x`. + * + * Routes boxed numbers to [[equalsNumObject]] and boxed characters to + * [[equalsCharObject]], so that numeric values compare equal across boxed + * types; a `null` `x` is equal only to a `null` `y`; any other `x` decides + * with its own `equals` method. On Scala.js the `java.lang.Number` case + * matches every boxed primitive number as well as `ScalaNumber`s such as + * `BigInt` and `BigDecimal`. + * + * @param x the left operand, may be `null` + * @param y the right operand, may be `null` + * @return `true` if `x` and `y` are equal by universal equality + */ @inline // only called by equals(), not by codegen def equals2(x: Object, y: Object): Boolean = { x match { @@ -83,6 +295,18 @@ object BoxesRunTime { } } + /** Compares a boxed number with an arbitrary object for universal equality. + * + * If `y` is also a number, delegates to [[equalsNumNum]]; if `y` is a + * boxed `Character`, compares `xn` numerically against the character's + * integer value. Otherwise a `null` `xn` is equal only to a `null` `y`, + * and any other `xn` decides with its own `equals` method, which lets a + * `ScalaNumber` equate itself with objects of other types. + * + * @param xn the boxed number, may be `null` + * @param y the right operand, may be `null` + * @return `true` if `xn` and `y` are equal by universal equality + */ def equalsNumObject(xn: java.lang.Number, y: Object): Boolean = { y match { case yn: java.lang.Number => equalsNumNum(xn, yn) @@ -95,6 +319,25 @@ object BoxesRunTime { } } + /** Compares two boxed numbers for universal equality, equating equal values across boxed types. + * + * On Scala.js every boxed `Byte`, `Short`, `Integer`, `Float` and `Double` + * is a primitive JavaScript number and matches the `Double` cases, so the + * per-type dispatch of the JVM version collapses to two representations: + * JavaScript numbers and `Long`s. Within and across those two, values are + * compared with primitive `==`, converting a `Long` operand to `Double` + * when the other operand is a `Double`. When exactly one operand is a + * [[scala.math.ScalaNumber]] (such as `BigInt` or `BigDecimal`), that + * operand's `equals` method decides; a primitively-typed operand compared + * with any other kind of `Number` yields `false`, since its `equals` + * could not accept such an argument. If `xn` is itself neither a + * JavaScript number nor a `Long`, its own `equals` method decides. A + * `null` `xn` is equal only to a `null` `yn`. + * + * @param xn the left boxed number, may be `null` + * @param yn the right boxed number, may be `null` + * @return `true` if `xn` and `yn` represent equal numeric values + */ def equalsNumNum(xn: java.lang.Number, yn: java.lang.Number): Boolean = { (xn: Any) match { case xn: Double => @@ -116,6 +359,18 @@ object BoxesRunTime { } } + /** Compares a boxed `Character` with an arbitrary object for universal equality. + * + * Two characters are equal if and only if their `charValue`s are equal. A + * character and a boxed number are compared numerically, using the + * character's integer value. Against any other `y`, a `null` `xc` is + * equal only to a `null` `y`, and a non-`null` `xc` is never equal, since + * its `equals` method could only accept another `Character`. + * + * @param xc the boxed character, may be `null` + * @param y the right operand, may be `null` + * @return `true` if `xc` and `y` are equal by universal equality + */ def equalsCharObject(xc: java.lang.Character, y: Object): Boolean = { y match { case yc: java.lang.Character => xc.charValue() == yc.charValue() @@ -139,18 +394,66 @@ object BoxesRunTime { } } + /** Returns the hash code of a boxed `Long`, consistent with universal equality. + * + * Delegates to `Statics.longHash`: a value in the `Int` range hashes to + * that `Int` itself, and only values outside it fall back to + * `java.lang.Long.hashCode`. Because `==` equates numeric values across + * boxed types (`5L == 5`), equal values must hash alike, so `5L` must + * hash like the boxed `Int` `5`. + * + * @param n the boxed `Long` to hash + * @return the hash code of `n`'s value + */ @inline def hashFromLong(n: java.lang.Long): Int = Statics.longHash(n.asInstanceOf[Long]) + /** Returns the hash code of a boxed `Double`, consistent with universal equality. + * + * Delegates to `Statics.doubleHash`, which hashes a value equal to an + * `Int` as that `Int`, a value equal to some `Long` like that `Long`, a + * value exactly representable as a `Float` via + * `java.lang.Float.hashCode`, and any other value via + * `java.lang.Double.hashCode`. Because `==` equates numeric values across + * boxed types (`5.0 == 5L` and `5.0 == 5`), equal values must hash alike. + * + * @param n the boxed `Double` to hash + * @return the hash code of `n`'s value + */ @inline def hashFromDouble(n: java.lang.Double): Int = Statics.doubleHash(n.asInstanceOf[Double]) + /** Returns the hash code of a boxed `Float`, consistent with universal equality. + * + * Delegates to `Statics.floatHash`, which hashes a value equal to an + * `Int` as that `Int`, a value equal to some `Long` like that `Long`, and + * any other value via `java.lang.Float.hashCode`. Because `==` equates + * numeric values across boxed types (`5.0f == 5L` and `5.0f == 5`), equal + * values must hash alike. + * + * @param n the boxed `Float` to hash + * @return the hash code of `n`'s value + */ @inline def hashFromFloat(n: java.lang.Float): Int = Statics.floatHash(n.asInstanceOf[Float]) + /** Returns the hash code of a boxed number, consistent with universal equality. + * + * On Scala.js every boxed `Byte`, `Short`, `Integer`, `Float` and `Double` + * is a primitive JavaScript number and matches the `Double` case, so the + * dispatch collapses to `Statics.doubleHash` for JavaScript numbers, + * `Statics.longHash` for `Long`s, and the value's own `hashCode` for any + * other `Number` (such as `BigInt` or `BigDecimal`). The `Statics` hashes + * give numerically equal values of different boxed types the same hash, + * as required for values that `==` equates: `5`, `5L` and `5.0` all hash + * to `5`. + * + * @param n the boxed number to hash + * @return the hash code of `n`'s value + */ @inline // called only by ScalaRunTime.hash() def hashFromNumber(n: java.lang.Number): Int = { (n: Any) match { @@ -160,6 +463,15 @@ object BoxesRunTime { } } + /** Returns the hash code of any value, consistent with universal equality. + * + * Delegates to `Statics.anyHash`: `null` hashes to `0`, boxed numbers + * hash by their numeric value so that values `==` equates hash alike, and + * every other value uses its own `hashCode`. + * + * @param a the value to hash, may be `null` + * @return the hash code of `a`, or `0` if `a` is `null` + */ @inline def hashFromObject(a: Object): Int = Statics.anyHash(a) diff --git a/library-js/src/scala/runtime/ScalaRunTime.scala b/library-js/src/scala/runtime/ScalaRunTime.scala index a638061b809b..a7f54030cc4d 100644 --- a/library-js/src/scala/runtime/ScalaRunTime.scala +++ b/library-js/src/scala/runtime/ScalaRunTime.scala @@ -29,6 +29,14 @@ import scala.collection.generic.IsIterable * outside the API and subject to change or removal without notice. */ object ScalaRunTime { + /** Tests whether `x` is an array with at least `atLevel` dimensions. + * + * @param x the value to test; `null` yields `false` + * @param atLevel the number of array dimensions required; the default, 1, accepts any array. + * A value below 1 never holds: the check descends one component type per + * level and fails once it reaches a non-array + * @return `true` if `x` is a non-null array of `atLevel` or more dimensions, `false` otherwise + */ def isArray(x: Any, atLevel: Int = 1): Boolean = x != null && isArrayClass(x.getClass, atLevel) @@ -36,6 +44,14 @@ object ScalaRunTime { clazz != null && clazz.isArray && (atLevel == 1 || isArrayClass(clazz.getComponentType, atLevel - 1)) // A helper method to make my life in the pattern matcher a lot easier. + /** Drops the first `num` elements of a collection-like value, preserving its representation type. + * + * @tparam Repr the representation type of the collection + * @param coll the collection-like value to drop from + * @param num the number of leading elements to drop + * @param iterable evidence for viewing `coll` as an iterable whose collection type conforms to `Repr` + * @return `coll` without its first `num` elements + */ def drop[Repr](coll: Repr, num: Int)(implicit iterable: IsIterable[Repr] { type C <: Repr }): Repr = iterable(coll) drop num @@ -120,6 +136,14 @@ object ScalaRunTime { // TODO: bytecode Object.clone() will in fact work here and avoids // the type switch. See Array_clone comment in BCodeBodyBuilder. + /** Clones a generic array, dispatching on its element type. + * + * A non-array argument fails with a `MatchError`. + * + * @param xs the array to clone + * @return a shallow copy of `xs`, with the same element type and length + * @throws NullPointerException if `xs` is `null` + */ def array_clone(xs: AnyRef): AnyRef = xs match { case x: Array[AnyRef] => x.clone() case x: Array[Int] => x.clone() @@ -150,6 +174,11 @@ object ScalaRunTime { dest } + /** Copies the elements of a sequence into a new `Array[AnyRef]`, boxing primitive values. + * + * @tparam T the element type of the sequence + * @param xs the sequence to copy + */ def toArray[T](xs: scala.collection.Seq[T]) = { val arr = new Array[AnyRef](xs.length) var i = 0 @@ -162,11 +191,29 @@ object ScalaRunTime { // Java bug: https://bugs.java.com/view_bug.do?bug_id=4071957 // More background at ticket #2318. + /** Makes the given method callable reflectively, calling `setAccessible(true)` on it if needed. + * + * Delegates to [[scala.reflect.ensureAccessible]]; a `SecurityException` thrown in the + * attempt is caught and discarded. + * + * @param m the method to make accessible + * @return `m` itself + */ def ensureAccessible(m: JMethod): JMethod = scala.reflect.ensureAccessible(m) + /** Returns the default case-class string representation of `x`: its product prefix + * followed by its elements, comma-separated in parentheses, e.g. `Foo(1,two)`. + * + * @param x the product to render + */ def _toString(x: Product): String = x.productIterator.mkString(x.productPrefix + "(", ",", ")") + /** Returns a hash code for a case class via [[scala.util.hashing.MurmurHash3.productHash]], + * mixing the hash of its `productPrefix` with those of its elements. + * + * @param x the product to hash + */ def _hashCode(x: Product): Int = scala.util.hashing.MurmurHash3.productHash(x) /** A helper for case classes. @@ -201,6 +248,22 @@ object ScalaRunTime { * @return a string representation of arg. */ def stringOf(arg: Any): String = stringOf(arg, scala.Int.MaxValue) + /** Returns a string representation of `arg`, rendering at most `maxElements` elements + * of any collection or array encountered. + * + * `null` is rendered as `"null"`; arrays as `Array(...)`; Scala collections with their + * class name followed by their elements, map entries as `key -> value`; tuples in + * parentheses; the empty string and strings with leading or trailing whitespace in + * double quotes. Elements are rendered recursively by the same rules. Values better + * served by their own `toString` (ranges, sorted collections, views, string builders, + * XML nodes, and iterables that are not strict Scala collections) are rendered with it. + * Truncation to `maxElements` is silent: no ellipsis marks the omitted elements. If + * rendering fails with an `UnsupportedOperationException` or `AssertionError`, falls + * back to `String.valueOf(arg)`. + * + * @param arg the value to stringify + * @param maxElements the maximum number of elements rendered per collection or array + */ def stringOf(arg: Any, maxElements: Int): String = { def packageOf(x: AnyRef) = x.getClass.getPackage match { case null => "" @@ -311,17 +374,80 @@ object ScalaRunTime { // preserving the previous behavior for backward compatibility. // Convert arrays to immutable.ArraySeq for use with Java varargs: + /** Wraps an array in an immutable [[scala.collection.immutable.ArraySeq]] without copying it, + * selecting the `ArraySeq` subclass matching the array's element type at runtime. + * + * Used when the element type is not statically known; the compiler otherwise emits a + * call to one of the type-specific `wrapXArray` methods below. + * + * @tparam T the element type of the array + * @param xs the array to wrap + * @return an `ArraySeq` backed by `xs`, or `null` if `xs` is `null` + */ def genericWrapArray[T](xs: Array[T]): ArraySeq[T] = mapNull(xs, ArraySeq.unsafeWrapArray(xs)) + /** Wraps an array of references in an immutable [[scala.collection.immutable.ArraySeq]] + * without copying it. + * + * @tparam T the reference element type of the array + * @param xs the array to wrap + * @return an `ArraySeq.ofRef` backed by `xs`, the shared empty `ArraySeq` if `xs` is empty, + * or `null` if `xs` is `null` + */ def wrapRefArray[T <: AnyRef | Null](xs: Array[T]): ArraySeq[T] = mapNull(xs, if (xs.length == 0) ArraySeq.empty[AnyRef].asInstanceOf[ArraySeq[T]] else new ArraySeq.ofRef[T](xs)) + /** Wraps an `Array[Int]` in an immutable [[scala.collection.immutable.ArraySeq]] without copying it. + * + * @param xs the array to wrap + * @return an `ArraySeq.ofInt` backed by `xs`, or `null` if `xs` is `null` + */ def wrapIntArray(xs: Array[Int]): ArraySeq[Int] = mapNull(xs, new ArraySeq.ofInt(xs)) + /** Wraps an `Array[Double]` in an immutable [[scala.collection.immutable.ArraySeq]] without copying it. + * + * @param xs the array to wrap + * @return an `ArraySeq.ofDouble` backed by `xs`, or `null` if `xs` is `null` + */ def wrapDoubleArray(xs: Array[Double]): ArraySeq[Double] = mapNull(xs, new ArraySeq.ofDouble(xs)) + /** Wraps an `Array[Long]` in an immutable [[scala.collection.immutable.ArraySeq]] without copying it. + * + * @param xs the array to wrap + * @return an `ArraySeq.ofLong` backed by `xs`, or `null` if `xs` is `null` + */ def wrapLongArray(xs: Array[Long]): ArraySeq[Long] = mapNull(xs, new ArraySeq.ofLong(xs)) + /** Wraps an `Array[Float]` in an immutable [[scala.collection.immutable.ArraySeq]] without copying it. + * + * @param xs the array to wrap + * @return an `ArraySeq.ofFloat` backed by `xs`, or `null` if `xs` is `null` + */ def wrapFloatArray(xs: Array[Float]): ArraySeq[Float] = mapNull(xs, new ArraySeq.ofFloat(xs)) + /** Wraps an `Array[Char]` in an immutable [[scala.collection.immutable.ArraySeq]] without copying it. + * + * @param xs the array to wrap + * @return an `ArraySeq.ofChar` backed by `xs`, or `null` if `xs` is `null` + */ def wrapCharArray(xs: Array[Char]): ArraySeq[Char] = mapNull(xs, new ArraySeq.ofChar(xs)) + /** Wraps an `Array[Byte]` in an immutable [[scala.collection.immutable.ArraySeq]] without copying it. + * + * @param xs the array to wrap + * @return an `ArraySeq.ofByte` backed by `xs`, or `null` if `xs` is `null` + */ def wrapByteArray(xs: Array[Byte]): ArraySeq[Byte] = mapNull(xs, new ArraySeq.ofByte(xs)) + /** Wraps an `Array[Short]` in an immutable [[scala.collection.immutable.ArraySeq]] without copying it. + * + * @param xs the array to wrap + * @return an `ArraySeq.ofShort` backed by `xs`, or `null` if `xs` is `null` + */ def wrapShortArray(xs: Array[Short]): ArraySeq[Short] = mapNull(xs, new ArraySeq.ofShort(xs)) + /** Wraps an `Array[Boolean]` in an immutable [[scala.collection.immutable.ArraySeq]] without copying it. + * + * @param xs the array to wrap + * @return an `ArraySeq.ofBoolean` backed by `xs`, or `null` if `xs` is `null` + */ def wrapBooleanArray(xs: Array[Boolean]): ArraySeq[Boolean] = mapNull(xs, new ArraySeq.ofBoolean(xs)) + /** Wraps an `Array[Unit]` in an immutable [[scala.collection.immutable.ArraySeq]] without copying it. + * + * @param xs the array to wrap + * @return an `ArraySeq.ofUnit` backed by `xs`, or `null` if `xs` is `null` + */ def wrapUnitArray(xs: Array[Unit]): ArraySeq[Unit] = mapNull(xs, new ArraySeq.ofUnit(xs)) } diff --git a/library-js/src/scala/runtime/VarArgsBuilder.scala b/library-js/src/scala/runtime/VarArgsBuilder.scala index 8d5304887ac9..8863f6a5e8be 100644 --- a/library-js/src/scala/runtime/VarArgsBuilder.scala +++ b/library-js/src/scala/runtime/VarArgsBuilder.scala @@ -7,10 +7,54 @@ import scala.scalajs.js import scala.scalajs.runtime.toScalaVarArgs import scala.scalajs.LinkingInfo.{isWebAssembly, linkTimeIf} +/** A builder used by compiler-generated code to construct the sequences produced + * by sequence literals that contain spread operators. + * + * The compiler (in `PostTyper`) translates a sequence literal such as + * `[1, xs*, 2, ys*]` into + * {{{ + * scala.runtime.VarArgsBuilder.ofInt(2 + xs.length + ys.length) + * .add(1) + * .addSeq(xs) + * .add(2) + * .addSeq(ys) + * .result() + * }}} + * choosing the companion-object factory that matches the element type. Each + * factory receives the total number of elements up front, and the generated + * code adds exactly that many elements before calling `result()` once. + * + * @tparam T the type of the elements of the sequence being built + */ sealed abstract class VarArgsBuilder[T]: + /** Adds a single element to the sequence being built. + * + * The compiler emits one call to this method per non-spread element of the + * sequence literal. + * + * @param elem the element to add + * @return this builder + */ def add(elem: T): this.type + /** Adds every element of a sequence, in order, to the sequence being built. + * + * The compiler emits a call to this method for each spread element `xs*` + * whose `xs` is a `Seq`. + * + * @param elems the sequence of elements to add + * @return this builder + */ def addSeq(elems: Seq[T]): this.type + /** Adds every element of an array, in order, to the sequence being built. + * + * The compiler emits a call to this method for each spread element `xs*` + * whose `xs` is an array. + * + * @param elems the array of elements to add + * @return this builder + */ def addArray(elems: Array[T]): this.type + /** Returns a sequence of the elements that were added to this builder, in order. */ def result(): Seq[T] object VarArgsBuilder: @@ -32,6 +76,18 @@ object VarArgsBuilder: def result(): Seq[T] = toScalaVarArgs(array) + /** Returns a builder for elements of a type not statically known to be a + * primitive or a reference type, such as an unbounded type parameter. + * + * The implementation is chosen at link time: when targeting WebAssembly, a + * builder backed by a fixed-size `Array[AnyRef]` of length `n`; when + * targeting JavaScript, a builder backed by a growable `js.Array`, in which + * case `n` is unused. + * + * @tparam T the element type + * @param n the exact number of elements that will be added to the builder; + * used only when targeting WebAssembly + */ @inline def generic[T](n: Int): VarArgsBuilder[T] = linkTimeIf[VarArgsBuilder[T]](isWebAssembly)(GenericVarArgsBuilder(n))(JSVarArgsBuilder()) @@ -56,6 +112,17 @@ object VarArgsBuilder: i += 1 this + /** Returns a builder for elements of a reference type. + * + * The implementation is chosen at link time: when targeting WebAssembly, a + * builder backed by a fixed-size `Array[AnyRef]` of length `n`; when + * targeting JavaScript, a builder backed by a growable `js.Array`, in which + * case `n` is unused. + * + * @tparam T the element type, a reference type + * @param n the exact number of elements that will be added to the builder; + * used only when targeting WebAssembly + */ def ofRef[T <: AnyRef](n: Int): VarArgsBuilder[T] = linkTimeIf[VarArgsBuilder[T]](isWebAssembly)(RefVarArgsBuilder(n))(JSVarArgsBuilder()) @@ -79,6 +146,16 @@ object VarArgsBuilder: i += 1 this + /** Returns a builder for `Byte` elements. + * + * The implementation is chosen at link time: when targeting WebAssembly, a + * builder backed by a fixed-size `Array[Byte]` of length `n`; when + * targeting JavaScript, a builder backed by a growable `js.Array`, in which + * case `n` is unused. + * + * @param n the exact number of elements that will be added to the builder; + * used only when targeting WebAssembly + */ def ofByte(n: Int): VarArgsBuilder[Byte] = linkTimeIf[VarArgsBuilder[Byte]](isWebAssembly)(ByteVarArgsBuilder(n))(JSVarArgsBuilder()) @@ -102,6 +179,16 @@ object VarArgsBuilder: i += 1 this + /** Returns a builder for `Short` elements. + * + * The implementation is chosen at link time: when targeting WebAssembly, a + * builder backed by a fixed-size `Array[Short]` of length `n`; when + * targeting JavaScript, a builder backed by a growable `js.Array`, in which + * case `n` is unused. + * + * @param n the exact number of elements that will be added to the builder; + * used only when targeting WebAssembly + */ def ofShort(n: Int): VarArgsBuilder[Short] = linkTimeIf[VarArgsBuilder[Short]](isWebAssembly)(ShortVarArgsBuilder(n))(JSVarArgsBuilder()) @@ -125,6 +212,16 @@ object VarArgsBuilder: i += 1 this + /** Returns a builder for `Char` elements. + * + * The implementation is chosen at link time: when targeting WebAssembly, a + * builder backed by a fixed-size `Array[Char]` of length `n`; when + * targeting JavaScript, a builder backed by a growable `js.Array`, in which + * case `n` is unused. + * + * @param n the exact number of elements that will be added to the builder; + * used only when targeting WebAssembly + */ def ofChar(n: Int): VarArgsBuilder[Char] = linkTimeIf[VarArgsBuilder[Char]](isWebAssembly)(CharVarArgsBuilder(n))(JSVarArgsBuilder()) @@ -148,6 +245,16 @@ object VarArgsBuilder: i += 1 this + /** Returns a builder for `Int` elements. + * + * The implementation is chosen at link time: when targeting WebAssembly, a + * builder backed by a fixed-size `Array[Int]` of length `n`; when targeting + * JavaScript, a builder backed by a growable `js.Array`, in which case `n` + * is unused. + * + * @param n the exact number of elements that will be added to the builder; + * used only when targeting WebAssembly + */ def ofInt(n: Int): VarArgsBuilder[Int] = linkTimeIf[VarArgsBuilder[Int]](isWebAssembly)(IntVarArgsBuilder(n))(JSVarArgsBuilder()) @@ -171,6 +278,16 @@ object VarArgsBuilder: i += 1 this + /** Returns a builder for `Long` elements. + * + * The implementation is chosen at link time: when targeting WebAssembly, a + * builder backed by a fixed-size `Array[Long]` of length `n`; when + * targeting JavaScript, a builder backed by a growable `js.Array`, in which + * case `n` is unused. + * + * @param n the exact number of elements that will be added to the builder; + * used only when targeting WebAssembly + */ def ofLong(n: Int): VarArgsBuilder[Long] = linkTimeIf[VarArgsBuilder[Long]](isWebAssembly)(LongVarArgsBuilder(n))(JSVarArgsBuilder()) @@ -194,6 +311,16 @@ object VarArgsBuilder: i += 1 this + /** Returns a builder for `Float` elements. + * + * The implementation is chosen at link time: when targeting WebAssembly, a + * builder backed by a fixed-size `Array[Float]` of length `n`; when + * targeting JavaScript, a builder backed by a growable `js.Array`, in which + * case `n` is unused. + * + * @param n the exact number of elements that will be added to the builder; + * used only when targeting WebAssembly + */ def ofFloat(n: Int): VarArgsBuilder[Float] = linkTimeIf[VarArgsBuilder[Float]](isWebAssembly)(FloatVarArgsBuilder(n))(JSVarArgsBuilder()) @@ -217,6 +344,16 @@ object VarArgsBuilder: i += 1 this + /** Returns a builder for `Double` elements. + * + * The implementation is chosen at link time: when targeting WebAssembly, a + * builder backed by a fixed-size `Array[Double]` of length `n`; when + * targeting JavaScript, a builder backed by a growable `js.Array`, in which + * case `n` is unused. + * + * @param n the exact number of elements that will be added to the builder; + * used only when targeting WebAssembly + */ def ofDouble(n: Int): VarArgsBuilder[Double] = linkTimeIf[VarArgsBuilder[Double]](isWebAssembly)(DoubleVarArgsBuilder(n))(JSVarArgsBuilder()) @@ -240,6 +377,16 @@ object VarArgsBuilder: i += 1 this + /** Returns a builder for `Boolean` elements. + * + * The implementation is chosen at link time: when targeting WebAssembly, a + * builder backed by a fixed-size `Array[Boolean]` of length `n`; when + * targeting JavaScript, a builder backed by a growable `js.Array`, in which + * case `n` is unused. + * + * @param n the exact number of elements that will be added to the builder; + * used only when targeting WebAssembly + */ def ofBoolean(n: Int): VarArgsBuilder[Boolean] = linkTimeIf[VarArgsBuilder[Boolean]](isWebAssembly)(BooleanVarArgsBuilder(n))(JSVarArgsBuilder()) @@ -263,6 +410,16 @@ object VarArgsBuilder: i += 1 this + /** Returns a builder for `Unit` elements. + * + * The implementation is chosen at link time: when targeting WebAssembly, a + * builder backed by a fixed-size `Array[Unit]` of length `n`; when + * targeting JavaScript, a builder backed by a growable `js.Array`, in which + * case `n` is unused. + * + * @param n the exact number of elements that will be added to the builder; + * used only when targeting WebAssembly + */ def ofUnit(n: Int): VarArgsBuilder[Unit] = linkTimeIf[VarArgsBuilder[Unit]](isWebAssembly)(UnitVarArgsBuilder(n))(JSVarArgsBuilder()) diff --git a/library-js/src/scala/scalajs/js/internal/UnitOps.scala b/library-js/src/scala/scalajs/js/internal/UnitOps.scala index b6174caeed18..9864849a01fc 100644 --- a/library-js/src/scala/scalajs/js/internal/UnitOps.scala +++ b/library-js/src/scala/scalajs/js/internal/UnitOps.scala @@ -4,5 +4,13 @@ import scala.scalajs.js /** Under -scalajs, this object is part of the implicit scope of `scala.Unit`. */ object UnitOps: + /** Converts a value of the union type `A | Unit`, which is how `js.UndefOr[A]` + * is interpreted under `-scalajs`, to a `js.UndefOrOps[A]` providing + * option-like operations such as `map`, `getOrElse` and `foreach`. + * + * @tparam A the type of the value when it is defined + * @param x the value to wrap + * @return a `js.UndefOrOps[A]` wrapping `x` + */ implicit def unitOrOps[A](x: A | Unit): js.UndefOrOps[A] = new js.UndefOrOps(x) diff --git a/library-js/src/scala/scalajs/runtime/AnonFunctionXXL.scala b/library-js/src/scala/scalajs/runtime/AnonFunctionXXL.scala index aa08afdce323..8da5d0734ce5 100644 --- a/library-js/src/scala/scalajs/runtime/AnonFunctionXXL.scala +++ b/library-js/src/scala/scalajs/runtime/AnonFunctionXXL.scala @@ -21,5 +21,13 @@ package scala.scalajs.runtime * NewLambda(AnonFunctionXXL, ..., (closureParam: Array[Object]) => closureBody) * which provides the best performance for old code. */ +/** The runtime representation of anonymous functions of arity above 22 in code + * compiled by Scala.js before version 1.19, backed by a function that takes + * the arguments as a single array. + * + * Retained only so that binaries compiled against earlier versions keep + * linking; the IR deserializer rewrites allocations of this class into + * `NewLambda` nodes. + */ @deprecated("used by the codegen before Scala.js 1.19", since = "3.7.0") sealed abstract class AnonFunctionXXL extends scala.runtime.FunctionXXL diff --git a/library-js/src/scala/util/DynamicVariable.scala b/library-js/src/scala/util/DynamicVariable.scala index 4b31786f984c..89f344dfd133 100644 --- a/library-js/src/scala/util/DynamicVariable.scala +++ b/library-js/src/scala/util/DynamicVariable.scala @@ -77,5 +77,6 @@ class DynamicVariable[T](init: T) { */ def value_=(newval: T) = v = newval + /** Returns a string representation of the form `DynamicVariable(value)`, where `value` is the current value. */ override def toString: String = "DynamicVariable(" + value + ")" } diff --git a/library-js/src/scala/util/control/NoStackTrace.scala b/library-js/src/scala/util/control/NoStackTrace.scala index e6023f3738c7..04ce8b9dc83d 100644 --- a/library-js/src/scala/util/control/NoStackTrace.scala +++ b/library-js/src/scala/util/control/NoStackTrace.scala @@ -26,12 +26,23 @@ import scala.language.`2.13` * @since 2.8 */ trait NoStackTrace extends Throwable { + /** Overrides the default stack trace filling behavior to optionally suppress stack traces for efficiency. + * + * @return this `Throwable` instance without filling in the stack trace if suppression is enabled, otherwise the result of the superclass implementation + */ override def fillInStackTrace(): Throwable = if (NoStackTrace.noSuppression) super.fillInStackTrace() else this } object NoStackTrace { + /** Returns whether stack trace suppression is disabled globally. + * + * On Scala.js this is always `false`, because the system property that + * disables suppression on the JVM is not supported. + * + * @return `true` if stack trace suppression is disabled, `false` otherwise + */ final def noSuppression = _noSuppression // two-stage init to make checkinit happy, since sys.SystemProperties.noTraceSupression.value calls back into NoStackTrace.noSuppression diff --git a/library/src/scala/collection/convert/AsJavaExtensions.scala b/library/src/scala/collection/convert/AsJavaExtensions.scala index 67f127d9e042..069ad5ec339c 100644 --- a/library/src/scala/collection/convert/AsJavaExtensions.scala +++ b/library/src/scala/collection/convert/AsJavaExtensions.scala @@ -24,6 +24,16 @@ import java.{lang => jl, util => ju} trait AsJavaExtensions { import scala.jdk.javaapi.{CollectionConverters => conv} + /** Provides `asJava` and `asJavaEnumeration` extension methods that convert a Scala `Iterator` + * to a Java `Iterator` or `Enumeration`. + * + * Each conversion returns a wrapper, not a copy: advancing the result consumes the underlying + * Scala iterator, and vice versa. An iterator that was itself obtained through the + * corresponding `asScala` conversion is unwrapped, returning the original Java object. + * + * @tparam A the element type of the iterator + * @param i the Scala `Iterator` to convert + */ implicit class IteratorHasAsJava[A](i: Iterator[A]) { /** Converts a Scala `Iterator` to a Java `Iterator`, see * [[AsJavaConverters.asJava[A](i:Iterator[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. @@ -36,6 +46,17 @@ trait AsJavaExtensions { def asJavaEnumeration: ju.Enumeration[A] = conv.asJavaEnumeration(i) } + /** Provides `asJava` and `asJavaCollection` extension methods that convert a Scala `Iterable` + * to a Java `Iterable` or `Collection`. + * + * Each conversion returns a wrapper, not a copy: the result is backed by the original Scala + * collection, so changes to that collection are visible through the Java view. An iterable + * that was itself obtained through the corresponding `asScala` conversion is unwrapped, + * returning the original Java object. + * + * @tparam A the element type of the collection + * @param i the Scala `Iterable` to convert + */ implicit class IterableHasAsJava[A](i: Iterable[A]) { /** Converts a Scala `Iterable` to a Java `Iterable`, see * [[AsJavaConverters.asJava[A](i:Iterable[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. @@ -48,6 +69,16 @@ trait AsJavaExtensions { def asJavaCollection: ju.Collection[A] = conv.asJavaCollection(i) } + /** Provides the `asJava` extension method that converts a Scala mutable `Buffer` to a Java + * `List`. + * + * The conversion returns a wrapper, not a copy: changes made through either interface are + * visible through the other. A buffer that was itself obtained through `asScala` is + * unwrapped, returning the original Java `List`. + * + * @tparam A the element type of the buffer + * @param b the Scala `Buffer` to convert + */ implicit class BufferHasAsJava[A](b: mutable.Buffer[A]) { /** Converts a Scala `Buffer` to a Java `List`, see * [[AsJavaConverters.asJava[A](b:scala\.collection\.mutable\.Buffer[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. @@ -55,6 +86,16 @@ trait AsJavaExtensions { def asJava: ju.List[A] = conv.asJava(b) } + /** Provides the `asJava` extension method that converts a Scala mutable `Seq` to a Java + * `List`. + * + * The conversion returns a wrapper, not a copy: changes made through either interface are + * visible through the other. A sequence that was itself obtained through `asScala` is + * unwrapped, returning the original Java `List`. + * + * @tparam A the element type of the sequence + * @param s the Scala mutable `Seq` to convert + */ implicit class MutableSeqHasAsJava[A](s: mutable.Seq[A]) { /** Converts a Scala `Seq` to a Java `List`, see * [[AsJavaConverters.asJava[A](s:scala\.collection\.mutable\.Seq[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. @@ -62,6 +103,15 @@ trait AsJavaExtensions { def asJava: ju.List[A] = conv.asJava(s) } + /** Provides the `asJava` extension method that converts a Scala `Seq` to a Java `List`. + * + * The conversion returns a wrapper, not a copy: the result is backed by the original Scala + * sequence, so changes to that sequence are visible through the Java view. A sequence that + * was itself obtained through `asScala` is unwrapped, returning the original Java `List`. + * + * @tparam A the element type of the sequence + * @param s the Scala `Seq` to convert + */ implicit class SeqHasAsJava[A](s: Seq[A]) { /** Converts a Scala `Seq` to a Java `List`, see * [[AsJavaConverters.asJava[A](s:scala\.collection\.Seq[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. @@ -69,6 +119,15 @@ trait AsJavaExtensions { def asJava: ju.List[A] = conv.asJava(s) } + /** Provides the `asJava` extension method that converts a Scala mutable `Set` to a Java `Set`. + * + * The conversion returns a wrapper, not a copy: changes made through either interface are + * visible through the other. A set that was itself obtained through `asScala` is unwrapped, + * returning the original Java `Set`. + * + * @tparam A the element type of the set + * @param s the Scala mutable `Set` to convert + */ implicit class MutableSetHasAsJava[A](s: mutable.Set[A]) { /** Converts a Scala `mutable.Set` to a Java `Set`, see * [[AsJavaConverters.asJava[A](s:scala\.collection\.mutable\.Set[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. @@ -76,6 +135,15 @@ trait AsJavaExtensions { def asJava: ju.Set[A] = conv.asJava(s) } + /** Provides the `asJava` extension method that converts a Scala `Set` to a Java `Set`. + * + * The conversion returns a wrapper, not a copy: the result is backed by the original Scala + * set, so changes to that set are visible through the Java view. A set that was itself + * obtained through `asScala` is unwrapped, returning the original Java `Set`. + * + * @tparam A the element type of the set + * @param s the Scala `Set` to convert + */ implicit class SetHasAsJava[A](s: Set[A]) { /** Converts a Scala `Set` to a Java `Set`, see * [[AsJavaConverters.asJava[A](s:scala\.collection\.Set[A])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. @@ -83,6 +151,17 @@ trait AsJavaExtensions { def asJava: ju.Set[A] = conv.asJava(s) } + /** Provides `asJava` and `asJavaDictionary` extension methods that convert a Scala mutable + * `Map` to a Java `Map` or `Dictionary`. + * + * Each conversion returns a wrapper, not a copy: changes made through either interface are + * visible through the other. A map that was itself obtained through the corresponding + * `asScala` conversion is unwrapped, returning the original Java object. + * + * @tparam K the key type of the map + * @tparam V the value type of the map + * @param m the Scala mutable `Map` to convert + */ implicit class MutableMapHasAsJava[K, V](m: mutable.Map[K, V]) { /** Converts a Scala `mutable.Map` to a Java `Map`, see * [[AsJavaConverters.asJava[K,V](m:scala\.collection\.mutable\.Map[K,V])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. @@ -95,6 +174,16 @@ trait AsJavaExtensions { def asJavaDictionary: ju.Dictionary[K, V] = conv.asJavaDictionary(m) } + /** Provides the `asJava` extension method that converts a Scala `Map` to a Java `Map`. + * + * The conversion returns a wrapper, not a copy: the result is backed by the original Scala + * map, so changes to that map are visible through the Java view. A map that was itself + * obtained through `asScala` is unwrapped, returning the original Java `Map`. + * + * @tparam K the key type of the map + * @tparam V the value type of the map + * @param m the Scala `Map` to convert + */ implicit class MapHasAsJava[K, V](m: Map[K, V]) { /** Converts a Scala `Map` to a Java `Map`, see * [[AsJavaConverters.asJava[K,V](m:scala\.collection\.Map[K,V])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. @@ -102,6 +191,17 @@ trait AsJavaExtensions { def asJava: ju.Map[K, V] = conv.asJava(m) } + /** Provides the `asJava` extension method that converts a Scala `concurrent.Map` to a Java + * `ConcurrentMap`. + * + * The conversion returns a wrapper, not a copy: changes made through either interface are + * visible through the other. A map that was itself obtained through `asScala` is unwrapped, + * returning the original Java `ConcurrentMap`. + * + * @tparam K the key type of the map + * @tparam V the value type of the map + * @param m the Scala `concurrent.Map` to convert + */ implicit class ConcurrentMapHasAsJava[K, V](m: concurrent.Map[K, V]) { /** Converts a Scala `concurrent.Map` to a Java `ConcurrentMap`, see * [[AsJavaConverters.asJava[K,V](m:scala\.collection\.concurrent\.Map[K,V])* `scala.jdk.javaapi.CollectionConverters.asJava`]]. diff --git a/library/src/scala/collection/convert/AsScalaExtensions.scala b/library/src/scala/collection/convert/AsScalaExtensions.scala index 3ef08f0690f3..ab47ec603894 100644 --- a/library/src/scala/collection/convert/AsScalaExtensions.scala +++ b/library/src/scala/collection/convert/AsScalaExtensions.scala @@ -24,6 +24,16 @@ import java.{lang => jl, util => ju} trait AsScalaExtensions { import scala.jdk.javaapi.{CollectionConverters => conv} + /** Provides the `asScala` extension method that converts a Java `Iterator` to a Scala + * `Iterator`. + * + * The conversion returns a wrapper, not a copy: advancing the result consumes the underlying + * Java iterator, and vice versa. An iterator that was itself obtained through `asJava` is + * unwrapped, returning the original Scala `Iterator`. + * + * @tparam A the element type of the iterator + * @param i the Java `Iterator` to convert + */ implicit class IteratorHasAsScala[A](i: ju.Iterator[A]) { /** Converts a Java `Iterator` to a Scala `Iterator`, see * [[AsScalaConverters.asScala[A](i:java\.util\.Iterator[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. @@ -31,6 +41,16 @@ trait AsScalaExtensions { def asScala: Iterator[A] = conv.asScala(i) } + /** Provides the `asScala` extension method that converts a Java `Enumeration` to a Scala + * `Iterator`. + * + * The conversion returns a wrapper, not a copy: advancing the result consumes the underlying + * Java enumeration, and vice versa. An enumeration that was itself obtained through + * `asJavaEnumeration` is unwrapped, returning the original Scala `Iterator`. + * + * @tparam A the element type of the enumeration + * @param e the Java `Enumeration` to convert + */ implicit class EnumerationHasAsScala[A](e: ju.Enumeration[A]) { /** Converts a Java `Enumeration` to a Scala `Iterator`, see * [[AsScalaConverters.asScala[A](e:java\.util\.Enumeration[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. @@ -38,6 +58,16 @@ trait AsScalaExtensions { def asScala: Iterator[A] = conv.asScala(e) } + /** Provides the `asScala` extension method that converts a Java `Iterable` to a Scala + * `Iterable`. + * + * The conversion returns a wrapper, not a copy: the result is backed by the original Java + * iterable, so changes to it are visible through the Scala view. An iterable that was itself + * obtained through `asJava` is unwrapped, returning the original Scala `Iterable`. + * + * @tparam A the element type of the iterable + * @param i the Java `Iterable` to convert + */ implicit class IterableHasAsScala[A](i: jl.Iterable[A]) { /** Converts a Java `Iterable` to a Scala `Iterable`, see * [[AsScalaConverters.asScala[A](i:Iterable[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. @@ -45,6 +75,17 @@ trait AsScalaExtensions { def asScala: Iterable[A] = conv.asScala(i) } + /** Provides the `asScala` extension method that converts a Java `Collection` to a Scala + * `Iterable`. + * + * The conversion returns a wrapper, not a copy: the result is backed by the original Java + * collection, so changes to it are visible through the Scala view. A collection that was + * itself obtained through `asJavaCollection` is unwrapped, returning the original Scala + * `Iterable`. + * + * @tparam A the element type of the collection + * @param c the Java `Collection` to convert + */ implicit class CollectionHasAsScala[A](c: ju.Collection[A]) { /** Converts a Java `Collection` to a Scala `Iterable`, see * [[AsScalaConverters.asScala[A](c:java\.util\.Collection[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. @@ -52,6 +93,16 @@ trait AsScalaExtensions { def asScala: Iterable[A] = conv.asScala(c) } + /** Provides the `asScala` extension method that converts a Java `List` to a Scala mutable + * `Buffer`. + * + * The conversion returns a wrapper, not a copy: changes made through either interface are + * visible through the other. A list that was itself obtained by calling `asJava` on a Scala + * `Buffer` is unwrapped, returning that original `Buffer`. + * + * @tparam A the element type of the list + * @param l the Java `List` to convert + */ implicit class ListHasAsScala[A](l: ju.List[A]) { /** Converts a Java `List` to a Scala `Buffer`, see * [[AsScalaConverters.asScala[A](l:java\.util\.List[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. @@ -59,6 +110,16 @@ trait AsScalaExtensions { def asScala: mutable.Buffer[A] = conv.asScala(l) } + /** Provides the `asScala` extension method that converts a Java `Set` to a Scala mutable + * `Set`. + * + * The conversion returns a wrapper, not a copy: changes made through either interface are + * visible through the other. A set that was itself obtained by calling `asJava` on a Scala + * mutable `Set` is unwrapped, returning that original `Set`. + * + * @tparam A the element type of the set + * @param s the Java `Set` to convert + */ implicit class SetHasAsScala[A](s: ju.Set[A]) { /** Converts a Java `Set` to a Scala `Set`, see * [[AsScalaConverters.asScala[A](s:java\.util\.Set[A])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. @@ -66,6 +127,17 @@ trait AsScalaExtensions { def asScala: mutable.Set[A] = conv.asScala(s) } + /** Provides the `asScala` extension method that converts a Java `Map` to a Scala mutable + * `Map`. + * + * The conversion returns a wrapper, not a copy: changes made through either interface are + * visible through the other. A map that was itself obtained by calling `asJava` on a Scala + * mutable `Map` is unwrapped, returning that original `Map`. + * + * @tparam K the key type of the map + * @tparam V the value type of the map + * @param m the Java `Map` to convert + */ implicit class MapHasAsScala[K, V](m: ju.Map[K, V]) { /** Converts a Java `Map` to a Scala `Map`, see * [[AsScalaConverters.asScala[A,B](m:java\.util\.Map[A,B])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. @@ -73,6 +145,17 @@ trait AsScalaExtensions { def asScala: mutable.Map[K, V] = conv.asScala(m) } + /** Provides the `asScala` extension method that converts a Java `ConcurrentMap` to a Scala + * `concurrent.Map`. + * + * The conversion returns a wrapper, not a copy: changes made through either interface are + * visible through the other. A map that was itself obtained through `asJava` is unwrapped, + * returning the original Scala `concurrent.Map`. + * + * @tparam K the key type of the map + * @tparam V the value type of the map + * @param m the Java `ConcurrentMap` to convert + */ implicit class ConcurrentMapHasAsScala[K, V](m: juc.ConcurrentMap[K, V]) { /** Converts a Java `ConcurrentMap` to a Scala `concurrent.Map`, see * [[AsScalaConverters.asScala[A,B](m:java\.util\.concurrent\.ConcurrentMap[A,B])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. @@ -80,6 +163,17 @@ trait AsScalaExtensions { def asScala: concurrent.Map[K, V] = conv.asScala(m) } + /** Provides the `asScala` extension method that converts a Java `Dictionary` to a Scala + * mutable `Map`. + * + * The conversion returns a wrapper, not a copy: changes made through either interface are + * visible through the other. A dictionary that was itself obtained through + * `asJavaDictionary` is unwrapped, returning the original Scala `Map`. + * + * @tparam K the key type of the dictionary + * @tparam V the value type of the dictionary + * @param d the Java `Dictionary` to convert + */ implicit class DictionaryHasAsScala[K, V](d: ju.Dictionary[K, V]) { /** Converts a Java `Dictionary` to a Scala `Map`, see * [[AsScalaConverters.asScala[A,B](d:java\.util\.Dictionary[A,B])* `scala.jdk.javaapi.CollectionConverters.asScala`]]. @@ -87,6 +181,19 @@ trait AsScalaExtensions { def asScala: mutable.Map[K, V] = conv.asScala(d) } + /** Provides the `asScala` extension method that converts a Java `Properties` to a Scala + * mutable `Map[String, String]`. + * + * The conversion returns a wrapper, not a copy: changes made through either interface are + * visible through the other. This conversion is one-way; there is no corresponding `asJava` + * conversion to `Properties`, and the result is always a new wrapper. + * + * The wrapper exposes only the `Properties` object's own entries; the defaults it may have + * been constructed with are not consulted. It also assumes every entry has a `String` key + * and value, so an entry of any other type can make its operations fail. + * + * @param i the Java `Properties` to convert + */ implicit class PropertiesHasAsScala(i: ju.Properties) { /** Converts a Java `Properties` to a Scala `Map`, see * [[AsScalaConverters.asScala(p:java\.util\.Properties)* `scala.jdk.javaapi.CollectionConverters.asScala`]]. diff --git a/library/src/scala/collection/convert/StreamExtensions.scala b/library/src/scala/collection/convert/StreamExtensions.scala index 62ea7da6811e..ab7fcdbf0906 100644 --- a/library/src/scala/collection/convert/StreamExtensions.scala +++ b/library/src/scala/collection/convert/StreamExtensions.scala @@ -33,6 +33,12 @@ trait StreamExtensions { this: StreamExtensions => // collections + /** Provides the `asJavaSeqStream` extension method that creates a sequential Java Stream over + * a Scala collection, via the collection's [[Stepper]]. + * + * @tparam A the element type of the collection + * @param cc the collection to create a Stream for + */ implicit class IterableHasSeqStream[A](cc: IterableOnce[A]) { /** Creates a sequential [[java.util.stream.Stream Java Stream]] for this collection. If the * collection contains primitive values, a corresponding specialized Stream is returned (e.g., @@ -49,8 +55,27 @@ trait StreamExtensions { } // Not `CC[X] <: IterableOnce[X]`, but `C` with an extra constraint, to support non-parametric classes like IntAccumulator + /** Provides the `asJavaParStream` extension method that creates a parallel Java Stream over a + * Scala collection, via the collection's [[Stepper]]. + * + * The receiver is typed as `C` with a separate `ev` constraint, rather than as + * `CC[X] <: IterableOnce[X]`, so that non-parametric collections such as + * [[scala.jdk.IntAccumulator]] are also supported. + * + * @tparam A the element type of the collection + * @tparam C the type of the collection + * @param c the collection to create a Stream for + * @param ev evidence that the collection type `C` has elements of type `A` + */ implicit class IterableNonGenericHasParStream[A, C <: IterableOnce[?]](c: C)(implicit ev: C <:< IterableOnce[A]) { private type IterableOnceWithEfficientStepper = IterableOnce[A] { + /** Returns a `Stepper` for this collection whose type records that it supports efficient + * splitting ([[Stepper.EfficientSplit]]), as required for parallel streams. + * + * @tparam S the type of the returned `Stepper`, determined by the element type `A` + * @param shape implicit evidence selecting the appropriate `Stepper` type for element type `A` + * @return a `Stepper` over this collection's elements that supports efficient splitting + */ def stepper[S <: Stepper[?]](implicit shape : StepperShape[A, S]) : S & EfficientSplit } @@ -71,6 +96,15 @@ trait StreamExtensions { // maps + /** Provides the `asJavaSeqKeyStream`, `asJavaSeqValueStream` and `asJavaSeqStream` extension + * methods that create sequential Java Streams over the keys, the values, or the + * `(key, value)` pairs of a Scala map, via the map's [[Stepper]]s. + * + * @tparam K the key type of the map + * @tparam V the value type of the map + * @tparam CC the type of the map + * @param cc the map to create Streams for + */ implicit class MapHasSeqKeyValueStream[K, V, CC[X, Y] <: collection.MapOps[X, Y, collection.Map, ?]](cc: CC[K, V]) { /** Creates a sequential [[java.util.stream.Stream Java Stream]] for the keys of this map. If * the keys are primitive values, a corresponding specialized Stream is returned (e.g., @@ -113,6 +147,16 @@ trait StreamExtensions { } + /** Provides the `asJavaParKeyStream`, `asJavaParValueStream` and `asJavaParStream` extension + * methods that create parallel Java Streams over the keys, the values, or the `(key, value)` + * pairs of a Scala map. Each method requires evidence that the corresponding [[Stepper]] of + * the map supports efficient splitting ([[Stepper.EfficientSplit]]). + * + * @tparam K the key type of the map + * @tparam V the value type of the map + * @tparam CC the type of the map + * @param cc the map to create Streams for + */ implicit class MapHasParKeyValueStream[K, V, CC[X, Y] <: collection.MapOps[X, Y, collection.Map, ?]](cc: CC[K, V]) { private type MapOpsWithEfficientKeyStepper = collection.MapOps[K, V, collection.Map, ?] { def keyStepper[S <: Stepper[?]](implicit shape : StepperShape[K, S]) : S & EfficientSplit } private type MapOpsWithEfficientValueStepper = collection.MapOps[K, V, collection.Map, ?] { def valueStepper[S <: Stepper[?]](implicit shape : StepperShape[V, S]) : S & EfficientSplit } @@ -163,6 +207,12 @@ trait StreamExtensions { // steppers + /** Provides the `asJavaSeqStream` extension method that creates a sequential Java Stream over + * the elements of a [[Stepper]]. + * + * @tparam A the element type of the stepper + * @param stepper the stepper to create a Stream for + */ implicit class StepperHasSeqStream[A](stepper: Stepper[A]) { /** Creates a sequential [[java.util.stream.Stream Java Stream]] for this stepper. If the * stepper yields primitive values, a corresponding specialized Stream is returned (e.g., @@ -183,6 +233,13 @@ trait StreamExtensions { } } + /** Provides the `asJavaParStream` extension method that creates a parallel Java Stream over + * the elements of a [[Stepper]] that supports efficient splitting + * ([[Stepper.EfficientSplit]]). + * + * @tparam A the element type of the stepper + * @param stepper the stepper to create a Stream for + */ implicit class StepperHasParStream[A](stepper: Stepper[A] & EfficientSplit) { /** Creates a parallel [[java.util.stream.Stream Java Stream]] for this stepper. If the * stepper yields primitive values, a corresponding specialized Stream is returned (e.g., @@ -209,6 +266,11 @@ trait StreamExtensions { // steppers are also available on byte/short/char/float arrays (`WidenedByteArrayStepper`), // JDK spliterators only for double/int/long/reference. + /** Provides `asJavaSeqStream` and `asJavaParStream` extension methods that create sequential + * or parallel [[java.util.stream.DoubleStream DoubleStream]]s over a `Double` array. + * + * @param a the array to create Streams for + */ implicit class DoubleArrayHasSeqParStream(a: Array[Double]) { /** Creates a sequential [[java.util.stream.DoubleStream Java DoubleStream]] for this array. */ def asJavaSeqStream: DoubleStream = java.util.Arrays.stream(a) @@ -216,6 +278,11 @@ trait StreamExtensions { def asJavaParStream: DoubleStream = asJavaSeqStream.parallel } + /** Provides `asJavaSeqStream` and `asJavaParStream` extension methods that create sequential + * or parallel [[java.util.stream.IntStream IntStream]]s over an `Int` array. + * + * @param a the array to create Streams for + */ implicit class IntArrayHasSeqParStream(a: Array[Int]) { /** Creates a sequential [[java.util.stream.IntStream Java IntStream]] for this array. */ def asJavaSeqStream: IntStream = java.util.Arrays.stream(a) @@ -223,6 +290,11 @@ trait StreamExtensions { def asJavaParStream: IntStream = asJavaSeqStream.parallel } + /** Provides `asJavaSeqStream` and `asJavaParStream` extension methods that create sequential + * or parallel [[java.util.stream.LongStream LongStream]]s over a `Long` array. + * + * @param a the array to create Streams for + */ implicit class LongArrayHasSeqParStream(a: Array[Long]) { /** Creates a sequential [[java.util.stream.LongStream Java LongStream]] for this array. */ def asJavaSeqStream: LongStream = java.util.Arrays.stream(a) @@ -230,6 +302,12 @@ trait StreamExtensions { def asJavaParStream: LongStream = asJavaSeqStream.parallel } + /** Provides `asJavaSeqStream` and `asJavaParStream` extension methods that create sequential + * or parallel [[java.util.stream.Stream Java Stream]]s over an array of references. + * + * @tparam A the element type of the array + * @param a the array to create Streams for + */ implicit class AnyArrayHasSeqParStream[A <: AnyRef](a: Array[A]) { /** Creates a sequential [[java.util.stream.Stream Java Stream]] for this array. */ def asJavaSeqStream: Stream[A] = java.util.Arrays.stream(a) @@ -237,6 +315,12 @@ trait StreamExtensions { def asJavaParStream: Stream[A] = asJavaSeqStream.parallel } + /** Provides `asJavaSeqStream` and `asJavaParStream` extension methods that create sequential + * or parallel [[java.util.stream.IntStream IntStream]]s over a `Byte` array; each element is + * widened to an `Int`. + * + * @param a the array to create Streams for + */ implicit class ByteArrayHasSeqParStream(a: Array[Byte]) { /** Creates a sequential [[java.util.stream.IntStream Java IntStream]] for this array. */ def asJavaSeqStream: IntStream = a.stepper.asJavaSeqStream @@ -244,6 +328,12 @@ trait StreamExtensions { def asJavaParStream: IntStream = a.stepper.asJavaParStream } + /** Provides `asJavaSeqStream` and `asJavaParStream` extension methods that create sequential + * or parallel [[java.util.stream.IntStream IntStream]]s over a `Short` array; each element is + * widened to an `Int`. + * + * @param a the array to create Streams for + */ implicit class ShortArrayHasSeqParStream(a: Array[Short]) { /** Creates a sequential [[java.util.stream.IntStream Java IntStream]] for this array. */ def asJavaSeqStream: IntStream = a.stepper.asJavaSeqStream @@ -251,6 +341,12 @@ trait StreamExtensions { def asJavaParStream: IntStream = a.stepper.asJavaParStream } + /** Provides `asJavaSeqStream` and `asJavaParStream` extension methods that create sequential + * or parallel [[java.util.stream.IntStream IntStream]]s over a `Char` array; each element is + * widened to an `Int`. + * + * @param a the array to create Streams for + */ implicit class CharArrayHasSeqParStream(a: Array[Char]) { /** Creates a sequential [[java.util.stream.IntStream Java IntStream]] for this array. */ def asJavaSeqStream: IntStream = a.stepper.asJavaSeqStream @@ -258,6 +354,12 @@ trait StreamExtensions { def asJavaParStream: IntStream = a.stepper.asJavaParStream } + /** Provides `asJavaSeqStream` and `asJavaParStream` extension methods that create sequential + * or parallel [[java.util.stream.DoubleStream DoubleStream]]s over a `Float` array; each + * element is widened to a `Double`. + * + * @param a the array to create Streams for + */ implicit class FloatArrayHasSeqParStream(a: Array[Float]) { /** Creates a sequential [[java.util.stream.DoubleStream Java DoubleStream]] for this array. */ def asJavaSeqStream: DoubleStream = a.stepper.asJavaSeqStream @@ -269,6 +371,12 @@ trait StreamExtensions { // strings + /** Provides extension methods that create sequential or parallel + * [[java.util.stream.IntStream IntStream]]s over the characters or the code points of a + * `String`. + * + * @param s the string to create Streams for + */ implicit class StringHasSeqParStream(s: String) { /** A sequential stream on the characters of a string, same as [[asJavaSeqCharStream]]. See also * [[asJavaSeqCodePointStream]]. @@ -292,6 +400,13 @@ trait StreamExtensions { // toScala for streams + /** Provides the `toScala` extension method that collects the elements of a Java Stream into a + * Scala collection, and the `asJavaPrimitiveStream` extension method that unboxes a Stream of + * boxed primitives to the corresponding primitive Stream. + * + * @tparam A the element type of the stream + * @param stream the Java Stream to convert + */ implicit class StreamHasToScala[A](stream: Stream[A]) { /** Copies the elements of this stream into a Scala collection. * @@ -335,6 +450,11 @@ trait StreamExtensions { def asJavaPrimitiveStream[S](implicit unboxer: StreamUnboxer[A, S]): S = unboxer(stream) } + /** Provides the `toScala` extension method that collects the elements of a Java `IntStream` + * into a Scala collection. + * + * @param stream the Java `IntStream` to convert + */ implicit class IntStreamHasToScala(stream: IntStream) { /** Copies the elements of this stream into a Scala collection. * @@ -366,6 +486,11 @@ trait StreamExtensions { } } + /** Provides the `toScala` extension method that collects the elements of a Java `LongStream` + * into a Scala collection. + * + * @param stream the Java `LongStream` to convert + */ implicit class LongStreamHasToScala(stream: LongStream) { /** Copies the elements of this stream into a Scala collection. * @@ -397,6 +522,11 @@ trait StreamExtensions { } } + /** Provides the `toScala` extension method that collects the elements of a Java `DoubleStream` + * into a Scala collection. + * + * @param stream the Java `DoubleStream` to convert + */ implicit class DoubleStreamHasToScala(stream: DoubleStream) { /** Copies the elements of this stream into a Scala collection. * @@ -439,30 +569,57 @@ object StreamExtensions { * @tparam St the type of `Stepper` used to traverse elements */ sealed trait StreamShape[T, S <: BaseStream[?, ?], St <: Stepper[?]] { + /** Creates a Java Stream over the elements of the given stepper by delegating to + * `mkStream`. + * + * @param st the stepper providing the elements + * @param par whether the returned stream is parallel (`true`) or sequential (`false`) + * @return a Java Stream of type `S` over the stepper's elements + */ final def fromStepper(st: St, par: Boolean): S = mkStream(st, par) + /** Creates a Java Stream of type `S` from the given stepper's spliterator. + * + * @param st the stepper providing the elements + * @param par whether the returned stream is parallel (`true`) or sequential (`false`) + * @return a Java Stream of type `S` over the stepper's elements + */ protected def mkStream(st: St, par: Boolean): S } object StreamShape extends StreamShapeLowPriority1 { // primitive + /** The `StreamShape` mapping `Int` elements to `IntStream` and `IntStepper`. */ implicit val intStreamShape : StreamShape[Int , IntStream , IntStepper] = mkIntStreamShape[Int] + /** The `StreamShape` mapping `Long` elements to `LongStream` and `LongStepper`. */ implicit val longStreamShape : StreamShape[Long , LongStream , LongStepper] = mkLongStreamShape[Long] + /** The `StreamShape` mapping `Double` elements to `DoubleStream` and `DoubleStepper`. */ implicit val doubleStreamShape: StreamShape[Double, DoubleStream, DoubleStepper] = mkDoubleStreamShape[Double] // widening + /** The `StreamShape` mapping `Byte` elements to `IntStream` and `IntStepper`; each element is widened to an `Int`. */ implicit val byteStreamShape : StreamShape[Byte , IntStream , IntStepper] = mkIntStreamShape[Byte] + /** The `StreamShape` mapping `Short` elements to `IntStream` and `IntStepper`; each element is widened to an `Int`. */ implicit val shortStreamShape: StreamShape[Short, IntStream , IntStepper] = mkIntStreamShape[Short] + /** The `StreamShape` mapping `Char` elements to `IntStream` and `IntStepper`; each element is widened to an `Int`. */ implicit val charStreamShape : StreamShape[Char , IntStream , IntStepper] = mkIntStreamShape[Char] + /** The `StreamShape` mapping `Float` elements to `DoubleStream` and `DoubleStepper`; each element is widened to a `Double`. */ implicit val floatStreamShape: StreamShape[Float, DoubleStream, DoubleStepper] = mkDoubleStreamShape[Float] // boxed java primitives + /** The `StreamShape` mapping boxed `java.lang.Integer` elements to the primitive `IntStream` and `IntStepper`. */ implicit val jIntegerStreamShape : StreamShape[jl.Integer , IntStream , IntStepper ] = mkIntStreamShape[jl.Integer] + /** The `StreamShape` mapping boxed `java.lang.Long` elements to the primitive `LongStream` and `LongStepper`. */ implicit val jLongStreamShape : StreamShape[jl.Long , LongStream , LongStepper ] = mkLongStreamShape[jl.Long] + /** The `StreamShape` mapping boxed `java.lang.Double` elements to the primitive `DoubleStream` and `DoubleStepper`. */ implicit val jDoubleStreamShape : StreamShape[jl.Double , DoubleStream, DoubleStepper] = mkDoubleStreamShape[jl.Double] + /** The `StreamShape` mapping boxed `java.lang.Byte` elements to `IntStream` and `IntStepper`; each element is widened to an `Int`. */ implicit val jByteStreamShape : StreamShape[jl.Byte , IntStream , IntStepper ] = mkIntStreamShape[jl.Byte] + /** The `StreamShape` mapping boxed `java.lang.Short` elements to `IntStream` and `IntStepper`; each element is widened to an `Int`. */ implicit val jShortStreamShape : StreamShape[jl.Short , IntStream , IntStepper ] = mkIntStreamShape[jl.Short] + /** The `StreamShape` mapping boxed `java.lang.Character` elements to `IntStream` and `IntStepper`; each element is widened to an `Int`. */ implicit val jCharacterStreamShape : StreamShape[jl.Character, IntStream , IntStepper ] = mkIntStreamShape[jl.Character] + /** The `StreamShape` mapping boxed `java.lang.Float` elements to `DoubleStream` and `DoubleStepper`; each element is widened to a `Double`. */ implicit val jFloatStreamShape : StreamShape[jl.Float , DoubleStream, DoubleStepper] = mkDoubleStreamShape[jl.Float] private def mkIntStreamShape[T]: StreamShape[T, IntStream, IntStepper] = new StreamShape[T, IntStream, IntStepper] { @@ -478,8 +635,17 @@ object StreamExtensions { } } + /** Defines the low-priority fallback `StreamShape` for arbitrary element types, used when no + * specialized shape applies. + */ trait StreamShapeLowPriority1 { // reference + /** Returns the fallback `StreamShape` mapping elements of any type `T` to a generic (boxed) + * `Stream[T]`. Applies only when `T` has no specialized shape. + * + * @tparam T the element type + * @return the single cached `StreamShape` instance, cast to element type `T` + */ implicit def anyStreamShape[T]: StreamShape[T, Stream[T], Stepper[T]] = anyStreamShapePrototype.asInstanceOf[StreamShape[T, Stream[T], Stepper[T]]] private val anyStreamShapePrototype: StreamShape[AnyRef, Stream[AnyRef], Stepper[AnyRef]] = new StreamShape[AnyRef, Stream[AnyRef], Stepper[AnyRef]] { @@ -494,22 +660,33 @@ object StreamExtensions { * @tparam S the target primitive stream type (e.g., `IntStream`, `LongStream`, `DoubleStream`) */ sealed trait StreamUnboxer[A, S] { + /** Converts the given boxed Java Stream to the corresponding primitive stream. + * + * @param s the boxed Java Stream to convert + * @return a primitive stream of type `S` with the unboxed elements of `s` + */ def apply(s: Stream[A]): S } object StreamUnboxer { + /** The `StreamUnboxer` converting a `Stream[Int]` to a primitive `IntStream`. */ implicit val intStreamUnboxer: StreamUnboxer[Int, IntStream] = new StreamUnboxer[Int, IntStream] { def apply(s: Stream[Int]): IntStream = s.mapToInt(x => x) } + /** The `StreamUnboxer` converting a `Stream[java.lang.Integer]` to a primitive `IntStream`. */ implicit val javaIntegerStreamUnboxer: StreamUnboxer[jl.Integer, IntStream] = intStreamUnboxer.asInstanceOf[StreamUnboxer[jl.Integer, IntStream]] + /** The `StreamUnboxer` converting a `Stream[Long]` to a primitive `LongStream`. */ implicit val longStreamUnboxer: StreamUnboxer[Long, LongStream] = new StreamUnboxer[Long, LongStream] { def apply(s: Stream[Long]): LongStream = s.mapToLong(x => x) } + /** The `StreamUnboxer` converting a `Stream[java.lang.Long]` to a primitive `LongStream`. */ implicit val javaLongStreamUnboxer: StreamUnboxer[jl.Long, LongStream] = longStreamUnboxer.asInstanceOf[StreamUnboxer[jl.Long, LongStream]] + /** The `StreamUnboxer` converting a `Stream[Double]` to a primitive `DoubleStream`. */ implicit val doubleStreamUnboxer: StreamUnboxer[Double, DoubleStream] = new StreamUnboxer[Double, DoubleStream] { def apply(s: Stream[Double]): DoubleStream = s.mapToDouble(x => x) } + /** The `StreamUnboxer` converting a `Stream[java.lang.Double]` to a primitive `DoubleStream`. */ implicit val javaDoubleStreamUnboxer: StreamUnboxer[jl.Double, DoubleStream] = doubleStreamUnboxer.asInstanceOf[StreamUnboxer[jl.Double, DoubleStream]] } @@ -526,35 +703,66 @@ object StreamExtensions { * @tparam C the target collection type, potentially a specialized `Accumulator` */ trait AccumulatorFactoryInfo[A, C] { + /** The companion object of the target `Accumulator` type ([[scala.jdk.AnyAccumulator]], + * [[scala.jdk.IntAccumulator]], etc.), or `null` when the target collection is not an + * accumulator. + */ val companion: AnyRef | Null } + /** Defines the low-priority fallback `AccumulatorFactoryInfo`, used when the target collection + * type is not an `Accumulator`. + */ trait LowPriorityAccumulatorFactoryInfo { + /** Returns the fallback `AccumulatorFactoryInfo`, whose `companion` is `null`. Applies only + * when the target collection type `C` is not an `Accumulator`. + * + * @tparam A the element type of the stream + * @tparam C the type of the target collection + * @return the single cached fallback instance, cast to `A` and `C` + */ implicit def noAccumulatorFactoryInfo[A, C]: AccumulatorFactoryInfo[A, C] = noAccumulatorFactoryInfoPrototype.asInstanceOf[AccumulatorFactoryInfo[A, C]] private val noAccumulatorFactoryInfoPrototype: AccumulatorFactoryInfo[AnyRef, AnyRef] = new AccumulatorFactoryInfo[AnyRef, AnyRef] { val companion: AnyRef | Null = null } } object AccumulatorFactoryInfo extends LowPriorityAccumulatorFactoryInfo { + /** Returns the `AccumulatorFactoryInfo` for collecting elements of any type into an + * [[scala.jdk.AnyAccumulator]]. + * + * @tparam A the element type of the stream + * @return the single cached instance, whose `companion` is `AnyAccumulator`, cast to + * element type `A` + */ implicit def anyAccumulatorFactoryInfo[A]: AccumulatorFactoryInfo[A, AnyAccumulator[A]] = anyAccumulatorFactoryInfoPrototype.asInstanceOf[AccumulatorFactoryInfo[A, AnyAccumulator[A]]] private object anyAccumulatorFactoryInfoPrototype extends AccumulatorFactoryInfo[AnyRef, AnyAccumulator[AnyRef]] { val companion: AnyRef | Null = AnyAccumulator } + /** The `AccumulatorFactoryInfo` for collecting `Int` elements into an [[scala.jdk.IntAccumulator]] without boxing. */ implicit val intAccumulatorFactoryInfo: AccumulatorFactoryInfo[Int, IntAccumulator] = new AccumulatorFactoryInfo[Int, IntAccumulator] { val companion: AnyRef | Null = IntAccumulator } + /** The `AccumulatorFactoryInfo` for collecting `Long` elements into a [[scala.jdk.LongAccumulator]] without boxing. */ implicit val longAccumulatorFactoryInfo: AccumulatorFactoryInfo[Long, LongAccumulator] = new AccumulatorFactoryInfo[Long, LongAccumulator] { val companion: AnyRef | Null = LongAccumulator } + /** The `AccumulatorFactoryInfo` for collecting `Double` elements into a [[scala.jdk.DoubleAccumulator]] without boxing. */ implicit val doubleAccumulatorFactoryInfo: AccumulatorFactoryInfo[Double, DoubleAccumulator] = new AccumulatorFactoryInfo[Double, DoubleAccumulator] { val companion: AnyRef | Null = DoubleAccumulator } + /** The `AccumulatorFactoryInfo` for collecting boxed `java.lang.Integer` elements into an [[scala.jdk.IntAccumulator]], reusing `intAccumulatorFactoryInfo`. */ implicit val jIntegerAccumulatorFactoryInfo: AccumulatorFactoryInfo[jl.Integer, IntAccumulator] = intAccumulatorFactoryInfo.asInstanceOf[AccumulatorFactoryInfo[jl.Integer, IntAccumulator]] + /** The `AccumulatorFactoryInfo` for streams of boxed `java.lang.Long` elements, reusing + * `longAccumulatorFactoryInfo`. + */ implicit val jLongAccumulatorFactoryInfo: AccumulatorFactoryInfo[jl.Long, IntAccumulator] = longAccumulatorFactoryInfo.asInstanceOf[AccumulatorFactoryInfo[jl.Long, IntAccumulator]] + /** The `AccumulatorFactoryInfo` for streams of boxed `java.lang.Double` elements, reusing + * `doubleAccumulatorFactoryInfo`. + */ implicit val jDoubleAccumulatorFactoryInfo: AccumulatorFactoryInfo[jl.Double, IntAccumulator] = doubleAccumulatorFactoryInfo.asInstanceOf[AccumulatorFactoryInfo[jl.Double, IntAccumulator]] } }