n0_error_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::{quote, ToTokens};
4use syn::{
5    parse_macro_input, parse_quote, punctuated::Punctuated, Attribute, DeriveInput, Expr, Field,
6    Fields, FieldsNamed, Ident,
7};
8
9/// Attribute macro to expand error enums or structs.
10///
11/// This macro can be applied to enums and structs and the syntax `#[stack_error(args)]`,
12/// where `args` is a comma-seperated list of arguments.
13/// * **`add_meta`** adds a `n0_error::Meta` field to the struct or each each enum variant. The `Meta`
14/// struct contains call-site location metadata for the error.
15///   - If the item has named fields, the field will added with name `meta`
16///   - If the item is a tuple, the field will be added as the last field and marked with `#[error(meta)]`
17///   - It the item is a unit, it will be altered to named fields, and the field
18///     will be added with name `meta`
19/// * **`derive`** will add `#[derive(StackError)]`. See the docs for the [`StackError`] for details.
20/// * The item-level options of [`StackError`] derive macro (namely `from_sources` and `std_sources`)
21///   can be set `stack_error` as well. They will be expanded to an `#[error]` attribute on the item.
22///   See the documentation for [`StackError`] for details.
23///
24/// This is an attribute macro because it *modifies* its item by adding the `meta` field,
25/// which derive macros cannot.
26///
27/// ## Example
28///
29/// ```rust
30/// #[stack_error(derive, add_meta, from_sources)]
31/// enum MyError {
32///     #[error("io failed")]
33///     Io { source: std::io::Error },
34///     #[error("remote failed with {_0}")]
35///     RemoteErrorCode(u32),
36/// }
37/// ```
38/// expands to
39/// ```rust
40/// #[derive(n0_error::StackError)]
41/// #[error(from_sources)]
42/// enum MyError {
43///     #[error("io failed")]
44///     Io {
45///         source: std::io::Error,
46///         meta: n0_error::Meta,
47///     },
48///     #[error("remote failed with {_0}")]
49///     RemoteErrorCode(u32, #[error(meta)] n0_error::Meta),
50/// }
51/// ```
52#[proc_macro_attribute]
53pub fn stack_error(args: TokenStream, item: TokenStream) -> TokenStream {
54    match stack_error_inner(args, parse_macro_input!(item as syn::Item)) {
55        Err(err) => err.to_compile_error().into(),
56        Ok(tokens) => tokens.into(),
57    }
58}
59
60fn stack_error_inner(
61    args: TokenStream,
62    mut input: syn::Item,
63) -> Result<proc_macro2::TokenStream, syn::Error> {
64    let args: StackErrAttrArgs = syn::parse(args.clone())?;
65    match &mut input {
66        syn::Item::Enum(item) => {
67            if args.add_meta {
68                for variant in item.variants.iter_mut() {
69                    add_meta_field(&mut variant.fields);
70                }
71            }
72            modify_attrs(&args, &mut item.attrs)?;
73            Ok(quote! { #item })
74        }
75        syn::Item::Struct(item) => {
76            if args.add_meta {
77                add_meta_field(&mut item.fields);
78            }
79            modify_attrs(&args, &mut item.attrs)?;
80            Ok(quote! { #item })
81        }
82        _ => Err(err(
83            &input,
84            "#[stack_error] only supports enums and structs",
85        )),
86    }
87}
88
89fn modify_attrs(args: &StackErrAttrArgs, attrs: &mut Vec<Attribute>) -> Result<(), syn::Error> {
90    attrs.retain(|attr| !attr.path().is_ident("stackerr"));
91    if args.derive {
92        attrs.insert(0, parse_quote!(#[derive(::n0_error::StackError)]));
93    }
94    let error_args: Vec<_> = [
95        args.from_sources.then(|| quote!(from_sources)),
96        args.std_sources.then(|| quote!(std_sources)),
97    ]
98    .into_iter()
99    .flatten()
100    .collect();
101    if !error_args.is_empty() {
102        attrs.push(parse_quote!(#[error(#(#error_args),*)]))
103    }
104
105    Ok(())
106}
107
108fn add_meta_field(fields: &mut Fields) {
109    let doc = "Captured call-site metadata";
110    match fields {
111        Fields::Named(fields) => {
112            let field: syn::Field = parse_quote! { #[doc = #doc] meta: ::n0_error::Meta };
113            fields.named.push(field);
114        }
115        Fields::Unit => {
116            let mut named = FieldsNamed {
117                brace_token: Default::default(),
118                named: Default::default(),
119            };
120            let field: syn::Field = parse_quote! { #[doc = #doc] meta: ::n0_error::Meta };
121            named.named.push(field);
122            *fields = Fields::Named(named);
123        }
124        Fields::Unnamed(fields) => {
125            let field: syn::Field = parse_quote! { #[doc = #doc] #[error(meta)] ::n0_error::Meta };
126            fields.unnamed.push(field);
127        }
128    }
129}
130
131/// Derive macro that implements `StackError`, `Display`, `Debug` and `std::error::Error`
132/// and generates `From<T>` impls for fields/variants configured via `#[error(..)]`.
133/// Derive macro for stack errors.
134///
135/// This derive macro can be applied to structs and enums. Unit, tuple and named-field variants
136/// are supported equally.
137///
138/// The macro will expand to implementations of `StackError`, [`Display`], [`Debug`] and [`Error`].
139/// It will also add [`From`] impls for fields configured via the `error` attribute.
140///
141/// Items with the derive macro applied accept an `#[error(args)]` attribute, where `args` is a comma-separated
142/// list of arguments. The supported arguments vary by on the kind of item:
143///
144/// * on enums:
145///   - `#[error(from_sources)]`: Creates `From` impls for the `source` types of all variants. Will fail to compile if multiple sources have the same type.
146///   - `#[error(std_sources)]`: Defaults all sources to be std errors instead of stack errors.
147/// * on structs and enum variants:
148///   - `#[error("format {field}: {}", a + b)]`: Sets the display formatting. You can refer to named fields by their names, and to tuple fields by `_0`, `_1` etc.
149///   - `#[error(transparent)]`: Directly forwards the display implementation to the error source, and omits the outer error in the source chain when reporting errors.
150/// * on fields:
151///   - `#[error(from)]`: Creates a `From` impl for the field's type to the error type.
152///   - `#[error(source)]`: The error's `source` method returns a reference to whatever field is named `source`, or
153///     has the `source` attribute set.
154///   - `#[error(std_err)]`: *Only on on `source` fields.* Marks the error as a `std` error.
155///     Source fields not marked as `std_err` need to implement `StackError`.
156///   - `#[error(stack_error)]`: *Only on `source` fields.* Marks the error as a `StackError`. This is the default unless `std_sources` is set on the top-level item.
157///   - `#[error(meta)]: Sets a field as the `meta` field for this error or variant. Must have type [`::n0_error::Error`]
158///
159/// [`Display`]: std::fmt::Display
160/// [`Debug`]: std::fmt::Debug
161/// [`Error`]: std::error::Error.
162#[proc_macro_derive(StackError, attributes(error))]
163pub fn derive_stack_error(input: TokenStream) -> TokenStream {
164    let input = parse_macro_input!(input as syn::DeriveInput);
165    match derive_error_inner(input) {
166        Err(err) => err.to_compile_error().into(),
167        Ok(tokens) => tokens.into(),
168    }
169}
170
171fn derive_error_inner(input: DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> {
172    match &input.data {
173        syn::Data::Enum(item) => {
174            let args = EnumAttrArgs::from_attributes(&input.attrs)?;
175            let infos = item
176                .variants
177                .iter()
178                .map(|v| {
179                    let ident = VariantIdent::Variant(&input.ident, &v.ident);
180                    VariantInfo::parse(ident, &v.fields, &v.attrs, &args)
181                })
182                .collect::<Result<Vec<_>, _>>()?;
183            Ok(generate_enum_impls(&input.ident, &input.generics, infos))
184        }
185        syn::Data::Struct(item) => {
186            let ident = VariantIdent::Struct(&input.ident);
187            let info = VariantInfo::parse(ident, &item.fields, &input.attrs, &Default::default())?;
188            Ok(generate_struct_impl(&input.ident, &input.generics, info))
189        }
190        _ => Err(err(
191            &input,
192            "#[derive(StackError)] only supports enums or structs",
193        )),
194    }
195}
196
197struct SourceField<'a> {
198    kind: SourceKind,
199    field: FieldInfo<'a>,
200}
201
202impl<'a> SourceField<'a> {
203    fn expr_error_ref(&self, expr: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
204        match self.kind {
205            SourceKind::Std => quote! { Some(::n0_error::ErrorRef::std(#expr)) },
206            SourceKind::Stack => quote! { Some(::n0_error::ErrorRef::stack(#expr)) },
207        }
208    }
209
210    fn expr_error_std(&self, expr: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
211        match self.kind {
212            SourceKind::Std => quote! { Some(#expr as &dyn ::std::error::Error) },
213            SourceKind::Stack => quote! { Some(::n0_error::StackError::as_std(#expr)) },
214        }
215    }
216}
217
218enum SourceKind {
219    Stack,
220    Std,
221}
222
223#[derive(Default, Clone, Copy)]
224struct StackErrAttrArgs {
225    add_meta: bool,
226    derive: bool,
227    from_sources: bool,
228    std_sources: bool,
229}
230
231impl syn::parse::Parse for StackErrAttrArgs {
232    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
233        let mut out = Self::default();
234        while !input.is_empty() {
235            let ident: Ident = input.parse()?;
236            match ident.to_string().as_str() {
237                "add_meta" => out.add_meta = true,
238                "derive" => out.derive = true,
239                "from_sources" => out.from_sources = true,
240                "std_sources" => out.std_sources = true,
241                other => Err(err(
242                    ident,
243                    format!("unknown stack_error option `{}`", other),
244                ))?,
245            }
246            if input.peek(syn::Token![,]) {
247                let _ = input.parse::<syn::Token![,]>()?;
248            }
249        }
250        Ok(out)
251    }
252}
253
254#[derive(Default, Clone, Copy)]
255struct EnumAttrArgs {
256    from_sources: bool,
257    std_sources: bool,
258}
259
260impl EnumAttrArgs {
261    fn from_attributes(attrs: &[Attribute]) -> Result<Self, syn::Error> {
262        let mut out = Self::default();
263        for ident in parse_error_attr_as_idents(attrs)? {
264            match ident.to_string().as_str() {
265                "from_sources" => out.from_sources = true,
266                "std_sources" => out.std_sources = true,
267                _ => Err(err(
268                    ident,
269                    "Invalid argument for the `error` attribute on fields",
270                ))?,
271            }
272        }
273        Ok(out)
274    }
275}
276
277#[derive(Default, Clone, Copy)]
278struct FieldAttrArgs {
279    source: bool,
280    from: bool,
281    std_err: bool,
282    stack_err: bool,
283    meta: bool,
284}
285
286impl FieldAttrArgs {
287    fn from_attributes(attrs: &[Attribute]) -> Result<Self, syn::Error> {
288        let mut out = Self::default();
289        for ident in parse_error_attr_as_idents(attrs)? {
290            match ident.to_string().as_str() {
291                "source" => out.source = true,
292                "from" => out.from = true,
293                "std_err" => out.std_err = true,
294                "stack_err" => out.stack_err = true,
295                "meta" => out.meta = true,
296                _ => Err(err(
297                    ident,
298                    "Invalid argument for the `error` attribute on fields",
299                ))?,
300            }
301        }
302        Ok(out)
303    }
304}
305
306#[derive(Debug, Clone, Copy)]
307enum VariantIdent<'a> {
308    Struct(&'a Ident),
309    Variant(&'a Ident, &'a Ident),
310}
311
312impl<'a> VariantIdent<'a> {
313    fn self_ident(&self) -> proc_macro2::TokenStream {
314        match self {
315            VariantIdent::Struct(_) => quote!(Self),
316            VariantIdent::Variant(_, variant) => quote!(Self::#variant),
317        }
318    }
319    fn item_ident(&self) -> &Ident {
320        match self {
321            VariantIdent::Struct(ident) => &ident,
322            VariantIdent::Variant(ident, _) => &ident,
323        }
324    }
325
326    fn inner(&self) -> &Ident {
327        match self {
328            VariantIdent::Struct(ident) => &ident,
329            VariantIdent::Variant(_, ident) => &ident,
330        }
331    }
332}
333
334#[derive(Clone, Copy, PartialEq, Eq)]
335enum Kind {
336    Named,
337    Unit,
338    Tuple,
339}
340
341struct VariantInfo<'a> {
342    ident: VariantIdent<'a>,
343    fields: Vec<FieldInfo<'a>>,
344    kind: Kind,
345    display: DisplayArgs,
346    source: Option<SourceField<'a>>,
347    /// The field that is used for From<..> impls
348    from: Option<FieldInfo<'a>>,
349    /// The meta field if present.
350    meta: Option<FieldInfo<'a>>,
351}
352
353impl<'a> VariantInfo<'a> {
354    fn parse(
355        ident: VariantIdent<'a>,
356        fields: &'a Fields,
357        attrs: &[Attribute],
358        args: &EnumAttrArgs,
359    ) -> Result<VariantInfo<'a>, syn::Error> {
360        let display = DisplayArgs::parse(&attrs)?;
361        let (kind, fields): (Kind, Vec<FieldInfo>) = match fields {
362            Fields::Named(ref fields) => (
363                Kind::Named,
364                fields
365                    .named
366                    .iter()
367                    .map(FieldInfo::from_named)
368                    .collect::<Result<_, _>>()?,
369            ),
370            Fields::Unit => (Kind::Unit, Vec::new()),
371            Fields::Unnamed(ref fields) => (
372                Kind::Tuple,
373                fields
374                    .unnamed
375                    .iter()
376                    .enumerate()
377                    .map(|(i, f)| FieldInfo::from_unnamed(i, f))
378                    .collect::<Result<_, _>>()?,
379            ),
380        };
381
382        if fields.iter().filter(|f| f.args.source).count() > 1 {
383            return Err(err(
384                ident.inner(),
385                "Only one field per variant may have #[error(source)]",
386            ));
387        }
388        let source_field = fields
389            .iter()
390            .find(|f| f.args.source)
391            .or_else(|| match kind {
392                Kind::Named => fields
393                    .iter()
394                    .find(|f| matches!(f.ident, FieldIdent::Named(name) if name == "source")),
395                Kind::Tuple if display.is_transparent() => fields.first(),
396                _ => None,
397            });
398
399        if display.is_transparent() && source_field.is_none() {
400            return Err(err(
401                ident.inner(),
402                "Variants with #[error(transparent)] require a source field",
403            ));
404        }
405
406        // Determine source kind and optional From based on field options and top-level switches
407        let source = source_field.as_ref().map(|field| {
408            let kind = if field.args.std_err || (args.std_sources && !field.args.stack_err) {
409                SourceKind::Std
410            } else {
411                SourceKind::Stack
412            };
413            SourceField {
414                kind,
415                field: (*field).clone(),
416            }
417        });
418
419        let from_field = fields
420            .iter()
421            .find(|f| f.args.from)
422            .or_else(|| args.from_sources.then(|| source_field).flatten());
423        let meta_field = fields.iter().find(|f| f.is_meta()).cloned();
424        Ok(VariantInfo {
425            ident: ident.clone(),
426            kind,
427            display,
428            from: from_field.cloned(),
429            meta: meta_field,
430            source,
431            fields,
432        })
433    }
434
435    fn transparent(&self) -> Option<&FieldInfo<'_>> {
436        match self.display {
437            DisplayArgs::Transparent => self.source.as_ref().map(|s| &s.field),
438            _ => None,
439        }
440    }
441
442    fn spread_field(&self, field: &FieldInfo<'a>, bind: &Ident) -> proc_macro2::TokenStream {
443        let slf = self.ident.self_ident();
444        match field.ident {
445            FieldIdent::Named(ident) => {
446                quote! { #slf { #ident: #bind, .. } }
447            }
448            FieldIdent::Unnamed(_) => {
449                let pats = self.fields.iter().map(|f| {
450                    if f.ident == field.ident {
451                        quote!(#bind)
452                    } else {
453                        quote!(_)
454                    }
455                });
456                quote! { #slf ( #(#pats),* ) }
457            }
458        }
459    }
460
461    fn spread_empty(&self) -> proc_macro2::TokenStream {
462        let slf = self.ident.self_ident();
463        match self.kind {
464            Kind::Unit => quote! { #slf },
465            Kind::Named => quote! { #slf { .. } },
466            Kind::Tuple => {
467                let pats = std::iter::repeat(quote! { _ }).take(self.fields.len());
468                quote! { #slf ( #(#pats),* ) }
469            }
470        }
471    }
472
473    fn spread_all(&self) -> proc_macro2::TokenStream {
474        let binds = self.fields.iter().map(|f| f.ident.as_ident());
475        self.spread(binds.map(|i| quote!(#i)))
476    }
477
478    fn spread(
479        &self,
480        fields: impl Iterator<Item = proc_macro2::TokenStream>,
481    ) -> proc_macro2::TokenStream {
482        let slf = self.ident.self_ident();
483        match self.kind {
484            Kind::Unit => quote! { #slf },
485            Kind::Named => quote! { #slf { #(#fields),* } },
486            Kind::Tuple => quote! { #slf ( #(#fields),* ) },
487        }
488    }
489
490    fn from_impl(&self) -> Option<(&syn::Type, proc_macro2::TokenStream)> {
491        self.from.as_ref().map(|from_field| {
492            let ty = &from_field.field.ty;
493            let fields = self.fields.iter().map(|field| {
494                if field.ident == from_field.ident {
495                    field.ident.init(quote!(source))
496                } else if field.is_meta() {
497                    field.ident.init(quote!(::n0_error::Meta::new()))
498                } else {
499                    field.ident.init(quote!(::std::default::Default::default()))
500                }
501            });
502            let construct = self.spread(fields);
503            (ty, construct)
504        })
505    }
506}
507
508#[derive(Clone)]
509struct FieldInfo<'a> {
510    field: &'a Field,
511    args: FieldAttrArgs,
512    ident: FieldIdent<'a>,
513}
514
515impl<'a> FieldInfo<'a> {
516    fn from_named(field: &'a Field) -> Result<Self, syn::Error> {
517        Ok(Self {
518            args: FieldAttrArgs::from_attributes(&field.attrs)?,
519            ident: FieldIdent::Named(field.ident.as_ref().unwrap()),
520            field,
521        })
522    }
523    fn from_unnamed(index: usize, field: &'a Field) -> Result<Self, syn::Error> {
524        Ok(Self {
525            args: FieldAttrArgs::from_attributes(&field.attrs)?,
526            ident: FieldIdent::Unnamed(index),
527            field,
528        })
529    }
530
531    fn is_meta(&self) -> bool {
532        self.args.meta || matches!(self.ident, FieldIdent::Named(ident) if ident == "meta")
533    }
534}
535
536#[derive(Clone, Copy, Eq, PartialEq)]
537enum FieldIdent<'a> {
538    Named(&'a Ident),
539    Unnamed(usize),
540}
541
542impl<'a> FieldIdent<'a> {
543    fn named(&self) -> Option<&'a Ident> {
544        match self {
545            FieldIdent::Named(ident) => Some(ident),
546            _ => None,
547        }
548    }
549
550    fn as_ident(&self) -> Ident {
551        match self {
552            FieldIdent::Named(ident) => (*ident).clone(),
553            FieldIdent::Unnamed(i) => syn::Ident::new(&format!("_{}", i), Span::call_site()),
554        }
555    }
556
557    fn self_expr(&self) -> proc_macro2::TokenStream {
558        match self {
559            FieldIdent::Named(ident) => quote!(self.#ident),
560            FieldIdent::Unnamed(i) => {
561                let idx = syn::Index::from(*i);
562                quote!(self.#idx)
563            }
564        }
565    }
566
567    fn init(&self, expr: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
568        match self {
569            FieldIdent::Named(ident) => quote!(#ident: #expr),
570            FieldIdent::Unnamed(_) => quote!(#expr),
571        }
572    }
573}
574
575fn generate_enum_impls(
576    enum_ident: &Ident,
577    generics: &syn::Generics,
578    variants: Vec<VariantInfo>,
579) -> proc_macro2::TokenStream {
580    let match_meta_arms = variants.iter().map(|vi| {
581        if let Some(meta_field) = &vi.meta {
582            let bind = syn::Ident::new("__meta", Span::call_site());
583            let pat = vi.spread_field(meta_field, &bind);
584            quote! { #pat => Some(&#bind) }
585        } else {
586            let pat = vi.spread_empty();
587            quote! { #pat => None }
588        }
589    });
590
591    let match_source_arms = variants.iter().map(|vi| match &vi.source {
592        Some(src) => {
593            let bind = syn::Ident::new("__source", Span::call_site());
594            let pat = vi.spread_field(&src.field, &bind);
595            let expr = src.expr_error_ref(quote!(#bind));
596            quote! { #pat => #expr, }
597        }
598        None => {
599            let pat = vi.spread_empty();
600            quote! { #pat => None, }
601        }
602    });
603
604    let match_std_source_arms = variants.iter().map(|vi| match &vi.source {
605        Some(src) => {
606            let bind = syn::Ident::new("__source", Span::call_site());
607            let pat = vi.spread_field(&src.field, &bind);
608            let expr = src.expr_error_std(quote!(#bind));
609            quote! { #pat => #expr, }
610        }
611        None => {
612            let pat = vi.spread_empty();
613            quote! { #pat => None, }
614        }
615    });
616
617    let match_transparent_arms = variants.iter().map(|vi| {
618        let value = vi.transparent().is_some();
619        let pat = vi.spread_empty();
620        quote! { #pat => #value }
621    });
622
623    let match_fmt_message_arms = variants.iter().map(|vi| match &vi.display {
624        DisplayArgs::Format(expr) => {
625            let pat = vi.spread_all();
626            quote! {
627                #[allow(unused)]
628                #pat => { #expr }
629            }
630        }
631        DisplayArgs::Default | DisplayArgs::Transparent => {
632            let text = format!("{}::{}", vi.ident.item_ident(), vi.ident.inner());
633            let pat = vi.spread_empty();
634            quote! { #pat => write!(f, #text) }
635        }
636    });
637
638    let match_debug_arms = variants.iter().map(|vi| {
639        let v_name = vi.ident.inner().to_string();
640        let binds = vi.fields.iter().map(|f| f.ident.as_ident());
641        let pat = vi.spread_all();
642        let labels: Vec<String> = vi
643            .fields
644            .iter()
645            .map(|f| match f.ident {
646                FieldIdent::Named(id) => id.to_string(),
647                FieldIdent::Unnamed(i) => i.to_string(),
648            })
649            .collect();
650        quote! {
651            #pat => {
652                let mut dbg = f.debug_struct(#v_name);
653                #( dbg.field(#labels, &#binds); )*; dbg.finish()?;
654            }
655        }
656    });
657
658    // From impls for variant fields marked with #[from]
659    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
660    let from_impls = variants.iter().filter_map(|vi| vi.from_impl()).map(|(ty, construct)| {
661        quote! {
662            impl #impl_generics ::core::convert::From<#ty> for #enum_ident #ty_generics #where_clause {
663                #[track_caller]
664                fn from(source: #ty) -> Self {
665                    #construct
666                }
667            }
668        }
669    });
670
671    quote! {
672        impl #impl_generics ::n0_error::StackError for #enum_ident #ty_generics #where_clause {
673            fn as_std(&self) -> &(dyn ::std::error::Error + ::std::marker::Send + ::std::marker::Sync + 'static) {
674                self
675            }
676            fn into_std(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn std::error::Error + ::std::marker::Send + ::std::marker::Sync> {
677                self
678            }
679            fn as_dyn(&self) -> &(dyn ::n0_error::StackError) {
680                self
681            }
682            fn fmt_message(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
683                match self {
684                    #( #match_fmt_message_arms, )*
685                }
686            }
687            fn meta(&self) -> Option<&::n0_error::Meta> {
688                match self {
689                    #( #match_meta_arms, )*
690                }
691            }
692            fn source(&self) -> Option<::n0_error::ErrorRef<'_>> {
693                match self {
694                    #( #match_source_arms )*
695                }
696            }
697            fn is_transparent(&self) -> bool {
698                match self {
699                    #( #match_transparent_arms, )*
700                }
701            }
702        }
703
704        impl #impl_generics ::core::convert::From<#enum_ident #ty_generics> for ::n0_error::AnyError #where_clause {
705            fn from(value: #enum_ident #ty_generics) -> ::n0_error::AnyError {
706                ::n0_error::AnyError::from_stack(value)
707            }
708        }
709
710        impl #impl_generics ::std::fmt::Display for #enum_ident #ty_generics #where_clause {
711            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
712                let sources = f.alternate().then_some(::n0_error::SourceFormat::OneLine);
713                write!(f, "{}", ::n0_error::StackError::report(self).sources(sources))
714            }
715        }
716
717        impl #impl_generics ::std::fmt::Debug for #enum_ident #ty_generics #where_clause {
718            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
719                if f.alternate() {
720                    match self {
721                        #(#match_debug_arms)*
722                    }
723                } else {
724                    write!(f, "{}", ::n0_error::StackError::report(self).full())?;
725                }
726                Ok(())
727            }
728        }
729
730        impl #impl_generics ::std::error::Error for #enum_ident #ty_generics #where_clause {
731            fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
732                match self {
733                    #( #match_std_source_arms )*
734                }
735            }
736        }
737        #( #from_impls )*
738    }
739}
740
741fn generate_struct_impl(
742    item_ident: &Ident,
743    generics: &syn::Generics,
744    info: VariantInfo,
745) -> proc_macro2::TokenStream {
746    let constructor = {
747        let params = info.fields.iter().filter(|f| !f.is_meta()).map(|f| {
748            let ty = &f.field.ty;
749            let ident = f.ident.as_ident();
750            quote! { #ident: #ty }
751        });
752        let fields = info.fields.iter().map(|f| {
753            if f.is_meta() {
754                f.ident.init(quote!(::n0_error::Meta::new()))
755            } else {
756                let ident = f.ident.as_ident();
757                quote!(#ident)
758            }
759        });
760        let construct = info.spread(fields);
761        let doc = format!("Creates a new [`{}`] error.", item_ident);
762        quote! {
763            #[doc = #doc]
764            #[track_caller]
765            pub fn new(#(#params),*) -> Self { #construct }
766        }
767    };
768    let get_meta = if let Some(field) = &info.meta {
769        let get_expr = field.ident.self_expr();
770        quote! { Some(&#get_expr) }
771    } else {
772        quote! { None }
773    };
774
775    let get_error_source = match &info.source {
776        Some(src) => src.expr_error_ref(src.field.ident.self_expr()),
777        None => quote! { None },
778    };
779
780    let get_std_source = match &info.source {
781        Some(src) => src.expr_error_std(src.field.ident.self_expr()),
782        None => quote! { None },
783    };
784
785    let is_transparent = info.transparent().is_some();
786
787    let get_fmt_message = {
788        match &info.display {
789            DisplayArgs::Format(expr) => {
790                let pat = info.spread_all();
791                quote! {
792                   #[allow(unused)]
793                   let #pat = self;
794                   #expr
795                }
796            }
797            DisplayArgs::Default | DisplayArgs::Transparent => {
798                let text = info.ident.item_ident().to_string();
799                quote! { write!(f, #text) }
800            }
801        }
802    };
803
804    let get_debug = {
805        let item_name = info.ident.item_ident().to_string();
806        match info.kind {
807            Kind::Unit => quote!(write!(f, #item_name)?;),
808            Kind::Named => {
809                let fields = info
810                    .fields
811                    .iter()
812                    .filter_map(|f| f.ident.named())
813                    .map(|ident| {
814                        let ident_s = ident.to_string();
815                        quote! { dbg.field(#ident_s, &self.#ident) }
816                    });
817                quote! {
818                    let mut dbg = f.debug_struct(#item_name);
819                    #(#fields);*;
820                    dbg.finish()?;
821                }
822            }
823            Kind::Tuple => {
824                let binds = (0..info.fields.len()).map(|i| syn::Index::from(i));
825                quote! {
826                    let mut dbg = f.debug_tuple(#item_name);
827                    #( dbg.field(&self.#binds); )*;
828                    dbg.finish()?;
829                }
830            }
831        }
832    };
833
834    // From impls for fields marked with #[error(from)] (or inferred via from_sources)
835    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
836    let from_impl = info.from_impl().map(|(ty, construct)| {
837        quote! {
838            impl #impl_generics ::core::convert::From<#ty> for #item_ident #ty_generics #where_clause {
839                #[track_caller]
840                fn from(source: #ty) -> Self { #construct }
841            }
842        }
843    });
844
845    quote! {
846        impl #impl_generics #item_ident #ty_generics #where_clause {
847            #constructor
848        }
849
850        impl #impl_generics ::n0_error::StackError for #item_ident #ty_generics #where_clause {
851            fn as_std(&self) -> &(dyn ::std::error::Error + ::std::marker::Send + ::std::marker::Sync + 'static) {
852                self
853            }
854            fn into_std(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn std::error::Error + ::std::marker::Send + ::std::marker::Sync> {
855                self
856            }
857            fn as_dyn(&self) -> &(dyn ::n0_error::StackError) {
858                self
859            }
860            fn meta(&self) -> Option<&::n0_error::Meta> {
861                #get_meta
862            }
863            fn source(&self) -> Option<::n0_error::ErrorRef<'_>> {
864                #get_error_source
865            }
866            fn is_transparent(&self) -> bool {
867                #is_transparent
868            }
869            fn fmt_message(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
870                #get_fmt_message
871            }
872        }
873
874
875        impl #impl_generics ::core::convert::From<#item_ident #ty_generics> for ::n0_error::AnyError #where_clause {
876            fn from(value: #item_ident #ty_generics) -> ::n0_error::AnyError {
877                ::n0_error::AnyError::from_stack(value)
878            }
879        }
880
881        impl #impl_generics ::std::fmt::Display for #item_ident #ty_generics #where_clause {
882            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
883                let sources = f.alternate().then_some(::n0_error::SourceFormat::OneLine);
884                write!(f, "{}", ::n0_error::StackError::report(self).sources(sources))
885            }
886        }
887
888        impl #impl_generics ::std::fmt::Debug for #item_ident #ty_generics #where_clause {
889            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
890                if f.alternate() {
891                    #get_debug
892                } else {
893                    write!(f, "{}", ::n0_error::StackError::report(self).full())?;
894                }
895                Ok(())
896            }
897        }
898
899        impl #impl_generics ::std::error::Error for #item_ident #ty_generics #where_clause {
900            fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
901                #get_std_source
902            }
903        }
904
905        #from_impl
906    }
907}
908
909enum DisplayArgs {
910    Default,
911    Transparent,
912    Format(proc_macro2::TokenStream),
913}
914
915impl DisplayArgs {
916    fn is_transparent(&self) -> bool {
917        matches!(self, Self::Transparent)
918    }
919
920    fn parse(attrs: &[Attribute]) -> Result<DisplayArgs, syn::Error> {
921        // Only consider #[error(...)]
922        let Some(attr) = attrs.iter().find(|a| a.path().is_ident("error")) else {
923            return Ok(DisplayArgs::Default);
924        };
925
926        let args: Punctuated<Expr, syn::Token![,]> =
927            attr.parse_args_with(Punctuated::<Expr, syn::Token![,]>::parse_terminated)?;
928
929        if args.is_empty() {
930            return Err(err(
931                attr,
932                "#[error(..)] requires arguments: a format string or `transparent`",
933            ));
934        }
935
936        // #[error(transparent)]
937        if args.len() == 1 {
938            if let Expr::Path(p) = &args[0] {
939                if p.path.is_ident("transparent") {
940                    return Ok(DisplayArgs::Transparent);
941                }
942            }
943        }
944
945        // #[error("...", args...)]
946        let mut args = args.into_iter();
947        let first = args.next().unwrap();
948        let fmt_lit = match first {
949            Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) => s,
950            other => return Err(err(
951                other,
952                "first argument to #[error(\"...\")] must be a string literal, or use #[error(transparent)]",
953            ))
954        };
955
956        let rest: Vec<Expr> = args.collect();
957        Ok(DisplayArgs::Format(
958            quote! { write!(f, #fmt_lit #(, #rest)* ) },
959        ))
960    }
961}
962
963fn err(ident: impl ToTokens, err: impl ToString) -> syn::Error {
964    syn::Error::new_spanned(ident, err.to_string())
965}
966
967fn parse_error_attr_as_idents(attrs: &[Attribute]) -> Result<Vec<Ident>, syn::Error> {
968    let mut out = vec![];
969    for attr in attrs.iter().filter(|a| a.path().is_ident("error")) {
970        let idents = attr.parse_args_with(|input: syn::parse::ParseStream<'_>| {
971            let list: Punctuated<Ident, syn::Token![,]> =
972                Punctuated::<Ident, syn::Token![,]>::parse_terminated(input)?;
973            Ok(list.into_iter())
974        })?;
975        out.extend(idents);
976    }
977    Ok(out)
978}