1#[cfg(doc)]
4use std::fmt;
5
6use proc_macro2::TokenStream;
7use quote::{format_ident, quote};
8use syn::{
9 ext::IdentExt as _,
10 parse::{Parse, ParseStream},
11 parse_quote,
12 spanned::Spanned as _,
13 token, LitStr,
14};
15
16use crate::utils::{
17 attr::{self, ParseMultiple as _},
18 Spanning,
19};
20
21use super::{trait_name_to_attribute_name, ContainsGenericsExt as _, FmtAttribute};
22
23pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenStream> {
35 let trait_name = normalize_trait_name(trait_name);
36 let attr_name = format_ident!("{}", trait_name_to_attribute_name(trait_name));
37
38 let attrs = ContainerAttributes::parse_attrs(&input.attrs, &attr_name)?
39 .map(Spanning::into_inner)
40 .unwrap_or_default();
41 let trait_ident = format_ident!("{trait_name}");
42 let ident = &input.ident;
43
44 let type_params = input
45 .generics
46 .params
47 .iter()
48 .filter_map(|p| match p {
49 syn::GenericParam::Type(t) => Some(&t.ident),
50 syn::GenericParam::Const(..) | syn::GenericParam::Lifetime(..) => None,
51 })
52 .collect::<Vec<_>>();
53
54 let ctx: ExpansionCtx = (&attrs, &type_params, ident, &trait_ident, &attr_name);
55 let (bounds, body) = match &input.data {
56 syn::Data::Struct(s) => expand_struct(s, ctx),
57 syn::Data::Enum(e) => expand_enum(e, ctx),
58 syn::Data::Union(u) => expand_union(u, ctx),
59 }?;
60
61 let (impl_gens, ty_gens, where_clause) = {
62 let (impl_gens, ty_gens, where_clause) = input.generics.split_for_impl();
63 let mut where_clause = where_clause
64 .cloned()
65 .unwrap_or_else(|| parse_quote! { where });
66 where_clause.predicates.extend(bounds);
67 (impl_gens, ty_gens, where_clause)
68 };
69
70 Ok(quote! {
71 #[allow(deprecated)] #[allow(unreachable_code)] #[automatically_derived]
74 impl #impl_gens derive_more::core::fmt::#trait_ident for #ident #ty_gens #where_clause {
75 fn fmt(
76 &self, __derive_more_f: &mut derive_more::core::fmt::Formatter<'_>
77 ) -> derive_more::core::fmt::Result {
78 #body
79 }
80 }
81 })
82}
83
84#[derive(Debug, Default)]
96struct ContainerAttributes {
97 rename_all: Option<attr::RenameAll>,
99
100 common: super::ContainerAttributes,
104}
105
106impl Parse for ContainerAttributes {
107 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
108 mod ident {
109 use syn::custom_keyword;
110
111 custom_keyword!(bounds);
112 custom_keyword!(bound);
113 custom_keyword!(rename_all);
114 }
115
116 FmtAttribute::check_legacy_fmt(input)?;
119
120 let ahead = input.lookahead1();
123 if ahead.peek(LitStr)
124 || ahead.peek(ident::bounds)
125 || ahead.peek(ident::bound)
126 || ahead.peek(token::Where)
127 {
128 Ok(Self {
129 common: input.parse()?,
130 ..Default::default()
131 })
132 } else if ahead.peek(ident::rename_all) {
133 Ok(Self {
134 rename_all: Some(input.parse()?),
135 ..Self::default()
136 })
137 } else {
138 Err(ahead.error())
139 }
140 }
141}
142
143impl attr::ParseMultiple for ContainerAttributes {
144 fn merge_attrs(
145 prev: Spanning<Self>,
146 new: Spanning<Self>,
147 name: &syn::Ident,
148 ) -> syn::Result<Spanning<Self>> {
149 let Spanning {
150 span: prev_span,
151 item: mut prev,
152 } = prev;
153 let Spanning {
154 span: new_span,
155 item: new,
156 } = new;
157
158 if new
159 .rename_all
160 .and_then(|n| prev.rename_all.replace(n))
161 .is_some()
162 {
163 return Err(syn::Error::new(
164 new_span,
165 format!("multiple `#[{name}(rename_all=\"...\")]` attributes aren't allowed"),
166 ));
167 }
168 prev.common = super::ContainerAttributes::merge_attrs(
169 Spanning::new(prev.common, prev_span),
170 Spanning::new(new.common, new_span),
171 name,
172 )?
173 .into_inner();
174
175 Ok(Spanning::new(
176 prev,
177 prev_span.join(new_span).unwrap_or(prev_span),
178 ))
179 }
180}
181
182type ExpansionCtx<'a> = (
191 &'a ContainerAttributes,
192 &'a [&'a syn::Ident],
193 &'a syn::Ident,
194 &'a syn::Ident,
195 &'a syn::Ident,
196);
197
198fn expand_struct(
200 s: &syn::DataStruct,
201 (attrs, type_params, ident, trait_ident, _): ExpansionCtx<'_>,
202) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
203 let s = Expansion {
204 shared_attr: None,
205 attrs,
206 fields: &s.fields,
207 type_params,
208 trait_ident,
209 ident,
210 };
211 let bounds = s.generate_bounds();
212 let body = s.generate_body()?;
213
214 let vars = s.fields.iter().enumerate().map(|(i, f)| {
215 let var = f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"));
216 let member = f
217 .ident
218 .clone()
219 .map_or_else(|| syn::Member::Unnamed(i.into()), syn::Member::Named);
220 quote! {
221 let #var = &self.#member;
222 }
223 });
224
225 let body = quote! {
226 #( #vars )*
227 #body
228 };
229
230 Ok((bounds, body))
231}
232
233fn expand_enum(
235 e: &syn::DataEnum,
236 (container_attrs, type_params, _, trait_ident, attr_name): ExpansionCtx<'_>,
237) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
238 if let Some(shared_fmt) = &container_attrs.common.fmt {
239 if shared_fmt
240 .placeholders_by_arg("_variant")
241 .any(|p| p.has_modifiers || p.trait_name != "Display")
242 {
243 return Err(syn::Error::new(
247 shared_fmt.span(),
248 "shared format `_variant` placeholder cannot contain format specifiers",
249 ));
250 }
251 }
252
253 let (bounds, match_arms) = e.variants.iter().try_fold(
254 (Vec::new(), TokenStream::new()),
255 |(mut bounds, mut arms), variant| {
256 let mut attrs = ContainerAttributes::parse_attrs(&variant.attrs, attr_name)?
257 .map(Spanning::into_inner)
258 .unwrap_or_default();
259 let ident = &variant.ident;
260
261 if attrs.common.fmt.is_none()
262 && variant.fields.is_empty()
263 && attr_name != "display" {
264 let container_fmt = container_attrs.common.fmt.as_ref();
265 if container_fmt.is_none() {
266 return Err(syn::Error::new(
267 e.variants.span(),
268 format!(
269 "implicit formatting of unit enum variant is supported only for \
270 `Display` macro, use `#[{attr_name}(\"...\")]` to explicitly specify \
271 the formatting on every variant or the enum",
272 ),
273 ));
274 } else if container_fmt.is_some_and(|fmt| fmt.contains_arg("_variant")) {
275 return Err(syn::Error::new_spanned(
276 variant,
277 format!(
278 "implicit formatting of unit enum variant is supported only for \
279 `Display` macro, use `#[{attr_name}(\"...\")]` to explicitly specify \
280 the formatting on every variant when using `{{_variant}}`",
281 ),
282 ));
283 }
284 }
285
286 if let Some(rename_all) = container_attrs.rename_all {
287 attrs.rename_all.get_or_insert(rename_all);
288 }
289
290 let v = Expansion {
291 shared_attr: container_attrs.common.fmt.as_ref(),
292 attrs: &attrs,
293 fields: &variant.fields,
294 type_params,
295 trait_ident,
296 ident,
297 };
298 let arm_body = v.generate_body()?;
299 bounds.extend(v.generate_bounds());
300
301 let fields_idents =
302 variant.fields.iter().enumerate().map(|(i, f)| {
303 f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"))
304 });
305 let matcher = match variant.fields {
306 syn::Fields::Named(_) => {
307 quote! { Self::#ident { #( #fields_idents ),* } }
308 }
309 syn::Fields::Unnamed(_) => {
310 quote! { Self::#ident ( #( #fields_idents ),* ) }
311 }
312 syn::Fields::Unit => quote! { Self::#ident },
313 };
314
315 arms.extend([quote! { #matcher => { #arm_body }, }]);
316
317 Ok::<_, syn::Error>((bounds, arms))
318 },
319 )?;
320
321 let body = if match_arms.is_empty() {
322 quote! { match *self {} }
323 } else {
324 quote! { match self { #match_arms } }
325 };
326
327 Ok((bounds, body))
328}
329
330fn expand_union(
332 u: &syn::DataUnion,
333 (attrs, _, _, _, attr_name): ExpansionCtx<'_>,
334) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
335 let fmt = &attrs.common.fmt.as_ref().ok_or_else(|| {
336 syn::Error::new(
337 u.fields.span(),
338 format!("unions must have `#[{attr_name}(\"...\", ...)]` attribute"),
339 )
340 })?;
341
342 Ok((
343 attrs.common.bounds.0.clone().into_iter().collect(),
344 quote! { derive_more::core::write!(__derive_more_f, #fmt) },
345 ))
346}
347
348#[derive(Debug)]
353struct Expansion<'a> {
354 shared_attr: Option<&'a FmtAttribute>,
358
359 attrs: &'a ContainerAttributes,
361
362 ident: &'a syn::Ident,
366
367 fields: &'a syn::Fields,
369
370 type_params: &'a [&'a syn::Ident],
372
373 trait_ident: &'a syn::Ident,
377}
378
379impl Expansion<'_> {
380 fn shared_attr_info(&self) -> (bool, bool) {
383 let shared_attr_contains_variant = self
384 .shared_attr
385 .map_or(true, |attr| attr.contains_arg("_variant"));
386 let has_shared_attr = self.shared_attr.is_some_and(|attr| {
388 attr.transparent_call().map_or(true, |(_, called_trait)| {
389 &called_trait != self.trait_ident || !shared_attr_contains_variant
390 })
391 });
392 (
393 has_shared_attr,
394 has_shared_attr && shared_attr_contains_variant,
395 )
396 }
397
398 fn generate_body(&self) -> syn::Result<TokenStream> {
406 let mut body = TokenStream::new();
407
408 let (has_shared_attr, shared_attr_is_wrapping) = self.shared_attr_info();
409
410 let wrap_into_shared_attr = match &self.attrs.common.fmt {
411 Some(fmt) => {
412 body = if shared_attr_is_wrapping {
413 let deref_args = fmt.additional_deref_args(self.fields);
414
415 quote! { &derive_more::core::format_args!(#fmt, #(#deref_args),*) }
416 } else if let Some((expr, trait_ident)) =
417 fmt.transparent_call_on_fields(self.fields)
418 {
419 quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) }
420 } else {
421 let deref_args = fmt.additional_deref_args(self.fields);
422
423 quote! { derive_more::core::write!(__derive_more_f, #fmt, #(#deref_args),*) }
424 };
425 shared_attr_is_wrapping
426 }
427 None => {
428 if shared_attr_is_wrapping || !has_shared_attr {
429 body = if self.fields.is_empty() {
430 let mut ident_str = self.ident.unraw().to_string();
431 if let Some(rename_all) = &self.attrs.rename_all {
432 ident_str = rename_all.convert_case(&ident_str);
433 }
434
435 if shared_attr_is_wrapping {
436 quote! { #ident_str }
437 } else {
438 quote! { __derive_more_f.write_str(#ident_str) }
439 }
440 } else if self.fields.len() == 1 {
441 let field = self
442 .fields
443 .iter()
444 .next()
445 .unwrap_or_else(|| unreachable!("count() == 1"));
446 let ident =
447 field.ident.clone().unwrap_or_else(|| format_ident!("_0"));
448 let trait_ident = self.trait_ident;
449
450 if shared_attr_is_wrapping {
451 let placeholder =
452 trait_name_to_default_placeholder_literal(trait_ident);
453
454 quote! { &derive_more::core::format_args!(#placeholder, #ident) }
455 } else {
456 quote! {
457 derive_more::core::fmt::#trait_ident::fmt(#ident, __derive_more_f)
458 }
459 }
460 } else {
461 return Err(syn::Error::new(
462 self.fields.span(),
463 format!(
464 "struct or enum variant with more than 1 field must have \
465 `#[{}(\"...\", ...)]` attribute",
466 trait_name_to_attribute_name(self.trait_ident),
467 ),
468 ));
469 };
470 }
471 has_shared_attr
472 }
473 };
474 if wrap_into_shared_attr {
475 if let Some(shared_fmt) = &self.shared_attr {
476 let deref_args = shared_fmt.additional_deref_args(self.fields);
477
478 let shared_body = if let Some((expr, trait_ident)) =
479 shared_fmt.transparent_call_on_fields(self.fields)
480 {
481 quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) }
482 } else {
483 quote! {
484 derive_more::core::write!(__derive_more_f, #shared_fmt, #(#deref_args),*)
485 }
486 };
487
488 body = if body.is_empty() {
489 shared_body
490 } else {
491 quote! { match #body { _variant => #shared_body } }
492 }
493 }
494 }
495
496 Ok(body)
497 }
498
499 fn generate_bounds(&self) -> Vec<syn::WherePredicate> {
501 let mut bounds = vec![];
502
503 let (has_shared_attr, shared_attr_is_wrapping) = self.shared_attr_info();
504
505 let mix_shared_attr_bounds = match &self.attrs.common.fmt {
506 Some(attr) => {
507 bounds.extend(
508 attr.bounded_types(self.fields)
509 .filter_map(|(ty, trait_name)| {
510 if !ty.contains_generics(self.type_params) {
511 return None;
512 }
513 let trait_ident = format_ident!("{trait_name}");
514
515 Some(parse_quote! { #ty: derive_more::core::fmt::#trait_ident })
516 })
517 .chain(self.attrs.common.bounds.0.clone()),
518 );
519 shared_attr_is_wrapping
520 }
521 None => {
522 if shared_attr_is_wrapping || !has_shared_attr {
523 bounds.extend(self.fields.iter().next().and_then(|f| {
524 let ty = &f.ty;
525 if !ty.contains_generics(self.type_params) {
526 return None;
527 }
528 let trait_ident = &self.trait_ident;
529 Some(parse_quote! { #ty: derive_more::core::fmt::#trait_ident })
530 }));
531 }
532 has_shared_attr
533 }
534 };
535 if mix_shared_attr_bounds {
536 bounds.extend(
537 self.shared_attr
538 .as_ref()
539 .unwrap()
540 .bounded_types(self.fields)
541 .filter_map(|(ty, trait_name)| {
542 if !ty.contains_generics(self.type_params) {
543 return None;
544 }
545 let trait_ident = format_ident!("{trait_name}");
546
547 Some(parse_quote! { #ty: derive_more::core::fmt::#trait_ident })
548 }),
549 );
550 }
551
552 bounds
553 }
554}
555
556fn normalize_trait_name(name: &str) -> &'static str {
558 match name {
559 "Binary" => "Binary",
560 "Display" => "Display",
561 "LowerExp" => "LowerExp",
562 "LowerHex" => "LowerHex",
563 "Octal" => "Octal",
564 "Pointer" => "Pointer",
565 "UpperExp" => "UpperExp",
566 "UpperHex" => "UpperHex",
567 _ => unimplemented!(),
568 }
569}
570
571fn trait_name_to_default_placeholder_literal(name: &syn::Ident) -> &'static str {
573 match () {
574 _ if name == "Binary" => "{:b}",
575 _ if name == "Debug" => "{:?}",
576 _ if name == "Display" => "{}",
577 _ if name == "LowerExp" => "{:e}",
578 _ if name == "LowerHex" => "{:x}",
579 _ if name == "Octal" => "{:o}",
580 _ if name == "Pointer" => "{:p}",
581 _ if name == "UpperExp" => "{:E}",
582 _ if name == "UpperHex" => "{:X}",
583 _ => unimplemented!(),
584 }
585}