Extend explicit_iter_loop to all types

This commit is contained in:
Jason Newcomb 2023-02-26 23:16:27 -05:00
parent 476efe92e7
commit b6fa4d43d3
8 changed files with 460 additions and 86 deletions

View file

@ -679,7 +679,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
});
store.register_late_pass(|_| Box::<shadow::Shadow>::default());
store.register_late_pass(|_| Box::new(unit_types::UnitTypes));
store.register_late_pass(|_| Box::new(loops::Loops));
store.register_late_pass(move |_| Box::new(loops::Loops::new(msrv())));
store.register_late_pass(|_| Box::<main_recursion::MainRecursion>::default());
store.register_late_pass(|_| Box::new(lifetimes::Lifetimes));
store.register_late_pass(|_| Box::new(entry::HashMapPass));

View file

@ -1,18 +1,37 @@
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::is_type_diagnostic_item;
use clippy_utils::ty::{implements_trait_with_env, make_normalized_projection_with_regions, normalize_with_regions,
make_normalized_projection, implements_trait, is_copy};
use rustc_errors::Applicability;
use rustc_hir::{Expr, Mutability};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
use rustc_middle::ty::{self, EarlyBinder, TypeAndMut, Ty};
use rustc_span::sym;
pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, method_name: &str) {
let should_lint = match method_name {
"iter" | "iter_mut" => is_ref_iterable_type(cx, self_arg),
"into_iter" if is_trait_method(cx, arg, sym::IntoIterator) => {
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(
@ -22,54 +41,159 @@ pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, m
mutbl: Mutability::Not,
},
);
receiver_ty_adjusted == ref_receiver_ty
},
_ => false,
};
if !should_lint {
if receiver_ty_adjusted == ref_receiver_ty {
AdjustKind::None
} else {
return;
}
},
_ => return,
};
let mut applicability = Applicability::MachineApplicable;
let object = snippet_with_applicability(cx, self_arg.span, "_", &mut applicability);
let muta = if method_name == "iter_mut" { "mut " } else { "" };
let prefix = match borrow_kind {
AdjustKind::None => "",
AdjustKind::Borrow => "&",
AdjustKind::BorrowMut => "&mut ",
AdjustKind::Deref => "*",
AdjustKind::Reborrow => "&*",
AdjustKind::ReborrowMut => "&mut *",
};
span_lint_and_sugg(
cx,
EXPLICIT_ITER_LOOP,
arg.span,
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!("&{muta}{object}"),
format!("{prefix}{object}"),
applicability,
);
}
/// Returns `true` if the type of expr is one that provides `IntoIterator` impls
/// for `&T` and `&mut T`, such as `Vec`.
#[rustfmt::skip]
fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
// no walk_ptrs_ty: calling iter() on a reference can make sense because it
// will allow further borrows afterwards
let ty = cx.typeck_results().expr_ty(e);
is_iterable_array(ty, cx) ||
is_type_diagnostic_item(cx, ty, sym::Vec) ||
is_type_diagnostic_item(cx, ty, sym::LinkedList) ||
is_type_diagnostic_item(cx, ty, sym::HashMap) ||
is_type_diagnostic_item(cx, ty, sym::HashSet) ||
is_type_diagnostic_item(cx, ty, sym::VecDeque) ||
is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
is_type_diagnostic_item(cx, ty, sym::BTreeMap) ||
is_type_diagnostic_item(cx, ty, sym::BTreeSet)
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 is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool {
// IntoIterator is currently only implemented for array sizes <= 32 in rustc
match ty.kind() {
ty::Array(_, n) => n
.try_eval_target_usize(cx.tcx, cx.param_env)
.map_or(false, |val| (0..=32).contains(&val)),
_ => false,
fn auto_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,
}
}
}
/// 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>)> {
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 {
return Some((AdjustKind::None, self_ty));
}
let res_ty = cx.tcx.erase_regions(EarlyBinder::bind(req_res_ty).subst(cx.tcx, typeck.node_substs(call_expr.hir_id)));
if !adjustments.is_empty() && self_is_copy {
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));
}
}
let mutbl = if let ty::Ref(_, _, mutbl) = *req_self_ty.kind() {
Some(mutbl)
} else {
None
};
if let Some(mutbl) = mutbl
&& !self_ty.is_ref()
{
let self_ty = cx.tcx.mk_ref(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 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::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
}
}

