Skip to content

Commit 0f3bfc2

Browse files
committed
Lint enum-to-int casts with cast_possible_truncation
1 parent 0ed8ca4 commit 0f3bfc2

File tree

6 files changed

+259
-39
lines changed

6 files changed

+259
-39
lines changed

clippy_lints/src/casts/cast_possible_truncation.rs

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use clippy_utils::consts::{constant, Constant};
22
use clippy_utils::diagnostics::span_lint;
33
use clippy_utils::expr_or_init;
44
use clippy_utils::ty::is_isize_or_usize;
5+
use rustc_hir::def::{DefKind, Res};
56
use rustc_hir::{BinOpKind, Expr, ExprKind};
67
use rustc_lint::LateContext;
78
use rustc_middle::ty::{self, FloatTy, Ty};
@@ -75,8 +76,8 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
7576
}
7677

7778
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
78-
let msg = match (cast_from.is_integral(), cast_to.is_integral()) {
79-
(true, true) => {
79+
let msg = match (cast_from.kind(), cast_to.is_integral()) {
80+
(ty::Int(_) | ty::Uint(_), true) => {
8081
let from_nbits = apply_reductions(
8182
cx,
8283
utils::int_ty_to_nbits(cast_from, cx.tcx),
@@ -108,19 +109,43 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
108109
)
109110
},
110111

111-
(false, true) => {
112-
format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to)
113-
},
114-
115-
(_, _) => {
116-
if matches!(cast_from.kind(), &ty::Float(FloatTy::F64))
117-
&& matches!(cast_to.kind(), &ty::Float(FloatTy::F32))
112+
(ty::Adt(def, _), true) if def.is_enum() => {
113+
if let ExprKind::Path(p) = &cast_expr.kind
114+
&& let Res::Def(DefKind::Ctor(..), _) = cx.qpath_res(p, cast_expr.hir_id)
118115
{
119-
"casting `f64` to `f32` may truncate the value".to_string()
116+
return
117+
}
118+
119+
let from_nbits = utils::enum_ty_to_nbits(def, cx.tcx);
120+
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
121+
122+
let suffix = if is_isize_or_usize(cast_to) {
123+
if from_nbits > 32 {
124+
" on targets with 32-bit wide pointers"
125+
} else {
126+
return;
127+
}
128+
} else if to_nbits < from_nbits {
129+
""
120130
} else {
121131
return;
122-
}
132+
};
133+
134+
format!(
135+
"casting `{}` to `{}` may truncate the value{}",
136+
cast_from, cast_to, suffix,
137+
)
138+
},
139+
140+
(ty::Float(_), true) => {
141+
format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to)
123142
},
143+
144+
(ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => {
145+
"casting `f64` to `f32` may truncate the value".to_string()
146+
},
147+
148+
_ => return,
124149
};
125150

126151
span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);

clippy_lints/src/casts/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,13 +441,12 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
441441
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
442442

443443
if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
444+
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
444445
if cast_from.is_numeric() {
445-
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
446446
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
447447
cast_precision_loss::check(cx, expr, cast_from, cast_to);
448448
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
449449
}
450-
451450
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
452451
}
453452
}

clippy_lints/src/casts/utils.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use rustc_middle::ty::{self, IntTy, Ty, TyCtxt, UintTy};
1+
use rustc_middle::mir::interpret::{ConstValue, Scalar};
2+
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr};
3+
use rustc_target::abi::Size;
24

35
/// Returns the size in bits of an integral type.
46
/// Will return 0 if the type is not an int or uint variant
@@ -23,3 +25,57 @@ pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 {
2325
_ => 0,
2426
}
2527
}
28+
29+
pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 {
30+
let mut explicit = 0i128;
31+
let (start, end) = adt
32+
.variants
33+
.iter()
34+
.fold((i128::MAX, i128::MIN), |(start, end), variant| match variant.discr {
35+
VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) {
36+
Some(x) => (start, end.max(x)),
37+
None => (i128::MIN, end),
38+
},
39+
VariantDiscr::Explicit(id) => {
40+
let ty = tcx.type_of(id);
41+
if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) {
42+
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
43+
let value = match (value.size().bytes(), ty.kind()) {
44+
(1, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8),
45+
(1, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8),
46+
(2, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16),
47+
(2, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16),
48+
(4, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32),
49+
(4, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32),
50+
(8, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64),
51+
(8, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64),
52+
(16, ty::Int(_)) => value.assert_bits(Size::from_bytes(16)) as i128,
53+
(16, ty::Uint(_)) => match i128::try_from(value.assert_bits(Size::from_bytes(16))) {
54+
Ok(x) => x,
55+
// Requires 128 bits
56+
Err(_) => return (i128::MIN, end),
57+
},
58+
// Shouldn't happen if compilation was successful
59+
_ => return (start, end),
60+
};
61+
explicit = value;
62+
(start.min(value), end.max(value))
63+
} else {
64+
// Shouldn't happen if compilation was successful
65+
(start, end)
66+
}
67+
},
68+
});
69+
70+
if start >= end {
71+
0
72+
} else {
73+
let neg_bits = if start < 0 {
74+
128 - (-(start + 1)).leading_zeros() + 1
75+
} else {
76+
0
77+
};
78+
let pos_bits = if end > 0 { 128 - end.leading_zeros() } else { 0 };
79+
neg_bits.max(pos_bits).into()
80+
}
81+
}

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#![feature(control_flow_enum)]
66
#![feature(drain_filter)]
77
#![feature(iter_intersperse)]
8+
#![feature(let_chains)]
89
#![feature(let_else)]
910
#![feature(once_cell)]
1011
#![feature(rustc_private)]

