fix: Fix loses associated bounds for replace_derive_with_manual_impl

Example
---
```rust
#[derive(Clo$0ne)]
struct Foo<T: core::ops::Deref>(T::Target);
```

**Before this PR**

```rust
struct Foo<T: core::ops::Deref>(T::Target);

impl<T: core::ops::Deref + Clone> Clone for Foo<T> {
    $0fn clone(&self) -> Self {
        Self(self.0.clone())
    }
}
```

**After this PR**

```rust
struct Foo<T: core::ops::Deref>(T::Target);

impl<T: core::ops::Deref + Clone> Clone for Foo<T>
where T::Target: Clone
{
    $0fn clone(&self) -> Self {
        Self(self.0.clone())
    }
}
```
This commit is contained in:
A4-Tacks 2026-02-04 12:22:42 +08:00
parent 492e3027a6
commit 9a84e66021
No known key found for this signature in database
GPG key ID: 9E63F956E66DD9C7
2 changed files with 75 additions and 1 deletions

View file

@ -1307,6 +1307,29 @@ impl<T: Clone> Clone for Foo<T> {
)
}
#[test]
fn add_custom_impl_clone_generic_tuple_struct_with_associated() {
check_assist(
replace_derive_with_manual_impl,
r#"
//- minicore: clone, derive, deref
#[derive(Clo$0ne)]
struct Foo<T: core::ops::Deref>(T::Target);
"#,
r#"
struct Foo<T: core::ops::Deref>(T::Target);
impl<T: core::ops::Deref + Clone> Clone for Foo<T>
where T::Target: Clone
{
$0fn clone(&self) -> Self {
Self(self.0.clone())
}
}
"#,
)
}
#[test]
fn test_ignore_derive_macro_without_input() {
check_assist_not_applicable(

View file

@ -15,6 +15,7 @@ use ide_db::{
path_transform::PathTransform,
syntax_helpers::{node_ext::preorder_expr, prettify_macro_expansion},
};
use itertools::Itertools;
use stdx::format_to;
use syntax::{
AstNode, AstToken, Direction, NodeOrToken, SourceFile,
@ -766,6 +767,11 @@ fn generate_impl_inner(
});
let generic_args =
generic_params.as_ref().map(|params| params.to_generic_args().clone_for_update());
let trait_where_clause = trait_
.as_ref()
.zip(generic_params.as_ref())
.and_then(|(trait_, params)| generic_param_associated_bounds(adt, trait_, params));
let ty = make::ty_path(make::ext::ident_path(&adt.name().unwrap().text()));
let cfg_attrs =
@ -781,7 +787,7 @@ fn generate_impl_inner(
false,
trait_,
ty,
None,
trait_where_clause,
adt.where_clause(),
body,
),
@ -790,6 +796,51 @@ fn generate_impl_inner(
.clone_for_update()
}
fn generic_param_associated_bounds(
adt: &ast::Adt,
trait_: &ast::Type,
generic_params: &ast::GenericParamList,
) -> Option<ast::WhereClause> {
let in_type_params = |name: &ast::NameRef| {
generic_params
.generic_params()
.filter_map(|param| match param {
ast::GenericParam::TypeParam(type_param) => type_param.name(),
_ => None,
})
.any(|param| param.text() == name.text())
};
let adt_body = match adt {
ast::Adt::Enum(e) => e.variant_list().map(|it| it.syntax().clone()),
ast::Adt::Struct(s) => s.field_list().map(|it| it.syntax().clone()),
ast::Adt::Union(u) => u.record_field_list().map(|it| it.syntax().clone()),
};
let mut trait_where_clause = adt_body
.into_iter()
.flat_map(|it| it.descendants())
.filter_map(ast::Path::cast)
.filter_map(|path| {
let qualifier = path.qualifier()?.as_single_segment()?;
let qualifier = qualifier
.name_ref()
.or_else(|| match qualifier.type_anchor()?.ty()? {
ast::Type::PathType(path_type) => path_type.path()?.as_single_name_ref(),
_ => None,
})
.filter(in_type_params)?;
Some((qualifier, path.segment()?.name_ref()?))
})
.map(|(qualifier, assoc_name)| {
let segments = [qualifier, assoc_name].map(make::path_segment);
let path = make::path_from_segments(segments, false);
let bounds = Some(make::type_bound(trait_.clone()));
make::where_pred(either::Either::Right(make::ty_path(path)), bounds)
})
.unique_by(|it| it.syntax().to_string())
.peekable();
trait_where_clause.peek().is_some().then(|| make::where_clause(trait_where_clause))
}
pub(crate) fn add_method_to_adt(
builder: &mut SourceChangeBuilder,
adt: &ast::Adt,