Skip to content

Commit 1df41b5

Browse files
committed
add manual_abs_diff lint
1 parent fe4dd5b commit 1df41b5

File tree

10 files changed

+392
-1
lines changed

10 files changed

+392
-1
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

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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::ty::is_type_diagnostic_item;
7+
use clippy_utils::{eq_expr_value, peel_blocks};
8+
use rustc_errors::Applicability;
9+
use rustc_hir::{BinOpKind, Expr, ExprKind};
10+
use rustc_lint::{LateContext, LateLintPass};
11+
use rustc_middle::ty;
12+
use rustc_session::impl_lint_pass;
13+
use rustc_span::source_map::Spanned;
14+
use rustc_span::sym;
15+
16+
declare_clippy_lint! {
17+
/// ### What it does
18+
/// Detects patterns like `if a > b { a - b } else { b - a }` and suggests using `a.abs_diff(b)`.
19+
///
20+
/// ### Why is this bad?
21+
/// Using `abs_diff` is shorter, more readable, and avoids control flow.
22+
///
23+
/// ### Examples
24+
/// ```no_run
25+
/// # let (a, b) = (5_usize, 3_usize);
26+
/// if a > b {
27+
/// a - b
28+
/// } else {
29+
/// b - a
30+
/// }
31+
/// # ;
32+
/// ```
33+
/// Use instead:
34+
/// ```no_run
35+
/// # let (a, b) = (5_usize, 3_usize);
36+
/// a.abs_diff(b)
37+
/// # ;
38+
/// ```
39+
#[clippy::version = "1.86.0"]
40+
pub MANUAL_ABS_DIFF,
41+
complexity,
42+
"using an if-else pattern instead of `abs_diff`"
43+
}
44+
45+
impl_lint_pass!(ManualAbsDiff => [MANUAL_ABS_DIFF]);
46+
47+
pub struct ManualAbsDiff {
48+
msrv: Msrv,
49+
}
50+
51+
impl ManualAbsDiff {
52+
pub fn new(conf: &'static Conf) -> Self {
53+
Self { msrv: conf.msrv }
54+
}
55+
}
56+
57+
impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff {
58+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
59+
if !expr.span.from_expansion()
60+
&& let Some(if_expr) = If::hir(expr)
61+
&& let Some(r#else) = if_expr.r#else
62+
&& let ExprKind::Binary(op, rhs, lhs) = if_expr.cond.kind
63+
&& let (BinOpKind::Gt | BinOpKind::Ge, a, b) | (BinOpKind::Lt | BinOpKind::Le, b, a) = (op.node, rhs, lhs)
64+
&& self.are_ty_eligible(cx, a, b)
65+
&& Self::is_sub_expr(cx, if_expr.then, a, b)
66+
&& Self::is_sub_expr(cx, r#else, b, a)
67+
{
68+
let a = Sugg::hir(cx, a, "..").maybe_paren();
69+
let b = Sugg::hir(cx, b, "..");
70+
span_lint_and_sugg(
71+
cx,
72+
MANUAL_ABS_DIFF,
73+
expr.span,
74+
"manual absolute difference pattern without using `abs_diff`",
75+
"replace with `abs_diff`",
76+
format!("{a}.abs_diff({b})"),
77+
Applicability::MachineApplicable,
78+
);
79+
}
80+
}
81+
}
82+
83+
impl ManualAbsDiff {
84+
/// Returns `true` if `a` and `b` are of the same type, and this lint can be applied to that
85+
/// type (currently, any primitive unsigned int, or a `Duration`)
86+
fn are_ty_eligible(&self, cx: &LateContext<'_>, a: &Expr<'_>, b: &Expr<'_>) -> bool {
87+
let a_ty = cx.typeck_results().expr_ty(a).peel_refs();
88+
a_ty == cx.typeck_results().expr_ty(b).peel_refs()
89+
&& ((matches!(a_ty.kind(), ty::Uint(_)) && self.msrv.meets(cx, msrvs::ABS_DIFF))
90+
|| (is_type_diagnostic_item(cx, a_ty, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF)))
91+
}
92+
93+
/// Checks if the given expression is a subtraction operation between two expected expressions,
94+
/// i.e. if `expr` is `{expected_a} - {expected_b}`.
95+
fn is_sub_expr(cx: &LateContext<'_>, expr: &Expr<'_>, expected_a: &Expr<'_>, expected_b: &Expr<'_>) -> bool {
96+
matches!(
97+
peel_blocks(expr).kind,
98+
ExprKind::Binary(
99+
Spanned {
100+
node: BinOpKind::Sub,
101+
..
102+
},
103+
a,
104+
b,
105+
)
106+
if eq_expr_value(cx, a, expected_a) && eq_expr_value(cx, b, expected_b)
107+
)
108+
}
109+
}

Diff for: clippy_utils/src/msrvs.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ msrv_aliases! {
2727
1,84,0 { CONST_OPTION_AS_SLICE }
2828
1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP }
2929
1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP }
30-
1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION }
30+
1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION, DURATION_ABS_DIFF }
3131
1,80,0 { BOX_INTO_ITER, LAZY_CELL }
3232
1,77,0 { C_STR_LITERALS }
3333
1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT }
@@ -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

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#![warn(clippy::manual_abs_diff)]
2+
3+
use std::time::Duration;
4+
5+
fn main() {
6+
let a: usize = 5;
7+
let b: usize = 3;
8+
let c: usize = 8;
9+
let d: usize = 11;
10+
11+
let _ = a.abs_diff(b);
12+
//~^ manual_abs_diff
13+
let _ = b.abs_diff(a);
14+
//~^ manual_abs_diff
15+
16+
let _ = a.abs_diff(b);
17+
//~^ manual_abs_diff
18+
let _ = b.abs_diff(a);
19+
//~^ manual_abs_diff
20+
21+
#[allow(arithmetic_overflow)]
22+
{
23+
let _ = if a > b { b - a } else { a - b };
24+
let _ = if a < b { a - b } else { b - a };
25+
}
26+
27+
let _ = (a + b).abs_diff(c + d);
28+
let _ = (c + d).abs_diff(a + b);
29+
30+
const A: usize = 5;
31+
const B: usize = 3;
32+
// check const context
33+
const _: usize = A.abs_diff(B);
34+
//~^ manual_abs_diff
35+
36+
let a = Duration::from_secs(3);
37+
let b = Duration::from_secs(5);
38+
let _ = a.abs_diff(b);
39+
//~^ manual_abs_diff
40+
41+
let a: isize = 3;
42+
let b: isize = -5;
43+
let _ = if a > b { a - b } else { b - a };
44+
}
45+
46+
// FIXME: bunch of patterns that should be linted
47+
fn fixme() {
48+
let a: usize = 5;
49+
let b: usize = 3;
50+
let c: usize = 8;
51+
let d: usize = 11;
52+
53+
{
54+
let out;
55+
if a > b {
56+
out = a - b;
57+
} else {
58+
out = b - a;
59+
}
60+
}
61+
62+
{
63+
let mut out = 0;
64+
if a > b {
65+
out = a - b;
66+
} else if a < b {
67+
out = b - a;
68+
}
69+
}
70+
71+
#[allow(clippy::implicit_saturating_sub)]
72+
let _ = if a > b {
73+
a - b
74+
} else if a < b {
75+
b - a
76+
} else {
77+
0
78+
};
79+
80+
let a: i32 = 3;
81+
let b: i32 = 5;
82+
let _: u32 = if a > b { (a - b) as u32 } else { (b - a) as u32 };
83+
let _: u32 = if a > b { a - b } else { b - a } as u32;
84+
}
85+
86+
fn non_primitive_ty() {
87+
#[derive(Eq, PartialEq, PartialOrd)]
88+
struct S(i32);
89+
90+
impl std::ops::Sub for S {
91+
type Output = S;
92+
93+
fn sub(self, rhs: Self) -> Self::Output {
94+
Self(self.0 - rhs.0)
95+
}
96+
}
97+
98+
let (a, b) = (S(10), S(20));
99+
let _ = if a < b { b - a } else { a - b };
100+
}

