Skip to content

Commit 42d13f8

Browse files
committedJan 25, 2024
[unconditional_recursion]: compare by types instead of DefIds
1 parent 66c29b9 commit 42d13f8

File tree

2 files changed

+82
-39
lines changed

2 files changed

+82
-39
lines changed
 

Diff for: ‎clippy_lints/src/unconditional_recursion.rs

+47-39
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,6 @@ fn span_error(cx: &LateContext<'_>, method_span: Span, expr: &Expr<'_>) {
6969
);
7070
}
7171

72-
fn get_ty_def_id(ty: Ty<'_>) -> Option<DefId> {
73-
match ty.peel_refs().kind() {
74-
ty::Adt(adt, _) => Some(adt.did()),
75-
ty::Foreign(def_id) => Some(*def_id),
76-
_ => None,
77-
}
78-
}
79-
8072
fn get_hir_ty_def_id<'tcx>(tcx: TyCtxt<'tcx>, hir_ty: rustc_hir::Ty<'tcx>) -> Option<DefId> {
8173
let TyKind::Path(qpath) = hir_ty.kind else { return None };
8274
match qpath {
@@ -131,21 +123,49 @@ fn get_impl_trait_def_id(cx: &LateContext<'_>, method_def_id: LocalDefId) -> Opt
131123
}
132124
}
133125

134-
#[allow(clippy::unnecessary_def_path)]
126+
/// When we have `x == y` where `x = &T` and `y = &T`, then that resolves to
127+
/// `<&T as PartialEq<&T>>::eq`, which is not the same as `<T as PartialEq<T>>::eq`,
128+
/// however we still would want to treat it the same, because we know that it's a blanket impl
129+
/// that simply delegates to the `PartialEq` impl with one reference removed.
130+
///
131+
/// Still, we can't just do `lty.peel_refs() == rty.peel_refs()` because when we have `x = &T` and
132+
/// `y = &&T`, this is not necessarily the same as `<T as PartialEq<T>>::eq`
133+
///
134+
/// So to avoid these FNs and FPs, we keep removing a layer of references from *both* sides
135+
/// until both sides match the expected LHS and RHS type (or they don't).
136+
fn matches_ty<'tcx>(
137+
mut left: Ty<'tcx>,
138+
mut right: Ty<'tcx>,
139+
expected_left: Ty<'tcx>,
140+
expected_right: Ty<'tcx>,
141+
) -> bool {
142+
while let (&ty::Ref(_, lty, _), &ty::Ref(_, rty, _)) = (left.kind(), right.kind()) {
143+
if lty == expected_left && rty == expected_right {
144+
return true;
145+
}
146+
left = lty;
147+
right = rty;
148+
}
149+
false
150+
}
151+
135152
fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
136-
let args = cx
137-
.tcx
138-
.instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder())
139-
.inputs();
153+
let Some(sig) = cx
154+
.typeck_results()
155+
.liberated_fn_sigs()
156+
.get(cx.tcx.local_def_id_to_hir_id(method_def_id))
157+
else {
158+
return;
159+
};
160+
140161
// That has two arguments.
141-
if let [self_arg, other_arg] = args
142-
&& let Some(self_arg) = get_ty_def_id(*self_arg)
143-
&& let Some(other_arg) = get_ty_def_id(*other_arg)
162+
if let [self_arg, other_arg] = sig.inputs()
163+
&& let &ty::Ref(_, self_arg, _) = self_arg.kind()
164+
&& let &ty::Ref(_, other_arg, _) = other_arg.kind()
144165
// The two arguments are of the same type.
145-
&& self_arg == other_arg
146166
&& let Some(trait_def_id) = get_impl_trait_def_id(cx, method_def_id)
147167
// The trait is `PartialEq`.
148-
&& Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"])
168+
&& cx.tcx.is_diagnostic_item(sym::PartialEq, trait_def_id)
149169
{
150170
let to_check_op = if name.name == sym::eq {
151171
BinOpKind::Eq
@@ -154,31 +174,19 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
154174
};
155175
let is_bad = match expr.kind {
156176
ExprKind::Binary(op, left, right) if op.node == to_check_op => {
157-
// Then we check if the left-hand element is of the same type as `self`.
158-
if let Some(left_ty) = cx.typeck_results().expr_ty_opt(left)
159-
&& let Some(left_id) = get_ty_def_id(left_ty)
160-
&& self_arg == left_id
161-
&& let Some(right_ty) = cx.typeck_results().expr_ty_opt(right)
162-
&& let Some(right_id) = get_ty_def_id(right_ty)
163-
&& other_arg == right_id
164-
{
165-
true
166-
} else {
167-
false
168-
}
177+
// Then we check if the LHS matches self_arg and RHS matches other_arg
178+
let left_ty = cx.typeck_results().expr_ty_adjusted(left);
179+
let right_ty = cx.typeck_results().expr_ty_adjusted(right);
180+
matches_ty(left_ty, right_ty, self_arg, other_arg)
169181
},
170-
ExprKind::MethodCall(segment, receiver, &[_arg], _) if segment.ident.name == name.name => {
171-
if let Some(ty) = cx.typeck_results().expr_ty_opt(receiver)
172-
&& let Some(ty_id) = get_ty_def_id(ty)
173-
&& self_arg != ty_id
174-
{
175-
// Since this called on a different type, the lint should not be
176-
// triggered here.
177-
return;
178-
}
182+
ExprKind::MethodCall(segment, receiver, [arg], _) if segment.ident.name == name.name => {
183+
let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver);
184+
let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
185+
179186
if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
180187
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
181188
&& trait_id == trait_def_id
189+
&& matches_ty(receiver_ty, arg_ty, self_arg, other_arg)
182190
{
183191
true
184192
} else {

Diff for: ‎tests/ui/unconditional_recursion.rs

+35
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,39 @@ impl PartialEq for S15<'_> {
288288
}
289289
}
290290

291+
mod issue12154 {
292+
struct MyBox<T>(T);
293+
294+
impl<T> std::ops::Deref for MyBox<T> {
295+
type Target = T;
296+
fn deref(&self) -> &T {
297+
&self.0
298+
}
299+
}
300+
301+
impl<T: PartialEq> PartialEq for MyBox<T> {
302+
fn eq(&self, other: &Self) -> bool {
303+
(**self).eq(&**other)
304+
}
305+
}
306+
307+
// Not necessarily related to the issue but another FP from the http crate that was fixed with it:
308+
// https://docs.rs/http/latest/src/http/header/name.rs.html#1424
309+
// We used to simply peel refs from the LHS and RHS, so we couldn't differentiate
310+
// between `PartialEq<T> for &T` and `PartialEq<&T> for T` impls.
311+
#[derive(PartialEq)]
312+
struct HeaderName;
313+
impl<'a> PartialEq<&'a HeaderName> for HeaderName {
314+
fn eq(&self, other: &&'a HeaderName) -> bool {
315+
*self == **other
316+
}
317+
}
318+
319+
impl<'a> PartialEq<HeaderName> for &'a HeaderName {
320+
fn eq(&self, other: &HeaderName) -> bool {
321+
*other == *self
322+
}
323+
}
324+
}
325+
291326
fn main() {}

0 commit comments

Comments
 (0)