Skip to content

Commit d55a12f

Browse files
authored
Merge pull request #495 from lrytz/toConserve
preserve immutable collections in to(Target) conversions
2 parents fc741eb + 2133b3f commit d55a12f

File tree

4 files changed

+322
-67
lines changed

4 files changed

+322
-67
lines changed

build.sbt

+44-35
Original file line numberDiff line numberDiff line change
@@ -72,44 +72,53 @@ lazy val compat = new MultiScalaCrossProject(
7272
sharedSourceDir / "scala-2.11_2.12"
7373
}
7474
},
75+
Test / unmanagedSourceDirectories += {
76+
val sharedSourceDir = (ThisBuild / baseDirectory).value / "compat/src/test"
77+
CrossVersion.partialVersion(scalaVersion.value) match {
78+
case Some((3, _) | (2, 13)) =>
79+
sharedSourceDir / "scala-2.13"
80+
case _ =>
81+
sharedSourceDir / "scala-2.11_2.12"
82+
}
83+
},
7584
versionPolicyIntention := Compatibility.BinaryCompatible,
76-
)
77-
.jvmSettings(
78-
Test / unmanagedSourceDirectories += (ThisBuild / baseDirectory).value / "compat/src/test/scala-jvm",
79-
junit,
8085
mimaBinaryIssueFilters ++= {
8186
import com.typesafe.tools.mima.core._
8287
import com.typesafe.tools.mima.core.ProblemFilters._
8388
Seq(
8489
exclude[ReversedMissingMethodProblem]("scala.collection.compat.PackageShared.*"), // it's package-private
90+
exclude[Problem]("scala.collection.compat.*PreservingBuilder*")
8591
)
8692
},
8793
)
94+
.jvmSettings(
95+
Test / unmanagedSourceDirectories += (ThisBuild / baseDirectory).value / "compat/src/test/scala-jvm",
96+
junit,
97+
)
8898
.disablePlugins(ScalafixPlugin),
8999
_.jsSettings(
90-
scalacOptions ++= {
91-
val x = (LocalRootProject / baseDirectory).value.toURI.toString
92-
val y = "https://raw.githubusercontent.com/scala/scala-collection-compat/" + sys.process
93-
.Process("git rev-parse HEAD")
94-
.lineStream_!
95-
.head
96-
val opt = CrossVersion.partialVersion(scalaVersion.value) match {
97-
case Some((3, _)) => "-scalajs-mapSourceURI"
98-
case _ => "-P:scalajs:mapSourceURI"
99-
}
100-
Seq(s"$opt:$x->$y/")
101-
},
102-
Test / fork := false // Scala.js cannot run forked tests
103-
)
104-
.jsEnablePlugins(ScalaJSJUnitPlugin),
100+
scalacOptions ++= {
101+
val x = (LocalRootProject / baseDirectory).value.toURI.toString
102+
val y = "https://raw.githubusercontent.com/scala/scala-collection-compat/" + sys.process
103+
.Process("git rev-parse HEAD")
104+
.lineStream_!
105+
.head
106+
val opt = CrossVersion.partialVersion(scalaVersion.value) match {
107+
case Some((3, _)) => "-scalajs-mapSourceURI"
108+
case _ => "-P:scalajs:mapSourceURI"
109+
}
110+
Seq(s"$opt:$x->$y/")
111+
},
112+
Test / fork := false // Scala.js cannot run forked tests
113+
).jsEnablePlugins(ScalaJSJUnitPlugin),
105114
_.nativeSettings(
106-
nativeLinkStubs := true,
107-
addCompilerPlugin(
108-
"org.scala-native" % "junit-plugin" % nativeVersion cross CrossVersion.full
109-
),
110-
libraryDependencies += "org.scala-native" %%% "junit-runtime" % nativeVersion,
111-
Test / fork := false // Scala Native cannot run forked tests
112-
)
115+
nativeLinkStubs := true,
116+
addCompilerPlugin(
117+
"org.scala-native" % "junit-plugin" % nativeVersion cross CrossVersion.full
118+
),
119+
libraryDependencies += "org.scala-native" %%% "junit-runtime" % nativeVersion,
120+
Test / fork := false // Scala Native cannot run forked tests
121+
)
113122
)
114123

