Skip to content

Commit 8110119

Browse files
committed
Properly handle break resolution inside non-breakable expressions
1 parent 1e66a5a commit 8110119

File tree

3 files changed

+163
-17
lines changed

3 files changed

+163
-17
lines changed

crates/hir-ty/src/infer.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,15 +424,39 @@ struct BreakableContext {
424424
coerce: CoerceMany,
425425
/// The optional label of the context.
426426
label: Option<name::Name>,
427+
kind: BreakableKind,
428+
}
429+
430+
#[derive(Clone, Debug)]
431+
enum BreakableKind {
432+
Block,
433+
Loop,
434+
/// A border is something like an async block, closure etc. Anything that prevents
435+
/// breaking/continuing through
436+
Border,
427437
}
428438

429439
fn find_breakable<'c>(
430440
ctxs: &'c mut [BreakableContext],
431441
label: Option<&name::Name>,
442+
) -> Option<&'c mut BreakableContext> {
443+
let mut ctxs = ctxs
444+
.iter_mut()
445+
.rev()
446+
.take_while(|it| matches!(it.kind, BreakableKind::Block | BreakableKind::Loop));
447+
match label {
448+
Some(_) => ctxs.find(|ctx| ctx.label.as_ref() == label),
449+
None => ctxs.find(|ctx| matches!(ctx.kind, BreakableKind::Loop)),
450+
}
451+
}
452+
453+
fn find_continuable<'c>(
454+
ctxs: &'c mut [BreakableContext],
455+
label: Option<&name::Name>,
432456
) -> Option<&'c mut BreakableContext> {
433457
match label {
434-
Some(_) => ctxs.iter_mut().rev().find(|ctx| ctx.label.as_ref() == label),
435-
None => ctxs.last_mut(),
458+
Some(_) => find_breakable(ctxs, label).filter(|it| matches!(it.kind, BreakableKind::Loop)),
459+
None => find_breakable(ctxs, label),
436460
}
437461
}
438462

crates/hir-ty/src/infer/expr.rs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use syntax::ast::RangeOp;
2323
use crate::{
2424
autoderef::{self, Autoderef},
2525
consteval,
26-
infer::coerce::CoerceMany,
26+
infer::{coerce::CoerceMany, find_continuable, BreakableKind},
2727
lower::{
2828
const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode,
2929
},
@@ -120,25 +120,37 @@ impl<'a> InferenceContext<'a> {
120120
let ty = match label {
121121
Some(_) => {
122122
let break_ty = self.table.new_type_var();
123-
let (breaks, ty) =
124-
self.with_breakable_ctx(break_ty.clone(), *label, |this| {
123+
let (breaks, ty) = self.with_breakable_ctx(
124+
BreakableKind::Block,
125+
break_ty.clone(),
126+
*label,
127+
|this| {
125128
this.infer_block(
126129
tgt_expr,
127130
statements,
128131
*tail,
129132
&Expectation::has_type(break_ty),
130133
)
131-
});
134+
},
135+
);
132136
breaks.unwrap_or(ty)
133137
}
134138
None => self.infer_block(tgt_expr, statements, *tail, expected),
135139
};
136140
self.resolver = old_resolver;
137141
ty
138142
}
139-
Expr::Unsafe { body } | Expr::Const { body } => self.infer_expr(*body, expected),
143+
Expr::Unsafe { body } => self.infer_expr(*body, expected),
144+
Expr::Const { body } => {
145+
self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
146+
this.infer_expr(*body, expected)
147+
})
148+
.1
149+
}
140150
Expr::TryBlock { body } => {
141-
let _inner = self.infer_expr(*body, expected);
151+
self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
152+
let _inner = this.infer_expr(*body, expected);
153+
});
142154
// FIXME should be std::result::Result<{inner}, _>
143155
self.err_ty()
144156
}
@@ -147,7 +159,10 @@ impl<'a> InferenceContext<'a> {
147159
let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
148160
let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
149161

150-
let inner_ty = self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty));
162+
let (_, inner_ty) =
163+
self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
164+
this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty))
165+
});
151166

