Skip to content

Commit 883a565

Browse files
committed
Replace unsound protected[this] members with explicit @uncheckedVariance annotations.
This commit is a port of scala/collection-strawman#521 Let’s assume that our code is unsound by not relying on the `protected[this]` escape hatch that disables variance checking. Instead, properly insert `@uncheckedVariance` annotations to see where we are doing bad things. On a positive note, this also encourages us to better document the restrictions of such unsound members. Add tests showing that `reverse` behaves correctly on ArrayDeque subclasses. Fixes scala/collection-strawman#505 Fixes scala/collection-strawman#547
1 parent 1657456 commit 883a565

39 files changed

+342
-179
lines changed

src/library/scala/Enumeration.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,8 @@ abstract class Enumeration (initial: Int) extends Serializable {
286286
* new array of longs */
287287
def toBitMask: Array[Long] = nnIds.toBitMask
288288

289-
override protected[this] def fromSpecificIterable(coll: Iterable[Value]) = ValueSet.fromSpecific(coll)
290-
override protected[this] def newSpecificBuilder() = ValueSet.newBuilder
289+
override protected def fromSpecificIterable(coll: Iterable[Value]) = ValueSet.fromSpecific(coll)
290+
override protected def newSpecificBuilder() = ValueSet.newBuilder
291291

292292
def map(f: Value => Value): ValueSet = fromSpecificIterable(new View.Map(toIterable, f))
293293
def flatMap(f: Value => IterableOnce[Value]): ValueSet = fromSpecificIterable(new View.FlatMap(toIterable, f))

src/library/scala/collection/BitSet.scala

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scala
22
package collection
33

4+
import scala.annotation.unchecked.uncheckedVariance
45
import scala.collection.mutable.Builder
56

67

@@ -17,8 +18,8 @@ import scala.collection.mutable.Builder
1718
* @define Coll `BitSet`
1819
*/
1920
trait BitSet extends SortedSet[Int] with BitSetOps[BitSet] {
20-
override protected[this] def fromSpecificIterable(coll: Iterable[Int]): BitSetC = bitSetFactory.fromSpecific(coll)
21-
override protected[this] def newSpecificBuilder(): Builder[Int, BitSetC] = bitSetFactory.newBuilder()
21+
override protected def fromSpecificIterable(coll: Iterable[Int]): BitSetC = bitSetFactory.fromSpecific(coll)
22+
override protected def newSpecificBuilder(): Builder[Int, BitSetC] = bitSetFactory.newBuilder()
2223
override def empty: BitSetC = bitSetFactory.empty
2324
}
2425

@@ -35,7 +36,14 @@ trait BitSetOps[+C <: BitSet with BitSetOps[C]]
3536

3637
def bitSetFactory: SpecificIterableFactory[Int, BitSetC]
3738

38-
protected[this] type BitSetC = C
39+
/**
40+
* Type alias to `C`. It is used to provide a default implementation of the `fromSpecificIterable`
41+
* and `newSpecificBuilder` operations.
42+
*
43+
* Due to the `@uncheckedVariance` annotation, usage of this type member can be unsound and is
44+
* therefore not recommended.
45+
*/
46+
protected type BitSetC = C @uncheckedVariance
3947

4048
final def ordering: Ordering[Int] = Ordering.Int
4149

src/library/scala/collection/IndexedSeq.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ trait IndexedSeqOps[+A, +CC[_], +C] extends Any with SeqOps[A, CC, C] { self =>
2727

2828
override def view: IndexedView[A] = new IndexedView.Id[A](this)
2929

30-
override protected[this] def reversed: Iterable[A] = new IndexedView.Reverse(this)
30+
override protected def reversed: Iterable[A] = new IndexedView.Reverse(this)
3131

3232
// Override transformation operations to use more efficient views than the default ones
3333
override def prepended[B >: A](elem: B): CC[B] = iterableFactory.from(new IndexedView.Prepended(elem, this))

src/library/scala/collection/Iterable.scala

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,19 @@ trait Iterable[+A] extends IterableOnce[A] with IterableOps[A, Iterable, Iterabl
2222
final def toIterable: this.type = this
2323

2424
//TODO scalac generates an override for this in AbstractMap; Making it final leads to a VerifyError
25-
protected[this] def coll: this.type = this
25+
protected def coll: this.type = this
2626

27-
protected[this] def fromSpecificIterable(coll: Iterable[A]): IterableCC[A] = iterableFactory.from(coll)
28-
protected[this] def newSpecificBuilder(): Builder[A, IterableCC[A]] = iterableFactory.newBuilder[A]()
27+
protected def fromSpecificIterable(coll: Iterable[A @uncheckedVariance]): IterableCC[A] @uncheckedVariance = iterableFactory.from(coll)
28+
protected def newSpecificBuilder(): Builder[A, IterableCC[A]] @uncheckedVariance = iterableFactory.newBuilder[A]()
2929

30+
/**
31+
* @note This operation '''has''' to be overridden by concrete collection classes to effectively
32+
* return an `IterableFactory[IterableCC]`. The implementation in `Iterable` only returns
33+
* an `IterableFactory[Iterable]`, but the compiler will '''not''' throw an error if the
34+
* effective `IterableCC` type constructor is more specific than `Iterable`.
35+
*
36+
* @return The factory of this collection.
37+
*/
3038
def iterableFactory: IterableFactory[IterableCC] = Iterable
3139
}
3240

@@ -66,7 +74,14 @@ trait Iterable[+A] extends IterableOnce[A] with IterableOps[A, Iterable, Iterabl
6674
*/
6775
trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with IterableOnceOps[A, CC, C] {
6876

69-
protected[this] type IterableCC[X] = CC[X]
77+
/**
78+
* Type alias to `CC`. It is used to provide a default implementation of the `fromSpecificIterable`
79+
* and `newSpecificBuilder` operations.
80+
*
81+
* Due to the `@uncheckedVariance` annotation, usage of this type member can be unsound and is
82+
* therefore not recommended.
83+
*/
84+
protected type IterableCC[X] = CC[X] @uncheckedVariance
7085

7186
/**
7287
* @return This collection as an `Iterable[A]`. No new collection will be built if `this` is already an `Iterable[A]`.
@@ -76,21 +91,26 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable
7691
/**
7792
* @return This collection as a `C`.
7893
*/
79-
protected[this] def coll: C
94+
protected def coll: C
8095

8196
/**
8297
* Defines how to turn a given `Iterable[A]` into a collection of type `C`.
8398
*
8499
* This process can be done in a strict way or a non-strict way (ie. without evaluating
85100
* the elements of the resulting collections). In other words, this methods defines
86101
* the evaluation model of the collection.
102+
*
103+
* @note As witnessed by the `@uncheckedVariance` annotation, using this method
104+
* might be unsound. However, as long as it is called with an
105+
* `Iterable[A]` obtained from `this` collection (as it is the case in the
106+
* implementations of operations where we use a `View[A]`), it is safe.
87107
*/
88-
protected[this] def fromSpecificIterable(coll: Iterable[A]): C
108+
protected def fromSpecificIterable(coll: Iterable[A @uncheckedVariance]): C
89109

90110
/** Similar to `fromSpecificIterable`, but for a (possibly) different type of element.
91111
* Note that the return type is now `CC[E]`.
92112
*/
93-
@`inline` final protected[this] def fromIterable[E](it: Iterable[E]): CC[E] = iterableFactory.from(it)
113+
@`inline` final protected def fromIterable[E](it: Iterable[E]): CC[E] = iterableFactory.from(it)
94114

95115
/**
96116
* @return The companion object of this ${coll}, providing various factory methods.
@@ -104,8 +124,12 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable
104124
* it is possible to implement this method but the resulting `Builder` will break laziness.
105125
* As a consequence, operations should preferably be implemented with `fromSpecificIterable`
106126
* instead of this method.
127+
*
128+
* @note As witnessed by the `@uncheckedVariance` annotation, using this method might
129+
* be unsound. However, as long as the returned builder is only fed
130+
* with `A` values taken from `this` instance, it is safe.
107131
*/
108-
protected[this] def newSpecificBuilder(): Builder[A, C]
132+
protected def newSpecificBuilder(): Builder[A @uncheckedVariance, C]
109133

110134
/** Selects the first element of this $coll.
111135
* $orderDependent
@@ -315,26 +339,7 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable
315339
* All these operations apply to those elements of this $coll
316340
* which satisfy the predicate `p`.
317341
*/
318-
def withFilter(p: A => Boolean): collection.WithFilter[A, CC] = new WithFilter(p)
319-
320-
/** A template trait that contains just the `map`, `flatMap`, `foreach` and `withFilter` methods
321-
* of trait `Iterable`.
322-
*
323-
* @define coll iterable collection
324-
*/
325-
class WithFilter(p: A => Boolean) extends collection.WithFilter[A, CC] {
326-
327-
protected[this] def filtered = new View.Filter(IterableOps.this, p, isFlipped = false)
328-
329-
def map[B](f: A => B): CC[B] = iterableFactory.from(new View.Map(filtered, f))
330-
331-
def flatMap[B](f: A => IterableOnce[B]): CC[B] = iterableFactory.from(new View.FlatMap(filtered, f))
332-
333-
def foreach[U](f: A => U): Unit = filtered.foreach(f)
334-
335-
def withFilter(q: A => Boolean): WithFilter = new WithFilter(a => p(a) && q(a))
336-
337-
}
342+
def withFilter(p: A => Boolean): collection.WithFilter[A, CC] = new IterableOps.WithFilter(this, p)
338343

339344
/** A pair of, first, all elements that satisfy prediacte `p` and, second,
340345
* all elements that do not. Interesting because it splits a collection in two.
@@ -710,6 +715,39 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable
710715
}
711716
}
712717

718+
object IterableOps {
719+
720+
/** A trait that contains just the `map`, `flatMap`, `foreach` and `withFilter` methods
721+
* of trait `Iterable`.
722+
*
723+
* @tparam A Element type (e.g. `Int`)
724+
* @tparam CC Collection type constructor (e.g. `List`)
725+
*
726+
* @define coll collection
727+
*/
728+
class WithFilter[+A, +CC[_]](
729+
self: IterableOps[A, CC, _],
730+
p: A => Boolean
731+
) extends collection.WithFilter[A, CC] {
732+
733+
protected def filtered: Iterable[A] =
734+
new View.Filter(self, p, isFlipped = false)
735+
736+
def map[B](f: A => B): CC[B] =
737+
self.iterableFactory.from(new View.Map(filtered, f))
738+
739+
def flatMap[B](f: A => IterableOnce[B]): CC[B] =
740+
self.iterableFactory.from(new View.FlatMap(filtered, f))
741+
742+
def foreach[U](f: A => U): Unit = filtered.foreach(f)
743+
744+
def withFilter(q: A => Boolean): WithFilter[A, CC] =
745+
new WithFilter(self, (a: A) => p(a) && q(a))
746+
747+
}
748+
749+
}
750+
713751
object Iterable extends IterableFactory.Delegate[Iterable](immutable.Iterable) {
714752
implicit def toLazyZipOps[A, CC[X] <: Iterable[X]](that: CC[A]): LazyZipOps[A, CC[A]] = new LazyZipOps(that)
715753
}

src/library/scala/collection/IterableOnce.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -994,7 +994,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] =>
994994
else mutable.ArrayBuffer.from(this).toArray[B]
995995

996996
// For internal use
997-
protected[this] def reversed: Iterable[A] = {
997+
protected def reversed: Iterable[A] = {
998998
var xs: immutable.List[A] = immutable.Nil
999999
val it = iterator()
10001000
while (it.hasNext) xs = it.next() :: xs

src/library/scala/collection/Map.scala

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,20 @@ trait Map[K, +V]
1313
with MapOps[K, V, Map, Map[K, V]]
1414
with Equals {
1515

16-
override protected[this] def fromSpecificIterable(coll: Iterable[(K, V)]): MapCC[K, V] = mapFactory.from(coll)
17-
override protected[this] def newSpecificBuilder(): mutable.Builder[(K, V), MapCC[K, V]] = mapFactory.newBuilder[K, V]()
18-
16+
override protected def fromSpecificIterable(coll: Iterable[(K, V)] @uncheckedVariance): MapCC[K, V] @uncheckedVariance = mapFactory.from(coll)
17+
override protected def newSpecificBuilder(): mutable.Builder[(K, V), MapCC[K, V]] @uncheckedVariance = mapFactory.newBuilder[K, V]()
18+
19+
/**
20+
* @note This operation '''has''' to be overridden by concrete collection classes to effectively
21+
* return a `MapFactory[MapCC]`. The implementation in `Map` only returns
22+
* a `MapFactory[Map]`, but the compiler will '''not''' throw an error if the
23+
* effective `MapCC` type constructor is more specific than `Map`.
24+
*
25+
* @return The factory of this collection.
26+
*/
1927
def mapFactory: scala.collection.MapFactory[MapCC] = Map
2028

21-
def empty: MapCC[K, V] = mapFactory.empty
29+
def empty: MapCC[K, V] @uncheckedVariance = mapFactory.empty
2230

2331
def canEqual(that: Any): Boolean = true
2432

@@ -60,12 +68,19 @@ trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C]
6068

6169
override def view: MapView[K, V] = new MapView.Id(this)
6270

63-
protected[this] type MapCC[K, V] = CC[K, V]
71+
/**
72+
* Type alias to `CC`. It is used to provide a default implementation of the `fromSpecificIterable`
73+
* and `newSpecificBuilder` operations.
74+
*
75+
* Due to the `@uncheckedVariance` annotation, usage of this type member can be unsound and is
76+
* therefore not recommended.
77+
*/
78+
protected type MapCC[K, V] = CC[K, V] @uncheckedVariance
6479

6580
/** Similar to `fromIterable`, but returns a Map collection type.
6681
* Note that the return type is now `CC[K2, V2]` aka `MapCC[K2, V2]` rather than `IterableCC[(K2, V2)]`.
6782
*/
68-
@`inline` protected[this] final def mapFromIterable[K2, V2](it: Iterable[(K2, V2)]): CC[K2, V2] = mapFactory.from(it)
83+
@`inline` protected final def mapFromIterable[K2, V2](it: Iterable[(K2, V2)]): CC[K2, V2] = mapFactory.from(it)
6984

7085
def mapFactory: MapFactory[MapCC]
7186

@@ -209,21 +224,7 @@ trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C]
209224
*/
210225
def empty: C
211226

212-
override def withFilter(p: ((K, V)) => Boolean): MapWithFilter = new MapWithFilter(p)
213-
214-
/** Specializes `WithFilter` for Map collection types
215-
*
216-
* @define coll map collection
217-
*/
218-
class MapWithFilter(p: ((K, V)) => Boolean) extends WithFilter(p) {
219-
220-
def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = mapFactory.from(new View.Map(filtered, f))
221-
222-
def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): CC[K2, V2] = mapFactory.from(new View.FlatMap(filtered, f))
223-
224-
override def withFilter(q: ((K, V)) => Boolean): MapWithFilter = new MapWithFilter(kv => p(kv) && q(kv))
225-
226-
}
227+
override def withFilter(p: ((K, V)) => Boolean): MapOps.WithFilter[K, V, IterableCC, CC] = new MapOps.WithFilter(this, p)
227228

228229
/** Builds a new map by applying a function to all elements of this $coll.
229230
*
@@ -280,6 +281,30 @@ trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C]
280281
def + [V1 >: V](kv: (K, V1)): CC[K, V1] = mapFactory.from(new View.Appended(toIterable, kv))
281282
}
282283

284+
object MapOps {
285+
/** Specializes `WithFilter` for Map collection types by adding overloads to transformation
286+
* operations that can return a Map.
287+
*
288+
* @define coll map collection
289+
*/
290+
class WithFilter[K, +V, +IterableCC[_], +CC[_, _] <: IterableOps[_, AnyConstr, _]](
291+
self: MapOps[K, V, CC, _] with IterableOps[(K, V), IterableCC, _],
292+
p: ((K, V)) => Boolean
293+
) extends IterableOps.WithFilter[(K, V), IterableCC](self, p) {
294+
295+
def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] =
296+
self.mapFactory.from(new View.Map(filtered, f))
297+
298+
def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): CC[K2, V2] =
299+
self.mapFactory.from(new View.FlatMap(filtered, f))
300+
301+
override def withFilter(q: ((K, V)) => Boolean): WithFilter[K, V, IterableCC, CC] =
302+
new WithFilter[K, V, IterableCC, CC](self, (kv: (K, V)) => p(kv) && q(kv))
303+
304+
}
305+
306+
}
307+
283308
/**
284309
* $factoryInfo
285310
* @define coll map

0 commit comments

Comments
 (0)