SE-0397: Freestanding declaration macros

Hello, Swift community.

The review of SE-0397: Freestanding declaration macros begins now and runs through May 8th, 2023. Please see the vision for macros for more background about how the Language Workgroup believes macros fit into the language.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager via the forum messaging feature or email. When contacting the review manager directly, please put "SE-0397" in the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

https://github.com/apple/swift-evolution/blob/main/process.md

Thank you in advance for contributing to this review.

John McCall
Review Manager

29 Likes

I don't have a lot of experience with macros in other languages. So I apologize if the answers to these questions/comments are obvious to others.

I think I understand the "declaration" role. But I find the examples of implementing #warning and #error as a bit confusing in that light, because those are not declarations. Those are diagnostics. Should there be a "diagnostic" macro role?

Also, the fact that the macro can have diagnostic side-effects seems a bit unexpected to me. The macro returns an array of [DeclSyntax]. But it also can mutate the context it's passed. Maybe this is ok, but it seems to be a side-effect. Is that side-effect ok?

On the second point, I think the idea is that generating diagnostics is something that any macro should be able to do, so there isn’t any reason to call it out.

The purpose of tracking other side-effects is to know when exactly the implementation needs to evaluate the macro, so that we aren’t forced to eagerly evaluate macros to do any sort of incremental type-checking (e.g. when resolving things in an IDE). But if you’re interested in getting all of the diagnostics for a module, you have to expand all the macros anyway because the code emitted by the macro can result in diagnostics even if the macro doesn’t emit one directly.

4 Likes

As for the name, this is because a declaration macro can appear (in theory) anywhere a declaration can: inside a function, outside a function, but not directly in the middle of an expression. This is by contrast with expression macros and attached macros. It’s true that “attached macros” aren’t called “attached attribute macros”, though, so maybe “freestanding macros” is sufficient?

I see, that makes sense. Thank you

If I understand Doug's taxonomy correctly, expression macros are also "freestanding" in that they aren't attached to a specific declaration. This affects their syntax: both are written with a # prefix. In principle I suppose you could have an expression macro attached to a variable or parameter that rewrote the initializer or argument expression; that macro would presumably be written with @. (I believe there's no inherent need for a macro that rewrites an initializer, however, since that can be done with an ordinary attached macro.)

And yes, the difference is that freestanding declaration macros serve as a (possibly empty) sequence of declarations while freestanding expression macros serve as an expression.

4 Likes

LGTM! As I mentioned on Slack just now, the main use-case that interests me is the gyb one and it appears to be covered here. I don't really mind how it's implemented, so long as I can use it. I know that's not very useful feedback, but hopefully it's encouragement enough to go ahead with it. Thanks for your efforts in pushing these forward

A short review due to lack of time: This proposal looks relatively straightforward and good. So it gets a +1 from me…

This proposal is fantastic. There are so many things this will enable once codeitem macros are also added. Without codeitem macros, the functionality may be somewhat limited.

I read the proposal, however I did not try to implement anything yet. I don't have any major criticisms or suggestions to add, only endorsement. Bikeshedding: free keyword instead of freestanding?

The first use case that came to mind was generating bindings for Metal kernel arguments. I would like to try giving the macro the name of a function, load the appropriate Metal file, parse it with a C++ parser, and generate a structure to expose those bindings to Swift.

How do freestanding macros interact with attached macros? Can I stick an attached macro on top of a freestanding macro to add more functionality?

Freestanding macros are going to be incredibly powerful when codeitem macros are introduced. For example, it looks like it would be possible to generate a Swift wrapper to a Metal compute kernel to dispatch it on a MTLComputeCommandEncoder just from the string function name argument to the macro. Or perhaps I am missing something?

Freestanding macros will be interesting for Core Data or even interacting with SQL (and other) databases.

5 Likes

I would like to try giving the macro the name of a function, load the appropriate Metal file...

As someone currently working on a project using Metal, this is a fantastic idea. But I have some questions about compiler performance (to the authors of this pitch, and perhaps the designers of macros in swift more generally, not necessarily to @timdecode).

Letting macros do file I/O seems like it could have a major negative impact on compiler performance, especially in WMO, as the macro might have to be evaluated multiple times per compilation of the module. I'm not familiar with how macros are invoked by the compiler, but is it possible to store the results of evaluating the macro in a way that can be shared between parsings of a given swift file? Are macros allowed to generate some form of derived data to aid in speeding up performance over multiple compilations?

Strong +1 to the pitch itself, can't wait to use this. I found the JSON example especially creative, seems like a great quick & dirty way to get started interacting with an API that serves JSON.

4 Likes

Cool idea! (I'll get back to the "load the appropriate Metal file" part in a bit)

I hadn't thought about this before. My expectation is that one can put an attached macro on top of a freestanding macro, and that the syntax node provided to the attached macro's expansion operation would be the freestanding macro use itself (e.g., #jsonCodable("...")).

I think you could do that with code-item macros; in truth, I've been fishing for examples for code-item macros, because they seem obviously useful, and yet I don't have uses in hand. With freestanding declaration macros as proposed, you could probably define a function for the Swift wrapper, and then calling that function would do the dispatch for you.

Right. At this point, we do not have any way for macros to do file I/O: they run in a sandbox that doesn't allow arbitrary file access. This is intentional, because when we do introduce the ability to access files, we want to make it something where the compiler (and build system in general) knows up front what files can be accessed, and will vend the contents of those files. That way...

Yes to both questions. By having the build system aware of the files that can be accessed by macros, and the compiler be in charge of vending the contents of those files, we can employ caching to eliminate redundant computation and have macros participate in incremental builds.

Doug

5 Likes

because when we do introduce the ability to access files

Glad to see this is being considered as part of the medium/long-term plan for macros. Are any of these plans documented anywhere? I didn't see anything in the vision for macros document.

1 Like

@Douglas_Gregor it would be really great if you could update Swift Macro Examples to build using the latest trunk development toolchain. Thanks!

Sure thing! Update to track changes to swift-syntax by DougGregor · Pull Request #25 · DougGregor/swift-macro-examples · GitHub

5 Likes

SE-0397 has been accepted in principle. The author has revised the proposal to address the interaction with attributes and modifiers on the freestanding macro site, and the review has been extended to address that narrow question. Please take any further discussion to the new review thread.

Thank you for reading and participating in this review.

John McCall
Review Manager

5 Likes