Skip to content

Commit 4f82dd8

Browse files
committed
Auto merge of #7948 - 5225225:castlosslessbool, r=llogiq
Lint for bool to integer casts in `cast_lossless` The lint description says > Checks for casts between *numerical* types that may be replaced by safe conversion functions. Which is strictly speaking being violated here, but it seems within the spirit of the lint. I think it is still a useful lint to have, and having a different lint for just this feels excessive. Thoughts? Fixes #7947 changelog: Lint for bool to integer casts in [`cast_lossless`]
2 parents 610b381 + d4c8cb6 commit 4f82dd8

10 files changed

+221
-21
lines changed

clippy_lints/src/bool_assert_comparison.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
7373
if let Some(span) = is_direct_expn_of(expr.span, mac) {
7474
if let Some(args) = higher::extract_assert_macro_args(expr) {
7575
if let [a, b, ..] = args[..] {
76-
let nb_bool_args = is_bool_lit(a) as usize + is_bool_lit(b) as usize;
76+
let nb_bool_args = usize::from(is_bool_lit(a)) + usize::from(is_bool_lit(b));
7777

7878
if nb_bool_args != 1 {
7979
// If there are two boolean arguments, we definitely don't understand

clippy_lints/src/casts/cast_lossless.rs

+32-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::in_constant;
32
use clippy_utils::source::snippet_opt;
43
use clippy_utils::ty::is_isize_or_usize;
4+
use clippy_utils::{in_constant, meets_msrv, msrvs};
55
use rustc_errors::Applicability;
66
use rustc_hir::{Expr, ExprKind};
77
use rustc_lint::LateContext;
88
use rustc_middle::ty::{self, FloatTy, Ty};
9+
use rustc_semver::RustcVersion;
910

1011
use super::{utils, CAST_LOSSLESS};
1112

12-
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
13-
if !should_lint(cx, expr, cast_from, cast_to) {
13+
pub(super) fn check(
14+
cx: &LateContext<'_>,
15+
expr: &Expr<'_>,
16+
cast_op: &Expr<'_>,
17+
cast_from: Ty<'_>,
18+
cast_to: Ty<'_>,
19+
msrv: &Option<RustcVersion>,
20+
) {
21+
if !should_lint(cx, expr, cast_from, cast_to, msrv) {
1422
return;
1523
}
1624

@@ -32,21 +40,36 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, c
3240
},
3341
);
3442

43+
let message = if cast_from.is_bool() {
44+
format!(
45+
"casting `{0:}` to `{1:}` is more cleanly stated with `{1:}::from(_)`",
46+
cast_from, cast_to
47+
)
48+
} else {
49+
format!(
50+
"casting `{}` to `{}` may become silently lossy if you later change the type",
51+
cast_from, cast_to
52+
)
53+
};
54+
3555
span_lint_and_sugg(
3656
cx,
3757
CAST_LOSSLESS,
3858
expr.span,
39-
&format!(
40-
"casting `{}` to `{}` may become silently lossy if you later change the type",
41-
cast_from, cast_to
42-
),
59+
&message,
4360
"try",
4461
format!("{}::from({})", cast_to, sugg),
4562
applicability,
4663
);
4764
}
4865

49-
fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool {
66+
fn should_lint(
67+
cx: &LateContext<'_>,
68+
expr: &Expr<'_>,
69+
cast_from: Ty<'_>,
70+
cast_to: Ty<'_>,
71+
msrv: &Option<RustcVersion>,
72+
) -> bool {
5073
// Do not suggest using From in consts/statics until it is valid to do so (see #2267).
5174
if in_constant(cx, expr.hir_id) {
5275
return false;
@@ -72,7 +95,7 @@ fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to
7295
};
7396
from_nbits < to_nbits
7497
},
75-
98+
(false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv.as_ref(), &msrvs::FROM_BOOL) => true,
7699
(_, _) => {
77100
matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64))
78101
},

clippy_lints/src/casts/mod.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -439,12 +439,16 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
439439
fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
440440
fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
441441
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
442-
if cast_from.is_numeric() && cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
443-
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
444-
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
445-
cast_precision_loss::check(cx, expr, cast_from, cast_to);
446-
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to);
447-
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
442+
443+
if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
444+
if cast_from.is_numeric() {
445+
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
446+
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
447+
cast_precision_loss::check(cx, expr, cast_from, cast_to);
448+
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
449+
}
450+
451+
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
448452
}
449453
}
450454

