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 = "e!(::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