Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Declarative macro_rules! attribute macros #3697

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Changes from 14 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
661912e
Declarative `macro_rules!` attribute macros
joshtriplett Sep 21, 2024
6c9944b
RFC 3697
joshtriplett Sep 21, 2024
6fd82e8
Attribute macros are active
joshtriplett Sep 21, 2024
2b4989b
Fix typo
joshtriplett Sep 21, 2024
258608e
Remove the extra `=>`
joshtriplett Sep 21, 2024
fa7820f
Abbreviate to `attr`, with `cfg_attr` as precedent
joshtriplett Sep 22, 2024
0e4e46c
Mention `$crate`
joshtriplett Sep 24, 2024
0fea475
Caching
joshtriplett Sep 24, 2024
b11f474
Add an alternative way of marking and handling attr/non-attr macros
joshtriplett Sep 25, 2024
d13e2ba
Clarify justification for having a single macro support attr and non-…
joshtriplett Sep 25, 2024
0900f9f
Add notes about backwards compatibility of adding `attr` rules
joshtriplett Sep 25, 2024
26e6969
Note that both `MacroMatcher`s share the same namespace
joshtriplett Sep 26, 2024
5cfb620
Document the cases of no arguments and empty arguments
joshtriplett Sep 26, 2024
10f011d
Future possibilities: better error reporting
joshtriplett Sep 26, 2024
1cfec3d
Add drawbacks section mentioning impact on crate maintainers
joshtriplett Sep 30, 2024
b7d4e06
Expand future work
joshtriplett Sep 30, 2024
0110363
Future possibilities: consider the case of multiple attributes in any…
joshtriplett Oct 1, 2024
23cd82f
Recursion
joshtriplett Oct 2, 2024
9fbf852
Clarify recursive invocation
joshtriplett Oct 2, 2024
95838c4
Fix typo
joshtriplett Oct 2, 2024
ff26c82
Add unresolved question to make sure we don't have awful error messages
joshtriplett Oct 2, 2024
25ab000
Word-wrapping
joshtriplett Oct 22, 2024
66ac59f
Support unsafe attributes
joshtriplett Oct 22, 2024
5fdd0d8
Copy a drawback to the unresolved questions section
joshtriplett Oct 22, 2024
f2eb3ed
Fix missing backquote
joshtriplett Oct 28, 2024
27efc29
Example
joshtriplett Oct 28, 2024
e43905e
Document that an attribute macro may invoke another, or itself
joshtriplett Nov 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions text/3697-declarative-attribute-macros.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
- Feature Name: `declarative_attribute_macros`
- Start Date: 2024-09-20
- RFC PR: [rust-lang/rfcs#3697](https://github.com/rust-lang/rfcs/pull/3697)
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)

# Summary
[summary]: #summary

Support defining `macro_rules!` macros that work as attribute macros.

# Motivation
[motivation]: #motivation

Many crates provide attribute macros. Today, this requires defining proc
macros, in a separate crate, typically with several additional dependencies
adding substantial compilation time, and typically guarded by a feature that
users need to remember to enable.

However, many common cases of attribute macros don't require any more power
than an ordinary `macro_rules!` macro. Supporting these common cases would
allow many crates to avoid defining proc macros, reduce dependencies and
compilation time, and provide these macros unconditionally without requiring
the user to enable a feature.

# Guide-level explanation
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
[guide-level-explanation]: #guide-level-explanation

When defining a `macro_rules!` macro, you can prefix some of the macro's rules
with `attr(...)` to allow using the macro as an attribute. The
arguments to the attribute, if any, are parsed by the *MacroMatcher* in the
first set of parentheses; the second *MacroMatcher* parses the entire construct
the attribute was applied to. The resulting macro will work anywhere an
attribute currently works.

```rust
macro_rules! main {
attr() ($func:item) => { make_async_main!($func) };
attr(threads = $threads:literal) ($func:item) => { make_async_main!($threads, $func) };
}

#[main]
async fn main() { ... }
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved

#[main(threads = 42)]
async fn main() { ... }
```

