]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/needless_question_mark.rs
Rollup merge of #105824 - zacchiro:patch-1, r=JohnTitor
[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::path_res;
3 use clippy_utils::source::snippet;
4 use if_chain::if_chain;
5 use rustc_errors::Applicability;
6 use rustc_hir::def::{DefKind, Res};
7 use rustc_hir::{AsyncGeneratorKind, Block, Body, Expr, ExprKind, GeneratorKind, LangItem, MatchSource, QPath};
8 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_middle::ty::DefIdTree;
10 use rustc_session::{declare_lint_pass, declare_tool_lint};
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?
17     /// There's no reason to use `?` to short-circuit when execution of the body will end there anyway.
18     ///
19     /// ### Example
20     /// ```rust
21     /// struct TO {
22     ///     magic: Option<usize>,
23     /// }
24     ///
25     /// fn f(to: TO) -> Option<usize> {
26     ///     Some(to.magic?)
27     /// }
28     ///
29     /// struct TR {
30     ///     magic: Result<usize, bool>,
31     /// }
32     ///
33     /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
34     ///     tr.and_then(|t| Ok(t.magic?))
35     /// }
36     ///
37     /// ```
38     /// Use instead:
39     /// ```rust
40     /// struct TO {
41     ///     magic: Option<usize>,
42     /// }
43     ///
44     /// fn f(to: TO) -> Option<usize> {
45     ///    to.magic
46     /// }
47     ///
48     /// struct TR {
49     ///     magic: Result<usize, bool>,
50     /// }
51     ///
52     /// fn g(tr: Result<TR, bool>) -> Result<usize, bool> {
53     ///     tr.and_then(|t| t.magic)
54     /// }
55     /// ```
56     #[clippy::version = "1.51.0"]
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 declare_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]);
63
64 impl LateLintPass<'_> for NeedlessQuestionMark {
65     /*
66      * The question mark operator is compatible with both Result<T, E> and Option<T>,
67      * from Rust 1.13 and 1.22 respectively.
68      */
69
70     /*
71      * What do we match:
72      * Expressions that look like this:
73      * Some(option?), Ok(result?)
74      *
75      * Where do we match:
76      *      Last expression of a body
77      *      Return statement
78      *      A body's value (single line closure)
79      *
80      * What do we not match:
81      *      Implicit calls to `from(..)` on the error value
82      */
83
84     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
85         if let ExprKind::Ret(Some(e)) = expr.kind {
86             check(cx, e);
87         }
88     }
89
90     fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
91         if let Some(GeneratorKind::Async(AsyncGeneratorKind::Fn)) = body.generator_kind {
92             if let ExprKind::Block(
93                 Block {
94                     expr:
95                         Some(Expr {
96                             kind: ExprKind::DropTemps(async_body),
97                             ..
98                         }),
99                     ..
100                 },
101                 _,
102             ) = body.value.kind
103             {
104                 if let ExprKind::Block(Block { expr: Some(expr), .. }, ..) = async_body.kind {
105                     check(cx, expr);
106                 }
107             }
108         } else {
109             check(cx, body.value.peel_blocks());
110         }
111     }
112 }
113
114 fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
115     if_chain! {
116         if let ExprKind::Call(path, [arg]) = expr.kind;
117         if let Res::Def(DefKind::Ctor(..), ctor_id) = path_res(cx, path);
118         if let Some(variant_id) = cx.tcx.opt_parent(ctor_id);
119         let sugg_remove = if cx.tcx.lang_items().option_some_variant() == Some(variant_id) {
120             "Some()"
121         } else if cx.tcx.lang_items().result_ok_variant() == Some(variant_id) {
122             "Ok()"
123         } else {
124             return;
125         };
126         if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &arg.kind;
127         if let ExprKind::Call(called, [inner_expr]) = &inner_expr_with_q.kind;
128         if let ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)) = &called.kind;
129         if expr.span.ctxt() == inner_expr.span.ctxt();
130         let expr_ty = cx.typeck_results().expr_ty(expr);
131         let inner_ty = cx.typeck_results().expr_ty(inner_expr);
132         if expr_ty == inner_ty;
133         then {
134             span_lint_and_sugg(
135                 cx,
136                 NEEDLESS_QUESTION_MARK,
137                 expr.span,
138                 "question mark operator is useless here",
139                 &format!("try removing question mark and `{sugg_remove}`"),
140                 format!("{}", snippet(cx, inner_expr.span, r#""...""#)),
141                 Applicability::MachineApplicable,
142             );
143         }
144     }
145 }