use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::in_constant; use clippy_utils::source::snippet_opt; use clippy_utils::ty::is_isize_or_usize; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, FloatTy, Ty}; use super::{utils, CAST_LOSSLESS}; pub(super) fn check( cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv, ) { if !should_lint(cx, expr, cast_from, cast_to, msrv) { return; } // The suggestion is to use a function call, so if the original expression // has parens on the outside, they are no longer needed. let mut applicability = Applicability::MachineApplicable; let opt = snippet_opt(cx, cast_op.span.source_callsite()); let sugg = opt.as_ref().map_or_else( || { applicability = Applicability::HasPlaceholders; ".." }, |snip| { if should_strip_parens(cast_op, snip) { &snip[1..snip.len() - 1] } else { snip.as_str() } }, ); let message = if cast_from.is_bool() { format!("casting `{cast_from:}` to `{cast_to:}` is more cleanly stated with `{cast_to:}::from(_)`") } else { format!("casting `{cast_from}` to `{cast_to}` may become silently lossy if you later change the type") }; span_lint_and_sugg( cx, CAST_LOSSLESS, expr.span, &message, "try", format!("{cast_to}::from({sugg})"), applicability, ); } fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool { // Do not suggest using From in consts/statics until it is valid to do so (see #2267). if in_constant(cx, expr.hir_id) { return false; } match (cast_from.is_integral(), cast_to.is_integral()) { (true, true) => { let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed(); let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); !is_isize_or_usize(cast_from) && !is_isize_or_usize(cast_to) && from_nbits < to_nbits && !cast_signed_to_unsigned }, (true, false) => { let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() { 32 } else { 64 }; !is_isize_or_usize(cast_from) && from_nbits < to_nbits }, (false, true) if matches!(cast_from.kind(), ty::Bool) && msrv.meets(msrvs::FROM_BOOL) => true, (_, _) => { matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64)) }, } } fn should_strip_parens(cast_expr: &Expr<'_>, snip: &str) -> bool { if let ExprKind::Binary(_, _, _) = cast_expr.kind { if snip.starts_with('(') && snip.ends_with(')') { return true; } } false }