Skip to content

Commit

Permalink
Fix explicit_into_iter_loop with mutable references
Browse files Browse the repository at this point in the history
  • Loading branch information
Jarcho committed Feb 27, 2023
1 parent d7498cc commit edd8360
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 77 deletions.
69 changes: 65 additions & 4 deletions clippy_lints/src/loops/explicit_into_iter_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,76 @@ use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_span::symbol::sym;

#[derive(Clone, Copy)]
enum AdjustKind {
None,
Borrow,
BorrowMut,
Reborrow,
ReborrowMut,
}
impl AdjustKind {
fn borrow(mutbl: AutoBorrowMutability) -> Self {
match mutbl {
AutoBorrowMutability::Not => Self::Borrow,
AutoBorrowMutability::Mut { .. } => Self::BorrowMut,
}
}

fn 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::Reborrow => "&*",
Self::ReborrowMut => "&mut *",
}
}
}

pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<'_>) {
let self_ty = cx.typeck_results().expr_ty(self_arg);
let self_ty_adjusted = cx.typeck_results().expr_ty_adjusted(self_arg);
if !(self_ty == self_ty_adjusted && is_trait_method(cx, call_expr, sym::IntoIterator)) {
if !is_trait_method(cx, call_expr, sym::IntoIterator) {
return;
}

let typeck = cx.typeck_results();
let self_ty = typeck.expr_ty(self_arg);
let adjust = match typeck.expr_adjustments(self_arg) {
[] => AdjustKind::None,
&[
Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(_, mutbl)),
..
},
] => AdjustKind::borrow(mutbl),
&[
Adjustment {
kind: Adjust::Deref(_), ..
},
Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(_, mutbl)),
target,
},
] => {
if self_ty == target && matches!(mutbl, AutoBorrowMutability::Not) {
AdjustKind::None
} else {
AdjustKind::reborrow(mutbl)
}
},
_ => return,
};

let mut applicability = Applicability::MachineApplicable;
let object = snippet_with_applicability(cx, self_arg.span, "_", &mut applicability);
span_lint_and_sugg(
Expand All @@ -23,7 +84,7 @@ pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<
"it is more concise to loop over containers instead of using explicit \
iteration methods",
"to write this more concisely, try",
object.to_string(),
format!("{}{}", adjust.display(), object.to_string()),
applicability,
);
}
93 changes: 47 additions & 46 deletions clippy_lints/src/loops/explicit_iter_loop.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,39 @@
use super::EXPLICIT_ITER_LOOP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_trait_method;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{implements_trait_with_env, make_normalized_projection_with_regions, normalize_with_regions,
make_normalized_projection, implements_trait, is_copy};
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, TypeAndMut, Ty};
use rustc_middle::ty::{self, EarlyBinder, Ty, TypeAndMut};
use rustc_span::sym;

pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<'_>, method_name: &str, msrv: &Msrv) {
let borrow_kind = match method_name {
"iter" | "iter_mut" => match is_ref_iterable(cx, self_arg, call_expr) {
Some((kind, ty)) => {
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
}
}
kind
},
None => return,
},
"into_iter" if is_trait_method(cx, call_expr, sym::IntoIterator) => {
let receiver_ty = cx.typeck_results().expr_ty(self_arg);
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(self_arg);
let ref_receiver_ty = cx.tcx.mk_ref(
cx.tcx.lifetimes.re_erased,
ty::TypeAndMut {
ty: receiver_ty,
mutbl: Mutability::Not,
},
);
if receiver_ty_adjusted == ref_receiver_ty {
AdjustKind::None
} else {
pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<'_>, msrv: &Msrv) {
let Some((adjust, ty)) = is_ref_iterable(cx, self_arg, call_expr) else {
return;
};
if let ty::Array(_, count) = *ty.peel_refs().kind() {
if !ty.is_ref() {
if !msrv.meets(msrvs::ARRAY_INTO_ITERATOR) {
return;
}
},
_ => 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);
let prefix = match borrow_kind {
let prefix = match adjust {
AdjustKind::None => "",
AdjustKind::Borrow => "&",
AdjustKind::BorrowMut => "&mut ",
Expand Down Expand Up @@ -105,7 +86,11 @@ impl AdjustKind {

/// Checks if an `iter` or `iter_mut` call returns `IntoIterator::IntoIter`. Returns how the
/// argument needs to be adjusted.
fn is_ref_iterable<'tcx>(cx: &LateContext<'tcx>, self_arg: &Expr<'_>, call_expr: &Expr<'_>) -> Option<(AdjustKind, Ty<'tcx>)> {
fn is_ref_iterable<'tcx>(
cx: &LateContext<'tcx>,
self_arg: &Expr<'_>,
call_expr: &Expr<'_>,
) -> 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)
Expand Down Expand Up @@ -158,10 +143,18 @@ fn is_ref_iterable<'tcx>(cx: &LateContext<'tcx>, self_arg: &Expr<'_>, call_expr:

match adjustments {
[] => Some((AdjustKind::None, self_ty)),
&[Adjustment { kind: Adjust::Deref(_), ..}, Adjustment { kind: Adjust::Borrow(AutoBorrow::Ref(_, mutbl)), target }, ..] => {
&[
Adjustment { kind: Adjust::Deref(_), ..},
Adjustment {
kind: Adjust::Borrow(AutoBorrow::Ref(_, mutbl)),
target,
},
..
] => {
if 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])
&& let Some(ty) =
make_normalized_projection(cx.tcx, cx.param_env, trait_id, sym!(IntoIter), [target])
&& ty == res_ty
{
Some((AdjustKind::reborrow(mutbl), target))
Expand All @@ -172,18 +165,26 @@ fn is_ref_iterable<'tcx>(cx: &LateContext<'tcx>, self_arg: &Expr<'_>, call_expr:
&[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])
&& 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 }, ..] => {
&[
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])
&& 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))
Expand Down
7 changes: 2 additions & 5 deletions clippy_lints/src/loops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -682,14 +682,11 @@ impl Loops {

fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
if let ExprKind::MethodCall(method, self_arg, [], _) = arg.kind {
let method_name = method.ident.as_str();
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
match method_name {
match method.ident.as_str() {
"iter" | "iter_mut" => {
explicit_iter_loop::check(cx, self_arg, arg, method_name, &self.msrv);
explicit_iter_loop::check(cx, self_arg, arg, &self.msrv);
},
"into_iter" => {
explicit_iter_loop::check(cx, self_arg, arg, method_name, &self.msrv);
explicit_into_iter_loop::check(cx, self_arg, arg);
},
"next" => {
Expand Down
34 changes: 24 additions & 10 deletions tests/ui/explicit_into_iter_loop.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,47 @@ fn main() {
where
for<'a> &'a T: IntoIterator<Item = &'a String>,
{
for i in iterator {
println!("{}", i);
}
for _ in iterator {}
}

struct T;
impl IntoIterator for &T {
type Item = ();
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
vec![].into_iter()
unimplemented!()
}
}

let t = T;
let r = &t;
let rr = &&t;

// This case is handled by `explicit_iter_loop`. No idea why.
for _ in t.into_iter() {}
let mut t = T;
for _ in &t {}

let r = &t;
for _ in r {}

// No suggestion for this.
// We'd have to suggest `for _ in *rr {}` which is less clear.
let rr = &&t;
for _ in rr.into_iter() {}

let mr = &mut t;
for _ in &*mr {}

struct U;
impl IntoIterator for &mut U {
type Item = ();
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}

let mut u = U;
for _ in &mut u {}

let mr = &mut u;
for _ in &mut *mr {}

// Issue #6900
struct S;
impl S {
Expand Down
32 changes: 23 additions & 9 deletions tests/ui/explicit_into_iter_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,47 @@ fn main() {
where
for<'a> &'a T: IntoIterator<Item = &'a String>,
{
for i in iterator.into_iter() {
println!("{}", i);
}
for _ in iterator.into_iter() {}
}

struct T;
impl IntoIterator for &T {
type Item = ();
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
vec![].into_iter()
unimplemented!()
}
}

let t = T;
let r = &t;
let rr = &&t;

// This case is handled by `explicit_iter_loop`. No idea why.
let mut t = T;
for _ in t.into_iter() {}

let r = &t;
for _ in r.into_iter() {}

// No suggestion for this.
// We'd have to suggest `for _ in *rr {}` which is less clear.
let rr = &&t;
for _ in rr.into_iter() {}

let mr = &mut t;
for _ in mr.into_iter() {}

struct U;
impl IntoIterator for &mut U {
type Item = ();
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}

let mut u = U;
for _ in u.into_iter() {}

let mr = &mut u;
for _ in mr.into_iter() {}

// Issue #6900
struct S;
impl S {
Expand Down
Loading

0 comments on commit edd8360

Please sign in to comment.