My goal is to have an annotation macro that will inject methods and variables into a trait. While this appears to generate the proper code for the trait, I cannot get the compiler to recognize that the methods now exist. FYI, a real implementation does work when trying to access the value.
I was hoping to use this to clean up some code (and get a better understanding of macros) in cases like this: https://github.com/ibm-et/spark-kernel/blob/b5b0dc1995ab010d0f0570fbb9fe0f4f19817e03/kernel-api/src/main/scala/com/ibm/spark/magic/dependencies/IncludeInterpreter.scala
I was following the code from a sample project pointed to in the Scala macro documentation. https://github.com/scalamacros/sbt-example-paradise
So, below is the actual code for my macro (being compiled using Scala 2.10.4 with the macro paradise plugin 2.1.0-M5:
import scala.reflect.internal.annotations.compileTimeOnly
import scala.reflect.macros.Context
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation
/**
* Represents a macro that injects appropriate getters and setters into the
* annotated trait such that it can be treated as a dependency.
*
* #param variableName The name of the variable to provide getter/setter
* #param className The class name of the variable
*/
#compileTimeOnly("Enable macro paradise to expand macro annotations")
class Dependency(
variableName: String,
className: String
) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro Dependency.impl
}
object Dependency {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
println("Starting macro")
println("Prefix: " + c.prefix)
val q"new Dependency(..$params)" = c.prefix.tree
println("Params: " + params)
val (variableName: String, className: String) = params match {
case p: List[_] =>
val stringParams = p.map(_.toString.replace("\"", ""))
(stringParams(0), stringParams(1))
case _ => c.abort(
c.enclosingPosition,
"Required arguments are (variable name: String) (class name: String)"
)
}
println("Variable name: " + variableName)
println("Class name: " + className)
def modifiedTrait(traitDecl: ClassDef) = {
val (name) = try {
val q"trait $name" = traitDecl
(name)
} catch {
case _: MatchError => c.abort(
c.enclosingPosition,
"Annotation is only supported on trait"
)
}
println("Trait name: " + name)
val actualVariableName = variableName.toLowerCase
println("Actual variable name: " + actualVariableName)
val actualVariableClass = newTypeName(className)
println("Actual class name: " + actualVariableClass)
val internalVariableName = newTermName("_" + actualVariableName)
println("Internal variable name: " + internalVariableName)
val getterName = newTermName(actualVariableName)
println("Getter name: " + getterName)
val setterName = newTermName(actualVariableName + "_=")
println("Setter name: " + setterName)
val setterVariableName = newTermName("new" + actualVariableName.capitalize)
println("Setter variable name: " + setterVariableName)
val generatedTrait = q"""
trait $name {
private var $internalVariableName: $actualVariableClass = _
def $setterName($setterVariableName: $actualVariableClass) =
$internalVariableName = $setterVariableName
def $getterName: $actualVariableClass = $internalVariableName
}
"""
println("Generated trait: " + generatedTrait)
c.Expr[Any](generatedTrait)
}
annottees.map(_.tree) match {
case (traitDecl: ClassDef) :: Nil => modifiedTrait(traitDecl)
case _ => c.abort(c.enclosingPosition, "Invalid annottee")
}
}
}
Below is my attempt to use the annotation within ScalaTest:
#Dependency("someFakeField", "Int") trait TestTrait
class A extends TestTrait
val a = new A
println("TestTrait")
classOf[TestTrait].getDeclaredMethods.foreach(println)
println("A")
classOf[A].getDeclaredMethods.foreach(println)
// This line fails
//a.someFakeField = 3
Below is the output from running my macro:
Warning:scalac: Starting macro
Warning:scalac:
Warning:scalac: Prefix: Expr[Nothing](new Dependency("someFakeField", "Int"))
Warning:scalac: Params: List("someFakeField", "Int")
Warning:scalac: Variable name: someFakeField
Warning:scalac: Class name: Int
Warning:scalac: Trait name: TestTrait
Warning:scalac: Actual variable name: somefakefield
Warning:scalac: Actual class name: Int
Warning:scalac: Internal variable name: _somefakefield
Warning:scalac: Getter name: somefakefield
Warning:scalac: Setter name: somefakefield_=
Warning:scalac: Setter variable name: newSomefakefield
Warning:scalac: Generated trait: abstract trait TestTrait extends scala.AnyRef {
def $init$() = {
()
};
private var _somefakefield: Int = _;
def somefakefield_=(newSomefakefield: Int) = _somefakefield = newSomefakefield;
def somefakefield: Int = _somefakefield
}
Here is the output from running the reflection to access the methods of the trait and class using the trait (notice the appearance of the methods):
TestTrait
public abstract void com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$TestTrait$1.somefakefield_=(int)
public abstract com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1 com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$TestTrait$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$$outer()
public abstract int com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$TestTrait$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$_somefakefield()
public abstract void com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$TestTrait$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$_somefakefield_$eq(int)
public abstract int com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$TestTrait$1.somefakefield()
A
public void com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.somefakefield_=(int)
public com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1 com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$A$$$outer()
public com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1 com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$$outer()
public int com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$_somefakefield()
public void com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.com$ibm$spark$annotations$DependencySpec$$anonfun$$anonfun$TestTrait$$_somefakefield_$eq(int)
public int com.ibm.spark.annotations.DependencySpec$$anonfun$1$$anonfun$apply$mcV$sp$1$A$1.somefakefield()
Here is a hand-written trait that does work:
trait IncludeInterpreter {
private var _interpreter: Interpreter = _
def interpreter: Interpreter = _interpreter
def interpreter_=(newInterpreter: Interpreter) =
_interpreter = newInterpreter
}
Any thoughts as to what I am doing wrong? If I cannot do something like this with the macros, then I guess I will need to continue writing by hand. FYI, I have also tried to compile the macro in one module, use the macro to generate the methods for the trait in another module, and actually using the trait in a third module. Not sure what needs to be separate and what does not.
When I have run the macro and then tried to access the methods from another module (not in ScalaTest), I do not even see the methods using reflection (although the output from the macro still appears).
Related
I am a starter with Scala 2 Macros (before I switch to Dotty) who after trying out the shapeless type class derivation wanted to go one step beyond and write a macro that can generate a type class instances for any scala.Product without it.
(for the sake of example let's ignore nested recursive types, so my goal is flat case classes.)
My type class is an abstract class Coder[T] (e.g. trait with encode() / decode()).
So the generated code for:
case class Pojo(
s: String,
i: Int,
l: List[Int]
)
should be something like:
import com.github.fpopic.scalamacros.Pojo
import org.apache.beam.sdk.coders.Coder
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
import java.util
class PojoCoder extends Coder[Pojo] {
import com.github.fpopic.scalamacros.beam.DefMacroCoder.{
stringCoder,
intCoder,
listCoder
}
override def encode(value: Pojo, os: OutputStream): Unit = {
stringCoder.encode(value.s, os)
intCoder.encode(value.i, os)
listCoder(intCoder).encode(value.l, os)
}
override def decode(is: InputStream): Pojo = {
Pojo(
s = stringCoder.decode(is),
i = intCoder.decode(is),
l = listCoder(intCoder).decode(is)
)
}
override def getCoderArguments: util.List[_ <: Coder[_]] = {
Collections.emptyList()
}
override def verifyDeterministic(): Unit = ()
}
(removed fully specified class names to improve readability)
In the macro I try to:
inspect the weakTypeOf[P]
iterate over each case class constructor field (of type F)
implicitly find their typeclass Coder[F] instance and add it to the tree
and append their encode() and decode() expression to the tree that contributes to the final Coder[P] methods.
def materializeProductCoder[P: c.WeakTypeTag](c: blackbox.Context): c.Expr[Coder[P]] = {
import c.universe._
val tpe = c.weakTypeOf[P]
val helper = new MacrosHelper[c.type](c)
val expressions =
helper.getPrimaryConstructorMembers(tpe).map { field =>
val fieldTerm = field.asTerm.name // e.g. value.s (for now just s)
val fieldType = field.typeSignature.finalResultType // e.g. String
val fieldCoderName = c.freshName(TermName("coder")) // e.g. give friendly name coder$...
val fieldCoderInstance = // e.g. finds instance of Coder[String]
c.typecheck(
tree = q"""_root_.scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[${fieldType}]]""",
silent = false
)
val fieldCoderExpression =
q"private val ${fieldCoderName}: org.apache.beam.sdk.coders.Coder[${fieldType}] = ${fieldCoderInstance}"
val fieldEncodeExpression =
q"${fieldCoderName}.encode(value.${fieldTerm}, os)" // replace with full relative name (with dots) instead of value
val fieldDecodeExpression =
q"${field.asTerm} = ${fieldCoderName}.decode(is)"
(fieldCoderExpression, fieldEncodeExpression, fieldDecodeExpression)
}
val fieldCodersExpression = expressions.map(_._1).distinct
val coderEncodeExpresions = expressions.map(_._2)
val coderDecodeExpresions = expressions.map(_._3)
val coderExpression =
q"""{
new org.apache.beam.sdk.coders.Coder[${tpe}] {
{import ${c.prefix}._}
..${fieldCodersExpression}
override def encode(value: ${tpe}, os: java.io.OutputStream): _root_.scala.Unit = {
..${coderEncodeExpresions}
}
override def decode(is: java.io.InputStream): ${tpe} = {
${tpe.typeConstructor}(
..${coderDecodeExpresions}
)
}
override def getCoderArguments: java.util.List[_ <: org.apache.beam.sdk.coders.Coder[_]] = {
java.util.Collections.emptyList
}
override def verifyDeterministic(): _root_.scala.Unit = ()
}
}
"""
val ret = coderExpression
c.Expr[Coder[P]](ret)
}
But get an error after invoking sbt Test / compile:
(a bit struggling with imports and implicit search so for now having intermediate private vals, and distinct is useless)
{
final class $anon extends org.apache.beam.sdk.coders.Coder[com.github.fpopic.scalamacros.beam.Pojo] {
def <init>() = {
super.<init>();
()
};
{
import DefMacroCoder._;
()
};
private val coder$macro$1: org.apache.beam.sdk.coders.Coder[String] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[String]](DefMacroCoder.stringCoder);
private val coder$macro$2: org.apache.beam.sdk.coders.Coder[Int] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[Int]](DefMacroCoder.intCoder);
private val coder$macro$3: org.apache.beam.sdk.coders.Coder[List[Int]] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[List[Int]]](DefMacroCoder.listCoder[Int](DefMacroCoder.intCoder));
override def encode(value: com.github.fpopic.scalamacros.beam.Pojo, os: java.io.OutputStream): _root_.scala.Unit = {
coder$macro$1.encode(value.s, os);
coder$macro$2.encode(value.i, os);
coder$macro$3.encode(value.l, os)
};
override def decode(is: java.io.InputStream): com.github.fpopic.scalamacros.beam.Pojo = com.github.fpopic.scalamacros.beam.Pojo(s = coder$macro$1.decode(is), i = coder$macro$2.decode(is), l = coder$macro$3.decode(is));
override def getCoderArguments: java.util.List[_$1] forSome {
<synthetic> type _$1 <: org.apache.beam.sdk.coders.Coder[_$2] forSome {
<synthetic> type _$2
}
} = java.util.Collections.emptyList;
override def verifyDeterministic(): _root_.scala.Unit = ()
};
new $anon()
}
[error] .../DefMacroCoderSpec.scala:17:56: com.github.fpopic.scalamacros.beam.Pojo does not take parameters
[error] val coder: Coder[Pojo] = DefMacroCoder.productCoder[Pojo]
[error] ^
[error] one error found
Which I believe comes from here but don't fully understand what the compiler is trying to tell me?
Link to the full code sample can be found here
Link to CI error can be found here.
The way you're instantiating your class is wrong:
${tpe.typeConstructor}(...)
It should be
new $tpe(...)
or if you want to do it with case class companion object's apply instead of plain constructor:
${tpe.typeSymbol.companion}(...)
NOTE: type constructor (also known as higher kinded type) has nothing to do with class constructor
When attempting to get the name of a class via a WeakTypeTag reference when defining a macro implementation, I can't seem to get the proper info if multiple layers of polymorphism are applied.
For example if I have the following setup:
object MacroSupport {
def get_name_impl[A: c.WeakTypeTag](c: blackbox.Context): c.Expr[String] = {
val nameOfA: String = weakTypeOf[A].toString
...
}
def getName[A] = macro get_name_impl[A]
}
abstract class GenericInterface[T] {
def getName: String = MacroSupport.getName[T]
}
case class ContainerA(
someValue: String
)
class FunctionalClass extends GenericInterface[ContainerA] {
val containerName: String = getName
}
What I hope to achieve is having any number of FunctionalClass's, each with their own Container class, and they can report the name of their container, which is used for some meta configuration. Basically MacroSupport and GenericInterface will exist in a library I'm writing while the FunctionalClass and Container levels will be written by others using the library.
The issue I'm having, due to the pass through type in the GenericInterface, FunctionalClass.containerName == "t", and attempts to access Type declarations yield nothing. How can I get the type information from the FunctionalClass declaration to the MacroSupport level?
Try materialization of type class
https://docs.scala-lang.org/overviews/macros/implicits.html#implicit-materializers
import scala.reflect.macros.blackbox
import scala.language.experimental.macros
object MacroSupport {
def getName[A](implicit gn: GetName[A]): String = gn()
trait GetName[A] {
def apply(): String
}
object GetName {
implicit def materializeGetName[A]: GetName[A] = macro materializeGetNameImpl[A]
def materializeGetNameImpl[A: c.WeakTypeTag](c: blackbox.Context): c.Expr[GetName[A]] = {
import c.universe._
c.Expr[GetName[A]] {
q"""
new MacroSupport.GetName[${weakTypeOf[A]}] {
override def apply(): _root_.java.lang.String = ${weakTypeOf[A].toString}
}
"""
}
}
}
}
import MacroSupport.GetName
abstract class GenericInterface[T: GetName] {
def getName: String = MacroSupport.getName[T]
}
case class ContainerA(
someValue: String
)
class FunctionalClass extends GenericInterface[ContainerA] {
val containerName: String = getName
}
(new FunctionalClass).containerName // ContainerA
By the way, shapeless.Typeable does the job. Typeable[A].describe is like our MacroSupport.getName[A].
I was following along this tutorial by Jonas Bonér on how to achieve AspectJ like aspect oriented programming in Scala. The mixins got me quite confused with the abstract overrides. I eventually got the implementation working by matching parsePointcutExpression. However, The pointcutExpression matching is incorrect:
My Invocation case class:
package aspect2
import java.lang.reflect.Method
object aspect {
case class Invocation(val method: Method, val args: Array[AnyRef], val target: AnyRef) {
def invoke: AnyRef = method.invoke(target, args:_*)
override def toString: String = "Invocation [method: " + method.getName + ", args: " + args + ", target: " + target + "]"
}
}
My interceptor and advice methods for matching pointcutExpressions and logging: Note I want to match against "execution(* *.bar(..))".
package aspect2
import aspect2.aspect.Invocation
import org.aspectj.weaver.tools.{PointcutExpression, PointcutParser}
object InterceptorImpl {
trait Interceptor {
protected val parser = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution
protected def matches(pointcut: PointcutExpression, invocation: Invocation): Boolean = {
pointcut.matchesMethodExecution(invocation.method).alwaysMatches ||
invocation.target.getClass.getDeclaredMethods.exists(pointcut.matchesMethodExecution(_).alwaysMatches) ||
false
}
def invoke(invocation: Invocation): AnyRef
}
trait LoggingInterceptor extends Interceptor {
val loggingPointcut = parser.parsePointcutExpression("execution(* *.bar(..))")
abstract override def invoke(invocation: Invocation): AnyRef =
if (matches(loggingPointcut , invocation)) {
println("=====> Enter: " + invocation.method.getName + " # " + invocation.target.getClass.getName)
val result = super.invoke(invocation)
println("=====> Exit: " + invocation.method.getName + " # " + invocation.target.getClass.getName)
result
} else super.invoke(invocation)
}
}
My factory methods to wire in the interceptors using Java Dynamic Proxy API
package aspect2
import java.lang.reflect.{InvocationHandler, Method, Proxy}
import aspect2.aspect.Invocation
object ManagedComponentFactory {
def createComponent[T](intf: Class[T] forSome {type T}, proxy: ManagedComponentProxy): T =
Proxy.newProxyInstance(
proxy.target.getClass.getClassLoader,
Array(intf),
proxy).asInstanceOf[T]
}
class ManagedComponentProxy(val target: AnyRef) extends InvocationHandler {
override def invoke(proxy: AnyRef, m: Method, args: Array[AnyRef]): AnyRef = invoke(Invocation(m, args, target))
def invoke(invocation: Invocation): AnyRef = invocation.invoke
}
My main function to run the demo:
package aspect2
import aspect2.InterceptorImpl.LoggingInterceptor
import aspect2.aspect.Invocation
object aspectDemo extends App{
var foo = ManagedComponentFactory.createComponent[Foo](
classOf[Foo],
new ManagedComponentProxy(new FooImpl)
with LoggingInterceptor{
override def invoke(invocation:Invocation):AnyRef={
super.invoke(invocation)
}
})
foo.foo("foo")
foo.bar("bar")
}
Foo and Bar are implemented as follows:
trait Foo {
def foo(msg: String)
def bar(msg: String)
}
class FooImpl extends Foo {
val bar: Bar = new BarImpl
def foo(msg: String) = println("msg: " + msg)
def bar(msg: String) = bar.bar(msg)
}
trait Bar {
def bar(msg: String)
}
class BarImpl extends Bar {
def bar(msg: String) = println("msg: " + msg)
}
My results show that my pointcut ("execution(* *.bar(..))") gets both foo.bar("bar") and foo.foo("foo")
The problem was in the line invocation.target.getClass.getDeclaredMethods.exists(pointcut.matchesMethodExecution(_).alwaysMatches) in the object InterceptorImpl which matches all methods of the class. Since both methods foo and bar belong to trait Foo which is extended by class FooImpl the pointcutExpression matches logging happens for both methods.
//File Animal.scala
abstract class Animal {
val name: String
def getSomething(tClass: TypeClass): String = {
tClass.tName.split('.').lift(0)
}
def apply(tClass: TypeClass): SomeOtherClassType = {
// something...
}
// File: DogSpike, this is used for some specific cases (overwrites
base class val)
object DogSpike extends Animal {
override val name: String = "Spike"
}
this call then works (calls apply)
myTransformation(() => DogSpike(this))
Now I would like to create a more generic object that one can pass arguments but I am unable to.
It would work to create a derived Object from Animal that takes one arguments and being able to use the apply call
object TheDog(name: String) extends Animal {
override val name: String = "Spike"
//...
}
not sure how to implicitly call Animal.apply for TheDog object where I could pass a parameter (name)
myTransformation(() => TheDog(this))
// also this seems to be incorrect "*Wrong top statement declaration*"
object TheDog(val n: String) extends Animal {
override val name: String = n
//...
}
As of *Wrong top statement declaration* (I can understand only this part of your question) - you can't have constructor in object as object is a singleton, so you should use a case class (ADT):
final case class TheDog(name: String) extends Animal
scala>TheDog("Spike")
res2_2: TheDog = TheDog("Spike")
val and companion object with apply is added automatically for case classes, so you don't need to define your own own apply in Animal. case class TheDog(val name: String) is same as case class TheDog(name: String).
I's also use traits instead of abstract class:
trait Animal {
val name: String
def getSomething: String = {
"Dog: " + name
}
}
I don't understand your TypeClass type, but if you really want type classes:
trait Animal {
def name: String
}
final case class TheDog(name: String) extends Animal
final case class TheCat(name: String) extends Animal
implicit class RichDog(dog: TheDog){
def getSomething: String = {
"Dog" + dog.name
}
}
implicit class RichCat(cat: TheCat){
def getSomething: String = {
"Cat: " + cat.name
}
}
scala> TheDog("Spike").getSomething
res4_5: String = "DogSpike"
scala> TheCat("Tom").getSomething
res4_6: String = "Cat: Tom"
About calling apply "implicitly", I don't know why would anyone need this, but:
trait AnimalFactory[A <: Animal] {
def apply(name: String)(implicit constructor: String => A) = constructor(name)
}
object TheeeDog extends AnimalFactory[TheDog]
implicit def createDog(name: String) = TheDog(name)
TheeeDog("Spike")
Of course you have to provide createDog and make it visible for a client, but it doesn't really make sense if you can just use ADTs and define additional required applys in companion object:
case class TheMouse(name: String)
object TheMouse{
def apply(isJerry: Boolean): TheMouse = if (isJerry) TheMouse("Jerry") else TheMouse("NotJerry")
}
TheMouse(true)
If you want to add some parameter to constructor, just add it:
class AnimalFactory(clazz: SomeClass){
def doSomething = clazz.name
def apply(name: String)
}
val dogFactory = new AnimalFactory(dogClassDescriptor)
val catFactory = new AnimalFactory(catClassDescriptor)
dogFactory("Spike")
catFactory("Tom")
You can even create a factory for factory (I wouldn't recommend - this solution already looks overcomplicated):
object AnimalFactory{ //please don't use classes for that - avoiding `new` is not their purpose
def apply(clazz: SomeClass) = new AnimalFactory(clazz)
}
val dogFactory = AnimalFactory(dogClassDescriptor)
//or even `val spike = AnimalFactory(dogClassDescriptor)("Spike")`
But still what's the point if you could just provide underlying clazz either as a member or just in a wrapper:
final case class ClazzWrapper[T <: Animal](clazz: SomeClass, animal: T)
Is there a way to access the constructor declaration of a macro annotation from within the macro implementation by reflection? For example, when I define this macro annotation:
class Component(x: String, y: Int = 0) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro ComponentMacro.impl
}
is it possible to reflect on the Component from within the macro implementation? When I use something like:
class ComponentMacro(val c: whitebox.Context) {
import c.universe._
def impl(annottees: c.Expr[Any]*) = {
/* ... */
val t = typeOf[Component] // throws exception
}
}
the compiler throws an error during macro expansion.
You can use c.prefix.tree, as in the example below:
class ComponentMacro(val c: scala.reflect.macros.whitebox.Context) {
import c.universe._
def impl(annottees: c.Expr[Any]*): c.Expr[Any] = {
val annottation = c.prefix.tree
annottation match {
case q""" new $className(..$params) """ => println(s"className=$className, x=$params")
}
c.Expr(q""" case class Test() """)
}
}
For this annotated class:
#Component("FooBar", 5)
case class Test()
It will output:
Warning:scalac: className=Component, x=List("FooBar", 5)