]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/casts/cast_lossless.rs
New upstream version 1.66.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / casts / cast_lossless.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::source::snippet_opt;
3 use clippy_utils::ty::is_isize_or_usize;
4 use clippy_utils::{in_constant, meets_msrv, msrvs};
5 use rustc_errors::Applicability;
6 use rustc_hir::{Expr, ExprKind};
7 use rustc_lint::LateContext;
8 use rustc_middle::ty::{self, FloatTy, Ty};
9 use rustc_semver::RustcVersion;
10
11 use super::{utils, CAST_LOSSLESS};
12
13 pub(super) fn check(
14 cx: &LateContext<'_>,
15 expr: &Expr<'_>,
16 cast_op: &Expr<'_>,
17 cast_from: Ty<'_>,
18 cast_to: Ty<'_>,
19 msrv: Option<RustcVersion>,
20 ) {
21 if !should_lint(cx, expr, cast_from, cast_to, msrv) {
22 return;
23 }
24
25 // The suggestion is to use a function call, so if the original expression
26 // has parens on the outside, they are no longer needed.
27 let mut applicability = Applicability::MachineApplicable;
28 let opt = snippet_opt(cx, cast_op.span);
29 let sugg = opt.as_ref().map_or_else(
30 || {
31 applicability = Applicability::HasPlaceholders;
32 ".."
33 },
34 |snip| {
35 if should_strip_parens(cast_op, snip) {
36 &snip[1..snip.len() - 1]
37 } else {
38 snip.as_str()
39 }
40 },
41 );
42
43 let message = if cast_from.is_bool() {
44 format!("casting `{cast_from:}` to `{cast_to:}` is more cleanly stated with `{cast_to:}::from(_)`")
45 } else {
46 format!("casting `{cast_from}` to `{cast_to}` may become silently lossy if you later change the type")
47 };
48
49 span_lint_and_sugg(
50 cx,
51 CAST_LOSSLESS,
52 expr.span,
53 &message,
54 "try",
55 format!("{cast_to}::from({sugg})"),
56 applicability,
57 );
58 }
59
60 fn should_lint(
61 cx: &LateContext<'_>,
62 expr: &Expr<'_>,
63 cast_from: Ty<'_>,
64 cast_to: Ty<'_>,
65 msrv: Option<RustcVersion>,
66 ) -> bool {
67 // Do not suggest using From in consts/statics until it is valid to do so (see #2267).
68 if in_constant(cx, expr.hir_id) {
69 return false;
70 }
71
72 match (cast_from.is_integral(), cast_to.is_integral()) {
73 (true, true) => {
74 let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed();
75 let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
76 let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
77 !is_isize_or_usize(cast_from)
78 && !is_isize_or_usize(cast_to)
79 && from_nbits < to_nbits
80 && !cast_signed_to_unsigned
81 },
82
83 (true, false) => {
84 let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
85 let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() {
86 32
87 } else {
88 64
89 };
90 !is_isize_or_usize(cast_from) && from_nbits < to_nbits
91 },
92 (false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv, msrvs::FROM_BOOL) => true,
93 (_, _) => {
94 matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64))
95 },
96 }
97 }
98
99 fn should_strip_parens(cast_expr: &Expr<'_>, snip: &str) -> bool {
100 if let ExprKind::Binary(_, _, _) = cast_expr.kind {
101 if snip.starts_with('(') && snip.ends_with(')') {
102 return true;
103 }
104 }
105 false
106 }