diff --git a/library/src/scala/collection/Map.scala b/library/src/scala/collection/Map.scala index ac22425c5922..81a8e6e7303a 100644 --- a/library/src/scala/collection/Map.scala +++ b/library/src/scala/collection/Map.scala @@ -191,6 +191,10 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[?, AnyConstr, ?], +C] * - To ensure an independent strict collection, use `m.keysIterator.toSet` * - To obtain a view on the keys, use `scala.collection.View.fromIteratorProvider(m.keysIterator)` * + * Specifically, for mutable collections, it is **not** guaranteed that the set would reflect the changes made onto + * the map, **nor** it is guaranteed that the set would _not_ reflect the changes. To guarantee either behavior, + * obtain a strict collection or a view as above. + * * @return a set representing the keys contained by this map */ def keySet: Set[K] = @@ -216,7 +220,7 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[?, AnyConstr, ?], +C] */ protected trait GenKeySet { this: Set[K] => // CC note: this is unavoidable to make the KeySet pure. - private[MapOps] val allKeys = MapOps.this.keysIterator.toList + private[MapOps] val allKeys = MapOps.this.keysIterator.to(mutable.LinkedHashSet) // We restore the lazy behavior in LazyKeySet def iterator: Iterator[K] = allKeys.iterator @@ -426,7 +430,7 @@ object MapOps { /** The implementation class of the set returned by `keySet`, for pure maps. */ - private class LazyKeySet[K, +V, +CC[_, _] <: IterableOps[?, AnyConstr, ?], +C](mp: MapOps[K, V, CC, C]) extends AbstractSet[K] with DefaultSerializable { + private[collection] class LazyKeySet[K, +V, +CC[_, _] <: IterableOps[?, AnyConstr, ?], +C](mp: MapOps[K, V, CC, C]) extends AbstractSet[K] with DefaultSerializable { def iterator: Iterator[K] = mp.keysIterator def diff(that: Set[K]): Set[K] = LazyKeySet.this.fromSpecific(this.view.filterNot(that)) def contains(key: K): Boolean = mp.contains(key) diff --git a/library/src/scala/collection/SortedMap.scala b/library/src/scala/collection/SortedMap.scala index 591abfb83d8f..635cd407924f 100644 --- a/library/src/scala/collection/SortedMap.scala +++ b/library/src/scala/collection/SortedMap.scala @@ -132,9 +132,22 @@ transparent trait SortedMapOps[K, +V, +CC[X, Y] <: Map[X, Y] & SortedMapOps[X, Y rangeUntil(next) } - override def keySet: SortedSet[K] = new KeySortedSet + override def keySet: SortedSet[K] = new LazyKeySortedSet /** The implementation class of the set returned by `keySet` */ + private[collection] class LazyKeySortedSet extends MapOps.LazyKeySet(this) with SortedSet[K] { + implicit def ordering: Ordering[K] = SortedMapOps.this.ordering + def iteratorFrom(start: K): Iterator[K] = SortedMapOps.this.keysIteratorFrom(start) + + override def diff(that: Set[K]): SortedSet[K] = fromSpecific(view.filterNot(that)) + override def rangeImpl(from: Option[K], until: Option[K]): SortedSet[K] = { + val map = SortedMapOps.this.rangeImpl(from, until) + new map.LazyKeySortedSet + } + } + + /** The old implementation class of the set returned by `keySet` */ + @deprecated("KeySortedSet is no longer used in the .keySet implementation", since = "3.8.0") protected class KeySortedSet extends SortedSet[K] with GenKeySet with GenKeySortedSet { def diff(that: Set[K]): SortedSet[K] = fromSpecific(view.filterNot(that)) def rangeImpl(from: Option[K], until: Option[K]): SortedSet[K] = { @@ -144,6 +157,7 @@ transparent trait SortedMapOps[K, +V, +CC[X, Y] <: Map[X, Y] & SortedMapOps[X, Y } /** A generic trait that is reused by sorted keyset implementations */ + @deprecated("GenKeySortedSet is no longer used in .keySet implementations", since = "3.8.0") protected trait GenKeySortedSet extends GenKeySet { this: SortedSet[K] => implicit def ordering: Ordering[K] = SortedMapOps.this.ordering def iteratorFrom(start: K): Iterator[K] = SortedMapOps.this.keysIteratorFrom(start) diff --git a/library/src/scala/collection/immutable/HashMap.scala b/library/src/scala/collection/immutable/HashMap.scala index ed18c329b7d9..9a37dcdefe68 100644 --- a/library/src/scala/collection/immutable/HashMap.scala +++ b/library/src/scala/collection/immutable/HashMap.scala @@ -60,7 +60,8 @@ final class HashMap[K, +V] private[immutable] (private[immutable] val rootNode: override def keySet: Set[K] = if (size == 0) Set.empty else new HashKeySet - private[immutable] final class HashKeySet extends ImmutableKeySet { + + private[immutable] final class HashKeySet extends LazyImmutableKeySet { private def newKeySetOrThis(newHashMap: HashMap[K, ?]): Set[K] = if (newHashMap eq HashMap.this) this else newHashMap.keySet diff --git a/library/src/scala/collection/immutable/Map.scala b/library/src/scala/collection/immutable/Map.scala index e7143afa5a7c..e16d08067cb6 100644 --- a/library/src/scala/collection/immutable/Map.scala +++ b/library/src/scala/collection/immutable/Map.scala @@ -143,9 +143,17 @@ transparent trait MapOps[K, +V, +CC[X, +Y] <: MapOps[X, Y, CC, ?], +C <: MapOps[ */ def transform[W](f: (K, V) => W): CC[K, W] = map { case (k, v) => (k, f(k, v)) } - override def keySet: Set[K] = new ImmutableKeySet + override def keySet: Set[K] = new LazyImmutableKeySet /** The implementation class of the set returned by `keySet` */ + private[immutable] class LazyImmutableKeySet extends MapOps.LazyKeySet(this) with Set[K] { + override def diff(that: collection.Set[K]): Set[K] = super.diff(that) + override def incl(elem: K): Set[K] = if (this(elem)) this else MapOps.this.updated(elem, ()).keySet + override def excl(elem: K): Set[K] = if (this(elem)) MapOps.this.removed(elem).keySet else this + } + + /** The implementation class of the set returned by `keySet` */ + @deprecated("ImmutableKeySet is no longer used in .keySet implementations", since = "3.8.0") protected[immutable] class ImmutableKeySet extends AbstractSet[K] with GenKeySet with DefaultSerializable { def incl(elem: K): Set[K] = if (this(elem)) this else empty ++ this + elem def excl(elem: K): Set[K] = if (this(elem)) empty ++ this - elem else this diff --git a/library/src/scala/collection/immutable/Set.scala b/library/src/scala/collection/immutable/Set.scala index 403f434beeb1..052efa5d55cd 100644 --- a/library/src/scala/collection/immutable/Set.scala +++ b/library/src/scala/collection/immutable/Set.scala @@ -110,7 +110,7 @@ object Set extends IterableFactory[Set] { case s: Set3[E] => s case s: Set4[E] => s case s: HashMap[E @unchecked, _]#HashKeySet => s - case s: MapOps[E, Any, Map, Map[E, Any]]#ImmutableKeySet @unchecked => s + case s: MapOps[E, Any, Map, Map[E, Any]]#LazyImmutableKeySet @unchecked => s // We also want `SortedSet` (and subclasses, such as `BitSet`) // to rebuild themselves, to avoid element type widening issues. case _ => newBuilder[E].addAll(it).result() diff --git a/library/src/scala/collection/immutable/SortedMap.scala b/library/src/scala/collection/immutable/SortedMap.scala index 45594b9577a2..8339f01ee29b 100644 --- a/library/src/scala/collection/immutable/SortedMap.scala +++ b/library/src/scala/collection/immutable/SortedMap.scala @@ -93,9 +93,21 @@ transparent trait SortedMapOps[K, +V, +CC[X, +Y] <: Map[X, Y] & SortedMapOps[X, def unsorted: Map[K, V] - override def keySet: SortedSet[K] = new ImmutableKeySortedSet + override def keySet: SortedSet[K] = new LazyImmutableKeySortedSet /** The implementation class of the set returned by `keySet` */ + private class LazyImmutableKeySortedSet extends LazyKeySortedSet with SortedSet[K] { + override def diff(that: scala.collection.Set[K]): SortedSet[K] = super.diff(that) + override def rangeImpl(from: Option[K], until: Option[K]): SortedSet[K] = { + val map = self.rangeImpl(from, until) + new map.LazyImmutableKeySortedSet + } + def incl(elem: K): SortedSet[K] = fromSpecific(this).incl(elem) + def excl(elem: K): SortedSet[K] = fromSpecific(this).excl(elem) + } + + /** The implementation class of the set returned by `keySet` */ + @deprecated("ImmutableKeySortedSet is no longer used by the .keySet implementation", since = "3.8.0") protected class ImmutableKeySortedSet extends AbstractSet[K] with SortedSet[K] with GenKeySet with GenKeySortedSet { def rangeImpl(from: Option[K], until: Option[K]): SortedSet[K] = { val map = self.rangeImpl(from, until) diff --git a/library/src/scala/collection/mutable/LinkedHashMap.scala b/library/src/scala/collection/mutable/LinkedHashMap.scala index d3ce49179fad..b21c76cae998 100644 --- a/library/src/scala/collection/mutable/LinkedHashMap.scala +++ b/library/src/scala/collection/mutable/LinkedHashMap.scala @@ -234,11 +234,15 @@ class LinkedHashMap[K, V] def extract(nd: Entry): (K, V) = (nd.key, nd.value) } + @deprecated("LinkedKeySet is now strict and no longer used in the implementation of .keySet", "3.8.0") + /** Note that a LinkedKeySet could be strict. */ protected class LinkedKeySet extends KeySet { override def iterableFactory: IterableFactory[collection.Set] = LinkedHashSet } - override def keySet: collection.Set[K] = new LinkedKeySet + override def keySet: collection.Set[K] = new MapOps.LazyKeySet(this) { + override def iterableFactory: IterableFactory[collection.Set] = LinkedHashSet + } override def keysIterator: Iterator[K] = if (size == 0) Iterator.empty diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 9a633397924a..61aa77ad3dc5 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -9,6 +9,18 @@ object MiMaFilters { // Additions that require a new minor version of the library Build.mimaPreviousDottyVersion -> Seq( ProblemFilters.exclude[DirectMissingMethodProblem]("scala.caps.package#package.freeze"), + + // against 3.8.0-RC1 + // private[MapOps] MapOps.allKeys changing types + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.collection.MapOps#GenKeySet.allKeys"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.collection.MapOps#GenKeySet.scala$collection$MapOps$GenKeySet$_setter_$allKeys_="), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.MapOps#KeySet.allKeys"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.SortedMapOps#KeySortedSet.allKeys"), + ProblemFilters.exclude[IncompatibleResultTypeProblem]("scala.collection.immutable.SortedMapOps#ImmutableKeySortedSet.allKeys"), + // new private[collection] classes + ProblemFilters.exclude[MissingClassProblem]("scala.collection.SortedMapOps$LazyKeySortedSet"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.MapOps$LazyImmutableKeySet"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.SortedMapOps$LazyImmutableKeySortedSet"), ), )