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 @@ -4487,6 +4487,29 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
tcx.disabled_nightly_features(err, [(String::new(), sym::trivial_bounds)]);
}
ObligationCauseCode::OpaqueReturnType(expr_info) => {
// Point at the method call in the returned expression's chain where an
// associated type diverged from what the signature's opaque type expects,
// regardless of how the failed predicate was derived from that expectation.
if let Some(typeck_results) = self.typeck_results.as_deref() {
let chain_expr = match expr_info {
Some((_, hir_id)) => Some(tcx.hir_expect_expr(hir_id)),
None => tcx.hir_node_by_def_id(body_def_id).body_id().and_then(|body_id| {
match tcx.hir_body(body_id).value.kind {
hir::ExprKind::Block(block, _) => block.expr,
_ => None,
}
}),
};
if let Some(chain_expr) = chain_expr {
self.point_at_chain_in_return_position(
body_def_id,
chain_expr,
typeck_results,
param_env,
err,
);
}
}
let (expr_ty, expr) = if let Some((expr_ty, hir_id)) = expr_info {
let expr = tcx.hir_expect_expr(hir_id);
(expr_ty, expr)
Expand Down Expand Up @@ -5438,6 +5461,109 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
assocs_in_this_method
}

/// When a `-> impl Trait<Assoc = Ty>` return type obligation fails, walk the method call
/// chain in the returned expression to point at where the associated type diverged from
/// what the signature expects.
///
/// ```text
/// note: the method call chain might not have had the expected associated types
/// --> $DIR/invalid-iterator-chain-in-return-position.rs:16:18
/// |
/// LL | x.iter_mut().map(foo)
/// | - ---------- ^^^^^^^^ `Iterator::Item` changed to `()` here
/// | | |
/// | | `Iterator::Item` is `&mut Vec<u8>` here
/// | this expression has type `Vec<Vec<u8>>`
/// ```
fn point_at_chain_in_return_position<G: EmissionGuarantee>(
&self,
body_def_id: LocalDefId,
expr: &hir::Expr<'_>,
typeck_results: &TypeckResults<'tcx>,
param_env: ty::ParamEnv<'tcx>,
err: &mut Diag<'_, G>,
) {
let tcx = self.tcx;
if !matches!(tcx.def_kind(body_def_id), DefKind::Fn | DefKind::AssocFn) {
return;
}
let output =
tcx.fn_sig(body_def_id).instantiate_identity().skip_norm_wip().output().skip_binder();
let &ty::Alias(_, ty::AliasTy { kind: ty::Opaque { def_id: opaque_def_id }, args, .. }) =
output.kind()
else {
return;
};

// The predicate that reaches here has been rewritten through the impls it was
// derived from (e.g. `Iterator for Map<I, F>` turns `Iterator::Item` requirements
// into requirements on `F`'s return type), so the associated types the user wrote
// in the signature are recovered from the opaque's bounds instead.
let mut probe_diffs = vec![];
for clause in tcx.item_bounds(opaque_def_id).instantiate(tcx, args).skip_norm_wip() {
let Some(proj) = clause.as_projection_clause() else { continue };
let proj = self.instantiate_binder_with_fresh_vars(
expr.span,
BoundRegionConversionTime::FnCall,
proj,
);
let Some(expected_term) = proj.term.as_type() else { continue };
// Only the projection (for its `DefId`) is used when probing the chain; the
// bound's own term is carried in `found` for the divergence check below and
// is replaced with the probed type afterwards.
probe_diffs.push(TypeError::Sorts(ty::error::ExpectedFound {
expected: proj.projection_term.expect_ty().to_ty(tcx, ty::IsRigid::No),
found: expected_term,
}));
}
if probe_diffs.is_empty() {
return;
}

// If the returned expression is a binding, walk the chain that created it instead.
let expr = if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
&& let hir::Path { res: Res::Local(hir_id), .. } = path
&& let hir::Node::Pat(binding) = tcx.hir_node(*hir_id)
&& let hir::Node::LetStmt(local) = tcx.parent_hir_node(binding.hir_id)
&& let Some(binding_expr) = local.init
{
binding_expr
} else {
expr
};

// Resolve what each bound associated type actually is for the returned expression,
// and keep only the ones that diverged from the signature.
let expr_ty = self.resolve_vars_if_possible(
typeck_results.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(tcx)),
);
let assocs = self.probe_assoc_types_at_expr(
&probe_diffs,
expr.span,
expr_ty,
expr.hir_id,
param_env,
);
let mut type_diffs = vec![];
for (probe_diff, assoc) in iter::zip(probe_diffs, assocs) {
let TypeError::Sorts(ty::error::ExpectedFound { expected, found: expected_term }) =
probe_diff
else {
continue;
};
let Some((_, (_, actual_ty))) = assoc else { continue };
if !self.can_eq(param_env, expected_term, actual_ty) {
type_diffs.push(TypeError::Sorts(ty::error::ExpectedFound {
expected,
found: actual_ty,
}));
}
}
if !type_diffs.is_empty() {
self.point_at_chain(expr, typeck_results, type_diffs, param_env, err);
}
}