Diff for: tests/ui/manual_abs_diff.rs

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#![warn(clippy::manual_abs_diff)]
2+
3+
use std::time::Duration;
4+
5+
fn main() {
6+
let a: usize = 5;
7+
let b: usize = 3;
8+
let c: usize = 8;
9+
let d: usize = 11;
10+
11+
let _ = if a > b { a - b } else { b - a };
12+
//~^ manual_abs_diff
13+
let _ = if a < b { b - a } else { a - b };
14+
//~^ manual_abs_diff
15+
16+
let _ = if a >= b { a - b } else { b - a };
17+
//~^ manual_abs_diff
18+
let _ = if a <= b { b - a } else { a - b };
19+
//~^ manual_abs_diff
20+
21+
#[allow(arithmetic_overflow)]
22+
{
23+
let _ = if a > b { b - a } else { a - b };
24+
let _ = if a < b { a - b } else { b - a };
25+
}
26+
27+
let _ = if (a + b) > (c + d) {
28+
//~^ manual_abs_diff
29+
(a + b) - (c + d)
30+
} else {
31+
(c + d) - (a + b)
32+
};
33+
let _ = if (a + b) < (c + d) {
34+
//~^ manual_abs_diff
35+
(c + d) - (a + b)
36+
} else {
37+
(a + b) - (c + d)
38+
};
39+
40+
const A: usize = 5;
41+
const B: usize = 3;
42+
// check const context
43+
const _: usize = if A > B { A - B } else { B - A };
44+
//~^ manual_abs_diff
45+
46+
let a = Duration::from_secs(3);
47+
let b = Duration::from_secs(5);
48+
let _ = if a > b { a - b } else { b - a };
49+
//~^ manual_abs_diff
50+
51+
let a: isize = 3;
52+
let b: isize = -5;
53+
let _ = if a > b { a - b } else { b - a };
54+
}
55+
56+
// FIXME: bunch of patterns that should be linted
57+
fn fixme() {
58+
let a: usize = 5;
59+
let b: usize = 3;
60+
let c: usize = 8;
61+
let d: usize = 11;
62+
63+
{
64+
let out;
65+
if a > b {
66+
out = a - b;
67+
} else {
68+
out = b - a;
69+
}
70+
}
71+
72+
{
73+
let mut out = 0;
74+
if a > b {
75+
out = a - b;
76+
} else if a < b {
77+
out = b - a;
78+
}
79+
}
80+
81+
#[allow(clippy::implicit_saturating_sub)]
82+
let _ = if a > b {
83+
a - b
84+
} else if a < b {
85+
b - a
86+
} else {
87+
0
88+
};
89+
90+
let a: i32 = 3;
91+
let b: i32 = 5;
92+
let _: u32 = if a > b { (a - b) as u32 } else { (b - a) as u32 };
93+
let _: u32 = if a > b { a - b } else { b - a } as u32;
94+
}
95+
96+
fn non_primitive_ty() {
97+
#[derive(Eq, PartialEq, PartialOrd)]
98+
struct S(i32);
99+
100+
impl std::ops::Sub for S {
101+
type Output = S;
102+
103+
fn sub(self, rhs: Self) -> Self::Output {
104+
Self(self.0 - rhs.0)
105+
}
106+
}
107+
108+
let (a, b) = (S(10), S(20));
109+
let _ = if a < b { b - a } else { a - b };
110+
}

0 commit comments

Comments
 (0)