Skip to content

Commit d275208

Browse files
committed
Take into account the enum variant's value in cast_possible_truncation
1 parent 0f3bfc2 commit d275208

File tree

4 files changed

+107
-47
lines changed

4 files changed

+107
-47
lines changed

clippy_lints/src/casts/cast_possible_truncation.rs

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ 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_ast::ast;
6+
use rustc_attr::IntType;
57
use rustc_hir::def::{DefKind, Res};
68
use rustc_hir::{BinOpKind, Expr, ExprKind};
79
use rustc_lint::LateContext;
8-
use rustc_middle::ty::{self, FloatTy, Ty};
10+
use rustc_middle::ty::{self, FloatTy, Ty, VariantDiscr};
911

1012
use super::{utils, CAST_POSSIBLE_TRUNCATION};
1113

@@ -110,25 +112,39 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
110112
},
111113

112114
(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)
115+
let from_nbits: u64 = if let ExprKind::Path(p) = &cast_expr.kind
116+
&& let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id)
115117
{
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;
118+
let i = def.variant_index_with_ctor_id(id);
119+
match def.variants[i].discr {
120+
VariantDiscr::Explicit(id) => utils::read_explicit_enum_value(cx.tcx, id).unwrap().nbits(),
121+
VariantDiscr::Relative(x) => {
122+
match def.variants[(i.as_usize() - x as usize).into()].discr {
123+
VariantDiscr::Explicit(id) => {
124+
utils::read_explicit_enum_value(cx.tcx, id).unwrap().add(x).nbits()
125+
}
126+
VariantDiscr::Relative(_) => (32 - x.leading_zeros()).into(),
127+
}
128+
}
127129
}
128-
} else if to_nbits < from_nbits {
129-
""
130130
} else {
131-
return;
131+
utils::enum_ty_to_nbits(def, cx.tcx)
132+
};
133+
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
134+
135+
let cast_from_ptr_size = def.repr.int.map_or(false, |ty| {
136+
matches!(
137+
ty,
138+
IntType::SignedInt(ast::IntTy::Isize) | IntType::UnsignedInt(ast::UintTy::Usize)
139+
)
140+
});
141+
let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
142+
(false, false) if from_nbits > to_nbits => "",
143+
(true, false) if to_nbits < 32 => "",
144+
(true, false) if to_nbits < 64 => " on targets with 64-bit wide pointers",
145+
(false, true) if from_nbits > 64 => "",
146+
(false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers",
147+
_ => return,
132148
};
133149

134150
format!(

clippy_lints/src/casts/utils.rs

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use rustc_middle::mir::interpret::{ConstValue, Scalar};
22
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr};
3+
use rustc_span::def_id::DefId;
34
use rustc_target::abi::Size;
45

56
/// Returns the size in bits of an integral type.
@@ -26,48 +27,83 @@ pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 {
2627
}
2728
}
2829

