Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 27c9c2f

Browse files
committed
Auto merge of rust-lang#13961 - lowr:fix/impl-missing-members-type-neq, r=Veykril
fix: don't generate `PartialEq`/`PartialOrd` methods body for types don't match Fixes rust-lang#12985 This PR changes the implementation of well-known trait methods body generation so that it takes generic arguments of traits into account and does not generate `PartialEq`/`PartialOrd` methods body when the self type and rhs type don't match. I took this opportunity to add `hir::TraitRef`, which has been suggested by a FIXME note. I didn't change the signature of the existing method `hir::Impl::trait_(self, db) -> Option<Trait>` as suggested by FIXME but added a new method because you quite often only want to know the trait rather than `TraitRef`s.
2 parents ff4d55e + fc56cac commit 27c9c2f

File tree

4 files changed

+131
-22
lines changed

4 files changed

+131
-22
lines changed

crates/hir/src/lib.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2791,14 +2791,19 @@ impl Impl {
27912791
all
27922792
}
27932793

2794-
// FIXME: the return type is wrong. This should be a hir version of
2795-
// `TraitRef` (to account for parameters and qualifiers)
27962794
pub fn trait_(self, db: &dyn HirDatabase) -> Option<Trait> {
2797-
let trait_ref = db.impl_trait(self.id)?.skip_binders().clone();
2798-
let id = hir_ty::from_chalk_trait_id(trait_ref.trait_id);
2795+
let trait_ref = db.impl_trait(self.id)?;
2796+
let id = trait_ref.skip_binders().hir_trait_id();
27992797
Some(Trait { id })
28002798
}
28012799

2800+
pub fn trait_ref(self, db: &dyn HirDatabase) -> Option<TraitRef> {
2801+
let substs = TyBuilder::placeholder_subst(db, self.id);
2802+
let trait_ref = db.impl_trait(self.id)?.substitute(Interner, &substs);
2803+
let resolver = self.id.resolver(db.upcast());
2804+
Some(TraitRef::new_with_resolver(db, &resolver, trait_ref))
2805+
}
2806+
28022807
pub fn self_ty(self, db: &dyn HirDatabase) -> Type {
28032808
let resolver = self.id.resolver(db.upcast());
28042809
let substs = TyBuilder::placeholder_subst(db, self.id);
@@ -2824,6 +2829,48 @@ impl Impl {
28242829
}
28252830
}
28262831

