or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

arrayseq.mdbuildfrom.mdcollection-extensions.mdfactory-operations.mdindex.mdmigration-tools.md

migration-tools.mddocs/

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.