about summary refs log tree commit diff
path: root/clippy_lints/src/casts/cast_lossless.rs
blob: 4a95bed1148dc4f89df5069527f7d6c0d4b3f7c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_isize_or_usize;
use clippy_utils::{in_constant, meets_msrv, msrvs};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, FloatTy, Ty};
use rustc_semver::RustcVersion;

use super::{utils, CAST_LOSSLESS};

pub(super) fn check(
    cx: &LateContext<'_>,
    expr: &Expr<'_>,
    cast_op: &Expr<'_>,
    cast_from: Ty<'_>,
    cast_to: Ty<'_>,
    msrv: &Option<RustcVersion>,
) {
    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);
    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 `{0:}` to `{1:}` is more cleanly stated with `{1:}::from(_)`",
            cast_from, cast_to
        )
    } else {
        format!(
            "casting `{}` to `{}` may become silently lossy if you later change the type",
            cast_from, cast_to
        )
    };

    span_lint_and_sugg(
        cx,
        CAST_LOSSLESS,
        expr.span,
        &message,
        "try",
        format!("{}::from({})", cast_to, sugg),
        applicability,
    );
}

fn should_lint(
    cx: &LateContext<'_>,
    expr: &Expr<'_>,
    cast_from: Ty<'_>,
    cast_to: Ty<'_>,
    msrv: &Option<RustcVersion>,
) -> 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
            };
            from_nbits < to_nbits
        },
        (false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv.as_ref(), &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
}