clippy_utils/src/msrvs.rs

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ msrv_aliases! {
2828
1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
2929
1,34,0 { TRY_FROM }
3030
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
31+
1,28,0 { FROM_BOOL }
3132
1,17,0 { FIELD_INIT_SHORTHAND, STATIC_IN_CONST }
3233
1,16,0 { STR_REPEAT }
3334
}

clippy_utils/src/source.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub fn reindent_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<us
128128
fn reindent_multiline_inner(s: &str, ignore_first: bool, indent: Option<usize>, ch: char) -> String {
129129
let x = s
130130
.lines()
131-
.skip(ignore_first as usize)
131+
.skip(usize::from(ignore_first))
132132
.filter_map(|l| {
133133
if l.is_empty() {
134134
None

tests/ui/cast_lossless_bool.fixed

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// run-rustfix
2+
3+
#![allow(dead_code)]
4+
#![warn(clippy::cast_lossless)]
5+
6+
fn main() {
7+
// Test clippy::cast_lossless with casts to integer types
8+
let _ = u8::from(true);
9+
let _ = u16::from(true);
10+
let _ = u32::from(true);
11+
let _ = u64::from(true);
12+
let _ = u128::from(true);
13+
let _ = usize::from(true);
14+
15+
let _ = i8::from(true);
16+
let _ = i16::from(true);
17+
let _ = i32::from(true);
18+
let _ = i64::from(true);
19+
let _ = i128::from(true);
20+
let _ = isize::from(true);
21+
22+
// Test with an expression wrapped in parens
23+
let _ = u16::from(true | false);
24+
}
25+
26+
// The lint would suggest using `u32::from(input)` here but the `XX::from` function is not const,
27+
// so we skip the lint if the expression is in a const fn.
28+
// See #3656
29+
const fn abc(input: bool) -> u32 {
30+
input as u32
31+
}
32+
33+
// Same as the above issue. We can't suggest `::from` in const fns in impls
34+
mod cast_lossless_in_impl {
35+
struct A;
36+
37+
impl A {
38+
pub const fn convert(x: bool) -> u64 {
39+
x as u64
40+
}
41+
}
42+
}

tests/ui/cast_lossless_bool.rs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// run-rustfix
2+
3+
#![allow(dead_code)]
4+
#![warn(clippy::cast_lossless)]
5+
6+
fn main() {
7+
// Test clippy::cast_lossless with casts to integer types
8+
let _ = true as u8;
9+
let _ = true as u16;
10+
let _ = true as u32;
11+
let _ = true as u64;
12+
let _ = true as u128;
13+
let _ = true as usize;
14+
15+
let _ = true as i8;
16+
let _ = true as i16;
17+
let _ = true as i32;
18+
let _ = true as i64;
19+
let _ = true as i128;
20+
let _ = true as isize;
21+
22+
// Test with an expression wrapped in parens
23+
let _ = (true | false) as u16;
24+
}
25+
26+
// The lint would suggest using `u32::from(input)` here but the `XX::from` function is not const,
27+
// so we skip the lint if the expression is in a const fn.
28+
// See #3656
29+
const fn abc(input: bool) -> u32 {
30+
input as u32
31+
}
32+
33+
// Same as the above issue. We can't suggest `::from` in const fns in impls
34+
mod cast_lossless_in_impl {
35+
struct A;
36+
37+
impl A {
38+
pub const fn convert(x: bool) -> u64 {
39+
x as u64
40+
}
41+
}
42+
}

tests/ui/cast_lossless_bool.stderr

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
error: casting `bool` to `u8` is more cleanly stated with `u8::from(_)`
2+
--> $DIR/cast_lossless_bool.rs:8:13
3+
|
4+
LL | let _ = true as u8;
5+
| ^^^^^^^^^^ help: try: `u8::from(true)`
6+
|
7+
= note: `-D clippy::cast-lossless` implied by `-D warnings`
8+
9+
error: casting `bool` to `u16` is more cleanly stated with `u16::from(_)`
10+
--> $DIR/cast_lossless_bool.rs:9:13
11+
|
12+
LL | let _ = true as u16;
13+
| ^^^^^^^^^^^ help: try: `u16::from(true)`
14+
15+
error: casting `bool` to `u32` is more cleanly stated with `u32::from(_)`
16+
--> $DIR/cast_lossless_bool.rs:10:13
17+
|
18+
LL | let _ = true as u32;
19+
| ^^^^^^^^^^^ help: try: `u32::from(true)`
20+
21+
error: casting `bool` to `u64` is more cleanly stated with `u64::from(_)`
22+
--> $DIR/cast_lossless_bool.rs:11:13
23+
|
24+
LL | let _ = true as u64;
25+
| ^^^^^^^^^^^ help: try: `u64::from(true)`
26+
27+
error: casting `bool` to `u128` is more cleanly stated with `u128::from(_)`
28+
--> $DIR/cast_lossless_bool.rs:12:13
29+
|
30+
LL | let _ = true as u128;
31+
| ^^^^^^^^^^^^ help: try: `u128::from(true)`
32+
33+
error: casting `bool` to `usize` is more cleanly stated with `usize::from(_)`
34+
--> $DIR/cast_lossless_bool.rs:13:13
35+
|
36+
LL | let _ = true as usize;
37+
| ^^^^^^^^^^^^^ help: try: `usize::from(true)`
38+
39+
error: casting `bool` to `i8` is more cleanly stated with `i8::from(_)`
40+
--> $DIR/cast_lossless_bool.rs:15:13
41+
|
42+
LL | let _ = true as i8;
43+
| ^^^^^^^^^^ help: try: `i8::from(true)`
44+
45+
error: casting `bool` to `i16` is more cleanly stated with `i16::from(_)`
46+
--> $DIR/cast_lossless_bool.rs:16:13
47+
|
48+
LL | let _ = true as i16;
49+
| ^^^^^^^^^^^ help: try: `i16::from(true)`
50+
51+
error: casting `bool` to `i32` is more cleanly stated with `i32::from(_)`
52+
--> $DIR/cast_lossless_bool.rs:17:13
53+
|
54+
LL | let _ = true as i32;
55+
| ^^^^^^^^^^^ help: try: `i32::from(true)`
56+
57+
error: casting `bool` to `i64` is more cleanly stated with `i64::from(_)`
58+
--> $DIR/cast_lossless_bool.rs:18:13
59+
|
60+
LL | let _ = true as i64;
61+
| ^^^^^^^^^^^ help: try: `i64::from(true)`
62+
63+
error: casting `bool` to `i128` is more cleanly stated with `i128::from(_)`
64+
--> $DIR/cast_lossless_bool.rs:19:13
65+
|
66+
LL | let _ = true as i128;
67+
| ^^^^^^^^^^^^ help: try: `i128::from(true)`
68+
69+
error: casting `bool` to `isize` is more cleanly stated with `isize::from(_)`
70+
--> $DIR/cast_lossless_bool.rs:20:13
71+
|
72+
LL | let _ = true as isize;
73+
| ^^^^^^^^^^^^^ help: try: `isize::from(true)`
74+
75+
error: casting `bool` to `u16` is more cleanly stated with `u16::from(_)`
76+
--> $DIR/cast_lossless_bool.rs:23:13
77+
|
78+
LL | let _ = (true | false) as u16;
79+
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `u16::from(true | false)`
80+
81+
error: aborting due to 13 previous errors
82+

tests/ui/min_rust_version_attr.rs

+6
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ fn unnest_or_patterns() {
140140
#[cfg_attr(rustfmt, rustfmt_skip)]
141141
fn deprecated_cfg_attr() {}
142142

143+
#[warn(clippy::cast_lossless)]
144+
fn int_from_bool() -> u8 {
145+
true as u8
146+
}
147+
143148
fn main() {
144149
filter_map_next();
145150
checked_conversion();
@@ -156,6 +161,7 @@ fn main() {
156161
map_unwrap_or();
157162
missing_const_for_fn();
158163
unnest_or_patterns();
164+
int_from_bool();
159165
}
160166

161167
mod just_under_msrv {

tests/ui/min_rust_version_attr.stderr

+4-4
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:180:24
2+
--> $DIR/min_rust_version_attr.rs:186: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:179:9
9+
--> $DIR/min_rust_version_attr.rs:185: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:192:24
20+
--> $DIR/min_rust_version_attr.rs:198: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:191:9
26+
--> $DIR/min_rust_version_attr.rs:197:9
2727
|
2828
LL | if s.starts_with("hello, ") {
2929
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)