From daac0114591777f29e2f6396e8c2bd5d2c506788 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Tue, 3 Oct 2023 00:20:59 +0000
Subject: [PATCH 1/4] Suggest labeling block if `break` is in bare block

Fix #103982.
---
 compiler/rustc_passes/messages.ftl            |  2 +
 compiler/rustc_passes/src/errors.rs           | 10 ++++
 compiler/rustc_passes/src/loops.rs            | 55 ++++++++++++++++---
 .../ui/parser/break-in-unlabeled-block.fixed  | 12 ++++
 tests/ui/parser/break-in-unlabeled-block.rs   | 11 ++++
 .../ui/parser/break-in-unlabeled-block.stderr | 27 +++++++++
 6 files changed, 109 insertions(+), 8 deletions(-)
 create mode 100644 tests/ui/parser/break-in-unlabeled-block.fixed
 create mode 100644 tests/ui/parser/break-in-unlabeled-block.rs
 create mode 100644 tests/ui/parser/break-in-unlabeled-block.stderr

diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index 214c6d70960e5..25ef5245cf169 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -580,6 +580,8 @@ passes_outside_loop =
         *[false] {""}
     }
 
+passes_outside_loop_suggestion = consider labeling this block to be able to break within it
+
 passes_params_not_allowed =
     referencing function parameters is not allowed in naked functions
     .help = follow the calling convention in asm block to use parameters
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index bcf5abbfe7d9d..4b27b6d8c3698 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1099,6 +1099,16 @@ pub struct OutsideLoop<'a> {
     pub span: Span,
     pub name: &'a str,
     pub is_break: bool,
+    #[subdiagnostic]
+    pub suggestion: Option<OutsideLoopSuggestion>,
+}
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(passes_outside_loop_suggestion, applicability = "maybe-incorrect")]
+pub struct OutsideLoopSuggestion {
+    #[suggestion_part(code = "'block: ")]
+    pub block_span: Span,
+    #[suggestion_part(code = " 'block")]
+    pub break_span: Span,
 }
 
 #[derive(Diagnostic)]
diff --git a/compiler/rustc_passes/src/loops.rs b/compiler/rustc_passes/src/loops.rs
index 0aaf85086e455..10763556c7c67 100644
--- a/compiler/rustc_passes/src/loops.rs
+++ b/compiler/rustc_passes/src/loops.rs
@@ -1,7 +1,7 @@
 use Context::*;
 
 use rustc_hir as hir;
-use rustc_hir::def_id::LocalModDefId;
+use rustc_hir::def_id::{LocalDefId, LocalModDefId};
 use rustc_hir::intravisit::{self, Visitor};
 use rustc_hir::{Destination, Movability, Node};
 use rustc_middle::hir::map::Map;
@@ -10,19 +10,21 @@ use rustc_middle::query::Providers;
 use rustc_middle::ty::TyCtxt;
 use rustc_session::Session;
 use rustc_span::hygiene::DesugaringKind;
-use rustc_span::Span;
+use rustc_span::{BytePos, Span};
 
 use crate::errors::{
     BreakInsideAsyncBlock, BreakInsideClosure, BreakNonLoop, ContinueLabeledBlock, OutsideLoop,
-    UnlabeledCfInWhileCondition, UnlabeledInLabeledBlock,
+    OutsideLoopSuggestion, UnlabeledCfInWhileCondition, UnlabeledInLabeledBlock,
 };
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 enum Context {
     Normal,
+    Fn,
     Loop(hir::LoopSource),
     Closure(Span),
     AsyncClosure(Span),
+    UnlabeledBlock(Span),
     LabeledBlock,
     Constant,
 }
@@ -60,6 +62,25 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
         self.with_context(Constant, |v| intravisit::walk_inline_const(v, c));
     }
 
