Skip to content

Commit f7ba114

Browse files
authored
Rollup merge of #156885 - Dnreikronos:fix/index-expr-ambiguous-type-span, r=BoxyUwU
Fix misattributed type inference error span for index expressions If `arr[idx.into()]` appears inside a cast or binary op, the type inference error gets blamed on the wrong expression. **Before:** - `[1, 2, 3][bad_idx.into()] as i32` → error points at `[1, 2, 3][bad_idx.into()]` (the whole indexing expr) - `0 + [1, 2, 3][bad_idx.into()]` → error points at `+` **After:** - Both cases point at `.into()`, which is where the ambiguity actually is. In `cast.rs`, before resolving the cast expression type, we check if it's an index with an unresolved index type. If so, resolve the index sub-expression first at its own span so the error lands there. If that already errored, skip the broader resolution to avoid duplicates. In `op.rs`, after writing the method call for an overloaded binary op, if the return type still has inference variables and the RHS is an index expression, force resolution of the index type at its span. Fixes #156738 r? compiler
2 parents a214ed1 + a09fc01 commit f7ba114

5 files changed

Lines changed: 266 additions & 2 deletions

File tree

compiler/rustc_hir_typeck/src/cast.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -739,9 +739,21 @@ impl<'a, 'tcx> CastCheck<'tcx> {
739739
);
740740
}
741741

742+
fn expr_span_for_type_resolution(&self, fcx: &FnCtxt<'a, 'tcx>) -> Span {
743+
if let hir::ExprKind::Index(_, idx, _) = self.expr.kind
744+
&& fcx.resolve_vars_if_possible(self.expr_ty).is_ty_var()
745+
&& fcx.resolve_vars_if_possible(fcx.node_ty(idx.hir_id)).is_ty_var()
746+
{
747+
index_operand_ambiguity_span(idx)
748+
} else {
749+
self.expr_span
750+
}
751+
}
752+
742753
#[instrument(skip(fcx), level = "debug")]
743754
pub(crate) fn check(mut self, fcx: &FnCtxt<'a, 'tcx>) {
744-
self.expr_ty = fcx.structurally_resolve_type(self.expr_span, self.expr_ty);
755+
let expr_span = self.expr_span_for_type_resolution(fcx);
756+
self.expr_ty = fcx.structurally_resolve_type(expr_span, self.expr_ty);
745757
self.cast_ty = fcx.structurally_resolve_type(self.cast_span, self.cast_ty);
746758

747759
debug!("check_cast({}, {:?} as {:?})", self.expr.hir_id, self.expr_ty, self.cast_ty);
@@ -1169,3 +1181,10 @@ impl<'a, 'tcx> CastCheck<'tcx> {
11691181
}
11701182
}
11711183
}
1184+
1185+
fn index_operand_ambiguity_span(expr: &hir::Expr<'_>) -> Span {
1186+
match expr.kind {
1187+
hir::ExprKind::MethodCall(segment, ..) => segment.ident.span,
1188+
_ => expr.span,
1189+
}
1190+
}

compiler/rustc_hir_typeck/src/fn_ctxt/adjust_fulfillment_errors.rs

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ use rustc_hir as hir;
44
use rustc_hir::def::{DefKind, Res};
55
use rustc_hir::def_id::DefId;
66
use rustc_infer::traits::ObligationCauseCode;
7-
use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
7+
use rustc_middle::ty::{
8+
self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
9+
};
810
use rustc_span::{Span, kw};
11+
use rustc_trait_selection::infer::InferCtxtExt;
912
use rustc_trait_selection::traits;
1013

