From 25aa03a707f28948bd8cfab10685868a0cac281e Mon Sep 17 00:00:00 2001 From: lapla-cogito Date: Tue, 25 Mar 2025 22:06:20 +0900 Subject: [PATCH 1/3] `cast_lossless`: lint when converting `char`, `usize`, `isize` and float as well --- clippy_lints/src/casts/cast_lossless.rs | 44 ++++++++++--------------- tests/ui/cast_lossless_char.fixed | 20 +++++++++++ tests/ui/cast_lossless_char.rs | 20 +++++++++++ tests/ui/cast_lossless_char.stderr | 43 ++++++++++++++++++++++++ tests/ui/cast_lossless_integer.fixed | 16 +++++++-- tests/ui/cast_lossless_integer.rs | 14 +++++++- tests/ui/cast_lossless_integer.stderr | 21 ++---------- 7 files changed, 130 insertions(+), 48 deletions(-) create mode 100644 tests/ui/cast_lossless_char.fixed create mode 100644 tests/ui/cast_lossless_char.rs create mode 100644 tests/ui/cast_lossless_char.stderr diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs index 3ae43732dc03..49b13dccbeb5 100644 --- a/clippy_lints/src/casts/cast_lossless.rs +++ b/clippy_lints/src/casts/cast_lossless.rs @@ -3,11 +3,10 @@ use clippy_utils::is_in_const_context; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::SpanRangeExt; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_isize_or_usize; use rustc_errors::Applicability; use rustc_hir::{Expr, QPath, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, FloatTy, Ty}; +use rustc_middle::ty::{self, FloatTy, IntTy, Ty, UintTy}; use rustc_span::hygiene; use super::{CAST_LOSSLESS, utils}; @@ -21,7 +20,7 @@ pub(super) fn check( cast_to_hir: &rustc_hir::Ty<'_>, msrv: Msrv, ) { - if !should_lint(cx, cast_from, cast_to, msrv) { + if !should_lint(cx, cast_from, cast_to, msrv) || !expr.span.ctxt().is_root() { return; } @@ -47,7 +46,8 @@ pub(super) fn check( ); }, // Don't suggest `A<_>::B::From(x)` or `macro!()::from(x)` - kind if matches!(kind, TyKind::Path(QPath::Resolved(_, path)) if path.segments.iter().any(|s| s.args.is_some())) + kind if matches!(kind, TyKind::Path(QPath::Resolved(_, path)) + if path.segments.iter().any(|s| s.args.is_some())) || !cast_to_hir.span.eq_ctxt(expr.span) => { diag.span_suggestion_verbose( @@ -76,29 +76,21 @@ fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: M return false; } - match (cast_from.is_integral(), cast_to.is_integral()) { - (true, true) => { - let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed(); - let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); - !is_isize_or_usize(cast_from) - && !is_isize_or_usize(cast_to) - && from_nbits < to_nbits - && !cast_signed_to_unsigned + match (cast_from.kind(), cast_to.kind()) { + (ty::Bool, ty::Uint(_) | ty::Int(_)) => msrv.meets(cx, msrvs::FROM_BOOL), + (ty::Uint(_), ty::Uint(UintTy::Usize)) | (ty::Uint(UintTy::U8) | ty::Int(_), ty::Int(IntTy::Isize)) => { + utils::int_ty_to_nbits(cast_from, cx.tcx) <= 16 }, - - (true, false) => { - let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); - let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() { - 32 - } else { - 64 - }; - !is_isize_or_usize(cast_from) && from_nbits < to_nbits - }, - (false, true) if matches!(cast_from.kind(), ty::Bool) && msrv.meets(cx, msrvs::FROM_BOOL) => true, - (_, _) => { - matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64)) + // No `f16` to `f32`: https://github.com/rust-lang/rust/issues/123831 + (ty::Uint(UintTy::Usize) | ty::Int(IntTy::Isize), _) + | (_, ty::Uint(UintTy::Usize) | ty::Int(IntTy::Isize)) + | (ty::Float(FloatTy::F16), ty::Float(FloatTy::F32)) => false, + (ty::Uint(_) | ty::Int(_), ty::Int(_)) | (ty::Uint(_), ty::Uint(_)) => { + utils::int_ty_to_nbits(cast_from, cx.tcx) < utils::int_ty_to_nbits(cast_to, cx.tcx) }, + (ty::Uint(_) | ty::Int(_), ty::Float(fl)) => utils::int_ty_to_nbits(cast_from, cx.tcx) < fl.bit_width(), + (ty::Char, ty::Uint(_)) => utils::int_ty_to_nbits(cast_to, cx.tcx) >= 32, + (ty::Float(fl_from), ty::Float(fl_to)) => fl_from.bit_width() < fl_to.bit_width(), + _ => false, } } diff --git a/tests/ui/cast_lossless_char.fixed b/tests/ui/cast_lossless_char.fixed new file mode 100644 index 000000000000..9068696633de --- /dev/null +++ b/tests/ui/cast_lossless_char.fixed @@ -0,0 +1,20 @@ +#![warn(clippy::cast_lossless)] +#![allow(clippy::char_lit_as_u8)] + +type I32 = i32; +type U32 = u32; + +fn main() { + let _ = u32::from('a'); + //~^ cast_lossless + let _ = 'a' as i32; + let _ = u64::from('a'); + //~^ cast_lossless + let _ = 'a' as i64; + let _ = U32::from('a'); + //~^ cast_lossless + let _ = 'a' as I32; + + let _ = 'a' as u8; + let _ = 'a' as i8; +} diff --git a/tests/ui/cast_lossless_char.rs b/tests/ui/cast_lossless_char.rs new file mode 100644 index 000000000000..1c86ba0f2d45 --- /dev/null +++ b/tests/ui/cast_lossless_char.rs @@ -0,0 +1,20 @@ +#![warn(clippy::cast_lossless)] +#![allow(clippy::char_lit_as_u8)] + +type I32 = i32; +type U32 = u32; + +fn main() { + let _ = 'a' as u32; + //~^ cast_lossless + let _ = 'a' as i32; + let _ = 'a' as u64; + //~^ cast_lossless + let _ = 'a' as i64; + let _ = 'a' as U32; + //~^ cast_lossless + let _ = 'a' as I32; + + let _ = 'a' as u8; + let _ = 'a' as i8; +} diff --git a/tests/ui/cast_lossless_char.stderr b/tests/ui/cast_lossless_char.stderr new file mode 100644 index 000000000000..5e5a6a8b6b50 --- /dev/null +++ b/tests/ui/cast_lossless_char.stderr @@ -0,0 +1,43 @@ +error: casts from `char` to `u32` can be expressed infallibly using `From` + --> tests/ui/cast_lossless_char.rs:8:13 + | +LL | let _ = 'a' as u32; + | ^^^^^^^^^^ + | + = help: an `as` cast can become silently lossy if the types change in the future + = note: `-D clippy::cast-lossless` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::cast_lossless)]` +help: use `u32::from` instead + | +LL - let _ = 'a' as u32; +LL + let _ = u32::from('a'); + | + +error: casts from `char` to `u64` can be expressed infallibly using `From` + --> tests/ui/cast_lossless_char.rs:11:13 + | +LL | let _ = 'a' as u64; + | ^^^^^^^^^^ + | + = help: an `as` cast can become silently lossy if the types change in the future +help: use `u64::from` instead + | +LL - let _ = 'a' as u64; +LL + let _ = u64::from('a'); + | + +error: casts from `char` to `u32` can be expressed infallibly using `From` + --> tests/ui/cast_lossless_char.rs:14:13 + | +LL | let _ = 'a' as U32; + | ^^^^^^^^^^ + | + = help: an `as` cast can become silently lossy if the types change in the future +help: use `U32::from` instead + | +LL - let _ = 'a' as U32; +LL + let _ = U32::from('a'); + | + +error: aborting due to 3 previous errors + diff --git a/tests/ui/cast_lossless_integer.fixed b/tests/ui/cast_lossless_integer.fixed index 2af2dbd1282d..2fbad06d8496 100644 --- a/tests/ui/cast_lossless_integer.fixed +++ b/tests/ui/cast_lossless_integer.fixed @@ -156,8 +156,7 @@ fn issue11458() { fn issue12695() { macro_rules! in_macro { () => { - u32::from(1u8) - //~^ cast_lossless + 1u8 as u32 }; } @@ -176,3 +175,16 @@ fn ty_from_macro() { } const IN_CONST: u64 = 0u8 as u64; + +fn macros() { + macro_rules! mac { + (to_i32 $x:expr) => { $x as u32 }; + (zero_as $t:ty) => { 0u8 as $t }; + ($x:expr => $t:ty) => { $x as $t }; + ($x:expr, $k:ident, $t:ty) => { $x $k $t }; + } + let _ = mac!(to_i32 0u16); + let _ = mac!(zero_as u32); + let _ = mac!(0u8 => u32); + let _ = mac!(0u8, as, u32); +} diff --git a/tests/ui/cast_lossless_integer.rs b/tests/ui/cast_lossless_integer.rs index c65b725bb729..c6f6cb9bb128 100644 --- a/tests/ui/cast_lossless_integer.rs +++ b/tests/ui/cast_lossless_integer.rs @@ -157,7 +157,6 @@ fn issue12695() { macro_rules! in_macro { () => { 1u8 as u32 - //~^ cast_lossless }; } @@ -176,3 +175,16 @@ fn ty_from_macro() { } const IN_CONST: u64 = 0u8 as u64; + +fn macros() { + macro_rules! mac { + (to_i32 $x:expr) => { $x as u32 }; + (zero_as $t:ty) => { 0u8 as $t }; + ($x:expr => $t:ty) => { $x as $t }; + ($x:expr, $k:ident, $t:ty) => { $x $k $t }; + } + let _ = mac!(to_i32 0u16); + let _ = mac!(zero_as u32); + let _ = mac!(0u8 => u32); + let _ = mac!(0u8, as, u32); +} diff --git a/tests/ui/cast_lossless_integer.stderr b/tests/ui/cast_lossless_integer.stderr index 9852eca71279..205f9d6ea0cc 100644 --- a/tests/ui/cast_lossless_integer.stderr +++ b/tests/ui/cast_lossless_integer.stderr @@ -495,24 +495,7 @@ LL + let _ = i32::from(sign_cast!(x, u8, i8) + 1); | error: casts from `u8` to `u32` can be expressed infallibly using `From` - --> tests/ui/cast_lossless_integer.rs:159:13 - | -LL | 1u8 as u32 - | ^^^^^^^^^^ -... -LL | let _ = in_macro!(); - | ----------- in this macro invocation - | - = help: an `as` cast can become silently lossy if the types change in the future - = note: this error originates in the macro `in_macro` (in Nightly builds, run with -Z macro-backtrace for more info) -help: use `u32::from` instead - | -LL - 1u8 as u32 -LL + u32::from(1u8) - | - -error: casts from `u8` to `u32` can be expressed infallibly using `From` - --> tests/ui/cast_lossless_integer.rs:174:13 + --> tests/ui/cast_lossless_integer.rs:173:13 | LL | let _ = 0u8 as ty!(); | ^^^^^^^^^^^^ @@ -524,5 +507,5 @@ LL - let _ = 0u8 as ty!(); LL + let _ = ::from(0u8); | -error: aborting due to 40 previous errors +error: aborting due to 39 previous errors From d172f5d062c1112b4dcc5f297bb55a446bf0f86e Mon Sep 17 00:00:00 2001 From: lapla-cogito Date: Tue, 25 Mar 2025 22:09:16 +0900 Subject: [PATCH 2/3] apply `cast_lossless` to Clippy source --- clippy_lints/src/booleans.rs | 6 +++--- clippy_lints/src/literal_string_with_formatting_args.rs | 2 +- clippy_lints/src/regex.rs | 2 +- clippy_lints/src/unicode.rs | 4 ++-- clippy_utils/src/source.rs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 36c1aea26378..627769b0bfd6 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -345,7 +345,7 @@ impl SuggestContext<'_, '_, '_> { self.output.push(')'); }, Term(n) => { - let terminal = self.terminals[n as usize]; + let terminal = self.terminals[usize::from(n)]; if let Some(str) = simplify_not(self.cx, self.msrv, terminal) { self.output.push_str(&str); } else { @@ -387,7 +387,7 @@ impl SuggestContext<'_, '_, '_> { }, &Term(n) => { self.output.push_str( - &self.terminals[n as usize] + &self.terminals[usize::from(n)] .span .source_callsite() .get_source_text(self.cx)?, @@ -528,7 +528,7 @@ fn terminal_stats(b: &Bool) -> Stats { recurse(inner, stats); } }, - &Term(n) => stats.terminals[n as usize] += 1, + &Term(n) => stats.terminals[usize::from(n)] += 1, } } use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True}; diff --git a/clippy_lints/src/literal_string_with_formatting_args.rs b/clippy_lints/src/literal_string_with_formatting_args.rs index 975e6833a35f..5f69ab713585 100644 --- a/clippy_lints/src/literal_string_with_formatting_args.rs +++ b/clippy_lints/src/literal_string_with_formatting_args.rs @@ -90,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for LiteralStringWithFormattingArg { LitKind::Str(symbol, style) => { let add = match style { StrStyle::Cooked => 1, - StrStyle::Raw(nb) => nb as usize + 2, + StrStyle::Raw(nb) => usize::from(nb) + 2, }; (add, symbol) }, diff --git a/clippy_lints/src/regex.rs b/clippy_lints/src/regex.rs index 9443dca154e3..4d6f6712ebc0 100644 --- a/clippy_lints/src/regex.rs +++ b/clippy_lints/src/regex.rs @@ -197,7 +197,7 @@ fn lint_syntax_error(cx: &LateContext<'_>, error: ®ex_syntax::Error, unescape if let Some((primary, auxiliary, kind)) = parts && let Some(literal_snippet) = base.get_source_text(cx) - && let Some(inner) = literal_snippet.get(offset as usize..) + && let Some(inner) = literal_snippet.get(usize::from(offset)..) // Only convert to native rustc spans if the parsed regex matches the // source snippet exactly, to ensure the span offsets are correct && inner.get(..unescaped.len()) == Some(unescaped) diff --git a/clippy_lints/src/unicode.rs b/clippy_lints/src/unicode.rs index e1fc644e4cee..df202f9ebd66 100644 --- a/clippy_lints/src/unicode.rs +++ b/clippy_lints/src/unicode.rs @@ -87,7 +87,7 @@ impl LateLintPass<'_> for Unicode { fn escape>(s: T) -> String { let mut result = String::new(); for c in s { - if c as u32 > 0x7F { + if u32::from(c) > 0x7F { for d in c.escape_unicode() { result.push(d); } @@ -119,7 +119,7 @@ fn check_str(cx: &LateContext<'_>, span: Span, id: HirId) { }); } - if string.chars().any(|c| c as u32 > 0x7F) { + if string.chars().any(|c| u32::from(c) > 0x7F) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( cx, diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 80066e9702d3..694c26a39026 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -713,7 +713,7 @@ pub fn str_literal_to_char_literal( { let snip = snippet_with_applicability(sess, expr.span, string, applicability); let ch = if let StrStyle::Raw(nhash) = style { - let nhash = nhash as usize; + let nhash = usize::from(nhash); // for raw string: r##"a"## &snip[(nhash + 2)..(snip.len() - 1 - nhash)] } else { From 896067ea6918e172b8e69ad8fa30703c1b026567 Mon Sep 17 00:00:00 2001 From: lapla-cogito Date: Thu, 3 Apr 2025 01:28:52 +0900 Subject: [PATCH 3/3] check three exprs share the same context --- clippy_lints/src/casts/cast_lossless.rs | 7 +++++- tests/ui/cast_lossless_integer.fixed | 9 +++---- tests/ui/cast_lossless_integer.rs | 3 +-- tests/ui/cast_lossless_integer.stderr | 33 +++++++++---------------- 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs index 49b13dccbeb5..2aa5d6a7154a 100644 --- a/clippy_lints/src/casts/cast_lossless.rs +++ b/clippy_lints/src/casts/cast_lossless.rs @@ -20,7 +20,12 @@ pub(super) fn check( cast_to_hir: &rustc_hir::Ty<'_>, msrv: Msrv, ) { - if !should_lint(cx, cast_from, cast_to, msrv) || !expr.span.ctxt().is_root() { + if !should_lint(cx, cast_from, cast_to, msrv) + // Checking the expression, cast_from_expr and cast_to_hir share the same context. + || [cast_from_expr.span, cast_to_hir.span] + .iter() + .any(|&span| !expr.span.eq_ctxt(span)) + { return; } diff --git a/tests/ui/cast_lossless_integer.fixed b/tests/ui/cast_lossless_integer.fixed index 2fbad06d8496..7d65e6514b41 100644 --- a/tests/ui/cast_lossless_integer.fixed +++ b/tests/ui/cast_lossless_integer.fixed @@ -146,8 +146,7 @@ fn issue11458() { }; } let x = 10_u128; - let _ = i32::from(sign_cast!(x, u8, i8)); - //~^ cast_lossless + let _ = sign_cast!(x, u8, i8) as i32; let _ = i32::from(sign_cast!(x, u8, i8) + 1); //~^ cast_lossless @@ -156,7 +155,8 @@ fn issue11458() { fn issue12695() { macro_rules! in_macro { () => { - 1u8 as u32 + u32::from(1u8) + //~^ cast_lossless }; } @@ -170,8 +170,7 @@ fn ty_from_macro() { }; } - let _ = ::from(0u8); - //~^ cast_lossless + let _ = 0u8 as ty!(); } const IN_CONST: u64 = 0u8 as u64; diff --git a/tests/ui/cast_lossless_integer.rs b/tests/ui/cast_lossless_integer.rs index c6f6cb9bb128..da1436010d2c 100644 --- a/tests/ui/cast_lossless_integer.rs +++ b/tests/ui/cast_lossless_integer.rs @@ -147,7 +147,6 @@ fn issue11458() { } let x = 10_u128; let _ = sign_cast!(x, u8, i8) as i32; - //~^ cast_lossless let _ = (sign_cast!(x, u8, i8) + 1) as i32; //~^ cast_lossless @@ -157,6 +156,7 @@ fn issue12695() { macro_rules! in_macro { () => { 1u8 as u32 + //~^ cast_lossless }; } @@ -171,7 +171,6 @@ fn ty_from_macro() { } let _ = 0u8 as ty!(); - //~^ cast_lossless } const IN_CONST: u64 = 0u8 as u64; diff --git a/tests/ui/cast_lossless_integer.stderr b/tests/ui/cast_lossless_integer.stderr index 205f9d6ea0cc..ac02807be9c7 100644 --- a/tests/ui/cast_lossless_integer.stderr +++ b/tests/ui/cast_lossless_integer.stderr @@ -469,20 +469,7 @@ LL + let _: u32 = (1i8 as u16).into(); | error: casts from `i8` to `i32` can be expressed infallibly using `From` - --> tests/ui/cast_lossless_integer.rs:149:13 - | -LL | let _ = sign_cast!(x, u8, i8) as i32; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: an `as` cast can become silently lossy if the types change in the future -help: use `i32::from` instead - | -LL - let _ = sign_cast!(x, u8, i8) as i32; -LL + let _ = i32::from(sign_cast!(x, u8, i8)); - | - -error: casts from `i8` to `i32` can be expressed infallibly using `From` - --> tests/ui/cast_lossless_integer.rs:152:13 + --> tests/ui/cast_lossless_integer.rs:151:13 | LL | let _ = (sign_cast!(x, u8, i8) + 1) as i32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -495,17 +482,21 @@ LL + let _ = i32::from(sign_cast!(x, u8, i8) + 1); | error: casts from `u8` to `u32` can be expressed infallibly using `From` - --> tests/ui/cast_lossless_integer.rs:173:13 + --> tests/ui/cast_lossless_integer.rs:158:13 | -LL | let _ = 0u8 as ty!(); - | ^^^^^^^^^^^^ +LL | 1u8 as u32 + | ^^^^^^^^^^ +... +LL | let _ = in_macro!(); + | ----------- in this macro invocation | = help: an `as` cast can become silently lossy if the types change in the future -help: use `::from` instead + = note: this error originates in the macro `in_macro` (in Nightly builds, run with -Z macro-backtrace for more info) +help: use `u32::from` instead | -LL - let _ = 0u8 as ty!(); -LL + let _ = ::from(0u8); +LL - 1u8 as u32 +LL + u32::from(1u8) | -error: aborting due to 39 previous errors +error: aborting due to 38 previous errors