Reflect on annotation class from within scala macro annotation? - scala

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)

Related

How to obtain a Trait/Class from a string with its name

I have multiple hierarchy of Scala traits that can compose an object in many ways. Example:
trait MainA { someDefs... }
trait SubA1 extends MainA { someDefs... }
trait SubA2 extends MainA { someDefs... }
trait MainB { someDefs... }
trait SubB1 extends MainB { someDefs... }
trait SubB2 extends MainB { someDefs... }
The composition should look (abstractly) like this:
new ComposedTrait with MainA with MainB
What I want to do is to create a composed trait on runtime with the prompted desired sub-traits of each case. On regular Java I know there is a method in class Class to obtain the class that matches with the name (Class.forName("name") I think), but it doesn't work in this case. This is what should look like:
//Should yield a ComposedTrait with a SubA2 and SubB1 traits
new ComposedTrait with Class.forName(SubA2) with Class.forName(SubB1)
Is there any way of making this possible? Like an alternative to the Class.forName that works in this scenario of composition?
If you know parent names at compile time you can write a macro (see sbt settings for macro projects)
import scala.language.experimental.macros
import scala.reflect.macros.{blackbox, whitebox}
def makeInstance[A](parentNames: String*): A = macro makeInstanceImpl[A]
def makeInstanceImpl[A: c.WeakTypeTag](
c: whitebox/*blackbox*/.Context
)(parentNames: c.Tree*): c.Tree = {
import c.universe._
def getValue(tree: Tree): String = tree match {
case q"${name: String}" => name
}
// def getValue(tree: Tree): String = c.eval(c.Expr[String](c.untypecheck(tree)))
val parents = parentNames.map(name => tq"${TypeName(getValue(name))}")
q"new ${weakTypeOf[A]} with ..$parents"
}
Please notice that if you make a macro whitebox then it can return a value of more precise type than declared (A)
val instance = makeInstance[ComposedTrait]("SubA2", "SubA1")
// checking the type
instance: ComposedTrait with SubA2 with SubA1 // not just ComposedTrait
// scalacOptions += "-Ymacro-debug-lite"
//scalac: {
// final class $anon extends ComposedTrait with SubA2 with SubA1 {
// def <init>() = {
// super.<init>();
// ()
// }
// };
// new $anon()
//}
Scala Reflection--Create Instance from class name string of this class type
If you know parent names at runtime you can use reflective toolbox (runtime compilation)
import scala.reflect.runtime
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
val rm = runtime.currentMirror
val tb = rm.mkToolBox()
def makeInstance[A: TypeTag](parentNames: String*): A = {
val parents = parentNames.map(name => tq"${TypeName(name)}")
tb.eval(q"new ${typeOf[A]} with ..$parents").asInstanceOf[A]
}
makeInstance[ComposedTrait]("SubA2", "SubA1")
Please notice that in such case statically you can't have a value of more precise type than ComposedTrait based on runtime strings (parent names).
If you need to create ComposedTrait at runtime you can do this as well
val classSymbol = tb.define(q"trait ComposedTrait".asInstanceOf[ClassDef])
def makeInstance(parentNames: String*): Any = {
val parents = parentNames.map(name => tq"${TypeName(name)}")
tb.eval(q"new $classSymbol with ..$parents")
}
makeInstance("SubA2", "SubA1")

Pass type identifier to static macro annotation

Is there a way to pass a type identifier to a macro annotation? Here is what I mean:
#compileTimeOnly("Compile-time only annotation")
class mymacro(val typeName: universe.TypeName) extends StaticAnnotation { // <-- does not work
def macroTransform(annottees: Any*): Any = macro impl
}
object mymacro {
def impl(c: whitebox.Context)(annottees: c.Expr[Any]*) = //...
}
use-case:
trait SomeTrait
#mymacro(SomeTrait)
class Test {
//...
}
Or maybe there is any other way to pass Type identifier of an arbitrary non-generic type to a macro annotation implementation?
Motivation behind this: I need to generate some class Test member function def foo depending on the type passed as an argument to the macro annotation (SomeTrait).
For example you can pass typeName as a string
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
#compileTimeOnly("Compile-time only annotation")
class mymacro(typeName: String) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro mymacro.impl
}
object mymacro {
def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
val typeName = TypeName(c.prefix.tree match {
case q"new mymacro(${str: String})" => str
})
println(typeName)
q"..$annottees"
}
}
Usage:
trait SomeTrait
#mymacro("SomeTrait")
class Test
// scalac: SomeTrait
Getting Parameters from Scala Macro Annotation
If you prefer the companion-object approach proposed by #user in comments then you can do
import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
#compileTimeOnly("Compile-time only annotation")
class mymacro(companionObject: Any) extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro mymacro.impl
}
object mymacro {
def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
val companionTrait = c.typecheck(c.prefix.tree) match {
case q"new mymacro($arg)" => arg.symbol.companion
}
println(companionTrait)
q"..$annottees"
}
}
Usage:
trait SomeTrait
object SomeTrait
#mymacro(SomeTrait)
class Test
//scalac: trait SomeTrait

scala: How to obtain class name through complex polymorphism with compile time macros?

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].

Implicit JsonWriter for trait not working

