|
| 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