1114
use crate::FnCtxt;
@@ -37,6 +40,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
3740
&self,
3841
error: &mut traits::FulfillmentError<'tcx>,
3942
) -> bool {
43+
if self.adjust_binop_index_operand(error) {
44+
return true;
45+
}
46+
4047
let (def_id, hir_id, idx, flavor) = match *error.obligation.cause.code().peel_derives() {
4148
ObligationCauseCode::WhereClauseInExpr(def_id, _, hir_id, idx) => {
4249
(def_id, hir_id, idx, ClauseFlavor::Where)
@@ -165,6 +172,119 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
165172
}
166173
}
167174

175+
fn adjust_binop_index_operand(&self, error: &mut traits::FulfillmentError<'tcx>) -> bool {
176+
let ObligationCauseCode::BinOp { lhs_hir_id, rhs_hir_id, .. } =
177+
*error.obligation.cause.code().peel_derives()
178+
else {
179+
return false;
180+
};
181+
if !matches!(error.code, traits::FulfillmentErrorCode::Ambiguity { .. })
182+
|| !error.obligation.predicate.has_infer()
183+
{
184+
return false;
185+
}
186+
187+
let hir::Node::Expr(lhs_expr) = self.tcx.hir_node(lhs_hir_id) else {
188+
return false;
189+
};
190+
let hir::Node::Expr(rhs_expr) = self.tcx.hir_node(rhs_hir_id) else {
191+
return false;
192+
};
193+
let Some(binop) = self.binop_for_operands(lhs_hir_id, rhs_hir_id) else {
194+
return false;
195+
};
196+
let hir::ExprKind::Index(indexed_expr, idx, _) = rhs_expr.kind else {
197+
return false;
198+
};
199+
if !self.resolve_vars_if_possible(self.node_ty(idx.hir_id)).is_ty_var() {
200+
return false;
201+
}
202+
let lhs_ty = self.resolve_vars_if_possible(self.node_ty(lhs_expr.hir_id));
203+
let indexed_ty = self.resolve_vars_if_possible(self.node_ty(indexed_expr.hir_id));
204+
let rhs_ty = match *indexed_ty.kind() {
205+
ty::Array(element_ty, _) | ty::Slice(element_ty) => element_ty,
206+
ty::Ref(_, pointee_ty, _) => match *pointee_ty.kind() {
207+
ty::Array(element_ty, _) | ty::Slice(element_ty) => element_ty,
208+
_ => self.resolve_vars_if_possible(self.node_ty(rhs_expr.hir_id)),
209+
},
210+
_ => self.resolve_vars_if_possible(self.node_ty(rhs_expr.hir_id)),
211+
};
212+
if !self.binop_accepts_types(binop.node, lhs_ty, rhs_ty) {
213+
return false;
214+
}
215+
216+
error.obligation.cause.span = match idx.kind {
217+
hir::ExprKind::MethodCall(segment, ..) => segment.ident.span,
218+
_ => idx.span,
219+
};
220+
true
221+
}
222+
223+
fn binop_for_operands(
224+
&self,
225+
lhs_hir_id: hir::HirId,
226+
rhs_hir_id: hir::HirId,
227+
) -> Option<hir::BinOp> {
228+
let hir::Node::Expr(parent_expr) = self.tcx.parent_hir_node(rhs_hir_id) else {
229+
return None;
230+
};
231+
let hir::ExprKind::Binary(binop, lhs_expr, rhs_expr) = parent_expr.kind else {
232+
return None;
233+
};
234+
(lhs_expr.hir_id == lhs_hir_id && rhs_expr.hir_id == rhs_hir_id).then_some(binop)
235+
}
236+
237+
fn binop_accepts_types(
238+
&self,
239+
binop: hir::BinOpKind,
240+
lhs_ty: Ty<'tcx>,
241+
rhs_ty: Ty<'tcx>,
242+
) -> bool {
243+
let lhs_ty = self.deref_ty_if_possible(lhs_ty);
244+
let rhs_ty = self.deref_ty_if_possible(rhs_ty);
245+
if lhs_ty.references_error() || rhs_ty.references_error() {
246+
return true;
247+
}
248+
249+
match binop {
250+
hir::BinOpKind::Shl | hir::BinOpKind::Shr => {
251+
lhs_ty.is_integral() && rhs_ty.is_integral()
252+
}
253+
hir::BinOpKind::Add
254+
| hir::BinOpKind::Sub
255+
| hir::BinOpKind::Mul
256+
| hir::BinOpKind::Div
257+
| hir::BinOpKind::Rem => {
258+
self.can_eq(self.param_env, lhs_ty, rhs_ty)
259+
&& (lhs_ty.is_integral() || lhs_ty.is_floating_point())
260+
&& (rhs_ty.is_integral() || rhs_ty.is_floating_point())
261+
}
262+
hir::BinOpKind::BitXor | hir::BinOpKind::BitAnd | hir::BinOpKind::BitOr => {
263+
self.can_eq(self.param_env, lhs_ty, rhs_ty)
264+
&& ((lhs_ty.is_integral() && rhs_ty.is_integral())
265+
|| (lhs_ty.is_bool() && rhs_ty.is_bool()))
266+
}
267+
hir::BinOpKind::Eq
268+
| hir::BinOpKind::Ne
269+
| hir::BinOpKind::Lt
270+
| hir::BinOpKind::Le
271+
| hir::BinOpKind::Ge
272+
| hir::BinOpKind::Gt => {
273+
self.can_eq(self.param_env, lhs_ty, rhs_ty)
274+
&& lhs_ty.is_scalar()
275+
&& rhs_ty.is_scalar()
276+
}
277+
hir::BinOpKind::And | hir::BinOpKind::Or => lhs_ty.is_bool() && rhs_ty.is_bool(),
278+
}
279+
}
280+
281+
fn deref_ty_if_possible(&self, ty: Ty<'tcx>) -> Ty<'tcx> {
282+
match ty.kind() {
283+
ty::Ref(_, ty, hir::Mutability::Not) => *ty,
284+
_ => ty,
285+
}
286+
}
287+
168288
fn point_at_expr_if_possible(
169289
&self,
170290
error: &mut traits::FulfillmentError<'tcx>,

compiler/rustc_hir_typeck/src/op.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
313313
}
314314

315315
self.write_method_call_and_enforce_effects(expr.hir_id, expr.span, method);
316+
316317
method.sig.output()
317318
}
318319
// error types are considered "builtin"
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Regression test for #156738
2+
//
3+
// When the index type in `arr[idx]` is ambiguous, the error should point
4+
// at the index sub-expression, not the whole indexing expression or
5+
// surrounding operators.
6+
7+
fn with_cast() {
8+
let bad_idx = 0u8;
9+
let _foo = [1, 2, 3][bad_idx.into()] as i32;
10+
//~^ ERROR type annotations needed
11+
}
12+
13+
fn with_binop() {
14+
let bad_idx = 0u8;
15+
let _foo = 0 + [1, 2, 3][bad_idx.into()];
16+
//~^ ERROR type annotations needed
17+
}
18+
19+
fn standalone() {
20+
let bad_idx = 0u8;
21+
let _foo = [1, 2, 3][bad_idx.into()];
22+
//~^ ERROR type annotations needed
23+
}
24+
25+
fn with_known_index_type() {
26+
let bad_idx = 0u8;
27+
let _foo = [1, 2, 3][Into::<usize>::into(bad_idx)] as i32;
28+
}
29+
30+
fn invalid_operator_with_ambiguous_index() {
31+
let bad_idx = 0u8;
32+
let _foo = true + [1, 2, 3][bad_idx.into()];
33+
//~^ ERROR cannot add
34+
}
35+
36+
fn mismatched_numeric_binop_with_ambiguous_index() {
37+
let bad_idx = 0u8;
38+
let _foo = 0u64 + [1i32, 2, 3][bad_idx.into()];
39+
//~^ ERROR type annotations needed
40+
}
41+
42+
fn shift_with_ambiguous_index() {
43+
let bad_idx = 0u8;
44+
let _foo = 1u32 << [0u8][bad_idx.into()];
45+
//~^ ERROR type annotations needed
46+
}
47+
48+
fn string_add_with_ambiguous_index() {
49+
let bad_idx = 0u8;
50+
let _foo = String::new() + [""][bad_idx.into()];
51+
//~^ ERROR type annotations needed
52+
}
53+
54+
fn main() {}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
error[E0282]: type annotations needed
2+
--> $DIR/index-expr-ambiguous-type.rs:9:34
3+
|
4+
LL | let _foo = [1, 2, 3][bad_idx.into()] as i32;
5+
| ^^^^ cannot infer type
6+
7+
error[E0284]: type annotations needed
8+
--> $DIR/index-expr-ambiguous-type.rs:15:38
9+
|
10+
LL | let _foo = 0 + [1, 2, 3][bad_idx.into()];
11+
| ^^^^ cannot infer type
12+
|
13+
= note: cannot satisfy `<i32 as Add<_>>::Output == _`
14+
15+
error[E0283]: type annotations needed
16+
--> $DIR/index-expr-ambiguous-type.rs:21:34
17+
|
18+
LL | let _foo = [1, 2, 3][bad_idx.into()];
19+
| ^^^^
20+
|
21+
= note: the type must implement `From<u8>`
22+
= note: required for `u8` to implement `Into<_>`
23+
help: try using a fully qualified path to specify the expected types
24+
|
25+
LL - let _foo = [1, 2, 3][bad_idx.into()];
26+
LL + let _foo = [1, 2, 3][<u8 as Into<T>>::into(bad_idx)];
27+
|
28+
29+
error[E0369]: cannot add `_` to `bool`
30+
--> $DIR/index-expr-ambiguous-type.rs:32:21
31+
|
32+
LL | let _foo = true + [1, 2, 3][bad_idx.into()];
33+
| ---- ^ ------------------------- _
34+
| |
35+
| bool
36+
37+
error[E0284]: type annotations needed
38+
--> $DIR/index-expr-ambiguous-type.rs:38:21
39+
|
40+
LL | let _foo = 0u64 + [1i32, 2, 3][bad_idx.into()];
41+
| ^ cannot infer type
42+
|
43+
= note: cannot satisfy `<u64 as Add<_>>::Output == _`
44+
45+
error[E0284]: type annotations needed
46+
--> $DIR/index-expr-ambiguous-type.rs:44:38
47+
|
48+
LL | let _foo = 1u32 << [0u8][bad_idx.into()];
49+
| ^^^^ cannot infer type
50+
|
51+
= note: cannot satisfy `<u32 as Shl<_>>::Output == _`
52+
53+
error[E0283]: type annotations needed
54+
--> $DIR/index-expr-ambiguous-type.rs:50:45
55+
|
56+
LL | let _foo = String::new() + [""][bad_idx.into()];
57+
| ^^^^
58+
|
59+
= note: the type must implement `From<u8>`
60+
= note: required for `u8` to implement `Into<_>`
61+
help: try using a fully qualified path to specify the expected types
62+
|
63+
LL - let _foo = String::new() + [""][bad_idx.into()];
64+
LL + let _foo = String::new() + [""][<u8 as Into<T>>::into(bad_idx)];
65+
|
66+
67+
error: aborting due to 7 previous errors
68+
69+
Some errors have detailed explanations: E0282, E0283, E0284, E0369.
70+
For more information about an error, try `rustc --explain E0282`.

0 commit comments

Comments
 (0)