8
votes

I am kind of blocked with the following (macro annotation) situation. Suppose I have an annotation called @factory which aims to generate an apply method for the annotated trait in the corresponding companion object. For instance, given the trait A:

@factory
trait A {
  val a1: Int
}

the expected code to be generated is the following one:

object A extends Factory[A] {
  def apply(_a1: Int) = new A {
    val a1 = _a1
  }
}

Now suppose we have a trait B which inherits from A:

@factory
trait B extends A {
  val b1: String
}

which is supposed to generate:

object B extends Factory[B] {
  def apply(_a1: Int, _b1: String) = new B {
    val a1 = _a1
    val b1 = _b1
  }
}

In the latter case, I need to know which are the attributes existing at A, but I don't know how to get any information about them. While dealing with macro annotations I have only access to the B trait AST (as a ClassDef). Although its template contains references to the parents (as TypeTrees), both fields tpe and symbol are empty.

It would be great for me to get access to the A AST. However, I think that's not feasible. Therefore, any symbol or type (pointing either the parent or the current type) would be good enough.

If you want to see more implementation details, I have uploaded the project to https://github.com/jesuslopez-gonzalez/cool-factory. It can generate the apply for the local values.

1
Trees that go into macro annotation arguments are purposefully untyped. However, I think, running c.typeCheck(q"(???: <tree that represents the parent>)").tpe might provide the missing information.Eugene Burmako
@EugeneBurmakoI had already tried that this morning, but it raised an error (not sure right now, but I think it said that type tree wasn't an expression). Anyway, I'll go further with typeCheck tomorrow. By the way, while I was analyzing the macros Context API, I read "provide facilities for exploring the compiler's symbol table". What's the way to do so? Thanks!jeslg
Sure, that's because you probably did c.typeCheck(<tree that represents the parent>). c.typeCheck expects a term, so it fails if you give it a type.Eugene Burmako
The facilities for exploring the symbol table are implemented in c.mirror.Eugene Burmako
I didn't notice the q in your first comment. You were right, I was passing the raw type to c.typeCHeck instead. Finally, I have used an asInstanceOf[A] as input expression and the type is generated nicely. However, there's still one problem. I can't declare both parent and child together (like this github.com/jesuslopez-gonzalez/cool-factory/blob/master/src/…) because if I do so, typeCheck can't find the parent type. Anyway, please, add a response in order to mark it as correct.jeslg

1 Answers

12
votes

Trees that go into macro annotation arguments are purposefully untyped. However running c.typeCheck(q"(??? : <tree that represents the parent>)").tpe will provide the missing information. Don't forget to duplicate that tree before typechecking, because c.typeCheck mutates the tree in place, which might be undesireable.

In case when both parent and child are declared in the same non-toplevel scope, there will be a problem of typeCheck not seeing the parent, as c.typeCheck's in macro annotations are performed in parent lexical scope, so that annotations don't get to see half-constructed scopes. Something similar has been reported here: https://github.com/aztek/scala-workflow/issues/2#issuecomment-23947943.

The decision to exclude current scope from typechecking is not a final one. This week I'll be thinking a bit more about how macro annotations should interact with enclosing scopes, and will probably change it to do what you would like it to do. I'd do the change right now, but I need to make sure there won't be any insane behaviour arising from that change.