tests/ui/cast.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#![feature(repr128)]
2+
#![allow(incomplete_features)]
3+
14
#[warn(
25
clippy::cast_precision_loss,
36
clippy::cast_possible_truncation,
@@ -115,4 +118,116 @@ fn main() {
115118
}) as u8;
116119
999999u64.clamp(0, 255) as u8;
117120
999999u64.clamp(0, 256) as u8; // should still be linted
121+
122+
#[derive(Clone, Copy)]
123+
enum E1 {
124+
A,
125+
B,
126+
C,
127+
}
128+
impl E1 {
129+
fn test(self) {
130+
let _ = self as u8; // Don't lint. `0..=2` fits in u8
131+
}
132+
}
133+
134+
#[derive(Clone, Copy)]
135+
enum E2 {
136+
A = 255,
137+
B,
138+
}
139+
impl E2 {
140+
fn test(self) {
141+
let _ = self as u8;
142+
let _ = self as i16; // Don't lint. `255..=256` fits in i16
143+
}
144+
}
145+
146+
#[derive(Clone, Copy)]
147+
enum E3 {
148+
A = -1,
149+
B,
150+
C = 50,
151+
}
152+
impl E3 {
153+
fn test(self) {
154+
let _ = self as i8; // Don't lint. `-1..=50` fits in i8
155+
}
156+
}
157+
158+
#[derive(Clone, Copy)]
159+
enum E4 {
160+
A = -128,
161+
B,
162+
}
163+
impl E4 {
164+
fn test(self) {
165+
let _ = self as i8; // Don't lint. `-128..=-127` fits in i8
166+
}
167+
}
168+
169+
#[derive(Clone, Copy)]
170+
enum E5 {
171+
A = -129,
172+
B = 127,
173+
}
174+
impl E5 {
175+
fn test(self) {
176+
let _ = self as i8;
177+
let _ = self as i16; // Don't lint. `-129..=127` fits in i16
178+
}
179+
}
180+
181+
#[derive(Clone, Copy)]
182+
#[repr(u32)]
183+
enum E6 {
184+
A = u16::MAX as u32,
185+
B,
186+
}
187+
impl E6 {
188+
fn test(self) {
189+
let _ = self as i16;
190+
let _ = Self::A as u16; // Don't lint. `2^16-1` fits in u16
191+
let _ = self as u32; // Don't lint. `2^16-1..=2^16` fits in u32
192+
}
193+
}
194+
195+
#[derive(Clone, Copy)]
196+
#[repr(u64)]
197+
enum E7 {
198+
A = u32::MAX as u64,
199+
B,
200+
}
201+
impl E7 {
202+
fn test(self) {
203+
let _ = self as usize;
204+
let _ = self as u64; // Don't lint. `2^32-1..=2^32` fits in u64
205+
}
206+
}
207+
208+
#[derive(Clone, Copy)]
209+
#[repr(i128)]
210+
enum E8 {
211+
A = i128::MIN,
212+
B,
213+
C = 0,
214+
D = i128::MAX,
215+
}
216+
impl E8 {
217+
fn test(self) {
218+
let _ = self as i128; // Don't lint. `-(2^127)..=2^127-1` fits it i128
219+
}
220+
}
221+
222+
#[derive(Clone, Copy)]
223+
#[repr(u128)]
224+
enum E9 {
225+
A,
226+
B = u128::MAX,
227+
}
228+
impl E9 {
229+
fn test(self) {
230+
let _ = self as u128; // Don't lint. `0..=2^128-1` fits in u128
231+
}
232+
}
118233
}

0 commit comments

Comments
 (0)