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