115124
val compat211 = compat(Seq(JSPlatform, JVMPlatform, NativePlatform), scala211)
@@ -171,7 +180,7 @@ lazy val scalafixRules = project
171180
.settings(
172181
scalaModuleAutomaticModuleName := None,
173182
versionPolicyIntention := Compatibility.None,
174-
versionCheck := {}, // I don't understand why this fails otherwise?! oh well
183+
versionCheck := {}, // I don't understand why this fails otherwise?! oh well
175184
name := "scala-collection-migrations",
176185
scalaVersion := scalafixScala212,
177186
libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % scalafixVersion
@@ -289,14 +298,14 @@ lazy val scalafixTests = project
289298
.dependsOn(scalafixInput, scalafixRules)
290299
.enablePlugins(BuildInfoPlugin, ScalafixTestkitPlugin)
291300

292-
val ciScalaVersion = sys.env.get("CI_SCALA_VERSION").flatMap(Version.parse)
293-
val isScalaJs = sys.env.get("CI_PLATFORM") == Some("js")
294-
val isScalaNative = sys.env.get("CI_PLATFORM") == Some("native")
295-
val isScalafix = sys.env.get("CI_MODE") == Some("testScalafix")
296-
val isScalafmt = sys.env.get("CI_MODE") == Some("testScalafmt")
297-
val isBinaryCompat = sys.env.get("CI_MODE") == Some("testBinaryCompat")
298-
val isHeaderCheck = sys.env.get("CI_MODE") == Some("headerCheck")
299-
val jdkVersion = sys.env.get("CI_JDK").map(_.toInt)
301+
val ciScalaVersion = sys.env.get("CI_SCALA_VERSION").flatMap(Version.parse)
302+
val isScalaJs = sys.env.get("CI_PLATFORM") == Some("js")
303+
val isScalaNative = sys.env.get("CI_PLATFORM") == Some("native")
304+
val isScalafix = sys.env.get("CI_MODE") == Some("testScalafix")
305+
val isScalafmt = sys.env.get("CI_MODE") == Some("testScalafmt")
306+
val isBinaryCompat = sys.env.get("CI_MODE") == Some("testBinaryCompat")
307+
val isHeaderCheck = sys.env.get("CI_MODE") == Some("headerCheck")
308+
val jdkVersion = sys.env.get("CI_JDK").map(_.toInt)
300309

301310
// required by sbt-scala-module
302311
inThisBuild {

compat/src/main/scala-2.11_2.12/scala/collection/compat/CompatImpl.scala

+34-19
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,35 @@ package scala.collection.compat
1414

1515
import scala.reflect.ClassTag
1616
import scala.collection.generic.CanBuildFrom
17+
import scala.{collection => c}
1718
import scala.collection.{immutable => i, mutable => m}
1819

1920
/* builder optimized for a single ++= call, which returns identity on result if possible
2021
* and defers to the underlying builder if not.
2122
*/
22-
private final class IdentityPreservingBuilder[A, CC[X] <: TraversableOnce[X]](
23-
that: m.Builder[A, CC[A]])(implicit ct: ClassTag[CC[A]])
24-
extends m.Builder[A, CC[A]] {
23+
private abstract class PreservingBuilder[A, C <: TraversableOnce[A]] extends m.Builder[A, C] {
24+
val that: m.Builder[A, C]
25+
val ct: ClassTag[C]
2526

2627
//invariant: ruined => (collection == null)
27-
var collection: CC[A] = null.asInstanceOf[CC[A]]
28-
var ruined = false
28+
var collection: C = null.asInstanceOf[C]
29+
var ruined = false
2930

3031
private[this] def ruin(): Unit = {
3132
if (collection != null) that ++= collection
32-
collection = null.asInstanceOf[CC[A]]
33+
collection = null.asInstanceOf[C]
3334
ruined = true
3435
}
3536

36-
override def ++=(elems: TraversableOnce[A]): this.type =
37-
elems match {
38-
case ct(ca) if collection == null && !ruined => {
39-
collection = ca
40-
this
41-
}
42-
case _ => {
37+
override def ++=(elems: TraversableOnce[A]): this.type = {
38+
(if (collection == null && !ruined) ct.unapply(elems) else None) match {
39+
case Some(c) => collection = c
40+
case _ =>
4341
ruin()
4442
that ++= elems
45-
this
46-
}
4743
}
44+
this
45+
}
4846

4947
def +=(elem: A): this.type = {
5048
ruin()
@@ -53,14 +51,31 @@ private final class IdentityPreservingBuilder[A, CC[X] <: TraversableOnce[X]](
5351
}
5452

5553
def clear(): Unit = {
56-
collection = null.asInstanceOf[CC[A]]
57-
if (ruined) that.clear()
58-
ruined = false
54+
collection = null.asInstanceOf[C]
55+
if (ruined) {
56+
that.clear()
57+
ruined = false
58+
}
5959
}
6060

61-
def result(): CC[A] = if (collection == null) that.result() else collection
61+
def result(): C = if (collection == null) that.result() else collection
6262
}
6363

64+
private final class IdentityPreservingBuilder[A, CC[X] <: TraversableOnce[X]](
65+
val that: m.Builder[A, CC[A]])(implicit val ct: ClassTag[CC[A]])
66+
extends PreservingBuilder[A, CC[A]]
67+
68+
private final class IdentityPreservingBitSetBuilder[C <: c.BitSet](val that: m.Builder[Int, C])(
69+
implicit val ct: ClassTag[C])
70+
extends PreservingBuilder[Int, C]
71+
72+
private final class IdentityPreservingMapBuilder[
73+
K,
74+
V,
75+
CC[X, Y] <: c.Map[X, Y] with c.MapLike[X, Y, CC[X, Y]]](val that: m.Builder[(K, V), CC[K, V]])(
76+
implicit val ct: ClassTag[CC[K, V]])
77+
extends PreservingBuilder[(K, V), CC[K, V]]
78+
6479
private[compat] object CompatImpl {
6580
def simpleCBF[A, C](f: => m.Builder[A, C]): CanBuildFrom[Any, A, C] =
6681
new CanBuildFrom[Any, A, C] {

compat/src/main/scala-2.11_2.12/scala/collection/compat/PackageShared.scala

+103-13
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212

1313
package scala.collection.compat
1414

15+
import scala.annotation.nowarn
1516
import scala.collection.generic._
1617
import scala.reflect.ClassTag
1718
import scala.collection.{
18-
BitSet,
1919
GenTraversable,
2020
IterableLike,
2121
IterableView,
@@ -61,39 +61,129 @@ private[compat] trait PackageShared {
6161
must be non-strict
6262
*/
6363
def builder: m.Builder[A, CC[A]] = fact match {
64-
case c.Seq | i.Seq => new IdentityPreservingBuilder[A, i.Seq](i.Seq.newBuilder[A])
64+
case c.Seq | i.Seq =>
65+
new IdentityPreservingBuilder[A, i.Seq](i.Seq.newBuilder[A])
66+
6567
case c.LinearSeq | i.LinearSeq =>
6668
new IdentityPreservingBuilder[A, i.LinearSeq](i.LinearSeq.newBuilder[A])
69+
case i.Queue =>
70+
new IdentityPreservingBuilder[A, i.Queue](i.Queue.newBuilder[A])
71+
case i.Stream =>
72+
new IdentityPreservingBuilder[A, i.Stream](i.Stream.newBuilder[A])
73+
case i.Stack =>
74+
new IdentityPreservingBuilder[A, i.Stack](i.Stack.newBuilder[A]): @nowarn("cat=deprecation")
75+
case i.List =>
76+
new IdentityPreservingBuilder[A, i.List](i.List.newBuilder[A])
77+
78+
case c.IndexedSeq | i.IndexedSeq =>
79+
new IdentityPreservingBuilder[A, i.IndexedSeq](i.IndexedSeq.newBuilder[A])
80+
case i.Vector =>
81+
new IdentityPreservingBuilder[A, i.Vector](i.Vector.newBuilder[A])
82+
83+
case c.Set | i.Set =>
84+
new IdentityPreservingBuilder[A, i.Set](i.Set.newBuilder[A])
85+
86+
case i.HashSet =>
87+
new IdentityPreservingBuilder[A, i.HashSet](i.HashSet.newBuilder[A])
88+
case i.ListSet =>
89+
new IdentityPreservingBuilder[A, i.ListSet](i.ListSet.newBuilder[A])
90+
91+
case c.Iterable | i.Iterable =>
92+
new IdentityPreservingBuilder[A, i.Iterable](i.Iterable.newBuilder[A])
93+
94+
case c.Traversable | i.Traversable =>
95+
new IdentityPreservingBuilder[A, i.Traversable](i.Traversable.newBuilder[A])
96+
6797
case _ => fact.newBuilder[A]
6898
}
6999
simpleCBF(builder)
70100
}
71101

72102
implicit def sortedSetCompanionToCBF[A: Ordering,
73103
CC[X] <: c.SortedSet[X] with c.SortedSetLike[X, CC[X]]](
74-
fact: SortedSetFactory[CC]): CanBuildFrom[Any, A, CC[A]] =
75-
simpleCBF(fact.newBuilder[A])
104+
fact: SortedSetFactory[CC]): CanBuildFrom[Any, A, CC[A]] = {
105+
def builder: m.Builder[A, CC[A]] = {
106+
val b = fact match {
107+
case c.SortedSet | i.SortedSet =>
108+
new IdentityPreservingBuilder[A, i.SortedSet](i.SortedSet.newBuilder[A])
109+
case i.TreeSet =>
110+
new IdentityPreservingBuilder[A, i.TreeSet](i.TreeSet.newBuilder[A])
111+
case _ =>
112+
fact.newBuilder[A]
113+
}
114+
// Cast needed because GADT inference doesn't unify CC (didn't dig down why). Example:
115+
// def t: CC[A] = fact match { case i.SortedSet => null: i.SortedSet[A] }
116+
b.asInstanceOf[m.Builder[A, CC[A]]]
117+
}
118+
simpleCBF(builder)
119+
}
76120

77121
implicit def arrayCompanionToCBF[A: ClassTag](fact: Array.type): CanBuildFrom[Any, A, Array[A]] =
78122
simpleCBF(Array.newBuilder[A])
79123

80-
implicit def mapFactoryToCBF[K, V, CC[A, B] <: Map[A, B] with MapLike[A, B, CC[A, B]]](
81-
fact: MapFactory[CC]): CanBuildFrom[Any, (K, V), CC[K, V]] =
82-
simpleCBF(fact.newBuilder[K, V])
124+
// bounds should be `c.` but binary compatibility
125+
implicit def mapFactoryToCBF[K,
126+
V,
127+
CC[A, B] <: /*c.*/ Map[A, B] with /*c.*/ MapLike[A, B, CC[A, B]]](
128+
fact: MapFactory[CC]): CanBuildFrom[Any, (K, V), CC[K, V]] = {
129+
def builder: m.Builder[(K, V), CC[K, V]] = {
130+
val b = fact match {
131+
case c.Map | i.Map =>
132+
new IdentityPreservingMapBuilder[K, V, i.Map](i.Map.newBuilder[K, V])
133+
case i.HashMap =>
134+
new IdentityPreservingMapBuilder[K, V, i.HashMap](i.HashMap.newBuilder[K, V])
135+
case i.ListMap =>
136+
new IdentityPreservingMapBuilder[K, V, i.ListMap](i.ListMap.newBuilder[K, V])
137+
case _ =>
138+
fact.newBuilder[K, V]
139+
}
140+
// Cast needed because GADT inference doesn't unify CC (didn't dig down why). Example:
141+
// def t: CC[K, V] = fact match { case i.Map => null: i.Map[K, V] }
142+
b.asInstanceOf[m.Builder[(K, V), CC[K, V]]]
143+
}
144+
simpleCBF(builder)
145+
}
83146

84147
implicit def sortedMapFactoryToCBF[
85148
K: Ordering,
86149
V,
87150
CC[A, B] <: c.SortedMap[A, B] with c.SortedMapLike[A, B, CC[A, B]]](
88-
fact: SortedMapFactory[CC]): CanBuildFrom[Any, (K, V), CC[K, V]] =
89-
simpleCBF(fact.newBuilder[K, V])
151+
fact: SortedMapFactory[CC]): CanBuildFrom[Any, (K, V), CC[K, V]] = {
152+
def builder: m.Builder[(K, V), CC[K, V]] = {
153+
val b = fact match {
154+
case c.SortedMap | i.SortedMap =>
155+
new IdentityPreservingMapBuilder[K, V, i.SortedMap](i.SortedMap.newBuilder[K, V])
156+
case i.TreeMap =>
157+
new IdentityPreservingMapBuilder[K, V, i.TreeMap](i.TreeMap.newBuilder[K, V])
158+
case _ =>
159+
fact.newBuilder[K, V]
160+
}
161+
b.asInstanceOf[m.Builder[(K, V), CC[K, V]]]
162+
}
163+
simpleCBF(builder)
164+
}
90165

91-
implicit def bitSetFactoryToCBF(fact: BitSetFactory[BitSet]): CanBuildFrom[Any, Int, BitSet] =
92-
simpleCBF(fact.newBuilder)
166+
implicit def bitSetFactoryToCBF(
167+
fact: BitSetFactory[c.BitSet]): CanBuildFrom[Any, Int, c.BitSet] = {
168+
def builder: m.Builder[Int, c.BitSet] = fact match {
169+
case c.BitSet =>
170+
new IdentityPreservingBitSetBuilder[i.BitSet](i.BitSet.newBuilder)
171+
case _ =>
172+
fact.newBuilder
173+
}
174+
simpleCBF(builder)
175+
}
93176

94177
implicit def immutableBitSetFactoryToCBF(
95-
fact: BitSetFactory[i.BitSet]): CanBuildFrom[Any, Int, ImmutableBitSetCC[Int]] =
96-
simpleCBF(fact.newBuilder)
178+
fact: BitSetFactory[i.BitSet]): CanBuildFrom[Any, Int, ImmutableBitSetCC[Int]] = {
179+
def builder: m.Builder[Int, i.BitSet] = fact match {
180+
case i.BitSet =>
181+
new IdentityPreservingBitSetBuilder[i.BitSet](i.BitSet.newBuilder)
182+
case _ =>
183+
fact.newBuilder
184+
}
185+
simpleCBF(builder)
186+
}
97187

98188
implicit def mutableBitSetFactoryToCBF(
99189
fact: BitSetFactory[m.BitSet]): CanBuildFrom[Any, Int, MutableBitSetCC[Int]] =

0 commit comments

Comments
 (0)