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