View file

@ -20,9 +20,10 @@ mod while_let_loop;
mod while_let_on_iterator;
use clippy_utils::higher;
use clippy_utils::msrvs::Msrv;
use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
use utils::{make_iterator_snippet, IncrementVisitor, InitializeVisitor};
@ -606,7 +607,15 @@ declare_clippy_lint! {
"checking for emptiness of a `Vec` in the loop condition and popping an element in the body"
}
declare_lint_pass!(Loops => [
pub struct Loops {
msrv: Msrv,
}
impl Loops {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
impl_lint_pass!(Loops => [
MANUAL_MEMCPY,
MANUAL_FLATTEN,
NEEDLESS_RANGE_LOOP,
@ -645,7 +654,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
if body.span.from_expansion() {
return;
}
check_for_loop(cx, pat, arg, body, expr, span);
self.check_for_loop(cx, pat, arg, body, expr, span);
if let ExprKind::Block(block, _) = body.kind {
never_loop::check(cx, block, loop_id, span, for_loop.as_ref());
}
@ -680,38 +689,40 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
}
}
fn check_for_loop<'tcx>(
impl Loops {
fn check_for_loop<'tcx>(
&self,
cx: &LateContext<'tcx>,
pat: &'tcx Pat<'_>,
arg: &'tcx Expr<'_>,
body: &'tcx Expr<'_>,
expr: &'tcx Expr<'_>,
span: Span,
) {
) {
let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr);
if !is_manual_memcpy_triggered {
needless_range_loop::check(cx, pat, arg, body, expr);
explicit_counter_loop::check(cx, pat, arg, body, expr);
}
check_for_loop_arg(cx, pat, arg);
self.check_for_loop_arg(cx, pat, arg);
for_kv_map::check(cx, pat, arg, body);
mut_range_bound::check(cx, arg, body);
single_element_loop::check(cx, pat, arg, body, expr);
same_item_push::check(cx, pat, arg, body, expr);
manual_flatten::check(cx, pat, arg, body, span);
manual_find::check(cx, pat, arg, body, span, expr);
}
}
fn check_for_loop_arg(cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
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 {
"iter" | "iter_mut" => {
explicit_iter_loop::check(cx, self_arg, arg, method_name);
explicit_iter_loop::check(cx, self_arg, arg, method_name, &self.msrv);
},
"into_iter" => {
explicit_iter_loop::check(cx, self_arg, arg, method_name);
explicit_iter_loop::check(cx, self_arg, arg, method_name, &self.msrv);
explicit_into_iter_loop::check(cx, self_arg, arg);
},
"next" => {
@ -720,4 +731,5 @@ fn check_for_loop_arg(cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
_ => {},
}
}
}
}

View file

@ -28,7 +28,7 @@ msrv_aliases! {
1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS }
1,50,0 { BOOL_THEN, CLAMP }
1,47,0 { TAU, IS_ASCII_DIGIT_CONST }
1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN }
1,46,0 { CONST_IF_MATCH }
1,45,0 { STR_STRIP_PREFIX }
1,43,0 { LOG2_10, LOG10_2 }

View file

