goldy_derive 0.1.0

Derive macros for goldy (LayoutCheckable, etc.)
Documentation
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};

/// Derive a `LAYOUT_CHECK` constant for `#[repr(C)]` structs.
///
/// Generates a `const LAYOUT_CHECK: goldy::LayoutCheck<'static>` that captures
/// each field's name, offset, and size — ready to pass to
/// [`ShaderModule::from_slang_with_options`](goldy::ShaderModule::from_slang_with_options).
///
/// # Example
///
/// ```rust,ignore
/// #[derive(LayoutCheckable)]
/// #[repr(C)]
/// struct SceneUniforms {
///     projection: [[f32; 4]; 4],
///     modelview:  [[f32; 4]; 4],
///     time:       f32,
/// }
///
/// // Now use SceneUniforms::LAYOUT_CHECK
/// ```
#[proc_macro_derive(LayoutCheckable)]
pub fn derive_layout_checkable(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let name_str = name.to_string();

    let fields = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(named) => &named.named,
            _ => {
                return syn::Error::new_spanned(name, "LayoutCheckable requires named fields")
                    .to_compile_error()
                    .into();
            }
        },
        _ => {
            return syn::Error::new_spanned(name, "LayoutCheckable can only be derived on structs")
                .to_compile_error()
                .into();
        }
    };

    let field_entries = fields.iter().map(|f| {
        let ident = f.ident.as_ref().unwrap();
        let ident_str = ident.to_string();
        let ty = &f.ty;
        quote! {
            (#ident_str, std::mem::offset_of!(#name, #ident), std::mem::size_of::<#ty>())
        }
    });

    let n = fields.len();

    let expanded = quote! {
        impl #name {
            /// Struct layout descriptor for Slang reflection validation.
            ///
            /// Auto-generated by `#[derive(LayoutCheckable)]`.
            pub const LAYOUT_CHECK: goldy::LayoutCheck<'static> = goldy::LayoutCheck {
                type_name: #name_str,
                rust_size: std::mem::size_of::<#name>(),
                rust_fields: {
                    const FIELDS: [(&str, usize, usize); #n] = [
                        #(#field_entries),*
                    ];
                    &FIELDS
                },
            };
        }
    };

    expanded.into()
}