Skip to content

Commit 136d74f

Browse files
committed
Auto merge of #116071 - estebank:issue-115905, r=compiler-errors
Point at cause of expectation of `break` value when possible When encountering a type error within the value of a `break` statement, climb the HIR tree to identify if the expectation comes from an assignment or a return type (if the loop is the tail expression of a `fn`). Fix #115905.
2 parents bf98263 + d3dea30 commit 136d74f

File tree

4 files changed

+100
-10
lines changed

4 files changed

+100
-10
lines changed

Diff for: compiler/rustc_hir_typeck/src/_match.rs

+14-9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::coercion::{AsCoercionSite, CoerceMany};
22
use crate::{Diverges, Expectation, FnCtxt, Needs};
33
use rustc_errors::Diagnostic;
44
use rustc_hir::{self as hir, ExprKind};
5+
use rustc_hir_pretty::ty_to_string;
56
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
67
use rustc_infer::traits::Obligation;
78
use rustc_middle::ty::{self, Ty};
@@ -252,7 +253,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
252253
{
253254
// If this `if` expr is the parent's function return expr,
254255
// the cause of the type coercion is the return type, point at it. (#25228)
255-
let ret_reason = self.maybe_get_coercion_reason(then_expr.hir_id, span);
256+
let hir_id = self.tcx.hir().parent_id(self.tcx.hir().parent_id(then_expr.hir_id));
257+
let ret_reason = self.maybe_get_coercion_reason(hir_id, span);
256258
let cause = self.cause(span, ObligationCauseCode::IfExpressionWithNoElse);
257259
let mut error = false;
258260
coercion.coerce_forced_unit(
@@ -275,11 +277,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
275277
error
276278
}
277279

278-
fn maybe_get_coercion_reason(&self, hir_id: hir::HirId, sp: Span) -> Option<(Span, String)> {
279-
let node = {
280-
let rslt = self.tcx.hir().parent_id(self.tcx.hir().parent_id(hir_id));
281-
self.tcx.hir().get(rslt)
282-
};
280+
pub fn maybe_get_coercion_reason(
281+
&self,
282+
hir_id: hir::HirId,
283+
sp: Span,
284+
) -> Option<(Span, String)> {
285+
let node = self.tcx.hir().get(hir_id);
283286
if let hir::Node::Block(block) = node {
284287
// check that the body's parent is an fn
285288
let parent = self.tcx.hir().get_parent(self.tcx.hir().parent_id(block.hir_id));
@@ -289,9 +292,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
289292
// check that the `if` expr without `else` is the fn body's expr
290293
if expr.span == sp {
291294
return self.get_fn_decl(hir_id).and_then(|(_, fn_decl, _)| {
292-
let span = fn_decl.output.span();
293-
let snippet = self.tcx.sess.source_map().span_to_snippet(span).ok()?;
294-
Some((span, format!("expected `{snippet}` because of this return type")))
295+
let (ty, span) = match fn_decl.output {
296+
hir::FnRetTy::DefaultReturn(span) => ("()".to_string(), span),
297+
hir::FnRetTy::Return(ty) => (ty_to_string(ty), ty.span),
298+
};
299+
Some((span, format!("expected `{ty}` because of this return type")))
295300
});
296301
}
297302
}

Diff for: compiler/rustc_hir_typeck/src/demand.rs

+62
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8383
}
8484

8585
self.annotate_expected_due_to_let_ty(err, expr, error);
86+
self.annotate_loop_expected_due_to_inference(err, expr, error);
8687

8788
// FIXME(#73154): For now, we do leak check when coercing function
8889
// pointers in typeck, instead of only during borrowck. This can lead
@@ -527,6 +528,67 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
527528
false
528529
}
529530

531+
// When encountering a type error on the value of a `break`, try to point at the reason for the
532+
// expected type.
533+
fn annotate_loop_expected_due_to_inference(
534+
&self,
535+
err: &mut Diagnostic,
536+
expr: &hir::Expr<'_>,
537+
error: Option<TypeError<'tcx>>,
538+
) {
539+
let Some(TypeError::Sorts(ExpectedFound { expected, .. })) = error else {
540+
return;
541+
};
542+
let mut parent_id = self.tcx.hir().parent_id(expr.hir_id);
543+
loop {
544+
// Climb the HIR tree to see if the current `Expr` is part of a `break;` statement.
545+
let Some(hir::Node::Expr(parent)) = self.tcx.hir().find(parent_id) else {
546+
break;
547+
};
548+
parent_id = self.tcx.hir().parent_id(parent.hir_id);
549+
let hir::ExprKind::Break(destination, _) = parent.kind else {
550+
continue;
551+
};
552+
let mut parent_id = parent.hir_id;
553+
loop {
554+
// Climb the HIR tree to find the (desugared) `loop` this `break` corresponds to.
555+
let parent = match self.tcx.hir().find(parent_id) {
556+
Some(hir::Node::Expr(&ref parent)) => {
557+
parent_id = self.tcx.hir().parent_id(parent.hir_id);
558+
parent
559+
}
560+
Some(hir::Node::Stmt(hir::Stmt {
561+
hir_id,
562+
kind: hir::StmtKind::Semi(&ref parent) | hir::StmtKind::Expr(&ref parent),
563+
..
564+
})) => {
565+
parent_id = self.tcx.hir().parent_id(*hir_id);
566+
parent
567+
}
568+
Some(hir::Node::Block(hir::Block { .. })) => {
569+
parent_id = self.tcx.hir().parent_id(parent_id);
570+
parent
571+
}
572+
_ => break,
573+
};
574+
if let hir::ExprKind::Loop(_, label, _, span) = parent.kind
575+
&& destination.label == label
576+
{
577+
if let Some((reason_span, message)) =
578+
self.maybe_get_coercion_reason(parent_id, parent.span)
579+
{
580+
err.span_label(reason_span, message);
581+
err.span_label(
582+
span,
583+
format!("this loop is expected to be of type `{expected}`"),
584+
);
585+
}
586+
break;
587+
}
588+
}
589+
}
590+
}
591+
530592
fn annotate_expected_due_to_let_ty(
531593
&self,
532594
err: &mut Diagnostic,

Diff for: tests/ui/loops/loop-break-value.rs

+3
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,7 @@ fn main() {
9595
break LOOP;
9696
//~^ ERROR cannot find value `LOOP` in this scope
9797
}
98+
loop { // point at the return type
99+
break 2; //~ ERROR mismatched types
100+
}
98101
}

Diff for: tests/ui/loops/loop-break-value.stderr

+21-1
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,21 @@ LL | break 123;
148148
error[E0308]: mismatched types
149149
--> $DIR/loop-break-value.rs:16:15
150150
|
151+
LL | let _: i32 = loop {
152+
| - ---- this loop is expected to be of type `i32`
153+
| |
154+
| expected because of this assignment
151155
LL | break "asdf";
152156
| ^^^^^^ expected `i32`, found `&str`
153157

154158
error[E0308]: mismatched types
155159
--> $DIR/loop-break-value.rs:21:31
156160
|
161+
LL | let _: i32 = 'outer_loop: loop {
162+
| - ---- this loop is expected to be of type `i32`
163+
| |
164+
| expected because of this assignment
165+
LL | loop {
157166
LL | break 'outer_loop "nope";
158167
| ^^^^^^ expected `i32`, found `&str`
159168

@@ -187,7 +196,18 @@ LL | break;
187196
| expected integer, found `()`
188197
| help: give it a value of the expected type: `break value`
189198

190-
error: aborting due to 17 previous errors; 1 warning emitted
199+
error[E0308]: mismatched types
200+
--> $DIR/loop-break-value.rs:99:15
201+
|
202+
LL | fn main() {
203+
| - expected `()` because of this return type
204+
...
205+
LL | loop { // point at the return type
206+
| ---- this loop is expected to be of type `()`
207+
LL | break 2;
208+
| ^ expected `()`, found integer
209+
210+
error: aborting due to 18 previous errors; 1 warning emitted
191211

192212
Some errors have detailed explanations: E0308, E0425, E0571.
193213
For more information about an error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)