amplify_derive/
display.rs

1// Rust language amplification derive library providing multiple generic trait
2// implementations, type wrappers, derive macros and other language enhancements
3//
4// Written in 2019-2020 by
5//     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
6//     Elichai Turkel <elichai.turkel@gmail.com>
7//
8// To the extent possible under law, the author(s) have dedicated all
9// copyright and related and neighboring rights to this software to
10// the public domain worldwide. This software is distributed without
11// any warranty.
12//
13// You should have received a copy of the MIT License
14// along with this software.
15// If not, see <https://opensource.org/licenses/MIT>.
16
17use proc_macro2::{Span, TokenStream as TokenStream2};
18use syn::spanned::Spanned;
19use syn::{
20    Attribute, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Fields, Ident, Index,
21    Lit, LitStr, Meta, MetaNameValue, NestedMeta, Path, Result,
22};
23
24const NAME: &str = "display";
25const EXAMPLE: &str = r#"#[display("format {} string" | Trait | Type::function)]"#;
26const FIELD_EXAMPLE: &str = r#"#[display(separator = "...")]"#;
27
28#[derive(Copy, Clone, PartialEq, Eq, Debug)]
29enum FormattingTrait {
30    Debug,
31    Octal,
32    Binary,
33    Pointer,
34    LowerHex,
35    UpperHex,
36    LowerExp,
37    UpperExp,
38}
39
40impl FormattingTrait {
41    pub fn from_path(path: &Path, span: Span) -> Result<Option<Self>> {
42        path.segments.first().map_or(
43            Err(attr_err!(span, NAME, "must contain at least one identifier", EXAMPLE)),
44            |segment| {
45                Ok(match segment.ident.to_string().as_str() {
46                    "Debug" => Some(FormattingTrait::Debug),
47                    "Octal" => Some(FormattingTrait::Octal),
48                    "Binary" => Some(FormattingTrait::Binary),
49                    "Pointer" => Some(FormattingTrait::Pointer),
50                    "LowerHex" => Some(FormattingTrait::LowerHex),
51                    "UpperHex" => Some(FormattingTrait::UpperHex),
52                    "LowerExp" => Some(FormattingTrait::LowerExp),
53                    "UpperExp" => Some(FormattingTrait::UpperExp),
54                    _ => None,
55                })
56            },
57        )
58    }
59
60    pub fn to_fmt(self, alt: bool) -> TokenStream2 {
61        let mut fmt = match self {
62            FormattingTrait::Debug => "{:?}",
63            FormattingTrait::Octal => "{:o}",
64            FormattingTrait::Binary => "{:b}",
65            FormattingTrait::Pointer => "{:p}",
66            FormattingTrait::LowerHex => "{:x}",
67            FormattingTrait::UpperHex => "{:X}",
68            FormattingTrait::LowerExp => "{:e}",
69            FormattingTrait::UpperExp => "{:E}",
70        }
71        .to_owned();
72        if alt {
73            fmt = fmt.replace(':', ":#");
74        }
75        quote! { #fmt }
76    }
77
78    pub fn into_token_stream2(self, span: Span) -> TokenStream2 {
79        match self {
80            FormattingTrait::Debug => quote_spanned! { span =>
81                ::core::fmt::Debug::fmt(&self, f)
82            },
83            FormattingTrait::Octal => quote_spanned! { span =>
84                ::core::fmt::Octal::fmt(&self, f)
85            },
86            FormattingTrait::Binary => quote_spanned! { span =>
87                ::core::fmt::Binary::fmt(&self, f)
88            },
89            FormattingTrait::Pointer => quote_spanned! { span =>
90                ::core::fmt::Pointer::fmt(&self, f)
91            },
92            FormattingTrait::LowerHex => quote_spanned! { span =>
93                ::core::fmt::LowerHex::fmt(&self, f)
94            },
95            FormattingTrait::UpperHex => quote_spanned! { span =>
96                ::core::fmt::UpperHex::fmt(&self, f)
97            },
98            FormattingTrait::LowerExp => quote_spanned! { span =>
99                ::core::fmt::LowerExp::fmt(&self, f)
100            },
101            FormattingTrait::UpperExp => quote_spanned! { span =>
102                ::core::fmt::UpperExp::fmt(&self, f)
103            },
104        }
105    }
106}
107
108#[derive(Clone)]
109enum Technique {
110    FromTrait(FormattingTrait),
111    FromMethod(Path),
112    WithFormat(LitStr, Option<LitStr>),
113    DocComments(String),
114    Inner,
115    Lowercase(String),
116    Uppercase(String),
117}
118
119impl Technique {
120    pub fn from_attrs<'a>(
121        attrs: impl IntoIterator<Item = &'a Attribute> + Clone,
122        span: Span,
123    ) -> Result<Option<Self>> {
124        let mut res = match attrs
125            .clone()
126            .into_iter()
127            .find(|attr| attr.path.is_ident(NAME))
128            .map(|attr| attr.parse_meta())
129            .map_or(Ok(None), |r| r.map(Some))?
130        {
131            Some(Meta::List(list)) => {
132                if list.nested.len() > 2 {
133                    return Err(attr_err!(span, "too many arguments"));
134                }
135                let mut iter = list.nested.iter();
136                let mut res = match iter.next() {
137                    Some(NestedMeta::Lit(Lit::Str(format))) => {
138                        Some(Technique::WithFormat(format.clone(), None))
139                    }
140                    Some(NestedMeta::Meta(Meta::Path(path)))
141                        if path.is_ident("doc_comments") || path.is_ident("docs") =>
142                    {
143                        Some(Technique::DocComments(String::new()))
144                    }
145                    Some(NestedMeta::Meta(Meta::Path(path))) if path.is_ident("inner") => {
146                        Some(Technique::Inner)
147                    }
148                    Some(NestedMeta::Meta(Meta::Path(path))) if path.is_ident("lowercase") => {
149                        Some(Technique::Lowercase(String::new()))
150                    }
151                    Some(NestedMeta::Meta(Meta::Path(path))) if path.is_ident("uppercase") => {
152                        Some(Technique::Uppercase(String::new()))
153                    }
154                    Some(NestedMeta::Meta(Meta::Path(path))) => Some(
155                        FormattingTrait::from_path(path, list.span())?
156                            .map_or(Technique::FromMethod(path.clone()), Technique::FromTrait),
157                    ),
158                    Some(_) => return Err(attr_err!(span, "argument must be a string literal")),
159                    None => return Err(attr_err!(span, "argument is required")),
160                };
161                res = match iter.next() {
162                    Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue {
163                        path,
164                        lit: Lit::Str(alt),
165                        ..
166                    }))) if Some("alt".to_string()) == path.get_ident().map(Ident::to_string) => {
167                        if iter.count() > 0 {
168                            return Err(attr_err!(span, "excessive arguments"));
169                        }
170                        match res {
171                            Some(Technique::WithFormat(fmt, _)) => {
172                                Some(Technique::WithFormat(fmt, Some(alt.clone())))
173                            }
174                            _ => {
175                                return Err(attr_err!(
176                                    span,
177                                    "alternative formatting can be given only if the first \
178                                     argument is a format string"
179                                ));
180                            }
181                        }
182                    }
183                    None => res,
184                    _ => return Err(attr_err!(span, "unrecognizable second argument")),
185                };
186                res
187            }
188            Some(Meta::NameValue(MetaNameValue {
189                lit: Lit::Str(format),
190                ..
191            })) => Some(Technique::WithFormat(format, None)),
192            Some(_) => return Err(attr_err!(span, "argument must be a string literal")),
193            None => None,
194        };
195
196        if let Some(r) = res.as_mut() {
197            r.apply_docs(attrs)
198        }
199        if let Some(r) = res.as_mut() {
200            r.fix_fmt()
201        };
202
203        Ok(res)
204    }
205
206    pub fn to_fmt(&self, alt: bool) -> TokenStream2 {
207        match self {
208            Technique::FromTrait(fmt) => fmt.to_fmt(alt),
209            Technique::FromMethod(_) => quote! { "{}" },
210            Technique::WithFormat(fmt, fmt_alt) => {
211                if alt && fmt_alt.is_some() {
212                    let alt = fmt_alt
213                        .as_ref()
214                        .expect("we just checked that there are data");
215                    quote! {#alt}
216                } else {
217                    quote! {#fmt}
218                }
219            }
220            Technique::DocComments(doc) => quote! { #doc },
221            Technique::Inner => {
222                if alt {
223                    quote! { "{_0:#}" }
224                } else {
225                    quote! { "{_0}" }
226                }
227            }
228            Technique::Lowercase(fields_fmt) => quote! { #fields_fmt },
229            Technique::Uppercase(fields_fmt) => quote! { #fields_fmt },
230        }
231    }
232
233    #[allow(clippy::unnecessary_unwrap)]
234    pub fn into_token_stream2(self, fields: &Fields, span: Span, alt: bool) -> TokenStream2 {
235        match self {
236            Technique::FromTrait(fmt) => fmt.into_token_stream2(span),
237            Technique::FromMethod(path) => quote_spanned! { span =>
238                ::core::fmt::Display::fmt(&#path(self), f)
239            },
240            Technique::WithFormat(fmt, fmt_alt) => {
241                let format = if alt && fmt_alt.is_some() {
242                    let alt = fmt_alt.expect("we just checked that there are data");
243                    quote_spanned! { span => #alt }
244                } else {
245                    quote_spanned! { span => #fmt }
246                };
247                Self::impl_format(fields, &format, span)
248            }
249            Technique::DocComments(doc) => {
250                let format = quote_spanned! { span => #doc };
251                Self::impl_format(fields, &format, span)
252            }
253            Technique::Inner => {
254                let format = if alt {
255                    quote_spanned! { span => "{_0:#}" }
256                } else {
257                    quote_spanned! { span => "{_0}" }
258                };
259                Self::impl_format(fields, &format, span)
260            }
261            Technique::Lowercase(fields_fmt) => {
262                let format = quote_spanned! { span => #fields_fmt };
263                Self::impl_format(fields, &format, span)
264            }
265            Technique::Uppercase(fields_fmt) => {
266                let format = quote_spanned! { span => #fields_fmt };
267                Self::impl_format(fields, &format, span)
268            }
269        }
270    }
271
272    fn impl_format(fields: &Fields, format: &TokenStream2, span: Span) -> TokenStream2 {
273        match fields {
274            // Format string
275            Fields::Named(fields) => {
276                let idents = fields
277                    .named
278                    .iter()
279                    .map(|f| f.ident.as_ref().unwrap())
280                    .collect::<Vec<_>>();
281                quote_spanned! { span =>
282                    write!(f, #format, #( #idents = self.#idents, )* )
283                }
284            }
285            Fields::Unnamed(fields) => {
286                let idents = (0..fields.unnamed.len())
287                    .map(|i| Ident::new(&format!("_{}", i), span))
288                    .collect::<Vec<_>>();
289                let selves = (0..fields.unnamed.len())
290                    .map(|i| {
291                        let index = Index::from(i);
292                        quote_spanned! { span => self.#index }
293                    })
294                    .collect::<Vec<_>>();
295                quote_spanned! { span =>
296                    write!(f, #format, #( #idents = #selves, )* )
297                }
298            }
299            Fields::Unit => {
300                quote_spanned! { span =>
301                    f.write_str(#format)
302                }
303            }
304        }
305    }
306
307    fn apply_docs<'a>(&mut self, attrs: impl IntoIterator<Item = &'a Attribute> + Clone) {
308        if let Technique::DocComments(ref mut doc) = self {
309            for attr in attrs.into_iter().filter(|attr| attr.path.is_ident("doc")) {
310                if let Ok(Meta::NameValue(MetaNameValue {
311                    lit: Lit::Str(s), ..
312                })) = attr.parse_meta()
313                {
314                    let fragment = s.value().trim().replace("\\n", "\n");
315                    if fragment.is_empty() || fragment == "\n" {
316                        doc.push('\n');
317                    } else {
318                        doc.push_str(&fragment);
319                        doc.push(' ');
320                    }
321                }
322            }
323            *doc = doc.trim().replace(" \n", "\n");
324        }
325    }
326
327    fn apply_case(&mut self, type_str: &str, fields: &Fields) {
328        let (type_str_cased, fields_fmt) = match self {
329            Technique::Lowercase(ref mut f) => (type_str.to_lowercase(), f),
330            Technique::Uppercase(ref mut f) => (type_str.to_uppercase(), f),
331            _ => unreachable!(),
332        };
333        *fields_fmt = match fields {
334            Fields::Named(f) => {
335                let idents = f
336                    .named
337                    .iter()
338                    .map(|f| f.ident.as_ref().unwrap())
339                    .collect::<Vec<_>>();
340                let inner = idents
341                    .iter()
342                    .map(|ident| format!("{}: {{{0}}}", ident.to_string()))
343                    .collect::<Vec<_>>()
344                    .join(", ");
345                format!("{} {{{{ {} }}}}", type_str_cased, inner)
346            }
347            Fields::Unnamed(f) => {
348                let inner = (0..f.unnamed.len())
349                    .map(|i| format!("{{_{}}}", i))
350                    .collect::<Vec<_>>()
351                    .join(", ");
352                format!("{}({})", type_str_cased, inner)
353            }
354            Fields::Unit => type_str_cased,
355        };
356    }
357
358    fn fix_fmt(&mut self) {
359        fn fix(s: &str) -> String {
360            s.replace("{0", "{_0")
361                .replace("{1", "{_1")
362                .replace("{2", "{_2")
363                .replace("{3", "{_3")
364                .replace("{4", "{_4")
365                .replace("{5", "{_5")
366                .replace("{6", "{_6")
367                .replace("{7", "{_7")
368                .replace("{8", "{_8")
369                .replace("{9", "{_9")
370        }
371
372        if let Technique::WithFormat(fmt, x) = self {
373            *self = Technique::WithFormat(
374                LitStr::new(&fix(&fmt.value()), Span::call_site()),
375                x.clone(),
376            );
377        }
378        if let Technique::WithFormat(x, Some(fmt)) = self {
379            *self = Technique::WithFormat(
380                x.clone(),
381                Some(LitStr::new(&fix(&fmt.value()), Span::call_site())),
382            );
383        }
384        if let Technique::DocComments(fmt) = self {
385            *self = Technique::DocComments(fix(fmt))
386        }
387    }
388}
389
390fn has_formatters(ident: impl ToString, s: &str) -> bool {
391    let m1 = format!("{}{}:", '{', ident.to_string());
392    let m2 = format!("{}{}{}", '{', ident.to_string(), '}');
393    s.contains(&m1) || s.contains(&m2)
394}
395
396pub(crate) fn inner(input: DeriveInput) -> Result<TokenStream2> {
397    match input.data {
398        Data::Struct(ref data) => inner_struct(&input, data),
399        Data::Enum(ref data) => inner_enum(&input, data),
400        Data::Union(ref data) => inner_union(&input, data),
401    }
402}
403
404fn inner_struct(input: &DeriveInput, data: &DataStruct) -> Result<TokenStream2> {
405    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
406    let ident_name = &input.ident;
407
408    let technique = Technique::from_attrs(&input.attrs, input.span())?.ok_or_else(|| {
409        Error::new(
410            input.span(),
411            format!("Deriving `Display`: required attribute `{}` is missing.\n{}", NAME, EXAMPLE),
412        )
413    })?;
414
415    let tokens_fmt = technique.to_fmt(false);
416    let tokens_alt = technique.to_fmt(true);
417    let str_fmt = tokens_fmt.to_string();
418    let str_alt = tokens_alt.to_string();
419
420    let display = match (&data.fields, &technique) {
421        (_, Technique::FromTrait(_)) | (_, Technique::FromMethod(_)) => technique
422            .clone()
423            .into_token_stream2(&data.fields, input.span(), false),
424        (Fields::Named(fields), Technique::Inner) => {
425            if fields.named.len() != 1 {
426                return Err(attr_err!(
427                    fields.span(),
428                    "display(inner) requires only single field in the structure"
429                ));
430            }
431            let field = fields
432                .named
433                .first()
434                .expect("we just checked that there is a single field")
435                .ident
436                .as_ref()
437                .expect("named fields always have ident with the name");
438            quote_spanned! { field.span() =>
439                write!(f, #tokens_fmt, _0 = self.#field)
440            }
441        }
442        (Fields::Named(fields), _) => {
443            let idents = fields
444                .named
445                .iter()
446                .filter_map(|field| format_field(field, &str_fmt).transpose())
447                .collect::<Result<Vec<_>>>()?;
448            if str_fmt == str_alt {
449                quote_spanned! { fields.span() =>
450                    write!(f, #tokens_fmt, #( #idents, )*)
451                }
452            } else {
453                let idents_alt = fields
454                    .named
455                    .iter()
456                    .filter_map(|field| format_field(field, &str_alt).transpose())
457                    .collect::<Result<Vec<_>>>()?;
458                if str_fmt != str_alt {
459                    quote_spanned! { fields.span() =>
460                        if !f.alternate() {
461                            write!(f, #tokens_fmt, #( #idents, )*)
462                        } else {
463                            write!(f, #tokens_alt, #( #idents_alt, )*)
464                        }
465                    }
466                } else {
467                    quote_spanned! { fields.span() =>
468                        write!(f, #tokens_fmt, #( #idents = self.#idents, )*)
469                    }
470                }
471            }
472        }
473        (Fields::Unnamed(fields), _) => {
474            let f = (0..fields.unnamed.len()).map(Index::from);
475            let idents = f
476                .clone()
477                .filter(|ident| has_formatters(format!("_{}", ident.index), &str_fmt));
478            let nums = idents
479                .clone()
480                .map(|ident| Ident::new(&format!("_{}", ident.index), fields.span()))
481                .collect::<Vec<_>>();
482            let idents = idents.collect::<Vec<_>>();
483            if str_fmt == str_alt {
484                quote_spanned! { fields.span() =>
485                    write!(f, #tokens_fmt, #( #nums = self.#idents, )*)
486                }
487            } else {
488                let idents_alt =
489                    f.filter(|ident| has_formatters(format!("_{}", ident.index), &str_alt));
490                let nums_alt = idents_alt
491                    .clone()
492                    .map(|ident| Ident::new(&format!("_{}", ident.index), fields.span()))
493                    .collect::<Vec<_>>();
494                let idents_alt = idents_alt.collect::<Vec<_>>();
495                if str_fmt != str_alt {
496                    quote_spanned! { fields.span() =>
497                        if !f.alternate() {
498                            write!(f, #tokens_fmt, #( #nums = self.#idents, )*)
499                        } else {
500                            write!(f, #tokens_alt, #( #nums_alt = self.#idents_alt, )*)
501                        }
502                    }
503                } else {
504                    quote_spanned! { fields.span() =>
505                        write!(f, #tokens_fmt, #( #nums = self.#idents, )*)
506                    }
507                }
508            }
509        }
510        (Fields::Unit, _) => {
511            if str_fmt == str_alt {
512                quote_spanned! { data.fields.span() =>
513                    f.write_str(#tokens_fmt)
514                }
515            } else {
516                quote_spanned! { data.fields.span() =>
517                    f.write_str(if !f.alternate() { #tokens_fmt } else { #tokens_alt })
518                }
519            }
520        }
521    };
522
523    Ok(quote! {
524        #[automatically_derived]
525        impl #impl_generics ::core::fmt::Display for #ident_name #ty_generics #where_clause {
526            fn fmt(&self, mut f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
527                #display
528            }
529        }
530    })
531}
532
533fn format_field(field: &syn::Field, str_fmt: &str) -> Result<Option<TokenStream2>> {
534    let ident = field.ident.as_ref().unwrap();
535    if !has_formatters(ident, str_fmt) {
536        return Ok(None);
537    }
538    let attr = match field.attrs.iter().find(|attr| attr.path.is_ident(NAME)) {
539        Some(attr) => attr,
540        None => return Ok(Some(quote_spanned! { ident.span() => #ident = self.#ident })),
541    };
542    match attr.parse_meta().unwrap() {
543        Meta::List(meta_list) => {
544            if meta_list.nested.len() > 1 {
545                return Err(attr_err!(attr, NAME, "too many arguments", FIELD_EXAMPLE));
546            }
547            match meta_list.nested.first() {
548                Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue {
549                    path,
550                    lit: Lit::Str(separator),
551                    ..
552                }))) if path.is_ident("separator") => Ok(Some(
553                    quote_spanned! { ident.span() => #ident = self.#ident.join(#separator) },
554                )),
555                _ => Err(attr_err!(attr, NAME, "unexpected argument", FIELD_EXAMPLE)),
556            }
557        }
558        _ => Err(attr_err!(attr, NAME, "expected an argument", FIELD_EXAMPLE)),
559    }
560}
561
562fn inner_enum(input: &DeriveInput, data: &DataEnum) -> Result<TokenStream2> {
563    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
564    let ident_name = &input.ident;
565    let mut display = TokenStream2::new();
566
567    let global = Technique::from_attrs(&input.attrs, input.span())?;
568    // Ancient rust versions do not known about `matches!` macro
569    #[allow(clippy::match_like_matches_macro)]
570    let mut use_global = match global {
571        Some(Technique::Inner) | Some(Technique::Lowercase(_)) | Some(Technique::Uppercase(_)) => {
572            false
573        }
574        _ => true,
575    };
576
577    for v in &data.variants {
578        let type_name = &v.ident;
579        let type_str = format!("{}", type_name);
580
581        let mut local = Technique::from_attrs(&v.attrs, v.span())?;
582        let mut parent = global.clone();
583        let current = local.as_mut().or(parent.as_mut());
584        let mut current = current
585            .map(|r| {
586                r.apply_docs(&v.attrs);
587                r
588            })
589            .cloned();
590
591        if local.is_some() {
592            use_global = false;
593        }
594
595        if let Some(Technique::DocComments(_)) |
596        Some(Technique::Lowercase(_)) |
597        Some(Technique::Uppercase(_)) = current
598        {
599            use_global = false;
600            if let Some(t) = current.as_mut() {
601                match t {
602                    Technique::DocComments(_) => {
603                        *t = Technique::DocComments(String::new());
604                        t.apply_docs(&v.attrs);
605                        t.fix_fmt();
606                    }
607                    Technique::Lowercase(_) => {
608                        *t = Technique::Lowercase(String::new());
609                        t.apply_case(&type_str, &v.fields);
610                        t.fix_fmt();
611                    }
612                    Technique::Uppercase(_) => {
613                        *t = Technique::Uppercase(String::new());
614                        t.apply_case(&type_str, &v.fields);
615                        t.fix_fmt();
616                    }
617                    _ => unreachable!(),
618                }
619            }
620        }
621
622        let tokens_fmt = current.as_ref().map(|t| t.to_fmt(false));
623        let tokens_alt = current.as_ref().map(|t| t.to_fmt(true));
624
625        match (&v.fields, &tokens_fmt, &tokens_alt) {
626            (Fields::Named(_), None, _) => {
627                display.extend(quote_spanned! { v.span() =>
628                    Self::#type_name { .. } => f.write_str(concat!(#type_str, " { .. }")),
629                });
630            }
631            (Fields::Unnamed(_), None, _) => {
632                display.extend(quote_spanned! { v.span() =>
633                    Self::#type_name(..) => f.write_str(concat!(#type_str, "(..)")),
634                });
635            }
636            (Fields::Unit, None, _) => {
637                display.extend(quote_spanned! { v.span() =>
638                    Self::#type_name => f.write_str(#type_str),
639                });
640            }
641            (Fields::Named(fields), Some(tokens_fmt), Some(tokens_alt)) => {
642                if let Some(Technique::Inner) = current {
643                    if fields.named.len() != 1 {
644                        return Err(attr_err!(
645                            fields.span(),
646                            "display(inner) requires only single field in the structure"
647                        ));
648                    }
649                    let field = fields
650                        .named
651                        .first()
652                        .expect("we just checked that there is a single field")
653                        .ident
654                        .as_ref()
655                        .expect("named fields always have ident with the name");
656                    display.extend(quote_spanned! { v.span() =>
657                        Self::#type_name { #field, .. } => {
658                            write!(f, #tokens_fmt, _0 = #field)
659                        }
660                    });
661                } else if let Some(Technique::FromTrait(tr)) = current {
662                    let stream =
663                        Technique::FromTrait(tr).into_token_stream2(&v.fields, v.span(), false);
664                    display.extend(quote_spanned! { v.span() =>
665                        Self::#type_name { .. } => {
666                            #stream
667                        }
668                    })
669                } else {
670                    let f = fields.named.iter().map(|f| f.ident.as_ref().unwrap());
671                    let idents = f
672                        .clone()
673                        .filter(|ident| has_formatters(ident, &tokens_fmt.to_string()))
674                        .collect::<Vec<_>>();
675                    let idents_alt = f
676                        .filter(|ident| has_formatters(ident, &tokens_alt.to_string()))
677                        .collect::<Vec<_>>();
678                    if tokens_fmt.to_string() != tokens_alt.to_string() {
679                        display.extend(quote_spanned! { v.span() =>
680                            Self::#type_name { #( #idents, )* .. } if !f.alternate() => {
681                                write!(f, #tokens_fmt, #( #idents = #idents, )*)
682                            },
683                            Self::#type_name { #( #idents, )* .. } => {
684                                write!(f, #tokens_alt, #( #idents_alt = #idents_alt, )*)
685                            },
686                        });
687                    } else {
688                        display.extend(quote_spanned! { v.span() =>
689                            Self::#type_name { #( #idents, )* .. } => {
690                                write!(f, #tokens_fmt, #( #idents = #idents, )*)
691                            },
692                        });
693                    }
694                }
695            }
696            (Fields::Unnamed(fields), Some(tokens_fmt), Some(tokens_alt)) => {
697                if let Some(Technique::FromTrait(tr)) = current {
698                    let stream =
699                        Technique::FromTrait(tr).into_token_stream2(&v.fields, v.span(), false);
700                    display.extend(quote_spanned! { v.span() =>
701                        Self::#type_name(..) => {
702                            #stream
703                        }
704                    })
705                } else {
706                    let f =
707                        (0..fields.unnamed.len()).map(|i| Ident::new(&format!("_{}", i), v.span()));
708                    let idents = f
709                        .clone()
710                        .filter(|ident| has_formatters(ident, &tokens_fmt.to_string()))
711                        .collect::<Vec<_>>();
712                    let idents_alt = f
713                        .filter(|ident| has_formatters(ident, &tokens_alt.to_string()))
714                        .collect::<Vec<_>>();
715                    if tokens_fmt.to_string() != tokens_alt.to_string() {
716                        display.extend(quote_spanned! { v.span() =>
717                            Self::#type_name ( #( #idents, )* .. ) if !f.alternate() => {
718                                write!(f, #tokens_fmt, #( #idents = #idents, )*)
719                            },
720                            Self::#type_name ( #( #idents, )* .. ) => {
721                                write!(f, #tokens_alt, #( #idents_alt = #idents_alt, )*)
722                            },
723                        });
724                    } else {
725                        display.extend(quote_spanned! { v.span() =>
726                            Self::#type_name ( #( #idents, )* .. ) => {
727                                write!(f, #tokens_fmt, #( #idents = #idents, )*)
728                            },
729                        });
730                    }
731                }
732            }
733            (Fields::Unit, Some(tokens_fmt), Some(tokens_alt)) => {
734                if let Some(Technique::Inner) = current {
735                    display.extend(quote_spanned! { v.span() =>
736                        Self::#type_name => f.write_str(#type_str),
737                    });
738                } else {
739                    display.extend(quote_spanned! { v.span() =>
740                        Self::#type_name => f.write_str(if !f.alternate() { #tokens_fmt } else { #tokens_alt }),
741                    });
742                }
743            }
744            _ => unreachable!(),
745        }
746    }
747
748    let content = match (use_global, global) {
749        (false, _) => quote! {
750            match self {
751                #display
752            }
753        },
754        (true, Some(tenchique)) => {
755            let format_str =
756                tenchique
757                    .clone()
758                    .into_token_stream2(&Fields::Unit, input.span(), false);
759            let format_alt = tenchique.into_token_stream2(&Fields::Unit, input.span(), true);
760            if format_str.to_string() != format_alt.to_string() {
761                quote! {
762                    if f.alternate() {
763                        #format_alt
764                    } else {
765                        #format_str
766                    }
767                }
768            } else {
769                quote! { #format_str }
770            }
771        }
772        _ => unreachable!(),
773    };
774
775    Ok(quote! {
776        #[automatically_derived]
777        impl #impl_generics ::core::fmt::Display for #ident_name #ty_generics #where_clause {
778            #![allow(clippy::if_same_then_else)]
779            fn fmt(&self, mut f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
780                #content
781            }
782        }
783    })
784}
785
786fn inner_union(input: &DeriveInput, data: &DataUnion) -> Result<TokenStream2> {
787    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
788    let ident_name = &input.ident;
789    let mut display = vec![];
790
791    let global = Technique::from_attrs(&input.attrs, input.span())?;
792
793    for field in &data.fields.named {
794        let type_name = field
795            .ident
796            .clone()
797            .expect("named attributes are always named");
798        let type_str = format!("{}", type_name);
799
800        let format = Technique::from_attrs(&field.attrs, field.span())?
801            .or_else(|| global.clone())
802            .map(|t| (t.to_fmt(false), t.to_fmt(true)));
803
804        match format {
805            None => {
806                display.push(quote_spanned! { field.span() =>
807                    Self::#type_name => #type_str,
808                });
809            }
810            Some((format_str, format_alt)) => {
811                display.push(quote_spanned! { field.span() =>
812                    Self::#type_name if !f.alternate() => #format_str,
813                    Self::#type_name => #format_alt,
814                });
815            }
816        }
817    }
818
819    let content = match global {
820        Some(tenchique) => {
821            let format_str =
822                tenchique
823                    .clone()
824                    .into_token_stream2(&Fields::Unit, input.span(), false);
825            let format_alt = tenchique.into_token_stream2(&Fields::Unit, input.span(), true);
826            if format_str.to_string() != format_alt.to_string() {
827                quote! {
828                    if f.alternate() {
829                        #format_alt
830                    } else {
831                        #format_str
832                    }
833                }
834            } else {
835                quote! { #format_str }
836            }
837        }
838        None => quote! {
839            let s = match self {
840                #( #display )*
841            };
842            f.write_str(s)
843        },
844    };
845    Ok(quote! {
846        #[automatically_derived]
847        impl #impl_generics ::core::fmt::Display for #ident_name #ty_generics #where_clause {
848            #![allow(clippy::if_same_then_else)]
849            fn fmt(&self, mut f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
850                #content
851            }
852        }
853    })
854}