Skip to content

Commit b12a490

Browse files
committed
[scala/scala#8469] IterableOnce#copyToArray returns number of elements copied (Int)
[scala/scala#11048] Array and ArrayDeque copyToArray do not throw IndexOutOfBoundException [11048] Add junit tests for {Queue,mutable.Queue}.copyToArray
1 parent d07cf7e commit b12a490

File tree

7 files changed

+93
-45
lines changed

7 files changed

+93
-45
lines changed

library/src/scala/collection/ArrayOps.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,13 +1263,20 @@ final class ArrayOps[A](val xs: Array[A]) extends AnyVal {
12631263
* @param len the maximal number of elements to copy.
12641264
* @tparam B the type of the elements of the array.
12651265
*/
1266-
def copyToArray[B >: A](dest: Array[B], start: Int, len: Int = Int.MaxValue): dest.type = {
1267-
Array.copy(xs, 0, dest, start, min(xs.length, min(dest.length-start, len)))
1268-
dest
1266+
def copyToArray[B >: A](dest: Array[B], start: Int, len: Int = Int.MaxValue): Int = {
1267+
val copied = IterableOnce.elemsToCopyToArray(xs.length, dest.length, start, len)
1268+
if (copied > 0) {
1269+
Array.copy(xs, 0, dest, start, copied)
1270+
}
1271+
copied
12691272
}
12701273

12711274
/** Create a copy of this array with the specified element type. */
1272-
def toArray[B >: A: ClassTag]: Array[B] = copyToArray(new Array[B](xs.length), 0)
1275+
def toArray[B >: A: ClassTag]: Array[B] = {
1276+
val destination = new Array[B](xs.length)
1277+
copyToArray(destination, 0)
1278+
destination
1279+
}
12731280

12741281
/** Counts the number of elements in this array which satisfy a predicate */
12751282
def count(p: A => Boolean): Int = {

library/src/scala/collection/IterableOnce.scala

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,17 @@ final class IterableOnceExtensionMethods[A](private val it: IterableOnce[A]) ext
216216
object IterableOnce {
217217
@`inline` implicit def iterableOnceExtensionMethods[A](it: IterableOnce[A]): IterableOnceExtensionMethods[A] =
218218
new IterableOnceExtensionMethods[A](it)
219+
220+
/** Computes the number of elements to copy to an array from a source IterableOnce
221+
*
222+
* @param srcLen the length of the source collection
223+
* @param destLen the length of the destination array
224+
* @param start the index in the destination array at which to start copying elements to
225+
* @param len the requested number of elements to copy (we may only be able to copy less than this)
226+
* @return the number of elements that will be copied to the destination array
227+
*/
228+
@inline private[collection] def elemsToCopyToArray(srcLen: Int, destLen: Int, start: Int, len: Int): Int =
229+
math.max(math.min(math.min(len, srcLen), destLen - start), 0)
219230
}
220231

221232
/** This implementation trait can be mixed into an `IterableOnce` to get the basic methods that are shared between
@@ -703,7 +714,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
703714
@deprecated("Use `dest ++= coll` instead", "2.13.0")
704715
@inline final def copyToBuffer[B >: A](dest: mutable.Buffer[B]): Unit = dest ++= this
705716

706-
/** Copy elements to an array.
717+
/** Copy elements to an array, returning the number of elements written.
707718
*
708719
* Fills the given array `xs` starting at index `start` with values of this $coll.
709720
*
@@ -712,14 +723,15 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
712723
*
713724
* @param xs the array to fill.
714725
* @tparam B the type of the elements of the array.
726+
* @return the number of elements written to the array
715727
*
716-
* @usecase def copyToArray(xs: Array[A]): Unit
728+
* @usecase def copyToArray(xs: Array[A]): Int
717729
*
718730
* $willNotTerminateInf
719731
*/
720-
def copyToArray[B >: A](xs: Array[B]): xs.type = copyToArray(xs, 0)
732+
def copyToArray[B >: A](xs: Array[B]): Int = copyToArray(xs, 0)
721733

722-
/** Copy elements to an array.
734+
/** Copy elements to an array, returning the number of elements written.
723735
*
724736
* Fills the given array `xs` starting at index `start` with values of this $coll.
725737
*
@@ -729,24 +741,25 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
729741
* @param xs the array to fill.
730742
* @param start the starting index of xs.
731743
* @tparam B the type of the elements of the array.
744+
* @return the number of elements written to the array
732745
*
733-
* @usecase def copyToArray(xs: Array[A], start: Int): Unit
746+
* @usecase def copyToArray(xs: Array[A], start: Int): Int
734747
*
735748
* $willNotTerminateInf
736749
*/
737750

738-
def copyToArray[B >: A](xs: Array[B], start: Int): xs.type = {
751+
def copyToArray[B >: A](xs: Array[B], start: Int): Int = {
739752
val xsLen = xs.length
740753
val it = iterator
741754
var i = start
742755
while (i < xsLen && it.hasNext) {
743756
xs(i) = it.next()
744757
i += 1
745758
}
746-
xs
759+
i - start
747760
}
748761

749-
/** Copy elements to an array.
762+
/** Copy elements to an array, returning the number of elements written.
750763
*
751764
* Fills the given array `xs` starting at index `start` with at most `len` elements of this $coll.
752765
*
@@ -757,22 +770,23 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
757770
* @param start the starting index of xs.
758771
* @param len the maximal number of elements to copy.
759772
* @tparam B the type of the elements of the array.
773+
* @return the number of elements written to the array
760774
*
761775
* @note Reuse: $consumesIterator
762776
*
763-
* @usecase def copyToArray(xs: Array[A], start: Int, len: Int): Unit
777+
* @usecase def copyToArray(xs: Array[A], start: Int, len: Int): Int
764778
*
765779
* $willNotTerminateInf
766780
*/
767-
def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): xs.type = {
781+
def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Int = {
768782
val it = iterator
769783
var i = start
770784
val end = start + math.min(len, xs.length - start)
771785
while (i < end && it.hasNext) {
772786
xs(i) = it.next()
773787
i += 1
774788
}
775-
xs
789+
i - start
776790
}
777791

778792
/** Sums up the elements of this collection.
@@ -1205,7 +1219,11 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
12051219

12061220
/** Convert collection to array. */
12071221
def toArray[B >: A: ClassTag]: Array[B] =
1208-
if (knownSize >= 0) copyToArray(new Array[B](knownSize), 0)
1222+
if (knownSize >= 0) {
1223+
val destination = new Array[B](knownSize)
1224+
copyToArray(destination, 0)
1225+
destination
1226+
}
12091227
else mutable.ArrayBuffer.from(this).toArray[B]
12101228

12111229
// For internal use

library/src/scala/collection/StringOps.scala

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,16 +1141,14 @@ final class StringOps(private val s: String) extends AnyVal {
11411141
@`inline` def filterNot(pred: Char => Boolean): String = filter(c => !pred(c))
11421142

11431143
/** Copy chars of this string to an array.
1144-
* Fills the given array `xs` starting at index `start` with at most `len` chars.
1145-
* Copying will stop once either the entire string has been copied,
1146-
* or the end of the array is reached or `len` chars have been copied.
1144+
* Fills the given array `xs` starting at index 0.
1145+
* Copying will stop once either the entire string has been copied
1146+
* or the end of the array is reached
11471147
*
11481148
* @param xs the array to fill.
1149-
* @param start the starting index.
1150-
* @param len the maximal number of elements to copy.
11511149
*/
1152-
def copyToArray(xs: Array[Char], start: Int, len: Int): Unit =
1153-
s.getChars(0, min(min(s.length, len), xs.length-start), xs, start)
1150+
@`inline` def copyToArray(xs: Array[Char]): Int =
1151+
copyToArray(xs, 0, Int.MaxValue)
11541152

11551153
/** Copy chars of this string to an array.
11561154
* Fills the given array `xs` starting at index `start`.
@@ -1159,11 +1157,27 @@ final class StringOps(private val s: String) extends AnyVal {
11591157
*
11601158
* @param xs the array to fill.
11611159
* @param start the starting index.
1162-
* @param len the maximal number of elements to copy.
11631160
*/
1164-
@`inline` def copyToArray(xs: Array[Char], start: Int): Unit =
1161+
@`inline` def copyToArray(xs: Array[Char], start: Int): Int =
11651162
copyToArray(xs, start, Int.MaxValue)
11661163

1164+
/** Copy chars of this string to an array.
1165+
* Fills the given array `xs` starting at index `start` with at most `len` chars.
1166+
* Copying will stop once either the entire string has been copied,
1167+
* or the end of the array is reached or `len` chars have been copied.
1168+
*
1169+
* @param xs the array to fill.
1170+
* @param start the starting index.
1171+
* @param len the maximal number of elements to copy.
1172+
*/
1173+
def copyToArray(xs: Array[Char], start: Int, len: Int): Int = {
1174+
val copied = IterableOnce.elemsToCopyToArray(s.length, xs.length, start, len)
1175+
if (copied > 0) {
1176+
s.getChars(0, copied, xs, start)
1177+
}
1178+
copied
1179+
}
1180+
11671181
/** Finds index of the first char satisfying some predicate after or at some start index.
11681182
*
11691183
* @param p the predicate used to test elements.

library/src/scala/collection/immutable/ArraySeq.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import java.io.{ObjectInputStream, ObjectOutputStream}
55

66
import scala.collection.mutable.{ArrayBuffer, ArrayBuilder, Builder, ArraySeq => MutableArraySeq}
77
import scala.collection.{ArrayOps, ClassTagSeqFactory, SeqFactory, StrictOptimizedClassTagSeqFactory, View}
8+
import scala.collection.IterableOnce
89
import scala.annotation.unchecked.uncheckedVariance
910
import scala.util.hashing.MurmurHash3
1011
import scala.reflect.ClassTag
@@ -133,12 +134,14 @@ sealed abstract class ArraySeq[+A]
133134

134135
override protected[this] def className = "ArraySeq"
135136

136-
override def copyToArray[B >: A](xs: Array[B], start: Int = 0): xs.type = copyToArray[B](xs, start, length)
137+
override def copyToArray[B >: A](xs: Array[B], start: Int = 0): Int = copyToArray[B](xs, start, length)
137138

138-
override def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): xs.type = {
139-
val l = scala.math.min(scala.math.min(len, length), xs.length-start)
140-
if(l > 0) Array.copy(unsafeArray, 0, xs, start, l)
141-
xs
139+
override def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Int = {
140+
val copied = IterableOnce.elemsToCopyToArray(length, xs.length, start, len)
141+
if(copied > 0) {
142+
Array.copy(unsafeArray, 0, xs, start, copied)
143+
}
144+
copied
142145
}
143146

144147
override protected[this] def writeReplace(): AnyRef = this

library/src/scala/collection/mutable/ArrayBuffer.scala

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ package scala
22
package collection
33
package mutable
44

5-
import java.lang.{IndexOutOfBoundsException, IllegalArgumentException}
6-
7-
85
/** An implementation of the `Buffer` class using an array to
96
* represent the assembled sequence internally. Append, update and random
107
* access take constant time (amortized time). Prepends and removes are
@@ -159,12 +156,14 @@ class ArrayBuffer[A] private (initialElements: Array[AnyRef], initialSize: Int)
159156

160157
override protected[this] def stringPrefix = "ArrayBuffer"
161158

162-
override def copyToArray[B >: A](xs: Array[B], start: Int): xs.type = copyToArray[B](xs, start, length)
159+
override def copyToArray[B >: A](xs: Array[B], start: Int): Int = copyToArray[B](xs, start, length)
163160

164-
override def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): xs.type = {
165-
val l = scala.math.min(scala.math.min(len, length), xs.length-start)
166-
if(l > 0) Array.copy(array, 0, xs, start, l)
167-
xs
161+
override def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Int = {
162+
val copied = IterableOnce.elemsToCopyToArray(length, xs.length, start, len)
163+
if(copied > 0) {
164+
Array.copy(array, 0, xs, start, copied)
165+
}
166+
copied
168167
}
169168

170169
/** Sorts this $coll in place according to an Ordering.

library/src/scala/collection/mutable/ArrayDeque.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,13 @@ class ArrayDeque[A] protected (
434434

435435
override def grouped(n: Int): Iterator[IterableCC[A]] = sliding(n, n)
436436

437-
override def copyToArray[B >: A](dest: Array[B], destStart: Int, len: Int): dest.type =
438-
copySliceToArray(srcStart = 0, dest = dest, destStart = destStart, maxItems = len)
437+
override def copyToArray[B >: A](dest: Array[B], destStart: Int, len: Int): Int = {
438+
val copied = IterableOnce.elemsToCopyToArray(length, dest.length, destStart, len)
439+
if (copied > 0) {
440+
copySliceToArray(srcStart = 0, dest = dest, destStart = destStart, maxItems = len)
441+
}
442+
copied
443+
}
439444

440445
override def toArray[B >: A: ClassTag]: Array[B] =
441446
copySliceToArray(srcStart = 0, dest = new Array[B](length), destStart = 0, maxItems = length)

library/src/scala/collection/mutable/ArraySeq.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,14 @@ sealed abstract class ArraySeq[T]
5959
/** Clones this object, including the underlying Array. */
6060
override def clone(): ArraySeq[T] = ArraySeq.make(array.clone()).asInstanceOf[ArraySeq[T]]
6161

62-
override def copyToArray[B >: T](xs: Array[B], start: Int): xs.type = copyToArray[B](xs, start, length)
62+
override def copyToArray[B >: T](xs: Array[B], start: Int): Int = copyToArray[B](xs, start, length)
6363

64-
override def copyToArray[B >: T](xs: Array[B], start: Int, len: Int): xs.type = {
65-
val l = scala.math.min(scala.math.min(len, length), xs.length-start)
66-
if(l > 0) Array.copy(array, 0, xs, start, l)
67-
xs
64+
override def copyToArray[B >: T](xs: Array[B], start: Int, len: Int): Int = {
65+
val copied = IterableOnce.elemsToCopyToArray(length, xs.length, start, len)
66+
if(copied > 0) {
67+
Array.copy(array, 0, xs, start, copied)
68+
}
69+
copied
6870
}
6971

7072
override protected[this] def writeReplace(): AnyRef = this

0 commit comments

Comments
 (0)