Lint enum-to-int casts with cast_possible_truncation
This commit is contained in:
parent
02f3c17593
commit
8a466454ab
5 changed files with 258 additions and 39 deletions
|
|
@ -2,6 +2,7 @@ use clippy_utils::consts::{constant, Constant};
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::expr_or_init;
|
||||
use clippy_utils::ty::is_isize_or_usize;
|
||||
use rustc_hir::def::{DefKind, Res};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, FloatTy, Ty};
|
||||
|
|
@ -75,8 +76,8 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b
|
|||
}
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) {
|
||||
let msg = match (cast_from.is_integral(), cast_to.is_integral()) {
|
||||
(true, true) => {
|
||||
let msg = match (cast_from.kind(), cast_to.is_integral()) {
|
||||
(ty::Int(_) | ty::Uint(_), true) => {
|
||||
let from_nbits = apply_reductions(
|
||||
cx,
|
||||
utils::int_ty_to_nbits(cast_from, cx.tcx),
|
||||
|
|
@ -108,19 +109,43 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
|
|||
)
|
||||
},
|
||||
|
||||
(false, true) => {
|
||||
(ty::Adt(def, _), true) if def.is_enum() => {
|
||||
if let ExprKind::Path(p) = &cast_expr.kind
|
||||
&& let Res::Def(DefKind::Ctor(..), _) = cx.qpath_res(p, cast_expr.hir_id)
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
let from_nbits = utils::enum_ty_to_nbits(def, cx.tcx);
|
||||
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
|
||||
|
||||
let suffix = if is_isize_or_usize(cast_to) {
|
||||
if from_nbits > 32 {
|
||||
" on targets with 32-bit wide pointers"
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if to_nbits < from_nbits {
|
||||
""
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
format!(
|
||||
"casting `{}` to `{}` may truncate the value{}",
|
||||
cast_from, cast_to, suffix,
|
||||
)
|
||||
},
|
||||
|
||||
(ty::Float(_), true) => {
|
||||
format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to)
|
||||
},
|
||||
|
||||
(_, _) => {
|
||||
if matches!(cast_from.kind(), &ty::Float(FloatTy::F64))
|
||||
&& matches!(cast_to.kind(), &ty::Float(FloatTy::F32))
|
||||
{
|
||||
"casting `f64` to `f32` may truncate the value".to_string()
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
(ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => {
|
||||
"casting `f64` to `f32` may truncate the value".to_string()
|
||||
},
|
||||
|
||||
_ => return,
|
||||
};
|
||||
|
||||
span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);
|
||||
|
|
|
|||
|
|
@ -445,13 +445,12 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
|
|||
fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
|
||||
if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) {
|
||||
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
if cast_from.is_numeric() {
|
||||
cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
|
||||
cast_precision_loss::check(cx, expr, cast_from, cast_to);
|
||||
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
|
||||
}
|
||||
|
||||
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use rustc_middle::ty::{self, IntTy, Ty, TyCtxt, UintTy};
|
||||
use rustc_middle::mir::interpret::{ConstValue, Scalar};
|
||||
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr};
|
||||
use rustc_target::abi::Size;
|
||||
|
||||
/// Returns the size in bits of an integral type.
|
||||
/// 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 {
|
|||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 {
|
||||
let mut explicit = 0i128;
|
||||
let (start, end) = adt
|
||||
.variants
|
||||
.iter()
|
||||
.fold((i128::MAX, i128::MIN), |(start, end), variant| match variant.discr {
|
||||
VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) {
|
||||
Some(x) => (start, end.max(x)),
|
||||
None => (i128::MIN, end),
|
||||
},
|
||||
VariantDiscr::Explicit(id) => {
|
||||
let ty = tcx.type_of(id);
|
||||
if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) {
|
||||
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||
let value = match (value.size().bytes(), ty.kind()) {
|
||||
(1, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8),
|
||||
(1, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8),
|
||||
(2, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16),
|
||||
(2, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16),
|
||||
(4, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32),
|
||||
(4, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32),
|
||||
(8, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64),
|
||||
(8, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64),
|
||||
(16, ty::Int(_)) => value.assert_bits(Size::from_bytes(16)) as i128,
|
||||
(16, ty::Uint(_)) => match i128::try_from(value.assert_bits(Size::from_bytes(16))) {
|
||||
Ok(x) => x,
|
||||
// Requires 128 bits
|
||||
Err(_) => return (i128::MIN, end),
|
||||
},
|
||||
// Shouldn't happen if compilation was successful
|
||||
_ => return (start, end),
|
||||
};
|
||||
explicit = value;
|
||||
(start.min(value), end.max(value))
|
||||
} else {
|
||||
// Shouldn't happen if compilation was successful
|
||||
(start, end)
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if start >= end {
|
||||
0
|
||||
} else {
|
||||
let neg_bits = if start < 0 {
|
||||
128 - (-(start + 1)).leading_zeros() + 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let pos_bits = if end > 0 { 128 - end.leading_zeros() } else { 0 };
|
||||
neg_bits.max(pos_bits).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue