diff --git a/community-build/community-projects/PPrint b/community-build/community-projects/PPrint index 01ca9aec25f6..fa560d29bbff 160000 --- a/community-build/community-projects/PPrint +++ b/community-build/community-projects/PPrint @@ -1 +1 @@ -Subproject commit 01ca9aec25f69cd81fa554a9dbfc1769b4702895 +Subproject commit fa560d29bbff693fd8a13fadc3499eccc4b15d8f diff --git a/library-js/src/scala/collection/mutable/Buffer.scala b/library-js/src/scala/collection/mutable/Buffer.scala deleted file mode 100644 index 95ba2237439e..000000000000 --- a/library-js/src/scala/collection/mutable/Buffer.scala +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala.collection -package mutable - -import scala.language.`2.13` - -import scala.scalajs.js -import scala.scalajs.LinkingInfo -import scala.scalajs.LinkingInfo.linkTimeIf - -/** A `Buffer` is a growable and shrinkable `Seq`. - * - * @tparam A the type of elements contained in this buffer - */ -trait Buffer[A] - extends Seq[A] - with SeqOps[A, Buffer, Buffer[A]] - with Growable[A] - with Shrinkable[A] - with IterableFactoryDefaults[A, Buffer] { - - override def iterableFactory: SeqFactory[Buffer] = Buffer - - 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` - /** Prepends a single element at the front of this $coll. - * - * @param elem the element to $add. - * @return the $coll itself - */ - def prepend(elem: A): this.type - - /** Appends the given elements to this buffer. - * - * @param elem the element to append. - * @return this buffer with the element appended - */ - @`inline` final def append(elem: A): this.type = addOne(elem) - - @deprecated("Use appendAll instead", "2.13.0") - @`inline` final def append(elems: A*): this.type = addAll(elems) - - /** Appends the elements contained in a iterable object to this buffer. - * - * @param xs the iterable object containing the elements to append. - * @return this buffer with the elements appended - */ - @`inline` final def appendAll(xs: IterableOnce[A]): this.type = addAll(xs) - - - /** Alias for `prepend`. */ - @`inline` final def +=: (elem: A): this.type = prepend(elem) - - def prependAll(elems: IterableOnce[A]): this.type = { insertAll(0, elems); this } - - @deprecated("Use prependAll instead", "2.13.0") - @`inline` final def prepend(elems: A*): this.type = prependAll(elems) - - /** Alias for `prependAll`. */ - @inline final def ++=:(elems: IterableOnce[A]): this.type = prependAll(elems) - - /** Inserts a new element at a given index into this buffer. - * - * @param idx the index where the new elements is inserted. - * @param elem the element to insert. - * @throws IndexOutOfBoundsException if the index `idx` is not in the valid range - * `0 <= idx <= length`. - */ - @throws[IndexOutOfBoundsException] - def insert(idx: Int, elem: A): Unit - - /** Inserts new elements at the index `idx`. Opposed to method - * `update`, this method will not replace an element with a new - * one. Instead, it will insert a new element at index `idx`. - * - * @param idx the index where a new element will be inserted. - * @param elems the iterable object providing all elements to insert. - * @throws IndexOutOfBoundsException if `idx` is out of bounds. - */ - @throws[IndexOutOfBoundsException] - def insertAll(idx: Int, elems: IterableOnce[A]): Unit - - /** Removes the element at a given index position. - * - * @param idx the index which refers to the element to delete. - * @return the element that was formerly at index `idx`. - */ - @throws[IndexOutOfBoundsException] - def remove(idx: Int): A - - /** Removes the element on a given index position. It takes time linear in - * the buffer size. - * - * @param idx the index which refers to the first element to remove. - * @param count the number of elements to remove. - * @throws IndexOutOfBoundsException if the index `idx` is not in the valid range - * `0 <= idx <= length - count` (with `count > 0`). - * @throws IllegalArgumentException if `count < 0`. - */ - @throws[IndexOutOfBoundsException] - @throws[IllegalArgumentException] - def remove(idx: Int, count: Int): Unit - - /** Removes a single element from this buffer, at its first occurrence. - * If the buffer does not contain that element, it is unchanged. - * - * @param x the element to remove. - * @return the buffer itself - */ - def subtractOne (x: A): this.type = { - val i = indexOf(x) - if (i != -1) remove(i) - this - } - - /** Removes the first *n* elements of this buffer. - * - * @param n the number of elements to remove from the beginning - * of this buffer. - */ - def trimStart(n: Int): Unit = remove(0, normalized(n)) - - /** Removes the last *n* elements of this buffer. - * - * @param n the number of elements to remove from the end - * of this buffer. - */ - def trimEnd(n: Int): Unit = { - val norm = normalized(n) - remove(length - norm, norm) - } - - def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A], replaced: Int): this.type - - // +=, ++=, clear inherited from Growable - // Per remark of @ichoran, we should preferably not have these: - // - // def +=:(elem: A): this.type = { insert(0, elem); this } - // def +=:(elem1: A, elem2: A, elems: A*): this.type = elem1 +=: elem2 +=: elems ++=: this - // def ++=:(elems: IterableOnce[A]): this.type = { insertAll(0, elems); this } - - def dropInPlace(n: Int): this.type = { remove(0, normalized(n)); this } - def dropRightInPlace(n: Int): this.type = { - val norm = normalized(n) - remove(length - norm, norm) - this - } - def takeInPlace(n: Int): this.type = { - val norm = normalized(n) - remove(norm, length - norm) - this - } - def takeRightInPlace(n: Int): this.type = { remove(0, length - normalized(n)); this } - def sliceInPlace(start: Int, end: Int): this.type = takeInPlace(end).dropInPlace(start) - private def normalized(n: Int): Int = math.min(math.max(n, 0), length) - - def dropWhileInPlace(p: A => Boolean): this.type = { - val idx = indexWhere(!p(_)) - if (idx < 0) { clear(); this } else dropInPlace(idx) - } - def takeWhileInPlace(p: A => Boolean): this.type = { - val idx = indexWhere(!p(_)) - if (idx < 0) this else takeInPlace(idx) - } - def padToInPlace(len: Int, elem: A): this.type = { - while (length < len) +=(elem) - this - } - - @deprecatedOverriding("Compatibility override", since="2.13.0") - override protected[this] def stringPrefix = "Buffer" -} - -trait IndexedBuffer[A] extends IndexedSeq[A] - with IndexedSeqOps[A, IndexedBuffer, IndexedBuffer[A]] - with Buffer[A] - with IterableFactoryDefaults[A, IndexedBuffer] { - - override def iterableFactory: SeqFactory[IndexedBuffer] = IndexedBuffer - - def flatMapInPlace(f: A => IterableOnce[A]): this.type = { - // There's scope for a better implementation which copies elements in place. - var i = 0 - val s = size - val newElems = new Array[IterableOnce[A]](s) - while (i < s) { newElems(i) = f(this(i)); i += 1 } - clear() - i = 0 - while (i < s) { ++=(newElems(i)); i += 1 } - this - } - - def filterInPlace(p: A => Boolean): this.type = { - var i, j = 0 - while (i < size) { - if (p(apply(i))) { - if (i != j) { - this(j) = this(i) - } - j += 1 - } - i += 1 - } - - if (i == j) this else takeInPlace(j) - } - - def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A], replaced: Int): this.type = { - val replaced0 = math.min(math.max(replaced, 0), length) - val i = math.min(math.max(from, 0), length) - var j = 0 - val iter = patch.iterator - while (iter.hasNext && j < replaced0 && i + j < length) { - update(i + j, iter.next()) - j += 1 - } - if (iter.hasNext) insertAll(i + j, iter) - else if (j < replaced0) remove(i + j, math.min(replaced0 - j, length - i - j)) - this - } -} - -@SerialVersionUID(3L) -object Buffer extends SeqFactory.Delegate[Buffer]( - linkTimeIf[SeqFactory[Buffer]](LinkingInfo.isWebAssembly) { - ArrayBuffer - } { - js.WrappedArray - } -) - -@SerialVersionUID(3L) -object IndexedBuffer extends SeqFactory.Delegate[IndexedBuffer]( - linkTimeIf[SeqFactory[IndexedBuffer]](LinkingInfo.isWebAssembly) { - ArrayBuffer - } { - js.WrappedArray - } -) - -/** Explicit instantiation of the `Buffer` trait to reduce class file size in subclasses. */ -@SerialVersionUID(3L) -abstract class AbstractBuffer[A] extends AbstractSeq[A] with Buffer[A] diff --git a/library/src/scala/collection/mutable/ArrayDeque.scala b/library/src/scala/collection/mutable/ArrayDeque.scala index cdbab33a58d1..7d470629985d 100644 --- a/library/src/scala/collection/mutable/ArrayDeque.scala +++ b/library/src/scala/collection/mutable/ArrayDeque.scala @@ -287,21 +287,25 @@ class ArrayDeque[A] protected ( this } - /** + /** Optionally removes and returns the first element of this $coll. * - * @param resizeInternalRepr If this is set, resize the internal representation to reclaim space once in a while - * @return + * Runs in constant time, unless `resizeInternalRepr` is set. + * + * @param resizeInternalRepr if `true`, shrinks the internal array when it is less than half full, + * which copies the remaining elements when it happens. + * @return the removed first element if this $coll is nonempty, `None` if it is empty. */ def removeHeadOption(resizeInternalRepr: Boolean = false): Option[A] = if (isEmpty) None else Some(removeHeadAssumingNonEmpty(resizeInternalRepr)) - /** - * Unsafely remove the first element (throws exception when empty) - * See also removeHeadOption() + /** Removes and returns the first element of this $coll. * - * @param resizeInternalRepr If this is set, resize the internal representation to reclaim space once in a while - * @throws NoSuchElementException when empty - * @return + * Runs in constant time, unless `resizeInternalRepr` is set. See also `removeHeadOption`. + * + * @param resizeInternalRepr if `true`, shrinks the internal array when it is less than half full, + * which copies the remaining elements when it happens. + * @return the removed first element. + * @throws NoSuchElementException if this $coll is empty. */ def removeHead(resizeInternalRepr: Boolean = false): A = if (isEmpty) throw new NoSuchElementException(s"empty collection") else removeHeadAssumingNonEmpty(resizeInternalRepr) @@ -314,21 +318,25 @@ class ArrayDeque[A] protected ( elem.asInstanceOf[A] } - /** + /** Optionally removes and returns the last element of this $coll. * - * @param resizeInternalRepr If this is set, resize the internal representation to reclaim space once in a while - * @return + * Runs in constant time, unless `resizeInternalRepr` is set. + * + * @param resizeInternalRepr if `true`, shrinks the internal array when it is less than half full, + * which copies the remaining elements when it happens. + * @return the removed last element if this $coll is nonempty, `None` if it is empty. */ def removeLastOption(resizeInternalRepr: Boolean = false): Option[A] = if (isEmpty) None else Some(removeLastAssumingNonEmpty(resizeInternalRepr)) - /** - * Unsafely remove the last element (throws exception when empty) - * See also removeLastOption() + /** Removes and returns the last element of this $coll. * - * @param resizeInternalRepr If this is set, resize the internal representation to reclaim space once in a while - * @throws NoSuchElementException when empty - * @return + * Runs in constant time, unless `resizeInternalRepr` is set. See also `removeLastOption`. + * + * @param resizeInternalRepr if `true`, shrinks the internal array when it is less than half full, + * which copies the remaining elements when it happens. + * @return the removed last element. + * @throws NoSuchElementException if this $coll is empty. */ def removeLast(resizeInternalRepr: Boolean = false): A = if (isEmpty) throw new NoSuchElementException(s"empty collection") else removeLastAssumingNonEmpty(resizeInternalRepr) @@ -341,11 +349,13 @@ class ArrayDeque[A] protected ( elem.asInstanceOf[A] } - /** - * Removes all elements from this collection and returns the elements while emptying this data structure. - * @return + /** Removes all elements from this $coll and returns them. + * + * Runs in `O(n)` time, where `n` is the length of this $coll. + * + * @return the removed elements, in their original order. */ - def removeAll(): scala.collection.immutable.Seq[A] = { + override def removeAll(): scala.collection.immutable.Seq[A] = { val elems = scala.collection.immutable.Seq.newBuilder[A] elems.sizeHint(length) while(nonEmpty) { @@ -354,11 +364,13 @@ class ArrayDeque[A] protected ( elems.result() } - /** - * Removes all elements from this collection and returns the elements in reverse while emptying this data structure. - * @return + /** Removes all elements from this $coll and returns them in reverse order. + * + * Runs in `O(n)` time, where `n` is the length of this $coll. + * + * @return the removed elements, last element first. */ - def removeAllReverse(): scala.collection.immutable.Seq[A] = { + override def removeAllReverse(): scala.collection.immutable.Seq[A] = { val elems = scala.collection.immutable.Seq.newBuilder[A] elems.sizeHint(length) while(nonEmpty) { @@ -367,13 +379,15 @@ class ArrayDeque[A] protected ( elems.result() } - /** - * Returns and removes all elements from the left of this queue which satisfy the given predicate + /** Removes elements from the front of this $coll for as long as they satisfy a predicate, + * and returns them. + * + * Runs in `O(k)` time, where `k` is the number of removed elements. * - * @param f the predicate used for choosing elements - * @return + * @param f the predicate used to test elements. + * @return the removed elements, in their original order. */ - def removeHeadWhile(f: A => Boolean): scala.collection.immutable.Seq[A] = { + override def removeHeadWhile(f: A => Boolean): scala.collection.immutable.Seq[A] = { val elems = scala.collection.immutable.Seq.newBuilder[A] while(headOption.exists(f)) { elems += removeHeadAssumingNonEmpty() @@ -381,13 +395,15 @@ class ArrayDeque[A] protected ( elems.result() } - /** - * Returns and removes all elements from the right of this queue which satisfy the given predicate + /** Removes elements from the end of this $coll for as long as they satisfy a predicate, + * and returns them. * - * @param f the predicate used for choosing elements - * @return + * Runs in `O(k)` time, where `k` is the number of removed elements. + * + * @param f the predicate used to test elements. + * @return the removed elements, last element first. */ - def removeLastWhile(f: A => Boolean): scala.collection.immutable.Seq[A] = { + override def removeLastWhile(f: A => Boolean): scala.collection.immutable.Seq[A] = { val elems = scala.collection.immutable.Seq.newBuilder[A] while(lastOption.exists(f)) { elems += removeLastAssumingNonEmpty() @@ -395,26 +411,29 @@ class ArrayDeque[A] protected ( elems.result() } - /** Returns the first element which satisfies the given predicate after or at some start index - * and removes this element from the collections + /** Removes and returns the first element at or after a given index that satisfies a predicate. + * + * Runs in `O(n)` time, where `n` is the length of this $coll: finding the element at index `i` + * takes `O(i)` time and removing it takes `O(min(i, n - i))` time. * - * @param p the predicate used for choosing the first element - * @param from the start index - * @return the first element of the queue for which p yields true + * @param p the predicate used to test elements. + * @param from the index at which to start searching. + * @return the removed element, or `None` if no element at or after `from` satisfies `p`. */ - def removeFirst(p: A => Boolean, from: Int = 0): Option[A] = { + override def removeFirst(p: A => Boolean, from: Int = 0): Option[A] = { val i = indexWhere(p, from) if (i < 0) None else Some(remove(i)) } - /** Returns all elements in this collection which satisfy the given predicate - * and removes those elements from this collections. + /** Removes all elements of this $coll that satisfy a predicate, and returns them. + * + * Runs in `O(n)` time, where `n` is the length of this $coll, using a single pass + * that compacts the remaining elements in place. * - * @param p the predicate used for choosing elements - * @return a sequence of all elements in the queue for which - * p yields true. + * @param p the predicate used to test elements. + * @return the removed elements, in their original order. */ - def removeAll(p: A => Boolean): scala.collection.immutable.Seq[A] = { + override def removeAll(p: A => Boolean): scala.collection.immutable.Seq[A] = { val res = scala.collection.immutable.Seq.newBuilder[A] var i, j = 0 while (i < size) { diff --git a/library/src/scala/collection/mutable/Buffer.scala b/library/src/scala/collection/mutable/Buffer.scala index 637acefcd91a..497fad0ac0ab 100644 --- a/library/src/scala/collection/mutable/Buffer.scala +++ b/library/src/scala/collection/mutable/Buffer.scala @@ -244,6 +244,143 @@ trait Buffer[A] this } + // === ArrayDeque-style methods with default implementations === + + /** Optionally removes and returns the first element of this $coll. + * + * Runs in constant time on the default `Buffer` implementation, `ArrayDeque`. + * In general it costs as much as `remove(0)`. + * + * @return the removed first element if this $coll is nonempty, `None` if it is empty. + */ + def removeHeadOption(): Option[A] = + if (isEmpty) None else Some(removeHead()) + + /** Removes and returns the first element of this $coll. + * + * Runs in constant time on the default `Buffer` implementation, `ArrayDeque`. + * In general it costs as much as `remove(0)`. + * + * @return the removed first element. + * @throws NoSuchElementException if this $coll is empty. + */ + def removeHead(): A = + if (isEmpty) throw new NoSuchElementException("empty collection") + else remove(0) + + /** Optionally removes and returns the last element of this $coll. + * + * Runs in constant time on the default `Buffer` implementation, `ArrayDeque`. + * In general it costs as much as `remove(length - 1)`. + * + * @return the removed last element if this $coll is nonempty, `None` if it is empty. + */ + def removeLastOption(): Option[A] = + if (isEmpty) None else Some(removeLast()) + + /** Removes and returns the last element of this $coll. + * + * Runs in constant time on the default `Buffer` implementation, `ArrayDeque`. + * In general it costs as much as `remove(length - 1)`. + * + * @return the removed last element. + * @throws NoSuchElementException if this $coll is empty. + */ + def removeLast(): A = + if (isEmpty) throw new NoSuchElementException("empty collection") + else remove(length - 1) + + /** Removes all elements from this $coll and returns them. + * + * Runs in `O(n)` time, where `n` is the length of this $coll. + * + * @return the removed elements, in their original order. + */ + def removeAll(): scala.collection.immutable.Seq[A] = { + val elems = scala.collection.immutable.Seq.from(this) + clear() + elems + } + + /** Removes all elements from this $coll and returns them in reverse order. + * + * Runs in `O(n)` time, where `n` is the length of this $coll. + * + * @return the removed elements, last element first. + */ + def removeAllReverse(): scala.collection.immutable.Seq[A] = { + val elems = scala.collection.immutable.Seq.from(reverseIterator) + clear() + elems + } + + /** Removes elements from the front of this $coll for as long as they satisfy a predicate, + * and returns them. + * + * Runs in `O(k)` time on the default `Buffer` implementation, `ArrayDeque`, where `k` is the + * number of removed elements. In general it costs as much as calling `removeHead()` `k` times. + * + * @param f the predicate used to test elements. + * @return the removed elements, in their original order. + */ + def removeHeadWhile(f: A => Boolean): scala.collection.immutable.Seq[A] = { + val elems = scala.collection.immutable.Seq.newBuilder[A] + while (headOption.exists(f)) { + elems += removeHead() + } + elems.result() + } + + /** Removes elements from the end of this $coll for as long as they satisfy a predicate, + * and returns them. + * + * Runs in `O(k)` time on the default `Buffer` implementation, `ArrayDeque`, where `k` is the + * number of removed elements. In general it costs as much as calling `removeLast()` `k` times. + * + * @param f the predicate used to test elements. + * @return the removed elements, last element first. + */ + def removeLastWhile(f: A => Boolean): scala.collection.immutable.Seq[A] = { + val elems = scala.collection.immutable.Seq.newBuilder[A] + while (lastOption.exists(f)) { + elems += removeLast() + } + elems.result() + } + + /** Removes and returns the first element at or after a given index that satisfies a predicate. + * + * Runs in `O(n)` time on the default `Buffer` implementation, `ArrayDeque`, where `n` is the + * length of this $coll. In general it costs as much as `indexWhere(p, from)` followed by `remove`. + * + * @param p the predicate used to test elements. + * @param from the index at which to start searching. + * @return the removed element, or `None` if no element at or after `from` satisfies `p`. + */ + def removeFirst(p: A => Boolean, from: Int = 0): Option[A] = { + val i = indexWhere(p, from) + if (i < 0) None else Some(remove(i)) + } + + /** Removes all elements of this $coll that satisfy a predicate, and returns them. + * + * Runs in `O(n)` time, where `n` is the length of this $coll. + * + * @param p the predicate used to test elements. + * @return the removed elements, in their original order. + */ + def removeAll(p: A => Boolean): scala.collection.immutable.Seq[A] = { + val removed = scala.collection.immutable.Seq.newBuilder[A] + val kept = scala.collection.immutable.Seq.newBuilder[A] + foreach(elem => if (p(elem)) removed += elem else kept += elem) + val res = removed.result() + if (res.nonEmpty) { + clear() + addAll(kept.result()) + } + res + } + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") override protected def stringPrefix = "Buffer" } @@ -308,10 +445,10 @@ trait IndexedBuffer[A] extends IndexedSeq[A] } @SerialVersionUID(3L) -object Buffer extends SeqFactory.Delegate[Buffer](ArrayBuffer) +object Buffer extends SeqFactory.Delegate[Buffer](ArrayDeque) @SerialVersionUID(3L) -object IndexedBuffer extends SeqFactory.Delegate[IndexedBuffer](ArrayBuffer) +object IndexedBuffer extends SeqFactory.Delegate[IndexedBuffer](ArrayDeque) /** Explicit instantiation of the `Buffer` trait to reduce class file size in subclasses. * diff --git a/library/test/scala/collection/mutable/BufferTest.scala b/library/test/scala/collection/mutable/BufferTest.scala new file mode 100644 index 000000000000..c3a2cb457956 --- /dev/null +++ b/library/test/scala/collection/mutable/BufferTest.scala @@ -0,0 +1,134 @@ +package scala.collection.mutable + +import org.junit.Test +import org.junit.Assert.* + +import tools.AssertUtil.assertThrows + +class BufferTest { + + @Test + def factoryCreatesArrayDeque: Unit = { + assertTrue(Buffer(1, 2, 3).isInstanceOf[ArrayDeque[?]]) + assertTrue(IndexedBuffer(1, 2, 3).isInstanceOf[ArrayDeque[?]]) + } + + @Test + def removeHead: Unit = { + val buf = Buffer(1, 2, 3) + assertEquals(1, buf.removeHead()) + assertEquals(Seq(2, 3), buf) + assertThrows[NoSuchElementException](Buffer[Int]().removeHead()) + } + + @Test + def removeLast: Unit = { + val buf = Buffer(1, 2, 3) + assertEquals(3, buf.removeLast()) + assertEquals(Seq(1, 2), buf) + assertThrows[NoSuchElementException](Buffer[Int]().removeLast()) + } + + @Test + def removeHeadOption: Unit = { + val buf = Buffer(1, 2, 3) + assertEquals(Some(1), buf.removeHeadOption()) + assertEquals(Seq(2, 3), buf) + assertEquals(None, Buffer[Int]().removeHeadOption()) + } + + @Test + def removeLastOption: Unit = { + val buf = Buffer(1, 2, 3) + assertEquals(Some(3), buf.removeLastOption()) + assertEquals(Seq(1, 2), buf) + assertEquals(None, Buffer[Int]().removeLastOption()) + } + + @Test + def removeAll: Unit = { + val buf = Buffer(1, 2, 3) + assertEquals(Seq(1, 2, 3), buf.removeAll()) + assertTrue(buf.isEmpty) + } + + @Test + def removeAllReverse: Unit = { + val buf = Buffer(1, 2, 3) + assertEquals(Seq(3, 2, 1), buf.removeAllReverse()) + assertTrue(buf.isEmpty) + } + + @Test + def removeHeadWhile: Unit = { + val buf = Buffer(1, 2, 3, 4, 5) + assertEquals(Seq(1, 2, 3), buf.removeHeadWhile(_ < 4)) + assertEquals(Seq(4, 5), buf) + + val unchanged = Buffer(5, 6, 7) + assertEquals(Seq(), unchanged.removeHeadWhile(_ < 4)) + assertEquals(Seq(5, 6, 7), unchanged) + } + + @Test + def removeLastWhile: Unit = { + val buf = Buffer(1, 2, 3, 4, 5) + assertEquals(Seq(5, 4, 3), buf.removeLastWhile(_ > 2)) + assertEquals(Seq(1, 2), buf) + + val unchanged = Buffer(1, 2, 3) + assertEquals(Seq(), unchanged.removeLastWhile(_ > 10)) + assertEquals(Seq(1, 2, 3), unchanged) + } + + @Test + def removeFirst: Unit = { + val buf = Buffer(1, 2, 3, 4, 5) + assertEquals(Some(2), buf.removeFirst(_ % 2 == 0)) + assertEquals(Seq(1, 3, 4, 5), buf) + + val fromIndex = Buffer(1, 2, 3, 4, 5) + assertEquals(Some(4), fromIndex.removeFirst(_ % 2 == 0, 2)) + assertEquals(Seq(1, 2, 3, 5), fromIndex) + + val notFound = Buffer(1, 3, 5) + assertEquals(None, notFound.removeFirst(_ % 2 == 0)) + assertEquals(Seq(1, 3, 5), notFound) + } + + @Test + def removeAllMatching: Unit = { + val buf = Buffer(1, 2, 3, 4, 5) + assertEquals(Seq(2, 4), buf.removeAll(_ % 2 == 0)) + assertEquals(Seq(1, 3, 5), buf) + + val noneMatch = Buffer(1, 3, 5) + assertEquals(Seq(), noneMatch.removeAll(_ % 2 == 0)) + assertEquals(Seq(1, 3, 5), noneMatch) + + val allMatch = Buffer(2, 4, 6) + assertEquals(Seq(2, 4, 6), allMatch.removeAll(_ % 2 == 0)) + assertTrue(allMatch.isEmpty) + } + + // `ArrayDeque` overrides these, so test the default implementations on other buffers + @Test + def removeAllDefaults: Unit = + for (mk <- Seq[Seq[Int] => Buffer[Int]](ArrayBuffer.from, ListBuffer.from)) { + val all = mk(Seq(1, 2, 3)) + assertEquals(Seq(1, 2, 3), all.removeAll()) + assertTrue(all.isEmpty) + + val reversed = mk(Seq(1, 2, 3)) + assertEquals(Seq(3, 2, 1), reversed.removeAllReverse()) + assertTrue(reversed.isEmpty) + + val matching = mk(Seq(1, 2, 3, 4, 5)) + assertEquals(Seq(2, 4), matching.removeAll(_ % 2 == 0)) + assertEquals(Seq(1, 3, 5), matching) + + val noneMatch = mk(Seq(1, 3, 5)) + assertEquals(Seq(), noneMatch.removeAll(_ % 2 == 0)) + assertEquals(Seq(1, 3, 5), noneMatch) + } +} diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 67073370331d..f52195370945 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -46,6 +46,29 @@ object MiMaFilters { ProblemFilters.exclude[MissingTypesProblem]("scala.util.hashing.MurmurHash3$"), // MurmurHash3 is now compiled by Scala 3 instead of copied from Scala 2.13, so it has a default value for a method that is no longer final ProblemFilters.exclude[FinalMethodProblem]("scala.util.hashing.MurmurHash3.productHash$default$3()Boolean"), + // New ArrayDeque-style methods added to Buffer trait + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.Buffer.removeHead"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.Buffer.removeHeadOption"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.Buffer.removeLast"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.Buffer.removeLastOption"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.Buffer.removeAll"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.Buffer.removeAllReverse"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.Buffer.removeHeadWhile"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.Buffer.removeLastWhile"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.Buffer.removeFirst"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.Buffer.removeFirst$default$2"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.Buffer.removeFirst$default$2$"), + // Forwarders in AbstractBuffer + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.AbstractBuffer.removeHead"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.AbstractBuffer.removeHeadOption"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.AbstractBuffer.removeLast"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.AbstractBuffer.removeLastOption"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.AbstractBuffer.removeAll"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.AbstractBuffer.removeAllReverse"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.AbstractBuffer.removeHeadWhile"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.AbstractBuffer.removeLastWhile"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.AbstractBuffer.removeFirst"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.mutable.AbstractBuffer.removeFirst$default$2"), ) ++ (0 to 22).flatMap { n => // `applyUsing` for arity `n` is defined in the companion object of `FunctionN`, // which is a new object for all arities except 1 diff --git a/repl/test-resources/repl/type-print-import b/repl/test-resources/repl/type-print-import index 67f811c97407..5487ec8b02bd 100644 --- a/repl/test-resources/repl/type-print-import +++ b/repl/test-resources/repl/type-print-import @@ -1,11 +1,11 @@ scala> collection.mutable.Buffer() -val res0: collection.mutable.Buffer[Nothing] = ArrayBuffer() +val res0: collection.mutable.Buffer[Nothing] = ArrayDeque() scala> import collection.mutable scala> mutable.Buffer() -val res1: mutable.Buffer[Nothing] = ArrayBuffer() +val res1: mutable.Buffer[Nothing] = ArrayDeque() scala> import collection.mutable.ArrayBuffer scala> ArrayBuffer(1, 2, 3) val res2: ArrayBuffer[Int] = ArrayBuffer(1, 2, 3) scala> import collection.mutable.{Buffer => B} scala> B() -val res3: B[Nothing] = ArrayBuffer() +val res3: B[Nothing] = ArrayDeque()