| 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 | |
| } | |
| } |