Attribute macros defined using `macro_rules!` follow the same scoping rules as
any other macro, and may be invoked by any path that resolves to them.

An attribute macro must not require itself for resolution, either directly or
indirectly (e.g. applied to a containing module or item).

Note that a single macro can have both `attr` and non-`attr` rules. Attribute
invocations can only match the `attr` rules, and non-attribute invocations can
only match the non-`attr` rules. This allows adding `attr` rules to an existing
macro without breaking backwards compatibility.

For simplicity, an attribute macro may not recursively invoke its `attr` rules;
to recurse, invoke a non-`attr` rule or another macro.
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved

The grammar for macros is extended as follows:

> _MacroRule_ :\
> &nbsp;&nbsp; ( `attr` _MacroMatcher_ )<sup>?</sup> _MacroMatcher_ `=>` _MacroTranscriber_

The first _MacroMatcher_ matches the attribute's arguments, which will be an
empty token tree if either not present (`#[myattr]`) or empty (`#[myattr()]`).
The second _MacroMatcher_ matches the entire construct the attribute was
applied to, receiving precisely what a proc-macro-based attribute would in the
same place.

Only a rule matching both the arguments to the attribute and the construct the
attribute was applied to will apply. Note that the captures in both
`MacroMatcher`s share the same namespace; attempting to use the same name for
two captures will give a "duplicate matcher binding" error.

This grammar addition is backwards compatible: previously, a _MacroRule_ could
only start with `(`, `[`, or `{`, so the parser can easily distinguish the
identifier `attr`.

Attribute macros declared using `macro_rules!` are
[active](https://doc.rust-lang.org/reference/attributes.html#active-and-inert-attributes),
just like those declared using proc macros.
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved

Adding `attr` rules to an existing macro is a semver-compatible change.

# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved

Adding this feature will allow many crates in the ecosystem to drop their proc
macro crates and corresponding dependencies, and decrease their build times.

This will also give attribute macros access to the `$crate` mechanism to refer
to the defining crate, which is simpler than mechanisms currently used in proc
macros to achieve the same goal.

Macros defined this way can more easily support caching, as they cannot depend
on arbitrary unspecified inputs.

Crates could instead define `macro_rules!` macros and encourage users to invoke
them using existing syntax like `macroname! { ... }`. This would provide the
same functionality, but would not support the same syntax people are accustomed
to, and could not maintain semver compatibility with an existing
proc-macro-based attribute.

We could require the `!` in attribute macros (`#[myattr!]` or similar).
However, proc-macro-based attribute macros do not require this, and this would
not allow declarative attribute macros to be fully compatible with
proc-macro-based attribute macros.

Many macros will want to parse their arguments and separately parse the
construct they're applied to, rather than a combinatorial explosion of both.
This problem is not unique to attribute macros. In both cases, the standard
solution is to parse one while carrying along the other.

We could leave out support for writing a function-like macro and an attribute
macro with the same name. However, this would prevent crates from preserving
backwards compatibility when adding attribute support to an existing
function-like macro.

Instead of or in addition to marking the individual rules, we could mark the
whole macro with `#[attribute_macro]` or similar, and allow having an attribute
macro and a non-attribute macro with the same name.

We could include another `=>` or other syntax between the first and second
macro matchers.

We could use `attribute` rather than `attr`. Rust usually avoids abbreviating
except for the most common constructs; however, `cfg_attr` provides precedent
for this abbreviation, and `attr` appears repeatedly in multiple rules which
motivates abbreviating it.

# Prior art
[prior-art]: #prior-art

We have had proc-macro-based attribute macros for a long time, and the
ecosystem makes extensive use of them.

The [`macro_rules_attribute`](https://crates.io/crates/macro_rules_attribute)
crate defines proc macros that allow invoking declarative macros as attributes,
demonstrating a demand for this. This feature would allow defining such
attributes without requiring proc macros at all, and would support the same
invocation syntax as a proc macro.

# Future possibilities
[future-possibilities]: #future-possibilities

We should provide a way to define `derive` macros declaratively, as well.

We should provide a way for `macro_rules!` macros to provide better error
reporting, with spans, rather than just pointing to the macro.