Skip to content

Commit a35500e

Browse files
committed
extract needless_return
1 parent 1a45361 commit a35500e

File tree

2 files changed

+270
-252
lines changed

2 files changed

+270
-252
lines changed

clippy_lints/src/returns/mod.rs

Lines changed: 10 additions & 252 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,27 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
2-
use clippy_utils::source::{SpanRangeExt, snippet_with_context};
2+
use clippy_utils::source::SpanRangeExt;
33
use clippy_utils::sugg::has_enclosing_paren;
44
use clippy_utils::visitors::for_each_expr;
55
use clippy_utils::{
6-
binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor,
7-
leaks_droppable_temporary_with_limited_lifetime, path_res, path_to_local_id, span_contains_cfg,
8-
span_find_starting_semi, sym,
6+
binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res,
7+
path_to_local_id, span_contains_cfg,
98
};
109
use core::ops::ControlFlow;
11-
use rustc_ast::MetaItemInner;
1210
use rustc_errors::Applicability;
1311
use rustc_hir::LangItem::ResultErr;
1412
use rustc_hir::intravisit::FnKind;
1513
use rustc_hir::{
16-
Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt,
17-
StmtKind,
14+
Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, MatchSource, Node, OwnerNode, PatKind, Stmt, StmtKind,
1815
};
19-
use rustc_lint::{LateContext, LateLintPass, Level, LintContext};
16+
use rustc_lint::{LateContext, LateLintPass, LintContext};
17+
use rustc_middle::ty::GenericArgKind;
2018
use rustc_middle::ty::adjustment::Adjust;
21-
use rustc_middle::ty::{self, GenericArgKind, Ty};
2219
use rustc_session::declare_lint_pass;
20+
use rustc_span::Span;
2321
use rustc_span::def_id::LocalDefId;
2422
use rustc_span::edition::Edition;
25-
use rustc_span::{BytePos, Pos, Span};
26-
use std::borrow::Cow;
27-
use std::fmt::Display;
23+
24+
mod needless_return;
2825

2926
declare_clippy_lint! {
3027
/// ### What it does
@@ -132,45 +129,6 @@ declare_clippy_lint! {
132129
"using a return statement like `return Err(expr)?;` where removing it would suffice"
133130
}
134131

135-
#[derive(PartialEq, Eq)]
136-
enum RetReplacement<'tcx> {
137-
Empty,
138-
Block,
139-
Unit,
140-
NeedsPar(Cow<'tcx, str>, Applicability),
141-
Expr(Cow<'tcx, str>, Applicability),
142-
}
143-
144-
impl RetReplacement<'_> {
145-
fn sugg_help(&self) -> &'static str {
146-
match self {
147-
Self::Empty | Self::Expr(..) => "remove `return`",
148-
Self::Block => "replace `return` with an empty block",
149-
Self::Unit => "replace `return` with a unit value",
150-
Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses",
151-
}
152-
}
153-
154-
fn applicability(&self) -> Applicability {
155-
match self {
156-
Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap,
157-
_ => Applicability::MachineApplicable,
158-
}
159-
}
160-
}
161-
162-
impl Display for RetReplacement<'_> {
163-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164-
match self {
165-
Self::Empty => f.write_str(""),
166-
Self::Block => f.write_str("{}"),
167-
Self::Unit => f.write_str("()"),
168-
Self::NeedsPar(inner, _) => write!(f, "({inner})"),
169-
Self::Expr(inner, _) => write!(f, "{inner}"),
170-
}
171-
}
172-
}
173-
174132
declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN, NEEDLESS_RETURN_WITH_QUESTION_MARK]);
175133