@ -1180,3 +1180,51 @@ pub fn is_interior_mut_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
_ => false,
}
}
pub fn make_normalized_projection_with_regions<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
container_id: DefId,
assoc_ty: Symbol,
substs: impl IntoIterator<Item = impl Into<GenericArg<'tcx>>>,
) -> Option<Ty<'tcx>> {
fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option<Ty<'tcx>> {
#[cfg(debug_assertions)]
if let Some((i, subst)) = ty
.substs
.iter()
.enumerate()
.find(|(_, subst)| subst.has_late_bound_regions())
{
debug_assert!(
false,
"substs contain late-bound region at index `{i}` which can't be normalized.\n\
use `TyCtxt::erase_late_bound_regions`\n\
note: subst is `{subst:#?}`",
);
return None;
}
let cause = rustc_middle::traits::ObligationCause::dummy();
match tcx
.infer_ctxt()
.build()
.at(&cause, param_env)
.query_normalize(tcx.mk_projection(ty.def_id, ty.substs))
{
Ok(ty) => Some(ty.value),
Err(e) => {
debug_assert!(false, "failed to normalize type `{ty}`: {e:#?}");
None
},
}
}
helper(tcx, param_env, make_projection(tcx, container_id, assoc_ty, substs)?)
}
pub fn normalize_with_regions<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
let cause = rustc_middle::traits::ObligationCause::dummy();
match tcx.infer_ctxt().build().at(&cause, param_env).query_normalize(ty) {
Ok(ty) => ty.value,
Err(_) => ty,
}
}

View file

@ -1,6 +1,6 @@
//@run-rustfix
#![allow(dead_code, unused)]
#![allow(clippy::uninlined_format_args, clippy::useless_vec)]
#![allow(clippy::uninlined_format_args, clippy::useless_vec, clippy::deref_addrof)]
use std::collections::*;
@ -52,11 +52,11 @@ fn main() {
for _v in &[1, 2, 3] {}
for _v in (&mut [1, 2, 3]).iter() {} // no error
for _v in &*(&mut [1, 2, 3]) {} // no error
for _v in &[0; 32] {}
for _v in [0; 33].iter() {} // no error
for _v in &[0; 33] {} // no error
let ll: LinkedList<()> = LinkedList::new();
for _v in &ll {}
@ -272,7 +272,7 @@ mod issue_4958 {
let rr = &&t;
// This case is handled by `explicit_iter_loop`. No idea why.
for _ in &t {}
for _ in t {}
for _ in r {}
@ -307,3 +307,86 @@ mod issue_6900 {
}
}
}
struct IntoIterDiffTy;
impl IntoIterator for &'_ IntoIterDiffTy {
type Item = &'static ();
type IntoIter = core::slice::Iter<'static, ()>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}
impl IntoIterDiffTy {
fn iter(&self) -> core::slice::Iter<'static, i32> {
unimplemented!()
}
}
struct IntoIterDiffSig;
impl IntoIterator for &'_ IntoIterDiffSig {
type Item = &'static ();
type IntoIter = core::slice::Iter<'static, ()>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}
impl IntoIterDiffSig {
fn iter(&self, _: u32) -> core::slice::Iter<'static, ()> {
unimplemented!()
}
}
struct IntoIterDiffLt<'a>(&'a ());
impl<'a> IntoIterator for &'a IntoIterDiffLt<'_> {
type Item = &'a ();
type IntoIter = core::slice::Iter<'a, ()>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}
impl<'a> IntoIterDiffLt<'a> {
fn iter(&self) -> core::slice::Iter<'a, ()> {
unimplemented!()
}
}
struct CustomType;
impl<'a> IntoIterator for &'a CustomType {
type Item = &'a u32;
type IntoIter = core::slice::Iter<'a, u32>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}
impl<'a> IntoIterator for &'a mut CustomType {
type Item = &'a mut u32;
type IntoIter = core::slice::IterMut<'a, u32>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}
impl CustomType {
fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
panic!()
}
fn iter_mut(&mut self) -> core::slice::IterMut<'_, u32> {
panic!()
}
}
#[warn(clippy::explicit_iter_loop)]
fn _f() {
let x = IntoIterDiffTy;
for _ in x.iter() {}
let x = IntoIterDiffSig;
for _ in x.iter(0) {}
let x = IntoIterDiffLt(&());
for _ in x.iter() {}
let mut x = CustomType;
for _ in &x {}
for _ in &mut x {}
}

View file

