]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/needless_question_mark.rs
Auto merge of #7138 - mgacek8:issue6808_iter_cloned_collect_FN_with_large_array,...
[rust.git] / clippy_lints / src / needless_question_mark.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::source::snippet;
3 use clippy_utils::ty::is_type_diagnostic_item;
4 use clippy_utils::{differing_macro_contexts, is_lang_ctor};
5 use if_chain::if_chain;
6 use rustc_errors::Applicability;
7 use rustc_hir::LangItem::{OptionSome, ResultOk};
8 use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath};
9 use rustc_lint::{LateContext, LateLintPass};
10 use rustc_session::{declare_lint_pass, declare_tool_lint};
11 use rustc_span::sym;
12
13 declare_clippy_lint! {
14     /// **What it does:**
15     /// Suggests alternatives for useless applications of `?` in terminating expressions
16     ///
17     /// **Why is this bad?** There's no reason to use `?` to short-circuit when execution of the body will end there anyway.
18     ///
19     /// **Known problems:** None.
20     ///
21     /// **Example:**
22     ///
23     /// ```rust
24     /// struct TO {
25     ///     magic: Option<usize>,
26     /// }
27     ///
28     /// fn f(to: TO) -> Option<usize> {
29     ///     Some(to.magic?)
30     /// }
31     ///
32     /// struct TR {
33     ///     magic: Result<usize, bool>,
34     /// }
35     ///
36     /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
37     ///     tr.and_then(|t| Ok(t.magic?))
38     /// }
39     ///
40     /// ```
41     /// Use instead:
42     /// ```rust
43     /// struct TO {
44     ///     magic: Option<usize>,
45     /// }
46     ///
47     /// fn f(to: TO) -> Option<usize> {
48     ///    to.magic
49     /// }
50     ///
51     /// struct TR {
52     ///     magic: Result<usize, bool>,
53     /// }
54     ///
55     /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
56     ///     tr.and_then(|t| t.magic)
57     /// }
58     /// ```
59     pub NEEDLESS_QUESTION_MARK,
60     complexity,
61     "Suggest `value.inner_option` instead of `Some(value.inner_option?)`. The same goes for `Result<T, E>`."
62 }
63
64 declare_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]);
65
66 #[derive(Debug)]
67 enum SomeOkCall<'a> {
68     SomeCall(&'a Expr<'a>, &'a Expr<'a>),
69     OkCall(&'a Expr<'a>, &'a Expr<'a>),
70 }
71
72 impl LateLintPass<'_> for NeedlessQuestionMark {
73     /*
74      * The question mark operator is compatible with both Result<T, E> and Option<T>,
75      * from Rust 1.13 and 1.22 respectively.
76      */
77
78     /*
79      * What do we match:
80      * Expressions that look like this:
81      * Some(option?), Ok(result?)
82      *
83      * Where do we match:
84      *      Last expression of a body
85      *      Return statement
86      *      A body's value (single line closure)
87      *
88      * What do we not match:
89      *      Implicit calls to `from(..)` on the error value
90      */
91
92     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
93         let e = match &expr.kind {
94             ExprKind::Ret(Some(e)) => e,
95             _ => return,
96         };
97
98         if let Some(ok_some_call) = is_some_or_ok_call(cx, e) {
99             emit_lint(cx, &ok_some_call);
100         }
101     }
102
103     fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
104         // Function / Closure block
105         let expr_opt = if let ExprKind::Block(block, _) = &body.value.kind {
106             block.expr
107         } else {
108             // Single line closure
109             Some(&body.value)
110         };
111
112         if_chain! {
113             if let Some(expr) = expr_opt;
114             if let Some(ok_some_call) = is_some_or_ok_call(cx, expr);
115             then {
116                 emit_lint(cx, &ok_some_call);
117             }
118         };
119     }
120 }
121
122 fn emit_lint(cx: &LateContext<'_>, expr: &SomeOkCall<'_>) {
123     let (entire_expr, inner_expr) = match expr {
124         SomeOkCall::OkCall(outer, inner) | SomeOkCall::SomeCall(outer, inner) => (outer, inner),
125     };
126
127     span_lint_and_sugg(
128         cx,
129         NEEDLESS_QUESTION_MARK,
130         entire_expr.span,
131         "question mark operator is useless here",
132         "try",
133         format!("{}", snippet(cx, inner_expr.span, r#""...""#)),
134         Applicability::MachineApplicable,
135     );
136 }
137
138 fn is_some_or_ok_call<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option<SomeOkCall<'a>> {
139     if_chain! {
140         // Check outer expression matches CALL_IDENT(ARGUMENT) format
141         if let ExprKind::Call(path, args) = &expr.kind;
142         if let ExprKind::Path(ref qpath) = &path.kind;
143         if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
144
145         // Extract inner expression from ARGUMENT
146         if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &args[0].kind;
147         if let ExprKind::Call(called, args) = &inner_expr_with_q.kind;
148         if args.len() == 1;
149
150         if let ExprKind::Path(QPath::LangItem(LangItem::TryIntoResult, _)) = &called.kind;
151         then {
152             // Extract inner expr type from match argument generated by
153             // question mark operator
154             let inner_expr = &args[0];
155
156             // if the inner expr is inside macro but the outer one is not, do not lint (#6921)
157             if  differing_macro_contexts(expr.span, inner_expr.span) {
158                 return None;
159             }
160
161             let inner_ty = cx.typeck_results().expr_ty(inner_expr);
162             let outer_ty = cx.typeck_results().expr_ty(expr);
163
164             // Check if outer and inner type are Option
165             let outer_is_some = is_type_diagnostic_item(cx, outer_ty, sym::option_type);
166             let inner_is_some = is_type_diagnostic_item(cx, inner_ty, sym::option_type);
167
168             // Check for Option MSRV
169             if outer_is_some && inner_is_some {
170                 return Some(SomeOkCall::SomeCall(expr, inner_expr));
171             }
172
173             // Check if outer and inner type are Result
174             let outer_is_result = is_type_diagnostic_item(cx, outer_ty, sym::result_type);
175             let inner_is_result = is_type_diagnostic_item(cx, inner_ty, sym::result_type);
176
177             // Additional check: if the error type of the Result can be converted
178             // via the From trait, then don't match
179             let does_not_call_from = !has_implicit_error_from(cx, expr, inner_expr);
180
181             // Must meet Result MSRV
182             if outer_is_result && inner_is_result && does_not_call_from {
183                 return Some(SomeOkCall::OkCall(expr, inner_expr));
184             }
185         }
186     }
187
188     None
189 }
190
191 fn has_implicit_error_from(cx: &LateContext<'_>, entire_expr: &Expr<'_>, inner_result_expr: &Expr<'_>) -> bool {
192     return cx.typeck_results().expr_ty(entire_expr) != cx.typeck_results().expr_ty(inner_result_expr);
193 }