Files
linux-stable-mirror/rust/macros/vtable.rs
Gary Guo 779f6e3e1c rust: macros: support #[cfg] properly in #[vtable] macro.
Currently, we generate `HAS_` constants as long as the definition exists in
the source, regardless if it is cfg-ed out or not.

Currently, uses of `#[cfg]` present in both trait and impl, so it is not a
problem; however if only the impl side uses `#[cfg]` then `HAS_` constants
will incorrectly be true while it shouldnt't.

With `syn` support, we can now implement `#[cfg]` handling properly by
propagating the `#[cfg]` attributes to the constants.

Signed-off-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Link: https://patch.msgid.link/20260113170529.2240744-1-gary@kernel.org
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
2026-01-28 13:44:17 +01:00

106 lines
3.0 KiB
Rust

// 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",
))?,
}
}