|
| 1 | +- Feature Name: `macro_attr` |
| 2 | +- Start Date: 2024-09-20 |
| 3 | +- RFC PR: [rust-lang/rfcs#3697](https://github.com/rust-lang/rfcs/pull/3697) |
| 4 | +- Rust Issue: [rust-lang/rust#143547](https://github.com/rust-lang/rust/issues/143547) |
| 5 | + |
| 6 | +# Summary |
| 7 | +[summary]: #summary |
| 8 | + |
| 9 | +Support defining `macro_rules!` macros that work as attribute macros. |
| 10 | + |
| 11 | +# Motivation |
| 12 | +[motivation]: #motivation |
| 13 | + |
| 14 | +Many crates provide attribute macros. Today, this requires defining proc |
| 15 | +macros, in a separate crate, typically with several additional dependencies |
| 16 | +adding substantial compilation time, and typically guarded by a feature that |
| 17 | +users need to remember to enable. |
| 18 | + |
| 19 | +However, many common cases of attribute macros don't require any more power |
| 20 | +than an ordinary `macro_rules!` macro. Supporting these common cases would |
| 21 | +allow many crates to avoid defining proc macros, reduce dependencies and |
| 22 | +compilation time, and provide these macros unconditionally without requiring |
| 23 | +the user to enable a feature. |
| 24 | + |
| 25 | +The [`macro_rules_attribute`](https://crates.io/crates/macro_rules_attribute) |
| 26 | +crate defines proc macros that allow invoking declarative macros as attributes, |
| 27 | +demonstrating a demand for this. This feature would allow defining such |
| 28 | +attributes without requiring proc macros at all, and would support the same |
| 29 | +invocation syntax as a proc macro. |
| 30 | + |
| 31 | +Some macros in the ecosystem already implement the equivalent of attribute |
| 32 | +using declarative macros; for instance, see |
| 33 | +[smol-macros](https://crates.io/crates/smol-macros), which provides a `main!` |
| 34 | +macro and recommends using it with `macro_rules_attribute::apply`. |
| 35 | + |
| 36 | +# Guide-level explanation |
| 37 | +[guide-level-explanation]: #guide-level-explanation |
| 38 | + |
| 39 | +When defining a `macro_rules!` macro, you can prefix some of the macro's rules |
| 40 | +with `attr(...)` to allow using the macro as an attribute. The |
| 41 | +arguments to the attribute, if any, are parsed by the *MacroMatcher* in the |
| 42 | +first set of parentheses; the second *MacroMatcher* parses the entire construct |
| 43 | +the attribute was applied to. The resulting macro will work anywhere an |
| 44 | +attribute currently works. |
| 45 | + |
| 46 | +```rust |
| 47 | +macro_rules! main { |
| 48 | + attr() ($func:item) => { make_async_main!($func) }; |
| 49 | + attr(threads = $threads:literal) ($func:item) => { make_async_main!($threads, $func) }; |
| 50 | +} |
| 51 | + |
| 52 | +#[main] |
| 53 | +async fn main() { ... } |
| 54 | + |
| 55 | +#[main(threads = 42)] |
| 56 | +async fn main() { ... } |
| 57 | +``` |
| 58 | + |
| 59 | +Attribute macros defined using `macro_rules!` follow the same scoping rules as |
| 60 | +any other macro, and may be invoked by any path that resolves to them. |
| 61 | + |
| 62 | +An attribute macro must not require itself for resolution, either directly or |
| 63 | +indirectly (e.g. applied to a containing module or item). |
| 64 | + |
| 65 | +Note that a single macro can have both `attr` and non-`attr` rules. Attribute |
| 66 | +invocations can only match the `attr` rules, and non-attribute invocations can |
| 67 | +only match the non-`attr` rules. This allows adding `attr` rules to an existing |
| 68 | +macro without breaking backwards compatibility. |
| 69 | + |
| 70 | +An attribute macro may emit code containing another attribute, including one |
| 71 | +provided by an attribute macro. An attribute macro may use this to recursively |
| 72 | +invoke itself. |
| 73 | + |
| 74 | +An `attr` rule may be prefixed with `unsafe`. Invoking an attribute macro in a |
| 75 | +way that makes use of a rule declared with `unsafe attr` requires the unsafe |
| 76 | +attribute syntax `#[unsafe(attribute_name)]`. |
| 77 | + |
| 78 | +# Reference-level explanation |
| 79 | +[reference-level-explanation]: #reference-level-explanation |
| 80 | + |
| 81 | +The grammar for macros is extended as follows: |
| 82 | + |
| 83 | +> _MacroRule_ :\ |
| 84 | +> ( `unsafe`<sup>?</sup> `attr` _MacroMatcher_ )<sup>?</sup> _MacroMatcher_ `=>` _MacroTranscriber_ |
| 85 | +
|
| 86 | +The first _MacroMatcher_ matches the attribute's arguments, which will be an |
| 87 | +empty token tree if either not present (`#[myattr]`) or empty (`#[myattr()]`). |
| 88 | +The second _MacroMatcher_ matches the entire construct the attribute was |
| 89 | +applied to, receiving precisely what a proc-macro-based attribute would in the |
| 90 | +same place. |
| 91 | + |
| 92 | +Only a rule matching both the arguments to the attribute and the construct the |
| 93 | +attribute was applied to will apply. Note that the captures in both |
| 94 | +`MacroMatcher`s share the same namespace; attempting to use the same name for |
| 95 | +two captures will give a "duplicate matcher binding" error. |
| 96 | + |
| 97 | +An attribute macro invocation that uses an `unsafe attr` rule will produce an |
| 98 | +error if invoked without using the `unsafe` attribute syntax. An attribute |
| 99 | +macro invocation that uses an `attr` rule will trigger the "unused unsafe" lint |
| 100 | +if invoked using the `unsafe` attribute syntax. A single attribute macro may |
| 101 | +have both `attr` and `unsafe attr` rules, such as if only some invocations are |
| 102 | +unsafe. |
| 103 | + |
| 104 | +This grammar addition is backwards compatible: previously, a _MacroRule_ could |
| 105 | +only start with `(`, `[`, or `{`, so the parser can easily distinguish rules |
| 106 | +that start with `attr` or `unsafe`. |
| 107 | + |
| 108 | +Attribute macros declared using `macro_rules!` are |
| 109 | +[active](https://doc.rust-lang.org/reference/attributes.html#active-and-inert-attributes), |
| 110 | +just like those declared using proc macros. |
| 111 | + |
| 112 | +Adding `attr` rules to an existing macro is a semver-compatible change. |
| 113 | + |
| 114 | +If a user invokes a macro as an attribute and that macro does not have any |
| 115 | +`attr` rules, the compiler should give a clear error stating that the macro is |
| 116 | +not usable as an attribute because it does not have any `attr` rules. |
| 117 | + |
| 118 | +# Drawbacks |
| 119 | +[drawbacks]: #drawbacks |
| 120 | + |
| 121 | +This feature will not be sufficient for *all* uses of proc macros in the |
| 122 | +ecosystem, and its existence may create social pressure for crate maintainers |
| 123 | +to switch even if the result is harder to maintain. |
| 124 | + |
| 125 | +Before stabilizing this feature, we should receive feedback from crate |
| 126 | +maintainers, and potentially make further improvements to `macro_rules` to make |
| 127 | +it easier to use for their use cases. This feature will provide motivation to |
| 128 | +evaluate many new use cases that previously weren't written using |
| 129 | +`macro_rules`, and we should consider quality-of-life improvements to better |
| 130 | +support those use cases. |
| 131 | + |
| 132 | +# Rationale and alternatives |
| 133 | +[rationale-and-alternatives]: #rationale-and-alternatives |
| 134 | + |
| 135 | +Adding this feature will allow many crates in the ecosystem to drop their proc |
| 136 | +macro crates and corresponding dependencies, and decrease their build times. |
| 137 | + |
| 138 | +This will also give attribute macros access to the `$crate` mechanism to refer |
| 139 | +to the defining crate, which is simpler than mechanisms currently used in proc |
| 140 | +macros to achieve the same goal. |
| 141 | + |
| 142 | +Macros defined this way can more easily support caching, as they cannot depend |
| 143 | +on arbitrary unspecified inputs. |
| 144 | + |
| 145 | +Crates could instead define `macro_rules!` macros and encourage users to invoke |
| 146 | +them using existing syntax like `macroname! { ... }`. This would provide the |
| 147 | +same functionality, but would not support the same syntax people are accustomed |
| 148 | +to, and could not maintain semver compatibility with an existing |
| 149 | +proc-macro-based attribute. |
| 150 | + |
| 151 | +We could require the `!` in attribute macros (`#[myattr!]` or similar). |
| 152 | +However, proc-macro-based attribute macros do not require this, and this would |
| 153 | +not allow declarative attribute macros to be fully compatible with |
| 154 | +proc-macro-based attribute macros. |
| 155 | + |
| 156 | +Many macros will want to parse their arguments and separately parse the |
| 157 | +construct they're applied to, rather than a combinatorial explosion of both. |
| 158 | +This problem is not unique to attribute macros. In both cases, the standard |
| 159 | +solution is to parse one while carrying along the other. |
| 160 | + |
| 161 | +We could leave out support for writing a function-like macro and an attribute |
| 162 | +macro with the same name. However, this would prevent crates from preserving |
| 163 | +backwards compatibility when adding attribute support to an existing |
| 164 | +function-like macro. |
| 165 | + |
| 166 | +Instead of or in addition to marking the individual rules, we could mark the |
| 167 | +whole macro with `#[attribute_macro]` or similar, and allow having an attribute |
| 168 | +macro and a non-attribute macro with the same name. |
| 169 | + |
| 170 | +We could include another `=>` or other syntax between the first and second |
| 171 | +macro matchers. |
| 172 | + |
| 173 | +We could use `attribute` rather than `attr`. Rust usually avoids abbreviating |
| 174 | +except for the most common constructs; however, `cfg_attr` provides precedent |
| 175 | +for this abbreviation, and `attr` appears repeatedly in multiple rules which |
| 176 | +motivates abbreviating it. |
| 177 | + |
| 178 | +# Prior art |
| 179 | +[prior-art]: #prior-art |
| 180 | + |
| 181 | +We have had proc-macro-based attribute macros for a long time, and the |
| 182 | +ecosystem makes extensive use of them. |
| 183 | + |
| 184 | +The [`macro_rules_attribute`](https://crates.io/crates/macro_rules_attribute) |
| 185 | +crate defines proc macros that allow invoking declarative macros as attributes, |
| 186 | +demonstrating a demand for this. This feature would allow defining such |
| 187 | +attributes without requiring proc macros at all, and would support the same |
| 188 | +invocation syntax as a proc macro. |
| 189 | + |
| 190 | +Some macros in the ecosystem already implement the equivalent of attribute |
| 191 | +using declarative macros; for instance, see |
| 192 | +[smol-macros](https://crates.io/crates/smol-macros), which provides a `main!` |
| 193 | +macro and recommends using it with `macro_rules_attribute::apply`. |
| 194 | + |
| 195 | +# Unresolved questions |
| 196 | +[unresolved-questions]: #unresolved-questions |
| 197 | + |
| 198 | +Is an attribute macro allowed to recursively invoke itself by emitting the |
| 199 | +attribute in its output? If there is no technical issue with allowing this, then |
| 200 | +we should do so, to allow simple recursion (e.g. handling defaults by invoking |
| 201 | +the same rule as if they were explicitly specified). |
| 202 | + |
| 203 | +Are there any places where we currently allow an attribute, but where |
| 204 | +implementation considerations make it difficult to allow a `macro_rules` |
| 205 | +attribute? (For instance, places where we currently allow attributes but don't |
| 206 | +allow proc-macro attributes.) |
| 207 | + |
| 208 | +Before stabilizing this feature, we should make sure it doesn't produce wildly |
| 209 | +worse error messages in common cases. |
| 210 | + |
| 211 | +Before stabilizing this feature, we should receive feedback from crate |
| 212 | +maintainers, and potentially make further improvements to `macro_rules` to make |
| 213 | +it easier to use for their use cases. This feature will provide motivation to |
| 214 | +evaluate many new use cases that previously weren't written using |
| 215 | +`macro_rules`, and we should consider quality-of-life improvements to better |
| 216 | +support those use cases. |
| 217 | + |
| 218 | +# Future possibilities |
| 219 | +[future-possibilities]: #future-possibilities |
| 220 | + |
| 221 | +We should provide a way to define `derive` macros declaratively, as well. |
| 222 | + |
| 223 | +We should provide a way for `macro_rules!` macros to provide better error |
| 224 | +reporting, with spans, rather than just pointing to the macro. |
| 225 | + |
| 226 | +We may want to provide more fine-grained control over the requirement for |
| 227 | +`unsafe`, to make it easier for attribute macros to be safe in some |
| 228 | +circumstances and unsafe in others (e.g. unsafe only if a given parameter is |
| 229 | +provided). |
| 230 | + |
| 231 | +As people test this feature and run into limitations of `macro_rules!` parsing, |
| 232 | +we should consider additional features to make this easier to use for various |
| 233 | +use cases. |
| 234 | + |
| 235 | +Some use cases involve multiple attribute macros that users expect to be able |
| 236 | +to apply in any order. For instance, `#[test]` and `#[should_panic]` can appear |
| 237 | +on the same function in any order. Implementing that via this mechanism for |
| 238 | +attribute macros would require making both of those attributes into macros that |
| 239 | +both do all the parsing regardless of which got invoked first, likely by |
| 240 | +invoking a common helper. We should consider if we consider that mechanism |
| 241 | +sufficient, or if we should provide another mechanism for a set of related |
| 242 | +attribute macros to appear in any order. |
| 243 | + |
| 244 | +If it turns out many users of attribute macros want to emit new tokens but |
| 245 | +leave the tokens they were applied to unmodified, we may want to have a |
| 246 | +convenient mechanism for that. |
0 commit comments