From 6d18fe730e88903fa881f7b753369623108a5d55 Mon Sep 17 00:00:00 2001 From: jrqc Date: Wed, 12 Aug 2020 15:43:44 +0300 Subject: [PATCH 1/7] Make needless_return a late lint pass --- clippy_lints/src/lib.rs | 8 +- clippy_lints/src/needless_return.rs | 166 ++++++++++++++++++++++++++++ clippy_lints/src/returns.rs | 141 +---------------------- src/lintlist/mod.rs | 2 +- 4 files changed, 175 insertions(+), 142 deletions(-) create mode 100644 clippy_lints/src/needless_return.rs diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 6ad525d76..d2f66cf9b 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -256,6 +256,7 @@ mod needless_borrow; mod needless_borrowed_ref; mod needless_continue; mod needless_pass_by_value; +mod needless_return; mod needless_update; mod neg_cmp_op_on_partial_ord; mod neg_multiply; @@ -726,6 +727,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE, &needless_continue::NEEDLESS_CONTINUE, &needless_pass_by_value::NEEDLESS_PASS_BY_VALUE, + &needless_return::NEEDLESS_RETURN, &needless_update::NEEDLESS_UPDATE, &neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD, &neg_multiply::NEG_MULTIPLY, @@ -769,7 +771,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ®ex::INVALID_REGEX, ®ex::TRIVIAL_REGEX, &repeat_once::REPEAT_ONCE, - &returns::NEEDLESS_RETURN, &returns::UNUSED_UNIT, &serde_api::SERDE_API_MISUSE, &shadow::SHADOW_REUSE, @@ -1027,6 +1028,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box redundant_closure_call::RedundantClosureCall); store.register_early_pass(|| box returns::Return); store.register_late_pass(|| box let_and_return::LetReturn); + store.register_late_pass(|| box needless_return::NeedlessReturn); store.register_early_pass(|| box collapsible_if::CollapsibleIf); store.register_early_pass(|| box items_after_statements::ItemsAfterStatements); store.register_early_pass(|| box precedence::Precedence); @@ -1381,6 +1383,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&needless_bool::BOOL_COMPARISON), LintId::of(&needless_bool::NEEDLESS_BOOL), LintId::of(&needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), + LintId::of(&needless_return::NEEDLESS_RETURN), LintId::of(&needless_update::NEEDLESS_UPDATE), LintId::of(&neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), LintId::of(&neg_multiply::NEG_MULTIPLY), @@ -1413,7 +1416,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(®ex::INVALID_REGEX), LintId::of(®ex::TRIVIAL_REGEX), LintId::of(&repeat_once::REPEAT_ONCE), - LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&returns::UNUSED_UNIT), LintId::of(&serde_api::SERDE_API_MISUSE), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), @@ -1543,6 +1545,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&misc_early::MIXED_CASE_HEX_LITERALS), LintId::of(&misc_early::REDUNDANT_PATTERN), LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED), + LintId::of(&needless_return::NEEDLESS_RETURN), LintId::of(&neg_multiply::NEG_MULTIPLY), LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT), LintId::of(&non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), @@ -1554,7 +1557,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES), LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), LintId::of(®ex::TRIVIAL_REGEX), - LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&returns::UNUSED_UNIT), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(&strings::STRING_LIT_AS_BYTES), diff --git a/clippy_lints/src/needless_return.rs b/clippy_lints/src/needless_return.rs new file mode 100644 index 000000000..a8876619a --- /dev/null +++ b/clippy_lints/src/needless_return.rs @@ -0,0 +1,166 @@ +use rustc_lint::{LateLintPass, LateContext}; +use rustc_ast::ast::Attribute; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_span::source_map::Span; +use rustc_middle::lint::in_external_macro; +use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, StmtKind}; + +use crate::utils::{snippet_opt, span_lint_and_sugg, span_lint_and_then}; + +declare_clippy_lint! { + /// **What it does:** Checks for return statements at the end of a block. + /// + /// **Why is this bad?** Removing the `return` and semicolon will make the code + /// more rusty. + /// + /// **Known problems:** If the computation returning the value borrows a local + /// variable, removing the `return` may run afoul of the borrow checker. + /// + /// **Example:** + /// ```rust + /// fn foo(x: usize) -> usize { + /// return x; + /// } + /// ``` + /// simplify to + /// ```rust + /// fn foo(x: usize) -> usize { + /// x + /// } + /// ``` + pub NEEDLESS_RETURN, + style, + "using a return statement like `return expr;` where an expression would suffice" +} + +#[derive(PartialEq, Eq, Copy, Clone)] +enum RetReplacement { + Empty, + Block, +} + +declare_lint_pass!(NeedlessReturn => [NEEDLESS_RETURN]); + +impl<'tcx> LateLintPass<'tcx> for NeedlessReturn { + fn check_fn(&mut self, cx: &LateContext<'tcx>, kind: FnKind<'tcx>, _: &'tcx FnDecl<'tcx>, body: &'tcx Body<'tcx>, _: Span, _: HirId) { + match kind { + FnKind::Closure(_) => { + check_final_expr(cx, &body.value, Some(body.value.span), RetReplacement::Empty) + } + FnKind::ItemFn(..) | FnKind::Method(..) => { + if let ExprKind::Block(ref block, _) = body.value.kind { + check_block_return(cx, block) + } + } + } + } +} + +fn attr_is_cfg(attr: &Attribute) -> bool { + attr.meta_item_list().is_some() && attr.has_name(sym!(cfg)) +} + +fn check_block_return(cx: &LateContext<'_>, block: &Block<'_>) { + if let Some(expr) = block.expr { + check_final_expr(cx, expr, Some(expr.span), RetReplacement::Empty); + } else if let Some(stmt) = block.stmts.iter().last() { + match stmt.kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { + check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty); + } + _ => (), + } + } +} + + +fn check_final_expr( + cx: &LateContext<'_>, + expr: &Expr<'_>, + span: Option, + replacement: RetReplacement, +) { + match expr.kind { + // simple return is always "bad" + ExprKind::Ret(ref inner) => { + + // allow `#[cfg(a)] return a; #[cfg(b)] return b;` + if !expr.attrs.iter().any(attr_is_cfg) { + emit_return_lint( + cx, + span.expect("`else return` is not possible"), + inner.as_ref().map(|i| i.span), + replacement, + ); + } + } + // a whole block? check it! + ExprKind::Block(ref block, _) => { + check_block_return(cx, block); + } + // a match expr, check all arms + // an if/if let expr, check both exprs + // note, if without else is going to be a type checking error anyways + // (except for unit type functions) so we don't match it + + ExprKind::Match(_, ref arms, source) => { + match source { + MatchSource::Normal => { + for arm in arms.iter() { + check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block); + } + } + MatchSource::IfDesugar { contains_else_clause: true } | MatchSource::IfLetDesugar { contains_else_clause: true } => { + if let ExprKind::Block(ref ifblock, _) = arms[0].body.kind { + check_block_return(cx, ifblock); + } + check_final_expr(cx, arms[1].body, None, RetReplacement::Empty); + } + _ => () + } + } + _ => (), + } +} + +fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option, replacement: RetReplacement) { + match inner_span { + Some(inner_span) => { + if in_external_macro(cx.tcx.sess, inner_span) || inner_span.from_expansion() { + return; + } + + span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| { + if let Some(snippet) = snippet_opt(cx, inner_span) { + diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable); + } + }) + } + None => match replacement { + RetReplacement::Empty => { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN, + ret_span, + "unneeded `return` statement", + "remove `return`", + String::new(), + Applicability::MachineApplicable, + ); + } + RetReplacement::Block => { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN, + ret_span, + "unneeded `return` statement", + "replace `return` with an empty block", + "{}".to_string(), + Applicability::MachineApplicable, + ); + } + }, + } +} diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 8ed20995a..2bd0cccd3 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -3,38 +3,11 @@ use rustc_ast::ast; use rustc_ast::visit::FnKind; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; -use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::BytePos; -use crate::utils::{snippet_opt, span_lint_and_sugg, span_lint_and_then}; - -declare_clippy_lint! { - /// **What it does:** Checks for return statements at the end of a block. - /// - /// **Why is this bad?** Removing the `return` and semicolon will make the code - /// more rusty. - /// - /// **Known problems:** If the computation returning the value borrows a local - /// variable, removing the `return` may run afoul of the borrow checker. - /// - /// **Example:** - /// ```rust - /// fn foo(x: usize) -> usize { - /// return x; - /// } - /// ``` - /// simplify to - /// ```rust - /// fn foo(x: usize) -> usize { - /// x - /// } - /// ``` - pub NEEDLESS_RETURN, - style, - "using a return statement like `return expr;` where an expression would suffice" -} +use crate::utils::{span_lint_and_sugg}; declare_clippy_lint! { /// **What it does:** Checks for unit (`()`) expressions that can be removed. @@ -57,117 +30,12 @@ declare_clippy_lint! { "needless unit expression" } -#[derive(PartialEq, Eq, Copy, Clone)] -enum RetReplacement { - Empty, - Block, -} -declare_lint_pass!(Return => [NEEDLESS_RETURN, UNUSED_UNIT]); - -impl Return { - // Check the final stmt or expr in a block for unnecessary return. - fn check_block_return(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) { - if let Some(stmt) = block.stmts.last() { - match stmt.kind { - ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => { - self.check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty); - }, - _ => (), - } - } - } - - // Check the final expression in a block if it's a return. - fn check_final_expr( - &mut self, - cx: &EarlyContext<'_>, - expr: &ast::Expr, - span: Option, - replacement: RetReplacement, - ) { - match expr.kind { - // simple return is always "bad" - ast::ExprKind::Ret(ref inner) => { - // allow `#[cfg(a)] return a; #[cfg(b)] return b;` - if !expr.attrs.iter().any(attr_is_cfg) { - Self::emit_return_lint( - cx, - span.expect("`else return` is not possible"), - inner.as_ref().map(|i| i.span), - replacement, - ); - } - }, - // a whole block? check it! - ast::ExprKind::Block(ref block, _) => { - self.check_block_return(cx, block); - }, - // an if/if let expr, check both exprs - // note, if without else is going to be a type checking error anyways - // (except for unit type functions) so we don't match it - ast::ExprKind::If(_, ref ifblock, Some(ref elsexpr)) => { - self.check_block_return(cx, ifblock); - self.check_final_expr(cx, elsexpr, None, RetReplacement::Empty); - }, - // a match expr, check all arms - ast::ExprKind::Match(_, ref arms) => { - for arm in arms { - self.check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block); - } - }, - _ => (), - } - } - - fn emit_return_lint(cx: &EarlyContext<'_>, ret_span: Span, inner_span: Option, replacement: RetReplacement) { - match inner_span { - Some(inner_span) => { - if in_external_macro(cx.sess(), inner_span) || inner_span.from_expansion() { - return; - } - - span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| { - if let Some(snippet) = snippet_opt(cx, inner_span) { - diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable); - } - }) - }, - None => match replacement { - RetReplacement::Empty => { - span_lint_and_sugg( - cx, - NEEDLESS_RETURN, - ret_span, - "unneeded `return` statement", - "remove `return`", - String::new(), - Applicability::MachineApplicable, - ); - }, - RetReplacement::Block => { - span_lint_and_sugg( - cx, - NEEDLESS_RETURN, - ret_span, - "unneeded `return` statement", - "replace `return` with an empty block", - "{}".to_string(), - Applicability::MachineApplicable, - ); - }, - }, - } - } -} +declare_lint_pass!(Return => [UNUSED_UNIT]); impl EarlyLintPass for Return { fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) { - match kind { - FnKind::Fn(.., Some(block)) => self.check_block_return(cx, block), - FnKind::Closure(_, body) => self.check_final_expr(cx, body, Some(body.span), RetReplacement::Empty), - FnKind::Fn(.., None) => {}, - } + if_chain! { if let ast::FnRetTy::Ty(ref ty) = kind.decl().output; if let ast::TyKind::Tup(ref vals) = ty.kind; @@ -234,9 +102,6 @@ impl EarlyLintPass for Return { } } -fn attr_is_cfg(attr: &ast::Attribute) -> bool { - attr.meta_item_list().is_some() && attr.has_name(sym!(cfg)) -} // get the def site #[must_use] diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index ccc9e2509..7464f1cc6 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1534,7 +1534,7 @@ pub static ref ALL_LINTS: Vec = vec![ group: "style", desc: "using a return statement like `return expr;` where an expression would suffice", deprecation: None, - module: "returns", + module: "needless_return", }, Lint { name: "needless_update", From 85f4ef0fbd92453cf480af4e3f9eed877071ea2e Mon Sep 17 00:00:00 2001 From: jrqc Date: Wed, 12 Aug 2020 17:14:12 +0300 Subject: [PATCH 2/7] Visitor added --- clippy_lints/src/needless_return.rs | 45 +++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/needless_return.rs b/clippy_lints/src/needless_return.rs index a8876619a..861a7ec55 100644 --- a/clippy_lints/src/needless_return.rs +++ b/clippy_lints/src/needless_return.rs @@ -2,12 +2,14 @@ use rustc_lint::{LateLintPass, LateContext}; use rustc_ast::ast::Attribute; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_errors::Applicability; -use rustc_hir::intravisit::FnKind; +use rustc_hir::intravisit::{FnKind, walk_expr, NestedVisitorMap, Visitor}; use rustc_span::source_map::Span; use rustc_middle::lint::in_external_macro; +use rustc_middle::hir::map::Map; +use rustc_middle::ty::subst::GenericArgKind; use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, StmtKind}; -use crate::utils::{snippet_opt, span_lint_and_sugg, span_lint_and_then}; +use crate::utils::{fn_def_id, snippet_opt, span_lint_and_sugg, span_lint_and_then}; declare_clippy_lint! { /// **What it does:** Checks for return statements at the end of a block. @@ -164,3 +166,42 @@ fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + let mut visitor = BorrowVisitor { cx, borrows: false }; + walk_expr(&mut visitor, expr); + visitor.borrows +} + +struct BorrowVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + borrows: bool, +} + +impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.borrows { + return; + } + + if let Some(def_id) = fn_def_id(self.cx, expr) { + self.borrows = self + .cx + .tcx + .fn_sig(def_id) + .output() + .skip_binder() + .walk() + .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_))); + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + From 65d10c7abf4752923f040264c79433da8fc234ea Mon Sep 17 00:00:00 2001 From: jrqc Date: Thu, 13 Aug 2020 15:14:08 +0300 Subject: [PATCH 3/7] Borrow checker added --- clippy_lints/src/needless_return.rs | 101 ++++++++++++++++------------ clippy_lints/src/returns.rs | 5 +- tests/ui/needless_return.fixed | 10 +++ tests/ui/needless_return.rs | 10 +++ 4 files changed, 80 insertions(+), 46 deletions(-) diff --git a/clippy_lints/src/needless_return.rs b/clippy_lints/src/needless_return.rs index 861a7ec55..ba280cd5a 100644 --- a/clippy_lints/src/needless_return.rs +++ b/clippy_lints/src/needless_return.rs @@ -1,13 +1,13 @@ -use rustc_lint::{LateLintPass, LateContext}; use rustc_ast::ast::Attribute; -use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_errors::Applicability; -use rustc_hir::intravisit::{FnKind, walk_expr, NestedVisitorMap, Visitor}; -use rustc_span::source_map::Span; -use rustc_middle::lint::in_external_macro; -use rustc_middle::hir::map::Map; -use rustc_middle::ty::subst::GenericArgKind; +use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor}; use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::subst::GenericArgKind; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; use crate::utils::{fn_def_id, snippet_opt, span_lint_and_sugg, span_lint_and_then}; @@ -46,16 +46,39 @@ enum RetReplacement { declare_lint_pass!(NeedlessReturn => [NEEDLESS_RETURN]); impl<'tcx> LateLintPass<'tcx> for NeedlessReturn { - fn check_fn(&mut self, cx: &LateContext<'tcx>, kind: FnKind<'tcx>, _: &'tcx FnDecl<'tcx>, body: &'tcx Body<'tcx>, _: Span, _: HirId) { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + _: &'tcx FnDecl<'tcx>, + body: &'tcx Body<'tcx>, + _: Span, + _: HirId, + ) { match kind { FnKind::Closure(_) => { - check_final_expr(cx, &body.value, Some(body.value.span), RetReplacement::Empty) - } + if !last_statement_borrows(cx, &body.value) { + check_final_expr(cx, &body.value, Some(body.value.span), RetReplacement::Empty) + } + }, FnKind::ItemFn(..) | FnKind::Method(..) => { if let ExprKind::Block(ref block, _) = body.value.kind { - check_block_return(cx, block) + if let Some(expr) = block.expr { + if !last_statement_borrows(cx, expr) { + check_final_expr(cx, expr, Some(expr.span), RetReplacement::Empty); + } + } else if let Some(stmt) = block.stmts.iter().last() { + match stmt.kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { + if !last_statement_borrows(cx, expr) { + check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty); + } + }, + _ => (), + } + } } - } + }, } } } @@ -71,23 +94,16 @@ fn check_block_return(cx: &LateContext<'_>, block: &Block<'_>) { match stmt.kind { StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty); - } + }, _ => (), } } } - -fn check_final_expr( - cx: &LateContext<'_>, - expr: &Expr<'_>, - span: Option, - replacement: RetReplacement, -) { +fn check_final_expr(cx: &LateContext<'_>, expr: &Expr<'_>, span: Option, replacement: RetReplacement) { match expr.kind { // simple return is always "bad" ExprKind::Ret(ref inner) => { - // allow `#[cfg(a)] return a; #[cfg(b)] return b;` if !expr.attrs.iter().any(attr_is_cfg) { emit_return_lint( @@ -97,32 +113,34 @@ fn check_final_expr( replacement, ); } - } + }, // a whole block? check it! ExprKind::Block(ref block, _) => { check_block_return(cx, block); - } + }, // a match expr, check all arms // an if/if let expr, check both exprs // note, if without else is going to be a type checking error anyways // (except for unit type functions) so we don't match it - - ExprKind::Match(_, ref arms, source) => { - match source { - MatchSource::Normal => { - for arm in arms.iter() { - check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block); - } + ExprKind::Match(_, ref arms, source) => match source { + MatchSource::Normal => { + for arm in arms.iter() { + check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block); } - MatchSource::IfDesugar { contains_else_clause: true } | MatchSource::IfLetDesugar { contains_else_clause: true } => { - if let ExprKind::Block(ref ifblock, _) = arms[0].body.kind { - check_block_return(cx, ifblock); - } - check_final_expr(cx, arms[1].body, None, RetReplacement::Empty); - } - _ => () + }, + MatchSource::IfDesugar { + contains_else_clause: true, } - } + | MatchSource::IfLetDesugar { + contains_else_clause: true, + } => { + if let ExprKind::Block(ref ifblock, _) = arms[0].body.kind { + check_block_return(cx, ifblock); + } + check_final_expr(cx, arms[1].body, None, RetReplacement::Empty); + }, + _ => (), + }, _ => (), } } @@ -139,7 +157,7 @@ fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option match replacement { RetReplacement::Empty => { span_lint_and_sugg( @@ -151,7 +169,7 @@ fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option { span_lint_and_sugg( cx, @@ -162,7 +180,7 @@ fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option Visitor<'tcx> for BorrowVisitor<'_, 'tcx> { NestedVisitorMap::None } } - diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 2bd0cccd3..593e2f6c7 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -7,7 +7,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::BytePos; -use crate::utils::{span_lint_and_sugg}; +use crate::utils::span_lint_and_sugg; declare_clippy_lint! { /// **What it does:** Checks for unit (`()`) expressions that can be removed. @@ -30,12 +30,10 @@ declare_clippy_lint! { "needless unit expression" } - declare_lint_pass!(Return => [UNUSED_UNIT]); impl EarlyLintPass for Return { fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) { - if_chain! { if let ast::FnRetTy::Ty(ref ty) = kind.decl().output; if let ast::TyKind::Tup(ref vals) = ty.kind; @@ -102,7 +100,6 @@ impl EarlyLintPass for Return { } } - // get the def site #[must_use] fn get_def(span: Span) -> Option { diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index ad20e2381..6b5cf2626 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -69,6 +69,16 @@ fn test_void_match(x: u32) { } } +mod no_lint_if_stmt_borrows { + mod issue_5858 { + fn read_line() -> String { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + return stdin.lock().lines().next().unwrap().unwrap(); + } + } +} + fn main() { let _ = test_end_of_fn(); let _ = test_no_semicolon(); diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index af0cdfb20..1a693c9aa 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -69,6 +69,16 @@ fn test_void_match(x: u32) { } } +mod no_lint_if_stmt_borrows { + mod issue_5858 { + fn read_line() -> String { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + return stdin.lock().lines().next().unwrap().unwrap(); + } + } +} + fn main() { let _ = test_end_of_fn(); let _ = test_no_semicolon(); From cd6ca72e0e4040a5c3a6623c2e8533db91db6042 Mon Sep 17 00:00:00 2001 From: jrqc Date: Thu, 13 Aug 2020 15:21:09 +0300 Subject: [PATCH 4/7] Known problems changed --- clippy_lints/src/needless_return.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clippy_lints/src/needless_return.rs b/clippy_lints/src/needless_return.rs index ba280cd5a..eb0bf12c0 100644 --- a/clippy_lints/src/needless_return.rs +++ b/clippy_lints/src/needless_return.rs @@ -17,8 +17,7 @@ declare_clippy_lint! { /// **Why is this bad?** Removing the `return` and semicolon will make the code /// more rusty. /// - /// **Known problems:** If the computation returning the value borrows a local - /// variable, removing the `return` may run afoul of the borrow checker. + /// **Known problems:** None. /// /// **Example:** /// ```rust From a7d5c2f967dd1f075ba5f8c4ca05c4b2ca2d22b4 Mon Sep 17 00:00:00 2001 From: jrqc Date: Thu, 13 Aug 2020 19:24:34 +0300 Subject: [PATCH 5/7] Modifications according to the code review --- clippy_lints/src/let_and_return.rs | 124 ---------- clippy_lints/src/lib.rs | 26 +- clippy_lints/src/needless_return.rs | 223 ------------------ clippy_lints/src/returns.rs | 353 ++++++++++++++++++++-------- clippy_lints/src/unused_unit.rs | 145 ++++++++++++ tests/ui/needless_return.fixed | 11 + tests/ui/needless_return.rs | 11 + 7 files changed, 428 insertions(+), 465 deletions(-) delete mode 100644 clippy_lints/src/let_and_return.rs delete mode 100644 clippy_lints/src/needless_return.rs create mode 100644 clippy_lints/src/unused_unit.rs diff --git a/clippy_lints/src/let_and_return.rs b/clippy_lints/src/let_and_return.rs deleted file mode 100644 index fa560ffb9..000000000 --- a/clippy_lints/src/let_and_return.rs +++ /dev/null @@ -1,124 +0,0 @@ -use if_chain::if_chain; -use rustc_errors::Applicability; -use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; -use rustc_hir::{Block, Expr, ExprKind, PatKind, StmtKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::hir::map::Map; -use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::subst::GenericArgKind; -use rustc_session::{declare_lint_pass, declare_tool_lint}; - -use crate::utils::{fn_def_id, in_macro, match_qpath, snippet_opt, span_lint_and_then}; - -declare_clippy_lint! { - /// **What it does:** Checks for `let`-bindings, which are subsequently - /// returned. - /// - /// **Why is this bad?** It is just extraneous code. Remove it to make your code - /// more rusty. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// fn foo() -> String { - /// let x = String::new(); - /// x - /// } - /// ``` - /// instead, use - /// ``` - /// fn foo() -> String { - /// String::new() - /// } - /// ``` - pub LET_AND_RETURN, - style, - "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" -} - -declare_lint_pass!(LetReturn => [LET_AND_RETURN]); - -impl<'tcx> LateLintPass<'tcx> for LetReturn { - fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { - // we need both a let-binding stmt and an expr - if_chain! { - if let Some(retexpr) = block.expr; - if let Some(stmt) = block.stmts.iter().last(); - if let StmtKind::Local(local) = &stmt.kind; - if local.ty.is_none(); - if local.attrs.is_empty(); - if let Some(initexpr) = &local.init; - if let PatKind::Binding(.., ident, _) = local.pat.kind; - if let ExprKind::Path(qpath) = &retexpr.kind; - if match_qpath(qpath, &[&*ident.name.as_str()]); - if !last_statement_borrows(cx, initexpr); - if !in_external_macro(cx.sess(), initexpr.span); - if !in_external_macro(cx.sess(), retexpr.span); - if !in_external_macro(cx.sess(), local.span); - if !in_macro(local.span); - then { - span_lint_and_then( - cx, - LET_AND_RETURN, - retexpr.span, - "returning the result of a `let` binding from a block", - |err| { - err.span_label(local.span, "unnecessary `let` binding"); - - if let Some(snippet) = snippet_opt(cx, initexpr.span) { - err.multipart_suggestion( - "return the expression directly", - vec![ - (local.span, String::new()), - (retexpr.span, snippet), - ], - Applicability::MachineApplicable, - ); - } else { - err.span_help(initexpr.span, "this expression can be directly returned"); - } - }, - ); - } - } - } -} - -fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - let mut visitor = BorrowVisitor { cx, borrows: false }; - walk_expr(&mut visitor, expr); - visitor.borrows -} - -struct BorrowVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - borrows: bool, -} - -impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.borrows { - return; - } - - if let Some(def_id) = fn_def_id(self.cx, expr) { - self.borrows = self - .cx - .tcx - .fn_sig(def_id) - .output() - .skip_binder() - .walk() - .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_))); - } - - walk_expr(self, expr); - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index d2f66cf9b..63de0f8a0 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -218,7 +218,6 @@ mod large_const_arrays; mod large_enum_variant; mod large_stack_arrays; mod len_zero; -mod let_and_return; mod let_if_seq; mod let_underscore; mod lifetimes; @@ -256,7 +255,6 @@ mod needless_borrow; mod needless_borrowed_ref; mod needless_continue; mod needless_pass_by_value; -mod needless_return; mod needless_update; mod neg_cmp_op_on_partial_ord; mod neg_multiply; @@ -311,6 +309,7 @@ mod unnested_or_patterns; mod unsafe_removed_from_name; mod unused_io_amount; mod unused_self; +mod unused_unit; mod unwrap; mod use_self; mod useless_conversion; @@ -587,7 +586,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &large_stack_arrays::LARGE_STACK_ARRAYS, &len_zero::LEN_WITHOUT_IS_EMPTY, &len_zero::LEN_ZERO, - &let_and_return::LET_AND_RETURN, + &returns::LET_AND_RETURN, &let_if_seq::USELESS_LET_IF_SEQ, &let_underscore::LET_UNDERSCORE_LOCK, &let_underscore::LET_UNDERSCORE_MUST_USE, @@ -727,7 +726,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE, &needless_continue::NEEDLESS_CONTINUE, &needless_pass_by_value::NEEDLESS_PASS_BY_VALUE, - &needless_return::NEEDLESS_RETURN, + &returns::NEEDLESS_RETURN, &needless_update::NEEDLESS_UPDATE, &neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD, &neg_multiply::NEG_MULTIPLY, @@ -771,7 +770,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ®ex::INVALID_REGEX, ®ex::TRIVIAL_REGEX, &repeat_once::REPEAT_ONCE, - &returns::UNUSED_UNIT, + &unused_unit::UNUSED_UNIT, &serde_api::SERDE_API_MISUSE, &shadow::SHADOW_REUSE, &shadow::SHADOW_SAME, @@ -1026,9 +1025,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| box misc_early::MiscEarlyLints); store.register_early_pass(|| box redundant_closure_call::RedundantClosureCall); store.register_late_pass(|| box redundant_closure_call::RedundantClosureCall); - store.register_early_pass(|| box returns::Return); - store.register_late_pass(|| box let_and_return::LetReturn); - store.register_late_pass(|| box needless_return::NeedlessReturn); + store.register_early_pass(|| box unused_unit::UnusedUnit); + store.register_late_pass(|| box returns::Return); store.register_early_pass(|| box collapsible_if::CollapsibleIf); store.register_early_pass(|| box items_after_statements::ItemsAfterStatements); store.register_early_pass(|| box precedence::Precedence); @@ -1286,7 +1284,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT), LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), LintId::of(&len_zero::LEN_ZERO), - LintId::of(&let_and_return::LET_AND_RETURN), + LintId::of(&returns::LET_AND_RETURN), LintId::of(&let_underscore::LET_UNDERSCORE_LOCK), LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES), LintId::of(&lifetimes::NEEDLESS_LIFETIMES), @@ -1383,7 +1381,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&needless_bool::BOOL_COMPARISON), LintId::of(&needless_bool::NEEDLESS_BOOL), LintId::of(&needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), - LintId::of(&needless_return::NEEDLESS_RETURN), + LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&needless_update::NEEDLESS_UPDATE), LintId::of(&neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), LintId::of(&neg_multiply::NEG_MULTIPLY), @@ -1416,7 +1414,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(®ex::INVALID_REGEX), LintId::of(®ex::TRIVIAL_REGEX), LintId::of(&repeat_once::REPEAT_ONCE), - LintId::of(&returns::UNUSED_UNIT), + LintId::of(&unused_unit::UNUSED_UNIT), LintId::of(&serde_api::SERDE_API_MISUSE), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), @@ -1502,7 +1500,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&inherent_to_string::INHERENT_TO_STRING), LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), LintId::of(&len_zero::LEN_ZERO), - LintId::of(&let_and_return::LET_AND_RETURN), + LintId::of(&returns::LET_AND_RETURN), LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING), LintId::of(&loops::EMPTY_LOOP), LintId::of(&loops::FOR_KV_MAP), @@ -1545,7 +1543,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&misc_early::MIXED_CASE_HEX_LITERALS), LintId::of(&misc_early::REDUNDANT_PATTERN), LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED), - LintId::of(&needless_return::NEEDLESS_RETURN), + LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&neg_multiply::NEG_MULTIPLY), LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT), LintId::of(&non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), @@ -1557,7 +1555,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES), LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), LintId::of(®ex::TRIVIAL_REGEX), - LintId::of(&returns::UNUSED_UNIT), + LintId::of(&unused_unit::UNUSED_UNIT), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(&strings::STRING_LIT_AS_BYTES), LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), diff --git a/clippy_lints/src/needless_return.rs b/clippy_lints/src/needless_return.rs deleted file mode 100644 index eb0bf12c0..000000000 --- a/clippy_lints/src/needless_return.rs +++ /dev/null @@ -1,223 +0,0 @@ -use rustc_ast::ast::Attribute; -use rustc_errors::Applicability; -use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor}; -use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, StmtKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::hir::map::Map; -use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::subst::GenericArgKind; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::Span; - -use crate::utils::{fn_def_id, snippet_opt, span_lint_and_sugg, span_lint_and_then}; - -declare_clippy_lint! { - /// **What it does:** Checks for return statements at the end of a block. - /// - /// **Why is this bad?** Removing the `return` and semicolon will make the code - /// more rusty. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// fn foo(x: usize) -> usize { - /// return x; - /// } - /// ``` - /// simplify to - /// ```rust - /// fn foo(x: usize) -> usize { - /// x - /// } - /// ``` - pub NEEDLESS_RETURN, - style, - "using a return statement like `return expr;` where an expression would suffice" -} - -#[derive(PartialEq, Eq, Copy, Clone)] -enum RetReplacement { - Empty, - Block, -} - -declare_lint_pass!(NeedlessReturn => [NEEDLESS_RETURN]); - -impl<'tcx> LateLintPass<'tcx> for NeedlessReturn { - fn check_fn( - &mut self, - cx: &LateContext<'tcx>, - kind: FnKind<'tcx>, - _: &'tcx FnDecl<'tcx>, - body: &'tcx Body<'tcx>, - _: Span, - _: HirId, - ) { - match kind { - FnKind::Closure(_) => { - if !last_statement_borrows(cx, &body.value) { - check_final_expr(cx, &body.value, Some(body.value.span), RetReplacement::Empty) - } - }, - FnKind::ItemFn(..) | FnKind::Method(..) => { - if let ExprKind::Block(ref block, _) = body.value.kind { - if let Some(expr) = block.expr { - if !last_statement_borrows(cx, expr) { - check_final_expr(cx, expr, Some(expr.span), RetReplacement::Empty); - } - } else if let Some(stmt) = block.stmts.iter().last() { - match stmt.kind { - StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { - if !last_statement_borrows(cx, expr) { - check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty); - } - }, - _ => (), - } - } - } - }, - } - } -} - -fn attr_is_cfg(attr: &Attribute) -> bool { - attr.meta_item_list().is_some() && attr.has_name(sym!(cfg)) -} - -fn check_block_return(cx: &LateContext<'_>, block: &Block<'_>) { - if let Some(expr) = block.expr { - check_final_expr(cx, expr, Some(expr.span), RetReplacement::Empty); - } else if let Some(stmt) = block.stmts.iter().last() { - match stmt.kind { - StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { - check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty); - }, - _ => (), - } - } -} - -fn check_final_expr(cx: &LateContext<'_>, expr: &Expr<'_>, span: Option, replacement: RetReplacement) { - match expr.kind { - // simple return is always "bad" - ExprKind::Ret(ref inner) => { - // allow `#[cfg(a)] return a; #[cfg(b)] return b;` - if !expr.attrs.iter().any(attr_is_cfg) { - emit_return_lint( - cx, - span.expect("`else return` is not possible"), - inner.as_ref().map(|i| i.span), - replacement, - ); - } - }, - // a whole block? check it! - ExprKind::Block(ref block, _) => { - check_block_return(cx, block); - }, - // a match expr, check all arms - // an if/if let expr, check both exprs - // note, if without else is going to be a type checking error anyways - // (except for unit type functions) so we don't match it - ExprKind::Match(_, ref arms, source) => match source { - MatchSource::Normal => { - for arm in arms.iter() { - check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block); - } - }, - MatchSource::IfDesugar { - contains_else_clause: true, - } - | MatchSource::IfLetDesugar { - contains_else_clause: true, - } => { - if let ExprKind::Block(ref ifblock, _) = arms[0].body.kind { - check_block_return(cx, ifblock); - } - check_final_expr(cx, arms[1].body, None, RetReplacement::Empty); - }, - _ => (), - }, - _ => (), - } -} - -fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option, replacement: RetReplacement) { - match inner_span { - Some(inner_span) => { - if in_external_macro(cx.tcx.sess, inner_span) || inner_span.from_expansion() { - return; - } - - span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| { - if let Some(snippet) = snippet_opt(cx, inner_span) { - diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable); - } - }) - }, - None => match replacement { - RetReplacement::Empty => { - span_lint_and_sugg( - cx, - NEEDLESS_RETURN, - ret_span, - "unneeded `return` statement", - "remove `return`", - String::new(), - Applicability::MachineApplicable, - ); - }, - RetReplacement::Block => { - span_lint_and_sugg( - cx, - NEEDLESS_RETURN, - ret_span, - "unneeded `return` statement", - "replace `return` with an empty block", - "{}".to_string(), - Applicability::MachineApplicable, - ); - }, - }, - } -} - -fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - let mut visitor = BorrowVisitor { cx, borrows: false }; - walk_expr(&mut visitor, expr); - visitor.borrows -} - -struct BorrowVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - borrows: bool, -} - -impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.borrows { - return; - } - - if let Some(def_id) = fn_def_id(self.cx, expr) { - self.borrows = self - .cx - .tcx - .fn_sig(def_id) - .output() - .skip_binder() - .walk() - .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_))); - } - - walk_expr(self, expr); - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 593e2f6c7..4d91f9be9 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -1,145 +1,290 @@ use if_chain::if_chain; -use rustc_ast::ast; -use rustc_ast::visit::FnKind; +use rustc_ast::ast::Attribute; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_hir::intravisit::{walk_expr, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, PatKind, StmtKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::map::Map; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::subst::GenericArgKind; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; -use rustc_span::BytePos; -use crate::utils::span_lint_and_sugg; +use crate::utils::{fn_def_id, in_macro, match_qpath, snippet_opt, span_lint_and_sugg, span_lint_and_then}; declare_clippy_lint! { - /// **What it does:** Checks for unit (`()`) expressions that can be removed. + /// **What it does:** Checks for `let`-bindings, which are subsequently + /// returned. /// - /// **Why is this bad?** Such expressions add no value, but can make the code - /// less readable. Depending on formatting they can make a `break` or `return` - /// statement look like a function call. + /// **Why is this bad?** It is just extraneous code. Remove it to make your code + /// more rusty. /// - /// **Known problems:** The lint currently misses unit return types in types, - /// e.g., the `F` in `fn generic_unit ()>(f: F) { .. }`. + /// **Known problems:** None. /// /// **Example:** /// ```rust - /// fn return_unit() -> () { - /// () + /// fn foo() -> String { + /// let x = String::new(); + /// x /// } /// ``` - pub UNUSED_UNIT, + /// instead, use + /// ``` + /// fn foo() -> String { + /// String::new() + /// } + /// ``` + pub LET_AND_RETURN, style, - "needless unit expression" + "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block" } -declare_lint_pass!(Return => [UNUSED_UNIT]); +declare_clippy_lint! { + /// **What it does:** Checks for return statements at the end of a block. + /// + /// **Why is this bad?** Removing the `return` and semicolon will make the code + /// more rusty. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn foo(x: usize) -> usize { + /// return x; + /// } + /// ``` + /// simplify to + /// ```rust + /// fn foo(x: usize) -> usize { + /// x + /// } + /// ``` + pub NEEDLESS_RETURN, + style, + "using a return statement like `return expr;` where an expression would suffice" +} -impl EarlyLintPass for Return { - fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) { - if_chain! { - if let ast::FnRetTy::Ty(ref ty) = kind.decl().output; - if let ast::TyKind::Tup(ref vals) = ty.kind; - if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span); - then { - lint_unneeded_unit_return(cx, ty, span); - } - } - } +#[derive(PartialEq, Eq, Copy, Clone)] +enum RetReplacement { + Empty, + Block, +} - fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) { +declare_lint_pass!(Return => [LET_AND_RETURN, NEEDLESS_RETURN]); + +impl<'tcx> LateLintPass<'tcx> for Return { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { + // we need both a let-binding stmt and an expr if_chain! { - if let Some(ref stmt) = block.stmts.last(); - if let ast::StmtKind::Expr(ref expr) = stmt.kind; - if is_unit_expr(expr) && !stmt.span.from_expansion(); + if let Some(retexpr) = block.expr; + if let Some(stmt) = block.stmts.iter().last(); + if let StmtKind::Local(local) = &stmt.kind; + if local.ty.is_none(); + if local.attrs.is_empty(); + if let Some(initexpr) = &local.init; + if let PatKind::Binding(.., ident, _) = local.pat.kind; + if let ExprKind::Path(qpath) = &retexpr.kind; + if match_qpath(qpath, &[&*ident.name.as_str()]); + if !last_statement_borrows(cx, initexpr); + if !in_external_macro(cx.sess(), initexpr.span); + if !in_external_macro(cx.sess(), retexpr.span); + if !in_external_macro(cx.sess(), local.span); + if !in_macro(local.span); then { - let sp = expr.span; - span_lint_and_sugg( + span_lint_and_then( cx, - UNUSED_UNIT, - sp, - "unneeded unit expression", - "remove the final `()`", - String::new(), - Applicability::MachineApplicable, + LET_AND_RETURN, + retexpr.span, + "returning the result of a `let` binding from a block", + |err| { + err.span_label(local.span, "unnecessary `let` binding"); + + if let Some(snippet) = snippet_opt(cx, initexpr.span) { + err.multipart_suggestion( + "return the expression directly", + vec![ + (local.span, String::new()), + (retexpr.span, snippet), + ], + Applicability::MachineApplicable, + ); + } else { + err.span_help(initexpr.span, "this expression can be directly returned"); + } + }, ); } } } - fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { - match e.kind { - ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => { - if is_unit_expr(expr) && !expr.span.from_expansion() { - span_lint_and_sugg( - cx, - UNUSED_UNIT, - expr.span, - "unneeded `()`", - "remove the `()`", - String::new(), - Applicability::MachineApplicable, - ); + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + _: &'tcx FnDecl<'tcx>, + body: &'tcx Body<'tcx>, + _: Span, + _: HirId, + ) { + match kind { + FnKind::Closure(_) => check_final_expr(cx, &body.value, Some(body.value.span), RetReplacement::Empty), + FnKind::ItemFn(..) | FnKind::Method(..) => { + if let ExprKind::Block(ref block, _) = body.value.kind { + check_block_return(cx, block); } }, + } + } +} + +fn attr_is_cfg(attr: &Attribute) -> bool { + attr.meta_item_list().is_some() && attr.has_name(sym!(cfg)) +} + +fn check_block_return<'tcx>(cx: &LateContext<'tcx>, block: &Block<'tcx>) { + if let Some(expr) = block.expr { + check_final_expr(cx, expr, Some(expr.span), RetReplacement::Empty); + } else if let Some(stmt) = block.stmts.iter().last() { + match stmt.kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { + check_final_expr(cx, expr, Some(stmt.span), RetReplacement::Empty); + }, _ => (), } } +} - fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef, _: &ast::TraitBoundModifier) { - let segments = &poly.trait_ref.path.segments; +fn check_final_expr<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + span: Option, + replacement: RetReplacement, +) { + if last_statement_borrows(cx, expr) { + return; + } - if_chain! { - if segments.len() == 1; - if ["Fn", "FnMut", "FnOnce"].contains(&&*segments[0].ident.name.as_str()); - if let Some(args) = &segments[0].args; - if let ast::GenericArgs::Parenthesized(generic_args) = &**args; - if let ast::FnRetTy::Ty(ty) = &generic_args.output; - if ty.kind.is_unit(); - then { - lint_unneeded_unit_return(cx, ty, generic_args.span); + match expr.kind { + // simple return is always "bad" + ExprKind::Ret(ref inner) => { + // allow `#[cfg(a)] return a; #[cfg(b)] return b;` + if !expr.attrs.iter().any(attr_is_cfg) { + let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner)); + if !borrows { + emit_return_lint( + cx, + span.expect("`else return` is not possible"), + inner.as_ref().map(|i| i.span), + replacement, + ); + } } - } + }, + // a whole block? check it! + ExprKind::Block(ref block, _) => { + check_block_return(cx, block); + }, + // a match expr, check all arms + // an if/if let expr, check both exprs + // note, if without else is going to be a type checking error anyways + // (except for unit type functions) so we don't match it + ExprKind::Match(_, ref arms, source) => match source { + MatchSource::Normal => { + for arm in arms.iter() { + check_final_expr(cx, &arm.body, Some(arm.body.span), RetReplacement::Block); + } + }, + MatchSource::IfDesugar { + contains_else_clause: true, + } + | MatchSource::IfLetDesugar { + contains_else_clause: true, + } => { + if let ExprKind::Block(ref ifblock, _) = arms[0].body.kind { + check_block_return(cx, ifblock); + } + check_final_expr(cx, arms[1].body, None, RetReplacement::Empty); + }, + _ => (), + }, + _ => (), } } -// get the def site -#[must_use] -fn get_def(span: Span) -> Option { - if span.from_expansion() { - Some(span.ctxt().outer_expn_data().def_site) - } else { - None - } -} +fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option, replacement: RetReplacement) { + match inner_span { + Some(inner_span) => { + if in_external_macro(cx.tcx.sess, inner_span) || inner_span.from_expansion() { + return; + } -// is this expr a `()` unit? -fn is_unit_expr(expr: &ast::Expr) -> bool { - if let ast::ExprKind::Tup(ref vals) = expr.kind { - vals.is_empty() - } else { - false - } -} - -fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) { - let (ret_span, appl) = if let Ok(fn_source) = cx.sess().source_map().span_to_snippet(span.with_hi(ty.span.hi())) { - fn_source - .rfind("->") - .map_or((ty.span, Applicability::MaybeIncorrect), |rpos| { - ( - #[allow(clippy::cast_possible_truncation)] - ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)), - Applicability::MachineApplicable, - ) + span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| { + if let Some(snippet) = snippet_opt(cx, inner_span) { + diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable); + } }) - } else { - (ty.span, Applicability::MaybeIncorrect) - }; - span_lint_and_sugg( - cx, - UNUSED_UNIT, - ret_span, - "unneeded unit return type", - "remove the `-> ()`", - String::new(), - appl, - ); + }, + None => match replacement { + RetReplacement::Empty => { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN, + ret_span, + "unneeded `return` statement", + "remove `return`", + String::new(), + Applicability::MachineApplicable, + ); + }, + RetReplacement::Block => { + span_lint_and_sugg( + cx, + NEEDLESS_RETURN, + ret_span, + "unneeded `return` statement", + "replace `return` with an empty block", + "{}".to_string(), + Applicability::MachineApplicable, + ); + }, + }, + } +} + +fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + let mut visitor = BorrowVisitor { cx, borrows: false }; + walk_expr(&mut visitor, expr); + visitor.borrows +} + +struct BorrowVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + borrows: bool, +} + +impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.borrows { + return; + } + + if let Some(def_id) = fn_def_id(self.cx, expr) { + self.borrows = self + .cx + .tcx + .fn_sig(def_id) + .output() + .skip_binder() + .walk() + .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_))); + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } } diff --git a/clippy_lints/src/unused_unit.rs b/clippy_lints/src/unused_unit.rs new file mode 100644 index 000000000..e322e4025 --- /dev/null +++ b/clippy_lints/src/unused_unit.rs @@ -0,0 +1,145 @@ +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_ast::visit::FnKind; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use rustc_span::BytePos; + +use crate::utils::span_lint_and_sugg; + +declare_clippy_lint! { + /// **What it does:** Checks for unit (`()`) expressions that can be removed. + /// + /// **Why is this bad?** Such expressions add no value, but can make the code + /// less readable. Depending on formatting they can make a `break` or `return` + /// statement look like a function call. + /// + /// **Known problems:** The lint currently misses unit return types in types, + /// e.g., the `F` in `fn generic_unit ()>(f: F) { .. }`. + /// + /// **Example:** + /// ```rust + /// fn return_unit() -> () { + /// () + /// } + /// ``` + pub UNUSED_UNIT, + style, + "needless unit expression" +} + +declare_lint_pass!(UnusedUnit => [UNUSED_UNIT]); + +impl EarlyLintPass for UnusedUnit { + fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, span: Span, _: ast::NodeId) { + if_chain! { + if let ast::FnRetTy::Ty(ref ty) = kind.decl().output; + if let ast::TyKind::Tup(ref vals) = ty.kind; + if vals.is_empty() && !ty.span.from_expansion() && get_def(span) == get_def(ty.span); + then { + lint_unneeded_unit_return(cx, ty, span); + } + } + } + + fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) { + if_chain! { + if let Some(ref stmt) = block.stmts.last(); + if let ast::StmtKind::Expr(ref expr) = stmt.kind; + if is_unit_expr(expr) && !stmt.span.from_expansion(); + then { + let sp = expr.span; + span_lint_and_sugg( + cx, + UNUSED_UNIT, + sp, + "unneeded unit expression", + "remove the final `()`", + String::new(), + Applicability::MachineApplicable, + ); + } + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { + match e.kind { + ast::ExprKind::Ret(Some(ref expr)) | ast::ExprKind::Break(_, Some(ref expr)) => { + if is_unit_expr(expr) && !expr.span.from_expansion() { + span_lint_and_sugg( + cx, + UNUSED_UNIT, + expr.span, + "unneeded `()`", + "remove the `()`", + String::new(), + Applicability::MachineApplicable, + ); + } + }, + _ => (), + } + } + + fn check_poly_trait_ref(&mut self, cx: &EarlyContext<'_>, poly: &ast::PolyTraitRef, _: &ast::TraitBoundModifier) { + let segments = &poly.trait_ref.path.segments; + + if_chain! { + if segments.len() == 1; + if ["Fn", "FnMut", "FnOnce"].contains(&&*segments[0].ident.name.as_str()); + if let Some(args) = &segments[0].args; + if let ast::GenericArgs::Parenthesized(generic_args) = &**args; + if let ast::FnRetTy::Ty(ty) = &generic_args.output; + if ty.kind.is_unit(); + then { + lint_unneeded_unit_return(cx, ty, generic_args.span); + } + } + } +} + +// get the def site +#[must_use] +fn get_def(span: Span) -> Option { + if span.from_expansion() { + Some(span.ctxt().outer_expn_data().def_site) + } else { + None + } +} + +// is this expr a `()` unit? +fn is_unit_expr(expr: &ast::Expr) -> bool { + if let ast::ExprKind::Tup(ref vals) = expr.kind { + vals.is_empty() + } else { + false + } +} + +fn lint_unneeded_unit_return(cx: &EarlyContext<'_>, ty: &ast::Ty, span: Span) { + let (ret_span, appl) = if let Ok(fn_source) = cx.sess().source_map().span_to_snippet(span.with_hi(ty.span.hi())) { + fn_source + .rfind("->") + .map_or((ty.span, Applicability::MaybeIncorrect), |rpos| { + ( + #[allow(clippy::cast_possible_truncation)] + ty.span.with_lo(BytePos(span.lo().0 + rpos as u32)), + Applicability::MachineApplicable, + ) + }) + } else { + (ty.span, Applicability::MaybeIncorrect) + }; + span_lint_and_sugg( + cx, + UNUSED_UNIT, + ret_span, + "unneeded unit return type", + "remove the `-> ()`", + String::new(), + appl, + ); +} diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 6b5cf2626..b795516f9 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -76,6 +76,17 @@ mod no_lint_if_stmt_borrows { let stdin = ::std::io::stdin(); return stdin.lock().lines().next().unwrap().unwrap(); } + + fn read_line2(value: bool) -> String { + if value { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + let _a = stdin.lock().lines().next().unwrap().unwrap(); + return String::from("test"); + } else { + return String::new(); + } + } } } diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index 1a693c9aa..354799193 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -76,6 +76,17 @@ mod no_lint_if_stmt_borrows { let stdin = ::std::io::stdin(); return stdin.lock().lines().next().unwrap().unwrap(); } + + fn read_line2(value: bool) -> String { + if value { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + let _a = stdin.lock().lines().next().unwrap().unwrap(); + return String::from("test"); + } else { + return String::new(); + } + } } } From 96efaee55240a3d3428ea5fe30c884a3227dad6e Mon Sep 17 00:00:00 2001 From: jrqc Date: Thu, 13 Aug 2020 19:30:49 +0300 Subject: [PATCH 6/7] cargo dev update_lints --- clippy_lints/src/lib.rs | 18 +++++++++--------- src/lintlist/mod.rs | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 63de0f8a0..c0b645e93 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -586,7 +586,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &large_stack_arrays::LARGE_STACK_ARRAYS, &len_zero::LEN_WITHOUT_IS_EMPTY, &len_zero::LEN_ZERO, - &returns::LET_AND_RETURN, &let_if_seq::USELESS_LET_IF_SEQ, &let_underscore::LET_UNDERSCORE_LOCK, &let_underscore::LET_UNDERSCORE_MUST_USE, @@ -726,7 +725,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE, &needless_continue::NEEDLESS_CONTINUE, &needless_pass_by_value::NEEDLESS_PASS_BY_VALUE, - &returns::NEEDLESS_RETURN, &needless_update::NEEDLESS_UPDATE, &neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD, &neg_multiply::NEG_MULTIPLY, @@ -770,7 +768,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ®ex::INVALID_REGEX, ®ex::TRIVIAL_REGEX, &repeat_once::REPEAT_ONCE, - &unused_unit::UNUSED_UNIT, + &returns::LET_AND_RETURN, + &returns::NEEDLESS_RETURN, &serde_api::SERDE_API_MISUSE, &shadow::SHADOW_REUSE, &shadow::SHADOW_SAME, @@ -840,6 +839,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, &unused_io_amount::UNUSED_IO_AMOUNT, &unused_self::UNUSED_SELF, + &unused_unit::UNUSED_UNIT, &unwrap::PANICKING_UNWRAP, &unwrap::UNNECESSARY_UNWRAP, &use_self::USE_SELF, @@ -1284,7 +1284,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT), LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), LintId::of(&len_zero::LEN_ZERO), - LintId::of(&returns::LET_AND_RETURN), LintId::of(&let_underscore::LET_UNDERSCORE_LOCK), LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES), LintId::of(&lifetimes::NEEDLESS_LIFETIMES), @@ -1381,7 +1380,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&needless_bool::BOOL_COMPARISON), LintId::of(&needless_bool::NEEDLESS_BOOL), LintId::of(&needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), - LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&needless_update::NEEDLESS_UPDATE), LintId::of(&neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), LintId::of(&neg_multiply::NEG_MULTIPLY), @@ -1414,7 +1412,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(®ex::INVALID_REGEX), LintId::of(®ex::TRIVIAL_REGEX), LintId::of(&repeat_once::REPEAT_ONCE), - LintId::of(&unused_unit::UNUSED_UNIT), + LintId::of(&returns::LET_AND_RETURN), + LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&serde_api::SERDE_API_MISUSE), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), @@ -1460,6 +1459,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(&unused_io_amount::UNUSED_IO_AMOUNT), + LintId::of(&unused_unit::UNUSED_UNIT), LintId::of(&unwrap::PANICKING_UNWRAP), LintId::of(&unwrap::UNNECESSARY_UNWRAP), LintId::of(&useless_conversion::USELESS_CONVERSION), @@ -1500,7 +1500,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&inherent_to_string::INHERENT_TO_STRING), LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), LintId::of(&len_zero::LEN_ZERO), - LintId::of(&returns::LET_AND_RETURN), LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING), LintId::of(&loops::EMPTY_LOOP), LintId::of(&loops::FOR_KV_MAP), @@ -1543,7 +1542,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&misc_early::MIXED_CASE_HEX_LITERALS), LintId::of(&misc_early::REDUNDANT_PATTERN), LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED), - LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&neg_multiply::NEG_MULTIPLY), LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT), LintId::of(&non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), @@ -1555,7 +1553,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES), LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), LintId::of(®ex::TRIVIAL_REGEX), - LintId::of(&unused_unit::UNUSED_UNIT), + LintId::of(&returns::LET_AND_RETURN), + LintId::of(&returns::NEEDLESS_RETURN), LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(&strings::STRING_LIT_AS_BYTES), LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), @@ -1564,6 +1563,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::FN_TO_NUMERIC_CAST), LintId::of(&types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), + LintId::of(&unused_unit::UNUSED_UNIT), LintId::of(&write::PRINTLN_EMPTY_STRING), LintId::of(&write::PRINT_LITERAL), LintId::of(&write::PRINT_WITH_NEWLINE), diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 7464f1cc6..dff6198d0 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1037,7 +1037,7 @@ pub static ref ALL_LINTS: Vec = vec![ group: "style", desc: "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block", deprecation: None, - module: "let_and_return", + module: "returns", }, Lint { name: "let_underscore_lock", @@ -1534,7 +1534,7 @@ pub static ref ALL_LINTS: Vec = vec![ group: "style", desc: "using a return statement like `return expr;` where an expression would suffice", deprecation: None, - module: "needless_return", + module: "returns", }, Lint { name: "needless_update", @@ -2479,7 +2479,7 @@ pub static ref ALL_LINTS: Vec = vec![ group: "style", desc: "needless unit expression", deprecation: None, - module: "returns", + module: "unused_unit", }, Lint { name: "unwrap_used", From baa4cb1cddc3a8ce1f47c4006e236edf082ee858 Mon Sep 17 00:00:00 2001 From: jrqc Date: Fri, 14 Aug 2020 09:25:26 +0300 Subject: [PATCH 7/7] early return removed --- clippy_lints/src/returns.rs | 4 ---- clippy_lints/src/unused_unit.rs | 3 +-- tests/ui/needless_return.fixed | 30 +++++++++++++----------------- tests/ui/needless_return.rs | 30 +++++++++++++----------------- tests/ui/needless_return.stderr | 14 +++++++++++++- 5 files changed, 40 insertions(+), 41 deletions(-) diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 4d91f9be9..3c5541e64 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -160,10 +160,6 @@ fn check_final_expr<'tcx>( span: Option, replacement: RetReplacement, ) { - if last_statement_borrows(cx, expr) { - return; - } - match expr.kind { // simple return is always "bad" ExprKind::Ret(ref inner) => { diff --git a/clippy_lints/src/unused_unit.rs b/clippy_lints/src/unused_unit.rs index e322e4025..7548c6afa 100644 --- a/clippy_lints/src/unused_unit.rs +++ b/clippy_lints/src/unused_unit.rs @@ -16,8 +16,7 @@ declare_clippy_lint! { /// less readable. Depending on formatting they can make a `break` or `return` /// statement look like a function call. /// - /// **Known problems:** The lint currently misses unit return types in types, - /// e.g., the `F` in `fn generic_unit ()>(f: F) { .. }`. + /// **Known problems:** None. /// /// **Example:** /// ```rust diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index b795516f9..d849e093d 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -69,24 +69,20 @@ fn test_void_match(x: u32) { } } -mod no_lint_if_stmt_borrows { - mod issue_5858 { - fn read_line() -> String { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - return stdin.lock().lines().next().unwrap().unwrap(); - } +fn read_line() -> String { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + return stdin.lock().lines().next().unwrap().unwrap(); +} - fn read_line2(value: bool) -> String { - if value { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - let _a = stdin.lock().lines().next().unwrap().unwrap(); - return String::from("test"); - } else { - return String::new(); - } - } +fn borrows_but_not_last(value: bool) -> String { + if value { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + let _a = stdin.lock().lines().next().unwrap().unwrap(); + String::from("test") + } else { + String::new() } } diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index 354799193..29f2bd185 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -69,24 +69,20 @@ fn test_void_match(x: u32) { } } -mod no_lint_if_stmt_borrows { - mod issue_5858 { - fn read_line() -> String { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - return stdin.lock().lines().next().unwrap().unwrap(); - } +fn read_line() -> String { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + return stdin.lock().lines().next().unwrap().unwrap(); +} - fn read_line2(value: bool) -> String { - if value { - use std::io::BufRead; - let stdin = ::std::io::stdin(); - let _a = stdin.lock().lines().next().unwrap().unwrap(); - return String::from("test"); - } else { - return String::new(); - } - } +fn borrows_but_not_last(value: bool) -> String { + if value { + use std::io::BufRead; + let stdin = ::std::io::stdin(); + let _a = stdin.lock().lines().next().unwrap().unwrap(); + return String::from("test"); + } else { + return String::new(); } } diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr index c34eecbcb..f73c833a8 100644 --- a/tests/ui/needless_return.stderr +++ b/tests/ui/needless_return.stderr @@ -72,5 +72,17 @@ error: unneeded `return` statement LL | _ => return, | ^^^^^^ help: replace `return` with an empty block: `{}` -error: aborting due to 12 previous errors +error: unneeded `return` statement + --> $DIR/needless_return.rs:83:9 + | +LL | return String::from("test"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::from("test")` + +error: unneeded `return` statement + --> $DIR/needless_return.rs:85:9 + | +LL | return String::new(); + | ^^^^^^^^^^^^^^^^^^^^^ help: remove `return`: `String::new()` + +error: aborting due to 14 previous errors