Skip to content

Commit 07dc39d

Browse files
authored
Consider associative types in utils::GenericsSearch (#474)
Revealed from #473 ## Synopsis `utils::GenericsSearch` doesn't consider associative type, leading to missing trait bounds in `AsRef`/`AsMut` expansions. ```rust #[derive(AsRef)] #[as_ref(forward)] struct Forward<T: Some> { first: T::Assoc, } ``` expands as: ```rust #[allow(deprecated)] #[allow(unreachable_code)] #[automatically_derived] impl<T: Some, __AsT: ?derive_more::core::marker::Sized> derive_more::core::convert::AsRef<__AsT> for Forward<T> { #[inline] fn as_ref(&self) -> &__AsT { <T::Assoc as derive_more::core::convert::AsRef<__AsT>>::as_ref(&self.first) } } ``` but should expand as: ```rust #[allow(deprecated)] #[allow(unreachable_code)] #[automatically_derived] impl<T: Some, __AsT: ?derive_more::core::marker::Sized> derive_more::core::convert::AsRef<__AsT> for Forward<T> where T::Assoc: derive_more::core::convert::AsRef<__AsT>, { #[inline] fn as_ref(&self) -> &__AsT { <T::Assoc as derive_more::core::convert::AsRef<__AsT>>::as_ref(&self.first) } } ``` ## Solution This PR fixes this situation and covers associative types usage in `AsRef`/`AsMut` tests. ## Additionally Improves `utils::GenericsSearch` performance by early returns (stop `Visit`ing whenever something is found). Also, unit tests for `utils::GenericsSearch` are added.
1 parent e648219 commit 07dc39d

File tree

6 files changed

+1965
-1035
lines changed

6 files changed

+1965
-1035
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
3131
([#454](https://github.com/JelteF/derive_more/pull/454))
3232
- Silent no-op when `#[try_from(repr)]` attribute is not specified for `TryFrom` derive.
3333
([#458](https://github.com/JelteF/derive_more/pull/458))
34+
- Missing trait bounds in `AsRef`/`AsMut` derives when associative types are involved.
35+
([#474](https://github.com/JelteF/derive_more/pull/474))
3436

3537
## 2.0.1 - 2025-02-03
3638

impl/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,4 @@ full = [
101101
"unwrap",
102102
]
103103

104-
testing-helpers = ["dep:rustc_version"]
104+
testing-helpers = ["syn/full", "dep:rustc_version"]

impl/src/as/mod.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -189,15 +189,7 @@ impl ToTokens for Expansion<'_> {
189189

190190
let field_ref = quote! { & #mut_ self.#field_ident };
191191

192-
let generics_search = GenericsSearch {
193-
types: self.generics.type_params().map(|p| &p.ident).collect(),
194-
lifetimes: self
195-
.generics
196-
.lifetimes()
197-
.map(|p| &p.lifetime.ident)
198-
.collect(),
199-
consts: self.generics.const_params().map(|p| &p.ident).collect(),
200-
};
192+
let generics_search = GenericsSearch::from(self.generics);
201193
let field_contains_generics = generics_search.any_in(field_ty);
202194

203195
let is_blanket =

impl/src/utils.rs

Lines changed: 133 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2400,6 +2400,16 @@ mod generics_search {
24002400
pub(crate) consts: HashSet<&'s syn::Ident>,
24012401
}
24022402

2403+
impl<'s> From<&'s syn::Generics> for GenericsSearch<'s> {
2404+
fn from(value: &'s syn::Generics) -> Self {
2405+
Self {
2406+
types: value.type_params().map(|p| &p.ident).collect(),
2407+
lifetimes: value.lifetimes().map(|p| &p.lifetime.ident).collect(),
2408+
consts: value.const_params().map(|p| &p.ident).collect(),
2409+
}
2410+
}
2411+
}
2412+
24032413
impl GenericsSearch<'_> {
24042414
/// Checks the provided [`syn::Type`] to contain anything from this [`GenericsSearch`].
24052415
pub(crate) fn any_in(&self, ty: &syn::Type) -> bool {
@@ -2422,27 +2432,139 @@ mod generics_search {
24222432
}
24232433

24242434
impl<'ast> Visit<'ast> for Visitor<'_> {
2435+
fn visit_expr_path(&mut self, ep: &'ast syn::ExprPath) {
2436+
self.found |= ep
2437+
.path
2438+
.get_ident()
2439+
.is_some_and(|ident| self.search.consts.contains(ident));
2440+
2441+
if !self.found {
2442+
syn::visit::visit_expr_path(self, ep);
2443+
}
2444+
}
2445+
2446+
fn visit_lifetime(&mut self, lf: &'ast syn::Lifetime) {
2447+
self.found |= self.search.lifetimes.contains(&lf.ident);
2448+
2449+
if !self.found {
2450+
syn::visit::visit_lifetime(self, lf);
2451+
}
2452+
}
2453+
24252454
fn visit_type_path(&mut self, tp: &'ast syn::TypePath) {
24262455
self.found |= tp.path.get_ident().is_some_and(|ident| {
24272456
self.search.types.contains(ident) || self.search.consts.contains(ident)
24282457
});
24292458

2430-
syn::visit::visit_type_path(self, tp)
2431-
}
2459+
if !self.found {
2460+
// `TypeParam::AssocType` case.
2461+
self.found |= tp.path.segments.first().is_some_and(|segment| {
2462+
matches!(segment.arguments, syn::PathArguments::None)
2463+
&& self.search.types.contains(&segment.ident)
2464+
});
2465+
}
24322466

2433-
fn visit_lifetime(&mut self, lf: &'ast syn::Lifetime) {
2434-
self.found |= self.search.lifetimes.contains(&lf.ident);
2467+
if !self.found {
2468+
syn::visit::visit_type_path(self, tp)
2469+
}
2470+
}
2471+
}
24352472

2436-
syn::visit::visit_lifetime(self, lf)
2473+
#[cfg(test)]
2474+
mod spec {
2475+
use quote::ToTokens as _;
2476+
use syn::parse_quote;
2477+
2478+
use super::GenericsSearch;
2479+
2480+
#[test]
2481+
fn types() {
2482+
let generics: syn::Generics = parse_quote! { <T> };
2483+
let search = GenericsSearch::from(&generics);
2484+
2485+
for input in [
2486+
parse_quote! { T },
2487+
parse_quote! { &T },
2488+
parse_quote! { &'a T },
2489+
parse_quote! { &&'a T },
2490+
parse_quote! { Type<'a, T> },
2491+
parse_quote! { path::Type<T, 'a> },
2492+
parse_quote! { path::<'a, T>::Type },
2493+
parse_quote! { <Self as Trait<'a, T>>::Type },
2494+
parse_quote! { <Self as Trait>::Type<T, 'a> },
2495+
parse_quote! { T::Type },
2496+
parse_quote! { <T as Trait>::Type },
2497+
parse_quote! { [T] },
2498+
parse_quote! { [T; 3] },
2499+
parse_quote! { [T; _] },
2500+
parse_quote! { (T) },
2501+
parse_quote! { (T,) },
2502+
parse_quote! { (T, u8) },
2503+
parse_quote! { (u8, T) },
2504+
parse_quote! { fn(T) },
2505+
parse_quote! { fn(u8, T) },
2506+
parse_quote! { fn(_) -> T },
2507+
parse_quote! { fn(_) -> (u8, T) },
2508+
] {
2509+
assert!(
2510+
search.any_in(&input),
2511+
"cannot find type parameter `T` in type `{}`",
2512+
input.into_token_stream(),
2513+
);
2514+
}
24372515
}
24382516

2439-
fn visit_expr_path(&mut self, ep: &'ast syn::ExprPath) {
2440-
self.found |= ep
2441-
.path
2442-
.get_ident()
2443-
.is_some_and(|ident| self.search.consts.contains(ident));
2517+
#[test]
2518+
fn lifetimes() {
2519+
let generics: syn::Generics = parse_quote! { <'a> };
2520+
let search = GenericsSearch::from(&generics);
2521+
2522+
for input in [
2523+
parse_quote! { &'a T },
2524+
parse_quote! { &&'a T },
2525+
parse_quote! { Type<'a> },
2526+
parse_quote! { path::Type<'a> },
2527+
parse_quote! { path::<'a>::Type },
2528+
parse_quote! { <Self as Trait<'a>>::Type },
2529+
parse_quote! { <Self as Trait>::Type<'a> },
2530+
] {
2531+
assert!(
2532+
search.any_in(&input),
2533+
"cannot find lifetime parameter `'a` in type `{}`",
2534+
input.into_token_stream(),
2535+
);
2536+
}
2537+
}
24442538

2445-
syn::visit::visit_expr_path(self, ep)
2539+
#[test]
2540+
fn consts() {
2541+
let generics: syn::Generics = parse_quote! { <const N: usize> };
2542+
let search = GenericsSearch::from(&generics);
2543+
2544+
for input in [
2545+
parse_quote! { [_; N] },
2546+
parse_quote! { Type<N> },
2547+
#[cfg(feature = "testing-helpers")] // requires `syn/full`
2548+
parse_quote! { Type<{ N }> },
2549+
parse_quote! { path::Type<N> },
2550+
#[cfg(feature = "testing-helpers")] // requires `syn/full`
2551+
parse_quote! { path::Type<{ N }> },
2552+
parse_quote! { path::<N>::Type },
2553+
#[cfg(feature = "testing-helpers")] // requires `syn/full`
2554+
parse_quote! { path::<{ N }>::Type },
2555+
parse_quote! { <Self as Trait<N>>::Type },
2556+
#[cfg(feature = "testing-helpers")] // requires `syn/full`
2557+
parse_quote! { <Self as Trait<{ N }>>::Type },
2558+
parse_quote! { <Self as Trait>::Type<N> },
2559+
#[cfg(feature = "testing-helpers")] // requires `syn/full`
2560+
parse_quote! { <Self as Trait>::Type<{ N }> },
2561+
] {
2562+
assert!(
2563+
search.any_in(&input),
2564+
"cannot find const parameter `N` in type `{}`",
2565+
input.into_token_stream(),
2566+
);
2567+
}
24462568
}
24472569
}
24482570
}

0 commit comments

Comments
 (0)