derive_more/
display.rs

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