Getting Parameters from Scala Macro Annotation - scala

So I have an annotation on a function (DefDef). This annotation has parameters.
However, I am confused on how to get the parameters from the constructor.
Usage example:
class TestMacro {
#Foo(true)
def foo(): String = ""
foo
}
Here's the code for the annotation:
class Foo(b: Boolean) extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro Foo.impl
}
object Foo {
def impl(c: whitebox.Context)(annottees: c.Tree*): c.Expr[Any] = {
import c.universe._
//how do I get value of `b` here???
c.abort(c.enclosingPosition, "message")
}
}

What about this:
val b: Boolean = c.prefix.tree match {
case q"new Foo($b)" => c.eval[Boolean](c.Expr(b))
}
For sake of completeness this is the full source:
import scala.reflect.macros.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly
import scala.reflect.api.Trees
import scala.reflect.runtime.universe._
class Foo(b: Boolean) extends StaticAnnotation {
def macroTransform(annottees: Any*) :Any = macro FooMacro.impl
}
object FooMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val b: Boolean = c.prefix.tree match {
case q"new Foo($b)" => c.eval[Boolean](c.Expr(b))
}
c.abort(c.enclosingPosition, "message")
}
}

This is an answer that shows a variation on Federico's technique, if you want to use a static annotation that has optional named arguments. In that case, you need to consider the possible invocation expressions in the case matching statement. An optional argument might be explicitly named, it might be given without a name, or it might be not present. Each of these shows up at compile time as a separate pattern in c.prefix.tree, as shown below.
#compileTimeOnly("Must enable the Scala macro paradise compiler plugin to expand static annotations")
class noop(arg1: Int, arg2: Int = 0) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AnnotationMacros.noop
}
class AnnotationMacros(val c: whitebox.Context) {
import c.universe._
// an annotation that doesn't do anything:
def noop(annottees: c.Expr[Any]*): c.Expr[Any] = {
// cases for handling optional arguments
val (arg1q, arg2q) = c.prefix.tree match {
case q"new noop($arg1, arg2 = $arg2)" => (arg1, arg2) // user gave named arg2
case q"new noop($arg1, $arg2)" => (arg1, arg2) // arg2 without name
case q"new noop($arg1)" => (arg1, q"0") // arg2 defaulted
case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!")
}
// print out the values
println(s"arg1= ${evalTree[Int](arg1q)} arg2= ${evalTree[Int](arg2q)}")
// just return the original annotee:
annottees.length match {
case 1 => c.Expr(q"{ ${annottees(0)} }")
case _ => c.abort(c.enclosingPosition, "Only one annottee!")
}
}
def evalTree[T](tree: Tree) = c.eval(c.Expr[T](c.untypecheck(tree.duplicate)))
}
Here is an example invocation that names arg2, and so it will match the first pattern - case q"new noop($arg1, arg2 = $arg2)" - above:
object demo {
// I will match this pattern: case q"new noop($arg1, arg2 = $arg2)"
#noop(1, arg2 = 2)
trait someDeclarationToAnnotate
}
Note also that because of the way these patterns work, you have to explicitly supply the default argument value inside the macro code, which is unfortunately a bit hacky, but the final evaluated class is not available to you.
As an experiment, I tried actually creating the class by calling evalTree[scope.of.class.noop](c.prefix.tree), but the Scala compiler throws an error because it considered that a reference to the annotation inside the annotation macro code, which is illegal.

Related

Scala macro: get companion object from class type

