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