From e15f471ed77f4ad41134ea5fc58dd199b5a52eca Mon Sep 17 00:00:00 2001 From: NthPortal Date: Sat, 27 Jun 2020 18:02:53 -0400 Subject: [PATCH] [bug#12059] Make MapView#values and #keys return Views Change`MapView#values` and `MapView#keys` to return `View`s instead of `AbstractIterable`s, so that further operations on the returned values remain lazy. --- build.sbt | 4 +++ src/library/scala/collection/MapView.scala | 30 +++++++++++++++++ test/junit/scala/collection/MapViewTest.scala | 32 +++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/build.sbt b/build.sbt index 4c852509e9d4..ff0652aa8203 100644 --- a/build.sbt +++ b/build.sbt @@ -394,6 +394,10 @@ val mimaFilterSettings = Seq { ProblemFilters.exclude[MissingTypesProblem]("scala.collection.convert.JavaCollectionWrappers$JPropertiesWrapper"), ProblemFilters.exclude[MissingTypesProblem]("scala.collection.convert.JavaCollectionWrappers$JSetWrapper"), ProblemFilters.exclude[MissingTypesProblem]("scala.collection.mutable.WeakHashMap"), + + // Fix for scala/bug#12059 + ProblemFilters.exclude[MissingClassProblem]("scala.collection.MapView$Keys"), + ProblemFilters.exclude[MissingClassProblem]("scala.collection.MapView$Values"), ), } diff --git a/src/library/scala/collection/MapView.scala b/src/library/scala/collection/MapView.scala index a72fea1bfdf5..52232e64ea17 100644 --- a/src/library/scala/collection/MapView.scala +++ b/src/library/scala/collection/MapView.scala @@ -21,6 +21,20 @@ trait MapView[K, +V] override def view: MapView[K, V] = this + // Ideally this returns a `View`, but bincompat + /** Creates a view over all keys of this map. + * + * @return the keys of this map as a view. + */ + override def keys: Iterable[K] = new MapView.Keys(this) + + // Ideally this returns a `View`, but bincompat + /** Creates a view over all values of this map. + * + * @return the values of this map as a view. + */ + override def values: Iterable[V] = new MapView.Values(this) + /** Filters this map by retaining only keys satisfying a predicate. * @param p the predicate used to test keys * @return an immutable map consisting only of those key value pairs of this map where the key satisfies @@ -82,6 +96,22 @@ object MapView extends MapViewFactory { override def isEmpty: Boolean = underlying.isEmpty } + // Ideally this is public, but bincompat + @SerialVersionUID(3L) + private class Keys[K](underlying: SomeMapOps[K, _]) extends AbstractView[K] { + def iterator: Iterator[K] = underlying.keysIterator + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + // Ideally this is public, but bincompat + @SerialVersionUID(3L) + private class Values[+V](underlying: SomeMapOps[_, V]) extends AbstractView[V] { + def iterator: Iterator[V] = underlying.valuesIterator + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + @SerialVersionUID(3L) class MapValues[K, +V, +W](underlying: SomeMapOps[K, V], f: V => W) extends AbstractMapView[K, W] { def iterator: Iterator[(K, W)] = underlying.iterator.map(kv => (kv._1, f(kv._2))) diff --git a/test/junit/scala/collection/MapViewTest.scala b/test/junit/scala/collection/MapViewTest.scala index 0a55f0e1d7be..32c59ea91a97 100644 --- a/test/junit/scala/collection/MapViewTest.scala +++ b/test/junit/scala/collection/MapViewTest.scala @@ -8,6 +8,7 @@ class MapViewTest { def _toString(): Unit = { assertEquals("MapView()", Map(1 -> 2).view.toString) } + @deprecated("Tests deprecated API", since="2.13") @Test def testStringPrefixToString(): Unit = { @@ -18,6 +19,7 @@ class MapViewTest { } assertEquals("FooMapView()", mapView.toString) } + @Test def testClassNameToString(): Unit = { val mapView = new collection.MapView[Int,Int] { @@ -27,4 +29,34 @@ class MapViewTest { } assertEquals("FooMapView()", mapView.toString) } + + @Test + def testKeysIsLazy(): Unit = { + var counter = 0 + def assertLazy(): Unit = assertEquals(0, counter) + + val map = (1 to 10).map(i => i -> i).toMap + val mapView = map.view.filterKeys(_ => { counter += 1; true }) + assertLazy() + val keys = mapView.keys + assert(keys.isInstanceOf[View[_]]) + assertLazy() + val _ = keys.map(_ + 1) + assertLazy() + } + + @Test + def testValuesIsLazy(): Unit = { + var counter = 0 + def assertLazy(): Unit = assertEquals(0, counter) + + val map = (1 to 10).map(i => i -> i).toMap + val mapView = map.view.mapValues(i => { counter += 1; i }) + assertLazy() + val values = mapView.values + assert(values.isInstanceOf[View[_]]) + assertLazy() + val _ = values.map(_ + 1) + assertLazy() + } }