152167
self.diverges = prev_diverges;
153168
self.return_ty = prev_ret_ty;
@@ -161,9 +176,10 @@ impl<'a> InferenceContext<'a> {
161176
}
162177
&Expr::Loop { body, label } => {
163178
let ty = self.table.new_type_var();
164-
let (breaks, ()) = self.with_breakable_ctx(ty, label, |this| {
165-
this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
166-
});
179+
let (breaks, ()) =
180+
self.with_breakable_ctx(BreakableKind::Loop, ty, label, |this| {
181+
this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
182+
});
167183

168184
match breaks {
169185
Some(breaks) => {
@@ -174,7 +190,7 @@ impl<'a> InferenceContext<'a> {
174190
}
175191
}
176192
&Expr::While { condition, body, label } => {
177-
self.with_breakable_ctx(self.err_ty(), label, |this| {
193+
self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| {
178194
this.infer_expr(
179195
condition,
180196
&Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)),
@@ -192,7 +208,7 @@ impl<'a> InferenceContext<'a> {
192208
self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
193209

194210
self.infer_pat(pat, &pat_ty, BindingMode::default());
195-
self.with_breakable_ctx(self.err_ty(), label, |this| {
211+
self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| {
196212
this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
197213
});
198214

@@ -251,7 +267,9 @@ impl<'a> InferenceContext<'a> {
251267
let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
252268
let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
253269

254-
self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty));
270+
self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
271+
this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty));
272+
});
255273

256274
self.diverges = prev_diverges;
257275
self.return_ty = prev_ret_ty;
@@ -355,7 +373,7 @@ impl<'a> InferenceContext<'a> {
355373
self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or_else(|| self.err_ty())
356374
}
357375
Expr::Continue { label } => {
358-
if let None = find_breakable(&mut self.breakables, label.as_ref()) {
376+
if let None = find_continuable(&mut self.breakables, label.as_ref()) {
359377
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
360378
expr: tgt_expr,
361379
is_break: false,
@@ -1466,13 +1484,14 @@ impl<'a> InferenceContext<'a> {
14661484

14671485
fn with_breakable_ctx<T>(
14681486
&mut self,
1487+
kind: BreakableKind,
14691488
ty: Ty,
14701489
label: Option<LabelId>,
14711490
cb: impl FnOnce(&mut Self) -> T,
14721491
) -> (Option<Ty>, T) {
14731492
self.breakables.push({
14741493
let label = label.map(|label| self.body[label].name.clone());
1475-
BreakableContext { may_break: false, coerce: CoerceMany::new(ty), label }
1494+
BreakableContext { kind, may_break: false, coerce: CoerceMany::new(ty), label }
14761495
});
14771496
let res = cb(self);
14781497
let ctx = self.breakables.pop().expect("breakable stack broken");

crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,109 @@ fn foo() {
3333
continue 'a;
3434
//^^^^^^^^^^^ error: continue outside of loop
3535
}
36+
"#,
37+
);
38+
}
39+
40+
#[test]
41+
fn try_blocks_are_borders() {
42+
check_diagnostics(
43+
r#"
44+
fn foo() {
45+
'a: loop {
46+
try {
47+
break;
48+
//^^^^^ error: break outside of loop
49+
break 'a;
50+
//^^^^^^^^ error: break outside of loop
51+
continue;
52+
//^^^^^^^^ error: continue outside of loop
53+
continue 'a;
54+
//^^^^^^^^^^^ error: continue outside of loop
55+
};
56+
}
57+
}
58+
"#,
59+
);
60+
}
61+
62+
#[test]
63+
fn async_blocks_are_borders() {
64+
check_diagnostics(
65+
r#"
66+
fn foo() {
67+
'a: loop {
68+
try {
69+
break;
70+
//^^^^^ error: break outside of loop
71+
break 'a;
72+
//^^^^^^^^ error: break outside of loop
73+
continue;
74+
//^^^^^^^^ error: continue outside of loop
75+
continue 'a;
76+
//^^^^^^^^^^^ error: continue outside of loop
77+
};
78+
}
79+
}
80+
"#,
81+
);
82+
}
83+
84+
#[test]
85+
fn closures_are_borders() {
86+
check_diagnostics(
87+
r#"
88+
fn foo() {
89+
'a: loop {
90+
try {
91+
break;
92+
//^^^^^ error: break outside of loop
93+
break 'a;
94+
//^^^^^^^^ error: break outside of loop
95+
continue;
96+
//^^^^^^^^ error: continue outside of loop
97+
continue 'a;
98+
//^^^^^^^^^^^ error: continue outside of loop
99+
};
100+
}
101+
}
102+
"#,
103+
);
104+
}
105+
106+
#[test]
107+
fn blocks_pass_through() {
108+
check_diagnostics(
109+
r#"
110+
fn foo() {
111+
'a: loop {
112+
{
113+
break;
114+
break 'a;
115+
continue;
116+
continue 'a;
117+
}
118+
}
119+
}
120+
"#,
121+
);
122+
}
123+
124+
#[test]
125+
fn label_blocks() {
126+
check_diagnostics(
127+
r#"
128+
fn foo() {
129+
'a: {
130+
break;
131+
//^^^^^ error: break outside of loop
132+
break 'a;
133+
continue;
134+
//^^^^^^^^ error: continue outside of loop
135+
continue 'a;
136+
//^^^^^^^^^^^ error: continue outside of loop
137+
}
138+
}
36139
"#,
37140
);
38141
}

0 commit comments

Comments
 (0)