Skip to content

Commit e9987db

Browse files
committed
Adapt apply, contains and +/incl for covariant Set
`contains` now takes an extra type parameter to pass variance checks. To avoid boxing we overload `contains` in BitSet. The implementation of `contains` in GenKeySet and TreeSet are currently unsafe and hard to make safe due to erasure, this will have to be resolved before this PR can be merged. `+`/`incl` are changed similarly. Additionally, overloaded versions with their old type signatures are added to InvariantSetOps. This is crucial for all the SortedSets since adding an element of the same type should preserve the sorting.
1 parent b98ddb9 commit e9987db

21 files changed

+93
-45
lines changed

src/library/scala/Enumeration.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,10 @@ abstract class Enumeration (initial: Int) extends Serializable {
277277
new ValueSet(nnIds.rangeImpl(from.map(_.id - bottomId), until.map(_.id - bottomId)))
278278

279279
override def empty = ValueSet.empty
280-
def contains(v: Value) = nnIds contains (v.id - bottomId)
280+
def contains [A1 >: Value](v: A1) = v match {
281+
case v: Value => nnIds contains (v.id - bottomId)
282+
case _ => false
283+
}
281284
def incl (value: Value) = new ValueSet(nnIds + (value.id - bottomId))
282285
def excl (value: Value) = new ValueSet(nnIds - (value.id - bottomId))
283286
def iterator = nnIds.iterator map (id => thisenum.apply(bottomId + id))

src/library/scala/collection/BitSet.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ trait BitSetOps[+C <: BitSet with BitSetOps[C]]
9494

9595
def contains(elem: Int): Boolean =
9696
0 <= elem && (word(elem >> LogWL) & (1L << elem)) != 0L
97+
def contains[A1 >: Int](elem: A1): Boolean =
98+
elem match {
99+
case elem: Int => contains(elem)
100+
case _ => false
101+
}
97102

98103
def iterator: Iterator[Int] = iteratorFrom(0)
99104

src/library/scala/collection/InvariantSetOps.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,25 @@ import scala.language.higherKinds
55

66
trait InvariantSetOps[A, +CC[X] <: InvariantSetOps[X, CC, _] with Set[X], +C <: InvariantSetOps[A, CC, C] with CC[A]]
77
extends SetOps[A, Set, C] {
8+
def concat(that: collection.Iterable[A]): C = fromSpecificIterable(new View.Concat(toIterable, that))
9+
10+
@deprecated("Consider requiring an immutable Set or fall back to Set.union", "2.13.0")
11+
def + (elem: A): C = fromSpecificIterable(new View.Appended(toIterable, elem))
12+
13+
@deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0")
14+
def + (elem1: A, elem2: A, elems: A*): C = fromSpecificIterable(new View.Concat(new View.Appended(new View.Appended(toIterable, elem1), elem2), elems))
15+
16+
/** Alias for `concat` */
17+
@`inline` final def ++ (that: collection.Iterable[A]): C = concat(that)
18+
19+
/** Computes the union between of set and another set.
20+
*
21+
* @param that the set to form the union with.
22+
* @return a new set consisting of all elements that are in this
23+
* set or in the given set `that`.
24+
*/
25+
@`inline` final def union(that: collection.Iterable[A]): C = concat(that)
26+
27+
/** Alias for `union` */
28+
@`inline` final def | (that: collection.Iterable[A]): C = concat(that)
829
}

src/library/scala/collection/Map.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C]
144144
/** A generic trait that is reused by keyset implementations */
145145
protected trait GenKeySet { this: Set[K] =>
146146
def iterator: Iterator[K] = MapOps.this.keysIterator
147-
def contains(key: K): Boolean = MapOps.this.contains(key)
147+
// FIXME: unsafe cast
148+
def contains[K1 >: K](key: K1): Boolean = MapOps.this.contains(key.asInstanceOf[K])
148149
override def size: Int = MapOps.this.size
149150
}
150151

