Skip to content

Commit 1e66a5a

Browse files
committed
Diagnose incorrect continue expressions
1 parent d6fc4a9 commit 1e66a5a

File tree

5 files changed

+48
-31
lines changed

5 files changed

+48
-31
lines changed

crates/hir-ty/src/infer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
182182
#[derive(Debug, PartialEq, Eq, Clone)]
183183
pub enum InferenceDiagnostic {
184184
NoSuchField { expr: ExprId },
185-
BreakOutsideOfLoop { expr: ExprId },
185+
BreakOutsideOfLoop { expr: ExprId, is_break: bool },
186186
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
187187
}
188188

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

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -120,19 +120,16 @@ impl<'a> InferenceContext<'a> {
120120
let ty = match label {
121121
Some(_) => {
122122
let break_ty = self.table.new_type_var();
123-
let (ctx, ty) = self.with_breakable_ctx(break_ty.clone(), *label, |this| {
124-
this.infer_block(
125-
tgt_expr,
126-
statements,
127-
*tail,
128-
&Expectation::has_type(break_ty),
129-
)
130-
});
131-
if ctx.may_break {
132-
ctx.coerce.complete()
133-
} else {
134-
ty
135-
}
123+
let (breaks, ty) =
124+
self.with_breakable_ctx(break_ty.clone(), *label, |this| {
125+
this.infer_block(
126+
tgt_expr,
127+
statements,
128+
*tail,
129+
&Expectation::has_type(break_ty),
130+
)
131+
});
132+
breaks.unwrap_or(ty)
136133
}
137134
None => self.infer_block(tgt_expr, statements, *tail, expected),
138135
};
@@ -164,15 +161,16 @@ impl<'a> InferenceContext<'a> {
164161
}
165162
&Expr::Loop { body, label } => {
166163
let ty = self.table.new_type_var();
167-
let (ctx, ()) = self.with_breakable_ctx(ty, label, |this| {
164+
let (breaks, ()) = self.with_breakable_ctx(ty, label, |this| {
168165
this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
169166
});
170167

171-
if ctx.may_break {
172-
self.diverges = Diverges::Maybe;
173-
ctx.coerce.complete()
174-
} else {
175-
TyKind::Never.intern(Interner)
168+
match breaks {
169+
Some(breaks) => {
170+
self.diverges = Diverges::Maybe;
171+
breaks
172+
}
173+
None => TyKind::Never.intern(Interner),
176174
}
177175
}
178176
&Expr::While { condition, body, label } => {
@@ -194,7 +192,7 @@ impl<'a> InferenceContext<'a> {
194192
self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
195193

196194
self.infer_pat(pat, &pat_ty, BindingMode::default());
197-
let (_ctx, ()) = self.with_breakable_ctx(self.err_ty(), label, |this| {
195+
self.with_breakable_ctx(self.err_ty(), label, |this| {
198196
this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
199197
});
200198

@@ -356,7 +354,15 @@ impl<'a> InferenceContext<'a> {
356354
let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr);
357355
self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or_else(|| self.err_ty())
358356
}
359-
Expr::Continue { .. } => TyKind::Never.intern(Interner),
357+
Expr::Continue { label } => {
358+
if let None = find_breakable(&mut self.breakables, label.as_ref()) {
359+
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
360+
expr: tgt_expr,
361+
is_break: false,
362+
});
363+
};
364+
TyKind::Never.intern(Interner)
365+
}
360366
Expr::Break { expr, label } => {
361367
let mut coerce = match find_breakable(&mut self.breakables, label.as_ref()) {
362368
Some(ctxt) => {
@@ -384,6 +390,7 @@ impl<'a> InferenceContext<'a> {
384390
} else {
385391
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
386392
expr: tgt_expr,
393+
is_break: true,
387394
});
388395
};
389396

@@ -1462,13 +1469,13 @@ impl<'a> InferenceContext<'a> {
14621469
ty: Ty,
14631470
label: Option<LabelId>,
14641471
cb: impl FnOnce(&mut Self) -> T,
1465-
) -> (BreakableContext, T) {
1472+
) -> (Option<Ty>, T) {
14661473
self.breakables.push({
14671474
let label = label.map(|label| self.body[label].name.clone());
14681475
BreakableContext { may_break: false, coerce: CoerceMany::new(ty), label }
14691476
});
14701477
let res = cb(self);
14711478
let ctx = self.breakables.pop().expect("breakable stack broken");
1472-
(ctx, res)
1479+
(ctx.may_break.then(|| ctx.coerce.complete()), res)
14731480
}
14741481
}

crates/hir/src/diagnostics.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ pub struct NoSuchField {
124124
#[derive(Debug)]
125125
pub struct BreakOutsideOfLoop {
126126
pub expr: InFile<AstPtr<ast::Expr>>,
127+
pub is_break: bool,
127128
}
128129

129130
#[derive(Debug)]

crates/hir/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,11 +1216,11 @@ impl DefWithBody {
12161216
let field = source_map.field_syntax(*expr);
12171217
acc.push(NoSuchField { field }.into())
12181218
}
1219-
hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr } => {
1219+
&hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => {
12201220
let expr = source_map
1221-
.expr_syntax(*expr)
1221+
.expr_syntax(expr)
12221222
.expect("break outside of loop in synthetic syntax");
1223-
acc.push(BreakOutsideOfLoop { expr }.into())
1223+
acc.push(BreakOutsideOfLoop { expr, is_break }.into())
12241224
}
12251225
hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
12261226
match source_map.expr_syntax(*call_expr) {

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ pub(crate) fn break_outside_of_loop(
77
ctx: &DiagnosticsContext<'_>,
88
d: &hir::BreakOutsideOfLoop,
99
) -> Diagnostic {
10+
let construct = if d.is_break { "break" } else { "continue" };
1011
Diagnostic::new(
1112
"break-outside-of-loop",
12-
"break outside of loop",
13+
format!("{construct} outside of loop"),
1314
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
1415
)
1516
}
@@ -19,11 +20,19 @@ mod tests {
1920
use crate::tests::check_diagnostics;
2021

2122
#[test]
22-
fn break_outside_of_loop() {
23+
fn outside_of_loop() {
2324
check_diagnostics(
2425
r#"
25-
fn foo() { break; }
26-
//^^^^^ error: break outside of loop
26+
fn foo() {
27+
break;
28+
//^^^^^ error: break outside of loop
29+
break 'a;
30+
//^^^^^^^^ error: break outside of loop
31+
continue;
32+
//^^^^^^^^ error: continue outside of loop
33+
continue 'a;
34+
//^^^^^^^^^^^ error: continue outside of loop
35+
}
2736
"#,
2837
);
2938
}

0 commit comments

Comments
 (0)