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