Skip to content

Commit bdb4cff

Browse files
committed
rust: macros: rewrite Zeroable derive macro using syn
Now that `syn` and `quote` are available in the Kernel use them for the `Zeroable` derive macro instead of the current hybrid proc-macro/declarative approach. Proc-macros written using `syn` are a lot more readable and thus easier to maintain than convoluted declarative macros. An additional advantage of `syn` is the improved error reporting, here is an example: #[derive(Zeroable)] enum Foo { A, B } Prior to this patch, the error reads: error: no rules expected the token `enum` --> samples/rust/rust_minimal.rs:41:1 | 41 | enum Foo { | ^^^^ no rules expected this token in macro call | note: while trying to match `struct` --> rust/kernel/init/macros.rs:1373:22 | 1373 | $vis:vis struct $name:ident | ^^^^^^ error: aborting due to 1 previous error After this patch, the error reads: error: `Zeroable` can only be derived for structs. --> samples/rust/rust_minimal.rs:41:1 | 41 | / enum Foo { 42 | | A, 43 | | B 44 | | } | |_^ error: aborting due to 1 previous error Signed-off-by: Benno Lossin <[email protected]>
1 parent 638dc79 commit bdb4cff

File tree

3 files changed

+60
-106
lines changed

3 files changed

+60
-106
lines changed

