Skip to content

Commit 4a7f2b8

Browse files
committed
Add unnecessary_intermediate_cast lint
1 parent 1826ec0 commit 4a7f2b8

19 files changed

Lines changed: 8750 additions & 116 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7117,6 +7117,7 @@ Released 2018-09-13
71177117
[`unnecessary_first_then_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_first_then_check
71187118
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
71197119
[`unnecessary_get_then_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_get_then_check
7120+
[`unnecessary_intermediate_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_intermediate_cast
71207121
[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
71217122
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
71227123
[`unnecessary_literal_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_bound

clippy_lints/src/casts/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ mod ptr_as_ptr;
2424
mod ptr_cast_constness;
2525
mod ref_as_ptr;
2626
mod unnecessary_cast;
27+
mod unnecessary_intermediate_cast;
2728
mod utils;
2829
mod zero_ptr;
2930

@@ -846,6 +847,29 @@ declare_clippy_lint! {
846847
"binding defined with one type but always cast to another"
847848
}
848849

850+
declare_clippy_lint! {
851+
/// ### What it does
852+
/// Checks for two casts in a row, that could be replaced with a single cast to the last type.
853+
///
854+
/// ### Why is this bad?
855+
/// It's just unnecessary.
856+
///
857+
/// ### Example
858+
/// ```no_run
859+
/// let _ = 1u32 as u64 as u128;
860+
/// let _ = 1i32 as i16 as i8;
861+
/// ```
862+
/// Use instead:
863+
/// ```no_run
864+
/// let _ = 1u32 as u128;
865+
/// let _ = 1i32 as i8;
866+
/// ```
867+
#[clippy::version = "1.95.0"]
868+
pub UNNECESSARY_INTERMEDIATE_CAST,
869+
complexity,
870+
"two casts in a row, where the second cast alone would have the same effect, e.g., `x as u16 as u32` where `x: u8`"
871+
}
872+
849873
pub struct Casts {
850874
msrv: Msrv,
851875
}
@@ -885,6 +909,7 @@ impl_lint_pass!(Casts => [
885909
MANUAL_DANGLING_PTR,
886910
CONFUSING_METHOD_TO_NUMERIC_CAST,
887911
NEEDLESS_TYPE_CAST,
912+
UNNECESSARY_INTERMEDIATE_CAST,
888913
]);
889914

890915
impl<'tcx> LateLintPass<'tcx> for Casts {
@@ -905,6 +930,11 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
905930
if !expr.span.from_expansion() && unnecessary_cast::check(cx, expr, cast_from_expr, cast_from, cast_to) {
906931
return;
907932
}
933+
if let ExprKind::Cast(cast_from_expr, _cast_to_hir) = cast_from_expr.kind {
934+
let cast_intermediate = cast_from;
935+
let cast_from = cx.typeck_results().expr_ty(cast_from_expr);
936+
unnecessary_intermediate_cast::check(cx, expr, cast_from_expr, cast_from, cast_intermediate, cast_to);
937+
}
908938
char_lit_as_u8::check(cx, expr, cast_from_expr, cast_to);
909939
cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, self.msrv);
910940
cast_ptr_alignment::check(cx, expr, cast_from, cast_to);
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
use std::cmp::Ordering;
2+
3+
use clippy_utils::diagnostics::span_lint_and_sugg;
4+
use clippy_utils::source::snippet_with_applicability;
5+
use rustc_errors::Applicability;
6+
use rustc_hir::Expr;
7+
use rustc_lint::LateContext;
8+
use rustc_middle::ty::{self, IntTy, Ty, TyCtxt, UintTy};
9+
10+
use super::UNNECESSARY_INTERMEDIATE_CAST;
11+
12+
pub(super) fn check<'tcx>(
13+
cx: &LateContext<'tcx>,
14+
expr: &Expr<'tcx>,
15+
cast_from_expr: &Expr<'tcx>,
16+
cast_from: Ty<'tcx>,
17+
cast_mid: Ty<'tcx>,
18+
cast_to: Ty<'tcx>,
19+
) {
20+
// If skipping the intermediate cast isn't even allowed in Rust, don't bother checking.
21+
if !is_cast_allowed(cx.tcx, cast_from, cast_to) {
22+
return;
23+
}
24+
25+
let mut from_class = TypeClass::from(cast_from);
26+
let mut mid_class = TypeClass::from(cast_mid);
27+
let mut to_class = TypeClass::from(cast_to);
28+
29+
if can_remove_intermediate_cast(from_class, mid_class, to_class) {
30+
let mut applicability = Applicability::MachineApplicable;
31+
let from_snippet = snippet_with_applicability(cx, cast_from_expr.span, "x", &mut applicability);
32+
33+
span_lint_and_sugg(
34+
cx,
35+
UNNECESSARY_INTERMEDIATE_CAST,
36+
expr.span,
37+
format!("intermediate cast is unnecessary (`{cast_from}` -> `{cast_mid}` -> `{cast_to}`)"),
38+
"try",
39+
format!("{from_snippet} as {cast_to}"),
40+
applicability,
41+
);
42+
return;
43+
}
44+
45+
let pointer_size = cx.tcx.data_layout.pointer_size().bits();
46+
from_class.set_pointer_size(pointer_size);
47+
mid_class.set_pointer_size(pointer_size);
48+
to_class.set_pointer_size(pointer_size);
49+
50+
if can_remove_intermediate_cast(from_class, mid_class, to_class) {
51+
// The user may want to keep casts with pointer-sized types, for cross-platform compatibility.
52+
let mut applicability = Applicability::MaybeIncorrect;
53+
let from_snippet = snippet_with_applicability(cx, cast_from_expr.span, "x", &mut applicability);
54+
55+
span_lint_and_sugg(
56+
cx,
57+
UNNECESSARY_INTERMEDIATE_CAST,
58+
expr.span,
59+
format!("intermediate cast is unnecessary (`{cast_from}` -> `{cast_mid}` -> `{cast_to}`)"),
60+
"try",
61+
format!("{from_snippet} as {cast_to}"),
62+
applicability,
63+
);
64+
}
65+
}
66+
67+
/// Returns whether Rust allows casting from `cast_from` to `cast_to`.
68+
/// This function may be incomplete and only considers only types handled by
69+
/// `can_remove_intermediate_cast`, returning `false` for everything else.
70+
fn is_cast_allowed<'tcx>(tcx: TyCtxt<'tcx>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) -> bool {
71+
#[allow(clippy::match_same_arms)]
72+
match (*cast_from.kind(), *cast_to.kind()) {
73+
// Integers and floats can indiscriminately cast between each other.
74+
(ty::Int(..) | ty::Uint(..) | ty::Float(..), ty::Int(..) | ty::Uint(..) | ty::Float(..)) => true,
75+
76+
// bool and char can cast to any integer.
77+
(ty::Bool | ty::Char, ty::Int(..) | ty::Uint(..)) => true,
78+
79+
// Only u8 can cast to char.
80+
(ty::Uint(UintTy::U8), ty::Char) => true,
81+
82+
// Pointers can cast to each other and to integers.
83+
(ty::RawPtr(..), ty::RawPtr(..) | ty::Int(..) | ty::Uint(..)) => true,
84+
85+
// Integers can only cast to pointers if they are thin.
86+
(ty::Int(..) | ty::Uint(..), ty::RawPtr(ty, ..)) => ty.has_trivial_sizedness(tcx, ty::SizedTraitKind::Sized),
87+
88+
_ => false,
89+
}
90+
}
91+
92+
/// Returns whether it's safe to remove the cast to `cast_mid` without affecting the result.
93+
/// This errs on the side of caution, and should not cause false positives.
94+
fn can_remove_intermediate_cast(from_class: TypeClass, mid_class: TypeClass, to_class: TypeClass) -> bool {
95+
#[allow(clippy::match_same_arms)]
96+
match (from_class, mid_class, to_class) {
97+
// Ignore any type classed as "Other"
98+
(TypeClass::Other, _, _) | (_, TypeClass::Other, _) | (_, _, TypeClass::Other) => false,
99+
100+
// Every other type can represent a bool, and sign never matters.
101+
(TypeClass::Bool, _, _) => true,
102+
103+
(TypeClass::Int(from_bits, from_signed), TypeClass::Int(mid_bits, mid_signed), TypeClass::Int(to_bits, _)) => {
104+
match (from_signed, mid_signed) {
105+
(false, false) | (true, true) => mid_bits >= to_bits || mid_bits >= from_bits,
106+
(false, true) => mid_bits >= to_bits || mid_bits > from_bits,
107+
(true, false) => mid_bits >= to_bits,
108+
}
109+
},
110+
111+
(TypeClass::Float(from_bits), TypeClass::Float(mid_bits), TypeClass::Float(to_bits)) => {
112+
mid_bits >= to_bits || mid_bits >= from_bits
113+
},
114+
115+
(TypeClass::Int(from_bits, from_signed), TypeClass::Int(mid_bits, mid_signed), TypeClass::Float(..)) => {
116+
match (from_signed, mid_signed) {
117+
(false, false) | (true, true) => mid_bits >= from_bits,
118+
(false, true) => mid_bits > from_bits,
119+
(true, false) => false,
120+
}
121+
},
122+
123+
(TypeClass::Int(from_bits, from_signed), TypeClass::Float(mid_bits), TypeClass::Int(to_bits, to_signed)) => {
124+
match (from_signed, to_signed) {
125+
(false, false) | (true, true) => mid_bits > from_bits && to_bits >= from_bits,
126+
(false, true) => mid_bits > from_bits && to_bits > from_bits,
127+
(true, false) => false,
128+
}
129+
},
130+
131+
(TypeClass::Int(from_bits, _), TypeClass::Float(mid_bits), TypeClass::Float(to_bits)) => {
132+
mid_bits >= to_bits || mid_bits > from_bits
133+
},
134+
135+
(TypeClass::Float(..), TypeClass::Int(..), TypeClass::Int(..)) => false,
136+
137+
(TypeClass::Float(..), TypeClass::Int(..), TypeClass::Float(..)) => false,
138+
139+
(TypeClass::Float(from_bits), TypeClass::Float(mid_bits), TypeClass::Int(to_bits, _)) => {
140+
mid_bits > to_bits || mid_bits >= from_bits
141+
},
142+
143+
_ => false,
144+
}
145+
}
146+
147+
#[derive(Clone, Copy)]
148+
enum TypeClass {
149+
Other,
150+
Int(IntSize, bool),
151+
Bool,
152+
Float(u64),
153+
}
154+
155+
impl TypeClass {
156+
fn set_pointer_size(&mut self, pointer_size: u64) {
157+
if let Self::Int(size @ IntSize::Pointer, _) = self {
158+
*size = IntSize::Fixed(pointer_size);
159+
}
160+
}
161+
}
162+
163+
impl From<Ty<'_>> for TypeClass {
164+
fn from(ty: Ty<'_>) -> Self {
165+
#[allow(clippy::match_same_arms)]
166+
match *ty.kind() {
167+
ty::Bool => Self::Bool,
168+
ty::Char => Self::Int(IntSize::Fixed(32), false),
169+
ty::Int(IntTy::Isize) => Self::Int(IntSize::Pointer, true),
170+
ty::Int(ty) => {
171+
let size = ty.bit_width().expect("all other integer types should have fixed sizes");
172+
Self::Int(IntSize::Fixed(size), true)
173+
},
174+
ty::Uint(UintTy::Usize) => Self::Int(IntSize::Pointer, false),
175+
ty::Uint(ty) => {
176+
let size = ty.bit_width().expect("all other integer types should have fixed sizes");
177+
Self::Int(IntSize::Fixed(size), false)
178+
},
179+
ty::Float(ty) => Self::Float(ty.bit_width()),
180+
ty::RawPtr(..) => Self::Int(IntSize::Pointer, false),
181+
_ => Self::Other,
182+
}
183+
}
184+
}
185+
186+
#[derive(Clone, Copy, PartialEq, Eq)]
187+
enum IntSize {
188+
Fixed(u64),
189+
Pointer,
190+
}
191+
192+
impl PartialOrd for IntSize {
193+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
194+
#[allow(clippy::match_same_arms)]
195+
match (self, other) {
196+
(IntSize::Fixed(from), IntSize::Fixed(to)) => Some(from.cmp(to)),
197+
(IntSize::Fixed(_), IntSize::Pointer) => None,
198+
(IntSize::Pointer, IntSize::Fixed(_)) => None,
199+
(IntSize::Pointer, IntSize::Pointer) => Some(Ordering::Equal),
200+
}
201+
}
202+
}
203+
204+
impl PartialOrd<u64> for IntSize {
205+
fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
206+
match self {
207+
IntSize::Fixed(size) => Some(size.cmp(other)),
208+
IntSize::Pointer => None,
209+
}
210+
}
211+
}
212+
213+
impl PartialEq<u64> for IntSize {
214+
fn eq(&self, other: &u64) -> bool {
215+
self.partial_cmp(other).is_some_and(Ordering::is_eq)
216+
}
217+
}
218+
219+
impl PartialOrd<IntSize> for u64 {
220+
fn partial_cmp(&self, other: &IntSize) -> Option<Ordering> {
221+
other.partial_cmp(self).map(Ordering::reverse)
222+
}
223+
}
224+
225+
impl PartialEq<IntSize> for u64 {
226+
fn eq(&self, other: &IntSize) -> bool {
227+
other.eq(self)
228+
}
229+
}

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
7575
crate::casts::PTR_CAST_CONSTNESS_INFO,
7676
crate::casts::REF_AS_PTR_INFO,
7777
crate::casts::UNNECESSARY_CAST_INFO,
78+
crate::casts::UNNECESSARY_INTERMEDIATE_CAST_INFO,
7879
crate::casts::ZERO_PTR_INFO,
7980
crate::cfg_not_test::CFG_NOT_TEST_INFO,
8081
crate::checked_conversions::CHECKED_CONVERSIONS_INFO,

