From d061dcb71f45ade7f38578edbbcbe486349037e8 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Sun, 24 May 2026 14:20:29 -0300 Subject: [PATCH 1/2] Fix misattributed type inference error span for index expressions When an index expression with an ambiguous type (e.g. `arr[idx.into()]`) appears inside a cast or binary operation, the type inference error was incorrectly attributed to the outer expression instead of the `.into()` call. Resolve the index sub-expression type first so the error points at the actual ambiguous site. --- compiler/rustc_hir_typeck/src/cast.rs | 16 ++++++- compiler/rustc_hir_typeck/src/op.rs | 10 +++++ .../index-expr-ambiguous-type.rs | 30 +++++++++++++ .../index-expr-ambiguous-type.stderr | 42 +++++++++++++++++++ 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 tests/ui/type-inference/index-expr-ambiguous-type.rs create mode 100644 tests/ui/type-inference/index-expr-ambiguous-type.stderr diff --git a/compiler/rustc_hir_typeck/src/cast.rs b/compiler/rustc_hir_typeck/src/cast.rs index 4d934703f4670..97ff7765896a8 100644 --- a/compiler/rustc_hir_typeck/src/cast.rs +++ b/compiler/rustc_hir_typeck/src/cast.rs @@ -727,7 +727,21 @@ impl<'a, 'tcx> CastCheck<'tcx> { #[instrument(skip(fcx), level = "debug")] pub(crate) fn check(mut self, fcx: &FnCtxt<'a, 'tcx>) { - self.expr_ty = fcx.structurally_resolve_type(self.expr_span, self.expr_ty); + if let hir::ExprKind::Index(_, idx, _) = self.expr.kind + && self.expr_ty.has_infer() + { + let idx_ty = fcx.resolve_vars_if_possible(fcx.node_ty(idx.hir_id)); + if idx_ty.is_ty_var() { + let resolved = fcx.structurally_resolve_type(idx.span, idx_ty); + if resolved.references_error() { + self.expr_ty = resolved; + } + } + } + // Skip if idx resolution above already emitted a diagnostic and set expr_ty to error. + if !self.expr_ty.references_error() { + self.expr_ty = fcx.structurally_resolve_type(self.expr_span, self.expr_ty); + } self.cast_ty = fcx.structurally_resolve_type(self.cast_span, self.cast_ty); debug!("check_cast({}, {:?} as {:?})", self.expr.hir_id, self.expr_ty, self.cast_ty); diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs index 1e9986fa761c4..0a67b384ae8de 100644 --- a/compiler/rustc_hir_typeck/src/op.rs +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -313,6 +313,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } self.write_method_call_and_enforce_effects(expr.hir_id, expr.span, method); + + if method.sig.output().has_infer() + && let hir::ExprKind::Index(_, idx, _) = rhs_expr.kind + { + let idx_ty = self.resolve_vars_if_possible(self.node_ty(idx.hir_id)); + if idx_ty.is_ty_var() { + self.structurally_resolve_type(idx.span, idx_ty); + } + } + method.sig.output() } // error types are considered "builtin" diff --git a/tests/ui/type-inference/index-expr-ambiguous-type.rs b/tests/ui/type-inference/index-expr-ambiguous-type.rs new file mode 100644 index 0000000000000..32b45703e2003 --- /dev/null +++ b/tests/ui/type-inference/index-expr-ambiguous-type.rs @@ -0,0 +1,30 @@ +// Regression test for #156738 +// +// When the index type in `arr[idx]` is ambiguous, the error should point +// at the index sub-expression, not the whole indexing expression or +// surrounding operators. + +fn with_cast() { + let bad_idx = 0u8; + let _foo = [1, 2, 3][bad_idx.into()] as i32; + //~^ ERROR type annotations needed +} + +fn with_binop() { + let bad_idx = 0u8; + let _foo = 0 + [1, 2, 3][bad_idx.into()]; + //~^ ERROR type annotations needed +} + +fn standalone() { + let bad_idx = 0u8; + let _foo = [1, 2, 3][bad_idx.into()]; + //~^ ERROR type annotations needed +} + +fn with_known_index_type() { + let bad_idx = 0u8; + let _foo = [1, 2, 3][Into::::into(bad_idx)] as i32; +} + +fn main() {} diff --git a/tests/ui/type-inference/index-expr-ambiguous-type.stderr b/tests/ui/type-inference/index-expr-ambiguous-type.stderr new file mode 100644 index 0000000000000..80c0aa6c4a952 --- /dev/null +++ b/tests/ui/type-inference/index-expr-ambiguous-type.stderr @@ -0,0 +1,42 @@ +error[E0282]: type annotations needed + --> $DIR/index-expr-ambiguous-type.rs:9:34 + | +LL | let _foo = [1, 2, 3][bad_idx.into()] as i32; + | ^^^^ + | +help: try using a fully qualified path to specify the expected types + | +LL - let _foo = [1, 2, 3][bad_idx.into()] as i32; +LL + let _foo = [1, 2, 3][>::into(bad_idx)] as i32; + | + +error[E0282]: type annotations needed + --> $DIR/index-expr-ambiguous-type.rs:15:38 + | +LL | let _foo = 0 + [1, 2, 3][bad_idx.into()]; + | ^^^^ + | +help: try using a fully qualified path to specify the expected types + | +LL - let _foo = 0 + [1, 2, 3][bad_idx.into()]; +LL + let _foo = 0 + [1, 2, 3][>::into(bad_idx)]; + | + +error[E0283]: type annotations needed + --> $DIR/index-expr-ambiguous-type.rs:21:34 + | +LL | let _foo = [1, 2, 3][bad_idx.into()]; + | ^^^^ + | + = note: the type must implement `From` + = note: required for `u8` to implement `Into<_>` +help: try using a fully qualified path to specify the expected types + | +LL - let _foo = [1, 2, 3][bad_idx.into()]; +LL + let _foo = [1, 2, 3][>::into(bad_idx)]; + | + +error: aborting due to 3 previous errors + +Some errors have detailed explanations: E0282, E0283. +For more information about an error, try `rustc --explain E0282`. From a09fc01c54cb4ba2e085612f8553fb43484964c0 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Fri, 19 Jun 2026 14:57:58 -0300 Subject: [PATCH 2/2] Avoid forcing index operand inference Adjust ambiguous index diagnostics without adding new index operand resolution points. Keep invalid operator and mismatched operand diagnostics on the operator while still pointing valid ambiguous index cases at the index operand. --- compiler/rustc_hir_typeck/src/cast.rs | 33 +++-- .../src/fn_ctxt/adjust_fulfillment_errors.rs | 122 +++++++++++++++++- compiler/rustc_hir_typeck/src/op.rs | 9 -- .../index-expr-ambiguous-type.rs | 24 ++++ .../index-expr-ambiguous-type.stderr | 60 ++++++--- 5 files changed, 208 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/cast.rs b/compiler/rustc_hir_typeck/src/cast.rs index 97ff7765896a8..8140788a7d988 100644 --- a/compiler/rustc_hir_typeck/src/cast.rs +++ b/compiler/rustc_hir_typeck/src/cast.rs @@ -725,23 +725,21 @@ impl<'a, 'tcx> CastCheck<'tcx> { ); } - #[instrument(skip(fcx), level = "debug")] - pub(crate) fn check(mut self, fcx: &FnCtxt<'a, 'tcx>) { + fn expr_span_for_type_resolution(&self, fcx: &FnCtxt<'a, 'tcx>) -> Span { if let hir::ExprKind::Index(_, idx, _) = self.expr.kind - && self.expr_ty.has_infer() + && fcx.resolve_vars_if_possible(self.expr_ty).is_ty_var() + && fcx.resolve_vars_if_possible(fcx.node_ty(idx.hir_id)).is_ty_var() { - let idx_ty = fcx.resolve_vars_if_possible(fcx.node_ty(idx.hir_id)); - if idx_ty.is_ty_var() { - let resolved = fcx.structurally_resolve_type(idx.span, idx_ty); - if resolved.references_error() { - self.expr_ty = resolved; - } - } - } - // Skip if idx resolution above already emitted a diagnostic and set expr_ty to error. - if !self.expr_ty.references_error() { - self.expr_ty = fcx.structurally_resolve_type(self.expr_span, self.expr_ty); + index_operand_ambiguity_span(idx) + } else { + self.expr_span } + } + + #[instrument(skip(fcx), level = "debug")] + pub(crate) fn check(mut self, fcx: &FnCtxt<'a, 'tcx>) { + let expr_span = self.expr_span_for_type_resolution(fcx); + self.expr_ty = fcx.structurally_resolve_type(expr_span, self.expr_ty); self.cast_ty = fcx.structurally_resolve_type(self.cast_span, self.cast_ty); debug!("check_cast({}, {:?} as {:?})", self.expr.hir_id, self.expr_ty, self.cast_ty); @@ -1222,3 +1220,10 @@ impl<'a, 'tcx> CastCheck<'tcx> { } } } + +fn index_operand_ambiguity_span(expr: &hir::Expr<'_>) -> Span { + match expr.kind { + hir::ExprKind::MethodCall(segment, ..) => segment.ident.span, + _ => expr.span, + } +} diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs index 825aa37065e15..2d436ec82b20e 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs @@ -4,8 +4,11 @@ use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_infer::traits::ObligationCauseCode; -use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; +use rustc_middle::ty::{ + self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, +}; use rustc_span::{Span, kw}; +use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits; use crate::FnCtxt; @@ -37,6 +40,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, error: &mut traits::FulfillmentError<'tcx>, ) -> bool { + if self.adjust_binop_index_operand(error) { + return true; + } + let (def_id, hir_id, idx, flavor) = match *error.obligation.cause.code().peel_derives() { ObligationCauseCode::WhereClauseInExpr(def_id, _, hir_id, idx) => { (def_id, hir_id, idx, ClauseFlavor::Where) @@ -165,6 +172,119 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn adjust_binop_index_operand(&self, error: &mut traits::FulfillmentError<'tcx>) -> bool { + let ObligationCauseCode::BinOp { lhs_hir_id, rhs_hir_id, .. } = + *error.obligation.cause.code().peel_derives() + else { + return false; + }; + if !matches!(error.code, traits::FulfillmentErrorCode::Ambiguity { .. }) + || !error.obligation.predicate.has_infer() + { + return false; + } + + let hir::Node::Expr(lhs_expr) = self.tcx.hir_node(lhs_hir_id) else { + return false; + }; + let hir::Node::Expr(rhs_expr) = self.tcx.hir_node(rhs_hir_id) else { + return false; + }; + let Some(binop) = self.binop_for_operands(lhs_hir_id, rhs_hir_id) else { + return false; + }; + let hir::ExprKind::Index(indexed_expr, idx, _) = rhs_expr.kind else { + return false; + }; + if !self.resolve_vars_if_possible(self.node_ty(idx.hir_id)).is_ty_var() { + return false; + } + let lhs_ty = self.resolve_vars_if_possible(self.node_ty(lhs_expr.hir_id)); + let indexed_ty = self.resolve_vars_if_possible(self.node_ty(indexed_expr.hir_id)); + let rhs_ty = match *indexed_ty.kind() { + ty::Array(element_ty, _) | ty::Slice(element_ty) => element_ty, + ty::Ref(_, pointee_ty, _) => match *pointee_ty.kind() { + ty::Array(element_ty, _) | ty::Slice(element_ty) => element_ty, + _ => self.resolve_vars_if_possible(self.node_ty(rhs_expr.hir_id)), + }, + _ => self.resolve_vars_if_possible(self.node_ty(rhs_expr.hir_id)), + }; + if !self.binop_accepts_types(binop.node, lhs_ty, rhs_ty) { + return false; + } + + error.obligation.cause.span = match idx.kind { + hir::ExprKind::MethodCall(segment, ..) => segment.ident.span, + _ => idx.span, + }; + true + } + + fn binop_for_operands( + &self, + lhs_hir_id: hir::HirId, + rhs_hir_id: hir::HirId, + ) -> Option { + let hir::Node::Expr(parent_expr) = self.tcx.parent_hir_node(rhs_hir_id) else { + return None; + }; + let hir::ExprKind::Binary(binop, lhs_expr, rhs_expr) = parent_expr.kind else { + return None; + }; + (lhs_expr.hir_id == lhs_hir_id && rhs_expr.hir_id == rhs_hir_id).then_some(binop) + } + + fn binop_accepts_types( + &self, + binop: hir::BinOpKind, + lhs_ty: Ty<'tcx>, + rhs_ty: Ty<'tcx>, + ) -> bool { + let lhs_ty = self.deref_ty_if_possible(lhs_ty); + let rhs_ty = self.deref_ty_if_possible(rhs_ty); + if lhs_ty.references_error() || rhs_ty.references_error() { + return true; + } + + match binop { + hir::BinOpKind::Shl | hir::BinOpKind::Shr => { + lhs_ty.is_integral() && rhs_ty.is_integral() + } + hir::BinOpKind::Add + | hir::BinOpKind::Sub + | hir::BinOpKind::Mul + | hir::BinOpKind::Div + | hir::BinOpKind::Rem => { + self.can_eq(self.param_env, lhs_ty, rhs_ty) + && (lhs_ty.is_integral() || lhs_ty.is_floating_point()) + && (rhs_ty.is_integral() || rhs_ty.is_floating_point()) + } + hir::BinOpKind::BitXor | hir::BinOpKind::BitAnd | hir::BinOpKind::BitOr => { + self.can_eq(self.param_env, lhs_ty, rhs_ty) + && ((lhs_ty.is_integral() && rhs_ty.is_integral()) + || (lhs_ty.is_bool() && rhs_ty.is_bool())) + } + hir::BinOpKind::Eq + | hir::BinOpKind::Ne + | hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt => { + self.can_eq(self.param_env, lhs_ty, rhs_ty) + && lhs_ty.is_scalar() + && rhs_ty.is_scalar() + } + hir::BinOpKind::And | hir::BinOpKind::Or => lhs_ty.is_bool() && rhs_ty.is_bool(), + } + } + + fn deref_ty_if_possible(&self, ty: Ty<'tcx>) -> Ty<'tcx> { + match ty.kind() { + ty::Ref(_, ty, hir::Mutability::Not) => *ty, + _ => ty, + } + } + fn point_at_expr_if_possible( &self, error: &mut traits::FulfillmentError<'tcx>, diff --git a/compiler/rustc_hir_typeck/src/op.rs b/compiler/rustc_hir_typeck/src/op.rs index 0a67b384ae8de..88a4241398ed8 100644 --- a/compiler/rustc_hir_typeck/src/op.rs +++ b/compiler/rustc_hir_typeck/src/op.rs @@ -314,15 +314,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_method_call_and_enforce_effects(expr.hir_id, expr.span, method); - if method.sig.output().has_infer() - && let hir::ExprKind::Index(_, idx, _) = rhs_expr.kind - { - let idx_ty = self.resolve_vars_if_possible(self.node_ty(idx.hir_id)); - if idx_ty.is_ty_var() { - self.structurally_resolve_type(idx.span, idx_ty); - } - } - method.sig.output() } // error types are considered "builtin" diff --git a/tests/ui/type-inference/index-expr-ambiguous-type.rs b/tests/ui/type-inference/index-expr-ambiguous-type.rs index 32b45703e2003..061699c8a013f 100644 --- a/tests/ui/type-inference/index-expr-ambiguous-type.rs +++ b/tests/ui/type-inference/index-expr-ambiguous-type.rs @@ -27,4 +27,28 @@ fn with_known_index_type() { let _foo = [1, 2, 3][Into::::into(bad_idx)] as i32; } +fn invalid_operator_with_ambiguous_index() { + let bad_idx = 0u8; + let _foo = true + [1, 2, 3][bad_idx.into()]; + //~^ ERROR cannot add +} + +fn mismatched_numeric_binop_with_ambiguous_index() { + let bad_idx = 0u8; + let _foo = 0u64 + [1i32, 2, 3][bad_idx.into()]; + //~^ ERROR type annotations needed +} + +fn shift_with_ambiguous_index() { + let bad_idx = 0u8; + let _foo = 1u32 << [0u8][bad_idx.into()]; + //~^ ERROR type annotations needed +} + +fn string_add_with_ambiguous_index() { + let bad_idx = 0u8; + let _foo = String::new() + [""][bad_idx.into()]; + //~^ ERROR type annotations needed +} + fn main() {} diff --git a/tests/ui/type-inference/index-expr-ambiguous-type.stderr b/tests/ui/type-inference/index-expr-ambiguous-type.stderr index 80c0aa6c4a952..83de98d80cae6 100644 --- a/tests/ui/type-inference/index-expr-ambiguous-type.stderr +++ b/tests/ui/type-inference/index-expr-ambiguous-type.stderr @@ -2,25 +2,15 @@ error[E0282]: type annotations needed --> $DIR/index-expr-ambiguous-type.rs:9:34 | LL | let _foo = [1, 2, 3][bad_idx.into()] as i32; - | ^^^^ - | -help: try using a fully qualified path to specify the expected types - | -LL - let _foo = [1, 2, 3][bad_idx.into()] as i32; -LL + let _foo = [1, 2, 3][>::into(bad_idx)] as i32; - | + | ^^^^ cannot infer type -error[E0282]: type annotations needed +error[E0284]: type annotations needed --> $DIR/index-expr-ambiguous-type.rs:15:38 | LL | let _foo = 0 + [1, 2, 3][bad_idx.into()]; - | ^^^^ - | -help: try using a fully qualified path to specify the expected types - | -LL - let _foo = 0 + [1, 2, 3][bad_idx.into()]; -LL + let _foo = 0 + [1, 2, 3][>::into(bad_idx)]; + | ^^^^ cannot infer type | + = note: cannot satisfy `>::Output == _` error[E0283]: type annotations needed --> $DIR/index-expr-ambiguous-type.rs:21:34 @@ -36,7 +26,45 @@ LL - let _foo = [1, 2, 3][bad_idx.into()]; LL + let _foo = [1, 2, 3][>::into(bad_idx)]; | -error: aborting due to 3 previous errors +error[E0369]: cannot add `_` to `bool` + --> $DIR/index-expr-ambiguous-type.rs:32:21 + | +LL | let _foo = true + [1, 2, 3][bad_idx.into()]; + | ---- ^ ------------------------- _ + | | + | bool + +error[E0284]: type annotations needed + --> $DIR/index-expr-ambiguous-type.rs:38:21 + | +LL | let _foo = 0u64 + [1i32, 2, 3][bad_idx.into()]; + | ^ cannot infer type + | + = note: cannot satisfy `>::Output == _` + +error[E0284]: type annotations needed + --> $DIR/index-expr-ambiguous-type.rs:44:38 + | +LL | let _foo = 1u32 << [0u8][bad_idx.into()]; + | ^^^^ cannot infer type + | + = note: cannot satisfy `>::Output == _` + +error[E0283]: type annotations needed + --> $DIR/index-expr-ambiguous-type.rs:50:45 + | +LL | let _foo = String::new() + [""][bad_idx.into()]; + | ^^^^ + | + = note: the type must implement `From` + = note: required for `u8` to implement `Into<_>` +help: try using a fully qualified path to specify the expected types + | +LL - let _foo = String::new() + [""][bad_idx.into()]; +LL + let _foo = String::new() + [""][>::into(bad_idx)]; + | + +error: aborting due to 7 previous errors -Some errors have detailed explanations: E0282, E0283. +Some errors have detailed explanations: E0282, E0283, E0284, E0369. For more information about an error, try `rustc --explain E0282`.