about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustc/cfg/construct.rs2
-rw-r--r--src/librustc/hir/intravisit.rs5
-rw-r--r--src/librustc/hir/lowering.rs10
-rw-r--r--src/librustc/hir/mod.rs9
-rw-r--r--src/librustc/hir/print.rs16
-rw-r--r--src/librustc/ich/impls_hir.rs2
-rw-r--r--src/librustc/middle/expr_use_visitor.rs2
-rw-r--r--src/librustc/middle/liveness.rs4
-rw-r--r--src/librustc/middle/region.rs2
-rw-r--r--src/librustc_lint/builtin.rs2
-rw-r--r--src/librustc_mir/build/block.rs2
-rw-r--r--src/librustc_mir/hair/cx/expr.rs2
-rw-r--r--src/librustc_mir/transform/add_validation.rs2
-rw-r--r--src/librustc_passes/diagnostics.rs39
-rw-r--r--src/librustc_passes/loops.rs62
-rw-r--r--src/librustc_passes/rvalue_promotion.rs4
-rw-r--r--src/librustc_resolve/lib.rs2
-rw-r--r--src/librustc_typeck/check/mod.rs8
-rw-r--r--src/libsyntax/ast.rs6
-rw-r--r--src/libsyntax/ext/build.rs2
-rw-r--r--src/libsyntax/feature_gate.rs9
-rw-r--r--src/libsyntax/fold.rs5
-rw-r--r--src/libsyntax/parse/classify.rs2
-rw-r--r--src/libsyntax/parse/parser.rs22
-rw-r--r--src/libsyntax/print/pprust.rs15
-rw-r--r--src/libsyntax/visit.rs5
-rw-r--r--src/test/run-pass/label_break_value.rs123
-rw-r--r--src/test/ui/compare-method/proj-outlives-region.stdout0
-rw-r--r--src/test/ui/compare-method/region-extra.stdout0
-rw-r--r--src/test/ui/compare-method/region-unrelated.stdout0
-rw-r--r--src/test/ui/const-eval/const_transmute.stderr0
-rw-r--r--src/test/ui/const-expr-addr-operator.stderr0
-rw-r--r--src/test/ui/const-pattern-not-const-evaluable.stderr0
-rw-r--r--src/test/ui/feature-gate-const-indexing.stderr0
-rw-r--r--src/test/ui/feature-gate-label_break_value.rs15
-rw-r--r--src/test/ui/feature-gate-label_break_value.stderr11
-rw-r--r--src/test/ui/issue-38875/issue_38875.stderr0
-rw-r--r--src/test/ui/label_break_value_continue.rs36
-rw-r--r--src/test/ui/label_break_value_continue.stderr30
-rw-r--r--src/test/ui/label_break_value_illegal_uses.rs31
-rw-r--r--src/test/ui/label_break_value_illegal_uses.stderr31
-rw-r--r--src/test/ui/label_break_value_unlabeled_break.rs29
-rw-r--r--src/test/ui/label_break_value_unlabeled_break.stderr15
-rw-r--r--src/test/ui/loop-break-value-no-repeat.stderr2
-rw-r--r--src/test/ui/nll/drop-may-dangle.stderr0
-rw-r--r--src/test/ui/nll/maybe-initialized-drop-uninitialized.stderr0
-rw-r--r--src/test/ui/nll/ty-outlives/ty-param-implied-bounds.stderr0
-rw-r--r--src/test/ui/print_type_sizes/anonymous.stderr0
-rw-r--r--src/test/ui/print_type_sizes/anonymous.stdout0
-rw-r--r--src/test/ui/print_type_sizes/multiple_types.stderr0
-rw-r--r--src/test/ui/print_type_sizes/packed.stderr0
-rw-r--r--src/test/ui/print_type_sizes/repr-align.stderr0
-rw-r--r--src/test/ui/reachable/expr_andand.stderr0
-rw-r--r--src/test/ui/reachable/expr_oror.stderr0
-rw-r--r--src/test/ui/rfc1598-generic-associated-types/generic-associated-types-where.stderr0
-rw-r--r--src/test/ui/rfc1598-generic-associated-types/shadowing.stdout0
-rw-r--r--src/test/ui/trivial-bounds-inconsistent-associated-functions.stderr0
-rw-r--r--src/test/ui/union/union-const-eval.stderr0
-rw-r--r--src/tools/tidy/src/ui_tests.rs63
59 files changed, 550 insertions, 77 deletions
diff --git a/src/librustc/cfg/construct.rs b/src/librustc/cfg/construct.rs
index d8cf147e3ee..7e03288f572 100644
--- a/src/librustc/cfg/construct.rs
+++ b/src/librustc/cfg/construct.rs
@@ -179,7 +179,7 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
 
     fn expr(&mut self, expr: &hir::Expr, pred: CFGIndex) -> CFGIndex {
         match expr.node {
-            hir::ExprBlock(ref blk) => {
+            hir::ExprBlock(ref blk, _) => {
                 let blk_exit = self.block(&blk, pred);
                 self.add_ast_node(expr.hir_id.local_id, &[blk_exit])
             }
diff --git a/src/librustc/hir/intravisit.rs b/src/librustc/hir/intravisit.rs
index 1fb496cca62..59b058e9861 100644
--- a/src/librustc/hir/intravisit.rs
+++ b/src/librustc/hir/intravisit.rs
@@ -1015,7 +1015,10 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr) {
                              expression.span,
                              expression.id)
         }
-        ExprBlock(ref block) => visitor.visit_block(block),
+        ExprBlock(ref block, ref opt_label) => {
+            walk_list!(visitor, visit_label, opt_label);
+            visitor.visit_block(block);
+        }
         ExprAssign(ref left_hand_expression, ref right_hand_expression) => {
             visitor.visit_expr(right_hand_expression);
             visitor.visit_expr(left_hand_expression)
diff --git a/src/librustc/hir/lowering.rs b/src/librustc/hir/lowering.rs
index 7a360f7ee43..0f4871954d6 100644
--- a/src/librustc/hir/lowering.rs
+++ b/src/librustc/hir/lowering.rs
@@ -3048,7 +3048,7 @@ impl<'a> LoweringContext<'a> {
                     );
                     block.expr = Some(this.wrap_in_try_constructor(
                         "from_ok", tail, unstable_span));
-                    hir::ExprBlock(P(block))
+                    hir::ExprBlock(P(block), None)
                 })
             }
             ExprKind::Match(ref expr, ref arms) => hir::ExprMatch(
@@ -3100,7 +3100,11 @@ impl<'a> LoweringContext<'a> {
                     })
                 })
             }
-            ExprKind::Block(ref blk) => hir::ExprBlock(self.lower_block(blk, false)),
+            ExprKind::Block(ref blk, opt_label) => {
+                hir::ExprBlock(self.lower_block(blk,
+                                                opt_label.is_some()),
+                                                self.lower_label(opt_label))
+            }
             ExprKind::Assign(ref el, ref er) => {
                 hir::ExprAssign(P(self.lower_expr(el)), P(self.lower_expr(er)))
             }
