blob: 1c2b7a169fc390f0fff140a63c1b3ad2c5af7176 [file] [log] [blame]
use super::EXPLICIT_ITER_LOOP;
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
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, TypeAndMut};
use rustc_span::sym;
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) else {
return;
};
if let ty::Array(_, count) = *ty.peel_refs().kind() {
if !ty.is_ref() {
if !msrv.meets(msrvs::ARRAY_INTO_ITERATOR) {
return;
}
} else if count
.try_eval_target_usize(cx.tcx, cx.param_env)
.map_or(true, |x| x > 32)
&& !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN)
{
return;
}
}
let mut applicability = Applicability::MachineApplicable;
let object = snippet_with_applicability(cx, self_arg.span, "_", &mut applicability);
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,
) -> 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 param_env = cx.tcx.param_env(fn_id)
&& implements_trait_with_env(cx.tcx, param_env, req_self_ty, trait_id, &[])
&& let Some(into_iter_ty) =
make_normalized_projection_with_regions(cx.tcx, param_env, trait_id, sym!(IntoIter), [req_self_ty])
&& let req_res_ty = normalize_with_regions(cx.tcx, param_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 adjustments.is_empty() && self_is_copy {
// Exact type match, already checked earlier
return Some((AdjustKind::None, self_ty));
}
let res_ty = cx
.tcx
.erase_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.param_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, TypeAndMut { ty, mutbl })
};
if implements_trait(cx, self_ty, trait_id, &[])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.param_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, TypeAndMut { ty: self_ty, mutbl });
if implements_trait(cx, self_ty, trait_id, &[])
&& let Some(ty) = make_normalized_projection(cx.tcx, cx.param_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.param_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.param_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.param_env, trait_id, sym!(IntoIter), [target])
&& ty == res_ty
{
Some((AdjustKind::auto_borrow(mutbl), target))
} else {
None
}
},
_ => None,
}
} else {
None
}
}