|
1 | 1 | // SPDX-License-Identifier: GPL-2.0
|
2 | 2 |
|
3 |
| -use proc_macro2::{Delimiter, Group, TokenStream, TokenTree}; |
| 3 | +use proc_macro2::{Ident, TokenStream}; |
| 4 | +use quote::quote; |
4 | 5 | use std::collections::HashSet;
|
5 |
| -use std::fmt::Write; |
| 6 | +use syn::{parse_quote, Error, ImplItem, Item, Result, TraitItem}; |
6 | 7 |
|
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 | + _ => {} |
49 | 19 | }
|
50 |
| - _ => (), |
51 | 20 | }
|
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 | + ); |
65 | 36 | // Skip if it's declared already -- this allows user override.
|
66 | 37 | if consts.contains(&gen_const_name) {
|
67 | 38 | continue;
|
68 | 39 | }
|
69 | 40 | // We don't know on the implementation-site whether a method is required or provided
|
70 | 41 | // 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); |
77 | 52 | consts.insert(gen_const_name);
|
78 | 53 | }
|
79 |
| - } else { |
80 |
| - const_items = "const USE_VTABLE_ATTR: () = ();".to_owned(); |
| 54 | + quote!(#$item) |
| 55 | + }} |
| 56 | +} |
81 | 57 |
|
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 | + )), |
89 | 66 | }
|
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() |
96 | 67 | }
|
0 commit comments