Skip to content

Commit 8f0b68e

Browse files
authored
Merge pull request #8196 from LPTK/iarray-cleanup
Remove unnecessary unsafe casts in IArray and fix erroneous cast
2 parents 898d62b + dfde5d8 commit 8f0b68e

File tree

1 file changed

+72
-59
lines changed

1 file changed

+72
-59
lines changed

library/src/scala/IArray.scala

Lines changed: 72 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import reflect.ClassTag
77
object opaques:
88
opaque type IArray[+T] = Array[_ <: T]
99

10+
private[scala] type Sub[A] >: Array[A] <: IArray[A]
11+
private[scala] type Sup[A] >: IArray[A] <: Array[_ <: A]
12+
1013
/** Defines extension methods for immutable arrays */
1114
given arrayOps: Object {
1215

@@ -41,39 +44,41 @@ object opaques:
4144

4245
/** Returns this array concatenated with the given array. */
4346
def [T, U >: T: ClassTag](arr: IArray[T]) ++(that: IArray[U]): IArray[U] =
44-
(genericArrayOps(arr) ++ that.asInstanceOf[Array[U]]).asInstanceOf[IArray[U]]
47+
genericArrayOps(arr) ++ that
4548

4649
/** Tests whether this array contains a given value as an element. */
4750
def [T](arr: IArray[T]) contains(elem: T): Boolean =
48-
genericArrayOps(arr.asInstanceOf[Array[T]]).contains(elem)
51+
// `genericArrayOps(arr).contains(elem)` does not work because `elem` does not have type `arr.T`
52+
// but we can use `exists` instead, which is how `ArrayOps#contains` itself is implemented:
53+
genericArrayOps(arr).exists(_ == elem)
4954

5055
/** Counts the number of elements in this array which satisfy a predicate */
5156
def [T](arr: IArray[T]) count(p: T => Boolean): Int =
5257
genericArrayOps(arr).count(p)
5358

5459
/** The rest of the array without its `n` first elements. */
5560
def [T](arr: IArray[T]) drop(n: Int): IArray[T] =
56-
genericArrayOps(arr).drop(n).asInstanceOf[IArray[T]]
61+
genericArrayOps(arr).drop(n)
5762

5863
/** The rest of the array without its `n` last elements. */
5964
def [T](arr: IArray[T]) dropRight(n: Int): IArray[T] =
60-
genericArrayOps(arr).dropRight(n).asInstanceOf[IArray[T]]
65+
genericArrayOps(arr).dropRight(n)
6166

6267
/** Drops longest prefix of elements that satisfy a predicate. */
6368
def [T](arr: IArray[T]) dropWhile(p: T => Boolean): IArray[T] =
64-
genericArrayOps(arr).dropWhile(p).asInstanceOf[IArray[T]]
69+
genericArrayOps(arr).dropWhile(p)
6570

6671
/** Tests whether a predicate holds for at least one element of this array. */
67-
def [T](arr: IArray[T]) exists(p: T => Boolean): IArray[T] =
68-
genericArrayOps(arr).exists(p).asInstanceOf[IArray[T]]
72+
def [T](arr: IArray[T]) exists(p: T => Boolean): Boolean =
73+
genericArrayOps(arr).exists(p)
6974

7075
/** Selects all elements of this array which satisfy a predicate. */
7176
def [T](arr: IArray[T]) filter(p: T => Boolean): IArray[T] =
72-
genericArrayOps(arr).filter(p).asInstanceOf[IArray[T]]
77+
genericArrayOps(arr).filter(p)
7378

7479
/** Selects all elements of this array which do not satisfy a predicate. */
7580
def [T](arr: IArray[T]) filterNot(p: T => Boolean): IArray[T] =
76-
genericArrayOps(arr).filterNot(p).asInstanceOf[IArray[T]]
81+
genericArrayOps(arr).filterNot(p)
7782

7883
/** Finds the first element of the array satisfying a predicate, if any. */
7984
def [T](arr: IArray[T]) find(p: T => Boolean): Option[T] =
@@ -82,12 +87,12 @@ object opaques:
8287
/** Builds a new array by applying a function to all elements of this array
8388
* and using the elements of the resulting collections. */
8489
def [T, U: ClassTag](arr: IArray[T]) flatMap(f: T => IterableOnce[U]): IArray[U] =
85-
genericArrayOps(arr).flatMap(f).asInstanceOf[IArray[U]]
90+
genericArrayOps(arr).flatMap(f)
8691

8792
/** Flattens a two-dimensional array by concatenating all its rows
8893
* into a single array. */
8994
def [T, U: ClassTag](arr: IArray[T]) flatten(using T => Iterable[U]): IArray[U] =
90-
genericArrayOps(arr).flatten.asInstanceOf[IArray[U]]
95+
genericArrayOps(arr).flatten
9196

9297
/** Folds the elements of this array using the specified associative binary operator. */
9398
def [T, U >: T: ClassTag](arr: IArray[T]) fold(z: U)(op: (U, U) => U): U =
@@ -121,7 +126,10 @@ object opaques:
121126

122127
/** Finds index of first occurrence of some value in this array after or at some start index. */
123128
def [T](arr: IArray[T]) indexOf(elem: T, from: Int = 0): Int =
124-
genericArrayOps(arr.asInstanceOf[Array[T]]).indexOf(elem, from)
129+
// `asInstanceOf` needed because `elem` does not have type `arr.T`
130+
// We could use `arr.iterator.indexOf(elem, from)` or `arr.indexWhere(_ == elem, from)`
131+
// but these would incur some overhead.
132+
genericArrayOps(arr).indexOf(elem.asInstanceOf, from)
125133

126134
/** Finds index of the first element satisfying some predicate after or at some start index. */
127135
def [T](arr: IArray[T]) indexWhere(p: T => Boolean, from: Int = 0): Int =
@@ -133,7 +141,7 @@ object opaques:
133141

134142
/** The initial part of the array without its last element. */
135143
def [T](arr: IArray[T]) init: IArray[T] =
136-
genericArrayOps(arr).init.asInstanceOf[IArray[T]]
144+
genericArrayOps(arr).init
137145

138146
/** Tests whether the array is empty. */
139147
def [T](arr: IArray[T]) isEmpty: Boolean =
@@ -153,136 +161,134 @@ object opaques:
153161

154162
/** Finds index of last occurrence of some value in this array before or at a given end index. */
155163
def [T](arr: IArray[T]) lastIndexOf(elem: T, end: Int = arr.length - 1): Int =
156-
genericArrayOps(arr.asInstanceOf[Array[T]]).lastIndexOf(elem, end)
164+
// see: same issue in `indexOf`
165+
genericArrayOps(arr).lastIndexOf(elem.asInstanceOf, end)
157166

158167
/** Finds index of last element satisfying some predicate before or at given end index. */
159168
def [T](arr: IArray[T]) lastIndexWhere(p: T => Boolean, end: Int = arr.length - 1): Int =
160169
genericArrayOps(arr).lastIndexWhere(p, end)
161170

162171
/** Builds a new array by applying a function to all elements of this array. */
163172
def [T, U: ClassTag](arr: IArray[T]) map(f: T => U): IArray[U] =
164-
genericArrayOps(arr).map(f).asInstanceOf[IArray[U]]
173+
genericArrayOps(arr).map(f)
165174

166175
/** Tests whether the array is not empty. */
167176
def [T](arr: IArray[T]) nonEmpty: Boolean =
168177
genericArrayOps(arr).nonEmpty
169178

170179
/** A pair of, first, all elements that satisfy predicate `p` and, second, all elements that do not. */
171180
def [T](arr: IArray[T]) partition(p: T => Boolean): (IArray[T], IArray[T]) =
172-
genericArrayOps(arr).partition(p) match {
173-
case (x, y) => (x.asInstanceOf[IArray[T]], y.asInstanceOf[IArray[T]])
174-
}
181+
genericArrayOps(arr).partition(p)
175182

176183
/** Returns a new array with the elements in reversed order. */
177184
def [T](arr: IArray[T]) reverse: IArray[T] =
178-
genericArrayOps(arr).reverse.asInstanceOf[IArray[T]]
185+
genericArrayOps(arr).reverse
179186

180187
/** Computes a prefix scan of the elements of the array. */
181188
def [T, U >: T: ClassTag](arr: IArray[T]) scan(z: U)(op: (U, U) => U): IArray[U] =
182-
genericArrayOps(arr).scan(z)(op).asInstanceOf[IArray[U]]
189+
genericArrayOps(arr).scan(z)(op)
183190

184191
/** Produces an array containing cumulative results of applying the binary
185192
* operator going left to right. */
186193
def [T, U: ClassTag](arr: IArray[T]) scanLeft(z: U)(op: (U, T) => U): IArray[U] =
187-
genericArrayOps(arr).scanLeft(z)(op).asInstanceOf[IArray[U]]
194+
genericArrayOps(arr).scanLeft(z)(op)
188195

189196
/** Produces an array containing cumulative results of applying the binary
190197
* operator going right to left. */
191198
def [T, U: ClassTag](arr: IArray[T]) scanRight(z: U)(op: (T, U) => U): IArray[U] =
192-
genericArrayOps(arr).scanRight(z)(op).asInstanceOf[IArray[U]]
199+
genericArrayOps(arr).scanRight(z)(op)
193200

194201
/** The size of this array. */
195202
def [T](arr: IArray[T]) size: Int =
196203
arr.length
197204

198205
/** Selects the interval of elements between the given indices. */
199206
def [T](arr: IArray[T]) slice(from: Int, until: Int): IArray[T] =
200-
genericArrayOps(arr).slice(from, until).asInstanceOf[IArray[T]]
207+
genericArrayOps(arr).slice(from, until)
201208

202209
/** Sorts this array according to the Ordering which results from transforming
203210
* an implicitly given Ordering with a transformation function. */
204211
def [T, U: ClassTag](arr: IArray[T]) sortBy(f: T => U)(using math.Ordering[U]): IArray[T] =
205-
genericArrayOps(arr).sortBy(f).asInstanceOf[IArray[T]]
212+
genericArrayOps(arr).sortBy(f)
206213

207214
/** Sorts this array according to a comparison function. */
208215
def [T](arr: IArray[T]) sortWith(f: (T, T) => Boolean): IArray[T] =
209-
genericArrayOps(arr).sortWith(f).asInstanceOf[IArray[T]]
216+
genericArrayOps(arr).sortWith(f)
210217

211218
/** Sorts this array according to an Ordering. */
212219
def [T](arr: IArray[T]) sorted(using math.Ordering[T]): IArray[T] =
213-
genericArrayOps(arr).sorted.asInstanceOf[IArray[T]]
220+
genericArrayOps(arr).sorted
214221

215222
/** Splits this array into a prefix/suffix pair according to a predicate. */
216223
def [T](arr: IArray[T]) span(p: T => Boolean): (IArray[T], IArray[T]) =
217-
genericArrayOps(arr).span(p) match {
218-
case (x, y) => (x.asInstanceOf[IArray[T]], y.asInstanceOf[IArray[T]])
219-
}
224+
genericArrayOps(arr).span(p)
220225

221226
/** Splits this array into two at a given position. */
222227
def [T](arr: IArray[T]) splitAt(n: Int): (IArray[T], IArray[T]) =
223-
genericArrayOps(arr).splitAt(n) match {
224-
case (x, y) => (x.asInstanceOf[IArray[T]], y.asInstanceOf[IArray[T]])
225-
}
228+
genericArrayOps(arr).splitAt(n)
226229

227230
/** Tests whether this array starts with the given array. */
228231
def [T, U >: T: ClassTag](arr: IArray[T]) startsWith(that: IArray[U], offset: Int = 0): Boolean =
229-
genericArrayOps(arr).startsWith(that.asInstanceOf[Array[U]])
232+
genericArrayOps(arr).startsWith(that)
230233

231234
/** The rest of the array without its first element. */
232235
def [T](arr: IArray[T]) tail: IArray[T] =
233-
genericArrayOps(arr).tail.asInstanceOf[IArray[T]]
236+
genericArrayOps(arr).tail
234237

235238
/** An array containing the first `n` elements of this array. */
236239
def [T](arr: IArray[T]) take(n: Int): IArray[T] =
237-
genericArrayOps(arr).take(n).asInstanceOf[IArray[T]]
240+
genericArrayOps(arr).take(n)
238241

239242
/** An array containing the last `n` elements of this array. */
240243
def [T](arr: IArray[T]) takeRight(n: Int): IArray[T] =
241-
genericArrayOps(arr).takeRight(n).asInstanceOf[IArray[T]]
244+
genericArrayOps(arr).takeRight(n)
242245

243246
/** Takes longest prefix of elements that satisfy a predicate. */
244247
def [T](arr: IArray[T]) takeWhile(p: T => Boolean): IArray[T] =
245-
genericArrayOps(arr).takeWhile(p).asInstanceOf[IArray[T]]
248+
genericArrayOps(arr).takeWhile(p)
246249

247250
/** Converts an array of pairs into an array of first elements and an array of second elements. */
248251
def [U: ClassTag, V: ClassTag](arr: IArray[(U, V)]) unzip: (IArray[U], IArray[V]) =
249-
genericArrayOps(arr).unzip match {
250-
case (x, y) => (x.asInstanceOf[IArray[U]], y.asInstanceOf[IArray[V]])
251-
}
252+
genericArrayOps(arr).unzip
252253

253254
/** Returns an array formed from this array and another iterable collection
254255
* by combining corresponding elements in pairs.
255256
* If one of the two collections is longer than the other, its remaining elements are ignored. */
256257
def [T, U: ClassTag](arr: IArray[T]) zip(that: IArray[U]): IArray[(T, U)] =
257-
genericArrayOps(arr).zip(that).asInstanceOf[IArray[(T, U)]]
258+
genericArrayOps(arr).zip(that)
258259
}
259260
end opaques
260261

