about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/hir-ty/src/infer.rs2
-rw-r--r--crates/hir-ty/src/infer/expr.rs53
-rw-r--r--crates/hir/src/diagnostics.rs1
-rw-r--r--crates/hir/src/lib.rs6
-rw-r--r--crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs17
5 files changed, 48 insertions, 31 deletions
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index ba18d0c5ea6..f41c4afaf56 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -182,7 +182,7 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, 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 a29e15ec5cb..09d83202522 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<LabelId>,
         cb: impl FnOnce(&mut Self) -> T,
-    ) -> (BreakableContext, T) {
+    ) -> (Option<Ty>, 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 50374f4b3fe..5edc16d8bce 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<AstPtr<ast::Expr>>,
+    pub is_break: bool,
 }
 
 #[derive(Debug)]
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 6dccf2ed20b..e4bb63a8647 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 d12594a4ce5..59203106efa 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
+}
 "#,
         );
     }