Skip to content

Commit ecb3287

Browse files
nbdd0121BennoLossin
authored andcommitted
rust: macros: convert #[vtable] macro to use syn
`#[vtable]` is converted to use syn. This is more robust than the previous heuristic-based searching of defined methods and functions. When doing so, the trait and impl are split into two code paths as the types are distinct when parsed by `syn`. Reviewed-by: Tamir Duberstein <tamird@gmail.com> Signed-off-by: Gary Guo <gary@garyguo.net>
1 parent 802b7d4 commit ecb3287

2 files changed

Lines changed: 98 additions & 80 deletions

File tree

rust/macros/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ mod vtable;
2222

2323
use proc_macro::TokenStream;
2424

25+
use syn::parse_macro_input;
26+
2527
/// Declares a kernel module.
2628
///
2729
/// The `type` argument should be a type which implements the [`Module`]
@@ -204,8 +206,11 @@ pub fn module(ts: TokenStream) -> TokenStream {
204206
///
205207
/// [`kernel::error::VTABLE_DEFAULT_ERROR`]: ../kernel/error/constant.VTABLE_DEFAULT_ERROR.html
206208
#[proc_macro_attribute]
207-
pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
208-
vtable::vtable(attr.into(), ts.into()).into()
209+
pub fn vtable(attr: TokenStream, input: TokenStream) -> TokenStream {
210+
parse_macro_input!(attr as syn::parse::Nothing);
211+
vtable::vtable(parse_macro_input!(input))
212+
.unwrap_or_else(|e| e.into_compile_error())
213+
.into()
209214
}
210215

211216
/// Export a function so that C code can call it via a header file.

rust/macros/vtable.rs

Lines changed: 91 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,110 @@
11
// SPDX-License-Identifier: GPL-2.0
22

3-
use std::collections::HashSet;
4-
use std::fmt::Write;
3+
use std::{
4+
collections::HashSet,
5+
iter::Extend, //
6+
};
57

6-
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
8+
use proc_macro2::{
9+
Ident,
10+
TokenStream, //
11+
};
12+
use quote::ToTokens;
13+
use syn::{
14+
parse_quote,
15+
Error,
16+
ImplItem,
17+
Item,
18+
ItemImpl,
19+
ItemTrait,
20+
Result,
21+
TraitItem, //
22+
};
723

8-
pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
9-
let mut tokens: Vec<_> = ts.into_iter().collect();
24+
fn handle_trait(mut item: ItemTrait) -> Result<ItemTrait> {
25+
let mut gen_items = Vec::new();
26+
let mut gen_consts = HashSet::new();
1027

11-
// Scan for the `trait` or `impl` keyword.
12-
let is_trait = tokens
13-
.iter()
14-
.find_map(|token| match token {
15-
TokenTree::Ident(ident) => match ident.to_string().as_str() {
16-
"trait" => Some(true),
17-
"impl" => Some(false),
18-
_ => None,
19-
},
20-
_ => None,
21-
})
22-
.expect("#[vtable] attribute should only be applied to trait or impl block");
28+
gen_items.push(parse_quote! {
29+
/// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable)
30+
/// attribute when implementing this trait.
31+
const USE_VTABLE_ATTR: ();
32+
});
2333

24-
// Retrieve the main body. The main body should be the last token tree.
25-
let body = match tokens.pop() {
26-
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group,
27-
_ => panic!("cannot locate main body of trait or impl block"),
28-
};
29-
30-
let mut body_it = body.stream().into_iter();
31-
let mut functions = Vec::new();
32-
let mut consts = HashSet::new();
33-
while let Some(token) = body_it.next() {
34-
match token {
35-
TokenTree::Ident(ident) if ident == "fn" => {
36-
let fn_name = match body_it.next() {
37-
Some(TokenTree::Ident(ident)) => ident.to_string(),
38-
// Possibly we've encountered a fn pointer type instead.
39-
_ => continue,
40-
};
41-
functions.push(fn_name);
42-
}
43-
TokenTree::Ident(ident) if ident == "const" => {
44-
let const_name = match body_it.next() {
45-
Some(TokenTree::Ident(ident)) => ident.to_string(),
46-
// Possibly we've encountered an inline const block instead.
47-
_ => continue,
48-
};
49-
consts.insert(const_name);
34+
for item in &item.items {
35+
if let TraitItem::Fn(fn_item) = item {
36+
let name = &fn_item.sig.ident;
37+
let gen_const_name = Ident::new(
38+
&format!("HAS_{}", name.to_string().to_uppercase()),
39+
name.span(),
40+
);
41+
// Skip if it's declared already -- this can happen if `#[cfg]` is used to selectively
42+
// define functions.
43+
// FIXME: `#[cfg]` should be copied and propagated to the generated consts.
44+
if gen_consts.contains(&gen_const_name) {
45+
continue;
5046
}
51-
_ => (),
47+
48+
// We don't know on the implementation-site whether a method is required or provided
49+
// so we have to generate a const for all methods.
50+
let comment =
51+
format!("Indicates if the `{name}` method is overridden by the implementor.");
52+
gen_items.push(parse_quote! {
53+
#[doc = #comment]
54+
const #gen_const_name: bool = false;
55+
});
56+
gen_consts.insert(gen_const_name);
5257
}
5358
}
5459

55-
let mut const_items;
56-
if is_trait {
57-
const_items = "
58-
/// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable)
59-
/// attribute when implementing this trait.
60-
const USE_VTABLE_ATTR: ();
61-
"
62-
.to_owned();
60+
item.items.extend(gen_items);
61+
Ok(item)
62+
}
6363

64-
for f in functions {
65-
let gen_const_name = format!("HAS_{}", f.to_uppercase());
66-
// Skip if it's declared already -- this allows user override.
67-
if consts.contains(&gen_const_name) {
68-
continue;
69-
}
70-
// We don't know on the implementation-site whether a method is required or provided
71-
// so we have to generate a const for all methods.
72-
write!(
73-
const_items,
74-
"/// Indicates if the `{f}` method is overridden by the implementor.
75-
const {gen_const_name}: bool = false;",
76-
)
77-
.unwrap();
78-
consts.insert(gen_const_name);
64+
fn handle_impl(mut item: ItemImpl) -> Result<ItemImpl> {
65+
let mut gen_items = Vec::new();
66+
let mut defined_consts = HashSet::new();
67+
68+
// Iterate over all user-defined constants to gather any possible explicit overrides.
69+
for item in &item.items {
70+
if let ImplItem::Const(const_item) = item {
71+
defined_consts.insert(const_item.ident.clone());
7972
}
80-
} else {
81-
const_items = "const USE_VTABLE_ATTR: () = ();".to_owned();
73+
}
74+
75+
gen_items.push(parse_quote! {
76+
const USE_VTABLE_ATTR: () = ();
77+
});
8278

83-
for f in functions {
84-
let gen_const_name = format!("HAS_{}", f.to_uppercase());
85-
if consts.contains(&gen_const_name) {
79+
for item in &item.items {
80+
if let ImplItem::Fn(fn_item) = item {
81+
let name = &fn_item.sig.ident;
82+
let gen_const_name = Ident::new(
83+
&format!("HAS_{}", name.to_string().to_uppercase()),
84+
name.span(),
85+
);
86+
// Skip if it's declared already -- this allows user override.
87+
if defined_consts.contains(&gen_const_name) {
8688
continue;
8789
}
88-
write!(const_items, "const {gen_const_name}: bool = true;").unwrap();
90+
gen_items.push(parse_quote! {
91+
const #gen_const_name: bool = true;
92+
});
93+
defined_consts.insert(gen_const_name);
8994
}
9095
}
9196

92-
let new_body = vec![const_items.parse().unwrap(), body.stream()]
93-
.into_iter()
94-
.collect();
95-
tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body)));
96-
tokens.into_iter().collect()
97+
item.items.extend(gen_items);
98+
Ok(item)
99+
}
100+
101+
pub(crate) fn vtable(input: Item) -> Result<TokenStream> {
102+
match input {
103+
Item::Trait(item) => Ok(handle_trait(item)?.into_token_stream()),
104+
Item::Impl(item) => Ok(handle_impl(item)?.into_token_stream()),
105+
_ => Err(Error::new_spanned(
106+
input,
107+
"`#[vtable]` attribute should only be applied to trait or impl block",
108+
))?,
109+
}
97110
}

0 commit comments

Comments
 (0)