From d3d87ea10f14fec0a7d580b601ebddadc80fce36 Mon Sep 17 00:00:00 2001 From: tyranron Date: Sat, 23 Dec 2023 02:55:45 +0100 Subject: [PATCH 1/3] Showcase --- tests/debug.rs | 67 +++++++++++++++++++++++++++++- tests/display.rs | 103 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 138 insertions(+), 32 deletions(-) diff --git a/tests/debug.rs b/tests/debug.rs index 15e7f12f..67fec54d 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -102,7 +102,7 @@ mod structs { assert_eq!(format!("{:03?}", UpperHex), "00B"); assert_eq!(format!("{:07?}", LowerExp), "03.15e0"); assert_eq!(format!("{:07?}", UpperExp), "03.15E0"); - assert_eq!(format!("{:018?}", Pointer).len(), 18); + assert_eq!(format!("{:018?}", Pointer), format!("{POINTER:018p}")); } mod omitted { @@ -246,6 +246,35 @@ mod structs { "Struct {\n field: 0.0,\n}", ); } + + mod pointer { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + struct Tuple<'a>(#[debug("{_0:p}.{:p}", self.0)] &'a i32); + + #[derive(Debug)] + struct Struct<'a> { + #[debug("{field:p}.{:p}", self.field)] + field: &'a i32, + } + + #[test] + fn assert() { + let a = 42; + assert_eq!( + format!("{:?}", Tuple(&a)), + format!("Tuple({0:p}.{0:p})", &a), + ); + assert_eq!( + format!("{:?}", Struct { field: &a }), + format!("Struct {{ field: {0:p}.{0:p} }}", &a), + ); + } + } } mod ignore { @@ -527,6 +556,37 @@ mod structs { assert_eq!(format!("{:?}", Tuple(10, true)), "10 * true"); assert_eq!(format!("{:?}", Struct { a: 10, b: true }), "10 * true"); } + + mod pointer { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + #[debug("{_0:p} * {_1:p}", _0 = self.0)] + struct Tuple<'a, 'b>(&'a u8, &'b bool); + + #[derive(Debug)] + #[debug("{a:p} * {b:p}", a = self.a)] + struct Struct<'a, 'b> { + a: &'a u8, + b: &'b bool, + } + + #[test] + fn assert() { + let (a, b) = (10, true); + assert_eq!( + format!("{:?}", Tuple(&a, &b)), + format!("{:p} * {:p}", &a, &b), + ); + assert_eq!( + format!("{:?}", Struct { a: &a, b: &b }), + format!("{:p} * {:p}", &a, &b), + ); + } + } } mod ignore { @@ -677,7 +737,10 @@ mod enums { assert_eq!(format!("{:03?}", Unit::UpperHex), "00B"); assert_eq!(format!("{:07?}", Unit::LowerExp), "03.15e0"); assert_eq!(format!("{:07?}", Unit::UpperExp), "03.15E0"); - assert_eq!(format!("{:018?}", Unit::Pointer).len(), 18); + assert_eq!( + format!("{:018?}", Unit::Pointer), + format!("{POINTER:018p}"), + ); } mod omitted { diff --git a/tests/display.rs b/tests/display.rs index 1e7ddfc1..29b28f16 100644 --- a/tests/display.rs +++ b/tests/display.rs @@ -140,7 +140,7 @@ mod structs { assert_eq!(format!("{:03}", UpperHex), "00B"); assert_eq!(format!("{:07}", LowerExp), "03.15e0"); assert_eq!(format!("{:07}", UpperExp), "03.15E0"); - assert_eq!(format!("{:018}", Pointer).len(), 18); + assert_eq!(format!("{:018}", Pointer), format!("{POINTER:018p}")); } } @@ -323,9 +323,10 @@ mod structs { format!("{:07E}", StructUpperExp { field: 42.0 }), "004.2E1", ); + let a = 42; assert_eq!( - format!("{:018p}", StructPointer { field: &42 }).len(), - 18, + format!("{:018p}", StructPointer { field: &a }), + format!("{:018p}", &a), ); } } @@ -395,9 +396,10 @@ mod structs { format!("{:07}", StructUpperExp { field: 42.0 }), "004.2E1", ); + let a = 42; assert_eq!( - format!("{:018}", StructPointer { field: &42 }).len(), - 18, + format!("{:018}", StructPointer { field: &a }), + format!("{:018p}", &a), ); } } @@ -467,9 +469,10 @@ mod structs { format!("{:07}", StructUpperExp { field: 42.0 }), "4.2E1", ); - assert_ne!( - format!("{:018}", StructPointer { field: &42 }).len(), - 18, + let a = 42; + assert_eq!( + format!("{:018}", StructPointer { field: &a }), + format!("{:p}", &a), ); } } @@ -672,9 +675,10 @@ mod structs { format!("{:07}", StructUpperExp { a: 41.0, b: 42.0 }), "004.2E1", ); + let (a, b) = (42, 43); assert_eq!( - format!("{:018}", StructPointer { a: &42, b: &43 }).len(), - 18, + format!("{:018}", StructPointer { a: &a, b: &b }), + format!("{:018p}", &b), ); } } @@ -763,7 +767,10 @@ mod enums { assert_eq!(format!("{:03}", Unit::UpperHex), "00B"); assert_eq!(format!("{:07}", Unit::LowerExp), "03.15e0"); assert_eq!(format!("{:07}", Unit::UpperExp), "03.15E0"); - assert_eq!(format!("{:018}", Unit::Pointer).len(), 18); + assert_eq!( + format!("{:018}", Unit::Pointer), + format!("{POINTER:018p}"), + ); } } @@ -921,8 +928,15 @@ mod enums { format!("{:07E}", UpperExp::B { field: 43.0 }), "004.3E1", ); - assert_eq!(format!("{:018p}", Pointer::A(&7)).len(), 18); - assert_eq!(format!("{:018p}", Pointer::B { field: &42 }).len(), 18); + let (a, b) = (7, 42); + assert_eq!( + format!("{:018p}", Pointer::A(&a)), + format!("{:018p}", &a), + ); + assert_eq!( + format!("{:018p}", Pointer::B { field: &b }), + format!("{:018p}", &b), + ); } } @@ -1025,8 +1039,15 @@ mod enums { format!("{:07}", UpperExp::B { field: 43.0 }), "004.3E1", ); - assert_eq!(format!("{:018}", Pointer::A(&7)).len(), 18); - assert_eq!(format!("{:018}", Pointer::B { field: &42 }).len(), 18); + let (a, b) = (7, 42); + assert_eq!( + format!("{:018}", Pointer::A(&a)), + format!("{:018p}", &a), + ); + assert_eq!( + format!("{:018}", Pointer::B { field: &b }), + format!("{:018p}", &b), + ); } } @@ -1123,8 +1144,12 @@ mod enums { assert_eq!(format!("{:07}", LowerExp::B { field: 43.0 }), "4.3e1"); assert_eq!(format!("{:07}", UpperExp::A(42.0)), "4.2E1"); assert_eq!(format!("{:07}", UpperExp::B { field: 43.0 }), "4.3E1"); - assert_ne!(format!("{:018}", Pointer::A(&7)).len(), 18); - assert_ne!(format!("{:018}", Pointer::B { field: &42 }).len(), 18); + let (a, b) = (7, 42); + assert_eq!(format!("{:018}", Pointer::A(&a)), format!("{:0p}", &a)); + assert_eq!( + format!("{:018}", Pointer::B { field: &b }), + format!("{:p}", &b), + ); } } } @@ -1275,10 +1300,14 @@ mod enums { format!("{:07}", UpperExp::B { a: 43.0, b: 52.0 }), "004.3E1", ); - assert_eq!(format!("{:018}", Pointer::A(&7.0, &8.3)).len(), 18); + let (a, b) = (8.3, 42.1); + assert_eq!( + format!("{:018}", Pointer::A(&7.0, &a)), + format!("{:018p}", &a), + ); assert_eq!( - format!("{:018}", Pointer::B { a: &42.1, b: &43.3 }).len(), - 18, + format!("{:018}", Pointer::B { a: &b, b: &43.3 }), + format!("{:018p}", &b), ); } } @@ -1869,12 +1898,19 @@ mod generic { format!("{:07E}", Enum::::B { field: 43.0 }), "004.3E1", ); - assert_eq!(format!("{:018p}", Tuple(&42)).len(), 18); - assert_eq!(format!("{:018p}", Struct { field: &42 }).len(), 18); - assert_eq!(format!("{:018p}", Enum::<_, &i8>::A(&7)).len(), 18); + let (a, b) = (42, 7); + assert_eq!(format!("{:018p}", Tuple(&a)), format!("{:018p}", &a)); assert_eq!( - format!("{:018p}", Enum::<&i8, _>::B { field: &42 }).len(), - 18, + format!("{:018p}", Struct { field: &a }), + format!("{:018p}", &a), + ); + assert_eq!( + format!("{:018p}", Enum::<_, &i8>::A(&b)), + format!("{:018p}", &b), + ); + assert_eq!( + format!("{:018p}", Enum::<&i8, _>::B { field: &a }), + format!("{:018p}", &a), ); } } @@ -2090,12 +2126,19 @@ mod generic { format!("{:07}", EnumUpperExp::::B { field: 43.0 }), "004.3E1", ); - assert_eq!(format!("{:018}", TuplePointer(&42)).len(), 18); - assert_eq!(format!("{:018}", StructPointer { field: &42 }).len(), 18); - assert_eq!(format!("{:018}", EnumPointer::<_, &i8>::A(&7)).len(), 18); + let (a, b) = (42, 7); + assert_eq!(format!("{:018}", TuplePointer(&a)), format!("{:018p}", &a)); + assert_eq!( + format!("{:018}", StructPointer { field: &a }), + format!("{:018p}", &a), + ); + assert_eq!( + format!("{:018}", EnumPointer::<_, &i8>::A(&b)), + format!("{:018p}", &b), + ); assert_eq!( - format!("{:018}", EnumPointer::<&i8, _>::B { field: &42 }).len(), - 18, + format!("{:018}", EnumPointer::<&i8, _>::B { field: &a }), + format!("{:018p}", &a), ); } } From 834d4b51c674d10ba4fe30bff0de37ea253ed2c6 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 5 Apr 2024 17:47:51 +0300 Subject: [PATCH 2/3] Some corrections [skip ci] --- impl/src/fmt/display.rs | 38 +++++++++++++++++++++----------------- impl/src/fmt/mod.rs | 18 ++++++++++++++++-- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/impl/src/fmt/display.rs b/impl/src/fmt/display.rs index e41dcbd1..f72c8c11 100644 --- a/impl/src/fmt/display.rs +++ b/impl/src/fmt/display.rs @@ -32,7 +32,7 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result expand_struct(s, ctx), syn::Data::Enum(e) => expand_enum(e, ctx), @@ -68,7 +68,7 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result = ( - &'a ContainerAttributes, + ContainerAttributes, &'a syn::Ident, &'a syn::Ident, &'a syn::Ident, @@ -79,29 +79,33 @@ fn expand_struct( s: &syn::DataStruct, (attrs, ident, trait_ident, _): ExpansionCtx<'_>, ) -> syn::Result<(Vec, TokenStream)> { - let s = Expansion { + let mut s = Expansion { attrs, fields: &s.fields, trait_ident, ident, }; + + // It's important to generate bounds first, before we're going to modify the `fmt` expression. let bounds = s.generate_bounds(); - let body = s.generate_body()?; - let vars = s.fields.iter().enumerate().map(|(i, f)| { + let args = s.fields.iter().enumerate().map(|(i, f)| { let var = f.ident.clone().unwrap_or_else(|| format_ident!("_{i}")); let member = f .ident .clone() .map_or_else(|| syn::Member::Unnamed(i.into()), syn::Member::Named); - quote! { - let #var = &self.#member; + parse_quote! { + #var = self.#member } }); + if let Some(fmt_attr) = &mut s.attrs.fmt { + fmt_attr.append_args(args); + } + let fmt_expr = s.generate_expr()?; let body = quote! { - #( #vars )* - #body + #fmt_expr }; Ok((bounds, body)) @@ -138,12 +142,12 @@ fn expand_enum( } let v = Expansion { - attrs: &attrs, + attrs, fields: &variant.fields, trait_ident, ident, }; - let arm_body = v.generate_body()?; + let arm_body = v.generate_expr()?; bounds.extend(v.generate_bounds()); let fields_idents = @@ -199,7 +203,7 @@ fn expand_union( #[derive(Debug)] struct Expansion<'a> { /// Derive macro [`ContainerAttributes`]. - attrs: &'a ContainerAttributes, + attrs: ContainerAttributes, /// Struct or enum [`syn::Ident`]. /// @@ -216,16 +220,15 @@ struct Expansion<'a> { } impl<'a> Expansion<'a> { - /// Generates [`Display::fmt()`] implementation for a struct or an enum variant. + /// Generates [`Display::fmt()`] implementation expression for a struct or an enum variant. /// /// # Errors /// - /// In case [`FmtAttribute`] is [`None`] and [`syn::Fields`] length is - /// greater than 1. + /// In case [`FmtAttribute`] is [`None`] and [`syn::Fields`] length is greater than 1. /// /// [`Display::fmt()`]: fmt::Display::fmt() /// [`FmtAttribute`]: super::FmtAttribute - fn generate_body(&self) -> syn::Result { + fn generate_expr(&self) -> syn::Result { match &self.attrs.fmt { Some(fmt) => { Ok(if let Some((expr, trait_ident)) = fmt.transparent_call() { @@ -246,7 +249,8 @@ impl<'a> Expansion<'a> { .fields .iter() .next() - .unwrap_or_else(|| unreachable!("count() == 1")); + .unwrap_or_else(|| unreachable!("fields.len() == 1")); + // TODO: Re-check `fmt::Pointer` scenario? let ident = field.ident.clone().unwrap_or_else(|| format_ident!("_0")); let trait_ident = self.trait_ident; diff --git a/impl/src/fmt/mod.rs b/impl/src/fmt/mod.rs index 324e574c..62614609 100644 --- a/impl/src/fmt/mod.rs +++ b/impl/src/fmt/mod.rs @@ -135,6 +135,21 @@ impl ToTokens for FmtAttribute { } impl FmtAttribute { + // TODO: + fn append_args(&mut self, more: impl IntoIterator) { + let more = more + .into_iter() + .filter(|new| { + new.alias.is_none() + || self + .args + .iter() + .all(|old| old.alias.is_none() || old.alias != new.alias) + }) + .collect::>(); + self.args.extend(more); + } + /// Checks whether this [`FmtAttribute`] can be replaced with a transparent delegation (calling /// a formatting trait directly instead of interpolation syntax). /// @@ -278,8 +293,7 @@ impl FmtAttribute { } } -/// Representation of a [named parameter][1] (`identifier '=' expression`) in -/// in a [`FmtAttribute`]. +/// Representation of a [named parameter][1] (`identifier '=' expression`) in a [`FmtAttribute`]. /// /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters #[derive(Debug)] From 110640e93ef729341537936b9de02723540eb8f0 Mon Sep 17 00:00:00 2001 From: tyranron Date: Fri, 5 Apr 2024 19:44:41 +0300 Subject: [PATCH 3/3] Basics [skip ci] --- impl/src/fmt/display.rs | 44 +++++++++++++++++------------ impl/src/fmt/mod.rs | 62 ++++++++++++++++++++++++++++------------- 2 files changed, 69 insertions(+), 37 deletions(-) diff --git a/impl/src/fmt/display.rs b/impl/src/fmt/display.rs index f72c8c11..8b830590 100644 --- a/impl/src/fmt/display.rs +++ b/impl/src/fmt/display.rs @@ -5,11 +5,11 @@ use std::fmt; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_quote, spanned::Spanned as _}; +use syn::{parse_quote, spanned::Spanned as _, token}; use crate::utils::{attr::ParseMultiple as _, Spanning}; -use super::{trait_name_to_attribute_name, ContainerAttributes}; +use super::{trait_name_to_attribute_name, ContainerAttributes, FmtArgument}; /// Expands a [`fmt::Display`]-like derive macro. /// @@ -32,7 +32,7 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result expand_struct(s, ctx), syn::Data::Enum(e) => expand_enum(e, ctx), @@ -68,7 +68,7 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result = ( - ContainerAttributes, + &'a ContainerAttributes, &'a syn::Ident, &'a syn::Ident, &'a syn::Ident, @@ -79,33 +79,30 @@ fn expand_struct( s: &syn::DataStruct, (attrs, ident, trait_ident, _): ExpansionCtx<'_>, ) -> syn::Result<(Vec, TokenStream)> { - let mut s = Expansion { + let s = Expansion { attrs, fields: &s.fields, trait_ident, ident, }; - // It's important to generate bounds first, before we're going to modify the `fmt` expression. + let expr = s.generate_expr()?; let bounds = s.generate_bounds(); - let args = s.fields.iter().enumerate().map(|(i, f)| { + let vars = s.fields.iter().enumerate().map(|(i, f)| { let var = f.ident.clone().unwrap_or_else(|| format_ident!("_{i}")); let member = f .ident .clone() .map_or_else(|| syn::Member::Unnamed(i.into()), syn::Member::Named); - parse_quote! { - #var = self.#member + quote! { + let #var = &self.#member; } }); - if let Some(fmt_attr) = &mut s.attrs.fmt { - fmt_attr.append_args(args); - } - let fmt_expr = s.generate_expr()?; let body = quote! { - #fmt_expr + #( #vars )* + #expr }; Ok((bounds, body)) @@ -142,11 +139,12 @@ fn expand_enum( } let v = Expansion { - attrs, + attrs: &attrs, fields: &variant.fields, trait_ident, ident, }; + let arm_body = v.generate_expr()?; bounds.extend(v.generate_bounds()); @@ -203,7 +201,7 @@ fn expand_union( #[derive(Debug)] struct Expansion<'a> { /// Derive macro [`ContainerAttributes`]. - attrs: ContainerAttributes, + attrs: &'a ContainerAttributes, /// Struct or enum [`syn::Ident`]. /// @@ -234,7 +232,18 @@ impl<'a> Expansion<'a> { Ok(if let Some((expr, trait_ident)) = fmt.transparent_call() { quote! { derive_more::core::fmt::#trait_ident::fmt(&(#expr), __derive_more_f) } } else { - quote! { derive_more::core::write!(__derive_more_f, #fmt) } + let mut fmt_expr = fmt.clone(); + let additional_args = fmt.iter_used_fields(&self.fields).map( + |(name, _)| -> FmtArgument { + parse_quote! { #name = *#name } + }, + ); + fmt_expr.args.extend(additional_args); + if !fmt_expr.args.is_empty() { // TODO: Move into separate method. + fmt_expr.comma = Some(token::Comma::default()); + } + + quote! { derive_more::core::write!(__derive_more_f, #fmt_expr) } }) } None if self.fields.is_empty() => { @@ -250,7 +259,6 @@ impl<'a> Expansion<'a> { .iter() .next() .unwrap_or_else(|| unreachable!("fields.len() == 1")); - // TODO: Re-check `fmt::Pointer` scenario? let ident = field.ident.clone().unwrap_or_else(|| format_ident!("_0")); let trait_ident = self.trait_ident; diff --git a/impl/src/fmt/mod.rs b/impl/src/fmt/mod.rs index 62614609..20ccea52 100644 --- a/impl/src/fmt/mod.rs +++ b/impl/src/fmt/mod.rs @@ -93,7 +93,7 @@ impl BoundsAttribute { /// ``` /// /// [`fmt`]: std::fmt -#[derive(Debug)] +#[derive(Clone, Debug)] struct FmtAttribute { /// Interpolation [`syn::LitStr`]. /// @@ -135,21 +135,6 @@ impl ToTokens for FmtAttribute { } impl FmtAttribute { - // TODO: - fn append_args(&mut self, more: impl IntoIterator) { - let more = more - .into_iter() - .filter(|new| { - new.alias.is_none() - || self - .args - .iter() - .all(|old| old.alias.is_none() || old.alias != new.alias) - }) - .collect::>(); - self.args.extend(more); - } - /// Checks whether this [`FmtAttribute`] can be replaced with a transparent delegation (calling /// a formatting trait directly instead of interpolation syntax). /// @@ -211,8 +196,8 @@ impl FmtAttribute { Some((expr, format_ident!("{trait_name}"))) } - /// Returns an [`Iterator`] over bounded [`syn::Type`]s (and correspondent trait names) by this - /// [`FmtAttribute`]. + /// Returns an [`Iterator`] over bounded [`syn::Type`]s (and correspondent trait names) of the + /// provided [`syn::Fields`] used by this [`FmtAttribute`]. fn bounded_types<'a>( &'a self, fields: &'a syn::Fields, @@ -250,6 +235,45 @@ impl FmtAttribute { }) } + /// Returns an [`Iterator`] over the provided [`syn::Field`]s used by this [`FmtAttribute`], + /// along with the correspondent [`syn::Ident`] it's referred by in this [`FmtAttribute`]. + fn iter_used_fields<'a>( + &'a self, + fields: &'a syn::Fields, + ) -> impl Iterator { + let placeholders = Placeholder::parse_fmt_string(&self.lit.value()); + + // We ignore unknown fields, as compiler will produce better error messages. + placeholders.into_iter().filter_map(move |placeholder| { + let name = match &placeholder.arg { + Parameter::Named(name) => self + .args + .iter() + .find_map(|a| (a.alias()? == &name).then_some(&a.expr)) + .map_or(Some(format_ident!("{name}")), |expr| expr.ident().cloned())?, + Parameter::Positional(i) => self + .args + .iter() + .nth(*i) + .and_then(|a| a.expr.ident().filter(|_| a.alias.is_none()))? + .clone(), + }; + let position = name.to_string().strip_prefix('_').and_then(|s| s.parse().ok()); + + let field = match (&fields, position) { + (syn::Fields::Unnamed(f), Some(i)) => { + f.unnamed.iter().nth(i) + } + (syn::Fields::Named(f), None) => f.named.iter().find_map(|f| { + f.ident.as_ref().filter(|s| **s == name).map(|_| f) + }), + _ => None, + }?; + + Some((name, field)) + }) + } + /// Errors in case legacy syntax is encountered: `fmt = "...", (arg),*`. fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> { let fork = input.fork(); @@ -296,7 +320,7 @@ impl FmtAttribute { /// Representation of a [named parameter][1] (`identifier '=' expression`) in a [`FmtAttribute`]. /// /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters -#[derive(Debug)] +#[derive(Clone, Debug)] struct FmtArgument { /// `identifier =` [`Ident`]. ///