30+
pub(super) enum EnumValue {
31+
Unsigned(u128),
32+
Signed(i128),
33+
}
34+
impl EnumValue {
35+
pub(super) fn add(self, n: u32) -> Self {
36+
match self {
37+
Self::Unsigned(x) => Self::Unsigned(x + u128::from(n)),
38+
Self::Signed(x) => Self::Signed(x + i128::from(n)),
39+
}
40+
}
41+
42+
pub(super) fn nbits(self) -> u64 {
43+
match self {
44+
Self::Unsigned(x) => 128 - x.leading_zeros(),
45+
Self::Signed(x) if x < 0 => 128 - (-(x + 1)).leading_zeros() + 1,
46+
Self::Signed(x) => 128 - x.leading_zeros(),
47+
}
48+
.into()
49+
}
50+
}
51+
52+
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
53+
pub(super) fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option<EnumValue> {
54+
if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) {
55+
match tcx.type_of(id).kind() {
56+
ty::Int(_) => Some(EnumValue::Signed(match value.size().bytes() {
57+
1 => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8),
58+
2 => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16),
59+
4 => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32),
60+
8 => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64),
61+
16 => value.assert_bits(Size::from_bytes(16)) as i128,
62+
_ => return None,
63+
})),
64+
ty::Uint(_) => Some(EnumValue::Unsigned(match value.size().bytes() {
65+
1 => value.assert_bits(Size::from_bytes(1)),
66+
2 => value.assert_bits(Size::from_bytes(2)),
67+
4 => value.assert_bits(Size::from_bytes(4)),
68+
8 => value.assert_bits(Size::from_bytes(8)),
69+
16 => value.assert_bits(Size::from_bytes(16)),
70+
_ => return None,
71+
})),
72+
_ => None,
73+
}
74+
} else {
75+
None
76+
}
77+
}
78+
2979
pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 {
3080
let mut explicit = 0i128;
3181
let (start, end) = adt
3282
.variants
3383
.iter()
34-
.fold((i128::MAX, i128::MIN), |(start, end), variant| match variant.discr {
84+
.fold((0, i128::MIN), |(start, end), variant| match variant.discr {
3585
VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) {
3686
Some(x) => (start, end.max(x)),
3787
None => (i128::MIN, end),
3888
},
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-
}
89+
VariantDiscr::Explicit(id) => match read_explicit_enum_value(tcx, id) {
90+
Some(EnumValue::Signed(x)) => {
91+
explicit = x;
92+
(start.min(x), end.max(x))
93+
},
94+
Some(EnumValue::Unsigned(x)) => match i128::try_from(x) {
95+
Ok(x) => {
96+
explicit = x;
97+
(start, end.max(x))
98+
},
99+
Err(_) => (i128::MIN, end),
100+
},
101+
None => (start, end),
67102
},
68103
});
69104

70-
if start >= end {
105+
if start > end {
106+
// No variants.
71107
0
72108
} else {
73109
let neg_bits = if start < 0 {

clippy_lints/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
2424
extern crate rustc_ast;
2525
extern crate rustc_ast_pretty;
26+
extern crate rustc_attr;
2627
extern crate rustc_data_structures;
2728
extern crate rustc_driver;
2829
extern crate rustc_errors;

tests/ui/cast.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ fn main() {
139139
impl E2 {
140140
fn test(self) {
141141
let _ = self as u8;
142+
let _ = Self::B as u8;
142143
let _ = self as i16; // Don't lint. `255..=256` fits in i16
144+
let _ = Self::A as u8; // Don't lint.
143145
}
144146
}
145147

@@ -174,7 +176,9 @@ fn main() {
174176
impl E5 {
175177
fn test(self) {
176178
let _ = self as i8;
179+
let _ = Self::A as i8;
177180
let _ = self as i16; // Don't lint. `-129..=127` fits in i16
181+
let _ = Self::B as u8; // Don't lint.
178182
}
179183
}
180184

@@ -189,6 +193,7 @@ fn main() {
189193
let _ = self as i16;
190194
let _ = Self::A as u16; // Don't lint. `2^16-1` fits in u16
191195
let _ = self as u32; // Don't lint. `2^16-1..=2^16` fits in u32
196+
let _ = Self::A as u16; // Don't lint.
192197
}
193198
}
194199

@@ -201,6 +206,7 @@ fn main() {
201206
impl E7 {
202207
fn test(self) {
203208
let _ = self as usize;
209+
let _ = Self::A as usize; // Don't lint.
204210
let _ = self as u64; // Don't lint. `2^32-1..=2^32` fits in u64
205211
}
206212
}
@@ -227,6 +233,7 @@ fn main() {
227233
}
228234
impl E9 {
229235
fn test(self) {
236+
let _ = Self::A as u8; // Don't lint.
230237
let _ = self as u128; // Don't lint. `0..=2^128-1` fits in u128
231238
}
232239
}

0 commit comments

Comments
 (0)