diff --git a/CHANGELOG.md b/CHANGELOG.md index 576d050b..7370e5d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/impl/src/fmt/debug.rs b/impl/src/fmt/debug.rs index 9d4dc0d3..f31270ca 100644 --- a/impl/src/fmt/debug.rs +++ b/impl/src/fmt/debug.rs @@ -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 _}, @@ -78,7 +78,7 @@ pub fn expand(input: &syn::DeriveInput, _: &str) -> syn::Result { /// [`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, @@ -209,8 +209,8 @@ type FieldAttribute = Either; 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, @@ -251,8 +251,12 @@ impl Expansion<'_> { fn generate_body(&self) -> syn::Result { 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) } }; @@ -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) { diff --git a/impl/src/fmt/display.rs b/impl/src/fmt/display.rs index 5e89773a..a7cca60f 100644 --- a/impl/src/fmt/display.rs +++ b/impl/src/fmt/display.rs @@ -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 { diff --git a/impl/src/fmt/mod.rs b/impl/src/fmt/mod.rs index 1e0172bd..8dc7e332 100644 --- a/impl/src/fmt/mod.rs +++ b/impl/src/fmt/mod.rs @@ -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 _, @@ -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 @@ -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, }?; @@ -291,7 +295,7 @@ impl FmtAttribute { .collect::>(); 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) })) diff --git a/tests/debug.rs b/tests/debug.rs index aa886b01..32728282 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -1820,6 +1820,140 @@ mod generic { } } + mod raw { + #[cfg(not(feature = "std"))] + use alloc::format; + + use derive_more::Debug; + + #[derive(Debug)] + struct StructOne { + r#thing: T, + } + + #[derive(Debug)] + struct StructOneKeyword { + r#struct: T, + } + + #[derive(Debug)] + enum Enum { + One { r#thing: T }, + } + + #[derive(Debug)] + enum EnumKeyword { + One { r#struct: T }, + } + + #[test] + fn assert() { + assert_eq!( + format!("{:?}", StructOne:: { r#thing: 8 }), + "StructOne { thing: 8 }", + ); + assert_eq!( + format!("{:?}", StructOneKeyword:: { r#struct: 8 }), + "StructOneKeyword { struct: 8 }", + ); + assert_eq!( + format!("{:?}", Enum::::One { r#thing: 8 }), + "One { thing: 8 }", + ); + assert_eq!( + format!("{:?}", EnumKeyword::::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 { + r#thing: T, + } + + #[derive(Debug)] + #[debug("{struct}")] + struct StructOneKeyword { + r#struct: T, + } + + #[derive(Debug)] + enum Enum1 { + #[debug("{thing}")] + One { r#thing: T }, + } + + #[derive(Debug)] + enum Enum1Keyword { + #[debug("{struct}")] + One { r#struct: T }, + } + + #[derive(Debug)] + #[debug("{a}:{b}")] + struct StructTwo { + r#a: A, + b: B, + } + + #[derive(Debug)] + #[debug("{pub}:{b}")] + struct StructTwoKeyword { + r#pub: A, + b: B, + } + + #[derive(Debug)] + enum Enum2 { + #[debug("{a}:{b}")] + Two { r#a: A, b: B }, + } + + #[derive(Debug)] + enum Enum2Keyword { + #[debug("{pub}:{b}")] + Two { r#pub: A, b: B }, + } + + #[test] + fn assert() { + assert_eq!(format!("{:?}", StructOne:: { r#thing: 8 }), "8"); + assert_eq!( + format!("{:?}", StructOneKeyword:: { r#struct: 8 }), + "8", + ); + assert_eq!(format!("{:?}", Enum1::::One { r#thing: 8 }), "8"); + assert_eq!( + format!("{:?}", Enum1Keyword::::One { r#struct: 8 }), + "8", + ); + assert_eq!( + format!("{:?}", StructTwo:: { r#a: 8, b: 16 }), + "8:16", + ); + assert_eq!( + format!("{:?}", StructTwoKeyword:: { r#pub: 8, b: 16 }), + "8:16", + ); + assert_eq!( + format!("{:?}", Enum2::::Two { r#a: 8, b: 16 }), + "8:16", + ); + assert_eq!( + format!("{:?}", Enum2Keyword::::Two { r#pub: 8, b: 16 }), + "8:16", + ); + } + } + } + mod bound { #[cfg(not(feature = "std"))] use alloc::format; diff --git a/tests/display.rs b/tests/display.rs index 66298bdd..a48ed792 100644 --- a/tests/display.rs +++ b/tests/display.rs @@ -2017,6 +2017,138 @@ mod generic { } } + mod raw { + use super::*; + + #[derive(Display)] + struct StructOne { + r#thing: T, + } + + #[derive(Display)] + struct StructOneKeyword { + r#struct: T, + } + + #[derive(Display)] + enum Enum { + One { r#thing: T }, + } + + #[test] + fn assert() { + assert_eq!(StructOne:: { r#thing: 8 }.to_string(), "8"); + assert_eq!(StructOneKeyword:: { r#struct: 8 }.to_string(), "8"); + assert_eq!(Enum::::One { r#thing: 8 }.to_string(), "8"); + } + + mod interpolated { + use super::*; + + #[derive(Display)] + #[display("{thing}")] + struct StructOne { + r#thing: T, + } + + #[derive(Display)] + #[display("{struct}")] + struct StructOneKeyword { + r#struct: T, + } + + #[derive(Display)] + enum Enum1 { + #[display("{thing}")] + One { r#thing: T }, + } + + #[derive(Display)] + enum Enum1Keyword { + #[display("{struct}")] + One { r#struct: T }, + } + + #[derive(Display)] + #[display("{thing}")] + enum Enum1Top { + One { r#thing: T }, + } + + #[derive(Display)] + #[display("{struct}")] + enum Enum1TopKeyword { + One { r#struct: T }, + } + + #[derive(Display)] + #[display("{a}:{b}")] + struct StructTwo { + r#a: A, + b: B, + } + + #[derive(Display)] + #[display("{pub}:{b}")] + struct StructTwoKeyword { + r#pub: A, + b: B, + } + + #[derive(Display)] + enum Enum2 { + #[display("{a}:{b}")] + Two { r#a: A, b: B }, + } + + #[derive(Display)] + enum Enum2Keyword { + #[display("{pub}:{b}")] + Two { r#pub: A, b: B }, + } + + #[derive(Display)] + #[display("{a}:{b}")] + enum Enum2Shared { + Two { r#a: A, b: B }, + } + + #[derive(Display)] + #[display("{pub}:{b}")] + enum Enum2SharedKeyword { + Two { r#pub: A, b: B }, + } + + #[test] + fn assert() { + assert_eq!(StructOne:: { r#thing: 8 }.to_string(), "8"); + assert_eq!(StructOneKeyword:: { r#struct: 8 }.to_string(), "8"); + assert_eq!(Enum1::::One { r#thing: 8 }.to_string(), "8"); + assert_eq!(Enum1Keyword::::One { r#struct: 8 }.to_string(), "8"); + assert_eq!(Enum1Top::::One { r#thing: 8 }.to_string(), "8"); + assert_eq!(Enum1TopKeyword::::One { r#struct: 8 }.to_string(), "8"); + assert_eq!(StructTwo:: { r#a: 8, b: 16 }.to_string(), "8:16"); + assert_eq!( + StructTwoKeyword:: { r#pub: 8, b: 16 }.to_string(), + "8:16", + ); + assert_eq!(Enum2::::Two { r#a: 8, b: 16 }.to_string(), "8:16"); + assert_eq!( + Enum2Keyword::::Two { r#pub: 8, b: 16 }.to_string(), + "8:16", + ); + assert_eq!( + Enum2Shared::::Two { r#a: 8, b: 16 }.to_string(), + "8:16", + ); + assert_eq!( + Enum2SharedKeyword::::Two { r#pub: 8, b: 16 }.to_string(), + "8:16", + ); + } + } + } + mod bound { use super::*;