diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cf78f30..16795055 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ([#206](https://github.com/JelteF/derive_more/pull/206)) - Add `TryUnwrap` derive similar to the `Unwrap` derive. This one returns a `Result` and does not panic. ([#206](https://github.com/JelteF/derive_more/pull/206)) +- Add support for container format in `Debug` derive with the same syntax as `Display` derives. + ([#279](https://github.com/JelteF/derive_more/pull/279)) ### Changed diff --git a/impl/doc/debug.md b/impl/doc/debug.md index 963ccd76..3429f514 100644 --- a/impl/doc/debug.md +++ b/impl/doc/debug.md @@ -3,7 +3,7 @@ This derive macro is a clever superset of `Debug` from standard library. Additional features include: - not imposing redundant trait bounds; - `#[debug(skip)]` attribute to skip formatting struct field or enum variant; -- `#[debug("...", args...)]` to specify custom formatting for a particular struct or enum variant field; +- `#[debug("...", args...)]` to specify custom formatting either for the whole struct or enum variant, or its particular field; - `#[debug(bounds(...))]` to impose additional custom trait bounds. @@ -11,11 +11,11 @@ This derive macro is a clever superset of `Debug` from standard library. Additio ## The format of the format -You supply a format by placing an attribute on particular struct or enum variant field (not enum variant itself): +You supply a format by placing an attribute on a struct or enum variant, or its particular field: `#[debug("...", args...)]`. The format is exactly like in [`format!()`] or any other [`format_args!()`]-based macros. -The variables available in the arguments is `self` and each member of the variant, with members of tuple structs being -named with a leading underscore and their index, i.e. `_0`, `_1`, `_2`, etc. +The variables available in the arguments is `self` and each member of the struct or enum variant, with members of tuple +structs being named with a leading underscore and their index, i.e. `_0`, `_1`, `_2`, etc. @@ -102,6 +102,10 @@ struct MyInt(i32); #[derive(Debug)] struct MyIntHex(#[debug("{_0:x}")] i32); +#[derive(Debug)] +#[debug("{_0} = {_1}")] +struct StructFormat(&'static str, u8); + #[derive(Debug)] enum E { Skipped { @@ -114,13 +118,17 @@ enum E { i: i8, }, Path(#[debug("{}", _0.display())] PathBuf), + #[debug("{_0}")] + EnumFormat(bool) } assert_eq!(format!("{:?}", MyInt(-2)), "MyInt(-2)"); assert_eq!(format!("{:?}", MyIntHex(-255)), "MyIntHex(ffffff01)"); +assert_eq!(format!("{:?}", StructFormat("answer", 42)), "answer = 42"); assert_eq!(format!("{:?}", E::Skipped { x: 10, y: 20 }), "Skipped { x: 10, .. }"); assert_eq!(format!("{:?}", E::Binary { i: -2 }), "Binary { i: 11111110 }"); assert_eq!(format!("{:?}", E::Path("abc".into())), "Path(abc)"); +assert_eq!(format!("{:?}", E::EnumFormat(true)), "true"); ``` [`format!()`]: https://doc.rust-lang.org/stable/std/macro.format.html diff --git a/impl/src/fmt/debug.rs b/impl/src/fmt/debug.rs index 5a5d70f6..4c8948fc 100644 --- a/impl/src/fmt/debug.rs +++ b/impl/src/fmt/debug.rs @@ -5,26 +5,26 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{ - parse::{Error, Parse, ParseStream, Result}, + parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, Ident, }; -use super::{BoundsAttribute, FmtAttribute}; +use super::{ContainerAttributes, FmtAttribute}; /// Expands a [`fmt::Debug`] derive macro. /// /// [`fmt::Debug`]: std::fmt::Debug -pub fn expand(input: &syn::DeriveInput, _: &str) -> Result { - let attrs = ContainerAttributes::parse_attrs(&input.attrs)?; +pub fn expand(input: &syn::DeriveInput, _: &str) -> syn::Result { + let attrs = ContainerAttributes::parse_attrs(&input.attrs, "Debug")?; let ident = &input.ident; let (bounds, body) = match &input.data { syn::Data::Struct(s) => expand_struct(attrs, ident, s), syn::Data::Enum(e) => expand_enum(attrs, e), syn::Data::Union(_) => { - return Err(Error::new( + return Err(syn::Error::new( input.span(), "`Debug` cannot be derived for unions", )); @@ -61,12 +61,13 @@ fn expand_struct( attrs: ContainerAttributes, ident: &Ident, s: &syn::DataStruct, -) -> Result<(Vec, TokenStream)> { +) -> syn::Result<(Vec, TokenStream)> { let s = Expansion { attr: &attrs, fields: &s.fields, ident, }; + s.validate_attrs()?; let bounds = s.generate_bounds()?; let body = s.generate_body()?; @@ -91,19 +92,43 @@ fn expand_struct( /// /// [`fmt::Debug`]: std::fmt::Debug fn expand_enum( - attrs: ContainerAttributes, + mut attrs: ContainerAttributes, e: &syn::DataEnum, -) -> Result<(Vec, TokenStream)> { +) -> syn::Result<(Vec, TokenStream)> { + if let Some(enum_fmt) = attrs.fmt.as_ref() { + return Err(syn::Error::new_spanned( + enum_fmt, + "`#[debug(\"...\", ...)]` attribute is not allowed on enum, place it on its variants \ + instead", + )); + } + let (bounds, match_arms) = e.variants.iter().try_fold( (Vec::new(), TokenStream::new()), |(mut bounds, mut arms), variant| { let ident = &variant.ident; + attrs.fmt = variant + .attrs + .iter() + .filter(|attr| attr.path().is_ident("debug")) + .try_fold(None, |mut attrs, attr| { + let attr = attr.parse_args::()?; + attrs.replace(attr).map_or(Ok(()), |dup| { + Err(syn::Error::new( + dup.span(), + "multiple `#[debug(\"...\", ...)]` attributes aren't allowed", + )) + })?; + Ok::<_, syn::Error>(attrs) + })?; + let v = Expansion { attr: &attrs, fields: &variant.fields, ident, }; + v.validate_attrs()?; let arm_body = v.generate_body()?; bounds.extend(v.generate_bounds()?); @@ -123,7 +148,7 @@ fn expand_enum( arms.extend([quote! { #matcher => { #arm_body }, }]); - Ok::<_, Error>((bounds, arms)) + Ok::<_, syn::Error>((bounds, arms)) }, )?; @@ -135,42 +160,6 @@ fn expand_enum( Ok((bounds, body)) } -/// Representation of a [`fmt::Debug`] derive macro container attribute. -/// -/// ```rust,ignore -/// #[debug(bound())] -/// ``` -/// -/// [`fmt::Debug`]: std::fmt::Debug -#[derive(Debug, Default)] -struct ContainerAttributes { - /// Additional trait bounds. - bounds: BoundsAttribute, -} - -impl ContainerAttributes { - /// Parses [`ContainerAttributes`] from the provided [`syn::Attribute`]s. - fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> Result { - attrs - .as_ref() - .iter() - .filter(|attr| attr.path().is_ident("debug")) - .try_fold(ContainerAttributes::default(), |mut attrs, attr| { - let attr = attr.parse_args::()?; - attrs.bounds.0.extend(attr.bounds.0); - Ok(attrs) - }) - } -} - -impl Parse for ContainerAttributes { - fn parse(input: ParseStream) -> Result { - BoundsAttribute::check_legacy_fmt(input)?; - - input.parse().map(|bounds| ContainerAttributes { bounds }) - } -} - /// Representation of a [`fmt::Debug`] derive macro field attribute. /// /// ```rust,ignore @@ -190,8 +179,8 @@ enum FieldAttribute { } impl FieldAttribute { - /// Parses [`ContainerAttributes`] from the provided [`syn::Attribute`]s. - fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> Result> { + /// Parses a [`FieldAttribute`] from the provided [`syn::Attribute`]s. + fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> syn::Result> { Ok(attrs .as_ref() .iter() @@ -199,7 +188,7 @@ impl FieldAttribute { .try_fold(None, |mut attrs, attr| { let field_attr = attr.parse_args::()?; if let Some((path, _)) = attrs.replace((attr.path(), field_attr)) { - Err(Error::new( + Err(syn::Error::new( path.span(), "only single `#[debug(...)]` attribute is allowed here", )) @@ -212,7 +201,7 @@ impl FieldAttribute { } impl Parse for FieldAttribute { - fn parse(input: ParseStream) -> Result { + fn parse(input: ParseStream) -> syn::Result { FmtAttribute::check_legacy_fmt(input)?; if input.peek(syn::LitStr) { @@ -222,7 +211,7 @@ impl Parse for FieldAttribute { if ["skip", "ignore"].into_iter().any(|i| p.is_ident(i)) { Ok(p) } else { - Err(Error::new( + Err(syn::Error::new( p.span(), "unknown attribute, expected `skip` or `ignore`", )) @@ -249,10 +238,34 @@ struct Expansion<'a> { } impl<'a> Expansion<'a> { + /// Validates attributes of this [`Expansion`] to be consistent. + fn validate_attrs(&self) -> syn::Result<()> { + if self.attr.fmt.is_some() { + for field_attr in self + .fields + .iter() + .map(|f| FieldAttribute::parse_attrs(&f.attrs)) + { + if let Some(FieldAttribute::Fmt(fmt)) = field_attr? { + return Err(syn::Error::new_spanned( + fmt, + "`#[debug(...)]` attributes are not allowed on fields when \ + `#[debug(\"...\", ...)]` is specified on struct or variant", + )); + } + } + } + Ok(()) + } + /// Generates [`Debug::fmt()`] implementation for a struct or an enum variant. /// /// [`Debug::fmt()`]: std::fmt::Debug::fmt() - fn generate_body(&self) -> Result { + fn generate_body(&self) -> syn::Result { + if let Some(fmt_attr) = &self.attr.fmt { + return Ok(quote! { ::core::write!(__derive_more_f, #fmt_attr) }); + }; + match self.fields { syn::Fields::Unit => { let ident = self.ident.to_string(); @@ -278,7 +291,7 @@ impl<'a> Expansion<'a> { |out, (i, field)| match FieldAttribute::parse_attrs(&field.attrs)? { Some(FieldAttribute::Skip) => { exhaustive = false; - Ok::<_, Error>(out) + Ok::<_, syn::Error>(out) } Some(FieldAttribute::Fmt(fmt)) => Ok(quote! { ::derive_more::__private::DebugTuple::field( @@ -318,7 +331,7 @@ impl<'a> Expansion<'a> { match FieldAttribute::parse_attrs(&field.attrs)? { Some(FieldAttribute::Skip) => { exhaustive = false; - Ok::<_, Error>(out) + Ok::<_, syn::Error>(out) } Some(FieldAttribute::Fmt(fmt)) => Ok(quote! { ::core::fmt::DebugStruct::field( @@ -342,10 +355,17 @@ impl<'a> Expansion<'a> { } /// Generates trait bounds for a struct or an enum variant. - fn generate_bounds(&self) -> Result> { - self.fields.iter().try_fold( - self.attr.bounds.0.clone().into_iter().collect::>(), - |mut out, field| { + fn generate_bounds(&self) -> syn::Result> { + let mut out = self.attr.bounds.0.clone().into_iter().collect::>(); + + 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 } + })); + Ok(out) + } else { + self.fields.iter().try_fold(out, |mut out, field| { let ty = &field.ty; match FieldAttribute::parse_attrs(&field.attrs)? { Some(FieldAttribute::Fmt(attr)) => { @@ -360,7 +380,7 @@ impl<'a> Expansion<'a> { None => out.extend([parse_quote! { #ty: ::core::fmt::Debug }]), } Ok(out) - }, - ) + }) + } } } diff --git a/impl/src/fmt/display.rs b/impl/src/fmt/display.rs index ff8f9280..5d1079f9 100644 --- a/impl/src/fmt/display.rs +++ b/impl/src/fmt/display.rs @@ -5,13 +5,9 @@ use std::fmt; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; -use syn::{ - parse::{Parse, ParseStream}, - parse_quote, - spanned::Spanned as _, -}; +use syn::{parse_quote, spanned::Spanned as _}; -use super::{BoundsAttribute, FmtAttribute}; +use super::{trait_name_to_attribute_name, ContainerAttributes}; /// Expands a [`fmt::Display`]-like derive macro. /// @@ -27,7 +23,7 @@ use super::{BoundsAttribute, FmtAttribute}; pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result { let trait_name = normalize_trait_name(trait_name); - let attrs = Attributes::parse_attrs(&input.attrs, trait_name)?; + let attrs = ContainerAttributes::parse_attrs(&input.attrs, trait_name)?; let trait_ident = format_ident!("{trait_name}"); let ident = &input.ident; @@ -62,11 +58,11 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result = (&'a Attributes, &'a Ident, &'a Ident, &'a str); +type ExpansionCtx<'a> = (&'a ContainerAttributes, &'a Ident, &'a Ident, &'a str); /// Expands a [`fmt::Display`]-like derive macro for the provided struct. fn expand_struct( @@ -113,7 +109,7 @@ fn expand_enum( let (bounds, match_arms) = e.variants.iter().try_fold( (Vec::new(), TokenStream::new()), |(mut bounds, mut arms), variant| { - let attrs = Attributes::parse_attrs(&variant.attrs, trait_name)?; + let attrs = ContainerAttributes::parse_attrs(&variant.attrs, trait_name)?; let ident = &variant.ident; if attrs.fmt.is_none() @@ -189,85 +185,14 @@ fn expand_union( )) } -/// Representation of a [`fmt::Display`]-like derive macro attribute. -/// -/// ```rust,ignore -/// #[("", )] -/// #[bound()] -/// ``` -/// -/// `#[(...)]` can be specified only once, while multiple -/// `#[(bound(...))]` are allowed. -#[derive(Debug, Default)] -struct Attributes { - /// Interpolation [`FmtAttribute`]. - fmt: Option, - - /// Addition trait bounds. - bounds: BoundsAttribute, -} - -impl Attributes { - /// Parses [`Attributes`] from the provided [`syn::Attribute`]s. - fn parse_attrs( - attrs: impl AsRef<[syn::Attribute]>, - trait_name: &str, - ) -> syn::Result { - attrs - .as_ref() - .iter() - .filter(|attr| attr.path().is_ident(trait_name_to_attribute_name(trait_name))) - .try_fold(Attributes::default(), |mut attrs, attr| { - let attr = attr.parse_args::()?; - match attr { - Attribute::Bounds(more) => { - attrs.bounds.0.extend(more.0); - } - Attribute::Fmt(fmt) => { - attrs.fmt.replace(fmt).map_or(Ok(()), |dup| Err(syn::Error::new( - dup.span(), - format!( - "multiple `#[{}(\"...\", ...)]` attributes aren't allowed", - trait_name_to_attribute_name(trait_name), - ))))?; - } - }; - Ok(attrs) - }) - } -} - -/// Representation of a single [`fmt::Display`]-like derive macro attribute. -#[derive(Debug)] -enum Attribute { - /// [`fmt`] attribute. - Fmt(FmtAttribute), - - /// Addition trait bounds. - Bounds(BoundsAttribute), -} - -impl Parse for Attribute { - fn parse(input: ParseStream<'_>) -> syn::Result { - BoundsAttribute::check_legacy_fmt(input)?; - FmtAttribute::check_legacy_fmt(input)?; - - if input.peek(syn::LitStr) { - input.parse().map(Attribute::Fmt) - } else { - input.parse().map(Attribute::Bounds) - } - } -} - /// Helper struct to generate [`Display::fmt()`] implementation body and trait /// bounds for a struct or an enum variant. /// /// [`Display::fmt()`]: fmt::Display::fmt() #[derive(Debug)] struct Expansion<'a> { - /// Derive macro [`Attributes`]. - attrs: &'a Attributes, + /// Derive macro [`ContainerAttributes`]. + attrs: &'a ContainerAttributes, /// Struct or enum [`Ident`]. ident: &'a Ident, @@ -343,24 +268,6 @@ impl<'a> Expansion<'a> { } } -/// Matches the provided `trait_name` to appropriate [`Attribute::Fmt`] argument name. -fn trait_name_to_attribute_name(trait_name: T) -> &'static str -where - T: for<'a> PartialEq<&'a str>, -{ - match () { - _ if trait_name == "Binary" => "binary", - _ if trait_name == "Display" => "display", - _ if trait_name == "LowerExp" => "lower_exp", - _ if trait_name == "LowerHex" => "lower_hex", - _ if trait_name == "Octal" => "octal", - _ if trait_name == "Pointer" => "pointer", - _ if trait_name == "UpperExp" => "upper_exp", - _ if trait_name == "UpperHex" => "upper_hex", - _ => unimplemented!(), - } -} - /// Matches the provided derive macro `name` to appropriate actual trait name. fn normalize_trait_name(name: &str) -> &'static str { match name { diff --git a/impl/src/fmt/mod.rs b/impl/src/fmt/mod.rs index 5727d671..0fc75650 100644 --- a/impl/src/fmt/mod.rs +++ b/impl/src/fmt/mod.rs @@ -317,6 +317,97 @@ impl Placeholder { } } +/// Representation of a [`fmt::Display`]-like derive macro attributes placed on a container (struct +/// or enum variant). +/// +/// ```rust,ignore +/// #[("", )] +/// #[bound()] +/// ``` +/// +/// `#[(...)]` can be specified only once, while multiple +/// `#[(bound(...))]` are allowed. +#[derive(Debug, Default)] +struct ContainerAttributes { + /// Interpolation [`FmtAttribute`]. + fmt: Option, + + /// Addition trait bounds. + bounds: BoundsAttribute, +} + +impl ContainerAttributes { + /// Parses [`ContainerAttributes`] from the provided [`syn::Attribute`]s. + pub(super) fn parse_attrs( + attrs: impl AsRef<[syn::Attribute]>, + trait_name: &str, + ) -> syn::Result { + attrs + .as_ref() + .iter() + .filter(|attr| attr.path().is_ident(trait_name_to_attribute_name(trait_name))) + .try_fold(Self::default(), |mut attrs, attr| { + match attr.parse_args::()? { + ContainerAttribute::Bounds(more) => { + attrs.bounds.0.extend(more.0); + } + ContainerAttribute::Fmt(fmt) => { + attrs.fmt.replace(fmt).map_or(Ok(()), |dup| Err(syn::Error::new( + dup.span(), + format!( + "multiple `#[{}(\"...\", ...)]` attributes aren't allowed", + trait_name_to_attribute_name(trait_name), + ))))?; + } + }; + Ok(attrs) + }) + } +} + +/// Representation of a single [`fmt::Display`]-like derive macro attribute, placed on a container +/// (struct or enum variant). +#[derive(Debug)] +enum ContainerAttribute { + /// [`fmt`] attribute. + Fmt(FmtAttribute), + + /// Addition trait bounds. + Bounds(BoundsAttribute), +} + +impl Parse for ContainerAttribute { + fn parse(input: ParseStream<'_>) -> syn::Result { + BoundsAttribute::check_legacy_fmt(input)?; + FmtAttribute::check_legacy_fmt(input)?; + + if input.peek(syn::LitStr) { + input.parse().map(Self::Fmt) + } else { + input.parse().map(Self::Bounds) + } + } +} + +/// Matches the provided `trait_name` to appropriate [`Attribute::Fmt`] argument name. +fn trait_name_to_attribute_name(trait_name: T) -> &'static str +where + T: for<'a> PartialEq<&'a str>, +{ + match () { + _ if trait_name == "Binary" => "binary", + _ if trait_name == "Debug" => "debug", + _ if trait_name == "Display" => "display", + _ if trait_name == "LowerExp" => "lower_exp", + _ if trait_name == "LowerHex" => "lower_hex", + _ if trait_name == "Octal" => "octal", + _ if trait_name == "Pointer" => "pointer", + _ if trait_name == "UpperExp" => "upper_exp", + _ if trait_name == "UpperHex" => "upper_hex", + _ => unimplemented!(), + } +} + #[cfg(test)] mod fmt_attribute_spec { use itertools::Itertools as _; diff --git a/tests/compile_fail/debug/duplicate_fmt_on_struct.rs b/tests/compile_fail/debug/duplicate_fmt_on_struct.rs new file mode 100644 index 00000000..f09c5fff --- /dev/null +++ b/tests/compile_fail/debug/duplicate_fmt_on_struct.rs @@ -0,0 +1,6 @@ +#[derive(derive_more::Debug)] +#[debug("Test")] +#[debug("Second")] +pub struct Foo {} + +fn main() {} diff --git a/tests/compile_fail/debug/duplicate_fmt_on_struct.stderr b/tests/compile_fail/debug/duplicate_fmt_on_struct.stderr new file mode 100644 index 00000000..ad98a725 --- /dev/null +++ b/tests/compile_fail/debug/duplicate_fmt_on_struct.stderr @@ -0,0 +1,5 @@ +error: multiple `#[debug("...", ...)]` attributes aren't allowed + --> tests/compile_fail/debug/duplicate_fmt_on_struct.rs:2:9 + | +2 | #[debug("Test")] + | ^^^^^^ diff --git a/tests/compile_fail/debug/duplicate_fmt_on_variant.rs b/tests/compile_fail/debug/duplicate_fmt_on_variant.rs new file mode 100644 index 00000000..9e20ddf9 --- /dev/null +++ b/tests/compile_fail/debug/duplicate_fmt_on_variant.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::Debug)] +pub enum Foo { + #[debug("Test")] + #[debug("Second")] + Unit, +} + +fn main() {} diff --git a/tests/compile_fail/debug/duplicate_fmt_on_variant.stderr b/tests/compile_fail/debug/duplicate_fmt_on_variant.stderr new file mode 100644 index 00000000..6dd45ba4 --- /dev/null +++ b/tests/compile_fail/debug/duplicate_fmt_on_variant.stderr @@ -0,0 +1,5 @@ +error: multiple `#[debug("...", ...)]` attributes aren't allowed + --> tests/compile_fail/debug/duplicate_fmt_on_variant.rs:3:13 + | +3 | #[debug("Test")] + | ^^^^^^ diff --git a/tests/compile_fail/debug/fmt_on_container_and_field_simultaneously.rs b/tests/compile_fail/debug/fmt_on_container_and_field_simultaneously.rs new file mode 100644 index 00000000..e72b9bd7 --- /dev/null +++ b/tests/compile_fail/debug/fmt_on_container_and_field_simultaneously.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::Debug)] +#[debug("{bar}")] +pub struct Foo { + #[debug("{bar}")] + bar: String, +} + +fn main() {} diff --git a/tests/compile_fail/debug/fmt_on_container_and_field_simultaneously.stderr b/tests/compile_fail/debug/fmt_on_container_and_field_simultaneously.stderr new file mode 100644 index 00000000..b029e8c6 --- /dev/null +++ b/tests/compile_fail/debug/fmt_on_container_and_field_simultaneously.stderr @@ -0,0 +1,5 @@ +error: `#[debug(...)]` attributes are not allowed on fields when `#[debug("...", ...)]` is specified on struct or variant + --> tests/compile_fail/debug/fmt_on_container_and_field_simultaneously.rs:4:13 + | +4 | #[debug("{bar}")] + | ^^^^^^^ diff --git a/tests/compile_fail/debug/fmt_on_enum.rs b/tests/compile_fail/debug/fmt_on_enum.rs new file mode 100644 index 00000000..edfbf819 --- /dev/null +++ b/tests/compile_fail/debug/fmt_on_enum.rs @@ -0,0 +1,7 @@ +#[derive(derive_more::Debug)] +#[debug("Test")] +pub enum Foo { + Unit +} + +fn main() {} diff --git a/tests/compile_fail/debug/fmt_on_enum.stderr b/tests/compile_fail/debug/fmt_on_enum.stderr new file mode 100644 index 00000000..43a02119 --- /dev/null +++ b/tests/compile_fail/debug/fmt_on_enum.stderr @@ -0,0 +1,5 @@ +error: `#[debug("...", ...)]` attribute is not allowed on enum, place it on its variants instead + --> tests/compile_fail/debug/fmt_on_enum.rs:2:9 + | +2 | #[debug("Test")] + | ^^^^^^ diff --git a/tests/debug.rs b/tests/debug.rs index 45eaf463..4b02412a 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -29,6 +29,22 @@ mod structs { assert_eq!(format!("{:?}", Struct {}), "Struct"); assert_eq!(format!("{:#?}", Struct {}), "Struct"); } + + mod interpolated_struct { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + #[debug("Format String")] + struct Unit; + + #[test] + fn assert() { + assert_eq!(format!("{Unit:?}"), "Format String"); + } + } } mod single_field { @@ -274,6 +290,30 @@ mod structs { } } + mod interpolated_struct { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + #[debug("{_0} * {_1}")] + struct Tuple(u8, bool); + + #[derive(Debug)] + #[debug("{a} * {b}")] + struct Struct { + a: u8, + b: bool, + } + + #[test] + fn assert() { + assert_eq!(format!("{:?}", Tuple(10, true)), "10 * true"); + assert_eq!(format!("{:?}", Struct { a: 10, b: true }), "10 * true"); + } + } + mod ignore { #[cfg(not(feature = "std"))] use alloc::format; @@ -568,6 +608,30 @@ mod enums { "SkippedNamed {\n field2: 2,\n ..\n}", ); } + + mod interpolated_variant { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + enum Enum { + #[debug("Format String")] + Unit, + #[debug("Format {a} String {b}")] + Fields { a: usize, b: u8 }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:?}", Enum::Unit), "Format String"); + assert_eq!( + format!("{:?}", Enum::Fields { a: 1, b: 2 }), + "Format 1 String 2", + ); + } + } } } @@ -685,6 +749,22 @@ mod generic { ); } + #[derive(Debug)] + #[debug("test_named")] + struct InterpolatedNamedGenericStructIgnored { + field: T, + } + #[test] + fn interpolated_named_generic_struct_ignored() { + assert_eq!( + format!( + "{:?}", + InterpolatedNamedGenericStructIgnored { field: NotDebug }, + ), + "test_named", + ); + } + #[derive(Debug)] struct AliasedNamedGenericStruct { #[debug("{alias}", alias = field)] @@ -782,6 +862,17 @@ mod generic { ); } + #[derive(Debug)] + #[debug("test_unnamed")] + struct InterpolatedUnnamedGenericStructIgnored(T); + #[test] + fn interpolated_unnamed_generic_struct_ignored() { + assert_eq!( + format!("{:?}", InterpolatedUnnamedGenericStructIgnored(NotDebug)), + "test_unnamed", + ); + } + #[derive(Debug)] struct AliasedUnnamedGenericStruct(#[debug("{alias}", alias = _0)] T); #[test] @@ -860,6 +951,28 @@ mod generic { ); } + #[derive(Debug)] + enum InterpolatedGenericEnumIngored { + #[debug("A {field}")] + A { field: A }, + #[debug("B")] + B(B), + } + #[test] + fn interpolated_generic_enum_ignored() { + assert_eq!( + format!( + "{:?}", + InterpolatedGenericEnumIngored::A::<_, u8> { field: NotDebug }, + ), + "A NotDebug", + ); + assert_eq!( + format!("{:?}", InterpolatedGenericEnumIngored::B::(NotDebug)), + "B", + ); + } + #[derive(Debug)] struct MultiTraitNamedGenericStruct { #[debug("{}.{}<->{0:o}.{1:#x}<->{0:?}.{1:X?}", a, b)]