easy way to replace term name in ast - scala

I have the following code for:
#compileTimeOnly("enable macro paradise to expand macro annotations")
class replace extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro replaceImpl.replace
}
object replaceImpl {
def replace(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
// ?
c.Expr[Any](q"")
}
}
Via this code, i want to replace variable name (x) in next example of usage:
#replace
def foo(x: Int) = x + 1
it's simple method, but if i have a big method with many expression, what are the simpest way to replace variable name (from x to y for example)?

After some investigation, i need use Transfomer#transform method, some like this:
val t = new Transformer {
override def transform(tree: Tree) = {
val nt = tree match {
case Ident(x) => Ident(someNewName)
case x => x
}
super.transform(newTree)
}
}
// and then
val result = t.transform(inputTree)

Related

Get fully qualified method name in scala macros

I use Scala macros and match Apply and I would like to get fully qualified name of the function which is called.
Examples:
println("") -> scala.Predef.println
scala.Predef.println("") -> scala.Predef.println
class Abc {
def met(): Unit = ???
}
case class X {
def met(): Unit = ???
def abc(): Abc = ???
}
val a = new Abc()
val x = new Abc()
a.met() -> Abc.met
new Abc().met() -> Abc.met
X() -> X.apply
X().met() -> X.met
x.met() -> X.met
x.abc.met() -> Abc.met
On the left side is what I have in code and on the right side after arrow is what I want to get. Is it possible? And how?
Here is the macro:
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
object ExampleMacro {
final val useFullyQualifiedName = false
def methodName(param: Any): String = macro debugParameters_Impl
def debugParameters_Impl(c: blackbox.Context)(param: c.Expr[Any]): c.Expr[String] = {
import c.universe._
param.tree match {
case Apply(Select(t, TermName(methodName)), _) =>
val baseClass = t.tpe.resultType.baseClasses.head // there may be a better way than this line
val className = if (useFullyQualifiedName) baseClass.fullName else baseClass.name
c.Expr[String](Literal(Constant(className + "." + methodName)))
case _ => sys.error("Not a method call: " + show(param.tree))
}
}
}
Usage of the macro:
object Main {
def main(args: Array[String]): Unit = {
class Abc {
def met(): Unit = ???
}
case class X() {
def met(): Unit = ???
def abc(): Abc = ???
}
val a = new Abc()
val x = X()
import sk.ygor.stackoverflow.q53326545.macros.ExampleMacro.methodName
println(methodName(Main.main(Array("foo", "bar"))))
println(methodName(a.met()))
println(methodName(new Abc().met()))
println(methodName(X()))
println(methodName(X().met()))
println(methodName(x.met()))
println(methodName(x.abc().met()))
println(methodName("a".getClass))
}
}
Source code for this example contains following:
it is a multi module SBT project, because macros have to be in a separate compilation unit than classes, which use the macro
macro modules depends explicitly on libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value,

How to prevent typecheck in StaticAnnotation?

I am creating some macro libraries that reads some information from annotation on the enclosing method.
#info(foo(bar, baz))
def enclosing() = {
myMacro()
}
These information are encoded as foo(bar, baz) in a StaticAnnotation #info.
foo(bar, baz) contains information myMacro need, however, foo(bar, baz) is not able to type-check at the position #info, and cause compiler error when type-checking foo(bar, baz).
I wonder if I can create a macro dontTypecheck that prevent foo(bar, baz) being type checked. So that I can create something like:
#info(dontTypecheck {
foo(bar, baz)
})
def enclosing() = {
myMacro()
}
The dontTypecheck macro should produce a Tree that contains untype-checked foo(bar, baz).
How to create the dontTypecheck macro?
one idea is use another annotation save info
class Info[T](t: T) extends scala.annotation.StaticAnnotation {
}
class AnnInfo extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AnnInfImpl.impl
}
trait AnnotationUtils {
val c: scala.reflect.macros.blackbox.Context
import c.universe._
final def getAnnotation(x: MemberDef) = x.mods.annotations
}
class AnnInfImpl(val c: blackbox.Context) extends AnnotationUtils {
import c.universe._
// edit 1
def impl(annottees: Tree*): Tree = {
annottees.head match {
case x: DefDef =>
// collect value from `#Info(value)`
val info: List[Tree] = getAnnotation(x).collect { case q"new $name ($value)" => value }
val newBody =
q"""
{
val info = ${info.map(e => show(e))}
println(info)// just print it
${x.rhs}
}"""
DefDef(
mods = Modifiers(), //dropMods
name = x.name,
tparams = x.tparams,
vparamss = x.vparamss,
tpt = x.tpt,
rhs = newBody
)
}
}
}
// test
class AnnInfoTest {
val a = 1
val b = 2
def f(a: Int, b: Int) = a + b
#Info(f(a, b))
#AnnInfo
def e = ???
}
if you call e will print List(f(a, b))

