about summary refs log tree commit diff
diff options
context:
space:
mode:
authorManish Goregaokar <manishsmail@gmail.com>2020-07-02 15:55:53 -0700
committerGitHub <noreply@github.com>2020-07-02 15:55:53 -0700
commit2d83cbb8b68d6a83a9f0559d89904a6a1e0bb9e3 (patch)
tree57b8be6fab0a2d0eee3d0c8d7f679fc28172986a
parent65342fd341f63bb2b594a47436c7591f2ebc0208 (diff)
parentcb541dc12cdf2ba14a8f5d210ac7a6b1efa69c22 (diff)
downloadrust-2d83cbb8b68d6a83a9f0559d89904a6a1e0bb9e3.tar.gz
rust-2d83cbb8b68d6a83a9f0559d89904a6a1e0bb9e3.zip
Rollup merge of #73726 - davidtwco:issue-73541-labelled-break-through-closure-async, r=petrochenkov
resolve: disallow labelled breaks/continues through closures/async blocks

Fixes #73541.

This PR modifies name resolution to prohibit labelled breaks/continues through closures or async blocks, fixing an ICE. In addition, it improves the diagnostics surrounding labelled breaks/continues through closures or async blocks by informing the user if the label exists in an parent scope and telling them that won't work.

r? @petrochenkov (resolve)
cc @estebank (diagnostic changes) @tmandry (issue is from `wg-async-foundations`)
-rw-r--r--src/librustc_error_codes/error_codes.rs1
-rw-r--r--src/librustc_error_codes/error_codes/E0767.md20
-rw-r--r--src/librustc_passes/liveness.rs11
-rw-r--r--src/librustc_resolve/diagnostics.rs78
-rw-r--r--src/librustc_resolve/late.rs167
-rw-r--r--src/librustc_resolve/late/diagnostics.rs28
-rw-r--r--src/librustc_resolve/lib.rs8
-rw-r--r--src/test/ui/break-outside-loop.rs4
-rw-r--r--src/test/ui/break-outside-loop.stderr15
-rw-r--r--src/test/ui/error-codes/E0767.rs7
-rw-r--r--src/test/ui/error-codes/E0767.stderr14
-rw-r--r--src/test/ui/issues/issue-62480.rs4
-rw-r--r--src/test/ui/issues/issue-62480.stderr20
-rw-r--r--src/test/ui/issues/issue-66702-break-outside-loop-val.rs4
-rw-r--r--src/test/ui/issues/issue-66702-break-outside-loop-val.stderr15
-rw-r--r--src/test/ui/issues/issue-73541-1.rs12
-rw-r--r--src/test/ui/issues/issue-73541-1.stderr14
-rw-r--r--src/test/ui/issues/issue-73541-2.rs20
-rw-r--r--src/test/ui/issues/issue-73541-2.stderr18
-rw-r--r--src/test/ui/issues/issue-73541-3.rs9
-rw-r--r--src/test/ui/issues/issue-73541-3.stderr12
-rw-r--r--src/test/ui/issues/issue-73541.rs9
-rw-r--r--src/test/ui/issues/issue-73541.stderr14
-rw-r--r--src/test/ui/resolve/resolve-label.rs2
-rw-r--r--src/test/ui/resolve/resolve-label.stderr11
-rw-r--r--src/test/ui/suggestions/suggest-labels.stderr30
26 files changed, 427 insertions, 120 deletions
diff --git a/src/librustc_error_codes/error_codes.rs b/src/librustc_error_codes/error_codes.rs
index a5527158aab..00c072e1b04 100644
--- a/src/librustc_error_codes/error_codes.rs
+++ b/src/librustc_error_codes/error_codes.rs
@@ -448,6 +448,7 @@ E0763: include_str!("./error_codes/E0763.md"),
 E0764: include_str!("./error_codes/E0764.md"),
 E0765: include_str!("./error_codes/E0765.md"),
 E0766: include_str!("./error_codes/E0766.md"),
+E0767: include_str!("./error_codes/E0767.md"),
 ;
 //  E0006, // merged with E0005
 //  E0008, // cannot bind by-move into a pattern guard