rust/kernel/init/macros.rs

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,38 +1363,3 @@ macro_rules! __init_internal {
13631363
);
13641364
};
13651365
}
1366-
1367-
#[doc(hidden)]
1368-
#[macro_export]
1369-
macro_rules! __derive_zeroable {
1370-
(parse_input:
1371-
@sig(
1372-
$(#[$($struct_attr:tt)*])*
1373-
$vis:vis struct $name:ident
1374-
$(where $($whr:tt)*)?
1375-
),
1376-
@impl_generics($($impl_generics:tt)*),
1377-
@ty_generics($($ty_generics:tt)*),
1378-
@body({
1379-
$(
1380-
$(#[$($field_attr:tt)*])*
1381-
$field:ident : $field_ty:ty
1382-
),* $(,)?
1383-
}),
1384-
) => {
1385-
// SAFETY: Every field type implements `Zeroable` and padding bytes may be zero.
1386-
#[automatically_derived]
1387-
unsafe impl<$($impl_generics)*> $crate::init::Zeroable for $name<$($ty_generics)*>
1388-
where
1389-
$($($whr)*)?
1390-
{}
1391-
const _: () = {
1392-
fn assert_zeroable<T: ?::core::marker::Sized + $crate::init::Zeroable>() {}
1393-
fn ensure_zeroable<$($impl_generics)*>()
1394-
where $($($whr)*)?
1395-
{
1396-
$(assert_zeroable::<$field_ty>();)*
1397-
}
1398-
};
1399-
};
1400-
}

rust/macros/lib.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ mod zeroable;
1313

1414
use proc_macro::TokenStream;
1515
use quote::quote;
16-
use syn::parse_macro_input;
16+
use syn::{parse_macro_input, DeriveInput};
1717

1818
/// Declares a kernel module.
1919
///
@@ -426,5 +426,11 @@ pub fn paste(input: TokenStream) -> TokenStream {
426426
/// ```
427427
#[proc_macro_derive(Zeroable)]
428428
pub fn derive_zeroable(input: TokenStream) -> TokenStream {
429-
zeroable::derive(input.into()).into()
429+
let raw_input = input.clone().into();
430+
let input = parse_macro_input!(input as DeriveInput);
431+
match zeroable::derive(input, raw_input) {
432+
Ok(output) => output,
433+
Err(err) => err.into_compile_error(),
434+
}
435+
.into()
430436
}

rust/macros/zeroable.rs

Lines changed: 52 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,57 @@
1-
// SPDX-License-Identifier: GPL-2.0
1+
// SPDX-License-Identifier: Apache-2.0 OR MIT
22

3-
use crate::helpers::{parse_generics, Generics};
4-
use proc_macro2::{TokenStream, TokenTree};
3+
use proc_macro2::TokenStream;
54
use quote::quote;
5+
use syn::{
6+
parse_quote, Data, DataStruct, DeriveInput, Error, GenericParam, Result, TypeParam,
7+
WherePredicate,
8+
};
69

7-
pub(crate) fn derive(input: TokenStream) -> TokenStream {
8-
let (
9-
Generics {
10-
impl_generics,
11-
decl_generics: _,
12-
ty_generics,
13-
},
14-
mut rest,
15-
) = parse_generics(input);
16-
// This should be the body of the struct `{...}`.
17-
let last = rest.pop();
18-
// Now we insert `Zeroable` as a bound for every generic parameter in `impl_generics`.
19-
let mut new_impl_generics = Vec::with_capacity(impl_generics.len());
20-
// Are we inside of a generic where we want to add `Zeroable`?
21-
let mut in_generic = !impl_generics.is_empty();
22-
// Have we already inserted `Zeroable`?
23-
let mut inserted = false;
24-
// Level of `<>` nestings.
25-
let mut nested = 0;
26-
for tt in impl_generics {
27-
match &tt {
28-
// If we find a `,`, then we have finished a generic/constant/lifetime parameter.
29-
TokenTree::Punct(p) if nested == 0 && p.as_char() == ',' => {
30-
if in_generic && !inserted {
31-
new_impl_generics.extend(quote! { : ::kernel::init::Zeroable });
32-
}
33-
in_generic = true;
34-
inserted = false;
35-
new_impl_generics.push(tt);
10+
pub(crate) fn derive(
11+
DeriveInput {
12+
ident,
13+
mut generics,
14+
data,
15+
..
16+
}: DeriveInput,
17+
raw_input: TokenStream,
18+
) -> Result<TokenStream> {
19+
let zeroable_bounds = generics
20+
.params
21+
.iter()
22+
.filter_map(|p| match p {
23+
GenericParam::Type(t) => Some(t),
24+
_ => None,
25+
})
26+
.map(|TypeParam { ident, .. }| {
27+
parse_quote! { #ident: ::kernel::init::Zeroable, }
28+
})
29+
.collect::<Vec<WherePredicate>>();
30+
generics
31+
.make_where_clause()
32+
.predicates
33+
.extend(zeroable_bounds);
34+
let (impl_g, type_g, whr) = generics.split_for_impl();
35+
let Data::Struct(DataStruct { fields, .. }) = data else {
36+
return Err(Error::new_spanned(
37+
raw_input,
38+
"`Zeroable` can only be derived for structs.",
39+
));
40+
};
41+
let field_ty = fields.iter().map(|f| &f.ty);
42+
Ok(quote! {
43+
// SAFETY: Every field type implements `Zeroable` and padding bytes may be zero.
44+
#[automatically_derived]
45+
unsafe impl #impl_g ::kernel::init::Zeroable for #ident #type_g
46+
#whr
47+
{}
48+
const _: () = {
49+
fn assert_zeroable<T: ?::core::marker::Sized + ::kernel::init::Zeroable>() {}
50+
fn ensure_zeroable #impl_g ()
51+
#whr
52+
{
53+
#(assert_zeroable::<#field_ty>();)*
3654
}
37-
// If we find `'`, then we are entering a lifetime.
38-
TokenTree::Punct(p) if nested == 0 && p.as_char() == '\'' => {
39-
in_generic = false;
40-
new_impl_generics.push(tt);
41-
}
42-
TokenTree::Punct(p) if nested == 0 && p.as_char() == ':' => {
43-
new_impl_generics.push(tt);
44-
if in_generic {
45-
new_impl_generics.extend(quote! { ::kernel::init::Zeroable + });
46-
inserted = true;
47-
}
48-
}
49-
TokenTree::Punct(p) if p.as_char() == '<' => {
50-
nested += 1;
51-
new_impl_generics.push(tt);
52-
}
53-
TokenTree::Punct(p) if p.as_char() == '>' => {
54-
assert!(nested > 0);
55-
nested -= 1;
56-
new_impl_generics.push(tt);
57-
}
58-
_ => new_impl_generics.push(tt),
59-
}
60-
}
61-
assert_eq!(nested, 0);
62-
if in_generic && !inserted {
63-
new_impl_generics.extend(quote! { : ::kernel::init::Zeroable });
64-
}
65-
quote! {
66-
::kernel::__derive_zeroable!(
67-
parse_input:
68-
@sig(#(#rest)*),
69-
@impl_generics(#(#new_impl_generics)*),
70-
@ty_generics(#(#ty_generics)*),
71-
@body(#last),
72-
);
73-
}
55+
};
56+
})
7457
}

0 commit comments

Comments
 (0)