Skip to content

Commit 574bf88

Browse files
committed
Auto merge of #8635 - pbor:unsigned-abs, r=giraffate
Add a lint to detect cast to unsigned for abs() and suggest unsigned_… …abs() changelog: Add a [`cast_abs_to_unsigned`] that checks for uses of `abs()` that are cast to the corresponding unsigned integer type and suggest to replace them with `unsigned_abs()`.
2 parents 650a0e5 + f2bbb5f commit 574bf88

14 files changed

+111
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3201,6 +3201,7 @@ Released 2018-09-13
32013201
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
32023202
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
32033203
[`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons
3204+
[`cast_abs_to_unsigned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_abs_to_unsigned
32043205
[`cast_enum_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_constructor
32053206
[`cast_enum_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_truncation
32063207
[`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::sugg::Sugg;
3+
use clippy_utils::{meets_msrv, msrvs};
4+
use if_chain::if_chain;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{Expr, ExprKind};
7+
use rustc_lint::LateContext;
8+
use rustc_middle::ty::Ty;
9+
use rustc_semver::RustcVersion;
10+
11+
use super::CAST_ABS_TO_UNSIGNED;
12+
13+
pub(super) fn check(
14+
cx: &LateContext<'_>,
15+
expr: &Expr<'_>,
16+
cast_expr: &Expr<'_>,
17+
cast_from: Ty<'_>,
18+
cast_to: Ty<'_>,
19+
msrv: &Option<RustcVersion>,
20+
) {
21+
if_chain! {
22+
if meets_msrv(msrv.as_ref(), &msrvs::UNSIGNED_ABS);
23+
if cast_from.is_integral();
24+
if cast_to.is_integral();
25+
if cast_from.is_signed();
26+
if !cast_to.is_signed();
27+
if let ExprKind::MethodCall(method_path, args, _) = cast_expr.kind;
28+
if let method_name = method_path.ident.name.as_str();
29+
if method_name == "abs";
30+
then {
31+
span_lint_and_sugg(
32+
cx,
33+
CAST_ABS_TO_UNSIGNED,
34+
expr.span,
35+
&format!("casting the result of `{}::{}()` to {}", cast_from, method_name, cast_to),
36+
"replace with",
37+
format!("{}.unsigned_abs()", Sugg::hir(cx, &args[0], "..")),
38+
Applicability::MachineApplicable,
39+
);
40+
}
41+
}
42+
}

clippy_lints/src/casts/mod.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod cast_abs_to_unsigned;
12
mod cast_enum_constructor;
23
mod cast_lossless;
34
mod cast_possible_truncation;
@@ -473,6 +474,28 @@ declare_clippy_lint! {
473474
"casts from an enum tuple constructor to an integer"
474475
}
475476

477+
declare_clippy_lint! {
478+
/// ### What it does
479+
/// Checks for uses of the `abs()` method that cast the result to unsigned.
480+
///
481+
/// ### Why is this bad?
482+
/// The `unsigned_abs()` method avoids panic when called on the MIN value.
483+
///
484+
/// ### Example
485+
/// ```rust
486+
/// let x: i32 = -42;
487+
/// let y: u32 = x.abs() as u32;
488+
/// ```
489+
/// Use instead:
490+
/// let x: i32 = -42;
491+
/// let y: u32 = x.unsigned_abs();
492+
/// ```
493+
#[clippy::version = "1.61.0"]
494+
pub CAST_ABS_TO_UNSIGNED,
495+
suspicious,
496+
"casting the result of `abs()` to an unsigned integer can panic"
497+
}
498+
476499
pub struct Casts {
477500
msrv: Option<RustcVersion>,
478501
}
@@ -500,7 +523,8 @@ impl_lint_pass!(Casts => [
500523
CHAR_LIT_AS_U8,
501524
PTR_AS_PTR,
502525
CAST_ENUM_TRUNCATION,
503-
CAST_ENUM_CONSTRUCTOR
526+
CAST_ENUM_CONSTRUCTOR,
527+
CAST_ABS_TO_UNSIGNED
504528
]);
505529

506530
impl<'tcx> LateLintPass<'tcx> for Casts {
@@ -536,6 +560,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
536560
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
537561
cast_precision_loss::check(cx, expr, cast_from, cast_to);
538562
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
563+
cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
539564
}
540565
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
541566
cast_enum_constructor::check(cx, expr, cast_expr, cast_from);

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
2323
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
2424
LintId::of(booleans::LOGIC_BUG),
2525
LintId::of(booleans::NONMINIMAL_BOOL),
26+
LintId::of(casts::CAST_ABS_TO_UNSIGNED),
2627
LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
2728
LintId::of(casts::CAST_ENUM_TRUNCATION),
2829
LintId::of(casts::CAST_REF_TO_MUT),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ store.register_lints(&[
7070
cargo::REDUNDANT_FEATURE_NAMES,
7171
cargo::WILDCARD_DEPENDENCIES,
7272
case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
73+
casts::CAST_ABS_TO_UNSIGNED,
7374
casts::CAST_ENUM_CONSTRUCTOR,
7475
casts::CAST_ENUM_TRUNCATION,
7576
casts::CAST_LOSSLESS,

clippy_lints/src/lib.register_suspicious.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
77
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
88
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
99
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
10+
LintId::of(casts::CAST_ABS_TO_UNSIGNED),
1011
LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
1112
LintId::of(casts::CAST_ENUM_TRUNCATION),
1213
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),

clippy_lints/src/utils/conf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ define_Conf! {
156156
///
157157
/// Suppress lints whenever the suggested change would cause breakage for other crates.
158158
(avoid_breaking_exported_api: bool = true),
159-
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, EXPECT_ERR.
159+
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED.
160160
///
161161
/// The minimum rust version that the project supports
162162
(msrv: Option<String> = None),

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ macro_rules! msrv_aliases {
1414
msrv_aliases! {
1515
1,53,0 { OR_PATTERNS, MANUAL_BITS }
1616
1,52,0 { STR_SPLIT_ONCE }
17-
1,51,0 { BORROW_AS_PTR }
17+
1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS }
1818
1,50,0 { BOOL_THEN }
1919
1,47,0 { TAU }
2020
1,46,0 { CONST_IF_MATCH }

tests/ui/cast.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
clippy::cast_sign_loss,
88
clippy::cast_possible_wrap
99
)]
10-
#[allow(clippy::no_effect, clippy::unnecessary_operation)]
10+
#[allow(clippy::cast_abs_to_unsigned, clippy::no_effect, clippy::unnecessary_operation)]
1111
fn main() {
1212
// Test clippy::cast_precision_loss
1313
let x0 = 1i32;

tests/ui/cast_abs_to_unsigned.fixed

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// run-rustfix
2+
#![warn(clippy::cast_abs_to_unsigned)]
3+
4+
fn main() {
5+
let x: i32 = -42;
6+
let y: u32 = x.unsigned_abs();
7+
println!("The absolute value of {} is {}", x, y);
8+
}

tests/ui/cast_abs_to_unsigned.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// run-rustfix
2+
#![warn(clippy::cast_abs_to_unsigned)]
3+
4+
fn main() {
5+
let x: i32 = -42;
6+
let y: u32 = x.abs() as u32;
7+
println!("The absolute value of {} is {}", x, y);
8+
}

tests/ui/cast_abs_to_unsigned.stderr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: casting the result of `i32::abs()` to u32
2+
--> $DIR/cast_abs_to_unsigned.rs:6:18
3+
|
4+
LL | let y: u32 = x.abs() as u32;
5+
| ^^^^^^^^^^^^^^ help: replace with: `x.unsigned_abs()`
6+
|
7+
= note: `-D clippy::cast-abs-to-unsigned` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+

tests/ui/min_rust_version_attr.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ fn err_expect() {
150150
x.err().expect("Testing expect_err");
151151
}
152152

153+
fn cast_abs_to_unsigned() {
154+
let x: i32 = 10;
155+
assert_eq!(10u32, x.abs() as u32);
156+
}
157+
153158
fn main() {
154159
filter_map_next();
155160
checked_conversion();
@@ -168,6 +173,7 @@ fn main() {
168173
unnest_or_patterns();
169174
int_from_bool();
170175
err_expect();
176+
cast_abs_to_unsigned();
171177
}
172178

173179
mod just_under_msrv {

tests/ui/min_rust_version_attr.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
error: stripping a prefix manually
2-
--> $DIR/min_rust_version_attr.rs:192:24
2+
--> $DIR/min_rust_version_attr.rs:198:24
33
|
44
LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
55
| ^^^^^^^^^^^^^^^^^^^^
66
|
77
= note: `-D clippy::manual-strip` implied by `-D warnings`
88
note: the prefix was tested here
9-
--> $DIR/min_rust_version_attr.rs:191:9
9+
--> $DIR/min_rust_version_attr.rs:197:9
1010
|
1111
LL | if s.starts_with("hello, ") {
1212
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -17,13 +17,13 @@ LL ~ assert_eq!(<stripped>.to_uppercase(), "WORLD!");
1717
|
1818

1919
error: stripping a prefix manually
20-
--> $DIR/min_rust_version_attr.rs:204:24
20+
--> $DIR/min_rust_version_attr.rs:210:24
2121
|
2222
LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
2323
| ^^^^^^^^^^^^^^^^^^^^
2424
|
2525
note: the prefix was tested here
26-
--> $DIR/min_rust_version_attr.rs:203:9
26+
--> $DIR/min_rust_version_attr.rs:209:9
2727
|
2828
LL | if s.starts_with("hello, ") {
2929
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)