@@ -3843,7 +3847,7 @@ impl<'a> LoweringContext<'a> {
     }
 
     fn expr_block(&mut self, b: P<hir::Block>, attrs: ThinVec<Attribute>) -> hir::Expr {
-        self.expr(b.span, hir::ExprBlock(b), attrs)
+        self.expr(b.span, hir::ExprBlock(b, None), attrs)
     }
 
     fn expr_tuple(&mut self, sp: Span, exprs: hir::HirVec<hir::Expr>) -> P<hir::Expr> {
diff --git a/src/librustc/hir/mod.rs b/src/librustc/hir/mod.rs
index 3f49e0bfd19..c48fb7ab7eb 100644
--- a/src/librustc/hir/mod.rs
+++ b/src/librustc/hir/mod.rs
@@ -778,9 +778,8 @@ pub struct Block {
     pub rules: BlockCheckMode,
     pub span: Span,
     /// If true, then there may exist `break 'a` values that aim to
-    /// break out of this block early. As of this writing, this is not
-    /// currently permitted in Rust itself, but it is generated as
-    /// part of `catch` statements.
+    /// break out of this block early.
+    /// Used by `'label: {}` blocks and by `catch` statements.
     pub targeted_by_break: bool,
     /// If true, don't emit return value type errors as the parser had
     /// to recover from a parse error so this block will not have an
@@ -1381,8 +1380,8 @@ pub enum Expr_ {
     /// This may also be a generator literal, indicated by the final boolean,
     /// in that case there is an GeneratorClause.
     ExprClosure(CaptureClause, P<FnDecl>, BodyId, Span, Option<GeneratorMovability>),
-    /// A block (`{ ... }`)
-    ExprBlock(P<Block>),
+    /// A block (`'label: { ... }`)
+    ExprBlock(P<Block>, Option<Label>),
 
     /// An assignment (`a = foo()`)
     ExprAssign(P<Expr>, P<Expr>),
diff --git a/src/librustc/hir/print.rs b/src/librustc/hir/print.rs
index 3943c30127d..9cd9e0dce54 100644
--- a/src/librustc/hir/print.rs
+++ b/src/librustc/hir/print.rs
@@ -1047,7 +1047,7 @@ impl<'a> State<'a> {
                         self.print_else(e.as_ref().map(|e| &**e))
                     }
                     // "final else"
-                    hir::ExprBlock(ref b) => {
+                    hir::ExprBlock(ref b, _) => {
                         self.cbox(indent_unit - 1)?;
                         self.ibox(0)?;
                         self.s.word(" else ")?;
@@ -1377,7 +1377,11 @@ impl<'a> State<'a> {
                 // empty box to satisfy the close.
                 self.ibox(0)?;
             }
-            hir::ExprBlock(ref blk) => {
+            hir::ExprBlock(ref blk, opt_label) => {
+                if let Some(label) = opt_label {
+                    self.print_name(label.name)?;
+                    self.word_space(":")?;
+                }
                 // containing cbox, will be closed by print-block at }
                 self.cbox(indent_unit)?;
                 // head-box, will be closed by print-block after {
@@ -1893,7 +1897,11 @@ impl<'a> State<'a> {
         self.word_space("=>")?;
 
         match arm.body.node {
-            hir::ExprBlock(ref blk) => {
+            hir::ExprBlock(ref blk, opt_label) => {
+                if let Some(label) = opt_label {
+                    self.print_name(label.name)?;
+                    self.word_space(":")?;
+                }
                 // the block will close the pattern's ibox
                 self.print_block_unclosed_indent(&blk, indent_unit)?;
 
@@ -2299,7 +2307,7 @@ fn expr_requires_semi_to_be_stmt(e: &hir::Expr) -> bool {
     match e.node {
         hir::ExprIf(..) |
         hir::ExprMatch(..) |
-        hir::ExprBlock(_) |
+        hir::ExprBlock(..) |
         hir::ExprWhile(..) |
         hir::ExprLoop(..) => false,
         _ => true,
diff --git a/src/librustc/ich/impls_hir.rs b/src/librustc/ich/impls_hir.rs
index 6edf1b35bdd..ed01704e6a4 100644
--- a/src/librustc/ich/impls_hir.rs
+++ b/src/librustc/ich/impls_hir.rs
@@ -589,7 +589,7 @@ impl_stable_hash_for!(enum hir::Expr_ {
     ExprLoop(body, label, loop_src),
     ExprMatch(matchee, arms, match_src),
     ExprClosure(capture_clause, decl, body_id, span, gen),
-    ExprBlock(blk),
+    ExprBlock(blk, label),
     ExprAssign(lhs, rhs),
     ExprAssignOp(op, lhs, rhs),
     ExprField(owner, field_name),
diff --git a/src/librustc/middle/expr_use_visitor.rs b/src/librustc/middle/expr_use_visitor.rs
index 614ae17fa46..3ba230fe55a 100644
--- a/src/librustc/middle/expr_use_visitor.rs
+++ b/src/librustc/middle/expr_use_visitor.rs
@@ -499,7 +499,7 @@ impl<'a, 'gcx, 'tcx> ExprUseVisitor<'a, 'gcx, 'tcx> {
                 self.consume_expr(&rhs);
             }
 
-            hir::ExprBlock(ref blk) => {
+            hir::ExprBlock(ref blk, _) => {
                 self.walk_block(&blk);
             }
 
diff --git a/src/librustc/middle/liveness.rs b/src/librustc/middle/liveness.rs
index 2b1663bed2f..b39311a7471 100644
--- a/src/librustc/middle/liveness.rs
+++ b/src/librustc/middle/liveness.rs
@@ -1187,7 +1187,9 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
             succ
           }
 
-          hir::ExprBlock(ref blk) => {
+          // Note that labels have been resolved, so we don't need to look
+          // at the label ident
+          hir::ExprBlock(ref blk, _) => {
             self.propagate_through_block(&blk, succ)
           }
         }
diff --git a/src/librustc/middle/region.rs b/src/librustc/middle/region.rs
index bfc9ff6660d..52217600759 100644
--- a/src/librustc/middle/region.rs
+++ b/src/librustc/middle/region.rs
@@ -1247,7 +1247,7 @@ fn resolve_local<'a, 'tcx>(visitor: &mut RegionResolutionVisitor<'a, 'tcx>,
             hir::ExprCast(ref subexpr, _) => {
                 record_rvalue_scope_if_borrow_expr(visitor, &subexpr, blk_id)
             }
-            hir::ExprBlock(ref block) => {
+            hir::ExprBlock(ref block, _) => {
                 if let Some(ref subexpr) = block.expr {
                     record_rvalue_scope_if_borrow_expr(
                         visitor, &subexpr, blk_id);
diff --git a/src/librustc_lint/builtin.rs b/src/librustc_lint/builtin.rs
index 0b1e9081a72..19280372d55 100644
--- a/src/librustc_lint/builtin.rs
+++ b/src/librustc_lint/builtin.rs
@@ -228,7 +228,7 @@ impl UnsafeCode {
 
 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnsafeCode {
     fn check_expr(&mut self, cx: &LateContext, e: &hir::Expr) {
-        if let hir::ExprBlock(ref blk) = e.node {
+        if let hir::ExprBlock(ref blk, _) = e.node {
             // Don't warn about generated blocks, that'll just pollute the output.
             if blk.rules == hir::UnsafeBlock(hir::UserProvided) {
                 self.report_unsafe(cx, blk.span, "usage of an `unsafe` block");
diff --git a/src/librustc_mir/build/block.rs b/src/librustc_mir/build/block.rs
index fae06db3162..c9f86d43998 100644
--- a/src/librustc_mir/build/block.rs
+++ b/src/librustc_mir/build/block.rs
@@ -36,7 +36,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
         self.in_opt_scope(opt_destruction_scope.map(|de|(de, source_info)), block, move |this| {
             this.in_scope((region_scope, source_info), LintLevel::Inherited, block, move |this| {
                 if targeted_by_break {
-                    // This is a `break`-able block (currently only `catch { ... }`)
+                    // This is a `break`-able block
                     let exit_block = this.cfg.start_new_block();
                     let block_exit = this.in_breakable_scope(
                         None, exit_block, destination.clone(), |this| {
diff --git a/src/librustc_mir/hair/cx/expr.rs b/src/librustc_mir/hair/cx/expr.rs
index 3e432d84c69..97a4e0f3724 100644
--- a/src/librustc_mir/hair/cx/expr.rs
+++ b/src/librustc_mir/hair/cx/expr.rs
@@ -292,7 +292,7 @@ fn make_mirror_unadjusted<'a, 'gcx, 'tcx>(cx: &mut Cx<'a, 'gcx, 'tcx>,
             }
         }
 
-        hir::ExprBlock(ref blk) => ExprKind::Block { body: &blk },
+        hir::ExprBlock(ref blk, _) => ExprKind::Block { body: &blk },
 
         hir::ExprAssign(ref lhs, ref rhs) => {
             ExprKind::Assign {
diff --git a/src/librustc_mir/transform/add_validation.rs b/src/librustc_mir/transform/add_validation.rs
index 584887d0373..85af00956c9 100644
--- a/src/librustc_mir/transform/add_validation.rs
+++ b/src/librustc_mir/transform/add_validation.rs
@@ -141,7 +141,7 @@ fn fn_contains_unsafe<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, src: MirSource) ->
             }
             // Check if this is an unsafe block, or an item
             match node {
-                Node::NodeExpr(&hir::Expr { node: hir::ExprBlock(ref block), ..}) => {
+                Node::NodeExpr(&hir::Expr { node: hir::ExprBlock(ref block, _), ..}) => {
                     if block_is_unsafe(&*block) {
                         // Found an unsafe block, we can bail out here.
                         return true;
diff --git a/src/librustc_passes/diagnostics.rs b/src/librustc_passes/diagnostics.rs
index 7a54fc72d53..d031694d853 100644
--- a/src/librustc_passes/diagnostics.rs
+++ b/src/librustc_passes/diagnostics.rs
@@ -259,6 +259,44 @@ let result = loop { // ok!
     i += 1;
 };
 ```
+"##,
+
+E0695: r##"
+A `break` statement without a label appeared inside a labeled block.
+
+Example of erroneous code:
+
+```compile_fail,E0695
+# #![feature(label_break_value)]
+loop {
+    'a: {
+        break;
+    }
+}
+```
+
+Make sure to always label the `break`:
+
+```
+# #![feature(label_break_value)]
+'l: loop {
+    'a: {
+        break 'l;
+    }
+}
+```
+
+Or if you want to `break` the labeled block:
+
+```
+# #![feature(label_break_value)]
+loop {
+    'a: {
+        break 'a;
+    }
+    break;
+}
+```
 "##
 }
 
@@ -271,4 +309,5 @@ register_diagnostics! {
     E0642, // patterns aren't allowed in methods without bodies
     E0666, // nested `impl Trait` is illegal
     E0667, // `impl Trait` in projections
+    E0696, // `continue` pointing to a labeled block
 }
diff --git a/src/librustc_passes/loops.rs b/src/librustc_passes/loops.rs
index 81299f4ba9f..2368b1aca69 100644
--- a/src/librustc_passes/loops.rs
+++ b/src/librustc_passes/loops.rs
@@ -13,7 +13,7 @@ use rustc::session::Session;
 
 use rustc::hir::map::Map;
 use rustc::hir::intravisit::{self, Visitor, NestedVisitorMap};
-use rustc::hir;
+use rustc::hir::{self, Destination};
 use syntax::ast;
 use syntax_pos::Span;
 
@@ -39,6 +39,7 @@ enum Context {
     Normal,
     Loop(LoopKind),
     Closure,
+    LabeledBlock,
 }
 
 #[derive(Copy, Clone)]
@@ -84,7 +85,16 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
             hir::ExprClosure(.., b, _, _) => {
                 self.with_context(Closure, |v| v.visit_nested_body(b));
             }
+            hir::ExprBlock(ref b, Some(_label)) => {
+                self.with_context(LabeledBlock, |v| v.visit_block(&b));
+            }
             hir::ExprBreak(label, ref opt_expr) => {
+                if self.require_label_in_labeled_block(e.span, &label, "break") {
+                    // If we emitted an error about an unlabeled break in a labeled
+                    // block, we don't need any further checking for this break any more
+                    return;
+                }
+
                 let loop_id = match label.target_id.into() {
                     Ok(loop_id) => loop_id,
                     Err(hir::LoopIdError::OutsideLoopScope) => ast::DUMMY_NODE_ID,
@@ -94,6 +104,7 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
                     },
                     Err(hir::LoopIdError::UnresolvedLabel) => ast::DUMMY_NODE_ID,
                 };
+
                 if loop_id != ast::DUMMY_NODE_ID {
                     match self.hir_map.find(loop_id).unwrap() {
                         hir::map::NodeBlock(_) => return,
@@ -113,13 +124,15 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
                         })
                     };
                     match loop_kind {
-                        None | Some(LoopKind::Loop(hir::LoopSource::Loop)) => (),
+                        None |
+                        Some(LoopKind::Loop(hir::LoopSource::Loop)) => (),
                         Some(kind) => {
                             struct_span_err!(self.sess, e.span, E0571,
                                              "`break` with value from a `{}` loop",
                                              kind.name())
                                 .span_label(e.span,
-                                            "can only break with a value inside `loop`")
+                                            "can only break with a value inside \
+                                            `loop` or breakable block")
                                 .span_suggestion(e.span,
                                                  &format!("instead, use `break` on its own \
                                                            without a value inside this `{}` loop",
@@ -130,13 +143,29 @@ impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> {
                     }
                 }
 
-                self.require_loop("break", e.span);
+                self.require_break_cx("break", e.span);
             }
             hir::ExprAgain(label) => {
-                if let Err(hir::LoopIdError::UnlabeledCfInWhileCondition) = label.target_id {
-                    self.emit_unlabled_cf_in_while_condition(e.span, "continue");
+                self.require_label_in_labeled_block(e.span, &label, "continue");
+
+                match label.target_id {
+                    Ok(loop_id) => {
+                        if let hir::map::NodeBlock(block) = self.hir_map.find(loop_id).unwrap() {
+                            struct_span_err!(self.sess, e.span, E0696,
+                                            "`continue` pointing to a labeled block")
+                                .span_label(e.span,
+                                            "labeled blocks cannot be `continue`'d")
+                                .span_note(block.span,
+                                            "labeled block the continue points to")
+                                .emit();
+                        }
+                    }
+                    Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
+                        self.emit_unlabled_cf_in_while_condition(e.span, "continue");
+                    }
+                    _ => {}
                 }
-                self.require_loop("continue", e.span)
+                self.require_break_cx("continue", e.span)
             },
             _ => intravisit::walk_expr(self, e),
         }
@@ -153,8 +182,9 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
         self.cx = old_cx;
     }
 
-    fn require_loop(&self, name: &str, span: Span) {
+    fn require_break_cx(&self, name: &str, span: Span) {
         match self.cx {
+            LabeledBlock |
             Loop(_) => {}
             Closure => {
                 struct_span_err!(self.sess, span, E0267, "`{}` inside of a closure", name)
@@ -169,6 +199,22 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
         }
     }
 
+    fn require_label_in_labeled_block(&mut self, span: Span, label: &Destination, cf_type: &str)
+        -> bool
+    {
+        if self.cx == LabeledBlock {
+            if label.label.is_none() {
+                struct_span_err!(self.sess, span, E0695,
+                                "unlabeled `{}` inside of a labeled block", cf_type)
+                    .span_label(span,
+                                format!("`{}` statements that would diverge to or through \
+                                a labeled block need to bear a label", cf_type))
+                    .emit();
+                return true;
+            }
+        }
+        return false;
+    }
     fn emit_unlabled_cf_in_while_condition(&mut self, span: Span, cf_type: &str) {
         struct_span_err!(self.sess, span, E0590,
                          "`break` or `continue` with no label in the condition of a `while` loop")
diff --git a/src/librustc_passes/rvalue_promotion.rs b/src/librustc_passes/rvalue_promotion.rs
index f20262c1333..2e737a016da 100644
--- a/src/librustc_passes/rvalue_promotion.rs
+++ b/src/librustc_passes/rvalue_promotion.rs
@@ -342,7 +342,7 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, e: &hir::Expr, node
             let mut callee = &**callee;
             loop {
                 callee = match callee.node {
-                    hir::ExprBlock(ref block) => match block.expr {
+                    hir::ExprBlock(ref block, _) => match block.expr {
                         Some(ref tail) => &tail,
                         None => break
                     },
@@ -404,7 +404,7 @@ fn check_expr<'a, 'tcx>(v: &mut CheckCrateVisitor<'a, 'tcx>, e: &hir::Expr, node
             }
         }
 
-        hir::ExprBlock(_) |
+        hir::ExprBlock(..) |
         hir::ExprIndex(..) |
         hir::ExprField(..) |
         hir::ExprArray(_) |
diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs
index 0f931d4374e..d2934bc9ee8 100644
--- a/src/librustc_resolve/lib.rs
+++ b/src/librustc_resolve/lib.rs
@@ -3753,6 +3753,8 @@ impl<'a> Resolver<'a> {
                 self.ribs[ValueNS].pop();
             }
 
+            ExprKind::Block(ref block, label) => self.resolve_labeled_block(label, block.id, block),
+
             // Equivalent to `visit::walk_expr` + passing some context to children.
             ExprKind::Field(ref subexpression, _) => {
                 self.resolve_expr(subexpression, Some(expr));
diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs
index 877d6b289c9..39de09fe34f 100644
--- a/src/librustc_typeck/check/mod.rs
+++ b/src/librustc_typeck/check/mod.rs
@@ -3536,7 +3536,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
 
         // Warn for non-block expressions with diverging children.
         match expr.node {
-            hir::ExprBlock(_) |
+            hir::ExprBlock(..) |
             hir::ExprLoop(..) | hir::ExprWhile(..) |
             hir::ExprIf(..) | hir::ExprMatch(..) => {}
 
@@ -3912,7 +3912,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
           hir::ExprClosure(capture, ref decl, body_id, _, gen) => {
               self.check_expr_closure(expr, capture, &decl, body_id, gen, expected)
           }
-          hir::ExprBlock(ref body) => {
+          hir::ExprBlock(ref body, _) => {
             self.check_block_with_expected(&body, expected)
           }
           hir::ExprCall(ref callee, ref args) => {
@@ -4326,8 +4326,8 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
         };
 
         // In some cases, blocks have just one exit, but other blocks
-        // can be targeted by multiple breaks. This cannot happen in
-        // normal Rust syntax today, but it can happen when we desugar
+        // can be targeted by multiple breaks. This can happen both
+        // with labeled blocks as well as when we desugar
         // a `do catch { ... }` expression.
         //
         // Example 1:
diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs
index f8cd6103bdf..2b6635ec783 100644
--- a/src/libsyntax/ast.rs
+++ b/src/libsyntax/ast.rs
@@ -934,7 +934,7 @@ impl Expr {
     /// Whether this expression would be valid somewhere that expects a value, for example, an `if`
     /// condition.
     pub fn returns(&self) -> bool {
-        if let ExprKind::Block(ref block) = self.node {
+        if let ExprKind::Block(ref block, _) = self.node {
             match block.stmts.last().map(|last_stmt| &last_stmt.node) {
                 // implicit return
                 Some(&StmtKind::Expr(_)) => true,
@@ -1121,8 +1121,8 @@ pub enum ExprKind {
     ///
     /// The final span is the span of the argument block `|...|`
     Closure(CaptureBy, Movability, P<FnDecl>, P<Expr>, Span),
-    /// A block (`{ ... }`)
-    Block(P<Block>),
+    /// A block (`'label: { ... }`)
+    Block(P<Block>, Option<Label>),
     /// A catch block (`catch { ... }`)
     Catch(P<Block>),
 
diff --git a/src/libsyntax/ext/build.rs b/src/libsyntax/ext/build.rs
index 0b64189b2bc..a4f9ebcf418 100644
--- a/src/libsyntax/ext/build.rs
+++ b/src/libsyntax/ext/build.rs
@@ -668,7 +668,7 @@ impl<'a> AstBuilder for ExtCtxt<'a> {
         self.expr(span, ast::ExprKind::MethodCall(segment, args))
     }
     fn expr_block(&self, b: P<ast::Block>) -> P<ast::Expr> {
-        self.expr(b.span, ast::ExprKind::Block(b))
+        self.expr(b.span, ast::ExprKind::Block(b, None))
     }
     fn field_imm(&self, span: Span, ident: Ident, e: P<ast::Expr>) -> ast::Field {
         ast::Field {
diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs
index bf78723e413..f1229520c77 100644
--- a/src/libsyntax/feature_gate.rs
+++ b/src/libsyntax/feature_gate.rs
@@ -466,6 +466,9 @@ declare_features! (
 
     // inconsistent bounds in where clauses
     (active, trivial_bounds, "1.28.0", Some(48214), None),
+
+    // 'a: { break 'a; }
+    (active, label_break_value, "1.28.0", Some(48594), None),
 );
 
 declare_features! (
@@ -1696,6 +1699,12 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
                                     "multiple patterns in `if let` and `while let` are unstable");
                 }
             }
+            ast::ExprKind::Block(_, opt_label) => {
+                if let Some(label) = opt_label {
+                    gate_feature_post!(&self, label_break_value, label.ident.span,
+                                    "labels on blocks are unstable");
+                }
+            }
             _ => {}
         }
         visit::walk_expr(self, e);
diff --git a/src/libsyntax/fold.rs b/src/libsyntax/fold.rs
index d67995761f6..29cc208c06b 100644
--- a/src/libsyntax/fold.rs
+++ b/src/libsyntax/fold.rs
@@ -1256,7 +1256,10 @@ pub fn noop_fold_expr<T: Folder>(Expr {id, node, span, attrs}: Expr, folder: &mu
                                   folder.fold_expr(body),
                                   folder.new_span(span))
             }
-            ExprKind::Block(blk) => ExprKind::Block(folder.fold_block(blk)),
+            ExprKind::Block(blk, opt_label) => {
+                ExprKind::Block(folder.fold_block(blk),
+                                opt_label.map(|label| folder.fold_label(label)))
+            }
             ExprKind::Assign(el, er) => {
                 ExprKind::Assign(folder.fold_expr(el), folder.fold_expr(er))
             }
diff --git a/src/libsyntax/parse/classify.rs b/src/libsyntax/parse/classify.rs
index b8e02556625..531483e7de1 100644
--- a/src/libsyntax/parse/classify.rs
+++ b/src/libsyntax/parse/classify.rs
@@ -26,7 +26,7 @@ pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
         ast::ExprKind::If(..) |
         ast::ExprKind::IfLet(..) |
         ast::ExprKind::Match(..) |
-        ast::ExprKind::Block(_) |
+        ast::ExprKind::Block(..) |
         ast::ExprKind::While(..) |
         ast::ExprKind::WhileLet(..) |
         ast::ExprKind::Loop(..) |
diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs
index 3f0df6d055b..7b91c491700 100644
--- a/src/libsyntax/parse/parser.rs
+++ b/src/libsyntax/parse/parser.rs
@@ -128,7 +128,7 @@ macro_rules! maybe_whole_expr {
                 token::NtBlock(ref block) => {
                     $p.bump();
                     let span = $p.span;
-                    let kind = ExprKind::Block((*block).clone());
+                    let kind = ExprKind::Block((*block).clone(), None);
                     return Ok($p.mk_expr(span, kind, ThinVec::new()));
                 }
                 _ => {},
@@ -2244,7 +2244,7 @@ impl<'a> Parser<'a> {
                 };
             }
             token::OpenDelim(token::Brace) => {
-                return self.parse_block_expr(lo, BlockCheckMode::Default, attrs);
+                return self.parse_block_expr(None, lo, BlockCheckMode::Default, attrs);
             }
             token::BinOp(token::Or) | token::OrOr => {
                 return self.parse_lambda_expr(attrs);
@@ -2318,7 +2318,13 @@ impl<'a> Parser<'a> {
                     if self.eat_keyword(keywords::Loop) {
                         return self.parse_loop_expr(Some(label), lo, attrs)
                     }
-                    let msg = "expected `while`, `for`, or `loop` after a label";
+                    if self.token == token::OpenDelim(token::Brace) {
+                        return self.parse_block_expr(Some(label),
+                                                     lo,
+                                                     BlockCheckMode::Default,
+                                                     attrs);
+                    }
+                    let msg = "expected `while`, `for`, `loop` or `{` after a label";
                     let mut err = self.fatal(msg);
                     err.span_label(self.span, msg);
                     return Err(err);
@@ -2338,6 +2344,7 @@ impl<'a> Parser<'a> {
                 }
                 if self.eat_keyword(keywords::Unsafe) {
                     return self.parse_block_expr(
+                        None,
                         lo,
                         BlockCheckMode::Unsafe(ast::UserProvided),
                         attrs);
@@ -2502,7 +2509,8 @@ impl<'a> Parser<'a> {
     }
 
     /// Parse a block or unsafe block
-    pub fn parse_block_expr(&mut self, lo: Span, blk_mode: BlockCheckMode,
+    pub fn parse_block_expr(&mut self, opt_label: Option<Label>,
+                            lo: Span, blk_mode: BlockCheckMode,
                             outer_attrs: ThinVec<Attribute>)
                             -> PResult<'a, P<Expr>> {
         self.expect(&token::OpenDelim(token::Brace))?;
@@ -2511,7 +2519,7 @@ impl<'a> Parser<'a> {
         attrs.extend(self.parse_inner_attributes()?);
 
         let blk = self.parse_block_tail(lo, blk_mode)?;
-        return Ok(self.mk_expr(blk.span, ExprKind::Block(blk), attrs));
+        return Ok(self.mk_expr(blk.span, ExprKind::Block(blk, opt_label), attrs));
     }
 
     /// parse a.b or a(13) or a[4] or just a
@@ -3261,7 +3269,7 @@ impl<'a> Parser<'a> {
                 // If an explicit return type is given, require a
                 // block to appear (RFC 968).
                 let body_lo = self.span;
-                self.parse_block_expr(body_lo, BlockCheckMode::Default, ThinVec::new())?
+                self.parse_block_expr(None, body_lo, BlockCheckMode::Default, ThinVec::new())?
             }
         };
 
@@ -3277,7 +3285,7 @@ impl<'a> Parser<'a> {
             return self.parse_if_expr(ThinVec::new());
         } else {
             let blk = self.parse_block()?;
-            return Ok(self.mk_expr(blk.span, ExprKind::Block(blk), ThinVec::new()));
+            return Ok(self.mk_expr(blk.span, ExprKind::Block(blk, None), ThinVec::new()));
         }
     }
 
diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs
index 8e33fa08083..a700799cde5 100644
--- a/src/libsyntax/print/pprust.rs
+++ b/src/libsyntax/print/pprust.rs
@@ -1792,7 +1792,7 @@ impl<'a> State<'a> {
                         self.print_else(e.as_ref().map(|e| &**e))
                     }
                     // "final else"
-                    ast::ExprKind::Block(ref b) => {
+                    ast::ExprKind::Block(ref b, _) => {
                         self.cbox(INDENT_UNIT - 1)?;
                         self.ibox(0)?;
                         self.s.word(" else ")?;
@@ -2182,7 +2182,11 @@ impl<'a> State<'a> {
                 // empty box to satisfy the close.
                 self.ibox(0)?;
             }
-            ast::ExprKind::Block(ref blk) => {
+            ast::ExprKind::Block(ref blk, opt_label) => {
+                if let Some(label) = opt_label {
+                    self.print_ident(label.ident)?;
+                    self.word_space(":")?;
+                }
                 // containing cbox, will be closed by print-block at }
                 self.cbox(INDENT_UNIT)?;
                 // head-box, will be closed by print-block after {
@@ -2695,7 +2699,12 @@ impl<'a> State<'a> {
         self.word_space("=>")?;
 
         match arm.body.node {
-            ast::ExprKind::Block(ref blk) => {
+            ast::ExprKind::Block(ref blk, opt_label) => {
+                if let Some(label) = opt_label {
+                    self.print_ident(label.ident)?;
+                    self.word_space(":")?;
+                }
+
                 // the block will close the pattern's ibox
                 self.print_block_unclosed_indent(blk, INDENT_UNIT)?;
 
diff --git a/src/libsyntax/visit.rs b/src/libsyntax/visit.rs
index 40d59d3ff8b..2013e838c05 100644
--- a/src/libsyntax/visit.rs
+++ b/src/libsyntax/visit.rs
@@ -736,7 +736,10 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) {
                              expression.span,
                              expression.id)
         }
-        ExprKind::Block(ref block) => visitor.visit_block(block),
+        ExprKind::Block(ref block, ref opt_label) => {
+            walk_list!(visitor, visit_label, opt_label);
+            visitor.visit_block(block);
+        }
         ExprKind::Assign(ref left_hand_expression, ref right_hand_expression) => {
             visitor.visit_expr(left_hand_expression);
             visitor.visit_expr(right_hand_expression);
diff --git a/src/test/run-pass/label_break_value.rs b/src/test/run-pass/label_break_value.rs
new file mode 100644
index 00000000000..444845e24dc
--- /dev/null
+++ b/src/test/run-pass/label_break_value.rs
@@ -0,0 +1,123 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(label_break_value)]
+
+// Test control flow to follow label_break_value semantics
+fn label_break(a: bool, b: bool) -> u32 {
+    let mut v = 0;
+    'b: {
+        v = 1;
+        if a {
+            break 'b;
+        }
+        v = 2;
+        if b {
+            break 'b;
+        }
+        v = 3;
+    }
+    return v;
+}
+
+// Test that values can be returned
+fn break_value(a: bool, b: bool) -> u32 {
+    let result = 'block: {
+        if a { break 'block 1; }
+        if b { break 'block 2; }
+        3
+    };
+    result
+}
+
+// Test nesting of labeled blocks
+// here we only check that it compiles
+fn label_break_nested() {
+    'b: {
+        println!("hi");
+        if false {
+            break 'b;
+        }
+        'c: {
+            if false {
+                break 'b;
+            }
+            break 'c;
+        }
+        println!("hello");
+        if true {
+            break 'b;
+        }
+    }
+}
+
+// Tests for mixing labeled blocks with loop constructs
+// This function should be the identity function
+fn label_break_mixed(v: u32) -> u32 {
+    let mut r = 0;
+    'b: {
+        // Unlabeled break still works
+        // (only crossing boundaries is an error)
+        loop {
+            break;
+        }
+        if v == 0 {
+            break 'b;
+        }
+        // Labeled breaking an inner loop still works
+        'c: loop {
+            if r == 1 {
+                break 'c;
+            }
+            r += 1;
+        }
+        assert_eq!(r, 1);
+        if v == 1 {
+            break 'b;
+        }
+        // Labeled breaking an outer loop still works
+        'd: loop {
+            'e: {
+                if v == r {
+                    break 'b;
+                }
+                if r == 5 {
+                    break 'd;
+                }
+                r += 1;
+            }
+        }
+        assert_eq!(r, 5);
+        assert!(v > r);
+        // Here we test return from inside a labeled block
+        return v;
+    }
+    r
+}
+
+pub fn main() {
+    assert_eq!(label_break(true, false), 1);
+    assert_eq!(label_break(false, true), 2);
+    assert_eq!(label_break(false, false), 3);
+
+    assert_eq!(break_value(true, false), 1);
+    assert_eq!(break_value(false, true), 2);
+    assert_eq!(break_value(false, false), 3);
+
+    assert_eq!(label_break_mixed(0), 0);
+    assert_eq!(label_break_mixed(1), 1);
+    assert_eq!(label_break_mixed(2), 2);
+    assert_eq!(label_break_mixed(3), 3);
+    assert_eq!(label_break_mixed(4), 4);
+    assert_eq!(label_break_mixed(5), 5);
+    assert_eq!(label_break_mixed(6), 6);
+
+    // FIXME: ensure that labeled blocks work if produced by macros and in match arms
+}
diff --git a/src/test/ui/compare-method/proj-outlives-region.stdout b/src/test/ui/compare-method/proj-outlives-region.stdout
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/compare-method/proj-outlives-region.stdout
+++ /dev/null
diff --git a/src/test/ui/compare-method/region-extra.stdout b/src/test/ui/compare-method/region-extra.stdout
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/compare-method/region-extra.stdout
+++ /dev/null
diff --git a/src/test/ui/compare-method/region-unrelated.stdout b/src/test/ui/compare-method/region-unrelated.stdout
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/compare-method/region-unrelated.stdout
+++ /dev/null
diff --git a/src/test/ui/const-eval/const_transmute.stderr b/src/test/ui/const-eval/const_transmute.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/const-eval/const_transmute.stderr
+++ /dev/null
diff --git a/src/test/ui/const-expr-addr-operator.stderr b/src/test/ui/const-expr-addr-operator.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/const-expr-addr-operator.stderr
+++ /dev/null
diff --git a/src/test/ui/const-pattern-not-const-evaluable.stderr b/src/test/ui/const-pattern-not-const-evaluable.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/const-pattern-not-const-evaluable.stderr
+++ /dev/null
diff --git a/src/test/ui/feature-gate-const-indexing.stderr b/src/test/ui/feature-gate-const-indexing.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/feature-gate-const-indexing.stderr
+++ /dev/null
diff --git a/src/test/ui/feature-gate-label_break_value.rs b/src/test/ui/feature-gate-label_break_value.rs
new file mode 100644
index 00000000000..dcda7580bda
--- /dev/null
+++ b/src/test/ui/feature-gate-label_break_value.rs
@@ -0,0 +1,15 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+pub fn main() {
+    'a: { //~ ERROR labels on blocks are unstable
+        break 'a;
+    }
+}
diff --git a/src/test/ui/feature-gate-label_break_value.stderr b/src/test/ui/feature-gate-label_break_value.stderr
new file mode 100644
index 00000000000..8e8f31e7871
--- /dev/null
+++ b/src/test/ui/feature-gate-label_break_value.stderr
@@ -0,0 +1,11 @@
+error[E0658]: labels on blocks are unstable (see issue #48594)
+  --> $DIR/feature-gate-label_break_value.rs:12:5
+   |
+LL |     'a: { //~ ERROR labels on blocks are unstable
+   |     ^^
+   |
+   = help: add #![feature(label_break_value)] to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/src/test/ui/issue-38875/issue_38875.stderr b/src/test/ui/issue-38875/issue_38875.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/issue-38875/issue_38875.stderr
+++ /dev/null
diff --git a/src/test/ui/label_break_value_continue.rs b/src/test/ui/label_break_value_continue.rs
new file mode 100644
index 00000000000..52e24b759d1
--- /dev/null
+++ b/src/test/ui/label_break_value_continue.rs
@@ -0,0 +1,36 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(label_break_value)]
+
+// Simple continue pointing to an unlabeled break should yield in an error
+fn continue_simple() {
+    'b: {
+        continue; //~ ERROR unlabeled `continue` inside of a labeled block
+    }
+}
+
+// Labeled continue pointing to an unlabeled break should yield in an error
+fn continue_labeled() {
+    'b: {
+        continue 'b; //~ ERROR `continue` pointing to a labeled block
+    }
+}
+
+// Simple continue that would cross a labeled block should yield in an error
+fn continue_crossing() {
+    loop {
+        'b: {
+            continue; //~ ERROR unlabeled `continue` inside of a labeled block
+        }
+    }
+}
+
+pub fn main() {}
diff --git a/src/test/ui/label_break_value_continue.stderr b/src/test/ui/label_break_value_continue.stderr
new file mode 100644
index 00000000000..24c2d1a22d0
--- /dev/null
+++ b/src/test/ui/label_break_value_continue.stderr
@@ -0,0 +1,30 @@
+error[E0695]: unlabeled `continue` inside of a labeled block
+  --> $DIR/label_break_value_continue.rs:16:9
+   |
+LL |         continue; //~ ERROR unlabeled `continue` inside of a labeled block
+   |         ^^^^^^^^ `continue` statements that would diverge to or through a labeled block need to bear a label
+
+error[E0696]: `continue` pointing to a labeled block
+  --> $DIR/label_break_value_continue.rs:23:9
+   |
+LL |         continue 'b; //~ ERROR `continue` pointing to a labeled block
+   |         ^^^^^^^^^^^ labeled blocks cannot be `continue`'d
+   |
+note: labeled block the continue points to
+  --> $DIR/label_break_value_continue.rs:22:5
+   |
+LL | /     'b: {
+LL | |         continue 'b; //~ ERROR `continue` pointing to a labeled block
+LL | |     }
+   | |_____^
+
+error[E0695]: unlabeled `continue` inside of a labeled block
+  --> $DIR/label_break_value_continue.rs:31:13
+   |
+LL |             continue; //~ ERROR unlabeled `continue` inside of a labeled block
+   |             ^^^^^^^^ `continue` statements that would diverge to or through a labeled block need to bear a label
+
+error: aborting due to 3 previous errors
+
+Some errors occurred: E0695, E0696.
+For more information about an error, try `rustc --explain E0695`.
diff --git a/src/test/ui/label_break_value_illegal_uses.rs b/src/test/ui/label_break_value_illegal_uses.rs
new file mode 100644
index 00000000000..18644033e4f
--- /dev/null
+++ b/src/test/ui/label_break_value_illegal_uses.rs
@@ -0,0 +1,31 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(label_break_value)]
+
+// These are forbidden occurences of label-break-value
+
+fn labeled_unsafe() {
+    unsafe 'b: {} //~ ERROR expected one of `extern`, `fn`, or `{`
+}
+
+fn labeled_if() {
+    if true 'b: {} //~ ERROR expected `{`, found `'b`
+}
+
+fn labeled_else() {
+    if true {} else 'b: {} //~ ERROR expected `{`, found `'b`
+}
+
+fn labeled_match() {
+    match false 'b: {} //~ ERROR expected one of `.`, `?`, `{`, or an operator
+}
+
+pub fn main() {}
diff --git a/src/test/ui/label_break_value_illegal_uses.stderr b/src/test/ui/label_break_value_illegal_uses.stderr
new file mode 100644
index 00000000000..0ab1ad2c242
--- /dev/null
+++ b/src/test/ui/label_break_value_illegal_uses.stderr
@@ -0,0 +1,31 @@
+error: expected one of `extern`, `fn`, or `{`, found `'b`
+  --> $DIR/label_break_value_illegal_uses.rs:16:12
+   |
+LL |     unsafe 'b: {} //~ ERROR expected one of `extern`, `fn`, or `{`
+   |            ^^ expected one of `extern`, `fn`, or `{` here
+
+error: expected `{`, found `'b`
+  --> $DIR/label_break_value_illegal_uses.rs:20:13
+   |
+LL |     if true 'b: {} //~ ERROR expected `{`, found `'b`
+   |     --      ^^----
+   |     |       |
+   |     |       help: try placing this code inside a block: `{ 'b: { } }`
+   |     this `if` statement has a condition, but no block
+
+error: expected `{`, found `'b`
+  --> $DIR/label_break_value_illegal_uses.rs:24:21
+   |
+LL |     if true {} else 'b: {} //~ ERROR expected `{`, found `'b`
+   |                     ^^----
+   |                     |
+   |                     help: try placing this code inside a block: `{ 'b: { } }`
+
+error: expected one of `.`, `?`, `{`, or an operator, found `'b`
+  --> $DIR/label_break_value_illegal_uses.rs:28:17
+   |
+LL |     match false 'b: {} //~ ERROR expected one of `.`, `?`, `{`, or an operator
+   |                 ^^ expected one of `.`, `?`, `{`, or an operator here
+
+error: aborting due to 4 previous errors
+
diff --git a/src/test/ui/label_break_value_unlabeled_break.rs b/src/test/ui/label_break_value_unlabeled_break.rs
new file mode 100644
index 00000000000..38918da291c
--- /dev/null
+++ b/src/test/ui/label_break_value_unlabeled_break.rs
@@ -0,0 +1,29 @@
+// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(label_break_value)]
+
+// Simple unlabeled break should yield in an error
+fn unlabeled_break_simple() {
+    'b: {
+        break; //~ ERROR unlabeled `break` inside of a labeled block
+    }
+}
+
+// Unlabeled break that would cross a labeled block should yield in an error
+fn unlabeled_break_crossing() {
+    loop {
+        'b: {
+            break; //~ ERROR unlabeled `break` inside of a labeled block
+        }
+    }
+}
+
+pub fn main() {}
diff --git a/src/test/ui/label_break_value_unlabeled_break.stderr b/src/test/ui/label_break_value_unlabeled_break.stderr
new file mode 100644
index 00000000000..8a25975a7bd
--- /dev/null
+++ b/src/test/ui/label_break_value_unlabeled_break.stderr
@@ -0,0 +1,15 @@
+error[E0695]: unlabeled `break` inside of a labeled block
+  --> $DIR/label_break_value_unlabeled_break.rs:16:9
+   |
+LL |         break; //~ ERROR unlabeled `break` inside of a labeled block
+   |         ^^^^^ `break` statements that would diverge to or through a labeled block need to bear a label
+
+error[E0695]: unlabeled `break` inside of a labeled block
+  --> $DIR/label_break_value_unlabeled_break.rs:24:13
+   |
+LL |             break; //~ ERROR unlabeled `break` inside of a labeled block
+   |             ^^^^^ `break` statements that would diverge to or through a labeled block need to bear a label
+
+error: aborting due to 2 previous errors
+
+For more information about this error, try `rustc --explain E0695`.
diff --git a/src/test/ui/loop-break-value-no-repeat.stderr b/src/test/ui/loop-break-value-no-repeat.stderr
index 4421f557e42..68a2bab1674 100644
--- a/src/test/ui/loop-break-value-no-repeat.stderr
+++ b/src/test/ui/loop-break-value-no-repeat.stderr
@@ -2,7 +2,7 @@ error[E0571]: `break` with value from a `for` loop
   --> $DIR/loop-break-value-no-repeat.rs:22:9
    |
 LL |         break 22 //~ ERROR `break` with value from a `for` loop
-   |         ^^^^^^^^ can only break with a value inside `loop`
+   |         ^^^^^^^^ can only break with a value inside `loop` or breakable block
 help: instead, use `break` on its own without a value inside this `for` loop
    |
 LL |         break //~ ERROR `break` with value from a `for` loop
diff --git a/src/test/ui/nll/drop-may-dangle.stderr b/src/test/ui/nll/drop-may-dangle.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/nll/drop-may-dangle.stderr
+++ /dev/null
diff --git a/src/test/ui/nll/maybe-initialized-drop-uninitialized.stderr b/src/test/ui/nll/maybe-initialized-drop-uninitialized.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/nll/maybe-initialized-drop-uninitialized.stderr
+++ /dev/null
diff --git a/src/test/ui/nll/ty-outlives/ty-param-implied-bounds.stderr b/src/test/ui/nll/ty-outlives/ty-param-implied-bounds.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/nll/ty-outlives/ty-param-implied-bounds.stderr
+++ /dev/null
diff --git a/src/test/ui/print_type_sizes/anonymous.stderr b/src/test/ui/print_type_sizes/anonymous.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/print_type_sizes/anonymous.stderr
+++ /dev/null
diff --git a/src/test/ui/print_type_sizes/anonymous.stdout b/src/test/ui/print_type_sizes/anonymous.stdout
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/print_type_sizes/anonymous.stdout
+++ /dev/null
diff --git a/src/test/ui/print_type_sizes/multiple_types.stderr b/src/test/ui/print_type_sizes/multiple_types.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/print_type_sizes/multiple_types.stderr
+++ /dev/null
diff --git a/src/test/ui/print_type_sizes/packed.stderr b/src/test/ui/print_type_sizes/packed.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/print_type_sizes/packed.stderr
+++ /dev/null
diff --git a/src/test/ui/print_type_sizes/repr-align.stderr b/src/test/ui/print_type_sizes/repr-align.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/print_type_sizes/repr-align.stderr
+++ /dev/null
diff --git a/src/test/ui/reachable/expr_andand.stderr b/src/test/ui/reachable/expr_andand.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/reachable/expr_andand.stderr
+++ /dev/null
diff --git a/src/test/ui/reachable/expr_oror.stderr b/src/test/ui/reachable/expr_oror.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/reachable/expr_oror.stderr
+++ /dev/null
diff --git a/src/test/ui/rfc1598-generic-associated-types/generic-associated-types-where.stderr b/src/test/ui/rfc1598-generic-associated-types/generic-associated-types-where.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/rfc1598-generic-associated-types/generic-associated-types-where.stderr
+++ /dev/null
diff --git a/src/test/ui/rfc1598-generic-associated-types/shadowing.stdout b/src/test/ui/rfc1598-generic-associated-types/shadowing.stdout
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/rfc1598-generic-associated-types/shadowing.stdout
+++ /dev/null
diff --git a/src/test/ui/trivial-bounds-inconsistent-associated-functions.stderr b/src/test/ui/trivial-bounds-inconsistent-associated-functions.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/trivial-bounds-inconsistent-associated-functions.stderr
+++ /dev/null
diff --git a/src/test/ui/union/union-const-eval.stderr b/src/test/ui/union/union-const-eval.stderr
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/src/test/ui/union/union-const-eval.stderr
+++ /dev/null
diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs
index 351005ff4b8..c1574407807 100644
--- a/src/tools/tidy/src/ui_tests.rs
+++ b/src/tools/tidy/src/ui_tests.rs
@@ -10,32 +10,49 @@
 
 //! Tidy check to ensure that there are no stray `.stderr` files in UI test directories.
 
+use std::fs;
 use std::path::Path;
 
 pub fn check(path: &Path, bad: &mut bool) {
-    super::walk_many(&[&path.join("test/ui"), &path.join("test/ui-fulldeps")],
-                     &mut |_| false,
-                     &mut |file_path| {
-        if let Some(ext) = file_path.extension() {
-            if ext == "stderr" || ext == "stdout" {
-                // Test output filenames have the format:
-                // $testname.stderr
-                // $testname.$mode.stderr
-                // $testname.$revision.stderr
-                // $testname.$revision.$mode.stderr
-                //
-                // For now, just make sure that there is a corresponding
-                // $testname.rs file.
-                let testname = file_path.file_name().unwrap()
-                                        .to_str().unwrap()
-                                        .splitn(2, '.').next().unwrap();
-                if !file_path.with_file_name(testname)
-                             .with_extension("rs")
-                             .exists() {
-                    println!("Stray file with UI testing output: {:?}", file_path);
-                    *bad = true;
+    super::walk_many(
+        &[&path.join("test/ui"), &path.join("test/ui-fulldeps")],
+        &mut |_| false,
+        &mut |file_path| {
+            if let Some(ext) = file_path.extension() {
+                if ext == "stderr" || ext == "stdout" {
+                    // Test output filenames have the format:
+                    // $testname.stderr
+                    // $testname.$mode.stderr
+                    // $testname.$revision.stderr
+                    // $testname.$revision.$mode.stderr
+                    //
+                    // For now, just make sure that there is a corresponding
+                    // $testname.rs file.
+                    let testname = file_path
+                        .file_name()
+                        .unwrap()
+                        .to_str()
+                        .unwrap()
+                        .splitn(2, '.')
+                        .next()
+                        .unwrap();
+                    if !file_path
+                        .with_file_name(testname)
+                        .with_extension("rs")
+                        .exists()
+                    {
+                        println!("Stray file with UI testing output: {:?}", file_path);
+                        *bad = true;
+                    }
+
+                    if let Ok(metadata) = fs::metadata(file_path) {
+                        if metadata.len() == 0 {
+                            println!("Empty file with UI testing output: {:?}", file_path);
+                            *bad = true;
+                        }
+                    }
                 }
             }
-        }
-    });
+        },
+    );
 }