Rollup merge of #156121 - thiago-fealves:suggest-collect-string, r=estebank

compiler: suggest `.collect()` when `String` is expected and `Iterator` is found

This commit adds a diagnostic suggestion to help users who forget to call `.collect()` when they have an iterator and the function or variable expects a `String`.

The logic checks if the expected type is `std::string::String` and if the found type implements the `Iterator` trait, if so the compiler provides a suggestion to add `.collect()`

Includes also a UI test to verify if the suggestion appears correctly
This commit is contained in:
Jonathan Brouwer
2026-05-17 15:52:35 +02:00
committed by GitHub
4 changed files with 148 additions and 0 deletions
+1
View File
@@ -40,6 +40,7 @@ pub(crate) fn emit_type_mismatch_suggestions(
|| self.suggest_semicolon_in_repeat_expr(err, expr, expr_ty)
|| self.suggest_deref_ref_or_into(err, expr, expected, expr_ty, expected_ty_expr)
|| self.suggest_option_to_bool(err, expr, expr_ty, expected)
|| self.suggest_collect(err, expr, expected, expr_ty)
|| self.suggest_compatible_variants(err, expr, expected, expr_ty)
|| self.suggest_non_zero_new_unwrap(err, expr, expected, expr_ty)
|| self.suggest_calling_boxed_future_when_appropriate(err, expr, expected, expr_ty)
@@ -250,6 +250,74 @@ pub(crate) fn suggest_two_fn_call(
}
}
/// Suggests calling `.collect()` on an `Iterator` it can be collected in the return type
/// ```compile_fail
/// let x: String = "foo".chars().map(|c| c); // with a .collect() here the code compiles
/// ```
pub(crate) fn suggest_collect(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'_>,
expected_type: Ty<'tcx>,
found_type: Ty<'tcx>,
) -> bool {
let tcx = self.tcx;
let expected = self.resolve_vars_if_possible(expected_type);
let found = self.resolve_vars_if_possible(found_type);
if expected.references_error() || found.references_error() || expected.is_unit() {
return false;
}
let Some(iterator_trait_id) = tcx.get_diagnostic_item(sym::Iterator) else {
return false;
};
if !self
.infcx
.type_implements_trait(iterator_trait_id, [found], self.param_env)
.must_apply_modulo_regions()
{
return false;
}
let Some(from_iterator_trait_id) = tcx.get_diagnostic_item(sym::FromIterator) else {
return false;
};
let Some(iterator_item_id) = tcx
.associated_items(iterator_trait_id)
.in_definition_order()
.find(|item| item.name() == sym::Item)
.map(|item| item.def_id)
else {
return false;
};
let item_type = Ty::new_projection(tcx, iterator_item_id, [found]);
let item_type =
self.normalize(expr.span, rustc_middle::ty::Unnormalized::new_wip(item_type));
let can_collect = self
.infcx
.type_implements_trait(from_iterator_trait_id, [expected, item_type], self.param_env)
.may_apply();
if can_collect {
err.span_suggestion_verbose(
expr.span.shrink_to_hi(),
format!(
"consider using `.collect()` to convert the `Iterator` into a `{expected}`"
),
".collect()",
rustc_errors::Applicability::MaybeIncorrect,
);
return true;
}
false
}
pub(crate) fn suggest_remove_last_method_call(
&self,
err: &mut Diag<'_>,
+16
View File
@@ -0,0 +1,16 @@
fn main() {
let _x: String = "hello".chars().map(|c| c);
//~^ ERROR mismatched types
//~| HELP consider using `.collect()` to convert the `Iterator` into a `String`
let _y: Vec<i32> = vec![1, 2, 3].into_iter().map(|x| x);
//~^ ERROR mismatched types
//~| HELP consider using `.collect()` to convert the `Iterator` into a `Vec<i32>`
let res: Result<Vec<i32>, _> = ["1", "2"].into_iter().map(|s| s.parse::<i32>());
//~^ ERROR mismatched types
//~| HELP consider using `.collect()` to convert the `Iterator` into a `Result<Vec<i32>, _>`
let (a, b): (Vec<i32>, Vec<i32>) = vec![1, 2].into_iter().map(|x| (x, x));
//~^ ERROR mismatched types
//~| HELP consider using `.collect()` to convert the `Iterator` into a `(Vec<i32>, Vec<i32>)`
}
@@ -0,0 +1,63 @@
error[E0308]: mismatched types
--> $DIR/suggest-collect.rs:2:22
|
LL | let _x: String = "hello".chars().map(|c| c);
| ------ ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `String`, found `Map<Chars<'_>, {closure@...}>`
| |
| expected due to this
|
= note: expected struct `String`
found struct `Map<Chars<'_>, {closure@$DIR/suggest-collect.rs:2:42: 2:45}>`
help: consider using `.collect()` to convert the `Iterator` into a `String`
|
LL | let _x: String = "hello".chars().map(|c| c).collect();
| ++++++++++
error[E0308]: mismatched types
--> $DIR/suggest-collect.rs:6:24
|
LL | let _y: Vec<i32> = vec![1, 2, 3].into_iter().map(|x| x);
| -------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Vec<i32>`, found `Map<IntoIter<{integer}>, {closure@...}>`
| |
| expected due to this
|
= note: expected struct `Vec<i32>`
found struct `Map<std::vec::IntoIter<{integer}>, {closure@$DIR/suggest-collect.rs:6:54: 6:57}>`
help: consider using `.collect()` to convert the `Iterator` into a `Vec<i32>`
|
LL | let _y: Vec<i32> = vec![1, 2, 3].into_iter().map(|x| x).collect();
| ++++++++++
error[E0308]: mismatched types
--> $DIR/suggest-collect.rs:10:36
|
LL | let res: Result<Vec<i32>, _> = ["1", "2"].into_iter().map(|s| s.parse::<i32>());
| ------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<Vec<i32>, _>`, found `Map<Iter<'_, &str>, {closure@...}>`
| |
| expected due to this
|
= note: expected enum `Result<Vec<i32>, _>`
found struct `Map<std::slice::Iter<'_, &str>, {closure@$DIR/suggest-collect.rs:10:63: 10:66}>`
help: consider using `.collect()` to convert the `Iterator` into a `Result<Vec<i32>, _>`
|
LL | let res: Result<Vec<i32>, _> = ["1", "2"].into_iter().map(|s| s.parse::<i32>()).collect();
| ++++++++++
error[E0308]: mismatched types
--> $DIR/suggest-collect.rs:13:40
|
LL | let (a, b): (Vec<i32>, Vec<i32>) = vec![1, 2].into_iter().map(|x| (x, x));
| -------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `(Vec<i32>, Vec<i32>)`, found `Map<IntoIter<{integer}>, {closure@...}>`
| |
| expected due to this
|
= note: expected tuple `(Vec<i32>, Vec<i32>)`
found struct `Map<std::vec::IntoIter<{integer}>, {closure@$DIR/suggest-collect.rs:13:67: 13:70}>`
help: consider using `.collect()` to convert the `Iterator` into a `(Vec<i32>, Vec<i32>)`
|
LL | let (a, b): (Vec<i32>, Vec<i32>) = vec![1, 2].into_iter().map(|x| (x, x)).collect();
| ++++++++++
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0308`.