/// If the type that failed selection is an array or a reference to an array,
/// but the trait is implemented for slices, suggest that the user converts
/// the array into a slice.
Expand Down
8 changes: 8 additions & 0 deletions tests/ui/associated-type-bounds/duplicate-bound-err.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ LL | fn foo() -> impl Iterator<Item = i32, Item = u32> {
...
LL | [2u32].into_iter()
| ------------------ return type was inferred to be `std::array::IntoIter<u32, 1>` here
|
note: the method call chain might not have had the expected associated types
--> $DIR/duplicate-bound-err.rs:110:16
|
LL | [2u32].into_iter()
| ------ ^^^^^^^^^^^ `Iterator::Item` is `u32` here
| |
| this expression has type `[u32; 1]`

error[E0271]: expected `impl Iterator<Item = u32>` to be an iterator that yields `i32`, but it yields `u32`
--> $DIR/duplicate-bound-err.rs:107:17
Expand Down
39 changes: 39 additions & 0 deletions tests/ui/iterators/invalid-iterator-chain-in-return-position.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! Check that a `-> impl Iterator<Item = Ty>` return type mismatch points at the
//! method call in the returned expression's chain where `Iterator::Item` diverged
//! from the signature's expectation, instead of only pointing at the signature.
//! Regression test for <https://github.com/rust-lang/rust/issues/106993>.

fn foo(items: &mut Vec<u8>) {
items.sort();
}

fn bar() -> impl Iterator<Item = i32> {
//~^ ERROR expected `foo` to return `i32`, but it returns `()`
let mut x: Vec<Vec<u8>> = vec![
vec![0, 2, 1],
vec![5, 4, 3],
];
x.iter_mut().map(foo)
}

fn baz() -> impl Iterator<Item = i32> {
//~^ ERROR expected `foo` to return `i32`, but it returns `()`
let mut x: Vec<Vec<u8>> = vec![
vec![0, 2, 1],
vec![5, 4, 3],
];
let it = x.iter_mut().map(foo);
it
}

fn chained() -> impl Iterator<Item = i32> {
//~^ ERROR expected `IntoIter<u32>` to be an iterator that yields `i32`, but it yields `u32`
let x = vec![0u32, 1, 2];
x.into_iter().filter(|x| *x > 0).map(|x| x.checked_add(1)).flatten()
}

fn main() {
bar();
baz();
chained();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
error[E0271]: expected `foo` to return `i32`, but it returns `()`
--> $DIR/invalid-iterator-chain-in-return-position.rs:10:13
|
LL | fn bar() -> impl Iterator<Item = i32> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `i32`, found `()`
...
LL | x.iter_mut().map(foo)
| --------------------- return type was inferred to be `Map<std::slice::IterMut<'_, Vec<u8>>, for<'a> fn(&'a mut Vec<u8>) {foo}>` here
|
= note: required for `Map<std::slice::IterMut<'_, Vec<u8>>, for<'a> fn(&'a mut Vec<u8>) {foo}>` to implement `Iterator`
note: the method call chain might not have had the expected associated types
--> $DIR/invalid-iterator-chain-in-return-position.rs:16:18
|
LL | let mut x: Vec<Vec<u8>> = vec![
| _______________________________-
LL | | vec![0, 2, 1],
LL | | vec![5, 4, 3],
LL | | ];
| |_____- this expression has type `Vec<Vec<u8>>`
LL | x.iter_mut().map(foo)
| ---------- ^^^^^^^^ `Iterator::Item` changed to `()` here
| |
| `Iterator::Item` is `&mut Vec<u8>` here

error[E0271]: expected `foo` to return `i32`, but it returns `()`
--> $DIR/invalid-iterator-chain-in-return-position.rs:19:13
|
LL | fn baz() -> impl Iterator<Item = i32> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `i32`, found `()`
...
LL | it
| -- return type was inferred to be `Map<std::slice::IterMut<'_, Vec<u8>>, for<'a> fn(&'a mut Vec<u8>) {foo}>` here
|
= note: required for `Map<std::slice::IterMut<'_, Vec<u8>>, for<'a> fn(&'a mut Vec<u8>) {foo}>` to implement `Iterator`
note: the method call chain might not have had the expected associated types
--> $DIR/invalid-iterator-chain-in-return-position.rs:25:27
|
LL | let mut x: Vec<Vec<u8>> = vec![
| _______________________________-
LL | | vec![0, 2, 1],
LL | | vec![5, 4, 3],
LL | | ];
| |_____- this expression has type `Vec<Vec<u8>>`
LL | let it = x.iter_mut().map(foo);
| ---------- ^^^^^^^^ `Iterator::Item` changed to `()` here
| |
| `Iterator::Item` is `&mut Vec<u8>` here

error[E0271]: expected `IntoIter<u32>` to be an iterator that yields `i32`, but it yields `u32`
--> $DIR/invalid-iterator-chain-in-return-position.rs:29:17
|
LL | fn chained() -> impl Iterator<Item = i32> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `i32`, found `u32`
|
note: the method call chain might not have had the expected associated types
--> $DIR/invalid-iterator-chain-in-return-position.rs:32:7
|
LL | let x = vec![0u32, 1, 2];
| ---------------- this expression has type `Vec<u32>`
LL | x.into_iter().filter(|x| *x > 0).map(|x| x.checked_add(1)).flatten()
| ^^^^^^^^^^^ ------------------ ------------------------- ^^^^^^^^^ `Iterator::Item` changed to `u32` here
| | | |
| | | `Iterator::Item` changed to `Option<u32>` here
| | `Iterator::Item` remains `u32` here
| `Iterator::Item` is `u32` here

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0271`.
9 changes: 9 additions & 0 deletions tests/ui/lint/issue-106991.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ LL | x.iter_mut().map(foo)
| --------------------- return type was inferred to be `Map<std::slice::IterMut<'_, Vec<u8>>, for<'a> fn(&'a mut Vec<u8>) {foo}>` here
|
= note: required for `Map<std::slice::IterMut<'_, Vec<u8>>, for<'a> fn(&'a mut Vec<u8>) {foo}>` to implement `Iterator`
note: the method call chain might not have had the expected associated types
--> $DIR/issue-106991.rs:8:18
|
LL | let mut x: Vec<Vec<u8>> = vec![vec![0, 2, 1], vec![5, 4, 3]];
| ---------------------------------- this expression has type `Vec<Vec<u8>>`
LL | x.iter_mut().map(foo)
| ---------- ^^^^^^^^ `Iterator::Item` changed to `()` here
| |
| `Iterator::Item` is `&mut Vec<u8>` here

error: aborting due to 1 previous error

Expand Down
Loading