Skip to content

Commit 9ef4dc5

Browse files
authored
Merge pull request #3697 from joshtriplett/declarative-attribute-macros.md
Declarative `macro_rules!` attribute macros - `macro_attr`
2 parents 6c4a20c + f4bfd55 commit 9ef4dc5

File tree

1 file changed

+246
-0
lines changed

1 file changed

+246
-0
lines changed
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
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+
> &nbsp;&nbsp; ( `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

Comments
 (0)