Skip to content

Commit 5c0cb0d

Browse files
committed
[needless_return] Recursively remove unneeded semicolons
1 parent 23d1d07 commit 5c0cb0d

File tree

4 files changed

+244
-64
lines changed

4 files changed

+244
-64
lines changed

clippy_lints/src/returns.rs

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use clippy_utils::diagnostics::{span_lint_hir_and_then, span_lint_and_then};
1+
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
22
use clippy_utils::source::{snippet_opt, snippet_with_context};
33
use clippy_utils::{fn_def_id, path_to_local_id};
44
use if_chain::if_chain;
@@ -9,7 +9,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
99
use rustc_middle::lint::in_external_macro;
1010
use rustc_middle::ty::subst::GenericArgKind;
1111
use rustc_session::{declare_lint_pass, declare_tool_lint};
12-
use rustc_span::source_map::Span;
12+
use rustc_span::source_map::{Span, DUMMY_SP};
1313

1414
declare_clippy_lint! {
1515
/// ### What it does
@@ -73,8 +73,8 @@ enum RetReplacement {
7373
}
7474

7575
impl RetReplacement {
76-
fn sugg_help(&self) -> &'static str {
77-
match *self {
76+
fn sugg_help(self) -> &'static str {
77+
match self {
7878
Self::Empty => "remove `return`",
7979
Self::Block => "replace `return` with an empty block",
8080
Self::Unit => "replace `return` with a unit value",
@@ -160,51 +160,65 @@ impl<'tcx> LateLintPass<'tcx> for Return {
160160
} else {
161161
RetReplacement::Empty
162162
};
163-
check_final_expr(cx, body.value, body.value.span, replacement);
163+
check_final_expr(cx, body.value, vec![], replacement);
164164
},
165165
FnKind::ItemFn(..) | FnKind::Method(..) => {
166-
check_block_return(cx, &body.value.kind);
166+
check_block_return(cx, &body.value.kind, vec![]);
167167
},
168168
}
169169
}
170170
}
171171

172172
// 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>) {
173+
fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, semi_spans: Vec<Span>) {
174174
if let ExprKind::Block(block, _) = expr_kind {
175175
if let Some(block_expr) = block.expr {
176-
check_final_expr(cx, block_expr, block_expr.span, RetReplacement::Empty);
176+
check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty);
177177
} else if let Some(stmt) = block.stmts.iter().last() {
178178
match stmt.kind {
179-
StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
180-
check_final_expr(cx, expr, stmt.span, RetReplacement::Empty);
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+
semi_spans_and_this_one.push(stmt.span.trim_start(semi_expr.span).unwrap_or(DUMMY_SP));
186+
check_final_expr(cx, semi_expr, semi_spans_and_this_one, RetReplacement::Empty);
181187
},
182188
_ => (),
183189
}
184190
}
185191
}
186192
}
187193

188-
fn check_final_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, span: Span, replacement: RetReplacement) {
189-
match &expr.peel_drop_temps().kind {
194+
fn check_final_expr<'tcx>(
195+
cx: &LateContext<'tcx>,
196+
expr: &'tcx Expr<'tcx>,
197+
semi_spans: Vec<Span>, /* containing all the places where we would need to remove semicolons if finding an
198+
* needless return */
199+
replacement: RetReplacement,
200+
) {
201+
let peeled_drop_expr = expr.peel_drop_temps();
202+
match &peeled_drop_expr.kind {
190203
// simple return is always "bad"
191204
ExprKind::Ret(ref inner) => {
192205
if cx.tcx.hir().attrs(expr.hir_id).is_empty() {
193206
let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner));
194207
if !borrows {
195208
emit_return_lint(
196209
cx,
197-
span,
210+
peeled_drop_expr.span,
211+
semi_spans,
198212
inner.as_ref().map(|i| i.span),
199213
replacement,
200214
);
201215
}
202216
}
203217
},
204218
ExprKind::If(_, then, else_clause_opt) => {
205-
check_block_return(cx, &then.kind);
219+
check_block_return(cx, &then.kind, semi_spans.clone());
206220
if let Some(else_clause) = else_clause_opt {
207-
check_block_return(cx, &else_clause.kind)
221+
check_block_return(cx, &else_clause.kind, semi_spans);
208222
}
209223
},
210224
// a match expr, check all arms
@@ -213,17 +227,18 @@ fn check_final_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, span:
213227
// (except for unit type functions) so we don't match it
214228
ExprKind::Match(_, arms, MatchSource::Normal) => {
215229
for arm in arms.iter() {
216-
check_final_expr(cx, arm.body, arm.body.span, RetReplacement::Unit);
230+
check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit);
217231
}
218232
},
219233
// if it's a whole block, check it
220-
other_expr_kind => check_block_return(cx, &other_expr_kind),
234+
other_expr_kind => check_block_return(cx, other_expr_kind, semi_spans),
221235
}
222236
}
223237

224238
fn emit_return_lint(
225239
cx: &LateContext<'_>,
226240
ret_span: Span,
241+
semi_spans: Vec<Span>,
227242
inner_span: Option<Span>,
228243
replacement: RetReplacement,
229244
) {
@@ -243,15 +258,13 @@ fn emit_return_lint(
243258
} else {
244259
replacement.sugg_help()
245260
};
246-
span_lint_and_then(
247-
cx,
248-
NEEDLESS_RETURN,
249-
ret_span,
250-
"unneeded `return` statement",
251-
|diag| {
252-
diag.span_suggestion(ret_span, sugg_help, return_replacement, applicability);
253-
},
254-
);
261+
span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
262+
diag.span_suggestion_hidden(ret_span, sugg_help, return_replacement, applicability);
263+
// for each parent statement, we need to remove the semicolon
264+
for semi_stmt_span in semi_spans {
265+
diag.tool_only_span_suggestion(semi_stmt_span, "remove this semicolon", "", applicability);
266+
}
267+
});
255268
}
256269

257270
fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {

tests/ui/needless_return.fixed

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,31 @@ fn issue_9361() -> i32 {
233233
return 1 + 2;
234234
}
235235

236+
fn issue8336(x: i32) -> bool {
237+
if x > 0 {
238+
println!("something");
239+
true
240+
} else {
241+
false
242+
}
243+
}
244+
245+
fn issue8156(x: u8) -> u64 {
246+
match x {
247+
80 => {
248+
10
249+
},
250+
_ => {
251+
100
252+
},
253+
}
254+
}
255+
256+
// Ideally the compiler should throw `unused_braces` in this case
257+
fn issue9192() -> i32 {
258+
{
259+
0
260+
}
261+
}
262+
236263
fn main() {}

tests/ui/needless_return.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,31 @@ fn issue_9361() -> i32 {
233233
return 1 + 2;
234234
}
235235

236+
fn issue8336(x: i32) -> bool {
237+
if x > 0 {
238+
println!("something");
239+
return true;
240+
} else {
241+
return false;
242+
};
243+
}
244+
245+
fn issue8156(x: u8) -> u64 {
246+
match x {
247+
80 => {
248+
return 10;
249+
},
250+
_ => {
251+
return 100;
252+
},
253+
};
254+
}
255+
256+
// Ideally the compiler should throw `unused_braces` in this case
257+
fn issue9192() -> i32 {
258+
{
259+
return 0;
260+
};
261+
}
262+
236263
fn main() {}

0 commit comments

Comments
 (0)