Skip to content

Commit

Permalink
Fix raw identifiers usage in Display/Debug derives (#434, #431)
Browse files Browse the repository at this point in the history
  • Loading branch information
tyranron authored Jan 16, 2025
1 parent 7b23de3 commit f14c7a7
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 16 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Top-level `#[display("...")]` attribute on an enum being incorrectly treated
as transparent or wrapping.
([#395](https://github.com/JelteF/derive_more/pull/395))
- Omitted raw identifiers in `Debug` and `Display` expansions.
([#431](https://github.com/JelteF/derive_more/pull/431))
- Incorrect rendering of raw identifiers as field names in `Debug` expansions.
([#431](https://github.com/JelteF/derive_more/pull/431))


## 1.0.0 - 2024-08-07
Expand Down
18 changes: 11 additions & 7 deletions impl/src/fmt/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_quote, spanned::Spanned as _, Ident};
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};

use crate::utils::{
attr::{self, ParseMultiple as _},
Expand Down Expand Up @@ -78,7 +78,7 @@ pub fn expand(input: &syn::DeriveInput, _: &str) -> syn::Result<TokenStream> {
/// [`fmt::Debug`]: std::fmt::Debug
fn expand_struct(
attrs: ContainerAttributes,
ident: &Ident,
ident: &syn::Ident,
s: &syn::DataStruct,
type_params: &[&syn::Ident],
attr_name: &syn::Ident,
Expand Down Expand Up @@ -209,8 +209,8 @@ type FieldAttribute = Either<attr::Skip, FmtAttribute>;
struct Expansion<'a> {
attr: &'a ContainerAttributes,

/// Struct or enum [`Ident`](struct@Ident).
ident: &'a Ident,
/// Struct or enum [`Ident`](struct@syn::Ident).
ident: &'a syn::Ident,

/// Struct or enum [`syn::Fields`].
fields: &'a syn::Fields,
Expand Down Expand Up @@ -251,8 +251,12 @@ impl Expansion<'_> {
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() {
let expr = if self.fields.fmt_args_idents().any(|field| expr == field) {
quote! { #expr }
let expr = if let Some(field) = self
.fields
.fmt_args_idents()
.find(|field| expr == *field || expr == field.unraw())
{
quote! { #field }
} else {
quote! { &(#expr) }
};
Expand Down Expand Up @@ -335,7 +339,7 @@ impl Expansion<'_> {
let field_ident = field.ident.as_ref().unwrap_or_else(|| {
unreachable!("`syn::Fields::Named`");
});
let field_str = field_ident.to_string();
let field_str = field_ident.unraw().to_string();
match FieldAttribute::parse_attrs(&field.attrs, self.attr_name)?
.map(Spanning::into_inner)
{
Expand Down
15 changes: 9 additions & 6 deletions impl/src/fmt/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,15 @@ impl Expansion<'_> {

quote! { &derive_more::core::format_args!(#fmt, #(#deref_args),*) }
} else if let Some((expr, trait_ident)) = fmt.transparent_call() {
let expr =
if self.fields.fmt_args_idents().any(|field| expr == field) {
quote! { #expr }
} else {
quote! { &(#expr) }
};
let expr = if let Some(field) = self
.fields
.fmt_args_idents()
.find(|field| expr == *field || expr == field.unraw())
{
quote! { #field }
} else {
quote! { &(#expr) }
};

quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) }
} else {
Expand Down
10 changes: 7 additions & 3 deletions impl/src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod parsing;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{
ext::IdentExt as _,
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned as _,
Expand Down Expand Up @@ -140,7 +141,7 @@ impl FmtAttribute {
/// 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
/// If such transparent call is possible, then returns an [`Ident`] of the delegated trait and
/// the [`Expr`] to pass into the call, otherwise [`None`].
///
/// [`Ident`]: struct@syn::Ident
Expand Down Expand Up @@ -228,7 +229,10 @@ impl FmtAttribute {
f.unnamed.iter().nth(i).map(|f| &f.ty)
}
(syn::Fields::Named(f), None) => f.named.iter().find_map(|f| {
f.ident.as_ref().filter(|s| **s == name).map(|_| &f.ty)
f.ident
.as_ref()
.filter(|s| s.unraw() == name)
.map(|_| &f.ty)
}),
_ => None,
}?;
Expand Down Expand Up @@ -291,7 +295,7 @@ impl FmtAttribute {
.collect::<Vec<_>>();

fields.fmt_args_idents().filter_map(move |field_name| {
(used_args.iter().any(|arg| field_name == arg)
(used_args.iter().any(|arg| field_name.unraw() == arg)
&& !self.args.iter().any(|arg| {
arg.alias.as_ref().is_some_and(|(n, _)| n == &field_name)
}))
Expand Down
134 changes: 134 additions & 0 deletions tests/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1820,6 +1820,140 @@ mod generic {
}
}

mod raw {
#[cfg(not(feature = "std"))]
use alloc::format;

use derive_more::Debug;

#[derive(Debug)]
struct StructOne<T> {
r#thing: T,
}

#[derive(Debug)]
struct StructOneKeyword<T> {
r#struct: T,
}

#[derive(Debug)]
enum Enum<T> {
One { r#thing: T },
}

#[derive(Debug)]
enum EnumKeyword<T> {
One { r#struct: T },
}

#[test]
fn assert() {
assert_eq!(
format!("{:?}", StructOne::<u8> { r#thing: 8 }),
"StructOne { thing: 8 }",
);
assert_eq!(
format!("{:?}", StructOneKeyword::<u8> { r#struct: 8 }),
"StructOneKeyword { struct: 8 }",
);
assert_eq!(
format!("{:?}", Enum::<u8>::One { r#thing: 8 }),
"One { thing: 8 }",
);
assert_eq!(
format!("{:?}", EnumKeyword::<u8>::One { r#struct: 8 }),
"One { struct: 8 }",
);
}

mod interpolated {
#[cfg(not(feature = "std"))]
use alloc::format;

use derive_more::Debug;

#[derive(Debug)]
#[debug("{thing}")]
struct StructOne<T> {
r#thing: T,
}

#[derive(Debug)]
#[debug("{struct}")]
struct StructOneKeyword<T> {
r#struct: T,
}

#[derive(Debug)]
enum Enum1<T> {
#[debug("{thing}")]
One { r#thing: T },
}

#[derive(Debug)]
enum Enum1Keyword<T> {
#[debug("{struct}")]
One { r#struct: T },
}

#[derive(Debug)]
#[debug("{a}:{b}")]
struct StructTwo<A, B> {
r#a: A,
b: B,
}

#[derive(Debug)]
#[debug("{pub}:{b}")]
struct StructTwoKeyword<A, B> {
r#pub: A,
b: B,
}

#[derive(Debug)]
enum Enum2<A, B> {
#[debug("{a}:{b}")]
Two { r#a: A, b: B },
}

#[derive(Debug)]
enum Enum2Keyword<A, B> {
#[debug("{pub}:{b}")]
Two { r#pub: A, b: B },
}

#[test]
fn assert() {
assert_eq!(format!("{:?}", StructOne::<u8> { r#thing: 8 }), "8");
assert_eq!(
format!("{:?}", StructOneKeyword::<u8> { r#struct: 8 }),
"8",
);
assert_eq!(format!("{:?}", Enum1::<u8>::One { r#thing: 8 }), "8");
assert_eq!(
format!("{:?}", Enum1Keyword::<u8>::One { r#struct: 8 }),
"8",
);
assert_eq!(
format!("{:?}", StructTwo::<u8, u16> { r#a: 8, b: 16 }),
"8:16",
);
assert_eq!(
format!("{:?}", StructTwoKeyword::<u8, u16> { r#pub: 8, b: 16 }),
"8:16",
);
assert_eq!(
format!("{:?}", Enum2::<u8, u16>::Two { r#a: 8, b: 16 }),
"8:16",
);
assert_eq!(
format!("{:?}", Enum2Keyword::<u8, u16>::Two { r#pub: 8, b: 16 }),
"8:16",
);
}
}
}

mod bound {
#[cfg(not(feature = "std"))]
use alloc::format;
Expand Down
Loading

0 comments on commit f14c7a7

Please sign in to comment.