src/library/scala/collection/Set.scala

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ trait Set[+A]
4040
trait SetOps[+A, +CC[+X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C] with CC[A]]
4141
extends IterableOps[A, CC, C] {
4242

43-
def contains(elem: A @uV): Boolean
43+
def contains[A1 >: A](elem: A1): Boolean
4444

4545
/** Tests if some element is contained in this set.
4646
*
@@ -49,7 +49,7 @@ trait SetOps[+A, +CC[+X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C]
4949
* @return `true` if `elem` is contained in this set, `false` otherwise.
5050
*/
5151
@deprecatedOverriding("This method should be final, but is not due to scala/bug#10853", "2.13.0")
52-
/*@`inline` final*/ def apply(elem: A @uV): Boolean = this.contains(elem)
52+
/*@`inline` final*/ def apply[A1 >: A](elem: A1): Boolean = this.contains(elem)
5353

5454
/** Tests whether this set is a subset of another set.
5555
*
@@ -174,27 +174,24 @@ trait SetOps[+A, +CC[+X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C]
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 @uV]): C = fromSpecificIterable(new View.Concat(toIterable, that))
177+
def concat[B >: A](that: collection.Iterable[B]): CC[B]
178178

179179
@deprecated("Consider requiring an immutable Set or fall back to Set.union", "2.13.0")
180-
def + (elem: A @uV): C = fromSpecificIterable(new View.Appended(toIterable, elem))
180+
def + [B >: A](elem: B): CC[B] = fromIterable(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 @uV, elem2: A @uV, elems: (A @uV)*): C = fromSpecificIterable(new View.Concat(new View.Appended(new View.Appended(toIterable, elem1), elem2), elems))
184-
185-
/** Alias for `concat` */
186-
@`inline` final def ++ (that: collection.Iterable[A @uV]): C = concat(that)
183+
def + [B >: A](elem1: B, elem2: B, elems: B*): CC[B] = fromIterable(new View.Concat(new View.Appended(new View.Appended(toIterable, elem1), elem2), elems))
187184

188185
/** Computes the union between of set and another set.
189186
*
190187
* @param that the set to form the union with.
191188
* @return a new set consisting of all elements that are in this
192189
* set or in the given set `that`.
193190
*/
194-
@`inline` final def union(that: collection.Iterable[A @uV]): C = concat(that)
191+
@`inline` final def union[B >: A](that: collection.Iterable[B]): CC[B] = concat(that)
195192

196193
/** Alias for `union` */
197-
@`inline` final def | (that: collection.Iterable[A @uV]): C = concat(that)
194+
@`inline` final def | [B >: A](that: collection.Iterable[B]): CC[B] = concat(that)
198195

199196
/** The empty set of the same type as this set
200197
* @return an empty set of type `C`.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ private[collection] trait Wrappers {
191191

192192
def iterator = underlying.iterator.asScala
193193

194-
def contains(elem: A): Boolean = underlying.contains(elem)
194+
def contains[A1 >: A](elem: A1): Boolean = underlying.contains(elem)
195195

196196
def addOne(elem: A): this.type = { underlying add elem; this }
197197
def subtractOne(elem: A): this.type = { underlying remove elem; this }

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ final class ChampHashSet[A] private[immutable] (val rootNode: SetNode[A], val ca
3636

3737
protected[immutable] def reverseIterator: Iterator[A] = new SetReverseIterator[A](rootNode)
3838

39-
def contains(element: A): Boolean = rootNode.contains(element, computeHash(element), 0)
39+
def contains[A1 >: A](element: A1): Boolean = rootNode.contains(element.asInstanceOf[A], computeHash(element), 0)
4040

4141
def incl(element: A): ChampHashSet[A] = {
4242
val effect = SetEffect[A]()

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ sealed abstract class HashSet[A]
3434

3535
override def iterableFactory = HashSet
3636

37-
def contains(elem: A): Boolean = get0(elem, computeHash(elem), 0)
37+
def contains[A1 >: A](elem: A1): Boolean = get0(elem.asInstanceOf[A], computeHash(elem), 0)
3838

3939
def incl(elem: A): HashSet[A] = updated0(elem, computeHash(elem), 0)
4040

src/library/scala/collection/immutable/InvariantSetOps.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,23 @@ import scala.language.higherKinds
77
trait InvariantSetOps[A, +CC[X] <: InvariantSetOps[X, CC, _] with Set[X], +C <: InvariantSetOps[A, CC, C] with CC[A]]
88
extends collection.InvariantSetOps[A, CC, C]
99
with SetOps[A, Set, C] {
10+
/** Creates a new set with an additional element, unless the element is
11+
* already present.
12+
*
13+
* @param elem the element to be added
14+
* @return a new set that contains all elements of this set and that also
15+
* contains `elem`.
16+
*/
17+
def incl(elem: A): C
18+
19+
/** Alias for `incl` */
20+
@deprecatedOverriding("This method should be final, but is not due to scala/bug#10853", "2.13.0")
21+
override /*final*/ def + (elem: A): C = incl(elem) // like in collection.Set but not deprecated
22+
23+
override def concat(that: collection.Iterable[A]): C = {
24+
var result: C = coll
25+
val it = that.iterator
26+
while (it.hasNext) result = (result.incl(it.next()))
27+
result
28+
}
1029
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ sealed class ListSet[A]
3838
override def size: Int = 0
3939
override def isEmpty: Boolean = true
4040

41-
def contains(elem: A): Boolean = false
41+
def contains[A1 >: A](elem: A1): Boolean = false
4242

4343
def incl(elem: A): ListSet[A] = new Node(elem)
4444
def excl(elem: A): ListSet[A] = this
@@ -71,7 +71,7 @@ sealed class ListSet[A]
7171

7272
override def isEmpty: Boolean = false
7373

74-
override def contains(e: A) = containsInternal(this, e)
74+
override def contains[A1 >: A](e: A1) = containsInternal(this, e.asInstanceOf[A])
7575

7676
@tailrec private[this] def containsInternal(n: ListSet[A], e: A): Boolean =
7777
!n.isEmpty && (n.elem == e || containsInternal(n.next, e))

src/library/scala/collection/immutable/Map.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ trait MapOps[K, +V, +CC[X, +Y] <: MapOps[X, Y, CC, _], +C <: MapOps[K, V, CC, C]
117117

118118
/** The implementation class of the set returned by `keySet` */
119119
protected class ImmutableKeySet extends AbstractSet[K] with GenKeySet {
120-
def incl(elem: K): Set[K] = if (this(elem)) this else empty ++ this + elem
120+
override def incl[K1 >: K](elem: K1): Set[K1] = if (this(elem)) this else empty ++ this + elem
121121
def excl(elem: K): Set[K] = if (this(elem)) empty ++ this - elem else this
122122
}
123123

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

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ trait SetOps[+A, +CC[+X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C]
2828
* @return a new set that contains all elements of this set and that also
2929
* contains `elem`.
3030
*/
31-
def incl(elem: A @uV): C
31+
def incl[B >: A](elem: B): CC[B] = fromIterable(new View.Appended(toIterable, elem))
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 @uV): C = incl(elem) // like in collection.Set but not deprecated
35+
override /*final*/ def + [B >: A](elem: B): CC[B] = incl(elem) // like in collection.Set but not deprecated
3636

3737
/** Creates a new set with a given element removed from this set.
3838
*
@@ -45,8 +45,8 @@ trait SetOps[+A, +CC[+X] <: SetOps[X, CC, _] with Set[X], +C <: SetOps[A, CC, C]
4545
/** Alias for `excl` */
4646
/* @`inline` final */ override def - (elem: A @uV): C = excl(elem)
4747

48-
override def concat(that: collection.Iterable[A @uV]): C = {
49-
var result: C = coll
48+
override def concat[B >: A](that: collection.Iterable[B]): CC[B] = {
49+
var result: CC[B] = coll
5050
val it = that.iterator
5151
while (it.hasNext) result = result + it.next()
5252
result
@@ -85,8 +85,8 @@ object Set extends IterableFactory[Set] {
8585
/** An optimized representation for immutable empty sets */
8686
private object EmptySet extends AbstractSet[Any] {
8787
override def size: Int = 0
88-
def contains(elem: Any): Boolean = false
89-
def incl(elem: Any): Set[Any] = new Set1(elem)
88+
def contains[A1 >: Any](elem: A1): Boolean = false
89+
override def incl[B >: Any](elem: B): Set[B] = new Set1(elem)
9090
def excl(elem: Any): Set[Any] = this
9191
def iterator: Iterator[Any] = Iterator.empty
9292
override def foreach[U](f: Any => U): Unit = ()
@@ -96,8 +96,8 @@ object Set extends IterableFactory[Set] {
9696
/** An optimized representation for immutable sets of size 1 */
9797
final class Set1[A] private[collection] (elem1: A) extends AbstractSet[A] with StrictOptimizedIterableOps[A, Set, Set[A]] {
9898
override def size: Int = 1
99-
def contains(elem: A): Boolean = elem == elem1
100-
def incl(elem: A): Set[A] =
99+
def contains[A1 >: A](elem: A1): Boolean = elem == elem1
100+
override def incl[B >: A](elem: B): Set[B] =
101101
if (contains(elem)) this
102102
else new Set2(elem1, elem)
103103
def excl(elem: A): Set[A] =
@@ -118,8 +118,8 @@ object Set extends IterableFactory[Set] {
118118
/** An optimized representation for immutable sets of size 2 */
119119
final class Set2[A] private[collection] (elem1: A, elem2: A) extends AbstractSet[A] with StrictOptimizedIterableOps[A, Set, Set[A]] {
120120
override def size: Int = 2
121-
def contains(elem: A): Boolean = elem == elem1 || elem == elem2
122-
def incl(elem: A): Set[A] =
121+
def contains[A1 >: A](elem: A1): Boolean = elem == elem1 || elem == elem2
122+
override def incl[B >: A](elem: B): Set[B] =
123123
if (contains(elem)) this
124124
else new Set3(elem1, elem2, elem)
125125
def excl(elem: A): Set[A] =
@@ -149,10 +149,10 @@ object Set extends IterableFactory[Set] {
149149
/** An optimized representation for immutable sets of size 3 */
150150
final class Set3[A] private[collection] (elem1: A, elem2: A, elem3: A) extends AbstractSet[A] with StrictOptimizedIterableOps[A, Set, Set[A]] {
151151
override def size: Int = 3
152-
def contains(elem: A): Boolean =
152+
def contains[A1 >: A](elem: A1): Boolean =
153153
elem == elem1 || elem == elem2 || elem == elem3
154-
def incl(elem: A): Set[A] =
155-
if (contains(elem)) this
154+
override def incl[B >: A](elem: B): Set[B] =
155+
if (contains(elem.asInstanceOf[A])) this
156156
else new Set4(elem1, elem2, elem3, elem)
157157
def excl(elem: A): Set[A] =
158158
if (elem == elem1) new Set2(elem2, elem3)
@@ -183,11 +183,11 @@ object Set extends IterableFactory[Set] {
183183
/** An optimized representation for immutable sets of size 4 */
184184
final class Set4[A] private[collection] (elem1: A, elem2: A, elem3: A, elem4: A) extends AbstractSet[A] with StrictOptimizedIterableOps[A, Set, Set[A]] {
185185
override def size: Int = 4
186-
def contains(elem: A): Boolean =
186+
def contains[A1 >: A](elem: A1): Boolean =
187187
elem == elem1 || elem == elem2 || elem == elem3 || elem == elem4
188-
def incl(elem: A): Set[A] =
189-
if (contains(elem)) this
190-
else (if (useBaseline) HashSet.empty[A] else ChampHashSet.empty[A]) + elem1 + elem2 + elem3 + elem4 + elem
188+
override def incl[B >: A](elem: B): Set[B] =
189+
if (contains(elem.asInstanceOf[A])) this
190+
else (if (useBaseline) HashSet.empty[B] else ChampHashSet.empty[B]) + elem1 + elem2 + elem3 + elem4 + elem
191191
def excl(elem: A): Set[A] =
192192
if (elem == elem1) new Set3(elem2, elem3, elem4)
193193
else if (elem == elem2) new Set3(elem1, elem3, elem4)

src/library/scala/collection/immutable/TreeSet.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ final class TreeSet[A] private (tree: RB.Tree[A, Unit])(implicit val ordering: O
104104
* @param elem the element to check for membership.
105105
* @return true, iff `elem` is contained in this set.
106106
*/
107-
def contains(elem: A): Boolean = RB.contains(tree, elem)
107+
// FIXME: unsafe cast
108+
def contains[A1 >: A](elem: A1): Boolean = RB.contains(tree, elem.asInstanceOf[A])
108109

109110
override def range(from: A, until: A): TreeSet[A] = newSet(RB.range(tree, from, until))
110111

src/library/scala/collection/mutable/HashSet.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ final class HashSet[A]
4141

4242
def clear(): Unit = table.clearTable()
4343

44-
def contains(elem: A): Boolean = table.containsElem(elem)
44+
def contains[A1 >: A](elem: A1): Boolean = table.containsElem(elem.asInstanceOf[A])
4545

4646
def get(elem: A): Option[A] = table.findEntry(elem)
4747

src/library/scala/collection/mutable/LinkedHashSet.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class LinkedHashSet[A]
6060

6161
override def size: Int = table.tableSize
6262

63-
def contains(elem: A): Boolean = table.findEntry(elem) ne null
63+
def contains[A1 >: A](elem: A1): Boolean = table.findEntry(elem.asInstanceOf[A]) ne null
6464

6565
def addOne(elem: A): this.type = {
6666
table.findOrAddEntry(elem, null)

src/library/scala/collection/mutable/TreeSet.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ sealed class TreeSet[A] private (tree: RB.Tree[A, Null])(implicit val ordering:
5454

5555
def clear(): Unit = RB.clear(tree)
5656

57-
def contains(elem: A): Boolean = RB.contains(tree, elem)
57+
// FIXME: unsafe cast
58+
def contains[A1 >: A](elem: A1): Boolean = RB.contains(tree, elem.asInstanceOf[A])
5859

5960
def get(elem: A): Option[A] = RB.getKey(tree, elem)
6061

@@ -124,7 +125,7 @@ sealed class TreeSet[A] private (tree: RB.Tree[A, Null])(implicit val ordering:
124125
override def rangeImpl(from: Option[A], until: Option[A]): TreeSet[A] =
125126
new TreeSetProjection(pickLowerBound(from), pickUpperBound(until))
126127

127-
override def contains(key: A) = isInsideViewBounds(key) && RB.contains(tree, key)
128+
override def contains[A1 >: A](key: A1) = isInsideViewBounds(key.asInstanceOf[A]) && RB.contains(tree, key.asInstanceOf[A])
128129

129130
override def iterator = RB.keysIterator(tree, from, until)
130131
override def iteratorFrom(start: A) = RB.keysIterator(tree, pickLowerBound(Some(start)), until)

src/reflect/scala/reflect/internal/util/WeakHashSet.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ final class WeakHashSet[A <: AnyRef](val initialCapacity: Int, val loadFactor: D
148148
tableLoop(0)
149149
}
150150

151-
def contains(elem: A): Boolean = findEntry(elem) ne null
151+
def contains[A1 >: A](elem: A1): Boolean = findEntry(elem.asInstanceOf[A]) ne null
152152

153153
// from scala.reflect.internal.Set, find an element or null if it isn't contained
154154
def findEntry(elem: A): A = elem match {

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 @scala.annotation.unchecked.uncheckedVariance): Boolean
2+
signature: SetOps.apply[A1 >: A](elem: A1): 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 @scala.annotation.unchecked.uncheckedVariance): Boolean
2+
signature: SetOps.apply[A1 >: A](elem: A1): Boolean
33
given arguments: <none>
44
List(1,2,3).toSet()
55
^

test/files/pos/virtpatmat_exist1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class HS[A]
1010
with StrictOptimizedIterableOps[A, HS, HS[A]]
1111
with Serializable {
1212
def get(elem: A): Option[A] = ???
13-
def contains(elem: A): Boolean = ???
13+
def contains[A1 >: A](elem: A1): Boolean = ???
1414
def addOne(elem: A): HS.this.type = ???
1515
def clear(): Unit = ???
1616
def iterator: Iterator[A] = ???

test/junit/scala/collection/mutable/SetTest.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class SetTest {
1818

1919
override def empty = new MySet(self.empty)
2020
def iterator = self.iterator
21-
def contains(elem: String) = self.contains(elem)
21+
def contains[A1 >: String](elem: A1) = self.contains(elem)
2222
def get(elem: String): Option[String] = self.get(elem)
2323
def clear(): Unit = self.clear()
2424
}

0 commit comments

Comments
 (0)