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