]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/casts/utils.rs
New lint `cast_enum_truncation`
[rust.git] / clippy_lints / src / casts / utils.rs
1 use rustc_middle::mir::interpret::{ConstValue, Scalar};
2 use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr};
3 use rustc_span::def_id::DefId;
4 use rustc_target::abi::Size;
5
6 /// Returns the size in bits of an integral type.
7 /// Will return 0 if the type is not an int or uint variant
8 pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 {
9     match typ.kind() {
10         ty::Int(i) => match i {
11             IntTy::Isize => tcx.data_layout.pointer_size.bits(),
12             IntTy::I8 => 8,
13             IntTy::I16 => 16,
14             IntTy::I32 => 32,
15             IntTy::I64 => 64,
16             IntTy::I128 => 128,
17         },
18         ty::Uint(i) => match i {
19             UintTy::Usize => tcx.data_layout.pointer_size.bits(),
20             UintTy::U8 => 8,
21             UintTy::U16 => 16,
22             UintTy::U32 => 32,
23             UintTy::U64 => 64,
24             UintTy::U128 => 128,
25         },
26         _ => 0,
27     }
28 }
29
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
79 pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 {
80     let mut explicit = 0i128;
81     let (start, end) = adt
82         .variants
83         .iter()
84         .fold((0, i128::MIN), |(start, end), variant| match variant.discr {
85             VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) {
86                 Some(x) => (start, end.max(x)),
87                 None => (i128::MIN, end),
88             },
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),
102             },
103         });
104
105     if start > end {
106         // No variants.
107         0
108     } else {
109         let neg_bits = if start < 0 {
110             128 - (-(start + 1)).leading_zeros() + 1
111         } else {
112             0
113         };
114         let pos_bits = if end > 0 { 128 - end.leading_zeros() } else { 0 };
115         neg_bits.max(pos_bits).into()
116     }
117 }