summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs
blob: 6657cdccd010c4398a7d0b3dbfd9d8a0e8ca6e91 (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
113
114
115
116
117
118
119
120
121
122
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_lang_ctor, path_def_id};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_lint::LateContext;
use rustc_middle::ty::DefIdTree;
use rustc_span::symbol::sym;

use super::OPTION_MAP_OR_NONE;
use super::RESULT_MAP_OR_INTO_OPTION;

// The expression inside a closure may or may not have surrounding braces
// which causes problems when generating a suggestion.
fn reduce_unit_expression<'a>(expr: &'a hir::Expr<'_>) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
    match expr.kind {
        hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)),
        hir::ExprKind::Block(block, _) => {
            match (block.stmts, block.expr) {
                (&[], Some(inner_expr)) => {
                    // If block only contains an expression,
                    // reduce `|x| { x + 1 }` to `|x| x + 1`
                    reduce_unit_expression(inner_expr)
                },
                _ => None,
            }
        },
        _ => None,
    }
}

/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s
pub(super) fn check<'tcx>(
    cx: &LateContext<'tcx>,
    expr: &'tcx hir::Expr<'_>,
    recv: &'tcx hir::Expr<'_>,
    def_arg: &'tcx hir::Expr<'_>,
    map_arg: &'tcx hir::Expr<'_>,
) {
    let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
    let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);

    // There are two variants of this `map_or` lint:
    // (1) using `map_or` as an adapter from `Result<T,E>` to `Option<T>`
    // (2) using `map_or` as a combinator instead of `and_then`
    //
    // (For this lint) we don't care if any other type calls `map_or`
    if !is_option && !is_result {
        return;
    }

    let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind {
        is_lang_ctor(cx, qpath, OptionNone)
    } else {
        return;
    };

    if !default_arg_is_none {
        // nothing to lint!
        return;
    }

    let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind {
        is_lang_ctor(cx, qpath, OptionSome)
    } else {
        false
    };

    if is_option {
        let self_snippet = snippet(cx, recv.span, "..");
        if_chain! {
            if let hir::ExprKind::Closure(&hir::Closure { body, fn_decl_span, .. }) = map_arg.kind;
            let arg_snippet = snippet(cx, fn_decl_span, "..");
            let body = cx.tcx.hir().body(body);
            if let Some((func, [arg_char])) = reduce_unit_expression(body.value);
            if let Some(id) = path_def_id(cx, func).map(|ctor_id| cx.tcx.parent(ctor_id));
            if Some(id) == cx.tcx.lang_items().option_some_variant();
            then {
                let func_snippet = snippet(cx, arg_char.span, "..");
                let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
                   `map(..)` instead";
                return span_lint_and_sugg(
                    cx,
                    OPTION_MAP_OR_NONE,
                    expr.span,
                    msg,
                    "try using `map` instead",
                    format!("{0}.map({1} {2})", self_snippet, arg_snippet,func_snippet),
                    Applicability::MachineApplicable,
                );
            }
        }

        let func_snippet = snippet(cx, map_arg.span, "..");
        let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
                       `and_then(..)` instead";
        span_lint_and_sugg(
            cx,
            OPTION_MAP_OR_NONE,
            expr.span,
            msg,
            "try using `and_then` instead",
            format!("{0}.and_then({1})", self_snippet, func_snippet),
            Applicability::MachineApplicable,
        );
    } else if f_arg_is_some {
        let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \
                       `ok()` instead";
        let self_snippet = snippet(cx, recv.span, "..");
        span_lint_and_sugg(
            cx,
            RESULT_MAP_OR_INTO_OPTION,
            expr.span,
            msg,
            "try using `ok` instead",
            format!("{0}.ok()", self_snippet),
            Applicability::MachineApplicable,
        );
    }
}