Skip to content

Commit 91d1767

Browse files
committed
Allow Self::cmp(self, other) as a correct impl
1 parent d4a6634 commit 91d1767

File tree

4 files changed

+131
-10
lines changed

4 files changed

+131
-10
lines changed

clippy_lints/src/incorrect_impls.rs

+62-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
2+
use clippy_utils::paths::ORD_CMP;
23
use clippy_utils::ty::implements_trait;
3-
use clippy_utils::{get_parent_node, is_res_lang_ctor, last_path_segment, path_res};
4+
use clippy_utils::{get_parent_node, is_res_lang_ctor, last_path_segment, match_def_path, path_res, std_or_core};
45
use rustc_errors::Applicability;
5-
use rustc_hir::def::Res;
6+
use rustc_hir::def_id::LocalDefId;
67
use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, ItemKind, LangItem, Node, UnOp};
78
use rustc_hir_analysis::hir_ty_to_ty;
89
use rustc_lint::{LateContext, LateLintPass};
@@ -60,6 +61,10 @@ declare_clippy_lint! {
6061
/// wrapping the result of `cmp` in `Some` for `partial_cmp`. Not doing this may silently
6162
/// introduce an error upon refactoring.
6263
///
64+
/// ### Known issues
65+
/// Code like `self.cmp(other).into()` will be flagged as incorrect, despite `.into()` wrapping
66+
/// it in `Some`.
67+
///
6368
/// ### Limitations
6469
/// Will not lint if `Self` and `Rhs` do not have the same type.
6570
///
@@ -191,6 +196,11 @@ impl LateLintPass<'_> for IncorrectImpls {
191196
trait_impl.substs,
192197
)
193198
{
199+
// If the `cmp` call needs to be fully qualified in the suggestion
200+
// (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't
201+
// access `cmp_expr` in the suggestion without major changes, as we lint in `else`.
202+
let mut needs_fully_qualified = false;
203+
194204
if block.stmts.is_empty()
195205
&& let Some(expr) = block.expr
196206
&& let ExprKind::Call(
@@ -202,9 +212,8 @@ impl LateLintPass<'_> for IncorrectImpls {
202212
[cmp_expr],
203213
) = expr.kind
204214
&& is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome)
205-
&& let ExprKind::MethodCall(cmp_path, _, [other_expr], ..) = cmp_expr.kind
206-
&& cmp_path.ident.name == sym::cmp
207-
&& let Res::Local(..) = path_res(cx, other_expr)
215+
// Fix #11178, allow `Self::cmp(self, ..)` too
216+
&& self_cmp_call(cx, cmp_expr, impl_item.owner_id.def_id, &mut needs_fully_qualified)
208217
{} else {
209218
// If `Self` and `Rhs` are not the same type, bail. This makes creating a valid
210219
// suggestion tons more complex.
@@ -221,14 +230,29 @@ impl LateLintPass<'_> for IncorrectImpls {
221230
let [_, other] = body.params else {
222231
return;
223232
};
233+
let Some(std_or_core) = std_or_core(cx) else {
234+
return;
235+
};
224236

225-
let suggs = if let Some(other_ident) = other.pat.simple_ident() {
226-
vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))]
227-
} else {
228-
vec![
237+
let suggs = match (other.pat.simple_ident(), needs_fully_qualified) {
238+
(Some(other_ident), true) => vec![(
239+
block.span,
240+
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name),
241+
)],
242+
(Some(other_ident), false) => {
243+
vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))]
244+
},
245+
(None, true) => vec![
246+
(
247+
block.span,
248+
format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"),
249+
),
250+
(other.pat.span, "other".to_owned()),
251+
],
252+
(None, false) => vec![
229253
(block.span, "{ Some(self.cmp(other)) }".to_owned()),
230254
(other.pat.span, "other".to_owned()),
231-
]
255+
],
232256
};
233257

