]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/returns.rs
Auto merge of #2712 - RalfJung:rustup, r=RalfJung
[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, MatchSource, PatKind, 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 cx.tcx.hir().attrs(expr.hir_id).is_empty() {
211                 let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner));
212                 if !borrows {
213                     // check if expr return nothing
214                     let ret_span = if inner.is_none() && replacement == RetReplacement::Empty {
215                         extend_span_to_previous_non_ws(cx, peeled_drop_expr.span)
216                     } else {
217                         peeled_drop_expr.span
218                     };
219
220                     emit_return_lint(cx, ret_span, semi_spans, inner.as_ref().map(|i| i.span), replacement);
221                 }
222             }
223         },
224         ExprKind::If(_, then, else_clause_opt) => {
225             check_block_return(cx, &then.kind, semi_spans.clone());
226             if let Some(else_clause) = else_clause_opt {
227                 check_block_return(cx, &else_clause.kind, semi_spans);
228             }
229         },
230         // a match expr, check all arms
231         // an if/if let expr, check both exprs
232         // note, if without else is going to be a type checking error anyways
233         // (except for unit type functions) so we don't match it
234         ExprKind::Match(_, arms, MatchSource::Normal) => {
235             for arm in arms.iter() {
236                 check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit);
237             }
238         },
239         // if it's a whole block, check it
240         other_expr_kind => check_block_return(cx, other_expr_kind, semi_spans),
241     }
242 }
243
244 fn emit_return_lint(
245     cx: &LateContext<'_>,
246     ret_span: Span,
247     semi_spans: Vec<Span>,
248     inner_span: Option<Span>,
249     replacement: RetReplacement,
250 ) {
251     if ret_span.from_expansion() {
252         return;
253     }
254     let mut applicability = Applicability::MachineApplicable;
255     let return_replacement = inner_span.map_or_else(
256         || replacement.to_string(),
257         |inner_span| {
258             let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability);
259             snippet.to_string()
260         },
261     );
262     let sugg_help = if inner_span.is_some() {
263         "remove `return`"
264     } else {
265         replacement.sugg_help()
266     };
267     span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
268         diag.span_suggestion_hidden(ret_span, sugg_help, return_replacement, applicability);
269         // for each parent statement, we need to remove the semicolon
270         for semi_stmt_span in semi_spans {
271             diag.tool_only_span_suggestion(semi_stmt_span, "remove this semicolon", "", applicability);
272         }
273     });
274 }
275
276 fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
277     for_each_expr(expr, |e| {
278         if let Some(def_id) = fn_def_id(cx, e)
279             && cx
280                 .tcx
281                 .fn_sig(def_id)
282                 .skip_binder()
283                 .output()
284                 .walk()
285                 .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)))
286         {
287             ControlFlow::Break(())
288         } else {
289             ControlFlow::Continue(Descend::from(!expr.span.from_expansion()))
290         }
291     })
292     .is_some()
293 }
294
295 // Go backwards while encountering whitespace and extend the given Span to that point.
296 fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span {
297     if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) {
298         let ws = [' ', '\t', '\n'];
299         if let Some(non_ws_pos) = prev_source.rfind(|c| !ws.contains(&c)) {
300             let len = prev_source.len() - non_ws_pos - 1;
301             return sp.with_lo(sp.lo() - BytePos::from_usize(len));
302         }
303     }
304
305     sp
306 }