tests/ui/cast_alignment.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
clippy::no_effect,
77
clippy::unnecessary_operation,
88
clippy::cast_lossless,
9-
clippy::borrow_as_ptr
9+
clippy::borrow_as_ptr,
10+
clippy::unnecessary_intermediate_cast
1011
)]
1112

1213
fn main() {

tests/ui/cast_alignment.stderr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: casting from `*const u8` to a more-strictly-aligned pointer (`*const u16`) (1 < 2 bytes)
2-
--> tests/ui/cast_alignment.rs:16:5
2+
--> tests/ui/cast_alignment.rs:17:5
33
|
44
LL | (&1u8 as *const u8) as *const u16;
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -8,19 +8,19 @@ LL | (&1u8 as *const u8) as *const u16;
88
= help: to override `-D warnings` add `#[allow(clippy::cast_ptr_alignment)]`
99

1010
error: casting from `*mut u8` to a more-strictly-aligned pointer (`*mut u16`) (1 < 2 bytes)
11-
--> tests/ui/cast_alignment.rs:19:5
11+
--> tests/ui/cast_alignment.rs:20:5
1212
|
1313
LL | (&mut 1u8 as *mut u8) as *mut u16;
1414
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1515

1616
error: casting from `*const u8` to a more-strictly-aligned pointer (`*const u16`) (1 < 2 bytes)
17-
--> tests/ui/cast_alignment.rs:23:5
17+
--> tests/ui/cast_alignment.rs:24:5
1818
|
1919
LL | (&1u8 as *const u8).cast::<u16>();
2020
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2121

2222
error: casting from `*mut u8` to a more-strictly-aligned pointer (`*mut u16`) (1 < 2 bytes)
23-
--> tests/ui/cast_alignment.rs:26:5
23+
--> tests/ui/cast_alignment.rs:27:5
2424
|
2525
LL | (&mut 1u8 as *mut u8).cast::<u16>();
2626
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/ui/cast_slice_different_sizes.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
//@no-rustfix: overlapping suggestions
2-
#![allow(clippy::let_unit_value, clippy::unnecessary_cast)]
2+
#![allow(
3+
clippy::let_unit_value,
4+
clippy::unnecessary_cast,
5+
clippy::unnecessary_intermediate_cast
6+
)]
37

48
fn main() {
59
let x: [i32; 3] = [1_i32, 2, 3];

0 commit comments

Comments
 (0)