Skip to content

Commit dfc3d13

Browse files
committed
add manual_abs_diff lint
1 parent fe4dd5b commit dfc3d13

File tree

10 files changed

+339
-0
lines changed

10 files changed

+339
-0
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5784,6 +5784,7 @@ Released 2018-09-13
57845784
[`macro_metavars_in_unsafe`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_metavars_in_unsafe
57855785
[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
57865786
[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
5787+
[`manual_abs_diff`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_abs_diff
57875788
[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
57885789
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
57895790
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits

Diff for: book/src/lint_configuration.md

+1
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
797797
* [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map)
798798
* [`legacy_numeric_constants`](https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants)
799799
* [`lines_filter_map_ok`](https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok)
800+
* [`manual_abs_diff`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_abs_diff)
800801
* [`manual_bits`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits)
801802
* [`manual_c_str_literals`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals)
802803
* [`manual_clamp`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp)

Diff for: clippy_config/src/conf.rs

+1
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ define_Conf! {
645645
iter_kv_map,
646646
legacy_numeric_constants,
647647
lines_filter_map_ok,
648+
manual_abs_diff,
648649
manual_bits,
649650
manual_c_str_literals,
650651
manual_clamp,

Diff for: clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
313313
crate::macro_metavars_in_unsafe::MACRO_METAVARS_IN_UNSAFE_INFO,
314314
crate::macro_use::MACRO_USE_IMPORTS_INFO,
315315
crate::main_recursion::MAIN_RECURSION_INFO,
316+
crate::manual_abs_diff::MANUAL_ABS_DIFF_INFO,
316317
crate::manual_assert::MANUAL_ASSERT_INFO,
317318
crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
318319
crate::manual_bits::MANUAL_BITS_INFO,

Diff for: clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ mod loops;
205205
mod macro_metavars_in_unsafe;
206206
mod macro_use;
207207
mod main_recursion;
208+
mod manual_abs_diff;
208209
mod manual_assert;
209210
mod manual_async_fn;
210211
mod manual_bits;
@@ -878,6 +879,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
878879
store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf)));
879880
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(conf)));
880881
store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone));
882+
store.register_late_pass(move |_| Box::new(manual_abs_diff::ManualAbsDiff::new(conf)));
881883
store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf)));
882884
store.register_late_pass(|_| Box::new(manual_string_new::ManualStringNew));
883885
store.register_late_pass(|_| Box::new(unused_peekable::UnusedPeekable));

