]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/casts/cast_lossless.rs
Auto merge of #9870 - koka831:unformat-unused-rounding, r=Jarcho
[rust.git] / 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 }