Can a macro depends on another macro?

Hello

I have a question about macros. I'm trying to implement two macros A and B.
A adds a property (public static a: String) to the struct where it is attached.
B would need to consume that property.

For example:

@A //adds public static a: String { get } 
struct MyStruct { ... }

@B(info: MyStruct.a) // <-- does not compile: error: type 'MyStruct' has no member 'a'
struct MyOtherStruct {
   func aFunction() { 
       print(MyStruct.a) //<-- works
   }
}

Is there a way to tell the compiler to generate the code for macro A before macro B ? Or another approach to solve this dependency.

Nope, there's no way to enforce ordering like that -- it would require some global figuring out which macros can run and when (basically a build system for all the macro expansions huh!).

In this specific example what I'd suggest is the following:

  • have @A require that MyStruct conforms to some protocol, Athingy { static a: String { get } }
  • have @B(info: MyStruct.self) and when expanding strip off the .self assume it's going to have the a

I'd play around in this direction somehow, wdyt?

6 Likes

That worked, thank you !
With the extra benefit that the conformance to the new protocol can be added automatically by the macro A too.

2 Likes

Great to hear that, yay!

Alternative approach if you don't want to introduce a protocol:

@B(info: "MyStruct.a")
struct MyOtherStruct {
    // ...
}

or

@B(info: MyStruct.self)
struct MyOtherStruct {
    // ...
}

This works because, while Swift macro expansion performs type check on its input and output, it doesn't validate the code in generated function while expanding each macro. For example, if you forgot to apply macro A, the entire generated code will fail to compile, but that isn't caught when expanding macro B.

Note: this understanding is based on my experience. I didn't read it in docs, but I used this approach a lot.

@rayx You're correct. I discovered that while playing with the macro API. The arguments are passed as String and because I generate code with String literals, these can me embedded. The macro API will verify that the generated code is syntactically correct, but will not try to compile.

However, the protocol approach is cleaner that the String approach. I went with the protocol :-)

1 Like