From 732bb122245192cb03f1c23ee6dc88a33cbf6c02 Mon Sep 17 00:00:00 2001 From: Kai Ren Date: Fri, 22 Dec 2023 12:27:04 +0100 Subject: [PATCH] Delegation for `fmt` traits (#322, #321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Synopsis See https://github.com/JelteF/derive_more/issues/321#issuecomment-1854069597: > You’re discarding formatting flags provided by the user in format string, e.g.: > > ```rust > #[derive(derive_more::Display)] > #[display(fmt = "{:?}", _0)] > struct Num(usize); > > impl std::fmt::Debug for Num { > fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result { > self.0.fmt(fmtr) > } > } > > fn main() { > let num = Num(7); > println!("{num:03?}"); // prints ‘007’ as expected > println!("{num:03}"); // prints ‘7’ instead > } > ``` ## Solution See https://github.com/JelteF/derive_more/issues/321#issuecomment-1854382465: > Theoretically, we can support this with the current syntax, because we can detect so-called _trivial_ cases and transform them into delegation (we do parse formatting string literal anyway). > > ```rust > #[derive(derive_more::Display)] > #[display("{_0:?}")] // <--- it's clear to be a trivial delegation case > struct Num(usize); > ``` > > would expand to > > ```rust > impl std::fmt::Display for Num { > fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result { > let _0 = &self.0; > std::fmt::Debug::fmt(_0, fmtr) > } > } > ``` > > rather than > > ```rust > impl std::fmt::Display for Num { > fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result { > let _0 = &self.0; > write!(fmtr, "{_0:?}") > } > } > ``` --- CHANGELOG.md | 4 + impl/doc/debug.md | 54 +- impl/doc/display.md | 65 ++ impl/src/fmt/debug.rs | 18 +- impl/src/fmt/display.rs | 19 +- impl/src/fmt/mod.rs | 80 ++- impl/src/fmt/parsing.rs | 375 ++++++++--- impl/src/parsing.rs | 8 +- impl/src/utils.rs | 1 - tests/debug.rs | 544 ++++++++++++++++ tests/display.rs | 1319 ++++++++++++++++++++++++++++++++++++++- 11 files changed, 2374 insertions(+), 113 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bfac31b..f44d5403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). `#[display(fmt = "...", (""),*)]`, and `#[display(bound())]` instead of `#[display(bound = "")]`. So without the double quotes around the expressions and bounds. +- The `Debug` and `Display` derives (and other `fmt`-like ones) now transparently + delegate to the inner type when `#[display("...", (),*)]` attribute is + trivially substitutable with a transparent call. + ([#322](https://github.com/JelteF/derive_more/pull/322)) - The `DebugCustom` derive is renamed to just `Debug` (gated now under a separate `debug` feature), and its semantics were changed to be a superset of `std` variant of `Debug`. diff --git a/impl/doc/debug.md b/impl/doc/debug.md index c56e6644..3d95b90a 100644 --- a/impl/doc/debug.md +++ b/impl/doc/debug.md @@ -18,8 +18,6 @@ The variables available in the arguments is `self` and each member of the struct structs being named with a leading underscore and their index, i.e. `_0`, `_1`, `_2`, etc. - - ### Generic data types When deriving `Debug` for a generic struct/enum, all generic type arguments _used_ during formatting @@ -53,8 +51,6 @@ The following where clauses would be generated: - `&'a T1: Pointer` - - ### Custom trait bounds Sometimes you may want to specify additional trait bounds on your generic type parameters, so that they could be used @@ -88,6 +84,54 @@ trait MyTrait { fn my_function(&self) -> i32; } ``` +### Transparency + +If the top-level `#[debug("...", args...)]` attribute (the one for a whole struct or variant) is specified +and can be trivially substituted with a transparent delegation call to the inner type, then all the additional +[formatting parameters][1] do work as expected: +```rust +# use derive_more::Debug; +# +#[derive(Debug)] +#[debug("{_0:o}")] // the same as calling `Octal::fmt()` +struct MyOctalInt(i32); + +// so, additional formatting parameters do work transparently +assert_eq!(format!("{:03?}", MyOctalInt(9)), "011"); + +#[derive(Debug)] +#[debug("{_0:02b}")] // cannot be trivially substituted with `Binary::fmt()`, +struct MyBinaryInt(i32); // because of specified formatting parameters + +// so, additional formatting parameters have no effect +assert_eq!(format!("{:07?}", MyBinaryInt(2)), "10"); +``` + +If, for some reason, transparency in trivial cases is not desired, it may be suppressed explicitly +either with the [`format_args!()`] macro usage: +```rust +# use derive_more::Debug; +# +#[derive(Debug)] +#[debug("{}", format_args!("{_0:o}"))] // `format_args!()` obscures the inner type +struct MyOctalInt(i32); + +// so, additional formatting parameters have no effect +assert_eq!(format!("{:07?}", MyOctalInt(9)), "11"); +``` +Or by adding [formatting parameters][1] which cause no visual effects: +```rust +# use derive_more::Debug; +# +#[derive(Debug)] +#[debug("{_0:^o}")] // `^` is centering, but in absence of additional width has no effect +struct MyOctalInt(i32); + +// and so, additional formatting parameters have no effect +assert_eq!(format!("{:07?}", MyOctalInt(9)), "11"); +``` + + ## Example usage @@ -133,3 +177,5 @@ assert_eq!(format!("{:?}", E::EnumFormat(true)), "true"); [`format!()`]: https://doc.rust-lang.org/stable/std/macro.format.html [`format_args!()`]: https://doc.rust-lang.org/stable/std/macro.format_args.html + +[1]: https://doc.rust-lang.org/stable/std/fmt/index.html#formatting-parameters diff --git a/impl/doc/display.md b/impl/doc/display.md index 85eee822..5c242bdf 100644 --- a/impl/doc/display.md +++ b/impl/doc/display.md @@ -97,6 +97,64 @@ struct MyStruct { ``` +### Transparency + +If the `#[display("...", args...)]` attribute is omitted, the implementation transparently delegates to the format +of the inner type, so all the additional [formatting parameters][1] do work as expected: +```rust +# use derive_more::Display; +# +#[derive(Display)] +struct MyInt(i32); + +assert_eq!(format!("{:03}", MyInt(7)), "007"); +``` + +If the `#[display("...", args...)]` attribute is specified and can be trivially substituted with a transparent +delegation call to the inner type, then additional [formatting parameters][1] will work too: +```rust +# use derive_more::Display; +# +#[derive(Display)] +#[display("{_0:o}")] // the same as calling `Octal::fmt()` +struct MyOctalInt(i32); + +// so, additional formatting parameters do work transparently +assert_eq!(format!("{:03}", MyOctalInt(9)), "011"); + +#[derive(Display)] +#[display("{_0:02b}")] // cannot be trivially substituted with `Binary::fmt()`, +struct MyBinaryInt(i32); // because of specified formatting parameters + +// so, additional formatting parameters have no effect +assert_eq!(format!("{:07}", MyBinaryInt(2)), "10"); +``` + +If, for some reason, transparency in trivial cases is not desired, it may be suppressed explicitly +either with the [`format_args!()`] macro usage: +```rust +# use derive_more::Display; +# +#[derive(Display)] +#[display("{}", format_args!("{_0:o}"))] // `format_args!()` obscures the inner type +struct MyOctalInt(i32); + +// so, additional formatting parameters have no effect +assert_eq!(format!("{:07}", MyOctalInt(9)), "11"); +``` +Or by adding [formatting parameters][1] which cause no visual effects: +```rust +# use derive_more::Display; +# +#[derive(Display)] +#[display("{_0:^o}")] // `^` is centering, but in absence of additional width has no effect +struct MyOctalInt(i32); + +// and so, additional formatting parameters have no effect +assert_eq!(format!("{:07}", MyOctalInt(9)), "11"); +``` + + ## Example usage @@ -176,3 +234,10 @@ assert_eq!(UnitStruct {}.to_string(), "UnitStruct"); assert_eq!(PositiveOrNegative { x: 1 }.to_string(), "Positive"); assert_eq!(PositiveOrNegative { x: -1 }.to_string(), "Negative"); ``` + + + + +[`format_args!()`]: https://doc.rust-lang.org/stable/std/macro.format_args.html + +[1]: https://doc.rust-lang.org/stable/std/fmt/index.html#formatting-parameters diff --git a/impl/src/fmt/debug.rs b/impl/src/fmt/debug.rs index 5cd43b10..8d96554c 100644 --- a/impl/src/fmt/debug.rs +++ b/impl/src/fmt/debug.rs @@ -227,8 +227,12 @@ impl<'a> Expansion<'a> { /// /// [`Debug::fmt()`]: std::fmt::Debug::fmt() fn generate_body(&self) -> syn::Result { - if let Some(fmt_attr) = &self.attr.fmt { - return Ok(quote! { ::core::write!(__derive_more_f, #fmt_attr) }); + if let Some(fmt) = &self.attr.fmt { + return Ok(if let Some((expr, trait_ident)) = fmt.transparent_call() { + quote! { ::core::fmt::#trait_ident::fmt(&(#expr), __derive_more_f) } + } else { + quote! { ::core::write!(__derive_more_f, #fmt) } + }); }; match self.fields { @@ -331,8 +335,9 @@ impl<'a> Expansion<'a> { if let Some(fmt) = self.attr.fmt.as_ref() { out.extend(fmt.bounded_types(self.fields).map(|(ty, trait_name)| { - let trait_name = format_ident!("{trait_name}"); - parse_quote! { #ty: ::core::fmt::#trait_name } + let trait_ident = format_ident!("{trait_name}"); + + parse_quote! { #ty: ::core::fmt::#trait_ident } })); Ok(out) } else { @@ -344,8 +349,9 @@ impl<'a> Expansion<'a> { Some(FieldAttribute::Right(fmt_attr)) => { out.extend(fmt_attr.bounded_types(self.fields).map( |(ty, trait_name)| { - let trait_name = format_ident!("{trait_name}"); - parse_quote! { #ty: ::core::fmt::#trait_name } + let trait_ident = format_ident!("{trait_name}"); + + parse_quote! { #ty: ::core::fmt::#trait_ident } }, )); } diff --git a/impl/src/fmt/display.rs b/impl/src/fmt/display.rs index 789f99b8..fb368928 100644 --- a/impl/src/fmt/display.rs +++ b/impl/src/fmt/display.rs @@ -222,10 +222,19 @@ impl<'a> Expansion<'a> { /// [`Display::fmt()`]: fmt::Display::fmt() fn generate_body(&self) -> syn::Result { match &self.attrs.fmt { - Some(fmt) => Ok(quote! { ::core::write!(__derive_more_f, #fmt) }), + Some(fmt) => { + Ok(if let Some((expr, trait_ident)) = fmt.transparent_call() { + quote! { ::core::fmt::#trait_ident::fmt(&(#expr), __derive_more_f) } + } else { + quote! { ::core::write!(__derive_more_f, #fmt) } + }) + } None if self.fields.is_empty() => { let ident_str = self.ident.to_string(); - Ok(quote! { ::core::write!(__derive_more_f, #ident_str) }) + + Ok(quote! { + ::core::write!(__derive_more_f, #ident_str) + }) } None if self.fields.len() == 1 => { let field = self @@ -235,6 +244,7 @@ impl<'a> Expansion<'a> { .unwrap_or_else(|| unreachable!("count() == 1")); let ident = field.ident.clone().unwrap_or_else(|| format_ident!("_0")); let trait_ident = self.trait_ident; + Ok(quote! { ::core::fmt::#trait_ident::fmt(#ident, __derive_more_f) }) @@ -267,8 +277,9 @@ impl<'a> Expansion<'a> { fmt.bounded_types(self.fields) .map(|(ty, trait_name)| { - let tr = format_ident!("{}", trait_name); - parse_quote! { #ty: ::core::fmt::#tr } + let trait_ident = format_ident!("{trait_name}"); + + parse_quote! { #ty: ::core::fmt::#trait_ident } }) .chain(self.attrs.bounds.0.clone()) .collect() diff --git a/impl/src/fmt/mod.rs b/impl/src/fmt/mod.rs index 0a3244ba..0f58990d 100644 --- a/impl/src/fmt/mod.rs +++ b/impl/src/fmt/mod.rs @@ -9,12 +9,12 @@ pub(crate) mod display; mod parsing; use proc_macro2::TokenStream; -use quote::ToTokens; +use quote::{format_ident, ToTokens}; use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned as _, - token, Ident, + token, }; use crate::{ @@ -133,7 +133,69 @@ impl ToTokens for FmtAttribute { } impl FmtAttribute { - /// Returns an [`Iterator`] over bounded [`syn::Type`]s and trait names. + /// Checks whether this [`FmtAttribute`] can be replaced with a transparent delegation (calling + /// a formatting trait directly instead of interpolation syntax). + /// + /// If such transparent call is possible, the returns an [`Ident`] of the delegated trait and + /// the [`Expr`] to pass into the call, otherwise [`None`]. + /// + /// [`Ident`]: struct@syn::Ident + fn transparent_call(&self) -> Option<(Expr, syn::Ident)> { + // `FmtAttribute` is transparent when: + + // (1) There is exactly one formatting parameter. + let lit = self.lit.value(); + let param = + parsing::format(&lit).and_then(|(more, p)| more.is_empty().then_some(p))?; + + // (2) And the formatting parameter doesn't contain any modifiers. + if param + .spec + .map(|s| { + s.align.is_some() + || s.sign.is_some() + || s.alternate.is_some() + || s.zero_padding.is_some() + || s.width.is_some() + || s.precision.is_some() + || !s.ty.is_trivial() + }) + .unwrap_or_default() + { + return None; + } + + let expr = match param.arg { + // (3) And either exactly one positional argument is specified. + Some(parsing::Argument::Integer(_)) | None => (self.args.len() == 1) + .then(|| self.args.first()) + .flatten() + .map(|a| a.expr.clone()), + + // (4) Or the formatting parameter's name refers to some outer binding. + Some(parsing::Argument::Identifier(name)) if self.args.is_empty() => { + Some(format_ident!("{name}").into()) + } + + // (5) Or exactly one named argument is specified for the formatting parameter's name. + Some(parsing::Argument::Identifier(name)) => (self.args.len() == 1) + .then(|| self.args.first()) + .flatten() + .filter(|a| a.alias.as_ref().map(|a| a.0 == name).unwrap_or_default()) + .map(|a| a.expr.clone()), + }?; + + let trait_name = param + .spec + .map(|s| s.ty) + .unwrap_or(parsing::Type::Display) + .trait_name(); + + Some((expr, format_ident!("{trait_name}"))) + } + + /// Returns an [`Iterator`] over bounded [`syn::Type`]s (and correspondent trait names) by this + /// [`FmtAttribute`]. fn bounded_types<'a>( &'a self, fields: &'a syn::Fields, @@ -221,7 +283,9 @@ impl FmtAttribute { #[derive(Debug)] struct FmtArgument { /// `identifier =` [`Ident`]. - alias: Option<(Ident, token::Eq)>, + /// + /// [`Ident`]: struct@syn::Ident + alias: Option<(syn::Ident, token::Eq)>, /// `expression` [`Expr`]. expr: Expr, @@ -231,7 +295,7 @@ impl FmtArgument { /// Returns an `identifier` of the [named parameter][1]. /// /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters - fn alias(&self) -> Option<&Ident> { + fn alias(&self) -> Option<&syn::Ident> { self.alias.as_ref().map(|(ident, _)| ident) } } @@ -239,7 +303,7 @@ impl FmtArgument { impl Parse for FmtArgument { fn parse(input: ParseStream) -> syn::Result { Ok(Self { - alias: (input.peek(Ident) && input.peek2(token::Eq)) + alias: (input.peek(syn::Ident) && input.peek2(token::Eq)) .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) .transpose()?, expr: input.parse()?, @@ -283,7 +347,7 @@ impl<'a> From> for Parameter { } /// Representation of a formatting placeholder. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Eq, PartialEq)] struct Placeholder { /// Formatting argument (either named or positional) to be used by this placeholder. arg: Parameter, @@ -378,7 +442,7 @@ impl attr::ParseMultiple for ContainerAttributes { fn merge_attrs( prev: Spanning, new: Spanning, - name: &Ident, + name: &syn::Ident, ) -> syn::Result> { let Spanning { span: prev_span, diff --git a/impl/src/fmt/parsing.rs b/impl/src/fmt/parsing.rs index e4586668..8a5d7527 100644 --- a/impl/src/fmt/parsing.rs +++ b/impl/src/fmt/parsing.rs @@ -2,12 +2,12 @@ //! //! [0]: std::fmt#syntax -use std::{convert::identity, iter}; +use std::iter; use unicode_xid::UnicodeXID as XID; /// Output of the [`format_string`] parser. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct FormatString<'a> { pub(crate) formats: Vec>, } @@ -22,38 +22,80 @@ pub(crate) struct Format<'a> { } /// Output of the [`format_spec`] parser. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) struct FormatSpec<'a> { + /// Parsed `[[fill]`[`align`]`]`. + pub(crate) align: Option<(Option, Align)>, + + /// Parsed `[`[`sign`]`]`. + pub(crate) sign: Option, + + /// Parsed `['#']` (alternation). + pub(crate) alternate: Option, + + /// Parsed `['0']` (padding with zeros). + pub(crate) zero_padding: Option, + + /// Parsed `[width]`. pub(crate) width: Option>, + + /// Parsed `['.' `[`precision`]`]`. pub(crate) precision: Option>, + + /// Parsed [`type`]. + /// + /// [`type`]: type_ pub(crate) ty: Type, } /// Output of the [`argument`] parser. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum Argument<'a> { Integer(usize), Identifier(&'a str), } +/// Output of the [`align`] parser. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum Align { + Left, + Center, + Right, +} + +/// Output of the [`sign`] parser. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum Sign { + Plus, + Minus, +} + +/// Type for the [`FormatSpec::alternate`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) struct Alternate; + +/// Type for the [`FormatSpec::zero_padding`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) struct ZeroPadding; + /// Output of the [`precision`] parser. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum Precision<'a> { Count(Count<'a>), Star, } /// Output of the [`count`] parser. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum Count<'a> { Integer(usize), Parameter(Parameter<'a>), } -/// Output of the [`type_`] parser. See [formatting traits][1] for more info. +/// Output of the [`type_`] parser. See [formatting traits][0] for more info. /// -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#formatting-traits -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +/// [0]: std::fmt#formatting-traits +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum Type { Display, Debug, @@ -72,19 +114,38 @@ impl Type { /// Returns trait name of this [`Type`]. pub(crate) fn trait_name(&self) -> &'static str { match self { - Type::Display => "Display", - Type::Debug | Type::LowerDebug | Type::UpperDebug => "Debug", - Type::Octal => "Octal", - Type::LowerHex => "LowerHex", - Type::UpperHex => "UpperHex", - Type::Pointer => "Pointer", - Type::Binary => "Binary", - Type::LowerExp => "LowerExp", - Type::UpperExp => "UpperExp", + Self::Display => "Display", + Self::Debug | Self::LowerDebug | Self::UpperDebug => "Debug", + Self::Octal => "Octal", + Self::LowerHex => "LowerHex", + Self::UpperHex => "UpperHex", + Self::Pointer => "Pointer", + Self::Binary => "Binary", + Self::LowerExp => "LowerExp", + Self::UpperExp => "UpperExp", + } + } + + /// Indicates whether this [`Type`] represents a trivial trait call without any modifications. + pub(crate) fn is_trivial(&self) -> bool { + match self { + Self::Display + | Self::Debug + | Self::Octal + | Self::LowerHex + | Self::UpperHex + | Self::Pointer + | Self::Binary + | Self::LowerExp + | Self::UpperExp => true, + Self::LowerDebug | Self::UpperDebug => false, } } } +/// Type alias for the `fill` in the [`FormatSpec::align`]. +type Fill = char; + /// Type alias for the [`FormatSpec::width`]. type Width<'a> = Count<'a>; @@ -102,7 +163,7 @@ type Parameter<'a> = Argument<'a>; /// [`str`]: prim@str type LeftToParse<'a> = &'a str; -/// Parses a `format_string` as defined in the [grammar spec][1]. +/// Parses a `format_string` as defined in the [grammar spec][0]. /// /// # Grammar /// @@ -126,7 +187,7 @@ type LeftToParse<'a> = &'a str; /// - [`None`] otherwise (not all characters are consumed by underlying /// parsers). /// -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [0]: std::fmt#syntax pub(crate) fn format_string(input: &str) -> Option> { let (mut input, _) = optional_result(text)(input); @@ -146,7 +207,7 @@ pub(crate) fn format_string(input: &str) -> Option> { input.is_empty().then_some(FormatString { formats }) } -/// Parses a `maybe_format` as defined in the [grammar spec][1]. +/// Parses a `maybe_format` as defined in the [grammar spec][0]. /// /// # Grammar /// @@ -163,7 +224,7 @@ pub(crate) fn format_string(input: &str) -> Option> { /// ``` /// /// [`format`]: fn@format -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [0]: std::fmt#syntax fn maybe_format(input: &str) -> Option<(LeftToParse<'_>, MaybeFormat<'_>)> { alt(&mut [ &mut map(str("{{"), |i| (i, None)), @@ -172,7 +233,7 @@ fn maybe_format(input: &str) -> Option<(LeftToParse<'_>, MaybeFormat<'_>)> { ])(input) } -/// Parses a `format` as defined in the [grammar spec][1]. +/// Parses a `format` as defined in the [grammar spec][0]. /// /// # Grammar /// @@ -187,8 +248,8 @@ fn maybe_format(input: &str) -> Option<(LeftToParse<'_>, MaybeFormat<'_>)> { /// ``` /// /// [`format`]: fn@format -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax -fn format(input: &str) -> Option<(LeftToParse<'_>, Format<'_>)> { +/// [0]: std::fmt#syntax +pub(crate) fn format(input: &str) -> Option<(LeftToParse<'_>, Format<'_>)> { let input = char('{')(input)?; let (input, arg) = optional_result(argument)(input); @@ -204,7 +265,7 @@ fn format(input: &str) -> Option<(LeftToParse<'_>, Format<'_>)> { Some((input, Format { arg, spec })) } -/// Parses an `argument` as defined in the [grammar spec][1]. +/// Parses an `argument` as defined in the [grammar spec][0]. /// /// # Grammar /// @@ -218,7 +279,7 @@ fn format(input: &str) -> Option<(LeftToParse<'_>, Format<'_>)> { /// Минск /// ``` /// -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [0]: std::fmt#syntax fn argument(input: &str) -> Option<(LeftToParse<'_>, Argument)> { alt(&mut [ &mut map(identifier, |(i, ident)| (i, Argument::Identifier(ident))), @@ -226,11 +287,11 @@ fn argument(input: &str) -> Option<(LeftToParse<'_>, Argument)> { ])(input) } -/// Parses a `format_spec` as defined in the [grammar spec][1]. +/// Parses a `format_spec` as defined in the [grammar spec][0]. /// /// # Grammar /// -/// [`format_spec`]` := [[fill]align][sign]['#']['0'][width]` +/// [`format_spec`]` := [[fill]`[`align`]`][`[`sign`]`]['#']['0'][width]` /// `['.' `[`precision`]`]`[`type`] /// /// # Example @@ -242,24 +303,26 @@ fn argument(input: &str) -> Option<(LeftToParse<'_>, Argument)> { /// ``` /// /// [`type`]: type_ -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [0]: std::fmt#syntax fn format_spec(input: &str) -> Option<(LeftToParse<'_>, FormatSpec<'_>)> { - let input = unwrap_or_else( - alt(&mut [ - &mut try_seq(&mut [&mut any_char, &mut one_of("<^>")]), - &mut one_of("<^>"), - ]), - identity, - )(input); + let (input, align) = optional_result(alt(&mut [ + &mut and_then(take_any_char, |(i, fill)| { + map(align, |(i, align)| (i, (Some(fill), align)))(i) + }), + &mut map(align, |(i, align)| (i, (None, align))), + ]))(input); + + let (input, sign) = optional_result(sign)(input); + + let (input, alternate) = optional_result(map(char('#'), |i| (i, Alternate)))(input); - let input = seq(&mut [ - &mut optional(one_of("+-")), - &mut optional(char('#')), - &mut optional(try_seq(&mut [ + let (input, zero_padding) = optional_result(map( + try_seq(&mut [ &mut char('0'), &mut lookahead(check_char(|c| !matches!(c, '$'))), - ])), - ])(input); + ]), + |i| (i, ZeroPadding), + ))(input); let (input, width) = optional_result(count)(input); @@ -274,6 +337,10 @@ fn format_spec(input: &str) -> Option<(LeftToParse<'_>, FormatSpec<'_>)> { Some(( input, FormatSpec { + align, + sign, + alternate, + zero_padding, width, precision, ty, @@ -281,7 +348,51 @@ fn format_spec(input: &str) -> Option<(LeftToParse<'_>, FormatSpec<'_>)> { )) } -/// Parses a `precision` as defined in the [grammar spec][1]. +/// Parses an `align` as defined in the [grammar spec][0]. +/// +/// # Grammar +/// +/// [`align`]` := '<' | '^' | '>'` +/// +/// # Example +/// +/// ```text +/// < +/// ^ +/// > +/// ``` +/// +/// [0]: std::fmt#syntax +fn align(input: &str) -> Option<(LeftToParse<'_>, Align)> { + alt(&mut [ + &mut map(char('<'), |i| (i, Align::Left)), + &mut map(char('^'), |i| (i, Align::Center)), + &mut map(char('>'), |i| (i, Align::Right)), + ])(input) +} + +/// Parses a `sign` as defined in the [grammar spec][0]. +/// +/// # Grammar +/// +/// [`sign`]` := '+' | '-'` +/// +/// # Example +/// +/// ```text +/// + +/// - +/// ``` +/// +/// [0]: std::fmt#syntax +fn sign(input: &str) -> Option<(LeftToParse<'_>, Sign)> { + alt(&mut [ + &mut map(char('+'), |i| (i, Sign::Plus)), + &mut map(char('-'), |i| (i, Sign::Minus)), + ])(input) +} + +/// Parses a `precision` as defined in the [grammar spec][0]. /// /// # Grammar /// @@ -296,7 +407,7 @@ fn format_spec(input: &str) -> Option<(LeftToParse<'_>, FormatSpec<'_>)> { /// * /// ``` /// -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [0]: std::fmt#syntax fn precision(input: &str) -> Option<(LeftToParse<'_>, Precision<'_>)> { alt(&mut [ &mut map(count, |(i, c)| (i, Precision::Count(c))), @@ -304,7 +415,7 @@ fn precision(input: &str) -> Option<(LeftToParse<'_>, Precision<'_>)> { ])(input) } -/// Parses a `type` as defined in the [grammar spec][1]. +/// Parses a `type` as defined in the [grammar spec][0]. /// /// # Grammar /// @@ -328,7 +439,7 @@ fn precision(input: &str) -> Option<(LeftToParse<'_>, Precision<'_>)> { /// ``` /// /// [`type`]: type_ -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [0]: std::fmt#syntax fn type_(input: &str) -> Option<(&str, Type)> { alt(&mut [ &mut map(str("x?"), |i| (i, Type::LowerDebug)), @@ -345,7 +456,7 @@ fn type_(input: &str) -> Option<(&str, Type)> { ])(input) } -/// Parses a `count` as defined in the [grammar spec][1]. +/// Parses a `count` as defined in the [grammar spec][0]. /// /// # Grammar /// @@ -359,7 +470,7 @@ fn type_(input: &str) -> Option<(&str, Type)> { /// par$ /// ``` /// -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [0]: std::fmt#syntax fn count(input: &str) -> Option<(LeftToParse<'_>, Count<'_>)> { alt(&mut [ &mut map(parameter, |(i, p)| (i, Count::Parameter(p))), @@ -367,7 +478,7 @@ fn count(input: &str) -> Option<(LeftToParse<'_>, Count<'_>)> { ])(input) } -/// Parses a `parameter` as defined in the [grammar spec][1]. +/// Parses a `parameter` as defined in the [grammar spec][0]. /// /// # Grammar /// @@ -380,12 +491,12 @@ fn count(input: &str) -> Option<(LeftToParse<'_>, Count<'_>)> { /// par$ /// ``` /// -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [0]: std::fmt#syntax fn parameter(input: &str) -> Option<(LeftToParse<'_>, Parameter<'_>)> { and_then(argument, |(i, arg)| map(char('$'), |i| (i, arg))(i))(input) } -/// Parses an `identifier` as defined in the [grammar spec][1]. +/// Parses an `identifier` as defined in the [grammar spec][0]. /// /// # Grammar /// @@ -400,7 +511,7 @@ fn parameter(input: &str) -> Option<(LeftToParse<'_>, Parameter<'_>)> { /// Минск /// ``` /// -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [0]: std::fmt#syntax /// [2]: https://doc.rust-lang.org/reference/identifiers.html fn identifier(input: &str) -> Option<(LeftToParse<'_>, Identifier<'_>)> { map( @@ -415,9 +526,9 @@ fn identifier(input: &str) -> Option<(LeftToParse<'_>, Identifier<'_>)> { )(input) } -/// Parses an `integer` as defined in the [grammar spec][1]. +/// Parses an `integer` as defined in the [grammar spec][0]. /// -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [0]: std::fmt#syntax fn integer(input: &str) -> Option<(LeftToParse<'_>, usize)> { and_then( take_while1(check_char(|c| c.is_ascii_digit())), @@ -425,20 +536,13 @@ fn integer(input: &str) -> Option<(LeftToParse<'_>, usize)> { )(input) } -/// Parses a `text` as defined in the [grammar spec][1]. +/// Parses a `text` as defined in the [grammar spec][0]. /// -/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [0]: std::fmt#syntax fn text(input: &str) -> Option<(LeftToParse<'_>, &str)> { take_until1(any_char, one_of("{}"))(input) } -type Parser<'p> = &'p mut dyn FnMut(&str) -> &str; - -/// Applies non-failing parsers in sequence. -fn seq<'p>(parsers: &'p mut [Parser<'p>]) -> impl FnMut(&str) -> LeftToParse<'_> + 'p { - move |input| parsers.iter_mut().fold(input, |i, p| (**p)(i)) -} - type FallibleParser<'p> = &'p mut dyn FnMut(&str) -> Option<&str>; /// Tries to apply parsers in sequence. Returns [`None`] in case one of them @@ -474,14 +578,6 @@ fn map_or_else<'i, I: 'i, O: 'i>( move |input| parser(input).map_or_else(|| default(input), &mut f) } -/// Returns the contained [`Some`] value or computes it from a closure. -fn unwrap_or_else<'i, O: 'i>( - mut parser: impl FnMut(&'i str) -> Option, - mut f: impl FnMut(&'i str) -> O, -) -> impl FnMut(&'i str) -> O { - move |input| parser(input).unwrap_or_else(|| f(input)) -} - /// Returns [`None`] if the parser returned is [`None`], otherwise calls `f` /// with the wrapped value and returns the result. fn and_then<'i, I: 'i, O: 'i>( @@ -499,14 +595,6 @@ fn lookahead( move |input| map(&mut parser, |_| input)(input) } -/// Makes underlying `parser` optional by returning the original `input` in case -/// it returned [`None`]. -fn optional( - mut parser: impl FnMut(&str) -> Option<&str>, -) -> impl FnMut(&str) -> LeftToParse<'_> { - move |input: &str| parser(input).unwrap_or(input) -} - /// Makes underlying `parser` optional by returning the original `input` and /// [`None`] in case it returned [`None`]. fn optional_result<'i, T: 'i>( @@ -619,6 +707,13 @@ fn any_char(input: &str) -> Option> { input.chars().next().map(|c| &input[c.len_utf8()..]) } +/// Parses any [`char`] and returns it. +/// +/// [`char`]: fn@char +fn take_any_char(input: &str) -> Option<(LeftToParse<'_>, char)> { + input.chars().next().map(|c| (&input[c.len_utf8()..], c)) +} + #[cfg(test)] mod tests { use super::*; @@ -685,6 +780,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: None, + zero_padding: None, width: None, precision: None, ty: Type::Display, @@ -698,6 +797,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((None, Align::Center)), + sign: None, + alternate: None, + zero_padding: None, width: None, precision: None, ty: Type::Display, @@ -711,6 +814,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((Some('-'), Align::Left)), + sign: None, + alternate: None, + zero_padding: None, width: None, precision: None, ty: Type::Display, @@ -724,6 +831,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((Some(' '), Align::Left)), + sign: None, + alternate: None, + zero_padding: None, width: None, precision: None, ty: Type::Display, @@ -737,6 +848,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((Some('^'), Align::Left)), + sign: None, + alternate: None, + zero_padding: None, width: None, precision: None, ty: Type::Display, @@ -750,6 +865,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: Some(Sign::Plus), + alternate: None, + zero_padding: None, width: None, precision: None, ty: Type::Display, @@ -763,6 +882,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((Some('^'), Align::Left)), + sign: Some(Sign::Minus), + alternate: None, + zero_padding: None, width: None, precision: None, ty: Type::Display, @@ -776,6 +899,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: Some(Alternate), + zero_padding: None, width: None, precision: None, ty: Type::Display, @@ -789,6 +916,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: Some(Sign::Plus), + alternate: Some(Alternate), + zero_padding: None, width: None, precision: None, ty: Type::Display, @@ -802,6 +933,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((Some('-'), Align::Left)), + sign: None, + alternate: Some(Alternate), + zero_padding: None, width: None, precision: None, ty: Type::Display, @@ -815,6 +950,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((Some('^'), Align::Left)), + sign: Some(Sign::Minus), + alternate: Some(Alternate), + zero_padding: None, width: None, precision: None, ty: Type::Display, @@ -828,6 +967,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: None, + zero_padding: Some(ZeroPadding), width: None, precision: None, ty: Type::Display, @@ -841,6 +984,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: Some(Alternate), + zero_padding: Some(ZeroPadding), width: None, precision: None, ty: Type::Display, @@ -854,6 +1001,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: Some(Sign::Minus), + alternate: None, + zero_padding: Some(ZeroPadding), width: None, precision: None, ty: Type::Display, @@ -867,6 +1018,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((Some('^'), Align::Left)), + sign: None, + alternate: None, + zero_padding: Some(ZeroPadding), width: None, precision: None, ty: Type::Display, @@ -880,6 +1035,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((Some('^'), Align::Left)), + sign: Some(Sign::Plus), + alternate: Some(Alternate), + zero_padding: Some(ZeroPadding), width: None, precision: None, ty: Type::Display, @@ -893,6 +1052,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: None, + zero_padding: None, width: Some(Count::Integer(1)), precision: None, ty: Type::Display, @@ -906,6 +1069,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: None, + zero_padding: None, width: Some(Count::Parameter(Argument::Integer(1))), precision: None, ty: Type::Display, @@ -919,6 +1086,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: None, + zero_padding: None, width: Some(Count::Parameter(Argument::Identifier("par"))), precision: None, ty: Type::Display, @@ -932,6 +1103,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((Some('-'), Align::Center)), + sign: Some(Sign::Minus), + alternate: Some(Alternate), + zero_padding: Some(ZeroPadding), width: Some(Count::Parameter(Argument::Identifier("Минск"))), precision: None, ty: Type::Display, @@ -945,6 +1120,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: None, + zero_padding: None, width: None, precision: Some(Precision::Star), ty: Type::Display, @@ -958,6 +1137,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: None, + zero_padding: None, width: None, precision: Some(Precision::Count(Count::Integer(0))), ty: Type::Display, @@ -971,6 +1154,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: None, + zero_padding: None, width: None, precision: Some(Precision::Count(Count::Parameter( Argument::Integer(0), @@ -986,6 +1173,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: None, + zero_padding: None, width: None, precision: Some(Precision::Count(Count::Parameter( Argument::Identifier("par"), @@ -1001,6 +1192,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((Some(' '), Align::Right)), + sign: Some(Sign::Plus), + alternate: Some(Alternate), + zero_padding: None, width: Some(Count::Parameter(Argument::Integer(2))), precision: Some(Precision::Count(Count::Parameter( Argument::Identifier("par"), @@ -1016,6 +1211,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: None, + zero_padding: None, width: None, precision: None, ty: Type::LowerDebug, @@ -1029,6 +1228,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: None, + sign: None, + alternate: None, + zero_padding: None, width: None, precision: None, ty: Type::UpperExp, @@ -1042,6 +1245,10 @@ mod tests { formats: vec![Format { arg: None, spec: Some(FormatSpec { + align: Some((Some(' '), Align::Right)), + sign: Some(Sign::Plus), + alternate: Some(Alternate), + zero_padding: None, width: Some(Count::Parameter(Argument::Identifier("par"))), precision: Some(Precision::Count(Count::Parameter( Argument::Identifier("par"), @@ -1062,6 +1269,10 @@ mod tests { Format { arg: Some(Argument::Integer(0)), spec: Some(FormatSpec { + align: None, + sign: None, + alternate: Some(Alternate), + zero_padding: None, width: None, precision: None, ty: Type::Debug, @@ -1070,6 +1281,10 @@ mod tests { Format { arg: Some(Argument::Identifier("par")), spec: Some(FormatSpec { + align: Some((Some('-'), Align::Center)), + sign: None, + alternate: None, + zero_padding: None, width: Some(Count::Parameter(Argument::Identifier("par"))), precision: Some(Precision::Count(Count::Parameter( Argument::Identifier("a"), diff --git a/impl/src/parsing.rs b/impl/src/parsing.rs index 98028f7d..ef3fa331 100644 --- a/impl/src/parsing.rs +++ b/impl/src/parsing.rs @@ -12,7 +12,7 @@ use syn::{ }; /// [`syn::Expr`] [`Parse`]ing polyfill. -#[derive(Debug)] +#[derive(Clone, Debug)] pub(crate) enum Expr { /// [`syn::Expr::Path`] of length 1 [`Parse`]ing polyfill. Ident(syn::Ident), @@ -31,6 +31,12 @@ impl Expr { } } +impl From for Expr { + fn from(ident: syn::Ident) -> Self { + Self::Ident(ident) + } +} + impl Parse for Expr { fn parse(input: ParseStream) -> syn::Result { if let Ok(ident) = input.step(|c| { diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 72f11eb4..e15cbd6d 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1520,7 +1520,6 @@ pub(crate) mod attr { #[cfg(any( feature = "as_ref", feature = "debug", - feature = "display", feature = "from", feature = "into", ))] diff --git a/tests/debug.rs b/tests/debug.rs index 28655c17..d2ca8776 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -45,6 +45,122 @@ mod structs { assert_eq!(format!("{Unit:?}"), "Format String"); } } + + mod transparency { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + const I32: i32 = 11; + const F64: f64 = 3.14; + const POINTER: &f64 = &3.14; + + #[derive(Debug)] + #[debug("{I32}")] + struct Display; + + #[derive(Debug)] + #[debug("{I32:?}")] + struct StructDebug; + + #[derive(Debug)] + #[debug("{:b}", I32)] + struct Binary; + + #[derive(Debug)] + #[debug("{0:o}", I32)] + struct Octal; + + #[derive(Debug)] + #[debug("{I32:x}")] + struct LowerHex; + + #[derive(Debug)] + #[debug("{:X}", I32)] + struct UpperHex; + + #[derive(Debug)] + #[debug("{F64:e}")] + struct LowerExp; + + #[derive(Debug)] + #[debug("{named:E}", named = F64)] + struct UpperExp; + + #[derive(Debug)] + #[debug("{POINTER:p}")] + struct Pointer; + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", Display), "011"); + assert_eq!(format!("{:03?}", StructDebug), "011"); + assert_eq!(format!("{:07?}", Binary), "0001011"); + assert_eq!(format!("{:07?}", Octal), "0000013"); + assert_eq!(format!("{:03?}", LowerHex), "00b"); + assert_eq!(format!("{:03?}", UpperHex), "00B"); + assert_eq!(format!("{:07?}", LowerExp), "03.14e0"); + assert_eq!(format!("{:07?}", UpperExp), "03.14E0"); + assert_eq!(format!("{:018?}", Pointer).len(), 18); + } + + mod omitted { + mod on_modifiers { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + const I32: i32 = 11; + const F64: f64 = 3.14; + + #[derive(Debug)] + #[debug("{I32:x?}")] + struct LowerDebug; + + #[derive(Debug)] + #[debug("{I32:X?}")] + struct UpperDebug; + + #[derive(Debug)] + #[debug("{:^}", I32)] + struct Align; + + #[derive(Debug)] + #[debug("{:+}", I32)] + struct Sign; + + #[derive(Debug)] + #[debug("{:#b}", I32)] + struct Alternate; + + #[derive(Debug)] + #[debug("{:0}", I32)] + struct ZeroPadded; + + #[derive(Debug)] + #[debug("{:07}", I32)] + struct Width; + + #[derive(Debug)] + #[debug("{:.1}", F64)] + struct Precision; + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", LowerDebug), "b"); + assert_eq!(format!("{:03?}", UpperDebug), "B"); + assert_eq!(format!("{:03?}", Align), "11"); + assert_eq!(format!("{:04?}", Sign), "+11"); + assert_eq!(format!("{:07?}", Alternate), "0b1011"); + assert_eq!(format!("{:07?}", ZeroPadded), "11"); + assert_eq!(format!("{:03?}", Width), "0000011"); + assert_eq!(format!("{:.3?}", Precision), "3.1"); + } + } + } + } } mod single_field { @@ -155,6 +271,105 @@ mod structs { assert_eq!(format!("{:#?}", Struct { field: 0 }), "Struct { .. }"); } } + + mod transparency { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + #[debug("{_0:?}")] + struct TupleDebug(i32); + + #[derive(Debug)] + #[debug("{}", field)] + struct StructDisplay { + field: i32, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", TupleDebug(7)), "007"); + assert_eq!(format!("{:03?}", StructDisplay { field: 7 }), "007"); + } + + mod suppressed { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + #[debug("{}", format_args!("{_0:?}"))] + struct TupleDebug(i32); + + #[derive(Debug)] + #[debug("{}", format_args!("{}", field))] + struct StructDisplay { + field: i32, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", TupleDebug(7)), "7"); + assert_eq!(format!("{:03?}", StructDisplay { field: 7 }), "7"); + } + } + + mod omitted { + mod on_modifiers { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + #[debug("{_0:x?}")] + struct LowerDebug(i32); + + #[derive(Debug)] + #[debug("{_0:X?}")] + struct UpperDebug(i32); + + #[derive(Debug)] + #[debug("{:^}", _0)] + struct Align(i32); + + #[derive(Debug)] + #[debug("{:+}", _0)] + struct Sign(i32); + + #[derive(Debug)] + #[debug("{:#b}", _0)] + struct Alternate(i32); + + #[derive(Debug)] + #[debug("{:0}", _0)] + struct ZeroPadded(i32); + + #[derive(Debug)] + #[debug("{:07}", _0)] + struct Width(i32); + + #[derive(Debug)] + #[debug("{:.5}", _0)] + struct Precision(f64); + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", LowerDebug(7)), "7"); + assert_eq!(format!("{:03?}", UpperDebug(8)), "8"); + assert_eq!(format!("{:03?}", Align(5)), "5"); + assert_eq!(format!("{:03?}", Sign(5)), "+5"); + assert_eq!(format!("{:07?}", Alternate(5)), "0b101"); + assert_eq!(format!("{:07?}", ZeroPadded(-5)), "-5"); + assert_eq!(format!("{:03?}", Width(5)), "0000005"); + assert_eq!(format!("{:.3?}", Precision(1.23456789)), "1.23457"); + } + } + } + } } mod multi_field { @@ -356,6 +571,33 @@ mod structs { ); } } + + mod transparency { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + #[debug("{0:o}", _0)] + struct TupleOctal(i32, i64); + + #[derive(Debug)] + #[debug("{named:e}", named = b)] + struct StructLowerExp { + a: i32, + b: f64, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", TupleOctal(9, 4)), "011"); + assert_eq!( + format!("{:.1?}", StructLowerExp { a: 7, b: 3.14 }), + "3.1e0", + ); + } + } } } @@ -392,6 +634,96 @@ mod enums { assert_eq!(format!("{:?}", Enum::Named {}), "Named"); assert_eq!(format!("{:#?}", Enum::Named {}), "Named"); } + + mod transparency { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + const I32: i32 = 11; + const F64: f64 = 3.14; + const POINTER: &f64 = &3.14; + + #[derive(Debug)] + enum Unit { + #[debug("{I32}")] + Display, + #[debug("{I32:?}")] + Debug, + #[debug("{:b}", I32)] + Binary, + #[debug("{0:o}", I32)] + Octal, + #[debug("{I32:x}")] + LowerHex, + #[debug("{:X}", I32)] + UpperHex, + #[debug("{F64:e}")] + LowerExp, + #[debug("{named:E}", named = F64)] + UpperExp, + #[debug("{POINTER:p}")] + Pointer, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", Unit::Display), "011"); + assert_eq!(format!("{:03?}", Unit::Debug), "011"); + assert_eq!(format!("{:07?}", Unit::Binary), "0001011"); + assert_eq!(format!("{:07?}", Unit::Octal), "0000013"); + assert_eq!(format!("{:03?}", Unit::LowerHex), "00b"); + assert_eq!(format!("{:03?}", Unit::UpperHex), "00B"); + assert_eq!(format!("{:07?}", Unit::LowerExp), "03.14e0"); + assert_eq!(format!("{:07?}", Unit::UpperExp), "03.14E0"); + assert_eq!(format!("{:018?}", Unit::Pointer).len(), 18); + } + + mod omitted { + mod on_modifiers { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + const I32: i32 = 11; + const F64: f64 = 3.14; + + #[derive(Debug)] + enum Unit { + #[debug("{I32:x?}")] + LowerDebug, + #[debug("{I32:X?}")] + UpperDebug, + #[debug("{:^}", I32)] + Align, + #[debug("{:+}", I32)] + Sign, + #[debug("{:#b}", I32)] + Alternate, + #[debug("{:0}", I32)] + ZeroPadded, + #[debug("{:07}", I32)] + Width, + #[debug("{:.1}", F64)] + Precision, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", Unit::LowerDebug), "b"); + assert_eq!(format!("{:03?}", Unit::UpperDebug), "B"); + assert_eq!(format!("{:03?}", Unit::Align), "11"); + assert_eq!(format!("{:04?}", Unit::Sign), "+11"); + assert_eq!(format!("{:07?}", Unit::Alternate), "0b1011"); + assert_eq!(format!("{:07?}", Unit::ZeroPadded), "11"); + assert_eq!(format!("{:03?}", Unit::Width), "0000011"); + assert_eq!(format!("{:.3?}", Unit::Precision), "3.1"); + } + } + } + } } mod single_field_variant { @@ -481,6 +813,92 @@ mod enums { "SkippedNamed { .. }", ); } + + mod transparency { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + enum Enum { + #[debug("{_0:?}")] + Debug(i32), + #[debug("{}", field)] + Display { field: i32 }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", Enum::Debug(7)), "007"); + assert_eq!(format!("{:03?}", Enum::Display { field: 7 }), "007"); + } + + mod suppressed { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + enum Enum { + #[debug("{}", format_args!("{_0:?}"))] + Debug(i32), + #[debug("{}", format_args!("{}", field))] + Display { field: i32 }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", Enum::Debug(7)), "7"); + assert_eq!(format!("{:03?}", Enum::Display { field: 7 }), "7"); + } + } + + mod omitted { + mod on_modifiers { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + enum Enum { + #[debug("{_0:x?}")] + LowerDebug(i32), + #[debug("{_0:X?}")] + UpperDebug(i32), + #[debug("{:^}", _0)] + Align(i32), + #[debug("{:+}", _0)] + Sign(i32), + #[debug("{:#b}", _0)] + Alternate(i32), + #[debug("{:0}", _0)] + ZeroPadded(i32), + #[debug("{:07}", _0)] + Width(i32), + #[debug("{:.5}", _0)] + Precision(f64), + } + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", Enum::LowerDebug(7)), "7"); + assert_eq!(format!("{:03?}", Enum::UpperDebug(8)), "8"); + assert_eq!(format!("{:03?}", Enum::Align(5)), "5"); + assert_eq!(format!("{:03?}", Enum::Sign(5)), "+5"); + assert_eq!(format!("{:07?}", Enum::Alternate(5)), "0b101"); + assert_eq!(format!("{:07?}", Enum::ZeroPadded(-5)), "-5"); + assert_eq!(format!("{:03?}", Enum::Width(5)), "0000005"); + assert_eq!( + format!("{:.3?}", Enum::Precision(1.23456789)), + "1.23457", + ); + } + } + } + } } mod multi_field_variant { @@ -632,6 +1050,30 @@ mod enums { ); } } + + mod transparency { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + enum Enum { + #[debug("{0:o}", _0)] + TupleOctal(i32, i64), + #[debug("{named:e}", named = b)] + StructLowerExp { a: i32, b: f64 }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", Enum::TupleOctal(9, 4)), "011"); + assert_eq!( + format!("{:.1?}", Enum::StructLowerExp { a: 7, b: 3.14 }), + "3.1e0", + ); + } + } } } @@ -1369,4 +1811,106 @@ mod generic { ); } } + + mod transparency { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + #[debug("{0:o}", _0)] + struct Tuple(T); + + #[derive(Debug)] + #[debug("{named:e}", named = b)] + struct Struct { + a: A, + b: B, + } + + #[derive(Debug)] + enum Enum { + #[debug("{_0:?}")] + Debug(A), + #[debug("{}", c)] + Display { b: B, c: C }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03?}", Tuple(9)), "011"); + assert_eq!(format!("{:.1?}", Struct { a: 9, b: 3.14 }), "3.1e0"); + assert_eq!(format!("{:03?}", Enum::<_, u8, u8>::Debug(7)), "007"); + assert_eq!( + format!("{:03?}", Enum::::Display { b: 7, c: 8 }), + "008", + ); + } + + mod omitted { + mod on_modifiers { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + enum Enum { + #[debug("{_0:x?}")] + LowerDebug(A), + #[debug("{_0:X?}")] + UpperDebug(B), + #[debug("{:^}", _0)] + Align(C), + #[debug("{:+}", _0)] + Sign(C), + #[debug("{:#b}", _0)] + Alternate(C), + #[debug("{:0}", _0)] + ZeroPadded(C), + #[debug("{:07}", _0)] + Width(C), + #[debug("{:.5}", _0)] + Precision(D), + } + + #[test] + fn assert() { + assert_eq!( + format!("{:03?}", Enum::<_, u8, u8, f64>::LowerDebug(7)), + "7", + ); + assert_eq!( + format!("{:03?}", Enum::::UpperDebug(8)), + "8", + ); + assert_eq!( + format!("{:03?}", Enum::::Align(5)), + "5", + ); + assert_eq!( + format!("{:03?}", Enum::::Sign(5)), + "+5", + ); + assert_eq!( + format!("{:07?}", Enum::::Alternate(5)), + "0b101", + ); + assert_eq!( + format!("{:07?}", Enum::::ZeroPadded(-5)), + "-5", + ); + assert_eq!( + format!("{:03?}", Enum::::Width(5)), + "0000005", + ); + assert_eq!( + format!("{:.3?}", Enum::::Precision(1.23456789)), + "1.23457", + ); + } + } + } + } } diff --git a/tests/display.rs b/tests/display.rs index 3ede71b8..a00aaaf8 100644 --- a/tests/display.rs +++ b/tests/display.rs @@ -12,7 +12,9 @@ use alloc::{ vec::Vec, }; -use derive_more::{Binary, Display, Octal, UpperHex}; +use derive_more::{ + Binary, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex, +}; mod structs { use super::*; @@ -81,6 +83,122 @@ mod structs { assert_eq!(Struct {}.to_string(), "struct: 0"); } } + + mod transparency { + use super::*; + + mod interpolated { + use super::*; + + const I32: i32 = 11; + const F64: f64 = 3.14; + const POINTER: &f64 = &3.14; + + #[derive(Display)] + #[display("{I32}")] + struct Display; + + #[derive(Display)] + #[display("{I32:?}")] + struct Debug; + + #[derive(Display)] + #[display("{:b}", I32)] + struct Binary; + + #[derive(Display)] + #[display("{0:o}", I32)] + struct Octal; + + #[derive(Display)] + #[display("{I32:x}")] + struct LowerHex; + + #[derive(Display)] + #[display("{:X}", I32)] + struct UpperHex; + + #[derive(Display)] + #[display("{F64:e}")] + struct LowerExp; + + #[derive(Display)] + #[display("{named:E}", named = F64)] + struct UpperExp; + + #[derive(Display)] + #[display("{POINTER:p}")] + struct Pointer; + + #[test] + fn assert() { + assert_eq!(format!("{:03}", Display), "011"); + assert_eq!(format!("{:03}", Debug), "011"); + assert_eq!(format!("{:07}", Binary), "0001011"); + assert_eq!(format!("{:07}", Octal), "0000013"); + assert_eq!(format!("{:03}", LowerHex), "00b"); + assert_eq!(format!("{:03}", UpperHex), "00B"); + assert_eq!(format!("{:07}", LowerExp), "03.14e0"); + assert_eq!(format!("{:07}", UpperExp), "03.14E0"); + assert_eq!(format!("{:018}", Pointer).len(), 18); + } + } + + mod omitted { + use super::*; + + mod on_modifiers { + use super::*; + + const I32: i32 = 11; + const F64: f64 = 3.14; + + #[derive(Display)] + #[display("{I32:x?}")] + struct LowerDebug; + + #[derive(Display)] + #[display("{I32:X?}")] + struct UpperDebug; + + #[derive(Display)] + #[display("{:^}", I32)] + struct Align; + + #[derive(Display)] + #[display("{:+}", I32)] + struct Sign; + + #[derive(Display)] + #[display("{:#b}", I32)] + struct Alternate; + + #[derive(Display)] + #[display("{:0}", I32)] + struct ZeroPadded; + + #[derive(Display)] + #[display("{:07}", I32)] + struct Width; + + #[derive(Display)] + #[display("{:.1}", F64)] + struct Precision; + + #[test] + fn assert() { + assert_eq!(format!("{:03}", LowerDebug), "b"); + assert_eq!(format!("{:03}", UpperDebug), "B"); + assert_eq!(format!("{:03}", Align), "11"); + assert_eq!(format!("{:04}", Sign), "+11"); + assert_eq!(format!("{:07}", Alternate), "0b1011"); + assert_eq!(format!("{:07}", ZeroPadded), "11"); + assert_eq!(format!("{:03}", Width), "0000011"); + assert_eq!(format!("{:.3}", Precision), "3.1"); + } + } + } + } } mod single_field { @@ -149,6 +267,265 @@ mod structs { assert_eq!(Struct { field: 0 }.to_string(), "struct: 0 0"); } } + + mod transparency { + use super::*; + + mod direct { + use super::*; + + #[derive(Display)] + struct TupleDisplay(i32); + + #[derive(Binary)] + struct TupleBinary(i32); + + #[derive(Octal)] + struct TupleOctal(i32); + + #[derive(LowerHex)] + struct StructLowerHex { + field: i32, + } + + #[derive(UpperHex)] + struct StructUpperHex { + field: i32, + } + + #[derive(LowerExp)] + struct StructLowerExp { + field: f64, + } + + #[derive(UpperExp)] + struct StructUpperExp { + field: f64, + } + + #[derive(Pointer)] + struct StructPointer<'a> { + field: &'a i32, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", TupleDisplay(7)), "007"); + assert_eq!(format!("{:07b}", TupleBinary(7)), "0000111"); + assert_eq!(format!("{:03o}", TupleOctal(9)), "011"); + assert_eq!(format!("{:03x}", StructLowerHex { field: 42 }), "02a"); + assert_eq!(format!("{:03X}", StructUpperHex { field: 42 }), "02A"); + assert_eq!( + format!("{:07e}", StructLowerExp { field: 42.0 }), + "004.2e1", + ); + assert_eq!( + format!("{:07E}", StructUpperExp { field: 42.0 }), + "004.2E1", + ); + assert_eq!( + format!("{:018p}", StructPointer { field: &42 }).len(), + 18, + ); + } + } + + mod interpolated { + use super::*; + + #[derive(Display)] + #[display("{_0}")] + struct TupleDisplay(i32); + + #[derive(Display)] + #[display("{_0:?}")] + struct TupleDebug(i32); + + #[derive(Display)] + #[display("{:b}", _0)] + struct TupleBinary(i32); + + #[derive(Display)] + #[display("{0:o}", _0)] + struct TupleOctal(i32); + + #[derive(Display)] + #[display("{field:x}")] + struct StructLowerHex { + field: i32, + } + + #[derive(Display)] + #[display("{:X}", field)] + struct StructUpperHex { + field: i32, + } + + #[derive(Display)] + #[display("{field:e}")] + struct StructLowerExp { + field: f64, + } + + #[derive(Display)] + #[display("{named:E}", named = field)] + struct StructUpperExp { + field: f64, + } + + #[derive(Display)] + #[display("{field:p}")] + struct StructPointer<'a> { + field: &'a i32, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", TupleDisplay(7)), "007"); + assert_eq!(format!("{:03}", TupleDebug(8)), "008"); + assert_eq!(format!("{:07}", TupleBinary(7)), "0000111"); + assert_eq!(format!("{:03}", TupleOctal(9)), "011"); + assert_eq!(format!("{:03}", StructLowerHex { field: 42 }), "02a"); + assert_eq!(format!("{:03}", StructUpperHex { field: 42 }), "02A"); + assert_eq!( + format!("{:07}", StructLowerExp { field: 42.0 }), + "004.2e1", + ); + assert_eq!( + format!("{:07}", StructUpperExp { field: 42.0 }), + "004.2E1", + ); + assert_eq!( + format!("{:018}", StructPointer { field: &42 }).len(), + 18, + ); + } + } + + mod suppressed { + use super::*; + + #[derive(Display)] + #[display("{}", format_args!("{_0}"))] + struct TupleDisplay(i32); + + #[derive(Display)] + #[display("{}", format_args!("{_0:?}"))] + struct TupleDebug(i32); + + #[derive(Display)] + #[display("{}", format_args!("{_0:b}"))] + struct TupleBinary(i32); + + #[derive(Display)] + #[display("{}", format_args!("{_0:o}"))] + struct TupleOctal(i32); + + #[derive(Display)] + #[display("{}", format_args!("{field:x}"))] + struct StructLowerHex { + field: i32, + } + + #[derive(Display)] + #[display("{}", format_args!("{field:X}"))] + struct StructUpperHex { + field: i32, + } + + #[derive(Display)] + #[display("{}", format_args!("{field:e}"))] + struct StructLowerExp { + field: f64, + } + + #[derive(Display)] + #[display("{}", format_args!("{field:E}"))] + struct StructUpperExp { + field: f64, + } + + #[derive(Display)] + #[display("{}", format_args!("{field:p}"))] + struct StructPointer<'a> { + field: &'a i32, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", TupleDisplay(7)), "7"); + assert_eq!(format!("{:03}", TupleDebug(8)), "8"); + assert_eq!(format!("{:07}", TupleBinary(7)), "111"); + assert_eq!(format!("{:03}", TupleOctal(9)), "11"); + assert_eq!(format!("{:03}", StructLowerHex { field: 42 }), "2a"); + assert_eq!(format!("{:03}", StructUpperHex { field: 42 }), "2A"); + assert_eq!( + format!("{:07}", StructLowerExp { field: 42.0 }), + "4.2e1", + ); + assert_eq!( + format!("{:07}", StructUpperExp { field: 42.0 }), + "4.2E1", + ); + assert_ne!( + format!("{:018}", StructPointer { field: &42 }).len(), + 18, + ); + } + } + + mod omitted { + use super::*; + + mod on_modifiers { + use super::*; + + #[derive(Display)] + #[display("{_0:x?}")] + struct LowerDebug(i32); + + #[derive(Display)] + #[display("{_0:X?}")] + struct UpperDebug(i32); + + #[derive(Display)] + #[display("{:^}", _0)] + struct Align(i32); + + #[derive(Display)] + #[display("{:+}", _0)] + struct Sign(i32); + + #[derive(Display)] + #[display("{:#b}", _0)] + struct Alternate(i32); + + #[derive(Display)] + #[display("{:0}", _0)] + struct ZeroPadded(i32); + + #[derive(Display)] + #[display("{:07}", _0)] + struct Width(i32); + + #[derive(Display)] + #[display("{:.5}", _0)] + struct Precision(f64); + + #[test] + fn assert() { + assert_eq!(format!("{:03}", LowerDebug(7)), "7"); + assert_eq!(format!("{:03}", UpperDebug(8)), "8"); + assert_eq!(format!("{:03}", Align(5)), "5"); + assert_eq!(format!("{:03}", Sign(5)), "+5"); + assert_eq!(format!("{:07}", Alternate(5)), "0b101"); + assert_eq!(format!("{:07}", ZeroPadded(-5)), "-5"); + assert_eq!(format!("{:03}", Width(5)), "0000005"); + assert_eq!(format!("{:.3}", Precision(1.23456789)), "1.23457"); + } + } + } + } } mod multi_field { @@ -187,15 +564,15 @@ mod structs { #[derive(Display)] #[display( - "{_0} {ident} {_1} {} {}", - _1, _0 + _1, ident = 123, _1 = _0, + "{_0} {ident} {_1} {} {}", + _1, _0 + _1, ident = 123, _1 = _0, )] struct Tuple(i32, i32); #[derive(Display)] #[display( - "{field1} {ident} {field2} {} {}", - field2, field1 + field2, ident = 123, field2 = field1, + "{field1} {ident} {field2} {} {}", + field2, field1 + field2, ident = 123, field2 = field1, )] struct Struct { field1: i32, @@ -215,6 +592,93 @@ mod structs { ); } } + + mod transparency { + use super::*; + + mod interpolated { + use super::*; + + #[derive(Display)] + #[display("{_0}")] + struct TupleDisplay(i32, u64); + + #[derive(Display)] + #[display("{:?}", _1)] + struct TupleDebug(i32, u64); + + #[derive(Display)] + #[display("{0:b}", _1)] + struct TupleBinary(i32, u64); + + #[derive(Display)] + #[display("{named:o}", named = _0)] + struct TupleOctal(i32, u64); + + #[derive(Display)] + #[display("{b:x}")] + struct StructLowerHex { + a: i32, + b: u64, + } + + #[derive(Display)] + #[display("{:X}", a)] + struct StructUpperHex { + a: i32, + b: u64, + } + + #[derive(Display)] + #[display("{a:e}")] + struct StructLowerExp { + a: f64, + b: f32, + } + + #[derive(Display)] + #[display("{:E}", b)] + struct StructUpperExp { + a: f64, + b: f32, + } + + #[derive(Display)] + #[display("{b:p}")] + struct StructPointer<'a> { + a: &'a i32, + b: &'a i32, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", TupleDisplay(7, 8)), "007"); + assert_eq!(format!("{:03}", TupleDebug(7, 8)), "008"); + assert_eq!(format!("{:07}", TupleBinary(6, 7)), "0000111"); + assert_eq!(format!("{:03}", TupleOctal(9, 10)), "011"); + assert_eq!( + format!("{:03}", StructLowerHex { a: 41, b: 42 }), + "02a" + ); + assert_eq!( + format!("{:03}", StructUpperHex { a: 42, b: 43 }), + "02A" + ); + assert_eq!( + format!("{:07}", StructLowerExp { a: 42.0, b: 43.0 }), + "004.2e1", + ); + assert_eq!( + format!("{:07}", StructUpperExp { a: 41.0, b: 42.0 }), + "004.2E1", + ); + assert_eq!( + format!("{:018}", StructPointer { a: &42, b: &43 }).len(), + 18, + ); + } + } + } } } @@ -256,6 +720,96 @@ mod enums { assert_eq!(Enum::StrUnnamed().to_string(), "STR_UNNAMED"); assert_eq!(Enum::StrNamed {}.to_string(), "STR_NAMED"); } + + mod transparency { + use super::*; + + mod interpolated { + use super::*; + + const I32: i32 = 11; + const F64: f64 = 3.14; + const POINTER: &f64 = &3.14; + + #[derive(Display)] + enum Unit { + #[display("{I32}")] + Display, + #[display("{I32:?}")] + Debug, + #[display("{:b}", I32)] + Binary, + #[display("{0:o}", I32)] + Octal, + #[display("{I32:x}")] + LowerHex, + #[display("{:X}", I32)] + UpperHex, + #[display("{F64:e}")] + LowerExp, + #[display("{named:E}", named = F64)] + UpperExp, + #[display("{POINTER:p}")] + Pointer, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", Unit::Display), "011"); + assert_eq!(format!("{:03}", Unit::Debug), "011"); + assert_eq!(format!("{:07}", Unit::Binary), "0001011"); + assert_eq!(format!("{:07}", Unit::Octal), "0000013"); + assert_eq!(format!("{:03}", Unit::LowerHex), "00b"); + assert_eq!(format!("{:03}", Unit::UpperHex), "00B"); + assert_eq!(format!("{:07}", Unit::LowerExp), "03.14e0"); + assert_eq!(format!("{:07}", Unit::UpperExp), "03.14E0"); + assert_eq!(format!("{:018}", Unit::Pointer).len(), 18); + } + } + + mod omitted { + use super::*; + + mod on_modifiers { + use super::*; + + const I32: i32 = 11; + const F64: f64 = 3.14; + + #[derive(Display)] + enum Unit { + #[display("{I32:x?}")] + LowerDebug, + #[display("{I32:X?}")] + UpperDebug, + #[display("{:^}", I32)] + Align, + #[display("{:+}", I32)] + Sign, + #[display("{:#b}", I32)] + Alternate, + #[display("{:0}", I32)] + ZeroPadded, + #[display("{:07}", I32)] + Width, + #[display("{:.1}", F64)] + Precision, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", Unit::LowerDebug), "b"); + assert_eq!(format!("{:03}", Unit::UpperDebug), "B"); + assert_eq!(format!("{:03}", Unit::Align), "11"); + assert_eq!(format!("{:04}", Unit::Sign), "+11"); + assert_eq!(format!("{:07}", Unit::Alternate), "0b1011"); + assert_eq!(format!("{:07}", Unit::ZeroPadded), "11"); + assert_eq!(format!("{:03}", Unit::Width), "0000011"); + assert_eq!(format!("{:.3}", Unit::Precision), "3.1"); + } + } + } + } } mod single_field_variant { @@ -290,6 +844,290 @@ mod enums { assert_eq!(Enum::InterpolatedUnnamed(1).to_string(), "1 1"); assert_eq!(Enum::InterpolatedNamed { field: 1 }.to_string(), "1 1"); } + + mod transparency { + use super::*; + + mod direct { + use super::*; + + #[derive(Display)] + enum Display { + A(i32), + B { field: u8 }, + } + + #[derive(Binary)] + enum Binary { + A(i32), + B { field: u8 }, + } + + #[derive(Octal)] + enum Octal { + A(i32), + B { field: u8 }, + } + + #[derive(LowerHex)] + enum LowerHex { + A(i32), + B { field: u8 }, + } + + #[derive(UpperHex)] + enum UpperHex { + A(i32), + B { field: u8 }, + } + + #[derive(LowerExp)] + enum LowerExp { + A(f64), + B { field: f32 }, + } + + #[derive(UpperExp)] + enum UpperExp { + A(f64), + B { field: f32 }, + } + + #[derive(Pointer)] + enum Pointer<'a> { + A(&'a i32), + B { field: &'a u8 }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", Display::A(7)), "007"); + assert_eq!(format!("{:03}", Display::B { field: 8 }), "008"); + assert_eq!(format!("{:07b}", Binary::A(7)), "0000111"); + assert_eq!(format!("{:07b}", Binary::B { field: 8 }), "0001000"); + assert_eq!(format!("{:03o}", Octal::A(9)), "011"); + assert_eq!(format!("{:03o}", Octal::B { field: 10 }), "012"); + assert_eq!(format!("{:03x}", LowerHex::A(42)), "02a"); + assert_eq!(format!("{:03x}", LowerHex::B { field: 43 }), "02b"); + assert_eq!(format!("{:03X}", UpperHex::A(42)), "02A"); + assert_eq!(format!("{:03X}", UpperHex::B { field: 43 }), "02B"); + assert_eq!(format!("{:07e}", LowerExp::A(42.0)), "004.2e1"); + assert_eq!( + format!("{:07e}", LowerExp::B { field: 43.0 }), + "004.3e1", + ); + assert_eq!(format!("{:07E}", UpperExp::A(42.0)), "004.2E1"); + assert_eq!( + 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); + } + } + + mod interpolated { + use super::*; + + #[derive(Display)] + enum Display { + #[display("{_0}")] + A(i32), + #[display("{}", field)] + B { field: u8 }, + } + + #[derive(Display)] + enum Debug { + #[display("{0:?}", _0)] + A(i32), + #[display("{named:?}", named = field)] + B { field: u8 }, + } + + #[derive(Display)] + enum Binary { + #[display("{_0:b}")] + A(i32), + #[display("{:b}", field)] + B { field: u8 }, + } + + #[derive(Display)] + enum Octal { + #[display("{_0:o}")] + A(i32), + #[display("{:o}", field)] + B { field: u8 }, + } + + #[derive(Display)] + enum LowerHex { + #[display("{_0:x}")] + A(i32), + #[display("{:x}", field)] + B { field: u8 }, + } + + #[derive(Display)] + enum UpperHex { + #[display("{_0:X}")] + A(i32), + #[display("{:X}", field)] + B { field: u8 }, + } + + #[derive(Display)] + enum LowerExp { + #[display("{:e}", _0)] + A(f64), + #[display("{field:e}")] + B { field: f32 }, + } + + #[derive(Display)] + enum UpperExp { + #[display("{:E}", _0)] + A(f64), + #[display("{field:E}")] + B { field: f32 }, + } + + #[derive(Display)] + enum Pointer<'a> { + #[display("{:p}", _0)] + A(&'a i32), + #[display("{field:p}")] + B { field: &'a u8 }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", Display::A(7)), "007"); + assert_eq!(format!("{:03}", Display::B { field: 8 }), "008"); + assert_eq!(format!("{:03}", Debug::A(8)), "008"); + assert_eq!(format!("{:03}", Debug::B { field: 9 }), "009"); + assert_eq!(format!("{:07}", Binary::A(7)), "0000111"); + assert_eq!(format!("{:07}", Binary::B { field: 8 }), "0001000"); + assert_eq!(format!("{:03}", Octal::A(9)), "011"); + assert_eq!(format!("{:03}", Octal::B { field: 10 }), "012"); + assert_eq!(format!("{:03}", LowerHex::A(42)), "02a"); + assert_eq!(format!("{:03}", LowerHex::B { field: 43 }), "02b"); + assert_eq!(format!("{:03}", UpperHex::A(42)), "02A"); + assert_eq!(format!("{:03}", UpperHex::B { field: 43 }), "02B"); + assert_eq!(format!("{:07}", LowerExp::A(42.0)), "004.2e1"); + assert_eq!( + format!("{:07}", LowerExp::B { field: 43.0 }), + "004.3e1", + ); + assert_eq!(format!("{:07}", UpperExp::A(42.0)), "004.2E1"); + assert_eq!( + 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); + } + } + + mod suppressed { + use super::*; + + #[derive(Display)] + enum Display { + #[display("{}", format_args!("{_0}"))] + A(i32), + #[display("{}", format_args!("{}", field))] + B { field: u8 }, + } + + #[derive(Display)] + enum Debug { + #[display("{}", format_args!("{_0:?}"))] + A(i32), + #[display("{}", format_args!("{:?}", field))] + B { field: u8 }, + } + + #[derive(Display)] + enum Binary { + #[display("{}", format_args!("{_0:b}"))] + A(i32), + #[display("{}", format_args!("{:b}", field))] + B { field: u8 }, + } + + #[derive(Display)] + enum Octal { + #[display("{}", format_args!("{_0:o}"))] + A(i32), + #[display("{}", format_args!("{:o}", field))] + B { field: u8 }, + } + + #[derive(Display)] + enum LowerHex { + #[display("{}", format_args!("{_0:x}"))] + A(i32), + #[display("{}", format_args!("{:x}", field))] + B { field: u8 }, + } + + #[derive(Display)] + enum UpperHex { + #[display("{}", format_args!("{_0:X}"))] + A(i32), + #[display("{}", format_args!("{:X}", field))] + B { field: u8 }, + } + + #[derive(Display)] + enum LowerExp { + #[display("{}", format_args!("{:e}", _0))] + A(f64), + #[display("{}", format_args!("{field:e}"))] + B { field: f32 }, + } + + #[derive(Display)] + enum UpperExp { + #[display("{}", format_args!("{:E}", _0))] + A(f64), + #[display("{}", format_args!("{field:E}"))] + B { field: f32 }, + } + + #[derive(Display)] + enum Pointer<'a> { + #[display("{}", format_args!("{:p}", _0))] + A(&'a i32), + #[display("{}", format_args!("{field:p}"))] + B { field: &'a u8 }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", Display::A(7)), "7"); + assert_eq!(format!("{:03}", Display::B { field: 8 }), "8"); + assert_eq!(format!("{:03}", Debug::A(8)), "8"); + assert_eq!(format!("{:03}", Debug::B { field: 9 }), "9"); + assert_eq!(format!("{:07}", Binary::A(7)), "111"); + assert_eq!(format!("{:07}", Binary::B { field: 8 }), "1000"); + assert_eq!(format!("{:03}", Octal::A(9)), "11"); + assert_eq!(format!("{:03}", Octal::B { field: 10 }), "12"); + assert_eq!(format!("{:03}", LowerHex::A(42)), "2a"); + assert_eq!(format!("{:03}", LowerHex::B { field: 43 }), "2b"); + assert_eq!(format!("{:03}", UpperHex::A(42)), "2A"); + assert_eq!(format!("{:03}", UpperHex::B { field: 43 }), "2B"); + assert_eq!(format!("{:07}", LowerExp::A(42.0)), "4.2e1"); + 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); + } + } + } } mod multi_field_variant { @@ -302,13 +1140,13 @@ mod enums { #[display("named")] StrNamed { field1: i32, field2: i32 }, #[display( - "{_0} {ident} {_1} {} {}", - _1, _0 + _1, ident = 123, _1 = _0, + "{_0} {ident} {_1} {} {}", + _1, _0 + _1, ident = 123, _1 = _0, )] InterpolatedUnnamed(i32, i32), #[display( - "{field1} {ident} {field2} {} {}", - field2, field1 + field2, ident = 123, field2 = field1, + "{field1} {ident} {field2} {} {}", + field2, field1 + field2, ident = 123, field2 = field1, )] InterpolatedNamed { field1: i32, field2: i32 }, } @@ -334,6 +1172,117 @@ mod enums { "1 123 1 2 3", ); } + + mod transparency { + use super::*; + + mod interpolated { + use super::*; + + #[derive(Display)] + enum Display { + #[display("{_0}")] + A(i32, i64), + #[display("{}", b)] + B { a: u8, b: i32 }, + } + + #[derive(Display)] + enum Debug { + #[display("{0:?}", _1)] + A(i32, i64), + #[display("{named:?}", named = a)] + B { a: u8, b: i32 }, + } + + #[derive(Display)] + enum Binary { + #[display("{_0:b}")] + A(i32, i64), + #[display("{:b}", b)] + B { a: u8, b: i32 }, + } + + #[derive(Display)] + enum Octal { + #[display("{_0:o}")] + A(i32, i64), + #[display("{:o}", b)] + B { a: u8, b: i32 }, + } + + #[derive(Display)] + enum LowerHex { + #[display("{_0:x}")] + A(i32, i64), + #[display("{:x}", b)] + B { a: u8, b: i32 }, + } + + #[derive(Display)] + enum UpperHex { + #[display("{_0:X}")] + A(i32, i64), + #[display("{:X}", b)] + B { a: u8, b: i32 }, + } + + #[derive(Display)] + enum LowerExp { + #[display("{:e}", _1)] + A(f64, f32), + #[display("{a:e}")] + B { a: f64, b: f32 }, + } + + #[derive(Display)] + enum UpperExp { + #[display("{:E}", _1)] + A(f64, f32), + #[display("{a:E}")] + B { a: f64, b: f32 }, + } + + #[derive(Display)] + enum Pointer<'a> { + #[display("{:p}", _1)] + A(&'a f64, &'a f32), + #[display("{a:p}")] + B { a: &'a f64, b: &'a f32 }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", Display::A(7, 8)), "007"); + assert_eq!(format!("{:03}", Display::B { a: 7, b: 8 }), "008"); + assert_eq!(format!("{:03}", Debug::A(7, 8)), "008"); + assert_eq!(format!("{:03}", Debug::B { a: 7, b: 8 }), "007"); + assert_eq!(format!("{:07}", Binary::A(7, 8)), "0000111"); + assert_eq!(format!("{:07}", Binary::B { a: 7, b: 8 }), "0001000"); + assert_eq!(format!("{:03}", Octal::A(9, 10)), "011"); + assert_eq!(format!("{:03}", Octal::B { a: 9, b: 10 }), "012"); + assert_eq!(format!("{:03}", LowerHex::A(42, 41)), "02a"); + assert_eq!(format!("{:03}", LowerHex::B { a: 42, b: 43 }), "02b"); + assert_eq!(format!("{:03}", UpperHex::A(42, 41)), "02A"); + assert_eq!(format!("{:03}", UpperHex::B { a: 42, b: 43 }), "02B"); + assert_eq!(format!("{:07}", LowerExp::A(41.0, 42.0)), "004.2e1"); + assert_eq!( + format!("{:07}", LowerExp::B { a: 43.0, b: 52.0 }), + "004.3e1", + ); + assert_eq!(format!("{:07}", UpperExp::A(41.0, 42.0)), "004.2E1"); + assert_eq!( + format!("{:07}", UpperExp::B { a: 43.0, b: 52.0 }), + "004.3E1", + ); + assert_eq!(format!("{:018}", Pointer::A(&7.0, &8.3)).len(), 18); + assert_eq!( + format!("{:018}", Pointer::B { a: &42.1, b: &43.3 }).len(), + 18, + ); + } + } + } } } @@ -857,4 +1806,356 @@ mod generic { assert_eq!(s.to_string(), "no display"); } } + + mod transparency { + use super::*; + + mod direct { + use super::*; + + #[derive( + Display, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer + )] + struct Tuple(T); + + #[derive( + Display, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer + )] + struct Struct { + field: T, + } + + #[derive( + Display, Binary, Octal, LowerHex, UpperHex, LowerExp, UpperExp, Pointer + )] + enum Enum { + A(A), + B { field: B }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", Tuple(7)), "007"); + assert_eq!(format!("{:03}", Struct { field: 7 }), "007"); + assert_eq!(format!("{:03}", Enum::<_, i8>::A(7)), "007"); + assert_eq!(format!("{:03}", Enum::::B { field: 8 }), "008"); + assert_eq!(format!("{:07b}", Tuple(7)), "0000111"); + assert_eq!(format!("{:07b}", Struct { field: 7 }), "0000111"); + assert_eq!(format!("{:07b}", Enum::<_, i8>::A(7)), "0000111"); + assert_eq!(format!("{:07b}", Enum::::B { field: 8 }), "0001000"); + assert_eq!(format!("{:03o}", Tuple(9)), "011"); + assert_eq!(format!("{:03o}", Struct { field: 9 }), "011"); + assert_eq!(format!("{:03o}", Enum::<_, i8>::A(9)), "011"); + assert_eq!(format!("{:03o}", Enum::::B { field: 10 }), "012"); + assert_eq!(format!("{:03x}", Tuple(42)), "02a"); + assert_eq!(format!("{:03x}", Struct { field: 42 }), "02a"); + assert_eq!(format!("{:03x}", Enum::<_, i8>::A(42)), "02a"); + assert_eq!(format!("{:03x}", Enum::::B { field: 43 }), "02b"); + assert_eq!(format!("{:03X}", Tuple(42)), "02A"); + assert_eq!(format!("{:03X}", Struct { field: 42 }), "02A"); + assert_eq!(format!("{:03X}", Enum::<_, i8>::A(42)), "02A"); + assert_eq!(format!("{:03X}", Enum::::B { field: 43 }), "02B"); + assert_eq!(format!("{:07e}", Tuple(42.0)), "004.2e1"); + assert_eq!(format!("{:07e}", Struct { field: 42.0 }), "004.2e1"); + assert_eq!(format!("{:07e}", Enum::<_, i8>::A(42.0)), "004.2e1"); + assert_eq!( + format!("{:07e}", Enum::::B { field: 43.0 }), + "004.3e1", + ); + assert_eq!(format!("{:07E}", Tuple(42.0)), "004.2E1"); + assert_eq!(format!("{:07E}", Struct { field: 42.0 }), "004.2E1"); + assert_eq!(format!("{:07E}", Enum::<_, i8>::A(42.0)), "004.2E1"); + assert_eq!( + 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); + assert_eq!( + format!("{:018p}", Enum::<&i8, _>::B { field: &42 }).len(), + 18, + ); + } + } + + mod interpolated { + use super::*; + + #[derive(Display)] + #[display("{_0}")] + struct TupleDisplay(T); + + #[derive(Display)] + #[display("{0:?}", _0)] + struct TupleDebug(T); + + #[derive(Display)] + #[display("{_0:b}")] + struct TupleBinary(T, Y); + + #[derive(Display)] + #[display("{_1:o}")] + struct TupleOctal(Y, T); + + #[derive(Display)] + #[display("{0:x}", _0)] + struct TupleLowerHex(T); + + #[derive(Display)] + #[display("{_0:X}")] + struct TupleUpperHex(T); + + #[derive(Display)] + #[display("{:e}", _0)] + struct TupleLowerExp(T); + + #[derive(Display)] + #[display("{:E}", _0)] + struct TupleUpperExp(T); + + #[derive(Display)] + #[display("{:p}", _0)] + struct TuplePointer(T); + + #[derive(Display)] + #[display("{field}")] + struct StructDisplay { + field: T, + } + + #[derive(Display)] + #[display("{a:b}")] + struct StructBinary { + a: T, + b: Y, + } + + #[derive(Display)] + #[display("{named:o}", named = b)] + struct StructOctal { + a: Y, + b: T, + } + + #[derive(Display)] + #[display("{field:x}")] + struct StructLowerHex { + field: T, + } + + #[derive(Display)] + #[display("{field:X}")] + struct StructUpperHex { + field: T, + } + + #[derive(Display)] + #[display("{:e}", field)] + struct StructLowerExp { + field: T, + } + + #[derive(Display)] + #[display("{:E}", field)] + struct StructUpperExp { + field: T, + } + + #[derive(Display)] + #[display("{:p}", field)] + struct StructPointer { + field: T, + } + + #[derive(Display)] + enum EnumDisplay { + #[display("{_0}")] + A(A), + #[display("{}", field)] + B { field: B }, + } + + #[derive(Display)] + enum EnumBinary { + #[display("{_0:b}")] + A(A, C), + #[display("{:b}", b)] + B { b: B, d: D }, + } + + #[derive(Display)] + enum EnumOctal { + #[display("{_1:o}")] + A(A, C), + #[display("{:o}", d)] + B { b: B, d: D }, + } + + #[derive(Display)] + enum EnumLowerHex { + #[display("{_0:x}")] + A(A), + #[display("{:x}", field)] + B { field: B }, + } + + #[derive(Display)] + enum EnumUpperHex { + #[display("{_0:X}")] + A(A), + #[display("{:X}", field)] + B { field: B }, + } + + #[derive(Display)] + enum EnumLowerExp { + #[display("{:e}", _0)] + A(A), + #[display("{field:e}")] + B { field: B }, + } + + #[derive(Display)] + enum EnumUpperExp { + #[display("{:E}", _0)] + A(A), + #[display("{field:E}")] + B { field: B }, + } + + #[derive(Display)] + enum EnumPointer { + #[display("{:p}", _0)] + A(A), + #[display("{field:p}")] + B { field: B }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:03}", TupleDisplay(7)), "007"); + assert_eq!(format!("{:03}", TupleDebug(8)), "008"); + assert_eq!(format!("{:03}", StructDisplay { field: 7 }), "007"); + assert_eq!(format!("{:03}", EnumDisplay::<_, i8>::A(7)), "007"); + assert_eq!( + format!("{:03}", EnumDisplay::::B { field: 8 }), + "008", + ); + assert_eq!(format!("{:07}", TupleBinary(7, ())), "0000111"); + assert_eq!(format!("{:07}", StructBinary { a: 7, b: () }), "0000111"); + assert_eq!( + format!("{:07}", EnumBinary::<_, i8, _, ()>::A(7, ())), + "0000111", + ); + assert_eq!( + format!("{:07}", EnumBinary::::B { b: 8, d: () }), + "0001000", + ); + assert_eq!(format!("{:03}", TupleOctal((), 9)), "011"); + assert_eq!(format!("{:03}", StructOctal { a: (), b: 9 }), "011"); + assert_eq!( + format!("{:03}", EnumOctal::<_, (), _, i8>::A((), 9)), + "011", + ); + assert_eq!( + format!("{:03}", EnumOctal::<(), _, i8, _>::B { b: (), d: 10 }), + "012", + ); + assert_eq!(format!("{:03}", TupleLowerHex(42)), "02a"); + assert_eq!(format!("{:03}", StructLowerHex { field: 42 }), "02a"); + assert_eq!(format!("{:03}", EnumLowerHex::<_, i8>::A(42)), "02a"); + assert_eq!( + format!("{:03}", EnumLowerHex::::B { field: 43 }), + "02b", + ); + assert_eq!(format!("{:03}", TupleUpperHex(42)), "02A"); + assert_eq!(format!("{:03}", StructUpperHex { field: 42 }), "02A"); + assert_eq!(format!("{:03}", EnumUpperHex::<_, i8>::A(42)), "02A"); + assert_eq!( + format!("{:03}", EnumUpperHex::::B { field: 43 }), + "02B", + ); + assert_eq!(format!("{:07}", TupleLowerExp(42.0)), "004.2e1"); + assert_eq!(format!("{:07}", StructLowerExp { field: 42.0 }), "004.2e1"); + 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}", TupleUpperExp(42.0)), "004.2E1"); + assert_eq!(format!("{:07}", StructUpperExp { field: 42.0 }), "004.2E1"); + assert_eq!(format!("{:07}", EnumUpperExp::<_, i8>::A(42.0)), "004.2E1"); + assert_eq!( + 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); + assert_eq!( + format!("{:018}", EnumPointer::<&i8, _>::B { field: &42 }).len(), + 18, + ); + } + } + + mod omitted { + use super::*; + + mod on_modifiers { + use super::*; + + #[derive(Display)] + enum Enum { + #[display("{_0:x?}")] + LowerDebug(A), + #[display("{_0:X?}")] + UpperDebug(B), + #[display("{:^}", _0)] + Align(C), + #[display("{:+}", _0)] + Sign(C), + #[display("{:#b}", _0)] + Alternate(C), + #[display("{:0}", _0)] + ZeroPadded(C), + #[display("{:07}", _0)] + Width(C), + #[display("{:.5}", _0)] + Precision(D), + } + + #[test] + fn assert() { + assert_eq!( + format!("{:03}", Enum::<_, u8, u8, f64>::LowerDebug(7)), + "7", + ); + assert_eq!( + format!("{:03}", Enum::::UpperDebug(8)), + "8", + ); + assert_eq!(format!("{:03}", Enum::::Align(5)), "5"); + assert_eq!(format!("{:03}", Enum::::Sign(5)), "+5"); + assert_eq!( + format!("{:07}", Enum::::Alternate(5)), + "0b101", + ); + assert_eq!( + format!("{:07}", Enum::::ZeroPadded(-5)), + "-5", + ); + assert_eq!( + format!("{:03}", Enum::::Width(5)), + "0000005", + ); + assert_eq!( + format!("{:.3}", Enum::::Precision(1.23456789)), + "1.23457", + ); + } + } + } + } }