scala proxy macro, issue converting method args to values

I am trying to write a proxy macro using scala macros. I want to be able to proxy a trait X and return instances of X that invoke a function for all methods of X.
Here is what I did so far. Say we want to proxy the trait TheTrait (which is defined below), we can run ProxyMacro.proxy passing a function that will be called for all invocations of the proxy methods.
trait TheTrait
{
def myMethod(x: String)(y: Int): String
}
val proxy = ProxyMacro.proxy[TheTrait] {
case ("myMethod", args) =>
"ok"
}
println(proxy.myMethod("hello")(5))
The implementation so far is this:
package macrotests
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
object ProxyMacro
{
type Implementor = (String, Any) => Any
def proxy[T](implementor: Implementor): T = macro impl[T]
def impl[T: c.WeakTypeTag](c: Context)(implementor: c.Expr[Implementor]): c.Expr[T] = {
import c.universe._
val tpe = weakTypeOf[T]
val decls = tpe.decls.map { decl =>
val termName = decl.name.toTermName
val method = decl.asMethod
val params = method.paramLists.map(_.map(s => internal.valDef(s)))
val paramVars = method.paramLists.flatMap(_.map { s =>
internal.captureVariable(s)
internal.referenceCapturedVariable(s)
})
q""" def $termName (...$params) = {
$implementor (${termName.toString}, List(..${paramVars}) ).asInstanceOf[${method.returnType}]
}"""
}
c.Expr[T] {
q"""
new $tpe {
..$decls
}
"""
}
}
}
But there is a problem. This doesn't compile due to List(..${paramVars}). This should just create a list with all the values of the method arguments.
But I get a compilation issue (not worth pasting it) on that line.
How can I convert the list of method arguments to their values?
showInfo is useful when you debug macro
def showInfo(s: String) =
c.info(c.enclosingPosition, s.split("\n").mkString("\n |---macro info---\n |", "\n |", ""), true)
change
val paramVars = method.paramLists.flatMap(_.map { s =>
internal.captureVariable(s)
internal.referenceCapturedVariable(s)
})(this result is List(x0$1, x1$1))
to
val paramVars = method.paramLists.flatMap(_.map { s =>
s.name
})(this result is List(x, y))

Scala macro and type erasure