+    fn visit_fn(
+        &mut self,
+        fk: hir::intravisit::FnKind<'hir>,
+        fd: &'hir hir::FnDecl<'hir>,
+        b: hir::BodyId,
+        _: Span,
+        id: LocalDefId,
+    ) {
+        self.with_context(Fn, |v| intravisit::walk_fn(v, fk, fd, b, id));
+    }
+
+    fn visit_trait_item(&mut self, trait_item: &'hir hir::TraitItem<'hir>) {
+        self.with_context(Fn, |v| intravisit::walk_trait_item(v, trait_item));
+    }
+
+    fn visit_impl_item(&mut self, impl_item: &'hir hir::ImplItem<'hir>) {
+        self.with_context(Fn, |v| intravisit::walk_impl_item(v, impl_item));
+    }
+
     fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {
         match e.kind {
             hir::ExprKind::Loop(ref b, _, source, _) => {
@@ -83,6 +104,14 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
             hir::ExprKind::Block(ref b, Some(_label)) => {
                 self.with_context(LabeledBlock, |v| v.visit_block(&b));
             }
+            hir::ExprKind::Block(ref b, None) if matches!(self.cx, Fn) => {
+                self.with_context(Normal, |v| v.visit_block(&b));
+            }
+            hir::ExprKind::Block(ref b, None)
+                if matches!(self.cx, Normal | Constant | UnlabeledBlock(_)) =>
+            {
+                self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(&b));
+            }
             hir::ExprKind::Break(break_label, ref opt_expr) => {
                 if let Some(e) = opt_expr {
                     self.visit_expr(e);
@@ -147,7 +176,12 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
                     }
                 }
 
-                self.require_break_cx("break", e.span);
+                let sp_lo = e.span.with_lo(e.span.lo() + BytePos("break".len() as u32));
+                let label_sp = match break_label.label {
+                    Some(label) => sp_lo.with_hi(label.ident.span.hi()),
+                    None => sp_lo.shrink_to_lo(),
+                };
+                self.require_break_cx("break", e.span, label_sp);
             }
             hir::ExprKind::Continue(destination) => {
                 self.require_label_in_labeled_block(e.span, &destination, "continue");
@@ -169,7 +203,7 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
                     }
                     Err(_) => {}
                 }
-                self.require_break_cx("continue", e.span)
+                self.require_break_cx("continue", e.span, e.span)
             }
             _ => intravisit::walk_expr(self, e),
         }
@@ -187,7 +221,8 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
         self.cx = old_cx;
     }
 
