From 1e66a5a8ce38c0ec96479f234e87d10850264e09 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 1 Sep 2022 14:30:57 +0200 Subject: [PATCH] Diagnose incorrect continue expressions --- crates/hir-ty/src/infer.rs | 2 +- crates/hir-ty/src/infer/expr.rs | 53 +++++++++++-------- crates/hir/src/diagnostics.rs | 1 + crates/hir/src/lib.rs | 6 +-- .../src/handlers/break_outside_of_loop.rs | 17 ++++-- 5 files changed, 48 insertions(+), 31 deletions(-) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index ba18d0c5ea..f41c4afaf5 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -182,7 +182,7 @@ pub(crate) type InferResult = Result, TypeError>; #[derive(Debug, PartialEq, Eq, Clone)] pub enum InferenceDiagnostic { NoSuchField { expr: ExprId }, - BreakOutsideOfLoop { expr: ExprId }, + BreakOutsideOfLoop { expr: ExprId, is_break: bool }, MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, } diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index a29e15ec5c..09d8320252 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -120,19 +120,16 @@ impl<'a> InferenceContext<'a> { let ty = match label { Some(_) => { let break_ty = self.table.new_type_var(); - let (ctx, ty) = self.with_breakable_ctx(break_ty.clone(), *label, |this| { - this.infer_block( - tgt_expr, - statements, - *tail, - &Expectation::has_type(break_ty), - ) - }); - if ctx.may_break { - ctx.coerce.complete() - } else { - ty - } + let (breaks, ty) = + self.with_breakable_ctx(break_ty.clone(), *label, |this| { + this.infer_block( + tgt_expr, + statements, + *tail, + &Expectation::has_type(break_ty), + ) + }); + breaks.unwrap_or(ty) } None => self.infer_block(tgt_expr, statements, *tail, expected), }; @@ -164,15 +161,16 @@ impl<'a> InferenceContext<'a> { } &Expr::Loop { body, label } => { 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())); }); - if ctx.may_break { - self.diverges = Diverges::Maybe; - ctx.coerce.complete() - } else { - TyKind::Never.intern(Interner) + match breaks { + Some(breaks) => { + self.diverges = Diverges::Maybe; + breaks + } + None => TyKind::Never.intern(Interner), } } &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.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())); }); @@ -356,7 +354,15 @@ impl<'a> InferenceContext<'a> { 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()) } - 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 } => { let mut coerce = match find_breakable(&mut self.breakables, label.as_ref()) { Some(ctxt) => { @@ -384,6 +390,7 @@ impl<'a> InferenceContext<'a> { } else { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { expr: tgt_expr, + is_break: true, }); }; @@ -1462,13 +1469,13 @@ impl<'a> InferenceContext<'a> { ty: Ty, label: Option, cb: impl FnOnce(&mut Self) -> T, - ) -> (BreakableContext, T) { + ) -> (Option, T) { self.breakables.push({ let label = label.map(|label| self.body[label].name.clone()); BreakableContext { may_break: false, coerce: CoerceMany::new(ty), label } }); let res = cb(self); let ctx = self.breakables.pop().expect("breakable stack broken"); - (ctx, res) + (ctx.may_break.then(|| ctx.coerce.complete()), res) } } diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 50374f4b3f..5edc16d8bc 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -124,6 +124,7 @@ pub struct NoSuchField { #[derive(Debug)] pub struct BreakOutsideOfLoop { pub expr: InFile>, + pub is_break: bool, } #[derive(Debug)] diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 6dccf2ed20..e4bb63a864 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1216,11 +1216,11 @@ impl DefWithBody { let field = source_map.field_syntax(*expr); acc.push(NoSuchField { field }.into()) } - hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr } => { + &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => { let expr = source_map - .expr_syntax(*expr) + .expr_syntax(expr) .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 } => { match source_map.expr_syntax(*call_expr) { diff --git a/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs b/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs index d12594a4ce..59203106ef 100644 --- a/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs +++ b/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs @@ -7,9 +7,10 @@ pub(crate) fn break_outside_of_loop( ctx: &DiagnosticsContext<'_>, d: &hir::BreakOutsideOfLoop, ) -> Diagnostic { + let construct = if d.is_break { "break" } else { "continue" }; Diagnostic::new( "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, ) } @@ -19,11 +20,19 @@ mod tests { use crate::tests::check_diagnostics; #[test] - fn break_outside_of_loop() { + fn outside_of_loop() { check_diagnostics( r#" -fn foo() { break; } - //^^^^^ error: break outside of loop +fn foo() { + 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 +} "#, ); }