dynpatch_macro/
lib.rs

1//! # dynpatch-macro
2//!
3//! Procedural macros for the dynpatch hot-patching system.
4//!
5//! Provides:
6//! - `#[patch_trait]` - Mark traits as patchable interfaces
7//! - `#[patchable]` - Mark functions/methods for hot-swapping
8//! - `#[patch_impl]` - Mark patch implementations
9//! - `#[patch_entry]` - Mark patch entry points
10
11use proc_macro::TokenStream;
12use quote::quote;
13use syn::{parse_macro_input, DeriveInput, ItemFn, ItemStruct, ItemTrait};
14
15/// Mark a trait as a patchable interface
16///
17/// This generates metadata and ensures the trait is properly structured
18/// for runtime patching.
19///
20/// # Example
21///
22/// ```ignore
23/// #[patch_trait]
24/// pub trait Handler {
25///     fn handle(&self, request: Request) -> Response;
26/// }
27/// ```
28#[proc_macro_attribute]
29pub fn patch_trait(_attr: TokenStream, item: TokenStream) -> TokenStream {
30    let input = parse_macro_input!(item as ItemTrait);
31    let trait_name = &input.ident;
32    let vis = &input.vis;
33    let trait_items = &input.items;
34    let generics = &input.generics;
35    let where_clause = &input.generics.where_clause;
36
37    let expanded = quote! {
38        #vis trait #trait_name #generics #where_clause {
39            #(#trait_items)*
40        }
41
42        // Generate metadata for this trait
43        impl #trait_name {
44            pub const fn __dynpatch_trait_version() -> (u32, u32, u32) {
45                (0, 1, 0)
46            }
47
48            pub const fn __dynpatch_trait_name() -> &'static str {
49                stringify!(#trait_name)
50            }
51        }
52    };
53
54    TokenStream::from(expanded)
55}
56
57/// Mark a function as patchable
58///
59/// This transforms the function to use indirection for hot-swapping.
60///
61/// # Example
62///
63/// ```ignore
64/// #[patchable]
65/// fn handle_request(req: Request) -> Response {
66///     // implementation
67/// }
68/// ```
69#[proc_macro_attribute]
70pub fn patchable(_attr: TokenStream, item: TokenStream) -> TokenStream {
71    let input = parse_macro_input!(item as ItemFn);
72    let fn_name = &input.sig.ident;
73    let vis = &input.vis;
74    let inputs = &input.sig.inputs;
75    let output = &input.sig.output;
76    let block = &input.block;
77    let attrs = &input.attrs;
78
79    // For now, we generate a wrapper that could be swapped
80    // In a full implementation, this would set up the indirection mechanism
81    let expanded = quote! {
82        #(#attrs)*
83        #vis fn #fn_name(#inputs) #output {
84            // Original implementation
85            #block
86        }
87
88        // Metadata for this patchable function
89        const _: () = {
90            #[used]
91            #[cfg_attr(target_os = "linux", link_section = ".dynpatch")]
92            #[cfg_attr(target_os = "macos", link_section = "__DATA,__dynpatch")]
93            #[cfg_attr(target_os = "windows", link_section = ".dynpatch")]
94            static __DYNPATCH_METADATA: &str = concat!(
95                "patchable:",
96                stringify!(#fn_name)
97            );
98        };
99    };
100
101    TokenStream::from(expanded)
102}
103
104/// Mark a struct as a patch implementation
105///
106/// This generates the necessary exports and metadata for the patch.
107///
108/// # Example
109///
110/// ```ignore
111/// #[patch_impl]
112/// pub struct HandlerV2;
113///
114/// impl Handler for HandlerV2 {
115///     fn handle(&self, req: Request) -> Response {
116///         // new implementation
117///     }
118/// }
119/// ```
120#[proc_macro_attribute]
121pub fn patch_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
122    let input = parse_macro_input!(item as ItemStruct);
123    let struct_name = &input.ident;
124    let vis = &input.vis;
125    let fields = &input.fields;
126
127    let expanded = quote! {
128        #vis struct #struct_name #fields
129
130        // Generate patch metadata export
131        #[no_mangle]
132        pub extern "C" fn __dynpatch_metadata() -> dynpatch_interface::PatchMetadata {
133            dynpatch_interface::PatchMetadata::new(
134                stringify!(#struct_name).to_string(),
135                dynpatch_interface::Version::new(0, 1, 0),
136                dynpatch_interface::Version::new(0, 1, 0),
137                dynpatch_interface::compute_type_hash(
138                    stringify!(#struct_name),
139                    core::mem::size_of::<#struct_name>(),
140                    core::mem::align_of::<#struct_name>(),
141                ),
142            )
143        }
144
145        // Constructor export
146        #[no_mangle]
147        pub extern "C" fn __dynpatch_create() -> Box<#struct_name> {
148            Box::new(#struct_name::default())
149        }
150    };
151
152    TokenStream::from(expanded)
153}
154
155/// Mark patch entry point with initialization
156///
157/// # Example
158///
159/// ```ignore
160/// #[patch_entry]
161/// pub fn init() -> Result<(), String> {
162///     // initialization code
163///     Ok(())
164/// }
165/// ```
166#[proc_macro_attribute]
167pub fn patch_entry(_attr: TokenStream, item: TokenStream) -> TokenStream {
168    let input = parse_macro_input!(item as ItemFn);
169    let fn_name = &input.sig.ident;
170    let vis = &input.vis;
171    let block = &input.block;
172
173    let expanded = quote! {
174        #vis fn #fn_name() -> Result<(), String> {
175            #block
176        }
177
178        #[no_mangle]
179        pub extern "C" fn __dynpatch_entry() -> i32 {
180            match #fn_name() {
181                Ok(()) => 0,
182                Err(_) => -1,
183            }
184        }
185    };
186
187    TokenStream::from(expanded)
188}
189
190/// Derive macro for hot-reloadable configuration
191///
192/// # Example
193///
194/// ```ignore
195/// #[derive(HotConfig)]
196/// struct AppConfig {
197///     max_connections: usize,
198///     log_level: String,
199/// }
200/// ```
201#[proc_macro_derive(HotConfig)]
202pub fn derive_hot_config(input: TokenStream) -> TokenStream {
203    let input = parse_macro_input!(input as DeriveInput);
204    let name = &input.ident;
205
206    let expanded = quote! {
207        impl #name {
208            pub fn __dynpatch_type_hash() -> u64 {
209                dynpatch_interface::compute_type_hash(
210                    stringify!(#name),
211                    core::mem::size_of::<#name>(),
212                    core::mem::align_of::<#name>(),
213                )
214            }
215        }
216    };
217
218    TokenStream::from(expanded)
219}