Skip to content

Commit 7a67d21

Browse files
committed
Fix soundness of Map.from and Set.from
Map.from and Set.from restrict which collections can be safely returned. Map#toMap leverages Map.from for this purpose. Inline SeqMap.from per review Delete instanceof kludge Move tests to junit
1 parent 2aa21b2 commit 7a67d21

File tree

4 files changed

+53
-16
lines changed

4 files changed

+53
-16
lines changed

library/src/scala/collection/immutable/HashMap.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ final class HashMap[K, +V] private[immutable] (private[immutable] val rootNode:
5757

5858
override def keySet: Set[K] = if (size == 0) Set.empty else new HashKeySet
5959

60-
private final class HashKeySet extends ImmutableKeySet {
60+
private[immutable] final class HashKeySet extends ImmutableKeySet {
6161

6262
private[this] def newKeySetOrThis(newHashMap: HashMap[K, _]): Set[K] =
6363
if (newHashMap eq HashMap.this) this else newHashMap.keySet

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

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import scala.annotation.unchecked.uncheckedVariance
1818
import scala.collection.generic.DefaultSerializable
1919
import scala.collection.immutable.Map.Map4
2020
import scala.collection.mutable.{Builder, ReusableBuilder}
21+
import SeqMap.{SeqMap1, SeqMap2, SeqMap3, SeqMap4}
2122

2223
/** Base type of immutable Maps */
2324
trait Map[K, +V]
@@ -28,7 +29,7 @@ trait Map[K, +V]
2829

2930
override def mapFactory: scala.collection.MapFactory[Map] = Map
3031

31-
override final def toMap[K2, V2](implicit ev: (K, V) <:< (K2, V2)): Map[K2, V2] = this.asInstanceOf[Map[K2, V2]]
32+
override final def toMap[K2, V2](implicit ev: (K, V) <:< (K2, V2)): Map[K2, V2] = Map.from(this.asInstanceOf[Map[K2, V2]])
3233

3334
/** The same map with a given default function.
3435
* Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc.
@@ -206,11 +207,30 @@ object Map extends MapFactory[Map] {
206207

207208
def empty[K, V]: Map[K, V] = EmptyMap.asInstanceOf[Map[K, V]]
208209

209-
def from[K, V](it: collection.IterableOnce[(K, V)]): Map[K, V] =
210+
def from[K, V](it: IterableOnce[(K, V)]): Map[K, V] =
210211
it match {
211212
case it: Iterable[_] if it.isEmpty => empty[K, V]
212-
case m: Map[K, V] => m
213-
case _ => (newBuilder[K, V] ++= it).result()
213+
// Since IterableOnce[(K, V)] launders the variance of K,
214+
// identify only our implementations which can be soundly substituted.
215+
// For example, the ordering used by sorted maps would fail on widened key type. (scala/bug#12745)
216+
// The following type test is not sufficient: case m: Map[K, V] => m
217+
case m: HashMap[K, V] => m
218+
case m: Map1[K, V] => m
219+
case m: Map2[K, V] => m
220+
case m: Map3[K, V] => m
221+
case m: Map4[K, V] => m
222+
//case m: WithDefault[K, V] => m // cf SortedMap.WithDefault
223+
//case m: SeqMap[K, V] => SeqMap.from(it) // inlined here to avoid hard dependency
224+
case m: ListMap[K, V] => m
225+
case m: TreeSeqMap[K, V] => m
226+
case m: VectorMap[K, V] => m
227+
case m: SeqMap1[K, V] => m
228+
case m: SeqMap2[K, V] => m
229+
case m: SeqMap3[K, V] => m
230+
case m: SeqMap4[K, V] => m
231+
232+
// Maps with a reified key type must be rebuilt, such as `SortedMap` and `IntMap`.
233+
case _ => newBuilder[K, V].addAll(it).result()
214234
}
215235

216236
def newBuilder[K, V]: Builder[(K, V), Map[K, V]] = new MapBuilderImpl

library/src/scala/collection/immutable/SeqMap.scala

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,15 @@ object SeqMap extends MapFactory[SeqMap] {
4646

4747
def from[K, V](it: collection.IterableOnce[(K, V)]): SeqMap[K, V] =
4848
it match {
49-
case sm: SeqMap[K, V] => sm
49+
//case sm: SeqMap[K, V] => sm
50+
case m: ListMap[K, V] => m
51+
case m: TreeSeqMap[K, V] => m
52+
case m: VectorMap[K, V] => m
53+
case m: SeqMap1[K, V] => m
54+
case m: SeqMap2[K, V] => m
55+
case m: SeqMap3[K, V] => m
56+
case m: SeqMap4[K, V] => m
57+
case it: Iterable[_] if it.isEmpty => empty[K, V]
5058
case _ => (newBuilder[K, V] ++= it).result()
5159
}
5260

@@ -66,7 +74,7 @@ object SeqMap extends MapFactory[SeqMap] {
6674
}
6775

6876
@SerialVersionUID(3L)
69-
private final class SeqMap1[K, +V](key1: K, value1: V) extends SeqMap[K,V] with Serializable {
77+
private[immutable] final class SeqMap1[K, +V](key1: K, value1: V) extends SeqMap[K,V] with Serializable {
7078
override def size: Int = 1
7179
override def knownSize: Int = 1
7280
override def apply(key: K) = if (key == key1) value1 else throw new NoSuchElementException("key not found: " + key)
@@ -90,7 +98,7 @@ object SeqMap extends MapFactory[SeqMap] {
9098
}
9199

92100
@SerialVersionUID(3L)
93-
private final class SeqMap2[K, +V](key1: K, value1: V, key2: K, value2: V) extends SeqMap[K,V] with Serializable {
101+
private[immutable] final class SeqMap2[K, +V](key1: K, value1: V, key2: K, value2: V) extends SeqMap[K,V] with Serializable {
94102
override def size: Int = 2
95103
override def knownSize: Int = 2
96104
override def apply(key: K) =
@@ -125,7 +133,7 @@ object SeqMap extends MapFactory[SeqMap] {
125133
}
126134

127135
@SerialVersionUID(3L)
128-
private class SeqMap3[K, +V](key1: K, value1: V, key2: K, value2: V, key3: K, value3: V) extends SeqMap[K,V] with Serializable {
136+
private[immutable] class SeqMap3[K, +V](key1: K, value1: V, key2: K, value2: V, key3: K, value3: V) extends SeqMap[K,V] with Serializable {
129137
override def size: Int = 3
130138
override def knownSize: Int = 3
131139
override def apply(key: K) =
@@ -166,7 +174,7 @@ object SeqMap extends MapFactory[SeqMap] {
166174
}
167175

168176
@SerialVersionUID(3L)
169-
private final class SeqMap4[K, +V](key1: K, value1: V, key2: K, value2: V, key3: K, value3: V, key4: K, value4: V) extends SeqMap[K,V] with Serializable {
177+
private[immutable] final class SeqMap4[K, +V](key1: K, value1: V, key2: K, value2: V, key3: K, value3: V, key4: K, value4: V) extends SeqMap[K,V] with Serializable {
170178
override def size: Int = 4
171179
override def knownSize: Int = 4
172180
override def apply(key: K) =

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,21 @@ object Set extends IterableFactory[Set] {
9696

9797
def from[E](it: collection.IterableOnce[E]): Set[E] =
9898
it match {
99-
// We want `SortedSet` (and subclasses, such as `BitSet`) to
100-
// rebuild themselves to avoid element type widening issues
101-
case _: SortedSet[E] => (newBuilder[E] ++= it).result()
102-
case _ if it.knownSize == 0 => empty[E]
103-
case s: Set[E] => s
104-
case _ => (newBuilder[E] ++= it).result()
99+
case _ if it.knownSize == 0 => empty[E]
100+
// Since IterableOnce[E] launders the variance of E,
101+
// identify only our implementations which can be soundly substituted.
102+
// It's not sufficient to match `SortedSet[E]` to rebuild and `Set[E]` to retain.
103+
case s: HashSet[E] => s
104+
case s: ListSet[E] => s
105+
case s: Set1[E] => s
106+
case s: Set2[E] => s
107+
case s: Set3[E] => s
108+
case s: Set4[E] => s
109+
//case s: MapOps[E @unchecked, _, _, _]#ImmutableKeySet => s
110+
case s: HashMap[E @unchecked, _]#HashKeySet => s
111+
// We also want `SortedSet` (and subclasses, such as `BitSet`)
112+
// to rebuild themselves, to avoid element type widening issues.
113+
case _ => newBuilder[E].addAll(it).result()
105114
}
106115

107116
def newBuilder[A]: Builder[A, Set[A]] = new SetBuilderImpl[A]

0 commit comments

Comments
 (0)