mirror of
https://github.com/apple/swift.git
synced 2026-10-14 15:51:20 +02:00
This adds a new kind of macro to the macro infrastructure. This macro kind is internal to the compiler, in both implementation and use: the macros can only be implemented as part of the compiler, and they can only by invoked by the compiler. It is intended solely as an implementation detail for synthesizing decls in the compiler, and in most cases won't be visible to the user at all. Other methods of synthesizing decls exist in the compiler, but unlike macros, which generate a string to be treated as a virtual file and parsed, these methods generate AST nodes directly. This has the downside of preventing source locations, since source locations are a thin abstraction over a `char*` into the source buffer: there's no way to provide a source location for something that wasn't parsed from a string. For less trivial synthesized decls it's beneficial to be less "magical". Macro expansions can be stepped through in the debugger, which is important for decls that may trap or otherwise contain control flow. They can also be expanded in IDEs, allowing users to jump to definition and see where the synthesized decl originated and figure out why it exists, and how it works. Since macros already have all the infrastructure set up for dealing with this, both in the compiler and in SourceKit, we may as well reuse that for these compiler synthesized decls. `@_SwiftifyImport` used a macro approach, but used plain Swift macros placed in the standard library. This has the unfortunate side-effect of allowing the compiler and the macro implementation fall out of sync, adding friction to updating the macro implementation since it must be both backward and forward compatible with newer and older compilers. By statically linking the macro implementation in the compiler, and synthesizing the macro declaration node, the compiler can fully control implementation, interface and invocation. Another lesson learnt from `@_SwiftifyImport` was the limitations of not being able to access semantic information (most importantly, types), since Swift macros operate purely on syntax. ClangImporter would have to pre-compute information, generate a string parameter, and pass that to the macro, for every piece of semantic information required by the macro. This is unwieldy, splits implementation between the compiler and the macro more than necessary, and requires eagerly computing the info regardless of whether the macro ends up expanded or not. One such example is `counted_by`'s count expression, which ClangImporter converts from a C expression to a string in Swift syntax, and passes that string along to the macro, even though generating Swift syntax is the macro's whole job. By making the macro implementation part of the compiler itself, we can simply pass the AST node to the macro implementation, and it can do all the semantic lookups it wants. It can also reuse utilities in the compiler, e.g. for determining whether a variable name requires escaping, or how to merge availability attributes, instead of reimplementing it purely based on syntactic information. While `@_SwiftifyImport` probably ought to be rewritten as an internal macro one day, it currently works and is already in use. Instead the first adopter will be a new macro, which will act as the opposite of `@_SwiftifyImport`. This will be added in a follow-up PR. Since this is not exposed to anything outside the compiler, this initial implementation is catered only to what is needed for the first macro. It only supports peer macros for now, and all the macro gets passed is a pointer to the node it's attached to. If future macros need something more we can easily add more and update all internal macro implementations (even though I expect the number to be quite low) in sync without breaking anything.