I cannot manage to get the companion object / singleton from a class type in Scala macro / quasiquotes. Tried to follow https://docs.scala-lang.org/overviews/quasiquotes/type-details.html#singleton-type, the given example works but it is based on a literal string to quasiquote to get the companion object directly, which I cannot quite achieve the same thing if I start off from an extracted class type of a param after some quasiquote unlifting.
I have simplified and tried to highlight the intended usage, and current macro implementation below:
// Intended usage
package utils
sealed trait Base
object Base {
import macros._
#Component
final case class X(v: XInner) extends Base
}
final case class XInner(v: Int)
Base.X(123) // No need to do Base.X(XInner(123))
The current macro implementation
package macros
import scala.reflect.macros.whitebox
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly
class Component() extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro ComponentMacro.impl
}
private class ComponentMacro(val c: whitebox.Context) {
import c.universe._
// To map function result while allowing the use of params forwarding syntax like `apply _`
// e.g. `def y = (X.apply _).mapResult(Y(_))`
implicit class Func1Extra[I1, O1, O2](f: I1 => O1) {
def mapResult(g: O1 => O2): I1 => O2 = (i1: I1) => g(f(i1))
}
def impl(annottees: Tree*): Tree = annottees match {
case (clsDef: ClassDef) :: Nil =>
clsDef match {
case q"final case class $className(..$fields) extends ..$parents" if fields.length == 1 => {
val fieldType = fields(0).tpt
val singletonType = tq"$fieldType.type"
val tq"$singleton.type" = singletonType
q"""
$clsDef
object ${clsDef.name.toTermName} {
def apply = (${singleton}.apply _).mapResult(new ${clsDef.name}(_))
}
"""
}
case _ => c.abort(c.enclosingPosition, "Invalid annotation target")
}
case _ => c.abort(c.enclosingPosition, "Invalid annotation target")
}
}
The error while compiling is:
value apply is not a member of Utils.XInner
The error message seems to suggest that the apply method was done on the XInner class type, rather than the companion object of XInner.
Any idea as to how to get the component object of the same type name? Thanks in advance!

Can I access the value(s) of the argument(s) of an annotation in the body of the variable (val/var) that is being annotated?

In my project, programmers can annotate certain fields of a class as a prediction in the following way :
class Foo() {
#prediction('p1) var quality = // access 'p1 here
}
A Symbol is given in the definition of a prediction annotation that represents its id (in this case the id of quality is 'p1).
My problem: I want to access the value of that Symbol in the implementation of the quality variable. I think this is achievable by using a macro but I wasn't able to implement it.
My question: How can I achieve this (macros are allowed)?
Yes, you can. Try to make prediction a macro annotation
class Foo() {
#prediction('p1) var quality = {
println(access) //'p1
}
}
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
#compileTimeOnly("enable macro paradise to expand macro annotations")
class prediction(s: Symbol) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro predictionMacro.impl
}
object predictionMacro {
def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
val symb = c.prefix.tree match {
case q"new prediction($s)" => s
}
annottees match {
case q"$mods var $tname: $tpt = $expr" :: _ =>
q"""$mods var $tname: $tpt = {
val access = $symb
$expr
}"""
}
}
}

Scala: How to access associated parameter values of a macro annotation in the macro implementation? [duplicate]

So I have an annotation on a function (DefDef). This annotation has parameters.
However, I am confused on how to get the parameters from the constructor.
Usage example:
class TestMacro {
#Foo(true)
def foo(): String = ""
foo
}
Here's the code for the annotation:
class Foo(b: Boolean) extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro Foo.impl
}
object Foo {
def impl(c: whitebox.Context)(annottees: c.Tree*): c.Expr[Any] = {
import c.universe._
//how do I get value of `b` here???
c.abort(c.enclosingPosition, "message")
}
}
What about this:
val b: Boolean = c.prefix.tree match {
case q"new Foo($b)" => c.eval[Boolean](c.Expr(b))
}
For sake of completeness this is the full source:
import scala.reflect.macros.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly
import scala.reflect.api.Trees
import scala.reflect.runtime.universe._
class Foo(b: Boolean) extends StaticAnnotation {
def macroTransform(annottees: Any*) :Any = macro FooMacro.impl
}
object FooMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val b: Boolean = c.prefix.tree match {
case q"new Foo($b)" => c.eval[Boolean](c.Expr(b))
}
c.abort(c.enclosingPosition, "message")
}
}
This is an answer that shows a variation on Federico's technique, if you want to use a static annotation that has optional named arguments. In that case, you need to consider the possible invocation expressions in the case matching statement. An optional argument might be explicitly named, it might be given without a name, or it might be not present. Each of these shows up at compile time as a separate pattern in c.prefix.tree, as shown below.
#compileTimeOnly("Must enable the Scala macro paradise compiler plugin to expand static annotations")
class noop(arg1: Int, arg2: Int = 0) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro AnnotationMacros.noop
}
class AnnotationMacros(val c: whitebox.Context) {
import c.universe._
// an annotation that doesn't do anything:
def noop(annottees: c.Expr[Any]*): c.Expr[Any] = {
// cases for handling optional arguments
val (arg1q, arg2q) = c.prefix.tree match {
case q"new noop($arg1, arg2 = $arg2)" => (arg1, arg2) // user gave named arg2
case q"new noop($arg1, $arg2)" => (arg1, arg2) // arg2 without name
case q"new noop($arg1)" => (arg1, q"0") // arg2 defaulted
case _ => c.abort(c.enclosingPosition, "unexpected annotation pattern!")
}
// print out the values
println(s"arg1= ${evalTree[Int](arg1q)} arg2= ${evalTree[Int](arg2q)}")
// just return the original annotee:
annottees.length match {
case 1 => c.Expr(q"{ ${annottees(0)} }")
case _ => c.abort(c.enclosingPosition, "Only one annottee!")
}
}
def evalTree[T](tree: Tree) = c.eval(c.Expr[T](c.untypecheck(tree.duplicate)))
}
Here is an example invocation that names arg2, and so it will match the first pattern - case q"new noop($arg1, arg2 = $arg2)" - above:
object demo {
// I will match this pattern: case q"new noop($arg1, arg2 = $arg2)"
#noop(1, arg2 = 2)
trait someDeclarationToAnnotate
}
Note also that because of the way these patterns work, you have to explicitly supply the default argument value inside the macro code, which is unfortunately a bit hacky, but the final evaluated class is not available to you.
As an experiment, I tried actually creating the class by calling evalTree[scope.of.class.noop](c.prefix.tree), but the Scala compiler throws an error because it considered that a reference to the annotation inside the annotation macro code, which is illegal.

