Skip to content

Commit 12b05d2

Browse files
committed
fix: add generic TypeBoundList in generated derivable impl
1 parent 4596847 commit 12b05d2

File tree

3 files changed

+98
-7
lines changed

3 files changed

+98
-7
lines changed

crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,68 @@ impl PartialEq for Foo {
994994
)
995995
}
996996

997+
#[test]
998+
fn add_custom_impl_partial_eq_tuple_enum_generic() {
999+
check_assist(
1000+
replace_derive_with_manual_impl,
1001+
r#"
1002+
//- minicore: eq, derive
1003+
#[derive(Partial$0Eq)]
1004+
enum Either<T, U> {
1005+
Left(T),
1006+
Right(U),
1007+
}
1008+
"#,
1009+
r#"
1010+
enum Either<T, U> {
1011+
Left(T),
1012+
Right(U),
1013+
}
1014+
1015+
impl<T: PartialEq, U: PartialEq> PartialEq for Either<T, U> {
1016+
$0fn eq(&self, other: &Self) -> bool {
1017+
match (self, other) {
1018+
(Self::Left(l0), Self::Left(r0)) => l0 == r0,
1019+
(Self::Right(l0), Self::Right(r0)) => l0 == r0,
1020+
_ => false,
1021+
}
1022+
}
1023+
}
1024+
"#,
1025+
)
1026+
}
1027+
1028+
#[test]
1029+
fn add_custom_impl_partial_eq_tuple_enum_generic_existing_bounds() {
1030+
check_assist(
1031+
replace_derive_with_manual_impl,
1032+
r#"
1033+
//- minicore: eq, derive
1034+
#[derive(Partial$0Eq)]
1035+
enum Either<T: PartialEq + Error, U: Clone> {
1036+
Left(T),
1037+
Right(U),
1038+
}
1039+
"#,
1040+
r#"
1041+
enum Either<T: PartialEq + Error, U: Clone> {
1042+
Left(T),
1043+
Right(U),
1044+
}
1045+
1046+
impl<T: PartialEq + Error, U: Clone + PartialEq> PartialEq for Either<T, U> {
1047+
$0fn eq(&self, other: &Self) -> bool {
1048+
match (self, other) {
1049+
(Self::Left(l0), Self::Left(r0)) => l0 == r0,
1050+
(Self::Right(l0), Self::Right(r0)) => l0 == r0,
1051+
_ => false,
1052+
}
1053+
}
1054+
}
1055+
"#,
1056+
)
1057+
}
1058+
9971059
#[test]
9981060
fn add_custom_impl_partial_eq_record_enum() {
9991061
check_assist(
@@ -1170,7 +1232,7 @@ struct Foo<T, U> {
11701232
bar: U,
11711233
}
11721234
1173-
impl<T, U> Default for Foo<T, U> {
1235+
impl<T: Default, U: Default> Default for Foo<T, U> {
11741236
$0fn default() -> Self {
11751237
Self { foo: Default::default(), bar: Default::default() }
11761238
}

crates/ide-assists/src/utils.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::ops;
55
pub(crate) use gen_trait_fn_body::gen_trait_fn_body;
66
use hir::{db::HirDatabase, HirDisplay, Semantics};
77
use ide_db::{famous_defs::FamousDefs, path_transform::PathTransform, RootDatabase, SnippetCap};
8+
use itertools::Itertools;
89
use stdx::format_to;
910
use syntax::{
1011
ast::{
@@ -452,15 +453,32 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str
452453
let lifetime_params =
453454
generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam);
454455
let ty_or_const_params = generic_params.type_or_const_params().filter_map(|param| {
455-
// remove defaults since they can't be specified in impls
456456
match param {
457457
ast::TypeOrConstParam::Type(param) => {
458458
let param = param.clone_for_update();
459+
// remove defaults since they can't be specified in impls
459460
param.remove_default();
461+
let mut bounds = param
462+
.type_bound_list()
463+
.map_or_else(Vec::new, |it| it.bounds().collect_vec());
464+
// `{ty_param}: {trait_text}`
465+
if let Some(trait_) = trait_text {
466+
// Defense against the following cases:
467+
// - The trait is undetermined, e.g. `$0`.
468+
// - The trait is a `From`, e.g. `From<T>`.
469+
if !trait_.starts_with('$')
470+
&& !matches!(trait_.split_once('<'), Some((left, _right)) if left.trim() == "From")
471+
{
472+
bounds.push(make::type_bound(trait_));
473+
}
474+
};
475+
let param =
476+
make::type_param(param.name().unwrap(), make::type_bound_list(bounds));
460477
Some(ast::GenericParam::TypeParam(param))
461478
}
462479
ast::TypeOrConstParam::Const(param) => {
463480
let param = param.clone_for_update();
481+
// remove defaults since they can't be specified in impls
464482
param.remove_default();
465483
Some(ast::GenericParam::ConstParam(param))
466484
}

crates/syntax/src/ast/make.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -719,11 +719,22 @@ pub fn param_list(
719719
ast_from_text(&list)
720720
}
721721

722-
pub fn type_param(name: ast::Name, ty: Option<ast::TypeBoundList>) -> ast::TypeParam {
723-
let bound = match ty {
724-
Some(it) => format!(": {it}"),
725-
None => String::new(),
726-
};
722+
pub fn type_bound(bound: &str) -> ast::TypeBound {
723+
ast_from_text(&format!("fn f<T: {bound}>() {{ }}"))
724+
}
725+
726+
pub fn type_bound_list(
727+
bounds: impl IntoIterator<Item = ast::TypeBound>,
728+
) -> Option<ast::TypeBoundList> {
729+
let bounds = bounds.into_iter().map(|it| it.to_string()).unique().join(" + ");
730+
if bounds.is_empty() {
731+
return None;
732+
}
733+
Some(ast_from_text(&format!("fn f<T: {bounds}>() {{ }}")))
734+
}
735+
736+
pub fn type_param(name: ast::Name, bounds: Option<ast::TypeBoundList>) -> ast::TypeParam {
737+
let bound = bounds.map_or_else(String::new, |it| format!(": {it}"));
727738
ast_from_text(&format!("fn f<{name}{bound}>() {{ }}"))
728739
}
729740

0 commit comments

Comments
 (0)