dterror_derive/lib.rs
1//! Procedural macros for the `dterror` crate. `#[derive(CtxError)]` generates code for error types.
2//! `#[derive(FromContext)]` generates code for kind types.
3//!
4//! Use documentation is in the `dterror` crate.
5
6mod derive_attr;
7mod expand_enum;
8mod expand_kind;
9mod expand_struct;
10mod field;
11mod naming;
12mod validate;
13
14use proc_macro::TokenStream;
15use proc_macro2::TokenStream as TokenStream2;
16use quote::format_ident;
17use syn::{Data, DeriveInput};
18
19/// Derives an implementation of `dterror::CtxError` for an error type.
20///
21/// For a struct, the macro generates:
22///
23/// - a `{Error}Ctx<'ctx>` struct that holds every field not marked with `#[source]`, `#[from]`, or
24/// `#[location]`. By default, the generated type derives nothing. The container-level
25/// `#[context(derive(...))]` attribute selects which traits the generated type derives.
26/// - a `pub fn new(...)` constructor on `{Error}Ctx`. It takes one argument per context field, in
27/// the declaration order.
28/// - an `impl ::dterror::CtxError for {Error}`. The generated `from_context` moves the context
29/// fields into the error and assigns `location` and `source` to the marked fields when the struct
30/// has those fields. A `#[source]` field of type `Option<T>` receives the source wrapped in
31/// `Some(source)`.
32///
33/// For an enum, the macro generates a `{Error}Ctx<'ctx>` **enum** instead. It creates one same-named
34/// variant per *constructible* variant of the error (a variant with a `#[source]` or `#[from]`
35/// field). It also creates one public associated constructor on `{Error}Ctx` per Ctx variant. The
36/// macro gives each constructor the `snake_case` form of the variant name. A variant without a `#[source]`
37/// or `#[from]` field gets no Ctx variant and no constructor.
38///
39/// # Attribute helpers
40///
41/// - `#[context(derive(Trait, ...))]`: container-level (on the struct or enum that you apply the
42/// derive to). It selects which traits the generated `{Error}Ctx` derives. You can add trait
43/// paths that `#[derive(...)]` itself accepts. The list replaces the default, so if you omit the
44/// attribute, the derive adds no traits. This default means context field types do not need to
45/// implement traits that call sites never use.
46/// - `#[source]` or `#[from]`: the field that stores the source of the error. At most one per struct
47/// or variant.
48/// - `#[location]`: the field that stores the caller's [`std::panic::Location`]. At most one per
49/// struct or variant.
50/// - `#[context(option)]`: source-field-only. It wraps the captured source in
51/// `::std::option::Option::Some(source)` when it builds the error. This is valid only on the
52/// `#[source]`/`#[from]` field, which must have the type `Option<T>`. The macro applies the
53/// wrapper automatically when the source field's type is already `Option<T>`. This helper only
54/// records that the `Option<T>` type is intentional.
55/// - `#[context(borrow = TargetType)]`: stores a borrowed reference (`&'ctx TargetType`) in Ctx
56/// instead of an owned value. The macro allocates only inside `from_context`. When the caller
57/// already holds a reference, the macro builds the context at no cost. Details are in
58/// `CtxError::Ctx` in the `dterror` crate.
59/// - `#[context(from = TargetType)]`: stores an owned value of type `TargetType` that is cheap to
60/// construct, instead of the field's own type. The constructor takes `impl Into<TargetType>`, and
61/// `from_context` converts the stored value to the field's owned type with `.into()`. If
62/// `TargetType` includes lifetime parameters (for example `KindCtx<'a>`), the macro binds those
63/// lifetimes to the `'ctx` lifetime parameter that `{Error}Ctx` carries.
64/// - `#[context(constructor = "name")]`: variant-level. It renames the generated constructor for
65/// that variant to `name`, a quoted string that parses as an identifier. At most one per variant.
66///
67/// A field may carry at most one `#[context(...)]` attribute, with at most one helper inside it. The
68/// helpers `borrow` and `from` are mutually exclusive. The borrow and from helpers are also mutually
69/// exclusive with a `location` marker. On a `#[source]`/`#[from]` field, the only accepted helper is
70/// `option`. Every other field becomes part of the generated `{Error}Ctx`.
71#[proc_macro_derive(CtxError, attributes(context, location, source, from))]
72pub fn derive_ctx_error(input: TokenStream) -> TokenStream {
73 let input = syn::parse_macro_input!(input as DeriveInput);
74 expand(&input)
75 .unwrap_or_else(syn::Error::into_compile_error)
76 .into()
77}
78
79/// Derives an implementation of `dterror::FromContext` for a kind type (a plain, non-error type).
80///
81/// For a struct, the macro generates:
82///
83/// - a `{Kind}Ctx<'ctx>` struct that holds every field. By default, the generated type derives
84/// nothing. The container-level `#[context(derive(...))]` attribute selects which traits the
85/// generated type derives.
86/// - a `pub fn new(...)` constructor that takes one argument per field, in the declaration order.
87/// - an `impl From<{Kind}Ctx<'_>> for {Kind}` that converts the context to the kind. The generated
88/// `From` implementation calls `.into()` on borrowed fields and from-marked surrogates.
89/// - an `impl dterror::FromContext for {Kind}` that aliases the generated type.
90///
91/// For an enum, the macro generates a `{Kind}Ctx<'ctx>` **enum** instead. It creates one same-named
92/// variant per variant (unit variants stay unit), plus one public associated constructor on
93/// `{Kind}Ctx` per variant. The macro gives each constructor the `snake_case` form of the variant name.
94///
95/// # Attribute helpers
96///
97/// - `#[context(derive(Trait, ...))]`: container-level (on the struct or enum that you apply the
98/// derive to). It selects which traits the generated `{Kind}Ctx` derives. You can add trait
99/// paths that `#[derive(...)]` itself accepts. The list replaces the default, so if you omit the
100/// attribute, the derive adds no traits. This default means context field types do not need to
101/// implement traits that call sites never use.
102/// - `#[context(borrow = TargetType)]`: stores a borrowed reference (`&'ctx TargetType`) in Ctx
103/// instead of an owned value. The constructor takes the reference directly. The generated `From`
104/// implementation converts the stored value to the field's owned type with `.into()`.
105/// - `#[context(from = TargetType)]`: stores an owned value of type `TargetType` that is cheap to
106/// construct, instead of the field's own type. The constructor takes `impl Into<TargetType>`. The
107/// generated `From` implementation converts the stored value with `.into()`. If `TargetType`
108/// includes lifetime parameters (for example `KindCtx<'a>`), the macro binds those lifetimes to
109/// the `'ctx` lifetime parameter that `{Kind}Ctx` carries.
110/// - `#[context(constructor = "name")]`: variant-level. It renames the generated constructor for
111/// that variant to `name`, a quoted string that parses as an identifier. At most one per variant.
112///
113/// A field may carry at most one `#[context(...)]` attribute, with at most one helper inside it. The
114/// helpers `borrow` and `from` are mutually exclusive. Every field of a kind becomes a context field.
115#[proc_macro_derive(FromContext, attributes(context))]
116pub fn derive_from_context(input: TokenStream) -> TokenStream {
117 let input = syn::parse_macro_input!(input as DeriveInput);
118 expand_kind::expand_kind_item(&input)
119 .unwrap_or_else(syn::Error::into_compile_error)
120 .into()
121}
122
123fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
124 let ctx_ident = format_ident!("{}Ctx", input.ident);
125
126 match &input.data {
127 Data::Struct(data) => expand_struct::expand_struct_item(input, data, &ctx_ident),
128 Data::Enum(data) => expand_enum::expand_enum_item(input, data, &ctx_ident),
129 Data::Union(data) => Err(syn::Error::new_spanned(
130 data.union_token,
131 "`#[derive(CtxError)]` does not support unions",
132 )),
133 }
134}