diff --git a/src/librustc_error_codes/error_codes/E0767.md b/src/librustc_error_codes/error_codes/E0767.md
new file mode 100644
index 00000000000..679fe7e41e9
--- /dev/null
+++ b/src/librustc_error_codes/error_codes/E0767.md
@@ -0,0 +1,20 @@
+An unreachable label was used.
+
+Erroneous code example:
+
+```compile_fail,E0767
+'a: loop {
+    || {
+        loop { break 'a } // error: use of unreachable label `'a`
+    };
+}
+```
+
+Ensure that the label is within scope. Labels are not reachable through
+functions, closures, async blocks or modules. Example:
+
+```
+'a: loop {
+    break 'a; // ok!
+}
+```
diff --git a/src/librustc_passes/liveness.rs b/src/librustc_passes/liveness.rs
index 798c6b8925b..b288a41e0cf 100644
--- a/src/librustc_passes/liveness.rs
+++ b/src/librustc_passes/liveness.rs
@@ -1123,16 +1123,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
 
                 match target {
                     Some(b) => self.propagate_through_opt_expr(opt_expr.as_ref().map(|e| &**e), b),
-                    None => {
-                        // FIXME: This should have been checked earlier. Once this is fixed,
-                        // replace with `delay_span_bug`. (#62480)
-                        self.ir
-                            .tcx
-                            .sess
-                            .struct_span_err(expr.span, "`break` to unknown label")
-                            .emit();
-                        rustc_errors::FatalError.raise()
-                    }
+                    None => span_bug!(expr.span, "`break` to unknown label"),
                 }
             }
 
diff --git a/src/librustc_resolve/diagnostics.rs b/src/librustc_resolve/diagnostics.rs
index 2854683b61b..c64c617b54b 100644
--- a/src/librustc_resolve/diagnostics.rs
+++ b/src/librustc_resolve/diagnostics.rs
@@ -33,6 +33,10 @@ type Res = def::Res<ast::NodeId>;
 /// A vector of spans and replacements, a message and applicability.
 crate type Suggestion = (Vec<(Span, String)>, String, Applicability);
 
+/// Potential candidate for an undeclared or out-of-scope label - contains the ident of a
+/// similarly named label and whether or not it is reachable.
+crate type LabelSuggestion = (Ident, bool);
+
 crate struct TypoSuggestion {
     pub candidate: Symbol,
     pub res: Res,
@@ -282,7 +286,7 @@ impl<'a> Resolver<'a> {
                 err.span_label(span, "used in a pattern more than once");
                 err
             }
-            ResolutionError::UndeclaredLabel(name, lev_candidate) => {
+            ResolutionError::UndeclaredLabel { name, suggestion } => {
                 let mut err = struct_span_err!(
                     self.session,
                     span,
@@ -290,16 +294,31 @@ impl<'a> Resolver<'a> {
                     "use of undeclared label `{}`",
                     name
                 );
-                if let Some(lev_candidate) = lev_candidate {
-                    err.span_suggestion(
-                        span,
-                        "a label with a similar name exists in this scope",
-                        lev_candidate.to_string(),
-                        Applicability::MaybeIncorrect,
-                    );
-                } else {
-                    err.span_label(span, format!("undeclared label `{}`", name));
+
+                err.span_label(span, format!("undeclared label `{}`", name));
+
+                match suggestion {
+                    // A reachable label with a similar name exists.
+                    Some((ident, true)) => {
+                        err.span_label(ident.span, "a label with a similar name is reachable");
+                        err.span_suggestion(
+                            span,
+                            "try using similarly named label",
+                            ident.name.to_string(),
+                            Applicability::MaybeIncorrect,
+                        );
+                    }
+                    // An unreachable label with a similar name exists.
+                    Some((ident, false)) => {
+                        err.span_label(
+                            ident.span,
+                            "a label with a similar name exists but is unreachable",
+                        );
+                    }
+                    // No similarly-named labels exist.
+                    None => (),
                 }
+
                 err
             }
             ResolutionError::SelfImportsOnlyAllowedWithin { root, span_with_rename } => {
@@ -433,6 +452,45 @@ impl<'a> Resolver<'a> {
                 err.span_label(span, "`Self` in type parameter default".to_string());
                 err
             }
+            ResolutionError::UnreachableLabel { name, definition_span, suggestion } => {
+                let mut err = struct_span_err!(
+                    self.session,
+                    span,
+                    E0767,
+                    "use of unreachable label `{}`",
+                    name,
+                );
+
+                err.span_label(definition_span, "unreachable label defined here");
+                err.span_label(span, format!("unreachable label `{}`", name));
+                err.note(
+                    "labels are unreachable through functions, closures, async blocks and modules",
+                );
+
+                match suggestion {
+                    // A reachable label with a similar name exists.
+                    Some((ident, true)) => {
+                        err.span_label(ident.span, "a label with a similar name is reachable");
+                        err.span_suggestion(
+                            span,
+                            "try using similarly named label",
+                            ident.name.to_string(),
+                            Applicability::MaybeIncorrect,
+                        );
+                    }
+                    // An unreachable label with a similar name exists.
+                    Some((ident, false)) => {
+                        err.span_label(
+                            ident.span,
+                            "a label with a similar name exists but is also unreachable",
+                        );
+                    }
+                    // No similarly-named labels exist.
+                    None => (),
+                }
+
+                err
+            }
         }
     }
 
diff --git a/src/librustc_resolve/late.rs b/src/librustc_resolve/late.rs
index 84ba9094ea1..4451791780e 100644
--- a/src/librustc_resolve/late.rs
+++ b/src/librustc_resolve/late.rs
@@ -13,7 +13,6 @@ use crate::{ResolutionError, Resolver, Segment, UseError};
 
 use rustc_ast::ast::*;
 use rustc_ast::ptr::P;
-use rustc_ast::util::lev_distance::find_best_match_for_name;
 use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor};
 use rustc_ast::{unwrap_or, walk_list};
 use rustc_ast_lowering::ResolverAstLowering;
@@ -101,6 +100,9 @@ crate enum RibKind<'a> {
     /// upvars).
     AssocItemRibKind,
 
+    /// We passed through a closure. Disallow labels.
+    ClosureOrAsyncRibKind,
+
     /// We passed through a function definition. Disallow upvars.
     /// Permit only those const parameters that are specified in the function's generics.
     FnItemRibKind,
@@ -124,11 +126,15 @@ crate enum RibKind<'a> {
 }
 
 impl RibKind<'_> {
-    // Whether this rib kind contains generic parameters, as opposed to local
-    // variables.
+    /// Whether this rib kind contains generic parameters, as opposed to local
+    /// variables.
     crate fn contains_params(&self) -> bool {
         match self {
-            NormalRibKind | FnItemRibKind | ConstantItemRibKind | ModuleRibKind(_)
+            NormalRibKind
+            | ClosureOrAsyncRibKind
+            | FnItemRibKind
+            | ConstantItemRibKind
+            | ModuleRibKind(_)
             | MacroDefinition(_) => false,
             AssocItemRibKind | ItemRibKind(_) | ForwardTyParamBanRibKind => true,
         }
@@ -474,7 +480,8 @@ impl<'a, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
             // Bail if there's no body.
             FnKind::Fn(.., None) => return visit::walk_fn(self, fn_kind, sp),
             FnKind::Fn(FnCtxt::Free | FnCtxt::Foreign, ..) => FnItemRibKind,
-            FnKind::Fn(FnCtxt::Assoc(_), ..) | FnKind::Closure(..) => NormalRibKind,
+            FnKind::Fn(FnCtxt::Assoc(_), ..) => NormalRibKind,
+            FnKind::Closure(..) => ClosureOrAsyncRibKind,
         };
         let previous_value =
             replace(&mut self.diagnostic_metadata.current_function, Some((fn_kind, sp)));
@@ -725,37 +732,81 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
         }
     }
 
-    /// Searches the current set of local scopes for labels. Returns the first non-`None` label that
-    /// is returned by the given predicate function
-    ///
-    /// Stops after meeting a closure.
-    fn search_label<P, R>(&self, mut ident: Ident, pred: P) -> Option<R>
-    where
-        P: Fn(&Rib<'_, NodeId>, Ident) -> Option<R>,
-    {
-        for rib in self.label_ribs.iter().rev() {
-            match rib.kind {
-                NormalRibKind => {}
+    /// Searches the current set of local scopes for labels. Returns the `NodeId` of the resolved
+    /// label and reports an error if the label is not found or is unreachable.
+    fn resolve_label(&self, mut label: Ident) -> Option<NodeId> {
+        let mut suggestion = None;
+
+        // Preserve the original span so that errors contain "in this macro invocation"
+        // information.
+        let original_span = label.span;
+
+        for i in (0..self.label_ribs.len()).rev() {
+            let rib = &self.label_ribs[i];
+
+            if let MacroDefinition(def) = rib.kind {
                 // If an invocation of this macro created `ident`, give up on `ident`
                 // and switch to `ident`'s source from the macro definition.
-                MacroDefinition(def) => {
-                    if def == self.r.macro_def(ident.span.ctxt()) {
-                        ident.span.remove_mark();
-                    }
-                }
-                _ => {
-                    // Do not resolve labels across function boundary
-                    return None;
+                if def == self.r.macro_def(label.span.ctxt()) {
+                    label.span.remove_mark();
                 }
             }
-            let r = pred(rib, ident);
-            if r.is_some() {
-                return r;
+
+            let ident = label.normalize_to_macro_rules();
+            if let Some((ident, id)) = rib.bindings.get_key_value(&ident) {
+                return if self.is_label_valid_from_rib(i) {
+                    Some(*id)
+                } else {
+                    self.r.report_error(
+                        original_span,
+                        ResolutionError::UnreachableLabel {
+                            name: &label.name.as_str(),
+                            definition_span: ident.span,
+                            suggestion,
+                        },
+                    );
+
+                    None
+                };
             }
+
+            // Diagnostics: Check if this rib contains a label with a similar name, keep track of
+            // the first such label that is encountered.
+            suggestion = suggestion.or_else(|| self.suggestion_for_label_in_rib(i, label));
         }
+
+        self.r.report_error(
+            original_span,
+            ResolutionError::UndeclaredLabel { name: &label.name.as_str(), suggestion },
+        );
         None
     }
 
+    /// Determine whether or not a label from the `rib_index`th label rib is reachable.
+    fn is_label_valid_from_rib(&self, rib_index: usize) -> bool {
+        let ribs = &self.label_ribs[rib_index + 1..];
+
+        for rib in ribs {
+            match rib.kind {
+                NormalRibKind | MacroDefinition(..) => {
+                    // Nothing to do. Continue.
+                }
+
+                AssocItemRibKind
+                | ClosureOrAsyncRibKind
+                | FnItemRibKind
+                | ItemRibKind(..)
+                | ConstantItemRibKind
+                | ModuleRibKind(..)
+                | ForwardTyParamBanRibKind => {
+                    return false;
+                }
+            }
+        }
+
+        true
+    }
+
     fn resolve_adt(&mut self, item: &'ast Item, generics: &'ast Generics) {
         debug!("resolve_adt");
         self.with_current_self_item(item, |this| {
@@ -2044,35 +2095,10 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
             }
 
             ExprKind::Break(Some(label), _) | ExprKind::Continue(Some(label)) => {
-                let node_id = self.search_label(label.ident, |rib, ident| {
-                    rib.bindings.get(&ident.normalize_to_macro_rules()).cloned()
-                });
-                match node_id {
-                    None => {
-                        // Search again for close matches...
-                        // Picks the first label that is "close enough", which is not necessarily
-                        // the closest match
-                        let close_match = self.search_label(label.ident, |rib, ident| {
-                            let names = rib.bindings.iter().filter_map(|(id, _)| {
-                                if id.span.ctxt() == label.ident.span.ctxt() {
-                                    Some(&id.name)
-                                } else {
-                                    None
-                                }
-                            });
-                            find_best_match_for_name(names, &ident.as_str(), None)
-                        });
-                        self.r.record_partial_res(expr.id, PartialRes::new(Res::Err));
-                        self.r.report_error(
-                            label.ident.span,
-                            ResolutionError::UndeclaredLabel(&label.ident.as_str(), close_match),
-                        );
-                    }
-                    Some(node_id) => {
-                        // Since this res is a label, it is never read.
-                        self.r.label_res_map.insert(expr.id, node_id);
-                        self.diagnostic_metadata.unused_labels.remove(&node_id);
-                    }
+                if let Some(node_id) = self.resolve_label(label.ident) {
+                    // Since this res is a label, it is never read.
+                    self.r.label_res_map.insert(expr.id, node_id);
+                    self.diagnostic_metadata.unused_labels.remove(&node_id);
                 }
 
                 // visit `break` argument if any
@@ -2144,21 +2170,26 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
             // closure are detected as upvars rather than normal closure arg usages.
             ExprKind::Closure(_, Async::Yes { .. }, _, ref fn_decl, ref body, _span) => {
                 self.with_rib(ValueNS, NormalRibKind, |this| {
-                    // Resolve arguments:
-                    this.resolve_params(&fn_decl.inputs);
-                    // No need to resolve return type --
-                    // the outer closure return type is `FnRetTy::Default`.
+                    this.with_label_rib(ClosureOrAsyncRibKind, |this| {
+                        // Resolve arguments:
+                        this.resolve_params(&fn_decl.inputs);
+                        // No need to resolve return type --
+                        // the outer closure return type is `FnRetTy::Default`.
 
-                    // Now resolve the inner closure
-                    {
-                        // No need to resolve arguments: the inner closure has none.
-                        // Resolve the return type:
-                        visit::walk_fn_ret_ty(this, &fn_decl.output);
-                        // Resolve the body
-                        this.visit_expr(body);
-                    }
+                        // Now resolve the inner closure
+                        {
+                            // No need to resolve arguments: the inner closure has none.
+                            // Resolve the return type:
+                            visit::walk_fn_ret_ty(this, &fn_decl.output);
+                            // Resolve the body
+                            this.visit_expr(body);
+                        }
+                    })
                 });
             }
+            ExprKind::Async(..) | ExprKind::Closure(..) => {
+                self.with_label_rib(ClosureOrAsyncRibKind, |this| visit::walk_expr(this, expr));
+            }
             _ => {
                 visit::walk_expr(self, expr);
             }
diff --git a/src/librustc_resolve/late/diagnostics.rs b/src/librustc_resolve/late/diagnostics.rs
index 1bce160acb6..e469ca80c59 100644
--- a/src/librustc_resolve/late/diagnostics.rs
+++ b/src/librustc_resolve/late/diagnostics.rs
@@ -1,4 +1,4 @@
-use crate::diagnostics::{ImportSuggestion, TypoSuggestion};
+use crate::diagnostics::{ImportSuggestion, LabelSuggestion, TypoSuggestion};
 use crate::late::lifetimes::{ElisionFailureInfo, LifetimeContext};
 use crate::late::{LateResolutionVisitor, RibKind};
 use crate::path_names_to_string;
@@ -992,6 +992,32 @@ impl<'a> LateResolutionVisitor<'a, '_, '_> {
         }
         None
     }
+
+    /// Given the target `label`, search the `rib_index`th label rib for similarly named labels,
+    /// optionally returning the closest match and whether it is reachable.
+    crate fn suggestion_for_label_in_rib(
+        &self,
+        rib_index: usize,
+        label: Ident,
+    ) -> Option<LabelSuggestion> {
+        // Are ribs from this `rib_index` within scope?
+        let within_scope = self.is_label_valid_from_rib(rib_index);
+
+        let rib = &self.label_ribs[rib_index];
+        let names = rib
+            .bindings
+            .iter()
+            .filter(|(id, _)| id.span.ctxt() == label.span.ctxt())
+            .map(|(id, _)| &id.name);
+
+        find_best_match_for_name(names, &label.as_str(), None).map(|symbol| {
+            // Upon finding a similar name, get the ident that it was from - the span
+            // contained within helps make a useful diagnostic. In addition, determine
+            // whether this candidate is within scope.
+            let (ident, _) = rib.bindings.iter().find(|(ident, _)| ident.name == symbol).unwrap();
+            (*ident, within_scope)
+        })
+    }
 }
 
 impl<'tcx> LifetimeContext<'_, 'tcx> {
diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs
index dccaf76723a..f3a1934abc9 100644
--- a/src/librustc_resolve/lib.rs
+++ b/src/librustc_resolve/lib.rs
@@ -61,7 +61,7 @@ use std::collections::BTreeSet;
 use std::{cmp, fmt, iter, ptr};
 
 use diagnostics::{extend_span_to_previous_binding, find_span_of_binding_until_next_binding};
-use diagnostics::{ImportSuggestion, Suggestion};
+use diagnostics::{ImportSuggestion, LabelSuggestion, Suggestion};
 use imports::{Import, ImportKind, ImportResolver, NameResolution};
 use late::{HasGenericParams, PathSource, Rib, RibKind::*};
 use macros::{MacroRulesBinding, MacroRulesScope};
@@ -197,7 +197,7 @@ enum ResolutionError<'a> {
     /// Error E0416: identifier is bound more than once in the same pattern.
     IdentifierBoundMoreThanOnceInSamePattern(&'a str),
     /// Error E0426: use of undeclared label.
-    UndeclaredLabel(&'a str, Option<Symbol>),
+    UndeclaredLabel { name: &'a str, suggestion: Option<LabelSuggestion> },
     /// Error E0429: `self` imports are only allowed within a `{ }` list.
     SelfImportsOnlyAllowedWithin { root: bool, span_with_rename: Span },
     /// Error E0430: `self` import can only appear once in the list.
@@ -216,6 +216,8 @@ enum ResolutionError<'a> {
     ForwardDeclaredTyParam, // FIXME(const_generics:defaults)
     /// Error E0735: type parameters with a default cannot use `Self`
     SelfInTyParamDefault,
+    /// Error E0767: use of unreachable label
+    UnreachableLabel { name: &'a str, definition_span: Span, suggestion: Option<LabelSuggestion> },
 }
 
 enum VisResolutionError<'a> {
@@ -2453,6 +2455,7 @@ impl<'a> Resolver<'a> {
                 for rib in ribs {
                     match rib.kind {
                         NormalRibKind
+                        | ClosureOrAsyncRibKind
                         | ModuleRibKind(..)
                         | MacroDefinition(..)
                         | ForwardTyParamBanRibKind => {
@@ -2488,6 +2491,7 @@ impl<'a> Resolver<'a> {
                 for rib in ribs {
                     let has_generic_params = match rib.kind {
                         NormalRibKind
+                        | ClosureOrAsyncRibKind
                         | AssocItemRibKind
                         | ModuleRibKind(..)
                         | MacroDefinition(..)
diff --git a/src/test/ui/break-outside-loop.rs b/src/test/ui/break-outside-loop.rs
index a6f9d0423d0..26769b30dd5 100644
--- a/src/test/ui/break-outside-loop.rs
+++ b/src/test/ui/break-outside-loop.rs
@@ -27,7 +27,9 @@ fn main() {
     // not the `loop`, which failed in the call to `find_breakable`. (#65383)
     'lab: loop {
         || {
-            break 'lab; //~ ERROR `break` inside of a closure
+            break 'lab;
+            //~^ ERROR use of unreachable label `'lab`
+            //~| ERROR `break` inside of a closure
         };
     }
 }