@ -1,6 +1,6 @@
//@run-rustfix
#![allow(dead_code, unused)]
#![allow(clippy::uninlined_format_args, clippy::useless_vec)]
#![allow(clippy::uninlined_format_args, clippy::useless_vec, clippy::deref_addrof)]
use std::collections::*;
@ -307,3 +307,86 @@ mod issue_6900 {
}
}
}
struct IntoIterDiffTy;
impl IntoIterator for &'_ IntoIterDiffTy {
type Item = &'static ();
type IntoIter = core::slice::Iter<'static, ()>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}
impl IntoIterDiffTy {
fn iter(&self) -> core::slice::Iter<'static, i32> {
unimplemented!()
}
}
struct IntoIterDiffSig;
impl IntoIterator for &'_ IntoIterDiffSig {
type Item = &'static ();
type IntoIter = core::slice::Iter<'static, ()>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}
impl IntoIterDiffSig {
fn iter(&self, _: u32) -> core::slice::Iter<'static, ()> {
unimplemented!()
}
}
struct IntoIterDiffLt<'a>(&'a ());
impl<'a> IntoIterator for &'a IntoIterDiffLt<'_> {
type Item = &'a ();
type IntoIter = core::slice::Iter<'a, ()>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}
impl<'a> IntoIterDiffLt<'a> {
fn iter(&self) -> core::slice::Iter<'a, ()> {
unimplemented!()
}
}
struct CustomType;
impl<'a> IntoIterator for &'a CustomType {
type Item = &'a u32;
type IntoIter = core::slice::Iter<'a, u32>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}
impl<'a> IntoIterator for &'a mut CustomType {
type Item = &'a mut u32;
type IntoIter = core::slice::IterMut<'a, u32>;
fn into_iter(self) -> Self::IntoIter {
unimplemented!()
}
}
impl CustomType {
fn iter(&self) -> <&'_ Self as IntoIterator>::IntoIter {
panic!()
}
fn iter_mut(&mut self) -> core::slice::IterMut<'_, u32> {
panic!()
}
}
#[warn(clippy::explicit_iter_loop)]
fn _f() {
let x = IntoIterDiffTy;
for _ in x.iter() {}
let x = IntoIterDiffSig;
for _ in x.iter(0) {}
let x = IntoIterDiffLt(&());
for _ in x.iter() {}
let mut x = CustomType;
for _ in x.iter() {}
for _ in x.iter_mut() {}
}

View file

@ -26,12 +26,24 @@ error: it is more concise to loop over references to containers instead of using
LL | for _v in [1, 2, 3].iter() {}
| ^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[1, 2, 3]`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/for_loop_fixable.rs:55:15
|
LL | for _v in (&mut [1, 2, 3]).iter() {} // no error
| ^^^^^^^^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `&*(&mut [1, 2, 3])`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/for_loop_fixable.rs:57:15
|
LL | for _v in [0; 32].iter() {}
| ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[0; 32]`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/for_loop_fixable.rs:59:15
|
LL | for _v in [0; 33].iter() {} // no error
| ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[0; 33]`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/for_loop_fixable.rs:62:15
|
@ -84,7 +96,7 @@ error: it is more concise to loop over references to containers instead of using
--> $DIR/for_loop_fixable.rs:275:18
|
LL | for _ in t.into_iter() {}
| ^^^^^^^^^^^^^ help: to write this more concisely, try: `&t`
| ^^^^^^^^^^^^^ help: to write this more concisely, try: `t`
error: it is more concise to loop over containers instead of using explicit iteration methods
--> $DIR/for_loop_fixable.rs:277:18
@ -92,5 +104,17 @@ error: it is more concise to loop over containers instead of using explicit iter
LL | for _ in r.into_iter() {}
| ^^^^^^^^^^^^^ help: to write this more concisely, try: `r`
error: aborting due to 15 previous errors
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/for_loop_fixable.rs:390:14
|
LL | for _ in x.iter() {}
| ^^^^^^^^ help: to write this more concisely, try: `&x`
error: it is more concise to loop over references to containers instead of using explicit iteration methods
--> $DIR/for_loop_fixable.rs:391:14
|
LL | for _ in x.iter_mut() {}
| ^^^^^^^^^^^^ help: to write this more concisely, try: `&mut x`
error: aborting due to 19 previous errors