Diagnose incorrect continue expressions

This commit is contained in:
Lukas Wirth 2022-09-01 14:30:57 +02:00
parent d6fc4a9ea6
commit 1e66a5a8ce
5 changed files with 48 additions and 31 deletions

View file

@ -182,7 +182,7 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub enum InferenceDiagnostic { pub enum InferenceDiagnostic {
NoSuchField { expr: ExprId }, NoSuchField { expr: ExprId },
BreakOutsideOfLoop { expr: ExprId }, BreakOutsideOfLoop { expr: ExprId, is_break: bool },
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
} }

View file

@ -120,19 +120,16 @@ impl<'a> InferenceContext<'a> {
let ty = match label { let ty = match label {
Some(_) => { Some(_) => {
let break_ty = self.table.new_type_var(); let break_ty = self.table.new_type_var();
let (ctx, ty) = self.with_breakable_ctx(break_ty.clone(), *label, |this| { let (breaks, ty) =
this.infer_block( self.with_breakable_ctx(break_ty.clone(), *label, |this| {
tgt_expr, this.infer_block(
statements, tgt_expr,
*tail, statements,
&Expectation::has_type(break_ty), *tail,
) &Expectation::has_type(break_ty),
}); )
if ctx.may_break { });
ctx.coerce.complete() breaks.unwrap_or(ty)
} else {
ty
}
} }
None => self.infer_block(tgt_expr, statements, *tail, expected), None => self.infer_block(tgt_expr, statements, *tail, expected),
}; };
@ -164,15 +161,16 @@ impl<'a> InferenceContext<'a> {
} }
&Expr::Loop { body, label } => { &Expr::Loop { body, label } => {
let ty = self.table.new_type_var(); let ty = self.table.new_type_var();
let (ctx, ()) = self.with_breakable_ctx(ty, label, |this| { let (breaks, ()) = self.with_breakable_ctx(ty, label, |this| {
this.infer_expr(body, &Expectation::has_type(TyBuilder::unit())); this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
}); });
if ctx.may_break { match breaks {
self.diverges = Diverges::Maybe; Some(breaks) => {
ctx.coerce.complete() self.diverges = Diverges::Maybe;
} else { breaks
TyKind::Never.intern(Interner) }
None => TyKind::Never.intern(Interner),
} }
} }
&Expr::While { condition, body, label } => { &Expr::While { condition, body, label } => {
@ -194,7 +192,7 @@ impl<'a> InferenceContext<'a> {
self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
self.infer_pat(pat, &pat_ty, BindingMode::default()); self.infer_pat(pat, &pat_ty, BindingMode::default());
let (_ctx, ()) = self.with_breakable_ctx(self.err_ty(), label, |this| { self.with_breakable_ctx(self.err_ty(), label, |this| {
this.infer_expr(body, &Expectation::has_type(TyBuilder::unit())); this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
}); });
@ -356,7 +354,15 @@ impl<'a> InferenceContext<'a> {
let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr);
self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or_else(|| self.err_ty()) self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or_else(|| self.err_ty())
} }
Expr::Continue { .. } => TyKind::Never.intern(Interner), Expr::Continue { label } => {
if let None = find_breakable(&mut self.breakables, label.as_ref()) {
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
expr: tgt_expr,
is_break: false,
});
};
TyKind::Never.intern(Interner)
}
Expr::Break { expr, label } => { Expr::Break { expr, label } => {
let mut coerce = match find_breakable(&mut self.breakables, label.as_ref()) { let mut coerce = match find_breakable(&mut self.breakables, label.as_ref()) {
Some(ctxt) => { Some(ctxt) => {
@ -384,6 +390,7 @@ impl<'a> InferenceContext<'a> {
} else { } else {
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
expr: tgt_expr, expr: tgt_expr,
is_break: true,
}); });
}; };
@ -1462,13 +1469,13 @@ impl<'a> InferenceContext<'a> {
ty: Ty, ty: Ty,
label: Option<LabelId>, label: Option<LabelId>,
cb: impl FnOnce(&mut Self) -> T, cb: impl FnOnce(&mut Self) -> T,
) -> (BreakableContext, T) { ) -> (Option<Ty>, T) {
self.breakables.push({ self.breakables.push({
let label = label.map(|label| self.body[label].name.clone()); let label = label.map(|label| self.body[label].name.clone());
BreakableContext { may_break: false, coerce: CoerceMany::new(ty), label } BreakableContext { may_break: false, coerce: CoerceMany::new(ty), label }
}); });
let res = cb(self); let res = cb(self);
let ctx = self.breakables.pop().expect("breakable stack broken"); let ctx = self.breakables.pop().expect("breakable stack broken");
(ctx, res) (ctx.may_break.then(|| ctx.coerce.complete()), res)
} }
} }

View file

@ -124,6 +124,7 @@ pub struct NoSuchField {
#[derive(Debug)] #[derive(Debug)]
pub struct BreakOutsideOfLoop { pub struct BreakOutsideOfLoop {
pub expr: InFile<AstPtr<ast::Expr>>, pub expr: InFile<AstPtr<ast::Expr>>,
pub is_break: bool,
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -1216,11 +1216,11 @@ impl DefWithBody {
let field = source_map.field_syntax(*expr); let field = source_map.field_syntax(*expr);
acc.push(NoSuchField { field }.into()) acc.push(NoSuchField { field }.into())
} }
hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr } => { &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => {
let expr = source_map let expr = source_map
.expr_syntax(*expr) .expr_syntax(expr)
.expect("break outside of loop in synthetic syntax"); .expect("break outside of loop in synthetic syntax");
acc.push(BreakOutsideOfLoop { expr }.into()) acc.push(BreakOutsideOfLoop { expr, is_break }.into())
} }
hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
match source_map.expr_syntax(*call_expr) { match source_map.expr_syntax(*call_expr) {

View file

@ -7,9 +7,10 @@ pub(crate) fn break_outside_of_loop(
ctx: &DiagnosticsContext<'_>, ctx: &DiagnosticsContext<'_>,
d: &hir::BreakOutsideOfLoop, d: &hir::BreakOutsideOfLoop,
) -> Diagnostic { ) -> Diagnostic {
let construct = if d.is_break { "break" } else { "continue" };
Diagnostic::new( Diagnostic::new(
"break-outside-of-loop", "break-outside-of-loop",
"break outside of loop", format!("{construct} outside of loop"),
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
) )
} }
@ -19,11 +20,19 @@ mod tests {
use crate::tests::check_diagnostics; use crate::tests::check_diagnostics;
#[test] #[test]
fn break_outside_of_loop() { fn outside_of_loop() {
check_diagnostics( check_diagnostics(
r#" r#"
fn foo() { break; } fn foo() {
//^^^^^ error: break outside of loop break;
//^^^^^ error: break outside of loop
break 'a;
//^^^^^^^^ error: break outside of loop
continue;
//^^^^^^^^ error: continue outside of loop
continue 'a;
//^^^^^^^^^^^ error: continue outside of loop
}
"#, "#,
); );
} }