I have class as below
trait RiskCheckStatusCode {
def code: String
def isSuccess: Boolean
}
object RiskCheckStatusCode {
val SUCCESS = SuccessRiskCheckStatusCode("1.1.1")
val FAIL = FailRiskCheckStatusCode("2.2.2")
case class SuccessRiskCheckStatusCode(code: String) extends RiskCheckStatusCode {
override def isSuccess = true
}
object SuccessRiskCheckStatusCode {
import spray.json.DefaultJsonProtocol._
implicit val formatter = jsonFormat1(SuccessRiskCheckStatusCode.apply)
}
case class FailRiskCheckStatusCode(code: String) extends RiskCheckStatusCode {
override def isSuccess = false
}
object FailRiskCheckStatusCode {
import spray.json.DefaultJsonProtocol._
implicit val formatter = jsonFormat1(FailRiskCheckStatusCode.apply)
}
}
and now I would like to convert the list of RiskCheckStatusCode to json
object Main extends App{
import spray.json._
import spray.json.DefaultJsonProtocol._
val l = List(RiskCheckStatusCode.SUCCESS, RiskCheckStatusCode.FAIL)
implicit object RiskCheckStatusCodeJsonFormat extends JsonWriter[RiskCheckStatusCode] {
override def write(obj: RiskCheckStatusCode): JsValue = obj match {
case obj: SuccessRiskCheckStatusCode => obj.toJson
case obj: FailRiskCheckStatusCode => obj.toJson
}
}
def json[T](list: T)(implicit formatter: JsonWriter[T]) = {
print(list.toJson)
}
json(l)
}
but the json method can not find jsonWriter[RiskCheckStatusCode].
Can you explain why? Maybe should I do it differently for trait type?
Edit:
It works for
val l: RiskCheckStatusCode = RiskCheckStatusCode.SUCCESS
so the problem is with List[RiskCheckStatusCode] because I have a formatter for RiskCheckStatusCode, not for List[RiskCheckStatusCode]. I tried import DefaultJsonProtocol but it still does not work.
import spray.json.DefaultJsonProtocol._
I have to change the definitions? From
implicit object RiskCheckStatusCodeJsonFormat extends JsonWriter[RiskCheckStatusCode]
to
implicit object RiskCheckStatusCodeJsonFormat extends JsonWriter[List[RiskCheckStatusCode]]
error:
Error:(28, 7) Cannot find JsonWriter or JsonFormat type class for List[com.example.status.RiskCheckStatusCode]
json(l)
Error:(28, 7) not enough arguments for method json: (implicit formatter: spray.json.JsonWriter[List[com.example.status.RiskCheckStatusCode]])Unit.
Unspecified value parameter formatter.
json(l)
Your code is fine you are just not having toJson in your scope (it is located in the package object of spray.json).
Add it and your code should compile:
object Main extends App with DefaultJsonProtocol {
import spray.json._
// ...
}
Furthermore spray has some issues to lift JsonWriter through derived formats (see this for details).
You can switch to JsonFormat instead:
implicit object RiskCheckStatusCodeJsonFormat extends JsonFormat[RiskCheckStatusCode] {
override def write(obj: RiskCheckStatusCode): JsValue = obj match {
case obj: SuccessRiskCheckStatusCode => obj.toJson
case obj: FailRiskCheckStatusCode => obj.toJson
}
override def read(json: JsValue): RiskCheckStatusCode = ???
}
In addition, to cleanup the type of your List change the definition of RiskCheckStatusCode to (this explains more details):
sealed trait RiskCheckStatusCode extends Serializable with Product

Finding the actual type in macro

I am trying to write a macro that extracts the type information. Here is a minimized version.
class Extractor[E] {
def extract(entity: E): Unit = macro ExtractorImpl.extractImpl[E]
}
object ExtractorImpl {
def extractImpl[E: c.WeakTypeTag](c: Context)(entity: c.Expr[E]): c.Tree = {
import c.universe._
val actualType = implicitly[WeakTypeTag[E]].tpe
c.info(c.enclosingPosition, actualType.toString, true)
q"{}"
}
}
Here is a typical implementation of Extractor:
case class Person(name: String)
object PersonExtractor extends Extractor[Person]
If I use this class directly as in PersonExtractor.extract(new Person("test name")), I get Person printed as info. But, if I use it indirectly as follows, it prints only E:
class Mapper[E](extractor: Extractor[E]) {
def extract(e: E) = extractor.extract(e)
}
class PersonMapper extends Mapper[Person](new Extractor[Person])
class Test {
new PersonMapper().extract(new Person("test name"))
}
How do I go about getting the Person type available as actualType?
use implicit macro
trait Extractor[E] {
def extract(entity: E): Unit
}
object Extractor {
implicit def i[E] : Extractor[E] = macro ExtractorImpl.extractImpl[E]
}
object ExtractorImpl {
def extractImpl[E: c.WeakTypeTag](c: Context):c.Tree = {
import c.universe._
val actualType = c.weakTypeOf[E]
c.info(c.enclosingPosition, actualType.toString, false)
q"""
new Extractor[$actualType]{
def extract(entity: $actualType): Unit = println("hello world")
}
"""
}
}
// test
case class Person(name: String)
//object PersonExtractor extends Extractor[Person]
class Mapper[E] {
def extract(e: E)(implicit extractor: Extractor[E]) = extractor.extract(e)
}
class PersonMapper extends Mapper[Person]
class Test {
new PersonMapper().extract(new Person("test name")) // show info:Person
}
Edit:
//test2
class Mapper2[E](extractor: Extractor[E]) {
def extract(e: E) = extractor.extract(e)
}
object Mapper2 extends Mapper2[Person](Extractor.i[Person])//also show info:Person