]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/question_mark.rs
Ignore associated items in trait *implementations* when considering type complexity
[rust.git] / clippy_lints / src / question_mark.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::higher;
3 use clippy_utils::is_lang_ctor;
4 use clippy_utils::source::snippet_with_applicability;
5 use clippy_utils::sugg::Sugg;
6 use clippy_utils::ty::is_type_diagnostic_item;
7 use clippy_utils::{eq_expr_value, path_to_local, path_to_local_id};
8 use if_chain::if_chain;
9 use rustc_errors::Applicability;
10 use rustc_hir::LangItem::{OptionNone, OptionSome, ResultOk};
11 use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, PatKind, StmtKind};
12 use rustc_lint::{LateContext, LateLintPass};
13 use rustc_session::{declare_lint_pass, declare_tool_lint};
14 use rustc_span::sym;
15
16 declare_clippy_lint! {
17     /// ### What it does
18     /// Checks for expressions that could be replaced by the question mark operator.
19     ///
20     /// ### Why is this bad?
21     /// Question mark usage is more idiomatic.
22     ///
23     /// ### Example
24     /// ```ignore
25     /// if option.is_none() {
26     ///     return None;
27     /// }
28     /// ```
29     ///
30     /// Could be written:
31     ///
32     /// ```ignore
33     /// option?;
34     /// ```
35     #[clippy::version = "pre 1.29.0"]
36     pub QUESTION_MARK,
37     style,
38     "checks for expressions that could be replaced by the question mark operator"
39 }
40
41 declare_lint_pass!(QuestionMark => [QUESTION_MARK]);
42
43 impl QuestionMark {
44     /// Checks if the given expression on the given context matches the following structure:
45     ///
46     /// ```ignore
47     /// if option.is_none() {
48     ///    return None;
49     /// }
50     /// ```
51     ///
52     /// ```ignore
53     /// if result.is_err() {
54     ///     return result;
55     /// }
56     /// ```
57     ///
58     /// If it matches, it will suggest to use the question mark operator instead
59     fn check_is_none_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
60         if_chain! {
61             if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr);
62             if let ExprKind::MethodCall(segment, _, args, _) = &cond.kind;
63             if let Some(subject) = args.get(0);
64             if (Self::option_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_none)) ||
65                 (Self::result_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_err));
66             then {
67                 let mut applicability = Applicability::MachineApplicable;
68                 let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability);
69                 let mut replacement: Option<String> = None;
70                 if let Some(else_inner) = r#else {
71                     if_chain! {
72                         if let ExprKind::Block(block, None) = &else_inner.kind;
73                         if block.stmts.is_empty();
74                         if let Some(block_expr) = &block.expr;
75                         if eq_expr_value(cx, subject, block_expr);
76                         then {
77                             replacement = Some(format!("Some({}?)", receiver_str));
78                         }
79                     }
80                 } else if Self::moves_by_default(cx, subject)
81                     && !matches!(subject.kind, ExprKind::Call(..) | ExprKind::MethodCall(..))
82                 {
83                     replacement = Some(format!("{}.as_ref()?;", receiver_str));
84                 } else {
85                     replacement = Some(format!("{}?;", receiver_str));
86                 }
87
88                 if let Some(replacement_str) = replacement {
89                     span_lint_and_sugg(
90                         cx,
91                         QUESTION_MARK,
92                         expr.span,
93                         "this block may be rewritten with the `?` operator",
94                         "replace it with",
95                         replacement_str,
96                         applicability,
97                     );
98                 }
99             }
100         }
101     }
102
103     fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) {
104         if_chain! {
105             if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
106                 = higher::IfLet::hir(cx, expr);
107             if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
108             if (Self::option_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, OptionSome)) ||
109                 (Self::result_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, ResultOk));
110
111             if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind;
112             let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
113             if let ExprKind::Block(block, None) = if_then.kind;
114             if block.stmts.is_empty();
115             if let Some(trailing_expr) = &block.expr;
116             if path_to_local_id(trailing_expr, bind_id);
117             then {
118                 let mut applicability = Applicability::MachineApplicable;
119                 let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability);
120                 let replacement = format!("{}{}?", receiver_str, if by_ref { ".as_ref()" } else { "" },);
121
122                 span_lint_and_sugg(
123                     cx,
124                     QUESTION_MARK,
125                     expr.span,
126                     "this if-let-else may be rewritten with the `?` operator",
127                     "replace it with",
128                     replacement,
129                     applicability,
130                 );
131             }
132         }
133     }
134
135     fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
136         Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(cx, nested_expr, expr)
137     }
138
139     fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
140         Self::is_option(cx, expr) && Self::expression_returns_none(cx, nested_expr)
141     }
142
143     fn moves_by_default(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
144         let expr_ty = cx.typeck_results().expr_ty(expression);
145
146         !expr_ty.is_copy_modulo_regions(cx.tcx.at(expression.span), cx.param_env)
147     }
148
149     fn is_option(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
150         let expr_ty = cx.typeck_results().expr_ty(expression);
151
152         is_type_diagnostic_item(cx, expr_ty, sym::Option)
153     }
154
155     fn is_result(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
156         let expr_ty = cx.typeck_results().expr_ty(expression);
157
158         is_type_diagnostic_item(cx, expr_ty, sym::Result)
159     }
160
161     fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool {
162         match expression.kind {
163             ExprKind::Block(block, _) => {
164                 if let Some(return_expression) = Self::return_expression(block) {
165                     return Self::expression_returns_none(cx, return_expression);
166                 }
167
168                 false
169             },
170             ExprKind::Ret(Some(expr)) => Self::expression_returns_none(cx, expr),
171             ExprKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
172             _ => false,
173         }
174     }
175
176     fn expression_returns_unmodified_err(cx: &LateContext<'_>, expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool {
177         match expr.kind {
178             ExprKind::Block(block, _) => {
179                 if let Some(return_expression) = Self::return_expression(block) {
180                     return Self::expression_returns_unmodified_err(cx, return_expression, cond_expr);
181                 }
182
183                 false
184             },
185             ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(cx, ret_expr, cond_expr),
186             ExprKind::Path(_) => path_to_local(expr) == path_to_local(cond_expr),
187             _ => false,
188         }
189     }
190
191     fn return_expression<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
192         // Check if last expression is a return statement. Then, return the expression
193         if_chain! {
194             if block.stmts.len() == 1;
195             if let Some(expr) = block.stmts.iter().last();
196             if let StmtKind::Semi(expr) = expr.kind;
197             if let ExprKind::Ret(Some(ret_expr)) = expr.kind;
198
199             then {
200                 return Some(ret_expr);
201             }
202         }
203
204         // Check for `return` without a semicolon.
205         if_chain! {
206             if block.stmts.is_empty();
207             if let Some(ExprKind::Ret(Some(ret_expr))) = block.expr.as_ref().map(|e| &e.kind);
208             then {
209                 return Some(ret_expr);
210             }
211         }
212
213         None
214     }
215 }
216
217 impl<'tcx> LateLintPass<'tcx> for QuestionMark {
218     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
219         Self::check_is_none_or_err_and_early_return(cx, expr);
220         Self::check_if_let_some_or_err_and_early_return(cx, expr);
221     }
222 }