Skip to content

Commit b98ddb9

Browse files
committed
Make Set covariant, create InvariantSetOps
As a first step, we make Set and immutable.Set covariant, but keep their operations as-is, annotated with @UV to typecheck. We also create InvariantSetOps and immutable.InvariantSetOps that we will use in subsequent commits to hold the operations that only make sense on invariant subclasses of Set. Note that currently, the only non-mutable Sets that must be invariant are the SortedSets, so we could fusion InvariantSetOps with SortedSetOps to reduce the API at the cost of some flexibility.
1 parent 3e9969a commit b98ddb9

22 files changed

+71
-64
lines changed

src/compiler/scala/tools/nsc/transform/Erasure.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ abstract class Erasure extends InfoTransform
166166
* This is important on Android because there is otherwise an interface explosion.
167167
*/
168168
def minimizeParents(cls: Symbol, parents: List[Type]): List[Type] = if (parents.isEmpty) parents else {
169-
val requiredDirect: Symbol => Boolean = requiredDirectInterfaces.getOrElse(cls, Set.empty)
169+
val requiredDirect: Symbol => Boolean = requiredDirectInterfaces.getOrElse(cls, Set.empty).contains
170170
var rest = parents.tail
171171
var leaves = collection.mutable.ListBuffer.empty[Type] += parents.head
172172
while (rest.nonEmpty) {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package scala
2+
package collection
3+
4+
import scala.language.higherKinds
5+
6+
trait InvariantSetOps[A, +CC[X] <: InvariantSetOps[X, CC, _] with Set[X], +C <: InvariantSetOps[A, CC, C] with CC[A]]
7+
extends SetOps[A, Set, C] {
8+
}

src/library/scala/collection/Set.scala

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package scala
22
package collection
33

4-
import scala.annotation.unchecked.uncheckedVariance
4+
import scala.annotation.unchecked.{uncheckedVariance => uV}
55
import scala.language.higherKinds
66
import scala.util.hashing.MurmurHash3
77
import java.lang.String
88

99
/** Base trait for set collections.
1010
*/
11-
trait Set[A]
11+
trait Set[+A]
1212
extends Iterable[A]
1313
with SetOps[A, Set, Set[A]]
1414
with Equals {
@@ -29,19 +29,18 @@ trait Set[A]
2929

3030
override def iterableFactory: IterableFactory[IterableCC] = Set
3131

32-
def empty: IterableCC[A] = iterableFactory.empty
32+
def empty: IterableCC[A] @uV = iterableFactory.empty
3333
}
3434

3535
/** Base trait for set operations
3636
*
3737
* @define coll set
3838
* @define Coll `Set`
3939
*/
40-
trait SetOps[A, +CC[X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C] with CC[A]]
41-
extends IterableOps[A, CC, C]
42-
with (A => Boolean) {
40+
trait SetOps[+A, +CC[+X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C] with CC[A]]
41+
extends IterableOps[A, CC, C] {
4342

44-
def contains(elem: A): Boolean
43+
def contains(elem: A @uV): Boolean
4544

4645
/** Tests if some element is contained in this set.
4746
*
@@ -50,15 +49,15 @@ trait SetOps[A, +CC[X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C] w
5049
* @return `true` if `elem` is contained in this set, `false` otherwise.
5150
*/
5251
@deprecatedOverriding("This method should be final, but is not due to scala/bug#10853", "2.13.0")
53-
/*@`inline` final*/ def apply(elem: A): Boolean = this.contains(elem)
52+
/*@`inline` final*/ def apply(elem: A @uV): Boolean = this.contains(elem)
5453

5554
/** Tests whether this set is a subset of another set.
5655
*
5756
* @param that the set to test.
5857
* @return `true` if this set is a subset of `that`, i.e. if
5958
* every element of this set is also an element of `that`.
6059
*/
61-
def subsetOf(that: Set[A]): Boolean = this.forall(that)
60+
def subsetOf(that: Set[A @uV]): Boolean = this.forall(that)
6261

6362
/** An iterator over all subsets of this set of the given size.
6463
* If the requested size is impossible, an empty iterator is returned.
@@ -136,30 +135,31 @@ trait SetOps[A, +CC[X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C] w
136135
* @return a new set consisting of all elements that are both in this
137136
* set and in the given set `that`.
138137
*/
139-
def intersect(that: Set[A]): C = this.filter(that)
138+
def intersect(that: Set[A @uV]): C = this.filter(that)
140139

141140
/** Alias for `intersect` */
142-
@`inline` final def & (that: Set[A]): C = intersect(that)
141+
@`inline` final def & (that: Set[A @uV]): C = intersect(that)
142+
143143

144144
/** Computes the difference of this set and another set.
145145
*
146146
* @param that the set of elements to exclude.
147147
* @return a set containing those elements of this
148148
* set that are not also contained in the given set `that`.
149149
*/
150-
def diff(that: Set[A]): C = this.filterNot(that)
150+
def diff(that: Set[A @uV]): C = this.filterNot(that)
151151

152152
/** Alias for `diff` */
153-
@`inline` final def &~ (that: Set[A]): C = this diff that
153+
@`inline` final def &~ (that: Set[A @uV]): C = this diff that
154154

155155
@deprecated("Use &~ or diff instead of --", "2.13.0")
156-
@`inline` final def -- (that: Set[A]): C = diff(that)
156+
@`inline` final def -- (that: Set[A @uV]): C = diff(that)
157157

158158
@deprecated("Consider requiring an immutable Set or fall back to Set.diff", "2.13.0")
159-
def - (elem: A): C = diff(Set(elem))
159+
def - (elem: A @uV): C = diff(Set(elem))
160160

161161
@deprecated("Use &- with an explicit collection argument instead of - with varargs", "2.13.0")
162-
def - (elem1: A, elem2: A, elems: A*): C = diff(elems.toSet + elem1 + elem2)
162+
def - (elem1: A @uV, elem2: A @uV, elems: (A @uV)*): C = diff(elems.toSet + elem1 + elem2)
163163

164164
/** Creates a new $coll by adding all elements contained in another collection to this $coll, omitting duplicates.
165165
*
@@ -174,27 +174,27 @@ trait SetOps[A, +CC[X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C] w
174174
* @param that the collection containing the elements to add.
175175
* @return a new $coll with the given elements added, omitting duplicates.
176176
*/
177-
def concat(that: collection.Iterable[A]): C = fromSpecificIterable(new View.Concat(toIterable, that))
177+
def concat(that: collection.Iterable[A @uV]): C = fromSpecificIterable(new View.Concat(toIterable, that))
178178

179179
@deprecated("Consider requiring an immutable Set or fall back to Set.union", "2.13.0")
180-
def + (elem: A): C = fromSpecificIterable(new View.Appended(toIterable, elem))
180+
def + (elem: A @uV): C = fromSpecificIterable(new View.Appended(toIterable, elem))
181181

182182
@deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0")
183-
def + (elem1: A, elem2: A, elems: A*): C = fromSpecificIterable(new View.Concat(new View.Appended(new View.Appended(toIterable, elem1), elem2), elems))
183+
def + (elem1: A @uV, elem2: A @uV, elems: (A @uV)*): C = fromSpecificIterable(new View.Concat(new View.Appended(new View.Appended(toIterable, elem1), elem2), elems))
184184

185185
/** Alias for `concat` */
186-
@`inline` final def ++ (that: collection.Iterable[A]): C = concat(that)
186+
@`inline` final def ++ (that: collection.Iterable[A @uV]): C = concat(that)
187187

188188
/** Computes the union between of set and another set.
189189
*
190190
* @param that the set to form the union with.
191191
* @return a new set consisting of all elements that are in this
192192
* set or in the given set `that`.
193193
*/
194-
@`inline` final def union(that: collection.Iterable[A]): C = concat(that)
194+
@`inline` final def union(that: collection.Iterable[A @uV]): C = concat(that)
195195

196196
/** Alias for `union` */
197-
@`inline` final def | (that: collection.Iterable[A]): C = concat(that)
197+
@`inline` final def | (that: collection.Iterable[A @uV]): C = concat(that)
198198

199199
/** The empty set of the same type as this set
200200
* @return an empty set of type `C`.
@@ -208,8 +208,10 @@ trait SetOps[A, +CC[X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C] w
208208
* @define Coll `Set`
209209
*/
210210
@SerialVersionUID(3L)
211-
object Set extends IterableFactory.Delegate[Set](immutable.Set)
211+
object Set extends IterableFactory.Delegate[Set](immutable.Set) {
212+
implicit def setToFunction[A](set: Set[A]): A => Boolean = set.contains
213+
}
212214

213215
/** Explicit instantiation of the `Set` trait to reduce class file size in subclasses. */
214216
@SerialVersionUID(3L)
215-
abstract class AbstractSet[A] extends AbstractIterable[A] with Set[A]
217+
abstract class AbstractSet[+A] extends AbstractIterable[A] with Set[A]

src/library/scala/collection/SortedSet.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ trait SortedSet[A] extends Set[A] with SortedSetOps[A, SortedSet, SortedSet[A]]
2727
}
2828

2929
trait SortedSetOps[A, +CC[X] <: SortedSetOps[X, CC, _] with SortedSet[X], +C <: SortedSetOps[A, CC, C] with CC[A]]
30-
extends SetOps[A, Set, C]
30+
extends InvariantSetOps[A, CC, C]
3131
with SortedOps[A, C] {
3232

3333
/**

src/library/scala/collection/convert/AsJavaConverters.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ trait AsJavaConverters {
179179
*/
180180
def setAsJavaSet[A](s: Set[A]): ju.Set[A] = s match {
181181
case null => null
182-
case JSetWrapper(wrapped) => wrapped
182+
case JSetWrapper(wrapped) => wrapped.asInstanceOf[ju.Set[A]]
183183
case _ => new SetWrapper(s)
184184
}
185185

src/library/scala/collection/convert/WrapAsJava.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ private[convert] trait LowPriorityWrapAsJava {
202202
*/
203203
implicit def setAsJavaSet[A](s: Set[A]): ju.Set[A] = s match {
204204
case null => null
205-
case JSetWrapper(wrapped) => wrapped
205+
case JSetWrapper(wrapped) => wrapped.asInstanceOf[ju.Set[A]]
206206
case _ => new SetWrapper(s)
207207
}
208208

src/library/scala/collection/convert/Wrappers.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ private[collection] trait Wrappers {
160160
case Some(e) =>
161161
underlying match {
162162
case ms: mutable.Set[a] =>
163-
ms remove e
163+
ms.remove(e.asInstanceOf[a])
164164
prev = None
165165
case _ =>
166166
throw new UnsupportedOperationException("remove")

src/library/scala/collection/immutable/ChampHashSet.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import java.lang.System.arraycopy
2121
*/
2222
final class ChampHashSet[A] private[immutable] (val rootNode: SetNode[A], val cachedJavaHashCode: Int, val cachedSize: Int)
2323
extends AbstractSet[A]
24-
with SetOps[A, ChampHashSet, ChampHashSet[A]]
24+
with InvariantSetOps[A, ChampHashSet, ChampHashSet[A]]
2525
with StrictOptimizedIterableOps[A, ChampHashSet, ChampHashSet[A]] {
2626

2727
override def iterableFactory: IterableFactory[ChampHashSet] = ChampHashSet

src/library/scala/collection/immutable/HashSet.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import scala.annotation.tailrec
2525
*/
2626
sealed abstract class HashSet[A]
2727
extends AbstractSet[A]
28-
with SetOps[A, HashSet, HashSet[A]]
28+
with InvariantSetOps[A, HashSet, HashSet[A]]
2929
with StrictOptimizedIterableOps[A, HashSet, HashSet[A]] {
3030

3131
import HashSet.{bufferSize, LeafHashSet, nullToEmpty}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package scala
2+
package collection
3+
package immutable
4+
5+
import scala.language.higherKinds
6+
7+
trait InvariantSetOps[A, +CC[X] <: InvariantSetOps[X, CC, _] with Set[X], +C <: InvariantSetOps[A, CC, C] with CC[A]]
8+
extends collection.InvariantSetOps[A, CC, C]
9+
with SetOps[A, Set, C] {
10+
}

src/library/scala/collection/immutable/ListSet.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import scala.annotation.tailrec
3030
*/
3131
sealed class ListSet[A]
3232
extends AbstractSet[A]
33-
with SetOps[A, ListSet, ListSet[A]]
33+
with InvariantSetOps[A, ListSet, ListSet[A]]
3434
with StrictOptimizedIterableOps[A, ListSet, ListSet[A]] {
3535

3636
override def className: String = "ListSet"

src/library/scala/collection/immutable/Set.scala

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ package immutable
55
import java.io.{ObjectInputStream, ObjectOutputStream}
66

77
import scala.collection.mutable.{Builder, ImmutableBuilder}
8+
import scala.annotation.unchecked.{uncheckedVariance => uV}
89
import scala.language.higherKinds
910

1011

1112
/** Base trait for immutable set collections */
12-
trait Set[A] extends Iterable[A] with collection.Set[A] with SetOps[A, Set, Set[A]] {
13+
trait Set[+A] extends Iterable[A] with collection.Set[A] with SetOps[A, Set, Set[A]] {
1314
override def iterableFactory: IterableFactory[IterableCC] = Set
1415
}
1516

@@ -18,34 +19,33 @@ trait Set[A] extends Iterable[A] with collection.Set[A] with SetOps[A, Set, Set[
1819
* @define coll immutable set
1920
* @define Coll `immutable.Set`
2021
*/
21-
trait SetOps[A, +CC[X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C] with CC[A]]
22+
trait SetOps[+A, +CC[+X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C] with CC[A]]
2223
extends collection.SetOps[A, CC, C] {
23-
2424
/** Creates a new set with an additional element, unless the element is
2525
* already present.
2626
*
2727
* @param elem the element to be added
2828
* @return a new set that contains all elements of this set and that also
2929
* contains `elem`.
3030
*/
31-
def incl(elem: A): C
31+
def incl(elem: A @uV): C
3232

3333
/** Alias for `incl` */
3434
@deprecatedOverriding("This method should be final, but is not due to scala/bug#10853", "2.13.0")
35-
override /*final*/ def + (elem: A): C = incl(elem) // like in collection.Set but not deprecated
35+
override /*final*/ def + (elem: A @uV): C = incl(elem) // like in collection.Set but not deprecated
3636

3737
/** Creates a new set with a given element removed from this set.
3838
*
3939
* @param elem the element to be removed
4040
* @return a new set that contains all elements of this set but that does not
4141
* contain `elem`.
4242
*/
43-
def excl(elem: A): C
43+
def excl(elem: A @uV): C
4444

4545
/** Alias for `excl` */
46-
/* @`inline` final */ override def - (elem: A): C = excl(elem)
46+
/* @`inline` final */ override def - (elem: A @uV): C = excl(elem)
4747

48-
override def concat(that: collection.Iterable[A]): C = {
48+
override def concat(that: collection.Iterable[A @uV]): C = {
4949
var result: C = coll
5050
val it = that.iterator
5151
while (it.hasNext) result = result + it.next()
@@ -224,4 +224,4 @@ object Set extends IterableFactory[Set] {
224224

225225
/** Explicit instantiation of the `Set` trait to reduce class file size in subclasses. */
226226
@SerialVersionUID(3L)
227-
abstract class AbstractSet[A] extends scala.collection.AbstractSet[A] with Set[A]
227+
abstract class AbstractSet[+A] extends scala.collection.AbstractSet[A] with Set[A]

src/library/scala/collection/immutable/SortedSet.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ trait SortedSet[A]
1818
* @define Coll `immutable.SortedSet`
1919
*/
2020
trait SortedSetOps[A, +CC[X] <: SortedSetOps[X, CC, _] with SortedSet[X], +C <: SortedSetOps[A, CC, C] with CC[A]]
21-
extends SetOps[A, Set, C]
21+
extends InvariantSetOps[A, CC, C]
2222
with collection.SortedSetOps[A, CC, C]
2323

2424
/**

src/library/scala/collection/mutable/Set.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ trait Set[A]
1818
*/
1919
trait SetOps[A, +CC[X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C] with CC[A]]
2020
extends IterableOps[A, CC, C]
21-
with collection.SetOps[A, CC, C]
21+
with collection.InvariantSetOps[A, CC, C]
2222
with Cloneable[C]
2323
with Growable[A]
2424
with Shrinkable[A] {

test/files/neg/found-req-variance.check

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,6 @@ Note: Int <: AnyVal (and Misc.MyData <: Misc.Data[Int]), but class Data is invar
161161
You may wish to define A as +A instead. (SLS 4.5)
162162
def f1 = Set[Data[AnyVal]]() + new MyData
163163
^
164-
found-req-variance.scala:100: error: type mismatch;
165-
found : Set[String]
166-
required: Set[CharSequence]
167-
Note: String <: CharSequence, but trait Set is invariant in type A.
168-
You may wish to investigate a wildcard type such as `_ <: CharSequence`. (SLS 3.2.10)
169-
foo(s)
170-
^
171164
found-req-variance.scala:104: error: type mismatch;
172165
found : Misc.Trippy[String,String,String]
173166
required: Misc.Trippy[Object,Object,Object]
@@ -182,4 +175,4 @@ Note: AnyRef >: String, but trait Map is invariant in type A.
182175
You may wish to investigate a wildcard type such as `_ >: String`. (SLS 3.2.10)
183176
def g2 = Set[Map[String, String]]() + Map[AnyRef, String]()
184177
^
185-
28 errors found
178+
27 errors found

test/files/neg/t8035-deprecated.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
t8035-deprecated.scala:2: warning: Adaptation of argument list by inserting () is deprecated: this is unlikely to be what you want.
2-
signature: SetOps.apply(elem: A): Boolean
2+
signature: SetOps.apply(elem: A @scala.annotation.unchecked.uncheckedVariance): Boolean
33
given arguments: <none>
44
after adaptation: SetOps((): Unit)
55
List(1,2,3).toSet()

test/files/neg/t8035-removed.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
t8035-removed.scala:2: error: Adaptation of argument list by inserting () has been removed.
2-
signature: SetOps.apply(elem: A): Boolean
2+
signature: SetOps.apply(elem: A @scala.annotation.unchecked.uncheckedVariance): Boolean
33
given arguments: <none>
44
List(1,2,3).toSet()
55
^

test/files/neg/unchecked.check

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
unchecked.scala:18: warning: non-variable type argument String in type pattern Iterable[String] (the underlying of Iterable[String]) is unchecked since it is eliminated by erasure
22
case xs: Iterable[String] => xs.head // unchecked
33
^
4-
unchecked.scala:22: warning: non-variable type argument Any in type pattern scala.collection.immutable.Set[Any] (the underlying of Set[Any]) is unchecked since it is eliminated by erasure
5-
case xs: Set[Any] => xs.head // unchecked
6-
^
74
unchecked.scala:26: warning: non-variable type argument Any in type pattern scala.collection.immutable.Map[Any,Any] (the underlying of Map[Any,Any]) is unchecked since it is eliminated by erasure
85
case xs: Map[Any, Any] => xs.head // unchecked
96
^
@@ -17,5 +14,5 @@ unchecked.scala:55: warning: non-variable type argument Array[T] in type pattern
1714
case ArrayApply(x: Exp[Array[T]], _, _) => x // unchecked
1815
^
1916
error: No warnings can be incurred under -Xfatal-warnings.
20-
6 warnings found
17+
5 warnings found
2118
one error found

test/files/neg/unchecked.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ object Test {
1919
case _ => 0
2020
}
2121
def f3(x: Any) = x match {
22-
case xs: Set[Any] => xs.head // unchecked
22+
case xs: Set[Any] => xs.head // okay
2323
case _ => 0
2424
}
2525
def f4(x: Any) = x match {

test/files/neg/unchecked3.check

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,9 @@ unchecked3.scala:62: warning: non-variable type argument Array[String] in type p
3131
unchecked3.scala:63: warning: non-variable type argument String in type pattern Array[Array[List[String]]] is unchecked since it is eliminated by erasure
3232
/* warn */ case _: Array[Array[List[String]]] => ()
3333
^
34-
unchecked3.scala:75: warning: abstract type A in type pattern scala.collection.immutable.Set[Q.this.A] (the underlying of Set[Q.this.A]) is unchecked since it is eliminated by erasure
35-
/* warn */ case xs: Set[A] => xs.head
36-
^
3734
unchecked3.scala:62: warning: unreachable code
3835
/* warn */ case _: Array[List[Array[String]]] => ()
3936
^
4037
error: No warnings can be incurred under -Xfatal-warnings.
41-
13 warnings found
38+
12 warnings found
4239
one error found

test/files/neg/unchecked3.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ object Matching {
7272
def f(xs: Iterable[B]) = xs match {
7373
/* nowarn */ case xs: List[A] => xs.head
7474
/* nowarn */ case xs: Seq[B] => xs.head
75-
/* warn */ case xs: Set[A] => xs.head
75+
/* nowarn */ case xs: Set[A] => xs.head
7676
}
7777
def f2[T <: B](xs: Iterable[T]) = xs match {
7878
/* nowarn */ case xs: List[B with T] => xs.head

0 commit comments

Comments
 (0)