From 154310dbee5d38416ec93be394be65e1dca0962f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 13 Aug 2023 16:32:28 -0500 Subject: [PATCH 01/51] Extract duplicate code in ChunkPlatform between 2.13 and 3 --- .../scala-2.13+/fs2/Chunk213And3Compat.scala | 42 +++++++++++++++++ .../main/scala-2.13/fs2/ChunkPlatform.scala | 43 +----------------- .../src/main/scala-3/fs2/ChunkPlatform.scala | 45 ++----------------- 3 files changed, 48 insertions(+), 82 deletions(-) create mode 100644 core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala new file mode 100644 index 0000000000..9b8d4f247c --- /dev/null +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -0,0 +1,42 @@ +package fs2 + +import scala.collection.immutable.ArraySeq +import scala.collection.immutable +import scala.reflect.ClassTag + +private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => + def toArraySeq[O2 >: O: ClassTag]: ArraySeq[O2] = { + val array: Array[O2] = new Array[O2](size) + copyToArray(array) + ArraySeq.unsafeWrapArray[O2](array) + } + + def toArraySeqUntagged: ArraySeq[O] = { + val buf = ArraySeq.untagged.newBuilder[O] + buf.sizeHint(size) + var i = 0 + while (i < size) { + buf += apply(i) + i += 1 + } + buf.result() + } +} + +private[fs2] trait ChunkCompanion213And3Compat { self: Chunk.type => + protected def platformIterable[O](i: Iterable[O]): Option[Chunk[O]] = + i match { + case a: immutable.ArraySeq[O] => Some(arraySeq(a)) + case _ => None + } + + /** Creates a chunk backed by an immutable `ArraySeq`. */ + def arraySeq[O](arraySeq: immutable.ArraySeq[O]): Chunk[O] = { + val arr = arraySeq.unsafeArray.asInstanceOf[Array[O]] + array(arr)(ClassTag[O](arr.getClass.getComponentType)) + } + + /** Creates a chunk from a `scala.collection.IterableOnce`. */ + def iterableOnce[O](i: collection.IterableOnce[O]): Chunk[O] = + iterator(i.iterator) +} diff --git a/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala b/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala index 402ae08aed..6ea7f012f6 100644 --- a/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala +++ b/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala @@ -21,46 +21,7 @@ package fs2 -import scala.collection.immutable.ArraySeq -import scala.collection.immutable -import scala.reflect.ClassTag +private[fs2] trait ChunkPlatform[+O] extends Chunk213And3Compat[O] { self: Chunk[O] => } -private[fs2] trait ChunkPlatform[+O] { self: Chunk[O] => - - def toArraySeq[O2 >: O: ClassTag]: ArraySeq[O2] = { - val array: Array[O2] = new Array[O2](size) - copyToArray(array) - ArraySeq.unsafeWrapArray[O2](array) - } - - def toArraySeqUntagged: ArraySeq[O] = { - val buf = ArraySeq.untagged.newBuilder[O] - buf.sizeHint(size) - var i = 0 - while (i < size) { - buf += apply(i) - i += 1 - } - buf.result() - } -} - -private[fs2] trait ChunkCompanionPlatform { self: Chunk.type => - - protected def platformIterable[O](i: Iterable[O]): Option[Chunk[O]] = - i match { - case a: immutable.ArraySeq[O] => Some(arraySeq(a)) - case _ => None - } - - /** Creates a chunk backed by an immutable `ArraySeq`. - */ - def arraySeq[O](arraySeq: immutable.ArraySeq[O]): Chunk[O] = { - val arr = arraySeq.unsafeArray.asInstanceOf[Array[O]] - array(arr)(ClassTag[O](arr.getClass.getComponentType)) - } - - /** Creates a chunk from a `scala.collection.IterableOnce`. */ - def iterableOnce[O](i: collection.IterableOnce[O]): Chunk[O] = - iterator(i.iterator) +private[fs2] trait ChunkCompanionPlatform extends ChunkCompanion213And3Compat { self: Chunk.type => } diff --git a/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala b/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala index 41782ed919..bb17128ca2 100644 --- a/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala +++ b/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala @@ -27,25 +27,7 @@ import scala.collection.immutable.ArraySeq import scala.collection.immutable import scala.reflect.ClassTag -private[fs2] trait ChunkPlatform[+O] { self: Chunk[O] => - - def toArraySeq[O2 >: O: ClassTag]: ArraySeq[O2] = { - val array: Array[O2] = new Array[O2](size) - copyToArray(array) - ArraySeq.unsafeWrapArray[O2](array) - } - - def toArraySeqUntagged: ArraySeq[O] = { - val buf = ArraySeq.untagged.newBuilder[O] - buf.sizeHint(size) - var i = 0 - while (i < size) { - buf += apply(i) - i += 1 - } - buf.result() - } - +private[fs2] trait ChunkPlatform[+O] extends Chunk213And3Compat[O] { self: Chunk[O] => def toIArray[O2 >: O: ClassTag]: IArray[O2] = IArray.unsafeFromArray(toArray) def toIArraySlice[O2 >: O](implicit ct: ClassTag[O2]): Chunk.IArraySlice[O2] = @@ -56,27 +38,12 @@ private[fs2] trait ChunkPlatform[+O] { self: Chunk[O] => } } -private[fs2] trait ChunkCompanionPlatform { self: Chunk.type => +private[fs2] trait ChunkCompanionPlatform extends ChunkCompanion213And3Compat { self: Chunk.type => - protected def platformIterable[O](i: Iterable[O]): Option[Chunk[O]] = - i match { - case a: immutable.ArraySeq[O] => Some(arraySeq(a)) - case _ => None - } - - /** Creates a chunk backed by an immutable `ArraySeq`. - */ - def arraySeq[O](arraySeq: immutable.ArraySeq[O]): Chunk[O] = { - val arr = arraySeq.unsafeArray.asInstanceOf[Array[O]] - array(arr)(ClassTag[O](arr.getClass.getComponentType)) - } - - /** Creates a chunk backed by an immutable array. - */ + /** Creates a chunk backed by an immutable array. */ def iarray[O: ClassTag](arr: IArray[O]): Chunk[O] = new IArraySlice(arr, 0, arr.length) - /** Creates a chunk backed by a slice of an immutable array. - */ + /** Creates a chunk backed by a slice of an immutable array. */ def iarray[O: ClassTag](arr: IArray[O], offset: Int, length: Int): Chunk[O] = new IArraySlice(arr, offset, length) @@ -121,8 +88,4 @@ private[fs2] trait ChunkCompanionPlatform { self: Chunk.type => ByteVector.view(values.asInstanceOf[Array[Byte]], offset, length) else ByteVector.viewAt(i => apply(i.toInt), size.toLong) } - - /** Creates a chunk from a `scala.collection.IterableOnce`. */ - def iterableOnce[O](i: collection.IterableOnce[O]): Chunk[O] = - iterator(i.iterator) } From 55d428f32bdb8c7cfd9add07cba05a13a183d68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 13 Aug 2023 16:40:26 -0500 Subject: [PATCH 02/51] Add Chunk.asSeq (2.13+) --- .../scala-2.13+/fs2/Chunk213And3Compat.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index 9b8d4f247c..988f52dcce 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -21,9 +21,28 @@ private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => } buf.result() } + + /** Views this Chunk as a Scala immutable Seq. + * Contrary to all methods that start with _"to"_ (e.g. {{toVector}}, {{toArray}}), + * this method does not copy data. + */ + def asSeq: Seq[O] = + new ChunkAsSeq(this) +} + +private[fs2] final class ChunkAsSeq[+O](chunk: Chunk[O]) extends immutable.Seq[O] { + override def iterator: Iterator[O] = + chunk.iterator + + override def apply(i: Int): O = + chunk.apply(i) + + override def length: Int = + chunk.size } private[fs2] trait ChunkCompanion213And3Compat { self: Chunk.type => + protected def platformIterable[O](i: Iterable[O]): Option[Chunk[O]] = i match { case a: immutable.ArraySeq[O] => Some(arraySeq(a)) From f0893df54a0efe3cb370949b54d3342582d615d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 13 Aug 2023 16:45:06 -0500 Subject: [PATCH 03/51] Add header to Chunk213And3Compat.scala --- .../scala-2.13+/fs2/Chunk213And3Compat.scala | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index 988f52dcce..c60f63dcd0 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2013 Functional Streams for Scala + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + package fs2 import scala.collection.immutable.ArraySeq From 36c4ab09fc5acabfe35eb3c02511286b5b093449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 13 Aug 2023 16:53:18 -0500 Subject: [PATCH 04/51] Make ChunkAsSeq extend Serializable --- core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index c60f63dcd0..2026fe6736 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -51,7 +51,9 @@ private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => new ChunkAsSeq(this) } -private[fs2] final class ChunkAsSeq[+O](chunk: Chunk[O]) extends immutable.Seq[O] { +private[fs2] final class ChunkAsSeq[+O](chunk: Chunk[O]) + extends immutable.Seq[O] + with Serializable { override def iterator: Iterator[O] = chunk.iterator From 7729762f9a9f84c1b962f1f22ee2471316c70ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 13 Aug 2023 17:53:44 -0500 Subject: [PATCH 05/51] Override more operations in ChunkAsSeq --- .../scala-2.13+/fs2/Chunk213And3Compat.scala | 68 ++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index 2026fe6736..6be676f16c 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -21,8 +21,8 @@ package fs2 +import scala.collection.Factory import scala.collection.immutable.ArraySeq -import scala.collection.immutable import scala.reflect.ClassTag private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => @@ -51,9 +51,7 @@ private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => new ChunkAsSeq(this) } -private[fs2] final class ChunkAsSeq[+O](chunk: Chunk[O]) - extends immutable.Seq[O] - with Serializable { +private[fs2] final class ChunkAsSeq[+O](chunk: Chunk[O]) extends Seq[O] with Serializable { override def iterator: Iterator[O] = chunk.iterator @@ -62,23 +60,77 @@ private[fs2] final class ChunkAsSeq[+O](chunk: Chunk[O]) override def length: Int = chunk.size + + override def foreach[U](f: O => U): Unit = + chunk.foreach { o => f(o); () } + + override def tapEach[U](f: O => U): Seq[O] = { + chunk.foreach { o => f(o); () } + this + } + + override def isEmpty: Boolean = + chunk.isEmpty + + override def copyToArray[B >: O](xs: Array[B], start: Int, len: Int): Int = { + chunk.take(len).copyToArray(xs, start) + math.min(len, xs.length - start) + } + + override def headOption: Option[O] = + chunk.head + + override def head: O = + if (chunk.nonEmpty) chunk.apply(0) + else throw new NoSuchElementException("head of empty Seq") + + override def lastOption: Option[O] = + chunk.last + + override def last: O = + if (chunk.nonEmpty) chunk.apply(chunk.size - 1) + else throw new NoSuchElementException("tail of empty Seq") + + override def take(n: Int): Seq[O] = + new ChunkAsSeq(chunk.take(n)) + + override def takeRight(n: Int): Seq[O] = + new ChunkAsSeq(chunk.takeRight(n)) + + override def to[C1](factory: Factory[O, C1]): C1 = + chunk.to(factory) + + override def toArray[B >: O: ClassTag]: Array[B] = + chunk.toArray + + override def toList: List[O] = + chunk.toList + + override def toVector: Vector[O] = + chunk.toVector + + override def toString: String = + chunk.toString + + override def zipWithIndex: Seq[(O, Int)] = + new ChunkAsSeq(chunk.zipWithIndex) } private[fs2] trait ChunkCompanion213And3Compat { self: Chunk.type => protected def platformIterable[O](i: Iterable[O]): Option[Chunk[O]] = i match { - case a: immutable.ArraySeq[O] => Some(arraySeq(a)) - case _ => None + case a: ArraySeq[O] => Some(arraySeq(a)) + case _ => None } /** Creates a chunk backed by an immutable `ArraySeq`. */ - def arraySeq[O](arraySeq: immutable.ArraySeq[O]): Chunk[O] = { + def arraySeq[O](arraySeq: ArraySeq[O]): Chunk[O] = { val arr = arraySeq.unsafeArray.asInstanceOf[Array[O]] array(arr)(ClassTag[O](arr.getClass.getComponentType)) } /** Creates a chunk from a `scala.collection.IterableOnce`. */ - def iterableOnce[O](i: collection.IterableOnce[O]): Chunk[O] = + def iterableOnce[O](i: IterableOnce[O]): Chunk[O] = iterator(i.iterator) } From dc591e816fa14b918a172e145792b1d9c88080f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 13 Aug 2023 19:02:59 -0500 Subject: [PATCH 06/51] Override equals and knowSize on ChunkAsSeq --- .../scala-2.13+/fs2/Chunk213And3Compat.scala | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index 6be676f16c..7db9db5a22 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -51,7 +51,10 @@ private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => new ChunkAsSeq(this) } -private[fs2] final class ChunkAsSeq[+O](chunk: Chunk[O]) extends Seq[O] with Serializable { +private[fs2] final class ChunkAsSeq[+O]( + private val chunk: Chunk[O] +) extends Seq[O] + with Serializable { override def iterator: Iterator[O] = chunk.iterator @@ -61,6 +64,12 @@ private[fs2] final class ChunkAsSeq[+O](chunk: Chunk[O]) extends Seq[O] with Ser override def length: Int = chunk.size + override def knownSize: Int = + chunk.size + + override def isEmpty: Boolean = + chunk.isEmpty + override def foreach[U](f: O => U): Unit = chunk.foreach { o => f(o); () } @@ -69,10 +78,7 @@ private[fs2] final class ChunkAsSeq[+O](chunk: Chunk[O]) extends Seq[O] with Ser this } - override def isEmpty: Boolean = - chunk.isEmpty - - override def copyToArray[B >: O](xs: Array[B], start: Int, len: Int): Int = { + override def copyToArray[O2 >: O](xs: Array[O2], start: Int, len: Int): Int = { chunk.take(len).copyToArray(xs, start) math.min(len, xs.length - start) } @@ -100,7 +106,7 @@ private[fs2] final class ChunkAsSeq[+O](chunk: Chunk[O]) extends Seq[O] with Ser override def to[C1](factory: Factory[O, C1]): C1 = chunk.to(factory) - override def toArray[B >: O: ClassTag]: Array[B] = + override def toArray[O2 >: O: ClassTag]: Array[O2] = chunk.toArray override def toList: List[O] = @@ -114,6 +120,15 @@ private[fs2] final class ChunkAsSeq[+O](chunk: Chunk[O]) extends Seq[O] with Ser override def zipWithIndex: Seq[(O, Int)] = new ChunkAsSeq(chunk.zipWithIndex) + + override def equals(that: Any): Boolean = + that match { + case thatChunkWrapper: ChunkAsSeq[_] => + chunk == thatChunkWrapper.chunk + + case _ => + false + } } private[fs2] trait ChunkCompanion213And3Compat { self: Chunk.type => From f10eefc1ccb745610130cf0df341d6564a08315d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 13 Aug 2023 19:10:20 -0500 Subject: [PATCH 07/51] Override reverseIterator on ChunkAsSeq --- core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index 7db9db5a22..d19b3e1375 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -70,6 +70,9 @@ private[fs2] final class ChunkAsSeq[+O]( override def isEmpty: Boolean = chunk.isEmpty + override def reverseIterator: Iterator[O] = + chunk.reverseIterator + override def foreach[U](f: O => U): Unit = chunk.foreach { o => f(o); () } From 44b2c405f0388934dfdcb6efb78600bdd72bc5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 13 Aug 2023 19:10:37 -0500 Subject: [PATCH 08/51] Add a couple of missing methods on Chunk --- core/shared/src/main/scala/fs2/Chunk.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 9e65bee0cf..48adab26a3 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -88,6 +88,10 @@ abstract class Chunk[+O] extends Serializable with ChunkPlatform[O] with ChunkRu Chunk.array(b.result()).asInstanceOf[Chunk[O2]] } + /** Returns true if the Chunk contains the given element. */ + def contains[O2 >: O](elem: O2): Boolean = + iterator.contains(elem) + /** Copies the elements of this chunk in to the specified array at the specified start index. */ def copyToArray[O2 >: O](xs: Array[O2], start: Int = 0): Unit @@ -104,12 +108,20 @@ abstract class Chunk[+O] extends Serializable with ChunkPlatform[O] with ChunkRu def compactUntagged[O2 >: O]: Chunk.ArraySlice[O2] = Chunk.ArraySlice(toArray[Any], 0, size).asInstanceOf[Chunk.ArraySlice[O2]] + /** Counts the number of elements which satisfy a predicate. */ + def count(p: O => Boolean): Int = + iterator.count(p) + /** Drops the first `n` elements of this chunk. */ def drop(n: Int): Chunk[O] = splitAt(n)._2 /** Drops the right-most `n` elements of this chunk queue in a way that preserves chunk structure. */ def dropRight(n: Int): Chunk[O] = if (n <= 0) this else take(size - n) + /** Returns true if at least one element passes the predicate. */ + def exists(p: O => Boolean): Boolean = + iterator.exists(p) + protected def thisClassTag: ClassTag[Any] = implicitly[ClassTag[Any]] /** Returns a chunk that has only the elements that satisfy the supplied predicate. */ From 9ad6aeee9cbf4aba024951f82d6bf70ffd23e942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 13 Aug 2023 19:12:32 -0500 Subject: [PATCH 09/51] Allow to unwrap a ChunkAsSeq --- .../src/main/scala-2.13+/fs2/Chunk213And3Compat.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index d19b3e1375..fa705d5c0a 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -52,7 +52,7 @@ private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => } private[fs2] final class ChunkAsSeq[+O]( - private val chunk: Chunk[O] + private[fs2] val chunk: Chunk[O] ) extends Seq[O] with Serializable { override def iterator: Iterator[O] = @@ -138,8 +138,9 @@ private[fs2] trait ChunkCompanion213And3Compat { self: Chunk.type => protected def platformIterable[O](i: Iterable[O]): Option[Chunk[O]] = i match { - case a: ArraySeq[O] => Some(arraySeq(a)) - case _ => None + case a: ArraySeq[O] => Some(arraySeq(a)) + case w: ChunkAsSeq[O] => Some(w.chunk) + case _ => None } /** Creates a chunk backed by an immutable `ArraySeq`. */ From bc2f3ab1a6e64308ce3e7a7d0a287a0dc3424795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 11:11:43 -0500 Subject: [PATCH 10/51] Fix Chunk.contains to use Eq --- core/shared/src/main/scala/fs2/Chunk.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 48adab26a3..165cdd5717 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -89,8 +89,8 @@ abstract class Chunk[+O] extends Serializable with ChunkPlatform[O] with ChunkRu } /** Returns true if the Chunk contains the given element. */ - def contains[O2 >: O](elem: O2): Boolean = - iterator.contains(elem) + def contains[O2 >: O: Eq](elem: O2): Boolean = + iterator.exists(o => Eq[O2].eqv(o, elem)) /** Copies the elements of this chunk in to the specified array at the specified start index. */ def copyToArray[O2 >: O](xs: Array[O2], start: Int = 0): Unit From d60a3a36bfbfe22298a13276d73f9e7a859eeee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 11:22:55 -0500 Subject: [PATCH 11/51] Override new methods on Foldable[Chunk] --- core/shared/src/main/scala/fs2/Chunk.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 165cdd5717..6d1880b1f0 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -1310,5 +1310,17 @@ object Chunk fa: Chunk[A] )(f: A => F[Option[B]])(implicit F: Applicative[F]): F[Chunk[B]] = fa.traverseFilter(f) override def mapFilter[A, B](fa: Chunk[A])(f: A => Option[B]): Chunk[B] = fa.mapFilter(f) + + override def contains_[A](fa: Chunk[A], v: A)(implicit ev: Eq[A]): Boolean = + fa.contains(v) + + override def count[A](fa: Chunk[A])(p: A => Boolean): Long = + fa.count(p).toLong + + override def exists[A](fa: Chunk[A])(p: A => Boolean): Boolean = + fa.exists(p) + + override def forall[A](fa: Chunk[A])(p: A => Boolean): Boolean = + fa.forall(p) } } From 1f439f725ce7fb7a37b2ddb39823f0e81ce4984b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 11:24:51 -0500 Subject: [PATCH 12/51] Be more explicit on the asSeq scaladoc --- core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index fa705d5c0a..cdd2e00620 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -46,6 +46,7 @@ private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => /** Views this Chunk as a Scala immutable Seq. * Contrary to all methods that start with _"to"_ (e.g. {{toVector}}, {{toArray}}), * this method does not copy data. + * As such, this method is mostly intended for `foreach` kind of interop. */ def asSeq: Seq[O] = new ChunkAsSeq(this) From 923db996be89f73b1a48d26cab8c092d8ce32023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 11:25:58 -0500 Subject: [PATCH 13/51] Make ChunkAsSeq.toString more explicit --- .sbtopts | 6 +++--- .../src/main/scala-2.13+/fs2/Chunk213And3Compat.scala | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.sbtopts b/.sbtopts index 224cb18950..19f5568a9b 100755 --- a/.sbtopts +++ b/.sbtopts @@ -1,3 +1,3 @@ --J-Xms2g --J-Xmx4g --J-XX:MaxMetaspaceSize=512m +-J-Xms4g +-J-Xmx8g +-J-XX:MaxMetaspaceSize=1024m diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index cdd2e00620..847b25dac5 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -120,7 +120,7 @@ private[fs2] final class ChunkAsSeq[+O]( chunk.toVector override def toString: String = - chunk.toString + chunk.iterator.mkString("ChunkAsSeq(", ", ", ")") override def zipWithIndex: Seq[(O, Int)] = new ChunkAsSeq(chunk.zipWithIndex) From 66256a8a68b609a43b78c9f7c00990f60caa90d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 12:45:50 -0500 Subject: [PATCH 14/51] Override ChunkAsSeq.iterableFactory (2.13+) --- .../src/main/scala-2.13+/fs2/Chunk213And3Compat.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index 847b25dac5..fad01bc301 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -21,7 +21,7 @@ package fs2 -import scala.collection.Factory +import scala.collection.{Factory, SeqFactory} import scala.collection.immutable.ArraySeq import scala.reflect.ClassTag @@ -133,6 +133,12 @@ private[fs2] final class ChunkAsSeq[+O]( case _ => false } + + override val iterableFactory: SeqFactory[Seq] = + ArraySeq.untagged + + override val empty: Seq[O] = + new ChunkAsSeq(Chunk.empty) } private[fs2] trait ChunkCompanion213And3Compat { self: Chunk.type => From dcab4c88e786820fbdeb86636afd2b7bef84ed14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 12:51:54 -0500 Subject: [PATCH 15/51] Revert .sbtops --- .sbtopts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.sbtopts b/.sbtopts index 19f5568a9b..224cb18950 100755 --- a/.sbtopts +++ b/.sbtopts @@ -1,3 +1,3 @@ --J-Xms4g --J-Xmx8g --J-XX:MaxMetaspaceSize=1024m +-J-Xms2g +-J-Xmx4g +-J-XX:MaxMetaspaceSize=512m From a41ce44e22828abd2fb7e75045b31807e7a45f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 14:21:53 -0500 Subject: [PATCH 16/51] Add Chunk.asJava --- core/shared/src/main/scala/fs2/Chunk.scala | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 6d1880b1f0..24abf081d5 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -22,11 +22,13 @@ package fs2 import scala.annotation.tailrec +import scala.annotation.unchecked.uncheckedVariance import scala.collection.immutable.{Queue => SQueue} import scala.collection.{IndexedSeq => GIndexedSeq, Seq => GSeq, mutable} import scala.reflect.ClassTag import scodec.bits.{BitVector, ByteVector} import java.nio.{Buffer => JBuffer, ByteBuffer => JByteBuffer, CharBuffer => JCharBuffer} +import java.{util => ju} import cats._ import cats.data.{Chain, NonEmptyList} @@ -525,6 +527,14 @@ abstract class Chunk[+O] extends Serializable with ChunkPlatform[O] with ChunkRu override def toString: String = iterator.mkString("Chunk(", ", ", ")") + + /** Views this Chunk as a Java unmodifiable List. + * Contrary to all methods that start with _"to"_ (e.g. {{toVector}}, {{toArray}}), + * this method does not copy data. + * As such, this method is mostly intended for `foreach` kind of interop. + */ + def asJava: ju.List[O @uncheckedVariance] = + new ChunkAsJavaList(this) } object Chunk @@ -1324,3 +1334,14 @@ object Chunk fa.forall(p) } } + +private[fs2] final class ChunkAsJavaList[O]( + private[fs2] val chunk: Chunk[O] +) extends ju.AbstractList[O] + with Serializable { + override def size(): Int = + chunk.size + + override def get(index: Int): O = + chunk.apply(index) +} From dff0e3917f5dd97bd3bce8017f7720cad3b90e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 15:17:00 -0500 Subject: [PATCH 17/51] Override more operations in ChunkAsJavaList --- core/shared/src/main/scala/fs2/Chunk.scala | 30 +++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 24abf081d5..66070a2341 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -1339,9 +1339,37 @@ private[fs2] final class ChunkAsJavaList[O]( private[fs2] val chunk: Chunk[O] ) extends ju.AbstractList[O] with Serializable { - override def size(): Int = + override def size: Int = chunk.size override def get(index: Int): O = chunk.apply(index) + + override def isEmpty: Boolean = + chunk.isEmpty + + override def iterator: ju.Iterator[O] = new ju.Iterator[O] { + private var i = 0 + + override def hasNext: Boolean = + i < chunk.size + + override def next(): O = { + val result = chunk.apply(i) + i += 1 + result + } + } + + override def toArray: Array[Object] = + chunk.toArray[Any].asInstanceOf[Array[Object]] + + override def toArray[T](a: Array[T with Object]): Array[T with Object] = { + val arr: Array[Object] = + if (a.length >= chunk.size) a.asInstanceOf[Array[Object]] + else new Array[Object](chunk.size) + + chunk.asInstanceOf[Chunk[Object]].copyToArray(arr) + arr.asInstanceOf[Array[T with Object]] + } } From 2041c746cc5d0ce36ebc3b2dc027a0e18eb206de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 15:24:58 -0500 Subject: [PATCH 18/51] Make ChunkAsJavaList implement RandomAccess --- core/shared/src/main/scala/fs2/Chunk.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 66070a2341..abc28f40b7 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -1338,6 +1338,7 @@ object Chunk private[fs2] final class ChunkAsJavaList[O]( private[fs2] val chunk: Chunk[O] ) extends ju.AbstractList[O] + with ju.RandomAccess with Serializable { override def size: Int = chunk.size From 76e808716e34eeb14c7a6a5a892d5e71d754d1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 15:37:46 -0500 Subject: [PATCH 19/51] Add Chunk.javaList to create chunks from Java lists (unwraps ChunkAsJavaList) --- core/shared/src/main/scala/fs2/Chunk.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index abc28f40b7..9e51671f09 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -641,6 +641,19 @@ object Chunk override def map[O2](f: O => O2): Chunk[O2] = indexedSeq(s.map(f)) } + /** Creates a chunk from a `java.util.List`. */ + def javaList[O](javaList: ju.List[O]): Chunk[O] = + javaList match { + case chunkAsJavaList: ChunkAsJavaList[O] => + chunkAsJavaList.chunk + + case _ => + val size = javaList.size + val arr = new Array[Object](size).asInstanceOf[Array[O with Object]] + javaList.toArray(arr) + new ArraySlice(arr, 0, size)(ClassTag.Object.asInstanceOf[ClassTag[O with Object]]) + } + /** Creates a chunk from a `scala.collection.Seq`. */ def seq[O](s: GSeq[O]): Chunk[O] = iterable(s) From 883e59e2af494438a2d8b181acb295d687e2bd1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 15:50:39 -0500 Subject: [PATCH 20/51] Optimize Chunk.asSeq to return the underlying Seq in the immutable.IndexedSeq case (2.13) --- .../main/scala-2.13+/fs2/Chunk213And3Compat.scala | 14 +++++++++++++- core/shared/src/main/scala/fs2/Chunk.scala | 4 +++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index fad01bc301..9514ee2540 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -49,7 +49,19 @@ private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => * As such, this method is mostly intended for `foreach` kind of interop. */ def asSeq: Seq[O] = - new ChunkAsSeq(this) + this match { + case indexedSeqChunk: Chunk.IndexedSeqChunk[_] => + indexedSeqChunk.s match { + case seq: Seq[O] => + seq + + case _ => + new ChunkAsSeq(this) + } + + case _ => + new ChunkAsSeq(this) + } } private[fs2] final class ChunkAsSeq[+O]( diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 9e51671f09..1116360c2f 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -611,7 +611,9 @@ object Chunk singleton(s.head) // Use size instead of tail.isEmpty as indexed seqs know their size else new IndexedSeqChunk(s) - private final class IndexedSeqChunk[O](s: GIndexedSeq[O]) extends Chunk[O] { + private[fs2] final class IndexedSeqChunk[O]( + private[fs2] val s: GIndexedSeq[O] + ) extends Chunk[O] { def size = s.length def apply(i: Int) = s(i) def copyToArray[O2 >: O](xs: Array[O2], start: Int): Unit = { From 803d72de90c4dfb6426f2f402843f51844a0ad65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 16:05:59 -0500 Subject: [PATCH 21/51] Add JavaListChunk --- core/shared/src/main/scala/fs2/Chunk.scala | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 1116360c2f..d3b15cf221 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -643,12 +643,15 @@ object Chunk override def map[O2](f: O => O2): Chunk[O2] = indexedSeq(s.map(f)) } - /** Creates a chunk from a `java.util.List`. */ + /** Creates a chunk from a mutable `java.util.List`. */ def javaList[O](javaList: ju.List[O]): Chunk[O] = javaList match { case chunkAsJavaList: ChunkAsJavaList[O] => chunkAsJavaList.chunk + case randomAccess: ju.RandomAccess => + new JavaListChunk(randomAccess) + case _ => val size = javaList.size val arr = new Array[Object](size).asInstanceOf[Array[O with Object]] @@ -656,6 +659,28 @@ object Chunk new ArraySlice(arr, 0, size)(ClassTag.Object.asInstanceOf[ClassTag[O with Object]]) } + private final class JavaListChunk[O]( + javaList: ju.List[O] with ju.RandomAccess + ) extends Chunk[O] { + override val size: Int = + javaList.size + + override def apply(i: Int): O = + javaList.get(i) + + override def copyToArray[O2 >: O](xs: Array[O2], start: Int): Unit = { + val javaListAsArray = javaList.toArray + System.arraycopy(javaListAsArray, 0, xs, start, javaListAsArray.length) + } + + override protected def splitAtChunk_(n: Int): (Chunk[O], Chunk[O]) = { + val left = javaList.subList(0, n + 1).asInstanceOf[ju.List[O] with ju.RandomAccess] + val right = javaList.subList(n + 1, size).asInstanceOf[ju.List[O] with ju.RandomAccess] + + new JavaListChunk(left) -> new JavaListChunk(right) + } + } + /** Creates a chunk from a `scala.collection.Seq`. */ def seq[O](s: GSeq[O]): Chunk[O] = iterable(s) From 4ab85a13155c774d3190d5db105e0a0e20f6ea6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 16:12:45 -0500 Subject: [PATCH 22/51] Optimize Chunk.asSeq to return an immutable ArraySeq in the ArraySlice case (2.13) --- .../src/main/scala-2.13+/fs2/Chunk213And3Compat.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index 9514ee2540..6b27691aa8 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -59,6 +59,14 @@ private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => new ChunkAsSeq(this) } + case arraySlice: Chunk.ArraySlice[_] => + ArraySeq + .unsafeWrapArray(arraySlice.values) + .slice( + from = arraySlice.offset, + until = arraySlice.offset + arraySlice.length + ) + case _ => new ChunkAsSeq(this) } From 5f0c1f7076a854f2a66e3d338ddbb4b8d60346e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 16:50:38 -0500 Subject: [PATCH 23/51] Move most of ChunkAsSeq code to the shared sources --- .../scala-2.13+/fs2/Chunk213And3Compat.scala | 112 +++--------------- core/shared/src/main/scala/fs2/Chunk.scala | 88 ++++++++++++++ 2 files changed, 103 insertions(+), 97 deletions(-) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index 6b27691aa8..9de048b5d0 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -25,7 +25,9 @@ import scala.collection.{Factory, SeqFactory} import scala.collection.immutable.ArraySeq import scala.reflect.ClassTag -private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => +private[fs2] trait Chunk213And3Compat[+O] { + self: Chunk[O] => + def toArraySeq[O2 >: O: ClassTag]: ArraySeq[O2] = { val array: Array[O2] = new Array[O2](size) copyToArray(array) @@ -42,117 +44,32 @@ private[fs2] trait Chunk213And3Compat[+O] { self: Chunk[O] => } buf.result() } - - /** Views this Chunk as a Scala immutable Seq. - * Contrary to all methods that start with _"to"_ (e.g. {{toVector}}, {{toArray}}), - * this method does not copy data. - * As such, this method is mostly intended for `foreach` kind of interop. - */ - def asSeq: Seq[O] = - this match { - case indexedSeqChunk: Chunk.IndexedSeqChunk[_] => - indexedSeqChunk.s match { - case seq: Seq[O] => - seq - - case _ => - new ChunkAsSeq(this) - } - - case arraySlice: Chunk.ArraySlice[_] => - ArraySeq - .unsafeWrapArray(arraySlice.values) - .slice( - from = arraySlice.offset, - until = arraySlice.offset + arraySlice.length - ) - - case _ => - new ChunkAsSeq(this) - } } -private[fs2] final class ChunkAsSeq[+O]( - private[fs2] val chunk: Chunk[O] -) extends Seq[O] - with Serializable { - override def iterator: Iterator[O] = - chunk.iterator - - override def apply(i: Int): O = - chunk.apply(i) - - override def length: Int = - chunk.size +private[fs2] trait ChunkAsSeq213And3Compat[+O] { + self: ChunkAsSeq[O] => override def knownSize: Int = chunk.size - override def isEmpty: Boolean = - chunk.isEmpty - - override def reverseIterator: Iterator[O] = - chunk.reverseIterator - - override def foreach[U](f: O => U): Unit = - chunk.foreach { o => f(o); () } - - override def tapEach[U](f: O => U): Seq[O] = { - chunk.foreach { o => f(o); () } - this - } - override def copyToArray[O2 >: O](xs: Array[O2], start: Int, len: Int): Int = { chunk.take(len).copyToArray(xs, start) math.min(len, xs.length - start) } - override def headOption: Option[O] = - chunk.head - - override def head: O = - if (chunk.nonEmpty) chunk.apply(0) - else throw new NoSuchElementException("head of empty Seq") - - override def lastOption: Option[O] = - chunk.last - - override def last: O = - if (chunk.nonEmpty) chunk.apply(chunk.size - 1) - else throw new NoSuchElementException("tail of empty Seq") - - override def take(n: Int): Seq[O] = - new ChunkAsSeq(chunk.take(n)) - - override def takeRight(n: Int): Seq[O] = - new ChunkAsSeq(chunk.takeRight(n)) - - override def to[C1](factory: Factory[O, C1]): C1 = - chunk.to(factory) - - override def toArray[O2 >: O: ClassTag]: Array[O2] = - chunk.toArray - - override def toList: List[O] = - chunk.toList - - override def toVector: Vector[O] = - chunk.toVector - - override def toString: String = - chunk.iterator.mkString("ChunkAsSeq(", ", ", ")") + override def map[O2](f: O => O2): Seq[O2] = + new ChunkAsSeq(chunk.map(f)) override def zipWithIndex: Seq[(O, Int)] = new ChunkAsSeq(chunk.zipWithIndex) - override def equals(that: Any): Boolean = - that match { - case thatChunkWrapper: ChunkAsSeq[_] => - chunk == thatChunkWrapper.chunk + override def tapEach[U](f: O => U): Seq[O] = { + chunk.foreach { o => f(o); () } + this + } - case _ => - false - } + override def to[C1](factory: Factory[O, C1]): C1 = + chunk.to(factory) override val iterableFactory: SeqFactory[Seq] = ArraySeq.untagged @@ -161,7 +78,8 @@ private[fs2] final class ChunkAsSeq[+O]( new ChunkAsSeq(Chunk.empty) } -private[fs2] trait ChunkCompanion213And3Compat { self: Chunk.type => +private[fs2] trait ChunkCompanion213And3Compat { + self: Chunk.type => protected def platformIterable[O](i: Iterable[O]): Option[Chunk[O]] = i match { diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index d3b15cf221..00c89bed77 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -528,6 +528,26 @@ abstract class Chunk[+O] extends Serializable with ChunkPlatform[O] with ChunkRu override def toString: String = iterator.mkString("Chunk(", ", ", ")") + /** Views this Chunk as a Scala immutable Seq. + * Contrary to all methods that start with _"to"_ (e.g. {{toVector}}, {{toArray}}), + * this method does not copy data. + * As such, this method is mostly intended for `foreach` kind of interop. + */ + def asSeq: Seq[O] = + asSeqPlatform.getOrElse(this match { + case indexedSeqChunk: Chunk.IndexedSeqChunk[_] => + indexedSeqChunk.s match { + case seq: Seq[O] => + seq + + case _ => + new ChunkAsSeq(this) + } + + case _ => + new ChunkAsSeq(this) + }) + /** Views this Chunk as a Java unmodifiable List. * Contrary to all methods that start with _"to"_ (e.g. {{toVector}}, {{toArray}}), * this method does not copy data. @@ -1375,6 +1395,74 @@ object Chunk } } +private[fs2] final class ChunkAsSeq[+O]( + private[fs2] val chunk: Chunk[O] +) extends Seq[O] + with ChunkAsSeqPlatform[O] + with Serializable { + override def iterator: Iterator[O] = + chunk.iterator + + override def apply(i: Int): O = + chunk.apply(i) + + override def length: Int = + chunk.size + + override def isEmpty: Boolean = + chunk.isEmpty + + override def reverseIterator: Iterator[O] = + chunk.reverseIterator + + override def foreach[U](f: O => U): Unit = + chunk.foreach { o => f(o); () } + + override def headOption: Option[O] = + chunk.head + + override def head: O = + if (chunk.nonEmpty) chunk.apply(0) + else throw new NoSuchElementException("head of empty Seq") + + override def lastOption: Option[O] = + chunk.last + + override def last: O = + if (chunk.nonEmpty) chunk.apply(chunk.size - 1) + else throw new NoSuchElementException("tail of empty Seq") + + override def filter(p: O => Boolean): Seq[O] = + new ChunkAsSeq(chunk.filter(p)) + + override def take(n: Int): Seq[O] = + new ChunkAsSeq(chunk.take(n)) + + override def takeRight(n: Int): Seq[O] = + new ChunkAsSeq(chunk.takeRight(n)) + + override def toArray[O2 >: O: ClassTag]: Array[O2] = + chunk.toArray + + override def toList: List[O] = + chunk.toList + + override def toVector: Vector[O] = + chunk.toVector + + override def toString: String = + chunk.iterator.mkString("ChunkAsSeq(", ", ", ")") + + override def equals(that: Any): Boolean = + that match { + case thatChunkWrapper: ChunkAsSeq[_] => + chunk == thatChunkWrapper.chunk + + case _ => + false + } +} + private[fs2] final class ChunkAsJavaList[O]( private[fs2] val chunk: Chunk[O] ) extends ju.AbstractList[O] From 3b3c63b7c03f0de6bb89ff504ff12f43d2f0907b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 16:51:20 -0500 Subject: [PATCH 24/51] Chunk.asSeq (Scala 2.13) --- .../main/scala-2.13/fs2/ChunkPlatform.scala | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala b/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala index 6ea7f012f6..662eded371 100644 --- a/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala +++ b/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala @@ -21,7 +21,32 @@ package fs2 -private[fs2] trait ChunkPlatform[+O] extends Chunk213And3Compat[O] { self: Chunk[O] => } +import scala.collection.immutable.ArraySeq -private[fs2] trait ChunkCompanionPlatform extends ChunkCompanion213And3Compat { self: Chunk.type => +private[fs2] trait ChunkPlatform[+O] extends Chunk213And3Compat[O] { + self: Chunk[O] => + + def asSeqPlatform: Option[Seq[O]] = + this match { + case arraySlice: Chunk.ArraySlice[_] => + Some( + ArraySeq + .unsafeWrapArray(arraySlice.values) + .slice( + from = arraySlice.offset, + until = arraySlice.offset + arraySlice.length + ) + ) + + case _ => + None + } +} + +private[fs2] trait ChunkAsSeqPlatform[+O] extends ChunkAsSeq213And3Compat[O] { + self: ChunkAsSeq[O] => +} + +private[fs2] trait ChunkCompanionPlatform extends ChunkCompanion213And3Compat { + self: Chunk.type => } From 063c730f647b13c2aaf38c4d115e2ed00bd39b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 17:02:53 -0500 Subject: [PATCH 25/51] Chunk.asSeq (Scala 3) --- .../src/main/scala-3/fs2/ChunkPlatform.scala | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala b/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala index bb17128ca2..6b7be179e0 100644 --- a/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala +++ b/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala @@ -27,7 +27,9 @@ import scala.collection.immutable.ArraySeq import scala.collection.immutable import scala.reflect.ClassTag -private[fs2] trait ChunkPlatform[+O] extends Chunk213And3Compat[O] { self: Chunk[O] => +private[fs2] trait ChunkPlatform[+O] extends Chunk213And3Compat[O] { + self: Chunk[O] => + def toIArray[O2 >: O: ClassTag]: IArray[O2] = IArray.unsafeFromArray(toArray) def toIArraySlice[O2 >: O](implicit ct: ClassTag[O2]): Chunk.IArraySlice[O2] = @@ -36,9 +38,42 @@ private[fs2] trait ChunkPlatform[+O] extends Chunk213And3Compat[O] { self: Chunk as.asInstanceOf[Chunk.IArraySlice[O2]] case _ => new Chunk.IArraySlice(IArray.unsafeFromArray(toArray(ct)), 0, size) } + + def asSeqPlatform: Option[Seq[O]] = + this match { + case arraySlice: Chunk.ArraySlice[_] => + Some( + ArraySeq + .unsafeWrapArray(arraySlice.values) + .slice( + from = arraySlice.offset, + until = arraySlice.offset + arraySlice.length + ) + ) + + case iArraySlice: Chunk.IArraySlice[_] => + Some( + ArraySeq + .unsafeWrapArray( + IArray.genericWrapArray(iArraySlice.values).toArray(iArraySlice.ct) + ) + .slice( + from = iArraySlice.offset, + until = iArraySlice.offset + iArraySlice.length + ) + ) + + case _ => + None + } +} + +private[fs2] trait ChunkAsSeqPlatform[+O] extends ChunkAsSeq213And3Compat[O] { + self: ChunkAsSeq[O] => } -private[fs2] trait ChunkCompanionPlatform extends ChunkCompanion213And3Compat { self: Chunk.type => +private[fs2] trait ChunkCompanionPlatform extends ChunkCompanion213And3Compat { + self: Chunk.type => /** Creates a chunk backed by an immutable array. */ def iarray[O: ClassTag](arr: IArray[O]): Chunk[O] = new IArraySlice(arr, 0, arr.length) @@ -47,8 +82,9 @@ private[fs2] trait ChunkCompanionPlatform extends ChunkCompanion213And3Compat { def iarray[O: ClassTag](arr: IArray[O], offset: Int, length: Int): Chunk[O] = new IArraySlice(arr, offset, length) - case class IArraySlice[O](values: IArray[O], offset: Int, length: Int)(implicit ct: ClassTag[O]) - extends Chunk[O] { + case class IArraySlice[O](values: IArray[O], offset: Int, length: Int)(implicit + private[fs2] val ct: ClassTag[O] + ) extends Chunk[O] { require( offset >= 0 && offset <= values.size && length >= 0 && length <= values.size && offset + length <= values.size ) From f85a50db1ef0ebbd87c9876869197da0837ad04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Mon, 14 Aug 2023 17:39:08 -0500 Subject: [PATCH 26/51] Chunk.asSeq (Scala 2.12) --- .../main/scala-2.12/fs2/ChunkPlatform.scala | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala b/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala index 24644073f3..28ea7c2a7a 100644 --- a/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala +++ b/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala @@ -21,12 +21,49 @@ package fs2 -import scala.collection.mutable.WrappedArray +import scala.annotation.unchecked.uncheckedVariance +import scala.collection.generic.{CanBuildFrom, GenericCompanion} +import scala.collection.mutable.{Builder, WrappedArray} import scala.reflect.ClassTag -private[fs2] trait ChunkPlatform[+O] { self: Chunk[O] => } +private[fs2] trait ChunkPlatform[+O] { + self: Chunk[O] => -private[fs2] trait ChunkCompanionPlatform { self: Chunk.type => + def asSeqPlatform: Option[Seq[O]] = + None +} + +private[fs2] trait ChunkAsSeqPlatform[+O] { + self: ChunkAsSeq[O] => + + override val hasDefiniteSize: Boolean = + true + + override def copyToArray[O2 >: O](xs: Array[O2], start: Int, len: Int): Unit = + chunk.take(len).copyToArray(xs, start) + + override def map[O2, That](f: O ⇒ O2)(implicit bf: CanBuildFrom[Seq[O], O2, That]): That = + new ChunkAsSeq(chunk.map(f)).asInstanceOf[That] + + override def zipWithIndex[O2 >: O, That](implicit + bf: CanBuildFrom[Seq[O], (O2, Int), That] + ): That = + new ChunkAsSeq(chunk.zipWithIndex).asInstanceOf[That] + + override def to[Col[_]](implicit + cbf: CanBuildFrom[Nothing, O, Col[O @uncheckedVariance]] + ): Col[O @uncheckedVariance] = + chunk.to(cbf) + + override def genericBuilder[B]: Builder[B, Seq[B]] = + Vector.newBuilder + + override def companion: GenericCompanion[Seq] = + Vector +} + +private[fs2] trait ChunkCompanionPlatform { + self: Chunk.type => protected def platformIterable[O](i: Iterable[O]): Option[Chunk[O]] = i match { From d56090fa8627c79beccb5f514c1050802d162d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 15 Aug 2023 18:08:11 -0500 Subject: [PATCH 27/51] Make asSeq return an IndexedSeq --- .../src/main/scala-2.12/fs2/ChunkPlatform.scala | 10 +++++----- .../main/scala-2.13+/fs2/Chunk213And3Compat.scala | 10 +++++----- .../src/main/scala-2.13/fs2/ChunkPlatform.scala | 2 +- .../src/main/scala-3/fs2/ChunkPlatform.scala | 2 +- core/shared/src/main/scala/fs2/Chunk.scala | 14 +++++++------- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala b/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala index 28ea7c2a7a..c37361e503 100644 --- a/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala +++ b/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala @@ -29,7 +29,7 @@ import scala.reflect.ClassTag private[fs2] trait ChunkPlatform[+O] { self: Chunk[O] => - def asSeqPlatform: Option[Seq[O]] = + def asSeqPlatform: Option[IndexedSeq[O]] = None } @@ -42,11 +42,11 @@ private[fs2] trait ChunkAsSeqPlatform[+O] { override def copyToArray[O2 >: O](xs: Array[O2], start: Int, len: Int): Unit = chunk.take(len).copyToArray(xs, start) - override def map[O2, That](f: O ⇒ O2)(implicit bf: CanBuildFrom[Seq[O], O2, That]): That = + override def map[O2, That](f: O ⇒ O2)(implicit bf: CanBuildFrom[IndexedSeq[O], O2, That]): That = new ChunkAsSeq(chunk.map(f)).asInstanceOf[That] override def zipWithIndex[O2 >: O, That](implicit - bf: CanBuildFrom[Seq[O], (O2, Int), That] + bf: CanBuildFrom[IndexedSeq[O], (O2, Int), That] ): That = new ChunkAsSeq(chunk.zipWithIndex).asInstanceOf[That] @@ -55,10 +55,10 @@ private[fs2] trait ChunkAsSeqPlatform[+O] { ): Col[O @uncheckedVariance] = chunk.to(cbf) - override def genericBuilder[B]: Builder[B, Seq[B]] = + override def genericBuilder[B]: Builder[B, IndexedSeq[B]] = Vector.newBuilder - override def companion: GenericCompanion[Seq] = + override def companion: GenericCompanion[IndexedSeq] = Vector } diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index 9de048b5d0..826af1c50e 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -57,13 +57,13 @@ private[fs2] trait ChunkAsSeq213And3Compat[+O] { math.min(len, xs.length - start) } - override def map[O2](f: O => O2): Seq[O2] = + override def map[O2](f: O => O2): IndexedSeq[O2] = new ChunkAsSeq(chunk.map(f)) - override def zipWithIndex: Seq[(O, Int)] = + override def zipWithIndex: IndexedSeq[(O, Int)] = new ChunkAsSeq(chunk.zipWithIndex) - override def tapEach[U](f: O => U): Seq[O] = { + override def tapEach[U](f: O => U): IndexedSeq[O] = { chunk.foreach { o => f(o); () } this } @@ -71,10 +71,10 @@ private[fs2] trait ChunkAsSeq213And3Compat[+O] { override def to[C1](factory: Factory[O, C1]): C1 = chunk.to(factory) - override val iterableFactory: SeqFactory[Seq] = + override val iterableFactory: SeqFactory[IndexedSeq] = ArraySeq.untagged - override val empty: Seq[O] = + override val empty: IndexedSeq[O] = new ChunkAsSeq(Chunk.empty) } diff --git a/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala b/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala index 662eded371..3a3273dff3 100644 --- a/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala +++ b/core/shared/src/main/scala-2.13/fs2/ChunkPlatform.scala @@ -26,7 +26,7 @@ import scala.collection.immutable.ArraySeq private[fs2] trait ChunkPlatform[+O] extends Chunk213And3Compat[O] { self: Chunk[O] => - def asSeqPlatform: Option[Seq[O]] = + def asSeqPlatform: Option[IndexedSeq[O]] = this match { case arraySlice: Chunk.ArraySlice[_] => Some( diff --git a/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala b/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala index 6b7be179e0..3ab49ed656 100644 --- a/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala +++ b/core/shared/src/main/scala-3/fs2/ChunkPlatform.scala @@ -39,7 +39,7 @@ private[fs2] trait ChunkPlatform[+O] extends Chunk213And3Compat[O] { case _ => new Chunk.IArraySlice(IArray.unsafeFromArray(toArray(ct)), 0, size) } - def asSeqPlatform: Option[Seq[O]] = + def asSeqPlatform: Option[IndexedSeq[O]] = this match { case arraySlice: Chunk.ArraySlice[_] => Some( diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 00c89bed77..39e60c940d 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -533,12 +533,12 @@ abstract class Chunk[+O] extends Serializable with ChunkPlatform[O] with ChunkRu * this method does not copy data. * As such, this method is mostly intended for `foreach` kind of interop. */ - def asSeq: Seq[O] = + def asSeq: IndexedSeq[O] = asSeqPlatform.getOrElse(this match { case indexedSeqChunk: Chunk.IndexedSeqChunk[_] => indexedSeqChunk.s match { - case seq: Seq[O] => - seq + case indexedSeq: IndexedSeq[O] => + indexedSeq case _ => new ChunkAsSeq(this) @@ -1397,7 +1397,7 @@ object Chunk private[fs2] final class ChunkAsSeq[+O]( private[fs2] val chunk: Chunk[O] -) extends Seq[O] +) extends IndexedSeq[O] with ChunkAsSeqPlatform[O] with Serializable { override def iterator: Iterator[O] = @@ -1432,13 +1432,13 @@ private[fs2] final class ChunkAsSeq[+O]( if (chunk.nonEmpty) chunk.apply(chunk.size - 1) else throw new NoSuchElementException("tail of empty Seq") - override def filter(p: O => Boolean): Seq[O] = + override def filter(p: O => Boolean): IndexedSeq[O] = new ChunkAsSeq(chunk.filter(p)) - override def take(n: Int): Seq[O] = + override def take(n: Int): IndexedSeq[O] = new ChunkAsSeq(chunk.take(n)) - override def takeRight(n: Int): Seq[O] = + override def takeRight(n: Int): IndexedSeq[O] = new ChunkAsSeq(chunk.takeRight(n)) override def toArray[O2 >: O: ClassTag]: Array[O2] = From 37bdb0589da92cf4e2be3e196e4d26e881242f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 15 Aug 2023 19:02:09 -0500 Subject: [PATCH 28/51] Improve Chunk.contains Co-authored-by: Sergey Torgashov --- core/shared/src/main/scala/fs2/Chunk.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 39e60c940d..0c67fb4ae9 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -91,8 +91,8 @@ abstract class Chunk[+O] extends Serializable with ChunkPlatform[O] with ChunkRu } /** Returns true if the Chunk contains the given element. */ - def contains[O2 >: O: Eq](elem: O2): Boolean = - iterator.exists(o => Eq[O2].eqv(o, elem)) + def contains[O2 >: O](elem: O2)(implicit ev: Eq[O2]): Boolean = + iterator.exists(o => ev.eqv(o, elem)) /** Copies the elements of this chunk in to the specified array at the specified start index. */ def copyToArray[O2 >: O](xs: Array[O2], start: Int = 0): Unit From 9050e92f2b4017aea1a5679a79f2ff0bef78f5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 15 Aug 2023 18:17:15 -0500 Subject: [PATCH 29/51] Add comments for JavaListChunk --- core/shared/src/main/scala/fs2/Chunk.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 0c67fb4ae9..2299c27a1f 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -679,6 +679,8 @@ object Chunk new ArraySlice(arr, 0, size)(ClassTag.Object.asInstanceOf[ClassTag[O with Object]]) } + // Added in the "Avoid copying" spirit. + // It may be better removed if data shows it has little usage. private final class JavaListChunk[O]( javaList: ju.List[O] with ju.RandomAccess ) extends Chunk[O] { From c197ef965fc31029069635f9d6bedc3078508da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Tue, 15 Aug 2023 18:35:31 -0500 Subject: [PATCH 30/51] Deprecate specific collection Chunk factories in favour of Chunk.from --- .../main/scala-2.12/fs2/ChunkPlatform.scala | 21 +++-- .../scala-2.13+/fs2/Chunk213And3Compat.scala | 26 ++++-- core/shared/src/main/scala/fs2/Chunk.scala | 84 +++++++++++++------ core/shared/src/main/scala/fs2/Stream.scala | 30 +++---- core/shared/src/main/scala/fs2/text.scala | 4 +- .../scala/fs2/timeseries/TimeSeries.scala | 2 +- .../scala/fs2/timeseries/TimeStamped.scala | 2 +- 7 files changed, 110 insertions(+), 59 deletions(-) diff --git a/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala b/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala index c37361e503..3730fd2f0f 100644 --- a/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala +++ b/core/shared/src/main/scala-2.12/fs2/ChunkPlatform.scala @@ -22,6 +22,7 @@ package fs2 import scala.annotation.unchecked.uncheckedVariance +import scala.collection.{Iterable => GIterable} import scala.collection.generic.{CanBuildFrom, GenericCompanion} import scala.collection.mutable.{Builder, WrappedArray} import scala.reflect.ClassTag @@ -65,16 +66,22 @@ private[fs2] trait ChunkAsSeqPlatform[+O] { private[fs2] trait ChunkCompanionPlatform { self: Chunk.type => - protected def platformIterable[O](i: Iterable[O]): Option[Chunk[O]] = + protected def platformFrom[O](i: GIterable[O]): Option[Chunk[O]] = i match { - case a: WrappedArray[O] => Some(wrappedArray(a)) - case _ => None + case wrappedArray: WrappedArray[O] => + val arr = wrappedArray.array.asInstanceOf[Array[O]] + Some(array(arr)(ClassTag(arr.getClass.getComponentType))) + + case _ => + None } /** Creates a chunk backed by a `WrappedArray` */ - def wrappedArray[O](wrappedArray: WrappedArray[O]): Chunk[O] = { - val arr = wrappedArray.array.asInstanceOf[Array[O]] - array(arr)(ClassTag(arr.getClass.getComponentType)) - } + @deprecated( + "Use the `from` general factory instead", + "3.8.1" + ) + def wrappedArray[O](wrappedArray: WrappedArray[O]): Chunk[O] = + from(wrappedArray) } diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index 826af1c50e..379bef9d37 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -21,7 +21,7 @@ package fs2 -import scala.collection.{Factory, SeqFactory} +import scala.collection.{Iterable => GIterable, Factory, SeqFactory} import scala.collection.immutable.ArraySeq import scala.reflect.ClassTag @@ -81,18 +81,26 @@ private[fs2] trait ChunkAsSeq213And3Compat[+O] { private[fs2] trait ChunkCompanion213And3Compat { self: Chunk.type => - protected def platformIterable[O](i: Iterable[O]): Option[Chunk[O]] = + protected def platformFrom[O](i: GIterable[O]): Option[Chunk[O]] = i match { - case a: ArraySeq[O] => Some(arraySeq(a)) - case w: ChunkAsSeq[O] => Some(w.chunk) - case _ => None + case arraySeq: ArraySeq[O] => + val arr = arraySeq.unsafeArray.asInstanceOf[Array[O]] + Some(array(arr)(ClassTag[O](arr.getClass.getComponentType))) + + case w: ChunkAsSeq[O] => + Some(w.chunk) + + case _ => + None } /** Creates a chunk backed by an immutable `ArraySeq`. */ - def arraySeq[O](arraySeq: ArraySeq[O]): Chunk[O] = { - val arr = arraySeq.unsafeArray.asInstanceOf[Array[O]] - array(arr)(ClassTag[O](arr.getClass.getComponentType)) - } + @deprecated( + "Use the `from` general factory instead", + "3.8.1" + ) + def arraySeq[O](arraySeq: ArraySeq[O]): Chunk[O] = + from(arraySeq) /** Creates a chunk from a `scala.collection.IterableOnce`. */ def iterableOnce[O](i: IterableOnce[O]): Chunk[O] = diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 2299c27a1f..4298825672 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -24,7 +24,7 @@ package fs2 import scala.annotation.tailrec import scala.annotation.unchecked.uncheckedVariance import scala.collection.immutable.{Queue => SQueue} -import scala.collection.{IndexedSeq => GIndexedSeq, Seq => GSeq, mutable} +import scala.collection.{Iterable => GIterable, IndexedSeq => GIndexedSeq, Seq => GSeq, mutable} import scala.reflect.ClassTag import scodec.bits.{BitVector, ByteVector} import java.nio.{Buffer => JBuffer, ByteBuffer => JByteBuffer, CharBuffer => JCharBuffer} @@ -289,7 +289,7 @@ abstract class Chunk[+O] extends Serializable with ChunkPlatform[O] with ChunkRu /** Check to see if this starts with the items in the given seq. */ def startsWith[O2 >: O](seq: Seq[O2]): Boolean = - startsWith(Chunk.seq(seq)) + startsWith(Chunk.from(seq)) /** Takes the first `n` elements of this chunk. */ def take(n: Int): Chunk[O] = splitAt(n)._1 @@ -622,14 +622,20 @@ object Chunk } /** Creates a chunk backed by a vector. */ - def vector[O](v: Vector[O]): Chunk[O] = indexedSeq(v) + @deprecated( + "Use the `from` general factory instead", + "3.8.1" + ) + def vector[O](v: Vector[O]): Chunk[O] = + from(v) /** Creates a chunk backed by an `IndexedSeq`. */ + @deprecated( + "Use the `from` general factory instead", + "3.8.1" + ) def indexedSeq[O](s: GIndexedSeq[O]): Chunk[O] = - if (s.isEmpty) empty - else if (s.size == 1) - singleton(s.head) // Use size instead of tail.isEmpty as indexed seqs know their size - else new IndexedSeqChunk(s) + from(s) private[fs2] final class IndexedSeqChunk[O]( private[fs2] val s: GIndexedSeq[O] @@ -649,18 +655,20 @@ object Chunk override def drop(n: Int): Chunk[O] = if (n <= 0) this else if (n >= size) Chunk.empty - else indexedSeq(s.drop(n)) + else from(s.drop(n)) override def take(n: Int): Chunk[O] = if (n <= 0) Chunk.empty else if (n >= size) this - else indexedSeq(s.take(n)) + else from(s.take(n)) protected def splitAtChunk_(n: Int): (Chunk[O], Chunk[O]) = { val (fst, snd) = s.splitAt(n) - indexedSeq(fst) -> indexedSeq(snd) + from(fst) -> from(snd) } - override def map[O2](f: O => O2): Chunk[O2] = indexedSeq(s.map(f)) + + override def map[O2](f: O => O2): Chunk[O2] = + from(s.map(f)) } /** Creates a chunk from a mutable `java.util.List`. */ @@ -704,13 +712,27 @@ object Chunk } /** Creates a chunk from a `scala.collection.Seq`. */ - def seq[O](s: GSeq[O]): Chunk[O] = iterable(s) + @deprecated( + "Use the `from` general factory instead", + "3.8.1" + ) + def seq[O](s: GSeq[O]): Chunk[O] = + from(s) /** Creates a chunk from a `scala.collection.Iterable`. */ - def iterable[O](i: collection.Iterable[O]): Chunk[O] = - platformIterable(i).getOrElse(i match { - case a: mutable.ArraySeq[o] => arraySeq[o](a).asInstanceOf[Chunk[O]] - case v: Vector[O] => vector(v) + @deprecated( + "Use the `from` general factory instead", + "3.8.1" + ) + def iterable[O](i: GIterable[O]): Chunk[O] = + from(i) + + def from[O](i: GIterable[O]): Chunk[O] = + platformFrom(i).getOrElse(i match { + case a: mutable.ArraySeq[o] => + val arr = a.array.asInstanceOf[Array[O]] + array(arr)(ClassTag(arr.getClass.getComponentType)) + case l: List[O] => if (l.isEmpty) empty else if (l.tail.isEmpty) singleton(l.head) @@ -719,7 +741,13 @@ object Chunk bldr ++= l array(bldr.result()).asInstanceOf[Chunk[O]] } - case ix: GIndexedSeq[O] => indexedSeq(ix) + + case s: GIndexedSeq[O] => + if (s.isEmpty) empty + else if (s.size == 1) + singleton(s.head) // Use size instead of tail.isEmpty as indexed seqs know their size + else new IndexedSeqChunk(s) + case _ => if (i.isEmpty) empty else iterator(i.iterator) @@ -740,10 +768,12 @@ object Chunk /** Creates a chunk backed by a mutable `ArraySeq`. */ - def arraySeq[O](arraySeq: mutable.ArraySeq[O]): Chunk[O] = { - val arr = arraySeq.array.asInstanceOf[Array[O]] - array(arr)(ClassTag(arr.getClass.getComponentType)) - } + @deprecated( + "Use the `from` general factory instead", + "3.8.1" + ) + def arraySeq[O](arraySeq: mutable.ArraySeq[O]): Chunk[O] = + from(arraySeq) /** Creates a chunk backed by a `Chain`. */ def chain[O](c: Chain[O]): Chunk[O] = @@ -767,7 +797,8 @@ object Chunk } /** Creates a chunk with the specified values. */ - def apply[O](os: O*): Chunk[O] = seq(os) + def apply[O](os: O*): Chunk[O] = + from(os) /** Creates a chunk backed by an array. */ def array[O: ClassTag](values: Array[O]): Chunk[O] = @@ -1037,7 +1068,7 @@ object Chunk } override def map[O2](f: Byte => O2): Chunk[O2] = - Chunk.indexedSeq(bv.toIndexedSeq.map(f)) + Chunk.from(bv.toIndexedSeq.map(f)) override def toByteVector[B >: Byte](implicit ev: B =:= Byte): ByteVector = bv @@ -1078,7 +1109,12 @@ object Chunk /** Creates a chunk consisting of the elements of `queue`. */ - def queue[A](queue: collection.immutable.Queue[A]): Chunk[A] = seq(queue) + @deprecated( + "Use the `from` general factory instead", + "3.8.1" + ) + def queue[A](queue: collection.immutable.Queue[A]): Chunk[A] = + from(queue) /** Creates a chunk consisting of the first `n` elements of `queue` and returns the remainder. */ diff --git a/core/shared/src/main/scala/fs2/Stream.scala b/core/shared/src/main/scala/fs2/Stream.scala index eebc94806c..58bfa2d325 100644 --- a/core/shared/src/main/scala/fs2/Stream.scala +++ b/core/shared/src/main/scala/fs2/Stream.scala @@ -332,13 +332,13 @@ final class Stream[+F[_], +O] private[fs2] (private[fs2] val underlying: Pull[F, case ((out, buf, last), i) => val cur = f(i) if (!cur && last) - (Chunk.vector(buf :+ i) :: out, Vector.empty, cur) + (Chunk.from(buf :+ i) :: out, Vector.empty, cur) else (out, buf :+ i, cur) } if (out.isEmpty) - go(Chunk.vector(buf) :: buffer, newLast, tl) + go(Chunk.from(buf) :: buffer, newLast, tl) else - dumpBuffer(buffer) >> dumpBuffer(out) >> go(List(Chunk.vector(buf)), newLast, tl) + dumpBuffer(buffer) >> dumpBuffer(out) >> go(List(Chunk.from(buf)), newLast, tl) case None => dumpBuffer(buffer) } @@ -576,7 +576,7 @@ final class Stream[+F[_], +O] private[fs2] (private[fs2] val underlying: Pull[F, /** Prepends a chunk onto the front of this stream. * * @example {{{ - * scala> Stream(1,2,3).consChunk(Chunk.vector(Vector(-1, 0))).toList + * scala> Stream(1,2,3).consChunk(Chunk.from(Vector(-1, 0))).toList * res0: List[Int] = List(-1, 0, 1, 2, 3) * }}} */ @@ -1153,7 +1153,7 @@ final class Stream[+F[_], +O] private[fs2] (private[fs2] val underlying: Pull[F, if (f(last, o)) (acc :+ o, o) else (acc, last) } - Pull.output(Chunk.vector(acc)) >> go(newLast, tl) + Pull.output(Chunk.from(acc)) >> go(newLast, tl) } } this.pull.uncons1.flatMap { @@ -1183,7 +1183,7 @@ final class Stream[+F[_], +O] private[fs2] (private[fs2] val underlying: Pull[F, * the source stream and concatenated all of the results. * * @example {{{ - * scala> Stream(1, 2, 3).flatMap { i => Stream.chunk(Chunk.seq(List.fill(i)(i))) }.toList + * scala> Stream(1, 2, 3).flatMap { i => Stream.chunk(Chunk.from(List.fill(i)(i))) }.toList * res0: List[Int] = List(1, 2, 2, 3, 3, 3) * }}} */ @@ -1338,10 +1338,10 @@ final class Stream[+F[_], +O] private[fs2] (private[fs2] val underlying: Pull[F, // whole chunk matches the current key, add this chunk to the accumulated output if (out.size + chunk.size < limit) { val newCurrent = Some((k1, out ++ chunk)) - Pull.output(Chunk.seq(acc)) >> go(newCurrent, s) + Pull.output(Chunk.from(acc)) >> go(newCurrent, s) } else { val (prefix, suffix) = chunk.splitAt(limit - out.size) - Pull.output(Chunk.seq(acc :+ ((k1, out ++ prefix)))) >> go( + Pull.output(Chunk.from(acc :+ ((k1, out ++ prefix)))) >> go( Some((k1, suffix)), s ) @@ -1505,7 +1505,7 @@ final class Stream[+F[_], +O] private[fs2] (private[fs2] val underlying: Pull[F, Stream .eval(dequeueNextOutput) .repeat - .collectWhile { case Some(data) => Chunk.vector(data) } + .collectWhile { case Some(data) => Chunk.from(data) } Stream .bracketCase(enqueueAsync) { case (upstream, exitCase) => @@ -1736,7 +1736,7 @@ final class Stream[+F[_], +O] private[fs2] (private[fs2] val underlying: Pull[F, bldr += separator bldr += o } - Chunk.vector(bldr.result()) + Chunk.from(bldr.result()) } def go(str: Stream[F, O]): Pull[F, O2, Unit] = str.pull.uncons.flatMap { @@ -2061,7 +2061,7 @@ final class Stream[+F[_], +O] private[fs2] (private[fs2] val underlying: Pull[F, rChunk.indexWhere(order.gteq(_, lLast)).getOrElse(rChunk.size) ) Pull.output( - Chunk.vector((emitLeft ++ emitRight).toVector.sorted(order)) + Chunk.from((emitLeft ++ emitRight).toVector.sorted(order)) ) >> go(leftLeg.setHead(keepLeft), rightLeg.setHead(keepRight)) } else { // otherwise, we need to shift leg if (lChunk.isEmpty) { @@ -3255,7 +3255,7 @@ object Stream extends StreamLowPriority { os match { case Nil => empty case collection.Seq(x) => emit(x) - case _ => Pull.output(Chunk.seq(os)).streamNoScope + case _ => Pull.output(Chunk.from(os)).streamNoScope } /** Empty pure stream. */ @@ -3468,7 +3468,7 @@ object Stream extends StreamLowPriority { F.suspend(hint) { for (_ <- 1 to chunkSize if i.hasNext) yield i.next() }.map { s => - if (s.isEmpty) None else Some((Chunk.seq(s), i)) + if (s.isEmpty) None else Some((Chunk.from(s), i)) } Stream.unfoldChunkEval(iterator)(getNextChunk) @@ -3697,7 +3697,7 @@ object Stream extends StreamLowPriority { /** Like `emits`, but works for any class that extends `Iterable` */ def iterable[F[x] >: Pure[x], A](os: Iterable[A]): Stream[F, A] = - Stream.chunk(Chunk.iterable(os)) + Stream.chunk(Chunk.from(os)) /** An infinite `Stream` that repeatedly applies a given function * to a start value. `start` is the first value emitted, followed @@ -3943,7 +3943,7 @@ object Stream extends StreamLowPriority { /** Like [[unfold]] but each invocation of `f` provides a chunk of output. * * @example {{{ - * scala> Stream.unfoldChunk(0)(i => if (i < 5) Some(Chunk.seq(List.fill(i)(i)) -> (i+1)) else None).toList + * scala> Stream.unfoldChunk(0)(i => if (i < 5) Some(Chunk.from(List.fill(i)(i)) -> (i+1)) else None).toList * res0: List[Int] = List(1, 2, 2, 3, 3, 3, 4, 4, 4, 4) * }}} */ diff --git a/core/shared/src/main/scala/fs2/text.scala b/core/shared/src/main/scala/fs2/text.scala index 70bdb84f09..378efde427 100644 --- a/core/shared/src/main/scala/fs2/text.scala +++ b/core/shared/src/main/scala/fs2/text.scala @@ -169,7 +169,7 @@ object text { buf1 = processSingleChunk(bldr, buf1, nextBytes) idx = idx + 1 } - Pull.output(Chunk.seq(bldr.result())) >> doPull(buf1, tail) + Pull.output(Chunk.from(bldr.result())) >> doPull(buf1, tail) case None if buf.nonEmpty => Pull.output1(new String(buf.toArray, utf8Charset)) case None => @@ -554,7 +554,7 @@ object text { new LineTooLongException(stringBuilder.length, max) )(raiseThrowable) case _ => - Pull.output(Chunk.indexedSeq(linesBuffer)) >> go(stream, stringBuilder, first = false) + Pull.output(Chunk.from(linesBuffer)) >> go(stream, stringBuilder, first = false) } } diff --git a/core/shared/src/main/scala/fs2/timeseries/TimeSeries.scala b/core/shared/src/main/scala/fs2/timeseries/TimeSeries.scala index e135a74e2d..532f0c570e 100644 --- a/core/shared/src/main/scala/fs2/timeseries/TimeSeries.scala +++ b/core/shared/src/main/scala/fs2/timeseries/TimeSeries.scala @@ -158,7 +158,7 @@ object TimeSeries { ((next.time.toMillis - nextTick.toMillis) / tickPeriod.toMillis + 1).toInt val tickTimes = (0 until tickCount).map(tickTime) val ticks = tickTimes.map(TimeStamped.tick) - val rest = Pull.output(Chunk.seq(ticks)) >> go(tickTime(tickCount), tl.cons(suffix)) + val rest = Pull.output(Chunk.from(ticks)) >> go(tickTime(tickCount), tl.cons(suffix)) out >> rest } case None => Pull.done diff --git a/core/shared/src/main/scala/fs2/timeseries/TimeStamped.scala b/core/shared/src/main/scala/fs2/timeseries/TimeStamped.scala index 893334cecd..d733cc9281 100644 --- a/core/shared/src/main/scala/fs2/timeseries/TimeStamped.scala +++ b/core/shared/src/main/scala/fs2/timeseries/TimeStamped.scala @@ -156,7 +156,7 @@ object TimeStamped { e2 = e2 + over } bldr += (tsa.map(Right.apply)) - ((Some(e2), f(tsa.value)), Chunk.seq(bldr.result())) + ((Some(e2), f(tsa.value)), Chunk.from(bldr.result())) } case None => ((Some(tsa.time + over), f(tsa.value)), Chunk(tsa.map(Right.apply))) } From 5d8caf17a1224e1e5d3b99c3ab10c7de9986573b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Wed, 16 Aug 2023 11:53:04 -0500 Subject: [PATCH 31/51] Optimize Chunk.Queue append and prepend --- core/shared/src/main/scala/fs2/Chunk.scala | 32 ++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 4298825672..26638be9c1 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -1186,11 +1186,39 @@ object Chunk /** Prepends a chunk to the start of this chunk queue. */ def +:[O2 >: O](c: Chunk[O2]): Queue[O2] = - if (c.isEmpty) this else new Queue(c +: chunks, c.size + size) + if (c.isEmpty) this + else + c match { + case q: Queue[O2] => + new Queue( + q.chunks ++ this.chunks, + this.size + q.size + ) + + case _ => + new Queue( + c +: this.chunks, + this.size + c.size + ) + } /** Appends a chunk to the end of this chunk queue. */ def :+[O2 >: O](c: Chunk[O2]): Queue[O2] = - if (c.isEmpty) this else new Queue(chunks :+ c, size + c.size) + if (c.isEmpty) this + else + c match { + case q: Queue[O2] => + new Queue( + this.chunks ++ q.chunks, + this.size + q.size + ) + + case _ => + new Queue( + this.chunks :+ c, + this.size + c.size + ) + } def apply(i: Int): O = { if (i < 0 || i >= size) throw new IndexOutOfBoundsException() From ce90f9b6d7b88cc00239ce4264fd7095f89d0909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Wed, 16 Aug 2023 12:01:18 -0500 Subject: [PATCH 32/51] Implement ChunkAsSeq.hashCode --- core/shared/src/main/scala/fs2/Chunk.scala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 26638be9c1..7b7cdcc69b 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -1519,6 +1519,15 @@ private[fs2] final class ChunkAsSeq[+O]( override def toString: String = chunk.iterator.mkString("ChunkAsSeq(", ", ", ")") + override def hashCode: Int = { + import util.hashing.MurmurHash3 + var h = MurmurHash3.stringHash("ChunkAsSeq") + chunk.foreach { o => + h = MurmurHash3.mix(h, o.##) + } + MurmurHash3.finalizeHash(h, chunk.size) + } + override def equals(that: Any): Boolean = that match { case thatChunkWrapper: ChunkAsSeq[_] => From d364324bfe479c54124a78ea822775f84fc12ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Wed, 16 Aug 2023 12:47:39 -0500 Subject: [PATCH 33/51] Fix ChunkAsSeq.equals --- core/shared/src/main/scala/fs2/Chunk.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 7b7cdcc69b..49de074660 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -1533,6 +1533,9 @@ private[fs2] final class ChunkAsSeq[+O]( case thatChunkWrapper: ChunkAsSeq[_] => chunk == thatChunkWrapper.chunk + case seq: GSeq[_] => + chunk.iterator.sameElements(seq.iterator) + case _ => false } From 3d1cf39f4087b7748c3278b2c28a1dbf27415f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Wed, 16 Aug 2023 13:53:22 -0500 Subject: [PATCH 34/51] Optimize Chunk.Queue concat --- core/shared/src/main/scala/fs2/Chunk.scala | 49 +++++++++++++++++++--- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 49de074660..cc2de16d36 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -1180,13 +1180,40 @@ object Chunk override def reverseIterator: Iterator[O] = chunks.reverseIterator.flatMap(_.reverseIterator) override def ++[O2 >: O](that: Chunk[O2]): Chunk[O2] = - if (that.isEmpty) this - else if (isEmpty) that - else new Queue(chunks :+ that, size + that.size) + if (that.isEmpty) + this + else if (this.isEmpty) + that + else + that match { + case q: Queue[O2] => + new Queue( + q.chunks ++ this.chunks, + this.size + q.size + ) + + case _ => + new Queue( + that +: this.chunks, + this.size + that.size + ) + } /** Prepends a chunk to the start of this chunk queue. */ def +:[O2 >: O](c: Chunk[O2]): Queue[O2] = - if (c.isEmpty) this + if (c.isEmpty) + this + else if (this.isEmpty) + c match { + case q: Queue[O2] => + q + + case _ => + new Queue( + chunks = collection.immutable.Queue(c), + size = c.size + ) + } else c match { case q: Queue[O2] => @@ -1204,7 +1231,19 @@ object Chunk /** Appends a chunk to the end of this chunk queue. */ def :+[O2 >: O](c: Chunk[O2]): Queue[O2] = - if (c.isEmpty) this + if (c.isEmpty) + this + else if (this.isEmpty) + c match { + case q: Queue[O2] => + q + + case _ => + new Queue( + chunks = collection.immutable.Queue(c), + size = c.size + ) + } else c match { case q: Queue[O2] => From 14c704ff60af17a07de813346955727d2a8d81eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Wed, 16 Aug 2023 14:06:16 -0500 Subject: [PATCH 35/51] Fix ChunkAsSeq.hashCode --- core/shared/src/main/scala/fs2/Chunk.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index cc2de16d36..4bb0395427 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -1560,11 +1560,7 @@ private[fs2] final class ChunkAsSeq[+O]( override def hashCode: Int = { import util.hashing.MurmurHash3 - var h = MurmurHash3.stringHash("ChunkAsSeq") - chunk.foreach { o => - h = MurmurHash3.mix(h, o.##) - } - MurmurHash3.finalizeHash(h, chunk.size) + MurmurHash3.indexedSeqHash(this, seed = MurmurHash3.seqSeed) } override def equals(that: Any): Boolean = From 66453a7bac3307a9806a8b36b3fd32404c5b8e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sat, 26 Aug 2023 15:28:39 -0500 Subject: [PATCH 36/51] Remove deprecated usages of Chunk.seq and friends --- .../scala/fs2/benchmark/ChunkBenchmark.scala | 2 +- .../scala/fs2/benchmark/ChunksBenchmark.scala | 4 ++-- .../scala/fs2/benchmark/LinesBenchmark.scala | 2 +- .../scala/fs2/benchmark/PullBenchmark.scala | 2 +- .../scala-2.13/fs2/ChunkPlatformSuite.scala | 18 ++---------------- .../src/test/scala/fs2/ChunkGenerators.scala | 4 ++-- .../src/test/scala/fs2/ChunkQueueSuite.scala | 2 +- .../shared/src/test/scala/fs2/ChunkSuite.scala | 14 ++++++-------- .../scala/fs2/StreamCombinatorsSuite.scala | 6 +++--- .../scala/fs2/StreamPerformanceSuite.scala | 2 +- .../mpeg/transport/psi/GroupedSections.scala | 2 +- 11 files changed, 21 insertions(+), 37 deletions(-) diff --git a/benchmark/src/main/scala/fs2/benchmark/ChunkBenchmark.scala b/benchmark/src/main/scala/fs2/benchmark/ChunkBenchmark.scala index 462e21bef9..b6f547811d 100644 --- a/benchmark/src/main/scala/fs2/benchmark/ChunkBenchmark.scala +++ b/benchmark/src/main/scala/fs2/benchmark/ChunkBenchmark.scala @@ -33,7 +33,7 @@ class ChunkBenchmark { @Setup def setup() = - ints = Chunk.seq((0 until chunkSize).map(_ + 1000)).compact + ints = Chunk.from((0 until chunkSize).map(_ + 1000)).compact @Benchmark def map(): Unit = { diff --git a/benchmark/src/main/scala/fs2/benchmark/ChunksBenchmark.scala b/benchmark/src/main/scala/fs2/benchmark/ChunksBenchmark.scala index b42f51e602..31bcae64cb 100644 --- a/benchmark/src/main/scala/fs2/benchmark/ChunksBenchmark.scala +++ b/benchmark/src/main/scala/fs2/benchmark/ChunksBenchmark.scala @@ -45,9 +45,9 @@ class ChunksBenchmark { @Setup def setup() = { - chunkSeq = Seq.range(0, chunkCount).map(_ => Chunk.seq(Seq.fill(chunkSize)(Obj.create))) + chunkSeq = Seq.range(0, chunkCount).map(_ => Chunk.from(Seq.fill(chunkSize)(Obj.create))) sizeHint = chunkSeq.foldLeft(0)(_ + _.size) - flattened = Chunk.seq(chunkSeq).flatten + flattened = Chunk.from(chunkSeq).flatten } @Benchmark diff --git a/benchmark/src/main/scala/fs2/benchmark/LinesBenchmark.scala b/benchmark/src/main/scala/fs2/benchmark/LinesBenchmark.scala index 2c77ffa648..2ab40fd610 100644 --- a/benchmark/src/main/scala/fs2/benchmark/LinesBenchmark.scala +++ b/benchmark/src/main/scala/fs2/benchmark/LinesBenchmark.scala @@ -49,7 +49,7 @@ class LinesBenchmark { .grouped(chunkSize) .grouped(chunkSize) .foldLeft(Stream[IO, String]()) { case (acc, strings) => - acc ++ Stream.chunk(Chunk.seq(strings)) + acc ++ Stream.chunk(Chunk.from(strings)) } } diff --git a/benchmark/src/main/scala/fs2/benchmark/PullBenchmark.scala b/benchmark/src/main/scala/fs2/benchmark/PullBenchmark.scala index 5f4dd1cbae..1d32bfde5c 100644 --- a/benchmark/src/main/scala/fs2/benchmark/PullBenchmark.scala +++ b/benchmark/src/main/scala/fs2/benchmark/PullBenchmark.scala @@ -34,7 +34,7 @@ class PullBenchmark { @Benchmark def unconsPull(): Int = { val s: Stream[Pure, Int] = Stream - .chunk(Chunk.seq(0 to 2560)) + .chunk(Chunk.from(0 to 2560)) .repeatPull { s => s.unconsN(n).flatMap { case Some((h, t)) => Pull.output(h).as(Some(t)) diff --git a/core/shared/src/test/scala-2.13/fs2/ChunkPlatformSuite.scala b/core/shared/src/test/scala-2.13/fs2/ChunkPlatformSuite.scala index f300ee7a8e..bf029225f8 100644 --- a/core/shared/src/test/scala-2.13/fs2/ChunkPlatformSuite.scala +++ b/core/shared/src/test/scala-2.13/fs2/ChunkPlatformSuite.scala @@ -99,13 +99,13 @@ class ChunkPlatformSuite extends Fs2Suite { group(s"fromArraySeq ArraySeq[$testTypeName]") { property("mutable") { forAll { (arraySeq: mutable.ArraySeq[A]) => - assertEquals(Chunk.arraySeq(arraySeq).toVector, arraySeq.toVector) + assertEquals(Chunk.from(arraySeq).toVector, arraySeq.toVector) } } property("immutable") { forAll { (arraySeq: immutable.ArraySeq[A]) => - assertEquals(Chunk.arraySeq(arraySeq).toVector, arraySeq.toVector) + assertEquals(Chunk.from(arraySeq).toVector, arraySeq.toVector) } } } @@ -123,18 +123,4 @@ class ChunkPlatformSuite extends Fs2Suite { testChunkFromArraySeq[Unit] testChunkFromArraySeq[String] } - - group("Chunk iterable") { - property("mutable ArraySeq") { - forAll { (a: mutable.ArraySeq[String]) => - assertEquals(Chunk.iterable(a).toVector, a.toVector) - } - } - - property("immutable ArraySeq") { - forAll { (a: immutable.ArraySeq[String]) => - assertEquals(Chunk.iterable(a).toVector, a.toVector) - } - } - } } diff --git a/core/shared/src/test/scala/fs2/ChunkGenerators.scala b/core/shared/src/test/scala/fs2/ChunkGenerators.scala index 0e35be94c7..b9d9c684e3 100644 --- a/core/shared/src/test/scala/fs2/ChunkGenerators.scala +++ b/core/shared/src/test/scala/fs2/ChunkGenerators.scala @@ -38,8 +38,8 @@ trait ChunkGeneratorsLowPriority1 extends MiscellaneousGenerators { val gen = Gen.frequency( 1 -> Gen.const(Chunk.empty[A]), 5 -> genA.map(Chunk.singleton), - 10 -> Gen.listOf(genA).map(Chunk.seq), - 10 -> Gen.listOf(genA).map(_.toVector).map(Chunk.vector), + 10 -> Gen.listOf(genA).map(Chunk.from), + 10 -> Gen.listOf(genA).map(_.toVector).map(Chunk.from), 10 -> Gen .listOf(genA) .map(_.toVector) diff --git a/core/shared/src/test/scala/fs2/ChunkQueueSuite.scala b/core/shared/src/test/scala/fs2/ChunkQueueSuite.scala index 8a52f2cbd5..cba44e0732 100644 --- a/core/shared/src/test/scala/fs2/ChunkQueueSuite.scala +++ b/core/shared/src/test/scala/fs2/ChunkQueueSuite.scala @@ -82,7 +82,7 @@ class ChunkQueueSuite extends Fs2Suite { assert(queue.startsWith(prefix)) } - val viaTake = queue.take(items.size) == Chunk.seq(items) + val viaTake = queue.take(items.size) == Chunk.from(items) val computed = flattened.startsWith(items) assertEquals(computed, viaTake) // here is another way to express the law: diff --git a/core/shared/src/test/scala/fs2/ChunkSuite.scala b/core/shared/src/test/scala/fs2/ChunkSuite.scala index 8805b96444..2e1e9bf2b7 100644 --- a/core/shared/src/test/scala/fs2/ChunkSuite.scala +++ b/core/shared/src/test/scala/fs2/ChunkSuite.scala @@ -47,10 +47,8 @@ class ChunkSuite extends Fs2Suite { property("chunk-formation (2)") { forAll { (c: Vector[Int]) => - assertEquals(Chunk.seq(c).toVector, c) - assertEquals(Chunk.seq(c).toList, c.toList) - assertEquals(Chunk.indexedSeq(c).toVector, c) - assertEquals(Chunk.indexedSeq(c).toList, c.toList) + assertEquals(Chunk.from(c).toVector, c) + assertEquals(Chunk.from(c).toList, c.toList) } } @@ -65,13 +63,13 @@ class ChunkSuite extends Fs2Suite { } } - test("Chunk.seq is optimized") { - assert(Chunk.seq(List(1)).isInstanceOf[Chunk.Singleton[_]]) + test("Chunk.from is optimized") { + assert(Chunk.from(List(1)).isInstanceOf[Chunk.Singleton[_]]) } - test("Array casts in Chunk.seq are safe") { + test("Array casts in Chunk.from are safe") { val as = collection.mutable.ArraySeq[Int](0, 1, 2) - val c = Chunk.seq(as) + val c = Chunk.from(as) assert(c.isInstanceOf[Chunk.ArraySlice[_]]) } } diff --git a/core/shared/src/test/scala/fs2/StreamCombinatorsSuite.scala b/core/shared/src/test/scala/fs2/StreamCombinatorsSuite.scala index 680c494e3e..bd5efec27f 100644 --- a/core/shared/src/test/scala/fs2/StreamCombinatorsSuite.scala +++ b/core/shared/src/test/scala/fs2/StreamCombinatorsSuite.scala @@ -1373,7 +1373,7 @@ class StreamCombinatorsSuite extends Fs2Suite { ) assertEquals( Stream("hel", "l", "o Wor", "ld") - .repartition(s => Chunk.indexedSeq(s.grouped(2).toVector)) + .repartition(s => Chunk.from(s.grouped(2).toVector)) .toList, List("he", "ll", "o ", "Wo", "rl", "d") ) @@ -1381,7 +1381,7 @@ class StreamCombinatorsSuite extends Fs2Suite { Stream("hello").repartition(_ => Chunk.empty).assertEmpty() def input = Stream("ab").repeat - def ones(s: String) = Chunk.vector(s.grouped(1).toVector) + def ones(s: String) = Chunk.from(s.grouped(1).toVector) assertEquals(input.take(2).repartition(ones).toVector, Vector("a", "b", "a", "b")) assertEquals( input.take(4).repartition(ones).toVector, @@ -1398,7 +1398,7 @@ class StreamCombinatorsSuite extends Fs2Suite { List(1, 3, 6, 10, 15, 15) ) assertEquals( - Stream(1, 10, 100).repartition(_ => Chunk.seq(1 to 1000)).take(4).toList, + Stream(1, 10, 100).repartition(_ => Chunk.from(1 to 1000)).take(4).toList, List(1, 2, 3, 4) ) } diff --git a/core/shared/src/test/scala/fs2/StreamPerformanceSuite.scala b/core/shared/src/test/scala/fs2/StreamPerformanceSuite.scala index 1c6f3eed06..2f7e188205 100644 --- a/core/shared/src/test/scala/fs2/StreamPerformanceSuite.scala +++ b/core/shared/src/test/scala/fs2/StreamPerformanceSuite.scala @@ -173,7 +173,7 @@ class StreamPerformanceSuite extends Fs2Suite { Ns.foreach { N => test(N.toString) { val s: Stream[Pure, Int] = Stream - .chunk(Chunk.seq(0 until N)) + .chunk(Chunk.from(0 until N)) .repeatPull { _.uncons1.flatMap { case None => Pull.pure(None) diff --git a/protocols/shared/src/main/scala/fs2/protocols/mpeg/transport/psi/GroupedSections.scala b/protocols/shared/src/main/scala/fs2/protocols/mpeg/transport/psi/GroupedSections.scala index 53044cca48..a165ca41ba 100644 --- a/protocols/shared/src/main/scala/fs2/protocols/mpeg/transport/psi/GroupedSections.scala +++ b/protocols/shared/src/main/scala/fs2/protocols/mpeg/transport/psi/GroupedSections.scala @@ -96,7 +96,7 @@ object GroupedSections { (newState, out) case Some(sections) => val newState = ExtendedSectionGrouperState(state.accumulatorByIds - key) - val out = Chunk.seq((Right(sections) :: err.map(e => Left(e)).toList).reverse) + val out = Chunk.from((Right(sections) :: err.map(e => Left(e)).toList).reverse) (newState, out) } } From e331b15c3aafcae9b09f18e215f7c8d53b774def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sat, 26 Aug 2023 16:23:30 -0500 Subject: [PATCH 37/51] Unwrap ChunkAsSeq in Chunk.from --- .../src/main/scala-2.13+/fs2/Chunk213And3Compat.scala | 3 --- core/shared/src/main/scala/fs2/Chunk.scala | 8 +++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index 379bef9d37..b68a8ca4bd 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -87,9 +87,6 @@ private[fs2] trait ChunkCompanion213And3Compat { val arr = arraySeq.unsafeArray.asInstanceOf[Array[O]] Some(array(arr)(ClassTag[O](arr.getClass.getComponentType))) - case w: ChunkAsSeq[O] => - Some(w.chunk) - case _ => None } diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 4bb0395427..073428cc2a 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -688,7 +688,7 @@ object Chunk } // Added in the "Avoid copying" spirit. - // It may be better removed if data shows it has little usage. + // It may be latter removed if data shows it has little usage. private final class JavaListChunk[O]( javaList: ju.List[O] with ju.RandomAccess ) extends Chunk[O] { @@ -729,6 +729,9 @@ object Chunk def from[O](i: GIterable[O]): Chunk[O] = platformFrom(i).getOrElse(i match { + case w: ChunkAsSeq[O] => + w.chunk + case a: mutable.ArraySeq[o] => val arr = a.array.asInstanceOf[Array[O]] array(arr)(ClassTag(arr.getClass.getComponentType)) @@ -744,8 +747,7 @@ object Chunk case s: GIndexedSeq[O] => if (s.isEmpty) empty - else if (s.size == 1) - singleton(s.head) // Use size instead of tail.isEmpty as indexed seqs know their size + else if (s.size == 1) singleton(s.head) else new IndexedSeqChunk(s) case _ => From 940c2d7fa5a793c462fd9e3a79a01df846868304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sat, 26 Aug 2023 16:35:56 -0500 Subject: [PATCH 38/51] Fix Chunk.Queue.++ --- core/shared/src/main/scala/fs2/Chunk.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 073428cc2a..9e25800ac7 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -1190,13 +1190,13 @@ object Chunk that match { case q: Queue[O2] => new Queue( - q.chunks ++ this.chunks, + this.chunks ++ q.chunks, this.size + q.size ) case _ => new Queue( - that +: this.chunks, + this.chunks :+ that, this.size + that.size ) } From 125b01daef0a2c699b4834fedb3499e7d5ab830d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sat, 26 Aug 2023 17:09:28 -0500 Subject: [PATCH 39/51] Add roundtrip tests --- .../scala-2.13+/fs2/Chunk213And3Compat.scala | 2 +- .../src/test/scala/fs2/ChunkSuite.scala | 42 +++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala index b68a8ca4bd..6afee8294e 100644 --- a/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala +++ b/core/shared/src/main/scala-2.13+/fs2/Chunk213And3Compat.scala @@ -74,7 +74,7 @@ private[fs2] trait ChunkAsSeq213And3Compat[+O] { override val iterableFactory: SeqFactory[IndexedSeq] = ArraySeq.untagged - override val empty: IndexedSeq[O] = + override lazy val empty: IndexedSeq[O] = new ChunkAsSeq(Chunk.empty) } diff --git a/core/shared/src/test/scala/fs2/ChunkSuite.scala b/core/shared/src/test/scala/fs2/ChunkSuite.scala index 2e1e9bf2b7..c5ddb61fd3 100644 --- a/core/shared/src/test/scala/fs2/ChunkSuite.scala +++ b/core/shared/src/test/scala/fs2/ChunkSuite.scala @@ -46,9 +46,9 @@ class ChunkSuite extends Fs2Suite { } property("chunk-formation (2)") { - forAll { (c: Vector[Int]) => - assertEquals(Chunk.from(c).toVector, c) - assertEquals(Chunk.from(c).toList, c.toList) + forAll { (v: Vector[Int]) => + assertEquals(Chunk.from(v).toVector, v) + assertEquals(Chunk.from(v).toList, v.toList) } } @@ -65,6 +65,7 @@ class ChunkSuite extends Fs2Suite { test("Chunk.from is optimized") { assert(Chunk.from(List(1)).isInstanceOf[Chunk.Singleton[_]]) + assert(Chunk.from(Vector(1)).isInstanceOf[Chunk.Singleton[_]]) } test("Array casts in Chunk.from are safe") { @@ -72,6 +73,41 @@ class ChunkSuite extends Fs2Suite { val c = Chunk.from(as) assert(c.isInstanceOf[Chunk.ArraySlice[_]]) } + + test("Chunk.asSeq roundtrip") { + forAll { (c: Chunk[Int]) => + // Chunk -> Seq -> Chunk + val seq = c.asSeq + val result = Chunk.from(seq) + + // Check data consistency. + assertEquals(result, c) + + // Check unwrap. + if (seq.isInstanceOf[ChunkAsSeq[_]]) { + assert(result eq c) + } + } && forAll { (e: Either[Seq[Int], Vector[Int]]) => + // Seq -> Chunk -> Seq + val seq = e.merge + val chunk = Chunk.from(seq) + val result = chunk.asSeq + + // Check data consistency. + assertEquals(result, seq) + + // Check unwrap. + if (seq.isInstanceOf[Vector[_]] && chunk.size >= 2) { + assert(result eq seq) + } + } + } + + test("Chunk.javaList unwraps asJava") { + forAll { (c: Chunk[Int]) => + assert(Chunk.javaList(c.asJava) eq c) + } + } } def testChunk[A: Arbitrary: ClassTag: CommutativeMonoid: Eq: Cogen]( From 32d0c3f236af829cfb0fa27de31b4e2798ecbb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sat, 26 Aug 2023 18:07:37 -0500 Subject: [PATCH 40/51] Add Chunk.javaList to the ChunkSpec --- core/shared/src/test/scala/fs2/ChunkGenerators.scala | 2 ++ core/shared/src/test/scala/fs2/ChunkSuite.scala | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/shared/src/test/scala/fs2/ChunkGenerators.scala b/core/shared/src/test/scala/fs2/ChunkGenerators.scala index b9d9c684e3..d62c00d50d 100644 --- a/core/shared/src/test/scala/fs2/ChunkGenerators.scala +++ b/core/shared/src/test/scala/fs2/ChunkGenerators.scala @@ -25,6 +25,7 @@ import cats.data.Chain import scala.reflect.ClassTag import scodec.bits.ByteVector import java.nio.{Buffer => JBuffer, ByteBuffer => JByteBuffer, CharBuffer => JCharBuffer} +import java.util.{List => JList} import org.scalacheck.{Arbitrary, Cogen, Gen} import Arbitrary.arbitrary @@ -40,6 +41,7 @@ trait ChunkGeneratorsLowPriority1 extends MiscellaneousGenerators { 5 -> genA.map(Chunk.singleton), 10 -> Gen.listOf(genA).map(Chunk.from), 10 -> Gen.listOf(genA).map(_.toVector).map(Chunk.from), + 10 -> Gen.listOf(genA).map(l => JList.of(l: _*)).map(Chunk.javaList), 10 -> Gen .listOf(genA) .map(_.toVector) diff --git a/core/shared/src/test/scala/fs2/ChunkSuite.scala b/core/shared/src/test/scala/fs2/ChunkSuite.scala index c5ddb61fd3..df20a0eddb 100644 --- a/core/shared/src/test/scala/fs2/ChunkSuite.scala +++ b/core/shared/src/test/scala/fs2/ChunkSuite.scala @@ -118,7 +118,11 @@ class ChunkSuite extends Fs2Suite { ): Unit = group(s"$name") { implicit val implicitChunkArb: Arbitrary[Chunk[A]] = Arbitrary(genChunk) - property("size")(forAll((c: Chunk[A]) => assertEquals(c.size, c.toList.size))) + property("size") { + forAll { (c: Chunk[A]) => + assertEquals(c.size, c.toList.size) + } + } property("take") { forAll { (c: Chunk[A], n: Int) => assertEquals(c.take(n).toVector, c.toVector.take(n)) From 0e7f05634275579e53ba5dc977f0824b73d5a397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 27 Aug 2023 10:21:02 -0500 Subject: [PATCH 41/51] Fix JavaListChunk.splitAtChunk_ --- core/shared/src/main/scala/fs2/Chunk.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 9e25800ac7..dba3ad55e5 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -704,8 +704,8 @@ object Chunk } override protected def splitAtChunk_(n: Int): (Chunk[O], Chunk[O]) = { - val left = javaList.subList(0, n + 1).asInstanceOf[ju.List[O] with ju.RandomAccess] - val right = javaList.subList(n + 1, size).asInstanceOf[ju.List[O] with ju.RandomAccess] + val left = javaList.subList(0, n).asInstanceOf[ju.List[O] with ju.RandomAccess] + val right = javaList.subList(n, size).asInstanceOf[ju.List[O] with ju.RandomAccess] new JavaListChunk(left) -> new JavaListChunk(right) } From ff268287f357ad25f4f8a9a1d9a0e8bcc95ed939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 27 Aug 2023 10:21:43 -0500 Subject: [PATCH 42/51] Remove special Chunk.empty.toString --- core/shared/src/main/scala/fs2/Chunk.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index dba3ad55e5..7183e2b2be 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -571,7 +571,6 @@ object Chunk sys.error("impossible") override def map[O2](f: Nothing => O2): Chunk[O2] = this override def toByteVector[B](implicit ev: B =:= Byte): ByteVector = ByteVector.empty - override def toString = "empty" } private[fs2] val unit: Chunk[Unit] = singleton(()) From 1dbb1cf207ae94cb45d10496f05b5e504ad27202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 27 Aug 2023 10:23:17 -0500 Subject: [PATCH 43/51] Fix ChunkAsSeq.hashCode in ScalaJS --- core/shared/src/main/scala/fs2/Chunk.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 7183e2b2be..0cb5574f5e 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -1559,10 +1559,8 @@ private[fs2] final class ChunkAsSeq[+O]( override def toString: String = chunk.iterator.mkString("ChunkAsSeq(", ", ", ")") - override def hashCode: Int = { - import util.hashing.MurmurHash3 - MurmurHash3.indexedSeqHash(this, seed = MurmurHash3.seqSeed) - } + override def hashCode: Int = + util.hashing.MurmurHash3.seqHash(this) override def equals(that: Any): Boolean = that match { From d53dafa31745f533ea2f61d8bad397494b88683c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 27 Aug 2023 11:43:04 -0500 Subject: [PATCH 44/51] Fix JavaListChunk.copyToArray --- core/shared/src/main/scala/fs2/Chunk.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/fs2/Chunk.scala b/core/shared/src/main/scala/fs2/Chunk.scala index 0cb5574f5e..469d707b01 100644 --- a/core/shared/src/main/scala/fs2/Chunk.scala +++ b/core/shared/src/main/scala/fs2/Chunk.scala @@ -698,8 +698,15 @@ object Chunk javaList.get(i) override def copyToArray[O2 >: O](xs: Array[O2], start: Int): Unit = { - val javaListAsArray = javaList.toArray - System.arraycopy(javaListAsArray, 0, xs, start, javaListAsArray.length) + var i = start + var j = 0 + val end = javaList.size + + while (j < end) { + xs(i) = javaList.get(j) + i += 1 + j += 1 + } } override protected def splitAtChunk_(n: Int): (Chunk[O], Chunk[O]) = { From 05424f3d3159534dd0da5413cea54efaa4854f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 27 Aug 2023 12:06:19 -0500 Subject: [PATCH 45/51] Add property tests for Chunk.asSeq --- .../src/test/scala/fs2/ChunkSuite.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/shared/src/test/scala/fs2/ChunkSuite.scala b/core/shared/src/test/scala/fs2/ChunkSuite.scala index df20a0eddb..a236b851b7 100644 --- a/core/shared/src/test/scala/fs2/ChunkSuite.scala +++ b/core/shared/src/test/scala/fs2/ChunkSuite.scala @@ -177,6 +177,25 @@ class ChunkSuite extends Fs2Suite { assertEquals((chunkScan.toList, chunkCarry), ((listScan.tail, listScan.last))) } } + property("asSeq") { + forAll { (c: Chunk[A]) => + val s = c.asSeq + val v = c.toVector + val l = c.toList + + // Equality. + assertEquals(s, v) + assertEquals(s, l: Seq[A]) + + // Hashcode. + assertEquals(s.hashCode, v.hashCode) + assertEquals(s.hashCode, l.hashCode) + + // Copy to array. + assertEquals(s.toArray.toVector, v.toArray.toVector) + assertEquals(s.toArray.toVector, l.toArray.toVector) + } + } if (implicitly[ClassTag[A]] == ClassTag.Byte) { property("toByteBuffer.byte") { From 8efa30ea1cd2afd311d5a0a349be106b37e435fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 27 Aug 2023 12:14:33 -0500 Subject: [PATCH 46/51] Add property tests for Chunk.asJava --- .../src/test/scala/fs2/ChunkSuite.scala | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/core/shared/src/test/scala/fs2/ChunkSuite.scala b/core/shared/src/test/scala/fs2/ChunkSuite.scala index a236b851b7..ee2cf523ef 100644 --- a/core/shared/src/test/scala/fs2/ChunkSuite.scala +++ b/core/shared/src/test/scala/fs2/ChunkSuite.scala @@ -31,6 +31,7 @@ import scodec.bits.ByteVector import java.nio.ByteBuffer import java.nio.CharBuffer +import java.util.{List => JList} import scala.reflect.ClassTag class ChunkSuite extends Fs2Suite { @@ -196,6 +197,28 @@ class ChunkSuite extends Fs2Suite { assertEquals(s.toArray.toVector, l.toArray.toVector) } } + property("asJava") { + forAll { (c: Chunk[A]) => + val view = c.asJava + val copy = JList.of(c.toVector: _*) + + // Equality. + assertEquals(view, copy) + + // Hashcode. + assertEquals(view.hashCode, copy.hashCode) + + // Copy to array (untyped). + assertEquals(view.toArray.toVector, copy.toArray.toVector) + + // Copy to array (typed). + val hint = Array.empty[A with Object] + val viewArray = view.toArray(hint) + val copyArray = copy.toArray(hint) + assertEquals(viewArray.toVector, copyArray.toVector) + assertEquals(viewArray.toVector, c.toVector) + } + } if (implicitly[ClassTag[A]] == ClassTag.Byte) { property("toByteBuffer.byte") { From afd9ef7cc3caacce82128f4f183c635e063fd5d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 27 Aug 2023 12:34:01 -0500 Subject: [PATCH 47/51] Remove Java 9+ method --- core/shared/src/test/scala/fs2/ChunkGenerators.scala | 3 +-- core/shared/src/test/scala/fs2/ChunkSuite.scala | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/core/shared/src/test/scala/fs2/ChunkGenerators.scala b/core/shared/src/test/scala/fs2/ChunkGenerators.scala index d62c00d50d..0534378e58 100644 --- a/core/shared/src/test/scala/fs2/ChunkGenerators.scala +++ b/core/shared/src/test/scala/fs2/ChunkGenerators.scala @@ -25,7 +25,6 @@ import cats.data.Chain import scala.reflect.ClassTag import scodec.bits.ByteVector import java.nio.{Buffer => JBuffer, ByteBuffer => JByteBuffer, CharBuffer => JCharBuffer} -import java.util.{List => JList} import org.scalacheck.{Arbitrary, Cogen, Gen} import Arbitrary.arbitrary @@ -41,7 +40,7 @@ trait ChunkGeneratorsLowPriority1 extends MiscellaneousGenerators { 5 -> genA.map(Chunk.singleton), 10 -> Gen.listOf(genA).map(Chunk.from), 10 -> Gen.listOf(genA).map(_.toVector).map(Chunk.from), - 10 -> Gen.listOf(genA).map(l => JList.of(l: _*)).map(Chunk.javaList), + 10 -> Gen.listOf(genA).map(l => java.util.Arrays.asList(l: _*)).map(Chunk.javaList), 10 -> Gen .listOf(genA) .map(_.toVector) diff --git a/core/shared/src/test/scala/fs2/ChunkSuite.scala b/core/shared/src/test/scala/fs2/ChunkSuite.scala index ee2cf523ef..7219a4af1d 100644 --- a/core/shared/src/test/scala/fs2/ChunkSuite.scala +++ b/core/shared/src/test/scala/fs2/ChunkSuite.scala @@ -31,7 +31,6 @@ import scodec.bits.ByteVector import java.nio.ByteBuffer import java.nio.CharBuffer -import java.util.{List => JList} import scala.reflect.ClassTag class ChunkSuite extends Fs2Suite { @@ -200,7 +199,7 @@ class ChunkSuite extends Fs2Suite { property("asJava") { forAll { (c: Chunk[A]) => val view = c.asJava - val copy = JList.of(c.toVector: _*) + val copy = java.util.Arrays.asList(c.toVector: _*) // Equality. assertEquals(view, copy) From 361bd4a74bc961ae87ef67a3436ad93b3d08a3f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 27 Aug 2023 12:46:41 -0500 Subject: [PATCH 48/51] Fix Scala 3 compilation error in ChunkSpec --- core/shared/src/test/scala/fs2/ChunkSuite.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/shared/src/test/scala/fs2/ChunkSuite.scala b/core/shared/src/test/scala/fs2/ChunkSuite.scala index 7219a4af1d..f414b28fbb 100644 --- a/core/shared/src/test/scala/fs2/ChunkSuite.scala +++ b/core/shared/src/test/scala/fs2/ChunkSuite.scala @@ -211,11 +211,13 @@ class ChunkSuite extends Fs2Suite { assertEquals(view.toArray.toVector, copy.toArray.toVector) // Copy to array (typed). - val hint = Array.empty[A with Object] + val hint = implicitly[ClassTag[A]].newArray(0).asInstanceOf[Array[A with Object]] val viewArray = view.toArray(hint) val copyArray = copy.toArray(hint) - assertEquals(viewArray.toVector, copyArray.toVector) - assertEquals(viewArray.toVector, c.toVector) + val viewVector: Vector[A] = viewArray.toVector + val copyVector: Vector[A] = copyArray.toVector + assertEquals(viewVector, copyVector) + assertEquals(viewVector, c.toVector) } } From b2b9ad77577407335c561b4f005f981fb911c439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 27 Aug 2023 12:52:45 -0500 Subject: [PATCH 49/51] Fix Stream docs using 'empty' rather thank 'Chunk()' --- core/shared/src/main/scala/fs2/Stream.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/fs2/Stream.scala b/core/shared/src/main/scala/fs2/Stream.scala index 58bfa2d325..cedfe3e10f 100644 --- a/core/shared/src/main/scala/fs2/Stream.scala +++ b/core/shared/src/main/scala/fs2/Stream.scala @@ -2706,7 +2706,7 @@ final class Stream[+F[_], +O] private[fs2] (private[fs2] val underlying: Pull[F, * * @example {{{ * scala> Stream.range(0, 10).split(_ % 4 == 0).toList - * res0: List[Chunk[Int]] = List(empty, Chunk(1, 2, 3), Chunk(5, 6, 7), Chunk(9)) + * res0: List[Chunk[Int]] = List(Chunk(), Chunk(1, 2, 3), Chunk(5, 6, 7), Chunk(9)) * }}} */ def split(f: O => Boolean): Stream[F, Chunk[O]] = { From 07e32a3b48b43ec1c16c70e4bba50f6de15b77f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 27 Aug 2023 13:07:48 -0500 Subject: [PATCH 50/51] Fix ChunkAsJavaList.toArray test --- core/shared/src/test/scala/fs2/ChunkSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/src/test/scala/fs2/ChunkSuite.scala b/core/shared/src/test/scala/fs2/ChunkSuite.scala index f414b28fbb..8685f6fe4b 100644 --- a/core/shared/src/test/scala/fs2/ChunkSuite.scala +++ b/core/shared/src/test/scala/fs2/ChunkSuite.scala @@ -211,7 +211,7 @@ class ChunkSuite extends Fs2Suite { assertEquals(view.toArray.toVector, copy.toArray.toVector) // Copy to array (typed). - val hint = implicitly[ClassTag[A]].newArray(0).asInstanceOf[Array[A with Object]] + val hint = Array.emptyObjectArray.asInstanceOf[Array[A with Object]] val viewArray = view.toArray(hint) val copyArray = copy.toArray(hint) val viewVector: Vector[A] = viewArray.toVector From b76a9339778cc7a0f7de701f06c7dafce9b3704b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Miguel=20Mej=C3=ADa=20Su=C3=A1rez?= Date: Sun, 27 Aug 2023 14:03:56 -0500 Subject: [PATCH 51/51] Fix MiMa issues --- build.sbt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.sbt b/build.sbt index a5ebde3c82..104e65e84d 100644 --- a/build.sbt +++ b/build.sbt @@ -215,6 +215,10 @@ ThisBuild / mimaBinaryIssueFilters ++= Seq( ), ProblemFilters.exclude[DirectMissingMethodProblem]( "fs2.io.file.Watcher#DefaultWatcher.this" + ), + // Private internal method: #3274 + ProblemFilters.exclude[DirectMissingMethodProblem]( + "fs2.Chunk.platformIterable" ) )