| use super::EXPLICIT_ITER_LOOP; |
| use clippy_utils::diagnostics::span_lint_and_sugg; |
| use clippy_utils::msrvs::{self, Msrv}; |
| use clippy_utils::res::MaybeDef; |
| use clippy_utils::source::snippet_with_context; |
| use clippy_utils::sym; |
| use clippy_utils::ty::{ |
| implements_trait, implements_trait_with_env, is_copy, make_normalized_projection, |
| make_normalized_projection_with_regions, normalize_with_regions, |
| }; |
| use rustc_errors::Applicability; |
| use rustc_hir::{Expr, Mutability}; |
| use rustc_lint::LateContext; |
| use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; |
| use rustc_middle::ty::{self, EarlyBinder, Ty}; |
| |
| pub(super) fn check( |
| cx: &LateContext<'_>, |
| self_arg: &Expr<'_>, |
| call_expr: &Expr<'_>, |
| msrv: Msrv, |
| enforce_iter_loop_reborrow: bool, |
| ) { |
| let Some((adjust, ty)) = is_ref_iterable(cx, self_arg, call_expr, enforce_iter_loop_reborrow, msrv) else { |
| return; |
| }; |
| |
| if let ty::Array(_, count) = *ty.peel_refs().kind() { |
| if !ty.is_ref() { |
| if !msrv.meets(cx, msrvs::ARRAY_INTO_ITERATOR) { |
| return; |
| } |
| } else if count.try_to_target_usize(cx.tcx).is_none_or(|x| x > 32) && !msrv.meets(cx, msrvs::ARRAY_IMPL_ANY_LEN) |
| { |
| return; |
| } |
| } |
| |
| let mut applicability = Applicability::MachineApplicable; |
| let object = snippet_with_context(cx, self_arg.span, call_expr.span.ctxt(), "_", &mut applicability).0; |
| span_lint_and_sugg( |
| cx, |
| EXPLICIT_ITER_LOOP, |
| call_expr.span, |
| "it is more concise to loop over references to containers instead of using explicit \ |
| iteration methods", |
| "to write this more concisely, try", |
| format!("{}{object}", adjust.display()), |
| applicability, |
| ); |
| } |
| |
| #[derive(Clone, Copy)] |
| enum AdjustKind { |
| None, |
| Borrow, |
| BorrowMut, |
| Deref, |
| Reborrow, |
| ReborrowMut, |
| } |
| impl AdjustKind { |
| fn borrow(mutbl: Mutability) -> Self { |
| match mutbl { |
| Mutability::Not => Self::Borrow, |
| Mutability::Mut => Self::BorrowMut, |
| } |
| } |
| |
| fn auto_borrow(mutbl: AutoBorrowMutability) -> Self { |
| match mutbl { |
| AutoBorrowMutability::Not => Self::Borrow, |
| AutoBorrowMutability::Mut { .. } => Self::BorrowMut, |
| } |
| } |
| |
| fn reborrow(mutbl: Mutability) -> Self { |
| match mutbl { |
| Mutability::Not => Self::Reborrow, |
| Mutability::Mut => Self::ReborrowMut, |
| } |
| } |
| |
| fn auto_reborrow(mutbl: AutoBorrowMutability) -> Self { |
| match mutbl { |
| AutoBorrowMutability::Not => Self::Reborrow, |
| AutoBorrowMutability::Mut { .. } => Self::ReborrowMut, |
| } |
| } |
| |
| fn display(self) -> &'static str { |
| match self { |
| Self::None => "", |
| Self::Borrow => "&", |
| Self::BorrowMut => "&mut ", |
| Self::Deref => "*", |
| Self::Reborrow => "&*", |
| Self::ReborrowMut => "&mut *", |
| } |
| } |
| } |
| |
| /// Checks if an `iter` or `iter_mut` call returns `IntoIterator::IntoIter`. Returns how the |
| /// argument needs to be adjusted. |
| #[expect(clippy::too_many_lines)] |
| fn is_ref_iterable<'tcx>( |
| cx: &LateContext<'tcx>, |
| self_arg: &Expr<'_>, |
| call_expr: &Expr<'_>, |
| enforce_iter_loop_reborrow: bool, |
| msrv: Msrv, |
| ) -> Option<(AdjustKind, Ty<'tcx>)> { |
| let typeck = cx.typeck_results(); |
| if let Some(trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator) |
| && let Some(fn_id) = typeck.type_dependent_def_id(call_expr.hir_id) |
| && let sig = cx |
| .tcx |
| .liberate_late_bound_regions(fn_id, cx.tcx.fn_sig(fn_id).skip_binder()) |
| && let &[req_self_ty, req_res_ty] = &**sig.inputs_and_output |
| && let typing_env = ty::TypingEnv::non_body_analysis(cx.tcx, fn_id) |
| && implements_trait_with_env(cx.tcx, typing_env, req_self_ty, trait_id, Some(fn_id), &[]) |
| && let Some(into_iter_ty) = |
| make_normalized_projection_with_regions(cx.tcx, typing_env, trait_id, sym::IntoIter, [req_self_ty]) |
| && let req_res_ty = normalize_with_regions(cx.tcx, typing_env, req_res_ty) |
| && into_iter_ty == req_res_ty |
| { |
| let adjustments = typeck.expr_adjustments(self_arg); |
| let self_ty = typeck.expr_ty(self_arg); |
| let self_is_copy = is_copy(cx, self_ty); |
| |
| if self_ty.peel_refs().is_lang_item(cx, rustc_hir::LangItem::OwnedBox) && !msrv.meets(cx, msrvs::BOX_INTO_ITER) |
| { |
| return None; |
| } |
| |
| if adjustments.is_empty() && self_is_copy { |
| // Exact type match, already checked earlier |
| return Some((AdjustKind::None, self_ty)); |
| } |
| |
| let res_ty = cx.tcx.erase_and_anonymize_regions( |
| EarlyBinder::bind(req_res_ty).instantiate(cx.tcx, typeck.node_args(call_expr.hir_id)), |
| ); |
| let mutbl = if let ty::Ref(_, _, mutbl) = *req_self_ty.kind() { |
| Some(mutbl) |
| } else { |
| None |
| }; |
| |
| if !adjustments.is_empty() { |
| if self_is_copy { |
| // Using by value won't consume anything |
| if implements_trait(cx, self_ty, trait_id, &[]) |
| && let Some(ty) = |
| make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [self_ty]) |
| && ty == res_ty |
| { |
| return Some((AdjustKind::None, self_ty)); |
| } |
| } else if enforce_iter_loop_reborrow |
| && let ty::Ref(region, ty, Mutability::Mut) = *self_ty.kind() |
| && let Some(mutbl) = mutbl |
| { |
| // Attempt to reborrow the mutable reference |
| let self_ty = if mutbl.is_mut() { |
| self_ty |
| } else { |
| Ty::new_ref(cx.tcx, region, ty, mutbl) |
| }; |
| if implements_trait(cx, self_ty, trait_id, &[]) |
| && let Some(ty) = |
| make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [self_ty]) |
| && ty == res_ty |
| { |
| return Some((AdjustKind::reborrow(mutbl), self_ty)); |
| } |
| } |
| } |
| if let Some(mutbl) = mutbl |
| && !self_ty.is_ref() |
| { |
| // Attempt to borrow |
| let self_ty = Ty::new_ref(cx.tcx, cx.tcx.lifetimes.re_erased, self_ty, mutbl); |
| if implements_trait(cx, self_ty, trait_id, &[]) |
| && let Some(ty) = |
| make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [self_ty]) |
| && ty == res_ty |
| { |
| return Some((AdjustKind::borrow(mutbl), self_ty)); |
| } |
| } |
| |
| match adjustments { |
| [] => Some((AdjustKind::None, self_ty)), |
| &[ |
| Adjustment { |
| kind: Adjust::Deref(_), .. |
| }, |
| Adjustment { |
| kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)), |
| target, |
| }, |
| .., |
| ] => { |
| if enforce_iter_loop_reborrow |
| && target != self_ty |
| && implements_trait(cx, target, trait_id, &[]) |
| && let Some(ty) = |
| make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [target]) |
| && ty == res_ty |
| { |
| Some((AdjustKind::auto_reborrow(mutbl), target)) |
| } else { |
| None |
| } |
| }, |
| &[ |
| Adjustment { |
| kind: Adjust::Deref(_), |
| target, |
| }, |
| .., |
| ] => { |
| if is_copy(cx, target) |
| && implements_trait(cx, target, trait_id, &[]) |
| && let Some(ty) = |
| make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [target]) |
| && ty == res_ty |
| { |
| Some((AdjustKind::Deref, target)) |
| } else { |
| None |
| } |
| }, |
| &[ |
| Adjustment { |
| kind: Adjust::Borrow(AutoBorrow::Ref(mutbl)), |
| target, |
| }, |
| .., |
| ] => { |
| if self_ty.is_ref() |
| && implements_trait(cx, target, trait_id, &[]) |
| && let Some(ty) = |
| make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [target]) |
| && ty == res_ty |
| { |
| Some((AdjustKind::auto_borrow(mutbl), target)) |
| } else { |
| None |
| } |
| }, |
| _ => None, |
| } |
| } else { |
| None |
| } |
| } |