Skip to content

Commit 28c079a

Browse files
committed
Suggest .iter() for shared projections
* Suggest `.iter()` for shared projections * address few nits * a few improvements
1 parent 36ba2c7 commit 28c079a

4 files changed

Lines changed: 178 additions & 9 deletions

File tree

compiler/rustc_hir_typeck/src/expr_use_visitor.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! normal visitor, which just walks the entire body in one shot, the
33
//! `ExprUseVisitor` determines how expressions are being used.
44
//!
5-
//! In the compiler, this is only used for upvar inference, but there
5+
//! In the compiler, this is only used for upvar inference and diagnostics, but there
66
//! are many uses within clippy.
77
88
use std::cell::{Ref, RefCell};
@@ -1855,3 +1855,27 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx
18551855
}
18561856
}
18571857
}
1858+
1859+
struct ExprPlaceDelegate;
1860+
1861+
impl<'tcx> Delegate<'tcx> for ExprPlaceDelegate {
1862+
fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
1863+
1864+
fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
1865+
1866+
fn borrow(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {}
1867+
1868+
fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
1869+
1870+
fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
1871+
}
1872+
1873+
/// Categorizes `expr` as a place for diagnostic suggestions.
1874+
///
1875+
/// This should be used for diagnostics purpose only.
1876+
pub(crate) fn expr_place<'tcx>(
1877+
fcx: &FnCtxt<'_, 'tcx>,
1878+
expr: &hir::Expr<'_>,
1879+
) -> Result<PlaceWithHirId<'tcx>, ErrorGuaranteed> {
1880+
ExprUseVisitor::new(fcx, ExprPlaceDelegate).cat_expr(expr)
1881+
}

compiler/rustc_hir_typeck/src/method/suggest.rs

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ use tracing::{debug, info, instrument};
4848
use super::probe::{AutorefOrPtrAdjustment, IsSuggestion, Mode, ProbeScope};
4949
use super::{CandidateSource, MethodError, NoMatchData};
5050
use crate::errors::{self, CandidateTraitNote, NoAssociatedItem};
51+
use crate::expr_use_visitor::expr_place;
5152
use crate::method::probe::UnsatisfiedPredicates;
5253
use crate::{Expectation, FnCtxt};
5354

@@ -189,6 +190,70 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
189190
false
190191
}
191192