176134
/// Checks if a return statement is "needed" in the middle of a block, or if it can be removed. This
@@ -288,197 +246,10 @@ impl<'tcx> LateLintPass<'tcx> for Return {
288246
sp: Span,
289247
_: LocalDefId,
290248
) {
291-
if sp.from_expansion() {
292-
return;
293-
}
294-
295-
match kind {
296-
FnKind::Closure => {
297-
// when returning without value in closure, replace this `return`
298-
// with an empty block to prevent invalid suggestion (see #6501)
299-
let replacement = if let ExprKind::Ret(None) = &body.value.kind {
300-
RetReplacement::Block
301-
} else {
302-
RetReplacement::Empty
303-
};
304-
check_final_expr(cx, body.value, vec![], replacement, None);
305-
},
306-
FnKind::ItemFn(..) | FnKind::Method(..) => {
307-
check_block_return(cx, &body.value.kind, sp, vec![]);
308-
},
309-
}
310-
}
311-
}
312-
313-
// if `expr` is a block, check if there are needless returns in it
314-
fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec<Span>) {
315-
if let ExprKind::Block(block, _) = expr_kind {
316-
if let Some(block_expr) = block.expr {
317-
check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty, None);
318-
} else if let Some(stmt) = block.stmts.last() {
319-
match stmt.kind {
320-
StmtKind::Expr(expr) => {
321-
check_final_expr(cx, expr, semi_spans, RetReplacement::Empty, None);
322-
},
323-
StmtKind::Semi(semi_expr) => {
324-
// Remove ending semicolons and any whitespace ' ' in between.
325-
// Without `return`, the suggestion might not compile if the semicolon is retained
326-
if let Some(semi_span) = stmt.span.trim_start(semi_expr.span) {
327-
let semi_span_to_remove =
328-
span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi()));
329-
semi_spans.push(semi_span_to_remove);
330-
}
331-
check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty, None);
332-
},
333-
_ => (),
334-
}
335-
}
249+
needless_return::check_fn(cx, kind, body, sp);
336250
}
337251
}
338252

