Skip to content

Commit 9762dca

Browse files
BennoLossinojeda
authored andcommitted
rust: macros: add decl_generics to parse_generics()
The generic parameters on a type definition can specify default values. Currently `parse_generics()` cannot handle this though. For example when parsing the following generics: <T: Clone, const N: usize = 0> The `impl_generics` will be set to `T: Clone, const N: usize = 0` and `ty_generics` will be set to `T, N`. Now using the `impl_generics` on an impl block: impl<$($impl_generics)*> Foo {} will result in invalid Rust code, because default values are only available on type definitions. Therefore add parsing support for generic parameter default values using a new kind of generics called `decl_generics` and change the old behavior of `impl_generics` to not contain the generic parameter default values. Now `Generics` has three fields: - `impl_generics`: the generics with bounds (e.g. `T: Clone, const N: usize`) - `decl_generics`: the generics with bounds and default values (e.g. `T: Clone, const N: usize = 0`) - `ty_generics`: contains the generics without bounds and without default values (e.g. `T, N`) `impl_generics` is designed to be used on `impl<$impl_generics>`, `decl_generics` for the type definition, so `struct Foo<$decl_generics>` and `ty_generics` whenever you use the type, so `Foo<$ty_generics>`. Here is an example that uses all three different types of generics: let (Generics { decl_generics, impl_generics, ty_generics }, rest) = parse_generics(input); quote! { struct Foo<$($decl_generics)*> { // ... } impl<$impl_generics> Foo<$ty_generics> { fn foo() { // ... } } } The next commit contains a fix to the `#[pin_data]` macro making it compatible with generic parameter default values by relying on this new behavior. Signed-off-by: Benno Lossin <benno.lossin@proton.me> Reviewed-by: Alice Ryhl <aliceryhl@google.com> Link: https://lore.kernel.org/r/20240309155243.482334-1-benno.lossin@proton.me Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
1 parent a321f3a commit 9762dca

File tree

3 files changed

+95
-30
lines changed

3 files changed

+95
-30
lines changed

rust/macros/helpers.rs

Lines changed: 93 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: GPL-2.0
22

3-
use proc_macro::{token_stream, Group, Punct, Spacing, TokenStream, TokenTree};
3+
use proc_macro::{token_stream, Group, TokenStream, TokenTree};
44

55
pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
66
if let Some(TokenTree::Ident(ident)) = it.next() {
@@ -70,15 +70,50 @@ pub(crate) fn expect_end(it: &mut token_stream::IntoIter) {
7070
}
7171
}
7272

73+
/// Parsed generics.
74+
///
75+
/// See the field documentation for an explanation what each of the fields represents.
76+
///
77+
/// # Examples
78+
///
79+
/// ```rust,ignore
80+
/// # let input = todo!();
81+
/// let (Generics { decl_generics, impl_generics, ty_generics }, rest) = parse_generics(input);
82+
/// quote! {
83+
/// struct Foo<$($decl_generics)*> {
84+
/// // ...
85+
/// }
86+
///
87+
/// impl<$impl_generics> Foo<$ty_generics> {
88+
/// fn foo() {
89+
/// // ...
90+
/// }
91+
/// }
92+
/// }
93+
/// ```
7394
pub(crate) struct Generics {
95+
/// The generics with bounds and default values (e.g. `T: Clone, const N: usize = 0`).
96+
///
97+
/// Use this on type definitions e.g. `struct Foo<$decl_generics> ...` (or `union`/`enum`).
98+
#[allow(dead_code)]
99+
pub(crate) decl_generics: Vec<TokenTree>,
100+
/// The generics with bounds (e.g. `T: Clone, const N: usize`).
101+
///
102+
/// Use this on `impl` blocks e.g. `impl<$impl_generics> Trait for ...`.
74103
pub(crate) impl_generics: Vec<TokenTree>,
104+
/// The generics without bounds and without default values (e.g. `T, N`).
105+
///
106+
/// Use this when you use the type that is declared with these generics e.g.
107+
/// `Foo<$ty_generics>`.
75108
pub(crate) ty_generics: Vec<TokenTree>,
76109
}
77110

78111
/// Parses the given `TokenStream` into `Generics` and the rest.
79112
///
80113
/// The generics are not present in the rest, but a where clause might remain.
81114
pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
115+
// The generics with bounds and default values.
116+
let mut decl_generics = vec![];
82117
// `impl_generics`, the declared generics with their bounds.
83118
let mut impl_generics = vec![];
84119
// Only the names of the generics, without any bounds.
@@ -90,10 +125,17 @@ pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
90125
let mut toks = input.into_iter();
91126
// If we are at the beginning of a generic parameter.
92127
let mut at_start = true;
93-
for tt in &mut toks {
128+
let mut skip_until_comma = false;
129+
while let Some(tt) = toks.next() {
130+
if nesting == 1 && matches!(&tt, TokenTree::Punct(p) if p.as_char() == '>') {
131+
// Found the end of the generics.
132+
break;
133+
} else if nesting >= 1 {
134+
decl_generics.push(tt.clone());
135+
}
94136
match tt.clone() {
95137
TokenTree::Punct(p) if p.as_char() == '<' => {
96-
if nesting >= 1 {
138+
if nesting >= 1 && !skip_until_comma {
97139
// This is inside of the generics and part of some bound.
98140
impl_generics.push(tt);
99141
}
@@ -105,49 +147,70 @@ pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
105147
break;
106148
} else {
107149
nesting -= 1;
108-
if nesting >= 1 {
150+
if nesting >= 1 && !skip_until_comma {
109151
// We are still inside of the generics and part of some bound.
110152
impl_generics.push(tt);
111153
}
112-
if nesting == 0 {
113-
break;
114-
}
115154
}
116155
}
117-
tt => {
156+
TokenTree::Punct(p) if skip_until_comma && p.as_char() == ',' => {
118157
if nesting == 1 {
119-
// Here depending on the token, it might be a generic variable name.
120-
match &tt {
121-
// Ignore const.
122-
TokenTree::Ident(i) if i.to_string() == "const" => {}
123-
TokenTree::Ident(_) if at_start => {
124-
ty_generics.push(tt.clone());
125-
// We also already push the `,` token, this makes it easier to append
126-
// generics.
127-
ty_generics.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
128-
at_start = false;
129-
}
130-
TokenTree::Punct(p) if p.as_char() == ',' => at_start = true,
131-
// Lifetimes begin with `'`.
132-
TokenTree::Punct(p) if p.as_char() == '\'' && at_start => {
133-
ty_generics.push(tt.clone());
134-
}
135-
_ => {}
136-
}
137-
}
138-
if nesting >= 1 {
158+
impl_generics.push(tt.clone());
139159
impl_generics.push(tt);
140-
} else if nesting == 0 {
160+
skip_until_comma = false;
161+
}
162+
}
163+
_ if !skip_until_comma => {
164+
match nesting {
141165
// If we haven't entered the generics yet, we still want to keep these tokens.
142-
rest.push(tt);
166+
0 => rest.push(tt),
167+
1 => {
168+
// Here depending on the token, it might be a generic variable name.
169+
match tt.clone() {
170+
TokenTree::Ident(i) if at_start && i.to_string() == "const" => {
171+
let Some(name) = toks.next() else {
172+
// Parsing error.
173+
break;
174+
};
175+
impl_generics.push(tt);
176+
impl_generics.push(name.clone());
177+
ty_generics.push(name.clone());
178+
decl_generics.push(name);
179+
at_start = false;
180+
}
181+
TokenTree::Ident(_) if at_start => {
182+
impl_generics.push(tt.clone());
183+
ty_generics.push(tt);
184+
at_start = false;
185+
}
186+
TokenTree::Punct(p) if p.as_char() == ',' => {
187+
impl_generics.push(tt.clone());
188+
ty_generics.push(tt);
189+
at_start = true;
190+
}
191+
// Lifetimes begin with `'`.
192+
TokenTree::Punct(p) if p.as_char() == '\'' && at_start => {
193+
impl_generics.push(tt.clone());
194+
ty_generics.push(tt);
195+
}
196+
// Generics can have default values, we skip these.
197+
TokenTree::Punct(p) if p.as_char() == '=' => {
198+
skip_until_comma = true;
199+
}
200+
_ => impl_generics.push(tt),
201+
}
202+
}
203+
_ => impl_generics.push(tt),
143204
}
144205
}
206+
_ => {}
145207
}
146208
}
147209
rest.extend(toks);
148210
(
149211
Generics {
150212
impl_generics,
213+
decl_generics,
151214
ty_generics,
152215
},
153216
rest,

rust/macros/pin_data.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub(crate) fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream {
1010
let (
1111
Generics {
1212
impl_generics,
13+
decl_generics: _,
1314
ty_generics,
1415
},
1516
rest,

rust/macros/zeroable.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub(crate) fn derive(input: TokenStream) -> TokenStream {
77
let (
88
Generics {
99
impl_generics,
10+
decl_generics: _,
1011
ty_generics,
1112
},
1213
mut rest,

0 commit comments

Comments
 (0)