Skip to main content

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/// ```ignore
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/// ```ignore
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_some(source_field).flatten());
423        let meta_field = fields.iter().find(|f| f.is_meta()).cloned();
424        Ok(VariantInfo {
425            ident,
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_n(quote! { _ }, 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 generate_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.generate_from_impl()).map(|(ty, construct)| {
661        quote! {
662            impl #impl_generics ::core::convert::From<#ty> for #enum_ident #ty_generics #where_clause {
663                #[allow(unreachable_code)]
664                #[track_caller]
665                fn from(source: #ty) -> Self {
666                    #construct
667                }
668            }
669        }
670    });
671
672    quote! {
673        impl #impl_generics ::n0_error::StackError for #enum_ident #ty_generics #where_clause {
674            fn as_std(&self) -> &(dyn ::std::error::Error + ::std::marker::Send + ::std::marker::Sync + 'static) {
675                self
676            }
677            fn into_std(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn std::error::Error + ::std::marker::Send + ::std::marker::Sync> {
678                self
679            }
680            fn as_dyn(&self) -> &(dyn ::n0_error::StackError) {
681                self
682            }
683            fn fmt_message(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
684                match self {
685                    #( #match_fmt_message_arms, )*
686                }
687            }
688            fn meta(&self) -> Option<&::n0_error::Meta> {
689                match self {
690                    #( #match_meta_arms, )*
691                }
692            }
693            fn source(&self) -> Option<::n0_error::ErrorRef<'_>> {
694                match self {
695                    #( #match_source_arms )*
696                }
697            }
698            fn is_transparent(&self) -> bool {
699                match self {
700                    #( #match_transparent_arms, )*
701                }
702            }
703        }
704
705        impl #impl_generics ::core::convert::From<#enum_ident #ty_generics> for ::n0_error::AnyError #where_clause {
706            fn from(value: #enum_ident #ty_generics) -> ::n0_error::AnyError {
707                ::n0_error::AnyError::from_stack(value)
708            }
709        }
710
711        impl #impl_generics ::std::fmt::Display for #enum_ident #ty_generics #where_clause {
712            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
713                let sources = f.alternate().then_some(::n0_error::SourceFormat::OneLine);
714                write!(f, "{}", ::n0_error::StackError::report(self).sources(sources))
715            }
716        }
717
718        impl #impl_generics ::std::fmt::Debug for #enum_ident #ty_generics #where_clause {
719            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
720                if f.alternate() {
721                    match self {
722                        #(#match_debug_arms)*
723                    }
724                } else {
725                    write!(f, "{}", ::n0_error::StackError::report(self).full())?;
726                }
727                Ok(())
728            }
729        }
730
731        impl #impl_generics ::std::error::Error for #enum_ident #ty_generics #where_clause {
732            fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
733                match self {
734                    #( #match_std_source_arms )*
735                }
736            }
737        }
738        #( #from_impls )*
739    }
740}
741
742fn generate_struct_impl(
743    item_ident: &Ident,
744    generics: &syn::Generics,
745    info: VariantInfo,
746) -> proc_macro2::TokenStream {
747    let constructor = {
748        let params = info.fields.iter().filter(|f| !f.is_meta()).map(|f| {
749            let ty = &f.field.ty;
750            let ident = f.ident.as_ident();
751            quote! { #ident: #ty }
752        });
753        let fields = info.fields.iter().map(|f| {
754            if f.is_meta() {
755                f.ident.init(quote!(::n0_error::Meta::new()))
756            } else {
757                let ident = f.ident.as_ident();
758                quote!(#ident)
759            }
760        });
761        let construct = info.spread(fields);
762        let doc = format!("Creates a new [`{}`] error.", item_ident);
763        quote! {
764            #[doc = #doc]
765            #[allow(unreachable_code)]
766            #[track_caller]
767            pub fn new(#(#params),*) -> Self { #construct }
768        }
769    };
770    let get_meta = if let Some(field) = &info.meta {
771        let get_expr = field.ident.self_expr();
772        quote! { Some(&#get_expr) }
773    } else {
774        quote! { None }
775    };
776
777    let get_error_source = match &info.source {
778        Some(src) => src.expr_error_ref(src.field.ident.self_expr()),
779        None => quote! { None },
780    };
781
782    let get_std_source = match &info.source {
783        Some(src) => src.expr_error_std(src.field.ident.self_expr()),
784        None => quote! { None },
785    };
786
787    let is_transparent = info.transparent().is_some();
788
789    let get_fmt_message = {
790        match &info.display {
791            DisplayArgs::Format(expr) => {
792                let pat = info.spread_all();
793                quote! {
794                   #[allow(unused)]
795                   let #pat = self;
796                   #expr
797                }
798            }
799            DisplayArgs::Default | DisplayArgs::Transparent => {
800                let text = info.ident.item_ident().to_string();
801                quote! { write!(f, #text) }
802            }
803        }
804    };
805
806    let get_debug = {
807        let item_name = info.ident.item_ident().to_string();
808        match info.kind {
809            Kind::Unit => quote!(write!(f, #item_name)?;),
810            Kind::Named => {
811                let fields = info
812                    .fields
813                    .iter()
814                    .filter_map(|f| f.ident.named())
815                    .map(|ident| {
816                        let ident_s = ident.to_string();
817                        quote! { dbg.field(#ident_s, &self.#ident) }
818                    });
819                quote! {
820                    let mut dbg = f.debug_struct(#item_name);
821                    #(#fields);*;
822                    dbg.finish()?;
823                }
824            }
825            Kind::Tuple => {
826                let binds = (0..info.fields.len()).map(syn::Index::from);
827                quote! {
828                    let mut dbg = f.debug_tuple(#item_name);
829                    #( dbg.field(&self.#binds); )*;
830                    dbg.finish()?;
831                }
832            }
833        }
834    };
835
836    // From impls for fields marked with #[error(from)] (or inferred via from_sources)
837    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
838    let from_impl = info.generate_from_impl().map(|(ty, construct)| {
839        quote! {
840            impl #impl_generics ::core::convert::From<#ty> for #item_ident #ty_generics #where_clause {
841                #[allow(unreachable_code)]
842                #[track_caller]
843                fn from(source: #ty) -> Self { #construct }
844            }
845        }
846    });
847
848    quote! {
849        impl #impl_generics #item_ident #ty_generics #where_clause {
850            #constructor
851        }
852
853        impl #impl_generics ::n0_error::StackError for #item_ident #ty_generics #where_clause {
854            fn as_std(&self) -> &(dyn ::std::error::Error + ::std::marker::Send + ::std::marker::Sync + 'static) {
855                self
856            }
857            fn into_std(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn std::error::Error + ::std::marker::Send + ::std::marker::Sync> {
858                self
859            }
860            fn as_dyn(&self) -> &(dyn ::n0_error::StackError) {
861                self
862            }
863            fn meta(&self) -> Option<&::n0_error::Meta> {
864                #get_meta
865            }
866            fn source(&self) -> Option<::n0_error::ErrorRef<'_>> {
867                #get_error_source
868            }
869            fn is_transparent(&self) -> bool {
870                #is_transparent
871            }
872            fn fmt_message(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
873                #get_fmt_message
874            }
875        }
876
877
878        impl #impl_generics ::core::convert::From<#item_ident #ty_generics> for ::n0_error::AnyError #where_clause {
879            fn from(value: #item_ident #ty_generics) -> ::n0_error::AnyError {
880                ::n0_error::AnyError::from_stack(value)
881            }
882        }
883
884        impl #impl_generics ::std::fmt::Display for #item_ident #ty_generics #where_clause {
885            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
886                let sources = f.alternate().then_some(::n0_error::SourceFormat::OneLine);
887                write!(f, "{}", ::n0_error::StackError::report(self).sources(sources))
888            }
889        }
890
891        impl #impl_generics ::std::fmt::Debug for #item_ident #ty_generics #where_clause {
892            fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
893                if f.alternate() {
894                    #get_debug
895                } else {
896                    write!(f, "{}", ::n0_error::StackError::report(self).full())?;
897                }
898                Ok(())
899            }
900        }
901
902        impl #impl_generics ::std::error::Error for #item_ident #ty_generics #where_clause {
903            fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
904                #get_std_source
905            }
906        }
907
908        #from_impl
909    }
910}
911
912enum DisplayArgs {
913    Default,
914    Transparent,
915    Format(proc_macro2::TokenStream),
916}
917
918impl DisplayArgs {
919    fn is_transparent(&self) -> bool {
920        matches!(self, Self::Transparent)
921    }
922
923    fn parse(attrs: &[Attribute]) -> Result<DisplayArgs, syn::Error> {
924        // Only consider #[error(...)]
925        let Some(attr) = attrs.iter().find(|a| a.path().is_ident("error")) else {
926            return Ok(DisplayArgs::Default);
927        };
928
929        let args: Punctuated<Expr, syn::Token![,]> =
930            attr.parse_args_with(Punctuated::<Expr, syn::Token![,]>::parse_terminated)?;
931
932        if args.is_empty() {
933            return Err(err(
934                attr,
935                "#[error(..)] requires arguments: a format string or `transparent`",
936            ));
937        }
938
939        // #[error(transparent)]
940        if args.len() == 1 {
941            if let Expr::Path(p) = &args[0] {
942                if p.path.is_ident("transparent") {
943                    return Ok(DisplayArgs::Transparent);
944                }
945            }
946        }
947
948        // #[error("...", args...)]
949        let mut args = args.into_iter();
950        let first = args.next().unwrap();
951        let fmt_lit = match first {
952            Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }) => s,
953            other => return Err(err(
954                other,
955                "first argument to #[error(\"...\")] must be a string literal, or use #[error(transparent)]",
956            ))
957        };
958
959        let rest: Vec<Expr> = args.collect();
960        Ok(DisplayArgs::Format(
961            quote! { write!(f, #fmt_lit #(, #rest)* ) },
962        ))
963    }
964}
965
966fn err(ident: impl ToTokens, err: impl ToString) -> syn::Error {
967    syn::Error::new_spanned(ident, err.to_string())
968}
969
970fn parse_error_attr_as_idents(attrs: &[Attribute]) -> Result<Vec<Ident>, syn::Error> {
971    let mut out = vec![];
972    for attr in attrs.iter().filter(|a| a.path().is_ident("error")) {
973        let idents = attr.parse_args_with(|input: syn::parse::ParseStream<'_>| {
974            let list: Punctuated<Ident, syn::Token![,]> =
975                Punctuated::<Ident, syn::Token![,]>::parse_terminated(input)?;
976            Ok(list.into_iter())
977        })?;
978        out.extend(idents);
979    }
980    Ok(out)
981}