Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ impl UnderspecifiedArgKind {

struct ClosureEraser<'a, 'tcx> {
infcx: &'a InferCtxt<'tcx>,
depth: usize,
}

impl<'a, 'tcx> ClosureEraser<'a, 'tcx> {
Expand All @@ -172,7 +173,8 @@ impl<'a, 'tcx> TypeFolder<TyCtxt<'tcx>> for ClosureEraser<'a, '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`.
Expand Down Expand Up @@ -233,9 +235,15 @@ impl<'a, 'tcx> TypeFolder<TyCtxt<'tcx>> for ClosureEraser<'a, '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> {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -467,6 +475,25 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
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
Expand All @@ -479,7 +506,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
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(),
) {
Expand Down Expand Up @@ -779,10 +806,20 @@ impl<'tcx> InferSourceKind<'tcx> {
| 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.
Expand Down Expand Up @@ -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,
Expand All @@ -826,12 +864,14 @@ impl<'a, 'tcx> FindInferSourceVisitor<'a, 'tcx> {
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,
Expand Down Expand Up @@ -1213,7 +1253,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> {
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<'_>,
Expand Down Expand Up @@ -1241,7 +1281,11 @@ impl<'a, 'tcx> Visitor<'tcx> for FindInferSourceVisitor<'a, 'tcx> {
_ => None,
}
}

if let Some(t) = self.ty
&& ty.has_infer()
{
ty = t;
Copy link
Member

@JohnTitor JohnTitor Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC the logic on ambiguity.rs doesn't check what's the local type at all, right?
I wonder sometimes (inferred) self type isn't same as the local type when it has an outer type unrelated to the trait resolution, e.g. Box<T>, so overwriting seems a bit risky.
What's happen on suggestions for such a case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the overwriting is purely local and affecting the diagnostic: we're changing what will be included in the LetBinding ty field below not affecting the type associated to local.hir_id, for that we'd have to go through typeck.adjustments_mut().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thank you for clarifying!

}
if let LocalSource::Normal = local.source
&& local.ty.is_none()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,37 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
.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!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2251,6 +2251,16 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
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;
}
Comment on lines +2254 to +2263
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gives us the

help: the trait `Sum` is implemented for `i32`
    --> library/core/src/iter/traits/accum.rs:48:9
     |
  48 |         impl Sum for $a {
     |         ^^^^^^^^^^^^^^^
...
 204 | integer_sum_product! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
     | ---------------------------------------------------------------------------- in this macro invocation

instead of

     = help: the following types implement trait `Sum<A>`:
               `Duration` implements `Sum<&'a Duration>`
               `Duration` implements `Sum`
               `Option<T>` implements `Sum<Option<U>>`
               `Result<T, E>` implements `Sum<Result<U, E>>`
               `Saturating<u128>` implements `Sum<&'a Saturating<u128>>`
               `Saturating<u128>` implements `Sum`
               `Saturating<u16>` implements `Sum<&'a Saturating<u16>>`
               `Saturating<u16>` implements `Sum`
             and 88 others

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)
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/const-generics/issues/issue-83249.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 13 additions & 0 deletions tests/ui/inference/cannot-infer-iterator-sum-return-type.fixed
Original file line number Diff line number Diff line change
@@ -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);
}
13 changes: 13 additions & 0 deletions tests/ui/inference/cannot-infer-iterator-sum-return-type.rs
Original file line number Diff line number Diff line change
@@ -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);
}
26 changes: 26 additions & 0 deletions tests/ui/inference/cannot-infer-iterator-sum-return-type.stderr
Original file line number Diff line number Diff line change
@@ -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`.
Loading