]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/casts/cast_lossless.rs
Rollup merge of #90407 - pierwill:edit-rustc-incremental-docs, r=cjgillot
[rust.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!(
45             "casting `{0:}` to `{1:}` is more cleanly stated with `{1:}::from(_)`",
46             cast_from, cast_to
47         )
48     } else {
49         format!(
50             "casting `{}` to `{}` may become silently lossy if you later change the type",
51             cast_from, cast_to
52         )
53     };
54
55     span_lint_and_sugg(
56         cx,
57         CAST_LOSSLESS,
58         expr.span,
59         &message,
60         "try",
61         format!("{}::from({})", cast_to, sugg),
62         applicability,
63     );
64 }
65
66 fn should_lint(
67     cx: &LateContext<'_>,
68     expr: &Expr<'_>,
69     cast_from: Ty<'_>,
70     cast_to: Ty<'_>,
71     msrv: &Option<RustcVersion>,
72 ) -> bool {
73     // Do not suggest using From in consts/statics until it is valid to do so (see #2267).
74     if in_constant(cx, expr.hir_id) {
75         return false;
76     }
77
78     match (cast_from.is_integral(), cast_to.is_integral()) {
79         (true, true) => {
80             let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed();
81             let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
82             let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
83             !is_isize_or_usize(cast_from)
84                 && !is_isize_or_usize(cast_to)
85                 && from_nbits < to_nbits
86                 && !cast_signed_to_unsigned
87         },
88
89         (true, false) => {
90             let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx);
91             let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() {
92                 32
93             } else {
94                 64
95             };
96             from_nbits < to_nbits
97         },
98         (false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv.as_ref(), &msrvs::FROM_BOOL) => true,
99         (_, _) => {
100             matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64))
101         },
102     }
103 }
104
105 fn should_strip_parens(cast_expr: &Expr<'_>, snip: &str) -> bool {
106     if let ExprKind::Binary(_, _, _) = cast_expr.kind {
107         if snip.starts_with('(') && snip.ends_with(')') {
108             return true;
109         }
110     }
111     false
112 }