xref: /linux/rust/macros/vtable.rs (revision 3ef975893041b9f826475298c802b5c046d0ba16)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 use std::{
4     collections::HashSet,
5     iter::Extend, //
6 };
7 
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 };
23 
24 fn handle_trait(mut item: ItemTrait) -> Result<ItemTrait> {
25     let mut gen_items = Vec::new();
26 
27     gen_items.push(parse_quote! {
28          /// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable)
29          /// attribute when implementing this trait.
30          const USE_VTABLE_ATTR: ();
31     });
32 
33     // Add `type OwnerModule: ModuleMetadata` as a required associated type if
34     // the trait does not already define it.
35     if !item
36         .items
37         .iter()
38         .any(|i| matches!(i, TraitItem::Type(t) if t.ident == "OwnerModule"))
39     {
40         gen_items.push(parse_quote! {
41             /// The module implementing this vtable trait.
42             ///
43             /// Automatically set to `crate::LocalModule` by the `#[vtable]`
44             /// impl macro.
45             type OwnerModule: ::kernel::ModuleMetadata;
46         });
47     }
48 
49     for item in &item.items {
50         if let TraitItem::Fn(fn_item) = item {
51             let name = &fn_item.sig.ident;
52             let gen_const_name = Ident::new(
53                 &format!("HAS_{}", name.to_string().to_uppercase()),
54                 name.span(),
55             );
56 
57             // We don't know on the implementation-site whether a method is required or provided
58             // so we have to generate a const for all methods.
59             let cfg_attrs = crate::helpers::gather_cfg_attrs(&fn_item.attrs);
60             let comment =
61                 format!("Indicates if the `{name}` method is overridden by the implementor.");
62             gen_items.push(parse_quote! {
63                 #(#cfg_attrs)*
64                 #[doc = #comment]
65                 const #gen_const_name: bool = false;
66             });
67         }
68     }
69 
70     item.items.extend(gen_items);
71     Ok(item)
72 }
73 
74 fn handle_impl(mut item: ItemImpl) -> Result<ItemImpl> {
75     let mut gen_items = Vec::new();
76     let mut defined_items = HashSet::new();
77 
78     // Iterate over all user-defined items to gather any possible explicit overrides.
79     for item in &item.items {
80         match item {
81             ImplItem::Const(const_item) => {
82                 defined_items.insert(const_item.ident.clone());
83             }
84             ImplItem::Type(type_item) => {
85                 defined_items.insert(type_item.ident.clone());
86             }
87             _ => {}
88         }
89     }
90 
91     gen_items.push(parse_quote! {
92         const USE_VTABLE_ATTR: () = ();
93     });
94 
95     // Auto-insert `type OwnerModule = crate::LocalModule` if not explicitly defined.
96     // `crate::LocalModule` resolves to the real module type (via `module!`) or a
97     // dummy fallback in non-module contexts (e.g., doctests).
98     if !defined_items.contains(&parse_quote!(OwnerModule)) {
99         gen_items.push(parse_quote! {
100             type OwnerModule = crate::LocalModule;
101         });
102     }
103 
104     for item in &item.items {
105         if let ImplItem::Fn(fn_item) = item {
106             let name = &fn_item.sig.ident;
107             let gen_const_name = Ident::new(
108                 &format!("HAS_{}", name.to_string().to_uppercase()),
109                 name.span(),
110             );
111             // Skip if it's declared already -- this allows user override.
112             if defined_items.contains(&gen_const_name) {
113                 continue;
114             }
115             let cfg_attrs = crate::helpers::gather_cfg_attrs(&fn_item.attrs);
116             gen_items.push(parse_quote! {
117                 #(#cfg_attrs)*
118                 const #gen_const_name: bool = true;
119             });
120         }
121     }
122 
123     item.items.extend(gen_items);
124     Ok(item)
125 }
126 
127 pub(crate) fn vtable(input: Item) -> Result<TokenStream> {
128     match input {
129         Item::Trait(item) => Ok(handle_trait(item)?.into_token_stream()),
130         Item::Impl(item) => Ok(handle_impl(item)?.into_token_stream()),
131         _ => Err(Error::new_spanned(
132             input,
133             "`#[vtable]` attribute should only be applied to trait or impl block",
134         ))?,
135     }
136 }
137