Skip to content

Commit 8938c4f

Browse files
committed
rust: macros: rewrite #[vtable] using syn
Now that `syn` and `quote` are available in the Kernel use them for the `#[vtable]` proc-macro attribute instead of the current string based proc-macro approach. Proc-macros written using `syn` are a lot more readable and thus easier to maintain than proc-macros written using string manipulation. An additional advantage of `syn` is the improved error reporting, here is an example: #[vtable] const _: () = (); Prior to this patch, the error reads: error: custom attribute panicked --> samples/rust/rust_minimal.rs:40:1 | 40 | #[vtable] | ^^^^^^^^^ | = help: message: #[vtable] attribute should only be applied to trait or impl block error: aborting due to 1 previous error After this patch, the error reads: error: `#[vtable]` expects a `trait` or `impl`. --> samples/rust/rust_minimal.rs:41:1 | 41 | const _: () = (); | ^^^^^^^^^^^^^^^^^ error: aborting due to 1 previous error Signed-off-by: Benno Lossin <[email protected]>
1 parent ed6e1a8 commit 8938c4f

File tree

2 files changed

+60
-81
lines changed

2 files changed

+60
-81
lines changed

rust/macros/lib.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod vtable;
1212
mod zeroable;
1313

1414
use proc_macro::TokenStream;
15+
use syn::parse_macro_input;
1516

