1use 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 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 #[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}