use clippy_utils::consts::{constant, Constant};
-use clippy_utils::diagnostics::span_lint;
+use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::expr_or_init;
+use clippy_utils::source::snippet;
use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize};
-use rustc_ast::ast;
-use rustc_attr::IntType;
+use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, FloatTy, Ty};
+use rustc_target::abi::IntegerType;
use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION};
ExprKind::Block(block, _) => block.expr.map_or(nbits, |e| apply_reductions(cx, nbits, e, signed)),
ExprKind::Binary(op, left, right) => match op.node {
BinOpKind::Div => {
- apply_reductions(cx, nbits, left, signed)
- - (if signed {
- 0 // let's be conservative here
- } else {
- // by dividing by 1, we remove 0 bits, etc.
- get_constant_bits(cx, right).map_or(0, |b| b.saturating_sub(1))
- })
+ apply_reductions(cx, nbits, left, signed).saturating_sub(if signed {
+ // let's be conservative here
+ 0
+ } else {
+ // by dividing by 1, we remove 0 bits, etc.
+ get_constant_bits(cx, right).map_or(0, |b| b.saturating_sub(1))
+ })
},
BinOpKind::Rem | BinOpKind::BitAnd => get_constant_bits(cx, right)
.unwrap_or(u64::max_value())
.min(apply_reductions(cx, nbits, left, signed)),
- BinOpKind::Shr => {
- apply_reductions(cx, nbits, left, signed)
- - constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))
- },
+ BinOpKind::Shr => apply_reductions(cx, nbits, left, signed)
+ .saturating_sub(constant_int(cx, right).map_or(0, |s| u64::try_from(s).expect("shift too high"))),
_ => nbits,
},
- ExprKind::MethodCall(method, [left, right], _) => {
+ ExprKind::MethodCall(method, left, [right], _) => {
if signed {
return nbits;
}
};
apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::max_value()))
},
- ExprKind::MethodCall(method, [_, lo, hi], _) => {
+ ExprKind::MethodCall(method, _, [lo, hi], _) => {
if method.ident.as_str() == "clamp" {
//FIXME: make this a diagnostic item
if let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi)) {
}
nbits
},
- ExprKind::MethodCall(method, [_value], _) => {
+ ExprKind::MethodCall(method, _value, [], _) => {
if method.ident.name.as_str() == "signum" {
0 // do not lint if cast comes from a `signum` function
} else {
return;
}
- format!(
- "casting `{}` to `{}` may truncate the value{}",
- cast_from, cast_to, suffix,
- )
+ format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}",)
},
(ty::Adt(def, _), true) if def.is_enum() => {
&& let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id)
{
let i = def.variant_index_with_ctor_id(id);
- let variant = &def.variants[i];
- let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, def, i));
+ let variant = def.variant(i);
+ let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, *def, i));
(nbits, Some(variant))
} else {
- (utils::enum_ty_to_nbits(def, cx.tcx), None)
+ (utils::enum_ty_to_nbits(*def, cx.tcx), None)
};
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
- let cast_from_ptr_size = def.repr.int.map_or(true, |ty| {
- matches!(
- ty,
- IntType::SignedInt(ast::IntTy::Isize) | IntType::UnsignedInt(ast::UintTy::Usize)
- )
- });
+ let cast_from_ptr_size = def.repr().int.map_or(true, |ty| matches!(ty, IntegerType::Pointer(_),));
let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
(false, false) if from_nbits > to_nbits => "",
(true, false) if from_nbits > to_nbits => "",
CAST_ENUM_TRUNCATION,
expr.span,
&format!(
- "casting `{}::{}` to `{}` will truncate the value{}",
- cast_from, variant.name, cast_to, suffix,
+ "casting `{cast_from}::{}` to `{cast_to}` will truncate the value{suffix}",
+ variant.name,
),
);
return;
}
- format!(
- "casting `{}` to `{}` may truncate the value{}",
- cast_from, cast_to, suffix,
- )
+ format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}")
},
(ty::Float(_), true) => {
- format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to)
+ format!("casting `{cast_from}` to `{cast_to}` may truncate the value")
},
(ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => {
_ => return,
};
- span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg);
+ let snippet = snippet(cx, expr.span, "..");
+ let name_of_cast_from = snippet.split(" as").next().unwrap_or("..");
+ let suggestion = format!("{cast_to}::try_from({name_of_cast_from})");
+
+ span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg, |diag| {
+ diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_precision_loss)]` ...");
+ diag.span_suggestion_with_style(
+ expr.span,
+ "... or use `try_from` and handle the error accordingly",
+ suggestion,
+ Applicability::Unspecified,
+ // always show the suggestion in a separate line
+ SuggestionStyle::ShowAlways,
+ );
+ });
}