|
1 | 1 | // SPDX-License-Identifier: GPL-2.0 |
2 | 2 |
|
3 | | -use std::collections::HashSet; |
4 | | -use std::fmt::Write; |
| 3 | +use std::{ |
| 4 | + collections::HashSet, |
| 5 | + iter::Extend, // |
| 6 | +}; |
5 | 7 |
|
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 | +}; |
7 | 23 |
|
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(); |
10 | 27 |
|
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 | + }); |
23 | 33 |
|
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; |
50 | 46 | } |
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); |
52 | 57 | } |
53 | 58 | } |
54 | 59 |
|
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 | +} |
63 | 63 |
|
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()); |
79 | 72 | } |
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 | + }); |
82 | 78 |
|
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) { |
86 | 88 | continue; |
87 | 89 | } |
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); |
89 | 94 | } |
90 | 95 | } |
91 | 96 |
|
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 | + } |
97 | 110 | } |
0 commit comments