diff --git a/CHANGELOG.md b/CHANGELOG.md index 7370e5d5..ebf7bfc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ([#431](https://github.com/JelteF/derive_more/pull/431)) - Incorrect rendering of raw identifiers as field names in `Debug` expansions. ([#431](https://github.com/JelteF/derive_more/pull/431)) +- Top-level `#[display("...")]` attribute on an enum not working transparently + for directly specified fields. + ([#438](https://github.com/JelteF/derive_more/pull/438)) ## 1.0.0 - 2024-08-07 diff --git a/impl/src/fmt/debug.rs b/impl/src/fmt/debug.rs index f31270ca..e2c8bc83 100644 --- a/impl/src/fmt/debug.rs +++ b/impl/src/fmt/debug.rs @@ -13,7 +13,7 @@ use crate::utils::{ use super::{ trait_name_to_attribute_name, ContainerAttributes, ContainsGenericsExt as _, - FieldsExt as _, FmtAttribute, + FmtAttribute, }; /// Expands a [`fmt::Debug`] derive macro. @@ -250,23 +250,17 @@ impl Expansion<'_> { /// [`Debug::fmt()`]: std::fmt::Debug::fmt() fn generate_body(&self) -> syn::Result { if let Some(fmt) = &self.attr.fmt { - return Ok(if let Some((expr, trait_ident)) = fmt.transparent_call() { - let expr = if let Some(field) = self - .fields - .fmt_args_idents() - .find(|field| expr == *field || expr == field.unraw()) + return Ok( + if let Some((expr, trait_ident)) = + fmt.transparent_call_on_fields(self.fields) { - quote! { #field } + quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) } } else { - quote! { &(#expr) } - }; - - quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) } - } else { - let deref_args = fmt.additional_deref_args(self.fields); + let deref_args = fmt.additional_deref_args(self.fields); - quote! { derive_more::core::write!(__derive_more_f, #fmt, #(#deref_args),*) } - }); + quote! { derive_more::core::write!(__derive_more_f, #fmt, #(#deref_args),*) } + }, + ); }; match self.fields { diff --git a/impl/src/fmt/display.rs b/impl/src/fmt/display.rs index a7cca60f..758d8586 100644 --- a/impl/src/fmt/display.rs +++ b/impl/src/fmt/display.rs @@ -11,7 +11,7 @@ use crate::utils::{attr::ParseMultiple as _, Spanning}; use super::{ trait_name_to_attribute_name, ContainerAttributes, ContainsGenericsExt as _, - FieldsExt as _, FmtAttribute, + FmtAttribute, }; /// Expands a [`fmt::Display`]-like derive macro. @@ -291,17 +291,9 @@ impl Expansion<'_> { let deref_args = fmt.additional_deref_args(self.fields); quote! { &derive_more::core::format_args!(#fmt, #(#deref_args),*) } - } else if let Some((expr, trait_ident)) = fmt.transparent_call() { - let expr = if let Some(field) = self - .fields - .fmt_args_idents() - .find(|field| expr == *field || expr == field.unraw()) - { - quote! { #field } - } else { - quote! { &(#expr) } - }; - + } else if let Some((expr, trait_ident)) = + fmt.transparent_call_on_fields(self.fields) + { quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) } } else { let deref_args = fmt.additional_deref_args(self.fields); @@ -358,8 +350,14 @@ impl Expansion<'_> { if let Some(shared_fmt) = &self.shared_attr { let deref_args = shared_fmt.additional_deref_args(self.fields); - let shared_body = quote! { - derive_more::core::write!(__derive_more_f, #shared_fmt, #(#deref_args),*) + let shared_body = if let Some((expr, trait_ident)) = + shared_fmt.transparent_call_on_fields(self.fields) + { + quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) } + } else { + quote! { + derive_more::core::write!(__derive_more_f, #shared_fmt, #(#deref_args),*) + } }; body = if body.is_empty() { diff --git a/impl/src/fmt/mod.rs b/impl/src/fmt/mod.rs index 8dc7e332..70ed4076 100644 --- a/impl/src/fmt/mod.rs +++ b/impl/src/fmt/mod.rs @@ -13,6 +13,7 @@ use quote::{format_ident, quote, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, + parse_quote, punctuated::Punctuated, spanned::Spanned as _, token, @@ -199,6 +200,30 @@ impl FmtAttribute { Some((expr, format_ident!("{trait_name}"))) } + /// Same as [`transparent_call()`], but additionally checks the returned [`Expr`] whether it's + /// one of the [`fmt_args_idents`] of the provided [`syn::Fields`], and makes it suitable for + /// passing directly into the transparent call of the delegated formatting trait. + /// + /// [`fmt_args_idents`]: FieldsExt::fmt_args_idents + /// [`transparent_call()`]: FmtAttribute::transparent_call + fn transparent_call_on_fields( + &self, + fields: &syn::Fields, + ) -> Option<(Expr, syn::Ident)> { + self.transparent_call().map(|(expr, trait_ident)| { + let expr = if let Some(field) = fields + .fmt_args_idents() + .find(|field| expr == *field || expr == field.unraw()) + { + field.into() + } else { + parse_quote! { &(#expr) } + }; + + (expr, trait_ident) + }) + } + /// Returns an [`Iterator`] over bounded [`syn::Type`]s (and correspondent trait names) by this /// [`FmtAttribute`]. fn bounded_types<'a>( diff --git a/tests/display.rs b/tests/display.rs index a48ed792..4120e3ac 100644 --- a/tests/display.rs +++ b/tests/display.rs @@ -2629,6 +2629,286 @@ mod generic { format!("{:018p}", &a), ); } + + mod shared_format { + use super::*; + + #[derive(Display)] + #[display("{_0}")] + enum EnumDisplay { + A(A), + B(B), + } + + #[derive(Display)] + #[display("{:b}", _0)] + enum EnumBinary { + A(A, C), + B(B, D), + } + + #[derive(Display)] + #[display("{:o}", d)] + enum EnumOctal { + A { b: A, d: C }, + B { b: B, d: D }, + } + + #[derive(Display)] + #[display("{field:x}")] + enum EnumLowerHex { + A { field: A }, + B { field: B }, + } + + #[derive(Display)] + #[display("{:X}", field)] + enum EnumUpperHex { + A { field: A }, + B { field: B }, + } + + #[derive(Display)] + #[display("{_0:e}")] + enum EnumLowerExp { + A(A), + B(B), + } + + #[derive(Display)] + #[display("{:E}", _0)] + enum EnumUpperExp { + A(A), + B(B), + } + + #[derive(Display)] + #[display("{_0:p}")] + enum EnumPointer { + A(A), + B(B), + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", EnumDisplay::::A(7)), "007"); + assert_eq!(format!("{:03}", EnumDisplay::::B(8)), "008"); + assert_eq!(format!("{:07}", TupleBinary(7, ())), "0000111"); + assert_eq!( + format!("{:07}", EnumBinary::::A(7, ())), + "0000111", + ); + assert_eq!( + format!("{:07}", EnumBinary::::B(8, ())), + "0001000", + ); + assert_eq!( + format!( + "{:03}", + EnumOctal::<(), (), i8, u8>::A { b: (), d: 9 }, + ), + "011", + ); + assert_eq!( + format!( + "{:03}", + EnumOctal::<(), (), i8, u8>::B { b: (), d: 10 }, + ), + "012", + ); + assert_eq!( + format!("{:03}", EnumLowerHex::::A { field: 42 }), + "02a", + ); + assert_eq!( + format!("{:03}", EnumLowerHex::::B { field: 43 }), + "02b", + ); + assert_eq!( + format!("{:03}", EnumUpperHex::::A { field: 42 }), + "02A", + ); + assert_eq!( + format!("{:03}", EnumUpperHex::::B { field: 43 }), + "02B", + ); + assert_eq!( + format!("{:07}", EnumLowerExp::::A(42.0)), + "004.2e1" + ); + assert_eq!( + format!("{:07}", EnumLowerExp::::B(43.0)), + "004.3e1" + ); + assert_eq!( + format!("{:07}", EnumUpperExp::::A(42.0)), + "004.2E1" + ); + assert_eq!( + format!("{:07}", EnumUpperExp::::B(43.0)), + "004.3E1", + ); + let (a, b) = (42, 7); + assert_eq!( + format!("{:018}", EnumPointer::<&i8, &u8>::A(&b)), + format!("{:018p}", &b), + ); + assert_eq!( + format!("{:018}", EnumPointer::<&i8, &u8>::B(&a)), + format!("{:018p}", &a), + ); + } + + mod mixed { + use super::*; + + #[derive(Display)] + #[display("{_variant}")] + enum EnumDisplay { + A(A), + #[display("{_0}")] + B(B), + } + + #[derive(Display)] + #[display("{_variant}")] + enum EnumBinary { + #[display("{_0:b}")] + A(A, C), + #[display("{:b}", b)] + B { b: B, d: D }, + } + + #[derive(Display)] + #[display("{_variant}")] + enum EnumOctal { + #[display("{_1:o}")] + A(A, C), + #[display("{:o}", d)] + B { b: B, d: D }, + } + + #[derive(Display)] + #[display("{_variant}")] + enum EnumLowerHex { + #[display("{_0:x}")] + A(A), + #[display("{:x}", field)] + B { field: B }, + } + + #[derive(Display)] + #[display("{_variant}")] + enum EnumUpperHex { + #[display("{_0:X}")] + A(A), + #[display("{:X}", field)] + B { field: B }, + } + + #[derive(Display)] + #[display("{_variant}")] + enum EnumLowerExp { + #[display("{:e}", _0)] + A(A), + #[display("{field:e}")] + B { field: B }, + } + + #[derive(Display)] + #[display("{_variant}")] + enum EnumUpperExp { + #[display("{:E}", _0)] + A(A), + #[display("{field:E}")] + B { field: B }, + } + + #[derive(Display)] + #[display("{_variant}")] + enum EnumPointer { + #[display("{_0:p}")] + A(A), + #[display("{field:p}")] + B { field: B }, + } + + #[test] + fn assert() { + assert_eq!( + format!("{:03}", EnumDisplay::::A(7)), + "007", + ); + assert_eq!( + format!("{:03}", EnumDisplay::::B(8)), + "008", + ); + assert_eq!( + format!("{:07}", EnumBinary::<_, i8, _, ()>::A(7, ())), + "0000111", + ); + assert_eq!( + format!( + "{:07}", + EnumBinary::::B { b: 8, d: () }, + ), + "0001000", + ); + assert_eq!( + format!("{:03}", EnumOctal::<_, (), _, i8>::A((), 9)), + "011", + ); + assert_eq!( + format!( + "{:03}", + EnumOctal::<(), _, i8, _>::B { b: (), d: 10 } + ), + "012", + ); + assert_eq!( + format!("{:03}", EnumLowerHex::<_, i8>::A(42)), + "02a", + ); + assert_eq!( + format!("{:03}", EnumLowerHex::::B { field: 43 }), + "02b", + ); + assert_eq!( + format!("{:03}", EnumUpperHex::<_, i8>::A(42)), + "02A", + ); + assert_eq!( + format!("{:03}", EnumUpperHex::::B { field: 43 }), + "02B", + ); + assert_eq!( + format!("{:07}", EnumLowerExp::<_, i8>::A(42.0)), + "004.2e1", + ); + assert_eq!( + format!("{:07}", EnumLowerExp::::B { field: 43.0 }), + "004.3e1", + ); + assert_eq!( + format!("{:07}", EnumUpperExp::<_, i8>::A(42.0)), + "004.2E1", + ); + assert_eq!( + format!("{:07}", EnumUpperExp::::B { field: 43.0 }), + "004.3E1", + ); + let (a, b) = (42, 7); + assert_eq!( + format!("{:018}", EnumPointer::<_, &i8>::A(&b)), + format!("{:018p}", &b), + ); + assert_eq!( + format!("{:018}", EnumPointer::<&i8, _>::B { field: &a }), + format!("{:018p}", &a), + ); + } + } + } } mod omitted {