Properly handle break resolution inside non-breakable expressions

This commit is contained in:
Lukas Wirth 2022-09-01 14:54:47 +02:00
parent 1e66a5a8ce
commit 8110119fef
3 changed files with 163 additions and 17 deletions

View file

@ -424,15 +424,39 @@ struct BreakableContext {
coerce: CoerceMany, coerce: CoerceMany,
/// The optional label of the context. /// The optional label of the context.
label: Option<name::Name>, label: Option<name::Name>,
kind: BreakableKind,
}
#[derive(Clone, Debug)]
enum BreakableKind {
Block,
Loop,
/// A border is something like an async block, closure etc. Anything that prevents
/// breaking/continuing through
Border,
} }
fn find_breakable<'c>( fn find_breakable<'c>(
ctxs: &'c mut [BreakableContext], ctxs: &'c mut [BreakableContext],
label: Option<&name::Name>, label: Option<&name::Name>,
) -> Option<&'c mut BreakableContext> { ) -> Option<&'c mut BreakableContext> {
let mut ctxs = ctxs
.iter_mut()
.rev()
.take_while(|it| matches!(it.kind, BreakableKind::Block | BreakableKind::Loop));
match label { match label {
Some(_) => ctxs.iter_mut().rev().find(|ctx| ctx.label.as_ref() == label), Some(_) => ctxs.find(|ctx| ctx.label.as_ref() == label),
None => ctxs.last_mut(), None => ctxs.find(|ctx| matches!(ctx.kind, BreakableKind::Loop)),
}
}
fn find_continuable<'c>(
ctxs: &'c mut [BreakableContext],
label: Option<&name::Name>,
) -> Option<&'c mut BreakableContext> {
match label {
Some(_) => find_breakable(ctxs, label).filter(|it| matches!(it.kind, BreakableKind::Loop)),
None => find_breakable(ctxs, label),
} }
} }

View file

@ -23,7 +23,7 @@ use syntax::ast::RangeOp;
use crate::{ use crate::{
autoderef::{self, Autoderef}, autoderef::{self, Autoderef},
consteval, consteval,
infer::coerce::CoerceMany, infer::{coerce::CoerceMany, find_continuable, BreakableKind},
lower::{ lower::{
const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode, const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode,
}, },
@ -120,15 +120,19 @@ 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 (breaks, ty) = let (breaks, ty) = self.with_breakable_ctx(
self.with_breakable_ctx(break_ty.clone(), *label, |this| { BreakableKind::Block,
break_ty.clone(),
*label,
|this| {
this.infer_block( this.infer_block(
tgt_expr, tgt_expr,
statements, statements,
*tail, *tail,
&Expectation::has_type(break_ty), &Expectation::has_type(break_ty),
) )
}); },
);
breaks.unwrap_or(ty) breaks.unwrap_or(ty)
} }
None => self.infer_block(tgt_expr, statements, *tail, expected), None => self.infer_block(tgt_expr, statements, *tail, expected),
@ -136,9 +140,17 @@ impl<'a> InferenceContext<'a> {
self.resolver = old_resolver; self.resolver = old_resolver;
ty ty
} }
Expr::Unsafe { body } | Expr::Const { body } => self.infer_expr(*body, expected), Expr::Unsafe { body } => self.infer_expr(*body, expected),
Expr::Const { body } => {
self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
this.infer_expr(*body, expected)
})
.1
}
Expr::TryBlock { body } => { Expr::TryBlock { body } => {
let _inner = self.infer_expr(*body, expected); self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
let _inner = this.infer_expr(*body, expected);
});
// FIXME should be std::result::Result<{inner}, _> // FIXME should be std::result::Result<{inner}, _>
self.err_ty() self.err_ty()
} }
@ -147,7 +159,10 @@ impl<'a> InferenceContext<'a> {
let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
let inner_ty = self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); let (_, inner_ty) =
self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty))
});
self.diverges = prev_diverges; self.diverges = prev_diverges;
self.return_ty = prev_ret_ty; self.return_ty = prev_ret_ty;
@ -161,9 +176,10 @@ 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 (breaks, ()) = self.with_breakable_ctx(ty, label, |this| { let (breaks, ()) =
this.infer_expr(body, &Expectation::has_type(TyBuilder::unit())); self.with_breakable_ctx(BreakableKind::Loop, ty, label, |this| {
}); this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
});
match breaks { match breaks {
Some(breaks) => { Some(breaks) => {
@ -174,7 +190,7 @@ impl<'a> InferenceContext<'a> {
} }
} }
&Expr::While { condition, body, label } => { &Expr::While { condition, body, label } => {
self.with_breakable_ctx(self.err_ty(), label, |this| { self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| {
this.infer_expr( this.infer_expr(
condition, condition,
&Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)), &Expectation::has_type(TyKind::Scalar(Scalar::Bool).intern(Interner)),
@ -192,7 +208,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());
self.with_breakable_ctx(self.err_ty(), label, |this| { self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| {
this.infer_expr(body, &Expectation::has_type(TyBuilder::unit())); this.infer_expr(body, &Expectation::has_type(TyBuilder::unit()));
}); });
@ -251,7 +267,9 @@ impl<'a> InferenceContext<'a> {
let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| {
this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty));
});
self.diverges = prev_diverges; self.diverges = prev_diverges;
self.return_ty = prev_ret_ty; self.return_ty = prev_ret_ty;
@ -355,7 +373,7 @@ impl<'a> InferenceContext<'a> {
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 { label } => { Expr::Continue { label } => {
if let None = find_breakable(&mut self.breakables, label.as_ref()) { if let None = find_continuable(&mut self.breakables, label.as_ref()) {
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
expr: tgt_expr, expr: tgt_expr,
is_break: false, is_break: false,
@ -1466,13 +1484,14 @@ impl<'a> InferenceContext<'a> {
fn with_breakable_ctx<T>( fn with_breakable_ctx<T>(
&mut self, &mut self,
kind: BreakableKind,
ty: Ty, ty: Ty,
label: Option<LabelId>, label: Option<LabelId>,
cb: impl FnOnce(&mut Self) -> T, cb: impl FnOnce(&mut Self) -> T,
) -> (Option<Ty>, 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 { kind, 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");

View file

@ -33,6 +33,109 @@ fn foo() {
continue 'a; continue 'a;
//^^^^^^^^^^^ error: continue outside of loop //^^^^^^^^^^^ error: continue outside of loop
} }
"#,
);
}
#[test]
fn try_blocks_are_borders() {
check_diagnostics(
r#"
fn foo() {
'a: loop {
try {
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
};
}
}
"#,
);
}
#[test]
fn async_blocks_are_borders() {
check_diagnostics(
r#"
fn foo() {
'a: loop {
try {
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
};
}
}
"#,
);
}
#[test]
fn closures_are_borders() {
check_diagnostics(
r#"
fn foo() {
'a: loop {
try {
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
};
}
}
"#,
);
}
#[test]
fn blocks_pass_through() {
check_diagnostics(
r#"
fn foo() {
'a: loop {
{
break;
break 'a;
continue;
continue 'a;
}
}
}
"#,
);
}
#[test]
fn label_blocks() {
check_diagnostics(
r#"
fn foo() {
'a: {
break;
//^^^^^ error: break outside of loop
break 'a;
continue;
//^^^^^^^^ error: continue outside of loop
continue 'a;
//^^^^^^^^^^^ error: continue outside of loop
}
}
"#, "#,
); );
} }