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 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 {
143 None
144 };
145 let display_impl = display_body.map(|body| {
146 let mut display_inferred_bounds = InferredBounds::new();
147 for (field, bound) in display_implied_bounds {
148 let field = &input.fields[field];
149 if field.contains_generic {
150 display_inferred_bounds.insert(field.ty, bound);
151 }
152 }
153 let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics);
154 quote! {
155 #[allow(unused_qualifications)]
156 #[automatically_derived]
157 impl #impl_generics ::core::fmt::Display for #ty #ty_generics #display_where_clause {
158 #[allow(clippy::used_underscore_binding)]
159 fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
160 #body
161 }
162 }
163 }
164 });
165
166 let from_impl = input.from_field().map(|from_field| {
167 let span = from_field.attrs.from.unwrap().span;
168 let backtrace_field = input.distinct_backtrace_field();
169 let from = unoptional_type(from_field.ty);
170 let track_caller = input.location_field().map(|_| quote!(#[track_caller]));
171 let source_var = Ident::new("source", span);
172 let body = from_initializer(
173 from_field,
174 backtrace_field,
175 &source_var,
176 input.location_field(),
177 );
178 let from_function = quote! {
179 #track_caller
180 fn from(#source_var: #from) -> Self {
181 #ty #body
182 }
183 };
184 let from_impl = quote_spanned! {span=>
185 #[automatically_derived]
186 impl #impl_generics ::core::convert::From<#from> for #ty #ty_generics #where_clause {
187 #from_function
188 }
189 };
190 Some(quote! {
191 #[allow(
192 deprecated,
193 unused_qualifications,
194 clippy::elidable_lifetime_names,
195 clippy::needless_lifetimes,
196 )]
197 #from_impl
198 })
199 });
200
201 if input.generics.type_params().next().is_some() {
202 let self_token = <Token![Self]>::default();
203 error_inferred_bounds.insert(self_token, Trait::Debug);
204 error_inferred_bounds.insert(self_token, Trait::Display);
205 }
206 let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics);
207
208 quote! {
209 #[allow(unused_qualifications)]
210 #[automatically_derived]
211 impl #impl_generics ::wherror::__private::Error for #ty #ty_generics #error_where_clause {
212 #source_method
213 #provide_method
214 }
215 #display_impl
216 #from_impl
217 }
218}
219
220fn impl_enum(input: Enum) -> TokenStream {
221 let ty = call_site_ident(&input.ident);
222 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
223 let mut error_inferred_bounds = InferredBounds::new();
224
225 let source_method = if input.has_source() {
226 let arms = input.variants.iter().map(|variant| {
227 let ident = &variant.ident;
228 if let Some(transparent_attr) = &variant.attrs.transparent {
229 let only_field = &variant.fields[0];
230 if only_field.contains_generic {
231 error_inferred_bounds.insert(only_field.ty, quote!(::wherror::__private::Error));
232 }
233 let member = &only_field.member;
234 let source = quote_spanned! {transparent_attr.span=>
235 ::wherror::__private::Error::source(transparent.as_dyn_error())
236 };
237 quote! {
238 #ty::#ident {#member: transparent} => #source,
239 }
240 } else if let Some(source_field) = variant.source_field() {
241 let source = &source_field.member;
242 if source_field.contains_generic {
243 let ty = unoptional_type(source_field.ty);
244 error_inferred_bounds.insert(ty, quote!(::wherror::__private::Error + 'static));
245 }
246 let asref = if type_is_option(source_field.ty) {
247 Some(quote_spanned!(source.span()=> .as_ref()?))
248 } else {
249 None
250 };
251 let varsource = quote!(source);
252 let dyn_error = quote_spanned! {source_field.source_span()=>
253 #varsource #asref.as_dyn_error()
254 };
255 quote! {
256 #ty::#ident {#source: #varsource, ..} => ::core::option::Option::Some(#dyn_error),
257 }
258 } else {
259 quote! {
260 #ty::#ident {..} => ::core::option::Option::None,
261 }
262 }
263 });
264 Some(quote! {
265 fn source(&self) -> ::core::option::Option<&(dyn ::wherror::__private::Error + 'static)> {
266 use ::wherror::__private::AsDynError as _;
267 #[allow(deprecated)]
268 match self {
269 #(#arms)*
270 }
271 }
272 })
273 } else {
274 None
275 };
276
277 let provide_method = if input.has_backtrace() {
278 let request = quote!(request);
279 let arms = input.variants.iter().map(|variant| {
280 let ident = &variant.ident;
281 match (variant.backtrace_field(), variant.source_field()) {
282 (Some(backtrace_field), Some(source_field))
283 if backtrace_field.attrs.backtrace.is_none() =>
284 {
285 let backtrace = &backtrace_field.member;
286 let source = &source_field.member;
287 let varsource = quote!(source);
288 let source_provide = if type_is_option(source_field.ty) {
289 quote_spanned! {source.span()=>
290 if let ::core::option::Option::Some(source) = #varsource {
291 source.thiserror_provide(#request);
292 }
293 }
294 } else {
295 quote_spanned! {source.span()=>
296 #varsource.thiserror_provide(#request);
297 }
298 };
299 let self_provide = if type_is_option(backtrace_field.ty) {
300 quote! {
301 if let ::core::option::Option::Some(backtrace) = backtrace {
302 #request.provide_ref::<::wherror::__private::Backtrace>(backtrace);
303 }
304 }
305 } else {
306 quote! {
307 #request.provide_ref::<::wherror::__private::Backtrace>(backtrace);
308 }
309 };
310 quote! {
311 #ty::#ident {
312 #backtrace: backtrace,
313 #source: #varsource,
314 ..
315 } => {
316 use ::wherror::__private::ThiserrorProvide as _;
317 #source_provide
318 #self_provide
319 }
320 }
321 }
322 (Some(backtrace_field), Some(source_field))
323 if backtrace_field.member == source_field.member =>
324 {
325 let backtrace = &backtrace_field.member;
326 let varsource = quote!(source);
327 let source_provide = if type_is_option(source_field.ty) {
328 quote_spanned! {backtrace.span()=>
329 if let ::core::option::Option::Some(source) = #varsource {
330 source.thiserror_provide(#request);
331 }
332 }
333 } else {
334 quote_spanned! {backtrace.span()=>
335 #varsource.thiserror_provide(#request);
336 }
337 };
338 quote! {
339 #ty::#ident {#backtrace: #varsource, ..} => {
340 use ::wherror::__private::ThiserrorProvide as _;
341 #source_provide
342 }
343 }
344 }
345 (Some(backtrace_field), _) => {
346 let backtrace = &backtrace_field.member;
347 let body = if type_is_option(backtrace_field.ty) {
348 quote! {
349 if let ::core::option::Option::Some(backtrace) = backtrace {
350 #request.provide_ref::<::wherror::__private::Backtrace>(backtrace);
351 }
352 }
353 } else {
354 quote! {
355 #request.provide_ref::<::wherror::__private::Backtrace>(backtrace);
356 }
357 };
358 quote! {
359 #ty::#ident {#backtrace: backtrace, ..} => {
360 #body
361 }
362 }
363 }
364 (None, _) => quote! {
365 #ty::#ident {..} => {}
366 },
367 }
368 });
369 Some(quote! {
370 fn provide<'_request>(&'_request self, #request: &mut ::core::error::Request<'_request>) {
371 #[allow(deprecated)]
372 match self {
373 #(#arms)*
374 }
375 }
376 })
377 } else {
378 None
379 };
380
381 let display_impl = if input.has_display() {
382 let mut display_inferred_bounds = InferredBounds::new();
383 let has_bonus_display = input.variants.iter().any(|v| {
384 v.attrs
385 .display
386 .as_ref()
387 .map_or(false, |display| display.has_bonus_display)
388 });
389 let use_as_display = use_as_display(has_bonus_display);
390 let void_deref = if input.variants.is_empty() {
391 Some(quote!(*))
392 } else {
393 None
394 };
395 let arms = input.variants.iter().map(|variant| {
396 let mut display_implied_bounds = Set::new();
397 let display = if let Some(display) = &variant.attrs.display {
398 display_implied_bounds.clone_from(&display.implied_bounds);
399 display.to_token_stream()
400 } else if let Some(fmt) = &variant.attrs.fmt {
401 let fmt_path = &fmt.path;
402 let vars = variant.fields.iter().map(|field| match &field.member {
403 MemberUnraw::Named(ident) => ident.to_local(),
404 MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
405 });
406 quote!(#fmt_path(#(#vars,)* __formatter))
407 } else {
408 let only_field = match &variant.fields[0].member {
409 MemberUnraw::Named(ident) => ident.to_local(),
410 MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
411 };
412 display_implied_bounds.insert((0, Trait::Display));
413 quote!(::core::fmt::Display::fmt(#only_field, __formatter))
414 };
415 for (field, bound) in display_implied_bounds {
416 let field = &variant.fields[field];
417 if field.contains_generic {
418 display_inferred_bounds.insert(field.ty, bound);
419 }
420 }
421 let ident = &variant.ident;
422 let pat = fields_pat(&variant.fields);
423 quote! {
424 #ty::#ident #pat => #display
425 }
426 });
427 let arms = arms.collect::<Vec<_>>();
428 let display_where_clause = display_inferred_bounds.augment_where_clause(input.generics);
429 Some(quote! {
430 #[allow(unused_qualifications)]
431 #[automatically_derived]
432 impl #impl_generics ::core::fmt::Display for #ty #ty_generics #display_where_clause {
433 fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
434 #use_as_display
435 #[allow(unused_variables, deprecated, clippy::used_underscore_binding)]
436 match #void_deref self {
437 #(#arms,)*
438 }
439 }
440 }
441 })
442 } else {
443 None
444 };
445
446 let from_impls = input.variants.iter().filter_map(|variant| {
447 let from_field = variant.from_field()?;
448 let span = from_field.attrs.from.unwrap().span;
449 let backtrace_field = variant.distinct_backtrace_field();
450 let location_field = variant.location_field();
451 let variant = &variant.ident;
452 let from = unoptional_type(from_field.ty);
453 let source_var = Ident::new("source", span);
454 let body = from_initializer(from_field, backtrace_field, &source_var, location_field);
455 let track_caller = location_field.map(|_| quote!(#[track_caller]));
456 let from_function = quote! {
457 #track_caller
458 fn from(#source_var: #from) -> Self {
459 #ty::#variant #body
460 }
461 };
462 let from_impl = quote_spanned! {span=>
463 #[automatically_derived]
464 impl #impl_generics ::core::convert::From<#from> for #ty #ty_generics #where_clause {
465 #from_function
466 }
467 };
468 Some(quote! {
469 #[allow(
470 deprecated,
471 unused_qualifications,
472 clippy::elidable_lifetime_names,
473 clippy::needless_lifetimes,
474 )]
475 #from_impl
476 })
477 });
478
479 if input.generics.type_params().next().is_some() {
480 let self_token = <Token![Self]>::default();
481 error_inferred_bounds.insert(self_token, Trait::Debug);
482 error_inferred_bounds.insert(self_token, Trait::Display);
483 }
484 let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics);
485
486 quote! {
487 #[allow(unused_qualifications)]
488 #[automatically_derived]
489 impl #impl_generics ::wherror::__private::Error for #ty #ty_generics #error_where_clause {
490 #source_method
491 #provide_method
492 }
493 #display_impl
494 #(#from_impls)*
495 }
496}
497
498pub(crate) fn call_site_ident(ident: &Ident) -> Ident {
501 let mut ident = ident.clone();
502 ident.set_span(ident.span().resolved_at(Span::call_site()));
503 ident
504}
505
506fn fields_pat(fields: &[Field]) -> TokenStream {
507 let mut members = fields.iter().map(|field| &field.member).peekable();
508 match members.peek() {
509 Some(MemberUnraw::Named(_)) => quote!({ #(#members),* }),
510 Some(MemberUnraw::Unnamed(_)) => {
511 let vars = members.map(|member| match member {
512 MemberUnraw::Unnamed(index) => format_ident!("_{}", index),
513 MemberUnraw::Named(_) => unreachable!(),
514 });
515 quote!((#(#vars),*))
516 }
517 None => quote!({}),
518 }
519}
520
521fn use_as_display(needs_as_display: bool) -> Option<TokenStream> {
522 if needs_as_display {
523 Some(quote! {
524 use ::wherror::__private::AsDisplay as _;
525 })
526 } else {
527 None
528 }
529}
530
531fn from_initializer(
532 from_field: &Field,
533 backtrace_field: Option<&Field>,
534 source_var: &Ident,
535 location_field: Option<&Field>,
536) -> TokenStream {
537 let from_member = &from_field.member;
538 let some_source = if type_is_option(from_field.ty) {
539 quote!(::core::option::Option::Some(#source_var))
540 } else {
541 quote!(#source_var)
542 };
543 let backtrace = backtrace_field.map(|backtrace_field| {
544 let backtrace_member = &backtrace_field.member;
545 if type_is_option(backtrace_field.ty) {
546 quote! {
547 #backtrace_member: ::core::option::Option::Some(::wherror::__private::Backtrace::capture()),
548 }
549 } else {
550 quote! {
551 #backtrace_member: ::core::convert::From::from(::wherror::__private::Backtrace::capture()),
552 }
553 }
554 });
555 let location = location_field.map(|location_field| {
556 let location_member = &location_field.member;
557
558 if type_is_option(location_field.ty) {
559 quote! {
560 #location_member: ::core::option::Option::Some(::core::panic::Location::caller()),
561 }
562 } else {
563 quote! {
564 #location_member: ::core::convert::From::from(::core::panic::Location::caller()),
565 }
566 }
567 });
568 quote!({
569 #from_member: #some_source,
570 #backtrace
571 #location
572 })
573}
574
575fn type_is_option(ty: &Type) -> bool {
576 type_parameter_of_option(ty).is_some()
577}
578
579fn unoptional_type(ty: &Type) -> TokenStream {
580 let unoptional = type_parameter_of_option(ty).unwrap_or(ty);
581 quote!(#unoptional)
582}
583
584fn type_parameter_of_option(ty: &Type) -> Option<&Type> {
585 let path = match ty {
586 Type::Path(ty) => &ty.path,
587 _ => return None,
588 };
589
590 let last = path.segments.last().unwrap();
591 if last.ident != "Option" {
592 return None;
593 }
594
595 let bracketed = match &last.arguments {
596 PathArguments::AngleBracketed(bracketed) => bracketed,
597 _ => return None,
598 };
599
600 if bracketed.args.len() != 1 {
601 return None;
602 }
603
604 match &bracketed.args[0] {
605 GenericArgument::Type(arg) => Some(arg),
606 _ => None,
607 }
608}