Generating and referencing immortal metadata in macros

In embedded software, it’s fairly common to make use of anonymous static declarations of values, particularly for arrays or nested/recursive structures with the knowledge that those may even have a static lifetime.

C and C++ allow inline static declarations to be initialised with array expressions and pointers to static structure expressions and Rust supports reference and slice expressions with static lifetime from const expressions. These languages also support the construction of these statics as part of macro implementations.

In Swift, a value is only guaranteed to be immortal if it is in a static binding and those are only permitted at the top level or in declarations/extensions. If a freestanding macro wants to generate any kind of static metadata value that can be used by the code it produces, it currently can’t produce that metadata in a value with a known immortal lifetime.

I can think of two potential approaches to resolving this:

  1. I think this could be resolved by permitting local static bindings to be generated in code (which would only be able to reference other static bindings in the scope) which could be produced in the direct output of a freestanding macro.
  2. Provide some mechanism for the macros of a plugin to accumulate state for a particular set of files and allow those plugins to then produce declarations at the scopes outside those macros like at the file level or even after all evaluations in across a module

The first of these approaches I think directly addresses the needs of referencing any static data; but the second could provide some opportunities for the plugin to accumulate, order, or optimise the static data generated by all of the plugins in the module. You could imagine this being used to accumulate all of the tests in a module such that a single object referencing all of those tests is produced at build time.

Are there other contexts where this functionality might be useful?


Implementation Thoughts

Compiler plugins can effectively be stateful today by using global state in their implementation; so you could theoretically have some inner macros stash metadata when they run and collect it in a macro at an outer scope. This is probably a bad idea as you can ensure that the macros within a peer are evaluated before you or that you only consider the output of macros in the shared scope.

I think a clean way to perhaps manage accumulating state across macro invocations might be to allow plugins to contribute data to the macro expansion context (perhaps by assigning it to a key type defined by the plugin module). New ‘file-level’ and/or ‘module-level’ macros could then be introduced which are automatically triggered at the top level of a file or after all files in a module if any other macros from the same plugin are used in that scope; those could then query the expansion context to see the merged aggregation of all values produced by those macros for the key type.