This repository contains a scalafix rule to generate a Visitor
trait for ADTs. When it finds a sealed trait or class,
it will:
- Define an abstract
def visit
declaration to the trait/class - Define a
trait Visitor[A]
in the companion object with one method per ADT member - Define
def visit
on each ADT member object defined in the companion object, proxying to the correct method inVisitor
ADT members defined in nested objects within the companion object will have their object paths reflected in the name of
the corresponding visit*
method in the Visitor
.
For example, this ADT definition:
sealed trait Test
object Test {
case object Foo extends Test
case object Bar extends Test
object nested {
case object Baz extends Test
}
}
is transformed into this:
sealed trait Test {
def visit[A](v: Test.Visitor[A]): A
}
object Test {
case object Foo extends Test { def visit[A](v: Test.Visitor[A]): A = v.visitFoo }
case object Bar extends Test { def visit[A](v: Test.Visitor[A]): A = v.visitBar }
object nested {
case object Baz extends Test { def visit[A](v: Test.Visitor[A]): A = v.visitNestedBaz }
}
trait Visitor[A] {
def visitFoo: A
def visitBar: A
def visitNestedBaz: A
}
}
- Formatting of source files is not preserved, so this rule is best combined with scalafmt
- The rule only discovers and modifies ADT members defined in the sealed trait/class' companion object
sealed trait Foo object Foo { case object Bar extends Foo // discovered } case object Baz extends Foo // not discovered
- The rule only recurses into nested objects within the companion object, not other classes or traits
sealed trait Foo object Foo { object NestedObject { case object Bar extends Foo // discovered } class NestedClass { case object Baz extends Foo // not discovered } }
- The rule will not modify or remove unrelated statements defined in the sealed trait/class, the companion object, any of the nested objects, or in any of the ADT members
sealed trait Foo { def other(): Unit // preserved } object Foo { def dontTouchMe(): Unit // preserved case object Bar extends Foo { def other(): Unit = () // preserved } }
- The rule will overwrite any code that conflicts with the naming/type conventions it sets, namely:
- If
def visit
is already declared in the trait/class but is defined with a different type or parameter name - If
def visit
is already defined in an ADT member but is defined with a different type, parameter name, or with a call to a method on theVisitor
not following the set naming convention - If
trait Visitor[A]
is defined in any way differently than having one abstractdef
per ADT member following the set naming convention
- If