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