diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 88deb4565eb2..ce2a9161d5f9 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -1,9 +1,11 @@ use clippy_utils::consts::{constant, Constant}; -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::expr_or_init; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize}; use rustc_ast::ast; use rustc_attr::IntType; +use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; @@ -145,7 +147,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, ); return; } - format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}",) + format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}") }, (ty::Float(_), true) => { @@ -159,5 +161,18 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, _ => return, }; - span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg); + let mut applicability = Applicability::MachineApplicable; + + let snippet = snippet_with_applicability(cx, expr.span, "x", &mut applicability); + let name_of_cast_from = snippet.split(" as").next().unwrap_or("x"); + + span_lint_and_sugg( + cx, + CAST_POSSIBLE_TRUNCATION, + expr.span, + &msg, + "avoid silent truncation by using", + format!("{cast_to}::try_from({name_of_cast_from}).unwrap()"), + applicability, + ); } diff --git a/src/docs/cast_possible_truncation.txt b/src/docs/cast_possible_truncation.txt index 0b164848cc7c..3d8e0e27200c 100644 --- a/src/docs/cast_possible_truncation.txt +++ b/src/docs/cast_possible_truncation.txt @@ -6,11 +6,21 @@ default. ### Why is this bad? In some problem domains, it is good practice to avoid truncation. This lint can be activated to help assess where additional -checks could be beneficial. +checks could be beneficial, and suggests implementing TryFrom trait. ### Example ``` fn as_u8(x: u64) -> u8 { x as u8 } -``` \ No newline at end of file +``` + +will silently truncate requiring detection and handling after the fact. + +``` +fn as_u8(x: u64) -> u8 { + u8::try_from(x).unwrap() +} +``` + +does not, but can now panic. \ No newline at end of file diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 0c63b4af3086..5d962642f746 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -40,7 +40,7 @@ error: casting `f32` to `i32` may truncate the value --> $DIR/cast.rs:24:5 | LL | 1f32 as i32; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: avoid silent truncation by using: `i32::try_from(1f32).unwrap()` | = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` @@ -48,7 +48,7 @@ error: casting `f32` to `u32` may truncate the value --> $DIR/cast.rs:25:5 | LL | 1f32 as u32; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: avoid silent truncation by using: `u32::try_from(1f32).unwrap()` error: casting `f32` to `u32` may lose the sign of the value --> $DIR/cast.rs:25:5 @@ -62,31 +62,31 @@ error: casting `f64` to `f32` may truncate the value --> $DIR/cast.rs:26:5 | LL | 1f64 as f32; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: avoid silent truncation by using: `f32::try_from(1f64).unwrap()` error: casting `i32` to `i8` may truncate the value --> $DIR/cast.rs:27:5 | LL | 1i32 as i8; - | ^^^^^^^^^^ + | ^^^^^^^^^^ help: avoid silent truncation by using: `i8::try_from(1i32).unwrap()` error: casting `i32` to `u8` may truncate the value --> $DIR/cast.rs:28:5 | LL | 1i32 as u8; - | ^^^^^^^^^^ + | ^^^^^^^^^^ help: avoid silent truncation by using: `u8::try_from(1i32).unwrap()` error: casting `f64` to `isize` may truncate the value --> $DIR/cast.rs:29:5 | LL | 1f64 as isize; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: avoid silent truncation by using: `isize::try_from(1f64).unwrap()` error: casting `f64` to `usize` may truncate the value --> $DIR/cast.rs:30:5 | LL | 1f64 as usize; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: avoid silent truncation by using: `usize::try_from(1f64).unwrap()` error: casting `f64` to `usize` may lose the sign of the value --> $DIR/cast.rs:30:5 @@ -142,19 +142,19 @@ error: casting `i64` to `i8` may truncate the value --> $DIR/cast.rs:108:5 | LL | (-99999999999i64).min(1) as i8; // should be linted because signed - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: avoid silent truncation by using: `i8::try_from((-99999999999i64).min(1)).unwrap()` error: casting `u64` to `u8` may truncate the value --> $DIR/cast.rs:120:5 | LL | 999999u64.clamp(0, 256) as u8; // should still be linted - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: avoid silent truncation by using: `u8::try_from(999999u64.clamp(0, 256)).unwrap()` error: casting `main::E2` to `u8` may truncate the value --> $DIR/cast.rs:141:21 | LL | let _ = self as u8; - | ^^^^^^^^^^ + | ^^^^^^^^^^ help: avoid silent truncation by using: `u8::try_from(self).unwrap()` error: casting `main::E2::B` to `u8` will truncate the value --> $DIR/cast.rs:142:21 @@ -168,7 +168,7 @@ error: casting `main::E5` to `i8` may truncate the value --> $DIR/cast.rs:178:21 | LL | let _ = self as i8; - | ^^^^^^^^^^ + | ^^^^^^^^^^ help: avoid silent truncation by using: `i8::try_from(self).unwrap()` error: casting `main::E5::A` to `i8` will truncate the value --> $DIR/cast.rs:179:21 @@ -180,31 +180,31 @@ error: casting `main::E6` to `i16` may truncate the value --> $DIR/cast.rs:193:21 | LL | let _ = self as i16; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: avoid silent truncation by using: `i16::try_from(self).unwrap()` error: casting `main::E7` to `usize` may truncate the value on targets with 32-bit wide pointers --> $DIR/cast.rs:208:21 | LL | let _ = self as usize; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: avoid silent truncation by using: `usize::try_from(self).unwrap()` error: casting `main::E10` to `u16` may truncate the value --> $DIR/cast.rs:249:21 | LL | let _ = self as u16; - | ^^^^^^^^^^^ + | ^^^^^^^^^^^ help: avoid silent truncation by using: `u16::try_from(self).unwrap()` error: casting `u32` to `u8` may truncate the value --> $DIR/cast.rs:257:13 | LL | let c = (q >> 16) as u8; - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ help: avoid silent truncation by using: `u8::try_from((q >> 16)).unwrap()` error: casting `u32` to `u8` may truncate the value --> $DIR/cast.rs:260:13 | LL | let c = (q / 1000) as u8; - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^ help: avoid silent truncation by using: `u8::try_from((q / 1000)).unwrap()` error: aborting due to 33 previous errors diff --git a/tests/ui/cast_size.stderr b/tests/ui/cast_size.stderr index 95552f2e2853..5d941db21150 100644 --- a/tests/ui/cast_size.stderr +++ b/tests/ui/cast_size.stderr @@ -2,7 +2,7 @@ error: casting `isize` to `i8` may truncate the value --> $DIR/cast_size.rs:12:5 | LL | 1isize as i8; - | ^^^^^^^^^^^^ + | ^^^^^^^^^^^^ help: avoid silent truncation by using: `i8::try_from(1isize).unwrap()` | = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` @@ -36,25 +36,25 @@ error: casting `isize` to `i32` may truncate the value on targets with 64-bit wi --> $DIR/cast_size.rs:19:5 | LL | 1isize as i32; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: avoid silent truncation by using: `i32::try_from(1isize).unwrap()` error: casting `isize` to `u32` may truncate the value on targets with 64-bit wide pointers --> $DIR/cast_size.rs:20:5 | LL | 1isize as u32; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: avoid silent truncation by using: `u32::try_from(1isize).unwrap()` error: casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers --> $DIR/cast_size.rs:21:5 | LL | 1usize as u32; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: avoid silent truncation by using: `u32::try_from(1usize).unwrap()` error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers --> $DIR/cast_size.rs:22:5 | LL | 1usize as i32; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: avoid silent truncation by using: `i32::try_from(1usize).unwrap()` error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers --> $DIR/cast_size.rs:22:5 @@ -68,19 +68,19 @@ error: casting `i64` to `isize` may truncate the value on targets with 32-bit wi --> $DIR/cast_size.rs:24:5 | LL | 1i64 as isize; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: avoid silent truncation by using: `isize::try_from(1i64).unwrap()` error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers --> $DIR/cast_size.rs:25:5 | LL | 1i64 as usize; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: avoid silent truncation by using: `usize::try_from(1i64).unwrap()` error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers --> $DIR/cast_size.rs:26:5 | LL | 1u64 as isize; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: avoid silent truncation by using: `isize::try_from(1u64).unwrap()` error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers --> $DIR/cast_size.rs:26:5 @@ -92,7 +92,7 @@ error: casting `u64` to `usize` may truncate the value on targets with 32-bit wi --> $DIR/cast_size.rs:27:5 | LL | 1u64 as usize; - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ help: avoid silent truncation by using: `usize::try_from(1u64).unwrap()` error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers --> $DIR/cast_size.rs:28:5