summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/functions/result.rs
blob: 9591405cb06f7b2d836d55f2c42148faf1ac0947 (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
use rustc_errors::Diagnostic;
use rustc_hir as hir;
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty};
use rustc_span::{sym, Span};

use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use clippy_utils::trait_ref_of_method;
use clippy_utils::ty::{approx_ty_size, is_type_diagnostic_item};

use super::{RESULT_LARGE_ERR, RESULT_UNIT_ERR};

/// The type of the `Err`-variant in a `std::result::Result` returned by the
/// given `FnDecl`
fn result_err_ty<'tcx>(
    cx: &LateContext<'tcx>,
    decl: &hir::FnDecl<'tcx>,
    id: hir::def_id::LocalDefId,
    item_span: Span,
) -> Option<(&'tcx hir::Ty<'tcx>, Ty<'tcx>)> {
    if !in_external_macro(cx.sess(), item_span)
        && let hir::FnRetTy::Return(hir_ty) = decl.output
        && let ty = cx.tcx.erase_late_bound_regions(cx.tcx.fn_sig(id).output())
        && is_type_diagnostic_item(cx, ty, sym::Result)
        && let ty::Adt(_, substs) = ty.kind()
    {
        let err_ty = substs.type_at(1);
        Some((hir_ty, err_ty))
    } else {
        None
    }
}

pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, large_err_threshold: u64) {
    if let hir::ItemKind::Fn(ref sig, _generics, _) = item.kind
        && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.def_id, item.span)
    {
        if cx.access_levels.is_exported(item.def_id) {
            let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
            check_result_unit_err(cx, err_ty, fn_header_span);
        }
        check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold);
    }
}

pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::ImplItem<'tcx>, large_err_threshold: u64) {
    // Don't lint if method is a trait's implementation, we can't do anything about those
    if let hir::ImplItemKind::Fn(ref sig, _) = item.kind
        && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.def_id, item.span)
        && trait_ref_of_method(cx, item.def_id).is_none()
    {
        if cx.access_levels.is_exported(item.def_id) {
            let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
            check_result_unit_err(cx, err_ty, fn_header_span);
        }
        check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold);
    }
}

pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::TraitItem<'tcx>, large_err_threshold: u64) {
    if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
        let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
        if let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.def_id, item.span) {
            if cx.access_levels.is_exported(item.def_id) {
                check_result_unit_err(cx, err_ty, fn_header_span);
            }
            check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold);
        }
    }
}

fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: Span) {
    if err_ty.is_unit() {
        span_lint_and_help(
            cx,
            RESULT_UNIT_ERR,
            fn_header_span,
            "this returns a `Result<_, ()>`",
            None,
            "use a custom `Error` type instead",
        );
    }
}

fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) {
    let ty_size = approx_ty_size(cx, err_ty);
    if ty_size >= large_err_threshold {
        span_lint_and_then(
            cx,
            RESULT_LARGE_ERR,
            hir_ty_span,
            "the `Err`-variant returned from this function is very large",
            |diag: &mut Diagnostic| {
                diag.span_label(hir_ty_span, format!("the `Err`-variant is at least {ty_size} bytes"));
                diag.help(format!("try reducing the size of `{err_ty}`, for example by boxing large elements or replacing it with `Box<{err_ty}>`"));
            },
        );
    }
}