Auto merge of #7289 - camsteffen:needless-collect-shadow, r=Manishearth

Fix needless_collect with binding shadowing

changelog: Fix [`needless_collect`] weird output when a binding is shadowed

Fixes #7200
This commit is contained in:
bors 2021-06-04 15:39:16 +00:00
commit 9991040258

View file

@ -7,10 +7,10 @@ use clippy_utils::{is_trait_method, path_to_local_id};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{Block, Expr, ExprKind, GenericArg, GenericArgs, HirId, Local, Pat, PatKind, QPath, StmtKind, Ty}; use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, StmtKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_span::symbol::{sym, Ident}; use rustc_span::sym;
use rustc_span::{MultiSpan, Span}; use rustc_span::{MultiSpan, Span};
const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
@ -24,10 +24,8 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont
if let ExprKind::MethodCall(method, _, args, _) = expr.kind; if let ExprKind::MethodCall(method, _, args, _) = expr.kind;
if let ExprKind::MethodCall(chain_method, method0_span, _, _) = args[0].kind; if let ExprKind::MethodCall(chain_method, method0_span, _, _) = args[0].kind;
if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator); if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
if let Some(generic_args) = chain_method.args;
if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
if let Some(ty) = cx.typeck_results().node_type_opt(ty.hir_id);
then { then {
let ty = cx.typeck_results().expr_ty(&args[0]);
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MachineApplicable;
let is_empty_sugg = "next().is_none()".to_string(); let is_empty_sugg = "next().is_none()".to_string();
let method_name = &*method.ident.name.as_str(); let method_name = &*method.ident.name.as_str();
@ -72,40 +70,25 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont
} }
fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
fn get_hir_id<'tcx>(ty: Option<&Ty<'tcx>>, method_args: Option<&GenericArgs<'tcx>>) -> Option<HirId> {
if let Some(ty) = ty {
return Some(ty.hir_id);
}
if let Some(generic_args) = method_args {
if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0) {
return Some(ty.hir_id);
}
}
None
}
if let ExprKind::Block(block, _) = expr.kind { if let ExprKind::Block(block, _) = expr.kind {
for stmt in block.stmts { for stmt in block.stmts {
if_chain! { if_chain! {
if let StmtKind::Local( if let StmtKind::Local(local) = stmt.kind;
Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. }, if let PatKind::Binding(_, id, ..) = local.pat.kind;
init: Some(init_expr), ty, .. } if let Some(init_expr) = local.init;
) = stmt.kind;
if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind; if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind;
if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator); if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator);
if let Some(hir_id) = get_hir_id(*ty, method_name.args); let ty = cx.typeck_results().expr_ty(init_expr);
if let Some(ty) = cx.typeck_results().node_type_opt(hir_id);
if is_type_diagnostic_item(cx, ty, sym::vec_type) || if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) ||
is_type_diagnostic_item(cx, ty, sym::BinaryHeap) || is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
is_type_diagnostic_item(cx, ty, sym::LinkedList); is_type_diagnostic_item(cx, ty, sym::LinkedList);
if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident); if let Some(iter_calls) = detect_iter_and_into_iters(block, id);
if let [iter_call] = &*iter_calls; if let [iter_call] = &*iter_calls;
then { then {
let mut used_count_visitor = UsedCountVisitor { let mut used_count_visitor = UsedCountVisitor {
cx, cx,
id: *pat_id, id,
count: 0, count: 0,
}; };
walk_block(&mut used_count_visitor, block); walk_block(&mut used_count_visitor, block);
@ -187,48 +170,40 @@ enum IterFunctionKind {
struct IterFunctionVisitor { struct IterFunctionVisitor {
uses: Vec<IterFunction>, uses: Vec<IterFunction>,
seen_other: bool, seen_other: bool,
target: Ident, target: HirId,
} }
impl<'tcx> Visitor<'tcx> for IterFunctionVisitor { impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
// Check function calls on our collection // Check function calls on our collection
if_chain! { if let ExprKind::MethodCall(method_name, _, [recv, args @ ..], _) = &expr.kind {
if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind; if path_to_local_id(recv, self.target) {
if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. }) = args.get(0); match &*method_name.ident.name.as_str() {
if let &[name] = &path.segments; "into_iter" => self.uses.push(IterFunction {
if name.ident == self.target; func: IterFunctionKind::IntoIter,
then { span: expr.span,
let len = sym!(len); }),
let is_empty = sym!(is_empty); "len" => self.uses.push(IterFunction {
let contains = sym!(contains); func: IterFunctionKind::Len,
match method_name.ident.name { span: expr.span,
sym::into_iter => self.uses.push( }),
IterFunction { func: IterFunctionKind::IntoIter, span: expr.span } "is_empty" => self.uses.push(IterFunction {
), func: IterFunctionKind::IsEmpty,
name if name == len => self.uses.push( span: expr.span,
IterFunction { func: IterFunctionKind::Len, span: expr.span } }),
), "contains" => self.uses.push(IterFunction {
name if name == is_empty => self.uses.push( func: IterFunctionKind::Contains(args[0].span),
IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span } span: expr.span,
), }),
name if name == contains => self.uses.push(
IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span }
),
_ => self.seen_other = true, _ => self.seen_other = true,
} }
return return;
} }
} }
// Check if the collection is used for anything else // Check if the collection is used for anything else
if_chain! { if path_to_local_id(expr, self.target) {
if let Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. } = expr; self.seen_other = true;
if let &[name] = &path.segments; } else {
if name.ident == self.target; walk_expr(self, expr);
then {
self.seen_other = true;
} else {
walk_expr(self, expr);
}
} }
} }
@ -262,10 +237,10 @@ impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
/// Detect the occurrences of calls to `iter` or `into_iter` for the /// Detect the occurrences of calls to `iter` or `into_iter` for the
/// given identifier /// given identifier
fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<IterFunction>> { fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, id: HirId) -> Option<Vec<IterFunction>> {
let mut visitor = IterFunctionVisitor { let mut visitor = IterFunctionVisitor {
uses: Vec::new(), uses: Vec::new(),
target: identifier, target: id,
seen_other: false, seen_other: false,
}; };
visitor.visit_block(block); visitor.visit_block(block);