]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/returns.rs
Rollup merge of #106260 - chenyukang:yukang/fix-106213-doc, r=GuillaumeGomez
[rust.git] / src / tools / clippy / clippy_lints / src / returns.rs
1 use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
2 use clippy_utils::source::{snippet_opt, snippet_with_context};
3 use clippy_utils::visitors::{for_each_expr, Descend};
4 use clippy_utils::{fn_def_id, path_to_local_id};
5 use core::ops::ControlFlow;
6 use if_chain::if_chain;
7 use rustc_errors::Applicability;
8 use rustc_hir::intravisit::FnKind;
9 use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, LangItem, MatchSource, PatKind, QPath, StmtKind};
10 use rustc_lint::{LateContext, LateLintPass, LintContext};
11 use rustc_middle::lint::in_external_macro;
12 use rustc_middle::ty::subst::GenericArgKind;
13 use rustc_session::{declare_lint_pass, declare_tool_lint};
14 use rustc_span::source_map::Span;
15 use rustc_span::{BytePos, Pos};
16
17 declare_clippy_lint! {
18     /// ### What it does
19     /// Checks for `let`-bindings, which are subsequently
20     /// returned.
21     ///
22     /// ### Why is this bad?
23     /// It is just extraneous code. Remove it to make your code
24     /// more rusty.
25     ///
26     /// ### Example
27     /// ```rust
28     /// fn foo() -> String {
29     ///     let x = String::new();
30     ///     x
31     /// }
32     /// ```
33     /// instead, use
34     /// ```
35     /// fn foo() -> String {
36     ///     String::new()
37     /// }
38     /// ```
39     #[clippy::version = "pre 1.29.0"]
40     pub LET_AND_RETURN,
41     style,
42     "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
43 }
44
45 declare_clippy_lint! {
46     /// ### What it does
47     /// Checks for return statements at the end of a block.
48     ///
49     /// ### Why is this bad?
50     /// Removing the `return` and semicolon will make the code
51     /// more rusty.
52     ///
53     /// ### Example
54     /// ```rust
55     /// fn foo(x: usize) -> usize {
56     ///     return x;
57     /// }
58     /// ```
59     /// simplify to
60     /// ```rust
61     /// fn foo(x: usize) -> usize {
62     ///     x
63     /// }
64     /// ```
65     #[clippy::version = "pre 1.29.0"]
66     pub NEEDLESS_RETURN,
67     style,
68     "using a return statement like `return expr;` where an expression would suffice"
69 }
70
71 #[derive(PartialEq, Eq, Copy, Clone)]
72 enum RetReplacement {
73     Empty,
74     Block,
75     Unit,
76 }
77
78 impl RetReplacement {
79     fn sugg_help(self) -> &'static str {
80         match self {
81             Self::Empty => "remove `return`",
82             Self::Block => "replace `return` with an empty block",
83             Self::Unit => "replace `return` with a unit value",
84         }
85     }
86 }
87
88 impl ToString for RetReplacement {
89     fn to_string(&self) -> String {
90         match *self {
91             Self::Empty => "",
92             Self::Block => "{}",
93             Self::Unit => "()",
94         }
95         .to_string()
96     }
97 }
98
99 declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN]);
100
101 impl<'tcx> LateLintPass<'tcx> for Return {
102     fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
103         // we need both a let-binding stmt and an expr
104         if_chain! {
105             if let Some(retexpr) = block.expr;
106             if let Some(stmt) = block.stmts.iter().last();
107             if let StmtKind::Local(local) = &stmt.kind;
108             if local.ty.is_none();
109             if cx.tcx.hir().attrs(local.hir_id).is_empty();
110             if let Some(initexpr) = &local.init;
111             if let PatKind::Binding(_, local_id, _, _) = local.pat.kind;
112             if path_to_local_id(retexpr, local_id);
113             if !last_statement_borrows(cx, initexpr);
114             if !in_external_macro(cx.sess(), initexpr.span);
115             if !in_external_macro(cx.sess(), retexpr.span);
116             if !local.span.from_expansion();
117             then {
118                 span_lint_hir_and_then(
119                     cx,
120                     LET_AND_RETURN,
121                     retexpr.hir_id,
122                     retexpr.span,
123                     "returning the result of a `let` binding from a block",
124                     |err| {
125                         err.span_label(local.span, "unnecessary `let` binding");
126
127                         if let Some(mut snippet) = snippet_opt(cx, initexpr.span) {
128                             if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
129                                 snippet.push_str(" as _");
130                             }
131                             err.multipart_suggestion(
132                                 "return the expression directly",
133                                 vec![
134                                     (local.span, String::new()),
135                                     (retexpr.span, snippet),
136                                 ],
137                                 Applicability::MachineApplicable,
138                             );
139                         } else {
140                             err.span_help(initexpr.span, "this expression can be directly returned");
141                         }
142                     },
143                 );
144             }
145         }
146     }
147
148     fn check_fn(
149         &mut self,
150         cx: &LateContext<'tcx>,
151         kind: FnKind<'tcx>,
152         _: &'tcx FnDecl<'tcx>,
153         body: &'tcx Body<'tcx>,
154         _: Span,
155         _: HirId,
156     ) {
157         match kind {
158             FnKind::Closure => {
159                 // when returning without value in closure, replace this `return`
160                 // with an empty block to prevent invalid suggestion (see #6501)
161                 let replacement = if let ExprKind::Ret(None) = &body.value.kind {
162                     RetReplacement::Block
163                 } else {
164                     RetReplacement::Empty
165                 };
166                 check_final_expr(cx, body.value, vec![], replacement);
167             },
168             FnKind::ItemFn(..) | FnKind::Method(..) => {
169                 check_block_return(cx, &body.value.kind, vec![]);
170             },
171         }
172     }
173 }
174
175 // if `expr` is a block, check if there are needless returns in it
176 fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, semi_spans: Vec<Span>) {
177     if let ExprKind::Block(block, _) = expr_kind {
178         if let Some(block_expr) = block.expr {
179             check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty);
180         } else if let Some(stmt) = block.stmts.iter().last() {
181             match stmt.kind {
182                 StmtKind::Expr(expr) => {
183                     check_final_expr(cx, expr, semi_spans, RetReplacement::Empty);
184                 },
185                 StmtKind::Semi(semi_expr) => {
186                     let mut semi_spans_and_this_one = semi_spans;
187                     // we only want the span containing the semicolon so we can remove it later. From `entry.rs:382`
188                     if let Some(semicolon_span) = stmt.span.trim_start(semi_expr.span) {
189                         semi_spans_and_this_one.push(semicolon_span);
190                         check_final_expr(cx, semi_expr, semi_spans_and_this_one, RetReplacement::Empty);
191                     }
192                 },
193                 _ => (),
194             }
195         }
196     }
197 }
198
199 fn check_final_expr<'tcx>(
200     cx: &LateContext<'tcx>,
201     expr: &'tcx Expr<'tcx>,
202     semi_spans: Vec<Span>, /* containing all the places where we would need to remove semicolons if finding an
203                             * needless return */
204     replacement: RetReplacement,
205 ) {
206     let peeled_drop_expr = expr.peel_drop_temps();
207     match &peeled_drop_expr.kind {
208         // simple return is always "bad"
209         ExprKind::Ret(ref inner) => {
210             // if desugar of `do yeet`, don't lint
211             if let Some(inner_expr) = inner
212                 && let ExprKind::Call(path_expr, _) = inner_expr.kind
213                 && let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, _, _)) = path_expr.kind {
214                     return;
215             }
216             if cx.tcx.hir().attrs(expr.hir_id).is_empty() {
217                 let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner));
218                 if !borrows {
219                     // check if expr return nothing
220                     let ret_span = if inner.is_none() && replacement == RetReplacement::Empty {
221                         extend_span_to_previous_non_ws(cx, peeled_drop_expr.span)
222                     } else {
223                         peeled_drop_expr.span
224                     };
225
226                     emit_return_lint(cx, ret_span, semi_spans, inner.as_ref().map(|i| i.span), replacement);
227                 }
228             }
229         },
230         ExprKind::If(_, then, else_clause_opt) => {
231             check_block_return(cx, &then.kind, semi_spans.clone());
232             if let Some(else_clause) = else_clause_opt {
233                 check_block_return(cx, &else_clause.kind, semi_spans);
234             }
235         },
236         // a match expr, check all arms
237         // an if/if let expr, check both exprs
238         // note, if without else is going to be a type checking error anyways
239         // (except for unit type functions) so we don't match it
240         ExprKind::Match(_, arms, MatchSource::Normal) => {
241             for arm in arms.iter() {
242                 check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit);
243             }
244         },
245         // if it's a whole block, check it
246         other_expr_kind => check_block_return(cx, other_expr_kind, semi_spans),
247     }
248 }
249
250 fn emit_return_lint(
251     cx: &LateContext<'_>,
252     ret_span: Span,
253     semi_spans: Vec<Span>,
254     inner_span: Option<Span>,
255     replacement: RetReplacement,
256 ) {
257     if ret_span.from_expansion() {
258         return;
259     }
260     let mut applicability = Applicability::MachineApplicable;
261     let return_replacement = inner_span.map_or_else(
262         || replacement.to_string(),
263         |inner_span| {
264             let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability);
265             snippet.to_string()
266         },
267     );
268     let sugg_help = if inner_span.is_some() {
269         "remove `return`"
270     } else {
271         replacement.sugg_help()
272     };
273     span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
274         diag.span_suggestion_hidden(ret_span, sugg_help, return_replacement, applicability);
275         // for each parent statement, we need to remove the semicolon
276         for semi_stmt_span in semi_spans {
277             diag.tool_only_span_suggestion(semi_stmt_span, "remove this semicolon", "", applicability);
278         }
279     });
280 }
281
282 fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
283     for_each_expr(expr, |e| {
284         if let Some(def_id) = fn_def_id(cx, e)
285             && cx
286                 .tcx
287                 .fn_sig(def_id)
288                 .skip_binder()
289                 .output()
290                 .walk()
291                 .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)))
292         {
293             ControlFlow::Break(())
294         } else {
295             ControlFlow::Continue(Descend::from(!expr.span.from_expansion()))
296         }
297     })
298     .is_some()
299 }
300
301 // Go backwards while encountering whitespace and extend the given Span to that point.
302 fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span {
303     if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) {
304         let ws = [' ', '\t', '\n'];
305         if let Some(non_ws_pos) = prev_source.rfind(|c| !ws.contains(&c)) {
306             let len = prev_source.len() - non_ws_pos - 1;
307             return sp.with_lo(sp.lo() - BytePos::from_usize(len));
308         }
309     }
310
311     sp
312 }