dterror-derive 0.1.0

Derive macro for the dterror FromContext trait
Documentation
//! Derive macro for the `dterror::FromContext` trait.
//!
//! See the `dterror` crate for usage documentation.

mod expand_enum;
mod expand_struct;
mod field;
mod naming;
mod validate;

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::format_ident;
use syn::{Data, DeriveInput};

/// Derive an implementation of `dterror::FromContext` for an error type.
///
/// For a struct, the macro generates:
///
/// - a `{Error}Ctx<'ctx>` struct holding every field not marked with `#[source]`, `#[from]`, or
///   `#[location]`, deriving `Clone`, `Debug`, and `PartialEq`;
/// - a `pub fn new(...)` constructor on `{Error}Ctx` taking one argument per context field, in
///   declaration order;
/// - an `impl ::dterror::FromContext for {Error}` whose `from_context` moves the context fields
///   into the error and assigns `location` and `source` to the marked fields, when present.
///
/// For an enum, the macro generates a `{Error}Ctx<'ctx>` **enum** instead: one same-named
/// variant per *constructible* variant of the error (a variant with at least one context field
/// AND a `#[source]`/`#[from]` field), plus one public associated constructor on `{Error}Ctx` per
/// Ctx variant, named after the variant in `snake_case`. Unit variants, marker-only
/// variants, and source-less variants get no Ctx variant and no constructor.
///
/// # Attribute helpers
///
/// - `#[source]` or `#[from]`: the field that stores the source of the error. At most one per
///   struct or variant.
/// - `#[location]`: the field that stores the caller's [`std::panic::Location`]. At most one
///   per struct or variant.
/// - `#[context(borrow = TargetType)]`: store a borrowed reference (`&'ctx TargetType`) in Ctx
///   instead of an owned value, deferring allocation to `from_context`. This enables zero-cost
///   context construction when the caller already holds a reference — no eager cloning occurs on
///   the Ok path. See `FromContext::Ctx` in the `dterror` crate for details. At most one
///   `#[context(...)]` attribute per field, and it cannot be combined with `#[source]`/`#[from]`
///   or `#[location]`.
/// - `#[context(constructor = "name")]`: variant-level; renames the generated constructor for
///   that variant to `name`, a quoted string that parses as an identifier. At most one per
///   variant.
///
/// Every other field becomes a field of the generated `{Error}Ctx`. A struct with no context
/// fields, or an enum with no constructible variant, fails to compile, as do duplicate or
/// conflicting markers and duplicate constructor names.
#[proc_macro_derive(FromContext, attributes(context, location, source, from))]
pub fn derive_from_context(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as DeriveInput);
    expand(&input)
        .unwrap_or_else(syn::Error::into_compile_error)
        .into()
}

fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
    let ctx_ident = format_ident!("{}Ctx", input.ident);

    match &input.data {
        Data::Struct(data) => expand_struct::expand_struct_item(input, data, &ctx_ident),
        Data::Enum(data) => expand_enum::expand_enum_item(input, data, &ctx_ident),
        Data::Union(data) => Err(syn::Error::new_spanned(
            data.union_token,
            "`#[derive(FromContext)]` does not support unions",
        )),
    }
}