I'm having some problems with a macro I've written to help me log metrics represented as case class instances to to InfluxDB. I presume I'm having a type erasure problem and that the tyep parameter T is getting lost, but I'm not entirely sure what's going on. (This is also my first exposure to Scala macros.)
import scala.language.experimental.macros
import play.api.libs.json.{JsNumber, JsString, JsObject, JsArray}
abstract class Metric[T] {
def series: String
def jsFields: JsArray = macro MetricsMacros.jsFields[T]
def jsValues: JsArray = macro MetricsMacros.jsValues[T]
}
object Metrics {
case class LoggedMetric(timestamp: Long, series: String, fields: JsArray, values: JsArray)
case object Kick
def log[T](metric: Metric[T]): Unit = {
println(LoggedMetric(
System.currentTimeMillis,
metric.series,
metric.jsFields,
metric.jsValues
))
}
}
And here's an example metric case class:
case class SessionCountMetric(a: Int, b: String) extends Metric[SessionCountMetric] {
val series = "sessioncount"
}
Here's what happens when I try to log it:
scala> val m = SessionCountMetric(1, "a")
m: com.confabulous.deva.SessionCountMetric = SessionCountMetric(1,a)
scala> Metrics.log(m)
LoggedMetric(1411450638296,sessioncount,[],[])
Even though the macro itself seems to work fine:
scala> m.jsFields
res1: play.api.libs.json.JsArray = ["a","b"]
scala> m.jsValues
res2: play.api.libs.json.JsArray = [1,"a"]
Here's the actual macro itself:
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
object MetricsMacros {
private def fieldNames[T: c.WeakTypeTag](c: Context)= {
val tpe = c.weakTypeOf[T]
tpe.decls.collect {
case field if field.isMethod && field.asMethod.isCaseAccessor => field.asTerm.name
}
}
def jsFields[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val names = fieldNames[T](c)
Apply(
q"play.api.libs.json.Json.arr",
names.map(name => Literal(Constant(name.toString))).toList
)
}
def jsValues[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val names = fieldNames[T](c)
Apply(
q"play.api.libs.json.Json.arr",
names.map(name => q"${c.prefix.tree}.$name").toList
)
}
}
Update
I tried Eugene's second suggestion like this:
abstract class Metric[T] {
def series: String
}
trait MetricSerializer[T] {
def fields: Seq[String]
def values(metric: T): Seq[Any]
}
object MetricSerializer {
implicit def materializeSerializer[T]: MetricSerializer[T] = macro MetricsMacros.materializeSerializer[T]
}
object Metrics {
def log[T: MetricSerializer](metric: T): Unit = {
val serializer = implicitly[MetricSerializer[T]]
println(serializer.fields)
println(serializer.values(metric))
}
}
with the macro now looking like this:
object MetricsMacros {
def materializeSerializer[T: c.WeakTypeTag](c: Context) = {
import c.universe._
val tpe = c.weakTypeOf[T]
val names = tpe.decls.collect {
case field if field.isMethod && field.asMethod.isCaseAccessor => field.asTerm.name
}
val fields = Apply(
q"Seq",
names.map(name => Literal(Constant(name.toString))).toList
)
val values = Apply(
q"Seq",
names.map(name => q"metric.$name").toList
)
q"""
new MetricSerializer[$tpe] {
def fields = $fields
def values(metric: Metric[$tpe]) = $values
}
"""
}
}
However, when I call Metrics.log -- specifically when it calls implicitly[MetricSerializer[T]] I get the following error:
error: value a is not a member of com.confabulous.deva.Metric[com.confabulous.deva.SessionCountMetric]
Why is it trying to use Metric[com.confabulous.deva.SessionCountMetric] instead of SessionCountMetric?
Conclusion
Fixed it.
def values(metric: Metric[$tpe]) = $values
should have been
def values(metric: $tpe) = $values
You're in a situation that's very close to one described in a recent question: scala macros: defer type inference.
As things stand right now, you'll have to turn log into a macro. An alternative would also to turn Metric.jsFields and Metric.jsValues into JsFieldable and JsValuable type classes materialized by implicit macros at callsites of log (http://docs.scala-lang.org/overviews/macros/implicits.html).

Rewriting val and var constructor arguments using macros

I am evaluating the possibility and effort to create a macro annotation to turn this:
#txn class Foo(var bar: Int)
into this:
import concurrent.stm.{Ref, InTxn}
class Foo(bar0: Int) {
private val _bar = Ref(bar0)
def bar(implicit tx: InTxn): Int = _bar()
def bar_=(value: Int)(implicit tx: InTxn): Unit = _bar() = value
}
(or perhaps creating a Foo companion object with apply method, no sure yet)
Now what I'm seeing from the ClassDef body is something like this
List(<paramaccessor> var bar: Int = _,
def <init>(bar: Int) = {
super.<init>();
()
})
So bar appears as param-accessor with no init (_), and also as argument to the <init> method.
How would I go about rewriting that body?
import scala.reflect.macros.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
class txn extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro txnMacro.impl
}
object txnMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val inputs = annottees.map(_.tree).toList
def reportInvalidAnnotationTarget(): Unit =
c.error(c.enclosingPosition,
"This annotation can only be used on a class definition")
val expandees: List[Tree] = inputs match {
case cd # ClassDef(mods, nme, tp, tmp # Template(parents, self, body)) :: Nil =>
println(s"Body: $body")
???
case _ =>
reportInvalidAnnotationTarget()
inputs
}
val outputs = expandees
c.Expr[Any](Block(outputs, Literal(Constant(()))))
}
}
As Eugene suggests, using quasiquotes can make this much easier. I haven't put together a full solution, but I tried some pieces so I think something along these lines will work:
case q"class $name extends $parent with ..$traits { ..$body }"=>
val newBody = body.flatMap {
case q"var $varName = $varBody" =>
List(
//put the three defs you need here. some variant of this ...
q"""private val ${stringToTermName("_" + varName.toString)} = Ref(${stringToTermName(varName.name + "0")})""",
//etc ...
)
case other => other
}
q"class $name extends $parent with ..$ttraits { ..${(newBody).toList} }"
You might find my blog post relevant: http://imranrashid.com/posts/learning-scala-macros/
The most relevant code snippet is here:
https://github.com/squito/learn_macros/blob/master/macros/src/main/scala/com/imranrashid/oleander/macros/FillTraitDefs.scala#L78
You may also find this useful for defining the getters & setters:
https://groups.google.com/forum/#!searchin/scala-user/macro|sort:date/scala-user/Ug75FW73mJI/F1ijU0uxZAUJ