You can create macro annotation (generating companion object, failing if it already exists)
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
@compileTimeOnly("enable macro paradise to expand macro annotations")
class GenerateCompanion extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro GenerateCompanion.impl
}
object GenerateCompanion {
def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
annottees match {
case (c@q"$_ class $tpname[..$_] $_(...$paramss) extends { ..$_ } with ..$_ { $_ => ..$_ }") :: Nil =>
val vals = paramss.flatten.map(p => {
val name = p.name.toString
q"val ${TermName(underscoreToCamel(name))}: String = $name"
})
q"""
$c
object ${tpname.toTermName} {..$vals}
"""
}
}
def underscoreToCamel(name: String): String = "_([a-z\\d])".r.replaceAllIn(name, _.group(1).toUpperCase)
}
and use it
@GenerateCompanion
class Pizza(val crust_type: String)
Pizza.crustType //crust_type
New macro (modifying companion object if it exists or generating it if it doesn't):
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
@compileTimeOnly("enable macro paradise to expand macro annotations")
class GenerateCompanion extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro GenerateCompanion.impl
}
object GenerateCompanion {
def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
def vals(paramss: Seq[Seq[ValDef]]): Seq[ValDef] =
paramss.flatten.map(p => {
val name = p.name.toString
q"val ${TermName(underscoreToCamel(name))}: String = $name"
})
annottees match {
case (c@q"$_ class $tpname[..$_] $_(...$paramss) extends { ..$_ } with ..$_ { $_ => ..$_ }") :: Nil =>
q"""
$c
object ${tpname.toTermName} {
..${vals(paramss)}
}
"""
case (c@q"$_ class $tpname[..$_] $_(...$paramss) extends { ..$_ } with ..$_ { $_ => ..$_ }") ::
q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
q"""
$c
$mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
..$body
..${vals(paramss)}
}
"""
}
}
def underscoreToCamel(name: String): String = "_([a-z\\d])".r.replaceAllIn(name, _.group(1).toUpperCase)
}
Usage:
@GenerateCompanion
class Pizza(val crust_type: String, val foo_foo: Int)
object Pizza {
def bar: String = "bar"
}
Pizza.crustType //crust_type
Pizza.fooFoo //foo_foo
Pizza.bar //bar