1#[cfg(feature = "debug")]
6pub(crate) mod debug;
7#[cfg(feature = "display")]
8pub(crate) mod display;
9mod parsing;
10
11use proc_macro2::TokenStream;
12use quote::{format_ident, quote, ToTokens};
13use syn::{
14 ext::IdentExt as _,
15 parse::{Parse, ParseStream},
16 parse_quote,
17 punctuated::Punctuated,
18 spanned::Spanned as _,
19 token,
20};
21
22use crate::{
23 parsing::Expr,
24 utils::{attr, Either, Spanning},
25};
26
27#[derive(Debug, Default)]
35struct BoundsAttribute(Punctuated<syn::WherePredicate, token::Comma>);
36
37impl Parse for BoundsAttribute {
38 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
39 Self::check_legacy_fmt(input)?;
40
41 let _ = input.parse::<syn::Path>().and_then(|p| {
42 if ["bound", "bounds", "where"]
43 .into_iter()
44 .any(|i| p.is_ident(i))
45 {
46 Ok(p)
47 } else {
48 Err(syn::Error::new(
49 p.span(),
50 "unknown attribute argument, expected `bound(...)`",
51 ))
52 }
53 })?;
54
55 let content;
56 syn::parenthesized!(content in input);
57
58 content
59 .parse_terminated(syn::WherePredicate::parse, token::Comma)
60 .map(Self)
61 }
62}
63
64impl BoundsAttribute {
65 fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> {
67 let fork = input.fork();
68
69 let path = fork
70 .parse::<syn::Path>()
71 .and_then(|path| fork.parse::<token::Eq>().map(|_| path));
72 match path {
73 Ok(path) if path.is_ident("bound") => fork
74 .parse::<syn::Lit>()
75 .ok()
76 .and_then(|lit| match lit {
77 syn::Lit::Str(s) => Some(s.value()),
78 _ => None,
79 })
80 .map_or(Ok(()), |bound| {
81 Err(syn::Error::new(
82 input.span(),
83 format!("legacy syntax, use `bound({bound})` instead"),
84 ))
85 }),
86 Ok(_) | Err(_) => Ok(()),
87 }
88 }
89}
90
91#[derive(Debug)]
99struct FmtAttribute {
100 lit: syn::LitStr,
104
105 comma: Option<token::Comma>,
109
110 args: Punctuated<FmtArgument, token::Comma>,
112}
113
114impl Parse for FmtAttribute {
115 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
116 Self::check_legacy_fmt(input)?;
117
118 let mut parsed = Self {
119 lit: input.parse()?,
120 comma: input
121 .peek(token::Comma)
122 .then(|| input.parse())
123 .transpose()?,
124 args: input.parse_terminated(FmtArgument::parse, token::Comma)?,
125 };
126 parsed.args.pop_punct();
127 Ok(parsed)
128 }
129}
130
131impl attr::ParseMultiple for FmtAttribute {}
132
133impl ToTokens for FmtAttribute {
134 fn to_tokens(&self, tokens: &mut TokenStream) {
135 self.lit.to_tokens(tokens);
136 self.comma.to_tokens(tokens);
137 self.args.to_tokens(tokens);
138 }
139}
140
141impl FmtAttribute {
142 fn transparent_call(&self) -> Option<(Expr, syn::Ident)> {
150 let lit = self.lit.value();
154 let param =
155 parsing::format(&lit).and_then(|(more, p)| more.is_empty().then_some(p))?;
156
157 if param
159 .spec
160 .map(|s| {
161 s.align.is_some()
162 || s.sign.is_some()
163 || s.alternate.is_some()
164 || s.zero_padding.is_some()
165 || s.width.is_some()
166 || s.precision.is_some()
167 || !s.ty.is_trivial()
168 })
169 .unwrap_or_default()
170 {
171 return None;
172 }
173
174 let expr = match param.arg {
175 Some(parsing::Argument::Integer(_)) | None => (self.args.len() == 1)
177 .then(|| self.args.first())
178 .flatten()
179 .map(|a| a.expr.clone()),
180
181 Some(parsing::Argument::Identifier(name)) if self.args.is_empty() => {
183 Some(format_ident!("{name}").into())
184 }
185
186 Some(parsing::Argument::Identifier(name)) => (self.args.len() == 1)
188 .then(|| self.args.first())
189 .flatten()
190 .filter(|a| a.alias.as_ref().map(|a| a.0 == name).unwrap_or_default())
191 .map(|a| a.expr.clone()),
192 }?;
193
194 let trait_name = param
195 .spec
196 .map(|s| s.ty)
197 .unwrap_or(parsing::Type::Display)
198 .trait_name();
199
200 Some((expr, format_ident!("{trait_name}")))
201 }
202
203 fn transparent_call_on_fields(
210 &self,
211 fields: &syn::Fields,
212 ) -> Option<(Expr, syn::Ident)> {
213 self.transparent_call().map(|(expr, trait_ident)| {
214 let expr = if let Some(field) = fields
215 .fmt_args_idents()
216 .find(|field| expr == *field || expr == field.unraw())
217 {
218 field.into()
219 } else {
220 parse_quote! { &(#expr) }
221 };
222
223 (expr, trait_ident)
224 })
225 }
226
227 fn bounded_types<'a>(
230 &'a self,
231 fields: &'a syn::Fields,
232 ) -> impl Iterator<Item = (&'a syn::Type, &'static str)> {
233 let placeholders = Placeholder::parse_fmt_string(&self.lit.value());
234
235 placeholders.into_iter().filter_map(move |placeholder| {
237 let name = match placeholder.arg {
238 Parameter::Named(name) => self
239 .args
240 .iter()
241 .find_map(|a| (a.alias()? == &name).then_some(&a.expr))
242 .map_or(Some(name), |expr| expr.ident().map(ToString::to_string))?,
243 Parameter::Positional(i) => self
244 .args
245 .iter()
246 .nth(i)
247 .and_then(|a| a.expr.ident().filter(|_| a.alias.is_none()))?
248 .to_string(),
249 };
250
251 let unnamed = name.strip_prefix('_').and_then(|s| s.parse().ok());
252 let ty = match (&fields, unnamed) {
253 (syn::Fields::Unnamed(f), Some(i)) => {
254 f.unnamed.iter().nth(i).map(|f| &f.ty)
255 }
256 (syn::Fields::Named(f), None) => f.named.iter().find_map(|f| {
257 f.ident
258 .as_ref()
259 .filter(|s| s.unraw() == name)
260 .map(|_| &f.ty)
261 }),
262 _ => None,
263 }?;
264
265 Some((ty, placeholder.trait_name))
266 })
267 }
268
269 #[cfg(feature = "display")]
270 fn contains_arg(&self, name: &str) -> bool {
273 self.placeholders_by_arg(name).next().is_some()
274 }
275
276 #[cfg(feature = "display")]
277 fn placeholders_by_arg<'a>(
281 &'a self,
282 name: &'a str,
283 ) -> impl Iterator<Item = Placeholder> + 'a {
284 let placeholders = Placeholder::parse_fmt_string(&self.lit.value());
285
286 placeholders.into_iter().filter(move |placeholder| {
287 match &placeholder.arg {
288 Parameter::Named(name) => self
289 .args
290 .iter()
291 .find_map(|a| (a.alias()? == name).then_some(&a.expr))
292 .map_or(Some(name.clone()), |expr| {
293 expr.ident().map(ToString::to_string)
294 }),
295 Parameter::Positional(i) => self
296 .args
297 .iter()
298 .nth(*i)
299 .and_then(|a| a.expr.ident().filter(|_| a.alias.is_none()))
300 .map(ToString::to_string),
301 }
302 .as_deref()
303 == Some(name)
304 })
305 }
306
307 fn additional_deref_args<'fmt: 'ret, 'fields: 'ret, 'ret>(
313 &'fmt self,
314 fields: &'fields syn::Fields,
315 ) -> impl Iterator<Item = TokenStream> + 'ret {
316 let used_args = Placeholder::parse_fmt_string(&self.lit.value())
317 .into_iter()
318 .filter_map(|placeholder| match placeholder.arg {
319 Parameter::Named(name) if placeholder.trait_name == "Pointer" => {
320 Some(name)
321 }
322 _ => None,
323 })
324 .collect::<Vec<_>>();
325
326 fields.fmt_args_idents().filter_map(move |field_name| {
327 (used_args.iter().any(|arg| field_name.unraw() == arg)
328 && !self.args.iter().any(|arg| {
329 arg.alias.as_ref().is_some_and(|(n, _)| n == &field_name)
330 }))
331 .then(|| quote! { #field_name = *#field_name })
332 })
333 }
334
335 fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> {
337 let fork = input.fork();
338
339 let path = fork
340 .parse::<syn::Path>()
341 .and_then(|path| fork.parse::<token::Eq>().map(|_| path));
342 match path {
343 Ok(path) if path.is_ident("fmt") => (|| {
344 let args = fork
345 .parse_terminated(
346 <Either<syn::Lit, syn::Ident>>::parse,
347 token::Comma,
348 )
349 .ok()?
350 .into_iter()
351 .enumerate()
352 .filter_map(|(i, arg)| match arg {
353 Either::Left(syn::Lit::Str(str)) => Some(if i == 0 {
354 format!("\"{}\"", str.value())
355 } else {
356 str.value()
357 }),
358 Either::Right(ident) => Some(ident.to_string()),
359 _ => None,
360 })
361 .collect::<Vec<_>>();
362 (!args.is_empty()).then_some(args)
363 })()
364 .map_or(Ok(()), |fmt| {
365 Err(syn::Error::new(
366 input.span(),
367 format!(
368 "legacy syntax, remove `fmt =` and use `{}` instead",
369 fmt.join(", "),
370 ),
371 ))
372 }),
373 Ok(_) | Err(_) => Ok(()),
374 }
375 }
376}
377
378#[derive(Debug)]
382struct FmtArgument {
383 alias: Option<(syn::Ident, token::Eq)>,
387
388 expr: Expr,
390}
391
392impl FmtArgument {
393 fn alias(&self) -> Option<&syn::Ident> {
397 self.alias.as_ref().map(|(ident, _)| ident)
398 }
399}
400
401impl Parse for FmtArgument {
402 fn parse(input: ParseStream) -> syn::Result<Self> {
403 Ok(Self {
404 alias: (input.peek(syn::Ident) && input.peek2(token::Eq))
405 .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?)))
406 .transpose()?,
407 expr: input.parse()?,
408 })
409 }
410}
411
412impl ToTokens for FmtArgument {
413 fn to_tokens(&self, tokens: &mut TokenStream) {
414 if let Some((ident, eq)) = &self.alias {
415 ident.to_tokens(tokens);
416 eq.to_tokens(tokens);
417 }
418 self.expr.to_tokens(tokens);
419 }
420}
421
422#[derive(Debug, Eq, PartialEq)]
426enum Parameter {
427 Positional(usize),
431
432 Named(String),
436}
437
438impl<'a> From<parsing::Argument<'a>> for Parameter {
439 fn from(arg: parsing::Argument<'a>) -> Self {
440 match arg {
441 parsing::Argument::Integer(i) => Self::Positional(i),
442 parsing::Argument::Identifier(i) => Self::Named(i.to_owned()),
443 }
444 }
445}
446
447#[derive(Debug, Eq, PartialEq)]
449struct Placeholder {
450 arg: Parameter,
452
453 has_modifiers: bool,
455
456 trait_name: &'static str,
458}
459
460impl Placeholder {
461 fn parse_fmt_string(s: &str) -> Vec<Self> {
463 let mut n = 0;
464 parsing::format_string(s)
465 .into_iter()
466 .flat_map(|f| f.formats)
467 .map(|format| {
468 let (maybe_arg, ty) = (
469 format.arg,
470 format.spec.map(|s| s.ty).unwrap_or(parsing::Type::Display),
471 );
472 let position = maybe_arg.map(Into::into).unwrap_or_else(|| {
473 n += 1;
476 Parameter::Positional(n - 1)
477 });
478
479 Self {
480 arg: position,
481 has_modifiers: format
482 .spec
483 .map(|s| {
484 s.align.is_some()
485 || s.sign.is_some()
486 || s.alternate.is_some()
487 || s.zero_padding.is_some()
488 || s.width.is_some()
489 || s.precision.is_some()
490 || !s.ty.is_trivial()
491 })
492 .unwrap_or_default(),
493 trait_name: ty.trait_name(),
494 }
495 })
496 .collect()
497 }
498}
499
500#[derive(Debug, Default)]
513struct ContainerAttributes {
514 fmt: Option<FmtAttribute>,
516
517 bounds: BoundsAttribute,
519}
520
521impl Parse for ContainerAttributes {
522 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
523 FmtAttribute::check_legacy_fmt(input)?;
526 <Either<FmtAttribute, BoundsAttribute>>::parse(input).map(|v| match v {
527 Either::Left(fmt) => Self {
528 bounds: BoundsAttribute::default(),
529 fmt: Some(fmt),
530 },
531 Either::Right(bounds) => Self { bounds, fmt: None },
532 })
533 }
534}
535
536impl attr::ParseMultiple for ContainerAttributes {
537 fn merge_attrs(
538 prev: Spanning<Self>,
539 new: Spanning<Self>,
540 name: &syn::Ident,
541 ) -> syn::Result<Spanning<Self>> {
542 let Spanning {
543 span: prev_span,
544 item: mut prev,
545 } = prev;
546 let Spanning {
547 span: new_span,
548 item: new,
549 } = new;
550
551 if new.fmt.and_then(|n| prev.fmt.replace(n)).is_some() {
552 return Err(syn::Error::new(
553 new_span,
554 format!("multiple `#[{name}(\"...\", ...)]` attributes aren't allowed"),
555 ));
556 }
557 prev.bounds.0.extend(new.bounds.0);
558
559 Ok(Spanning::new(
560 prev,
561 prev_span.join(new_span).unwrap_or(prev_span),
562 ))
563 }
564}
565
566fn trait_name_to_attribute_name<T>(trait_name: T) -> &'static str
568where
569 T: for<'a> PartialEq<&'a str>,
570{
571 match () {
572 _ if trait_name == "Binary" => "binary",
573 _ if trait_name == "Debug" => "debug",
574 _ if trait_name == "Display" => "display",
575 _ if trait_name == "LowerExp" => "lower_exp",
576 _ if trait_name == "LowerHex" => "lower_hex",
577 _ if trait_name == "Octal" => "octal",
578 _ if trait_name == "Pointer" => "pointer",
579 _ if trait_name == "UpperExp" => "upper_exp",
580 _ if trait_name == "UpperHex" => "upper_hex",
581 _ => unimplemented!(),
582 }
583}
584
585trait ContainsGenericsExt {
587 fn contains_generics(&self, type_params: &[&syn::Ident]) -> bool;
589}
590
591impl ContainsGenericsExt for syn::Type {
592 fn contains_generics(&self, type_params: &[&syn::Ident]) -> bool {
593 if type_params.is_empty() {
594 return false;
595 }
596 match self {
597 Self::Path(syn::TypePath { qself, path }) => {
598 if let Some(qself) = qself {
599 if qself.ty.contains_generics(type_params) {
600 return true;
601 }
602 }
603
604 if let Some(ident) = path.get_ident() {
605 type_params.iter().any(|param| *param == ident)
606 } else {
607 path.contains_generics(type_params)
608 }
609 }
610
611 Self::Array(syn::TypeArray { elem, .. })
612 | Self::Group(syn::TypeGroup { elem, .. })
613 | Self::Paren(syn::TypeParen { elem, .. })
614 | Self::Ptr(syn::TypePtr { elem, .. })
615 | Self::Reference(syn::TypeReference { elem, .. })
616 | Self::Slice(syn::TypeSlice { elem, .. }) => {
617 elem.contains_generics(type_params)
618 }
619
620 Self::BareFn(syn::TypeBareFn { inputs, output, .. }) => {
621 inputs
622 .iter()
623 .any(|arg| arg.ty.contains_generics(type_params))
624 || match output {
625 syn::ReturnType::Default => false,
626 syn::ReturnType::Type(_, ty) => {
627 ty.contains_generics(type_params)
628 }
629 }
630 }
631
632 Self::Tuple(syn::TypeTuple { elems, .. }) => {
633 elems.iter().any(|ty| ty.contains_generics(type_params))
634 }
635
636 Self::TraitObject(syn::TypeTraitObject { bounds, .. }) => {
637 bounds.iter().any(|bound| match bound {
638 syn::TypeParamBound::Trait(syn::TraitBound { path, .. }) => {
639 path.contains_generics(type_params)
640 }
641 syn::TypeParamBound::Lifetime(..)
642 | syn::TypeParamBound::Verbatim(..) => false,
643 _ => unimplemented!(
644 "syntax is not supported by `derive_more`, please report a bug",
645 ),
646 })
647 }
648
649 Self::ImplTrait(..)
650 | Self::Infer(..)
651 | Self::Macro(..)
652 | Self::Never(..)
653 | Self::Verbatim(..) => false,
654 _ => unimplemented!(
655 "syntax is not supported by `derive_more`, please report a bug",
656 ),
657 }
658 }
659}
660
661impl ContainsGenericsExt for syn::Path {
662 fn contains_generics(&self, type_params: &[&syn::Ident]) -> bool {
663 if type_params.is_empty() {
664 return false;
665 }
666 self.segments
667 .iter()
668 .enumerate()
669 .any(|(n, segment)| match &segment.arguments {
670 syn::PathArguments::None => {
671 (n == 0) && type_params.contains(&&segment.ident)
673 }
674 syn::PathArguments::AngleBracketed(
675 syn::AngleBracketedGenericArguments { args, .. },
676 ) => args.iter().any(|generic| match generic {
677 syn::GenericArgument::Type(ty)
678 | syn::GenericArgument::AssocType(syn::AssocType { ty, .. }) => {
679 ty.contains_generics(type_params)
680 }
681
682 syn::GenericArgument::Lifetime(..)
683 | syn::GenericArgument::Const(..)
684 | syn::GenericArgument::AssocConst(..)
685 | syn::GenericArgument::Constraint(..) => false,
686 _ => unimplemented!(
687 "syntax is not supported by `derive_more`, please report a bug",
688 ),
689 }),
690 syn::PathArguments::Parenthesized(
691 syn::ParenthesizedGenericArguments { inputs, output, .. },
692 ) => {
693 inputs.iter().any(|ty| ty.contains_generics(type_params))
694 || match output {
695 syn::ReturnType::Default => false,
696 syn::ReturnType::Type(_, ty) => {
697 ty.contains_generics(type_params)
698 }
699 }
700 }
701 })
702 }
703}
704
705trait FieldsExt {
707 fn fmt_args_idents(&self) -> impl Iterator<Item = syn::Ident> + '_;
712}
713
714impl FieldsExt for syn::Fields {
715 fn fmt_args_idents(&self) -> impl Iterator<Item = syn::Ident> + '_ {
716 self.iter()
717 .enumerate()
718 .map(|(i, f)| f.ident.clone().unwrap_or_else(|| format_ident!("_{i}")))
719 }
720}
721
722#[cfg(test)]
723mod fmt_attribute_spec {
724 use itertools::Itertools as _;
725 use quote::ToTokens;
726
727 use super::FmtAttribute;
728
729 fn assert<'a>(input: &'a str, parsed: impl AsRef<[&'a str]>) {
730 let parsed = parsed.as_ref();
731 let attr = syn::parse_str::<FmtAttribute>(&format!("\"\", {}", input)).unwrap();
732 let fmt_args = attr
733 .args
734 .into_iter()
735 .map(|arg| arg.into_token_stream().to_string())
736 .collect::<Vec<String>>();
737 fmt_args.iter().zip_eq(parsed).enumerate().for_each(
738 |(i, (found, expected))| {
739 assert_eq!(
740 *expected, found,
741 "Mismatch at index {i}\n\
742 Expected: {parsed:?}\n\
743 Found: {fmt_args:?}",
744 );
745 },
746 );
747 }
748
749 #[test]
750 fn cases() {
751 let cases = [
752 "ident",
753 "alias = ident",
754 "[a , b , c , d]",
755 "counter += 1",
756 "async { fut . await }",
757 "a < b",
758 "a > b",
759 "{ let x = (a , b) ; }",
760 "invoke (a , b)",
761 "foo as f64",
762 "| a , b | a + b",
763 "obj . k",
764 "for pat in expr { break pat ; }",
765 "if expr { true } else { false }",
766 "vector [2]",
767 "1",
768 "\"foo\"",
769 "loop { break i ; }",
770 "format ! (\"{}\" , q)",
771 "match n { Some (n) => { } , None => { } }",
772 "x . foo ::< T > (a , b)",
773 "x . foo ::< T < [T < T >; if a < b { 1 } else { 2 }] >, { a < b } > (a , b)",
774 "(a + b)",
775 "i32 :: MAX",
776 "1 .. 2",
777 "& a",
778 "[0u8 ; N]",
779 "(a , b , c , d)",
780 "< Ty as Trait > :: T",
781 "< Ty < Ty < T >, { a < b } > as Trait < T > > :: T",
782 ];
783
784 assert("", []);
785 for i in 1..4 {
786 for permutations in cases.into_iter().permutations(i) {
787 let mut input = permutations.clone().join(",");
788 assert(&input, &permutations);
789 input.push(',');
790 assert(&input, &permutations);
791 }
792 }
793 }
794}
795
796#[cfg(test)]
797mod placeholder_parse_fmt_string_spec {
798 use super::{Parameter, Placeholder};
799
800 #[test]
801 fn indicates_position_and_trait_name_for_each_fmt_placeholder() {
802 let fmt_string = "{},{:?},{{}},{{{1:0$}}}-{2:.1$x}{par:#?}{:width$}";
803 assert_eq!(
804 Placeholder::parse_fmt_string(fmt_string),
805 vec![
806 Placeholder {
807 arg: Parameter::Positional(0),
808 has_modifiers: false,
809 trait_name: "Display",
810 },
811 Placeholder {
812 arg: Parameter::Positional(1),
813 has_modifiers: false,
814 trait_name: "Debug",
815 },
816 Placeholder {
817 arg: Parameter::Positional(1),
818 has_modifiers: true,
819 trait_name: "Display",
820 },
821 Placeholder {
822 arg: Parameter::Positional(2),
823 has_modifiers: true,
824 trait_name: "LowerHex",
825 },
826 Placeholder {
827 arg: Parameter::Named("par".to_owned()),
828 has_modifiers: true,
829 trait_name: "Debug",
830 },
831 Placeholder {
832 arg: Parameter::Positional(2),
833 has_modifiers: true,
834 trait_name: "Display",
835 },
836 ],
837 );
838 }
839}