|
| 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 | +} |
0 commit comments