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