261262
type IArray[+T] = opaques.IArray[T]
262263

263264
object IArray {
265+
import opaques.Sub
266+
import opaques.Sup
267+
268+
// A convenience to avoid having to cast everything by hand
269+
private given [A] as Conversion[Array[A], IArray[A]] = identity[Sub[A]]
264270

265271
/** An immutable array of length 0. */
266-
def empty[T: ClassTag]: IArray[T] = new Array[T](0).asInstanceOf
272+
def empty[T: ClassTag]: IArray[T] = new Array[T](0)
267273

268274
/** An immutable boolean array of length 0. */
269-
def emptyBooleanIArray = Array.emptyBooleanArray.asInstanceOf[IArray[Boolean]]
275+
def emptyBooleanIArray: IArray[Boolean] = Array.emptyBooleanArray
270276
/** An immutable byte array of length 0. */
271-
def emptyByteIArray = Array.emptyByteArray.asInstanceOf[IArray[Byte]]
277+
def emptyByteIArray: IArray[Byte] = Array.emptyByteArray
272278
/** An immutable char array of length 0. */
273-
def emptyCharIArray = Array.emptyCharArray.asInstanceOf[IArray[Char]]
279+
def emptyCharIArray: IArray[Char] = Array.emptyCharArray
274280
/** An immutable double array of length 0. */
275-
def emptyDoubleIArray = Array.emptyDoubleArray.asInstanceOf[IArray[Double]]
281+
def emptyDoubleIArray: IArray[Double] = Array.emptyDoubleArray
276282
/** An immutable float array of length 0. */
277-
def emptyFloatIArray = Array.emptyFloatArray.asInstanceOf[IArray[Float]]
283+
def emptyFloatIArray: IArray[Float] = Array.emptyFloatArray
278284
/** An immutable int array of length 0. */
279-
def emptyIntIArray = Array.emptyIntArray.asInstanceOf[IArray[Int]]
285+
def emptyIntIArray: IArray[Int] = Array.emptyIntArray
280286
/** An immutable long array of length 0. */
281-
def emptyLongIArray = Array.emptyLongArray.asInstanceOf[IArray[Long]]
287+
def emptyLongIArray: IArray[Long] = Array.emptyLongArray
282288
/** An immutable short array of length 0. */
283-
def emptyShortIArray = Array.emptyShortArray.asInstanceOf[IArray[Short]]
289+
def emptyShortIArray: IArray[Short] = Array.emptyShortArray
284290
/** An immutable object array of length 0. */
285-
def emptyObjectIArray = Array.emptyObjectArray.asInstanceOf[IArray[Object]]
291+
def emptyObjectIArray: IArray[Object] = Array.emptyObjectArray
286292

287293
/** An immutable array with given elements. */
288294
inline def apply[T](inline xs: T*)(using inline ct: ClassTag[T]): IArray[T] = Array(xs: _*).asInstanceOf
@@ -311,7 +317,10 @@ object IArray {
311317
* @return the array created from concatenating `xss`
312318
*/
313319
def concat[T: ClassTag](xss: IArray[T]*): IArray[T] =
314-
Array.concat[T](xss.asInstanceOf[Seq[Array[T]]]: _*).asInstanceOf
320+
// `Array.concat` should arguably take in a `Seq[Array[_ <: T]]`,
321+
// but since it currently takes a `Seq[Array[T]]` we have to perform a cast,
322+
// knowing tacitly that `concat` is not going to do the wrong thing.
323+
Array.concat[T](xss.asInstanceOf[Seq[Array[T]]]: _*)
315324

316325
/** Returns an immutable array that contains the results of some element computation a number
317326
* of times. Each element is determined by a separate computation.
@@ -320,7 +329,7 @@ object IArray {
320329
* @param elem the element computation
321330
*/
322331
def fill[T: ClassTag](n: Int)(elem: => T): IArray[T] =
323-
Array.fill(n)(elem).asInstanceOf
332+
Array.fill(n)(elem)
324333

325334
/** Returns a two-dimensional immutable array that contains the results of some element computation a number
326335
* of times. Each element is determined by a separate computation.
@@ -330,6 +339,7 @@ object IArray {
330339
* @param elem the element computation
331340
*/
332341
def fill[T: ClassTag](n1: Int, n2: Int)(elem: => T): IArray[IArray[T]] =
342+
// We cannot avoid a cast here as Array.fill creates inner arrays out of our control:
333343
Array.fill(n1, n2)(elem).asInstanceOf
334344

335345
/** Returns a three-dimensional immutable array that contains the results of some element computation a number
@@ -375,7 +385,7 @@ object IArray {
375385
* @param f The function computing element values
376386
*/
377387
def tabulate[T: ClassTag](n: Int)(f: Int => T): IArray[T] =
378-
Array.tabulate(n)(f).asInstanceOf
388+
Array.tabulate(n)(f)
379389

380390
/** Returns a two-dimensional immutable array containing values of a given function
381391
* over ranges of integer values starting from `0`.
@@ -430,7 +440,7 @@ object IArray {
430440
* @return the immutable array with values in range `start, start + 1, ..., end - 1`
431441
* up to, but excluding, `end`.
432442
*/
433-
def range(start: Int, end: Int): IArray[Int] = Array.range(start, end).asInstanceOf
443+
def range(start: Int, end: Int): IArray[Int] = Array.range(start, end)
434444

435445
/** Returns an immutable array containing equally spaced values in some integer interval.
436446
*
@@ -439,7 +449,7 @@ object IArray {
439449
* @param step the increment value of the array (may not be zero)
440450
* @return the immutable array with values in `start, start + step, ...` up to, but excluding `end`
441451
*/
442-
def range(start: Int, end: Int, step: Int): IArray[Int] = Array.range(start, end, step).asInstanceOf
452+
def range(start: Int, end: Int, step: Int): IArray[Int] = Array.range(start, end, step)
443453

444454
/** Returns an immutable array containing repeated applications of a function to a start value.
445455
*
@@ -448,13 +458,16 @@ object IArray {
448458
* @param f the function that is repeatedly applied
449459
* @return the immutable array returning `len` values in the sequence `start, f(start), f(f(start)), ...`
450460
*/
451-
def iterate[T: ClassTag](start: T, len: Int)(f: T => T): IArray[T] = Array.iterate(start, len)(f).asInstanceOf
461+
def iterate[T: ClassTag](start: T, len: Int)(f: T => T): IArray[T] = Array.iterate(start, len)(f)
452462

453463
/** Returns a decomposition of the array into a sequence. This supports
454464
* a pattern match like `{ case IArray(x,y,z) => println('3 elements')}`.
455465
*
456466
* @param x the selector value
457467
* @return sequence wrapped in a [[scala.Some]], if `x` is a Seq, otherwise `None`
458468
*/
459-
def unapplySeq[T](x: IArray[T]) = Array.unapplySeq[T](x.asInstanceOf[Array[T]])
460-
}
469+
def unapplySeq[T](x: IArray[T]) =
470+
// The double type ascription is currently needed,
471+
// for some reason (see: https://scastie.scala-lang.org/sSsmOhKxSKym405MgNRKqQ)
472+
Array.unapplySeq((x: Sup[T]): Array[_ <: T])
473+
}

0 commit comments

Comments
 (0)