Skip to content

Instantly share code, notes, and snippets.

Created May 26, 2016 06:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/df7b4c091752a1e4599656ecef10c222 to your computer and use it in GitHub Desktop.
Save anonymous/df7b4c091752a1e4599656ecef10c222 to your computer and use it in GitHub Desktop.
the description for this gist
/** annotation macro extends the companion object with a def macro, this way other def macros see the implicits (e.g. Json.format[A])*/
object addImplicitConversions {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
def jsonFormatter(className: TypeName, simpleType: TypeName) = {
q"implicit val jsonValueFormat: play.api.libs.json.Format[$className] = extensions.ImplicitDefs.jsonFormat[$className, $simpleType]"
}
def pathBindable(className: TypeName, simpleType: TypeName) = {
q"implicit def bindable: play.api.mvc.PathBindable[$className] = extensions.ImplicitDefs.pathBindable[$className, $simpleType]"
}
def extendCompanionObject(
obj: TermName,
bases: Seq[Tree],
body: Seq[Tree],
format: Tree,
bindable: Tree) = {
q"""
object $obj extends ..$bases {
..$body
$format
$bindable
}
"""
}
def createCompanionObject(className: TypeName, format: Tree, bindable: Tree) = {
q"""
object ${className.toTermName} {
$format
$bindable
}
"""
}
def addImplicits(valueCaseClassDeclaration: ClassDef, companionModuleOpt: Option[ModuleDef] = None) = {
def extractValueFieldType(classDecl: ClassDef) = classDecl match {
case q"""
case class $_
(value: ${Ident(valueFieldType)})
extends $_ { ..$_ }
"""=> valueFieldType.asInstanceOf[TypeName]
case _ => c.abort(c.enclosingPosition, "Case class has to have a 'value' field")
}
def addImplicits(
companionModuleOpt: Option[ModuleDef],
implicitJsonFormatMacro: Tree,
implicitPathBindableMacro: Tree,
className: TypeName) = {
companionModuleOpt map { compDecl =>
val q"object $obj extends ..$bases { ..$body }" = compDecl
//explicit type casts to make compiler happy as it does not pick up reflected types.
extendCompanionObject(
obj.asInstanceOf[TermName],
bases.asInstanceOf[Seq[Tree]],
body.asInstanceOf[Seq[Tree]],
implicitJsonFormatMacro,
implicitPathBindableMacro)
} getOrElse {
createCompanionObject(className, implicitJsonFormatMacro, implicitPathBindableMacro)
}
}
val fieldType = extractValueFieldType(valueCaseClassDeclaration)
val implicitJsonFormatMacro = jsonFormatter(valueCaseClassDeclaration.name, fieldType)
val implicitPathBindableMacro = pathBindable(valueCaseClassDeclaration.name, fieldType)
val companionDeclaration = addImplicits(
companionModuleOpt,
implicitJsonFormatMacro,
implicitPathBindableMacro,
valueCaseClassDeclaration.name)
c.Expr(
q"""
$valueCaseClassDeclaration
$companionDeclaration
""")
}
annottees.map(_.tree) match {
case (classDeclaration: ClassDef) :: Nil => addImplicits(classDeclaration)
case (classDeclaration: ClassDef) :: (companionModule: ModuleDef) :: Nil => addImplicits(classDeclaration, Some(companionModule))
case _ => c.abort(c.enclosingPosition, "Annotation should be used on a case class")
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment