diff options
| author | flip1995 <philipp.krones@embecosm.com> | 2021-12-17 13:40:22 +0100 |
|---|---|---|
| committer | flip1995 <philipp.krones@embecosm.com> | 2021-12-17 13:40:22 +0100 |
| commit | ece0946d7f60bdee8b9544753a3f1a3acddcb1f7 (patch) | |
| tree | 78cfdf1e08b149bae2122b496dd4cfd6fb55c4d4 /clippy_lints/src | |
| parent | b2f8a27ff2662b440d0dd65cdb609d303b2fa280 (diff) | |
Merge commit '23d11428de3e973b34a5090a78d62887f821c90e' into clippyup
Diffstat (limited to 'clippy_lints/src')
41 files changed, 976 insertions, 356 deletions
diff --git a/clippy_lints/src/asm_syntax.rs b/clippy_lints/src/asm_syntax.rs index 0322698f029..f419781dbc8 100644 --- a/clippy_lints/src/asm_syntax.rs +++ b/clippy_lints/src/asm_syntax.rs @@ -65,6 +65,7 @@ declare_clippy_lint! { /// ```rust,no_run /// # #![feature(asm)] /// # unsafe { let ptr = "".as_ptr(); + /// # use std::arch::asm; /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); /// # } /// ``` @@ -72,6 +73,7 @@ declare_clippy_lint! { /// ```rust,no_run /// # #![feature(asm)] /// # unsafe { let ptr = "".as_ptr(); + /// # use std::arch::asm; /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); /// # } /// ``` @@ -102,6 +104,7 @@ declare_clippy_lint! { /// ```rust,no_run /// # #![feature(asm)] /// # unsafe { let ptr = "".as_ptr(); + /// # use std::arch::asm; /// asm!("lea ({}), {}", in(reg) ptr, lateout(reg) _, options(att_syntax)); /// # } /// ``` @@ -109,6 +112,7 @@ declare_clippy_lint! { /// ```rust,no_run /// # #![feature(asm)] /// # unsafe { let ptr = "".as_ptr(); + /// # use std::arch::asm; /// asm!("lea {}, [{}]", lateout(reg) _, in(reg) ptr); /// # } /// ``` diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index 521fc84ee9c..b7f414742f1 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -2,7 +2,7 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::higher; use clippy_utils::source::snippet_opt; -use clippy_utils::{is_direct_expn_of, is_expn_of, match_panic_call}; +use clippy_utils::{is_direct_expn_of, is_expn_of, match_panic_call, peel_blocks}; use if_chain::if_chain; use rustc_hir::{Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; @@ -122,15 +122,7 @@ fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) if let ExprKind::Unary(UnOp::Not, expr) = cond.kind; // bind the first argument of the `assert!` macro if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr); - // block - if let ExprKind::Block(block, _) = then.kind; - if block.stmts.is_empty(); - if let Some(block_expr) = &block.expr; - // inner block is optional. unwrap it if it exists, or use the expression as is otherwise. - if let Some(begin_panic_call) = match block_expr.kind { - ExprKind::Block(inner_block, _) => &inner_block.expr, - _ => &block.expr, - }; + let begin_panic_call = peel_blocks(then); // function call if let Some(arg) = match_panic_call(cx, begin_panic_call); // bind the second argument of the `assert!` macro if it exists diff --git a/clippy_lints/src/blocks_in_if_conditions.rs b/clippy_lints/src/blocks_in_if_conditions.rs index b59f49357df..475fdb440d4 100644 --- a/clippy_lints/src/blocks_in_if_conditions.rs +++ b/clippy_lints/src/blocks_in_if_conditions.rs @@ -73,9 +73,11 @@ impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> { let body = self.cx.tcx.hir().body(eid); let ex = &body.value; - if matches!(ex.kind, ExprKind::Block(_, _)) && !body.value.span.from_expansion() { - self.found_block = Some(ex); - return; + if let ExprKind::Block(block, _) = ex.kind { + if !body.value.span.from_expansion() && !block.stmts.is_empty() { + self.found_block = Some(ex); + return; + } } } walk_expr(self, expr); diff --git a/clippy_lints/src/bytecount.rs b/clippy_lints/src/bytecount.rs index afb317421d0..92336a54e27 100644 --- a/clippy_lints/src/bytecount.rs +++ b/clippy_lints/src/bytecount.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::match_type; use clippy_utils::visitors::is_local_used; -use clippy_utils::{path_to_local_id, paths, peel_ref_operators, remove_blocks, strip_pat_refs}; +use clippy_utils::{path_to_local_id, paths, peel_blocks, peel_ref_operators, strip_pat_refs}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind}; @@ -42,7 +42,7 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if_chain! { if let ExprKind::MethodCall(count, _, [count_recv], _) = expr.kind; - if count.ident.name == sym!(count); + if count.ident.name == sym::count; if let ExprKind::MethodCall(filter, _, [filter_recv, filter_arg], _) = count_recv.kind; if filter.ident.name == sym!(filter); if let ExprKind::Closure(_, _, body_id, _, _) = filter_arg.kind; @@ -55,7 +55,7 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount { cx.typeck_results().expr_ty(filter_recv).peel_refs(), &paths::SLICE_ITER); let operand_is_arg = |expr| { - let expr = peel_ref_operators(cx, remove_blocks(expr)); + let expr = peel_ref_operators(cx, peel_blocks(expr)); path_to_local_id(expr, arg_id) }; let needle = if operand_is_arg(l) { diff --git a/clippy_lints/src/collapsible_match.rs b/clippy_lints/src/collapsible_match.rs index 626f9971f01..c71e9f10f79 100644 --- a/clippy_lints/src/collapsible_match.rs +++ b/clippy_lints/src/collapsible_match.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::IfLetOrMatch; use clippy_utils::visitors::is_local_used; -use clippy_utils::{is_lang_ctor, is_unit_expr, path_to_local, peel_ref_operators, SpanlessEq}; +use clippy_utils::{is_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, SpanlessEq}; use if_chain::if_chain; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, StmtKind}; +use rustc_hir::{Arm, Expr, Guard, HirId, Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::{MultiSpan, Span}; @@ -75,7 +75,7 @@ fn check_arm<'tcx>( outer_guard: Option<&'tcx Guard<'tcx>>, outer_else_body: Option<&'tcx Expr<'tcx>>, ) { - let inner_expr = strip_singleton_blocks(outer_then_body); + let inner_expr = peel_blocks_with_stmt(outer_then_body); if_chain! { if let Some(inner) = IfLetOrMatch::parse(cx, inner_expr); if let Some((inner_scrutinee, inner_then_pat, inner_else_body)) = match inner { @@ -138,20 +138,6 @@ fn check_arm<'tcx>( } } -fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> { - while let ExprKind::Block(block, _) = expr.kind { - match (block.stmts, block.expr) { - ([stmt], None) => match stmt.kind { - StmtKind::Expr(e) | StmtKind::Semi(e) => expr = e, - _ => break, - }, - ([], Some(e)) => expr = e, - _ => break, - } - } - expr -} - /// A "wild-like" arm has a wild (`_`) or `None` pattern and no guard. Such arms can be "collapsed" /// into a single wild arm without any significant loss in semantics or readability. fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index d0fab2b48fb..eccb18982f3 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::{is_automatically_derived, is_default_equivalent, remove_blocks}; +use clippy_utils::{is_automatically_derived, is_default_equivalent, peel_blocks}; use rustc_hir::{ def::{DefKind, Res}, Body, Expr, ExprKind, GenericArg, Impl, ImplItemKind, Item, ItemKind, Node, PathSegment, QPath, TyKind, @@ -95,7 +95,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { } } } - let should_emit = match remove_blocks(func_expr).kind { + let should_emit = match peel_blocks(func_expr).kind { ExprKind::Tup(fields) => fields.iter().all(|e| is_default_equivalent(cx, e)), ExprKind::Call(callee, args) if is_path_self(callee) => args.iter().all(|e| is_default_equivalent(cx, e)), diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index 3df511ea8e7..2de2bfc040b 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -4,7 +4,7 @@ use clippy_utils::consts::{ }; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher; -use clippy_utils::{eq_expr_value, get_parent_expr, in_constant, numeric_literal, sugg}; +use clippy_utils::{eq_expr_value, get_parent_expr, in_constant, numeric_literal, peel_blocks, sugg}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}; @@ -546,13 +546,9 @@ fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) { if_chain! { - if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr); - if let ExprKind::Block(block, _) = then.kind; - if block.stmts.is_empty(); - if let Some(if_body_expr) = block.expr; - if let Some(ExprKind::Block(else_block, _)) = r#else.map(|el| &el.kind); - if else_block.stmts.is_empty(); - if let Some(else_body_expr) = else_block.expr; + if let Some(higher::If { cond, then, r#else: Some(r#else) }) = higher::If::hir(expr); + let if_body_expr = peel_blocks(then); + let else_body_expr = peel_blocks(r#else); if let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr); then { let positive_abs_sugg = ( diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index 30d222bd7d2..16e5c5ca603 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::source::snippet_with_macro_callsite; -use clippy_utils::{contains_return, higher, is_else_clause, is_lang_ctor, meets_msrv, msrvs}; +use clippy_utils::{contains_return, higher, is_else_clause, is_lang_ctor, meets_msrv, msrvs, peel_blocks}; use if_chain::if_chain; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind, Stmt, StmtKind}; @@ -77,10 +77,7 @@ impl LateLintPass<'_> for IfThenSomeElseNone { if let ExprKind::Call(then_call, [then_arg]) = then_expr.kind; if let ExprKind::Path(ref then_call_qpath) = then_call.kind; if is_lang_ctor(cx, then_call_qpath, OptionSome); - if let ExprKind::Block(els_block, _) = els.kind; - if els_block.stmts.is_empty(); - if let Some(els_expr) = els_block.expr; - if let ExprKind::Path(ref qpath) = els_expr.kind; + if let ExprKind::Path(ref qpath) = peel_blocks(els).kind; if is_lang_ctor(cx, qpath, OptionNone); if !stmts_contains_early_return(then_block.stmts); then { diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index 4088c54623d..26a196aab59 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::higher; -use clippy_utils::SpanlessEq; +use clippy_utils::{higher, peel_blocks_with_stmt, SpanlessEq}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; -use rustc_hir::{lang_items::LangItem, BinOpKind, Expr, ExprKind, QPath, StmtKind}; +use rustc_hir::{lang_items::LangItem, BinOpKind, Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -52,13 +51,8 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { // Ensure that the binary operator is >, != and < if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node; - // Check if the true condition block has only one statement - if let ExprKind::Block(block, _) = then.kind; - if block.stmts.len() == 1 && block.expr.is_none(); - // Check if assign operation is done - if let StmtKind::Semi(e) = block.stmts[0].kind; - if let Some(target) = subtracts_one(cx, e); + if let Some(target) = subtracts_one(cx, then); // Extracting out the variable name if let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind; @@ -138,8 +132,8 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { } } -fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &Expr<'a>) -> Option<&'a Expr<'a>> { - match expr.kind { +fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> { + match peel_blocks_with_stmt(expr).kind { ExprKind::AssignOp(ref op1, target, value) => { if_chain! { if BinOpKind::Sub == op1.node; diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index b7b5f059de6..3d3999d4cc0 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -181,6 +181,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::UNNECESSARY_FILTER_MAP), LintId::of(methods::UNNECESSARY_FOLD), LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS), + LintId::of(methods::UNNECESSARY_TO_OWNED), LintId::of(methods::UNWRAP_OR_ELSE_DEFAULT), LintId::of(methods::USELESS_ASREF), LintId::of(methods::WRONG_SELF_CONVENTION), @@ -220,7 +221,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(non_copy_const::DECLARE_INTERIOR_MUTABLE_CONST), LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS), LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS), - LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY), LintId::of(octal_escapes::OCTAL_ESCAPES), LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS), LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP), @@ -246,6 +246,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(reference::REF_IN_DEREF), LintId::of(regex::INVALID_REGEX), LintId::of(repeat_once::REPEAT_ONCE), + LintId::of(return_self_not_must_use::RETURN_SELF_NOT_MUST_USE), LintId::of(returns::LET_AND_RETURN), LintId::of(returns::NEEDLESS_RETURN), LintId::of(self_assignment::SELF_ASSIGNMENT), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index bb159e50373..766c5ba1bcb 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -315,6 +315,7 @@ store.register_lints(&[ methods::UNNECESSARY_FILTER_MAP, methods::UNNECESSARY_FOLD, methods::UNNECESSARY_LAZY_EVALUATIONS, + methods::UNNECESSARY_TO_OWNED, methods::UNWRAP_OR_ELSE_DEFAULT, methods::UNWRAP_USED, methods::USELESS_ASREF, @@ -422,6 +423,7 @@ store.register_lints(&[ regex::INVALID_REGEX, regex::TRIVIAL_REGEX, repeat_once::REPEAT_ONCE, + return_self_not_must_use::RETURN_SELF_NOT_MUST_USE, returns::LET_AND_RETURN, returns::NEEDLESS_RETURN, same_name_method::SAME_NAME_METHOD, diff --git a/clippy_lints/src/lib.register_nursery.rs b/clippy_lints/src/lib.register_nursery.rs index 59182fd8175..e3cf0670018 100644 --- a/clippy_lints/src/lib.register_nursery.rs +++ b/clippy_lints/src/lib.register_nursery.rs @@ -18,6 +18,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN), LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL), LintId::of(mutex_atomic::MUTEX_INTEGER), + LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY), LintId::of(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES), LintId::of(option_if_let_else::OPTION_IF_LET_ELSE), LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE), diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index a0d5cf9418e..2ea0b696f1f 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -17,6 +17,7 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ LintId::of(methods::MANUAL_STR_REPEAT), LintId::of(methods::OR_FUN_CALL), LintId::of(methods::SINGLE_CHAR_PATTERN), + LintId::of(methods::UNNECESSARY_TO_OWNED), LintId::of(misc::CMP_OWNED), LintId::of(mutex_atomic::MUTEX_ATOMIC), LintId::of(redundant_clone::REDUNDANT_CLONE), diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs index 414bfc42fdf..8594338ffa5 100644 --- a/clippy_lints/src/lib.register_suspicious.rs +++ b/clippy_lints/src/lib.register_suspicious.rs @@ -15,8 +15,8 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(loops::MUT_RANGE_BOUND), LintId::of(methods::SUSPICIOUS_MAP), LintId::of(mut_key::MUTABLE_KEY_TYPE), - LintId::of(non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY), LintId::of(octal_escapes::OCTAL_ESCAPES), + LintId::of(return_self_not_must_use::RETURN_SELF_NOT_MUST_USE), LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), ]) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 77b7fee6389..d1c7956a7a5 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -340,6 +340,7 @@ mod ref_option_ref; mod reference; mod regex; mod repeat_once; +mod return_self_not_must_use; mod returns; mod same_name_method; mod self_assignment; @@ -852,6 +853,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(trailing_empty_array::TrailingEmptyArray)); store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes)); store.register_late_pass(|| Box::new(needless_late_init::NeedlessLateInit)); + store.register_late_pass(|| Box::new(return_self_not_must_use::ReturnSelfNotMustUse)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index fad3343d128..0e2b78609c2 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -29,7 +29,7 @@ declare_clippy_lint! { /// /// ### Known problems /// - We bail out if the function has a `where` clause where lifetimes - /// are mentioned due to potenial false positives. + /// are mentioned due to potential false positives. /// - Lifetime bounds such as `impl Foo + 'a` and `T: 'a` must be elided with the /// placeholder notation `'_` because the fully elided notation leaves the type bound to `'static`. /// diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs index 5b6e27085d5..d276c901059 100644 --- a/clippy_lints/src/loops/manual_flatten.rs +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -3,11 +3,11 @@ use super::MANUAL_FLATTEN; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher; use clippy_utils::visitors::is_local_used; -use clippy_utils::{is_lang_ctor, path_to_local_id}; +use clippy_utils::{is_lang_ctor, path_to_local_id, peel_blocks_with_stmt}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionSome, ResultOk}; -use rustc_hir::{Expr, ExprKind, Pat, PatKind, StmtKind}; +use rustc_hir::{Expr, Pat, PatKind}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::source_map::Span; @@ -21,71 +21,55 @@ pub(super) fn check<'tcx>( body: &'tcx Expr<'_>, span: Span, ) { - if let ExprKind::Block(block, _) = body.kind { - // Ensure the `if let` statement is the only expression or statement in the for-loop - let inner_expr = if block.stmts.len() == 1 && block.expr.is_none() { - let match_stmt = &block.stmts[0]; - if let StmtKind::Semi(inner_expr) = match_stmt.kind { - Some(inner_expr) - } else { - None - } - } else if block.stmts.is_empty() { - block.expr - } else { - None - }; + let inner_expr = peel_blocks_with_stmt(body); + if_chain! { + if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None }) + = higher::IfLet::hir(cx, inner_expr); + // Ensure match_expr in `if let` statement is the same as the pat from the for-loop + if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind; + if path_to_local_id(let_expr, pat_hir_id); + // Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result` + if let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind; + let some_ctor = is_lang_ctor(cx, qpath, OptionSome); + let ok_ctor = is_lang_ctor(cx, qpath, ResultOk); + if some_ctor || ok_ctor; + // Ensure expr in `if let` is not used afterwards + if !is_local_used(cx, if_then, pat_hir_id); + then { + let if_let_type = if some_ctor { "Some" } else { "Ok" }; + // Prepare the error message + let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type); - if_chain! { - if let Some(inner_expr) = inner_expr; - if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None }) - = higher::IfLet::hir(cx, inner_expr); - // Ensure match_expr in `if let` statement is the same as the pat from the for-loop - if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind; - if path_to_local_id(let_expr, pat_hir_id); - // Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result` - if let PatKind::TupleStruct(ref qpath, _, _) = let_pat.kind; - let some_ctor = is_lang_ctor(cx, qpath, OptionSome); - let ok_ctor = is_lang_ctor(cx, qpath, ResultOk); - if some_ctor || ok_ctor; - // Ensure epxr in `if let` is not used afterwards - if !is_local_used(cx, if_then, pat_hir_id); - then { - let if_let_type = if some_ctor { "Some" } else { "Ok" }; - // Prepare the error message - let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type); - - // Prepare the help message - let mut applicability = Applicability::MaybeIncorrect; - let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability); - let copied = match cx.typeck_results().expr_ty(let_expr).kind() { - ty::Ref(_, inner, _) => match inner.kind() { - ty::Ref(..) => ".copied()", - _ => "" - } + // Prepare the help message + let mut applicability = Applicability::MaybeIncorrect; + let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability); + let copied = match cx.typeck_results().expr_ty(let_expr).kind() { + ty::Ref(_, inner, _) => match inner.kind() { + ty::Ref(..) => ".copied()", _ => "" - }; + } + _ => "" + }; - span_lint_and_then( - cx, - MANUAL_FLATTEN, - span, - &msg, - |diag| { - let sugg = format!("{}{}.flatten()", arg_snippet, copied); - diag.span_suggestion( - arg.span, - "try", - sugg, - Applicability::MaybeIncorrect, - ); - diag.span_help( - inner_expr.span, - "...and remove the `if let` statement in the for loop", - ); - } - ); - } + span_lint_and_then( + cx, + MANUAL_FLATTEN, + span, + &msg, + |diag| { + let sugg = format!("{}{}.flatten()", arg_snippet, copied); + diag.span_suggestion( + arg.span, + "try", + sugg, + Applicability::MaybeIncorrect, + ); + diag.span_help( + inner_expr.span, + "...and remove the `if let` statement in the for loop", + ); + } + ); } } } diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 86b7d6d989a..68ffcd1abfb 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -92,9 +92,7 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult } fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult { - let stmts = block.stmts.iter().map(stmt_to_expr); - let expr = once(block.expr); - let mut iter = stmts.chain(expr).flatten(); + let mut iter = block.stmts.iter().filter_map(stmt_to_expr).chain(block.expr); never_loop_expr_seq(&mut iter, main_loop_id) } diff --git a/clippy_lints/src/manual_map.rs b/clippy_lints/src/manual_map.rs index 4d8ad566e6b..34a70ca76c6 100644 --- a/clippy_lints/src/manual_map.rs +++ b/clippy_lints/src/manual_map.rs @@ -5,7 +5,7 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function}; use clippy_utils::{ can_move_expr_to_closure, in_constant, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id, - peel_hir_expr_refs, peel_hir_expr_while, CaptureKind, + peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind, }; use rustc_ast::util::parser::PREC_POSTFIX; use rustc_errors::Applicability; @@ -307,16 +307,5 @@ fn get_some_expr( // Checks for the `None` value. fn is_none_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { - match expr.kind { - ExprKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone), - ExprKind::Block( - Block { - stmts: [], - expr: Some(expr), - .. - }, - _, - ) => is_none_expr(cx, expr), - _ => false, - } + matches!(peel_blocks(expr).kind, ExprKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone)) } diff --git a/clippy_lints/src/map_clone.rs b/clippy_lints/src/map_clone.rs index c2b78e21861..174c7da28d3 100644 --- a/clippy_lints/src/map_clone.rs +++ b/clippy_lints/src/map_clone.rs @@ -1,8 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_trait_method; -use clippy_utils::remove_blocks; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; +use clippy_utils::{is_trait_method, peel_blocks}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -60,7 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for MapClone { if let hir::ExprKind::Closure(_, _, body_id, _, _) = args[1].kind; then { let closure_body = cx.tcx.hir().body(body_id); - let closure_expr = remove_blocks(&closure_body.value); + let closure_expr = peel_blocks(&closure_body.value); match closure_body.params[0].pat.kind { hir::PatKind::Ref(inner, hir::Mutability::Not) => if let hir::PatKind::Binding( hir::BindingAnnotation::Unannotated, .., name, None diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index eacbfa54cf7..acac0887226 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -9,7 +9,7 @@ use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, pe use clippy_utils::visitors::is_local_used; use clippy_utils::{ get_parent_expr, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs, - path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, remove_blocks, + path_to_local, path_to_local_id, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, strip_pat_refs, }; use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash}; @@ -658,7 +658,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind; if args.len() == 1; if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind; - let body = remove_blocks(arms[0].body); + let body = peel_blocks(arms[0].body); if path_to_local_id(body, arg); then { @@ -723,7 +723,7 @@ fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], exp return; } let els = arms[1].body; - let els = if is_unit_expr(remove_blocks(els)) { + let els = if is_unit_expr(peel_blocks(els)) { None } else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind { if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() { @@ -1481,7 +1481,7 @@ fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[A let matched_vars = ex.span; let bind_names = arms[0].pat.span; - let match_body = remove_blocks(arms[0].body); + let match_body = peel_blocks(arms[0].body); let mut snippet_body = if match_body.span.from_expansion() { Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string() } else { @@ -1678,7 +1678,7 @@ fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotat if is_lang_ctor(cx, qpath, OptionSome); if let PatKind::Binding(rb, .., ident, _) = first_pat.kind; if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; - if let ExprKind::Call(e, args) = remove_blocks(arm.body).kind; + if let ExprKind::Call(e, args) = peel_blocks(arm.body).kind; if let ExprKind::Path(ref some_path) = e.kind; if is_lang_ctor(cx, some_path, OptionSome) && args.len() == 1; if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind; diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index b7690cf9222..150bafc0f5d 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -1,7 +1,7 @@ use super::{contains_return, BIND_INSTEAD_OF_MAP}; use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet, snippet_with_macro_callsite}; -use clippy_utils::{remove_blocks, visitors::find_all_ret_expressions}; +use clippy_utils::{peel_blocks, visitors::find_all_ret_expressions}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -152,7 +152,7 @@ pub(crate) trait BindInsteadOfMap { match arg.kind { hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => { let closure_body = cx.tcx.hir().body(body_id); - let closure_expr = remove_blocks(&closure_body.value); + let closure_expr = peel_blocks(&closure_body.value); if Self::lint_closure_autofixable(cx, expr, recv, closure_expr, closure_args_span) { true diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index c96c817bb8b..6d8733c08b4 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, reindent_multiline, snippet}; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_trait_method, path_to_local_id, remove_blocks, SpanlessEq}; +use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, SpanlessEq}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -25,7 +25,7 @@ fn is_method<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, method_name: Sy }, hir::ExprKind::Closure(_, _, c, _, _) => { let body = cx.tcx.hir().body(*c); - let closure_expr = remove_blocks(&body.value); + let closure_expr = peel_blocks(&body.value); let arg_id = body.params[0].pat.hir_id; match closure_expr.kind { hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, _, args, _) => { diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs index 81c42de145f..90492ffda3c 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -12,15 +12,7 @@ use super::IMPLICIT_CLONE; pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, span: Span) { if_chain! { if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); - if match method_name { - "to_os_string" => is_diag_item_method(cx, method_def_id, sym::OsStr), - "to_owned" => is_diag_trait_item(cx, method_def_id, sym::ToOwned), - "to_path_buf" => is_diag_item_method(cx, method_def_id, sym::Path), - "to_vec" => cx.tcx.impl_of_method(method_def_id) - .map(|impl_did| Some(impl_did) == cx.tcx.lang_items().slice_alloc_impl()) - == Some(true), - _ => false, - }; + if is_clone_like(cx, method_name, method_def_id); let return_type = cx.typeck_results().expr_ty(expr); let input_type = cx.typeck_results().expr_ty(recv).peel_refs(); if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did)); @@ -38,3 +30,22 @@ pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv } } } + +/// Returns true if the named method can be used to clone the receiver. +/// Note that `to_string` is not flagged by `implicit_clone`. So other lints that call +/// `is_clone_like` and that do flag `to_string` must handle it separately. See, e.g., +/// `is_to_owned_like` in `unnecessary_to_owned.rs`. +pub fn is_clone_like(cx: &LateContext<'_>, method_name: &str, method_def_id: hir::def_id::DefId) -> bool { + match method_name { + "to_os_string" => is_diag_item_method(cx, method_def_id, sym::OsStr), + "to_owned" => is_diag_trait_item(cx, method_def_id, sym::ToOwned), + "to_path_buf" => is_diag_item_method(cx, method_def_id, sym::Path), + "to_vec" => { + cx.tcx + .impl_of_method(method_def_id) + .map(|impl_did| Some(impl_did) == cx.tcx.lang_items().slice_alloc_impl()) + == Some(true) + }, + _ => false, + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 58ec2213535..3c43671dd34 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -56,7 +56,9 @@ mod suspicious_splitn; mod uninit_assumed_init; mod unnecessary_filter_map; mod unnecessary_fold; +mod unnecessary_iter_cloned; mod unnecessary_lazy_eval; +mod unnecessary_to_owned; mod unwrap_or_else_default; mod unwrap_used; mod useless_asref; @@ -1885,6 +1887,32 @@ declare_clippy_lint! { "usages of `str::splitn` that can be replaced with `str::split`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary calls to [`ToOwned::to_owned`](https://doc.rust-lang.org/std/borrow/trait.ToOwned.html#tymethod.to_owned) + /// and other `to_owned`-like functions. + /// + /// ### Why is this bad? + /// The unnecessary calls result in useless allocations. + /// + /// ### Example + /// ```rust + /// let path = std::path::Path::new("x"); + /// foo(&path.to_string_lossy().to_string()); + /// fn foo(s: &str) {} + /// ``` + /// Use instead: + /// ```rust + /// let path = std::path::Path::new("x"); + /// foo(&path.to_string_lossy()); + /// fn foo(s: &str) {} + /// ``` + #[clippy::version = "1.58.0"] + pub UNNECESSARY_TO_OWNED, + perf, + "unnecessary calls to `to_owned`-like functions" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Option<RustcVersion>, @@ -1964,7 +1992,8 @@ impl_lint_pass!(Methods => [ MANUAL_STR_REPEAT, EXTEND_WITH_DRAIN, MANUAL_SPLIT_ONCE, - NEEDLESS_SPLITN + NEEDLESS_SPLITN, + UNNECESSARY_TO_OWNED, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -2007,6 +2036,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { single_char_add_str::check(cx, expr, args); into_iter_on_ref::check(cx, expr, *method_span, method_call.ident.name, args); single_char_pattern::check(cx, expr, method_call.ident.name, args); + unnecessary_to_owned::check(cx, expr, method_call.ident.name, args); }, hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => { let mut info = BinaryExprInfo { diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints/src/methods/option_as_ref_deref.rs index d3f40d26208..fa74a8f3dc3 100644 --- a/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/clippy_lints/src/methods/option_as_ref_deref.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{match_def_path, meets_msrv, msrvs, path_to_local_id, paths, remove_blocks}; +use clippy_utils::{match_def_path, meets_msrv, msrvs, path_to_local_id, paths, peel_blocks}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -53,7 +53,7 @@ pub(super) fn check<'tcx>( }), hir::ExprKind::Closure(_, _, body_id, _, _) => { let closure_body = cx.tcx.hir().body(body_id); - let closure_expr = remove_blocks(&closure_body.value); + let closure_expr = peel_blocks(&closure_body.value); match &closure_expr.kind { hir::ExprKind::MethodCall(_, _, args, _) => { diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints/src/methods/unnecessary_fold.rs index 4c4034437da..47a81199608 100644 --- a/clippy_lints/src/methods/unnecessary_fold.rs +++ b/clippy_lints/src/methods/unnecessary_fold.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_trait_method, path_to_local_id, remove_blocks, strip_pat_refs}; +use clippy_utils::{is_trait_method, path_to_local_id, peel_blocks, strip_pat_refs}; use if_chain::if_chain; use rustc_ast::ast; use rustc_errors::Applicability; @@ -31,7 +31,7 @@ pub(super) fn check( // Extract the body of the closure passed to fold if let hir::ExprKind::Closure(_, _, body_id, _, _) = acc.kind; let closure_body = cx.tcx.hir().body(body_id); - let closure_expr = remove_blocks(&closure_body.value); + let closure_expr = peel_blocks(&closure_body.value); // Check if the closure body is of the form `acc <op> some_expr(x)` if let hir::ExprKind::Binary(ref bin_op, left_expr, right_expr) = closure_expr.kind; diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs new file mode 100644 index 00000000000..8300df03e99 --- /dev/null +++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -0,0 +1,177 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::higher::ForLoop; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait}; +use clippy_utils::{fn_def_id, get_parent_expr, path_to_local_id, usage}; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, HirId, LangItem, Mutability, Pat}; +use rustc_lint::LateContext; +use rustc_middle::{hir::map::Map, ty}; +use rustc_span::{sym, Symbol}; + +use super::UNNECESSARY_TO_OWNED; + +pub fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, receiver: &'tcx Expr<'tcx>) -> bool { + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let Some(callee_def_id) = fn_def_id(cx, parent); + if is_into_iter(cx, callee_def_id); + then { + check_for_loop_iter(cx, parent, method_name, receiver) + } else { + false + } + } +} + +/// Checks whether `expr` is an iterator in a `for` loop and, if so, determines whether the +/// iterated-over items could be iterated over by reference. The reason why `check` above does not +/// include this code directly is so that it can be called from +/// `unnecessary_into_owned::check_into_iter_call_arg`. +pub fn check_for_loop_iter( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + method_name: Symbol, + receiver: &'tcx Expr<'tcx>, +) -> bool { + if_chain! { + if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent)); + if let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent); + let (clone_or_copy_needed, addr_of_exprs) = clone_or_copy_needed(cx, pat, body); + if !clone_or_copy_needed; + if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); + then { + let snippet = if_chain! { + if let ExprKind::MethodCall(maybe_iter_method_name, _, [collection], _) = receiver.kind; + if maybe_iter_method_name.ident.name == sym::iter; + + if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator); + let receiver_ty = cx.typeck_results().expr_ty(receiver); + if implements_trait(cx, receiver_ty, iterator_trait_id, &[]); + if let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty); + + if let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator); + let collection_ty = cx.typeck_results().expr_ty(collection); + if implements_trait(cx, collection_ty, into_iterator_trait_id, &[]); + if let Some(into_iter_item_ty) = get_associated_type(cx, collection_ty, into_iterator_trait_id, "Item"); + + if iter_item_ty == into_iter_item_ty; + if let Some(collection_snippet) = snippet_opt(cx, collection.span); + then { + collection_snippet + } else { + receiver_snippet + } + }; + span_lint_and_then( + cx, + UNNECESSARY_TO_OWNED, + expr.span, + &format!("unnecessary use of `{}`", method_name), + |diag| { + diag.span_suggestion(expr.span, "use", snippet, Applicability::MachineApplicable); + for addr_of_expr in addr_of_exprs { + match addr_of_expr.kind { + ExprKind::AddrOf(_, _, referent) => { + let span = addr_of_expr.span.with_hi(referent.span.lo()); + diag.span_suggestion(span, "remove this `&`", String::new(), Applicability::MachineApplicable); + } + _ => unreachable!(), + } + } + } + ); + return true; + } + } + false +} + +/// The core logic of `check_for_loop_iter` above, this function wraps a use of +/// `CloneOrCopyVisitor`. +fn clone_or_copy_needed( + cx: &LateContext<'tcx>, + pat: &Pat<'tcx>, + body: &'tcx Expr<'tcx>, +) -> (bool, Vec<&'tcx Expr<'tcx>>) { + let mut visitor = CloneOrCopyVisitor { + cx, + binding_hir_ids: pat_bindings(pat), + clone_or_copy_needed: false, + addr_of_exprs: Vec::new(), + }; + visitor.visit_expr(body); + (visitor.clone_or_copy_needed, visitor.addr_of_exprs) +} + +/// Returns a vector of all `HirId`s bound by the pattern. +fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> { + let mut collector = usage::ParamBindingIdCollector { + binding_hir_ids: Vec::new(), + }; + collector.visit_pat(pat); + collector.binding_hir_ids +} + +/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only +/// operations performed on `binding_hir_ids` are: +/// * to take non-mutable references to them +/// * to use them as non-mutable `&self` in method calls +/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true +/// when `CloneOrCopyVisitor` is done visiting. +struct CloneOrCopyVisitor<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + binding_hir_ids: Vec<HirId>, + clone_or_copy_needed: bool, + addr_of_exprs: Vec<&'tcx Expr<'tcx>>, +} + +impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { + type Map = Map<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + walk_expr(self, expr); + if self.is_binding(expr) { + if let Some(parent) = get_parent_expr(self.cx, expr) { + match parent.kind { + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => { + self.addr_of_exprs.push(parent); + return; + }, + ExprKind::MethodCall(_, _, args, _) => { + if_chain! { + if args.iter().skip(1).all(|arg| !self.is_binding(arg)); + if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id); + let method_ty = self.cx.tcx.type_of(method_def_id); + let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder(); + if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not)); + then { + return; + } + } + }, + _ => {}, + } + } + self.clone_or_copy_needed = true; + } + } +} + +impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> { + fn is_binding(&self, expr: &Expr<'tcx>) -> bool { + self.binding_hir_ids + .iter() + .any(|hir_id| path_to_local_id(expr, *hir_id)) + } +} + +/// Returns true if the named method is `IntoIterator::into_iter`. +pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool { + cx.tcx.lang_items().require(LangItem::IntoIterIntoIter) == Ok(callee_def_id) +} diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs new file mode 100644 index 00000000000..c48bacfce0d --- /dev/null +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -0,0 +1,397 @@ +use super::implicit_clone::is_clone_like; +use super::unnecessary_iter_cloned::{self, is_into_iter}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs}; +use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item}; +use rustc_errors::Applicability; +use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::mir::Mutability; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref}; +use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef}; +use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty}; +use rustc_span::{sym, Symbol}; +use std::cmp::max; + +use super::UNNECESSARY_TO_OWNED; + +pub fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, args: &'tcx [Expr<'tcx>]) { + if_chain! { + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if let [receiver] = args; + then { + if is_cloned_or_copied(cx, method_name, method_def_id) { + unnecessary_iter_cloned::check(cx, expr, method_name, receiver); + } else if is_to_owned_like(cx, method_name, method_def_id) { + // At this point, we know the call is of a `to_owned`-like function. The functions + // `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary + // based on its context, that is, whether it is a referent in an `AddrOf` expression, an + // argument in a `into_iter` call, or an argument in the call of some other function. + if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) { + return; + } + if check_into_iter_call_arg(cx, expr, method_name, receiver) { + return; + } + check_other_call_arg(cx, expr, method_name, receiver); + } + } + } +} + +/// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its +/// call of a `to_owned`-like function is unnecessary. +#[allow(clippy::too_many_lines)] +fn check_addr_of_expr( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + method_name: Symbol, + method_def_id: DefId, + receiver: &'tcx Expr<'tcx>, +) -> bool { + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind; + let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>(); + if let Some(target_ty) = match adjustments[..] + { + // For matching uses of `Cow::from` + [ + Adjustment { + kind: Adjust::Deref(None), + .. + }, + Adjustment { + kind: Adjust::Borrow(_), + target: target_ty, + }, + ] + // For matching uses of arrays + | [ + Adjustment { + kind: Adjust::Deref(None), + .. + }, + Adjustment { + kind: Adjust::Borrow(_), + .. + }, + Adjustment { + kind: Adjust::Pointer(_), + target: target_ty, + }, + ] + // For matching everything else + | [ + Adjustment { + kind: Adjust::Deref(None), + .. + }, + Adjustment { + kind: Adjust::Deref(Some(OverloadedDeref { .. })), + .. + }, + Adjustment { + kind: Adjust::Borrow(_), + target: target_ty, + }, + ] => Some(target_ty), + _ => None, + }; + let receiver_ty = cx.typeck_results().expr_ty(receiver); + // Only flag cases where the receiver is copyable or the method is `Cow::into_owned`. This + // restriction is to ensure there is not overlap between `redundant_clone` and this lint. + if is_copy(cx, receiver_ty) || is_cow_into_owned(cx, method_name, method_def_id); + if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); + then { + let (target_ty, n_target_refs) = peel_mid_ty_refs(target_ty); + let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty); + if receiver_ty == target_ty && n_target_refs >= n_receiver_refs { + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + parent.span, + &format!("unnecessary use of `{}`", method_name), + "use", + format!("{:&>width$}{}", "", receiver_snippet, width = n_target_refs - n_receiver_refs), + Applicability::MachineApplicable, + ); + return true; + } + if_chain! { + if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref); + if implements_trait(cx, receiver_ty, deref_trait_id, &[]); + if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty); + then { + if n_receiver_refs > 0 { + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + parent.span, + &format!("unnecessary use of `{}`", method_name), + "use", + receiver_snippet, + Applicability::MachineApplicable, + ); + } else { + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + expr.span.with_lo(receiver.span.hi()), + &format!("unnecessary use of `{}`", method_name), + "remove this", + String::new(), + Applicability::MachineApplicable, + ); + } + return true; + } + } + if_chain! { + if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef); + if implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)]); + then { + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + parent.span, + &format!("unnecessary use of `{}`", method_name), + "use", + format!("{}.as_ref()", receiver_snippet), + Applicability::MachineApplicable, + ); + return true; + } + } + } + } + false +} + +/// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its +/// call of a `to_owned`-like function is unnecessary. +fn check_into_iter_call_arg( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + method_name: Symbol, + receiver: &'tcx Expr<'tcx>, +) -> bool { + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let Some(callee_def_id) = fn_def_id(cx, parent); + if is_into_iter(cx, callee_def_id); + if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator); + let parent_ty = cx.typeck_results().expr_ty(parent); + if implements_trait(cx, parent_ty, iterator_trait_id, &[]); + if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty); + if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); + then { + if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver) { + return true; + } + let cloned_or_copied = if is_copy(cx, item_ty) { + "copied" + } else { + "cloned" + }; + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + parent.span, + &format!("unnecessary use of `{}`", method_name), + "use", + format!("{}.iter().{}()", receiver_snippet, cloned_or_copied), + Applicability::MachineApplicable, + ); + return true; + } + } + false +} + +/// Checks whether `expr` is an argument in a function call and, if so, determines whether its call +/// of a `to_owned`-like function is unnecessary. +fn check_other_call_arg( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + method_name: Symbol, + receiver: &'tcx Expr<'tcx>, +) -> bool { + if_chain! { + if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr); + if let Some((callee_def_id, call_substs, call_args)) = get_callee_substs_and_args(cx, maybe_call); + let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); + if let Some(i) = call_args.iter().position(|arg| arg.hir_id == maybe_arg.hir_id); + if let Some(input) = fn_sig.inputs().get(i); + let (input, n_refs) = peel_mid_ty_refs(input); + if let (trait_predicates, projection_predicates) = get_input_traits_and_projections(cx, callee_def_id, input); + if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait(); + if let [trait_predicate] = trait_predicates + .iter() + .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id) + .collect::<Vec<_>>()[..]; + if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref); + if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef); + let receiver_ty = cx.typeck_results().expr_ty(receiver); + // If the callee has type parameters, they could appear in `projection_predicate.ty` or the + // types of `trait_predicate.trait_ref.substs`. + if if trait_predicate.def_id() == deref_trait_id { + if let [projection_predicate] = projection_predicates[..] { + let normalized_ty = + cx.tcx.subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.ty); + implements_trait(cx, receiver_ty, deref_trait_id, &[]) + && get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(normalized_ty) + } else { + false + } + } else if trait_predicate.def_id() == as_ref_trait_id { + let composed_substs = compose_substs( + cx, + &trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..], + call_substs + ); + implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs) + } else { + false + }; + // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match + // `Target = T`. + if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id; + let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 }); + if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); + then { + span_lint_and_sugg( + cx, + UNNECESSARY_TO_OWNED, + maybe_arg.span, + &format!("unnecessary use of `{}`", method_name), + "use", + format!("{:&>width$}{}", "", receiver_snippet, width = n_refs), + Applicability::MachineApplicable, + ); + return true; + } + } + false +} + +/// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such +/// expression found (if any) along with the immediately prior expression. +fn skip_addr_of_ancestors( + cx: &LateContext<'tcx>, + mut expr: &'tcx Expr<'tcx>, +) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + while let Some(parent) = get_parent_expr(cx, expr) { + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind { + expr = parent; + } else { + return Some((parent, expr)); + } + } + None +} + +/// Checks whether an expression is a function or method call and, if so, returns its `DefId`, +/// `Substs`, and arguments. +fn get_callee_substs_and_args( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, +) -> Option<(DefId, SubstsRef<'tcx>, &'tcx [Expr<'tcx>])> { + if_chain! { + if let ExprKind::Call(callee, args) = expr.kind; + let callee_ty = cx.typeck_results().expr_ty(callee); + if let ty::FnDef(callee_def_id, _) = callee_ty.kind(); + then { + let substs = cx.typeck_results().node_substs(callee.hir_id); + return Some((*callee_def_id, substs, args)); + } + } + if_chain! { + if let ExprKind::MethodCall(_, _, args, _) = expr.kind; + if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + then { + let substs = cx.typeck_results().node_substs(expr.hir_id); + return Some((method_def_id, substs, args)); + } + } + None +} + +/// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type. +fn get_input_traits_and_projections( + cx: &LateContext<'tcx>, + callee_def_id: DefId, + input: Ty<'tcx>, +) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) { + let mut trait_predicates = Vec::new(); + let mut projection_predicates = Vec::new(); + for (predicate, _) in cx.tcx.predicates_of(callee_def_id).predicates.iter() { + // `substs` should have 1 + n elements. The first is the type on the left hand side of an + // `as`. The remaining n are trait parameters. + let is_input_substs = |substs: SubstsRef<'tcx>| { + if_chain! { + if let Some(arg) = substs.iter().next(); + if let GenericArgKind::Type(arg_ty) = arg.unpack(); + if arg_ty == input; + then { + true + } else { + false + } + } + }; + match predicate.kind().skip_binder() { + PredicateKind::Trait(trait_predicate) => { + if is_input_substs(trait_predicate.trait_ref.substs) { + trait_predicates.push(trait_predicate); + } + }, + PredicateKind::Projection(projection_predicate) => { + if is_input_substs(projection_predicate.projection_ty.substs) { + projection_predicates.push(projection_predicate); + } + }, + _ => {}, + } + } + (trait_predicates, projection_predicates) +} + +/// Composes two substitutions by applying the latter to the types of the former. +fn compose_substs(cx: &LateContext<'tcx>, left: &[GenericArg<'tcx>], right: SubstsRef<'tcx>) -> Vec<GenericArg<'tcx>> { + left.iter() + .map(|arg| { + if let GenericArgKind::Type(arg_ty) = arg.unpack() { + let normalized_ty = cx.tcx.subst_and_normalize_erasing_regions(right, cx.param_env, arg_ty); + GenericArg::from(normalized_ty) + } else { + *arg + } + }) + .collect() +} + +/// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`. +fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool { + (method_name.as_str() == "cloned" || method_name.as_str() == "copied") + && is_diag_trait_item(cx, method_def_id, sym::Iterator) +} + +/// Returns true if the named method can be used to convert the receiver to its "owned" +/// representation. +fn is_to_owned_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool { + is_clone_like(cx, &*method_name.as_str(), method_def_id) + || is_cow_into_owned(cx, method_name, method_def_id) + || is_to_string(cx, method_name, method_def_id) +} + +/// Returns true if the named method is `Cow::into_owned`. +fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool { + method_name.as_str() == "into_owned" && is_diag_item_method(cx, method_def_id, sym::Cow) +} + +/// Returns true if the named method is `ToString::to_string`. +fn is_to_string(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool { + method_name.as_str() == "to_string" && is_diag_trait_item(cx, method_def_id, sym::ToString) +} diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index 3709f3948c2..d391fbecf82 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -6,10 +6,10 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::higher; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; -use clippy_utils::{is_else_clause, is_expn_of}; +use clippy_utils::{get_parent_node, is_else_clause, is_expn_of, peel_blocks, peel_blocks_with_stmt}; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp}; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Node, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Spanned; @@ -74,6 +74,36 @@ declare_clippy_lint! { declare_lint_pass!(NeedlessBool => [NEEDLESS_BOOL]); +fn condition_needs_parentheses(e: &Expr<'_>) -> bool { + let mut inner = e; + while let ExprKind::Binary(_, i, _) + | ExprKind::Call(i, _) + | ExprKind::Cast(i, _) + | ExprKind::Type(i, _) + | ExprKind::Index(i, _) = inner.kind + { + if matches!( + i.kind, + ExprKind::Block(..) + | ExprKind::ConstBlock(..) + | ExprKind::If(..) + | ExprKind::Loop(..) + | ExprKind::Match(..) + ) { + return true; + } + inner = i; + } + false +} + +fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool { + matches!( + get_parent_node(cx.tcx, id), + Some(Node::Stmt(..) | Node::Block(Block { stmts: &[], .. })) + ) +} + impl<'tcx> LateLintPass<'tcx> for NeedlessBool { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { use self::Expression::{Bool, RetBool}; @@ -99,6 +129,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { snip = snip.blockify(); } + if condition_needs_parentheses(cond) && is_parent_stmt(cx, e.hir_id) { + snip = snip.maybe_par(); + } + span_lint_and_sugg( cx, NEEDLESS_BOOL, @@ -109,8 +143,8 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { applicability, ); }; - if let ExprKind::Block(then, _) = then.kind { - match (fetch_bool_block(then), fetch_bool_expr(r#else)) { + if let Some((a, b)) = fetch_bool_block(then).and_then(|a| Some((a, fetch_bool_block(r#else)?))) { + match (a, b) { (RetBool(true), RetBool(true)) | (Bool(true), Bool(true)) => { span_lint( cx, @@ -133,8 +167,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { (Bool(false), Bool(true)) => reduce(false, true), _ => (), } - } else { - panic!("IfExpr `then` node is not an `ExprKind::Block`"); } } } @@ -237,8 +269,6 @@ fn check_comparison<'a, 'tcx>( right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &str)>, no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &str)>, ) { - use self::Expression::{Bool, Other}; - if let ExprKind::Binary(op, left_side, right_side) = e.kind { let (l_ty, r_ty) = ( cx.typeck_results().expr_ty(left_side), @@ -270,19 +300,19 @@ fn check_comparison<'a, 'tcx>( } match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { - (Bool(true), Other) => left_true.map_or((), |(h, m)| { + (Some(true), None) => left_true.map_or((), |(h, m)| { suggest_bool_comparison(cx, e, right_side, applicability, m, h); }), - (Other, Bool(true)) => right_true.map_or((), |(h, m)| { + (None, Some(true)) => right_true.map_or((), |(h, m)| { suggest_bool_comparison(cx, e, left_side, applicability, m, h); }), - (Bool(false), Other) => left_false.map_or((), |(h, m)| { + (Some(false), None) => left_false.map_or((), |(h, m)| { suggest_bool_comparison(cx, e, right_side, applicability, m, h); }), - (Other, Bool(false)) => right_false.map_or((), |(h, m)| { + (None, Some(false)) => right_false.map_or((), |(h, m)| { suggest_bool_comparison(cx, e, left_side, applicability, m, h); }), - (Other, Other) => no_literal.map_or((), |(h, m)| { + (None, None) => no_literal.map_or((), |(h, m)| { let left_side = Sugg::hir_with_applicability(cx, left_side, "..", &mut applicability); let right_side = Sugg::hir_with_applicability(cx, right_side, "..", &mut applicability); span_lint_and_sugg( @@ -331,41 +361,20 @@ fn suggest_bool_comparison<'a, 'tcx>( enum Expression { Bool(bool), RetBool(bool), - Other, } -fn fetch_bool_block(block: &Block<'_>) -> Expression { - match (&*block.stmts, block.expr.as_ref()) { - (&[], Some(e)) => fetch_bool_expr(&**e), - (&[ref e], None) => { - if let StmtKind::Semi(e) = e.kind { - if let ExprKind::Ret(_) = e.kind { - fetch_bool_expr(e) - } else { - Expression::Other - } - } else { - Expression::Other - } - }, - _ => Expression::Other, +fn fetch_bool_block(expr: &Expr<'_>) -> Option<Expression> { + match peel_blocks_with_stmt(expr).kind { + ExprKind::Ret(Some(ret)) => Some(Expression::RetBool(fetch_bool_expr(ret)?)), + _ => Some(Expression::Bool(fetch_bool_expr(expr)?)), } } -fn fetch_bool_expr(expr: &Expr<'_>) -> Expression { - match expr.kind { - ExprKind::Block(block, _) => fetch_bool_block(block), - ExprKind::Lit(ref lit_ptr) => { - if let LitKind::Bool(value) = lit_ptr.node { - Expression::Bool(value) - } else { - Expression::Other - } - }, - ExprKind::Ret(Some(expr)) => match fetch_bool_expr(expr) { - Expression::Bool(value) => Expression::RetBool(value), - _ => Expression::Other, - }, - _ => Expression::Other, +fn fetch_bool_expr(expr: &Expr<'_>) -> Option<bool> { + if let ExprKind::Lit(ref lit_ptr) = peel_blocks(expr).kind { + if let LitKind::Bool(value) = lit_ptr.node { + return Some(value); + } } + None } diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index 5b098659377..094a3f111ba 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -73,7 +73,7 @@ fn contains_assign_expr<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> seen } -#[derive(Debug, Clone)] +#[derive(Debug)] struct LocalAssign { lhs_id: HirId, lhs_span: Span, @@ -154,14 +154,15 @@ fn assignment_suggestions<'tcx>( assignments.push(assign); } - let suggestions = assignments.clone() - .into_iter() + let suggestions = assignments + .iter() .map(|assignment| Some((assignment.span.until(assignment.rhs_span), String::new()))) - .chain( - assignments - .into_iter() - .map(|assignment| Some((assignment.rhs_span.shrink_to_hi().with_hi(assignment.span.hi()), String::new()))) - ) + .chain(assignments.iter().map(|assignment| { + Some(( + assignment.rhs_span.shrink_to_hi().with_hi(assignment.span.hi()), + String::new(), + )) + })) .collect::<Option<Vec<(Span, String)>>>()?; let applicability = if suggestions.len() > 1 { diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 6fcc9ca29b9..9d5babc5de8 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::is_lint_allowed; +use clippy_utils::peel_blocks; use clippy_utils::source::snippet_opt; use clippy_utils::ty::has_drop; use rustc_errors::Applicability; @@ -114,7 +115,7 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if expr.span.from_expansion() { return false; } - match expr.kind { + match peel_blocks(expr).kind { ExprKind::Lit(..) | ExprKind::Closure(..) => true, ExprKind::Path(..) => !has_drop(cx, cx.typeck_results().expr_ty(expr)), ExprKind::Index(a, b) | ExprKind::Binary(_, a, b) => has_no_effect(cx, a) && has_no_effect(cx, b), @@ -150,9 +151,6 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { false } }, - ExprKind::Block(block, _) => { - block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| has_no_effect(cx, expr)) - }, _ => false, } } diff --git a/clippy_lints/src/non_send_fields_in_send_ty.rs b/clippy_lints/src/non_send_fields_in_send_ty.rs index bba542ce8ca..203f03d3603 100644 --- a/clippy_lints/src/non_send_fields_in_send_ty.rs +++ b/clippy_lints/src/non_send_fields_in_send_ty.rs @@ -13,24 +13,28 @@ use rustc_span::sym; declare_clippy_lint! { /// ### What it does - /// Warns about fields in struct implementing `Send` that are neither `Send` nor `Copy`. + /// This lint warns about a `Send` implementation for a type that + /// contains fields that are not safe to be sent across threads. + /// It tries to detect fields that can cause a soundness issue + /// when sent to another thread (e.g., `Rc`) while allowing `!Send` fields + /// that are expected to exist in a `Send` type, such as raw pointers. /// /// ### Why is this bad? - /// Sending the struct to another thread will transfer the ownership to - /// the new thread by dropping in the current thread during the transfer. - /// This causes soundness issues for non-`Send` fields, as they are also - /// dropped and might not be set up to handle this. + /// Sending the struct to another thread effectively sends all of its fields, + /// and the fields that do not implement `Send` can lead to soundness bugs + /// such as data races when accessed in a thread + /// that is different from the thread that created it. /// /// See: /// * [*The Rustonomicon* about *Send and Sync*](https://doc.rust-lang.org/nomicon/send-and-sync.html) /// * [The documentation of `Send`](https://doc.rust-lang.org/std/marker/trait.Send.html) /// /// ### Known Problems - /// Data structures that contain raw pointers may cause false positives. - /// They are sometimes safe to be sent across threads but do not implement - /// the `Send` trait. This lint has a heuristic to filter out basic cases - /// such as `Vec<*const T>`, but it's not perfect. Feel free to create an - /// issue if you have a suggestion on how this heuristic can be improved. + /// This lint relies on heuristics to distinguish types that are actually + /// unsafe to be sent across threads and `!Send` types that are expected to + /// exist in `Send` type. Its rule can filter out basic cases such as + /// `Vec<*const T>`, but it's not perfect. Feel free to create an issue if + /// you have a suggestion on how this heuristic can be improved. /// /// ### Example /// ```rust,ignore @@ -46,8 +50,8 @@ declare_clippy_lint! { /// or specify correct bounds on generic type parameters (`T: Send`). #[clippy::version = "1.57.0"] pub NON_SEND_FIELDS_IN_SEND_TY, - suspicious, - "there is field that does not implement `Send` in a `Send` struct" + nursery, + "there is a field that is not safe to be sent to another thread in a `Send` struct" } #[derive(Copy, Clone)] @@ -120,14 +124,14 @@ impl<'tcx> LateLintPass<'tcx> for NonSendFieldInSendTy { NON_SEND_FIELDS_IN_SEND_TY, item.span, &format!( - "this implementation is unsound, as some fields in `{}` are `!Send`", + "some fields in `{}` are not safe to be sent to another thread", snippet(cx, hir_impl.self_ty.span, "Unknown") ), |diag| { for field in non_send_fields { diag.span_note( field.def.span, - &format!("the type of field `{}` is `!Send`", field.def.ident.name), + &format!("it is not safe to send field `{}` to another thread", field.def.ident.name), ); match field.generic_params.len() { diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 262be17f617..953de0f72a8 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -1,15 +1,14 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::higher; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{ - can_move_expr_to_closure, eager_or_lazy, in_constant, is_else_clause, is_lang_ctor, peel_hir_expr_while, - CaptureKind, + can_move_expr_to_closure, eager_or_lazy, higher, in_constant, is_else_clause, is_lang_ctor, peel_blocks, + peel_hir_expr_while, CaptureKind, }; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionSome; -use rustc_hir::{def::Res, BindingAnnotation, Block, Expr, ExprKind, Mutability, PatKind, Path, QPath, UnOp}; +use rustc_hir::{def::Res, BindingAnnotation, Expr, ExprKind, Mutability, PatKind, Path, QPath, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; @@ -86,28 +85,6 @@ struct OptionIfLetElseOccurence { none_expr: String, } -/// Extracts the body of a given arm. If the arm contains only an expression, -/// then it returns the expression. Otherwise, it returns the entire block -fn extract_body_from_expr<'a>(expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> { - if let ExprKind::Block( - Block { - stmts: block_stmts, - expr: Some(block_expr), - .. - }, - _, - ) = expr.kind - { - if let [] = block_stmts { - Some(block_expr) - } else { - Some(expr) - } - } else { - None - } -} - fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: bool, as_mut: bool) -> String { format!( "{}{}", @@ -135,7 +112,7 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> if !is_result_ok(cx, let_expr); // Don't lint on Result::ok because a different lint does it already if let PatKind::TupleStruct(struct_qpath, [inner_pat], _) = &let_pat.kind; if is_lang_ctor(cx, struct_qpath, OptionSome); - if let PatKind::Binding(bind_annotation, _, id, _) = &inner_pat.kind; + if let PatKind::Binding(bind_annotation, _, id, None) = &inner_pat.kind; if let Some(some_captures) = can_move_expr_to_closure(cx, if_then); if let Some(none_captures) = can_move_expr_to_closure(cx, if_else); if some_captures @@ -145,8 +122,8 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> then { let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" }; - let some_body = extract_body_from_expr(if_then)?; - let none_body = extract_body_from_expr(if_else)?; + let some_body = peel_blocks(if_then); + let none_body = peel_blocks(if_else); let method_sugg = if eager_or_lazy::switch_to_eager_eval(cx, none_body) { "map_or" } else { "map_or_else" }; let capture_name = id.name.to_ident_string(); let (as_ref, as_mut) = match &let_expr.kind { diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index a5531993ee6..c765c8962cf 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -1,14 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher; -use clippy_utils::is_lang_ctor; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{eq_expr_value, path_to_local, path_to_local_id}; +use clippy_utils::{eq_expr_value, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultOk}; -use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, PatKind, StmtKind}; +use rustc_hir::{BindingAnnotation, Expr, ExprKind, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; @@ -68,14 +67,8 @@ impl QuestionMark { let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability); let mut replacement: Option<String> = None; if let Some(else_inner) = r#else { - if_chain! { - if let ExprKind::Block(block, None) = &else_inner.kind; - if block.stmts.is_empty(); - if let Some(block_expr) = &block.expr; - if eq_expr_value(cx, subject, block_expr); - then { - replacement = Some(format!("Some({}?)", receiver_str)); - } + if eq_expr_value(cx, subject, peel_blocks(else_inner)) { + replacement = Some(format!("Some({}?)", receiver_str)); } } else if Self::moves_by_default(cx, subject) && !matches!(subject.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) @@ -110,10 +103,7 @@ impl QuestionMark { if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind; let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut); - if let ExprKind::Block(block, None) = if_then.kind; - if block.stmts.is_empty(); - if let Some(trailing_expr) = &block.expr; - if path_to_local_id(trailing_expr, bind_id); + if path_to_local_id(peel_blocks(if_then), bind_id); then { let mut applicability = Applicability::MachineApplicable; let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); @@ -159,14 +149,7 @@ impl QuestionMark { } fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { - match expression.kind { - ExprKind::Block(block, _) => { - if let Some(return_expression) = Self::return_expression(block) { - return Self::expression_returns_none(cx, return_expression); - } - - false - }, + match peel_blocks_with_stmt(expression).kind { ExprKind::Ret(Some(expr)) => Self::expression_returns_none(cx, expr), ExprKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone), _ => false, @@ -174,44 +157,12 @@ impl QuestionMark { } fn expression_returns_unmodified_err(cx: &LateContext<'_>, expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool { - match expr.kind { - ExprKind::Block(block, _) => { - if let Some(return_expression) = Self::return_expression(block) { - return Self::expression_returns_unmodified_err(cx, return_expression, cond_expr); - } - - false - }, + match peel_blocks_with_stmt(expr).kind { ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(cx, ret_expr, cond_expr), - ExprKind::Path(_) => path_to_local(expr) == path_to_local(cond_expr), + ExprKind::Path(_) => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr), _ => false, } } - - fn return_expression<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { - // Check if last expression is a return statement. Then, return the expression - if_chain! { - if block.stmts.len() == 1; - if let Some(expr) = block.stmts.iter().last(); - if let StmtKind::Semi(expr) = expr.kind; - if let ExprKind::Ret(Some(ret_expr)) = expr.kind; - - then { - return Some(ret_expr); - } - } - - // Check for `return` without a semicolon. - if_chain! { - if block.stmts.is_empty(); - if let Some(ExprKind::Ret(Some(ret_expr))) = block.expr.as_ref().map(|e| &e.kind); - then { - return Some(ret_expr); - } - } - - None - } } impl<'tcx> LateLintPass<'tcx> for QuestionMark { diff --git a/clippy_lints/src/return_self_not_must_use.rs b/clippy_lints/src/return_self_not_must_use.rs new file mode 100644 index 00000000000..1118da6c8cb --- /dev/null +++ b/clippy_lints/src/return_self_not_must_use.rs @@ -0,0 +1,105 @@ +use clippy_utils::{diagnostics::span_lint, must_use_attr, nth_arg, return_ty}; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{Body, FnDecl, HirId, TraitItem, TraitItemKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when a method returning `Self` doesn't have the `#[must_use]` attribute. + /// + /// ### Why is this bad? + /// It prevents to "forget" to use the newly created value. + /// + /// ### Limitations + /// This lint is only applied on methods taking a `self` argument. It would be mostly noise + /// if it was added on constructors for example. + /// + /// ### Example + /// ```rust + /// pub struct Bar; + /// + /// impl Bar { + /// // Bad + /// pub fn bar(&self) -> Self { + /// Self + /// } + /// + /// // Good + /// #[must_use] + /// pub fn foo(&self) -> Self { + /// Self + /// } + /// } + /// ``` + #[clippy::version = "1.59.0"] + pub RETURN_SELF_NOT_MUST_USE, + suspicious, + "missing `#[must_use]` annotation on a method returning `Self`" +} + +declare_lint_pass!(ReturnSelfNotMustUse => [RETURN_SELF_NOT_MUST_USE]); + +fn check_method(cx: &LateContext<'tcx>, decl: &'tcx FnDecl<'tcx>, fn_def: LocalDefId, span: Span, hir_id: HirId) { + if_chain! { + // If it comes from an external macro, better ignore it. + if !in_external_macro(cx.sess(), span); + if decl.implicit_self.has_implicit_self(); + // We only show this warning for public exported methods. + if cx.access_levels.is_exported(fn_def); + if cx.tcx.visibility(fn_def.to_def_id()).is_public(); + // No need to warn if the attribute is already present. + if must_use_attr(cx.tcx.hir().attrs(hir_id)).is_none(); + let ret_ty = return_ty(cx, hir_id); + let self_arg = nth_arg(cx, hir_id, 0); + // If `Self` has the same type as the returned type, then we want to warn. + // + // For this check, we don't want to remove the reference on the returned type because if + // there is one, we shouldn't emit a warning! + if self_arg.peel_refs() == ret_ty; + + then { + span_lint( + cx, + RETURN_SELF_NOT_MUST_USE, + span, + "missing `#[must_use]` attribute on a method returning `Self`", + ); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for ReturnSelfNotMustUse { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'tcx>, + _: &'tcx Body<'tcx>, + span: Span, + hir_id: HirId, + ) { + if_chain! { + // We are only interested in methods, not in functions or associated functions. + if matches!(kind, FnKind::Method(_, _, _)); + if let Some(fn_def) = cx.tcx.hir().opt_local_def_id(hir_id); + if let Some(impl_def) = cx.tcx.impl_of_method(fn_def.to_def_id()); + // We don't want this method to be te implementation of a trait because the + // `#[must_use]` should be put on the trait definition directly. + if cx.tcx.trait_id_of_impl(impl_def).is_none(); + + then { + check_method(cx, decl, fn_def, span, hir_id); + } + } + } + + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'tcx>) { + if let TraitItemKind::Fn(ref sig, _) = item.kind { + check_method(cx, sig.decl, item.def_id, item.span, item.hir_id()); + } + } +} diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index c2163a24b7f..73600a8a087 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::SpanlessEq; use clippy_utils::{get_parent_expr, is_lint_allowed, match_function_call, method_calls, paths}; +use clippy_utils::{peel_blocks, SpanlessEq}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, QPath}; @@ -201,7 +201,7 @@ fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { } fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool { - match src.kind { + match peel_blocks(src).kind { ExprKind::Binary( Spanned { node: BinOpKind::Add, .. @@ -209,9 +209,6 @@ fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool { left, _, ) => SpanlessEq::new(cx).eq_expr(target, left), - ExprKind::Block(block, _) => { - block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| is_add(cx, expr, target)) - }, _ => false, } } diff --git a/clippy_lints/src/types/mod.rs b/clippy_lints/src/types/mod.rs index 5a7ef760a30..481e5957435 100644 --- a/clippy_lints/src/types/mod.rs +++ b/clippy_lints/src/types/mod.rs @@ -350,16 +350,28 @@ impl<'tcx> LateLintPass<'tcx> for Types { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { match item.kind { - ImplItemKind::Const(ty, _) | ImplItemKind::TyAlias(ty) => self.check_ty( - cx, - ty, - CheckTyContext { - is_in_trait_impl: true, - ..CheckTyContext::default() - }, - ), - // methods are covered by check_fn - ImplItemKind::Fn(..) => (), + ImplItemKind::Const(ty, _) => { + let is_in_trait_impl = if let Some(hir::Node::Item(item)) = + cx.tcx.hir().find(cx.tcx.hir().get_parent_item(item.hir_id())) + { + matches!(item.kind, ItemKind::Impl(hir::Impl { of_trait: Some(_), .. })) + } else { + false + }; + + self.check_ty( + cx, + ty, + CheckTyContext { + is_in_trait_impl, + ..CheckTyContext::default() + }, + ); + }, + // Methods are covered by check_fn. + // Type aliases are ignored because oftentimes it's impossible to + // make type alias declaration in trait simpler, see #1013 + ImplItemKind::Fn(..) | ImplItemKind::TyAlias(..) => (), } } @@ -417,6 +429,14 @@ impl Types { } fn check_fn_decl(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, context: CheckTyContext) { + // Ignore functions in trait implementations as they are usually forced by the trait definition. + // + // FIXME: idially we would like to warn *if the compicated type can be simplified*, but it's hard to + // check. + if context.is_in_trait_impl { + return; + } + for input in decl.inputs { self.check_ty(cx, input, context); } @@ -435,12 +455,12 @@ impl Types { return; } - if !context.is_nested_call && type_complexity::check(cx, hir_ty, self.type_complexity_threshold) { + // Skip trait implementations; see issue #605. + if context.is_in_trait_impl { return; } - // Skip trait implementations; see issue #605. - if context.is_in_trait_impl { + if !context.is_nested_call && type_complexity::check(cx, hir_ty, self.type_complexity_threshold) { return; } diff --git a/clippy_lints/src/unit_return_expecting_ord.rs b/clippy_lints/src/unit_return_expecting_ord.rs index 9fb8f236899..fe35ff33d35 100644 --- a/clippy_lints/src/unit_return_expecting_ord.rs +++ b/clippy_lints/src/unit_return_expecting_ord.rs @@ -169,7 +169,7 @@ impl<'tcx> LateLintPass<'tcx> for UnitReturnExpectingOrd { trait_name ), Some(last_semi), - &"probably caused by this trailing semicolon".to_string(), + "probably caused by this trailing semicolon", ); }, None => {}, diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index 1f97c8ba7e6..81e9c2e15c9 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -1,11 +1,10 @@ use clippy_utils::consts::{constant_simple, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::higher; use clippy_utils::source::snippet; use clippy_utils::ty::match_type; use clippy_utils::{ - is_else_clause, is_expn_of, is_expr_path_def_path, is_lint_allowed, match_def_path, method_calls, path_to_res, - paths, SpanlessEq, + higher, is_else_clause, is_expn_of, is_expr_path_def_path, is_lint_allowed, match_def_path, method_calls, + path_to_res, paths, peel_blocks_with_stmt, SpanlessEq, }; use if_chain::if_chain; use rustc_ast as ast; @@ -662,10 +661,7 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { if and_then_args.len() == 5; if let ExprKind::Closure(_, _, body_id, _, _) = &and_then_args[4].kind; let body = cx.tcx.hir().body(*body_id); - if let ExprKind::Block(block, _) = &body.value.kind; - let stmts = &block.stmts; - if stmts.len() == 1 && block.expr.is_none(); - if let StmtKind::Semi(only_expr) = &stmts[0].kind; + let only_expr = peel_blocks_with_stmt(&body.value); if let ExprKind::MethodCall(ps, _, span_call_args, _) = &only_expr.kind; then { let and_then_snippets = get_and_then_snippets(cx, and_then_args); diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 8051c58bad7..7707eebd622 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -8,6 +8,11 @@ //! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such //! a simple mistake) +use crate::utils::internal_lints::{extract_clippy_version_value, is_lint_ref_type}; + +use clippy_utils::diagnostics::span_lint; +use clippy_utils::ty::{match_type, walk_ptrs_ty_depth}; +use clippy_utils::{last_path_segment, match_def_path, match_function_call, match_path, paths}; use if_chain::if_chain; use rustc_ast as ast; use rustc_data_structures::fx::FxHashMap; @@ -25,12 +30,6 @@ use std::fs::{self, OpenOptions}; use std::io::prelude::*; use std::path::Path; -use crate::utils::internal_lints::{extract_clippy_version_value, is_lint_ref_type}; -use clippy_utils::{ - diagnostics::span_lint, last_path_segment, match_def_path, match_function_call, match_path, paths, ty::match_type, - ty::walk_ptrs_ty_depth, -}; - /// This is the output file of the lint collector. const OUTPUT_FILE: &str = "../util/gh-pages/lints.json"; /// These lints are excluded from the export. |
