dterror-derive 0.2.0

Proc-macros for the dterror CtxError and FromContext traits
Documentation
//! Procedural macros for the `dterror` crate. `#[derive(CtxError)]` generates code for error types.
//! `#[derive(FromContext)]` generates code for kind types.
//!
//! Use documentation is in the `dterror` crate.

mod derive_attr;
mod expand_enum;
mod expand_kind;
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};

/// Derives an implementation of `dterror::CtxError` for an error type.
///
/// For a struct, the macro generates:
///
/// - a `{Error}Ctx<'ctx>` struct that holds every field not marked with `#[source]`, `#[from]`, or
///   `#[location]`. By default, the generated type derives nothing. The container-level
///   `#[context(derive(...))]` attribute selects which traits the generated type derives.
/// - a `pub fn new(...)` constructor on `{Error}Ctx`. It takes one argument per context field, in
///   the declaration order.
/// - an `impl ::dterror::CtxError for {Error}`. The generated `from_context` moves the context
///   fields into the error and assigns `location` and `source` to the marked fields when the struct
///   has those fields. A `#[source]` field of type `Option<T>` receives the source wrapped in
///   `Some(source)`.
///
/// For an enum, the macro generates a `{Error}Ctx<'ctx>` **enum** instead. It creates one same-named
/// variant per *constructible* variant of the error (a variant with a `#[source]` or `#[from]`
/// field). It also creates one public associated constructor on `{Error}Ctx` per Ctx variant. The
/// macro gives each constructor the `snake_case` form of the variant name. A variant without a `#[source]`
/// or `#[from]` field gets no Ctx variant and no constructor.
///
/// # Attribute helpers
///
/// - `#[context(derive(Trait, ...))]`: container-level (on the struct or enum that you apply the
///   derive to). It selects which traits the generated `{Error}Ctx` derives. You can add trait
///   paths that `#[derive(...)]` itself accepts. The list replaces the default, so if you omit the
///   attribute, the derive adds no traits. This default means context field types do not need to
///   implement traits that call sites never use.
/// - `#[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(option)]`: source-field-only. It wraps the captured source in
///   `::std::option::Option::Some(source)` when it builds the error. This is valid only on the
///   `#[source]`/`#[from]` field, which must have the type `Option<T>`. The macro applies the
///   wrapper automatically when the source field's type is already `Option<T>`. This helper only
///   records that the `Option<T>` type is intentional.
/// - `#[context(borrow = TargetType)]`: stores a borrowed reference (`&'ctx TargetType`) in Ctx
///   instead of an owned value. The macro allocates only inside `from_context`. When the caller
///   already holds a reference, the macro builds the context at no cost. Details are in
///   `CtxError::Ctx` in the `dterror` crate.
/// - `#[context(from = TargetType)]`: stores an owned value of type `TargetType` that is cheap to
///   construct, instead of the field's own type. The constructor takes `impl Into<TargetType>`, and
///   `from_context` converts the stored value to the field's owned type with `.into()`. If
///   `TargetType` includes lifetime parameters (for example `KindCtx<'a>`), the macro binds those
///   lifetimes to the `'ctx` lifetime parameter that `{Error}Ctx` carries.
/// - `#[context(constructor = "name")]`: variant-level. It renames the generated constructor for
///   that variant to `name`, a quoted string that parses as an identifier. At most one per variant.
///
/// A field may carry at most one `#[context(...)]` attribute, with at most one helper inside it. The
/// helpers `borrow` and `from` are mutually exclusive. The borrow and from helpers are also mutually
/// exclusive with a `location` marker. On a `#[source]`/`#[from]` field, the only accepted helper is
/// `option`. Every other field becomes part of the generated `{Error}Ctx`.
#[proc_macro_derive(CtxError, attributes(context, location, source, from))]
pub fn derive_ctx_error(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as DeriveInput);
    expand(&input)
        .unwrap_or_else(syn::Error::into_compile_error)
        .into()
}

/// Derives an implementation of `dterror::FromContext` for a kind type (a plain, non-error type).
///
/// For a struct, the macro generates:
///
/// - a `{Kind}Ctx<'ctx>` struct that holds every field. By default, the generated type derives
///   nothing. The container-level `#[context(derive(...))]` attribute selects which traits the
///   generated type derives.
/// - a `pub fn new(...)` constructor that takes one argument per field, in the declaration order.
/// - an `impl From<{Kind}Ctx<'_>> for {Kind}` that converts the context to the kind. The generated
///   `From` implementation calls `.into()` on borrowed fields and from-marked surrogates.
/// - an `impl dterror::FromContext for {Kind}` that aliases the generated type.
///
/// For an enum, the macro generates a `{Kind}Ctx<'ctx>` **enum** instead. It creates one same-named
/// variant per variant (unit variants stay unit), plus one public associated constructor on
/// `{Kind}Ctx` per variant. The macro gives each constructor the `snake_case` form of the variant name.
///
/// # Attribute helpers
///
/// - `#[context(derive(Trait, ...))]`: container-level (on the struct or enum that you apply the
///   derive to). It selects which traits the generated `{Kind}Ctx` derives. You can add trait
///   paths that `#[derive(...)]` itself accepts. The list replaces the default, so if you omit the
///   attribute, the derive adds no traits. This default means context field types do not need to
///   implement traits that call sites never use.
/// - `#[context(borrow = TargetType)]`: stores a borrowed reference (`&'ctx TargetType`) in Ctx
///   instead of an owned value. The constructor takes the reference directly. The generated `From`
///   implementation converts the stored value to the field's owned type with `.into()`.
/// - `#[context(from = TargetType)]`: stores an owned value of type `TargetType` that is cheap to
///   construct, instead of the field's own type. The constructor takes `impl Into<TargetType>`. The
///   generated `From` implementation converts the stored value with `.into()`. If `TargetType`
///   includes lifetime parameters (for example `KindCtx<'a>`), the macro binds those lifetimes to
///   the `'ctx` lifetime parameter that `{Kind}Ctx` carries.
/// - `#[context(constructor = "name")]`: variant-level. It renames the generated constructor for
///   that variant to `name`, a quoted string that parses as an identifier. At most one per variant.
///
/// A field may carry at most one `#[context(...)]` attribute, with at most one helper inside it. The
/// helpers `borrow` and `from` are mutually exclusive. Every field of a kind becomes a context field.
#[proc_macro_derive(FromContext, attributes(context))]
pub fn derive_from_context(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as DeriveInput);
    expand_kind::expand_kind_item(&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(CtxError)]` does not support unions",
        )),
    }
}