tryparse-derive 0.4.2

Derive macros for tryparse
Documentation
//! Derive macros for tryparse
//!
//! This crate provides the `LlmDeserialize` derive macro for automatically
//! generating fuzzy deserialization logic from Rust types.

mod attributes;
mod enum_gen;
mod struct_gen;
mod union_gen;

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput};

use attributes::has_union_attribute;
use enum_gen::generate_enum_deserialize;
use struct_gen::generate_struct_deserialize;
use union_gen::generate_union_deserialize;

/// Derives the `LlmDeserialize` trait for structs and enums.
///
/// This macro generates a custom deserialization implementation using BAML's
/// algorithms for fuzzy field matching and type coercion.
///
/// # Features
///
/// - **Fuzzy field matching**: Handles different naming conventions (userName ↔ user_name)
/// - **Fuzzy enum matching**: Case-insensitive, substring, and edit-distance matching for variants
/// - **Union types**: Score-based variant selection with `#[llm(union)]`
/// - **Optional fields**: Automatic handling of `Option<T>` fields
/// - **Transformation tracking**: Records all coercions applied during parsing
///
/// # Example
///
/// ```ignore
/// use tryparse::deserializer::LlmDeserialize;
///
/// #[derive(LlmDeserialize)]
/// struct User {
///     name: String,
///     age: u32,
///     email: Option<String>, // Optional field
/// }
///
/// // Handles messy input like:
/// // {"userName": "Alice", "age": "30"}  // camelCase + string number
/// ```
///
/// # Union Types
///
/// ```ignore
/// #[derive(LlmDeserialize)]
/// #[llm(union)]
/// enum Value {
///     Number(i64),
///     Text(String),
/// }
///
/// // Automatically picks the best matching variant
/// ```
#[proc_macro_derive(LlmDeserialize, attributes(llm))]
pub fn derive_llm_deserialize(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let name = &input.ident;
    let generics = &input.generics;
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    match &input.data {
        Data::Struct(data_struct) => {
            let deserialize_impl = generate_struct_deserialize(name, data_struct);
            let name_str = name.to_string();

            let expanded = quote! {
                // Compile-time check: LlmDeserialize requires serde::Deserialize
                const _: () = {
                    fn __assert_deserialize_impl<__T: ::serde::de::DeserializeOwned>() {}
                    fn __check_deserialize_bound() {
                        __assert_deserialize_impl::<#name #ty_generics>();
                    }

                    // Provide a helpful error message
                    #[doc = concat!(
                        "LlmDeserialize requires serde::Deserialize. ",
                        "Add `#[derive(serde::Deserialize)]` to `", #name_str, "`."
                    )]
                    const __LLMDESERIALIZE_REQUIRES_SERDE: () = ();
                };

                impl #impl_generics ::tryparse::deserializer::LlmDeserialize for #name #ty_generics #where_clause {
                    #deserialize_impl
                }
            };

            TokenStream::from(expanded)
        }
        Data::Enum(data_enum) => {
            // Check if this is a union enum (has #[llm(union)] attribute)
            let is_union = has_union_attribute(&input.attrs);

            let deserialize_impl = if is_union {
                generate_union_deserialize(name, data_enum, &input.attrs)
            } else {
                generate_enum_deserialize(name, data_enum, &input.attrs)
            };

            let name_str = name.to_string();

            let expanded = quote! {
                // Compile-time check: LlmDeserialize requires serde::Deserialize
                const _: () = {
                    fn __assert_deserialize_impl<__T: ::serde::de::DeserializeOwned>() {}
                    fn __check_deserialize_bound() {
                        __assert_deserialize_impl::<#name #ty_generics>();
                    }

                    // Provide a helpful error message
                    #[doc = concat!(
                        "LlmDeserialize requires serde::Deserialize. ",
                        "Add `#[derive(serde::Deserialize)]` to `", #name_str, "`."
                    )]
                    const __LLMDESERIALIZE_REQUIRES_SERDE: () = ();
                };

                impl #impl_generics ::tryparse::deserializer::LlmDeserialize for #name #ty_generics #where_clause {
                    #deserialize_impl
                }
            };

            TokenStream::from(expanded)
        }
        Data::Union(_) => {
            syn::Error::new_spanned(input, "LlmDeserialize cannot be derived for unions")
                .to_compile_error()
                .into()
        }
    }
}