diff --git a/src/test/ui/break-outside-loop.stderr b/src/test/ui/break-outside-loop.stderr
index 8e300fd848d..287bf9af62e 100644
--- a/src/test/ui/break-outside-loop.stderr
+++ b/src/test/ui/break-outside-loop.stderr
@@ -1,3 +1,14 @@
+error[E0767]: use of unreachable label `'lab`
+  --> $DIR/break-outside-loop.rs:30:19
+   |
+LL |     'lab: loop {
+   |     ---- unreachable label defined here
+LL |         || {
+LL |             break 'lab;
+   |                   ^^^^ unreachable label `'lab`
+   |
+   = note: labels are unreachable through functions, closures, async blocks and modules
+
 error[E0268]: `break` outside of a loop
   --> $DIR/break-outside-loop.rs:10:15
    |
@@ -41,7 +52,7 @@ LL |         || {
 LL |             break 'lab;
    |             ^^^^^^^^^^ cannot `break` inside of a closure
 
-error: aborting due to 6 previous errors
+error: aborting due to 7 previous errors
 
-Some errors have detailed explanations: E0267, E0268.
+Some errors have detailed explanations: E0267, E0268, E0767.
 For more information about an error, try `rustc --explain E0267`.
diff --git a/src/test/ui/error-codes/E0767.rs b/src/test/ui/error-codes/E0767.rs
new file mode 100644
index 00000000000..6c6cb746e6c
--- /dev/null
+++ b/src/test/ui/error-codes/E0767.rs
@@ -0,0 +1,7 @@
+fn main () {
+    'a: loop {
+        || {
+            loop { break 'a; } //~ ERROR E0767
+        }
+    }
+}
diff --git a/src/test/ui/error-codes/E0767.stderr b/src/test/ui/error-codes/E0767.stderr
new file mode 100644
index 00000000000..2429823306b
--- /dev/null
+++ b/src/test/ui/error-codes/E0767.stderr
@@ -0,0 +1,14 @@
+error[E0767]: use of unreachable label `'a`
+  --> $DIR/E0767.rs:4:26
+   |
+LL |     'a: loop {
+   |     -- unreachable label defined here
+LL |         || {
+LL |             loop { break 'a; }
+   |                          ^^ unreachable label `'a`
+   |
+   = note: labels are unreachable through functions, closures, async blocks and modules
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0767`.
diff --git a/src/test/ui/issues/issue-62480.rs b/src/test/ui/issues/issue-62480.rs
index bc3e6c69a60..5c3be3e64ee 100644
--- a/src/test/ui/issues/issue-62480.rs
+++ b/src/test/ui/issues/issue-62480.rs
@@ -5,6 +5,8 @@ fn main() {
     // `propagate_through_expr` would be the closure and not the `loop`, which wouldn't be found in
     // `self.break_ln`. (#62480)
     'a: {
-        || break 'a //~ ERROR `break` to unknown label
+        || break 'a
+        //~^ ERROR use of unreachable label `'a`
+        //~| ERROR `break` inside of a closure
     }
 }
diff --git a/src/test/ui/issues/issue-62480.stderr b/src/test/ui/issues/issue-62480.stderr
index de8451ad7df..17085ef908b 100644
--- a/src/test/ui/issues/issue-62480.stderr
+++ b/src/test/ui/issues/issue-62480.stderr
@@ -1,8 +1,22 @@
-error: `break` to unknown label
+error[E0767]: use of unreachable label `'a`
+  --> $DIR/issue-62480.rs:8:18
+   |
+LL |     'a: {
+   |     -- unreachable label defined here
+LL |         || break 'a
+   |                  ^^ unreachable label `'a`
+   |
+   = note: labels are unreachable through functions, closures, async blocks and modules
+
+error[E0267]: `break` inside of a closure
   --> $DIR/issue-62480.rs:8:12
    |
 LL |         || break 'a
-   |            ^^^^^^^^
+   |         -- ^^^^^^^^ cannot `break` inside of a closure
+   |         |
+   |         enclosing closure
 
-error: aborting due to previous error
+error: aborting due to 2 previous errors
 
+Some errors have detailed explanations: E0267, E0767.
+For more information about an error, try `rustc --explain E0267`.
diff --git a/src/test/ui/issues/issue-66702-break-outside-loop-val.rs b/src/test/ui/issues/issue-66702-break-outside-loop-val.rs
index bd3c00d2621..05b86cbbf8f 100644
--- a/src/test/ui/issues/issue-66702-break-outside-loop-val.rs
+++ b/src/test/ui/issues/issue-66702-break-outside-loop-val.rs
@@ -2,6 +2,8 @@
 
 fn main() {
     'some_label: loop {
-        || break 'some_label (); //~ ERROR: `break` inside of a closure
+        || break 'some_label ();
+        //~^ ERROR: use of unreachable label `'some_label`
+        //~| ERROR: `break` inside of a closure
     }
 }
diff --git a/src/test/ui/issues/issue-66702-break-outside-loop-val.stderr b/src/test/ui/issues/issue-66702-break-outside-loop-val.stderr
index 83bde9775b2..f92ede311af 100644
--- a/src/test/ui/issues/issue-66702-break-outside-loop-val.stderr
+++ b/src/test/ui/issues/issue-66702-break-outside-loop-val.stderr
@@ -1,3 +1,13 @@
+error[E0767]: use of unreachable label `'some_label`
+  --> $DIR/issue-66702-break-outside-loop-val.rs:5:18
+   |
+LL |     'some_label: loop {
+   |     ----------- unreachable label defined here
+LL |         || break 'some_label ();
+   |                  ^^^^^^^^^^^ unreachable label `'some_label`
+   |
+   = note: labels are unreachable through functions, closures, async blocks and modules
+
 error[E0267]: `break` inside of a closure
   --> $DIR/issue-66702-break-outside-loop-val.rs:5:12
    |
@@ -6,6 +16,7 @@ LL |         || break 'some_label ();
    |         |
    |         enclosing closure
 
-error: aborting due to previous error
+error: aborting due to 2 previous errors
 
-For more information about this error, try `rustc --explain E0267`.
+Some errors have detailed explanations: E0267, E0767.
+For more information about an error, try `rustc --explain E0267`.
diff --git a/src/test/ui/issues/issue-73541-1.rs b/src/test/ui/issues/issue-73541-1.rs
new file mode 100644
index 00000000000..7fb0d6c39ff
--- /dev/null
+++ b/src/test/ui/issues/issue-73541-1.rs
@@ -0,0 +1,12 @@
+// edition:2018
+
+fn main() {
+    'a: loop {
+        async {
+            loop {
+                continue 'a
+                //~^ ERROR use of unreachable label `'a`
+            }
+        };
+    }
+}
diff --git a/src/test/ui/issues/issue-73541-1.stderr b/src/test/ui/issues/issue-73541-1.stderr
new file mode 100644
index 00000000000..80c1fdf002a
--- /dev/null
+++ b/src/test/ui/issues/issue-73541-1.stderr
@@ -0,0 +1,14 @@
+error[E0767]: use of unreachable label `'a`
+  --> $DIR/issue-73541-1.rs:7:26
+   |
+LL |     'a: loop {
+   |     -- unreachable label defined here
+...
+LL |                 continue 'a
+   |                          ^^ unreachable label `'a`
+   |
+   = note: labels are unreachable through functions, closures, async blocks and modules
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0767`.
diff --git a/src/test/ui/issues/issue-73541-2.rs b/src/test/ui/issues/issue-73541-2.rs
new file mode 100644
index 00000000000..70b4ab25376
--- /dev/null
+++ b/src/test/ui/issues/issue-73541-2.rs
@@ -0,0 +1,20 @@
+// edition:2018
+
+async fn c() {
+    'a: loop {
+        macro_rules! b {
+            () => {
+                continue 'a
+                //~^ ERROR use of unreachable label `'a`
+            }
+        }
+
+        async {
+            loop {
+                b!();
+            }
+        };
+    }
+}
+
+fn main() { }
diff --git a/src/test/ui/issues/issue-73541-2.stderr b/src/test/ui/issues/issue-73541-2.stderr
new file mode 100644
index 00000000000..75d769c4e15
--- /dev/null
+++ b/src/test/ui/issues/issue-73541-2.stderr
@@ -0,0 +1,18 @@
+error[E0767]: use of unreachable label `'a`
+  --> $DIR/issue-73541-2.rs:7:26
+   |
+LL |     'a: loop {
+   |     -- unreachable label defined here
+...
+LL |                 continue 'a
+   |                          ^^ unreachable label `'a`
+...
+LL |                 b!();
+   |                 ----- in this macro invocation
+   |
+   = note: labels are unreachable through functions, closures, async blocks and modules
+   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0767`.
diff --git a/src/test/ui/issues/issue-73541-3.rs b/src/test/ui/issues/issue-73541-3.rs
new file mode 100644
index 00000000000..02ca02da8ed
--- /dev/null
+++ b/src/test/ui/issues/issue-73541-3.rs
@@ -0,0 +1,9 @@
+fn main() {
+    'aaaaab: loop {
+        || {
+            loop { continue 'aaaaaa }
+            //~^ ERROR use of undeclared label `'aaaaaa`
+        };
+
+    }
+}
diff --git a/src/test/ui/issues/issue-73541-3.stderr b/src/test/ui/issues/issue-73541-3.stderr
new file mode 100644
index 00000000000..53487aaca99
--- /dev/null
+++ b/src/test/ui/issues/issue-73541-3.stderr
@@ -0,0 +1,12 @@
+error[E0426]: use of undeclared label `'aaaaaa`
+  --> $DIR/issue-73541-3.rs:4:29
+   |
+LL |     'aaaaab: loop {
+   |     ------- a label with a similar name exists but is unreachable
+LL |         || {
+LL |             loop { continue 'aaaaaa }
+   |                             ^^^^^^^ undeclared label `'aaaaaa`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0426`.
diff --git a/src/test/ui/issues/issue-73541.rs b/src/test/ui/issues/issue-73541.rs
new file mode 100644
index 00000000000..399a07cd3fc
--- /dev/null
+++ b/src/test/ui/issues/issue-73541.rs
@@ -0,0 +1,9 @@
+fn main() {
+    'a: loop {
+        || {
+            loop { continue 'a }
+            //~^ ERROR use of unreachable label `'a`
+        };
+
+    }
+}
diff --git a/src/test/ui/issues/issue-73541.stderr b/src/test/ui/issues/issue-73541.stderr
new file mode 100644
index 00000000000..4bb466ff16c
--- /dev/null
+++ b/src/test/ui/issues/issue-73541.stderr
@@ -0,0 +1,14 @@
+error[E0767]: use of unreachable label `'a`
+  --> $DIR/issue-73541.rs:4:29
+   |
+LL |     'a: loop {
+   |     -- unreachable label defined here
+LL |         || {
+LL |             loop { continue 'a }
+   |                             ^^ unreachable label `'a`
+   |
+   = note: labels are unreachable through functions, closures, async blocks and modules
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0767`.
diff --git a/src/test/ui/resolve/resolve-label.rs b/src/test/ui/resolve/resolve-label.rs
index 965ee450104..ac5410cd355 100644
--- a/src/test/ui/resolve/resolve-label.rs
+++ b/src/test/ui/resolve/resolve-label.rs
@@ -2,7 +2,7 @@ fn f() {
     'l: loop {
         fn g() {
             loop {
-                break 'l; //~ ERROR use of undeclared label
+                break 'l; //~ ERROR use of unreachable label
             }
         }
     }
diff --git a/src/test/ui/resolve/resolve-label.stderr b/src/test/ui/resolve/resolve-label.stderr
index 72a8e443bac..5729348ef21 100644
--- a/src/test/ui/resolve/resolve-label.stderr
+++ b/src/test/ui/resolve/resolve-label.stderr
@@ -1,9 +1,14 @@
-error[E0426]: use of undeclared label `'l`
+error[E0767]: use of unreachable label `'l`
   --> $DIR/resolve-label.rs:5:23
    |
+LL |     'l: loop {
+   |     -- unreachable label defined here
+...
 LL |                 break 'l;
-   |                       ^^ undeclared label `'l`
+   |                       ^^ unreachable label `'l`
+   |
+   = note: labels are unreachable through functions, closures, async blocks and modules
 
 error: aborting due to previous error
 
-For more information about this error, try `rustc --explain E0426`.
+For more information about this error, try `rustc --explain E0767`.
diff --git a/src/test/ui/suggestions/suggest-labels.stderr b/src/test/ui/suggestions/suggest-labels.stderr
index 079821e6490..aac6515b527 100644
--- a/src/test/ui/suggestions/suggest-labels.stderr
+++ b/src/test/ui/suggestions/suggest-labels.stderr
@@ -1,35 +1,35 @@
 error[E0426]: use of undeclared label `'fo`
   --> $DIR/suggest-labels.rs:4:15
    |
+LL |     'foo: loop {
+   |     ---- a label with a similar name is reachable
 LL |         break 'fo;
    |               ^^^
-   |
-help: a label with a similar name exists in this scope
-   |
-LL |         break 'foo;
-   |               ^^^^
+   |               |
+   |               undeclared label `'fo`
+   |               help: try using similarly named label: `'foo`
 
 error[E0426]: use of undeclared label `'bor`
   --> $DIR/suggest-labels.rs:8:18
    |
+LL |     'bar: loop {
+   |     ---- a label with a similar name is reachable
 LL |         continue 'bor;
    |                  ^^^^
-   |
-help: a label with a similar name exists in this scope
-   |
-LL |         continue 'bar;
-   |                  ^^^^
+   |                  |
+   |                  undeclared label `'bor`
+   |                  help: try using similarly named label: `'bar`
 
 error[E0426]: use of undeclared label `'longlable`
   --> $DIR/suggest-labels.rs:13:19
    |
+LL |         'longlabel1: loop {
+   |         ----------- a label with a similar name is reachable
 LL |             break 'longlable;
    |                   ^^^^^^^^^^
-   |
-help: a label with a similar name exists in this scope
-   |
-LL |             break 'longlabel1;
-   |                   ^^^^^^^^^^^
+   |                   |
+   |                   undeclared label `'longlable`
+   |                   help: try using similarly named label: `'longlabel1`
 
 error: aborting due to 3 previous errors