Scalameta Decl.Def not works on a trait def method

I'm use Scalameta(v1.8.0) annotation to a def declaration:
trait MyTrait {
#MyDeclDef
def f2(): Int
}
The annotation class defined just return input, as this:
import scala.meta._
class MyDeclDef extends scala.annotation.StaticAnnotation {
inline def apply(defn: Any): Any = meta {
defn match {
case defn: Decl.Def =>
defn
case _ =>
println(defn.structure)
abort("#MyDeclDef most annotate a Decl.Def")
}
}
}
some compiler error encounter:
Error:'=' expected but eof found.
def f2(): Unit
Error:illegal start of simple expression
def f2(): Unit
Besides, If I use Decl.Var to var v2: Int it works fine.
How to right annotate a trait def? Thanks
I didn't work with scala-meta before and tried your example. It looks like we have to implement this method in the macros. For example, if we specify a default implementation:
class MyDeclDef extends scala.annotation.StaticAnnotation {
inline def apply(defn: Any): Any = meta {
defn match {
case defn: Decl.Def =>
val q"def $name(): $tpe" = defn
q"def $name(): $tpe = 1"
we will be able to create an instance of MyTrait and print a value of the f2 method :)
val x = new MyTrait
println(x.f2) // Prints 1
var v2: Int works fine, because it fits to case _ in which we abort the compilation.

Scala macro expansion of class with companion: type not found

I'm trying to define a macro annotation on a case class that expands to a class with a companion object, but I'm running in some problems.
I can create an object with the same name as the class, and define methods on it.
But when I try to use the typename of the class as a return or argument type of a method, I get an "not found: type "
class term extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro termMacro.impl
}
object termMacro {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val inputs = annottees.map(_.tree).toList
val (cls, comp) = annottees.map(_.tree) match {
case cd#q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$_ } with ..$_ { $self => ..$stats }" :: tail =>
val paramnames = paramss.head.map {
case q"$mods val $name: $tpt = $default" => name
}
val ctorparams = List(paramss.head ++ Seq(
q"val position: SourcePosition = (0,0)"
))
val ctorname = TermName(tpname.decodedName.toString)
//val clstype =
(
q"""
import ast.Term
case class $tpname(...$ctorparams) extends Term { $self =>
def children() = {
List(..$paramnames)
}
..$stats
}
""",
q"""
object $ctorname {
def unapply(t: $tpname): Option[(Int, Int)] = {
Some(Tuple2(3, 4))
}
}
"""
)
case head :: tail =>
c.abort(c.enclosingPosition, s"The #Term annotation is for case classes, found $head")
}
c.error(c.enclosingPosition, showRaw(cls) + "\n" + showRaw(comp))
c.Expr[Any](Block(List(cls, comp), Literal(Constant(()))))
}
}
e.g. usage would be: #term case class A(x: Term) extends Term, and would give me the compiler error "not found: type A' at location #term in that definition.
I've narrowed the location down to the unapply method in the object definition.
Any help is appreciated.
I'm very much new to scala macros, so any further advice is also appreciated.
Side question: any advice on debugging macros in intellij / gradle projects?
q"import foo; class Bar" creates a block, so the macro annotation will inadvertently replace class Bar with { import foo; class Bar }, which makes Bar a local class, which is invisible from the outside of the block.