1 use std::{
2     collections::{HashMap, HashSet},
3     fmt::Display,
4     iter::FromIterator as _,
5     str::FromStr as _,
6 };
7 
8 use proc_macro2::{Ident, Span, TokenStream};
9 use quote::{quote, quote_spanned};
10 use syn::{
11     parse::Parser as _, punctuated::Punctuated, spanned::Spanned as _, Error, Result,
12 };
13 
14 use crate::utils;
15 
16 /// Provides the hook to expand `#[derive(Display)]` into an implementation of `From`
expand(input: &syn::DeriveInput, trait_name: &str) -> Result<TokenStream>17 pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> Result<TokenStream> {
18     let trait_name = trait_name.trim_end_matches("Custom");
19     let trait_ident = syn::Ident::new(trait_name, Span::call_site());
20     let trait_path = &quote!(::core::fmt::#trait_ident);
21     let trait_attr = trait_name_to_attribute_name(trait_name);
22     let type_params = input
23         .generics
24         .type_params()
25         .map(|t| t.ident.clone())
26         .collect();
27 
28     let ParseResult {
29         arms,
30         bounds,
31         requires_helper,
32     } = State {
33         trait_path,
34         trait_attr,
35         input,
36         type_params,
37     }
38     .get_match_arms_and_extra_bounds()?;
39 
40     let generics = if !bounds.is_empty() {
41         let bounds: Vec<_> = bounds
42             .into_iter()
43             .map(|(ty, trait_names)| {
44                 let bounds: Vec<_> = trait_names
45                     .into_iter()
46                     .map(|bound| quote!(#bound))
47                     .collect();
48                 quote!(#ty: #(#bounds)+*)
49             })
50             .collect();
51         let where_clause = quote_spanned!(input.span()=> where #(#bounds),*);
52         utils::add_extra_where_clauses(&input.generics, where_clause)
53     } else {
54         input.generics.clone()
55     };
56     let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
57     let name = &input.ident;
58 
59     let helper_struct = if requires_helper {
60         display_as_helper_struct()
61     } else {
62         TokenStream::new()
63     };
64 
65     Ok(quote! {
66         impl #impl_generics #trait_path for #name #ty_generics #where_clause
67         {
68             #[allow(unused_variables)]
69             #[inline]
70             fn fmt(&self, _derive_more_display_formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
71                 #helper_struct
72 
73                 match self {
74                     #arms
75                     _ => Ok(()) // This is needed for empty enums
76                 }
77             }
78         }
79     })
80 }
81 
trait_name_to_attribute_name(trait_name: &str) -> &'static str82 fn trait_name_to_attribute_name(trait_name: &str) -> &'static str {
83     match trait_name {
84         "Display" => "display",
85         "Binary" => "binary",
86         "Octal" => "octal",
87         "LowerHex" => "lower_hex",
88         "UpperHex" => "upper_hex",
89         "LowerExp" => "lower_exp",
90         "UpperExp" => "upper_exp",
91         "Pointer" => "pointer",
92         "Debug" => "debug",
93         _ => unimplemented!(),
94     }
95 }
96 
attribute_name_to_trait_name(attribute_name: &str) -> &'static str97 fn attribute_name_to_trait_name(attribute_name: &str) -> &'static str {
98     match attribute_name {
99         "display" => "Display",
100         "binary" => "Binary",
101         "octal" => "Octal",
102         "lower_hex" => "LowerHex",
103         "upper_hex" => "UpperHex",
104         "lower_exp" => "LowerExp",
105         "upper_exp" => "UpperExp",
106         "pointer" => "Pointer",
107         _ => unreachable!(),
108     }
109 }
110 
trait_name_to_trait_bound(trait_name: &str) -> syn::TraitBound111 fn trait_name_to_trait_bound(trait_name: &str) -> syn::TraitBound {
112     let path_segments_iterator = vec!["core", "fmt", trait_name]
113         .into_iter()
114         .map(|segment| syn::PathSegment::from(Ident::new(segment, Span::call_site())));
115 
116     syn::TraitBound {
117         lifetimes: None,
118         modifier: syn::TraitBoundModifier::None,
119         paren_token: None,
120         path: syn::Path {
121             leading_colon: Some(syn::Token![::](Span::call_site())),
122             segments: syn::punctuated::Punctuated::from_iter(path_segments_iterator),
123         },
124     }
125 }
126 
127 /// Create a helper struct that is required by some `Display` impls.
128 ///
129 /// The struct is necessary in cases where `Display` is derived for an enum
130 /// with an outer `#[display(fmt = "...")]` attribute and if that outer
131 /// format-string contains a single placeholder. In that case, we have to
132 /// format twice:
133 ///
134 /// - we need to format each variant according to its own, optional
135 ///   format-string,
136 /// - we then need to insert this formatted variant into the outer
137 ///   format-string.
138 ///
139 /// This helper struct solves this as follows:
140 /// - formatting the whole object inserts the helper struct into the outer
141 ///   format string,
142 /// - upon being formatted, the helper struct calls an inner closure to produce
143 ///   its formatted result,
144 /// - the closure in turn uses the inner, optional format-string to produce its
145 ///   result. If there is no inner format-string, it falls back to plain
146 ///   `$trait::fmt()`.
display_as_helper_struct() -> TokenStream147 fn display_as_helper_struct() -> TokenStream {
148     quote! {
149         struct _derive_more_DisplayAs<F>(F)
150         where
151             F: ::core::ops::Fn(&mut ::core::fmt::Formatter) -> ::core::fmt::Result;
152 
153         const _derive_more_DisplayAs_impl: () = {
154             impl<F> ::core::fmt::Display for _derive_more_DisplayAs<F>
155             where
156                 F: ::core::ops::Fn(&mut ::core::fmt::Formatter) -> ::core::fmt::Result
157             {
158                 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
159                     (self.0)(f)
160                 }
161             }
162         };
163     }
164 }
165 
166 /// Result type of `State::get_match_arms_and_extra_bounds()`.
167 #[derive(Default)]
168 struct ParseResult {
169     /// The match arms destructuring `self`.
170     arms: TokenStream,
171     /// Any trait bounds that may be required.
172     bounds: HashMap<syn::Type, HashSet<syn::TraitBound>>,
173     /// `true` if the Display impl requires the `DisplayAs` helper struct.
174     requires_helper: bool,
175 }
176 
177 struct State<'a, 'b> {
178     trait_path: &'b TokenStream,
179     trait_attr: &'static str,
180     input: &'a syn::DeriveInput,
181     type_params: HashSet<Ident>,
182 }
183 
184 impl<'a, 'b> State<'a, 'b> {
get_proper_fmt_syntax(&self) -> impl Display185     fn get_proper_fmt_syntax(&self) -> impl Display {
186         format!(
187             r#"Proper syntax: #[{}(fmt = "My format", "arg1", "arg2")]"#,
188             self.trait_attr
189         )
190     }
get_proper_bound_syntax(&self) -> impl Display191     fn get_proper_bound_syntax(&self) -> impl Display {
192         format!(
193             "Proper syntax: #[{}(bound = \"T, U: Trait1 + Trait2, V: Trait3\")]",
194             self.trait_attr
195         )
196     }
197 
get_matcher(&self, fields: &syn::Fields) -> TokenStream198     fn get_matcher(&self, fields: &syn::Fields) -> TokenStream {
199         match fields {
200             syn::Fields::Unit => TokenStream::new(),
201             syn::Fields::Unnamed(fields) => {
202                 let fields: TokenStream = (0..fields.unnamed.len())
203                     .map(|n| {
204                         let i = Ident::new(&format!("_{}", n), Span::call_site());
205                         quote!(#i,)
206                     })
207                     .collect();
208                 quote!((#fields))
209             }
210             syn::Fields::Named(fields) => {
211                 let fields: TokenStream = fields
212                     .named
213                     .iter()
214                     .map(|f| {
215                         let i = f.ident.as_ref().unwrap();
216                         quote!(#i,)
217                     })
218                     .collect();
219                 quote!({#fields})
220             }
221         }
222     }
find_meta( &self, attrs: &[syn::Attribute], meta_key: &str, ) -> Result<Option<syn::Meta>>223     fn find_meta(
224         &self,
225         attrs: &[syn::Attribute],
226         meta_key: &str,
227     ) -> Result<Option<syn::Meta>> {
228         let mut iterator = attrs
229             .iter()
230             .filter_map(|attr| attr.parse_meta().ok())
231             .filter(|meta| {
232                 let meta = match meta {
233                     syn::Meta::List(meta) => meta,
234                     _ => return false,
235                 };
236 
237                 if !meta.path.is_ident(self.trait_attr) || meta.nested.is_empty() {
238                     return false;
239                 }
240 
241                 let meta = match &meta.nested[0] {
242                     syn::NestedMeta::Meta(meta) => meta,
243                     _ => return false,
244                 };
245 
246                 let meta = match meta {
247                     syn::Meta::NameValue(meta) => meta,
248                     _ => return false,
249                 };
250 
251                 meta.path.is_ident(meta_key)
252             });
253 
254         let meta = iterator.next();
255         if iterator.next().is_none() {
256             Ok(meta)
257         } else {
258             Err(Error::new(meta.span(), "Too many attributes specified"))
259         }
260     }
parse_meta_bounds( &self, bounds: &syn::LitStr, ) -> Result<HashMap<syn::Type, HashSet<syn::TraitBound>>>261     fn parse_meta_bounds(
262         &self,
263         bounds: &syn::LitStr,
264     ) -> Result<HashMap<syn::Type, HashSet<syn::TraitBound>>> {
265         let span = bounds.span();
266 
267         let input = bounds.value();
268         let tokens = TokenStream::from_str(&input)?;
269         let parser = Punctuated::<syn::GenericParam, syn::Token![,]>::parse_terminated;
270 
271         let generic_params = parser
272             .parse2(tokens)
273             .map_err(|error| Error::new(span, error.to_string()))?;
274 
275         if generic_params.is_empty() {
276             return Err(Error::new(span, "No bounds specified"));
277         }
278 
279         let mut bounds = HashMap::new();
280 
281         for generic_param in generic_params {
282             let type_param = match generic_param {
283                 syn::GenericParam::Type(type_param) => type_param,
284                 _ => return Err(Error::new(span, "Only trait bounds allowed")),
285             };
286 
287             if !self.type_params.contains(&type_param.ident) {
288                 return Err(Error::new(
289                     span,
290                     "Unknown generic type argument specified",
291                 ));
292             } else if !type_param.attrs.is_empty() {
293                 return Err(Error::new(span, "Attributes aren't allowed"));
294             } else if type_param.eq_token.is_some() || type_param.default.is_some() {
295                 return Err(Error::new(span, "Default type parameters aren't allowed"));
296             }
297 
298             let ident = type_param.ident.to_string();
299 
300             let ty = syn::Type::Path(syn::TypePath {
301                 qself: None,
302                 path: type_param.ident.into(),
303             });
304             let bounds = bounds.entry(ty).or_insert_with(HashSet::new);
305 
306             for bound in type_param.bounds {
307                 let bound = match bound {
308                     syn::TypeParamBound::Trait(bound) => bound,
309                     _ => return Err(Error::new(span, "Only trait bounds allowed")),
310                 };
311 
312                 if bound.lifetimes.is_some() {
313                     return Err(Error::new(
314                         span,
315                         "Higher-rank trait bounds aren't allowed",
316                     ));
317                 }
318 
319                 bounds.insert(bound);
320             }
321 
322             if bounds.is_empty() {
323                 return Err(Error::new(
324                     span,
325                     format!("No bounds specified for type parameter {}", ident),
326                 ));
327             }
328         }
329 
330         Ok(bounds)
331     }
parse_meta_fmt( &self, meta: &syn::Meta, outer_enum: bool, ) -> Result<(TokenStream, bool)>332     fn parse_meta_fmt(
333         &self,
334         meta: &syn::Meta,
335         outer_enum: bool,
336     ) -> Result<(TokenStream, bool)> {
337         let list = match meta {
338             syn::Meta::List(list) => list,
339             _ => {
340                 return Err(Error::new(meta.span(), self.get_proper_fmt_syntax()));
341             }
342         };
343 
344         match &list.nested[0] {
345             syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
346                 path,
347                 lit: syn::Lit::Str(fmt),
348                 ..
349             })) => match path {
350                 op if op.segments.first().expect("path shouldn't be empty").ident
351                     == "fmt" =>
352                 {
353                     if outer_enum {
354                         if list.nested.iter().skip(1).count() != 0 {
355                             return Err(Error::new(
356                                 list.nested[1].span(),
357                                 "`fmt` formatting requires a single `fmt` argument",
358                             ));
359                         }
360                         // TODO: Check for a single `Display` group?
361                         let fmt_string = match &list.nested[0] {
362                             syn::NestedMeta::Meta(syn::Meta::NameValue(
363                                 syn::MetaNameValue {
364                                     path,
365                                     lit: syn::Lit::Str(s),
366                                     ..
367                                 },
368                             )) if path
369                                 .segments
370                                 .first()
371                                 .expect("path shouldn't be empty")
372                                 .ident
373                                 == "fmt" =>
374                             {
375                                 s.value()
376                             }
377                             // This one has been checked already in get_meta_fmt() method.
378                             _ => unreachable!(),
379                         };
380 
381                         let num_placeholders =
382                             Placeholder::parse_fmt_string(&fmt_string).len();
383                         if num_placeholders > 1 {
384                             return Err(Error::new(
385                                 list.nested[1].span(),
386                                 "fmt string for enum should have at at most 1 placeholder",
387                             ));
388                         }
389                         if num_placeholders == 1 {
390                             return Ok((quote_spanned!(fmt.span()=> #fmt), true));
391                         }
392                     }
393                     let args = list
394                         .nested
395                         .iter()
396                         .skip(1) // skip fmt = "..."
397                         .try_fold(TokenStream::new(), |args, arg| {
398                             let arg = match arg {
399                                 syn::NestedMeta::Lit(syn::Lit::Str(s)) => s,
400                                 syn::NestedMeta::Meta(syn::Meta::Path(i)) => {
401                                     return Ok(quote_spanned!(list.span()=> #args #i,));
402                                 }
403                                 _ => {
404                                     return Err(Error::new(
405                                         arg.span(),
406                                         self.get_proper_fmt_syntax(),
407                                     ))
408                                 }
409                             };
410                             let arg: TokenStream =
411                                 arg.parse().map_err(|e| Error::new(arg.span(), e))?;
412                             Ok(quote_spanned!(list.span()=> #args #arg,))
413                         })?;
414 
415                     Ok((
416                         quote_spanned!(meta.span()=> write!(_derive_more_display_formatter, #fmt, #args)),
417                         false,
418                     ))
419                 }
420                 _ => Err(Error::new(
421                     list.nested[0].span(),
422                     self.get_proper_fmt_syntax(),
423                 )),
424             },
425             _ => Err(Error::new(
426                 list.nested[0].span(),
427                 self.get_proper_fmt_syntax(),
428             )),
429         }
430     }
infer_fmt(&self, fields: &syn::Fields, name: &Ident) -> Result<TokenStream>431     fn infer_fmt(&self, fields: &syn::Fields, name: &Ident) -> Result<TokenStream> {
432         let fields = match fields {
433             syn::Fields::Unit => {
434                 return Ok(quote!(
435                     _derive_more_display_formatter.write_str(stringify!(#name))
436                 ))
437             }
438             syn::Fields::Named(fields) => &fields.named,
439             syn::Fields::Unnamed(fields) => &fields.unnamed,
440         };
441         if fields.is_empty() {
442             return Ok(quote!(
443                 _derive_more_display_formatter.write_str(stringify!(#name))
444             ));
445         } else if fields.len() > 1 {
446             return Err(Error::new(
447                 fields.span(),
448                 "Can not automatically infer format for types with more than 1 field",
449             ));
450         }
451 
452         let trait_path = self.trait_path;
453         if let Some(ident) = &fields.iter().next().as_ref().unwrap().ident {
454             Ok(quote!(#trait_path::fmt(#ident, _derive_more_display_formatter)))
455         } else {
456             Ok(quote!(#trait_path::fmt(_0, _derive_more_display_formatter)))
457         }
458     }
get_match_arms_and_extra_bounds(&self) -> Result<ParseResult>459     fn get_match_arms_and_extra_bounds(&self) -> Result<ParseResult> {
460         let result: Result<_> = match &self.input.data {
461             syn::Data::Enum(e) => {
462                 match self
463                     .find_meta(&self.input.attrs, "fmt")
464                     .and_then(|m| m.map(|m| self.parse_meta_fmt(&m, true)).transpose())?
465                 {
466                     // #[display(fmt = "no placeholder")] on whole enum.
467                     Some((fmt, false)) => {
468                         e.variants.iter().try_for_each(|v| {
469                             if let Some(meta) = self.find_meta(&v.attrs, "fmt")? {
470                                 Err(Error::new(
471                                     meta.span(),
472                                     "`fmt` cannot be used on variant when the whole enum has a format string without a placeholder, maybe you want to add a placeholder?",
473                                 ))
474                             } else {
475                                 Ok(())
476                             }
477                         })?;
478 
479                         Ok(ParseResult {
480                             arms: quote_spanned!(self.input.span()=> _ => #fmt,),
481                             bounds: HashMap::new(),
482                             requires_helper: false,
483                         })
484                     }
485                     // #[display(fmt = "one placeholder: {}")] on whole enum.
486                     Some((outer_fmt, true)) => {
487                         let fmt: Result<TokenStream> = e.variants.iter().try_fold(TokenStream::new(), |arms, v| {
488                             let matcher = self.get_matcher(&v.fields);
489                             let fmt = if let Some(meta) = self.find_meta(&v.attrs, "fmt")? {
490                                 self.parse_meta_fmt(&meta, false)?.0
491                             } else {
492                                 self.infer_fmt(&v.fields, &v.ident)?
493                             };
494                             let name = &self.input.ident;
495                             let v_name = &v.ident;
496                             Ok(quote_spanned!(fmt.span()=> #arms #name::#v_name #matcher => write!(
497                                 _derive_more_display_formatter,
498                                 #outer_fmt,
499                                 _derive_more_DisplayAs(|_derive_more_display_formatter| #fmt)
500                             ),))
501                         });
502                         let fmt = fmt?;
503                         Ok(ParseResult {
504                             arms: quote_spanned!(self.input.span()=> #fmt),
505                             bounds: HashMap::new(),
506                             requires_helper: true,
507                         })
508                     }
509                     // No format attribute on whole enum.
510                     None => e.variants.iter().try_fold(ParseResult::default(), |result, v| {
511                         let ParseResult{ arms, mut bounds, requires_helper } = result;
512                         let matcher = self.get_matcher(&v.fields);
513                         let name = &self.input.ident;
514                         let v_name = &v.ident;
515                         let fmt: TokenStream;
516                         let these_bounds: HashMap<_, _>;
517 
518                         if let Some(meta) = self.find_meta(&v.attrs, "fmt")? {
519                             fmt = self.parse_meta_fmt(&meta, false)?.0;
520                             these_bounds = self.get_used_type_params_bounds(&v.fields, &meta);
521                         } else {
522                             fmt = self.infer_fmt(&v.fields, v_name)?;
523                             these_bounds = self.infer_type_params_bounds(&v.fields);
524                         };
525                         these_bounds.into_iter().for_each(|(ty, trait_names)| {
526                             bounds.entry(ty).or_default().extend(trait_names)
527                         });
528                         let arms = quote_spanned!(self.input.span()=> #arms #name::#v_name #matcher => #fmt,);
529 
530                         Ok(ParseResult{ arms, bounds, requires_helper })
531                     }),
532                 }
533             }
534             syn::Data::Struct(s) => {
535                 let matcher = self.get_matcher(&s.fields);
536                 let name = &self.input.ident;
537                 let fmt: TokenStream;
538                 let bounds: HashMap<_, _>;
539 
540                 if let Some(meta) = self.find_meta(&self.input.attrs, "fmt")? {
541                     fmt = self.parse_meta_fmt(&meta, false)?.0;
542                     bounds = self.get_used_type_params_bounds(&s.fields, &meta);
543                 } else {
544                     fmt = self.infer_fmt(&s.fields, name)?;
545                     bounds = self.infer_type_params_bounds(&s.fields);
546                 }
547 
548                 Ok(ParseResult {
549                     arms: quote_spanned!(self.input.span()=> #name #matcher => #fmt,),
550                     bounds,
551                     requires_helper: false,
552                 })
553             }
554             syn::Data::Union(_) => {
555                 let meta =
556                     self.find_meta(&self.input.attrs, "fmt")?.ok_or_else(|| {
557                         Error::new(
558                             self.input.span(),
559                             "Can not automatically infer format for unions",
560                         )
561                     })?;
562                 let fmt = self.parse_meta_fmt(&meta, false)?.0;
563 
564                 Ok(ParseResult {
565                     arms: quote_spanned!(self.input.span()=> _ => #fmt,),
566                     bounds: HashMap::new(),
567                     requires_helper: false,
568                 })
569             }
570         };
571 
572         let mut result = result?;
573 
574         let meta = match self.find_meta(&self.input.attrs, "bound")? {
575             Some(meta) => meta,
576             _ => return Ok(result),
577         };
578 
579         let span = meta.span();
580 
581         let meta = match meta {
582             syn::Meta::List(meta) => meta.nested,
583             _ => return Err(Error::new(span, self.get_proper_bound_syntax())),
584         };
585 
586         if meta.len() != 1 {
587             return Err(Error::new(span, self.get_proper_bound_syntax()));
588         }
589 
590         let meta = match &meta[0] {
591             syn::NestedMeta::Meta(syn::Meta::NameValue(meta)) => meta,
592             _ => return Err(Error::new(span, self.get_proper_bound_syntax())),
593         };
594 
595         let extra_bounds = match &meta.lit {
596             syn::Lit::Str(extra_bounds) => extra_bounds,
597             _ => return Err(Error::new(span, self.get_proper_bound_syntax())),
598         };
599 
600         let extra_bounds = self.parse_meta_bounds(extra_bounds)?;
601 
602         extra_bounds.into_iter().for_each(|(ty, trait_names)| {
603             result.bounds.entry(ty).or_default().extend(trait_names)
604         });
605 
606         Ok(result)
607     }
get_used_type_params_bounds( &self, fields: &syn::Fields, meta: &syn::Meta, ) -> HashMap<syn::Type, HashSet<syn::TraitBound>>608     fn get_used_type_params_bounds(
609         &self,
610         fields: &syn::Fields,
611         meta: &syn::Meta,
612     ) -> HashMap<syn::Type, HashSet<syn::TraitBound>> {
613         if self.type_params.is_empty() {
614             return HashMap::new();
615         }
616 
617         let fields_type_params: HashMap<syn::Path, _> = fields
618             .iter()
619             .enumerate()
620             .filter_map(|(i, field)| {
621                 utils::get_if_type_parameter_used_in_type(&self.type_params, &field.ty)
622                     .map(|ty| {
623                         (
624                             field
625                                 .ident
626                                 .clone()
627                                 .unwrap_or_else(|| {
628                                     Ident::new(&format!("_{}", i), Span::call_site())
629                                 })
630                                 .into(),
631                             ty,
632                         )
633                     })
634             })
635             .collect();
636         if fields_type_params.is_empty() {
637             return HashMap::new();
638         }
639 
640         let list = match meta {
641             syn::Meta::List(list) => list,
642             // This one has been checked already in get_meta_fmt() method.
643             _ => unreachable!(),
644         };
645         let fmt_args: HashMap<_, _> = list
646             .nested
647             .iter()
648             .skip(1) // skip fmt = "..."
649             .enumerate()
650             .filter_map(|(i, arg)| match arg {
651                 syn::NestedMeta::Lit(syn::Lit::Str(ref s)) => {
652                     syn::parse_str(&s.value()).ok().map(|id| (i, id))
653                 }
654                 syn::NestedMeta::Meta(syn::Meta::Path(ref id)) => Some((i, id.clone())),
655                 // This one has been checked already in get_meta_fmt() method.
656                 _ => unreachable!(),
657             })
658             .collect();
659         if fmt_args.is_empty() {
660             return HashMap::new();
661         }
662         let fmt_string = match &list.nested[0] {
663             syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
664                 path,
665                 lit: syn::Lit::Str(s),
666                 ..
667             })) if path
668                 .segments
669                 .first()
670                 .expect("path shouldn't be empty")
671                 .ident
672                 == "fmt" =>
673             {
674                 s.value()
675             }
676             // This one has been checked already in get_meta_fmt() method.
677             _ => unreachable!(),
678         };
679 
680         Placeholder::parse_fmt_string(&fmt_string).into_iter().fold(
681             HashMap::new(),
682             |mut bounds, pl| {
683                 if let Some(arg) = fmt_args.get(&pl.position) {
684                     if fields_type_params.contains_key(arg) {
685                         bounds
686                             .entry(fields_type_params[arg].clone())
687                             .or_insert_with(HashSet::new)
688                             .insert(trait_name_to_trait_bound(pl.trait_name));
689                     }
690                 }
691                 bounds
692             },
693         )
694     }
infer_type_params_bounds( &self, fields: &syn::Fields, ) -> HashMap<syn::Type, HashSet<syn::TraitBound>>695     fn infer_type_params_bounds(
696         &self,
697         fields: &syn::Fields,
698     ) -> HashMap<syn::Type, HashSet<syn::TraitBound>> {
699         if self.type_params.is_empty() {
700             return HashMap::new();
701         }
702         if let syn::Fields::Unit = fields {
703             return HashMap::new();
704         }
705         // infer_fmt() uses only first field.
706         fields
707             .iter()
708             .take(1)
709             .filter_map(|field| {
710                 utils::get_if_type_parameter_used_in_type(&self.type_params, &field.ty)
711                     .map(|ty| {
712                         (
713                             ty,
714                             [trait_name_to_trait_bound(attribute_name_to_trait_name(
715                                 self.trait_attr,
716                             ))]
717                             .iter()
718                             .cloned()
719                             .collect(),
720                         )
721                     })
722             })
723             .collect()
724     }
725 }
726 
727 /// Representation of formatting placeholder.
728 #[derive(Debug, PartialEq)]
729 struct Placeholder {
730     /// Position of formatting argument to be used for this placeholder.
731     position: usize,
732     /// Name of [`std::fmt`] trait to be used for rendering this placeholder.
733     trait_name: &'static str,
734 }
735 
736 impl Placeholder {
737     /// Parses [`Placeholder`]s from a given formatting string.
parse_fmt_string(s: &str) -> Vec<Placeholder>738     fn parse_fmt_string(s: &str) -> Vec<Placeholder> {
739         let mut n = 0;
740         crate::parsing::all_placeholders(s)
741             .into_iter()
742             .flatten()
743             .map(|m| {
744                 let (maybe_arg, maybe_typ) = crate::parsing::format(m).unwrap();
745                 let position = maybe_arg.unwrap_or_else(|| {
746                     // Assign "the next argument".
747                     // https://doc.rust-lang.org/stable/std/fmt/index.html#positional-parameters
748                     n += 1;
749                     n - 1
750                 });
751                 let typ = maybe_typ.unwrap_or_default();
752                 let trait_name = match typ {
753                     "" => "Display",
754                     "?" | "x?" | "X?" => "Debug",
755                     "o" => "Octal",
756                     "x" => "LowerHex",
757                     "X" => "UpperHex",
758                     "p" => "Pointer",
759                     "b" => "Binary",
760                     "e" => "LowerExp",
761                     "E" => "UpperExp",
762                     _ => unreachable!(),
763                 };
764                 Placeholder {
765                     position,
766                     trait_name,
767                 }
768             })
769             .collect()
770     }
771 }
772 
773 #[cfg(test)]
774 mod regex_maybe_placeholder_spec {
775 
776     #[test]
parses_placeholders_and_omits_escaped()777     fn parses_placeholders_and_omits_escaped() {
778         let fmt_string = "{}, {:?}, {{}}, {{{1:0$}}}";
779         let placeholders: Vec<_> = crate::parsing::all_placeholders(&fmt_string)
780             .into_iter()
781             .flat_map(|x| x)
782             .collect();
783         assert_eq!(placeholders, vec!["{}", "{:?}", "{1:0$}"]);
784     }
785 }
786 
787 #[cfg(test)]
788 mod regex_placeholder_format_spec {
789 
790     #[test]
detects_type()791     fn detects_type() {
792         for (p, expected) in vec![
793             ("{}", ""),
794             ("{:?}", "?"),
795             ("{:x?}", "x?"),
796             ("{:X?}", "X?"),
797             ("{:o}", "o"),
798             ("{:x}", "x"),
799             ("{:X}", "X"),
800             ("{:p}", "p"),
801             ("{:b}", "b"),
802             ("{:e}", "e"),
803             ("{:E}", "E"),
804             ("{:.*}", ""),
805             ("{8}", ""),
806             ("{:04}", ""),
807             ("{1:0$}", ""),
808             ("{:width$}", ""),
809             ("{9:>8.*}", ""),
810             ("{2:.1$x}", "x"),
811         ] {
812             let typ = crate::parsing::format(p).unwrap().1.unwrap_or_default();
813             assert_eq!(typ, expected);
814         }
815     }
816 
817     #[test]
detects_arg()818     fn detects_arg() {
819         for (p, expected) in vec![
820             ("{}", ""),
821             ("{0:?}", "0"),
822             ("{12:x?}", "12"),
823             ("{3:X?}", "3"),
824             ("{5:o}", "5"),
825             ("{6:x}", "6"),
826             ("{:X}", ""),
827             ("{8}", "8"),
828             ("{:04}", ""),
829             ("{1:0$}", "1"),
830             ("{:width$}", ""),
831             ("{9:>8.*}", "9"),
832             ("{2:.1$x}", "2"),
833         ] {
834             let arg = crate::parsing::format(p)
835                 .unwrap()
836                 .0
837                 .map(|s| s.to_string())
838                 .unwrap_or_default();
839             assert_eq!(arg, String::from(expected));
840         }
841     }
842 }
843 
844 #[cfg(test)]
845 mod placeholder_parse_fmt_string_spec {
846     use super::*;
847 
848     #[test]
indicates_position_and_trait_name_for_each_fmt_placeholder()849     fn indicates_position_and_trait_name_for_each_fmt_placeholder() {
850         let fmt_string = "{},{:?},{{}},{{{1:0$}}}-{2:.1$x}{0:#?}{:width$}";
851         assert_eq!(
852             Placeholder::parse_fmt_string(&fmt_string),
853             vec![
854                 Placeholder {
855                     position: 0,
856                     trait_name: "Display",
857                 },
858                 Placeholder {
859                     position: 1,
860                     trait_name: "Debug",
861                 },
862                 Placeholder {
863                     position: 1,
864                     trait_name: "Display",
865                 },
866                 Placeholder {
867                     position: 2,
868                     trait_name: "LowerHex",
869                 },
870                 Placeholder {
871                     position: 0,
872                     trait_name: "Debug",
873                 },
874                 Placeholder {
875                     position: 2,
876                     trait_name: "Display",
877                 },
878             ],
879         )
880     }
881 }
882