Skip to content

Commit

Permalink
Fix issue
Browse files Browse the repository at this point in the history
  • Loading branch information
JelteF committed Jul 4, 2024
1 parent 85a18f1 commit 1d1d9fb
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 44 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Hygiene of macro expansions in presence of custom `core` crate.
([#327](https://github.com/JelteF/derive_more/pull/327))
- Fix documentation of generated methods in `IsVariant` derive.
- Make `{field:p}` do the expected thing in format strings for `Display` and
`Debug`. Also document weirdness around `Pointer` formatting when using
expressions, due to field variables being references.

## 0.99.10 - 2020-09-11

Expand Down
23 changes: 21 additions & 2 deletions impl/doc/debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,27 @@ This derive macro is a clever superset of `Debug` from standard library. Additio
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 struct or enum 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. Due to
ownership/lifetime limitations the member variables are all references to the
fields, except when used directly in the format string. For most purposes this
detail doesn't matter, but it is quite important when using `Pointer`
formatting. If you don't use the `{field:p}` syntax, you have to dereference
once to get the address of the field itself, instead of the reference to the
field:


```rust
#[derive(derive_more::Debug)]
#[debug("{field:p} {:p}", *field)]
struct RefInt<'a> {
field: &'a i32,
}

let a = &123;
assert_eq!(format!("{:?}", RefInt{field: &a}), format!("{a:p} {:p}", a));
```


### Generic data types
Expand Down
27 changes: 23 additions & 4 deletions impl/doc/display.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,28 @@ inner variable. If there is no such variable, or there is more than 1, an error
You supply a format by attaching an attribute of the syntax: `#[display("...", args...)]`.
The format supplied is passed verbatim to `write!`.

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
variant, with members of tuple structs being named with a leading underscore
and their index, i.e. `_0`, `_1`, `_2`, etc. Due to ownership/lifetime
limitations the member variables are all references to the fields, except when
used directly in the format string. For most purposes this detail doesn't
matter, but it is quite important when using `Pointer` formatting. If you don't
use the `{field:p}` syntax, you have to dereference once to get the address of
the field itself, instead of the reference to the field:


```rust
# use derive_more::Display;
#
#[derive(Display)]
#[display("{field:p} {:p}", *field)]
struct RefInt<'a> {
field: &'a i32,
}

let a = &123;
assert_eq!(format!("{}", RefInt{field: &a}), format!("{a:p} {:p}", a));
```

For enums you can also specify a shared format on the enum itself instead of
the variant. This format is used for each of the variants, and can be
Expand Down Expand Up @@ -55,7 +74,7 @@ E.g., for a structure `Foo` defined like this:
# trait Trait { type Type; }
#
#[derive(Display)]
#[display("{} {} {:?} {:p}", a, b, c, d)]
#[display("{a} {b} {c:?} {d:p}")]
struct Foo<'a, T1, T2: Trait, T3> {
a: T1,
b: <T2 as Trait>::Type,
Expand Down
87 changes: 73 additions & 14 deletions impl/src/fmt/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::utils::{
Either, Spanning,
};

use super::{trait_name_to_attribute_name, ContainerAttributes, FmtAttribute};
use super::{parsing, trait_name_to_attribute_name, ContainerAttributes, FmtAttribute};

/// Expands a [`fmt::Debug`] derive macro.
///
Expand Down Expand Up @@ -222,16 +222,69 @@ impl<'a> Expansion<'a> {
Ok(())
}

fn used_arguments<'s>(
&self,
format_string: &'s str,
) -> impl Iterator<Item = &'s str>
where
'a: 's,
{
parsing::format_string_formats(format_string)
.into_iter()
.flatten()
.flat_map(|f| match f.arg {
Some(parsing::Argument::Identifier(name)) => Some(name),
_ => None,
})
}

fn field_idents(&self) -> impl Iterator<Item = Ident> + '_ {
self.fields
.iter()
.enumerate()
.map(|(i, f)| f.ident.clone().unwrap_or_else(|| format_ident!("_{i}")))
}

