Skip to content

Commit 1927252

Browse files
committed
Fragment Specifiers for Generic Arguments
1 parent 832a070 commit 1927252

File tree

1 file changed

+155
-0
lines changed

1 file changed

+155
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
- Feature Name: fragment-specifiers-for-generic-arguments
2+
- Start Date: 2023-05-31
3+
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Right now, there is no support for parsing the syntax of generic
10+
parameters/arguments in declarative macros. This makes it difficult to
11+
impossible to write a declarative macro that handles arbitrary generics easily.
12+
I propose adding fragment specifiers to parse a generic parameter definition as
13+
a whole and also parts of the definition.
14+
15+
# Motivation
16+
[motivation]: #motivation
17+
18+
I personally encountered this issue when attempting to write a declarative macro
19+
to implement a trait on a type. I wanted to write something like this minimal
20+
toy example:
21+
22+
```rust
23+
macro_rules! implement_debug {
24+
{ $params:generic $type:ty } => {
25+
impl $params Debug for $ty {
26+
/* .. implementation goes here .. */
27+
}
28+
};
29+
}
30+
31+
struct Container<'a, T>(&'a T);
32+
33+
implement_debug!(<'a, T: Debug + 'a> Container<'a, T>);
34+
```
35+
36+
However, with the current state of declarative macros, there's no way to parse
37+
arbitrary generic parameters in the body of the macro, forcing this macro of
38+
mine to be a procedural macro. However, declarative macros are easier to read
39+
and write, and this could be a declarative macro if there was only a way to
40+
parse generic parameters.
41+
42+
Additionally, more complicated macros want to be able to parse each parameters
43+
and its bounds for use in various places, so I'd like this as well:
44+
45+
```rust
46+
macro_rules! implement_debug {
47+
{ < $( $param:generic_param $( : $bound:generic_bound )? ),+ > $type:ty } => {
48+
impl < $( $param $( : $bound )? ),+ > Debug for $ty {
49+
/* .. implementation goes here .. */
50+
}
51+
};
52+
}
53+
54+
struct Container<'a, T>(&'a T);
55+
56+
implement_debug!(<'a, T: Debug + 'a> Container<'a, T>);
57+
```
58+
59+
Any toy example will be obviously redundant with `:generic`, but more involved
60+
macros sometimes want it.
61+
62+
# Guide-level explanation
63+
[guide-level-explanation]: #guide-level-explanation
64+
65+
When explaining fragment specifiers, this can be explained by adding the new
66+
fragment specifiers and their descriptions:
67+
68+
* `:generic`: A full set of generic parameters and their bounds (e.g. `<'a, T:
69+
'a + SomeTrait, const N: usize>`)
70+
* `:generic_param`: A generic parameter (e.g. `'a`, `T`, or `const N`)
71+
* `:generic_bound`: Bounds on a generic parameter (e.g. `'lifetime + SomeTrait`
72+
on a type or `usize` on a const parameter).
73+
* `:generic_default`: A default value for a generic type or lifetime
74+
75+
These four parameters are designed to make it easier to write declarative macros
76+
that take in generic arguments (e.g. to use with a type or function), and then
77+
use them to be generic on e.g. type definitions or `impl` blocks.
78+
79+
# Reference-level explanation
80+
[reference-level-explanation]: #reference-level-explanation
81+
82+
Exact parsing behavior:
83+
* `:generic` matches the
84+
[`GenericParams`](https://doc.rust-lang.org/reference/items/generics.html)
85+
grammar item.
86+
* `:generic_param` matches any of a lifetime, an identifier, or `const` followed
87+
by an identifier.
88+
* `:generic_bound` matches the
89+
[`TypeParamBounds`](https://doc.rust-lang.org/reference/trait-bounds.html)
90+
(can be the bounds on a type parameter) or
91+
[`LifetimeBounds`](https://doc.rust-lang.org/reference/trait-bounds.html) (can
92+
be the bounds on a lifetime parameter) grammar items, or a type (can be the
93+
bounds on a const parameter).
94+
* `:generic_default` matches a type (can be the default for a type parameter) or
95+
anything that can be default for a const parameter (a block, an identifier, or
96+
a literal).
97+
98+
All of these can potentially pick up on multiple tokens, so the result of any of
99+
these parses is undestructible in the declarative macro.
100+
101+
Following behavior:
102+
* `:generic` can be followed by anything, as it unambiguously ends when the
103+
closing `>` appears.
104+
* `:generic_param` is similarly bounded and so anything can follow it, as well.
105+
* `:generic_bound` can be followed by anything that follows `:path` and `:ty`,
106+
as it contains some repetition of lifetimes and paths separated by `+`, or a
107+
type, and `+` is already illegal following a path or type.
108+
* `:generic_default` can be followed by anything that follows `:ty`, since the
109+
other options all have an unambiguous end.
110+
111+
# Drawbacks
112+
[drawbacks]: #drawbacks
113+
114+
This provides more features which will need to be supported going forward. This
115+
also provides another features which "macros 2.0" will need to implement for
116+
parity with existing declarative macros.
117+
118+
As far as I can tell, this additional cost to implementing and maintaining extra
119+
code is the only drawback associated with this feature.
120+
121+
# Rationale and alternatives
122+
[rationale-and-alternatives]: #rationale-and-alternatives
123+
124+
We can do nothing, which provides no additional features and avoids the time and
125+
effort cost of implementing and maintaining this feature.
126+
127+
# Prior art
128+
[prior-art]: #prior-art
129+
130+
I'm not personally aware of any other languages that have similar declarative
131+
macros to Rust with an equivalent to fragment specifiers, nor any prior effort
132+
to add fragment specifiers covering this usage into Rust, so I don't know of any
133+
prior art on this topic.
134+
135+
# Unresolved questions
136+
[unresolved-questions]: #unresolved-questions
137+
138+
Are these the best fragment specifiers to use for parsing macros? I'd like
139+
opinions from other people who write macros that would want something like this
140+
about if breaking up generics into some other form might be more useful. I think
141+
this set of fragment specifiers is the best for my use cases, but other people
142+
might be interested in macros that parse differently and they might want other
143+
things instead.
144+
145+
Also, should `:generic_bound` include the preceding `:` in the match (e.g. `: 'a
146+
+ SomeTrait` in the example above)? And likewise with the `=` before
147+
`:generic_default`? I personally think it looks nicer without, but other people
148+
may disagree with my aesthetic preferences.
149+
150+
# Future possibilities
151+
[future-possibilities]: #future-possibilities
152+
153+
This could be combined with metavariable expressions for doing something with
154+
them. I don't know what expressions would be useful for this, but other people
155+
might have ideas.

0 commit comments

Comments
 (0)