Diagnose value breaks in incorrect breakables

This commit is contained in:
Lukas Wirth 2023-03-03 17:28:57 +01:00
parent 02eb2d758e
commit 41f234df09
5 changed files with 70 additions and 37 deletions

View file

@ -167,7 +167,8 @@ pub enum InferenceDiagnostic {
NoSuchField { expr: ExprId }, NoSuchField { expr: ExprId },
PrivateField { expr: ExprId, field: FieldId }, PrivateField { expr: ExprId, field: FieldId },
PrivateAssocItem { id: ExprOrPatId, item: AssocItemId }, PrivateAssocItem { id: ExprOrPatId, item: AssocItemId },
BreakOutsideOfLoop { expr: ExprId, is_break: bool }, // FIXME: Make this proper
BreakOutsideOfLoop { expr: ExprId, is_break: bool, bad_value_break: bool },
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
} }
@ -411,7 +412,7 @@ struct BreakableContext {
/// Whether this context contains at least one break expression. /// Whether this context contains at least one break expression.
may_break: bool, may_break: bool,
/// The coercion target of the context. /// The coercion target of the context.
coerce: CoerceMany, coerce: Option<CoerceMany>,
/// The optional label of the context. /// The optional label of the context.
label: Option<name::Name>, label: Option<name::Name>,
kind: BreakableKind, kind: BreakableKind,

View file

@ -133,7 +133,7 @@ impl<'a> InferenceContext<'a> {
let break_ty = self.table.new_type_var(); let break_ty = self.table.new_type_var();
let (breaks, ty) = self.with_breakable_ctx( let (breaks, ty) = self.with_breakable_ctx(
BreakableKind::Block, BreakableKind::Block,
break_ty.clone(), Some(break_ty.clone()),
*label, *label,
|this| { |this| {
this.infer_block( this.infer_block(
@ -153,7 +153,7 @@ impl<'a> InferenceContext<'a> {
} }
Expr::Unsafe { body } => self.infer_expr(*body, expected), Expr::Unsafe { body } => self.infer_expr(*body, expected),
Expr::Const { body } => { Expr::Const { body } => {
self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { self.with_breakable_ctx(BreakableKind::Border, None, None, |this| {
this.infer_expr(*body, expected) this.infer_expr(*body, expected)
}) })
.1 .1
@ -169,7 +169,7 @@ impl<'a> InferenceContext<'a> {
let ok_ty = let ok_ty =
self.resolve_associated_type(try_ty.clone(), self.resolve_ops_try_output()); self.resolve_associated_type(try_ty.clone(), self.resolve_ops_try_output());
self.with_breakable_ctx(BreakableKind::Block, ok_ty.clone(), None, |this| { self.with_breakable_ctx(BreakableKind::Block, Some(ok_ty.clone()), None, |this| {
this.infer_expr(*body, &Expectation::has_type(ok_ty)); this.infer_expr(*body, &Expectation::has_type(ok_ty));
}); });
@ -183,7 +183,7 @@ impl<'a> InferenceContext<'a> {
mem::replace(&mut self.return_coercion, Some(CoerceMany::new(ret_ty.clone()))); mem::replace(&mut self.return_coercion, Some(CoerceMany::new(ret_ty.clone())));
let (_, inner_ty) = let (_, inner_ty) =
self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { self.with_breakable_ctx(BreakableKind::Border, None, None, |this| {
this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)) this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty))
}); });
@ -203,7 +203,7 @@ impl<'a> InferenceContext<'a> {
// let ty = expected.coercion_target_type(&mut self.table); // let ty = expected.coercion_target_type(&mut self.table);
let ty = self.table.new_type_var(); let ty = self.table.new_type_var();
let (breaks, ()) = let (breaks, ()) =
self.with_breakable_ctx(BreakableKind::Loop, ty, label, |this| { self.with_breakable_ctx(BreakableKind::Loop, Some(ty), label, |this| {
this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); this.infer_expr(body, &Expectation::HasType(TyBuilder::unit()));
}); });
@ -216,7 +216,7 @@ impl<'a> InferenceContext<'a> {
} }
} }
&Expr::While { condition, body, label } => { &Expr::While { condition, body, label } => {
self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| { self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| {
this.infer_expr( this.infer_expr(
condition, condition,
&Expectation::HasType(this.result.standard_types.bool_.clone()), &Expectation::HasType(this.result.standard_types.bool_.clone()),
@ -236,7 +236,7 @@ impl<'a> InferenceContext<'a> {
self.resolve_associated_type(into_iter_ty, self.resolve_iterator_item()); self.resolve_associated_type(into_iter_ty, self.resolve_iterator_item());
self.infer_top_pat(pat, &pat_ty); self.infer_top_pat(pat, &pat_ty);
self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| { self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| {
this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); this.infer_expr(body, &Expectation::HasType(TyBuilder::unit()));
}); });
@ -321,7 +321,7 @@ impl<'a> InferenceContext<'a> {
let prev_resume_yield_tys = let prev_resume_yield_tys =
mem::replace(&mut self.resume_yield_tys, resume_yield_tys); mem::replace(&mut self.resume_yield_tys, resume_yield_tys);
self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { self.with_breakable_ctx(BreakableKind::Border, None, None, |this| {
this.infer_return(*body); this.infer_return(*body);
}); });
@ -439,42 +439,50 @@ impl<'a> InferenceContext<'a> {
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
expr: tgt_expr, expr: tgt_expr,
is_break: false, is_break: false,
bad_value_break: false,
}); });
}; };
self.result.standard_types.never.clone() self.result.standard_types.never.clone()
} }
Expr::Break { expr, label } => { Expr::Break { expr, label } => {
let val_ty = if let Some(expr) = *expr { let val_ty = if let Some(expr) = *expr {
let opt_coerce_to = find_breakable(&mut self.breakables, label.as_ref()) let opt_coerce_to = match find_breakable(&mut self.breakables, label.as_ref()) {
.map(|ctxt| ctxt.coerce.expected_ty()); Some(ctxt) => match &ctxt.coerce {
self.infer_expr_inner( Some(coerce) => coerce.expected_ty(),
expr, None => {
&Expectation::HasType(opt_coerce_to.unwrap_or_else(|| self.err_ty())), self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
) expr: tgt_expr,
is_break: true,
bad_value_break: true,
});
self.err_ty()
}
},
None => self.err_ty(),
};
self.infer_expr_inner(expr, &Expectation::HasType(opt_coerce_to))
} else { } else {
TyBuilder::unit() TyBuilder::unit()
}; };
match find_breakable(&mut self.breakables, label.as_ref()) { match find_breakable(&mut self.breakables, label.as_ref()) {
Some(ctxt) => { Some(ctxt) => match ctxt.coerce.take() {
// avoiding the borrowck Some(mut coerce) => {
let mut coerce = mem::replace( coerce.coerce(self, *expr, &val_ty);
&mut ctxt.coerce,
CoerceMany::new(expected.coercion_target_type(&mut self.table)),
);
// FIXME: create a synthetic `()` during lowering so we have something to refer to here? // Avoiding borrowck
coerce.coerce(self, *expr, &val_ty); let ctxt = find_breakable(&mut self.breakables, label.as_ref())
.expect("breakable stack changed during coercion");
let ctxt = find_breakable(&mut self.breakables, label.as_ref()) ctxt.may_break = true;
.expect("breakable stack changed during coercion"); ctxt.coerce = Some(coerce);
ctxt.coerce = coerce; }
ctxt.may_break = true; None => ctxt.may_break = true,
} },
None => { None => {
self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop {
expr: tgt_expr, expr: tgt_expr,
is_break: true, is_break: true,
bad_value_break: false,
}); });
} }
} }
@ -1712,16 +1720,16 @@ impl<'a> InferenceContext<'a> {
fn with_breakable_ctx<T>( fn with_breakable_ctx<T>(
&mut self, &mut self,
kind: BreakableKind, kind: BreakableKind,
ty: Ty, ty: Option<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 { kind, may_break: false, coerce: CoerceMany::new(ty), label } BreakableContext { kind, may_break: false, coerce: ty.map(CoerceMany::new), 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.may_break.then(|| ctx.coerce.complete(self)), res) (if ctx.may_break { ctx.coerce.map(|ctx| ctx.complete(self)) } else { None }, res)
} }
} }

View file

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

View file

@ -1373,11 +1373,15 @@ 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, is_break } => { &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop {
expr,
is_break,
bad_value_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, is_break }.into()) acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_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,10 +7,15 @@ 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" }; let message = if d.bad_value_break {
"can't break with a value in this position".to_owned()
} else {
let construct = if d.is_break { "break" } else { "continue" };
format!("{construct} outside of loop")
};
Diagnostic::new( Diagnostic::new(
"break-outside-of-loop", "break-outside-of-loop",
format!("{construct} outside of loop"), message,
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,
) )
} }
@ -132,6 +137,20 @@ fn foo() {
//^^^^^^^^^^^ error: continue outside of loop //^^^^^^^^^^^ error: continue outside of loop
} }
} }
"#,
);
}
#[test]
fn value_break_in_for_loop() {
check_diagnostics(
r#"
fn test() {
for _ in [()] {
break 3;
// ^^^^^^^ error: can't break with a value in this position
}
}
"#, "#,
); );
} }