From 16713df62ef4fd7ca7f68bae503ebea3a6a0e73e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustav=20S=C3=B6rn=C3=A4s?= Date: Tue, 18 Jun 2024 17:53:07 +0200 Subject: [PATCH] Only put Debug-like bounds on type variables --- impl/src/fmt/debug.rs | 133 ++++++++++++++++++++++++++++++++++++++++-- tests/debug.rs | 67 +++++++++++++++++++++ 2 files changed, 194 insertions(+), 6 deletions(-) diff --git a/impl/src/fmt/debug.rs b/impl/src/fmt/debug.rs index 68e873e1..ddcd44ed 100644 --- a/impl/src/fmt/debug.rs +++ b/impl/src/fmt/debug.rs @@ -24,9 +24,22 @@ pub fn expand(input: &syn::DeriveInput, _: &str) -> syn::Result { .unwrap_or_default(); let ident = &input.ident; + let type_variables = input + .generics + .params + .iter() + .filter_map(|param| match param { + syn::GenericParam::Lifetime(_) => None, + syn::GenericParam::Type(ty) => Some(&ty.ident), + syn::GenericParam::Const(_) => None, + }) + .collect(); + let (bounds, body) = match &input.data { - syn::Data::Struct(s) => expand_struct(attrs, ident, s, &attr_name), - syn::Data::Enum(e) => expand_enum(attrs, e, &attr_name), + syn::Data::Struct(s) => { + expand_struct(attrs, ident, s, type_variables, &attr_name) + } + syn::Data::Enum(e) => expand_enum(attrs, e, type_variables, &attr_name), syn::Data::Union(_) => { return Err(syn::Error::new( input.span(), @@ -64,11 +77,13 @@ fn expand_struct( attrs: ContainerAttributes, ident: &Ident, s: &syn::DataStruct, + type_variables: Vec<&syn::Ident>, attr_name: &syn::Ident, ) -> syn::Result<(Vec, TokenStream)> { let s = Expansion { attr: &attrs, fields: &s.fields, + type_variables: &type_variables, ident, attr_name, }; @@ -99,6 +114,7 @@ fn expand_struct( fn expand_enum( mut attrs: ContainerAttributes, e: &syn::DataEnum, + type_variables: Vec<&syn::Ident>, attr_name: &syn::Ident, ) -> syn::Result<(Vec, TokenStream)> { if let Some(enum_fmt) = attrs.fmt.as_ref() { @@ -136,6 +152,7 @@ fn expand_enum( let v = Expansion { attr: &attrs, fields: &variant.fields, + type_variables: &type_variables, ident, attr_name, }; @@ -195,6 +212,9 @@ struct Expansion<'a> { /// Struct or enum [`syn::Fields`]. fields: &'a syn::Fields, + /// Type variables in this struct or enum. + type_variables: &'a [&'a syn::Ident], + /// Name of the attributes, considered by this macro. attr_name: &'a syn::Ident, } @@ -334,15 +354,26 @@ impl<'a> Expansion<'a> { 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_ident = format_ident!("{trait_name}"); + out.extend(fmt.bounded_types(self.fields).filter_map( + |(ty, trait_name)| { + if !self.contains_type_variable(ty) { + return None; + } - parse_quote! { #ty: derive_more::core::fmt::#trait_ident } - })); + let trait_ident = format_ident!("{trait_name}"); + + Some(parse_quote! { #ty: derive_more::core::fmt::#trait_ident }) + }, + )); Ok(out) } else { self.fields.iter().try_fold(out, |mut out, field| { let ty = &field.ty; + + if !self.contains_type_variable(ty) { + return Ok(out); + } + match FieldAttribute::parse_attrs(&field.attrs, self.attr_name)? .map(Spanning::into_inner) { @@ -362,4 +393,94 @@ impl<'a> Expansion<'a> { }) } } + + fn contains_type_variable(&self, ty: &syn::Type) -> bool { + match ty { + syn::Type::Path(syn::TypePath { qself, path }) => { + if let Some(qself) = qself { + if self.contains_type_variable(&qself.ty) { + return true; + } + } + + if let Some(ident) = path.get_ident() { + self.type_variables + .iter() + .any(|type_var| *type_var == ident) + } else { + path.segments.iter().any(|segment| { + self.type_variables + .iter() + .any(|type_var| *type_var == &segment.ident) + || match &segment.arguments { + syn::PathArguments::None => false, + syn::PathArguments::AngleBracketed( + syn::AngleBracketedGenericArguments { + args, .. + }, + ) => args.iter().any(|generic| match generic { + syn::GenericArgument::Type(ty) + | syn::GenericArgument::AssocType( + syn::AssocType { ty, .. }, + ) => self.contains_type_variable(ty), + + syn::GenericArgument::Lifetime(_) + | syn::GenericArgument::Const(_) + | syn::GenericArgument::AssocConst(_) + | syn::GenericArgument::Constraint(_) => false, + _ => false, + }), + syn::PathArguments::Parenthesized( + syn::ParenthesizedGenericArguments { + inputs, + output, + .. + }, + ) => { + inputs + .iter() + .any(|ty| self.contains_type_variable(ty)) + || match output { + syn::ReturnType::Default => false, + syn::ReturnType::Type(_, ty) => { + self.contains_type_variable(ty) + } + } + } + } + }) + } + } + + syn::Type::Array(syn::TypeArray { elem, .. }) + | syn::Type::Group(syn::TypeGroup { elem, .. }) + | syn::Type::Paren(syn::TypeParen { elem, .. }) + | syn::Type::Ptr(syn::TypePtr { elem, .. }) + | syn::Type::Reference(syn::TypeReference { elem, .. }) + | syn::Type::Slice(syn::TypeSlice { elem, .. }) => { + self.contains_type_variable(elem) + } + + syn::Type::BareFn(syn::TypeBareFn { inputs, output, .. }) => { + inputs + .iter() + .any(|arg| self.contains_type_variable(&arg.ty)) + || match output { + syn::ReturnType::Default => false, + syn::ReturnType::Type(_, ty) => self.contains_type_variable(ty), + } + } + syn::Type::Tuple(syn::TypeTuple { elems, .. }) => { + elems.iter().any(|ty| self.contains_type_variable(ty)) + } + + syn::Type::ImplTrait(_) => false, + syn::Type::Infer(_) => false, + syn::Type::Macro(_) => false, + syn::Type::Never(_) => false, + syn::Type::TraitObject(_) => false, + syn::Type::Verbatim(_) => false, + _ => false, + } + } } diff --git a/tests/debug.rs b/tests/debug.rs index 15e7f12f..43e00091 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -1932,3 +1932,70 @@ mod complex_enum_syntax { assert_eq!(format!("{:?}", Enum::A), "A"); } } + +// See: https://github.com/JelteF/derive_more/issues/363 +mod type_variables { + #[cfg(not(feature = "std"))] + use alloc::{boxed::Box, format, vec, vec::Vec}; + + use derive_more::Debug; + + #[derive(Debug)] + struct ItemStruct { + next: Option>, + } + + #[derive(Debug)] + struct ItemTuple(Option>); + + #[derive(Debug)] + #[debug("Item({_0:?})")] + struct ItemTupleContainerFmt(Option>); + + #[derive(Debug)] + enum ItemEnum { + Node { children: Vec, inner: i32 }, + Leaf { inner: i32 }, + } + + #[test] + fn assert() { + assert_eq!( + format!( + "{:?}", + ItemStruct { + next: Some(Box::new(ItemStruct { next: None })) + } + ), + "ItemStruct { next: Some(ItemStruct { next: None }) }" + ); + + assert_eq!( + format!("{:?}", ItemTuple(Some(Box::new(ItemTuple(None)))),), + "ItemTuple(Some(ItemTuple(None)))" + ); + + assert_eq!( + format!( + "{:?}", + ItemTupleContainerFmt(Some(Box::new(ItemTupleContainerFmt(None)))), + ), + "Item(Some(Item(None)))" + ); + + let item = ItemEnum::Node { + children: vec![ + ItemEnum::Node { + children: vec![], + inner: 0, + }, + ItemEnum::Leaf { inner: 1 }, + ], + inner: 2, + }; + assert_eq!( + format!("{item:?}"), + "Node { children: [Node { children: [], inner: 0 }, Leaf { inner: 1 }], inner: 2 }", + ) + } +}