wherror_impl/
expand.rs

1use crate::ast::{Enum, Field, Input, Struct};
2use crate::attr::Trait;
3use crate::fallback;
4use crate::generics::InferredBounds;
5use crate::unraw::MemberUnraw;
6use proc_macro2::{Ident, Span, TokenStream};
7use quote::{format_ident, quote, quote_spanned, ToTokens};
8use std::collections::BTreeSet as Set;
9use syn::{DeriveInput, GenericArgument, PathArguments, Result, Token, Type};
10
11pub fn derive(input: &DeriveInput) -> TokenStream {
12    match try_expand(input) {
13        Ok(expanded) => expanded,
14        // If there are invalid attributes in the input, expand to an Error impl
15        // anyway to minimize spurious secondary errors in other code that uses
16        // this type as an Error.
17        Err(error) => fallback::expand(input, error),
18    }
19}
20
21fn try_expand(input: &DeriveInput) -> Result<TokenStream> {
22    let input = Input::from_syn(input)?;
23    input.validate()?;
24    Ok(match input {
25        Input::Struct(input) => impl_struct(input),
26        Input::Enum(input) => impl_enum(input),
27    })
28}
29
30fn impl_struct(input: Struct) -> TokenStream {
31    let ty = call_site_ident(&input.ident);
32    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
33    let mut error_inferred_bounds = InferredBounds::new();
34
35    let source_body = if let Some(transparent_attr) = &input.attrs.transparent {
36        let only_field = &input.fields[0];
37        if only_field.contains_generic {
38            error_inferred_bounds.insert(only_field.ty, quote!(::wherror::__private::Error));
39        }
40        let member = &only_field.member;
41        Some(quote_spanned! {transparent_attr.span=>
42            ::wherror::__private::Error::source(self.#member.as_dyn_error())
43        })
44    } else if let Some(source_field) = input.source_field() {
45        let source = &source_field.member;
46        if source_field.contains_generic {
47            let ty = unoptional_type(source_field.ty);
48            error_inferred_bounds.insert(ty, quote!(::wherror::__private::Error + 'static));
49        }
50        let asref = if type_is_option(source_field.ty) {
51            Some(quote_spanned!(source.span()=> .as_ref()?))
52        } else {
53            None
54        };
55        let dyn_error = quote_spanned! {source_field.source_span()=>
56            self.#source #asref.as_dyn_error()
57        };
58        Some(quote! {
59            ::core::option::Option::Some(#dyn_error)
60        })
61    } else {
62        None
63    };
64    let source_method = source_body.map(|body| {
65        quote! {
66            fn source(&self) -> ::core::option::Option<&(dyn ::wherror::__private::Error + 'static)> {
67                use ::wherror::__private::AsDynError as _;
68                #body
69            }
70        }
71    });
72
73    let provide_method = input.backtrace_field().map(|backtrace_field| {
74        let request = quote!(request);
75        let backtrace = &backtrace_field.member;
76        let body = if let Some(source_field) = input.source_field() {
77            let source = &source_field.member;
78            let source_provide = if type_is_option(source_field.ty) {
79                quote_spanned! {source.span()=>
80                    if let ::core::option::Option::Some(source) = &self.#source {
81                        source.thiserror_provide(#request);
82                    }
83                }
84            } else {
85                quote_spanned! {source.span()=>
86                    self.#source.thiserror_provide(#request);
87                }
88            };
89            let self_provide = if source == backtrace {
90                None
91            } else if type_is_option(backtrace_field.ty) {
92                Some(quote! {
93                    if let ::core::option::Option::Some(backtrace) = &self.#backtrace {
94                        #request.provide_ref::<::wherror::__private::Backtrace>(backtrace);
95                    }
96                })
97            } else {
98                Some(quote! {
99                    #request.provide_ref::<::wherror::__private::Backtrace>(&self.#backtrace);
100                })
101            };
102            quote! {
103                use ::wherror::__private::ThiserrorProvide as _;
104                #source_provide
105                #self_provide
106            }
107        } else if type_is_option(backtrace_field.ty) {
108            quote! {
109                if let ::core::option::Option::Some(backtrace) = &self.#backtrace {
110                    #request.provide_ref::<::wherror::__private::Backtrace>(backtrace);
111                }
112            }
113        } else {
114            quote! {
115                #request.provide_ref::<::wherror::__private::Backtrace>(&self.#backtrace);
116            }
117        };
118        quote! {
119            fn provide<'_request>(&'_request self, #request: &mut ::core::error::Request<'_request>) {
120                #body
121            }
122        }
123    });
124
125    let mut display_implied_bounds = Set::new();
126    let display_body = if input.attrs.transparent.is_some() {
127        let only_field = &input.fields[0].member;
128        display_implied_bounds.insert((0, Trait::Display));
129        Some(quote! {
130            ::core::fmt::Display::fmt(&self.#only_field, __formatter)
131        })
132    } else if let Some(display) = &input.attrs.display {
133        display_implied_bounds.clone_from(&display.implied_bounds);
134        let use_as_display = use_as_display(display.has_bonus_display);
135        let pat = fields_pat(&input.fields);
136        Some(quote! {
137            #use_as_display
138            #[allow(unused_variables, deprecated)]
139            let Self #pat = self;
140            #display
141        })
142    } else if let Some(debug_attr) = &input.attrs.debug {
143        // Fall back to Debug representation when #[error(debug)] is specified
144        Some(quote_spanned! {debug_attr.span=>
145            ::core::fmt::Debug::fmt(self, __formatter)
146        })
147    } else {
148        None
149    };
150    let display_impl = display_body.map(|body| {
151        let mut display_inferred_bounds = InferredBounds::new();
152        for (field, bound) in display_implied_bounds {
153            let field = &input.fields[field];
154            if field.contains_generic {
155                display_inferred_bounds.insert(field.ty, bound);
156            }
157        }
158        let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics);
159        quote! {
160            #[allow(unused_qualifications)]
161            #[automatically_derived]
162            impl #impl_generics ::core::fmt::Display for #ty #ty_generics #display_where_clause {
163                #[allow(clippy::used_underscore_binding)]
164                fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
165                    #body
166                }
167            }
168        }
169    });
170
171    let from_impl = input.from_field().map(|from_field| {
172        let span = from_field.attrs.from.unwrap().span;
173        let backtrace_field = input.distinct_backtrace_field();
174        let from = unoptional_type(from_field.ty);
175        let track_caller = input.location_field().map(|_| quote!(#[track_caller]));
176        let source_var = Ident::new("source", span);
177        let body = from_initializer(
178            from_field,
179            backtrace_field,
180            &source_var,
181            input.location_field(),
182        );
183
184        // Check if the field type (after unwrapping Option) is Box<T>
185        let field_type = type_parameter_of_option(from_field.ty).unwrap_or(from_field.ty);
186        let box_implementations = type_parameter_of_box(field_type).map(|inner_type| {
187            // Generate From<T> implementation that boxes the value
188            let inner_source_var = Ident::new("source", span);
189            let boxed_body = from_initializer_for_box(
190                from_field,
191                backtrace_field,
192                &inner_source_var,
193                input.location_field(),
194            );
195            let inner_from_function = quote! {
196                #track_caller
197                fn from(#inner_source_var: #inner_type) -> Self {
198                    #ty #boxed_body
199                }
200            };
201            let inner_from_impl = quote_spanned! {span=>
202                #[automatically_derived]
203                impl #impl_generics ::core::convert::From<#inner_type> for #ty #ty_generics #where_clause {
204                    #inner_from_function
205                }
206            };
207            quote! {
208                #[allow(
209                    deprecated,
210                    unused_qualifications,
211                    clippy::elidable_lifetime_names,
212                    clippy::needless_lifetimes,
213                )]
214                #inner_from_impl
215            }
216        });
217
218        let from_function = quote! {
219            #track_caller
220            fn from(#source_var: #from) -> Self {
221                #ty #body
222            }
223        };
224        let from_impl = quote_spanned! {span=>
225            #[automatically_derived]
226            impl #impl_generics ::core::convert::From<#from> for #ty #ty_generics #where_clause {
227                #from_function
228            }
229        };
230        Some(quote! {
231            #[allow(
232                deprecated,
233                unused_qualifications,
234                clippy::elidable_lifetime_names,
235                clippy::needless_lifetimes,
236            )]
237            #from_impl
238            #box_implementations
239        })
240    });
241
242    let location_impl = input.location_field().map(|location_field| {
243        let location = &location_field.member;
244        let body = if type_is_option(location_field.ty) {
245            quote! {
246                self.#location
247            }
248        } else {
249            quote! {
250                Some(self.#location)
251            }
252        };
253        quote! {
254            #[allow(unused_qualifications)]
255            #[automatically_derived]
256            impl #impl_generics #ty #ty_generics #where_clause {
257                pub fn location(&self) -> Option<&'static ::core::panic::Location<'static>> {
258                    #body
259                }
260            }
261        }
262    });
263
264    if input.generics.type_params().next().is_some() {
265        let self_token = <Token![Self]>::default();
266        error_inferred_bounds.insert(self_token, Trait::Debug);
267        error_inferred_bounds.insert(self_token, Trait::Display);
268    }
269    let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics);
270
271    quote! {
272        #[allow(unused_qualifications)]
273        #[automatically_derived]
274        impl #impl_generics ::wherror::__private::Error for #ty #ty_generics #error_where_clause {
275            #source_method
276            #provide_method
277        }
278        #display_impl
279        #from_impl
280        #location_impl
281    }
282}
283
284fn impl_enum(input: Enum) -> TokenStream {
285    let ty = call_site_ident(&input.ident);
286    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
287    let mut error_inferred_bounds = InferredBounds::new();
288
289    let source_method = if input.has_source() {
290        let arms = input.variants.iter().map(|variant| {
291            let ident = &variant.ident;
292            if let Some(transparent_attr) = &variant.attrs.transparent {
293                let only_field = &variant.fields[0];
294                if only_field.contains_generic {
295                    error_inferred_bounds.insert(only_field.ty, quote!(::wherror::__private::Error));
296                }
297                let member = &only_field.member;
298                let source = quote_spanned! {transparent_attr.span=>
299                    ::wherror::__private::Error::source(transparent.as_dyn_error())
300                };
301                quote! {
302                    #ty::#ident {#member: transparent} => #source,
303                }
304            } else if let Some(source_field) = variant.source_field() {
305                let source = &source_field.member;
306                if source_field.contains_generic {
307                    let ty = unoptional_type(source_field.ty);
308                    error_inferred_bounds.insert(ty, quote!(::wherror::__private::Error + 'static));
309                }
310                let asref = if type_is_option(source_field.ty) {
311                    Some(quote_spanned!(source.span()=> .as_ref()?))
312                } else {
313                    None
314                };
315                let varsource = quote!(source);
316                let dyn_error = quote_spanned! {source_field.source_span()=>
317                    #varsource #asref.as_dyn_error()
318                };
319                quote! {
320                    #ty::#ident {#source: #varsource, ..} => ::core::option::Option::Some(#dyn_error),
321                }
322            } else {
323                quote! {
324                    #ty::#ident {..} => ::core::option::Option::None,
325                }
326            }
327        });
328        Some(quote! {
329            fn source(&self) -> ::core::option::Option<&(dyn ::wherror::__private::Error + 'static)> {
330                use ::wherror::__private::AsDynError as _;
331                #[allow(deprecated)]
332                match self {
333                    #(#arms)*
334                }
335            }
336        })
337    } else {
338        None
339    };
340
341    let provide_method = if input.has_backtrace() {
342        let request = quote!(request);
343        let arms = input.variants.iter().map(|variant| {
344            let ident = &variant.ident;
345            match (variant.backtrace_field(), variant.source_field()) {
346                (Some(backtrace_field), Some(source_field))
347                    if backtrace_field.attrs.backtrace.is_none() =>
348                {
349                    let backtrace = &backtrace_field.member;
350                    let source = &source_field.member;
351                    let varsource = quote!(source);
352                    let source_provide = if type_is_option(source_field.ty) {
353                        quote_spanned! {source.span()=>
354                            if let ::core::option::Option::Some(source) = #varsource {
355                                source.thiserror_provide(#request);
356                            }
357                        }
358                    } else {
359                        quote_spanned! {source.span()=>
360                            #varsource.thiserror_provide(#request);
361                        }
362                    };
363                    let self_provide = if type_is_option(backtrace_field.ty) {
364                        quote! {
365                            if let ::core::option::Option::Some(backtrace) = backtrace {
366                                #request.provide_ref::<::wherror::__private::Backtrace>(backtrace);
367                            }
368                        }
369                    } else {
370                        quote! {
371                            #request.provide_ref::<::wherror::__private::Backtrace>(backtrace);
372                        }
373                    };
374                    quote! {
375                        #ty::#ident {
376                            #backtrace: backtrace,
377                            #source: #varsource,
378                            ..
379                        } => {
380                            use ::wherror::__private::ThiserrorProvide as _;
381                            #source_provide
382                            #self_provide
383                        }
384                    }
385                }
386                (Some(backtrace_field), Some(source_field))
387                    if backtrace_field.member == source_field.member =>
388                {
389                    let backtrace = &backtrace_field.member;
390                    let varsource = quote!(source);
391                    let source_provide = if type_is_option(source_field.ty) {
392                        quote_spanned! {backtrace.span()=>
393                            if let ::core::option::Option::Some(source) = #varsource {
394                                source.thiserror_provide(#request);
395                            }
396                        }
397                    } else {
398                        quote_spanned! {backtrace.span()=>
399                            #varsource.thiserror_provide(#request);
400                        }
401                    };
402                    quote! {
403                        #ty::#ident {#backtrace: #varsource, ..} => {
404                            use ::wherror::__private::ThiserrorProvide as _;
405                            #source_provide
406                        }
407                    }
408                }
409                (Some(backtrace_field), _) => {
410                    let backtrace = &backtrace_field.member;
411                    let body = if type_is_option(backtrace_field.ty) {
412                        quote! {
413                            if let ::core::option::Option::Some(backtrace) = backtrace {
414                                #request.provide_ref::<::wherror::__private::Backtrace>(backtrace);
415                            }
416                        }
417                    } else {
418                        quote! {
419                            #request.provide_ref::<::wherror::__private::Backtrace>(backtrace);
420                        }
421                    };
422                    quote! {
423                        #ty::#ident {#backtrace: backtrace, ..} => {
424                            #body
425                        }
426                    }
427                }
428                (None, _) => quote! {
429                    #ty::#ident {..} => {}
430                },
431            }
432        });
433        Some(quote! {
434            fn provide<'_request>(&'_request self, #request: &mut ::core::error::Request<'_request>) {
435                #[allow(deprecated)]
436                match self {
437                    #(#arms)*
438                }
439            }
440        })
441    } else {
442        None
443    };
444
445    let display_impl = if input.has_display() {
446        let mut display_inferred_bounds = InferredBounds::new();
447        let has_bonus_display = input.variants.iter().any(|v| {
448            v.attrs
449                .display
450                .as_ref()
451                .map_or(false, |display| display.has_bonus_display)
452        });
453        let use_as_display = use_as_display(has_bonus_display);
454        let void_deref = if input.variants.is_empty() {
455            Some(quote!(*))
456        } else {
457            None
458        };
459        let arms = input.variants.iter().map(|variant| {
460            let mut display_implied_bounds = Set::new();
461            let display = if let Some(display) = &variant.attrs.display {
462                display_implied_bounds.clone_from(&display.implied_bounds);
463                display.to_token_stream()
464            } else if let Some(fmt) = &variant.attrs.fmt {
465                let fmt_path = &fmt.path;
466                let vars = variant.fields.iter().map(|field| match &field.member {
467                    MemberUnraw::Named(ident) => ident.to_local(),
468                    MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
469                });
470                quote!(#fmt_path(#(#vars,)* __formatter))
471            } else if let Some(_transparent_attr) = &variant.attrs.transparent {
472                // Transparent: forward to the single field's Display implementation
473                let only_field = match &variant.fields[0].member {
474                    MemberUnraw::Named(ident) => ident.to_local(),
475                    MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
476                };
477                display_implied_bounds.insert((0, Trait::Display));
478                quote!(::core::fmt::Display::fmt(#only_field, __formatter))
479            } else if let Some(debug_attr) = &variant.attrs.debug {
480                // Variant-level #[error(debug)]: debug the variant fields directly
481                let ident = &variant.ident;
482                let debug_span = debug_attr.span;
483                if variant.fields.is_empty() {
484                    // Unit variant: just display the variant name
485                    let variant_name = ident.to_string();
486                    quote_spanned! {debug_span=>
487                        ::core::write!(__formatter, "{}", #variant_name)
488                    }
489                } else if variant.fields.len() == 1 && matches!(variant.fields[0].member, MemberUnraw::Unnamed(_)) {
490                    // Tuple variant with single field: Format as Variant(field)
491                    let mut field_var = format_ident!("_0");
492                    field_var.set_span(debug_span);
493                    quote_spanned! {debug_span=>
494                        ::core::write!(__formatter, "{}({:?})", stringify!(#ident), #field_var)
495                    }
496                } else if variant.fields.iter().all(|f| matches!(f.member, MemberUnraw::Unnamed(_))) {
497                    // Tuple variant with multiple fields: Format as Variant(field1, field2, ...)
498                    let field_vars: Vec<_> = variant.fields.iter().enumerate().map(|(i, _)| {
499                        let var = format_ident!("_{}", i);
500                        quote_spanned! {debug_span=> #var}
501                    }).collect();
502                    quote_spanned! {debug_span=>
503                        ::core::write!(__formatter, "{}({:?})", stringify!(#ident), (#(#field_vars,)*))
504                    }
505                } else {
506                    // Struct variant: generate proper debug formatting showing all field values
507                    let field_writes: Vec<_> = variant.fields.iter().enumerate().map(|(i, field)| {
508                        let comma = if i < variant.fields.len() - 1 {
509                            quote_spanned! {debug_span=> ::core::write!(__formatter, ", ")?; }
510                        } else {
511                            quote_spanned! {debug_span=> }
512                        };
513                        match &field.member {
514                            MemberUnraw::Named(ident) => {
515                                let field_name = ident.to_string();
516                                let var = ident.to_local();
517                                quote_spanned! {debug_span=>
518                                    ::core::write!(__formatter, "{}: {:?}", #field_name, #var)?;
519                                    #comma
520                                }
521                            }
522                            MemberUnraw::Unnamed(index) => {
523                                let var = format_ident!("_{}", index);
524                                quote_spanned! {debug_span=>
525                                    ::core::write!(__formatter, "{:?}", #var)?;
526                                    #comma
527                                }
528                            }
529                        }
530                    }).collect();
531
532                    quote_spanned! {debug_span=>
533                        {
534                            ::core::write!(__formatter, "{} {{ ", stringify!(#ident))?;
535                            #(#field_writes)*
536                            ::core::write!(__formatter, " }}")
537                        }
538                    }
539                }
540            } else if let Some(debug_attr) = &input.attrs.debug {
541                // Top-level #[error(debug)]: fall back to Debug representation of the variant
542                // This implements the feature request from issue #172
543                let ident = &variant.ident;
544                if variant.fields.is_empty() {
545                    // Unit variant: just display the variant name
546                    let variant_name = ident.to_string();
547                    quote_spanned! {debug_attr.span=>
548                        ::core::write!(__formatter, "{}", #variant_name)
549                    }
550                } else if variant.fields.len() == 1 && matches!(variant.fields[0].member, MemberUnraw::Unnamed(_)) {
551                    // Tuple variant with single field: Format as Variant(field)
552                    let mut field_var = format_ident!("_0");
553                    field_var.set_span(debug_attr.span);
554                    quote_spanned! {debug_attr.span=>
555                        ::core::write!(__formatter, "{}({:?})", stringify!(#ident), #field_var)
556                    }
557                } else if variant.fields.iter().all(|f| matches!(f.member, MemberUnraw::Unnamed(_))) {
558                    // Tuple variant with multiple fields: Format as Variant(field1, field2, ...)
559                    let field_vars: Vec<_> = variant.fields.iter().enumerate().map(|(i, _)| {
560                        let mut var = format_ident!("_{}", i);
561                        var.set_span(debug_attr.span);
562                        var
563                    }).collect();
564                    quote_spanned! {debug_attr.span=>
565                        ::core::write!(__formatter, "{}({:?})", stringify!(#ident), (#(#field_vars,)*))
566                    }
567                } else {
568                    // Struct variant: generate proper debug formatting showing all field values
569                    let field_writes: Vec<_> = variant.fields.iter().enumerate().map(|(i, field)| {
570                        let comma = if i < variant.fields.len() - 1 {
571                            quote_spanned! {debug_attr.span=> ::core::write!(__formatter, ", ")?; }
572                        } else {
573                            quote_spanned! {debug_attr.span=> }
574                        };
575                        match &field.member {
576                            MemberUnraw::Named(ident) => {
577                                let field_name = ident.to_string();
578                                let var = ident.to_local();
579                                quote_spanned! {debug_attr.span=>
580                                    ::core::write!(__formatter, "{}: {:?}", #field_name, #var)?;
581                                    #comma
582                                }
583                            }
584                            MemberUnraw::Unnamed(index) => {
585                                let var = format_ident!("_{}", index);
586                                quote_spanned! {debug_attr.span=>
587                                    ::core::write!(__formatter, "{:?}", #var)?;
588                                    #comma
589                                }
590                            }
591                        }
592                    }).collect();
593
594                    quote_spanned! {debug_attr.span=>
595                        {
596                            ::core::write!(__formatter, "{} {{ ", stringify!(#ident))?;
597                            #(#field_writes)*
598                            ::core::write!(__formatter, " }}")
599                        }
600                    }
601                }
602            } else {
603                // This should be caught by validation - no display attribute and no fallback
604                quote!(unreachable!("missing display attribute should have been caught by validation"))
605            };
606            for (field, bound) in display_implied_bounds {
607                let field = &variant.fields[field];
608                if field.contains_generic {
609                    display_inferred_bounds.insert(field.ty, bound);
610                }
611            }
612            let ident = &variant.ident;
613            let pat = fields_pat(&variant.fields);
614            quote! {
615                #ty::#ident #pat => #display
616            }
617        });
618        let arms = arms.collect::<Vec<_>>();
619        let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics);
620        Some(quote! {
621            #[allow(unused_qualifications)]
622            #[automatically_derived]
623            impl #impl_generics ::core::fmt::Display for #ty #ty_generics #display_where_clause {
624                fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
625                    #use_as_display
626                    #[allow(unused_variables, deprecated, clippy::used_underscore_binding)]
627                    match #void_deref self {
628                        #(#arms,)*
629                    }
630                }
631            }
632        })
633    } else {
634        None
635    };
636
637    let from_impls = input.variants.iter().flat_map(|variant| {
638        let from_field = variant.from_field()?;
639        let span = from_field.attrs.from.unwrap().span;
640        let backtrace_field = variant.distinct_backtrace_field();
641        let location_field = variant.location_field();
642        let variant_ident = &variant.ident;
643        let from = unoptional_type(from_field.ty);
644        let source_var = Ident::new("source", span);
645        let body = from_initializer(from_field, backtrace_field, &source_var, location_field);
646        let track_caller = location_field.map(|_| quote!(#[track_caller]));
647
648        let mut implementations = Vec::new();
649
650        // Main From implementation (always generated)
651        let from_function = quote! {
652            #track_caller
653            fn from(#source_var: #from) -> Self {
654                #ty::#variant_ident #body
655            }
656        };
657        let from_impl = quote_spanned! {span=>
658            #[automatically_derived]
659            impl #impl_generics ::core::convert::From<#from> for #ty #ty_generics #where_clause {
660                #from_function
661            }
662        };
663        implementations.push(quote! {
664            #[allow(
665                deprecated,
666                unused_qualifications,
667                clippy::elidable_lifetime_names,
668                clippy::needless_lifetimes,
669            )]
670            #from_impl
671        });
672
673        // Check if the field type (after unwrapping Option) is Box<T>
674        let field_type = type_parameter_of_option(from_field.ty).unwrap_or(from_field.ty);
675        if let Some(inner_type) = type_parameter_of_box(field_type) {
676            // Generate additional From<T> implementation that boxes the value
677            let inner_source_var = Ident::new("source", span);
678            let boxed_body = from_initializer_for_box(
679                from_field,
680                backtrace_field,
681                &inner_source_var,
682                location_field,
683            );
684            let inner_from_function = quote! {
685                #track_caller
686                fn from(#inner_source_var: #inner_type) -> Self {
687                    #ty::#variant_ident #boxed_body
688                }
689            };
690            let inner_from_impl = quote_spanned! {span=>
691                #[automatically_derived]
692                impl #impl_generics ::core::convert::From<#inner_type> for #ty #ty_generics #where_clause {
693                    #inner_from_function
694                }
695            };
696            implementations.push(quote! {
697                #[allow(
698                    deprecated,
699                    unused_qualifications,
700                    clippy::elidable_lifetime_names,
701                    clippy::needless_lifetimes,
702                )]
703                #inner_from_impl
704            });
705        }
706
707        Some(implementations)
708    }).flatten();
709
710    let location_impl = if input.has_location() {
711        let arms = input.variants.iter().map(|variant| {
712            let ident = &variant.ident;
713            if let Some(location_field) = variant.location_field() {
714                let location = &location_field.member;
715                let var_location = quote!(location);
716                let body = if type_is_option(location_field.ty) {
717                    quote! {
718                        #var_location
719                    }
720                } else {
721                    quote! {
722                        Some(#var_location)
723                    }
724                };
725                quote! {
726                    #ty::#ident {#location: #var_location, ..} => #body,
727                }
728            } else {
729                quote! {
730                    #ty::#ident {..} => None,
731                }
732            }
733        });
734        Some(quote! {
735            #[allow(unused_qualifications)]
736            #[automatically_derived]
737            impl #impl_generics #ty #ty_generics #where_clause {
738                pub fn location(&self) -> Option<&'static ::core::panic::Location<'static>> {
739                    #[allow(deprecated)]
740                    match self {
741                        #(#arms)*
742                    }
743                }
744            }
745        })
746    } else {
747        None
748    };
749
750    if input.generics.type_params().next().is_some() {
751        let self_token = <Token![Self]>::default();
752        error_inferred_bounds.insert(self_token, Trait::Debug);
753        error_inferred_bounds.insert(self_token, Trait::Display);
754    }
755    let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics);
756
757    quote! {
758        #[allow(unused_qualifications)]
759        #[automatically_derived]
760        impl #impl_generics ::wherror::__private::Error for #ty #ty_generics #error_where_clause {
761            #source_method
762            #provide_method
763        }
764        #display_impl
765        #(#from_impls)*
766        #location_impl
767    }
768}
769
770// Create an ident with which we can expand `impl Trait for #ident {}` on a
771// deprecated type without triggering deprecation warning on the generated impl.
772pub(crate) fn call_site_ident(ident: &Ident) -> Ident {
773    let mut ident = ident.clone();
774    ident.set_span(ident.span().resolved_at(Span::call_site()));
775    ident
776}
777
778fn fields_pat(fields: &[Field]) -> TokenStream {
779    let mut members = fields.iter().map(|field| &field.member).peekable();
780    match members.peek() {
781        Some(MemberUnraw::Named(_)) => quote!({ #(#members),* }),
782        Some(MemberUnraw::Unnamed(_)) => {
783            let vars = members.map(|member| match member {
784                MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
785                MemberUnraw::Named(_) => unreachable!(),
786            });
787            quote!((#(#vars),*))
788        }
789        None => quote!({}),
790    }
791}
792
793fn use_as_display(needs_as_display: bool) -> Option<TokenStream> {
794    if needs_as_display {
795        Some(quote! {
796            use ::wherror::__private::AsDisplay as _;
797        })
798    } else {
799        None
800    }
801}
802
803fn from_initializer(
804    from_field: &Field,
805    backtrace_field: Option<&Field>,
806    source_var: &Ident,
807    location_field: Option<&Field>,
808) -> TokenStream {
809    let from_member = &from_field.member;
810    let some_source = if type_is_option(from_field.ty) {
811        quote!(::core::option::Option::Some(#source_var))
812    } else {
813        quote!(#source_var)
814    };
815    let backtrace = backtrace_field.map(|backtrace_field| {
816        let backtrace_member = &backtrace_field.member;
817        if type_is_option(backtrace_field.ty) {
818            quote! {
819                #backtrace_member: ::core::option::Option::Some(::wherror::__private::Backtrace::capture()),
820            }
821        } else {
822            quote! {
823                #backtrace_member: ::core::convert::From::from(::wherror::__private::Backtrace::capture()),
824            }
825        }
826    });
827    let location = location_field.map(|location_field| {
828        let location_member = &location_field.member;
829
830        if type_is_option(location_field.ty) {
831            quote! {
832                #location_member: ::core::option::Option::Some(::core::panic::Location::caller()),
833            }
834        } else {
835            quote! {
836                #location_member: ::core::convert::From::from(::core::panic::Location::caller()),
837            }
838        }
839    });
840    quote!({
841        #from_member: #some_source,
842        #backtrace
843        #location
844    })
845}
846
847fn from_initializer_for_box(
848    from_field: &Field,
849    backtrace_field: Option<&Field>,
850    source_var: &Ident,
851    location_field: Option<&Field>,
852) -> TokenStream {
853    let from_member = &from_field.member;
854    // For Box<T> fields, we need to Box::new the source when receiving T
855    let some_source = if type_is_option(from_field.ty) {
856        quote!(::core::option::Option::Some(::std::boxed::Box::new(#source_var)))
857    } else {
858        quote!(::std::boxed::Box::new(#source_var))
859    };
860    let backtrace = backtrace_field.map(|backtrace_field| {
861        let backtrace_member = &backtrace_field.member;
862        if type_is_option(backtrace_field.ty) {
863            quote! {
864                #backtrace_member: ::core::option::Option::Some(::wherror::__private::Backtrace::capture()),
865            }
866        } else {
867            quote! {
868                #backtrace_member: ::core::convert::From::from(::wherror::__private::Backtrace::capture()),
869            }
870        }
871    });
872    let location = location_field.map(|location_field| {
873        let location_member = &location_field.member;
874
875        if type_is_option(location_field.ty) {
876            quote! {
877                #location_member: ::core::option::Option::Some(::core::panic::Location::caller()),
878            }
879        } else {
880            quote! {
881                #location_member: ::core::convert::From::from(::core::panic::Location::caller()),
882            }
883        }
884    });
885    quote!({
886        #from_member: #some_source,
887        #backtrace
888        #location
889    })
890}
891
892fn type_is_option(ty: &Type) -> bool {
893    type_parameter_of_option(ty).is_some()
894}
895
896fn unoptional_type(ty: &Type) -> TokenStream {
897    let unoptional = type_parameter_of_option(ty).unwrap_or(ty);
898    quote!(#unoptional)
899}
900
901fn type_parameter_of_option(ty: &Type) -> Option<&Type> {
902    let path = match ty {
903        Type::Path(ty) => &ty.path,
904        _ => return None,
905    };
906
907    let last = path.segments.last().unwrap();
908    if last.ident != "Option" {
909        return None;
910    }
911
912    let bracketed = match &last.arguments {
913        PathArguments::AngleBracketed(bracketed) => bracketed,
914        _ => return None,
915    };
916
917    if bracketed.args.len() != 1 {
918        return None;
919    }
920
921    match &bracketed.args[0] {
922        GenericArgument::Type(arg) => Some(arg),
923        _ => None,
924    }
925}
926
927fn type_parameter_of_box(ty: &Type) -> Option<&Type> {
928    let path = match ty {
929        Type::Path(ty) => &ty.path,
930        _ => return None,
931    };
932
933    let last = path.segments.last().unwrap();
934    if last.ident != "Box" {
935        return None;
936    }
937
938    let bracketed = match &last.arguments {
939        PathArguments::AngleBracketed(bracketed) => bracketed,
940        _ => return None,
941    };
942
943    if bracketed.args.len() != 1 {
944        return None;
945    }
946
947    match &bracketed.args[0] {
948        GenericArgument::Type(arg) => Some(arg),
949        _ => None,
950    }
951}