2832+
#[derive(Clone, PartialEq, Eq, Debug)]
2833+
pub struct TraitRef {
2834+
env: Arc<TraitEnvironment>,
2835+
trait_ref: hir_ty::TraitRef,
2836+
}
2837+
2838+
impl TraitRef {
2839+
pub(crate) fn new_with_resolver(
2840+
db: &dyn HirDatabase,
2841+
resolver: &Resolver,
2842+
trait_ref: hir_ty::TraitRef,
2843+
) -> TraitRef {
2844+
let env = resolver.generic_def().map_or_else(
2845+
|| Arc::new(TraitEnvironment::empty(resolver.krate())),
2846+
|d| db.trait_environment(d),
2847+
);
2848+
TraitRef { env, trait_ref }
2849+
}
2850+
2851+
pub fn trait_(&self) -> Trait {
2852+
let id = self.trait_ref.hir_trait_id();
2853+
Trait { id }
2854+
}
2855+
2856+
pub fn self_ty(&self) -> Type {
2857+
let ty = self.trait_ref.self_type_parameter(Interner);
2858+
Type { env: self.env.clone(), ty }
2859+
}
2860+
2861+
/// Returns `idx`-th argument of this trait reference if it is a type argument. Note that the
2862+
/// first argument is the `Self` type.
2863+
pub fn get_type_argument(&self, idx: usize) -> Option<Type> {
2864+
self.trait_ref
2865+
.substitution
2866+
.as_slice(Interner)
2867+
.get(idx)
2868+
.and_then(|arg| arg.ty(Interner))
2869+
.cloned()
2870+
.map(|ty| Type { env: self.env.clone(), ty })
2871+
}
2872+
}
2873+
28272874
#[derive(Clone, PartialEq, Eq, Debug)]
28282875
pub struct Type {
28292876
env: Arc<TraitEnvironment>,

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

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use hir::HasSource;
2-
use ide_db::{
3-
syntax_helpers::insert_whitespace_into_node::insert_ws_into, traits::resolve_target_trait,
4-
};
2+
use ide_db::syntax_helpers::insert_whitespace_into_node::insert_ws_into;
53
use syntax::ast::{self, make, AstNode};
64

75
use crate::{
@@ -107,6 +105,7 @@ fn add_missing_impl_members_inner(
107105
) -> Option<()> {
108106
let _p = profile::span("add_missing_impl_members_inner");
109107
let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
108+
let impl_ = ctx.sema.to_def(&impl_def)?;
110109

111110
if ctx.token_at_offset().all(|t| {
112111
t.parent_ancestors()
@@ -116,7 +115,8 @@ fn add_missing_impl_members_inner(
116115
}
117116

118117
let target_scope = ctx.sema.scope(impl_def.syntax())?;
119-
let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
118+
let trait_ref = impl_.trait_ref(ctx.db())?;
119+
let trait_ = trait_ref.trait_();
120120

121121
let missing_items = filter_assoc_items(
122122
&ctx.sema,
@@ -155,7 +155,7 @@ fn add_missing_impl_members_inner(
155155
let placeholder;
156156
if let DefaultMethods::No = mode {
157157
if let ast::AssocItem::Fn(func) = &first_new_item {
158-
if try_gen_trait_body(ctx, func, &trait_, &impl_def).is_none() {
158+
if try_gen_trait_body(ctx, func, trait_ref, &impl_def).is_none() {
159159
if let Some(m) =
160160
func.syntax().descendants().find_map(ast::MacroCall::cast)
161161
{
@@ -180,13 +180,13 @@ fn add_missing_impl_members_inner(
180180
fn try_gen_trait_body(
181181
ctx: &AssistContext<'_>,
182182
func: &ast::Fn,
183-
trait_: &hir::Trait,
183+
trait_ref: hir::TraitRef,
184184
impl_def: &ast::Impl,
185185
) -> Option<()> {
186-
let trait_path = make::ext::ident_path(&trait_.name(ctx.db()).to_string());
186+
let trait_path = make::ext::ident_path(&trait_ref.trait_().name(ctx.db()).to_string());
187187
let hir_ty = ctx.sema.resolve_type(&impl_def.self_ty()?)?;
188188
let adt = hir_ty.as_adt()?.source(ctx.db())?;
189-
gen_trait_fn_body(func, &trait_path, &adt.value)
189+
gen_trait_fn_body(func, &trait_path, &adt.value, Some(trait_ref))
190190
}
191191

192192
#[cfg(test)]
@@ -1352,6 +1352,50 @@ impl PartialEq for SomeStruct {
13521352
);
13531353
}
13541354

1355+
#[test]
1356+
fn test_partial_eq_body_when_types_semantically_match() {
1357+
check_assist(
1358+
add_missing_impl_members,
1359+
r#"
1360+
//- minicore: eq
1361+
struct S<T, U>(T, U);
1362+
type Alias<T> = S<T, T>;
1363+
impl<T> PartialEq<Alias<T>> for S<T, T> {$0}
1364+
"#,
1365+
r#"
1366+
struct S<T, U>(T, U);
1367+
type Alias<T> = S<T, T>;
1368+
impl<T> PartialEq<Alias<T>> for S<T, T> {
1369+
$0fn eq(&self, other: &Alias<T>) -> bool {
1370+
self.0 == other.0 && self.1 == other.1
1371+
}
1372+
}
1373+
"#,
1374+
);
1375+
}
1376+
1377+
#[test]
1378+
fn test_partial_eq_body_when_types_dont_match() {
1379+
check_assist(
1380+
add_missing_impl_members,
1381+
r#"
1382+
//- minicore: eq
1383+
struct S<T, U>(T, U);
1384+
type Alias<T> = S<T, T>;
1385+
impl<T> PartialEq<Alias<T>> for S<T, i32> {$0}
1386+
"#,
1387+
r#"
1388+
struct S<T, U>(T, U);
1389+
type Alias<T> = S<T, T>;
1390+
impl<T> PartialEq<Alias<T>> for S<T, i32> {
1391+
fn eq(&self, other: &Alias<T>) -> bool {
1392+
${0:todo!()}
1393+
}
1394+
}
1395+
"#,
1396+
);
1397+
}
1398+
13551399
#[test]
13561400
fn test_ignore_function_body() {
13571401
check_assist_not_applicable(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ fn impl_def_from_trait(
214214

215215
// Generate a default `impl` function body for the derived trait.
216216
if let ast::AssocItem::Fn(ref func) = first_assoc_item {
217-
let _ = gen_trait_fn_body(func, trait_path, adt);
217+
let _ = gen_trait_fn_body(func, trait_path, adt, None);
218218
};
219219

220220
Some((impl_def, first_assoc_item))

crates/ide-assists/src/utils/gen_trait_fn_body.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,31 @@
11
//! This module contains functions to generate default trait impl function bodies where possible.
22
3+
use hir::TraitRef;
34
use syntax::{
45
ast::{self, edit::AstNodeEdit, make, AstNode, BinaryOp, CmpOp, HasName, LogicOp},
56
ted,
67
};
78

89
/// Generate custom trait bodies without default implementation where possible.
910
///
11+
/// If `func` is defined within an existing impl block, pass [`TraitRef`]. Otherwise pass `None`.
12+
///
1013
/// Returns `Option` so that we can use `?` rather than `if let Some`. Returning
1114
/// `None` means that generating a custom trait body failed, and the body will remain
1215
/// as `todo!` instead.
1316
pub(crate) fn gen_trait_fn_body(
1417
func: &ast::Fn,
1518
trait_path: &ast::Path,
1619
adt: &ast::Adt,
20+
trait_ref: Option<TraitRef>,
1721
) -> Option<()> {
1822
match trait_path.segment()?.name_ref()?.text().as_str() {
1923
"Clone" => gen_clone_impl(adt, func),
2024
"Debug" => gen_debug_impl(adt, func),
2125
"Default" => gen_default_impl(adt, func),
2226
"Hash" => gen_hash_impl(adt, func),
23-
"PartialEq" => gen_partial_eq(adt, func),
24-
"PartialOrd" => gen_partial_ord(adt, func),
27+
"PartialEq" => gen_partial_eq(adt, func, trait_ref),
28+
"PartialOrd" => gen_partial_ord(adt, func, trait_ref),
2529
_ => None,
2630
}
2731
}
@@ -395,7 +399,7 @@ fn gen_hash_impl(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
395399
}
396400

397401
/// Generate a `PartialEq` impl based on the fields and members of the target type.
398-
fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
402+
fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef>) -> Option<()> {
399403
stdx::always!(func.name().map_or(false, |name| name.text() == "eq"));
400404
fn gen_eq_chain(expr: Option<ast::Expr>, cmp: ast::Expr) -> Option<ast::Expr> {
401405
match expr {
@@ -423,8 +427,15 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
423427
ast::Pat::IdentPat(make::ident_pat(false, false, make::name(field_name)))
424428
}
425429

426-
// FIXME: return `None` if the trait carries a generic type; we can only
427-
// generate this code `Self` for the time being.
430+
// Check that self type and rhs type match. We don't know how to implement the method
431+
// automatically otherwise.
432+
if let Some(trait_ref) = trait_ref {
433+
let self_ty = trait_ref.self_ty();
434+
let rhs_ty = trait_ref.get_type_argument(1)?;
435+
if self_ty != rhs_ty {
436+
return None;
437+
}
438+
}
428439

429440
let body = match adt {
430441
// `PartialEq` cannot be derived for unions, so no default impl can be provided.
@@ -568,7 +579,7 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
568579
make::block_expr(None, expr).indent(ast::edit::IndentLevel(1))
569580
}
570581

571-
// No fields in the body means there's nothing to hash.
582+
// No fields in the body means there's nothing to compare.
572583
None => {
573584
let expr = make::expr_literal("true").into();
574585
make::block_expr(None, Some(expr)).indent(ast::edit::IndentLevel(1))
@@ -580,7 +591,7 @@ fn gen_partial_eq(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
580591
Some(())
581592
}
582593

583-
fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
594+
fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn, trait_ref: Option<TraitRef>) -> Option<()> {
584595
stdx::always!(func.name().map_or(false, |name| name.text() == "partial_cmp"));
585596
fn gen_partial_eq_match(match_target: ast::Expr) -> Option<ast::Stmt> {
586597
let mut arms = vec![];
@@ -605,8 +616,15 @@ fn gen_partial_ord(adt: &ast::Adt, func: &ast::Fn) -> Option<()> {
605616
make::expr_method_call(lhs, method, make::arg_list(Some(rhs)))
606617
}
607618

608-
// FIXME: return `None` if the trait carries a generic type; we can only
609-
// generate this code `Self` for the time being.
619+
// Check that self type and rhs type match. We don't know how to implement the method
620+
// automatically otherwise.
621+
if let Some(trait_ref) = trait_ref {
622+
let self_ty = trait_ref.self_ty();
623+
let rhs_ty = trait_ref.get_type_argument(1)?;
624+
if self_ty != rhs_ty {
625+
return None;
626+
}
627+
}
610628

611629
let body = match adt {
612630
// `PartialOrd` cannot be derived for unions, so no default impl can be provided.

0 commit comments

Comments
 (0)