Diff for: clippy_lints/src/manual_abs_diff.rs

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use clippy_config::Conf;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::higher::If;
4+
use clippy_utils::msrvs::{self, Msrv};
5+
use clippy_utils::sugg::Sugg;
6+
use clippy_utils::{eq_expr_value, peel_blocks};
7+
use rustc_errors::Applicability;
8+
use rustc_hir::{BinOpKind, Expr, ExprKind};
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_session::impl_lint_pass;
11+
use rustc_span::Span;
12+
use rustc_span::source_map::Spanned;
13+
14+
declare_clippy_lint! {
15+
/// ### What it does
16+
/// Detects patterns like `if a > b { a - b } else { b - a }` and suggests using `a.abs_diff(b)`.
17+
///
18+
/// ### Why is this bad?
19+
/// Using `abs_diff` is shorter, more readable, and avoids control flow.
20+
///
21+
/// ### Examples
22+
/// ```no_run
23+
/// # let (a, b) = (5_usize, 3_usize);
24+
/// if a > b {
25+
/// a - b
26+
/// } else {
27+
/// b - a
28+
/// }
29+
/// # ;
30+
/// ```
31+
/// Use instead:
32+
/// ```no_run
33+
/// # let (a, b) = (5_usize, 3_usize);
34+
/// a.abs_diff(b)
35+
/// # ;
36+
/// ```
37+
#[clippy::version = "1.86.0"]
38+
pub MANUAL_ABS_DIFF,
39+
complexity,
40+
"using an if-else pattern instead of `abs_diff`"
41+
}
42+
43+
impl_lint_pass!(ManualAbsDiff => [MANUAL_ABS_DIFF]);
44+
45+
pub struct ManualAbsDiff {
46+
msrv: Msrv,
47+
}
48+
49+
impl ManualAbsDiff {
50+
pub fn new(conf: &'static Conf) -> Self {
51+
Self { msrv: conf.msrv }
52+
}
53+
}
54+
55+
#[derive(Debug)]
56+
struct Suggestion<'tcx> {
57+
params: Input<'tcx>,
58+
span: Span,
59+
}
60+
61+
#[derive(Debug)]
62+
struct Input<'tcx> {
63+
a: &'tcx Expr<'tcx>,
64+
b: &'tcx Expr<'tcx>,
65+
}
66+
67+
impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff {
68+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
69+
if !expr.span.from_expansion()
70+
&& self.msrv.meets(cx, msrvs::ABS_DIFF)
71+
&& let Some(suggestion) = is_manual_abs_diff_pattern(cx, expr)
72+
{
73+
emit_suggestion(cx, &suggestion);
74+
}
75+
}
76+
}
77+
78+
/// Checks if the given expression is a subtraction operation between two expected expressions, i.e.
79+
/// if `expr` is `{expected_a} - {expected_b}`.
80+
fn is_sub_expr(cx: &LateContext<'_>, expr: &Expr<'_>, expected_a: &Expr<'_>, expected_b: &Expr<'_>) -> bool {
81+
matches!(
82+
peel_blocks(expr).kind,
83+
ExprKind::Binary(
84+
Spanned {
85+
node: BinOpKind::Sub,
86+
..
87+
},
88+
a,
89+
b,
90+
)
91+
if eq_expr_value(cx, a, expected_a) && eq_expr_value(cx, b, expected_b)
92+
)
93+
}
94+
95+
/// Matches a pattern such as `if a > b { a - b } else { b - a }`
96+
fn is_manual_abs_diff_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<Suggestion<'tcx>> {
97+
if let Some(If {
98+
cond,
99+
then,
100+
r#else: Some(r#else),
101+
}) = If::hir(expr)
102+
&& let ExprKind::Binary(
103+
Spanned {
104+
node: BinOpKind::Gt, ..
105+
},
106+
a,
107+
b,
108+
)
109+
| ExprKind::Binary(
110+
Spanned {
111+
node: BinOpKind::Lt, ..
112+
},
113+
b,
114+
a,
115+
) = cond.kind
116+
&& is_sub_expr(cx, then, a, b)
117+
&& is_sub_expr(cx, r#else, b, a)
118+
{
119+
Some(Suggestion {
120+
params: Input { a, b },
121+
span: expr.span,
122+
})
123+
} else {
124+
None
125+
}
126+
}
127+
128+
fn emit_suggestion<'tcx>(cx: &LateContext<'tcx>, suggestion: &Suggestion<'tcx>) {
129+
let Suggestion {
130+
params: Input { a, b, .. },
131+
span,
132+
..
133+
} = suggestion;
134+
135+
let a = Sugg::hir(cx, a, "..").maybe_paren();
136+
let b = Sugg::hir(cx, b, "..");
137+
let suggestion = format!("{a}.abs_diff({b})");
138+
let msg = "manual absolute difference pattern without using `abs_diff`";
139+
let help = "replace with `abs_diff`";
140+
span_lint_and_sugg(
141+
cx,
142+
MANUAL_ABS_DIFF,
143+
*span,
144+
msg,
145+
help,
146+
suggestion,
147+
Applicability::MachineApplicable,
148+
);
149+
}

Diff for: clippy_utils/src/msrvs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ msrv_aliases! {
4040
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
4141
1,63,0 { CLONE_INTO }
4242
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }
43+
1,60,0 { ABS_DIFF }
4344
1,59,0 { THREAD_LOCAL_CONST_INIT }
4445
1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF }
4546
1,57,0 { MAP_WHILE }

Diff for: tests/ui/manual_abs_diff.fixed

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#![warn(clippy::manual_abs_diff)]
2+
3+
fn main() {
4+
let a: usize = 5;
5+
let b: usize = 3;
6+
let c: usize = 8;
7+
let d: usize = 11;
8+
9+
let _ = a.abs_diff(b);
10+
//~^ manual_abs_diff
11+
let _ = b.abs_diff(a);
12+
//~^ manual_abs_diff
13+
14+
#[allow(arithmetic_overflow)]
15+
{
16+
let _ = if a > b { b - a } else { a - b };
17+
let _ = if a < b { a - b } else { b - a };
18+
}
19+
20+
let _ = (a + b).abs_diff(c + d);
21+
let _ = (c + d).abs_diff(a + b);
22+
23+
const A: usize = 5;
24+
const B: usize = 3;
25+
// check const context
26+
const _: usize = A.abs_diff(B);
27+
//~^ manual_abs_diff
28+
}
29+
30+
// FIXME: bunch of patterns that should be linted
31+
fn fixme() {
32+
let a: usize = 5;
33+
let b: usize = 3;
34+
let c: usize = 8;
35+
let d: usize = 11;
36+
37+
{
38+
let out;
39+
if a > b {
40+
out = a - b;
41+
} else {
42+
out = b - a;
43+
}
44+
}
45+
46+
{
47+
let mut out = 0;
48+
if a > b {
49+
out = a - b;
50+
} else if a < b {
51+
out = b - a;
52+
}
53+
}
54+
55+
#[allow(clippy::implicit_saturating_sub)]
56+
let _ = if a > b {
57+
a - b
58+
} else if a < b {
59+
b - a
60+
} else {
61+
0
62+
};
63+
}

