dynpatch-macro 0.1.0

Procedural macros for dynpatch - #[patchable], #[patch_impl], #[patch_trait]
Documentation
//! # dynpatch-macro
//!
//! Procedural macros for the dynpatch hot-patching system.
//!
//! Provides:
//! - `#[patch_trait]` - Mark traits as patchable interfaces
//! - `#[patchable]` - Mark functions/methods for hot-swapping
//! - `#[patch_impl]` - Mark patch implementations
//! - `#[patch_entry]` - Mark patch entry points

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, ItemFn, ItemStruct, ItemTrait};

/// Mark a trait as a patchable interface
///
/// This generates metadata and ensures the trait is properly structured
/// for runtime patching.
///
/// # Example
///
/// ```ignore
/// #[patch_trait]
/// pub trait Handler {
///     fn handle(&self, request: Request) -> Response;
/// }
/// ```
#[proc_macro_attribute]
pub fn patch_trait(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemTrait);
    let trait_name = &input.ident;
    let vis = &input.vis;
    let trait_items = &input.items;
    let generics = &input.generics;
    let where_clause = &input.generics.where_clause;

    let expanded = quote! {
        #vis trait #trait_name #generics #where_clause {
            #(#trait_items)*
        }

        // Generate metadata for this trait
        impl #trait_name {
            pub const fn __dynpatch_trait_version() -> (u32, u32, u32) {
                (0, 1, 0)
            }

            pub const fn __dynpatch_trait_name() -> &'static str {
                stringify!(#trait_name)
            }
        }
    };

    TokenStream::from(expanded)
}

/// Mark a function as patchable
///
/// This transforms the function to use indirection for hot-swapping.
///
/// # Example
///
/// ```ignore
/// #[patchable]
/// fn handle_request(req: Request) -> Response {
///     // implementation
/// }
/// ```
#[proc_macro_attribute]
pub fn patchable(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let fn_name = &input.sig.ident;
    let vis = &input.vis;
    let inputs = &input.sig.inputs;
    let output = &input.sig.output;
    let block = &input.block;
    let attrs = &input.attrs;

    // For now, we generate a wrapper that could be swapped
    // In a full implementation, this would set up the indirection mechanism
    let expanded = quote! {
        #(#attrs)*
        #vis fn #fn_name(#inputs) #output {
            // Original implementation
            #block
        }

        // Metadata for this patchable function
        const _: () = {
            #[used]
            #[cfg_attr(target_os = "linux", link_section = ".dynpatch")]
            #[cfg_attr(target_os = "macos", link_section = "__DATA,__dynpatch")]
            #[cfg_attr(target_os = "windows", link_section = ".dynpatch")]
            static __DYNPATCH_METADATA: &str = concat!(
                "patchable:",
                stringify!(#fn_name)
            );
        };
    };

    TokenStream::from(expanded)
}

/// Mark a struct as a patch implementation
///
/// This generates the necessary exports and metadata for the patch.
///
/// # Example
///
/// ```ignore
/// #[patch_impl]
/// pub struct HandlerV2;
///
/// impl Handler for HandlerV2 {
///     fn handle(&self, req: Request) -> Response {
///         // new implementation
///     }
/// }
/// ```
#[proc_macro_attribute]
pub fn patch_impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemStruct);
    let struct_name = &input.ident;
    let vis = &input.vis;
    let fields = &input.fields;

    let expanded = quote! {
        #vis struct #struct_name #fields

        // Generate patch metadata export
        #[no_mangle]
        pub extern "C" fn __dynpatch_metadata() -> dynpatch_interface::PatchMetadata {
            dynpatch_interface::PatchMetadata::new(
                stringify!(#struct_name).to_string(),
                dynpatch_interface::Version::new(0, 1, 0),
                dynpatch_interface::Version::new(0, 1, 0),
                dynpatch_interface::compute_type_hash(
                    stringify!(#struct_name),
                    core::mem::size_of::<#struct_name>(),
                    core::mem::align_of::<#struct_name>(),
                ),
            )
        }

        // Constructor export
        #[no_mangle]
        pub extern "C" fn __dynpatch_create() -> Box<#struct_name> {
            Box::new(#struct_name::default())
        }
    };

    TokenStream::from(expanded)
}

/// Mark patch entry point with initialization
///
/// # Example
///
/// ```ignore
/// #[patch_entry]
/// pub fn init() -> Result<(), String> {
///     // initialization code
///     Ok(())
/// }
/// ```
#[proc_macro_attribute]
pub fn patch_entry(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let fn_name = &input.sig.ident;
    let vis = &input.vis;
    let block = &input.block;

    let expanded = quote! {
        #vis fn #fn_name() -> Result<(), String> {
            #block
        }

        #[no_mangle]
        pub extern "C" fn __dynpatch_entry() -> i32 {
            match #fn_name() {
                Ok(()) => 0,
                Err(_) => -1,
            }
        }
    };

    TokenStream::from(expanded)
}

/// Derive macro for hot-reloadable configuration
///
/// # Example
///
/// ```ignore
/// #[derive(HotConfig)]
/// struct AppConfig {
///     max_connections: usize,
///     log_level: String,
/// }
/// ```
#[proc_macro_derive(HotConfig)]
pub fn derive_hot_config(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    let expanded = quote! {
        impl #name {
            pub fn __dynpatch_type_hash() -> u64 {
                dynpatch_interface::compute_type_hash(
                    stringify!(#name),
                    core::mem::size_of::<#name>(),
                    core::mem::align_of::<#name>(),
                )
            }
        }
    };

    TokenStream::from(expanded)
}