fn field_params<'selff, 's>(
&'selff self,
format_string: &'s str,
) -> impl Iterator<Item = TokenStream> + 's
where
'a: 's,
'selff: 's,
{
let used_arguments: Vec<&'s str> = self.used_arguments(format_string).collect();
self.field_idents().filter_map(move |var| {
if used_arguments.contains(&var.to_string().as_str()) {
Some(quote! { #var = *#var })
} else {
None
}
})
}

/// Generates [`Debug::fmt()`] implementation for a struct or an enum variant.
///
/// [`Debug::fmt()`]: std::fmt::Debug::fmt()
fn generate_body(&self) -> syn::Result<TokenStream> {
if let Some(fmt) = &self.attr.fmt {
return Ok(if let Some((expr, trait_ident)) = fmt.transparent_call() {
quote! { derive_more::core::fmt::#trait_ident::fmt(&(#expr), __derive_more_f) }
} else {
quote! { derive_more::core::write!(__derive_more_f, #fmt) }
});
let format_string = fmt.lit.value();
return Ok(
if let Some((expr, trait_ident)) = fmt.transparent_call() {
let expr = if self.field_idents().any(|var| {
self.used_arguments(&format_string)
.any(|arg| arg == var.to_string().as_str())
}) {
quote! { #expr }
} else {
quote! { &(#expr) }
};
quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) }
} else {
let field_params = self.field_params(&format_string);
quote! { derive_more::core::write!(__derive_more_f, #fmt, #(#field_params),*) }
},
);
};

match self.fields {
Expand Down Expand Up @@ -266,12 +319,15 @@ impl<'a> Expansion<'a> {
exhaustive = false;
Ok::<_, syn::Error>(out)
}
Some(FieldAttribute::Right(fmt_attr)) => Ok(quote! {
derive_more::__private::DebugTuple::field(
#out,
&derive_more::core::format_args!(#fmt_attr),
Some(FieldAttribute::Right(fmt_attr)) => {
let format_string = fmt_attr.lit.value();
let field_params = self.field_params(&format_string);
Ok(quote! {
derive_more::__private::DebugTuple::field(
#out,
&derive_more::core::format_args!(#fmt_attr, #(#field_params),*),
)
}),
})},
None => {
let ident = format_ident!("_{i}");
Ok(quote! {
Expand Down Expand Up @@ -308,13 +364,16 @@ impl<'a> Expansion<'a> {
exhaustive = false;
Ok::<_, syn::Error>(out)
}
Some(FieldAttribute::Right(fmt_attr)) => Ok(quote! {
Some(FieldAttribute::Right(fmt_attr)) => {
let format_string = fmt_attr.lit.value();
let field_params = self.field_params(&format_string);
Ok(quote! {
derive_more::core::fmt::DebugStruct::field(
#out,
#field_str,
&derive_more::core::format_args!(#fmt_attr),
&derive_more::core::format_args!(#fmt_attr, #(#field_params),*),
)
}),
})},
None => Ok(quote! {
derive_more::core::fmt::DebugStruct::field(#out, #field_str, &#field_ident)
}),
Expand Down
55 changes: 53 additions & 2 deletions impl/src/fmt/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,20 @@ impl<'a> Expansion<'a> {
fn generate_body_impl(&self) -> syn::Result<TokenStream> {
match &self.attrs.fmt {
Some(fmt) => {
let format_string = fmt.lit.value();
Ok(if let Some((expr, trait_ident)) = fmt.transparent_call() {
quote! { derive_more::core::fmt::#trait_ident::fmt(&(#expr), __derive_more_f) }
let expr = if self.field_idents().any(|var| {
self.used_arguments(&format_string)
.any(|arg| arg == var.to_string().as_str())
}) {
quote! { #expr }
} else {
quote! { &(#expr) }
};
quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) }
} else {
quote! { derive_more::core::write!(__derive_more_f, #fmt) }
let field_params = self.field_params(&format_string);
quote! { derive_more::core::write!(__derive_more_f, #fmt, #(#field_params),*) }
})
}
None if self.fields.is_empty() => {
Expand Down Expand Up @@ -341,6 +351,47 @@ impl<'a> Expansion<'a> {
}
}

fn used_arguments<'s>(
&self,
format_string: &'s str,
) -> impl Iterator<Item = &'s str>
where
'a: 's,
{
parsing::format_string_formats(format_string)
.into_iter()
.flatten()
.flat_map(|f| match f.arg {
Some(parsing::Argument::Identifier(name)) => Some(name),
_ => None,
})
}

fn field_idents(&self) -> impl Iterator<Item = syn::Ident> + '_ {
self.fields
.iter()
.enumerate()
.map(|(i, f)| f.ident.clone().unwrap_or_else(|| format_ident!("_{i}")))
}

fn field_params<'selff, 's>(
&'selff self,
format_string: &'s str,
) -> impl Iterator<Item = TokenStream> + 's
where
'a: 's,
'selff: 's,
{
let used_arguments: Vec<&'s str> = self.used_arguments(format_string).collect();
self.field_idents().filter_map(move |var| {
if used_arguments.contains(&var.to_string().as_str()) {
Some(quote! { #var = *#var })
} else {
None
}
})
}

/// Generates trait bounds for a struct or an enum variant.
fn generate_bounds(&self) -> Vec<syn::WherePredicate> {
let mut bounds: Vec<syn::WherePredicate> =
Expand Down
6 changes: 4 additions & 2 deletions impl/src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,16 @@ impl Parse for FmtAttribute {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
Self::check_legacy_fmt(input)?;

Ok(Self {
let mut parsed = Self {
lit: input.parse()?,
comma: input
.peek(token::Comma)
.then(|| input.parse())
.transpose()?,
args: input.parse_terminated(FmtArgument::parse, token::Comma)?,
})
};
parsed.args.pop_punct();
Ok(parsed)
}
}

Expand Down
28 changes: 24 additions & 4 deletions tests/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,18 @@ mod structs {
field: &'a i32,
}

#[derive(Debug)]
#[debug("{_0:p}")]
struct TupleTransparent<'a> (
&'a i32,
);

#[derive(Debug)]
#[debug("{field:p}")]
struct StructTransparent<'a> {
field: &'a i32,
}

#[test]
fn assert() {
let a = 42;
Expand All @@ -273,6 +285,14 @@ mod structs {
format!("{:?}", Struct { field: &a }),
format!("Struct {{ field: {0:p}.{0:p} }}", &a),
);
assert_eq!(
format!("{:?}", TupleTransparent(&a)),
format!("{0:p}", &a),
);
assert_eq!(
format!("{:?}", StructTransparent { field: &a }),
format!("{0:p}", &a),
);
}
}
}
Expand Down Expand Up @@ -564,11 +584,11 @@ mod structs {
use derive_more::Debug;

#[derive(Debug)]
#[debug("{_0:p} * {_1:p}", _0 = self.0)]
#[debug("{_0:p} * {_1:p}")]
struct Tuple<'a, 'b>(&'a u8, &'b bool);

#[derive(Debug)]
#[debug("{a:p} * {b:p}", a = self.a)]
#[debug("{a:p} * {b:p}")]
struct Struct<'a, 'b> {
a: &'a u8,
b: &'b bool,
Expand Down Expand Up @@ -1305,7 +1325,7 @@ mod generic {

#[derive(Debug)]
struct AliasedFieldNamedGenericStruct<T> {
#[debug("{field1}", field1 = field2)]
#[debug("{field3}", field3 = field2)]
field1: T,
field2: i32,
}
Expand Down Expand Up @@ -1423,7 +1443,7 @@ mod generic {
}

#[derive(Debug)]
struct AliasedFieldUnnamedGenericStruct<T>(#[debug("{_0}", _0 = _1)] T, i32);
struct AliasedFieldUnnamedGenericStruct<T>(#[debug("{_2}", _2 = _1)] T, i32);
#[test]
fn aliased_field_unnamed_generic_struct() {
assert_eq!(
Expand Down
Loading

0 comments on commit 1d1d9fb

Please sign in to comment.