Scala宏:基于Config生成类方法

时间:2018-07-11 05:10:03

标签: scala scala-macros

有没有一种方法可以在编译时基于TypesafeConfig对象在特定的类/特征/对象中生成方法?

例如,我有这个:

object Main {
  val config: Config = ConfigFactory.parseString(
    """
      |object {
      |  name = "go"
      |}
    """.stripMargin)

  generate(config)
}

预期结果是:

object Main {
  val config: Config = ConfigFactory.parseString(
    """
      |object {
      |  name = "go"
      |}
    """.stripMargin)

  def method: Unit = {
    print("go") /* the string comes from the config above */
  }
}

这个想法是要能够在宏实现的范围内实例化Config对象并使用其属性来生成代码,例如(非常感谢Dmytro Minin的示例):

object GenerateMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    confObj = ... /* somehow get the real object based on macro input */        

    annottees match {
      case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
        q"""$mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
           ..$body

           def method: Unit = {
             print(s"${confObj.getString("object.name")}") /* use confObj's property to "embed" the value into the method generated */
           }
        }"""
    }
  }
}

1 个答案:

答案 0 :(得分:0)

您可以创建宏注释:

macros / scr / main / scala / generate.scala

import com.typesafe.config.Config
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

@compileTimeOnly("enable macro paradise to expand macro annotations")
class generate extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro GenerateMacro.impl
}

object GenerateMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._


    val confObj: Config = c.prefix.tree match {
      case q"new generate($config)" => c.eval(c.Expr[Config](/*c.untypecheck(*/config/*)*/))
    }

    println(confObj) //Config(SimpleConfigObject({"object":{"name":"go"}}))

    annottees match {
      case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil =>
        q"""$mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
         ..$body

         def method: Unit = {
           print(${confObj.getString("object.name")})
         }
      }"""
    }
  }
}

并使用它:

core / scr / main / scala / Main.scala

@generate(com.typesafe.config.ConfigFactory.parseString(
  """
    |object {
    |  name = "go"
    |}
  """.stripMargin))
object Main

那你就可以做

Main.method //go

build.sbt

scalaVersion := "2.12.6"

lazy val commonSettings = Seq(
  addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)
)

lazy val macros: Project = (project in file("macros")).settings(
  commonSettings,
  libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
)

lazy val core: Project = (project in file("core")).aggregate(macros).dependsOn(macros).settings(
  commonSettings,
  libraryDependencies += "com.typesafe" % "config" % "1.3.2"
)