about summary refs log tree commit diff
path: root/clippy_lints/src/needless_question_mark.rs
blob: 42e48336e1539595d7f877698bd2f42031a83507 (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
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_lang_ctor;
use clippy_utils::source::snippet;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionSome, ResultOk};
use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyS;
use rustc_session::{declare_lint_pass, declare_tool_lint};

declare_clippy_lint! {
    /// ### What it does
    /// Suggests alternatives for useless applications of `?` in terminating expressions
    ///
    /// ### Why is this bad?
    /// There's no reason to use `?` to short-circuit when execution of the body will end there anyway.
    ///
    /// ### Example
    /// ```rust
    /// struct TO {
    ///     magic: Option<usize>,
    /// }
    ///
    /// fn f(to: TO) -> Option<usize> {
    ///     Some(to.magic?)
    /// }
    ///
    /// struct TR {
    ///     magic: Result<usize, bool>,
    /// }
    ///
    /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
    ///     tr.and_then(|t| Ok(t.magic?))
    /// }
    ///
    /// ```
    /// Use instead:
    /// ```rust
    /// struct TO {
    ///     magic: Option<usize>,
    /// }
    ///
    /// fn f(to: TO) -> Option<usize> {
    ///    to.magic
    /// }
    ///
    /// struct TR {
    ///     magic: Result<usize, bool>,
    /// }
    ///
    /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
    ///     tr.and_then(|t| t.magic)
    /// }
    /// ```
    pub NEEDLESS_QUESTION_MARK,
    complexity,
    "Suggest `value.inner_option` instead of `Some(value.inner_option?)`. The same goes for `Result<T, E>`."
}

declare_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]);

impl LateLintPass<'_> for NeedlessQuestionMark {
    /*
     * The question mark operator is compatible with both Result<T, E> and Option<T>,
     * from Rust 1.13 and 1.22 respectively.
     */

    /*
     * What do we match:
     * Expressions that look like this:
     * Some(option?), Ok(result?)
     *
     * Where do we match:
     *      Last expression of a body
     *      Return statement
     *      A body's value (single line closure)
     *
     * What do we not match:
     *      Implicit calls to `from(..)` on the error value
     */

    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
        if let ExprKind::Ret(Some(e)) = expr.kind {
            check(cx, e);
        }
    }

    fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
        check(cx, body.value.peel_blocks());
    }
}

fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
    let inner_expr = if_chain! {
        if let ExprKind::Call(path, [arg]) = &expr.kind;
        if let ExprKind::Path(ref qpath) = &path.kind;
        if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
        if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &arg.kind;
        if let ExprKind::Call(called, [inner_expr]) = &inner_expr_with_q.kind;
        if let ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, _)) = &called.kind;
        if expr.span.ctxt() == inner_expr.span.ctxt();
        let expr_ty = cx.typeck_results().expr_ty(expr);
        let inner_ty = cx.typeck_results().expr_ty(inner_expr);
        if TyS::same_type(expr_ty, inner_ty);
        then { inner_expr } else { return; }
    };
    span_lint_and_sugg(
        cx,
        NEEDLESS_QUESTION_MARK,
        expr.span,
        "question mark operator is useless here",
        "try",
        format!("{}", snippet(cx, inner_expr.span, r#""...""#)),
        Applicability::MachineApplicable,
    );
}