ohze / scala-rewrites

Scalafix Rewrites for Scala

Version Matrix

Scalafix Rewrites to migrate scala 2 -> 3 (dotty) | latest scala 2.13.x

CI

What?

This repo contains the following scalafix rules:

fix.scala213.DottyMigrate

Combine and run all following rules when traveling the scalameta trees represent scala files once

rules = [
  ProcedureSyntax # built-in scalafix rule
  fix.scala213.ConstructorProcedureSyntax
  fix.scala213.ParensAroundLambda
  fix.scala213.NullaryOverride
  fix.explicittypes.ExplicitImplicitTypes
  fix.scala213.MultiArgInfix
  fix.scala213.Any2StringAdd
  fix.scala213.ExplicitNonNullaryApply
  fix.scala213.ExplicitNullaryEtaExpansion
]

This rule is successfully used to migrate akka project to scala 2.13.3/ scala 3 (dotty)

fix.scala213.ConstructorProcedureSyntax

Remove constructor procedure syntax: def this(..) {..} => def this(..) = {..}

This rule complement to the built-in ProcedureSyntax rule.

fix.scala213.ParensAroundLambda

Fix: parentheses are required around the parameter of a lambda

Seq(1).map { i: Int => // rewrite to: Seq(1).map { (i: Int) =>
 i + 1
}
Seq(1).map { i => i + 1 } // keep

fix.scala213.NullaryOverride

Consistent nullary overriding

trait A {
  def i: Int
  def u(): Unit
}
trait B extends A {
  def i() = 1 // fix by remove `()`: def i = 1
  def u = println("hi") // fix by add `()`: def u() = println("hi")
}

fix.explicittypes.ExplicitImplicitTypes

Explicitly add type to implicit def/val/vars (required by dotty)

trait Foo {
  // rewrite implicit members of class/trait
  // rewrite to `implicit val s: Seq[String] = Nil`
  implicit val s = Seq.empty[String]
  // rewrite to `implicit def i: Int = 1`
  implicit def i = 1

  def f() = {
    class C {
      // Also rewrite implicit local `def/val/var`s that its parent is a trait/class (required by dotty)
      // rewrite to `implicit val i: Int = 1`
      implicit val i = 1
    }
    // local implicits like this don't need to be rewritten
    implicit val s = ""
    ???
  }
}

Optional. Using symbolReplacements with this config in .scalafix.conf:

ExplicitImplicitTypes.symbolReplacements {
  "scala/concurrent/ExecutionContextExecutor#" = "scala/concurrent/ExecutionContext#"
}

Then:

import scala.concurrent.ExecutionContextExecutor

trait T {
  def someEc(): ExecutionContextExecutor
}
trait A {
  def t: T
  // rewrite to `implicit def ec: ExecutionContext = t.someEc()`
  implicit def ec = t.someEc()
}

fix.scala213.MultiArgInfix

trait PipeToSupport {
    def to(recipient: Int, sender: Int): Unit
}
def p: PipeToSupport = ???
// rewrite to `p.to(1, 2)
p to (1, 2)

fix.scala213.FinalObject

Remove redundant final modifier for objects:

final object Abc

fix.scala213.Any2StringAdd

Nil + "foo" // => String.valueOf(Nil) + "foo"

type A
def x: A = ???
x + "foo" // => String.valueOf(x) + "foo"

1 + "foo" // => "" + 1 + "foo"

fix.scala213.ExplicitNonNullaryApply

// Given:
def meth() = ???
def meth2()(implicit s: String) = ???
object meth3 {
  def apply[A]()(implicit a: A) = ???
}
def prop(implicit s: String) = ???

// Then:
meth // rewrite to `meth()`
meth2 // rewrite to `meth2()`
meth3[Int] // rewrite to: meth3[Int]()
prop // keep

fix.scala213.ExplicitNullaryEtaExpansion

def prop         = ""
def meth()       = ""
def void(x: Any) = ""

def def_prop = prop _ // rewrite to: def def_prop = () => prop
def def_meth = meth _ // leave
def def_idty = void _ // leave

def lzy(f: => Int) = {
  val k = f _ // rewrite to: val k = () => f
  ???
}

Other rules to migrate scala 2.12 -> 2.13

  • fix.scala213.Core
  • fix.scala213.NullaryHashHash
  • fix.scala213.ScalaSeq
  • fix.scala213.Varargs

How

Migrate sbt projects to latest scala 2.13 or to dotty

See also this commit message

  1. Optional. Add to .jvmopts
-Xss8m
-Xms1G
-Xmx8G
  1. Add the sbt-scalafix sbt plugin, with the SemanticDB compiler plugin enabled (official docs):
// project/plugins.sbt
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.31")
// build.sbt
inThisBuild(List(
  semanticdbEnabled := true,
  semanticdbOptions += "-P:semanticdb:synthetics:on", // make sure to add this
  semanticdbVersion := scalafixSemanticdb.revision,
  scalafixScalaBinaryVersion := CrossVersion.binaryScalaVersion(scalaVersion.value),
))
  1. Run fix.scala213.DottyMigrate rule (official docs), in sbt: Replace <version> with an actual value, eg 0.1.6-sd. See releases
> scalafixAll dependency:fix.scala213.DottyMigrate@com.sandinh:scala-rewrites:<version>

You can also add the following to your build.sbt:

ThisBuild / scalafixDependencies += "com.sandinh" %% "scala-rewrites" % "<version>"

and then:

> scalafixAll fix.scala213.DottyMigrate
  1. Run fix.scala213.NullaryOverride rule

(bash)

echo 'rules = [ fix.scala213.NullaryOverride ]' > .NullaryOverride.conf
echo 'NullaryOverride.mode = Rewrite' >> .NullaryOverride.conf

(sbt)

> set scalafixConfig := Some(file(".NullaryOverride.conf"))
> scalafixAll
  1. Optional. Run other rules
> scalafixAll fix.scala213.FinalObject
> // similar for other rules

To develop/contribute to any of the rewrites

sbt ~tests/test
# edit rewrites/src/main/scala/...