From 62ac4bd1b2b94cb97f61d7f42fdd4fcd8794d2b0 Mon Sep 17 00:00:00 2001 From: nahuakang Date: Sun, 7 Feb 2021 13:35:27 +0100 Subject: [PATCH 01/22] create loops dir; arrange manual_flatten lint and utils --- clippy_lints/src/loops/manual_flatten.rs | 78 +++++++++++++ clippy_lints/src/{loops.rs => loops/mod.rs} | 121 ++------------------ clippy_lints/src/loops/utils.rs | 40 +++++++ 3 files changed, 127 insertions(+), 112 deletions(-) create mode 100644 clippy_lints/src/loops/manual_flatten.rs rename clippy_lints/src/{loops.rs => loops/mod.rs} (95%) create mode 100644 clippy_lints/src/loops/utils.rs diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs new file mode 100644 index 00000000000..b5881a1b8c4 --- /dev/null +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -0,0 +1,78 @@ +use super::utils::make_iterator_snippet; +use crate::utils::{is_ok_ctor, is_some_ctor, path_to_local_id, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, StmtKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the +/// iterator element is used. +pub(super) fn lint<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + span: Span, +) { + if let ExprKind::Block(ref 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 + }; + + if_chain! { + if let Some(inner_expr) = inner_expr; + if let ExprKind::Match( + ref match_expr, ref match_arms, MatchSource::IfLetDesugar{ contains_else_clause: false } + ) = inner_expr.kind; + // 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(match_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(QPath::Resolved(None, path), _, _) = match_arms[0].pat.kind; + let some_ctor = is_some_ctor(cx, path.res); + let ok_ctor = is_ok_ctor(cx, path.res); + if some_ctor || ok_ctor; + let if_let_type = if some_ctor { "Some" } else { "Ok" }; + + then { + // 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); + + span_lint_and_then( + cx, + super::MANUAL_FLATTEN, + span, + &msg, + |diag| { + let sugg = format!("{}.flatten()", arg_snippet); + 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.rs b/clippy_lints/src/loops/mod.rs similarity index 95% rename from clippy_lints/src/loops.rs rename to clippy_lints/src/loops/mod.rs index 711cd5b3b15..18b7e96d42c 100644 --- a/clippy_lints/src/loops.rs +++ b/clippy_lints/src/loops/mod.rs @@ -1,13 +1,16 @@ +mod manual_flatten; +mod utils; + use crate::consts::constant; use crate::utils::sugg::Sugg; use crate::utils::usage::mutated_variables; use crate::utils::visitors::LocalUsedVisitor; use crate::utils::{ contains_name, get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, - indent_of, is_in_panic_handler, is_integer_const, is_no_std_crate, is_ok_ctor, is_refutable, is_some_ctor, - is_type_diagnostic_item, last_path_segment, match_trait_method, match_type, multispan_sugg, path_to_local, - path_to_local_id, paths, single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, - span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, SpanlessEq, + indent_of, is_in_panic_handler, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, + last_path_segment, match_trait_method, match_type, multispan_sugg, path_to_local, path_to_local_id, paths, + single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint, + span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, SpanlessEq, }; use if_chain::if_chain; use rustc_ast::ast; @@ -31,6 +34,7 @@ use rustc_span::symbol::{sym, Ident, Symbol}; use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use std::iter::{once, Iterator}; use std::mem; +use utils::make_iterator_snippet; declare_clippy_lint! { /// **What it does:** Checks for for-loops that manually copy items between @@ -862,7 +866,7 @@ fn check_for_loop<'tcx>( check_for_mut_range_bound(cx, arg, body); check_for_single_element_loop(cx, pat, arg, body, expr); detect_same_item_push(cx, pat, arg, body, expr); - check_manual_flatten(cx, pat, arg, body, span); + manual_flatten::lint(cx, pat, arg, body, span); } // this function assumes the given expression is a `for` loop. @@ -1839,42 +1843,6 @@ fn check_for_loop_explicit_counter<'tcx>( } } -/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the -/// actual `Iterator` that the loop uses. -fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String { - let impls_iterator = get_trait_def_id(cx, &paths::ITERATOR).map_or(false, |id| { - implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[]) - }); - if impls_iterator { - format!( - "{}", - sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() - ) - } else { - // (&x).into_iter() ==> x.iter() - // (&mut x).into_iter() ==> x.iter_mut() - match &arg.kind { - ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner) - if has_iter_method(cx, cx.typeck_results().expr_ty(&arg_inner)).is_some() => - { - let meth_name = match mutability { - Mutability::Mut => "iter_mut", - Mutability::Not => "iter", - }; - format!( - "{}.{}()", - sugg::Sugg::hir_with_applicability(cx, &arg_inner, "_", applic_ref).maybe_par(), - meth_name, - ) - } - _ => format!( - "{}.into_iter()", - sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() - ), - } - } -} - /// Checks for the `FOR_KV_MAP` lint. fn check_for_loop_over_map_kv<'tcx>( cx: &LateContext<'tcx>, @@ -1964,77 +1932,6 @@ fn check_for_single_element_loop<'tcx>( } } -/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the -/// iterator element is used. -fn check_manual_flatten<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - span: Span, -) { - if let ExprKind::Block(ref 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 - }; - - if_chain! { - if let Some(inner_expr) = inner_expr; - if let ExprKind::Match( - ref match_expr, ref match_arms, MatchSource::IfLetDesugar{ contains_else_clause: false } - ) = inner_expr.kind; - // 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(match_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(QPath::Resolved(None, path), _, _) = match_arms[0].pat.kind; - let some_ctor = is_some_ctor(cx, path.res); - let ok_ctor = is_ok_ctor(cx, path.res); - if some_ctor || ok_ctor; - let if_let_type = if some_ctor { "Some" } else { "Ok" }; - - then { - // 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); - - span_lint_and_then( - cx, - MANUAL_FLATTEN, - span, - &msg, - |diag| { - let sugg = format!("{}.flatten()", arg_snippet); - 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", - ); - } - ); - } - } - } -} - struct MutatePairDelegate<'a, 'tcx> { cx: &'a LateContext<'tcx>, hir_id_low: Option, diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs new file mode 100644 index 00000000000..b06df2d75f3 --- /dev/null +++ b/clippy_lints/src/loops/utils.rs @@ -0,0 +1,40 @@ +use crate::utils::{get_trait_def_id, has_iter_method, implements_trait, paths, sugg}; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability}; +use rustc_lint::LateContext; + +/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the +/// actual `Iterator` that the loop uses. +pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String { + let impls_iterator = get_trait_def_id(cx, &paths::ITERATOR).map_or(false, |id| { + implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[]) + }); + if impls_iterator { + format!( + "{}", + sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() + ) + } else { + // (&x).into_iter() ==> x.iter() + // (&mut x).into_iter() ==> x.iter_mut() + match &arg.kind { + ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner) + if has_iter_method(cx, cx.typeck_results().expr_ty(&arg_inner)).is_some() => + { + let meth_name = match mutability { + Mutability::Mut => "iter_mut", + Mutability::Not => "iter", + }; + format!( + "{}.{}()", + sugg::Sugg::hir_with_applicability(cx, &arg_inner, "_", applic_ref).maybe_par(), + meth_name, + ) + } + _ => format!( + "{}.into_iter()", + sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() + ), + } + } +} From 408368a82c2c394b65524cb995e01ecfc3c1867c Mon Sep 17 00:00:00 2001 From: nahuakang Date: Mon, 8 Feb 2021 20:47:35 +0100 Subject: [PATCH 02/22] Refactor check_for_loop_arg; rename manual_flatten's lint back to check_manual_flatten --- clippy_lints/src/loops/for_loop_arg.rs | 149 +++++++++++++++++++++++ clippy_lints/src/loops/manual_flatten.rs | 2 +- clippy_lints/src/loops/mod.rs | 147 +--------------------- 3 files changed, 154 insertions(+), 144 deletions(-) create mode 100644 clippy_lints/src/loops/for_loop_arg.rs diff --git a/clippy_lints/src/loops/for_loop_arg.rs b/clippy_lints/src/loops/for_loop_arg.rs new file mode 100644 index 00000000000..b33e7183984 --- /dev/null +++ b/clippy_lints/src/loops/for_loop_arg.rs @@ -0,0 +1,149 @@ +use crate::utils::{ + is_type_diagnostic_item, match_trait_method, match_type, paths, snippet, snippet_with_applicability, span_lint, + span_lint_and_help, span_lint_and_sugg, +}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Mutability, Pat}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty, TyS}; +use rustc_span::symbol::sym; + +pub(super) fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) { + let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used + if let ExprKind::MethodCall(ref method, _, ref args, _) = arg.kind { + // just the receiver, no arguments + if args.len() == 1 { + let method_name = &*method.ident.as_str(); + // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x + if method_name == "iter" || method_name == "iter_mut" { + if is_ref_iterable_type(cx, &args[0]) { + lint_iter_method(cx, args, arg, method_name); + } + } else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) { + let receiver_ty = cx.typeck_results().expr_ty(&args[0]); + let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]); + if TyS::same_type(receiver_ty, receiver_ty_adjusted) { + let mut applicability = Applicability::MachineApplicable; + let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); + span_lint_and_sugg( + cx, + super::EXPLICIT_INTO_ITER_LOOP, + arg.span, + "it is more concise to loop over containers instead of using explicit \ + iteration methods", + "to write this more concisely, try", + object.to_string(), + applicability, + ); + } else { + let ref_receiver_ty = cx.tcx.mk_ref( + cx.tcx.lifetimes.re_erased, + ty::TypeAndMut { + ty: receiver_ty, + mutbl: Mutability::Not, + }, + ); + if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) { + lint_iter_method(cx, args, arg, method_name) + } + } + } else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) { + span_lint( + cx, + super::ITER_NEXT_LOOP, + expr.span, + "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ + probably not what you want", + ); + next_loop_linted = true; + } + } + } + if !next_loop_linted { + check_arg_type(cx, pat, arg); + } +} + +/// Checks for `for` loops over `Option`s and `Result`s. +fn check_arg_type(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { + let ty = cx.typeck_results().expr_ty(arg); + if is_type_diagnostic_item(cx, ty, sym::option_type) { + span_lint_and_help( + cx, + super::FOR_LOOPS_OVER_FALLIBLES, + arg.span, + &format!( + "for loop over `{0}`, which is an `Option`. This is more readably written as an \ + `if let` statement", + snippet(cx, arg.span, "_") + ), + None, + &format!( + "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ), + ); + } else if is_type_diagnostic_item(cx, ty, sym::result_type) { + span_lint_and_help( + cx, + super::FOR_LOOPS_OVER_FALLIBLES, + arg.span, + &format!( + "for loop over `{0}`, which is a `Result`. This is more readably written as an \ + `if let` statement", + snippet(cx, arg.span, "_") + ), + None, + &format!( + "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ), + ); + } +} + +fn lint_iter_method(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { + let mut applicability = Applicability::MachineApplicable; + let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); + let muta = if method_name == "iter_mut" { "mut " } else { "" }; + span_lint_and_sugg( + cx, + super::EXPLICIT_ITER_LOOP, + arg.span, + "it is more concise to loop over references to containers instead of using explicit \ + iteration methods", + "to write this more concisely, try", + format!("&{}{}", muta, object), + applicability, + ) +} + +/// Returns `true` if the type of expr is one that provides `IntoIterator` impls +/// for `&T` and `&mut T`, such as `Vec`. +#[rustfmt::skip] +fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + // no walk_ptrs_ty: calling iter() on a reference can make sense because it + // will allow further borrows afterwards + let ty = cx.typeck_results().expr_ty(e); + is_iterable_array(ty, cx) || + is_type_diagnostic_item(cx, ty, sym::vec_type) || + match_type(cx, ty, &paths::LINKED_LIST) || + is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || + is_type_diagnostic_item(cx, ty, sym!(hashset_type)) || + is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || + match_type(cx, ty, &paths::BINARY_HEAP) || + match_type(cx, ty, &paths::BTREEMAP) || + match_type(cx, ty, &paths::BTREESET) +} + +fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { + // IntoIterator is currently only implemented for array sizes <= 32 in rustc + match ty.kind() { + ty::Array(_, n) => n + .try_eval_usize(cx.tcx, cx.param_env) + .map_or(false, |val| (0..=32).contains(&val)), + _ => false, + } +} diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs index b5881a1b8c4..db90d2c0468 100644 --- a/clippy_lints/src/loops/manual_flatten.rs +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -8,7 +8,7 @@ use rustc_span::source_map::Span; /// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the /// iterator element is used. -pub(super) fn lint<'tcx>( +pub(super) fn check_manual_flatten<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 18b7e96d42c..63eeb3607b6 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -1,3 +1,4 @@ +mod for_loop_arg; mod manual_flatten; mod utils; @@ -27,7 +28,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; use rustc_middle::lint::in_external_macro; use rustc_middle::middle::region; -use rustc_middle::ty::{self, Ty, TyS}; +use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::symbol::{sym, Ident, Symbol}; @@ -861,12 +862,12 @@ fn check_for_loop<'tcx>( check_for_loop_range(cx, pat, arg, body, expr); check_for_loop_explicit_counter(cx, pat, arg, body, expr); } - check_for_loop_arg(cx, pat, arg, expr); + for_loop_arg::check_for_loop_arg(cx, pat, arg, expr); check_for_loop_over_map_kv(cx, pat, arg, body, expr); check_for_mut_range_bound(cx, arg, body); check_for_single_element_loop(cx, pat, arg, body, expr); detect_same_item_push(cx, pat, arg, body, expr); - manual_flatten::lint(cx, pat, arg, body, span); + manual_flatten::check_manual_flatten(cx, pat, arg, body, span); } // this function assumes the given expression is a `for` loop. @@ -1682,118 +1683,6 @@ fn is_end_eq_array_len<'tcx>( false } -fn lint_iter_method(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { - let mut applicability = Applicability::MachineApplicable; - let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); - let muta = if method_name == "iter_mut" { "mut " } else { "" }; - span_lint_and_sugg( - cx, - EXPLICIT_ITER_LOOP, - arg.span, - "it is more concise to loop over references to containers instead of using explicit \ - iteration methods", - "to write this more concisely, try", - format!("&{}{}", muta, object), - applicability, - ) -} - -fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) { - let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used - if let ExprKind::MethodCall(ref method, _, ref args, _) = arg.kind { - // just the receiver, no arguments - if args.len() == 1 { - let method_name = &*method.ident.as_str(); - // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x - if method_name == "iter" || method_name == "iter_mut" { - if is_ref_iterable_type(cx, &args[0]) { - lint_iter_method(cx, args, arg, method_name); - } - } else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) { - let receiver_ty = cx.typeck_results().expr_ty(&args[0]); - let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]); - if TyS::same_type(receiver_ty, receiver_ty_adjusted) { - let mut applicability = Applicability::MachineApplicable; - let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); - span_lint_and_sugg( - cx, - EXPLICIT_INTO_ITER_LOOP, - arg.span, - "it is more concise to loop over containers instead of using explicit \ - iteration methods", - "to write this more concisely, try", - object.to_string(), - applicability, - ); - } else { - let ref_receiver_ty = cx.tcx.mk_ref( - cx.tcx.lifetimes.re_erased, - ty::TypeAndMut { - ty: receiver_ty, - mutbl: Mutability::Not, - }, - ); - if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) { - lint_iter_method(cx, args, arg, method_name) - } - } - } else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) { - span_lint( - cx, - ITER_NEXT_LOOP, - expr.span, - "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ - probably not what you want", - ); - next_loop_linted = true; - } - } - } - if !next_loop_linted { - check_arg_type(cx, pat, arg); - } -} - -/// Checks for `for` loops over `Option`s and `Result`s. -fn check_arg_type(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { - let ty = cx.typeck_results().expr_ty(arg); - if is_type_diagnostic_item(cx, ty, sym::option_type) { - span_lint_and_help( - cx, - FOR_LOOPS_OVER_FALLIBLES, - arg.span, - &format!( - "for loop over `{0}`, which is an `Option`. This is more readably written as an \ - `if let` statement", - snippet(cx, arg.span, "_") - ), - None, - &format!( - "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", - snippet(cx, pat.span, "_"), - snippet(cx, arg.span, "_") - ), - ); - } else if is_type_diagnostic_item(cx, ty, sym::result_type) { - span_lint_and_help( - cx, - FOR_LOOPS_OVER_FALLIBLES, - arg.span, - &format!( - "for loop over `{0}`, which is a `Result`. This is more readably written as an \ - `if let` statement", - snippet(cx, arg.span, "_") - ), - None, - &format!( - "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", - snippet(cx, pat.span, "_"), - snippet(cx, arg.span, "_") - ), - ); - } -} - // To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be // incremented exactly once in the loop body, and initialized to zero // at the start of the loop. @@ -2270,34 +2159,6 @@ impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor { } } -/// Returns `true` if the type of expr is one that provides `IntoIterator` impls -/// for `&T` and `&mut T`, such as `Vec`. -#[rustfmt::skip] -fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - // no walk_ptrs_ty: calling iter() on a reference can make sense because it - // will allow further borrows afterwards - let ty = cx.typeck_results().expr_ty(e); - is_iterable_array(ty, cx) || - is_type_diagnostic_item(cx, ty, sym::vec_type) || - match_type(cx, ty, &paths::LINKED_LIST) || - is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || - is_type_diagnostic_item(cx, ty, sym!(hashset_type)) || - is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || - match_type(cx, ty, &paths::BINARY_HEAP) || - match_type(cx, ty, &paths::BTREEMAP) || - match_type(cx, ty, &paths::BTREESET) -} - -fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { - // IntoIterator is currently only implemented for array sizes <= 32 in rustc - match ty.kind() { - ty::Array(_, n) => n - .try_eval_usize(cx.tcx, cx.param_env) - .map_or(false, |val| (0..=32).contains(&val)), - _ => false, - } -} - /// If a block begins with a statement (possibly a `let` binding) and has an /// expression, return it. fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { From 7b0f588b7706f4ec212e7772084eb7c0e90cfb56 Mon Sep 17 00:00:00 2001 From: nahuakang Date: Mon, 8 Feb 2021 21:04:33 +0100 Subject: [PATCH 03/22] Refactor check_for_loop_over_map_kv to its own module --- .../src/loops/for_loop_over_map_kv.rs | 69 +++++++++++++++++++ clippy_lints/src/loops/mod.rs | 67 +----------------- 2 files changed, 71 insertions(+), 65 deletions(-) create mode 100644 clippy_lints/src/loops/for_loop_over_map_kv.rs diff --git a/clippy_lints/src/loops/for_loop_over_map_kv.rs b/clippy_lints/src/loops/for_loop_over_map_kv.rs new file mode 100644 index 00000000000..c5f0b0909ba --- /dev/null +++ b/clippy_lints/src/loops/for_loop_over_map_kv.rs @@ -0,0 +1,69 @@ +use crate::utils::visitors::LocalUsedVisitor; +use crate::utils::{is_type_diagnostic_item, match_type, multispan_sugg, paths, snippet, span_lint_and_then, sugg}; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +/// Checks for the `FOR_KV_MAP` lint. +pub(super) fn check_for_loop_over_map_kv<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + let pat_span = pat.span; + + if let PatKind::Tuple(ref pat, _) = pat.kind { + if pat.len() == 2 { + let arg_span = arg.span; + let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() { + ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) { + (key, _) if pat_is_wild(key, body) => (pat[1].span, "value", ty, mutbl), + (_, value) if pat_is_wild(value, body) => (pat[0].span, "key", ty, Mutability::Not), + _ => return, + }, + _ => return, + }; + let mutbl = match mutbl { + Mutability::Not => "", + Mutability::Mut => "_mut", + }; + let arg = match arg.kind { + ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) => &**expr, + _ => arg, + }; + + if is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || match_type(cx, ty, &paths::BTREEMAP) { + span_lint_and_then( + cx, + super::FOR_KV_MAP, + expr.span, + &format!("you seem to want to iterate on a map's {}s", kind), + |diag| { + let map = sugg::Sugg::hir(cx, arg, "map"); + multispan_sugg( + diag, + "use the corresponding method", + vec![ + (pat_span, snippet(cx, new_pat_span, kind).into_owned()), + (arg_span, format!("{}.{}s{}()", map.maybe_par(), kind, mutbl)), + ], + ); + }, + ); + } + } + } +} + +/// Returns `true` if the pattern is a `PatWild` or an ident prefixed with `_`. +fn pat_is_wild<'tcx>(pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { + match *pat { + PatKind::Wild => true, + PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => { + !LocalUsedVisitor::new(id).check_expr(body) + }, + _ => false, + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 63eeb3607b6..672f0731859 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -1,4 +1,5 @@ mod for_loop_arg; +mod for_loop_over_map_kv; mod manual_flatten; mod utils; @@ -863,7 +864,7 @@ fn check_for_loop<'tcx>( check_for_loop_explicit_counter(cx, pat, arg, body, expr); } for_loop_arg::check_for_loop_arg(cx, pat, arg, expr); - check_for_loop_over_map_kv(cx, pat, arg, body, expr); + for_loop_over_map_kv::check_for_loop_over_map_kv(cx, pat, arg, body, expr); check_for_mut_range_bound(cx, arg, body); check_for_single_element_loop(cx, pat, arg, body, expr); detect_same_item_push(cx, pat, arg, body, expr); @@ -1732,59 +1733,6 @@ fn check_for_loop_explicit_counter<'tcx>( } } -/// Checks for the `FOR_KV_MAP` lint. -fn check_for_loop_over_map_kv<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, -) { - let pat_span = pat.span; - - if let PatKind::Tuple(ref pat, _) = pat.kind { - if pat.len() == 2 { - let arg_span = arg.span; - let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() { - ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) { - (key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl), - (_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not), - _ => return, - }, - _ => return, - }; - let mutbl = match mutbl { - Mutability::Not => "", - Mutability::Mut => "_mut", - }; - let arg = match arg.kind { - ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) => &**expr, - _ => arg, - }; - - if is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || match_type(cx, ty, &paths::BTREEMAP) { - span_lint_and_then( - cx, - FOR_KV_MAP, - expr.span, - &format!("you seem to want to iterate on a map's {}s", kind), - |diag| { - let map = sugg::Sugg::hir(cx, arg, "map"); - multispan_sugg( - diag, - "use the corresponding method", - vec![ - (pat_span, snippet(cx, new_pat_span, kind).into_owned()), - (arg_span, format!("{}.{}s{}()", map.maybe_par(), kind, mutbl)), - ], - ); - }, - ); - } - } - } -} - fn check_for_single_element_loop<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, @@ -1927,17 +1875,6 @@ fn check_for_mutation<'tcx>( delegate.mutation_span() } -/// Returns `true` if the pattern is a `PatWild` or an ident prefixed with `_`. -fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { - match *pat { - PatKind::Wild => true, - PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => { - !LocalUsedVisitor::new(cx, id).check_expr(body) - }, - _ => false, - } -} - struct VarVisitor<'a, 'tcx> { /// context reference cx: &'a LateContext<'tcx>, From 6e4663dedf92f871471cc240069d270b65a8160b Mon Sep 17 00:00:00 2001 From: nahuakang Date: Mon, 8 Feb 2021 21:20:23 +0100 Subject: [PATCH 04/22] Refactor check_for_mut_range_bound to its own module --- clippy_lints/src/loops/for_mut_range_bound.rs | 114 ++++++++++++++++++ clippy_lints/src/loops/mod.rs | 111 +---------------- 2 files changed, 116 insertions(+), 109 deletions(-) create mode 100644 clippy_lints/src/loops/for_mut_range_bound.rs diff --git a/clippy_lints/src/loops/for_mut_range_bound.rs b/clippy_lints/src/loops/for_mut_range_bound.rs new file mode 100644 index 00000000000..d305c55cb95 --- /dev/null +++ b/clippy_lints/src/loops/for_mut_range_bound.rs @@ -0,0 +1,114 @@ +use crate::utils::{higher, path_to_local, span_lint}; +use if_chain::if_chain; +use rustc_hir::{BindingAnnotation, Expr, HirId, Node, PatKind}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Span; +use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; + +pub(super) fn check_for_mut_range_bound(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) { + if let Some(higher::Range { + start: Some(start), + end: Some(end), + .. + }) = higher::range(arg) + { + let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)]; + if mut_ids[0].is_some() || mut_ids[1].is_some() { + let (span_low, span_high) = check_for_mutation(cx, body, &mut_ids); + mut_warn_with_span(cx, span_low); + mut_warn_with_span(cx, span_high); + } + } +} + +fn mut_warn_with_span(cx: &LateContext<'_>, span: Option) { + if let Some(sp) = span { + span_lint( + cx, + super::MUT_RANGE_BOUND, + sp, + "attempt to mutate range bound within loop; note that the range of the loop is unchanged", + ); + } +} + +fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option { + if_chain! { + if let Some(hir_id) = path_to_local(bound); + if let Node::Binding(pat) = cx.tcx.hir().get(hir_id); + if let PatKind::Binding(BindingAnnotation::Mutable, ..) = pat.kind; + then { + return Some(hir_id); + } + } + None +} + +fn check_for_mutation<'tcx>( + cx: &LateContext<'tcx>, + body: &Expr<'_>, + bound_ids: &[Option], +) -> (Option, Option) { + let mut delegate = MutatePairDelegate { + cx, + hir_id_low: bound_ids[0], + hir_id_high: bound_ids[1], + span_low: None, + span_high: None, + }; + cx.tcx.infer_ctxt().enter(|infcx| { + ExprUseVisitor::new( + &mut delegate, + &infcx, + body.hir_id.owner, + cx.param_env, + cx.typeck_results(), + ) + .walk_expr(body); + }); + delegate.mutation_span() +} + +struct MutatePairDelegate<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + hir_id_low: Option, + hir_id_high: Option, + span_low: Option, + span_high: Option, +} + +impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> { + fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ConsumeMode) {} + + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { + if let ty::BorrowKind::MutBorrow = bk { + if let PlaceBase::Local(id) = cmt.place.base { + if Some(id) == self.hir_id_low { + self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + if Some(id) == self.hir_id_high { + self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + } + } + } + + fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { + if let PlaceBase::Local(id) = cmt.place.base { + if Some(id) == self.hir_id_low { + self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + if Some(id) == self.hir_id_high { + self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + } + } +} + +impl MutatePairDelegate<'_, '_> { + fn mutation_span(&self) -> (Option, Option) { + (self.span_low, self.span_high) + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 672f0731859..20f0090ebb4 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -1,5 +1,6 @@ mod for_loop_arg; mod for_loop_over_map_kv; +mod for_mut_range_bound; mod manual_flatten; mod utils; @@ -24,7 +25,6 @@ use rustc_hir::{ def_id, BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, InlineAsmOperand, Local, LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind, }; -use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; use rustc_middle::lint::in_external_macro; @@ -33,7 +33,6 @@ use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::symbol::{sym, Ident, Symbol}; -use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use std::iter::{once, Iterator}; use std::mem; use utils::make_iterator_snippet; @@ -865,7 +864,7 @@ fn check_for_loop<'tcx>( } for_loop_arg::check_for_loop_arg(cx, pat, arg, expr); for_loop_over_map_kv::check_for_loop_over_map_kv(cx, pat, arg, body, expr); - check_for_mut_range_bound(cx, arg, body); + for_mut_range_bound::check_for_mut_range_bound(cx, arg, body); check_for_single_element_loop(cx, pat, arg, body, expr); detect_same_item_push(cx, pat, arg, body, expr); manual_flatten::check_manual_flatten(cx, pat, arg, body, span); @@ -1769,112 +1768,6 @@ fn check_for_single_element_loop<'tcx>( } } -struct MutatePairDelegate<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - hir_id_low: Option, - hir_id_high: Option, - span_low: Option, - span_high: Option, -} - -impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> { - fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ConsumeMode) {} - - fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { - if let ty::BorrowKind::MutBorrow = bk { - if let PlaceBase::Local(id) = cmt.place.base { - if Some(id) == self.hir_id_low { - self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)) - } - if Some(id) == self.hir_id_high { - self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)) - } - } - } - } - - fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { - if let PlaceBase::Local(id) = cmt.place.base { - if Some(id) == self.hir_id_low { - self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)) - } - if Some(id) == self.hir_id_high { - self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)) - } - } - } -} - -impl MutatePairDelegate<'_, '_> { - fn mutation_span(&self) -> (Option, Option) { - (self.span_low, self.span_high) - } -} - -fn check_for_mut_range_bound(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) { - if let Some(higher::Range { - start: Some(start), - end: Some(end), - .. - }) = higher::range(arg) - { - let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)]; - if mut_ids[0].is_some() || mut_ids[1].is_some() { - let (span_low, span_high) = check_for_mutation(cx, body, &mut_ids); - mut_warn_with_span(cx, span_low); - mut_warn_with_span(cx, span_high); - } - } -} - -fn mut_warn_with_span(cx: &LateContext<'_>, span: Option) { - if let Some(sp) = span { - span_lint( - cx, - MUT_RANGE_BOUND, - sp, - "attempt to mutate range bound within loop; note that the range of the loop is unchanged", - ); - } -} - -fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option { - if_chain! { - if let Some(hir_id) = path_to_local(bound); - if let Node::Binding(pat) = cx.tcx.hir().get(hir_id); - if let PatKind::Binding(BindingAnnotation::Mutable, ..) = pat.kind; - then { - return Some(hir_id); - } - } - None -} - -fn check_for_mutation<'tcx>( - cx: &LateContext<'tcx>, - body: &Expr<'_>, - bound_ids: &[Option], -) -> (Option, Option) { - let mut delegate = MutatePairDelegate { - cx, - hir_id_low: bound_ids[0], - hir_id_high: bound_ids[1], - span_low: None, - span_high: None, - }; - cx.tcx.infer_ctxt().enter(|infcx| { - ExprUseVisitor::new( - &mut delegate, - &infcx, - body.hir_id.owner, - cx.param_env, - cx.typeck_results(), - ) - .walk_expr(body); - }); - delegate.mutation_span() -} - struct VarVisitor<'a, 'tcx> { /// context reference cx: &'a LateContext<'tcx>, From 71c9fdf8b136cf9f879d5ca99615043ef6d09e68 Mon Sep 17 00:00:00 2001 From: nahuakang Date: Mon, 8 Feb 2021 21:38:00 +0100 Subject: [PATCH 05/22] Refactor check_for_loop_range into its module --- clippy_lints/src/loops/for_loop_range.rs | 390 +++++++++++++++++++++++ clippy_lints/src/loops/mod.rs | 387 +--------------------- 2 files changed, 396 insertions(+), 381 deletions(-) create mode 100644 clippy_lints/src/loops/for_loop_range.rs diff --git a/clippy_lints/src/loops/for_loop_range.rs b/clippy_lints/src/loops/for_loop_range.rs new file mode 100644 index 00000000000..43560abb7f2 --- /dev/null +++ b/clippy_lints/src/loops/for_loop_range.rs @@ -0,0 +1,390 @@ +use crate::utils::visitors::LocalUsedVisitor; +use crate::utils::{ + contains_name, has_iter_method, higher, is_integer_const, match_trait_method, multispan_sugg, path_to_local_id, + paths, snippet, span_lint_and_then, sugg, SpanlessEq, +}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_middle::middle::region; +use rustc_middle::ty::{self, Ty}; +use rustc_span::symbol::{sym, Symbol}; +use std::iter::Iterator; +use std::mem; + +/// Checks for looping over a range and then indexing a sequence with it. +/// The iteratee must be a range literal. +#[allow(clippy::too_many_lines)] +pub(super) fn check_for_loop_range<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + if let Some(higher::Range { + start: Some(start), + ref end, + limits, + }) = higher::range(arg) + { + // the var must be a single name + if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind { + let mut visitor = VarVisitor { + cx, + var: canonical_id, + indexed_mut: FxHashSet::default(), + indexed_indirectly: FxHashMap::default(), + indexed_directly: FxHashMap::default(), + referenced: FxHashSet::default(), + nonindex: false, + prefer_mutable: false, + }; + walk_expr(&mut visitor, body); + + // linting condition: we only indexed one variable, and indexed it directly + if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 { + let (indexed, (indexed_extent, indexed_ty)) = visitor + .indexed_directly + .into_iter() + .next() + .expect("already checked that we have exactly 1 element"); + + // ensure that the indexed variable was declared before the loop, see #601 + if let Some(indexed_extent) = indexed_extent { + let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id); + let parent_def_id = cx.tcx.hir().local_def_id(parent_id); + let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); + let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id); + if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { + return; + } + } + + // don't lint if the container that is indexed does not have .iter() method + let has_iter = has_iter_method(cx, indexed_ty); + if has_iter.is_none() { + return; + } + + // don't lint if the container that is indexed into is also used without + // indexing + if visitor.referenced.contains(&indexed) { + return; + } + + let starts_at_zero = is_integer_const(cx, start, 0); + + let skip = if starts_at_zero { + String::new() + } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start) { + return; + } else { + format!(".skip({})", snippet(cx, start.span, "..")) + }; + + let mut end_is_start_plus_val = false; + + let take = if let Some(end) = *end { + let mut take_expr = end; + + if let ExprKind::Binary(ref op, ref left, ref right) = end.kind { + if let BinOpKind::Add = op.node { + let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left); + let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right); + + if start_equal_left { + take_expr = right; + } else if start_equal_right { + take_expr = left; + } + + end_is_start_plus_val = start_equal_left | start_equal_right; + } + } + + if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) { + String::new() + } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr) { + return; + } else { + match limits { + ast::RangeLimits::Closed => { + let take_expr = sugg::Sugg::hir(cx, take_expr, ""); + format!(".take({})", take_expr + sugg::ONE) + }, + ast::RangeLimits::HalfOpen => format!(".take({})", snippet(cx, take_expr.span, "..")), + } + } + } else { + String::new() + }; + + let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) { + ("mut ", "iter_mut") + } else { + ("", "iter") + }; + + let take_is_empty = take.is_empty(); + let mut method_1 = take; + let mut method_2 = skip; + + if end_is_start_plus_val { + mem::swap(&mut method_1, &mut method_2); + } + + if visitor.nonindex { + span_lint_and_then( + cx, + super::NEEDLESS_RANGE_LOOP, + expr.span, + &format!("the loop variable `{}` is used to index `{}`", ident.name, indexed), + |diag| { + multispan_sugg( + diag, + "consider using an iterator", + vec![ + (pat.span, format!("({}, )", ident.name)), + ( + arg.span, + format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2), + ), + ], + ); + }, + ); + } else { + let repl = if starts_at_zero && take_is_empty { + format!("&{}{}", ref_mut, indexed) + } else { + format!("{}.{}(){}{}", indexed, method, method_1, method_2) + }; + + span_lint_and_then( + cx, + super::NEEDLESS_RANGE_LOOP, + expr.span, + &format!("the loop variable `{}` is only used to index `{}`", ident.name, indexed), + |diag| { + multispan_sugg( + diag, + "consider using an iterator", + vec![(pat.span, "".to_string()), (arg.span, repl)], + ); + }, + ); + } + } + } + } +} + +fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool { + if_chain! { + if let ExprKind::MethodCall(ref method, _, ref len_args, _) = expr.kind; + if len_args.len() == 1; + if method.ident.name == sym!(len); + if let ExprKind::Path(QPath::Resolved(_, ref path)) = len_args[0].kind; + if path.segments.len() == 1; + if path.segments[0].ident.name == var; + then { + return true; + } + } + + false +} + +fn is_end_eq_array_len<'tcx>( + cx: &LateContext<'tcx>, + end: &Expr<'_>, + limits: ast::RangeLimits, + indexed_ty: Ty<'tcx>, +) -> bool { + if_chain! { + if let ExprKind::Lit(ref lit) = end.kind; + if let ast::LitKind::Int(end_int, _) = lit.node; + if let ty::Array(_, arr_len_const) = indexed_ty.kind(); + if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env); + then { + return match limits { + ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(), + ast::RangeLimits::HalfOpen => end_int >= arr_len.into(), + }; + } + } + + false +} + +struct VarVisitor<'a, 'tcx> { + /// context reference + cx: &'a LateContext<'tcx>, + /// var name to look for as index + var: HirId, + /// indexed variables that are used mutably + indexed_mut: FxHashSet, + /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global + indexed_indirectly: FxHashMap>, + /// subset of `indexed` of vars that are indexed directly: `v[i]` + /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]` + indexed_directly: FxHashMap, Ty<'tcx>)>, + /// Any names that are used outside an index operation. + /// Used to detect things like `&mut vec` used together with `vec[i]` + referenced: FxHashSet, + /// has the loop variable been used in expressions other than the index of + /// an index op? + nonindex: bool, + /// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar + /// takes `&mut self` + prefer_mutable: bool, +} + +impl<'a, 'tcx> VarVisitor<'a, 'tcx> { + fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { + if_chain! { + // the indexed container is referenced by a name + if let ExprKind::Path(ref seqpath) = seqexpr.kind; + if let QPath::Resolved(None, ref seqvar) = *seqpath; + if seqvar.segments.len() == 1; + then { + let index_used_directly = path_to_local_id(idx, self.var); + let indexed_indirectly = { + let mut used_visitor = LocalUsedVisitor::new(self.var); + walk_expr(&mut used_visitor, idx); + used_visitor.used + }; + + if indexed_indirectly || index_used_directly { + if self.prefer_mutable { + self.indexed_mut.insert(seqvar.segments[0].ident.name); + } + let res = self.cx.qpath_res(seqpath, seqexpr.hir_id); + match res { + Res::Local(hir_id) => { + let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); + let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id); + let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id); + if indexed_indirectly { + self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent)); + } + if index_used_directly { + self.indexed_directly.insert( + seqvar.segments[0].ident.name, + (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)), + ); + } + return false; // no need to walk further *on the variable* + } + Res::Def(DefKind::Static | DefKind::Const, ..) => { + if indexed_indirectly { + self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None); + } + if index_used_directly { + self.indexed_directly.insert( + seqvar.segments[0].ident.name, + (None, self.cx.typeck_results().node_type(seqexpr.hir_id)), + ); + } + return false; // no need to walk further *on the variable* + } + _ => (), + } + } + } + } + true + } +} + +impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + // a range index op + if let ExprKind::MethodCall(ref meth, _, ref args, _) = expr.kind; + if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX)) + || (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT)); + if !self.check(&args[1], &args[0], expr); + then { return } + } + + if_chain! { + // an index op + if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind; + if !self.check(idx, seqexpr, expr); + then { return } + } + + if_chain! { + // directly using a variable + if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind; + if let Res::Local(local_id) = path.res; + then { + if local_id == self.var { + self.nonindex = true; + } else { + // not the correct variable, but still a variable + self.referenced.insert(path.segments[0].ident.name); + } + } + } + + let old = self.prefer_mutable; + match expr.kind { + ExprKind::AssignOp(_, ref lhs, ref rhs) | ExprKind::Assign(ref lhs, ref rhs, _) => { + self.prefer_mutable = true; + self.visit_expr(lhs); + self.prefer_mutable = false; + self.visit_expr(rhs); + }, + ExprKind::AddrOf(BorrowKind::Ref, mutbl, ref expr) => { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + self.visit_expr(expr); + }, + ExprKind::Call(ref f, args) => { + self.visit_expr(f); + for expr in args { + let ty = self.cx.typeck_results().expr_ty_adjusted(expr); + self.prefer_mutable = false; + if let ty::Ref(_, _, mutbl) = *ty.kind() { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + } + self.visit_expr(expr); + } + }, + ExprKind::MethodCall(_, _, args, _) => { + let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); + for (ty, expr) in self.cx.tcx.fn_sig(def_id).inputs().skip_binder().iter().zip(args) { + self.prefer_mutable = false; + if let ty::Ref(_, _, mutbl) = *ty.kind() { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + } + self.visit_expr(expr); + } + }, + ExprKind::Closure(_, _, body_id, ..) => { + let body = self.cx.tcx.hir().body(body_id); + self.visit_expr(&body.value); + }, + _ => walk_expr(self, expr), + } + self.prefer_mutable = old; + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 20f0090ebb4..204f9f9b255 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -1,5 +1,6 @@ mod for_loop_arg; mod for_loop_over_map_kv; +mod for_loop_range; mod for_mut_range_bound; mod manual_flatten; mod utils; @@ -7,13 +8,11 @@ mod utils; use crate::consts::constant; use crate::utils::sugg::Sugg; use crate::utils::usage::mutated_variables; -use crate::utils::visitors::LocalUsedVisitor; use crate::utils::{ - contains_name, get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, - indent_of, is_in_panic_handler, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, - last_path_segment, match_trait_method, match_type, multispan_sugg, path_to_local, path_to_local_id, paths, - single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint, - span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, SpanlessEq, + get_enclosing_block, get_parent_expr, get_trait_def_id, higher, implements_trait, indent_of, is_in_panic_handler, + is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, match_trait_method, + match_type, path_to_local, path_to_local_id, paths, single_segment_path, snippet, snippet_with_applicability, + snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, }; use if_chain::if_chain; use rustc_ast::ast; @@ -28,13 +27,11 @@ use rustc_hir::{ use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; use rustc_middle::lint::in_external_macro; -use rustc_middle::middle::region; use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::symbol::{sym, Ident, Symbol}; use std::iter::{once, Iterator}; -use std::mem; use utils::make_iterator_snippet; declare_clippy_lint! { @@ -859,7 +856,7 @@ fn check_for_loop<'tcx>( ) { let is_manual_memcpy_triggered = detect_manual_memcpy(cx, pat, arg, body, expr); if !is_manual_memcpy_triggered { - check_for_loop_range(cx, pat, arg, body, expr); + for_loop_range::check_for_loop_range(cx, pat, arg, body, expr); check_for_loop_explicit_counter(cx, pat, arg, body, expr); } for_loop_arg::check_for_loop_arg(cx, pat, arg, expr); @@ -1477,212 +1474,6 @@ fn detect_same_item_push<'tcx>( } } -/// Checks for looping over a range and then indexing a sequence with it. -/// The iteratee must be a range literal. -#[allow(clippy::too_many_lines)] -fn check_for_loop_range<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, -) { - if let Some(higher::Range { - start: Some(start), - ref end, - limits, - }) = higher::range(arg) - { - // the var must be a single name - if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind { - let mut visitor = VarVisitor { - cx, - var: canonical_id, - indexed_mut: FxHashSet::default(), - indexed_indirectly: FxHashMap::default(), - indexed_directly: FxHashMap::default(), - referenced: FxHashSet::default(), - nonindex: false, - prefer_mutable: false, - }; - walk_expr(&mut visitor, body); - - // linting condition: we only indexed one variable, and indexed it directly - if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 { - let (indexed, (indexed_extent, indexed_ty)) = visitor - .indexed_directly - .into_iter() - .next() - .expect("already checked that we have exactly 1 element"); - - // ensure that the indexed variable was declared before the loop, see #601 - if let Some(indexed_extent) = indexed_extent { - let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id); - let parent_def_id = cx.tcx.hir().local_def_id(parent_id); - let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); - let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id); - if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { - return; - } - } - - // don't lint if the container that is indexed does not have .iter() method - let has_iter = has_iter_method(cx, indexed_ty); - if has_iter.is_none() { - return; - } - - // don't lint if the container that is indexed into is also used without - // indexing - if visitor.referenced.contains(&indexed) { - return; - } - - let starts_at_zero = is_integer_const(cx, start, 0); - - let skip = if starts_at_zero { - String::new() - } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start) { - return; - } else { - format!(".skip({})", snippet(cx, start.span, "..")) - }; - - let mut end_is_start_plus_val = false; - - let take = if let Some(end) = *end { - let mut take_expr = end; - - if let ExprKind::Binary(ref op, ref left, ref right) = end.kind { - if let BinOpKind::Add = op.node { - let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left); - let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right); - - if start_equal_left { - take_expr = right; - } else if start_equal_right { - take_expr = left; - } - - end_is_start_plus_val = start_equal_left | start_equal_right; - } - } - - if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) { - String::new() - } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr) { - return; - } else { - match limits { - ast::RangeLimits::Closed => { - let take_expr = sugg::Sugg::hir(cx, take_expr, ""); - format!(".take({})", take_expr + sugg::ONE) - }, - ast::RangeLimits::HalfOpen => format!(".take({})", snippet(cx, take_expr.span, "..")), - } - } - } else { - String::new() - }; - - let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) { - ("mut ", "iter_mut") - } else { - ("", "iter") - }; - - let take_is_empty = take.is_empty(); - let mut method_1 = take; - let mut method_2 = skip; - - if end_is_start_plus_val { - mem::swap(&mut method_1, &mut method_2); - } - - if visitor.nonindex { - span_lint_and_then( - cx, - NEEDLESS_RANGE_LOOP, - expr.span, - &format!("the loop variable `{}` is used to index `{}`", ident.name, indexed), - |diag| { - multispan_sugg( - diag, - "consider using an iterator", - vec![ - (pat.span, format!("({}, )", ident.name)), - ( - arg.span, - format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2), - ), - ], - ); - }, - ); - } else { - let repl = if starts_at_zero && take_is_empty { - format!("&{}{}", ref_mut, indexed) - } else { - format!("{}.{}(){}{}", indexed, method, method_1, method_2) - }; - - span_lint_and_then( - cx, - NEEDLESS_RANGE_LOOP, - expr.span, - &format!("the loop variable `{}` is only used to index `{}`", ident.name, indexed), - |diag| { - multispan_sugg( - diag, - "consider using an iterator", - vec![(pat.span, "".to_string()), (arg.span, repl)], - ); - }, - ); - } - } - } - } -} - -fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool { - if_chain! { - if let ExprKind::MethodCall(ref method, _, ref len_args, _) = expr.kind; - if len_args.len() == 1; - if method.ident.name == sym!(len); - if let ExprKind::Path(QPath::Resolved(_, ref path)) = len_args[0].kind; - if path.segments.len() == 1; - if path.segments[0].ident.name == var; - then { - return true; - } - } - - false -} - -fn is_end_eq_array_len<'tcx>( - cx: &LateContext<'tcx>, - end: &Expr<'_>, - limits: ast::RangeLimits, - indexed_ty: Ty<'tcx>, -) -> bool { - if_chain! { - if let ExprKind::Lit(ref lit) = end.kind; - if let ast::LitKind::Int(end_int, _) = lit.node; - if let ty::Array(_, arr_len_const) = indexed_ty.kind(); - if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env); - then { - return match limits { - ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(), - ast::RangeLimits::HalfOpen => end_int >= arr_len.into(), - }; - } - } - - false -} - // To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be // incremented exactly once in the loop body, and initialized to zero // at the start of the loop. @@ -1768,172 +1559,6 @@ fn check_for_single_element_loop<'tcx>( } } -struct VarVisitor<'a, 'tcx> { - /// context reference - cx: &'a LateContext<'tcx>, - /// var name to look for as index - var: HirId, - /// indexed variables that are used mutably - indexed_mut: FxHashSet, - /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global - indexed_indirectly: FxHashMap>, - /// subset of `indexed` of vars that are indexed directly: `v[i]` - /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]` - indexed_directly: FxHashMap, Ty<'tcx>)>, - /// Any names that are used outside an index operation. - /// Used to detect things like `&mut vec` used together with `vec[i]` - referenced: FxHashSet, - /// has the loop variable been used in expressions other than the index of - /// an index op? - nonindex: bool, - /// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar - /// takes `&mut self` - prefer_mutable: bool, -} - -impl<'a, 'tcx> VarVisitor<'a, 'tcx> { - fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { - if_chain! { - // the indexed container is referenced by a name - if let ExprKind::Path(ref seqpath) = seqexpr.kind; - if let QPath::Resolved(None, ref seqvar) = *seqpath; - if seqvar.segments.len() == 1; - then { - let index_used_directly = path_to_local_id(idx, self.var); - let indexed_indirectly = { - let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var); - walk_expr(&mut used_visitor, idx); - used_visitor.used - }; - - if indexed_indirectly || index_used_directly { - if self.prefer_mutable { - self.indexed_mut.insert(seqvar.segments[0].ident.name); - } - let res = self.cx.qpath_res(seqpath, seqexpr.hir_id); - match res { - Res::Local(hir_id) => { - let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); - let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id); - let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id); - if indexed_indirectly { - self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent)); - } - if index_used_directly { - self.indexed_directly.insert( - seqvar.segments[0].ident.name, - (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)), - ); - } - return false; // no need to walk further *on the variable* - } - Res::Def(DefKind::Static | DefKind::Const, ..) => { - if indexed_indirectly { - self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None); - } - if index_used_directly { - self.indexed_directly.insert( - seqvar.segments[0].ident.name, - (None, self.cx.typeck_results().node_type(seqexpr.hir_id)), - ); - } - return false; // no need to walk further *on the variable* - } - _ => (), - } - } - } - } - true - } -} - -impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if_chain! { - // a range index op - if let ExprKind::MethodCall(ref meth, _, ref args, _) = expr.kind; - if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX)) - || (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT)); - if !self.check(&args[1], &args[0], expr); - then { return } - } - - if_chain! { - // an index op - if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind; - if !self.check(idx, seqexpr, expr); - then { return } - } - - if_chain! { - // directly using a variable - if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind; - if let Res::Local(local_id) = path.res; - then { - if local_id == self.var { - self.nonindex = true; - } else { - // not the correct variable, but still a variable - self.referenced.insert(path.segments[0].ident.name); - } - } - } - - let old = self.prefer_mutable; - match expr.kind { - ExprKind::AssignOp(_, ref lhs, ref rhs) | ExprKind::Assign(ref lhs, ref rhs, _) => { - self.prefer_mutable = true; - self.visit_expr(lhs); - self.prefer_mutable = false; - self.visit_expr(rhs); - }, - ExprKind::AddrOf(BorrowKind::Ref, mutbl, ref expr) => { - if mutbl == Mutability::Mut { - self.prefer_mutable = true; - } - self.visit_expr(expr); - }, - ExprKind::Call(ref f, args) => { - self.visit_expr(f); - for expr in args { - let ty = self.cx.typeck_results().expr_ty_adjusted(expr); - self.prefer_mutable = false; - if let ty::Ref(_, _, mutbl) = *ty.kind() { - if mutbl == Mutability::Mut { - self.prefer_mutable = true; - } - } - self.visit_expr(expr); - } - }, - ExprKind::MethodCall(_, _, args, _) => { - let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); - for (ty, expr) in self.cx.tcx.fn_sig(def_id).inputs().skip_binder().iter().zip(args) { - self.prefer_mutable = false; - if let ty::Ref(_, _, mutbl) = *ty.kind() { - if mutbl == Mutability::Mut { - self.prefer_mutable = true; - } - } - self.visit_expr(expr); - } - }, - ExprKind::Closure(_, _, body_id, ..) => { - let body = self.cx.tcx.hir().body(body_id); - self.visit_expr(&body.value); - }, - _ => walk_expr(self, expr), - } - self.prefer_mutable = old; - } - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} - fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool { let def_id = match path_to_local(expr) { Some(id) => id, From 71026cad541615875fc2570bd3eb51c30ce2151d Mon Sep 17 00:00:00 2001 From: nahuakang Date: Mon, 8 Feb 2021 22:01:05 +0100 Subject: [PATCH 06/22] Refactor check_for_loop_explicit_counter to its own module --- .../src/loops/for_loop_explicit_counter.rs | 56 ++++++++++++++++ clippy_lints/src/loops/mod.rs | 65 +------------------ clippy_lints/src/loops/utils.rs | 12 ++++ 3 files changed, 71 insertions(+), 62 deletions(-) create mode 100644 clippy_lints/src/loops/for_loop_explicit_counter.rs diff --git a/clippy_lints/src/loops/for_loop_explicit_counter.rs b/clippy_lints/src/loops/for_loop_explicit_counter.rs new file mode 100644 index 00000000000..68fee269eb1 --- /dev/null +++ b/clippy_lints/src/loops/for_loop_explicit_counter.rs @@ -0,0 +1,56 @@ +use super::{get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor}; +use crate::utils::{get_enclosing_block, is_integer_const, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_block, walk_expr}; +use rustc_hir::{Expr, Pat}; +use rustc_lint::LateContext; + +// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be +// incremented exactly once in the loop body, and initialized to zero +// at the start of the loop. +pub(super) fn check_for_loop_explicit_counter<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + // Look for variables that are incremented once per loop iteration. + let mut increment_visitor = IncrementVisitor::new(cx); + walk_expr(&mut increment_visitor, body); + + // For each candidate, check the parent block to see if + // it's initialized to zero at the start of the loop. + if let Some(block) = get_enclosing_block(&cx, expr.hir_id) { + for id in increment_visitor.into_results() { + let mut initialize_visitor = InitializeVisitor::new(cx, expr, id); + walk_block(&mut initialize_visitor, block); + + if_chain! { + if let Some((name, initializer)) = initialize_visitor.get_result(); + if is_integer_const(cx, initializer, 0); + then { + let mut applicability = Applicability::MachineApplicable; + + let for_span = get_span_of_entire_for_loop(expr); + + span_lint_and_sugg( + cx, + super::EXPLICIT_COUNTER_LOOP, + for_span.with_hi(arg.span.hi()), + &format!("the variable `{}` is used as a loop counter", name), + "consider using", + format!( + "for ({}, {}) in {}.enumerate()", + name, + snippet_with_applicability(cx, pat.span, "item", &mut applicability), + make_iterator_snippet(cx, arg, &mut applicability), + ), + applicability, + ); + } + } + } + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 204f9f9b255..f06b3ae4c9f 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -1,4 +1,5 @@ mod for_loop_arg; +mod for_loop_explicit_counter; mod for_loop_over_map_kv; mod for_loop_range; mod for_mut_range_bound; @@ -32,7 +33,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::symbol::{sym, Ident, Symbol}; use std::iter::{once, Iterator}; -use utils::make_iterator_snippet; +use utils::{get_span_of_entire_for_loop, make_iterator_snippet}; declare_clippy_lint! { /// **What it does:** Checks for for-loops that manually copy items between @@ -857,7 +858,7 @@ fn check_for_loop<'tcx>( let is_manual_memcpy_triggered = detect_manual_memcpy(cx, pat, arg, body, expr); if !is_manual_memcpy_triggered { for_loop_range::check_for_loop_range(cx, pat, arg, body, expr); - check_for_loop_explicit_counter(cx, pat, arg, body, expr); + for_loop_explicit_counter::check_for_loop_explicit_counter(cx, pat, arg, body, expr); } for_loop_arg::check_for_loop_arg(cx, pat, arg, expr); for_loop_over_map_kv::check_for_loop_over_map_kv(cx, pat, arg, body, expr); @@ -867,17 +868,6 @@ fn check_for_loop<'tcx>( manual_flatten::check_manual_flatten(cx, pat, arg, body, span); } -// this function assumes the given expression is a `for` loop. -fn get_span_of_entire_for_loop(expr: &Expr<'_>) -> Span { - // for some reason this is the only way to get the `Span` - // of the entire `for` loop - if let ExprKind::Match(_, arms, _) = &expr.kind { - arms[0].body.span - } else { - unreachable!() - } -} - /// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`; /// and also, it avoids subtracting a variable from the same one by replacing it with `0`. /// it exists for the convenience of the overloaded operators while normal functions can do the @@ -1474,55 +1464,6 @@ fn detect_same_item_push<'tcx>( } } -// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be -// incremented exactly once in the loop body, and initialized to zero -// at the start of the loop. -fn check_for_loop_explicit_counter<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, -) { - // Look for variables that are incremented once per loop iteration. - let mut increment_visitor = IncrementVisitor::new(cx); - walk_expr(&mut increment_visitor, body); - - // For each candidate, check the parent block to see if - // it's initialized to zero at the start of the loop. - if let Some(block) = get_enclosing_block(&cx, expr.hir_id) { - for id in increment_visitor.into_results() { - let mut initialize_visitor = InitializeVisitor::new(cx, expr, id); - walk_block(&mut initialize_visitor, block); - - if_chain! { - if let Some((name, initializer)) = initialize_visitor.get_result(); - if is_integer_const(cx, initializer, 0); - then { - let mut applicability = Applicability::MachineApplicable; - - let for_span = get_span_of_entire_for_loop(expr); - - span_lint_and_sugg( - cx, - EXPLICIT_COUNTER_LOOP, - for_span.with_hi(arg.span.hi()), - &format!("the variable `{}` is used as a loop counter", name), - "consider using", - format!( - "for ({}, {}) in {}.enumerate()", - name, - snippet_with_applicability(cx, pat.span, "item", &mut applicability), - make_iterator_snippet(cx, arg, &mut applicability), - ), - applicability, - ); - } - } - } - } -} - fn check_for_single_element_loop<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index b06df2d75f3..b9c934d5e54 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -2,6 +2,18 @@ use crate::utils::{get_trait_def_id, has_iter_method, implements_trait, paths, s use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability}; use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +// this function assumes the given expression is a `for` loop. +pub(super) fn get_span_of_entire_for_loop(expr: &Expr<'_>) -> Span { + // for some reason this is the only way to get the `Span` + // of the entire `for` loop + if let ExprKind::Match(_, arms, _) = &expr.kind { + arms[0].body.span + } else { + unreachable!() + } +} /// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the /// actual `Iterator` that the loop uses. From 1e5e541ac57e6757fe8c6c3b316a750730ba7f8c Mon Sep 17 00:00:00 2001 From: nahuakang Date: Mon, 8 Feb 2021 22:12:32 +0100 Subject: [PATCH 07/22] Refactor check_for_single_element_loop to its own module --- .../src/loops/for_single_element_loop.rs | 42 ++++++++++++++++++ clippy_lints/src/loops/mod.rs | 43 ++----------------- 2 files changed, 46 insertions(+), 39 deletions(-) create mode 100644 clippy_lints/src/loops/for_single_element_loop.rs diff --git a/clippy_lints/src/loops/for_single_element_loop.rs b/clippy_lints/src/loops/for_single_element_loop.rs new file mode 100644 index 00000000000..8ba19a2233a --- /dev/null +++ b/clippy_lints/src/loops/for_single_element_loop.rs @@ -0,0 +1,42 @@ +use super::get_span_of_entire_for_loop; +use crate::utils::{indent_of, single_segment_path, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind, Pat, PatKind}; +use rustc_lint::LateContext; + +pub(super) fn check_for_single_element_loop<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + if_chain! { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg_expr) = arg.kind; + if let PatKind::Binding(.., target, _) = pat.kind; + if let ExprKind::Array([arg_expression]) = arg_expr.kind; + if let ExprKind::Path(ref list_item) = arg_expression.kind; + if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name); + if let ExprKind::Block(ref block, _) = body.kind; + if !block.stmts.is_empty(); + + then { + let for_span = get_span_of_entire_for_loop(expr); + let mut block_str = snippet(cx, block.span, "..").into_owned(); + block_str.remove(0); + block_str.pop(); + + + span_lint_and_sugg( + cx, + super::SINGLE_ELEMENT_LOOP, + for_span, + "for loop over a single element", + "try", + format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str), + Applicability::MachineApplicable + ) + } + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index f06b3ae4c9f..d434762bc22 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -3,6 +3,7 @@ mod for_loop_explicit_counter; mod for_loop_over_map_kv; mod for_loop_range; mod for_mut_range_bound; +mod for_single_element_loop; mod manual_flatten; mod utils; @@ -10,9 +11,9 @@ use crate::consts::constant; use crate::utils::sugg::Sugg; use crate::utils::usage::mutated_variables; use crate::utils::{ - get_enclosing_block, get_parent_expr, get_trait_def_id, higher, implements_trait, indent_of, is_in_panic_handler, + get_enclosing_block, get_parent_expr, get_trait_def_id, higher, implements_trait, is_in_panic_handler, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, match_trait_method, - match_type, path_to_local, path_to_local_id, paths, single_segment_path, snippet, snippet_with_applicability, + match_type, path_to_local, path_to_local_id, paths, snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, }; use if_chain::if_chain; @@ -863,7 +864,7 @@ fn check_for_loop<'tcx>( for_loop_arg::check_for_loop_arg(cx, pat, arg, expr); for_loop_over_map_kv::check_for_loop_over_map_kv(cx, pat, arg, body, expr); for_mut_range_bound::check_for_mut_range_bound(cx, arg, body); - check_for_single_element_loop(cx, pat, arg, body, expr); + for_single_element_loop::check_for_single_element_loop(cx, pat, arg, body, expr); detect_same_item_push(cx, pat, arg, body, expr); manual_flatten::check_manual_flatten(cx, pat, arg, body, span); } @@ -1464,42 +1465,6 @@ fn detect_same_item_push<'tcx>( } } -fn check_for_single_element_loop<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, -) { - if_chain! { - if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg_expr) = arg.kind; - if let PatKind::Binding(.., target, _) = pat.kind; - if let ExprKind::Array([arg_expression]) = arg_expr.kind; - if let ExprKind::Path(ref list_item) = arg_expression.kind; - if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name); - if let ExprKind::Block(ref block, _) = body.kind; - if !block.stmts.is_empty(); - - then { - let for_span = get_span_of_entire_for_loop(expr); - let mut block_str = snippet(cx, block.span, "..").into_owned(); - block_str.remove(0); - block_str.pop(); - - - span_lint_and_sugg( - cx, - SINGLE_ELEMENT_LOOP, - for_span, - "for loop over a single element", - "try", - format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str), - Applicability::MachineApplicable - ) - } - } -} - fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool { let def_id = match path_to_local(expr) { Some(id) => id, From 9520cba554e2844c92ede32066138c38de3d327f Mon Sep 17 00:00:00 2001 From: nahuakang Date: Tue, 9 Feb 2021 20:21:22 +0100 Subject: [PATCH 08/22] Add check_infinite_loop to its own module --- clippy_lints/src/loops/infinite_loop.rs | 138 ++++++++++++++++++++++++ clippy_lints/src/loops/mod.rs | 137 +---------------------- 2 files changed, 143 insertions(+), 132 deletions(-) create mode 100644 clippy_lints/src/loops/infinite_loop.rs diff --git a/clippy_lints/src/loops/infinite_loop.rs b/clippy_lints/src/loops/infinite_loop.rs new file mode 100644 index 00000000000..b89942fb647 --- /dev/null +++ b/clippy_lints/src/loops/infinite_loop.rs @@ -0,0 +1,138 @@ +use crate::consts::constant; +use crate::utils::span_lint_and_then; +use crate::utils::usage::mutated_variables; +use if_chain::if_chain; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{def_id, Expr, ExprKind, HirId, QPath}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use std::iter::Iterator; + +pub(super) fn check_infinite_loop<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { + if constant(cx, cx.typeck_results(), cond).is_some() { + // A pure constant condition (e.g., `while false`) is not linted. + return; + } + + let mut var_visitor = VarCollectorVisitor { + cx, + ids: FxHashSet::default(), + def_ids: FxHashMap::default(), + skip: false, + }; + var_visitor.visit_expr(cond); + if var_visitor.skip { + return; + } + let used_in_condition = &var_visitor.ids; + let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) { + used_in_condition.is_disjoint(&used_mutably) + } else { + return; + }; + let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v); + + let mut has_break_or_return_visitor = HasBreakOrReturnVisitor { + has_break_or_return: false, + }; + has_break_or_return_visitor.visit_expr(expr); + let has_break_or_return = has_break_or_return_visitor.has_break_or_return; + + if no_cond_variable_mutated && !mutable_static_in_cond { + span_lint_and_then( + cx, + super::WHILE_IMMUTABLE_CONDITION, + cond.span, + "variables in the condition are not mutated in the loop body", + |diag| { + diag.note("this may lead to an infinite or to a never running loop"); + + if has_break_or_return { + diag.note("this loop contains `return`s or `break`s"); + diag.help("rewrite it as `if cond { loop { } }`"); + } + }, + ); + } +} + +struct HasBreakOrReturnVisitor { + has_break_or_return: bool, +} + +impl<'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.has_break_or_return { + return; + } + + match expr.kind { + ExprKind::Ret(_) | ExprKind::Break(_, _) => { + self.has_break_or_return = true; + return; + }, + _ => {}, + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +/// Collects the set of variables in an expression +/// Stops analysis if a function call is found +/// Note: In some cases such as `self`, there are no mutable annotation, +/// All variables definition IDs are collected +struct VarCollectorVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + ids: FxHashSet, + def_ids: FxHashMap, + skip: bool, +} + +impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> { + fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Path(ref qpath) = ex.kind; + if let QPath::Resolved(None, _) = *qpath; + let res = self.cx.qpath_res(qpath, ex.hir_id); + then { + match res { + Res::Local(hir_id) => { + self.ids.insert(hir_id); + }, + Res::Def(DefKind::Static, def_id) => { + let mutable = self.cx.tcx.is_mutable_static(def_id); + self.def_ids.insert(def_id, mutable); + }, + _ => {}, + } + } + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { + match ex.kind { + ExprKind::Path(_) => self.insert_def_id(ex), + // If there is any function/method call… we just stop analysis + ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true, + + _ => walk_expr(self, ex), + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index d434762bc22..6adfbb6b955 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -4,10 +4,10 @@ mod for_loop_over_map_kv; mod for_loop_range; mod for_mut_range_bound; mod for_single_element_loop; +mod infinite_loop; mod manual_flatten; mod utils; -use crate::consts::constant; use crate::utils::sugg::Sugg; use crate::utils::usage::mutated_variables; use crate::utils::{ @@ -18,13 +18,13 @@ use crate::utils::{ }; use if_chain::if_chain; use rustc_ast::ast; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; use rustc_hir::{ - def_id, BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, InlineAsmOperand, - Local, LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind, + BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, InlineAsmOperand, Local, + LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; @@ -683,7 +683,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { } if let Some((cond, body)) = higher::while_loop(&expr) { - check_infinite_loop(cx, cond, body); + infinite_loop::check_infinite_loop(cx, cond, body); } check_needless_collect(expr, cx); @@ -1895,133 +1895,6 @@ impl<'tcx> Visitor<'tcx> for LoopNestVisitor { } } -fn check_infinite_loop<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { - if constant(cx, cx.typeck_results(), cond).is_some() { - // A pure constant condition (e.g., `while false`) is not linted. - return; - } - - let mut var_visitor = VarCollectorVisitor { - cx, - ids: FxHashSet::default(), - def_ids: FxHashMap::default(), - skip: false, - }; - var_visitor.visit_expr(cond); - if var_visitor.skip { - return; - } - let used_in_condition = &var_visitor.ids; - let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) { - used_in_condition.is_disjoint(&used_mutably) - } else { - return; - }; - let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v); - - let mut has_break_or_return_visitor = HasBreakOrReturnVisitor { - has_break_or_return: false, - }; - has_break_or_return_visitor.visit_expr(expr); - let has_break_or_return = has_break_or_return_visitor.has_break_or_return; - - if no_cond_variable_mutated && !mutable_static_in_cond { - span_lint_and_then( - cx, - WHILE_IMMUTABLE_CONDITION, - cond.span, - "variables in the condition are not mutated in the loop body", - |diag| { - diag.note("this may lead to an infinite or to a never running loop"); - - if has_break_or_return { - diag.note("this loop contains `return`s or `break`s"); - diag.help("rewrite it as `if cond { loop { } }`"); - } - }, - ); - } -} - -struct HasBreakOrReturnVisitor { - has_break_or_return: bool, -} - -impl<'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.has_break_or_return { - return; - } - - match expr.kind { - ExprKind::Ret(_) | ExprKind::Break(_, _) => { - self.has_break_or_return = true; - return; - }, - _ => {}, - } - - walk_expr(self, expr); - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} - -/// Collects the set of variables in an expression -/// Stops analysis if a function call is found -/// Note: In some cases such as `self`, there are no mutable annotation, -/// All variables definition IDs are collected -struct VarCollectorVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - ids: FxHashSet, - def_ids: FxHashMap, - skip: bool, -} - -impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> { - fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) { - if_chain! { - if let ExprKind::Path(ref qpath) = ex.kind; - if let QPath::Resolved(None, _) = *qpath; - let res = self.cx.qpath_res(qpath, ex.hir_id); - then { - match res { - Res::Local(hir_id) => { - self.ids.insert(hir_id); - }, - Res::Def(DefKind::Static, def_id) => { - let mutable = self.cx.tcx.is_mutable_static(def_id); - self.def_ids.insert(def_id, mutable); - }, - _ => {}, - } - } - } - } -} - -impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { - match ex.kind { - ExprKind::Path(_) => self.insert_def_id(ex), - // If there is any function/method call… we just stop analysis - ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true, - - _ => walk_expr(self, ex), - } - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} - const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; fn check_needless_collect<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { From 453e6b97ace822f715b6118a60ec9b33e0ae87ec Mon Sep 17 00:00:00 2001 From: nahuakang Date: Tue, 9 Feb 2021 21:26:46 +0100 Subject: [PATCH 09/22] Add check_needless_collect to its own module --- clippy_lints/src/loops/mod.rs | 282 +-------------------- clippy_lints/src/loops/needless_collect.rs | 282 +++++++++++++++++++++ 2 files changed, 289 insertions(+), 275 deletions(-) create mode 100644 clippy_lints/src/loops/needless_collect.rs diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 6adfbb6b955..043bbc9d1c5 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -6,6 +6,7 @@ mod for_mut_range_bound; mod for_single_element_loop; mod infinite_loop; mod manual_flatten; +mod needless_collect; mod utils; use crate::utils::sugg::Sugg; @@ -13,8 +14,8 @@ use crate::utils::usage::mutated_variables; use crate::utils::{ get_enclosing_block, get_parent_expr, get_trait_def_id, higher, implements_trait, is_in_panic_handler, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, match_trait_method, - match_type, path_to_local, path_to_local_id, paths, snippet, snippet_with_applicability, - snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, + path_to_local, path_to_local_id, paths, snippet, snippet_with_applicability, snippet_with_macro_callsite, + span_lint, span_lint_and_help, span_lint_and_sugg, sugg, }; use if_chain::if_chain; use rustc_ast::ast; @@ -23,8 +24,8 @@ use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; use rustc_hir::{ - BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, InlineAsmOperand, Local, - LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind, + BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, HirId, InlineAsmOperand, LoopSource, MatchSource, + Mutability, Node, Pat, PatKind, Stmt, StmtKind, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; @@ -32,7 +33,7 @@ use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; -use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_span::symbol::{sym, Symbol}; use std::iter::{once, Iterator}; use utils::{get_span_of_entire_for_loop, make_iterator_snippet}; @@ -686,7 +687,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { infinite_loop::check_infinite_loop(cx, cond, body); } - check_needless_collect(expr, cx); + needless_collect::check_needless_collect(expr, cx); } } @@ -1894,272 +1895,3 @@ impl<'tcx> Visitor<'tcx> for LoopNestVisitor { NestedVisitorMap::None } } - -const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; - -fn check_needless_collect<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { - check_needless_collect_direct_usage(expr, cx); - check_needless_collect_indirect_usage(expr, cx); -} -fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { - if_chain! { - if let ExprKind::MethodCall(ref method, _, ref args, _) = expr.kind; - if let ExprKind::MethodCall(ref chain_method, _, _, _) = args[0].kind; - if chain_method.ident.name == sym!(collect) && match_trait_method(cx, &args[0], &paths::ITERATOR); - if let Some(ref generic_args) = chain_method.args; - if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); - then { - let ty = cx.typeck_results().node_type(ty.hir_id); - if is_type_diagnostic_item(cx, ty, sym::vec_type) || - is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || - match_type(cx, ty, &paths::BTREEMAP) || - is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) { - if method.ident.name == sym!(len) { - let span = shorten_needless_collect_span(expr); - span_lint_and_sugg( - cx, - NEEDLESS_COLLECT, - span, - NEEDLESS_COLLECT_MSG, - "replace with", - "count()".to_string(), - Applicability::MachineApplicable, - ); - } - if method.ident.name == sym!(is_empty) { - let span = shorten_needless_collect_span(expr); - span_lint_and_sugg( - cx, - NEEDLESS_COLLECT, - span, - NEEDLESS_COLLECT_MSG, - "replace with", - "next().is_none()".to_string(), - Applicability::MachineApplicable, - ); - } - if method.ident.name == sym!(contains) { - let contains_arg = snippet(cx, args[1].span, "??"); - let span = shorten_needless_collect_span(expr); - span_lint_and_then( - cx, - NEEDLESS_COLLECT, - span, - NEEDLESS_COLLECT_MSG, - |diag| { - let (arg, pred) = contains_arg - .strip_prefix('&') - .map_or(("&x", &*contains_arg), |s| ("x", s)); - diag.span_suggestion( - span, - "replace with", - format!( - "any(|{}| x == {})", - arg, pred - ), - Applicability::MachineApplicable, - ); - } - ); - } - } - } - } -} - -fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { - if let ExprKind::Block(ref block, _) = expr.kind { - for ref stmt in block.stmts { - if_chain! { - if let StmtKind::Local( - Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. }, - init: Some(ref init_expr), .. } - ) = stmt.kind; - if let ExprKind::MethodCall(ref method_name, _, &[ref iter_source], ..) = init_expr.kind; - if method_name.ident.name == sym!(collect) && match_trait_method(cx, &init_expr, &paths::ITERATOR); - if let Some(ref generic_args) = method_name.args; - if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); - if let ty = cx.typeck_results().node_type(ty.hir_id); - if is_type_diagnostic_item(cx, ty, sym::vec_type) || - is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || - match_type(cx, ty, &paths::LINKED_LIST); - if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident); - if iter_calls.len() == 1; - then { - let mut used_count_visitor = UsedCountVisitor { - cx, - id: *pat_id, - count: 0, - }; - walk_block(&mut used_count_visitor, block); - if used_count_visitor.count > 1 { - return; - } - - // Suggest replacing iter_call with iter_replacement, and removing stmt - let iter_call = &iter_calls[0]; - span_lint_and_then( - cx, - NEEDLESS_COLLECT, - stmt.span.until(iter_call.span), - NEEDLESS_COLLECT_MSG, - |diag| { - let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx)); - diag.multipart_suggestion( - iter_call.get_suggestion_text(), - vec![ - (stmt.span, String::new()), - (iter_call.span, iter_replacement) - ], - Applicability::MachineApplicable,// MaybeIncorrect, - ).emit(); - }, - ); - } - } - } - } -} - -struct IterFunction { - func: IterFunctionKind, - span: Span, -} -impl IterFunction { - fn get_iter_method(&self, cx: &LateContext<'_>) -> String { - match &self.func { - IterFunctionKind::IntoIter => String::new(), - IterFunctionKind::Len => String::from(".count()"), - IterFunctionKind::IsEmpty => String::from(".next().is_none()"), - IterFunctionKind::Contains(span) => { - let s = snippet(cx, *span, ".."); - if let Some(stripped) = s.strip_prefix('&') { - format!(".any(|x| x == {})", stripped) - } else { - format!(".any(|x| x == *{})", s) - } - }, - } - } - fn get_suggestion_text(&self) -> &'static str { - match &self.func { - IterFunctionKind::IntoIter => { - "use the original Iterator instead of collecting it and then producing a new one" - }, - IterFunctionKind::Len => { - "take the original Iterator's count instead of collecting it and finding the length" - }, - IterFunctionKind::IsEmpty => { - "check if the original Iterator has anything instead of collecting it and seeing if it's empty" - }, - IterFunctionKind::Contains(_) => { - "check if the original Iterator contains an element instead of collecting then checking" - }, - } - } -} -enum IterFunctionKind { - IntoIter, - Len, - IsEmpty, - Contains(Span), -} - -struct IterFunctionVisitor { - uses: Vec, - seen_other: bool, - target: Ident, -} -impl<'tcx> Visitor<'tcx> for IterFunctionVisitor { - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - // Check function calls on our collection - if_chain! { - if let ExprKind::MethodCall(method_name, _, ref args, _) = &expr.kind; - if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. }) = args.get(0); - if let &[name] = &path.segments; - if name.ident == self.target; - then { - let len = sym!(len); - let is_empty = sym!(is_empty); - let contains = sym!(contains); - match method_name.ident.name { - sym::into_iter => self.uses.push( - IterFunction { func: IterFunctionKind::IntoIter, span: expr.span } - ), - name if name == len => self.uses.push( - IterFunction { func: IterFunctionKind::Len, span: expr.span } - ), - name if name == is_empty => self.uses.push( - IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span } - ), - name if name == contains => self.uses.push( - IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span } - ), - _ => self.seen_other = true, - } - return - } - } - // Check if the collection is used for anything else - if_chain! { - if let Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. } = expr; - if let &[name] = &path.segments; - if name.ident == self.target; - then { - self.seen_other = true; - } else { - walk_expr(self, expr); - } - } - } - - type Map = Map<'tcx>; - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} - -struct UsedCountVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - id: HirId, - count: usize, -} - -impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if path_to_local_id(expr, self.id) { - self.count += 1; - } else { - walk_expr(self, expr); - } - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) - } -} - -/// Detect the occurrences of calls to `iter` or `into_iter` for the -/// given identifier -fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option> { - let mut visitor = IterFunctionVisitor { - uses: Vec::new(), - target: identifier, - seen_other: false, - }; - visitor.visit_block(block); - if visitor.seen_other { None } else { Some(visitor.uses) } -} - -fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span { - if_chain! { - if let ExprKind::MethodCall(.., args, _) = &expr.kind; - if let ExprKind::MethodCall(_, span, ..) = &args[0].kind; - then { - return expr.span.with_lo(span.lo()); - } - } - unreachable!(); -} diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs new file mode 100644 index 00000000000..cc2e2975492 --- /dev/null +++ b/clippy_lints/src/loops/needless_collect.rs @@ -0,0 +1,282 @@ +use crate::utils::sugg::Sugg; +use crate::utils::{ + is_type_diagnostic_item, match_trait_method, match_type, path_to_local_id, paths, snippet, span_lint_and_sugg, + span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{Block, Expr, ExprKind, GenericArg, HirId, Local, Pat, PatKind, QPath, StmtKind}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::source_map::Span; +use rustc_span::symbol::{sym, Ident}; + +const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; + +pub(super) fn check_needless_collect<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { + check_needless_collect_direct_usage(expr, cx); + check_needless_collect_indirect_usage(expr, cx); +} +fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { + if_chain! { + if let ExprKind::MethodCall(ref method, _, ref args, _) = expr.kind; + if let ExprKind::MethodCall(ref chain_method, _, _, _) = args[0].kind; + if chain_method.ident.name == sym!(collect) && match_trait_method(cx, &args[0], &paths::ITERATOR); + if let Some(ref generic_args) = chain_method.args; + if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); + then { + let ty = cx.typeck_results().node_type(ty.hir_id); + if is_type_diagnostic_item(cx, ty, sym::vec_type) || + is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || + match_type(cx, ty, &paths::BTREEMAP) || + is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) { + if method.ident.name == sym!(len) { + let span = shorten_needless_collect_span(expr); + span_lint_and_sugg( + cx, + super::NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + "replace with", + "count()".to_string(), + Applicability::MachineApplicable, + ); + } + if method.ident.name == sym!(is_empty) { + let span = shorten_needless_collect_span(expr); + span_lint_and_sugg( + cx, + super::NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + "replace with", + "next().is_none()".to_string(), + Applicability::MachineApplicable, + ); + } + if method.ident.name == sym!(contains) { + let contains_arg = snippet(cx, args[1].span, "??"); + let span = shorten_needless_collect_span(expr); + span_lint_and_then( + cx, + super::NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + |diag| { + let (arg, pred) = contains_arg + .strip_prefix('&') + .map_or(("&x", &*contains_arg), |s| ("x", s)); + diag.span_suggestion( + span, + "replace with", + format!( + "any(|{}| x == {})", + arg, pred + ), + Applicability::MachineApplicable, + ); + } + ); + } + } + } + } +} + +fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { + if let ExprKind::Block(ref block, _) = expr.kind { + for ref stmt in block.stmts { + if_chain! { + if let StmtKind::Local( + Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. }, + init: Some(ref init_expr), .. } + ) = stmt.kind; + if let ExprKind::MethodCall(ref method_name, _, &[ref iter_source], ..) = init_expr.kind; + if method_name.ident.name == sym!(collect) && match_trait_method(cx, &init_expr, &paths::ITERATOR); + if let Some(ref generic_args) = method_name.args; + if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); + if let ty = cx.typeck_results().node_type(ty.hir_id); + if is_type_diagnostic_item(cx, ty, sym::vec_type) || + is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || + match_type(cx, ty, &paths::LINKED_LIST); + if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident); + if iter_calls.len() == 1; + then { + let mut used_count_visitor = UsedCountVisitor { + cx, + id: *pat_id, + count: 0, + }; + walk_block(&mut used_count_visitor, block); + if used_count_visitor.count > 1 { + return; + } + + // Suggest replacing iter_call with iter_replacement, and removing stmt + let iter_call = &iter_calls[0]; + span_lint_and_then( + cx, + super::NEEDLESS_COLLECT, + stmt.span.until(iter_call.span), + NEEDLESS_COLLECT_MSG, + |diag| { + let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx)); + diag.multipart_suggestion( + iter_call.get_suggestion_text(), + vec![ + (stmt.span, String::new()), + (iter_call.span, iter_replacement) + ], + Applicability::MachineApplicable,// MaybeIncorrect, + ).emit(); + }, + ); + } + } + } + } +} + +struct IterFunction { + func: IterFunctionKind, + span: Span, +} +impl IterFunction { + fn get_iter_method(&self, cx: &LateContext<'_>) -> String { + match &self.func { + IterFunctionKind::IntoIter => String::new(), + IterFunctionKind::Len => String::from(".count()"), + IterFunctionKind::IsEmpty => String::from(".next().is_none()"), + IterFunctionKind::Contains(span) => { + let s = snippet(cx, *span, ".."); + if let Some(stripped) = s.strip_prefix('&') { + format!(".any(|x| x == {})", stripped) + } else { + format!(".any(|x| x == *{})", s) + } + }, + } + } + fn get_suggestion_text(&self) -> &'static str { + match &self.func { + IterFunctionKind::IntoIter => { + "use the original Iterator instead of collecting it and then producing a new one" + }, + IterFunctionKind::Len => { + "take the original Iterator's count instead of collecting it and finding the length" + }, + IterFunctionKind::IsEmpty => { + "check if the original Iterator has anything instead of collecting it and seeing if it's empty" + }, + IterFunctionKind::Contains(_) => { + "check if the original Iterator contains an element instead of collecting then checking" + }, + } + } +} +enum IterFunctionKind { + IntoIter, + Len, + IsEmpty, + Contains(Span), +} + +struct IterFunctionVisitor { + uses: Vec, + seen_other: bool, + target: Ident, +} +impl<'tcx> Visitor<'tcx> for IterFunctionVisitor { + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + // Check function calls on our collection + if_chain! { + if let ExprKind::MethodCall(method_name, _, ref args, _) = &expr.kind; + if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. }) = args.get(0); + if let &[name] = &path.segments; + if name.ident == self.target; + then { + let len = sym!(len); + let is_empty = sym!(is_empty); + let contains = sym!(contains); + match method_name.ident.name { + sym::into_iter => self.uses.push( + IterFunction { func: IterFunctionKind::IntoIter, span: expr.span } + ), + name if name == len => self.uses.push( + IterFunction { func: IterFunctionKind::Len, span: expr.span } + ), + name if name == is_empty => self.uses.push( + IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span } + ), + name if name == contains => self.uses.push( + IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span } + ), + _ => self.seen_other = true, + } + return + } + } + // Check if the collection is used for anything else + if_chain! { + if let Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. } = expr; + if let &[name] = &path.segments; + if name.ident == self.target; + then { + self.seen_other = true; + } else { + walk_expr(self, expr); + } + } + } + + type Map = Map<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +struct UsedCountVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + id: HirId, + count: usize, +} + +impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if path_to_local_id(expr, self.id) { + self.count += 1; + } else { + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} + +/// Detect the occurrences of calls to `iter` or `into_iter` for the +/// given identifier +fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option> { + let mut visitor = IterFunctionVisitor { + uses: Vec::new(), + target: identifier, + seen_other: false, + }; + visitor.visit_block(block); + if visitor.seen_other { None } else { Some(visitor.uses) } +} + +fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span { + if_chain! { + if let ExprKind::MethodCall(.., args, _) = &expr.kind; + if let ExprKind::MethodCall(_, span, ..) = &args[0].kind; + then { + return expr.span.with_lo(span.lo()); + } + } + unreachable!(); +} From 2c1f676bfe831f488cbd8e07d46b7b9be727ca24 Mon Sep 17 00:00:00 2001 From: nahuakang Date: Tue, 9 Feb 2021 21:58:10 +0100 Subject: [PATCH 10/22] Add detect_same_item_push to its own module --- clippy_lints/src/loops/mod.rs | 171 +---------------------- clippy_lints/src/loops/same_item_push.rs | 168 ++++++++++++++++++++++ 2 files changed, 174 insertions(+), 165 deletions(-) create mode 100644 clippy_lints/src/loops/same_item_push.rs diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 043bbc9d1c5..66b71a7202e 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -7,6 +7,7 @@ mod for_single_element_loop; mod infinite_loop; mod manual_flatten; mod needless_collect; +mod same_item_push; mod utils; use crate::utils::sugg::Sugg; @@ -14,18 +15,17 @@ use crate::utils::usage::mutated_variables; use crate::utils::{ get_enclosing_block, get_parent_expr, get_trait_def_id, higher, implements_trait, is_in_panic_handler, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, match_trait_method, - path_to_local, path_to_local_id, paths, snippet, snippet_with_applicability, snippet_with_macro_callsite, - span_lint, span_lint_and_help, span_lint_and_sugg, sugg, + path_to_local, path_to_local_id, paths, snippet, snippet_with_applicability, span_lint, span_lint_and_help, + span_lint_and_sugg, sugg, }; use if_chain::if_chain; use rustc_ast::ast; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; -use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; use rustc_hir::{ - BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, HirId, InlineAsmOperand, LoopSource, MatchSource, - Mutability, Node, Pat, PatKind, Stmt, StmtKind, + BinOpKind, Block, BorrowKind, Expr, ExprKind, HirId, InlineAsmOperand, LoopSource, MatchSource, Mutability, Node, + Pat, PatKind, Stmt, StmtKind, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; @@ -866,7 +866,7 @@ fn check_for_loop<'tcx>( for_loop_over_map_kv::check_for_loop_over_map_kv(cx, pat, arg, body, expr); for_mut_range_bound::check_for_mut_range_bound(cx, arg, body); for_single_element_loop::check_for_single_element_loop(cx, pat, arg, body, expr); - detect_same_item_push(cx, pat, arg, body, expr); + same_item_push::detect_same_item_push(cx, pat, arg, body, expr); manual_flatten::check_manual_flatten(cx, pat, arg, body, span); } @@ -1307,165 +1307,6 @@ fn detect_manual_memcpy<'tcx>( false } -// Scans the body of the for loop and determines whether lint should be given -struct SameItemPushVisitor<'a, 'tcx> { - should_lint: bool, - // this field holds the last vec push operation visited, which should be the only push seen - vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>, - cx: &'a LateContext<'tcx>, -} - -impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - match &expr.kind { - // Non-determinism may occur ... don't give a lint - ExprKind::Loop(..) | ExprKind::Match(..) => self.should_lint = false, - ExprKind::Block(block, _) => self.visit_block(block), - _ => {}, - } - } - - fn visit_block(&mut self, b: &'tcx Block<'_>) { - for stmt in b.stmts.iter() { - self.visit_stmt(stmt); - } - } - - fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) { - let vec_push_option = get_vec_push(self.cx, s); - if vec_push_option.is_none() { - // Current statement is not a push so visit inside - match &s.kind { - StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(&expr), - _ => {}, - } - } else { - // Current statement is a push ...check whether another - // push had been previously done - if self.vec_push.is_none() { - self.vec_push = vec_push_option; - } else { - // There are multiple pushes ... don't lint - self.should_lint = false; - } - } - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} - -// Given some statement, determine if that statement is a push on a Vec. If it is, return -// the Vec being pushed into and the item being pushed -fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { - if_chain! { - // Extract method being called - if let StmtKind::Semi(semi_stmt) = &stmt.kind; - if let ExprKind::MethodCall(path, _, args, _) = &semi_stmt.kind; - // Figure out the parameters for the method call - if let Some(self_expr) = args.get(0); - if let Some(pushed_item) = args.get(1); - // Check that the method being called is push() on a Vec - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::vec_type); - if path.ident.name.as_str() == "push"; - then { - return Some((self_expr, pushed_item)) - } - } - None -} - -/// Detects for loop pushing the same item into a Vec -fn detect_same_item_push<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - _: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - _: &'tcx Expr<'_>, -) { - fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) { - let vec_str = snippet_with_macro_callsite(cx, vec.span, ""); - let item_str = snippet_with_macro_callsite(cx, pushed_item.span, ""); - - span_lint_and_help( - cx, - SAME_ITEM_PUSH, - vec.span, - "it looks like the same item is being pushed into this Vec", - None, - &format!( - "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})", - item_str, vec_str, item_str - ), - ) - } - - if !matches!(pat.kind, PatKind::Wild) { - return; - } - - // Determine whether it is safe to lint the body - let mut same_item_push_visitor = SameItemPushVisitor { - should_lint: true, - vec_push: None, - cx, - }; - walk_expr(&mut same_item_push_visitor, body); - if same_item_push_visitor.should_lint { - if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push { - let vec_ty = cx.typeck_results().expr_ty(vec); - let ty = vec_ty.walk().nth(1).unwrap().expect_ty(); - if cx - .tcx - .lang_items() - .clone_trait() - .map_or(false, |id| implements_trait(cx, ty, id, &[])) - { - // Make sure that the push does not involve possibly mutating values - match pushed_item.kind { - ExprKind::Path(ref qpath) => { - match cx.qpath_res(qpath, pushed_item.hir_id) { - // immutable bindings that are initialized with literal or constant - Res::Local(hir_id) => { - if_chain! { - let node = cx.tcx.hir().get(hir_id); - if let Node::Binding(pat) = node; - if let PatKind::Binding(bind_ann, ..) = pat.kind; - if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable); - let parent_node = cx.tcx.hir().get_parent_node(hir_id); - if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node); - if let Some(init) = parent_let_expr.init; - then { - match init.kind { - // immutable bindings that are initialized with literal - ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), - // immutable bindings that are initialized with constant - ExprKind::Path(ref path) => { - if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) { - emit_lint(cx, vec, pushed_item); - } - } - _ => {}, - } - } - } - }, - // constant - Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item), - _ => {}, - } - }, - ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), - _ => {}, - } - } - } - } -} - fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool { let def_id = match path_to_local(expr) { Some(id) => id, diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs new file mode 100644 index 00000000000..62efb58a38f --- /dev/null +++ b/clippy_lints/src/loops/same_item_push.rs @@ -0,0 +1,168 @@ +use crate::utils::{implements_trait, is_type_diagnostic_item, snippet_with_macro_callsite, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Node, Pat, PatKind, Stmt, StmtKind}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::symbol::sym; +use std::iter::Iterator; + +/// Detects for loop pushing the same item into a Vec +pub(super) fn detect_same_item_push<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + _: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + _: &'tcx Expr<'_>, +) { + fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) { + let vec_str = snippet_with_macro_callsite(cx, vec.span, ""); + let item_str = snippet_with_macro_callsite(cx, pushed_item.span, ""); + + span_lint_and_help( + cx, + super::SAME_ITEM_PUSH, + vec.span, + "it looks like the same item is being pushed into this Vec", + None, + &format!( + "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})", + item_str, vec_str, item_str + ), + ) + } + + if !matches!(pat.kind, PatKind::Wild) { + return; + } + + // Determine whether it is safe to lint the body + let mut same_item_push_visitor = SameItemPushVisitor { + should_lint: true, + vec_push: None, + cx, + }; + walk_expr(&mut same_item_push_visitor, body); + if same_item_push_visitor.should_lint { + if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push { + let vec_ty = cx.typeck_results().expr_ty(vec); + let ty = vec_ty.walk().nth(1).unwrap().expect_ty(); + if cx + .tcx + .lang_items() + .clone_trait() + .map_or(false, |id| implements_trait(cx, ty, id, &[])) + { + // Make sure that the push does not involve possibly mutating values + match pushed_item.kind { + ExprKind::Path(ref qpath) => { + match cx.qpath_res(qpath, pushed_item.hir_id) { + // immutable bindings that are initialized with literal or constant + Res::Local(hir_id) => { + if_chain! { + let node = cx.tcx.hir().get(hir_id); + if let Node::Binding(pat) = node; + if let PatKind::Binding(bind_ann, ..) = pat.kind; + if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable); + let parent_node = cx.tcx.hir().get_parent_node(hir_id); + if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node); + if let Some(init) = parent_let_expr.init; + then { + match init.kind { + // immutable bindings that are initialized with literal + ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), + // immutable bindings that are initialized with constant + ExprKind::Path(ref path) => { + if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) { + emit_lint(cx, vec, pushed_item); + } + } + _ => {}, + } + } + } + }, + // constant + Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item), + _ => {}, + } + }, + ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), + _ => {}, + } + } + } + } +} + +// Scans the body of the for loop and determines whether lint should be given +struct SameItemPushVisitor<'a, 'tcx> { + should_lint: bool, + // this field holds the last vec push operation visited, which should be the only push seen + vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>, + cx: &'a LateContext<'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + match &expr.kind { + // Non-determinism may occur ... don't give a lint + ExprKind::Loop(..) | ExprKind::Match(..) => self.should_lint = false, + ExprKind::Block(block, _) => self.visit_block(block), + _ => {}, + } + } + + fn visit_block(&mut self, b: &'tcx Block<'_>) { + for stmt in b.stmts.iter() { + self.visit_stmt(stmt); + } + } + + fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) { + let vec_push_option = get_vec_push(self.cx, s); + if vec_push_option.is_none() { + // Current statement is not a push so visit inside + match &s.kind { + StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(&expr), + _ => {}, + } + } else { + // Current statement is a push ...check whether another + // push had been previously done + if self.vec_push.is_none() { + self.vec_push = vec_push_option; + } else { + // There are multiple pushes ... don't lint + self.should_lint = false; + } + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +// Given some statement, determine if that statement is a push on a Vec. If it is, return +// the Vec being pushed into and the item being pushed +fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + if_chain! { + // Extract method being called + if let StmtKind::Semi(semi_stmt) = &stmt.kind; + if let ExprKind::MethodCall(path, _, args, _) = &semi_stmt.kind; + // Figure out the parameters for the method call + if let Some(self_expr) = args.get(0); + if let Some(pushed_item) = args.get(1); + // Check that the method being called is push() on a Vec + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::vec_type); + if path.ident.name.as_str() == "push"; + then { + return Some((self_expr, pushed_item)) + } + } + None +} From 5b870e1b36e781d842c7c84290707f2aaf5dc8cf Mon Sep 17 00:00:00 2001 From: nahuakang Date: Tue, 9 Feb 2021 23:27:03 +0100 Subject: [PATCH 11/22] Move detect_manual_memcpy to its module; split up utils structs --- clippy_lints/src/loops/manual_memcpy.rs | 381 +++++++++++++ clippy_lints/src/loops/mod.rs | 684 +----------------------- clippy_lints/src/loops/utils.rs | 302 ++++++++++- 3 files changed, 694 insertions(+), 673 deletions(-) create mode 100644 clippy_lints/src/loops/manual_memcpy.rs diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs new file mode 100644 index 00000000000..85b3dfaece7 --- /dev/null +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -0,0 +1,381 @@ +use super::{get_span_of_entire_for_loop, IncrementVisitor, InitializeVisitor, MinifyingSugg}; +use crate::utils::sugg::Sugg; +use crate::utils::{ + get_enclosing_block, higher, is_type_diagnostic_item, path_to_local, snippet, span_lint_and_sugg, sugg, +}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir::intravisit::walk_block; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Pat, PatKind, StmtKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::symbol::sym; +use std::iter::Iterator; + +/// Checks for for loops that sequentially copy items from one slice-like +/// object to another. +pub(super) fn detect_manual_memcpy<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) -> bool { + if let Some(higher::Range { + start: Some(start), + end: Some(end), + limits, + }) = higher::range(arg) + { + // the var must be a single name + if let PatKind::Binding(_, canonical_id, _, _) = pat.kind { + let mut starts = vec![Start { + id: canonical_id, + kind: StartKind::Range, + }]; + + // This is one of few ways to return different iterators + // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434 + let mut iter_a = None; + let mut iter_b = None; + + if let ExprKind::Block(block, _) = body.kind { + if let Some(loop_counters) = get_loop_counters(cx, block, expr) { + starts.extend(loop_counters); + } + iter_a = Some(get_assignments(block, &starts)); + } else { + iter_b = Some(get_assignment(body)); + } + + let assignments = iter_a.into_iter().flatten().chain(iter_b.into_iter()); + + let big_sugg = assignments + // The only statements in the for loops can be indexed assignments from + // indexed retrievals (except increments of loop counters). + .map(|o| { + o.and_then(|(lhs, rhs)| { + let rhs = fetch_cloned_expr(rhs); + if_chain! { + if let ExprKind::Index(base_left, idx_left) = lhs.kind; + if let ExprKind::Index(base_right, idx_right) = rhs.kind; + if is_slice_like(cx, cx.typeck_results().expr_ty(base_left)) + && is_slice_like(cx, cx.typeck_results().expr_ty(base_right)); + if let Some((start_left, offset_left)) = get_details_from_idx(cx, &idx_left, &starts); + if let Some((start_right, offset_right)) = get_details_from_idx(cx, &idx_right, &starts); + + // Source and destination must be different + if path_to_local(base_left) != path_to_local(base_right); + then { + Some((IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left }, + IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right })) + } else { + None + } + } + }) + }) + .map(|o| o.map(|(dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, &dst, &src))) + .collect::>>() + .filter(|v| !v.is_empty()) + .map(|v| v.join("\n ")); + + if let Some(big_sugg) = big_sugg { + span_lint_and_sugg( + cx, + super::MANUAL_MEMCPY, + get_span_of_entire_for_loop(expr), + "it looks like you're manually copying between slices", + "try replacing the loop by", + big_sugg, + Applicability::Unspecified, + ); + return true; + } + } + } + false +} + +fn build_manual_memcpy_suggestion<'tcx>( + cx: &LateContext<'tcx>, + start: &Expr<'_>, + end: &Expr<'_>, + limits: ast::RangeLimits, + dst: &IndexExpr<'_>, + src: &IndexExpr<'_>, +) -> String { + fn print_offset(offset: MinifyingSugg<'static>) -> MinifyingSugg<'static> { + if offset.as_str() == "0" { + sugg::EMPTY.into() + } else { + offset + } + } + + let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| { + if_chain! { + if let ExprKind::MethodCall(method, _, len_args, _) = end.kind; + if method.ident.name == sym!(len); + if len_args.len() == 1; + if let Some(arg) = len_args.get(0); + if path_to_local(arg) == path_to_local(base); + then { + if sugg.as_str() == end_str { + sugg::EMPTY.into() + } else { + sugg + } + } else { + match limits { + ast::RangeLimits::Closed => { + sugg + &sugg::ONE.into() + }, + ast::RangeLimits::HalfOpen => sugg, + } + } + } + }; + + let start_str = Sugg::hir(cx, start, "").into(); + let end_str: MinifyingSugg<'_> = Sugg::hir(cx, end, "").into(); + + let print_offset_and_limit = |idx_expr: &IndexExpr<'_>| match idx_expr.idx { + StartKind::Range => ( + print_offset(apply_offset(&start_str, &idx_expr.idx_offset)).into_sugg(), + print_limit( + end, + end_str.as_str(), + idx_expr.base, + apply_offset(&end_str, &idx_expr.idx_offset), + ) + .into_sugg(), + ), + StartKind::Counter { initializer } => { + let counter_start = Sugg::hir(cx, initializer, "").into(); + ( + print_offset(apply_offset(&counter_start, &idx_expr.idx_offset)).into_sugg(), + print_limit( + end, + end_str.as_str(), + idx_expr.base, + apply_offset(&end_str, &idx_expr.idx_offset) + &counter_start - &start_str, + ) + .into_sugg(), + ) + }, + }; + + let (dst_offset, dst_limit) = print_offset_and_limit(&dst); + let (src_offset, src_limit) = print_offset_and_limit(&src); + + let dst_base_str = snippet(cx, dst.base.span, "???"); + let src_base_str = snippet(cx, src.base.span, "???"); + + let dst = if dst_offset == sugg::EMPTY && dst_limit == sugg::EMPTY { + dst_base_str + } else { + format!( + "{}[{}..{}]", + dst_base_str, + dst_offset.maybe_par(), + dst_limit.maybe_par() + ) + .into() + }; + + format!( + "{}.clone_from_slice(&{}[{}..{}]);", + dst, + src_base_str, + src_offset.maybe_par(), + src_limit.maybe_par() + ) +} + +/// a wrapper around `MinifyingSugg`, which carries a operator like currying +/// so that the suggested code become more efficient (e.g. `foo + -bar` `foo - bar`). +struct Offset { + value: MinifyingSugg<'static>, + sign: OffsetSign, +} + +#[derive(Clone, Copy)] +enum OffsetSign { + Positive, + Negative, +} + +impl Offset { + fn negative(value: Sugg<'static>) -> Self { + Self { + value: value.into(), + sign: OffsetSign::Negative, + } + } + + fn positive(value: Sugg<'static>) -> Self { + Self { + value: value.into(), + sign: OffsetSign::Positive, + } + } + + fn empty() -> Self { + Self::positive(sugg::ZERO) + } +} + +fn apply_offset(lhs: &MinifyingSugg<'static>, rhs: &Offset) -> MinifyingSugg<'static> { + match rhs.sign { + OffsetSign::Positive => lhs + &rhs.value, + OffsetSign::Negative => lhs - &rhs.value, + } +} + +#[derive(Debug, Clone, Copy)] +enum StartKind<'hir> { + Range, + Counter { initializer: &'hir Expr<'hir> }, +} + +struct IndexExpr<'hir> { + base: &'hir Expr<'hir>, + idx: StartKind<'hir>, + idx_offset: Offset, +} + +struct Start<'hir> { + id: HirId, + kind: StartKind<'hir>, +} + +fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'_>) -> bool { + let is_slice = match ty.kind() { + ty::Ref(_, subty, _) => is_slice_like(cx, subty), + ty::Slice(..) | ty::Array(..) => true, + _ => false, + }; + + is_slice || is_type_diagnostic_item(cx, ty, sym::vec_type) || is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) +} + +fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { + if_chain! { + if let ExprKind::MethodCall(method, _, args, _) = expr.kind; + if method.ident.name == sym::clone; + if args.len() == 1; + if let Some(arg) = args.get(0); + then { arg } else { expr } + } +} + +fn get_details_from_idx<'tcx>( + cx: &LateContext<'tcx>, + idx: &Expr<'_>, + starts: &[Start<'tcx>], +) -> Option<(StartKind<'tcx>, Offset)> { + fn get_start<'tcx>(e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option> { + let id = path_to_local(e)?; + starts.iter().find(|start| start.id == id).map(|start| start.kind) + } + + fn get_offset<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option> { + match &e.kind { + ExprKind::Lit(l) => match l.node { + ast::LitKind::Int(x, _ty) => Some(Sugg::NonParen(x.to_string().into())), + _ => None, + }, + ExprKind::Path(..) if get_start(e, starts).is_none() => Some(Sugg::hir(cx, e, "???")), + _ => None, + } + } + + match idx.kind { + ExprKind::Binary(op, lhs, rhs) => match op.node { + BinOpKind::Add => { + let offset_opt = get_start(lhs, starts) + .and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, o))) + .or_else(|| get_start(rhs, starts).and_then(|s| get_offset(cx, lhs, starts).map(|o| (s, o)))); + + offset_opt.map(|(s, o)| (s, Offset::positive(o))) + }, + BinOpKind::Sub => { + get_start(lhs, starts).and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, Offset::negative(o)))) + }, + _ => None, + }, + ExprKind::Path(..) => get_start(idx, starts).map(|s| (s, Offset::empty())), + _ => None, + } +} + +fn get_assignment<'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + if let ExprKind::Assign(lhs, rhs, _) = e.kind { + Some((lhs, rhs)) + } else { + None + } +} + +/// Get assignments from the given block. +/// The returned iterator yields `None` if no assignment expressions are there, +/// filtering out the increments of the given whitelisted loop counters; +/// because its job is to make sure there's nothing other than assignments and the increments. +fn get_assignments<'a, 'tcx>( + Block { stmts, expr, .. }: &'tcx Block<'tcx>, + loop_counters: &'a [Start<'tcx>], +) -> impl Iterator, &'tcx Expr<'tcx>)>> + 'a { + // As the `filter` and `map` below do different things, I think putting together + // just increases complexity. (cc #3188 and #4193) + stmts + .iter() + .filter_map(move |stmt| match stmt.kind { + StmtKind::Local(..) | StmtKind::Item(..) => None, + StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e), + }) + .chain((*expr).into_iter()) + .filter(move |e| { + if let ExprKind::AssignOp(_, place, _) = e.kind { + path_to_local(place).map_or(false, |id| { + !loop_counters + .iter() + // skip the first item which should be `StartKind::Range` + // this makes it possible to use the slice with `StartKind::Range` in the same iterator loop. + .skip(1) + .any(|counter| counter.id == id) + }) + } else { + true + } + }) + .map(get_assignment) +} + +fn get_loop_counters<'a, 'tcx>( + cx: &'a LateContext<'tcx>, + body: &'tcx Block<'tcx>, + expr: &'tcx Expr<'_>, +) -> Option> + 'a> { + // Look for variables that are incremented once per loop iteration. + let mut increment_visitor = IncrementVisitor::new(cx); + walk_block(&mut increment_visitor, body); + + // For each candidate, check the parent block to see if + // it's initialized to zero at the start of the loop. + get_enclosing_block(&cx, expr.hir_id).and_then(|block| { + increment_visitor + .into_results() + .filter_map(move |var_id| { + let mut initialize_visitor = InitializeVisitor::new(cx, expr, var_id); + walk_block(&mut initialize_visitor, block); + + initialize_visitor.get_result().map(|(_, initializer)| Start { + id: var_id, + kind: StartKind::Counter { initializer }, + }) + }) + .into() + }) +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 66b71a7202e..c6055e34eb2 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -6,6 +6,7 @@ mod for_mut_range_bound; mod for_single_element_loop; mod infinite_loop; mod manual_flatten; +mod manual_memcpy; mod needless_collect; mod same_item_push; mod utils; @@ -13,29 +14,26 @@ mod utils; use crate::utils::sugg::Sugg; use crate::utils::usage::mutated_variables; use crate::utils::{ - get_enclosing_block, get_parent_expr, get_trait_def_id, higher, implements_trait, is_in_panic_handler, - is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, match_trait_method, - path_to_local, path_to_local_id, paths, snippet, snippet_with_applicability, span_lint, span_lint_and_help, - span_lint_and_sugg, sugg, + get_enclosing_block, get_trait_def_id, higher, implements_trait, is_in_panic_handler, is_no_std_crate, + is_refutable, last_path_segment, match_trait_method, path_to_local, path_to_local_id, paths, + snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg, sugg, }; use if_chain::if_chain; -use rustc_ast::ast; -use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; -use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; +use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::{ - BinOpKind, Block, BorrowKind, Expr, ExprKind, HirId, InlineAsmOperand, LoopSource, MatchSource, Mutability, Node, - Pat, PatKind, Stmt, StmtKind, + Block, Expr, ExprKind, HirId, InlineAsmOperand, LoopSource, MatchSource, Node, Pat, PatKind, Stmt, StmtKind, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; -use rustc_span::symbol::{sym, Symbol}; +use rustc_span::symbol::sym; use std::iter::{once, Iterator}; -use utils::{get_span_of_entire_for_loop, make_iterator_snippet}; +use utils::{ + get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor, LoopNestVisitor, Nesting, +}; declare_clippy_lint! { /// **What it does:** Checks for for-loops that manually copy items between @@ -857,7 +855,7 @@ fn check_for_loop<'tcx>( expr: &'tcx Expr<'_>, span: Span, ) { - let is_manual_memcpy_triggered = detect_manual_memcpy(cx, pat, arg, body, expr); + let is_manual_memcpy_triggered = manual_memcpy::detect_manual_memcpy(cx, pat, arg, body, expr); if !is_manual_memcpy_triggered { for_loop_range::check_for_loop_range(cx, pat, arg, body, expr); for_loop_explicit_counter::check_for_loop_explicit_counter(cx, pat, arg, body, expr); @@ -940,373 +938,6 @@ impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> { } } -/// a wrapper around `MinifyingSugg`, which carries a operator like currying -/// so that the suggested code become more efficient (e.g. `foo + -bar` `foo - bar`). -struct Offset { - value: MinifyingSugg<'static>, - sign: OffsetSign, -} - -#[derive(Clone, Copy)] -enum OffsetSign { - Positive, - Negative, -} - -impl Offset { - fn negative(value: Sugg<'static>) -> Self { - Self { - value: value.into(), - sign: OffsetSign::Negative, - } - } - - fn positive(value: Sugg<'static>) -> Self { - Self { - value: value.into(), - sign: OffsetSign::Positive, - } - } - - fn empty() -> Self { - Self::positive(sugg::ZERO) - } -} - -fn apply_offset(lhs: &MinifyingSugg<'static>, rhs: &Offset) -> MinifyingSugg<'static> { - match rhs.sign { - OffsetSign::Positive => lhs + &rhs.value, - OffsetSign::Negative => lhs - &rhs.value, - } -} - -#[derive(Debug, Clone, Copy)] -enum StartKind<'hir> { - Range, - Counter { initializer: &'hir Expr<'hir> }, -} - -struct IndexExpr<'hir> { - base: &'hir Expr<'hir>, - idx: StartKind<'hir>, - idx_offset: Offset, -} - -struct Start<'hir> { - id: HirId, - kind: StartKind<'hir>, -} - -fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'_>) -> bool { - let is_slice = match ty.kind() { - ty::Ref(_, subty, _) => is_slice_like(cx, subty), - ty::Slice(..) | ty::Array(..) => true, - _ => false, - }; - - is_slice || is_type_diagnostic_item(cx, ty, sym::vec_type) || is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) -} - -fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { - if_chain! { - if let ExprKind::MethodCall(method, _, args, _) = expr.kind; - if method.ident.name == sym::clone; - if args.len() == 1; - if let Some(arg) = args.get(0); - then { arg } else { expr } - } -} - -fn get_details_from_idx<'tcx>( - cx: &LateContext<'tcx>, - idx: &Expr<'_>, - starts: &[Start<'tcx>], -) -> Option<(StartKind<'tcx>, Offset)> { - fn get_start<'tcx>(e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option> { - let id = path_to_local(e)?; - starts.iter().find(|start| start.id == id).map(|start| start.kind) - } - - fn get_offset<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option> { - match &e.kind { - ExprKind::Lit(l) => match l.node { - ast::LitKind::Int(x, _ty) => Some(Sugg::NonParen(x.to_string().into())), - _ => None, - }, - ExprKind::Path(..) if get_start(e, starts).is_none() => Some(Sugg::hir(cx, e, "???")), - _ => None, - } - } - - match idx.kind { - ExprKind::Binary(op, lhs, rhs) => match op.node { - BinOpKind::Add => { - let offset_opt = get_start(lhs, starts) - .and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, o))) - .or_else(|| get_start(rhs, starts).and_then(|s| get_offset(cx, lhs, starts).map(|o| (s, o)))); - - offset_opt.map(|(s, o)| (s, Offset::positive(o))) - }, - BinOpKind::Sub => { - get_start(lhs, starts).and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, Offset::negative(o)))) - }, - _ => None, - }, - ExprKind::Path(..) => get_start(idx, starts).map(|s| (s, Offset::empty())), - _ => None, - } -} - -fn get_assignment<'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { - if let ExprKind::Assign(lhs, rhs, _) = e.kind { - Some((lhs, rhs)) - } else { - None - } -} - -/// Get assignments from the given block. -/// The returned iterator yields `None` if no assignment expressions are there, -/// filtering out the increments of the given whitelisted loop counters; -/// because its job is to make sure there's nothing other than assignments and the increments. -fn get_assignments<'a, 'tcx>( - Block { stmts, expr, .. }: &'tcx Block<'tcx>, - loop_counters: &'a [Start<'tcx>], -) -> impl Iterator, &'tcx Expr<'tcx>)>> + 'a { - // As the `filter` and `map` below do different things, I think putting together - // just increases complexity. (cc #3188 and #4193) - stmts - .iter() - .filter_map(move |stmt| match stmt.kind { - StmtKind::Local(..) | StmtKind::Item(..) => None, - StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e), - }) - .chain((*expr).into_iter()) - .filter(move |e| { - if let ExprKind::AssignOp(_, place, _) = e.kind { - path_to_local(place).map_or(false, |id| { - !loop_counters - .iter() - // skip the first item which should be `StartKind::Range` - // this makes it possible to use the slice with `StartKind::Range` in the same iterator loop. - .skip(1) - .any(|counter| counter.id == id) - }) - } else { - true - } - }) - .map(get_assignment) -} - -fn get_loop_counters<'a, 'tcx>( - cx: &'a LateContext<'tcx>, - body: &'tcx Block<'tcx>, - expr: &'tcx Expr<'_>, -) -> Option> + 'a> { - // Look for variables that are incremented once per loop iteration. - let mut increment_visitor = IncrementVisitor::new(cx); - walk_block(&mut increment_visitor, body); - - // For each candidate, check the parent block to see if - // it's initialized to zero at the start of the loop. - get_enclosing_block(&cx, expr.hir_id).and_then(|block| { - increment_visitor - .into_results() - .filter_map(move |var_id| { - let mut initialize_visitor = InitializeVisitor::new(cx, expr, var_id); - walk_block(&mut initialize_visitor, block); - - initialize_visitor.get_result().map(|(_, initializer)| Start { - id: var_id, - kind: StartKind::Counter { initializer }, - }) - }) - .into() - }) -} - -fn build_manual_memcpy_suggestion<'tcx>( - cx: &LateContext<'tcx>, - start: &Expr<'_>, - end: &Expr<'_>, - limits: ast::RangeLimits, - dst: &IndexExpr<'_>, - src: &IndexExpr<'_>, -) -> String { - fn print_offset(offset: MinifyingSugg<'static>) -> MinifyingSugg<'static> { - if offset.as_str() == "0" { - sugg::EMPTY.into() - } else { - offset - } - } - - let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| { - if_chain! { - if let ExprKind::MethodCall(method, _, len_args, _) = end.kind; - if method.ident.name == sym!(len); - if len_args.len() == 1; - if let Some(arg) = len_args.get(0); - if path_to_local(arg) == path_to_local(base); - then { - if sugg.as_str() == end_str { - sugg::EMPTY.into() - } else { - sugg - } - } else { - match limits { - ast::RangeLimits::Closed => { - sugg + &sugg::ONE.into() - }, - ast::RangeLimits::HalfOpen => sugg, - } - } - } - }; - - let start_str = Sugg::hir(cx, start, "").into(); - let end_str: MinifyingSugg<'_> = Sugg::hir(cx, end, "").into(); - - let print_offset_and_limit = |idx_expr: &IndexExpr<'_>| match idx_expr.idx { - StartKind::Range => ( - print_offset(apply_offset(&start_str, &idx_expr.idx_offset)).into_sugg(), - print_limit( - end, - end_str.as_str(), - idx_expr.base, - apply_offset(&end_str, &idx_expr.idx_offset), - ) - .into_sugg(), - ), - StartKind::Counter { initializer } => { - let counter_start = Sugg::hir(cx, initializer, "").into(); - ( - print_offset(apply_offset(&counter_start, &idx_expr.idx_offset)).into_sugg(), - print_limit( - end, - end_str.as_str(), - idx_expr.base, - apply_offset(&end_str, &idx_expr.idx_offset) + &counter_start - &start_str, - ) - .into_sugg(), - ) - }, - }; - - let (dst_offset, dst_limit) = print_offset_and_limit(&dst); - let (src_offset, src_limit) = print_offset_and_limit(&src); - - let dst_base_str = snippet(cx, dst.base.span, "???"); - let src_base_str = snippet(cx, src.base.span, "???"); - - let dst = if dst_offset == sugg::EMPTY && dst_limit == sugg::EMPTY { - dst_base_str - } else { - format!( - "{}[{}..{}]", - dst_base_str, - dst_offset.maybe_par(), - dst_limit.maybe_par() - ) - .into() - }; - - format!( - "{}.clone_from_slice(&{}[{}..{}]);", - dst, - src_base_str, - src_offset.maybe_par(), - src_limit.maybe_par() - ) -} - -/// Checks for for loops that sequentially copy items from one slice-like -/// object to another. -fn detect_manual_memcpy<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, -) -> bool { - if let Some(higher::Range { - start: Some(start), - end: Some(end), - limits, - }) = higher::range(arg) - { - // the var must be a single name - if let PatKind::Binding(_, canonical_id, _, _) = pat.kind { - let mut starts = vec![Start { - id: canonical_id, - kind: StartKind::Range, - }]; - - // This is one of few ways to return different iterators - // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434 - let mut iter_a = None; - let mut iter_b = None; - - if let ExprKind::Block(block, _) = body.kind { - if let Some(loop_counters) = get_loop_counters(cx, block, expr) { - starts.extend(loop_counters); - } - iter_a = Some(get_assignments(block, &starts)); - } else { - iter_b = Some(get_assignment(body)); - } - - let assignments = iter_a.into_iter().flatten().chain(iter_b.into_iter()); - - let big_sugg = assignments - // The only statements in the for loops can be indexed assignments from - // indexed retrievals (except increments of loop counters). - .map(|o| { - o.and_then(|(lhs, rhs)| { - let rhs = fetch_cloned_expr(rhs); - if_chain! { - if let ExprKind::Index(base_left, idx_left) = lhs.kind; - if let ExprKind::Index(base_right, idx_right) = rhs.kind; - if is_slice_like(cx, cx.typeck_results().expr_ty(base_left)) - && is_slice_like(cx, cx.typeck_results().expr_ty(base_right)); - if let Some((start_left, offset_left)) = get_details_from_idx(cx, &idx_left, &starts); - if let Some((start_right, offset_right)) = get_details_from_idx(cx, &idx_right, &starts); - - // Source and destination must be different - if path_to_local(base_left) != path_to_local(base_right); - then { - Some((IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left }, - IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right })) - } else { - None - } - } - }) - }) - .map(|o| o.map(|(dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, &dst, &src))) - .collect::>>() - .filter(|v| !v.is_empty()) - .map(|v| v.join("\n ")); - - if let Some(big_sugg) = big_sugg { - span_lint_and_sugg( - cx, - MANUAL_MEMCPY, - get_span_of_entire_for_loop(expr), - "it looks like you're manually copying between slices", - "try replacing the loop by", - big_sugg, - Applicability::Unspecified, - ); - return true; - } - } - } - false -} - fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool { let def_id = match path_to_local(expr) { Some(id) => id, @@ -1398,233 +1029,6 @@ fn is_simple_break_expr(expr: &Expr<'_>) -> bool { } } -#[derive(Debug, PartialEq)] -enum IncrementVisitorVarState { - Initial, // Not examined yet - IncrOnce, // Incremented exactly once, may be a loop counter - DontWarn, -} - -/// Scan a for loop for variables that are incremented exactly once and not used after that. -struct IncrementVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, // context reference - states: FxHashMap, // incremented variables - depth: u32, // depth of conditional expressions - done: bool, -} - -impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>) -> Self { - Self { - cx, - states: FxHashMap::default(), - depth: 0, - done: false, - } - } - - fn into_results(self) -> impl Iterator { - self.states.into_iter().filter_map(|(id, state)| { - if state == IncrementVisitorVarState::IncrOnce { - Some(id) - } else { - None - } - }) - } -} - -impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.done { - return; - } - - // If node is a variable - if let Some(def_id) = path_to_local(expr) { - if let Some(parent) = get_parent_expr(self.cx, expr) { - let state = self.states.entry(def_id).or_insert(IncrementVisitorVarState::Initial); - if *state == IncrementVisitorVarState::IncrOnce { - *state = IncrementVisitorVarState::DontWarn; - return; - } - - match parent.kind { - ExprKind::AssignOp(op, ref lhs, ref rhs) => { - if lhs.hir_id == expr.hir_id { - *state = if op.node == BinOpKind::Add - && is_integer_const(self.cx, rhs, 1) - && *state == IncrementVisitorVarState::Initial - && self.depth == 0 - { - IncrementVisitorVarState::IncrOnce - } else { - // Assigned some other value or assigned multiple times - IncrementVisitorVarState::DontWarn - }; - } - }, - ExprKind::Assign(ref lhs, _, _) if lhs.hir_id == expr.hir_id => { - *state = IncrementVisitorVarState::DontWarn - }, - ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { - *state = IncrementVisitorVarState::DontWarn - }, - _ => (), - } - } - - walk_expr(self, expr); - } else if is_loop(expr) || is_conditional(expr) { - self.depth += 1; - walk_expr(self, expr); - self.depth -= 1; - } else if let ExprKind::Continue(_) = expr.kind { - self.done = true; - } else { - walk_expr(self, expr); - } - } - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} - -enum InitializeVisitorState<'hir> { - Initial, // Not examined yet - Declared(Symbol), // Declared but not (yet) initialized - Initialized { - name: Symbol, - initializer: &'hir Expr<'hir>, - }, - DontWarn, -} - -/// Checks whether a variable is initialized at the start of a loop and not modified -/// and used after the loop. -struct InitializeVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, // context reference - end_expr: &'tcx Expr<'tcx>, // the for loop. Stop scanning here. - var_id: HirId, - state: InitializeVisitorState<'tcx>, - depth: u32, // depth of conditional expressions - past_loop: bool, -} - -impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>, end_expr: &'tcx Expr<'tcx>, var_id: HirId) -> Self { - Self { - cx, - end_expr, - var_id, - state: InitializeVisitorState::Initial, - depth: 0, - past_loop: false, - } - } - - fn get_result(&self) -> Option<(Symbol, &'tcx Expr<'tcx>)> { - if let InitializeVisitorState::Initialized { name, initializer } = self.state { - Some((name, initializer)) - } else { - None - } - } -} - -impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { - // Look for declarations of the variable - if_chain! { - if let StmtKind::Local(ref local) = stmt.kind; - if local.pat.hir_id == self.var_id; - if let PatKind::Binding(.., ident, _) = local.pat.kind; - then { - self.state = local.init.map_or(InitializeVisitorState::Declared(ident.name), |init| { - InitializeVisitorState::Initialized { - initializer: init, - name: ident.name, - } - }) - } - } - walk_stmt(self, stmt); - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if matches!(self.state, InitializeVisitorState::DontWarn) { - return; - } - if expr.hir_id == self.end_expr.hir_id { - self.past_loop = true; - return; - } - // No need to visit expressions before the variable is - // declared - if matches!(self.state, InitializeVisitorState::Initial) { - return; - } - - // If node is the desired variable, see how it's used - if path_to_local_id(expr, self.var_id) { - if self.past_loop { - self.state = InitializeVisitorState::DontWarn; - return; - } - - if let Some(parent) = get_parent_expr(self.cx, expr) { - match parent.kind { - ExprKind::AssignOp(_, ref lhs, _) if lhs.hir_id == expr.hir_id => { - self.state = InitializeVisitorState::DontWarn; - }, - ExprKind::Assign(ref lhs, ref rhs, _) if lhs.hir_id == expr.hir_id => { - self.state = if_chain! { - if self.depth == 0; - if let InitializeVisitorState::Declared(name) - | InitializeVisitorState::Initialized { name, ..} = self.state; - then { - InitializeVisitorState::Initialized { initializer: rhs, name } - } else { - InitializeVisitorState::DontWarn - } - } - }, - ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { - self.state = InitializeVisitorState::DontWarn - }, - _ => (), - } - } - - walk_expr(self, expr); - } else if !self.past_loop && is_loop(expr) { - self.state = InitializeVisitorState::DontWarn; - } else if is_conditional(expr) { - self.depth += 1; - walk_expr(self, expr); - self.depth -= 1; - } else { - walk_expr(self, expr); - } - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) - } -} - -fn is_loop(expr: &Expr<'_>) -> bool { - matches!(expr.kind, ExprKind::Loop(..)) -} - -fn is_conditional(expr: &Expr<'_>) -> bool { - matches!(expr.kind, ExprKind::If(..) | ExprKind::Match(..)) -} - fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { if_chain! { if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id); @@ -1659,10 +1063,10 @@ fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<' let mut block_visitor = LoopNestVisitor { hir_id: id, iterator: iter_id, - nesting: Unknown, + nesting: Nesting::Unknown, }; walk_block(&mut block_visitor, block); - if block_visitor.nesting == RuledOut { + if block_visitor.nesting == Nesting::RuledOut { return false; } }, @@ -1674,65 +1078,3 @@ fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<' id = parent; } } - -#[derive(PartialEq, Eq)] -enum Nesting { - Unknown, // no nesting detected yet - RuledOut, // the iterator is initialized or assigned within scope - LookFurther, // no nesting detected, no further walk required -} - -use self::Nesting::{LookFurther, RuledOut, Unknown}; - -struct LoopNestVisitor { - hir_id: HirId, - iterator: HirId, - nesting: Nesting, -} - -impl<'tcx> Visitor<'tcx> for LoopNestVisitor { - type Map = Map<'tcx>; - - fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { - if stmt.hir_id == self.hir_id { - self.nesting = LookFurther; - } else if self.nesting == Unknown { - walk_stmt(self, stmt); - } - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.nesting != Unknown { - return; - } - if expr.hir_id == self.hir_id { - self.nesting = LookFurther; - return; - } - match expr.kind { - ExprKind::Assign(ref path, _, _) | ExprKind::AssignOp(_, ref path, _) => { - if path_to_local_id(path, self.iterator) { - self.nesting = RuledOut; - } - }, - _ => walk_expr(self, expr), - } - } - - fn visit_pat(&mut self, pat: &'tcx Pat<'_>) { - if self.nesting != Unknown { - return; - } - if let PatKind::Binding(_, id, ..) = pat.kind { - if id == self.iterator { - self.nesting = RuledOut; - return; - } - } - walk_pat(self, pat) - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index b9c934d5e54..9e38e17719a 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -1,8 +1,306 @@ -use crate::utils::{get_trait_def_id, has_iter_method, implements_trait, paths, sugg}; +use crate::utils::{ + get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, is_integer_const, path_to_local, + path_to_local_id, paths, sugg, +}; +use if_chain::if_chain; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; -use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability}; +use rustc_hir::intravisit::{walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Stmt, StmtKind}; use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; use rustc_span::source_map::Span; +use rustc_span::symbol::Symbol; +use std::iter::Iterator; + +#[derive(Debug, PartialEq)] +enum IncrementVisitorVarState { + Initial, // Not examined yet + IncrOnce, // Incremented exactly once, may be a loop counter + DontWarn, +} + +/// Scan a for loop for variables that are incremented exactly once and not used after that. +pub(super) struct IncrementVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, // context reference + states: FxHashMap, // incremented variables + depth: u32, // depth of conditional expressions + done: bool, +} + +impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> { + pub(super) fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { + cx, + states: FxHashMap::default(), + depth: 0, + done: false, + } + } + + pub(super) fn into_results(self) -> impl Iterator { + self.states.into_iter().filter_map(|(id, state)| { + if state == IncrementVisitorVarState::IncrOnce { + Some(id) + } else { + None + } + }) + } +} + +impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.done { + return; + } + + // If node is a variable + if let Some(def_id) = path_to_local(expr) { + if let Some(parent) = get_parent_expr(self.cx, expr) { + let state = self.states.entry(def_id).or_insert(IncrementVisitorVarState::Initial); + if *state == IncrementVisitorVarState::IncrOnce { + *state = IncrementVisitorVarState::DontWarn; + return; + } + + match parent.kind { + ExprKind::AssignOp(op, ref lhs, ref rhs) => { + if lhs.hir_id == expr.hir_id { + *state = if op.node == BinOpKind::Add + && is_integer_const(self.cx, rhs, 1) + && *state == IncrementVisitorVarState::Initial + && self.depth == 0 + { + IncrementVisitorVarState::IncrOnce + } else { + // Assigned some other value or assigned multiple times + IncrementVisitorVarState::DontWarn + }; + } + }, + ExprKind::Assign(ref lhs, _, _) if lhs.hir_id == expr.hir_id => { + *state = IncrementVisitorVarState::DontWarn + }, + ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + *state = IncrementVisitorVarState::DontWarn + }, + _ => (), + } + } + + walk_expr(self, expr); + } else if is_loop(expr) || is_conditional(expr) { + self.depth += 1; + walk_expr(self, expr); + self.depth -= 1; + } else if let ExprKind::Continue(_) = expr.kind { + self.done = true; + } else { + walk_expr(self, expr); + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} + +enum InitializeVisitorState<'hir> { + Initial, // Not examined yet + Declared(Symbol), // Declared but not (yet) initialized + Initialized { + name: Symbol, + initializer: &'hir Expr<'hir>, + }, + DontWarn, +} + +/// Checks whether a variable is initialized at the start of a loop and not modified +/// and used after the loop. +pub(super) struct InitializeVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, // context reference + end_expr: &'tcx Expr<'tcx>, // the for loop. Stop scanning here. + var_id: HirId, + state: InitializeVisitorState<'tcx>, + depth: u32, // depth of conditional expressions + past_loop: bool, +} + +impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> { + pub(super) fn new(cx: &'a LateContext<'tcx>, end_expr: &'tcx Expr<'tcx>, var_id: HirId) -> Self { + Self { + cx, + end_expr, + var_id, + state: InitializeVisitorState::Initial, + depth: 0, + past_loop: false, + } + } + + pub(super) fn get_result(&self) -> Option<(Symbol, &'tcx Expr<'tcx>)> { + if let InitializeVisitorState::Initialized { name, initializer } = self.state { + Some((name, initializer)) + } else { + None + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + // Look for declarations of the variable + if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if local.pat.hir_id == self.var_id; + if let PatKind::Binding(.., ident, _) = local.pat.kind; + then { + self.state = local.init.map_or(InitializeVisitorState::Declared(ident.name), |init| { + InitializeVisitorState::Initialized { + initializer: init, + name: ident.name, + } + }) + } + } + walk_stmt(self, stmt); + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if matches!(self.state, InitializeVisitorState::DontWarn) { + return; + } + if expr.hir_id == self.end_expr.hir_id { + self.past_loop = true; + return; + } + // No need to visit expressions before the variable is + // declared + if matches!(self.state, InitializeVisitorState::Initial) { + return; + } + + // If node is the desired variable, see how it's used + if path_to_local_id(expr, self.var_id) { + if self.past_loop { + self.state = InitializeVisitorState::DontWarn; + return; + } + + if let Some(parent) = get_parent_expr(self.cx, expr) { + match parent.kind { + ExprKind::AssignOp(_, ref lhs, _) if lhs.hir_id == expr.hir_id => { + self.state = InitializeVisitorState::DontWarn; + }, + ExprKind::Assign(ref lhs, ref rhs, _) if lhs.hir_id == expr.hir_id => { + self.state = if_chain! { + if self.depth == 0; + if let InitializeVisitorState::Declared(name) + | InitializeVisitorState::Initialized { name, ..} = self.state; + then { + InitializeVisitorState::Initialized { initializer: rhs, name } + } else { + InitializeVisitorState::DontWarn + } + } + }, + ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + self.state = InitializeVisitorState::DontWarn + }, + _ => (), + } + } + + walk_expr(self, expr); + } else if !self.past_loop && is_loop(expr) { + self.state = InitializeVisitorState::DontWarn; + } else if is_conditional(expr) { + self.depth += 1; + walk_expr(self, expr); + self.depth -= 1; + } else { + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} + +fn is_loop(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::Loop(..)) +} + +fn is_conditional(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::If(..) | ExprKind::Match(..)) +} + +#[derive(PartialEq, Eq)] +pub(super) enum Nesting { + Unknown, // no nesting detected yet + RuledOut, // the iterator is initialized or assigned within scope + LookFurther, // no nesting detected, no further walk required +} + +use self::Nesting::{LookFurther, RuledOut, Unknown}; + +pub(super) struct LoopNestVisitor { + pub(super) hir_id: HirId, + pub(super) iterator: HirId, + pub(super) nesting: Nesting, +} + +impl<'tcx> Visitor<'tcx> for LoopNestVisitor { + type Map = Map<'tcx>; + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + if stmt.hir_id == self.hir_id { + self.nesting = LookFurther; + } else if self.nesting == Unknown { + walk_stmt(self, stmt); + } + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.nesting != Unknown { + return; + } + if expr.hir_id == self.hir_id { + self.nesting = LookFurther; + return; + } + match expr.kind { + ExprKind::Assign(ref path, _, _) | ExprKind::AssignOp(_, ref path, _) => { + if path_to_local_id(path, self.iterator) { + self.nesting = RuledOut; + } + }, + _ => walk_expr(self, expr), + } + } + + fn visit_pat(&mut self, pat: &'tcx Pat<'_>) { + if self.nesting != Unknown { + return; + } + if let PatKind::Binding(_, id, ..) = pat.kind { + if id == self.iterator { + self.nesting = RuledOut; + return; + } + } + walk_pat(self, pat) + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} // this function assumes the given expression is a `for` loop. pub(super) fn get_span_of_entire_for_loop(expr: &Expr<'_>) -> Span { From 455d0476b11c1a1a2ae8005e781c6ea9b2f4d2ac Mon Sep 17 00:00:00 2001 From: nahuakang Date: Sun, 21 Feb 2021 15:58:05 +0100 Subject: [PATCH 12/22] Refactor never loop to its own module --- clippy_lints/src/loops/mod.rs | 173 +-------------------------- clippy_lints/src/loops/never_loop.rs | 172 ++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 169 deletions(-) create mode 100644 clippy_lints/src/loops/never_loop.rs diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index c6055e34eb2..9a4cbf27c49 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -8,6 +8,7 @@ mod infinite_loop; mod manual_flatten; mod manual_memcpy; mod needless_collect; +mod never_loop; mod same_item_push; mod utils; @@ -16,21 +17,18 @@ use crate::utils::usage::mutated_variables; use crate::utils::{ get_enclosing_block, get_trait_def_id, higher, implements_trait, is_in_panic_handler, is_no_std_crate, is_refutable, last_path_segment, match_trait_method, path_to_local, path_to_local_id, paths, - snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg, sugg, + snippet_with_applicability, span_lint_and_help, span_lint_and_sugg, sugg, }; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; -use rustc_hir::{ - Block, Expr, ExprKind, HirId, InlineAsmOperand, LoopSource, MatchSource, Node, Pat, PatKind, Stmt, StmtKind, -}; +use rustc_hir::{Block, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, PatKind, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; 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::symbol::sym; -use std::iter::{once, Iterator}; use utils::{ get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor, LoopNestVisitor, Nesting, }; @@ -567,12 +565,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { } // check for never_loop - if let ExprKind::Loop(ref block, _, _, _) = expr.kind { - match never_loop_block(block, expr.hir_id) { - NeverLoopResult::AlwaysBreak => span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops"), - NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (), - } - } + never_loop::check_never_loop(cx, expr); // check for `loop { if let {} else break }` that could be `while let` // (also matches an explicit "match" instead of "if let") @@ -689,164 +682,6 @@ impl<'tcx> LateLintPass<'tcx> for Loops { } } -enum NeverLoopResult { - // A break/return always get triggered but not necessarily for the main loop. - AlwaysBreak, - // A continue may occur for the main loop. - MayContinueMainLoop, - Otherwise, -} - -#[must_use] -fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult { - match *arg { - NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise, - NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop, - } -} - -// Combine two results for parts that are called in order. -#[must_use] -fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult { - match first { - NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first, - NeverLoopResult::Otherwise => second, - } -} - -// Combine two results where both parts are called but not necessarily in order. -#[must_use] -fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult { - match (left, right) { - (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { - NeverLoopResult::MayContinueMainLoop - }, - (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, - (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, - } -} - -// Combine two results where only one of the part may have been executed. -#[must_use] -fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult { - match (b1, b2) { - (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, - (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { - NeverLoopResult::MayContinueMainLoop - }, - (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, - } -} - -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.as_deref()); - let mut iter = stmts.chain(expr).flatten(); - never_loop_expr_seq(&mut iter, main_loop_id) -} - -fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> { - match stmt.kind { - StmtKind::Semi(ref e, ..) | StmtKind::Expr(ref e, ..) => Some(e), - StmtKind::Local(ref local) => local.init.as_deref(), - _ => None, - } -} - -fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { - match expr.kind { - ExprKind::Box(ref e) - | ExprKind::Unary(_, ref e) - | ExprKind::Cast(ref e, _) - | ExprKind::Type(ref e, _) - | ExprKind::Field(ref e, _) - | ExprKind::AddrOf(_, _, ref e) - | ExprKind::Struct(_, _, Some(ref e)) - | ExprKind::Repeat(ref e, _) - | ExprKind::DropTemps(ref e) => never_loop_expr(e, main_loop_id), - ExprKind::Array(ref es) | ExprKind::MethodCall(_, _, ref es, _) | ExprKind::Tup(ref es) => { - never_loop_expr_all(&mut es.iter(), main_loop_id) - }, - ExprKind::Call(ref e, ref es) => never_loop_expr_all(&mut once(&**e).chain(es.iter()), main_loop_id), - ExprKind::Binary(_, ref e1, ref e2) - | ExprKind::Assign(ref e1, ref e2, _) - | ExprKind::AssignOp(_, ref e1, ref e2) - | ExprKind::Index(ref e1, ref e2) => never_loop_expr_all(&mut [&**e1, &**e2].iter().cloned(), main_loop_id), - ExprKind::Loop(ref b, _, _, _) => { - // Break can come from the inner loop so remove them. - absorb_break(&never_loop_block(b, main_loop_id)) - }, - ExprKind::If(ref e, ref e2, ref e3) => { - let e1 = never_loop_expr(e, main_loop_id); - let e2 = never_loop_expr(e2, main_loop_id); - let e3 = e3 - .as_ref() - .map_or(NeverLoopResult::Otherwise, |e| never_loop_expr(e, main_loop_id)); - combine_seq(e1, combine_branches(e2, e3)) - }, - ExprKind::Match(ref e, ref arms, _) => { - let e = never_loop_expr(e, main_loop_id); - if arms.is_empty() { - e - } else { - let arms = never_loop_expr_branch(&mut arms.iter().map(|a| &*a.body), main_loop_id); - combine_seq(e, arms) - } - }, - ExprKind::Block(ref b, _) => never_loop_block(b, main_loop_id), - ExprKind::Continue(d) => { - let id = d - .target_id - .expect("target ID can only be missing in the presence of compilation errors"); - if id == main_loop_id { - NeverLoopResult::MayContinueMainLoop - } else { - NeverLoopResult::AlwaysBreak - } - }, - ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| { - combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak) - }), - ExprKind::InlineAsm(ref asm) => asm - .operands - .iter() - .map(|(o, _)| match o { - InlineAsmOperand::In { expr, .. } - | InlineAsmOperand::InOut { expr, .. } - | InlineAsmOperand::Const { expr } - | InlineAsmOperand::Sym { expr } => never_loop_expr(expr, main_loop_id), - InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter(), main_loop_id), - InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { - never_loop_expr_all(&mut once(in_expr).chain(out_expr.iter()), main_loop_id) - }, - }) - .fold(NeverLoopResult::Otherwise, combine_both), - ExprKind::Struct(_, _, None) - | ExprKind::Yield(_, _) - | ExprKind::Closure(_, _, _, _, _) - | ExprKind::LlvmInlineAsm(_) - | ExprKind::Path(_) - | ExprKind::ConstBlock(_) - | ExprKind::Lit(_) - | ExprKind::Err => NeverLoopResult::Otherwise, - } -} - -fn never_loop_expr_seq<'a, T: Iterator>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { - es.map(|e| never_loop_expr(e, main_loop_id)) - .fold(NeverLoopResult::Otherwise, combine_seq) -} - -fn never_loop_expr_all<'a, T: Iterator>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { - es.map(|e| never_loop_expr(e, main_loop_id)) - .fold(NeverLoopResult::Otherwise, combine_both) -} - -fn never_loop_expr_branch<'a, T: Iterator>>(e: &mut T, main_loop_id: HirId) -> NeverLoopResult { - e.map(|e| never_loop_expr(e, main_loop_id)) - .fold(NeverLoopResult::AlwaysBreak, combine_branches) -} - fn check_for_loop<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs new file mode 100644 index 00000000000..d51ff81103f --- /dev/null +++ b/clippy_lints/src/loops/never_loop.rs @@ -0,0 +1,172 @@ +use super::NEVER_LOOP; +use crate::utils::span_lint; +use rustc_hir::{Block, Expr, ExprKind, HirId, InlineAsmOperand, Stmt, StmtKind}; +use rustc_lint::LateContext; +use std::iter::{once, Iterator}; + +pub(super) fn check_never_loop(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Loop(ref block, _, _, _) = expr.kind { + match never_loop_block(block, expr.hir_id) { + NeverLoopResult::AlwaysBreak => span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops"), + NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (), + } + } +} + +enum NeverLoopResult { + // A break/return always get triggered but not necessarily for the main loop. + AlwaysBreak, + // A continue may occur for the main loop. + MayContinueMainLoop, + Otherwise, +} + +#[must_use] +fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult { + match *arg { + NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise, + NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop, + } +} + +// Combine two results for parts that are called in order. +#[must_use] +fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult { + match first { + NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first, + NeverLoopResult::Otherwise => second, + } +} + +// Combine two results where both parts are called but not necessarily in order. +#[must_use] +fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult { + match (left, right) { + (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { + NeverLoopResult::MayContinueMainLoop + }, + (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, + (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, + } +} + +// Combine two results where only one of the part may have been executed. +#[must_use] +fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult { + match (b1, b2) { + (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, + (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { + NeverLoopResult::MayContinueMainLoop + }, + (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, + } +} + +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.as_deref()); + let mut iter = stmts.chain(expr).flatten(); + never_loop_expr_seq(&mut iter, main_loop_id) +} + +fn never_loop_expr_seq<'a, T: Iterator>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { + es.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::Otherwise, combine_seq) +} + +fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match stmt.kind { + StmtKind::Semi(ref e, ..) | StmtKind::Expr(ref e, ..) => Some(e), + StmtKind::Local(ref local) => local.init.as_deref(), + _ => None, + } +} + +fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { + match expr.kind { + ExprKind::Box(ref e) + | ExprKind::Unary(_, ref e) + | ExprKind::Cast(ref e, _) + | ExprKind::Type(ref e, _) + | ExprKind::Field(ref e, _) + | ExprKind::AddrOf(_, _, ref e) + | ExprKind::Struct(_, _, Some(ref e)) + | ExprKind::Repeat(ref e, _) + | ExprKind::DropTemps(ref e) => never_loop_expr(e, main_loop_id), + ExprKind::Array(ref es) | ExprKind::MethodCall(_, _, ref es, _) | ExprKind::Tup(ref es) => { + never_loop_expr_all(&mut es.iter(), main_loop_id) + }, + ExprKind::Call(ref e, ref es) => never_loop_expr_all(&mut once(&**e).chain(es.iter()), main_loop_id), + ExprKind::Binary(_, ref e1, ref e2) + | ExprKind::Assign(ref e1, ref e2, _) + | ExprKind::AssignOp(_, ref e1, ref e2) + | ExprKind::Index(ref e1, ref e2) => never_loop_expr_all(&mut [&**e1, &**e2].iter().cloned(), main_loop_id), + ExprKind::Loop(ref b, _, _, _) => { + // Break can come from the inner loop so remove them. + absorb_break(&never_loop_block(b, main_loop_id)) + }, + ExprKind::If(ref e, ref e2, ref e3) => { + let e1 = never_loop_expr(e, main_loop_id); + let e2 = never_loop_expr(e2, main_loop_id); + let e3 = e3 + .as_ref() + .map_or(NeverLoopResult::Otherwise, |e| never_loop_expr(e, main_loop_id)); + combine_seq(e1, combine_branches(e2, e3)) + }, + ExprKind::Match(ref e, ref arms, _) => { + let e = never_loop_expr(e, main_loop_id); + if arms.is_empty() { + e + } else { + let arms = never_loop_expr_branch(&mut arms.iter().map(|a| &*a.body), main_loop_id); + combine_seq(e, arms) + } + }, + ExprKind::Block(ref b, _) => never_loop_block(b, main_loop_id), + ExprKind::Continue(d) => { + let id = d + .target_id + .expect("target ID can only be missing in the presence of compilation errors"); + if id == main_loop_id { + NeverLoopResult::MayContinueMainLoop + } else { + NeverLoopResult::AlwaysBreak + } + }, + ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| { + combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak) + }), + ExprKind::InlineAsm(ref asm) => asm + .operands + .iter() + .map(|(o, _)| match o { + InlineAsmOperand::In { expr, .. } + | InlineAsmOperand::InOut { expr, .. } + | InlineAsmOperand::Const { expr } + | InlineAsmOperand::Sym { expr } => never_loop_expr(expr, main_loop_id), + InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter(), main_loop_id), + InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + never_loop_expr_all(&mut once(in_expr).chain(out_expr.iter()), main_loop_id) + }, + }) + .fold(NeverLoopResult::Otherwise, combine_both), + ExprKind::Struct(_, _, None) + | ExprKind::Yield(_, _) + | ExprKind::Closure(_, _, _, _, _) + | ExprKind::LlvmInlineAsm(_) + | ExprKind::Path(_) + | ExprKind::ConstBlock(_) + | ExprKind::Lit(_) + | ExprKind::Err => NeverLoopResult::Otherwise, + } +} + +fn never_loop_expr_all<'a, T: Iterator>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { + es.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::Otherwise, combine_both) +} + +fn never_loop_expr_branch<'a, T: Iterator>>(e: &mut T, main_loop_id: HirId) -> NeverLoopResult { + e.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::AlwaysBreak, combine_branches) +} From d0b657c0b7335ccb4a594af100ab49d8bd660e97 Mon Sep 17 00:00:00 2001 From: nahuakang Date: Sun, 21 Feb 2021 16:37:09 +0100 Subject: [PATCH 13/22] Refactor while let on iterator lint to its module; rename for loop explicit counter to explicit counter loop --- ...it_counter.rs => explicit_counter_loop.rs} | 0 clippy_lints/src/loops/mod.rs | 174 +----------------- .../src/loops/while_let_on_iterator.rs | 171 +++++++++++++++++ 3 files changed, 179 insertions(+), 166 deletions(-) rename clippy_lints/src/loops/{for_loop_explicit_counter.rs => explicit_counter_loop.rs} (100%) create mode 100644 clippy_lints/src/loops/while_let_on_iterator.rs diff --git a/clippy_lints/src/loops/for_loop_explicit_counter.rs b/clippy_lints/src/loops/explicit_counter_loop.rs similarity index 100% rename from clippy_lints/src/loops/for_loop_explicit_counter.rs rename to clippy_lints/src/loops/explicit_counter_loop.rs diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 9a4cbf27c49..cadf9241256 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -1,5 +1,5 @@ +mod explicit_counter_loop; mod for_loop_arg; -mod for_loop_explicit_counter; mod for_loop_over_map_kv; mod for_loop_range; mod for_mut_range_bound; @@ -11,27 +11,20 @@ mod needless_collect; mod never_loop; mod same_item_push; mod utils; +mod while_let_on_iterator; use crate::utils::sugg::Sugg; -use crate::utils::usage::mutated_variables; use crate::utils::{ - get_enclosing_block, get_trait_def_id, higher, implements_trait, is_in_panic_handler, is_no_std_crate, - is_refutable, last_path_segment, match_trait_method, path_to_local, path_to_local_id, paths, - snippet_with_applicability, span_lint_and_help, span_lint_and_sugg, sugg, + higher, is_in_panic_handler, is_no_std_crate, snippet_with_applicability, span_lint_and_help, span_lint_and_sugg, + sugg, }; -use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; -use rustc_hir::{Block, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, PatKind, StmtKind}; +use rustc_hir::{Block, Expr, ExprKind, LoopSource, MatchSource, Pat, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::hir::map::Map; 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::symbol::sym; -use utils::{ - get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor, LoopNestVisitor, Nesting, -}; +use utils::{get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor}; declare_clippy_lint! { /// **What it does:** Checks for for-loops that manually copy items between @@ -625,54 +618,8 @@ impl<'tcx> LateLintPass<'tcx> for Loops { } } } - if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind { - let pat = &arms[0].pat.kind; - if let ( - &PatKind::TupleStruct(ref qpath, ref pat_args, _), - &ExprKind::MethodCall(ref method_path, _, ref method_args, _), - ) = (pat, &match_expr.kind) - { - let iter_expr = &method_args[0]; - // Don't lint when the iterator is recreated on every iteration - if_chain! { - if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind; - if let Some(iter_def_id) = get_trait_def_id(cx, &paths::ITERATOR); - if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]); - then { - return; - } - } - - let lhs_constructor = last_path_segment(qpath); - if method_path.ident.name == sym::next - && match_trait_method(cx, match_expr, &paths::ITERATOR) - && lhs_constructor.ident.name == sym::Some - && (pat_args.is_empty() - || !is_refutable(cx, &pat_args[0]) - && !is_used_inside(cx, iter_expr, &arms[0].body) - && !is_iterator_used_after_while_let(cx, iter_expr) - && !is_nested(cx, expr, &method_args[0])) - { - let mut applicability = Applicability::MachineApplicable; - let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability); - let loop_var = if pat_args.is_empty() { - "_".to_string() - } else { - snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned() - }; - span_lint_and_sugg( - cx, - WHILE_LET_ON_ITERATOR, - expr.span.with_hi(match_expr.span.hi()), - "this loop could be written as a `for` loop", - "try", - format!("for {} in {}", loop_var, iterator), - applicability, - ); - } - } - } + while_let_on_iterator::check_while_let_on_iterator(cx, expr); if let Some((cond, body)) = higher::while_loop(&expr) { infinite_loop::check_infinite_loop(cx, cond, body); @@ -693,7 +640,7 @@ fn check_for_loop<'tcx>( let is_manual_memcpy_triggered = manual_memcpy::detect_manual_memcpy(cx, pat, arg, body, expr); if !is_manual_memcpy_triggered { for_loop_range::check_for_loop_range(cx, pat, arg, body, expr); - for_loop_explicit_counter::check_for_loop_explicit_counter(cx, pat, arg, body, expr); + explicit_counter_loop::check_for_loop_explicit_counter(cx, pat, arg, body, expr); } for_loop_arg::check_for_loop_arg(cx, pat, arg, expr); for_loop_over_map_kv::check_for_loop_over_map_kv(cx, pat, arg, body, expr); @@ -773,61 +720,6 @@ impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> { } } -fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool { - let def_id = match path_to_local(expr) { - Some(id) => id, - None => return false, - }; - if let Some(used_mutably) = mutated_variables(container, cx) { - if used_mutably.contains(&def_id) { - return true; - } - } - false -} - -fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool { - let def_id = match path_to_local(iter_expr) { - Some(id) => id, - None => return false, - }; - let mut visitor = VarUsedAfterLoopVisitor { - def_id, - iter_expr_id: iter_expr.hir_id, - past_while_let: false, - var_used_after_while_let: false, - }; - if let Some(enclosing_block) = get_enclosing_block(cx, def_id) { - walk_block(&mut visitor, enclosing_block); - } - visitor.var_used_after_while_let -} - -struct VarUsedAfterLoopVisitor { - def_id: HirId, - iter_expr_id: HirId, - past_while_let: bool, - var_used_after_while_let: bool, -} - -impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.past_while_let { - if path_to_local_id(expr, self.def_id) { - self.var_used_after_while_let = true; - } - } else if self.iter_expr_id == expr.hir_id { - self.past_while_let = true; - } - walk_expr(self, expr); - } - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None - } -} - /// If a block begins with a statement (possibly a `let` binding) and has an /// expression, return it. fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { @@ -863,53 +755,3 @@ fn is_simple_break_expr(expr: &Expr<'_>) -> bool { _ => false, } } - -fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { - if_chain! { - if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id); - let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id); - if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node); - then { - return is_loop_nested(cx, loop_expr, iter_expr) - } - } - false -} - -fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { - let mut id = loop_expr.hir_id; - let iter_id = if let Some(id) = path_to_local(iter_expr) { - id - } else { - return true; - }; - loop { - let parent = cx.tcx.hir().get_parent_node(id); - if parent == id { - return false; - } - match cx.tcx.hir().find(parent) { - Some(Node::Expr(expr)) => { - if let ExprKind::Loop(..) = expr.kind { - return true; - }; - }, - Some(Node::Block(block)) => { - let mut block_visitor = LoopNestVisitor { - hir_id: id, - iterator: iter_id, - nesting: Nesting::Unknown, - }; - walk_block(&mut block_visitor, block); - if block_visitor.nesting == Nesting::RuledOut { - return false; - } - }, - Some(Node::Stmt(_)) => (), - _ => { - return false; - }, - } - id = parent; - } -} diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs new file mode 100644 index 00000000000..090c8ceba97 --- /dev/null +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -0,0 +1,171 @@ +use super::utils::{LoopNestVisitor, Nesting}; +use super::WHILE_LET_ON_ITERATOR; +use crate::utils::usage::mutated_variables; +use crate::utils::{ + get_enclosing_block, get_trait_def_id, implements_trait, is_refutable, last_path_segment, match_trait_method, + path_to_local, path_to_local_id, paths, snippet_with_applicability, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{Expr, ExprKind, HirId, MatchSource, Node, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; + +use rustc_span::symbol::sym; + +pub(super) fn check_while_let_on_iterator(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind { + let pat = &arms[0].pat.kind; + if let ( + &PatKind::TupleStruct(ref qpath, ref pat_args, _), + &ExprKind::MethodCall(ref method_path, _, ref method_args, _), + ) = (pat, &match_expr.kind) + { + let iter_expr = &method_args[0]; + + // Don't lint when the iterator is recreated on every iteration + if_chain! { + if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind; + if let Some(iter_def_id) = get_trait_def_id(cx, &paths::ITERATOR); + if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]); + then { + return; + } + } + + let lhs_constructor = last_path_segment(qpath); + if method_path.ident.name == sym::next + && match_trait_method(cx, match_expr, &paths::ITERATOR) + && lhs_constructor.ident.name == sym::Some + && (pat_args.is_empty() + || !is_refutable(cx, &pat_args[0]) + && !is_used_inside(cx, iter_expr, &arms[0].body) + && !is_iterator_used_after_while_let(cx, iter_expr) + && !is_nested(cx, expr, &method_args[0])) + { + let mut applicability = Applicability::MachineApplicable; + let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability); + let loop_var = if pat_args.is_empty() { + "_".to_string() + } else { + snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned() + }; + span_lint_and_sugg( + cx, + WHILE_LET_ON_ITERATOR, + expr.span.with_hi(match_expr.span.hi()), + "this loop could be written as a `for` loop", + "try", + format!("for {} in {}", loop_var, iterator), + applicability, + ); + } + } + } +} + +fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool { + let def_id = match path_to_local(expr) { + Some(id) => id, + None => return false, + }; + if let Some(used_mutably) = mutated_variables(container, cx) { + if used_mutably.contains(&def_id) { + return true; + } + } + false +} + +fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool { + let def_id = match path_to_local(iter_expr) { + Some(id) => id, + None => return false, + }; + let mut visitor = VarUsedAfterLoopVisitor { + def_id, + iter_expr_id: iter_expr.hir_id, + past_while_let: false, + var_used_after_while_let: false, + }; + if let Some(enclosing_block) = get_enclosing_block(cx, def_id) { + walk_block(&mut visitor, enclosing_block); + } + visitor.var_used_after_while_let +} + +fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { + if_chain! { + if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id); + let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id); + if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node); + then { + return is_loop_nested(cx, loop_expr, iter_expr) + } + } + false +} + +fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { + let mut id = loop_expr.hir_id; + let iter_id = if let Some(id) = path_to_local(iter_expr) { + id + } else { + return true; + }; + loop { + let parent = cx.tcx.hir().get_parent_node(id); + if parent == id { + return false; + } + match cx.tcx.hir().find(parent) { + Some(Node::Expr(expr)) => { + if let ExprKind::Loop(..) = expr.kind { + return true; + }; + }, + Some(Node::Block(block)) => { + let mut block_visitor = LoopNestVisitor { + hir_id: id, + iterator: iter_id, + nesting: Nesting::Unknown, + }; + walk_block(&mut block_visitor, block); + if block_visitor.nesting == Nesting::RuledOut { + return false; + } + }, + Some(Node::Stmt(_)) => (), + _ => { + return false; + }, + } + id = parent; + } +} + +struct VarUsedAfterLoopVisitor { + def_id: HirId, + iter_expr_id: HirId, + past_while_let: bool, + var_used_after_while_let: bool, +} + +impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.past_while_let { + if path_to_local_id(expr, self.def_id) { + self.var_used_after_while_let = true; + } + } else if self.iter_expr_id == expr.hir_id { + self.past_while_let = true; + } + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } +} From 287a4f8ab176a95cff2b82f6414d37e33809110a Mon Sep 17 00:00:00 2001 From: nahuakang Date: Sun, 21 Feb 2021 16:48:16 +0100 Subject: [PATCH 14/22] Refactor empty loop to its own module --- clippy_lints/src/loops/empty_loop.rs | 17 +++++++++++++++++ clippy_lints/src/loops/mod.rs | 16 +++------------- 2 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 clippy_lints/src/loops/empty_loop.rs diff --git a/clippy_lints/src/loops/empty_loop.rs b/clippy_lints/src/loops/empty_loop.rs new file mode 100644 index 00000000000..fdc99e190e3 --- /dev/null +++ b/clippy_lints/src/loops/empty_loop.rs @@ -0,0 +1,17 @@ +use super::EMPTY_LOOP; +use crate::utils::{is_in_panic_handler, is_no_std_crate, span_lint_and_help}; + +use rustc_hir::{Block, Expr}; +use rustc_lint::LateContext; + +pub(super) fn check_empty_loop(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { + if loop_block.stmts.is_empty() && loop_block.expr.is_none() && !is_in_panic_handler(cx, expr) { + let msg = "empty `loop {}` wastes CPU cycles"; + let help = if is_no_std_crate(cx.tcx.hir().krate()) { + "you should either use `panic!()` or add a call pausing or sleeping the thread to the loop body" + } else { + "you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body" + }; + span_lint_and_help(cx, EMPTY_LOOP, expr.span, msg, None, help); + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index cadf9241256..d205f053679 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -1,3 +1,4 @@ +mod empty_loop; mod explicit_counter_loop; mod for_loop_arg; mod for_loop_over_map_kv; @@ -14,10 +15,7 @@ mod utils; mod while_let_on_iterator; use crate::utils::sugg::Sugg; -use crate::utils::{ - higher, is_in_panic_handler, is_no_std_crate, snippet_with_applicability, span_lint_and_help, span_lint_and_sugg, - sugg, -}; +use crate::utils::{higher, snippet_with_applicability, span_lint_and_sugg, sugg}; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, LoopSource, MatchSource, Pat, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -565,15 +563,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { // (even if the "match" or "if let" is used for declaration) if let ExprKind::Loop(ref block, _, LoopSource::Loop, _) = expr.kind { // also check for empty `loop {}` statements, skipping those in #[panic_handler] - if block.stmts.is_empty() && block.expr.is_none() && !is_in_panic_handler(cx, expr) { - let msg = "empty `loop {}` wastes CPU cycles"; - let help = if is_no_std_crate(cx.tcx.hir().krate()) { - "you should either use `panic!()` or add a call pausing or sleeping the thread to the loop body" - } else { - "you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body" - }; - span_lint_and_help(cx, EMPTY_LOOP, expr.span, msg, None, help); - } + empty_loop::check_empty_loop(cx, expr, block); // extract the expression from the first statement (if any) in a block let inner_stmt_expr = extract_expr_from_first_stmt(block); From 7158c944a4f033f6feb57fbcdbf3453333d892b7 Mon Sep 17 00:00:00 2001 From: nahuakang Date: Sun, 21 Feb 2021 17:01:49 +0100 Subject: [PATCH 15/22] Refactor while let loop to its own module --- clippy_lints/src/loops/mod.rs | 89 ++---------------------- clippy_lints/src/loops/while_let_loop.rs | 87 +++++++++++++++++++++++ 2 files changed, 92 insertions(+), 84 deletions(-) create mode 100644 clippy_lints/src/loops/while_let_loop.rs diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index d205f053679..9076e48584a 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -12,14 +12,13 @@ mod needless_collect; mod never_loop; mod same_item_push; mod utils; +mod while_let_loop; mod while_let_on_iterator; use crate::utils::sugg::Sugg; -use crate::utils::{higher, snippet_with_applicability, span_lint_and_sugg, sugg}; -use rustc_errors::Applicability; -use rustc_hir::{Block, Expr, ExprKind, LoopSource, MatchSource, Pat, StmtKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::lint::in_external_macro; +use crate::utils::{higher, sugg}; +use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use utils::{get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor}; @@ -564,49 +563,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { if let ExprKind::Loop(ref block, _, LoopSource::Loop, _) = expr.kind { // also check for empty `loop {}` statements, skipping those in #[panic_handler] empty_loop::check_empty_loop(cx, expr, block); - - // extract the expression from the first statement (if any) in a block - let inner_stmt_expr = extract_expr_from_first_stmt(block); - // or extract the first expression (if any) from the block - if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(block)) { - if let ExprKind::Match(ref matchexpr, ref arms, ref source) = inner.kind { - // ensure "if let" compatible match structure - match *source { - MatchSource::Normal | MatchSource::IfLetDesugar { .. } => { - if arms.len() == 2 - && arms[0].guard.is_none() - && arms[1].guard.is_none() - && is_simple_break_expr(&arms[1].body) - { - if in_external_macro(cx.sess(), expr.span) { - return; - } - - // NOTE: we used to build a body here instead of using - // ellipsis, this was removed because: - // 1) it was ugly with big bodies; - // 2) it was not indented properly; - // 3) it wasn’t very smart (see #675). - let mut applicability = Applicability::HasPlaceholders; - span_lint_and_sugg( - cx, - WHILE_LET_LOOP, - expr.span, - "this loop could be written as a `while let` loop", - "try", - format!( - "while let {} = {} {{ .. }}", - snippet_with_applicability(cx, arms[0].pat.span, "..", &mut applicability), - snippet_with_applicability(cx, matchexpr.span, "..", &mut applicability), - ), - applicability, - ); - } - }, - _ => (), - } - } - } + while_let_loop::check_while_let_loop(cx, expr, block); } while_let_on_iterator::check_while_let_on_iterator(cx, expr); @@ -709,39 +666,3 @@ impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> { } } } - -/// If a block begins with a statement (possibly a `let` binding) and has an -/// expression, return it. -fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { - if block.stmts.is_empty() { - return None; - } - if let StmtKind::Local(ref local) = block.stmts[0].kind { - local.init //.map(|expr| expr) - } else { - None - } -} - -/// If a block begins with an expression (with or without semicolon), return it. -fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { - match block.expr { - Some(ref expr) if block.stmts.is_empty() => Some(expr), - None if !block.stmts.is_empty() => match block.stmts[0].kind { - StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => Some(expr), - StmtKind::Local(..) | StmtKind::Item(..) => None, - }, - _ => None, - } -} - -/// Returns `true` if expr contains a single break expr without destination label -/// and -/// passed expression. The expression may be within a block. -fn is_simple_break_expr(expr: &Expr<'_>) -> bool { - match expr.kind { - ExprKind::Break(dest, ref passed_expr) if dest.label.is_none() && passed_expr.is_none() => true, - ExprKind::Block(ref b, _) => extract_first_expr(b).map_or(false, |subexpr| is_simple_break_expr(subexpr)), - _ => false, - } -} diff --git a/clippy_lints/src/loops/while_let_loop.rs b/clippy_lints/src/loops/while_let_loop.rs new file mode 100644 index 00000000000..0c957986964 --- /dev/null +++ b/clippy_lints/src/loops/while_let_loop.rs @@ -0,0 +1,87 @@ +use super::WHILE_LET_LOOP; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, ExprKind, MatchSource, StmtKind}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::lint::in_external_macro; + +pub(super) fn check_while_let_loop(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { + // extract the expression from the first statement (if any) in a block + let inner_stmt_expr = extract_expr_from_first_stmt(loop_block); + // or extract the first expression (if any) from the block + if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(loop_block)) { + if let ExprKind::Match(ref matchexpr, ref arms, ref source) = inner.kind { + // ensure "if let" compatible match structure + match *source { + MatchSource::Normal | MatchSource::IfLetDesugar { .. } => { + if arms.len() == 2 + && arms[0].guard.is_none() + && arms[1].guard.is_none() + && is_simple_break_expr(&arms[1].body) + { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + // NOTE: we used to build a body here instead of using + // ellipsis, this was removed because: + // 1) it was ugly with big bodies; + // 2) it was not indented properly; + // 3) it wasn’t very smart (see #675). + let mut applicability = Applicability::HasPlaceholders; + span_lint_and_sugg( + cx, + WHILE_LET_LOOP, + expr.span, + "this loop could be written as a `while let` loop", + "try", + format!( + "while let {} = {} {{ .. }}", + snippet_with_applicability(cx, arms[0].pat.span, "..", &mut applicability), + snippet_with_applicability(cx, matchexpr.span, "..", &mut applicability), + ), + applicability, + ); + } + }, + _ => (), + } + } + } +} + +/// If a block begins with a statement (possibly a `let` binding) and has an +/// expression, return it. +fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if block.stmts.is_empty() { + return None; + } + if let StmtKind::Local(ref local) = block.stmts[0].kind { + local.init //.map(|expr| expr) + } else { + None + } +} + +/// If a block begins with an expression (with or without semicolon), return it. +fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match block.expr { + Some(ref expr) if block.stmts.is_empty() => Some(expr), + None if !block.stmts.is_empty() => match block.stmts[0].kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => Some(expr), + StmtKind::Local(..) | StmtKind::Item(..) => None, + }, + _ => None, + } +} + +/// Returns `true` if expr contains a single break expr without destination label +/// and +/// passed expression. The expression may be within a block. +fn is_simple_break_expr(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Break(dest, ref passed_expr) if dest.label.is_none() && passed_expr.is_none() => true, + ExprKind::Block(ref b, _) => extract_first_expr(b).map_or(false, |subexpr| is_simple_break_expr(subexpr)), + _ => false, + } +} From 7cfdef6de19a0aaddcc3018baf1031ae54c419f6 Mon Sep 17 00:00:00 2001 From: nahuakang Date: Sun, 21 Feb 2021 17:10:07 +0100 Subject: [PATCH 16/22] Move MinifyingSugg into manual_memcpy --- clippy_lints/src/loops/manual_memcpy.rs | 72 +++++++++++++++++++++++- clippy_lints/src/loops/mod.rs | 73 +------------------------ 2 files changed, 72 insertions(+), 73 deletions(-) diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs index 85b3dfaece7..13a434aa210 100644 --- a/clippy_lints/src/loops/manual_memcpy.rs +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -1,4 +1,4 @@ -use super::{get_span_of_entire_for_loop, IncrementVisitor, InitializeVisitor, MinifyingSugg}; +use super::{get_span_of_entire_for_loop, IncrementVisitor, InitializeVisitor}; use crate::utils::sugg::Sugg; use crate::utils::{ get_enclosing_block, higher, is_type_diagnostic_item, path_to_local, snippet, span_lint_and_sugg, sugg, @@ -194,6 +194,76 @@ fn build_manual_memcpy_suggestion<'tcx>( ) } +/// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`; +/// and also, it avoids subtracting a variable from the same one by replacing it with `0`. +/// it exists for the convenience of the overloaded operators while normal functions can do the +/// same. +#[derive(Clone)] +struct MinifyingSugg<'a>(Sugg<'a>); + +impl<'a> MinifyingSugg<'a> { + fn as_str(&self) -> &str { + let Sugg::NonParen(s) | Sugg::MaybeParen(s) | Sugg::BinOp(_, s) = &self.0; + s.as_ref() + } + + fn into_sugg(self) -> Sugg<'a> { + self.0 + } +} + +impl<'a> From> for MinifyingSugg<'a> { + fn from(sugg: Sugg<'a>) -> Self { + Self(sugg) + } +} + +impl std::ops::Add for &MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + ("0", _) => rhs.clone(), + (_, "0") => self.clone(), + (_, _) => (&self.0 + &rhs.0).into(), + } + } +} + +impl std::ops::Sub for &MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + (_, "0") => self.clone(), + ("0", _) => (-rhs.0.clone()).into(), + (x, y) if x == y => sugg::ZERO.into(), + (_, _) => (&self.0 - &rhs.0).into(), + } + } +} + +impl std::ops::Add<&MinifyingSugg<'static>> for MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + ("0", _) => rhs.clone(), + (_, "0") => self, + (_, _) => (self.0 + &rhs.0).into(), + } + } +} + +impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + (_, "0") => self, + ("0", _) => (-rhs.0.clone()).into(), + (x, y) if x == y => sugg::ZERO.into(), + (_, _) => (self.0 - &rhs.0).into(), + } + } +} + /// a wrapper around `MinifyingSugg`, which carries a operator like currying /// so that the suggested code become more efficient (e.g. `foo + -bar` `foo - bar`). struct Offset { diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 9076e48584a..c0191e43dea 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -15,8 +15,7 @@ mod utils; mod while_let_loop; mod while_let_on_iterator; -use crate::utils::sugg::Sugg; -use crate::utils::{higher, sugg}; +use crate::utils::higher; use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -596,73 +595,3 @@ fn check_for_loop<'tcx>( same_item_push::detect_same_item_push(cx, pat, arg, body, expr); manual_flatten::check_manual_flatten(cx, pat, arg, body, span); } - -/// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`; -/// and also, it avoids subtracting a variable from the same one by replacing it with `0`. -/// it exists for the convenience of the overloaded operators while normal functions can do the -/// same. -#[derive(Clone)] -struct MinifyingSugg<'a>(Sugg<'a>); - -impl<'a> MinifyingSugg<'a> { - fn as_str(&self) -> &str { - let Sugg::NonParen(s) | Sugg::MaybeParen(s) | Sugg::BinOp(_, s) = &self.0; - s.as_ref() - } - - fn into_sugg(self) -> Sugg<'a> { - self.0 - } -} - -impl<'a> From> for MinifyingSugg<'a> { - fn from(sugg: Sugg<'a>) -> Self { - Self(sugg) - } -} - -impl std::ops::Add for &MinifyingSugg<'static> { - type Output = MinifyingSugg<'static>; - fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { - match (self.as_str(), rhs.as_str()) { - ("0", _) => rhs.clone(), - (_, "0") => self.clone(), - (_, _) => (&self.0 + &rhs.0).into(), - } - } -} - -impl std::ops::Sub for &MinifyingSugg<'static> { - type Output = MinifyingSugg<'static>; - fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { - match (self.as_str(), rhs.as_str()) { - (_, "0") => self.clone(), - ("0", _) => (-rhs.0.clone()).into(), - (x, y) if x == y => sugg::ZERO.into(), - (_, _) => (&self.0 - &rhs.0).into(), - } - } -} - -impl std::ops::Add<&MinifyingSugg<'static>> for MinifyingSugg<'static> { - type Output = MinifyingSugg<'static>; - fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { - match (self.as_str(), rhs.as_str()) { - ("0", _) => rhs.clone(), - (_, "0") => self, - (_, _) => (self.0 + &rhs.0).into(), - } - } -} - -impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> { - type Output = MinifyingSugg<'static>; - fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { - match (self.as_str(), rhs.as_str()) { - (_, "0") => self, - ("0", _) => (-rhs.0.clone()).into(), - (x, y) if x == y => sugg::ZERO.into(), - (_, _) => (self.0 - &rhs.0).into(), - } - } -} From ecebfe0c9cee0694e986af63a446031606825d18 Mon Sep 17 00:00:00 2001 From: nahuakang Date: Sun, 21 Feb 2021 18:12:17 +0100 Subject: [PATCH 17/22] Move check_for_loop_arg back to mod; split into 4 lint files --- .../src/loops/explicit_into_iter_loop.rs | 20 +++++ clippy_lints/src/loops/explicit_iter_loop.rs | 21 +++++ .../src/loops/for_loops_over_fallibles.rs | 45 +++++++++++ clippy_lints/src/loops/iter_next_loop.rs | 14 ++++ clippy_lints/src/loops/mod.rs | 80 ++++++++++++++++++- 5 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 clippy_lints/src/loops/explicit_into_iter_loop.rs create mode 100644 clippy_lints/src/loops/explicit_iter_loop.rs create mode 100644 clippy_lints/src/loops/for_loops_over_fallibles.rs create mode 100644 clippy_lints/src/loops/iter_next_loop.rs diff --git a/clippy_lints/src/loops/explicit_into_iter_loop.rs b/clippy_lints/src/loops/explicit_into_iter_loop.rs new file mode 100644 index 00000000000..d5d2bedaf1b --- /dev/null +++ b/clippy_lints/src/loops/explicit_into_iter_loop.rs @@ -0,0 +1,20 @@ +use super::EXPLICIT_INTO_ITER_LOOP; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; + +pub(super) fn check_explicit_into_iter_loop(cx: &LateContext<'_>, method_args: &'hir [Expr<'hir>], arg: &Expr<'_>) { + let mut applicability = Applicability::MachineApplicable; + let object = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability); + span_lint_and_sugg( + cx, + EXPLICIT_INTO_ITER_LOOP, + arg.span, + "it is more concise to loop over containers instead of using explicit \ + iteration methods", + "to write this more concisely, try", + object.to_string(), + applicability, + ); +} diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs new file mode 100644 index 00000000000..14184865da3 --- /dev/null +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -0,0 +1,21 @@ +use super::EXPLICIT_ITER_LOOP; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; + +pub(super) fn lint_iter_method(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { + let mut applicability = Applicability::MachineApplicable; + let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); + let muta = if method_name == "iter_mut" { "mut " } else { "" }; + span_lint_and_sugg( + cx, + EXPLICIT_ITER_LOOP, + arg.span, + "it is more concise to loop over references to containers instead of using explicit \ + iteration methods", + "to write this more concisely, try", + format!("&{}{}", muta, object), + applicability, + ) +} diff --git a/clippy_lints/src/loops/for_loops_over_fallibles.rs b/clippy_lints/src/loops/for_loops_over_fallibles.rs new file mode 100644 index 00000000000..1339af33759 --- /dev/null +++ b/clippy_lints/src/loops/for_loops_over_fallibles.rs @@ -0,0 +1,45 @@ +use super::FOR_LOOPS_OVER_FALLIBLES; +use crate::utils::{is_type_diagnostic_item, snippet, span_lint_and_help}; +use rustc_hir::{Expr, Pat}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +/// Checks for `for` loops over `Option`s and `Result`s. +pub(super) fn check_arg_type(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { + let ty = cx.typeck_results().expr_ty(arg); + if is_type_diagnostic_item(cx, ty, sym::option_type) { + span_lint_and_help( + cx, + FOR_LOOPS_OVER_FALLIBLES, + arg.span, + &format!( + "for loop over `{0}`, which is an `Option`. This is more readably written as an \ + `if let` statement", + snippet(cx, arg.span, "_") + ), + None, + &format!( + "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ), + ); + } else if is_type_diagnostic_item(cx, ty, sym::result_type) { + span_lint_and_help( + cx, + FOR_LOOPS_OVER_FALLIBLES, + arg.span, + &format!( + "for loop over `{0}`, which is a `Result`. This is more readably written as an \ + `if let` statement", + snippet(cx, arg.span, "_") + ), + None, + &format!( + "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ), + ); + } +} diff --git a/clippy_lints/src/loops/iter_next_loop.rs b/clippy_lints/src/loops/iter_next_loop.rs new file mode 100644 index 00000000000..7a5a873a358 --- /dev/null +++ b/clippy_lints/src/loops/iter_next_loop.rs @@ -0,0 +1,14 @@ +use super::ITER_NEXT_LOOP; +use crate::utils::span_lint; +use rustc_hir::Expr; +use rustc_lint::LateContext; + +pub(super) fn lint(cx: &LateContext<'_>, expr: &Expr<'_>) { + span_lint( + cx, + ITER_NEXT_LOOP, + expr.span, + "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ + probably not what you want", + ); +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index c0191e43dea..4594afc2332 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -1,11 +1,14 @@ mod empty_loop; mod explicit_counter_loop; -mod for_loop_arg; +mod explicit_into_iter_loop; +mod explicit_iter_loop; mod for_loop_over_map_kv; mod for_loop_range; +mod for_loops_over_fallibles; mod for_mut_range_bound; mod for_single_element_loop; mod infinite_loop; +mod iter_next_loop; mod manual_flatten; mod manual_memcpy; mod needless_collect; @@ -15,11 +18,13 @@ mod utils; mod while_let_loop; mod while_let_on_iterator; -use crate::utils::higher; -use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; +use crate::utils::{higher, is_type_diagnostic_item, match_trait_method, match_type, paths}; +use rustc_hir::{Expr, ExprKind, LoopSource, Mutability, Pat}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty, TyS}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; +use rustc_span::symbol::sym; use utils::{get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor}; declare_clippy_lint! { @@ -588,10 +593,77 @@ fn check_for_loop<'tcx>( for_loop_range::check_for_loop_range(cx, pat, arg, body, expr); explicit_counter_loop::check_for_loop_explicit_counter(cx, pat, arg, body, expr); } - for_loop_arg::check_for_loop_arg(cx, pat, arg, expr); + check_for_loop_arg(cx, pat, arg, expr); for_loop_over_map_kv::check_for_loop_over_map_kv(cx, pat, arg, body, expr); for_mut_range_bound::check_for_mut_range_bound(cx, arg, body); for_single_element_loop::check_for_single_element_loop(cx, pat, arg, body, expr); same_item_push::detect_same_item_push(cx, pat, arg, body, expr); manual_flatten::check_manual_flatten(cx, pat, arg, body, span); } + +fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) { + let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used + if let ExprKind::MethodCall(ref method, _, ref args, _) = arg.kind { + // just the receiver, no arguments + if args.len() == 1 { + let method_name = &*method.ident.as_str(); + // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x + if method_name == "iter" || method_name == "iter_mut" { + if is_ref_iterable_type(cx, &args[0]) { + explicit_iter_loop::lint_iter_method(cx, args, arg, method_name); + } + } else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) { + let receiver_ty = cx.typeck_results().expr_ty(&args[0]); + let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]); + if TyS::same_type(receiver_ty, receiver_ty_adjusted) { + explicit_into_iter_loop::check_explicit_into_iter_loop(cx, args, arg); + } else { + let ref_receiver_ty = cx.tcx.mk_ref( + cx.tcx.lifetimes.re_erased, + ty::TypeAndMut { + ty: receiver_ty, + mutbl: Mutability::Not, + }, + ); + if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) { + explicit_iter_loop::lint_iter_method(cx, args, arg, method_name) + } + } + } else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) { + iter_next_loop::lint(cx, expr); + next_loop_linted = true; + } + } + } + if !next_loop_linted { + for_loops_over_fallibles::check_arg_type(cx, pat, arg); + } +} + +/// Returns `true` if the type of expr is one that provides `IntoIterator` impls +/// for `&T` and `&mut T`, such as `Vec`. +#[rustfmt::skip] +fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + // no walk_ptrs_ty: calling iter() on a reference can make sense because it + // will allow further borrows afterwards + let ty = cx.typeck_results().expr_ty(e); + is_iterable_array(ty, cx) || + is_type_diagnostic_item(cx, ty, sym::vec_type) || + match_type(cx, ty, &paths::LINKED_LIST) || + is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || + is_type_diagnostic_item(cx, ty, sym!(hashset_type)) || + is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || + match_type(cx, ty, &paths::BINARY_HEAP) || + match_type(cx, ty, &paths::BTREEMAP) || + match_type(cx, ty, &paths::BTREESET) +} + +fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { + // IntoIterator is currently only implemented for array sizes <= 32 in rustc + match ty.kind() { + ty::Array(_, n) => n + .try_eval_usize(cx.tcx, cx.param_env) + .map_or(false, |val| (0..=32).contains(&val)), + _ => false, + } +} From 2229a0839efe52a3014133db5e469770000341c4 Mon Sep 17 00:00:00 2001 From: nahuakang Date: Sun, 21 Feb 2021 18:25:50 +0100 Subject: [PATCH 18/22] Clean up: Rename some files to be consistent with lint names; import lints to each file --- .../src/loops/explicit_counter_loop.rs | 6 ++++-- ...{for_loop_over_map_kv.rs => for_kv_map.rs} | 3 ++- clippy_lints/src/loops/manual_flatten.rs | 3 ++- clippy_lints/src/loops/manual_memcpy.rs | 4 ++-- clippy_lints/src/loops/mod.rs | 20 +++++++++---------- ..._mut_range_bound.rs => mut_range_bound.rs} | 3 ++- clippy_lints/src/loops/needless_collect.rs | 7 ++++--- ...r_loop_range.rs => needless_range_loop.rs} | 5 +++-- clippy_lints/src/loops/same_item_push.rs | 3 ++- ...element_loop.rs => single_element_loop.rs} | 4 ++-- ...e_loop.rs => while_immutable_condition.rs} | 3 ++- 11 files changed, 35 insertions(+), 26 deletions(-) rename clippy_lints/src/loops/{for_loop_over_map_kv.rs => for_kv_map.rs} (97%) rename clippy_lints/src/loops/{for_mut_range_bound.rs => mut_range_bound.rs} (98%) rename clippy_lints/src/loops/{for_loop_range.rs => needless_range_loop.rs} (99%) rename clippy_lints/src/loops/{for_single_element_loop.rs => single_element_loop.rs} (93%) rename clippy_lints/src/loops/{infinite_loop.rs => while_immutable_condition.rs} (98%) diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs index 68fee269eb1..59116032f66 100644 --- a/clippy_lints/src/loops/explicit_counter_loop.rs +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -1,4 +1,6 @@ -use super::{get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor}; +use super::{ + get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT_COUNTER_LOOP, +}; use crate::utils::{get_enclosing_block, is_integer_const, snippet_with_applicability, span_lint_and_sugg}; use if_chain::if_chain; use rustc_errors::Applicability; @@ -37,7 +39,7 @@ pub(super) fn check_for_loop_explicit_counter<'tcx>( span_lint_and_sugg( cx, - super::EXPLICIT_COUNTER_LOOP, + EXPLICIT_COUNTER_LOOP, for_span.with_hi(arg.span.hi()), &format!("the variable `{}` is used as a loop counter", name), "consider using", diff --git a/clippy_lints/src/loops/for_loop_over_map_kv.rs b/clippy_lints/src/loops/for_kv_map.rs similarity index 97% rename from clippy_lints/src/loops/for_loop_over_map_kv.rs rename to clippy_lints/src/loops/for_kv_map.rs index c5f0b0909ba..228d462f6e1 100644 --- a/clippy_lints/src/loops/for_loop_over_map_kv.rs +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -1,3 +1,4 @@ +use super::FOR_KV_MAP; use crate::utils::visitors::LocalUsedVisitor; use crate::utils::{is_type_diagnostic_item, match_type, multispan_sugg, paths, snippet, span_lint_and_then, sugg}; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; @@ -37,7 +38,7 @@ pub(super) fn check_for_loop_over_map_kv<'tcx>( if is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || match_type(cx, ty, &paths::BTREEMAP) { span_lint_and_then( cx, - super::FOR_KV_MAP, + FOR_KV_MAP, expr.span, &format!("you seem to want to iterate on a map's {}s", kind), |diag| { diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs index db90d2c0468..efacda77284 100644 --- a/clippy_lints/src/loops/manual_flatten.rs +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -1,4 +1,5 @@ use super::utils::make_iterator_snippet; +use super::MANUAL_FLATTEN; use crate::utils::{is_ok_ctor, is_some_ctor, path_to_local_id, span_lint_and_then}; use if_chain::if_chain; use rustc_errors::Applicability; @@ -55,7 +56,7 @@ pub(super) fn check_manual_flatten<'tcx>( span_lint_and_then( cx, - super::MANUAL_FLATTEN, + MANUAL_FLATTEN, span, &msg, |diag| { diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs index 13a434aa210..e3080d9f556 100644 --- a/clippy_lints/src/loops/manual_memcpy.rs +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -1,4 +1,4 @@ -use super::{get_span_of_entire_for_loop, IncrementVisitor, InitializeVisitor}; +use super::{get_span_of_entire_for_loop, IncrementVisitor, InitializeVisitor, MANUAL_MEMCPY}; use crate::utils::sugg::Sugg; use crate::utils::{ get_enclosing_block, higher, is_type_diagnostic_item, path_to_local, snippet, span_lint_and_sugg, sugg, @@ -84,7 +84,7 @@ pub(super) fn detect_manual_memcpy<'tcx>( if let Some(big_sugg) = big_sugg { span_lint_and_sugg( cx, - super::MANUAL_MEMCPY, + MANUAL_MEMCPY, get_span_of_entire_for_loop(expr), "it looks like you're manually copying between slices", "try replacing the loop by", diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 4594afc2332..0d408698fc9 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -2,19 +2,19 @@ mod empty_loop; mod explicit_counter_loop; mod explicit_into_iter_loop; mod explicit_iter_loop; -mod for_loop_over_map_kv; -mod for_loop_range; +mod for_kv_map; mod for_loops_over_fallibles; -mod for_mut_range_bound; -mod for_single_element_loop; -mod infinite_loop; mod iter_next_loop; mod manual_flatten; mod manual_memcpy; +mod mut_range_bound; mod needless_collect; +mod needless_range_loop; mod never_loop; mod same_item_push; +mod single_element_loop; mod utils; +mod while_immutable_condition; mod while_let_loop; mod while_let_on_iterator; @@ -573,7 +573,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { while_let_on_iterator::check_while_let_on_iterator(cx, expr); if let Some((cond, body)) = higher::while_loop(&expr) { - infinite_loop::check_infinite_loop(cx, cond, body); + while_immutable_condition::check_infinite_loop(cx, cond, body); } needless_collect::check_needless_collect(expr, cx); @@ -590,13 +590,13 @@ fn check_for_loop<'tcx>( ) { let is_manual_memcpy_triggered = manual_memcpy::detect_manual_memcpy(cx, pat, arg, body, expr); if !is_manual_memcpy_triggered { - for_loop_range::check_for_loop_range(cx, pat, arg, body, expr); + needless_range_loop::check_for_loop_range(cx, pat, arg, body, expr); explicit_counter_loop::check_for_loop_explicit_counter(cx, pat, arg, body, expr); } check_for_loop_arg(cx, pat, arg, expr); - for_loop_over_map_kv::check_for_loop_over_map_kv(cx, pat, arg, body, expr); - for_mut_range_bound::check_for_mut_range_bound(cx, arg, body); - for_single_element_loop::check_for_single_element_loop(cx, pat, arg, body, expr); + for_kv_map::check_for_loop_over_map_kv(cx, pat, arg, body, expr); + mut_range_bound::check_for_mut_range_bound(cx, arg, body); + single_element_loop::check_for_single_element_loop(cx, pat, arg, body, expr); same_item_push::detect_same_item_push(cx, pat, arg, body, expr); manual_flatten::check_manual_flatten(cx, pat, arg, body, span); } diff --git a/clippy_lints/src/loops/for_mut_range_bound.rs b/clippy_lints/src/loops/mut_range_bound.rs similarity index 98% rename from clippy_lints/src/loops/for_mut_range_bound.rs rename to clippy_lints/src/loops/mut_range_bound.rs index d305c55cb95..9f31617edeb 100644 --- a/clippy_lints/src/loops/for_mut_range_bound.rs +++ b/clippy_lints/src/loops/mut_range_bound.rs @@ -1,3 +1,4 @@ +use super::MUT_RANGE_BOUND; use crate::utils::{higher, path_to_local, span_lint}; use if_chain::if_chain; use rustc_hir::{BindingAnnotation, Expr, HirId, Node, PatKind}; @@ -27,7 +28,7 @@ fn mut_warn_with_span(cx: &LateContext<'_>, span: Option) { if let Some(sp) = span { span_lint( cx, - super::MUT_RANGE_BOUND, + MUT_RANGE_BOUND, sp, "attempt to mutate range bound within loop; note that the range of the loop is unchanged", ); diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index cc2e2975492..d1ce055445b 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -1,3 +1,4 @@ +use super::NEEDLESS_COLLECT; use crate::utils::sugg::Sugg; use crate::utils::{ is_type_diagnostic_item, match_trait_method, match_type, path_to_local_id, paths, snippet, span_lint_and_sugg, @@ -35,7 +36,7 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont let span = shorten_needless_collect_span(expr); span_lint_and_sugg( cx, - super::NEEDLESS_COLLECT, + NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, "replace with", @@ -47,7 +48,7 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont let span = shorten_needless_collect_span(expr); span_lint_and_sugg( cx, - super::NEEDLESS_COLLECT, + NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, "replace with", @@ -60,7 +61,7 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont let span = shorten_needless_collect_span(expr); span_lint_and_then( cx, - super::NEEDLESS_COLLECT, + NEEDLESS_COLLECT, span, NEEDLESS_COLLECT_MSG, |diag| { diff --git a/clippy_lints/src/loops/for_loop_range.rs b/clippy_lints/src/loops/needless_range_loop.rs similarity index 99% rename from clippy_lints/src/loops/for_loop_range.rs rename to clippy_lints/src/loops/needless_range_loop.rs index 43560abb7f2..298753cc031 100644 --- a/clippy_lints/src/loops/for_loop_range.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -1,3 +1,4 @@ +use super::NEEDLESS_RANGE_LOOP; use crate::utils::visitors::LocalUsedVisitor; use crate::utils::{ contains_name, has_iter_method, higher, is_integer_const, match_trait_method, multispan_sugg, path_to_local_id, @@ -142,7 +143,7 @@ pub(super) fn check_for_loop_range<'tcx>( if visitor.nonindex { span_lint_and_then( cx, - super::NEEDLESS_RANGE_LOOP, + NEEDLESS_RANGE_LOOP, expr.span, &format!("the loop variable `{}` is used to index `{}`", ident.name, indexed), |diag| { @@ -168,7 +169,7 @@ pub(super) fn check_for_loop_range<'tcx>( span_lint_and_then( cx, - super::NEEDLESS_RANGE_LOOP, + NEEDLESS_RANGE_LOOP, expr.span, &format!("the loop variable `{}` is only used to index `{}`", ident.name, indexed), |diag| { diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs index 62efb58a38f..c1d5f8b7186 100644 --- a/clippy_lints/src/loops/same_item_push.rs +++ b/clippy_lints/src/loops/same_item_push.rs @@ -1,3 +1,4 @@ +use super::SAME_ITEM_PUSH; use crate::utils::{implements_trait, is_type_diagnostic_item, snippet_with_macro_callsite, span_lint_and_help}; use if_chain::if_chain; use rustc_hir::def::{DefKind, Res}; @@ -22,7 +23,7 @@ pub(super) fn detect_same_item_push<'tcx>( span_lint_and_help( cx, - super::SAME_ITEM_PUSH, + SAME_ITEM_PUSH, vec.span, "it looks like the same item is being pushed into this Vec", None, diff --git a/clippy_lints/src/loops/for_single_element_loop.rs b/clippy_lints/src/loops/single_element_loop.rs similarity index 93% rename from clippy_lints/src/loops/for_single_element_loop.rs rename to clippy_lints/src/loops/single_element_loop.rs index 8ba19a2233a..9e2697186db 100644 --- a/clippy_lints/src/loops/for_single_element_loop.rs +++ b/clippy_lints/src/loops/single_element_loop.rs @@ -1,4 +1,4 @@ -use super::get_span_of_entire_for_loop; +use super::{get_span_of_entire_for_loop, SINGLE_ELEMENT_LOOP}; use crate::utils::{indent_of, single_segment_path, snippet, span_lint_and_sugg}; use if_chain::if_chain; use rustc_errors::Applicability; @@ -30,7 +30,7 @@ pub(super) fn check_for_single_element_loop<'tcx>( span_lint_and_sugg( cx, - super::SINGLE_ELEMENT_LOOP, + SINGLE_ELEMENT_LOOP, for_span, "for loop over a single element", "try", diff --git a/clippy_lints/src/loops/infinite_loop.rs b/clippy_lints/src/loops/while_immutable_condition.rs similarity index 98% rename from clippy_lints/src/loops/infinite_loop.rs rename to clippy_lints/src/loops/while_immutable_condition.rs index b89942fb647..42e6551b681 100644 --- a/clippy_lints/src/loops/infinite_loop.rs +++ b/clippy_lints/src/loops/while_immutable_condition.rs @@ -1,3 +1,4 @@ +use super::WHILE_IMMUTABLE_CONDITION; use crate::consts::constant; use crate::utils::span_lint_and_then; use crate::utils::usage::mutated_variables; @@ -43,7 +44,7 @@ pub(super) fn check_infinite_loop<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr if no_cond_variable_mutated && !mutable_static_in_cond { span_lint_and_then( cx, - super::WHILE_IMMUTABLE_CONDITION, + WHILE_IMMUTABLE_CONDITION, cond.span, "variables in the condition are not mutated in the loop body", |diag| { From 845a3a061c541a4c2b2293022113cbfd3e6e9cb2 Mon Sep 17 00:00:00 2001 From: nahuakang Date: Mon, 22 Feb 2021 16:24:25 +0100 Subject: [PATCH 19/22] Include loops.rs changes from PR#6698 --- clippy_lints/src/loops/for_kv_map.rs | 8 ++++---- clippy_lints/src/loops/needless_range_loop.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs index 228d462f6e1..a5aaf082dd8 100644 --- a/clippy_lints/src/loops/for_kv_map.rs +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -20,8 +20,8 @@ pub(super) fn check_for_loop_over_map_kv<'tcx>( let arg_span = arg.span; let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() { ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) { - (key, _) if pat_is_wild(key, body) => (pat[1].span, "value", ty, mutbl), - (_, value) if pat_is_wild(value, body) => (pat[0].span, "key", ty, Mutability::Not), + (key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl), + (_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not), _ => return, }, _ => return, @@ -59,11 +59,11 @@ pub(super) fn check_for_loop_over_map_kv<'tcx>( } /// Returns `true` if the pattern is a `PatWild` or an ident prefixed with `_`. -fn pat_is_wild<'tcx>(pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { +fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { match *pat { PatKind::Wild => true, PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => { - !LocalUsedVisitor::new(id).check_expr(body) + !LocalUsedVisitor::new(cx, id).check_expr(body) }, _ => false, } diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 298753cc031..ee275bc5bd6 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -257,7 +257,7 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> { then { let index_used_directly = path_to_local_id(idx, self.var); let indexed_indirectly = { - let mut used_visitor = LocalUsedVisitor::new(self.var); + let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var); walk_expr(&mut used_visitor, idx); used_visitor.used }; From eaf63d6df75e0a046ba65d8b3cad0314b447f63e Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Tue, 2 Mar 2021 11:49:14 +0900 Subject: [PATCH 20/22] Unify names of lint entry functions in loops to 'check' --- clippy_lints/src/loops/empty_loop.rs | 2 +- .../src/loops/explicit_counter_loop.rs | 2 +- .../src/loops/explicit_into_iter_loop.rs | 2 +- clippy_lints/src/loops/explicit_iter_loop.rs | 2 +- clippy_lints/src/loops/for_kv_map.rs | 2 +- .../src/loops/for_loops_over_fallibles.rs | 2 +- clippy_lints/src/loops/iter_next_loop.rs | 2 +- clippy_lints/src/loops/manual_flatten.rs | 2 +- clippy_lints/src/loops/manual_memcpy.rs | 2 +- clippy_lints/src/loops/mod.rs | 38 +++++++++---------- clippy_lints/src/loops/mut_range_bound.rs | 2 +- clippy_lints/src/loops/needless_collect.rs | 2 +- clippy_lints/src/loops/needless_range_loop.rs | 2 +- clippy_lints/src/loops/never_loop.rs | 2 +- clippy_lints/src/loops/same_item_push.rs | 2 +- clippy_lints/src/loops/single_element_loop.rs | 2 +- .../src/loops/while_immutable_condition.rs | 2 +- clippy_lints/src/loops/while_let_loop.rs | 2 +- .../src/loops/while_let_on_iterator.rs | 2 +- 19 files changed, 37 insertions(+), 37 deletions(-) diff --git a/clippy_lints/src/loops/empty_loop.rs b/clippy_lints/src/loops/empty_loop.rs index fdc99e190e3..67d1ac89b97 100644 --- a/clippy_lints/src/loops/empty_loop.rs +++ b/clippy_lints/src/loops/empty_loop.rs @@ -4,7 +4,7 @@ use crate::utils::{is_in_panic_handler, is_no_std_crate, span_lint_and_help}; use rustc_hir::{Block, Expr}; use rustc_lint::LateContext; -pub(super) fn check_empty_loop(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { if loop_block.stmts.is_empty() && loop_block.expr.is_none() && !is_in_panic_handler(cx, expr) { let msg = "empty `loop {}` wastes CPU cycles"; let help = if is_no_std_crate(cx.tcx.hir().krate()) { diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs index 59116032f66..8d98b940c66 100644 --- a/clippy_lints/src/loops/explicit_counter_loop.rs +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -11,7 +11,7 @@ use rustc_lint::LateContext; // To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be // incremented exactly once in the loop body, and initialized to zero // at the start of the loop. -pub(super) fn check_for_loop_explicit_counter<'tcx>( +pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, diff --git a/clippy_lints/src/loops/explicit_into_iter_loop.rs b/clippy_lints/src/loops/explicit_into_iter_loop.rs index d5d2bedaf1b..f89c4b85103 100644 --- a/clippy_lints/src/loops/explicit_into_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_into_iter_loop.rs @@ -4,7 +4,7 @@ use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -pub(super) fn check_explicit_into_iter_loop(cx: &LateContext<'_>, method_args: &'hir [Expr<'hir>], arg: &Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, method_args: &'hir [Expr<'hir>], arg: &Expr<'_>) { let mut applicability = Applicability::MachineApplicable; let object = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability); span_lint_and_sugg( diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 14184865da3..0836054796a 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -4,7 +4,7 @@ use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -pub(super) fn lint_iter_method(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { +pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { let mut applicability = Applicability::MachineApplicable; let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); let muta = if method_name == "iter_mut" { "mut " } else { "" }; diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs index a5aaf082dd8..eb53c3179ca 100644 --- a/clippy_lints/src/loops/for_kv_map.rs +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -6,7 +6,7 @@ use rustc_lint::LateContext; use rustc_middle::ty; /// Checks for the `FOR_KV_MAP` lint. -pub(super) fn check_for_loop_over_map_kv<'tcx>( +pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, diff --git a/clippy_lints/src/loops/for_loops_over_fallibles.rs b/clippy_lints/src/loops/for_loops_over_fallibles.rs index 1339af33759..db22d90a304 100644 --- a/clippy_lints/src/loops/for_loops_over_fallibles.rs +++ b/clippy_lints/src/loops/for_loops_over_fallibles.rs @@ -5,7 +5,7 @@ use rustc_lint::LateContext; use rustc_span::symbol::sym; /// Checks for `for` loops over `Option`s and `Result`s. -pub(super) fn check_arg_type(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { let ty = cx.typeck_results().expr_ty(arg); if is_type_diagnostic_item(cx, ty, sym::option_type) { span_lint_and_help( diff --git a/clippy_lints/src/loops/iter_next_loop.rs b/clippy_lints/src/loops/iter_next_loop.rs index 7a5a873a358..cb9916deb62 100644 --- a/clippy_lints/src/loops/iter_next_loop.rs +++ b/clippy_lints/src/loops/iter_next_loop.rs @@ -3,7 +3,7 @@ use crate::utils::span_lint; use rustc_hir::Expr; use rustc_lint::LateContext; -pub(super) fn lint(cx: &LateContext<'_>, expr: &Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { span_lint( cx, ITER_NEXT_LOOP, diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs index efacda77284..3d3ae6f3152 100644 --- a/clippy_lints/src/loops/manual_flatten.rs +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -9,7 +9,7 @@ use rustc_span::source_map::Span; /// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the /// iterator element is used. -pub(super) fn check_manual_flatten<'tcx>( +pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs index e3080d9f556..bf0b8cc2459 100644 --- a/clippy_lints/src/loops/manual_memcpy.rs +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -15,7 +15,7 @@ use std::iter::Iterator; /// Checks for for loops that sequentially copy items from one slice-like /// object to another. -pub(super) fn detect_manual_memcpy<'tcx>( +pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 0d408698fc9..5c96128c6a4 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -559,24 +559,24 @@ impl<'tcx> LateLintPass<'tcx> for Loops { } // check for never_loop - never_loop::check_never_loop(cx, expr); + never_loop::check(cx, expr); // check for `loop { if let {} else break }` that could be `while let` // (also matches an explicit "match" instead of "if let") // (even if the "match" or "if let" is used for declaration) if let ExprKind::Loop(ref block, _, LoopSource::Loop, _) = expr.kind { // also check for empty `loop {}` statements, skipping those in #[panic_handler] - empty_loop::check_empty_loop(cx, expr, block); - while_let_loop::check_while_let_loop(cx, expr, block); + empty_loop::check(cx, expr, block); + while_let_loop::check(cx, expr, block); } - while_let_on_iterator::check_while_let_on_iterator(cx, expr); + while_let_on_iterator::check(cx, expr); if let Some((cond, body)) = higher::while_loop(&expr) { - while_immutable_condition::check_infinite_loop(cx, cond, body); + while_immutable_condition::check(cx, cond, body); } - needless_collect::check_needless_collect(expr, cx); + needless_collect::check(expr, cx); } } @@ -588,17 +588,17 @@ fn check_for_loop<'tcx>( expr: &'tcx Expr<'_>, span: Span, ) { - let is_manual_memcpy_triggered = manual_memcpy::detect_manual_memcpy(cx, pat, arg, body, expr); + let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr); if !is_manual_memcpy_triggered { - needless_range_loop::check_for_loop_range(cx, pat, arg, body, expr); - explicit_counter_loop::check_for_loop_explicit_counter(cx, pat, arg, body, expr); + needless_range_loop::check(cx, pat, arg, body, expr); + explicit_counter_loop::check(cx, pat, arg, body, expr); } check_for_loop_arg(cx, pat, arg, expr); - for_kv_map::check_for_loop_over_map_kv(cx, pat, arg, body, expr); - mut_range_bound::check_for_mut_range_bound(cx, arg, body); - single_element_loop::check_for_single_element_loop(cx, pat, arg, body, expr); - same_item_push::detect_same_item_push(cx, pat, arg, body, expr); - manual_flatten::check_manual_flatten(cx, pat, arg, body, span); + for_kv_map::check(cx, pat, arg, body, expr); + mut_range_bound::check(cx, arg, body); + single_element_loop::check(cx, pat, arg, body, expr); + same_item_push::check(cx, pat, arg, body, expr); + manual_flatten::check(cx, pat, arg, body, span); } fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) { @@ -610,13 +610,13 @@ fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x if method_name == "iter" || method_name == "iter_mut" { if is_ref_iterable_type(cx, &args[0]) { - explicit_iter_loop::lint_iter_method(cx, args, arg, method_name); + explicit_iter_loop::check(cx, args, arg, method_name); } } else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) { let receiver_ty = cx.typeck_results().expr_ty(&args[0]); let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]); if TyS::same_type(receiver_ty, receiver_ty_adjusted) { - explicit_into_iter_loop::check_explicit_into_iter_loop(cx, args, arg); + explicit_into_iter_loop::check(cx, args, arg); } else { let ref_receiver_ty = cx.tcx.mk_ref( cx.tcx.lifetimes.re_erased, @@ -626,17 +626,17 @@ fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: }, ); if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) { - explicit_iter_loop::lint_iter_method(cx, args, arg, method_name) + explicit_iter_loop::check(cx, args, arg, method_name) } } } else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) { - iter_next_loop::lint(cx, expr); + iter_next_loop::check(cx, expr); next_loop_linted = true; } } } if !next_loop_linted { - for_loops_over_fallibles::check_arg_type(cx, pat, arg); + for_loops_over_fallibles::check(cx, pat, arg); } } diff --git a/clippy_lints/src/loops/mut_range_bound.rs b/clippy_lints/src/loops/mut_range_bound.rs index 9f31617edeb..3ae592950f1 100644 --- a/clippy_lints/src/loops/mut_range_bound.rs +++ b/clippy_lints/src/loops/mut_range_bound.rs @@ -8,7 +8,7 @@ use rustc_middle::ty; use rustc_span::source_map::Span; use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; -pub(super) fn check_for_mut_range_bound(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) { if let Some(higher::Range { start: Some(start), end: Some(end), diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index d1ce055445b..6f271309105 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -15,7 +15,7 @@ use rustc_span::symbol::{sym, Ident}; const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; -pub(super) fn check_needless_collect<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { +pub(super) fn check<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { check_needless_collect_direct_usage(expr, cx); check_needless_collect_indirect_usage(expr, cx); } diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index ee275bc5bd6..5f02e4b9d87 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -21,7 +21,7 @@ use std::mem; /// Checks for looping over a range and then indexing a sequence with it. /// The iteratee must be a range literal. #[allow(clippy::too_many_lines)] -pub(super) fn check_for_loop_range<'tcx>( +pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index d51ff81103f..45e1001d755 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -4,7 +4,7 @@ use rustc_hir::{Block, Expr, ExprKind, HirId, InlineAsmOperand, Stmt, StmtKind}; use rustc_lint::LateContext; use std::iter::{once, Iterator}; -pub(super) fn check_never_loop(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Loop(ref block, _, _, _) = expr.kind { match never_loop_block(block, expr.hir_id) { NeverLoopResult::AlwaysBreak => span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops"), diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs index c1d5f8b7186..f3585830e4a 100644 --- a/clippy_lints/src/loops/same_item_push.rs +++ b/clippy_lints/src/loops/same_item_push.rs @@ -10,7 +10,7 @@ use rustc_span::symbol::sym; use std::iter::Iterator; /// Detects for loop pushing the same item into a Vec -pub(super) fn detect_same_item_push<'tcx>( +pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, _: &'tcx Expr<'_>, diff --git a/clippy_lints/src/loops/single_element_loop.rs b/clippy_lints/src/loops/single_element_loop.rs index 9e2697186db..38400c93c9a 100644 --- a/clippy_lints/src/loops/single_element_loop.rs +++ b/clippy_lints/src/loops/single_element_loop.rs @@ -5,7 +5,7 @@ use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Pat, PatKind}; use rustc_lint::LateContext; -pub(super) fn check_for_single_element_loop<'tcx>( +pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, diff --git a/clippy_lints/src/loops/while_immutable_condition.rs b/clippy_lints/src/loops/while_immutable_condition.rs index 42e6551b681..05e0a722563 100644 --- a/clippy_lints/src/loops/while_immutable_condition.rs +++ b/clippy_lints/src/loops/while_immutable_condition.rs @@ -11,7 +11,7 @@ use rustc_lint::LateContext; use rustc_middle::hir::map::Map; use std::iter::Iterator; -pub(super) fn check_infinite_loop<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { if constant(cx, cx.typeck_results(), cond).is_some() { // A pure constant condition (e.g., `while false`) is not linted. return; diff --git a/clippy_lints/src/loops/while_let_loop.rs b/clippy_lints/src/loops/while_let_loop.rs index 0c957986964..65d8f2f1111 100644 --- a/clippy_lints/src/loops/while_let_loop.rs +++ b/clippy_lints/src/loops/while_let_loop.rs @@ -5,7 +5,7 @@ use rustc_hir::{Block, Expr, ExprKind, MatchSource, StmtKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; -pub(super) fn check_while_let_loop(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { // extract the expression from the first statement (if any) in a block let inner_stmt_expr = extract_expr_from_first_stmt(loop_block); // or extract the first expression (if any) from the block diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 090c8ceba97..e5a47694faa 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -14,7 +14,7 @@ use rustc_middle::hir::map::Map; use rustc_span::symbol::sym; -pub(super) fn check_while_let_on_iterator(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind { let pat = &arms[0].pat.kind; if let ( From 74bd806b05b16528ff773e469145df215760a88b Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Tue, 2 Mar 2021 12:41:06 +0900 Subject: [PATCH 21/22] Simplify check_for_loop_arg --- .../src/loops/explicit_into_iter_loop.rs | 11 +++- clippy_lints/src/loops/explicit_iter_loop.rs | 57 +++++++++++++++- clippy_lints/src/loops/iter_next_loop.rs | 23 ++++--- clippy_lints/src/loops/mod.rs | 66 ++++--------------- 4 files changed, 90 insertions(+), 67 deletions(-) diff --git a/clippy_lints/src/loops/explicit_into_iter_loop.rs b/clippy_lints/src/loops/explicit_into_iter_loop.rs index f89c4b85103..1d778205a2a 100644 --- a/clippy_lints/src/loops/explicit_into_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_into_iter_loop.rs @@ -3,10 +3,17 @@ use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; +use rustc_middle::ty::TyS; + +pub(super) fn check(cx: &LateContext<'_>, args: &'hir [Expr<'hir>], arg: &Expr<'_>) { + let receiver_ty = cx.typeck_results().expr_ty(&args[0]); + let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]); + if !TyS::same_type(receiver_ty, receiver_ty_adjusted) { + return; + } -pub(super) fn check(cx: &LateContext<'_>, method_args: &'hir [Expr<'hir>], arg: &Expr<'_>) { let mut applicability = Applicability::MachineApplicable; - let object = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability); + let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); span_lint_and_sugg( cx, EXPLICIT_INTO_ITER_LOOP, diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 0836054796a..44d08916891 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -1,10 +1,35 @@ use super::EXPLICIT_ITER_LOOP; -use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use crate::utils::{match_trait_method, snippet_with_applicability, span_lint_and_sugg}; use rustc_errors::Applicability; -use rustc_hir::Expr; +use rustc_hir::{Expr, Mutability}; use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty, TyS}; +use rustc_span::symbol::sym; + +use crate::utils::{is_type_diagnostic_item, match_type, paths}; pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { + let should_lint = match method_name { + "iter" | "iter_mut" => is_ref_iterable_type(cx, &args[0]), + "into_iter" if match_trait_method(cx, arg, &paths::INTO_ITERATOR) => { + let receiver_ty = cx.typeck_results().expr_ty(&args[0]); + let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]); + let ref_receiver_ty = cx.tcx.mk_ref( + cx.tcx.lifetimes.re_erased, + ty::TypeAndMut { + ty: receiver_ty, + mutbl: Mutability::Not, + }, + ); + TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) + }, + _ => false, + }; + + if !should_lint { + return; + } + let mut applicability = Applicability::MachineApplicable; let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); let muta = if method_name == "iter_mut" { "mut " } else { "" }; @@ -19,3 +44,31 @@ pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, met applicability, ) } + +/// Returns `true` if the type of expr is one that provides `IntoIterator` impls +/// for `&T` and `&mut T`, such as `Vec`. +#[rustfmt::skip] +fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + // no walk_ptrs_ty: calling iter() on a reference can make sense because it + // will allow further borrows afterwards + let ty = cx.typeck_results().expr_ty(e); + is_iterable_array(ty, cx) || + is_type_diagnostic_item(cx, ty, sym::vec_type) || + match_type(cx, ty, &paths::LINKED_LIST) || + is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || + is_type_diagnostic_item(cx, ty, sym!(hashset_type)) || + is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || + match_type(cx, ty, &paths::BINARY_HEAP) || + match_type(cx, ty, &paths::BTREEMAP) || + match_type(cx, ty, &paths::BTREESET) +} + +fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { + // IntoIterator is currently only implemented for array sizes <= 32 in rustc + match ty.kind() { + ty::Array(_, n) => n + .try_eval_usize(cx.tcx, cx.param_env) + .map_or(false, |val| (0..=32).contains(&val)), + _ => false, + } +} diff --git a/clippy_lints/src/loops/iter_next_loop.rs b/clippy_lints/src/loops/iter_next_loop.rs index cb9916deb62..cf78bbc49a3 100644 --- a/clippy_lints/src/loops/iter_next_loop.rs +++ b/clippy_lints/src/loops/iter_next_loop.rs @@ -1,14 +1,19 @@ use super::ITER_NEXT_LOOP; -use crate::utils::span_lint; +use crate::utils::{match_trait_method, paths, span_lint}; use rustc_hir::Expr; use rustc_lint::LateContext; -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { - span_lint( - cx, - ITER_NEXT_LOOP, - expr.span, - "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ - probably not what you want", - ); +pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, expr: &Expr<'_>) -> bool { + if match_trait_method(cx, arg, &paths::ITERATOR) { + span_lint( + cx, + ITER_NEXT_LOOP, + expr.span, + "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ + probably not what you want", + ); + true + } else { + false + } } diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 5c96128c6a4..2a372c6307e 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -18,13 +18,11 @@ mod while_immutable_condition; mod while_let_loop; mod while_let_on_iterator; -use crate::utils::{higher, is_type_diagnostic_item, match_trait_method, match_type, paths}; -use rustc_hir::{Expr, ExprKind, LoopSource, Mutability, Pat}; +use crate::utils::higher; +use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Ty, TyS}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; -use rustc_span::symbol::sym; use utils::{get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor}; declare_clippy_lint! { @@ -603,67 +601,27 @@ fn check_for_loop<'tcx>( fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) { let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used + if let ExprKind::MethodCall(ref method, _, ref args, _) = arg.kind { // just the receiver, no arguments if args.len() == 1 { let method_name = &*method.ident.as_str(); // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x - if method_name == "iter" || method_name == "iter_mut" { - if is_ref_iterable_type(cx, &args[0]) { + match method_name { + "iter" | "iter_mut" => explicit_iter_loop::check(cx, args, arg, method_name), + "into_iter" => { explicit_iter_loop::check(cx, args, arg, method_name); - } - } else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) { - let receiver_ty = cx.typeck_results().expr_ty(&args[0]); - let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]); - if TyS::same_type(receiver_ty, receiver_ty_adjusted) { explicit_into_iter_loop::check(cx, args, arg); - } else { - let ref_receiver_ty = cx.tcx.mk_ref( - cx.tcx.lifetimes.re_erased, - ty::TypeAndMut { - ty: receiver_ty, - mutbl: Mutability::Not, - }, - ); - if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) { - explicit_iter_loop::check(cx, args, arg, method_name) - } - } - } else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) { - iter_next_loop::check(cx, expr); - next_loop_linted = true; + }, + "next" => { + next_loop_linted = iter_next_loop::check(cx, arg, expr); + }, + _ => {}, } } } + if !next_loop_linted { for_loops_over_fallibles::check(cx, pat, arg); } } - -/// Returns `true` if the type of expr is one that provides `IntoIterator` impls -/// for `&T` and `&mut T`, such as `Vec`. -#[rustfmt::skip] -fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - // no walk_ptrs_ty: calling iter() on a reference can make sense because it - // will allow further borrows afterwards - let ty = cx.typeck_results().expr_ty(e); - is_iterable_array(ty, cx) || - is_type_diagnostic_item(cx, ty, sym::vec_type) || - match_type(cx, ty, &paths::LINKED_LIST) || - is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || - is_type_diagnostic_item(cx, ty, sym!(hashset_type)) || - is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || - match_type(cx, ty, &paths::BINARY_HEAP) || - match_type(cx, ty, &paths::BTREEMAP) || - match_type(cx, ty, &paths::BTREESET) -} - -fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { - // IntoIterator is currently only implemented for array sizes <= 32 in rustc - match ty.kind() { - ty::Array(_, n) => n - .try_eval_usize(cx.tcx, cx.param_env) - .map_or(false, |val| (0..=32).contains(&val)), - _ => false, - } -} From 19c886b4076ebf264ef919b1fd78ebb9f193476c Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Tue, 2 Mar 2021 21:09:04 +0900 Subject: [PATCH 22/22] Remove "for_loop_arg.rs" --- clippy_lints/src/loops/for_loop_arg.rs | 149 ------------------------- 1 file changed, 149 deletions(-) delete mode 100644 clippy_lints/src/loops/for_loop_arg.rs diff --git a/clippy_lints/src/loops/for_loop_arg.rs b/clippy_lints/src/loops/for_loop_arg.rs deleted file mode 100644 index b33e7183984..00000000000 --- a/clippy_lints/src/loops/for_loop_arg.rs +++ /dev/null @@ -1,149 +0,0 @@ -use crate::utils::{ - is_type_diagnostic_item, match_trait_method, match_type, paths, snippet, snippet_with_applicability, span_lint, - span_lint_and_help, span_lint_and_sugg, -}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Mutability, Pat}; -use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty, TyS}; -use rustc_span::symbol::sym; - -pub(super) fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) { - let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used - if let ExprKind::MethodCall(ref method, _, ref args, _) = arg.kind { - // just the receiver, no arguments - if args.len() == 1 { - let method_name = &*method.ident.as_str(); - // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x - if method_name == "iter" || method_name == "iter_mut" { - if is_ref_iterable_type(cx, &args[0]) { - lint_iter_method(cx, args, arg, method_name); - } - } else if method_name == "into_iter" && match_trait_method(cx, arg, &paths::INTO_ITERATOR) { - let receiver_ty = cx.typeck_results().expr_ty(&args[0]); - let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]); - if TyS::same_type(receiver_ty, receiver_ty_adjusted) { - let mut applicability = Applicability::MachineApplicable; - let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); - span_lint_and_sugg( - cx, - super::EXPLICIT_INTO_ITER_LOOP, - arg.span, - "it is more concise to loop over containers instead of using explicit \ - iteration methods", - "to write this more concisely, try", - object.to_string(), - applicability, - ); - } else { - let ref_receiver_ty = cx.tcx.mk_ref( - cx.tcx.lifetimes.re_erased, - ty::TypeAndMut { - ty: receiver_ty, - mutbl: Mutability::Not, - }, - ); - if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) { - lint_iter_method(cx, args, arg, method_name) - } - } - } else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) { - span_lint( - cx, - super::ITER_NEXT_LOOP, - expr.span, - "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ - probably not what you want", - ); - next_loop_linted = true; - } - } - } - if !next_loop_linted { - check_arg_type(cx, pat, arg); - } -} - -/// Checks for `for` loops over `Option`s and `Result`s. -fn check_arg_type(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { - let ty = cx.typeck_results().expr_ty(arg); - if is_type_diagnostic_item(cx, ty, sym::option_type) { - span_lint_and_help( - cx, - super::FOR_LOOPS_OVER_FALLIBLES, - arg.span, - &format!( - "for loop over `{0}`, which is an `Option`. This is more readably written as an \ - `if let` statement", - snippet(cx, arg.span, "_") - ), - None, - &format!( - "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", - snippet(cx, pat.span, "_"), - snippet(cx, arg.span, "_") - ), - ); - } else if is_type_diagnostic_item(cx, ty, sym::result_type) { - span_lint_and_help( - cx, - super::FOR_LOOPS_OVER_FALLIBLES, - arg.span, - &format!( - "for loop over `{0}`, which is a `Result`. This is more readably written as an \ - `if let` statement", - snippet(cx, arg.span, "_") - ), - None, - &format!( - "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", - snippet(cx, pat.span, "_"), - snippet(cx, arg.span, "_") - ), - ); - } -} - -fn lint_iter_method(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { - let mut applicability = Applicability::MachineApplicable; - let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); - let muta = if method_name == "iter_mut" { "mut " } else { "" }; - span_lint_and_sugg( - cx, - super::EXPLICIT_ITER_LOOP, - arg.span, - "it is more concise to loop over references to containers instead of using explicit \ - iteration methods", - "to write this more concisely, try", - format!("&{}{}", muta, object), - applicability, - ) -} - -/// Returns `true` if the type of expr is one that provides `IntoIterator` impls -/// for `&T` and `&mut T`, such as `Vec`. -#[rustfmt::skip] -fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - // no walk_ptrs_ty: calling iter() on a reference can make sense because it - // will allow further borrows afterwards - let ty = cx.typeck_results().expr_ty(e); - is_iterable_array(ty, cx) || - is_type_diagnostic_item(cx, ty, sym::vec_type) || - match_type(cx, ty, &paths::LINKED_LIST) || - is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || - is_type_diagnostic_item(cx, ty, sym!(hashset_type)) || - is_type_diagnostic_item(cx, ty, sym!(vecdeque_type)) || - match_type(cx, ty, &paths::BINARY_HEAP) || - match_type(cx, ty, &paths::BTREEMAP) || - match_type(cx, ty, &paths::BTREESET) -} - -fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { - // IntoIterator is currently only implemented for array sizes <= 32 in rustc - match ty.kind() { - ty::Array(_, n) => n - .try_eval_usize(cx.tcx, cx.param_env) - .map_or(false, |val| (0..=32).contains(&val)), - _ => false, - } -}