From d7c4a68f205fc4074b68a78ea70c4deaf2bf3db5 Mon Sep 17 00:00:00 2001 From: Kai Ren Date: Tue, 20 Aug 2024 13:28:05 +0200 Subject: [PATCH] Consider associated types of type parameters for implied bounds (#399) Related to #387 ## Synopsis After #387, the following snippet fails to compile: ```rust #[derive(Debug)] struct AssocType { iter: I, elem: Option, } ``` This happens, because the implied bound `Option: Debug` is not generated. ## Solution Correct the `ContainsGenericsExt::contains_generics()` implementations to consider associated types of the type parameter. --- CHANGELOG.md | 5 +++++ impl/src/fmt/mod.rs | 8 ++++++-- tests/debug.rs | 49 ++++++++++++++++++++++++++++++++++++++++++--- tests/display.rs | 49 ++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 103 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 460b4151..49e08e00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## 1.0.1 - Unreleased +### Fixed + +- Associated types of type parameters not being treated as generics in `Debug` + and `Display` expansions. + ([#399](https://github.com/JelteF/derive_more/pull/399)) ## 1.0.0 - 2024-08-07 diff --git a/impl/src/fmt/mod.rs b/impl/src/fmt/mod.rs index 6044745d..e09d3d68 100644 --- a/impl/src/fmt/mod.rs +++ b/impl/src/fmt/mod.rs @@ -632,8 +632,12 @@ impl ContainsGenericsExt for syn::Path { } self.segments .iter() - .any(|segment| match &segment.arguments { - syn::PathArguments::None => false, + .enumerate() + .any(|(n, segment)| match &segment.arguments { + syn::PathArguments::None => { + // `TypeParam::AssocType` case. + (n == 0) && type_params.contains(&&segment.ident) + } syn::PathArguments::AngleBracketed( syn::AngleBracketedGenericArguments { args, .. }, ) => args.iter().any(|generic| match generic { diff --git a/tests/debug.rs b/tests/debug.rs index 40bdf19a..d88903b3 100644 --- a/tests/debug.rs +++ b/tests/debug.rs @@ -2019,11 +2019,13 @@ mod type_variables { mod our_alloc { #[cfg(not(feature = "std"))] pub use alloc::{boxed::Box, format, vec, vec::Vec}; + #[cfg(not(feature = "std"))] + pub use core::iter; #[cfg(feature = "std")] - pub use std::{boxed::Box, format, vec, vec::Vec}; + pub use std::{boxed::Box, format, iter, vec, vec::Vec}; } - use our_alloc::{format, vec, Box, Vec}; + use our_alloc::{format, iter, vec, Box, Vec}; use derive_more::Debug; @@ -2110,6 +2112,25 @@ mod type_variables { t: Box>, } + #[derive(Debug)] + struct AssocType { + iter: I, + elem: Option, + } + + #[derive(derive_more::Debug)] + struct CollidedPathName { + item: Item, + elem: Option, + } + + mod some_path { + use super::Debug; + + #[derive(Debug)] + pub struct Item; + } + #[test] fn assert() { assert_eq!( @@ -2148,6 +2169,28 @@ mod type_variables { assert_eq!( format!("{item:?}"), "Node { children: [Node { children: [], inner: 0 }, Leaf { inner: 1 }], inner: 2 }", - ) + ); + + assert_eq!( + format!( + "{:?}", + AssocType { + iter: iter::empty::(), + elem: None, + }, + ), + "AssocType { iter: Empty, elem: None }", + ); + + assert_eq!( + format!( + "{:?}", + CollidedPathName { + item: true, + elem: None, + }, + ), + "CollidedPathName { item: true, elem: None }", + ); } } diff --git a/tests/display.rs b/tests/display.rs index 2a9dbb8b..c3cf1c14 100644 --- a/tests/display.rs +++ b/tests/display.rs @@ -2388,11 +2388,13 @@ mod type_variables { mod our_alloc { #[cfg(not(feature = "std"))] pub use alloc::{boxed::Box, format, vec::Vec}; + #[cfg(not(feature = "std"))] + pub use core::iter; #[cfg(feature = "std")] - pub use std::{boxed::Box, format, vec::Vec}; + pub use std::{boxed::Box, format, iter, vec::Vec}; } - use our_alloc::{format, Box}; + use our_alloc::{format, iter, Box}; // We want `Vec` in scope to test that code generation works if it is there. #[allow(unused_imports)] @@ -2512,6 +2514,25 @@ mod type_variables { t: Box>, } + #[derive(Display)] + #[display("{iter:?} with {elem:?}")] + struct AssocType { + iter: I, + elem: Option, + } + + #[derive(Display)] + #[display("{item:?} with {elem:?}")] + struct CollidedPathName { + item: Item, + elem: Option, + } + + mod some_path { + #[derive(Debug)] + pub struct Item; + } + #[test] fn assert() { assert_eq!( @@ -2560,6 +2581,28 @@ mod type_variables { assert_eq!( format!("{item}"), "Some(Variant2 { next: OptionalBox { inner: None } })", - ) + ); + + assert_eq!( + format!( + "{}", + AssocType { + iter: iter::empty::(), + elem: None, + }, + ), + "Empty with None", + ); + + assert_eq!( + format!( + "{}", + CollidedPathName { + item: false, + elem: Some(some_path::Item), + }, + ), + "false with Some(Item)", + ); } }