2
votes

I'm attempting to build a scala class based on some java classes generated by a third party annotation pre-processor.

I'd like to be able to "point" to a class from an annotated object, for example:

@MyAnnotation(classOf[GeneratedJavaClass]) object MyObject

or

@MyAnnotation object MyObject extends PlaceHolderTrait[GeneratedJavaClass]

Once I'm in the actual macro implementation, I would like to reflect on GeneratedJavaClass to find it's members which I'll use to build the implementation of MyObject

My starting point so far is based on https://github.com/travisbrown/type-provider-examples/blob/master/rdfs-public/src/main/scala/public/PrefixGenerator.scala.

I've tried to understand how I could take a Class[T] as the argument to the annotation and then match on c.macroApplication with Apply(Select(Apply(_, List(TypeApply(_, List(catalog)))), _), _) but the type I get is a TypeApply(_, List(Trees$Ident) and I don't see a way to get the class from there (I assume classOf[T] isn't a literal).

As an alternative, I thought I'd try to extract the type I want from a trait that I have the object extend. I tried matching the annotee against case List(q"object $name extends PlaceHolderTrait[$parent] { ..$body }") but again end up with a Trees$Ident and am not sure how to get the class that is being referenced.

I realize I could probably just pass a String of the fully qualified name and use reflection to get the class, but I was hoping for a nicer alternative. Note that I'm not tied to those 2 alternatives for specifying the class, they are just the 2 options I've been able to come up with.

2

2 Answers

1
votes

Ok, finally got something working, based on https://github.com/scalamacros/paradise/issues/69 I realized that at the stage I'm running in the typer hasn't run and therefore expecting to be given a type is a bit silly. The macro api does provide the typeCheck method however, which lets you run the typer on a tree, as follows:

class AnnotationArgument[T] extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro AnnotationArgumentImpl.impl
}

class AnnotationArgumentImpl(val c: blackbox.Context) {

  import c.universe._

  def impl(annottees: c.Expr[Any]*): c.Tree = {
      val macroTypeWithArguments = c.typeCheck(q"${c.prefix.tree}").tpe // my.package.AnnotationArgument[my.package.MyClass]
      val annotationClass: ClassSymbol = macroTypeWithArguments.typeSymbol.asClass // my.package.AnnotationArgument
      val annotationTypePlaceholder: Type = annotationClass.typeParams.head.asType.toType // T
      val argumentType: Type = annotationTypePlaceholder.asSeenFrom(args, annotationClass) // my.package.MyClass

      println(s"the argument's type is $argumentType")


    q"..${annottees}"
  }
}

import my.package.MyClass
@AnnotationArgument[MyClass]
class AnnotationArgumentTestClass
0
votes

another thought :

use another class annotation save class info

class AnnotationArgumentClass[T](clazz: Class[T]) extends StaticAnnotation

class AnnotationArgument extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro AnnotationArgumentImpl.impl
}

class AnnotationArgumentImpl(val c: blackbox.Context) {

  import c.universe._

  def impl(annottees: c.Expr[Any]*): c.Tree = {
    val classValue = annottees.head.tree match {
      case x: MemberDefApi => x.mods.annotations collectFirst {
        case q"new $annotation($classValue)" => classValue
      }
    }

    /*edit :*/
    val x = (c.eval(c.Expr[Type](c.typecheck(classValue.get))))
    println(x.typeSymbol.fullName)

    q"..${annottees}"
  }
}

test :

package aaa 
class AAA

//

import aaa.AAA 
@AnnotationArgument
@AnnotationArgumentClass(classOf[AAA])
class AnnotationArgumentTestClass