193+
// Pick the iterator method to suggest: `.into_iter()` by default, and
194+
// `.iter()`/`.iter_mut()` for projections through references.
195+
fn preferred_iterator_method(
196+
&self,
197+
source: SelfSource<'tcx>,
198+
rcvr_ty: Ty<'tcx>,
199+
) -> Option<Symbol> {
200+
let SelfSource::MethodCall(rcvr_expr) = source else {
201+
return Some(sym::into_iter);
202+
};
203+
204+
let rcvr_expr = rcvr_expr.peel_drop_temps().peel_blocks();
205+
let Ok(place_with_id) = expr_place(self, rcvr_expr) else {
206+
return None;
207+
};
208+
209+
let mut projection_mutability = None;
210+
for pointer_ty in place_with_id.place.deref_tys() {
211+
match self.structurally_resolve_type(rcvr_expr.span, pointer_ty).kind() {
212+
ty::Ref(.., Mutability::Not) => {
213+
projection_mutability = Some(Mutability::Not);
214+
break;
215+
}
216+
ty::Ref(.., Mutability::Mut) => {
217+
projection_mutability.get_or_insert(Mutability::Mut);
218+
}
219+
ty::RawPtr(..) => return None,
220+
_ => {}
221+
}
222+
}
223+
224+
// Keep `.into_iter()` for receivers like `&Vec<_>`; only projections that
225+
// dereference a reference need to switch to `iter`/`iter_mut`.
226+
let Some(projection_mutability) = projection_mutability else {
227+
return Some(sym::into_iter);
228+
};
229+
230+
let call_expr = self.tcx.hir_expect_expr(self.tcx.parent_hir_id(rcvr_expr.hir_id));
231+
// `IntoIterator` does not imply inherent `iter`/`iter_mut` methods.
232+
let has_method = |method_name| {
233+
self.lookup_probe_for_diagnostic(
234+
Ident::with_dummy_span(method_name),
235+
rcvr_ty,
236+
call_expr,
237+
ProbeScope::TraitsInScope,
238+
None,
239+
)
240+
.is_ok()
241+
};
242+
243+
match projection_mutability {
244+
Mutability::Not => has_method(sym::iter).then_some(sym::iter),
245+
Mutability::Mut => {
246+
if has_method(sym::iter_mut) {
247+
Some(sym::iter_mut)
248+
} else if has_method(sym::iter) {
249+
Some(sym::iter)
250+
} else {
251+
None
252+
}
253+
}
254+
}
255+
}
256+
192257
#[instrument(level = "debug", skip(self))]
193258
pub(crate) fn report_method_error(
194259
&self,
@@ -855,10 +920,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
855920
} else if self.impl_into_iterator_should_be_iterator(rcvr_ty, span, unsatisfied_predicates)
856921
{
857922
err.span_label(span, format!("`{rcvr_ty}` is not an iterator"));
858-
if !span.in_external_macro(self.tcx.sess.source_map()) {
923+
if !span.in_external_macro(self.tcx.sess.source_map())
924+
&& let Some(method_name) = self.preferred_iterator_method(source, rcvr_ty)
925+
{
859926
err.multipart_suggestion(
860-
"call `.into_iter()` first",
861-
vec![(span.shrink_to_lo(), format!("into_iter()."))],
927+
format!("call `.{method_name}()` first"),
928+
vec![(span.shrink_to_lo(), format!("{method_name}()."))],
862929
Applicability::MaybeIncorrect,
863930
);
864931
}
Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
// Tests that the compiler suggests an `into_iter` call when an `Iterator` method
2-
// is called on something that implements `IntoIterator`
1+
// Tests that the compiler suggests an iterator method when an `Iterator` method
2+
// is called on something that implements `IntoIterator`.
33

44
fn main() {
55
let items = items();
66
let other_items = items.map(|i| i + 1);
77
//~^ ERROR no method named `map` found for opaque type `impl IntoIterator<Item = i32>` in the current scope
8+
//~| HELP: call `.into_iter()` first
89
let vec: Vec<i32> = items.collect();
910
//~^ ERROR no method named `collect` found for opaque type `impl IntoIterator<Item = i32>` in the current scope
11+
//~| HELP: call `.into_iter()` first
1012
}
1113

1214
fn items() -> impl IntoIterator<Item = i32> {
@@ -16,4 +18,36 @@ fn items() -> impl IntoIterator<Item = i32> {
1618
fn process(items: impl IntoIterator<Item = String>) -> Vec<String> {
1719
items.collect()
1820
//~^ ERROR no method named `collect` found for type parameter `impl IntoIterator<Item = String>` in the current scope
21+
//~| HELP: call `.into_iter()` first
22+
}
23+
24+
// Regression test for https://github.com/rust-lang/rust/issues/155365
25+
struct Demo {
26+
contents: Vec<u32>,
27+
}
28+
29+
impl Demo {
30+
fn count_odds(&self) -> usize {
31+
self.contents.filter(|v| *v % 2 == 1).count()
32+
//~^ ERROR no method named `filter` found for struct `Vec<u32>` in the current scope
33+
//~| HELP: call `.iter()` first
34+
}
35+
36+
fn increment(&mut self) {
37+
self.contents.for_each(|v| *v += 1)
38+
//~^ ERROR no method named `for_each` found for struct `Vec<u32>` in the current scope
39+
//~| HELP: call `.iter_mut()` first
40+
}
41+
}
42+
43+
fn count_odds_param(contents: &Vec<u32>) -> usize {
44+
contents.filter(|v| *v % 2 == 1).count()
45+
//~^ ERROR no method named `filter` found for reference `&Vec<u32>` in the current scope
46+
//~| HELP: call `.into_iter()` first
47+
}
48+
49+
fn count_odds_explicit_deref(contents: &Vec<u32>) -> usize {
50+
(*contents).filter(|v| *v % 2 == 1).count()
51+
//~^ ERROR no method named `filter` found for struct `Vec<u32>` in the current scope
52+
//~| HELP: call `.iter()` first
1953
}

tests/ui/did_you_mean/collect-without-into-iter-call.stderr

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ LL | let other_items = items.into_iter().map(|i| i + 1);
1010
| ++++++++++++
1111

1212
error[E0599]: no method named `collect` found for opaque type `impl IntoIterator<Item = i32>` in the current scope
13-
--> $DIR/collect-without-into-iter-call.rs:8:31
13+
--> $DIR/collect-without-into-iter-call.rs:9:31
1414
|
1515
LL | let vec: Vec<i32> = items.collect();
1616
| ^^^^^^^ `impl IntoIterator<Item = i32>` is not an iterator
@@ -21,7 +21,7 @@ LL | let vec: Vec<i32> = items.into_iter().collect();
2121
| ++++++++++++
2222

2323
error[E0599]: no method named `collect` found for type parameter `impl IntoIterator<Item = String>` in the current scope
24-
--> $DIR/collect-without-into-iter-call.rs:17:11
24+
--> $DIR/collect-without-into-iter-call.rs:19:11
2525
|
2626
LL | items.collect()
2727
| ^^^^^^^ `impl IntoIterator<Item = String>` is not an iterator
@@ -31,6 +31,50 @@ help: call `.into_iter()` first
3131
LL | items.into_iter().collect()
3232
| ++++++++++++
3333

34-
error: aborting due to 3 previous errors
34+
error[E0599]: no method named `filter` found for struct `Vec<u32>` in the current scope
35+
--> $DIR/collect-without-into-iter-call.rs:31:23
36+
|
37+
LL | self.contents.filter(|v| *v % 2 == 1).count()
38+
| ^^^^^^ `Vec<u32>` is not an iterator
39+
|
40+
help: call `.iter()` first
41+
|
42+
LL | self.contents.iter().filter(|v| *v % 2 == 1).count()
43+
| +++++++
44+
45+
error[E0599]: no method named `for_each` found for struct `Vec<u32>` in the current scope
46+
--> $DIR/collect-without-into-iter-call.rs:37:23
47+
|
48+
LL | self.contents.for_each(|v| *v += 1)
49+
| ^^^^^^^^ `Vec<u32>` is not an iterator
50+
|
51+
help: call `.iter_mut()` first
52+
|
53+
LL | self.contents.iter_mut().for_each(|v| *v += 1)
54+
| +++++++++++
55+
56+
error[E0599]: no method named `filter` found for reference `&Vec<u32>` in the current scope
57+
--> $DIR/collect-without-into-iter-call.rs:44:14
58+
|
59+
LL | contents.filter(|v| *v % 2 == 1).count()
60+
| ^^^^^^ `&Vec<u32>` is not an iterator
61+
|
62+
help: call `.into_iter()` first
63+
|
64+
LL | contents.into_iter().filter(|v| *v % 2 == 1).count()
65+
| ++++++++++++
66+
67+
error[E0599]: no method named `filter` found for struct `Vec<u32>` in the current scope
68+
--> $DIR/collect-without-into-iter-call.rs:50:17
69+
|
70+
LL | (*contents).filter(|v| *v % 2 == 1).count()
71+
| ^^^^^^ `Vec<u32>` is not an iterator
72+
|
73+
help: call `.iter()` first
74+
|
75+
LL | (*contents).iter().filter(|v| *v % 2 == 1).count()
76+
| +++++++
77+
78+
error: aborting due to 7 previous errors
3579

3680
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)