The API is definitely low level, and arguably incomplete. Definitely missing documentation and clarity.
Difficult to tell what you mean by I can’t actually see the generated members,
. I’m guess that the macro does something like:
class AddY extends MacroAnnotation {
import scala.quoted.*
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = {
import quotes.reflect.*
println(tree.show(using Printer.TreeStructure))
tree match {
case ClassDef(name, ctr, parents, self, body) => {
val ySym = Symbol.newVal(tree.symbol, "y", TypeRepr.of[String], Flags.EmptyFlags, Symbol.noSymbol)
val yDef = ValDef(ySym, Some(Literal(StringConstant("bacon"))))
val newClassDef = ClassDef.copy(tree)(name, ctr, parents, self, body :+ yDef)
List(newClassDef)
}
}
}
}
Which adds a val y: String = "bacon"
.
And that is being applied to some class:
@experimental
@AddY
class Foo {
val x: Int = 10
}
And then there is some code that constructs a Foo
and attempts to access the added y
:
val foo = new Foo
println(foo.y) // oh no! This fails!
Sound about right?
Per
Macro annotations cannot add defs,vals, classes that are then visible to user code.
So… what can they do?
Let’s examine the above macro, is there a hidden cost to that macro?
Take the question “what are the members of foo?”. To answer that question requires going through an abstraction. This can be done via tooling (query the semantic db / tasty) or by browsing the code but regardless, when we get to “AddY” that must be de-abstracted somehow to find out “val y” was added.
Alternatively, if there was a deriveY
macro that similarly generated an implementation of y: String
but instead was a method:
class Foo {
val x: Int = 10
val y: String = deriveY
}
This directly contains the full methods. No declarations users should be aware of sneakily added by a macro.
At least… that’s the argument as I understand
Which I mostly agree with.