| use std::borrow::Cow; | |
| use std::ops::ControlFlow; | |
| use super::NEEDLESS_COLLECT; | |
| use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; | |
| use clippy_utils::res::{MaybeDef, MaybeResPath, MaybeTypeckRes}; | |
| use clippy_utils::source::{snippet, snippet_with_applicability}; | |
| use clippy_utils::sugg::Sugg; | |
| use clippy_utils::ty::{has_non_owning_mutable_access, make_normalized_projection, make_projection}; | |
| use clippy_utils::{CaptureKind, can_move_expr_to_closure, fn_def_id, get_enclosing_block, higher, sym}; | |
| use rustc_data_structures::fx::FxHashMap; | |
| use rustc_errors::{Applicability, MultiSpan}; | |
| use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt}; | |
| use rustc_hir::{ | |
| BindingMode, Block, Expr, ExprKind, HirId, HirIdSet, LetStmt, Mutability, Node, Pat, PatKind, Stmt, StmtKind, | |
| }; | |
| use rustc_lint::LateContext; | |
| use rustc_middle::hir::nested_filter; | |
| use rustc_middle::ty::{self, AssocTag, ClauseKind, EarlyBinder, GenericArg, GenericArgKind, Ty}; | |
| use rustc_span::symbol::Ident; | |
| use rustc_span::{Span, Symbol}; | |
| const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; | |
| #[expect(clippy::too_many_lines)] | |
| pub(super) fn check<'tcx>( | |
| cx: &LateContext<'tcx>, | |
| name_span: Span, | |
| collect_expr: &'tcx Expr<'_>, | |
| iter_expr: &'tcx Expr<'tcx>, | |
| call_span: Span, | |
| ) { | |
| let iter_ty = cx.typeck_results().expr_ty(iter_expr); | |
| if has_non_owning_mutable_access(cx, iter_ty) { | |
| return; // don't lint if the iterator has side effects | |
| } | |
| match cx.tcx.parent_hir_node(collect_expr.hir_id) { | |
| Node::Expr(parent) => { | |
| check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr); | |
| let sugg: String; | |
| let mut app; | |
| if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind { | |
| app = Applicability::MachineApplicable; | |
| let collect_ty = cx.typeck_results().expr_ty(collect_expr); | |
| sugg = match name.ident.name { | |
| sym::len => { | |
| if let Some(adt) = collect_ty.ty_adt_def() | |
| && matches!( | |
| cx.tcx.get_diagnostic_name(adt.did()), | |
| Some(sym::Vec | sym::VecDeque | sym::LinkedList | sym::BinaryHeap) | |
| ) | |
| { | |
| "count()".into() | |
| } else { | |
| return; | |
| } | |
| }, | |
| sym::is_empty | |
| if is_is_empty_sig(cx, parent.hir_id) | |
| && iterates_same_ty(cx, cx.typeck_results().expr_ty(iter_expr), collect_ty) => | |
| { | |
| "next().is_none()".into() | |
| }, | |
| sym::contains => { | |
| if is_contains_sig(cx, parent.hir_id, iter_expr) | |
| && let Some(arg) = args.first() | |
| { | |
| let (span, prefix) = if let ExprKind::AddrOf(_, _, arg) = arg.kind { | |
| (arg.span, "") | |
| } else { | |
| (arg.span, "*") | |
| }; | |
| let snip = snippet_with_applicability(cx, span, "??", &mut app); | |
| format!("any(|x| x == {prefix}{snip})") | |
| } else { | |
| return; | |
| } | |
| }, | |
| _ => return, | |
| }; | |
| } else if let ExprKind::Index(_, index, _) = parent.kind | |
| && cx.typeck_results().expr_ty(index).is_usize() | |
| { | |
| app = Applicability::MaybeIncorrect; | |
| let snip = snippet_with_applicability(cx, index.span, "_", &mut app); | |
| sugg = format!("nth({snip}).unwrap()"); | |
| } else { | |
| return; | |
| } | |
| span_lint_and_sugg( | |
| cx, | |
| NEEDLESS_COLLECT, | |
| call_span.with_hi(parent.span.hi()), | |
| NEEDLESS_COLLECT_MSG, | |
| "replace with", | |
| sugg, | |
| app, | |
| ); | |
| }, | |
| Node::LetStmt(l) => { | |
| if let PatKind::Binding(BindingMode::NONE | BindingMode::MUT, id, _, None) = l.pat.kind | |
| && let ty = cx.typeck_results().expr_ty(collect_expr) | |
| && let Some(extra_spec) = ty.opt_diag_name(cx).and_then(ExtraFunctionSpec::new) | |
| && let iter_ty = cx.typeck_results().expr_ty(iter_expr) | |
| && let Some(block) = get_enclosing_block(cx, l.hir_id) | |
| && let Some((iter_calls, extra_calls)) = | |
| detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty), extra_spec) | |
| && let [iter_call] = &*iter_calls | |
| { | |
| let mut used_count_visitor = UsedCountVisitor { | |
| cx, | |
| id, | |
| extra_spec, | |
| count: 0, | |
| }; | |
| walk_block(&mut used_count_visitor, block); | |
| if used_count_visitor.count > 1 { | |
| return; | |
| } | |
| if let IterFunctionKind::IntoIter(hir_id) = iter_call.func | |
| && !check_iter_expr_used_only_as_iterator(cx, hir_id, block) | |
| { | |
| return; | |
| } | |
| // Suggest replacing iter_call with iter_replacement, and removing stmt | |
| let mut span = MultiSpan::from_span(name_span); | |
| span.push_span_label(iter_call.span, "the iterator could be used here instead"); | |
| span_lint_hir_and_then( | |
| cx, | |
| NEEDLESS_COLLECT, | |
| collect_expr.hir_id, | |
| span, | |
| NEEDLESS_COLLECT_MSG, | |
| |diag| { | |
| let iter_snippet = Sugg::hir(cx, iter_expr, ".."); | |
| let mut iter_replacement = iter_snippet.to_string(); | |
| for extra in &extra_calls { | |
| iter_replacement = extra.apply_iter_method(cx, &iter_replacement); | |
| } | |
| iter_replacement.push_str(&iter_call.get_iter_method(cx)); | |
| let mut remove_suggestions = vec![(l.span, String::new())]; | |
| remove_suggestions.extend( | |
| extra_calls | |
| .iter() | |
| .flat_map(|extra| extra.span().map(|s| (s, String::new()))), | |
| ); | |
| remove_suggestions.push((iter_call.span, iter_replacement)); | |
| diag.multipart_suggestion( | |
| iter_call.get_suggestion_text(), | |
| remove_suggestions, | |
| Applicability::MaybeIncorrect, | |
| ); | |
| }, | |
| ); | |
| } | |
| }, | |
| _ => (), | |
| } | |
| } | |
| /// checks for collecting into a (generic) method or function argument | |
| /// taking an `IntoIterator` | |
| fn check_collect_into_intoiterator<'tcx>( | |
| cx: &LateContext<'tcx>, | |
| parent: &'tcx Expr<'tcx>, | |
| collect_expr: &'tcx Expr<'tcx>, | |
| call_span: Span, | |
| iter_expr: &'tcx Expr<'tcx>, | |
| ) { | |
| if let Some(id) = fn_def_id(cx, parent) { | |
| let args = match parent.kind { | |
| ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) => args, | |
| _ => &[], | |
| }; | |
| // find the argument index of the `collect_expr` in the | |
| // function / method call | |
| if let Some(arg_idx) = args.iter().position(|e| e.hir_id == collect_expr.hir_id).map(|i| { | |
| if matches!(parent.kind, ExprKind::MethodCall(_, _, _, _)) { | |
| i + 1 | |
| } else { | |
| i | |
| } | |
| }) { | |
| // extract the input types of the function/method call | |
| // that contains `collect_expr` | |
| let inputs = cx | |
| .tcx | |
| .liberate_late_bound_regions(id, cx.tcx.fn_sig(id).instantiate_identity()) | |
| .inputs(); | |
| // map IntoIterator generic bounds to their signature | |
| // types and check whether the argument type is an | |
| // `IntoIterator` | |
| if cx | |
| .tcx | |
| .param_env(id) | |
| .caller_bounds() | |
| .into_iter() | |
| .filter_map(|p| { | |
| if let ClauseKind::Trait(t) = p.kind().skip_binder() | |
| && cx.tcx.is_diagnostic_item(sym::IntoIterator, t.trait_ref.def_id) | |
| { | |
| Some(t.self_ty()) | |
| } else { | |
| None | |
| } | |
| }) | |
| .any(|ty| ty == inputs[arg_idx]) | |
| { | |
| span_lint_and_sugg( | |
| cx, | |
| NEEDLESS_COLLECT, | |
| call_span.with_lo(iter_expr.span.hi()), | |
| NEEDLESS_COLLECT_MSG, | |
| "remove this call", | |
| String::new(), | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| /// Checks if the given method call matches the expected signature of `([&[mut]] self) -> bool` | |
| fn is_is_empty_sig(cx: &LateContext<'_>, call_id: HirId) -> bool { | |
| cx.typeck_results().type_dependent_def_id(call_id).is_some_and(|id| { | |
| let sig = cx.tcx.fn_sig(id).instantiate_identity().skip_binder(); | |
| sig.inputs().len() == 1 && sig.output().is_bool() | |
| }) | |
| } | |
| /// Checks if `<iter_ty as Iterator>::Item` is the same as `<collect_ty as IntoIter>::Item` | |
| fn iterates_same_ty<'tcx>(cx: &LateContext<'tcx>, iter_ty: Ty<'tcx>, collect_ty: Ty<'tcx>) -> bool { | |
| if let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator) | |
| && let Some(into_iter_trait) = cx.tcx.get_diagnostic_item(sym::IntoIterator) | |
| && let Some(iter_item_ty) = | |
| make_normalized_projection(cx.tcx, cx.typing_env(), iter_trait, sym::Item, [iter_ty]) | |
| && let Some(into_iter_item_proj) = make_projection(cx.tcx, into_iter_trait, sym::Item, [collect_ty]) | |
| && let Ok(into_iter_item_ty) = cx.tcx.try_normalize_erasing_regions( | |
| cx.typing_env(), | |
| Ty::new_projection_from_args(cx.tcx, into_iter_item_proj.kind.def_id(), into_iter_item_proj.args), | |
| ) | |
| { | |
| iter_item_ty == into_iter_item_ty | |
| } else { | |
| false | |
| } | |
| } | |
| /// Checks if the given method call matches the expected signature of | |
| /// `([&[mut]] self, &<iter_ty as Iterator>::Item) -> bool` | |
| fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) -> bool { | |
| let typeck = cx.typeck_results(); | |
| if let Some(id) = typeck.type_dependent_def_id(call_id) | |
| && let sig = cx.tcx.fn_sig(id).instantiate_identity() | |
| && sig.skip_binder().output().is_bool() | |
| && let [_, search_ty] = *sig.skip_binder().inputs() | |
| && let ty::Ref(_, search_ty, Mutability::Not) = *cx | |
| .tcx | |
| .instantiate_bound_regions_with_erased(sig.rebind(search_ty)) | |
| .kind() | |
| && let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator) | |
| && let Some(iter_item) = cx.tcx.associated_items(iter_trait).find_by_ident_and_kind( | |
| cx.tcx, | |
| Ident::with_dummy_span(sym::Item), | |
| AssocTag::Type, | |
| iter_trait, | |
| ) | |
| && let args = cx.tcx.mk_args(&[GenericArg::from(typeck.expr_ty_adjusted(iter_expr))]) | |
| && let proj_ty = Ty::new_projection_from_args(cx.tcx, iter_item.def_id, args) | |
| && let Ok(item_ty) = cx.tcx.try_normalize_erasing_regions(cx.typing_env(), proj_ty) | |
| { | |
| item_ty == EarlyBinder::bind(search_ty).instantiate(cx.tcx, cx.typeck_results().node_args(call_id)) | |
| } else { | |
| false | |
| } | |
| } | |
| 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(HirId), | |
| Len, | |
| IsEmpty, | |
| Contains(Span), | |
| } | |
| struct ExtraFunctionSpan { | |
| /// Span of the function call | |
| func_span: Span, | |
| /// Span of the argument | |
| arg_span: Span, | |
| } | |
| enum ExtraFunction { | |
| Push { | |
| back: Vec<ExtraFunctionSpan>, | |
| front: Vec<ExtraFunctionSpan>, | |
| }, | |
| Extend(ExtraFunctionSpan), | |
| } | |
| impl ExtraFunction { | |
| fn apply_iter_method(&self, cx: &LateContext<'_>, inner: &str) -> String { | |
| match &self { | |
| ExtraFunction::Push { back, front } => { | |
| let back_sugg = back | |
| .iter() | |
| .map(|span| snippet(cx, span.arg_span, "..")) | |
| .intersperse(Cow::Borrowed(", ")) | |
| .collect::<String>(); | |
| let front = front | |
| .iter() | |
| .map(|span| snippet(cx, span.arg_span, "..")) | |
| .intersperse(Cow::Borrowed(", ")) | |
| .collect::<String>(); | |
| match (front.is_empty(), back_sugg.is_empty()) { | |
| (true, true) => inner.to_string(), | |
| (true, false) => format!("{inner}.chain([{back_sugg}])"), | |
| (false, true) => format!("[{front}].into_iter().chain({inner})"), | |
| (false, false) => format!("[{front}].into_iter().chain({inner}).chain([{back_sugg}])"), | |
| } | |
| }, | |
| ExtraFunction::Extend(span) => { | |
| let s = snippet(cx, span.arg_span, ".."); | |
| format!("{inner}.chain({s})") | |
| }, | |
| } | |
| } | |
| fn span(&self) -> Box<dyn Iterator<Item = Span> + '_> { | |
| match &self { | |
| ExtraFunction::Push { back, front } => Box::new( | |
| back.iter() | |
| .map(|s| s.func_span) | |
| .chain(front.iter().map(|s| s.func_span)), | |
| ), | |
| ExtraFunction::Extend(span) => Box::new(std::iter::once(span.func_span)), | |
| } | |
| } | |
| } | |
| #[derive(Clone, Copy)] | |
| struct ExtraFunctionPushSpec { | |
| back: Option<Symbol>, | |
| front: Option<Symbol>, | |
| } | |
| #[derive(Clone, Copy)] | |
| struct ExtraFunctionSpec { | |
| push_symbol: ExtraFunctionPushSpec, | |
| extend_symbol: Option<Symbol>, | |
| } | |
| impl ExtraFunctionSpec { | |
| fn new(target: Symbol) -> Option<Self> { | |
| match target { | |
| sym::Vec => Some(ExtraFunctionSpec { | |
| push_symbol: ExtraFunctionPushSpec { | |
| back: Some(sym::push), | |
| front: None, | |
| }, | |
| extend_symbol: Some(sym::extend), | |
| }), | |
| sym::VecDeque | sym::LinkedList => Some(ExtraFunctionSpec { | |
| push_symbol: ExtraFunctionPushSpec { | |
| back: Some(sym::push_back), | |
| front: Some(sym::push_front), | |
| }, | |
| extend_symbol: Some(sym::extend), | |
| }), | |
| sym::BinaryHeap => Some(ExtraFunctionSpec { | |
| push_symbol: ExtraFunctionPushSpec { | |
| back: None, | |
| front: None, | |
| }, | |
| extend_symbol: None, | |
| }), | |
| _ => None, | |
| } | |
| } | |
| fn is_extra_function(self, name: Symbol) -> bool { | |
| self.push_symbol.back == Some(name) || self.push_symbol.front == Some(name) || self.extend_symbol == Some(name) | |
| } | |
| } | |
| struct IterFunctionVisitor<'a, 'tcx> { | |
| illegal_mutable_capture_ids: HirIdSet, | |
| current_mutably_captured_ids: HirIdSet, | |
| cx: &'a LateContext<'tcx>, | |
| uses: Vec<Option<IterFunction>>, | |
| extras: Vec<ExtraFunction>, | |
| extra_spec: ExtraFunctionSpec, | |
| hir_id_uses_map: FxHashMap<HirId, usize>, | |
| current_statement_hir_id: Option<HirId>, | |
| seen_other: bool, | |
| target: HirId, | |
| } | |
| impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { | |
| fn visit_block(&mut self, block: &'tcx Block<'tcx>) { | |
| for (expr, hir_id) in block.stmts.iter().filter_map(get_expr_and_hir_id_from_stmt) { | |
| if check_loop_kind(expr).is_some() { | |
| continue; | |
| } | |
| self.visit_block_expr(expr, hir_id); | |
| } | |
| if let Some(expr) = block.expr { | |
| if let Some(loop_kind) = check_loop_kind(expr) { | |
| if let LoopKind::Conditional(block_expr) = loop_kind { | |
| self.visit_block_expr(block_expr, None); | |
| } | |
| } else { | |
| self.visit_block_expr(expr, None); | |
| } | |
| } | |
| } | |
| #[expect(clippy::too_many_lines)] | |
| fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { | |
| // Check function calls on our collection | |
| if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind { | |
| if args.is_empty() | |
| && method_name.ident.name == sym::collect | |
| && self | |
| .cx | |
| .ty_based_def(expr) | |
| .opt_parent(self.cx) | |
| .is_diag_item(self.cx, sym::Iterator) | |
| { | |
| self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv)); | |
| self.visit_expr(recv); | |
| return; | |
| } | |
| if recv.res_local_id() == Some(self.target) { | |
| if self | |
| .illegal_mutable_capture_ids | |
| .intersection(&self.current_mutably_captured_ids) | |
| .next() | |
| .is_none() | |
| { | |
| if let Some(hir_id) = self.current_statement_hir_id { | |
| self.hir_id_uses_map.insert(hir_id, self.uses.len()); | |
| } | |
| match method_name.ident.name { | |
| sym::into_iter => self.uses.push(Some(IterFunction { | |
| func: IterFunctionKind::IntoIter(expr.hir_id), | |
| span: expr.span, | |
| })), | |
| sym::len => self.uses.push(Some(IterFunction { | |
| func: IterFunctionKind::Len, | |
| span: expr.span, | |
| })), | |
| sym::is_empty => self.uses.push(Some(IterFunction { | |
| func: IterFunctionKind::IsEmpty, | |
| span: expr.span, | |
| })), | |
| sym::contains => self.uses.push(Some(IterFunction { | |
| func: IterFunctionKind::Contains(args[0].span), | |
| span: expr.span, | |
| })), | |
| name if let is_push_back = self.extra_spec.push_symbol.back.is_some_and(|sym| name == sym) | |
| && (is_push_back || self.extra_spec.push_symbol.front.is_some_and(|sym| name == sym)) | |
| && self.uses.is_empty() => | |
| { | |
| let span = get_span_of_expr_or_parent_stmt(self.cx, expr); | |
| match self.extras.last_mut() { | |
| Some(ExtraFunction::Push { back, .. }) if is_push_back => { | |
| back.push(ExtraFunctionSpan { | |
| func_span: span, | |
| arg_span: args[0].span, | |
| }); | |
| }, | |
| Some(ExtraFunction::Push { front, .. }) => { | |
| front.push(ExtraFunctionSpan { | |
| func_span: span, | |
| arg_span: args[0].span, | |
| }); | |
| }, | |
| _ if is_push_back => { | |
| self.extras.push(ExtraFunction::Push { | |
| back: vec![ExtraFunctionSpan { | |
| func_span: span, | |
| arg_span: args[0].span, | |
| }], | |
| front: Vec::new(), | |
| }); | |
| }, | |
| _ => { | |
| self.extras.push(ExtraFunction::Push { | |
| back: Vec::new(), | |
| front: vec![ExtraFunctionSpan { | |
| func_span: span, | |
| arg_span: args[0].span, | |
| }], | |
| }); | |
| }, | |
| } | |
| }, | |
| name if self.extra_spec.extend_symbol.is_some_and(|sym| name == sym) | |
| && self.uses.is_empty() => | |
| { | |
| let span = get_span_of_expr_or_parent_stmt(self.cx, expr); | |
| self.extras.push(ExtraFunction::Extend(ExtraFunctionSpan { | |
| func_span: span, | |
| arg_span: args[0].span, | |
| })); | |
| }, | |
| _ => { | |
| self.seen_other = true; | |
| if let Some(hir_id) = self.current_statement_hir_id { | |
| self.hir_id_uses_map.remove(&hir_id); | |
| } | |
| }, | |
| } | |
| } | |
| return; | |
| } | |
| if let Some(hir_id) = recv.res_local_id() | |
| && let Some(index) = self.hir_id_uses_map.remove(&hir_id) | |
| { | |
| if self | |
| .illegal_mutable_capture_ids | |
| .intersection(&self.current_mutably_captured_ids) | |
| .next() | |
| .is_none() | |
| { | |
| if let Some(hir_id) = self.current_statement_hir_id { | |
| self.hir_id_uses_map.insert(hir_id, index); | |
| } | |
| } else { | |
| self.uses[index] = None; | |
| } | |
| } | |
| } | |
| // Check if the collection is used for anything else | |
| if expr.res_local_id() == Some(self.target) { | |
| self.seen_other = true; | |
| } else { | |
| walk_expr(self, expr); | |
| } | |
| } | |
| } | |
| /// If parent of the `expr` is a statement, return the span of the statement, otherwise return the | |
| /// span of the expression. | |
| fn get_span_of_expr_or_parent_stmt<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Span { | |
| if let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id) { | |
| stmt.span | |
| } else { | |
| expr.span | |
| } | |
| } | |
| enum LoopKind<'tcx> { | |
| Conditional(&'tcx Expr<'tcx>), | |
| Loop, | |
| } | |
| fn check_loop_kind<'tcx>(expr: &Expr<'tcx>) -> Option<LoopKind<'tcx>> { | |
| if let Some(higher::WhileLet { let_expr, .. }) = higher::WhileLet::hir(expr) { | |
| return Some(LoopKind::Conditional(let_expr)); | |
| } | |
| if let Some(higher::While { condition, .. }) = higher::While::hir(expr) { | |
| return Some(LoopKind::Conditional(condition)); | |
| } | |
| if let Some(higher::ForLoop { arg, .. }) = higher::ForLoop::hir(expr) { | |
| return Some(LoopKind::Conditional(arg)); | |
| } | |
| if let ExprKind::Loop { .. } = expr.kind { | |
| return Some(LoopKind::Loop); | |
| } | |
| None | |
| } | |
| impl<'tcx> IterFunctionVisitor<'_, 'tcx> { | |
| fn visit_block_expr(&mut self, expr: &'tcx Expr<'tcx>, hir_id: Option<HirId>) { | |
| self.current_statement_hir_id = hir_id; | |
| self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(expr)); | |
| self.visit_expr(expr); | |
| } | |
| } | |
| fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v>, Option<HirId>)> { | |
| match stmt.kind { | |
| StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some((expr, None)), | |
| StmtKind::Item(..) => None, | |
| StmtKind::Let(LetStmt { init, pat, .. }) => { | |
| if let PatKind::Binding(_, hir_id, ..) = pat.kind { | |
| init.map(|init_expr| (init_expr, Some(hir_id))) | |
| } else { | |
| init.map(|init_expr| (init_expr, None)) | |
| } | |
| }, | |
| } | |
| } | |
| struct UsedCountVisitor<'a, 'tcx> { | |
| cx: &'a LateContext<'tcx>, | |
| id: HirId, | |
| extra_spec: ExtraFunctionSpec, | |
| count: usize, | |
| } | |
| impl<'tcx> Visitor<'tcx> for UsedCountVisitor<'_, 'tcx> { | |
| type NestedFilter = nested_filter::OnlyBodies; | |
| fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { | |
| if expr.res_local_id() != Some(self.id) { | |
| walk_expr(self, expr); | |
| return; | |
| } | |
| let parent = self.cx.tcx.parent_hir_node(expr.hir_id); | |
| if let Node::Expr(expr) = parent | |
| && let ExprKind::MethodCall(method_name, _, _, _) = &expr.kind | |
| && self.extra_spec.is_extra_function(method_name.ident.name) | |
| { | |
| return; | |
| } | |
| self.count += 1; | |
| } | |
| fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { | |
| self.cx.tcx | |
| } | |
| } | |
| /// Detect the occurrences of calls to `iter` or `into_iter` for the | |
| /// given identifier | |
| fn detect_iter_and_into_iters<'tcx: 'a, 'a>( | |
| block: &'tcx Block<'tcx>, | |
| id: HirId, | |
| cx: &'a LateContext<'tcx>, | |
| captured_ids: HirIdSet, | |
| extra_spec: ExtraFunctionSpec, | |
| ) -> Option<(Vec<IterFunction>, Vec<ExtraFunction>)> { | |
| let mut visitor = IterFunctionVisitor { | |
| illegal_mutable_capture_ids: captured_ids, | |
| current_mutably_captured_ids: HirIdSet::default(), | |
| cx, | |
| uses: Vec::new(), | |
| extras: Vec::new(), | |
| extra_spec, | |
| hir_id_uses_map: FxHashMap::default(), | |
| current_statement_hir_id: None, | |
| seen_other: false, | |
| target: id, | |
| }; | |
| visitor.visit_block(block); | |
| if visitor.seen_other { | |
| None | |
| } else { | |
| Some((visitor.uses.into_iter().flatten().collect(), visitor.extras)) | |
| } | |
| } | |
| fn get_captured_ids(cx: &LateContext<'_>, ty: Ty<'_>) -> HirIdSet { | |
| fn get_captured_ids_recursive(cx: &LateContext<'_>, ty: Ty<'_>, set: &mut HirIdSet) { | |
| match ty.kind() { | |
| ty::Adt(_, generics) => { | |
| for generic in *generics { | |
| if let GenericArgKind::Type(ty) = generic.kind() { | |
| get_captured_ids_recursive(cx, ty, set); | |
| } | |
| } | |
| }, | |
| ty::Closure(def_id, _) => { | |
| let closure_hir_node = cx.tcx.hir_get_if_local(*def_id).unwrap(); | |
| if let Node::Expr(closure_expr) = closure_hir_node { | |
| can_move_expr_to_closure(cx, closure_expr) | |
| .unwrap() | |
| .into_iter() | |
| .for_each(|(hir_id, capture_kind)| { | |
| if matches!(capture_kind, CaptureKind::Ref(Mutability::Mut)) { | |
| set.insert(hir_id); | |
| } | |
| }); | |
| } | |
| }, | |
| _ => (), | |
| } | |
| } | |
| let mut set = HirIdSet::default(); | |
| get_captured_ids_recursive(cx, ty, &mut set); | |
| set | |
| } | |
| struct IteratorMethodCheckVisitor<'a, 'tcx> { | |
| cx: &'a LateContext<'tcx>, | |
| hir_id_of_expr: HirId, | |
| hir_id_of_let_binding: Option<HirId>, | |
| } | |
| impl<'tcx> Visitor<'tcx> for IteratorMethodCheckVisitor<'_, 'tcx> { | |
| type Result = ControlFlow<()>; | |
| fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> ControlFlow<()> { | |
| if let ExprKind::MethodCall(_method_name, recv, _args, _) = &expr.kind | |
| && (recv.hir_id == self.hir_id_of_expr | |
| || self | |
| .hir_id_of_let_binding | |
| .is_some_and(|hid| recv.res_local_id() == Some(hid))) | |
| && !self | |
| .cx | |
| .ty_based_def(expr) | |
| .opt_parent(self.cx) | |
| .is_diag_item(self.cx, sym::Iterator) | |
| { | |
| return ControlFlow::Break(()); | |
| } else if let ExprKind::Assign(place, value, _span) = &expr.kind | |
| && value.hir_id == self.hir_id_of_expr | |
| && let Some(id) = place.res_local_id() | |
| { | |
| // our iterator was directly assigned to a variable | |
| self.hir_id_of_let_binding = Some(id); | |
| } | |
| walk_expr(self, expr) | |
| } | |
| fn visit_stmt(&mut self, stmt: &'tcx Stmt<'tcx>) -> ControlFlow<()> { | |
| if let StmtKind::Let(LetStmt { | |
| init: Some(expr), | |
| pat: | |
| Pat { | |
| kind: PatKind::Binding(BindingMode::NONE | BindingMode::MUT, id, _, None), | |
| .. | |
| }, | |
| .. | |
| }) = &stmt.kind | |
| && expr.hir_id == self.hir_id_of_expr | |
| { | |
| // our iterator was directly assigned to a variable | |
| self.hir_id_of_let_binding = Some(*id); | |
| } | |
| walk_stmt(self, stmt) | |
| } | |
| } | |
| fn check_iter_expr_used_only_as_iterator<'tcx>( | |
| cx: &LateContext<'tcx>, | |
| hir_id_of_expr: HirId, | |
| block: &'tcx Block<'tcx>, | |
| ) -> bool { | |
| let mut visitor = IteratorMethodCheckVisitor { | |
| cx, | |
| hir_id_of_expr, | |
| hir_id_of_let_binding: None, | |
| }; | |
| visitor.visit_block(block).is_continue() | |
| } |