[Pitch] Declaration macros

Super glad we got to decl macros, I've been longing for them for a long time :smile:

Very glad that both freestanding and attached macros are supported. Especially since these macros can't modify the attached to function, I guess this might be good enough for now...

I like the macro declarations defining what peers/members they are going to introduce, and the spellings of that look fine. I can see the benefits knowing what a macro will produce for the compiler, and it's not too hard to declare what you're going to be emitting.

:question: I'm assuming when we declare a specific member the macro is expected to emit (like rawValue), and the macro fails to emit such, that'd be a compile error -- since the macro didn't do what it claimed. And if there's any "we may be able to make some member" then it should be using .arbitrary I suppose.

:question: I wasn't entirely sure about the @declaration(.attached, members: where we attached to a variable decl. It is "members" specifically because we're emitting accessors, right? And those are "inside"

  @dictionaryStorage
  var name: String // { get set } are "inside", thus members:

:heavy_check_mark: Should a freestanding macro have "peers" specified or doesn't that really make sense... Say, if it's a #nice_description that makes a nice var description: String {} would that declare @declaration(.freestanding, peers: [.named("description")]) macro?

:white_check_mark: The showcased addCompletionHandler is very close to a practical use-case I have with interoperating between existing large/complex C++ codebases with their own concurrency mechanisms (futures). Using such type of macro we'll be able tons of boilerplate and make Swift a viable and productive citizen alongside a large existing codebase. :partying_face:

:question: I'm a bit sad about lack of ability to modify the attached to decl (though I do see the complications it'd cause). It means we can't implement a traced function:

:white_check_mark: We can actually, I missed the AttachedDeclarationExpansion.functionBody in the default design, thanks for pointing that out!

@traced func hello() {} // won't work; not allowed to modify the body.

Since a traced function needs to "startSpan()" in function prologue, and "end()" when the function returns (as well as setError when the function is actually throwing). In that sense then, we can't simulate this using an in-line freestanding macro like:

// won't work
func hello() {
  #traced("span name") // can't work, no way to set error
  // it could (probably?) emit:
  // - let span = startSpan
  // - defer { span.end() } // expression though...
  throw Boom() // bad, we can't intercept this to `span.setError` here

Plain task-locals we could help a bit; specifically in order order to un-pyramid-of-doom the Task locals use case explained here: Un-pyramid-of-doom the `with` style methods - #8 by ktoso we would be able emit the push/defer{pop} expressions a task-local needs, which is neat (using expression macros), but a traced remains impossible with either type of macro AFAICS :thinking:

I was also thinking if we could steal some ideas from aspect-oriented-programming which was a big trend some time ago in JVM land, where an aspect would do either "around", "before" or "after" inject code into functions... and the "around" would help us here, but it would probably be the same complexity as the modifying the entire function... or perhaps it might be simpler, if we didn't give the real body of the func to the macro somehow...? Either way, this sounds like a more difficult problem, that I would love to see solved, but perhaps that's a different proposal :thinking:

1 Like