Quotes API seems incomplete


Trying to write an annotation macro that modifies a class, I’ve gotten to generating the intended code just fine (as show by decompiling the file), nevertheless from the code I can’t actually see the generated members, I imagine this is because I’m not “modifying” the symbol of the resulting class, just its tree (using ClassDef.copy), now looking into the api of Symbol.newClass, it seems incomplete? I can’t specify flags for instance, there’s no specific primary constructor symbol (I imagine it’s just a decl?). I don’t know how to extend my original Class Symbol because I can’t even pass the data I get from the original symbol (and there is no Symbol.copy method).

This is in contrast with newModule which looks much more complete.

Thanks in advance.

Nobody seems to know what’s going on with Scala 3 macros :rofl: It might very well be incomplete, who knows?

This does not answer your question (sorry), but in case others come here looking, there is a work-in-progress effort by Kit Langton to demystify macros: https://macros.scala.school/ Also Advanced Programming in Scala is supposed to explain it, but it’s not finished yet.

1 Like

Then I think I’ll report a bug. The API seems very much incomplete, and nobody seems to know otherwise.

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)

Which adds a val y: String = "bacon".

And that is being applied to some class:

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?


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 :slight_smile: Which I mostly agree with.