234258
diag.multipart_suggestion(
@@ -242,3 +266,31 @@ impl LateLintPass<'_> for IncorrectImpls {
242266
}
243267
}
244268
}
269+
270+
/// Returns whether this is any of `self.cmp(..)`, `Self::cmp(self, ..)` or `Ord::cmp(self, ..)`.
271+
fn self_cmp_call<'tcx>(
272+
cx: &LateContext<'tcx>,
273+
cmp_expr: &'tcx Expr<'tcx>,
274+
def_id: LocalDefId,
275+
needs_fully_qualified: &mut bool,
276+
) -> bool {
277+
match cmp_expr.kind {
278+
ExprKind::Call(path, [_self, _other]) => path_res(cx, path)
279+
.opt_def_id()
280+
.is_some_and(|def_id| match_def_path(cx, def_id, &ORD_CMP)),
281+
ExprKind::MethodCall(_, _, [_other], ..) => {
282+
// We can set this to true here no matter what as if it's a `MethodCall` and goes to the
283+
// `else` branch, it must be a method named `cmp` that isn't `Ord::cmp`
284+
*needs_fully_qualified = true;
285+
286+
// It's a bit annoying but `typeck_results` only gives us the CURRENT body, which we
287+
// have none, not of any `LocalDefId` we want, so we must call the query itself to avoid
288+
// an immediate ICE
289+
cx.tcx
290+
.typeck(def_id)
291+
.type_dependent_def_id(cmp_expr.hir_id)
292+
.is_some_and(|def_id| match_def_path(cx, def_id, &ORD_CMP))
293+
},
294+
_ => false,
295+
}
296+
}

clippy_utils/src/paths.rs

+1
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,4 @@ pub const OPTION_UNWRAP: [&str; 4] = ["core", "option", "Option", "unwrap"];
161161
pub const OPTION_EXPECT: [&str; 4] = ["core", "option", "Option", "expect"];
162162
pub const FORMATTER: [&str; 3] = ["core", "fmt", "Formatter"];
163163
pub const DEBUG_STRUCT: [&str; 4] = ["core", "fmt", "builders", "DebugStruct"];
164+
pub const ORD_CMP: [&str; 4] = ["core", "cmp", "Ord", "cmp"];

tests/ui/incorrect_partial_ord_impl_on_ord_type.fixed

+34
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,37 @@ impl PartialOrd<u32> for F {
112112
todo!();
113113
}
114114
}
115+
116+
// #11178, do not lint
117+
118+
#[derive(Eq, PartialEq)]
119+
struct G(u32);
120+
121+
impl Ord for G {
122+
fn cmp(&self, other: &Self) -> Ordering {
123+
todo!();
124+
}
125+
}
126+
127+
impl PartialOrd for G {
128+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
129+
Some(Self::cmp(self, other))
130+
}
131+
}
132+
133+
#[derive(Eq, PartialEq)]
134+
struct H(u32);
135+
136+
impl Ord for H {
137+
fn cmp(&self, other: &Self) -> Ordering {
138+
todo!();
139+
}
140+
}
141+
142+
impl PartialOrd for H {
143+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
144+
Some(Ord::cmp(self, other))
145+
}
146+
}
147+
148+
// TODO: We need tests for a method named `cmp` that isn't `Ord::cmp`. I will do this tomorrow ^^

tests/ui/incorrect_partial_ord_impl_on_ord_type.rs

+34
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,37 @@ impl PartialOrd<u32> for F {
116116
todo!();
117117
}
118118
}
119+
120+
// #11178, do not lint
121+
122+
#[derive(Eq, PartialEq)]
123+
struct G(u32);
124+
125+
impl Ord for G {
126+
fn cmp(&self, other: &Self) -> Ordering {
127+
todo!();
128+
}
129+
}
130+
131+
impl PartialOrd for G {
132+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
133+
Some(Self::cmp(self, other))
134+
}
135+
}
136+
137+
#[derive(Eq, PartialEq)]
138+
struct H(u32);
139+
140+
impl Ord for H {
141+
fn cmp(&self, other: &Self) -> Ordering {
142+
todo!();
143+
}
144+
}
145+
146+
impl PartialOrd for H {
147+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
148+
Some(Ord::cmp(self, other))
149+
}
150+
}
151+
152+
// TODO: We need tests for a method named `cmp` that isn't `Ord::cmp`. I will do this tomorrow ^^

0 commit comments

Comments
 (0)