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