339-
fn check_final_expr<'tcx>(
340-
cx: &LateContext<'tcx>,
341-
expr: &'tcx Expr<'tcx>,
342-
semi_spans: Vec<Span>, /* containing all the places where we would need to remove semicolons if finding an
343-
* needless return */
344-
replacement: RetReplacement<'tcx>,
345-
match_ty_opt: Option<Ty<'_>>,
346-
) {
347-
let peeled_drop_expr = expr.peel_drop_temps();
348-
match &peeled_drop_expr.kind {
349-
// simple return is always "bad"
350-
ExprKind::Ret(inner) => {
351-
// check if expr return nothing
352-
let ret_span = if inner.is_none() && replacement == RetReplacement::Empty {
353-
extend_span_to_previous_non_ws(cx, peeled_drop_expr.span)
354-
} else {
355-
peeled_drop_expr.span
356-
};
357-
358-
let replacement = if let Some(inner_expr) = inner {
359-
// if desugar of `do yeet`, don't lint
360-
if let ExprKind::Call(path_expr, [_]) = inner_expr.kind
361-
&& let ExprKind::Path(QPath::LangItem(LangItem::TryTraitFromYeet, ..)) = path_expr.kind
362-
{
363-
return;
364-
}
365-
366-
let mut applicability = Applicability::MachineApplicable;
367-
let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability);
368-
if binary_expr_needs_parentheses(inner_expr) {
369-
RetReplacement::NeedsPar(snippet, applicability)
370-
} else {
371-
RetReplacement::Expr(snippet, applicability)
372-
}
373-
} else {
374-
match match_ty_opt {
375-
Some(match_ty) => {
376-
match match_ty.kind() {
377-
// If the code got till here with
378-
// tuple not getting detected before it,
379-
// then we are sure it's going to be Unit
380-
// type
381-
ty::Tuple(_) => RetReplacement::Unit,
382-
// We don't want to anything in this case
383-
// cause we can't predict what the user would
384-
// want here
385-
_ => return,
386-
}
387-
},
388-
None => replacement,
389-
}
390-
};
391-
392-
if inner.is_some_and(|inner| leaks_droppable_temporary_with_limited_lifetime(cx, inner)) {
393-
return;
394-
}
395-
396-
if ret_span.from_expansion() || is_from_proc_macro(cx, expr) {
397-
return;
398-
}
399-
400-
// Returns may be used to turn an expression into a statement in rustc's AST.
401-
// This allows the addition of attributes, like `#[allow]` (See: clippy#9361)
402-
// `#[expect(clippy::needless_return)]` needs to be handled separately to
403-
// actually fulfill the expectation (clippy::#12998)
404-
match cx.tcx.hir_attrs(expr.hir_id) {
405-
[] => {},
406-
[attr] => {
407-
if matches!(Level::from_attr(attr), Some((Level::Expect, _)))
408-
&& let metas = attr.meta_item_list()
409-
&& let Some(lst) = metas
410-
&& let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice()
411-
&& let [tool, lint_name] = meta_item.path.segments.as_slice()
412-
&& tool.ident.name == sym::clippy
413-
&& matches!(
414-
lint_name.ident.name,
415-
sym::needless_return | sym::style | sym::all | sym::warnings
416-
)
417-
{
418-
// This is an expectation of the `needless_return` lint
419-
} else {
420-
return;
421-
}
422-
},
423-
_ => return,
424-
}
425-
426-
emit_return_lint(
427-
cx,
428-
peeled_drop_expr.span,
429-
ret_span,
430-
semi_spans,
431-
&replacement,
432-
expr.hir_id,
433-
);
434-
},
435-
ExprKind::If(_, then, else_clause_opt) => {
436-
check_block_return(cx, &then.kind, peeled_drop_expr.span, semi_spans.clone());
437-
if let Some(else_clause) = else_clause_opt {
438-
// The `RetReplacement` won't be used there as `else_clause` will be either a block or
439-
// a `if` expression.
440-
check_final_expr(cx, else_clause, semi_spans, RetReplacement::Empty, match_ty_opt);
441-
}
442-
},
443-
// a match expr, check all arms
444-
// an if/if let expr, check both exprs
445-
// note, if without else is going to be a type checking error anyways
446-
// (except for unit type functions) so we don't match it
447-
ExprKind::Match(_, arms, MatchSource::Normal) => {
448-
let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr);
449-
for arm in *arms {
450-
check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit, Some(match_ty));
451-
}
452-
},
453-
// if it's a whole block, check it
454-
other_expr_kind => check_block_return(cx, other_expr_kind, peeled_drop_expr.span, semi_spans),
455-
}
456-
}
457-
458-
fn emit_return_lint(
459-
cx: &LateContext<'_>,
460-
lint_span: Span,
461-
ret_span: Span,
462-
semi_spans: Vec<Span>,
463-
replacement: &RetReplacement<'_>,
464-
at: HirId,
465-
) {
466-
span_lint_hir_and_then(
467-
cx,
468-
NEEDLESS_RETURN,
469-
at,
470-
lint_span,
471-
"unneeded `return` statement",
472-
|diag| {
473-
let suggestions = std::iter::once((ret_span, replacement.to_string()))
474-
.chain(semi_spans.into_iter().map(|span| (span, String::new())))
475-
.collect();
476-
477-
diag.multipart_suggestion_verbose(replacement.sugg_help(), suggestions, replacement.applicability());
478-
},
479-
);
480-
}
481-
482253
fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
483254
for_each_expr(cx, expr, |e| {
484255
if let Some(def_id) = fn_def_id(cx, e)
@@ -498,16 +269,3 @@ fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>)
498269
})
499270
.is_some()
500271
}
501-
502-
// Go backwards while encountering whitespace and extend the given Span to that point.
503-
fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span {
504-
if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) {
505-
let ws = [b' ', b'\t', b'\n'];
506-
if let Some(non_ws_pos) = prev_source.bytes().rposition(|c| !ws.contains(&c)) {
507-
let len = prev_source.len() - non_ws_pos - 1;
508-
return sp.with_lo(sp.lo() - BytePos::from_usize(len));
509-
}
510-
}
511-
512-
sp
513-
}

0 commit comments

Comments
 (0)