-    fn require_break_cx(&self, name: &str, span: Span) {
+    fn require_break_cx(&self, name: &str, span: Span, break_span: Span) {
+        let is_break = name == "break";
         match self.cx {
             LabeledBlock | Loop(_) => {}
             Closure(closure_span) => {
@@ -196,8 +231,12 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
             AsyncClosure(closure_span) => {
                 self.sess.emit_err(BreakInsideAsyncBlock { span, closure_span, name });
             }
-            Normal | Constant => {
-                self.sess.emit_err(OutsideLoop { span, name, is_break: name == "break" });
+            UnlabeledBlock(block_span) if is_break => {
+                let suggestion = Some(OutsideLoopSuggestion { block_span, break_span });
+                self.sess.emit_err(OutsideLoop { span, name, is_break, suggestion });
+            }
+            Normal | Constant | Fn | UnlabeledBlock(_) => {
+                self.sess.emit_err(OutsideLoop { span, name, is_break, suggestion: None });
             }
         }
     }
diff --git a/tests/ui/parser/break-in-unlabeled-block.fixed b/tests/ui/parser/break-in-unlabeled-block.fixed
new file mode 100644
index 0000000000000..0e496430ae677
--- /dev/null
+++ b/tests/ui/parser/break-in-unlabeled-block.fixed
@@ -0,0 +1,12 @@
+// run-rustfix
+fn main() {
+    'block: {
+        break 'block (); //~ ERROR `break` outside of a loop or labeled block
+    }
+    {
+        'block: {
+            break 'block (); //~ ERROR `break` outside of a loop or labeled block
+        }
+    }
+}
+
diff --git a/tests/ui/parser/break-in-unlabeled-block.rs b/tests/ui/parser/break-in-unlabeled-block.rs
new file mode 100644
index 0000000000000..3e5587e9f9c97
--- /dev/null
+++ b/tests/ui/parser/break-in-unlabeled-block.rs
@@ -0,0 +1,11 @@
+// run-rustfix
+fn main() {
+    {
+        break (); //~ ERROR `break` outside of a loop or labeled block
+    }
+    {
+        {
+            break (); //~ ERROR `break` outside of a loop or labeled block
+        }
+    }
+}
diff --git a/tests/ui/parser/break-in-unlabeled-block.stderr b/tests/ui/parser/break-in-unlabeled-block.stderr
new file mode 100644
index 0000000000000..632cca80d8c03
--- /dev/null
+++ b/tests/ui/parser/break-in-unlabeled-block.stderr
@@ -0,0 +1,27 @@
+error[E0268]: `break` outside of a loop or labeled block
+  --> $DIR/break-in-unlabeled-block.rs:4:9
+   |
+LL |         break ();
+   |         ^^^^^^^^ cannot `break` outside of a loop or labeled block
+   |
+help: consider labeling this block to be able to break within it
+   |
+LL ~     'block: {
+LL ~         break 'block ();
+   |
+
+error[E0268]: `break` outside of a loop or labeled block
+  --> $DIR/break-in-unlabeled-block.rs:8:13
+   |
+LL |             break ();
+   |             ^^^^^^^^ cannot `break` outside of a loop or labeled block
+   |
+help: consider labeling this block to be able to break within it
+   |
+LL ~         'block: {
+LL ~             break 'block ();
+   |
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0268`.

From 5c17b8be61b69be6c38619f829ba81c64212110d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Tue, 3 Oct 2023 21:34:52 +0000
Subject: [PATCH 2/4] Move some tests around

---
 tests/ui/parser/{ => attribute}/attr-bad-meta-2.rs                | 0
 tests/ui/parser/{ => attribute}/attr-bad-meta-2.stderr            | 0
 tests/ui/parser/{ => attribute}/attr-bad-meta-3.rs                | 0
 tests/ui/parser/{ => attribute}/attr-bad-meta-3.stderr            | 0
 tests/ui/parser/{ => attribute}/attr-bad-meta.rs                  | 0
 tests/ui/parser/{ => attribute}/attr-bad-meta.stderr              | 0
 tests/ui/parser/{ => attribute}/attr-before-eof.rs                | 0
 tests/ui/parser/{ => attribute}/attr-before-eof.stderr            | 0
 tests/ui/parser/{ => attribute}/attr-dangling-in-fn.rs            | 0
 tests/ui/parser/{ => attribute}/attr-dangling-in-fn.stderr        | 0
 tests/ui/parser/{ => attribute}/attr-dangling-in-mod.rs           | 0
 tests/ui/parser/{ => attribute}/attr-dangling-in-mod.stderr       | 0
 tests/ui/parser/{ => attribute}/attr-stmt-expr-attr-bad.rs        | 0
 tests/ui/parser/{ => attribute}/attr-stmt-expr-attr-bad.stderr    | 0
 tests/ui/parser/{ => attribute}/attr-with-a-semicolon.rs          | 0
 tests/ui/parser/{ => attribute}/attr-with-a-semicolon.stderr      | 0
 tests/ui/parser/{ => attribute}/attr.rs                           | 0
 tests/ui/parser/{ => attribute}/attr.stderr                       | 0
 .../attribute-with-no-generics-in-parameter-list.rs               | 0
 .../attribute-with-no-generics-in-parameter-list.stderr           | 0
 tests/ui/parser/{ => attribute}/attrs-after-extern-mod.rs         | 0
 tests/ui/parser/{ => attribute}/attrs-after-extern-mod.stderr     | 0
 22 files changed, 0 insertions(+), 0 deletions(-)
 rename tests/ui/parser/{ => attribute}/attr-bad-meta-2.rs (100%)
 rename tests/ui/parser/{ => attribute}/attr-bad-meta-2.stderr (100%)
 rename tests/ui/parser/{ => attribute}/attr-bad-meta-3.rs (100%)
 rename tests/ui/parser/{ => attribute}/attr-bad-meta-3.stderr (100%)
 rename tests/ui/parser/{ => attribute}/attr-bad-meta.rs (100%)
 rename tests/ui/parser/{ => attribute}/attr-bad-meta.stderr (100%)
 rename tests/ui/parser/{ => attribute}/attr-before-eof.rs (100%)
 rename tests/ui/parser/{ => attribute}/attr-before-eof.stderr (100%)
 rename tests/ui/parser/{ => attribute}/attr-dangling-in-fn.rs (100%)
 rename tests/ui/parser/{ => attribute}/attr-dangling-in-fn.stderr (100%)
 rename tests/ui/parser/{ => attribute}/attr-dangling-in-mod.rs (100%)
 rename tests/ui/parser/{ => attribute}/attr-dangling-in-mod.stderr (100%)
 rename tests/ui/parser/{ => attribute}/attr-stmt-expr-attr-bad.rs (100%)
 rename tests/ui/parser/{ => attribute}/attr-stmt-expr-attr-bad.stderr (100%)
 rename tests/ui/parser/{ => attribute}/attr-with-a-semicolon.rs (100%)
 rename tests/ui/parser/{ => attribute}/attr-with-a-semicolon.stderr (100%)
 rename tests/ui/parser/{ => attribute}/attr.rs (100%)
 rename tests/ui/parser/{ => attribute}/attr.stderr (100%)
 rename tests/ui/parser/{ => attribute}/attribute-with-no-generics-in-parameter-list.rs (100%)
 rename tests/ui/parser/{ => attribute}/attribute-with-no-generics-in-parameter-list.stderr (100%)
 rename tests/ui/parser/{ => attribute}/attrs-after-extern-mod.rs (100%)
 rename tests/ui/parser/{ => attribute}/attrs-after-extern-mod.stderr (100%)

diff --git a/tests/ui/parser/attr-bad-meta-2.rs b/tests/ui/parser/attribute/attr-bad-meta-2.rs
similarity index 100%
rename from tests/ui/parser/attr-bad-meta-2.rs
rename to tests/ui/parser/attribute/attr-bad-meta-2.rs
diff --git a/tests/ui/parser/attr-bad-meta-2.stderr b/tests/ui/parser/attribute/attr-bad-meta-2.stderr
similarity index 100%
rename from tests/ui/parser/attr-bad-meta-2.stderr
rename to tests/ui/parser/attribute/attr-bad-meta-2.stderr
diff --git a/tests/ui/parser/attr-bad-meta-3.rs b/tests/ui/parser/attribute/attr-bad-meta-3.rs
similarity index 100%
rename from tests/ui/parser/attr-bad-meta-3.rs
rename to tests/ui/parser/attribute/attr-bad-meta-3.rs
diff --git a/tests/ui/parser/attr-bad-meta-3.stderr b/tests/ui/parser/attribute/attr-bad-meta-3.stderr
similarity index 100%
rename from tests/ui/parser/attr-bad-meta-3.stderr
rename to tests/ui/parser/attribute/attr-bad-meta-3.stderr
diff --git a/tests/ui/parser/attr-bad-meta.rs b/tests/ui/parser/attribute/attr-bad-meta.rs
similarity index 100%
rename from tests/ui/parser/attr-bad-meta.rs
rename to tests/ui/parser/attribute/attr-bad-meta.rs
diff --git a/tests/ui/parser/attr-bad-meta.stderr b/tests/ui/parser/attribute/attr-bad-meta.stderr
similarity index 100%
rename from tests/ui/parser/attr-bad-meta.stderr
rename to tests/ui/parser/attribute/attr-bad-meta.stderr
diff --git a/tests/ui/parser/attr-before-eof.rs b/tests/ui/parser/attribute/attr-before-eof.rs
similarity index 100%
rename from tests/ui/parser/attr-before-eof.rs
rename to tests/ui/parser/attribute/attr-before-eof.rs
diff --git a/tests/ui/parser/attr-before-eof.stderr b/tests/ui/parser/attribute/attr-before-eof.stderr
similarity index 100%
rename from tests/ui/parser/attr-before-eof.stderr
rename to tests/ui/parser/attribute/attr-before-eof.stderr
diff --git a/tests/ui/parser/attr-dangling-in-fn.rs b/tests/ui/parser/attribute/attr-dangling-in-fn.rs
similarity index 100%
rename from tests/ui/parser/attr-dangling-in-fn.rs
rename to tests/ui/parser/attribute/attr-dangling-in-fn.rs
diff --git a/tests/ui/parser/attr-dangling-in-fn.stderr b/tests/ui/parser/attribute/attr-dangling-in-fn.stderr
similarity index 100%
rename from tests/ui/parser/attr-dangling-in-fn.stderr
rename to tests/ui/parser/attribute/attr-dangling-in-fn.stderr
diff --git a/tests/ui/parser/attr-dangling-in-mod.rs b/tests/ui/parser/attribute/attr-dangling-in-mod.rs
similarity index 100%
rename from tests/ui/parser/attr-dangling-in-mod.rs
rename to tests/ui/parser/attribute/attr-dangling-in-mod.rs
diff --git a/tests/ui/parser/attr-dangling-in-mod.stderr b/tests/ui/parser/attribute/attr-dangling-in-mod.stderr
similarity index 100%
rename from tests/ui/parser/attr-dangling-in-mod.stderr
rename to tests/ui/parser/attribute/attr-dangling-in-mod.stderr
diff --git a/tests/ui/parser/attr-stmt-expr-attr-bad.rs b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs
similarity index 100%
rename from tests/ui/parser/attr-stmt-expr-attr-bad.rs
rename to tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs
diff --git a/tests/ui/parser/attr-stmt-expr-attr-bad.stderr b/tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr
similarity index 100%
rename from tests/ui/parser/attr-stmt-expr-attr-bad.stderr
rename to tests/ui/parser/attribute/attr-stmt-expr-attr-bad.stderr
diff --git a/tests/ui/parser/attr-with-a-semicolon.rs b/tests/ui/parser/attribute/attr-with-a-semicolon.rs
similarity index 100%
rename from tests/ui/parser/attr-with-a-semicolon.rs
rename to tests/ui/parser/attribute/attr-with-a-semicolon.rs
diff --git a/tests/ui/parser/attr-with-a-semicolon.stderr b/tests/ui/parser/attribute/attr-with-a-semicolon.stderr
similarity index 100%
rename from tests/ui/parser/attr-with-a-semicolon.stderr
rename to tests/ui/parser/attribute/attr-with-a-semicolon.stderr
diff --git a/tests/ui/parser/attr.rs b/tests/ui/parser/attribute/attr.rs
similarity index 100%
rename from tests/ui/parser/attr.rs
rename to tests/ui/parser/attribute/attr.rs
diff --git a/tests/ui/parser/attr.stderr b/tests/ui/parser/attribute/attr.stderr
similarity index 100%
rename from tests/ui/parser/attr.stderr
rename to tests/ui/parser/attribute/attr.stderr
diff --git a/tests/ui/parser/attribute-with-no-generics-in-parameter-list.rs b/tests/ui/parser/attribute/attribute-with-no-generics-in-parameter-list.rs
similarity index 100%
rename from tests/ui/parser/attribute-with-no-generics-in-parameter-list.rs
rename to tests/ui/parser/attribute/attribute-with-no-generics-in-parameter-list.rs
diff --git a/tests/ui/parser/attribute-with-no-generics-in-parameter-list.stderr b/tests/ui/parser/attribute/attribute-with-no-generics-in-parameter-list.stderr
similarity index 100%
rename from tests/ui/parser/attribute-with-no-generics-in-parameter-list.stderr
rename to tests/ui/parser/attribute/attribute-with-no-generics-in-parameter-list.stderr
diff --git a/tests/ui/parser/attrs-after-extern-mod.rs b/tests/ui/parser/attribute/attrs-after-extern-mod.rs
similarity index 100%
rename from tests/ui/parser/attrs-after-extern-mod.rs
rename to tests/ui/parser/attribute/attrs-after-extern-mod.rs
diff --git a/tests/ui/parser/attrs-after-extern-mod.stderr b/tests/ui/parser/attribute/attrs-after-extern-mod.stderr
similarity index 100%
rename from tests/ui/parser/attrs-after-extern-mod.stderr
rename to tests/ui/parser/attribute/attrs-after-extern-mod.stderr

From c30d57bb77535f3923cbfa666e84d1916b6bce37 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Wed, 4 Oct 2023 01:29:45 +0000
Subject: [PATCH 3/4] fix

---
 tests/ui/parser/break-in-unlabeled-block.fixed | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/ui/parser/break-in-unlabeled-block.fixed b/tests/ui/parser/break-in-unlabeled-block.fixed
index 0e496430ae677..0885623252160 100644
--- a/tests/ui/parser/break-in-unlabeled-block.fixed
+++ b/tests/ui/parser/break-in-unlabeled-block.fixed
@@ -9,4 +9,3 @@ fn main() {
         }
     }
 }
-

From d23dc2093c2037a3f401d917ddb9e9c8507ef116 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Esteban=20K=C3=BCber?= <esteban@kuber.com.ar>
Date: Mon, 9 Oct 2023 22:48:10 +0000
Subject: [PATCH 4/4] Account for macros

---
 compiler/rustc_passes/src/loops.rs            |  2 +-
 .../break-in-unlabeled-block-in-macro.rs      | 43 ++++++++++++
 .../break-in-unlabeled-block-in-macro.stderr  | 69 +++++++++++++++++++
 3 files changed, 113 insertions(+), 1 deletion(-)
 create mode 100644 tests/ui/parser/break-in-unlabeled-block-in-macro.rs
 create mode 100644 tests/ui/parser/break-in-unlabeled-block-in-macro.stderr

diff --git a/compiler/rustc_passes/src/loops.rs b/compiler/rustc_passes/src/loops.rs
index 10763556c7c67..4590ab9e4f579 100644
--- a/compiler/rustc_passes/src/loops.rs
+++ b/compiler/rustc_passes/src/loops.rs
@@ -231,7 +231,7 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
             AsyncClosure(closure_span) => {
                 self.sess.emit_err(BreakInsideAsyncBlock { span, closure_span, name });
             }
-            UnlabeledBlock(block_span) if is_break => {
+            UnlabeledBlock(block_span) if is_break && block_span.ctxt() == break_span.ctxt() => {
                 let suggestion = Some(OutsideLoopSuggestion { block_span, break_span });
                 self.sess.emit_err(OutsideLoop { span, name, is_break, suggestion });
             }
diff --git a/tests/ui/parser/break-in-unlabeled-block-in-macro.rs b/tests/ui/parser/break-in-unlabeled-block-in-macro.rs
new file mode 100644
index 0000000000000..eecc0026b12d6
--- /dev/null
+++ b/tests/ui/parser/break-in-unlabeled-block-in-macro.rs
@@ -0,0 +1,43 @@
+macro_rules! foo {
+    () => {
+        break (); //~ ERROR `break` outside of a loop or labeled block
+    };
+    ($e: expr) => {
+        break $e; //~ ERROR `break` outside of a loop or labeled block
+    };
+    (stmt $s: stmt) => {
+        $s
+    };
+    (@ $e: expr) => {
+        { break $e; } //~ ERROR `break` outside of a loop or labeled block
+    };
+    (=> $s: stmt) => {
+        { $s }
+    };
+}
+
+fn main() {
+    {
+        foo!();
+    }
+    {
+        foo!(());
+    }
+    {
+        foo!(stmt break ()); //~ ERROR `break` outside of a loop or labeled block
+    }
+    {
+        foo!(@ ());
+    }
+    {
+        foo!(=> break ()); //~ ERROR `break` outside of a loop or labeled block
+    }
+    {
+        macro_rules! bar {
+            () => {
+                break () //~ ERROR `break` outside of a loop or labeled block
+            };
+        }
+        bar!()
+    }
+}
diff --git a/tests/ui/parser/break-in-unlabeled-block-in-macro.stderr b/tests/ui/parser/break-in-unlabeled-block-in-macro.stderr
new file mode 100644
index 0000000000000..9407e8ac0029d
--- /dev/null
+++ b/tests/ui/parser/break-in-unlabeled-block-in-macro.stderr
@@ -0,0 +1,69 @@
+error[E0268]: `break` outside of a loop or labeled block
+  --> $DIR/break-in-unlabeled-block-in-macro.rs:3:9
+   |
+LL |         break ();
+   |         ^^^^^^^^ cannot `break` outside of a loop or labeled block
+...
+LL |         foo!();
+   |         ------ in this macro invocation
+   |
+   = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0268]: `break` outside of a loop or labeled block
+  --> $DIR/break-in-unlabeled-block-in-macro.rs:6:9
+   |
+LL |         break $e;
+   |         ^^^^^^^^ cannot `break` outside of a loop or labeled block
+...
+LL |         foo!(());
+   |         -------- in this macro invocation
+   |
+   = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0268]: `break` outside of a loop or labeled block
+  --> $DIR/break-in-unlabeled-block-in-macro.rs:27:19
+   |
+LL |         foo!(stmt break ());
+   |                   ^^^^^^^^ cannot `break` outside of a loop or labeled block
+   |
+help: consider labeling this block to be able to break within it
+   |
+LL ~     'block: {
+LL ~         foo!(stmt break 'block ());
+   |
+
+error[E0268]: `break` outside of a loop or labeled block
+  --> $DIR/break-in-unlabeled-block-in-macro.rs:12:11
+   |
+LL |         { break $e; }
+   |           ^^^^^^^^ cannot `break` outside of a loop or labeled block
+...
+LL |         foo!(@ ());
+   |         ---------- in this macro invocation
+   |
+   = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)
+help: consider labeling this block to be able to break within it
+   |
+LL |         'block: { break 'block $e; }
+   |         +++++++         ++++++
+
+error[E0268]: `break` outside of a loop or labeled block
+  --> $DIR/break-in-unlabeled-block-in-macro.rs:33:17
+   |
+LL |         foo!(=> break ());
+   |                 ^^^^^^^^ cannot `break` outside of a loop or labeled block
+
+error[E0268]: `break` outside of a loop or labeled block
+  --> $DIR/break-in-unlabeled-block-in-macro.rs:38:17
+   |
+LL |                 break ()
+   |                 ^^^^^^^^ cannot `break` outside of a loop or labeled block
+...
+LL |         bar!()
+   |         ------ in this macro invocation
+   |
+   = note: this error originates in the macro `bar` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 6 previous errors
+
+For more information about this error, try `rustc --explain E0268`.