Skip to content

Commit 1deaa44

Browse files
committed
add needless_clone_impl lint
1 parent 60aeef0 commit 1deaa44

23 files changed

+564
-172
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -4917,7 +4917,6 @@ Released 2018-09-13
49174917
[`manual_next_back`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_next_back
49184918
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
49194919
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
4920-
[`manual_partial_ord_and_ord_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_partial_ord_and_ord_impl
49214920
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
49224921
[`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid
49234922
[`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain
@@ -5002,6 +5001,7 @@ Released 2018-09-13
50025001
[`needless_bool_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool_assign
50035002
[`needless_borrow`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
50045003
[`needless_borrowed_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrowed_reference
5004+
[`needless_clone_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_clone_impl
50055005
[`needless_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect
50065006
[`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue
50075007
[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
@@ -5013,6 +5013,7 @@ Released 2018-09-13
50135013
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
50145014
[`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take
50155015
[`needless_parens_on_range_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_parens_on_range_literals
5016+
[`needless_partial_ord_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_partial_ord_impl
50165017
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
50175018
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
50185019
[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop

clippy_lints/src/declared_lints.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
273273
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
274274
crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,
275275
crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,
276-
crate::manual_partial_ord_and_ord_impl::MANUAL_PARTIAL_ORD_AND_ORD_IMPL_INFO,
277276
crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO,
278277
crate::manual_retain::MANUAL_RETAIN_INFO,
279278
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
@@ -458,6 +457,8 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
458457
crate::needless_continue::NEEDLESS_CONTINUE_INFO,
459458
crate::needless_else::NEEDLESS_ELSE_INFO,
460459
crate::needless_for_each::NEEDLESS_FOR_EACH_INFO,
460+
crate::needless_impls::NEEDLESS_CLONE_IMPL_INFO,
461+
crate::needless_impls::NEEDLESS_PARTIAL_ORD_IMPL_INFO,
461462
crate::needless_late_init::NEEDLESS_LATE_INIT_INFO,
462463
crate::needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS_INFO,
463464
crate::needless_pass_by_value::NEEDLESS_PASS_BY_VALUE_INFO,

clippy_lints/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ mod manual_is_ascii_check;
184184
mod manual_let_else;
185185
mod manual_main_separator_str;
186186
mod manual_non_exhaustive;
187-
mod manual_partial_ord_and_ord_impl;
188187
mod manual_rem_euclid;
189188
mod manual_retain;
190189
mod manual_slice_size_calculation;
@@ -222,6 +221,7 @@ mod needless_borrowed_ref;
222221
mod needless_continue;
223222
mod needless_else;
224223
mod needless_for_each;
224+
mod needless_impls;
225225
mod needless_late_init;
226226
mod needless_parens_on_range_literals;
227227
mod needless_pass_by_value;
@@ -1013,7 +1013,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10131013
store.register_early_pass(|| Box::new(needless_else::NeedlessElse));
10141014
store.register_late_pass(|_| Box::new(missing_fields_in_debug::MissingFieldsInDebug));
10151015
store.register_late_pass(|_| Box::new(endian_bytes::EndianBytes));
1016-
store.register_late_pass(|_| Box::new(manual_partial_ord_and_ord_impl::ManualPartialOrdAndOrdImpl));
1016+
store.register_late_pass(|_| Box::new(needless_impls::NeedlessImpls));
10171017
// add lints here, do not remove this comment, it's used in `new_lint`
10181018
}
10191019

clippy_lints/src/manual_partial_ord_and_ord_impl.rs

-122
This file was deleted.

clippy_lints/src/needless_impls.rs

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
use clippy_utils::{
2+
diagnostics::{span_lint_and_sugg, span_lint_and_then},
3+
get_parent_node, last_path_segment, match_qpath, path_res,
4+
ty::implements_trait,
5+
};
6+
use rustc_errors::Applicability;
7+
use rustc_hir::{def::Res, Expr, ExprKind, ImplItem, ImplItemKind, ItemKind, Node, PatKind, UnOp};
8+
use rustc_hir_analysis::hir_ty_to_ty;
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_middle::ty::EarlyBinder;
11+
use rustc_session::{declare_lint_pass, declare_tool_lint};
12+
use rustc_span::{sym, symbol};
13+
14+
declare_clippy_lint! {
15+
/// ### What it does
16+
/// Checks for manual implementations of `Clone` when `Copy` is already implemented.
17+
///
18+
/// ### Why is this bad?
19+
/// If both `Clone` and `Copy` are implemented, they must agree. This is done by dereferencing
20+
/// `self` in `Clone`'s implementation. Anything else is incorrect.
21+
///
22+
/// ### Example
23+
/// ```rust,ignore
24+
/// #[derive(Eq, PartialEq)]
25+
/// struct A(u32);
26+
///
27+
/// impl Clone for A {
28+
/// fn clone(&self) -> Self {
29+
/// Self(self.0)
30+
/// }
31+
/// }
32+
///
33+
/// impl Copy for A {}
34+
/// ```
35+
/// Use instead:
36+
/// ```rust,ignore
37+
/// #[derive(Eq, PartialEq)]
38+
/// struct A(u32);
39+
///
40+
/// impl Clone for A {
41+
/// fn clone(&self) -> Self {
42+
/// *self
43+
/// }
44+
/// }
45+
///
46+
/// impl Copy for A {}
47+
/// ```
48+
#[clippy::version = "1.72.0"]
49+
pub NEEDLESS_CLONE_IMPL,
50+
correctness,
51+
"manual implementation of `Clone` when `Copy` is implemented"
52+
}
53+
declare_clippy_lint! {
54+
/// ### What it does
55+
/// Checks for manual implementations of both `PartialOrd` and `Ord` when only `Ord` is
56+
/// necessary.
57+
///
58+
/// ### Why is this bad?
59+
/// If both `PartialOrd` and `Ord` are implemented, they must agree. This is commonly done by
60+
/// wrapping the result of `cmp` in `Some` for `partial_cmp`. Not doing this may silently
61+
/// introduce an error upon refactoring.
62+
///
63+
/// ### Example
64+
/// ```rust,ignore
65+
/// #[derive(Eq, PartialEq)]
66+
/// struct A(u32);
67+
///
68+
/// impl Ord for A {
69+
/// fn cmp(&self, other: &Self) -> Ordering {
70+
/// todo!();
71+
/// }
72+
/// }
73+
///
74+
/// impl PartialOrd for A {
75+
/// fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
76+
/// todo!();
77+
/// }
78+
/// }
79+
/// ```
80+
/// Use instead:
81+
/// ```rust,ignore
82+
/// #[derive(Eq, PartialEq)]
83+
/// struct A(u32);
84+
///
85+
/// impl Ord for A {
86+
/// fn cmp(&self, other: &Self) -> Ordering {
87+
/// todo!();
88+
/// }
89+
/// }
90+
///
91+
/// impl PartialOrd for A {
92+
/// fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
93+
/// Some(self.cmp(other))
94+
/// }
95+
/// }
96+
/// ```
97+
#[clippy::version = "1.72.0"]
98+
pub NEEDLESS_PARTIAL_ORD_IMPL,
99+
correctness,
100+
"manual implementation of `PartialOrd` when `Ord` is already implemented"
101+
}
102+
declare_lint_pass!(NeedlessImpls => [NEEDLESS_CLONE_IMPL, NEEDLESS_PARTIAL_ORD_IMPL]);
103+
104+
impl LateLintPass<'_> for NeedlessImpls {
105+
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
106+
let node = get_parent_node(cx.tcx, impl_item.hir_id());
107+
let Some(Node::Item(item)) = node else {
108+
return;
109+
};
110+
let ItemKind::Impl(imp) = item.kind else {
111+
return;
112+
};
113+
let Some(trait_impl) = cx.tcx.impl_trait_ref(item.owner_id).map(EarlyBinder::skip_binder) else {
114+
return;
115+
};
116+
let trait_impl_def_id = trait_impl.def_id;
117+
if cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) {
118+
return;
119+
}
120+
let ImplItemKind::Fn(_, impl_item_id) = cx.tcx.hir().impl_item(impl_item.impl_item_id()).kind else {
121+
return;
122+
};
123+
let body = cx.tcx.hir().body(impl_item_id);
124+
let ExprKind::Block(block, ..) = body.value.kind else {
125+
return;
126+
};
127+
128+
if cx.tcx.is_diagnostic_item(sym::Clone, trait_impl_def_id)
129+
&& impl_item.ident.name == sym::clone
130+
&& let Some(copy_def_id) = cx
131+
.tcx
132+
.diagnostic_items(trait_impl.def_id.krate)
133+
.name_to_id
134+
.get(&sym::Copy)
135+
&& implements_trait(
136+
cx,
137+
hir_ty_to_ty(cx.tcx, imp.self_ty),
138+
*copy_def_id,
139+
trait_impl.substs,
140+
)
141+
{
142+
if block.stmts.is_empty()
143+
&& let Some(expr) = block.expr
144+
&& let ExprKind::Unary(UnOp::Deref, inner) = expr.kind
145+
&& let ExprKind::Path(qpath) = inner.kind
146+
&& last_path_segment(&qpath).ident.name == symbol::kw::SelfLower
147+
{} else {
148+
span_lint_and_sugg(
149+
cx,
150+
NEEDLESS_CLONE_IMPL,
151+
block.span,
152+
"manual implementation of `Clone` when `Copy` is already implemented",
153+
"change this to",
154+
"{ *self }".to_owned(),
155+
Applicability::Unspecified,
156+
);
157+
158+
return;
159+
}
160+
}
161+
162+
if cx.tcx.is_diagnostic_item(sym::PartialOrd, trait_impl_def_id)
163+
&& impl_item.ident.name == sym::partial_cmp
164+
&& let Some(ord_def_id) = cx
165+
.tcx
166+
.diagnostic_items(trait_impl.def_id.krate)
167+
.name_to_id
168+
.get(&sym::Ord)
169+
&& implements_trait(
170+
cx,
171+
hir_ty_to_ty(cx.tcx, imp.self_ty),
172+
*ord_def_id,
173+
trait_impl.substs,
174+
)
175+
{
176+
if block.stmts.is_empty()
177+
&& let Some(expr) = block.expr
178+
&& let ExprKind::Call(Expr { kind: ExprKind::Path(some_path), .. }, [cmp_expr]) = expr.kind
179+
// TODO: We can likely use the `option_some_variant` lang item instead, but this may be tricky
180+
// due to the fact that `expr` is the constructor, not `option_some_variant` itself.
181+
&& match_qpath(some_path, &["Some"])
182+
&& let ExprKind::MethodCall(cmp_path, _, [other_expr], ..) = cmp_expr.kind
183+
&& cmp_path.ident.name == sym::cmp
184+
&& let Res::Local(..) = path_res(cx, other_expr)
185+
{} else {
186+
span_lint_and_then(
187+
cx,
188+
NEEDLESS_PARTIAL_ORD_IMPL,
189+
item.span,
190+
"manual implementation of `PartialOrd` when `Ord` is already implemented",
191+
|diag| {
192+
if let Some(other) = body.params.get(0)
193+
&& let PatKind::Binding(_, _, other_ident, ..) = other.pat.kind
194+
{
195+
diag.span_suggestion(
196+
block.span,
197+
"change this to",
198+
format!("{{ Some(self.cmp({})) }}", other_ident.name),
199+
Applicability::Unspecified,
200+
);
201+
} else {
202+
diag.help("return the value of `self.cmp` wrapped in `Some` instead");
203+
};
204+
}
205+
);
206+
}
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)