Skip to content

Commit ae65912

Browse files
committed
Auto merge of rust-lang#13763 - rami3l:fix/gen-partial-eq-generic, r=Veykril
fix: add generic `TypeBoundList` in generated derivable impl Potentially fixes rust-lang#13727. Continuing with the work in rust-lang#13732, this fix tries to add correct type bounds in the generated `impl` block: ```diff enum Either<T, U> { Left(T), Right(U), } - impl<T, U> PartialEq for Either<T, U> { + impl<T: PartialEq, U: PartialEq> PartialEq for Either<T, U> { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Left(l0), Self::Left(r0)) => l0 == r0, (Self::Right(l0), Self::Right(r0)) => l0 == r0, _ => false, } } } ```
2 parents fe8ee9c + cfa9149 commit ae65912

File tree

5 files changed

+130
-23
lines changed

5 files changed

+130
-23
lines changed

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use ide_db::{famous_defs::FamousDefs, RootDatabase};
22
use syntax::ast::{self, AstNode, HasName};
33

4-
use crate::{utils::generate_trait_impl_text, AssistContext, AssistId, AssistKind, Assists};
4+
use crate::{
5+
utils::generate_trait_impl_text_intransitive, AssistContext, AssistId, AssistKind, Assists,
6+
};
57

68
// Assist: generate_from_impl_for_enum
79
//
@@ -70,7 +72,7 @@ pub(crate) fn generate_from_impl_for_enum(
7072
}}"#
7173
)
7274
};
73-
let from_impl = generate_trait_impl_text(&enum_, &from_trait, &impl_code);
75+
let from_impl = generate_trait_impl_text_intransitive(&enum_, &from_trait, &impl_code);
7476
edit.insert(start_offset, from_impl);
7577
},
7678
)

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use syntax::ast::{self, AstNode, HasName};
22

33
use crate::{
4-
utils::{generate_impl_text, generate_trait_impl_text},
4+
utils::{generate_impl_text, generate_trait_impl_text_intransitive},
55
AssistContext, AssistId, AssistKind, Assists,
66
};
77

@@ -89,11 +89,11 @@ pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
8989
let start_offset = nominal.syntax().text_range().end();
9090
match ctx.config.snippet_cap {
9191
Some(cap) => {
92-
let snippet = generate_trait_impl_text(&nominal, "$0", "");
92+
let snippet = generate_trait_impl_text_intransitive(&nominal, "$0", "");
9393
edit.insert_snippet(cap, start_offset, snippet);
9494
}
9595
None => {
96-
let text = generate_trait_impl_text(&nominal, "", "");
96+
let text = generate_trait_impl_text_intransitive(&nominal, "", "");
9797
edit.insert(start_offset, text);
9898
}
9999
}

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

+63-1
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

+43-11
Original file line numberDiff line numberDiff line change
@@ -434,35 +434,67 @@ pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Opti
434434
Some(end)
435435
}
436436

437-
// Generates the surrounding `impl Type { <code> }` including type and lifetime
438-
// parameters
437+
/// Generates the surrounding `impl Type { <code> }` including type and lifetime
438+
/// parameters.
439439
pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String {
440-
generate_impl_text_inner(adt, None, code)
440+
generate_impl_text_inner(adt, None, true, code)
441441
}
442442

443-
// Generates the surrounding `impl <trait> for Type { <code> }` including type
444-
// and lifetime parameters
443+
/// Generates the surrounding `impl <trait> for Type { <code> }` including type
444+
/// and lifetime parameters, with `<trait>` appended to `impl`'s generic parameters' bounds.
445+
///
446+
/// This is useful for traits like `PartialEq`, since `impl<T> PartialEq for U<T>` often requires `T: PartialEq`.
445447
pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: &str) -> String {
446-
generate_impl_text_inner(adt, Some(trait_text), code)
448+
generate_impl_text_inner(adt, Some(trait_text), true, code)
449+
}
450+
451+
/// Generates the surrounding `impl <trait> for Type { <code> }` including type
452+
/// and lifetime parameters, with `impl`'s generic parameters' bounds kept as-is.
453+
///
454+
/// This is useful for traits like `From<T>`, since `impl<T> From<T> for U<T>` doesn't require `T: From<T>`.
455+
pub(crate) fn generate_trait_impl_text_intransitive(
456+
adt: &ast::Adt,
457+
trait_text: &str,
458+
code: &str,
459+
) -> String {
460+
generate_impl_text_inner(adt, Some(trait_text), false, code)
447461
}
448462

449-
fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str) -> String {
463+
fn generate_impl_text_inner(
464+
adt: &ast::Adt,
465+
trait_text: Option<&str>,
466+
trait_is_transitive: bool,
467+
code: &str,
468+
) -> String {
450469
// Ensure lifetime params are before type & const params
451470
let generic_params = adt.generic_param_list().map(|generic_params| {
452471
let lifetime_params =
453472
generic_params.lifetime_params().map(ast::GenericParam::LifetimeParam);
454-
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
473+
let ty_or_const_params = generic_params.type_or_const_params().map(|param| {
456474
match param {
457475
ast::TypeOrConstParam::Type(param) => {
458476
let param = param.clone_for_update();
477+
// remove defaults since they can't be specified in impls
459478
param.remove_default();
460-
Some(ast::GenericParam::TypeParam(param))
479+
let mut bounds =
480+
param.type_bound_list().map_or_else(Vec::new, |it| it.bounds().collect());
481+
if let Some(trait_) = trait_text {
482+
// Add the current trait to `bounds` if the trait is transitive,
483+
// meaning `impl<T> Trait for U<T>` requires `T: Trait`.
484+
if trait_is_transitive {
485+
bounds.push(make::type_bound(trait_));
486+
}
487+
};
488+
// `{ty_param}: {bounds}`
489+
let param =
490+
make::type_param(param.name().unwrap(), make::type_bound_list(bounds));
491+
ast::GenericParam::TypeParam(param)
461492
}
462493
ast::TypeOrConstParam::Const(param) => {
463494
let param = param.clone_for_update();
495+
// remove defaults since they can't be specified in impls
464496
param.remove_default();
465-
Some(ast::GenericParam::ConstParam(param))
497+
ast::GenericParam::ConstParam(param)
466498
}
467499
}
468500
});

crates/syntax/src/ast/make.rs

+17-6
Original file line numberDiff line numberDiff line change
@@ -719,12 +719,23 @@ 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-
};
727-
ast_from_text(&format!("fn f<{name}{bound}>() {{ }}"))
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 bounds = bounds.map_or_else(String::new, |it| format!(": {it}"));
738+
ast_from_text(&format!("fn f<{name}{bounds}>() {{ }}"))
728739
}
729740

730741
pub fn lifetime_param(lifetime: ast::Lifetime) -> ast::LifetimeParam {

0 commit comments

Comments
 (0)