bolt_attribute_bolt_component/
lib.rs

1use proc_macro::TokenStream;
2
3use quote::quote;
4use syn::{
5    parse_macro_input, parse_quote, Attribute, DeriveInput, Lit, Meta, MetaList, MetaNameValue,
6    NestedMeta,
7};
8
9use bolt_utils::add_bolt_metadata;
10
11/// This Component attribute is used to automatically generate the seed and size functions
12///
13/// The component_id can be used to define the seed used to generate the PDA which stores the component data.
14/// The macro also adds the InitSpace and Default derives to the struct.
15///
16/// #[component]
17/// #[derive(Default, Copy)]
18/// pub struct Position {
19///     pub x: i64,
20///     pub y: i64,
21///     pub z: i64,
22/// }
23/// ```
24#[proc_macro_attribute]
25pub fn component(attr: TokenStream, item: TokenStream) -> TokenStream {
26    let mut input = parse_macro_input!(item as DeriveInput);
27    let mut component_id_value = None;
28    let mut delegate_set = false;
29
30    if !attr.is_empty() {
31        let attr_meta = parse_macro_input!(attr as Meta);
32        delegate_set = is_delegate_set(&attr_meta);
33        component_id_value = match attr_meta {
34            Meta::Path(_) => None,
35            Meta::NameValue(meta_name_value) => extract_component_id(&meta_name_value),
36            Meta::List(meta_list) => {
37                if !delegate_set {
38                    delegate_set = is_delegate_set(&Meta::List(meta_list.clone()));
39                }
40                find_component_id_in_list(meta_list)
41            }
42        };
43    }
44
45    let component_id_value = component_id_value.unwrap_or_else(|| "".to_string());
46
47    let additional_macro: Attribute = parse_quote! { #[account] };
48    let additional_derives: Attribute = parse_quote! { #[derive(InitSpace)] };
49    input.attrs.push(additional_derives);
50
51    let new_fn = define_new_fn(&input);
52
53    add_bolt_metadata(&mut input);
54
55    let name = &input.ident;
56    let component_name = ligen_ir::Identifier::new(name.to_string()).to_snake_case();
57    let component_name = syn::Ident::new(&component_name.to_string(), input.ident.span());
58
59    let bolt_program = if delegate_set {
60        quote! {
61            #[delegate(#name)]
62            #[bolt_program(#name)]
63            pub mod #component_name {
64                use super::*;
65            }
66        }
67    } else {
68        quote! {
69            #[bolt_program(#name)]
70            pub mod #component_name {
71                use super::*;
72            }
73        }
74    };
75
76    let expanded = quote! {
77        #bolt_program
78
79        #additional_macro
80        #input
81
82        #new_fn
83
84        #[automatically_derived]
85        impl ComponentTraits for #name {
86            fn seed() -> &'static [u8] {
87                #component_id_value.as_bytes()
88            }
89
90            fn size() -> usize {
91                8 + <#name>::INIT_SPACE
92            }
93        }
94
95    };
96    expanded.into()
97}
98
99/// Create a fn `new` to initialize the struct without bolt_metadata field
100fn define_new_fn(input: &DeriveInput) -> proc_macro2::TokenStream {
101    let struct_name = &input.ident;
102    let init_struct_name = syn::Ident::new(&format!("{}Init", struct_name), struct_name.span());
103
104    if let syn::Data::Struct(ref data) = input.data {
105        if let syn::Fields::Named(ref fields) = data.fields {
106            // Generate fields for the init struct
107            let init_struct_fields = fields.named.iter().map(|f| {
108                let name = &f.ident;
109                let ty = &f.ty;
110                quote! { pub #name: #ty }
111            });
112
113            // Generate struct initialization code using the init struct
114            let struct_init_fields = fields.named.iter().map(|f| {
115                let name = &f.ident;
116                quote! { #name: init_struct.#name }
117            });
118
119            // Generate the new function and the init struct
120            let gen = quote! {
121                // Define a new struct to hold initialization parameters
122                pub struct #init_struct_name {
123                    #(#init_struct_fields),*
124                }
125
126                impl #struct_name {
127                    pub fn new(init_struct: #init_struct_name) -> Self {
128                        Self {
129                            #(#struct_init_fields,)*
130                            bolt_metadata: BoltMetadata::default(),
131                        }
132                    }
133                }
134            };
135            return gen;
136        }
137    }
138    quote! {}
139}
140
141fn is_delegate_set(meta: &Meta) -> bool {
142    match meta {
143        Meta::Path(path) => path.is_ident("delegate"),
144        Meta::List(meta_list) => meta_list.nested.iter().any(|nested_meta| {
145            if let NestedMeta::Meta(Meta::Path(path)) = nested_meta {
146                path.is_ident("delegate")
147            } else {
148                false
149            }
150        }),
151        _ => false,
152    }
153}
154
155fn extract_component_id(meta_name_value: &MetaNameValue) -> Option<String> {
156    if meta_name_value.path.is_ident("component_id") {
157        if let Lit::Str(lit) = &meta_name_value.lit {
158            return Some(lit.value());
159        }
160    }
161    None
162}
163
164fn find_component_id_in_list(meta_list: MetaList) -> Option<String> {
165    meta_list.nested.into_iter().find_map(|nested_meta| {
166        if let NestedMeta::Meta(Meta::NameValue(meta_name_value)) = nested_meta {
167            extract_component_id(&meta_name_value)
168        } else {
169            None
170        }
171    })
172}