Skip to content

Commit ba17aad

Browse files
BennoLossinfbq
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 <[email protected]> Reviewed-by: Martin Rodriguez Reboredo <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 0d01533 commit ba17aad

File tree

3 files changed

+94
-30
lines changed

3 files changed

+94
-30
lines changed

rust/macros/helpers.rs

Lines changed: 92 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,8 +70,40 @@ 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+
pub(crate) decl_generics: Vec<TokenTree>,
99+
/// The generics with bounds (e.g. `T: Clone, const N: usize`).
100+
///
101+
/// Use this on `impl` blocks e.g. `impl<$impl_generics> Trait for ...`.
74102
pub(crate) impl_generics: Vec<TokenTree>,
103+
/// The generics without bounds and without default values (e.g. `T, N`).
104+
///
105+
/// Use this when you use the type that is declared with these generics e.g.
106+
/// `Foo<$ty_generics>`.
75107
pub(crate) ty_generics: Vec<TokenTree>,
76108
}
77109

@@ -81,6 +113,8 @@ pub(crate) struct Generics {
81113
pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
82114
// `impl_generics`, the declared generics with their bounds.
83115
let mut impl_generics = vec![];
116+
// The generics with bounds and default values.
117+
let mut decl_generics = vec![];
84118
// Only the names of the generics, without any bounds.
85119
let mut ty_generics = vec![];
86120
// Tokens not related to the generics e.g. the `where` token and definition.
@@ -90,10 +124,17 @@ pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
90124
let mut toks = input.into_iter();
91125
// If we are at the beginning of a generic parameter.
92126
let mut at_start = true;
93-
for tt in &mut toks {
127+
let mut skip_until_comma = false;
128+
while let Some(tt) = toks.next() {
129+
if nesting == 1 && matches!(&tt, TokenTree::Punct(p) if p.as_char() == '>') {
130+
// Found the end of the generics.
131+
break;
132+
} else if nesting >= 1 {
133+
decl_generics.push(tt.clone());
134+
}
94135
match tt.clone() {
95136
TokenTree::Punct(p) if p.as_char() == '<' => {
96-
if nesting >= 1 {
137+
if nesting >= 1 && !skip_until_comma {
97138
// This is inside of the generics and part of some bound.
98139
impl_generics.push(tt);
99140
}
@@ -105,49 +146,70 @@ pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
105146
break;
106147
} else {
107148
nesting -= 1;
108-
if nesting >= 1 {
149+
if nesting >= 1 && !skip_until_comma {
109150
// We are still inside of the generics and part of some bound.
110151
impl_generics.push(tt);
111152
}
112-
if nesting == 0 {
113-
break;
114-
}
115153
}
116154
}
117-
tt => {
155+
TokenTree::Punct(p) if skip_until_comma && p.as_char() == ',' => {
118156
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-
}
157+
impl_generics.push(TokenTree::Punct(p.clone()));
158+
ty_generics.push(TokenTree::Punct(p));
159+
skip_until_comma = false;
137160
}
138-
if nesting >= 1 {
139-
impl_generics.push(tt);
140-
} else if nesting == 0 {
161+
}
162+
tt if !skip_until_comma => {
163+
match nesting {
141164
// If we haven't entered the generics yet, we still want to keep these tokens.
142-
rest.push(tt);
165+
0 => rest.push(tt),
166+
1 => {
167+
// Here depending on the token, it might be a generic variable name.
168+
match tt {
169+
TokenTree::Ident(i) if at_start && i.to_string() == "const" => {
170+
let Some(name) = toks.next() else {
171+
// Parsing error.
172+
break;
173+
};
174+
impl_generics.push(TokenTree::Ident(i));
175+
impl_generics.push(name.clone());
176+
ty_generics.push(name.clone());
177+
decl_generics.push(name);
178+
at_start = false;
179+
}
180+
tt @ TokenTree::Ident(_) if at_start => {
181+
impl_generics.push(tt.clone());
182+
ty_generics.push(tt);
183+
at_start = false;
184+
}
185+
TokenTree::Punct(p) if p.as_char() == ',' => {
186+
impl_generics.push(TokenTree::Punct(p.clone()));
187+
ty_generics.push(TokenTree::Punct(p));
188+
at_start = true;
189+
}
190+
// Lifetimes begin with `'`.
191+
TokenTree::Punct(p) if p.as_char() == '\'' && at_start => {
192+
ty_generics.push(TokenTree::Punct(p.clone()));
193+
impl_generics.push(TokenTree::Punct(p));
194+
}
195+
// Generics can have default values, we skip these.
196+
TokenTree::Punct(p) if p.as_char() == '=' => {
197+
skip_until_comma = true;
198+
}
199+
tt => impl_generics.push(tt),
200+
}
201+
}
202+
_ => impl_generics.push(tt),
143203
}
144204
}
205+
_ => {}
145206
}
146207
}
147208
rest.extend(toks);
148209
(
149210
Generics {
150211
impl_generics,
212+
decl_generics,
151213
ty_generics,
152214
},
153215
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)