1617
/// Declares a kernel module.
1718
///
@@ -148,8 +149,15 @@ pub fn module(ts: TokenStream) -> TokenStream {
148149
///
149150
/// [`kernel::error::VTABLE_DEFAULT_ERROR`]: ../kernel/error/constant.VTABLE_DEFAULT_ERROR.html
150151
#[proc_macro_attribute]
151-
pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
152-
vtable::vtable(attr.into(), ts.into()).into()
152+
pub fn vtable(args: TokenStream, input: TokenStream) -> TokenStream {
153+
parse_macro_input!(args as syn::parse::Nothing);
154+
match syn::parse(input.clone()) {
155+
Ok(input) => vtable::vtable(input)
156+
.unwrap_or_else(|e| e.into_compile_error())
157+
.into(),
158+
// Item parsing falied, let the compiler handle the nice error output.
159+
Err(_) => input,
160+
}
153161
}
154162

155163
/// Concatenate two identifiers.

rust/macros/vtable.rs

Lines changed: 50 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,67 @@
11
// SPDX-License-Identifier: GPL-2.0
22

3-
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
3+
use proc_macro2::{Ident, TokenStream};
4+
use quote::quote;
45
use std::collections::HashSet;
5-
use std::fmt::Write;
6+
use syn::{parse_quote, Error, ImplItem, Item, Result, TraitItem};
67

7-
pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
8-
let mut tokens: Vec<_> = ts.into_iter().collect();
9-
10-
// Scan for the `trait` or `impl` keyword.
11-
let is_trait = tokens
12-
.iter()
13-
.find_map(|token| match token {
14-
TokenTree::Ident(ident) => match ident.to_string().as_str() {
15-
"trait" => Some(true),
16-
"impl" => Some(false),
17-
_ => None,
18-
},
19-
_ => None,
20-
})
21-
.expect("#[vtable] attribute should only be applied to trait or impl block");
22-
23-
// Retrieve the main body. The main body should be the last token tree.
24-
let body = match tokens.pop() {
25-
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group,
26-
_ => panic!("cannot locate main body of trait or impl block"),
27-
};
28-
29-
let mut body_it = body.stream().into_iter();
30-
let mut functions = Vec::new();
31-
let mut consts = HashSet::new();
32-
while let Some(token) = body_it.next() {
33-
match token {
34-
TokenTree::Ident(ident) if ident == "fn" => {
35-
let fn_name = match body_it.next() {
36-
Some(TokenTree::Ident(ident)) => ident.to_string(),
37-
// Possibly we've encountered a fn pointer type instead.
38-
_ => continue,
39-
};
40-
functions.push(fn_name);
41-
}
42-
TokenTree::Ident(ident) if ident == "const" => {
43-
let const_name = match body_it.next() {
44-
Some(TokenTree::Ident(ident)) => ident.to_string(),
45-
// Possibly we've encountered an inline const block instead.
46-
_ => continue,
47-
};
48-
consts.insert(const_name);
8+
macro_rules! handle_item {
9+
($item_type:ident, $item:ident, $with_comment:literal) => {{
10+
let mut functions = Vec::new();
11+
let mut consts = HashSet::new();
12+
for item in &$item.items {
13+
match item {
14+
$item_type::Fn(fn_) => functions.push(fn_.sig.ident.clone()),
15+
$item_type::Const(const_) => {
16+
consts.insert(const_.ident.clone());
17+
}
18+
_ => {}
4919
}
50-
_ => (),
5120
}
52-
}
53-
54-
let mut const_items;
55-
if is_trait {
56-
const_items = "
57-
/// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable)
58-
/// attribute when implementing this trait.
59-
const USE_VTABLE_ATTR: ();
60-
"
61-
.to_owned();
62-
63-
for f in functions {
64-
let gen_const_name = format!("HAS_{}", f.to_uppercase());
21+
let item = if $with_comment {
22+
parse_quote!(
23+
/// A marker to prevent implementors from forgetting to use the [`#[vtable]`](vtable)
24+
/// attribute macro when implementing this trait.
25+
const USE_VTABLE_ATTR: () = ();
26+
)
27+
} else {
28+
parse_quote!(const USE_VTABLE_ATTR: () = ();)
29+
};
30+
$item.items.push(item);
31+
for func in functions {
32+
let gen_const_name = Ident::new(
33+
&format!("HAS_{}", func.to_string().to_uppercase()),
34+
func.span(),
35+
);
6536
// Skip if it's declared already -- this allows user override.
6637
if consts.contains(&gen_const_name) {
6738
continue;
6839
}
6940
// We don't know on the implementation-site whether a method is required or provided
7041
// so we have to generate a const for all methods.
71-
write!(
72-
const_items,
73-
"/// Indicates if the `{f}` method is overridden by the implementor.
74-
const {gen_const_name}: bool = false;",
75-
)
76-
.unwrap();
42+
let comment = format!("Indicates if the `{func}` method is overridden by the implementor.");
43+
let item = if $with_comment {
44+
parse_quote!(
45+
#[doc = #comment]
46+
const #gen_const_name: bool = false;
47+
)
48+
} else {
49+
parse_quote!(const #gen_const_name: bool = false;)
50+
};
51+
$item.items.push(item);
7752
consts.insert(gen_const_name);
7853
}
79-
} else {
80-
const_items = "const USE_VTABLE_ATTR: () = ();".to_owned();
54+
quote!(#$item)
55+
}}
56+
}
8157

82-
for f in functions {
83-
let gen_const_name = format!("HAS_{}", f.to_uppercase());
84-
if consts.contains(&gen_const_name) {
85-
continue;
86-
}
87-
write!(const_items, "const {gen_const_name}: bool = true;").unwrap();
88-
}
58+
pub(crate) fn vtable(input: Item) -> Result<TokenStream> {
59+
match input {
60+
Item::Impl(mut impl_) => Ok(handle_item!(ImplItem, impl_, false)),
61+
Item::Trait(mut trait_) => Ok(handle_item!(TraitItem, trait_, true)),
62+
other => Err(Error::new_spanned(
63+
other,
64+
"`#[vtable]` expects a `trait` or `impl`.",
65+
)),
8966
}
90-
91-
let new_body = vec![const_items.parse().unwrap(), body.stream()]
92-
.into_iter()
93-
.collect();
94-
tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body)));
95-
tokens.into_iter().collect()
9667
}

0 commit comments

Comments
 (0)