Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
([#454](https://github.com/JelteF/derive_more/pull/454))
- Silent no-op when `#[try_from(repr)]` attribute is not specified for `TryFrom` derive.
([#458](https://github.com/JelteF/derive_more/pull/458))
- Missing trait bounds in `AsRef`/`AsMut` derives when associative types are involved.
([#474](https://github.com/JelteF/derive_more/pull/474))

## 2.0.1 - 2025-02-03

Expand Down
2 changes: 1 addition & 1 deletion impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,4 @@ full = [
"unwrap",
]

testing-helpers = ["dep:rustc_version"]
testing-helpers = ["syn/full", "dep:rustc_version"]
10 changes: 1 addition & 9 deletions impl/src/as/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,15 +189,7 @@ impl ToTokens for Expansion<'_> {

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

let generics_search = GenericsSearch {
types: self.generics.type_params().map(|p| &p.ident).collect(),
lifetimes: self
.generics
.lifetimes()
.map(|p| &p.lifetime.ident)
.collect(),
consts: self.generics.const_params().map(|p| &p.ident).collect(),
};
let generics_search = GenericsSearch::from(self.generics);
let field_contains_generics = generics_search.any_in(field_ty);

let is_blanket =
Expand Down
144 changes: 133 additions & 11 deletions impl/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2400,6 +2400,16 @@ mod generics_search {
pub(crate) consts: HashSet<&'s syn::Ident>,
}

impl<'s> From<&'s syn::Generics> for GenericsSearch<'s> {
fn from(value: &'s syn::Generics) -> Self {
Self {
types: value.type_params().map(|p| &p.ident).collect(),
lifetimes: value.lifetimes().map(|p| &p.lifetime.ident).collect(),
consts: value.const_params().map(|p| &p.ident).collect(),
}
}
}

impl GenericsSearch<'_> {
/// Checks the provided [`syn::Type`] to contain anything from this [`GenericsSearch`].
pub(crate) fn any_in(&self, ty: &syn::Type) -> bool {
Expand All @@ -2422,27 +2432,139 @@ mod generics_search {
}

impl<'ast> Visit<'ast> for Visitor<'_> {
fn visit_expr_path(&mut self, ep: &'ast syn::ExprPath) {
self.found |= ep
.path
.get_ident()
.is_some_and(|ident| self.search.consts.contains(ident));

if !self.found {
syn::visit::visit_expr_path(self, ep);
}
}

fn visit_lifetime(&mut self, lf: &'ast syn::Lifetime) {
self.found |= self.search.lifetimes.contains(&lf.ident);

if !self.found {
syn::visit::visit_lifetime(self, lf);
}
}

fn visit_type_path(&mut self, tp: &'ast syn::TypePath) {
self.found |= tp.path.get_ident().is_some_and(|ident| {
self.search.types.contains(ident) || self.search.consts.contains(ident)
});

syn::visit::visit_type_path(self, tp)
}
if !self.found {
// `TypeParam::AssocType` case.
self.found |= tp.path.segments.first().is_some_and(|segment| {
matches!(segment.arguments, syn::PathArguments::None)
&& self.search.types.contains(&segment.ident)
});
}

fn visit_lifetime(&mut self, lf: &'ast syn::Lifetime) {
self.found |= self.search.lifetimes.contains(&lf.ident);
if !self.found {
syn::visit::visit_type_path(self, tp)
}
}
}

syn::visit::visit_lifetime(self, lf)
#[cfg(test)]
mod spec {
use quote::ToTokens as _;
use syn::parse_quote;

use super::GenericsSearch;

#[test]
fn types() {
let generics: syn::Generics = parse_quote! { <T> };
let search = GenericsSearch::from(&generics);

for input in [
parse_quote! { T },
parse_quote! { &T },
parse_quote! { &'a T },
parse_quote! { &&'a T },
parse_quote! { Type<'a, T> },
parse_quote! { path::Type<T, 'a> },
parse_quote! { path::<'a, T>::Type },
parse_quote! { <Self as Trait<'a, T>>::Type },
parse_quote! { <Self as Trait>::Type<T, 'a> },
parse_quote! { T::Type },
parse_quote! { <T as Trait>::Type },
parse_quote! { [T] },
parse_quote! { [T; 3] },
parse_quote! { [T; _] },
parse_quote! { (T) },
parse_quote! { (T,) },
parse_quote! { (T, u8) },
parse_quote! { (u8, T) },
parse_quote! { fn(T) },
parse_quote! { fn(u8, T) },
parse_quote! { fn(_) -> T },
parse_quote! { fn(_) -> (u8, T) },
] {
assert!(
search.any_in(&input),
"cannot find type parameter `T` in type `{}`",
input.into_token_stream(),
);
}
}

fn visit_expr_path(&mut self, ep: &'ast syn::ExprPath) {
self.found |= ep
.path
.get_ident()
.is_some_and(|ident| self.search.consts.contains(ident));
#[test]
fn lifetimes() {
let generics: syn::Generics = parse_quote! { <'a> };
let search = GenericsSearch::from(&generics);

for input in [
parse_quote! { &'a T },
parse_quote! { &&'a T },
parse_quote! { Type<'a> },
parse_quote! { path::Type<'a> },
parse_quote! { path::<'a>::Type },
parse_quote! { <Self as Trait<'a>>::Type },
parse_quote! { <Self as Trait>::Type<'a> },
] {
assert!(
search.any_in(&input),
"cannot find lifetime parameter `'a` in type `{}`",
input.into_token_stream(),
);
}
}

syn::visit::visit_expr_path(self, ep)
#[test]
fn consts() {
let generics: syn::Generics = parse_quote! { <const N: usize> };
let search = GenericsSearch::from(&generics);

for input in [
parse_quote! { [_; N] },
parse_quote! { Type<N> },
#[cfg(feature = "testing-helpers")] // requires `syn/full`
parse_quote! { Type<{ N }> },
parse_quote! { path::Type<N> },
#[cfg(feature = "testing-helpers")] // requires `syn/full`
parse_quote! { path::Type<{ N }> },
parse_quote! { path::<N>::Type },
#[cfg(feature = "testing-helpers")] // requires `syn/full`
parse_quote! { path::<{ N }>::Type },
parse_quote! { <Self as Trait<N>>::Type },
#[cfg(feature = "testing-helpers")] // requires `syn/full`
parse_quote! { <Self as Trait<{ N }>>::Type },
parse_quote! { <Self as Trait>::Type<N> },
#[cfg(feature = "testing-helpers")] // requires `syn/full`
parse_quote! { <Self as Trait>::Type<{ N }> },
] {
assert!(
search.any_in(&input),
"cannot find const parameter `N` in type `{}`",
input.into_token_stream(),
);
}
}
}
}
Loading