0
# Migration Tools
1
2
This document covers the Scalafix-based migration tools for automated code transformation between collection API versions.
3
4
## Overview
5
6
The scala-collection-compat library provides two main Scalafix rules for migrating code to use Scala 2.13 collection APIs:
7
8
1. **`Collection213CrossCompat`**: For libraries that need to cross-compile across Scala versions
9
2. **`Collection213Upgrade`**: For applications that can migrate directly to Scala 2.13
10
11
## Collection213CrossCompat
12
13
### Rule Definition
14
15
```scala { .api }
16
class Collection213CrossCompat extends LegacySemanticRule("Collection213CrossCompat", index => new Collection213CrossCompatV0(index))
17
with Stable212BaseCheck {
18
19
override def description: String =
20
"Upgrade to 2.13's collections with cross compatibility for 2.11 and 2.12 (for libraries)"
21
}
22
23
case class Collection213CrossCompatV0(index: SemanticdbIndex)
24
extends SemanticRule(index, "CrossCompat")
25
with CrossCompatibility
26
with Stable212Base {
27
28
def isCrossCompatible: Boolean = true
29
}
30
```
31
32
This rule transforms code to use 2.13 collection APIs while maintaining compatibility with Scala 2.11 and 2.12 by introducing dependencies on the scala-collection-compat library.
33
34
### Usage
35
36
Add to your `build.sbt`:
37
38
```scala
39
scalafixDependencies in ThisBuild += "org.scala-lang.modules" %% "scala-collection-migrations" % "1.0.0"
40
libraryDependencies += "org.scala-lang.modules" %% "scala-collection-compat" % "1.0.0"
41
scalacOptions ++= List("-Yrangepos", "-P:semanticdb:synthetics:on")
42
```
43
44
Run the migration:
45
46
```bash
47
sbt> ;test:scalafix Collection213CrossCompat ;scalafix Collection213CrossCompat
48
```
49
50
### Transformations Applied
51
52
The CrossCompat rule applies transformations that maintain compatibility:
53
54
- Adds `import scala.collection.compat._` imports
55
- Transforms `to[Collection]` syntax to `to(Collection)`
56
- Updates factory method calls to use compat extensions
57
- Preserves existing `CanBuildFrom` usage where possible
58
59
## Collection213Upgrade
60
61
### Rule Definition
62
63
```scala { .api }
64
class Collection213Upgrade extends LegacySemanticRule("Collection213Upgrade", index => new Collection213UpgradeV0(index))
65
with Stable212BaseCheck {
66
67
override def description: String =
68
"Upgrade to 2.13's collections (for applications)"
69
}
70
71
case class Collection213UpgradeV0(index: SemanticdbIndex)
72
extends SemanticRule(index, "Upgrade213")
73
with Stable212Base {
74
75
def isCrossCompatible: Boolean = false
76
}
77
```
78
79
This rule transforms code directly to Scala 2.13 APIs without maintaining backward compatibility.
80
81
### Usage
82
83
Add to your `build.sbt`:
84
85
```scala
86
scalafixDependencies in ThisBuild += "org.scala-lang.modules" %% "scala-collection-migrations" % "1.0.0"
87
scalacOptions ++= List("-Yrangepos", "-P:semanticdb:synthetics:on")
88
```
89
90
Run the migration:
91
92
```bash
93
sbt> ;test:scalafix Collection213Upgrade ;scalafix Collection213Upgrade
94
```
95
96
## Specific Transformations
97
98
### Symbol Replacements
99
100
```scala { .api }
101
// In Collection213Upgrade only
102
def replaceSymbols(ctx: RuleCtx): Patch = {
103
ctx.replaceSymbols(
104
"scala.TraversableOnce" -> "scala.IterableOnce",
105
"scala.collection.TraversableOnce" -> "scala.collection.IterableOnce"
106
)
107
}
108
```
109
110
**Before:**
111
```scala
112
import scala.collection.TraversableOnce
113
114
def process(data: TraversableOnce[Int]): List[Int] = data.toList
115
```
116
117
**After:**
118
```scala
119
import scala.collection.IterableOnce
120
121
def process(data: IterableOnce[Int]): List[Int] = data.toList
122
```
123
124
### Mutable Collection Method Updates
125
126
```scala { .api }
127
// Mutable Set retain -> filterInPlace
128
def replaceMutableSet(ctx: RuleCtx): Patch = {
129
ctx.tree.collect {
130
case retainSet(n: Name) =>
131
ctx.replaceTree(n, "filterInPlace")
132
}.asPatch
133
}
134
135
// Mutable Map retain -> filterInPlace with syntax transformation
136
def replaceMutableMap(ctx: RuleCtx): Patch = {
137
ctx.tree.collect {
138
case Term.Apply(Term.Select(_, retainMap(n: Name)), List(_: Term.PartialFunction)) =>
139
ctx.replaceTree(n, "filterInPlace")
140
141
case Term.Apply(Term.Select(_, retainMap(n: Name)), List(_: Term.Function)) =>
142
// Transform function to partial function syntax
143
ctx.replaceToken(open, "{case ") +
144
ctx.replaceToken(close, "}") +
145
ctx.replaceTree(n, "filterInPlace")
146
}.asPatch
147
}
148
```
149
150
**Before:**
151
```scala
152
val set = mutable.Set(1, 2, 3, 4, 5)
153
set.retain(_ > 3)
154
155
val map = mutable.Map("a" -> 1, "b" -> 2, "c" -> 3)
156
map.retain((k, v) => v > 1)
157
```
158
159
**After:**
160
```scala
161
val set = mutable.Set(1, 2, 3, 4, 5)
162
set.filterInPlace(_ > 3)
163
164
val map = mutable.Map("a" -> 1, "b" -> 2, "c" -> 3)
165
map.filterInPlace{case (k, v) => v > 1}
166
```
167
168
### Tuple Zipped Transformation
169
170
```scala { .api }
171
def replaceTupleZipped(ctx: RuleCtx): Patch = {
172
ctx.tree.collect {
173
case tupleZipped(Term.Select(Term.Tuple(args), name)) =>
174
// Transform (a, b).zipped to a.lazyZip(b)
175
val replaceCommasPatch = commas match {
176
case head :: tail =>
177
ctx.replaceToken(head, ".lazyZip(") ++
178
tail.map(comma => ctx.replaceToken(comma, ").lazyZip("))
179
case _ => Patch.empty
180
}
181
removeTokensPatch + replaceCommasPatch
182
}.asPatch
183
}
184
```
185
186
**Before:**
187
```scala
188
val list1 = List(1, 2, 3)
189
val list2 = List("a", "b", "c")
190
val list3 = List(true, false, true)
191
192
val zipped = (list1, list2, list3).zipped.map((i, s, b) => s"$i:$s:$b")
193
```
194
195
**After:**
196
```scala
197
val list1 = List(1, 2, 3)
198
val list2 = List("a", "b", "c")
199
val list3 = List(true, false, true)
200
201
val zipped = list1.lazyZip(list2).lazyZip(list3).map((i, s, b) => s"$i:$s:$b")
202
```
203
204
## Base Transformation Infrastructure
205
206
### Stable212Base
207
208
```scala { .api }
209
trait Stable212Base extends SemanticRule {
210
// Provides common transformations shared between both rules
211
// Handles CanBuildFrom patterns, breakOut transformations, etc.
212
}
213
```
214
215
### CrossCompatibility
216
217
```scala { .api }
218
trait CrossCompatibility {
219
// Provides logic for maintaining cross-version compatibility
220
// Determines when to add compat imports vs direct transformations
221
}
222
```
223
224
## Migration Examples
225
226
### Library Migration (CrossCompat)
227
228
**Original Code:**
229
```scala
230
import scala.collection.breakOut
231
232
class MyLibrary {
233
def processData[T](items: List[T]): Vector[String] = {
234
items.map(_.toString)(breakOut)
235
}
236
237
def convertToSet[T](items: List[T]): Set[T] = {
238
items.to[Set]
239
}
240
}
241
```
242
243
**After CrossCompat Migration:**
244
```scala
245
import scala.collection.compat._
246
247
class MyLibrary {
248
def processData[T](items: List[T]): Vector[String] = {
249
items.map(_.toString).to(Vector)
250
}
251
252
def convertToSet[T](items: List[T]): Set[T] = {
253
items.to(Set)
254
}
255
}
256
```
257
258
### Application Migration (Upgrade)
259
260
**Original Code:**
261
```scala
262
import scala.collection.TraversableOnce
263
264
class DataProcessor {
265
def aggregate(data: TraversableOnce[Int]): Int = data.sum
266
267
val mutableData = scala.collection.mutable.Set(1, 2, 3)
268
mutableData.retain(_ > 1)
269
270
val tuples = (List(1, 2), List("a", "b")).zipped.toList
271
}
272
```
273
274
**After Upgrade Migration:**
275
```scala
276
import scala.collection.IterableOnce
277
278
class DataProcessor {
279
def aggregate(data: IterableOnce[Int]): Int = data.sum
280
281
val mutableData = scala.collection.mutable.Set(1, 2, 3)
282
mutableData.filterInPlace(_ > 1)
283
284
val tuples = List(1, 2).lazyZip(List("a", "b")).toList
285
}
286
```
287
288
## Setup and Configuration
289
290
### SBT Configuration
291
292
For cross-compatible migrations:
293
```scala
294
// build.sbt
295
scalafixDependencies in ThisBuild += "org.scala-lang.modules" %% "scala-collection-migrations" % "1.0.0"
296
libraryDependencies += "org.scala-lang.modules" %% "scala-collection-compat" % "1.0.0"
297
scalacOptions ++= List("-Yrangepos", "-P:semanticdb:synthetics:on")
298
```
299
300
For direct upgrades:
301
```scala
302
// build.sbt
303
scalafixDependencies in ThisBuild += "org.scala-lang.modules" %% "scala-collection-migrations" % "1.0.0"
304
scalacOptions ++= List("-Yrangepos", "-P:semanticdb:synthetics:on")
305
```
306
307
### Build.scala Configuration
308
309
If using `project/Build.scala`:
310
```scala
311
import scalafix.sbt.ScalafixPlugin.autoImport.{scalafixDependencies, scalafixSemanticdb}
312
```
313
314
## Running Migrations
315
316
### Command Line Usage
317
318
```bash
319
# Cross-compatible migration
320
sbt> ;test:scalafix Collection213CrossCompat ;scalafix Collection213CrossCompat
321
322
# Direct upgrade migration
323
sbt> ;test:scalafix Collection213Upgrade ;scalafix Collection213Upgrade
324
325
# Run on specific files
326
sbt> scalafix Collection213CrossCompat src/main/scala/MyFile.scala
327
328
# Check what would be changed without applying
329
sbt> scalafix --check Collection213CrossCompat
330
```
331
332
### Incremental Migration
333
334
```bash
335
# Migrate test code first to verify transformations
336
sbt> test:scalafix Collection213CrossCompat
337
338
# Then migrate main code
339
sbt> scalafix Collection213CrossCompat
340
341
# Verify compilation
342
sbt> compile test
343
```
344
345
## Migration Strategy
346
347
### For Libraries (use Collection213CrossCompat)
348
349
1. Add scala-collection-compat dependency
350
2. Run CrossCompat rule on test code first
351
3. Verify tests still pass on all Scala versions
352
4. Run CrossCompat rule on main code
353
5. Publish cross-compiled artifacts
354
355
### For Applications (use Collection213Upgrade)
356
357
1. Ensure you're ready to move to Scala 2.13 only
358
2. Run Upgrade rule on test code first
359
3. Verify tests pass on Scala 2.13
360
4. Run Upgrade rule on main code
361
5. Migrate build to Scala 2.13
362
363
The migration tools provide automated transformation of collection-related code, significantly reducing the manual effort required to adopt Scala 2.13 collection APIs while maintaining appropriate compatibility constraints for your project type.