| // SPDX-License-Identifier: GPL-2.0 |
| |
| use std::{ |
| collections::HashSet, |
| iter::Extend, // |
| }; |
| |
| use proc_macro2::{ |
| Ident, |
| TokenStream, // |
| }; |
| use quote::ToTokens; |
| use syn::{ |
| parse_quote, |
| Error, |
| ImplItem, |
| Item, |
| ItemImpl, |
| ItemTrait, |
| Result, |
| TraitItem, // |
| }; |
| |
| fn handle_trait(mut item: ItemTrait) -> Result<ItemTrait> { |
| let mut gen_items = Vec::new(); |
| |
| gen_items.push(parse_quote! { |
| /// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable) |
| /// attribute when implementing this trait. |
| const USE_VTABLE_ATTR: (); |
| }); |
| |
| for item in &item.items { |
| if let TraitItem::Fn(fn_item) = item { |
| let name = &fn_item.sig.ident; |
| let gen_const_name = Ident::new( |
| &format!("HAS_{}", name.to_string().to_uppercase()), |
| name.span(), |
| ); |
| |
| // We don't know on the implementation-site whether a method is required or provided |
| // so we have to generate a const for all methods. |
| let cfg_attrs = crate::helpers::gather_cfg_attrs(&fn_item.attrs); |
| let comment = |
| format!("Indicates if the `{name}` method is overridden by the implementor."); |
| gen_items.push(parse_quote! { |
| #(#cfg_attrs)* |
| #[doc = #comment] |
| const #gen_const_name: bool = false; |
| }); |
| } |
| } |
| |
| item.items.extend(gen_items); |
| Ok(item) |
| } |
| |
| fn handle_impl(mut item: ItemImpl) -> Result<ItemImpl> { |
| let mut gen_items = Vec::new(); |
| let mut defined_consts = HashSet::new(); |
| |
| // Iterate over all user-defined constants to gather any possible explicit overrides. |
| for item in &item.items { |
| if let ImplItem::Const(const_item) = item { |
| defined_consts.insert(const_item.ident.clone()); |
| } |
| } |
| |
| gen_items.push(parse_quote! { |
| const USE_VTABLE_ATTR: () = (); |
| }); |
| |
| for item in &item.items { |
| if let ImplItem::Fn(fn_item) = item { |
| let name = &fn_item.sig.ident; |
| let gen_const_name = Ident::new( |
| &format!("HAS_{}", name.to_string().to_uppercase()), |
| name.span(), |
| ); |
| // Skip if it's declared already -- this allows user override. |
| if defined_consts.contains(&gen_const_name) { |
| continue; |
| } |
| let cfg_attrs = crate::helpers::gather_cfg_attrs(&fn_item.attrs); |
| gen_items.push(parse_quote! { |
| #(#cfg_attrs)* |
| const #gen_const_name: bool = true; |
| }); |
| } |
| } |
| |
| item.items.extend(gen_items); |
| Ok(item) |
| } |
| |
| pub(crate) fn vtable(input: Item) -> Result<TokenStream> { |
| match input { |
| Item::Trait(item) => Ok(handle_trait(item)?.into_token_stream()), |
| Item::Impl(item) => Ok(handle_impl(item)?.into_token_stream()), |
| _ => Err(Error::new_spanned( |
| input, |
| "`#[vtable]` attribute should only be applied to trait or impl block", |
| ))?, |
| } |
| } |