When single impl can satisfy inference error, suggest type

When encountering an inference error where a return type must be known, like when calling `Iterator::sum::<T>()` without specifying `T`, look for all `T` that would satisfy `Sum<S>`. If only one, suggest it.

```
error[E0283]: type annotations needed
  --> $DIR/cannot-infer-iterator-sum-return-type.rs:4:9
   |
LL |     let sum = v
   |         ^^^
...
LL |         .sum(); // `sum::<T>` needs `T` to be specified
   |          --- type must be known at this point
   |
   = note: cannot satisfy `_: Sum<i32>`
help: the trait `Sum` is implemented for `i32`
  --> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL
  ::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL
   |
   = note: in this macro invocation
note: required by a bound in `std::iter::Iterator::sum`
  --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL
   = note: this error originates in the macro `integer_sum_product` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider giving `sum` an explicit type, where the type for type parameter `S` is specified
   |
LL |     let sum: i32 = v
   |            +++++
```
This commit is contained in:
Esteban Küber
2026-03-11 16:39:03 +00:00
parent 53d60bb1c5
commit 49bb371ca7
7 changed files with 145 additions and 14 deletions
@@ -158,6 +158,7 @@ fn try_get_prefix(&self) -> Option<&str> {
struct ClosureEraser<'a, 'tcx> {
infcx: &'a InferCtxt<'tcx>,
depth: usize,
}
impl<'a, 'tcx> ClosureEraser<'a, 'tcx> {
@@ -172,7 +173,8 @@ fn cx(&self) -> TyCtxt<'tcx> {
}
fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
match ty.kind() {
self.depth += 1;
let ty = match ty.kind() {
ty::Closure(_, args) => {
// For a closure type, we turn it into a function pointer so that it gets rendered
// as `fn(args) -> Ret`.
@@ -233,9 +235,15 @@ fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
// its type parameters.
ty.super_fold_with(self)
}
// We don't have an unknown type parameter anywhere, replace with `_`.
// We are in the top-level type, not one of its type parameters. Name it with its
// parameters replaced.
_ if self.depth == 1 => ty.super_fold_with(self),
// We don't have an unknown type parameter anywhere, and we are in a type parameter.
// Replace with `_`.
_ => self.new_infer(),
}
};
self.depth -= 1;
ty
}
fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> {
@@ -287,7 +295,7 @@ fn ty_to_string<'tcx>(
let ty = infcx.resolve_vars_if_possible(ty);
// We use `fn` ptr syntax for closures, but this only works when the closure does not capture
// anything. We also remove all type parameters that are fully known to the type system.
let ty = ty.fold_with(&mut ClosureEraser { infcx });
let ty = ty.fold_with(&mut ClosureEraser { infcx, depth: 0 });
match (ty.kind(), called_method_def_id) {
// We don't want the regular output for `fn`s because it includes its path in
@@ -467,6 +475,25 @@ pub fn emit_inference_failure_err(
term: Term<'tcx>,
error_code: TypeAnnotationNeeded,
should_label_span: bool,
) -> Diag<'a> {
self.emit_inference_failure_err_with_type_hint(
body_def_id,
failure_span,
term,
error_code,
should_label_span,
None,
)
}
pub fn emit_inference_failure_err_with_type_hint(
&self,
body_def_id: LocalDefId,
failure_span: Span,
term: Term<'tcx>,
error_code: TypeAnnotationNeeded,
should_label_span: bool,
ty: Option<Ty<'tcx>>,
) -> Diag<'a> {
let term = self.resolve_vars_if_possible(term);
let arg_data = self
@@ -479,7 +506,7 @@ pub fn emit_inference_failure_err(
return self.bad_inference_failure_err(failure_span, arg_data, error_code);
};
let mut local_visitor = FindInferSourceVisitor::new(self, typeck_results, term);
let mut local_visitor = FindInferSourceVisitor::new(self, typeck_results, term, ty);
if let Some(body) = self.tcx.hir_maybe_body_owned_by(
self.tcx.typeck_root_def_id(body_def_id.to_def_id()).expect_local(),
) {
@@ -779,10 +806,20 @@ fn ty_localized_msg(&self, infcx: &InferCtxt<'tcx>) -> (&'static str, String, Op
| InferSourceKind::ClosureReturn { ty, .. } => {
if ty.is_closure() {
("closure", closure_as_fn_str(infcx, ty), long_ty_path)
} else if !ty.is_ty_or_numeric_infer() {
("normal", infcx.tcx.short_string(ty, &mut long_ty_path), long_ty_path)
} else {
} else if ty.is_ty_or_numeric_infer()
|| ty.is_primitive()
|| matches!(
ty.kind(),
ty::Adt(_, args)
if args.types().count() == 0 && args.consts().count() == 0
)
{
// `ty` is either `_`, a primitive type like `u32` or a type with no type or
// const parameters. We will not mention the type in the main inference error
// message.
("other", String::new(), long_ty_path)
} else {
("normal", infcx.tcx.short_string(ty, &mut long_ty_path), long_ty_path)
}
}
// FIXME: We should be able to add some additional info here.
@@ -815,6 +852,7 @@ struct FindInferSourceVisitor<'a, 'tcx> {
typeck_results: &'a TypeckResults<'tcx>,
target: Term<'tcx>,
ty: Option<Ty<'tcx>>,
attempt: usize,
infer_source_cost: usize,
@@ -826,12 +864,14 @@ fn new(
tecx: &'a TypeErrCtxt<'a, 'tcx>,
typeck_results: &'a TypeckResults<'tcx>,
target: Term<'tcx>,
ty: Option<Ty<'tcx>>,
) -> Self {
FindInferSourceVisitor {
tecx,
typeck_results,
target,
ty,
attempt: 0,
infer_source_cost: usize::MAX,
@@ -1213,7 +1253,7 @@ fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
fn visit_local(&mut self, local: &'tcx LetStmt<'tcx>) {
intravisit::walk_local(self, local);
if let Some(ty) = self.opt_node_type(local.hir_id) {
if let Some(mut ty) = self.opt_node_type(local.hir_id) {
if self.generic_arg_contains_target(ty.into()) {
fn get_did(
typeck_results: &TypeckResults<'_>,
@@ -1241,7 +1281,11 @@ fn get_did(
_ => None,
}
}
if let Some(t) = self.ty
&& ty.has_infer()
{
ty = t;
}
if let LocalSource::Normal = local.source
&& local.ty.is_none()
{
@@ -246,12 +246,37 @@ pub(super) fn maybe_report_ambiguity(
.find(|s| s.has_non_region_infer());
let mut err = if let Some(term) = term {
self.emit_inference_failure_err(
let candidates: Vec<_> = self
.tcx
.all_impls(trait_pred.def_id())
.filter_map(|def_id| {
let imp = self.tcx.impl_trait_header(def_id);
if imp.polarity != ty::ImplPolarity::Positive
|| !self.tcx.is_user_visible_dep(def_id.krate)
{
return None;
}
let imp = imp.trait_ref.skip_binder();
if imp
.with_replaced_self_ty(self.tcx, trait_pred.skip_binder().self_ty())
== trait_pred.skip_binder().trait_ref
{
Some(imp.self_ty())
} else {
None
}
})
.collect();
self.emit_inference_failure_err_with_type_hint(
obligation.cause.body_id,
span,
term,
TypeAnnotationNeeded::E0283,
true,
match &candidates[..] {
[candidate] => Some(*candidate),
_ => None,
},
)
} else {
struct_span_code_err!(
@@ -2251,6 +2251,16 @@ pub(super) fn report_similar_impl_candidates(
if candidates.is_empty() {
return false;
}
let mut specific_candidates = candidates.clone();
specific_candidates.retain(|(tr, _)| {
tr.with_replaced_self_ty(self.tcx, trait_pred.skip_binder().self_ty())
== trait_pred.skip_binder().trait_ref
});
if !specific_candidates.is_empty() {
// We have found a subset of impls that fully satisfy the expected trait, only
// mention those types.
candidates = specific_candidates;
}
if let &[(cand, def_id)] = &candidates[..] {
if self.tcx.is_diagnostic_item(sym::FromResidual, cand.def_id)
&& !self.tcx.features().enabled(sym::try_trait_v2)
@@ -17,10 +17,10 @@ note: required by a bound in `foo`
|
LL | fn foo<T: Foo>(_: [u8; T::N]) -> T {
| ^^^ required by this bound in `foo`
help: consider giving this pattern a type
help: consider giving this pattern a type, where the type for type parameter `T` is specified
|
LL | let _: /* Type */ = foo([0; 1]);
| ++++++++++++
LL | let _: u8 = foo([0; 1]);
| ++++
error: aborting due to 1 previous error
@@ -0,0 +1,13 @@
//@ run-rustfix
fn main() {
let v = vec![1, 2];
let sum: i32 = v //~ ERROR: type annotations needed
.iter()
.map(|val| *val)
.sum(); // `sum::<T>` needs `T` to be specified
// In this case any integer would fit, but we resolve to `i32` because that's what `{integer}`
// got coerced to. If the user needs further hinting that they can change the integer type, that
// can come from other suggestions. (#100802)
let bool = sum > 0;
assert_eq!(bool, true);
}
@@ -0,0 +1,13 @@
//@ run-rustfix
fn main() {
let v = vec![1, 2];
let sum = v //~ ERROR: type annotations needed
.iter()
.map(|val| *val)
.sum(); // `sum::<T>` needs `T` to be specified
// In this case any integer would fit, but we resolve to `i32` because that's what `{integer}`
// got coerced to. If the user needs further hinting that they can change the integer type, that
// can come from other suggestions. (#100802)
let bool = sum > 0;
assert_eq!(bool, true);
}
@@ -0,0 +1,26 @@
error[E0283]: type annotations needed
--> $DIR/cannot-infer-iterator-sum-return-type.rs:4:9
|
LL | let sum = v
| ^^^
...
LL | .sum(); // `sum::<T>` needs `T` to be specified
| --- type must be known at this point
|
= note: the type must implement `Sum<i32>`
help: the trait `Sum` is implemented for `i32`
--> $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL
::: $SRC_DIR/core/src/iter/traits/accum.rs:LL:COL
|
= note: in this macro invocation
note: required by a bound in `std::iter::Iterator::sum`
--> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL
= note: this error originates in the macro `integer_sum_product` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider giving `sum` an explicit type, where the type for type parameter `S` is specified
|
LL | let sum: i32 = v
| +++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0283`.