Diff for: tests/ui/manual_abs_diff.rs

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#![warn(clippy::manual_abs_diff)]
2+
3+
fn main() {
4+
let a: usize = 5;
5+
let b: usize = 3;
6+
let c: usize = 8;
7+
let d: usize = 11;
8+
9+
let _ = if a > b { a - b } else { b - a };
10+
//~^ manual_abs_diff
11+
let _ = if a < b { b - a } else { a - b };
12+
//~^ manual_abs_diff
13+
14+
#[allow(arithmetic_overflow)]
15+
{
16+
let _ = if a > b { b - a } else { a - b };
17+
let _ = if a < b { a - b } else { b - a };
18+
}
19+
20+
let _ = if (a + b) > (c + d) {
21+
//~^ manual_abs_diff
22+
(a + b) - (c + d)
23+
} else {
24+
(c + d) - (a + b)
25+
};
26+
let _ = if (a + b) < (c + d) {
27+
//~^ manual_abs_diff
28+
(c + d) - (a + b)
29+
} else {
30+
(a + b) - (c + d)
31+
};
32+
33+
const A: usize = 5;
34+
const B: usize = 3;
35+
// check const context
36+
const _: usize = if A > B { A - B } else { B - A };
37+
//~^ manual_abs_diff
38+
}
39+
40+
// FIXME: bunch of patterns that should be linted
41+
fn fixme() {
42+
let a: usize = 5;
43+
let b: usize = 3;
44+
let c: usize = 8;
45+
let d: usize = 11;
46+
47+
{
48+
let out;
49+
if a > b {
50+
out = a - b;
51+
} else {
52+
out = b - a;
53+
}
54+
}
55+
56+
{
57+
let mut out = 0;
58+
if a > b {
59+
out = a - b;
60+
} else if a < b {
61+
out = b - a;
62+
}
63+
}
64+
65+
#[allow(clippy::implicit_saturating_sub)]
66+
let _ = if a > b {
67+
a - b
68+
} else if a < b {
69+
b - a
70+
} else {
71+
0
72+
};
73+
}

Diff for: tests/ui/manual_abs_diff.stderr

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
error: manual absolute difference pattern without using `abs_diff`
2+
--> tests/ui/manual_abs_diff.rs:9:13
3+
|
4+
LL | let _ = if a > b { a - b } else { b - a };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `abs_diff`: `a.abs_diff(b)`
6+
|
7+
= note: `-D clippy::manual-abs-diff` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::manual_abs_diff)]`
9+
10+
error: manual absolute difference pattern without using `abs_diff`
11+
--> tests/ui/manual_abs_diff.rs:11:13
12+
|
13+
LL | let _ = if a < b { b - a } else { a - b };
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `abs_diff`: `b.abs_diff(a)`
15+
16+
error: manual absolute difference pattern without using `abs_diff`
17+
--> tests/ui/manual_abs_diff.rs:20:13
18+
|
19+
LL | let _ = if (a + b) > (c + d) {
20+
| _____________^
21+
LL | |
22+
LL | | (a + b) - (c + d)
23+
LL | | } else {
24+
LL | | (c + d) - (a + b)
25+
LL | | };
26+
| |_____^ help: replace with `abs_diff`: `(a + b).abs_diff(c + d)`
27+
28+
error: manual absolute difference pattern without using `abs_diff`
29+
--> tests/ui/manual_abs_diff.rs:26:13
30+
|
31+
LL | let _ = if (a + b) < (c + d) {
32+
| _____________^
33+
LL | |
34+
LL | | (c + d) - (a + b)
35+
LL | | } else {
36+
LL | | (a + b) - (c + d)
37+
LL | | };
38+
| |_____^ help: replace with `abs_diff`: `(c + d).abs_diff(a + b)`
39+
40+
error: manual absolute difference pattern without using `abs_diff`
41+
--> tests/ui/manual_abs_diff.rs:36:22
42+
|
43+
LL | const _: usize = if A > B { A - B } else { B - A };
44+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `abs_diff`: `A.abs_diff(B)`
45+
46+
error: aborting due to 5 previous errors
47+

0 commit comments

Comments
 (0)