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