Skip to content

Commit 2d83cbb

Browse files
authored
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`)
2 parents 65342fd + cb541dc commit 2d83cbb

26 files changed

+427
-120
lines changed

src/librustc_error_codes/error_codes.rs

+1
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ E0763: include_str!("./error_codes/E0763.md"),
448448
E0764: include_str!("./error_codes/E0764.md"),
449449
E0765: include_str!("./error_codes/E0765.md"),
450450
E0766: include_str!("./error_codes/E0766.md"),
451+
E0767: include_str!("./error_codes/E0767.md"),
451452
;
452453
// E0006, // merged with E0005
453454
// E0008, // cannot bind by-move into a pattern guard
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
An unreachable label was used.
2+
3+
Erroneous code example:
4+
5+
```compile_fail,E0767
6+
'a: loop {
7+
|| {
8+
loop { break 'a } // error: use of unreachable label `'a`
9+
};
10+
}
11+
```
12+
13+
Ensure that the label is within scope. Labels are not reachable through
14+
functions, closures, async blocks or modules. Example:
15+
16+
```
17+
'a: loop {
18+
break 'a; // ok!
19+
}
20+
```

src/librustc_passes/liveness.rs

+1-10
Original file line numberDiff line numberDiff line change
@@ -1123,16 +1123,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
11231123

11241124
match target {
11251125
Some(b) => self.propagate_through_opt_expr(opt_expr.as_ref().map(|e| &**e), b),
1126-
None => {
1127-
// FIXME: This should have been checked earlier. Once this is fixed,
1128-
// replace with `delay_span_bug`. (#62480)
1129-
self.ir
1130-
.tcx
1131-
.sess
1132-
.struct_span_err(expr.span, "`break` to unknown label")
1133-
.emit();
1134-
rustc_errors::FatalError.raise()
1135-
}
1126+
None => span_bug!(expr.span, "`break` to unknown label"),
11361127
}
11371128
}
11381129

src/librustc_resolve/diagnostics.rs

+68-10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ type Res = def::Res<ast::NodeId>;
3333
/// A vector of spans and replacements, a message and applicability.
3434
crate type Suggestion = (Vec<(Span, String)>, String, Applicability);
3535

36+
/// Potential candidate for an undeclared or out-of-scope label - contains the ident of a
37+
/// similarly named label and whether or not it is reachable.
38+
crate type LabelSuggestion = (Ident, bool);
39+
3640
crate struct TypoSuggestion {
3741
pub candidate: Symbol,
3842
pub res: Res,
@@ -282,24 +286,39 @@ impl<'a> Resolver<'a> {
282286
err.span_label(span, "used in a pattern more than once");
283287
err
284288
}
285-
ResolutionError::UndeclaredLabel(name, lev_candidate) => {
289+
ResolutionError::UndeclaredLabel { name, suggestion } => {
286290
let mut err = struct_span_err!(
287291
self.session,
288292
span,
289293
E0426,
290294
"use of undeclared label `{}`",
291295
name
292296
);
293-
if let Some(lev_candidate) = lev_candidate {
294-
err.span_suggestion(
295-
span,
296-
"a label with a similar name exists in this scope",
297-
lev_candidate.to_string(),
298-
Applicability::MaybeIncorrect,
299-
);
300-
} else {
301-
err.span_label(span, format!("undeclared label `{}`", name));
297+
298+
err.span_label(span, format!("undeclared label `{}`", name));
299+
300+
match suggestion {
301+
// A reachable label with a similar name exists.
302+
Some((ident, true)) => {
303+
err.span_label(ident.span, "a label with a similar name is reachable");
304+
err.span_suggestion(
305+
span,
306+
"try using similarly named label",
307+
ident.name.to_string(),
308+
Applicability::MaybeIncorrect,
309+
);
310+
}
311+
// An unreachable label with a similar name exists.
312+
Some((ident, false)) => {
313+
err.span_label(
314+
ident.span,
315+
"a label with a similar name exists but is unreachable",
316+
);
317+
}
318+
// No similarly-named labels exist.
319+
None => (),
302320
}
321+
303322
err
304323
}
305324
ResolutionError::SelfImportsOnlyAllowedWithin { root, span_with_rename } => {
@@ -433,6 +452,45 @@ impl<'a> Resolver<'a> {
433452
err.span_label(span, "`Self` in type parameter default".to_string());
434453
err
435454
}
455+
ResolutionError::UnreachableLabel { name, definition_span, suggestion } => {
456+
let mut err = struct_span_err!(
457+
self.session,
458+
span,
459+
E0767,
460+
"use of unreachable label `{}`",
461+
name,
462+
);
463+
464+
err.span_label(definition_span, "unreachable label defined here");
465+
err.span_label(span, format!("unreachable label `{}`", name));
466+
err.note(
467+
"labels are unreachable through functions, closures, async blocks and modules",
468+
);
469+
470+
match suggestion {
471+
// A reachable label with a similar name exists.
472+
Some((ident, true)) => {
473+
err.span_label(ident.span, "a label with a similar name is reachable");
474+
err.span_suggestion(
475+
span,
476+
"try using similarly named label",
477+
ident.name.to_string(),
478+
Applicability::MaybeIncorrect,
479+
);
480+
}
481+
// An unreachable label with a similar name exists.
482+
Some((ident, false)) => {
483+
err.span_label(
484+
ident.span,
485+
"a label with a similar name exists but is also unreachable",
486+
);
487+
}
488+
// No similarly-named labels exist.
489+
None => (),
490+
}
491+
492+
err
493+
}
436494
}
437495
}
438496

src/librustc_resolve/late.rs

+99-68
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use crate::{ResolutionError, Resolver, Segment, UseError};
1313

1414
use rustc_ast::ast::*;
1515
use rustc_ast::ptr::P;
16-
use rustc_ast::util::lev_distance::find_best_match_for_name;
1716
use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor};
1817
use rustc_ast::{unwrap_or, walk_list};
1918
use rustc_ast_lowering::ResolverAstLowering;
@@ -101,6 +100,9 @@ crate enum RibKind<'a> {
101100
/// upvars).
102101
AssocItemRibKind,
103102

103+
/// We passed through a closure. Disallow labels.
104+
ClosureOrAsyncRibKind,
105+
104106
/// We passed through a function definition. Disallow upvars.
105107
/// Permit only those const parameters that are specified in the function's generics.
106108
FnItemRibKind,
@@ -124,11 +126,15 @@ crate enum RibKind<'a> {
124126
}
125127

126128
impl RibKind<'_> {
127-
// Whether this rib kind contains generic parameters, as opposed to local
128-
// variables.
129+
/// Whether this rib kind contains generic parameters, as opposed to local
130+
/// variables.
129131
crate fn contains_params(&self) -> bool {
130132
match self {
131-
NormalRibKind | FnItemRibKind | ConstantItemRibKind | ModuleRibKind(_)
133+
NormalRibKind
134+
| ClosureOrAsyncRibKind
135+
| FnItemRibKind
136+
| ConstantItemRibKind
137+
| ModuleRibKind(_)
132138
| MacroDefinition(_) => false,
133139
AssocItemRibKind | ItemRibKind(_) | ForwardTyParamBanRibKind => true,
134140
}
@@ -474,7 +480,8 @@ impl<'a, 'ast> Visitor<'ast> for LateResolutionVisitor<'a, '_, 'ast> {
474480
// Bail if there's no body.
475481
FnKind::Fn(.., None) => return visit::walk_fn(self, fn_kind, sp),
476482
FnKind::Fn(FnCtxt::Free | FnCtxt::Foreign, ..) => FnItemRibKind,
477-
FnKind::Fn(FnCtxt::Assoc(_), ..) | FnKind::Closure(..) => NormalRibKind,
483+
FnKind::Fn(FnCtxt::Assoc(_), ..) => NormalRibKind,
484+
FnKind::Closure(..) => ClosureOrAsyncRibKind,
478485
};
479486
let previous_value =
480487
replace(&mut self.diagnostic_metadata.current_function, Some((fn_kind, sp)));
@@ -725,37 +732,81 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
725732
}
726733
}
727734

728-
/// Searches the current set of local scopes for labels. Returns the first non-`None` label that
729-
/// is returned by the given predicate function
730-
///
731-
/// Stops after meeting a closure.
732-
fn search_label<P, R>(&self, mut ident: Ident, pred: P) -> Option<R>
733-
where
734-
P: Fn(&Rib<'_, NodeId>, Ident) -> Option<R>,
735-
{
736-
for rib in self.label_ribs.iter().rev() {
737-
match rib.kind {
738-
NormalRibKind => {}
735+
/// Searches the current set of local scopes for labels. Returns the `NodeId` of the resolved
736+
/// label and reports an error if the label is not found or is unreachable.
737+
fn resolve_label(&self, mut label: Ident) -> Option<NodeId> {
738+
let mut suggestion = None;
739+
740+
// Preserve the original span so that errors contain "in this macro invocation"
741+
// information.
742+
let original_span = label.span;
743+
744+
for i in (0..self.label_ribs.len()).rev() {
745+
let rib = &self.label_ribs[i];
746+
747+
if let MacroDefinition(def) = rib.kind {
739748
// If an invocation of this macro created `ident`, give up on `ident`
740749
// and switch to `ident`'s source from the macro definition.
741-
MacroDefinition(def) => {
742-
if def == self.r.macro_def(ident.span.ctxt()) {
743-
ident.span.remove_mark();
744-
}
745-
}
746-
_ => {
747-
// Do not resolve labels across function boundary
748-
return None;
750+
if def == self.r.macro_def(label.span.ctxt()) {
751+
label.span.remove_mark();
749752
}
750753
}
751-
let r = pred(rib, ident);
752-
if r.is_some() {
753-
return r;
754+
755+
let ident = label.normalize_to_macro_rules();
756+
if let Some((ident, id)) = rib.bindings.get_key_value(&ident) {
757+
return if self.is_label_valid_from_rib(i) {
758+
Some(*id)
759+
} else {
760+
self.r.report_error(
761+
original_span,
762+
ResolutionError::UnreachableLabel {
763+
name: &label.name.as_str(),
764+
definition_span: ident.span,
765+
suggestion,
766+
},
767+
);
768+
769+
None
770+
};
754771
}
772+
773+
// Diagnostics: Check if this rib contains a label with a similar name, keep track of
774+
// the first such label that is encountered.
775+
suggestion = suggestion.or_else(|| self.suggestion_for_label_in_rib(i, label));
755776
}
777+
778+
self.r.report_error(
779+
original_span,
780+
ResolutionError::UndeclaredLabel { name: &label.name.as_str(), suggestion },
781+
);
756782
None
757783
}
758784

785+
/// Determine whether or not a label from the `rib_index`th label rib is reachable.
786+
fn is_label_valid_from_rib(&self, rib_index: usize) -> bool {
787+
let ribs = &self.label_ribs[rib_index + 1..];
788+
789+
for rib in ribs {
790+
match rib.kind {
791+
NormalRibKind | MacroDefinition(..) => {
792+
// Nothing to do. Continue.
793+
}
794+
795+
AssocItemRibKind
796+
| ClosureOrAsyncRibKind
797+
| FnItemRibKind
798+
| ItemRibKind(..)
799+
| ConstantItemRibKind
800+
| ModuleRibKind(..)
801+
| ForwardTyParamBanRibKind => {
802+
return false;
803+
}
804+
}
805+
}
806+
807+
true
808+
}
809+
759810
fn resolve_adt(&mut self, item: &'ast Item, generics: &'ast Generics) {
760811
debug!("resolve_adt");
761812
self.with_current_self_item(item, |this| {
@@ -2044,35 +2095,10 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
20442095
}
20452096

20462097
ExprKind::Break(Some(label), _) | ExprKind::Continue(Some(label)) => {
2047-
let node_id = self.search_label(label.ident, |rib, ident| {
2048-
rib.bindings.get(&ident.normalize_to_macro_rules()).cloned()
2049-
});
2050-
match node_id {
2051-
None => {
2052-
// Search again for close matches...
2053-
// Picks the first label that is "close enough", which is not necessarily
2054-
// the closest match
2055-
let close_match = self.search_label(label.ident, |rib, ident| {
2056-
let names = rib.bindings.iter().filter_map(|(id, _)| {
2057-
if id.span.ctxt() == label.ident.span.ctxt() {
2058-
Some(&id.name)
2059-
} else {
2060-
None
2061-
}
2062-
});
2063-
find_best_match_for_name(names, &ident.as_str(), None)
2064-
});
2065-
self.r.record_partial_res(expr.id, PartialRes::new(Res::Err));
2066-
self.r.report_error(
2067-
label.ident.span,
2068-
ResolutionError::UndeclaredLabel(&label.ident.as_str(), close_match),
2069-
);
2070-
}
2071-
Some(node_id) => {
2072-
// Since this res is a label, it is never read.
2073-
self.r.label_res_map.insert(expr.id, node_id);
2074-
self.diagnostic_metadata.unused_labels.remove(&node_id);
2075-
}
2098+
if let Some(node_id) = self.resolve_label(label.ident) {
2099+
// Since this res is a label, it is never read.
2100+
self.r.label_res_map.insert(expr.id, node_id);
2101+
self.diagnostic_metadata.unused_labels.remove(&node_id);
20762102
}
20772103

20782104
// visit `break` argument if any
@@ -2144,21 +2170,26 @@ impl<'a, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
21442170
// closure are detected as upvars rather than normal closure arg usages.
21452171
ExprKind::Closure(_, Async::Yes { .. }, _, ref fn_decl, ref body, _span) => {
21462172
self.with_rib(ValueNS, NormalRibKind, |this| {
2147-
// Resolve arguments:
2148-
this.resolve_params(&fn_decl.inputs);
2149-
// No need to resolve return type --
2150-
// the outer closure return type is `FnRetTy::Default`.
2173+
this.with_label_rib(ClosureOrAsyncRibKind, |this| {
2174+
// Resolve arguments:
2175+
this.resolve_params(&fn_decl.inputs);
2176+
// No need to resolve return type --
2177+
// the outer closure return type is `FnRetTy::Default`.
21512178

2152-
// Now resolve the inner closure
2153-
{
2154-
// No need to resolve arguments: the inner closure has none.
2155-
// Resolve the return type:
2156-
visit::walk_fn_ret_ty(this, &fn_decl.output);
2157-
// Resolve the body
2158-
this.visit_expr(body);
2159-
}
2179+
// Now resolve the inner closure
2180+
{
2181+
// No need to resolve arguments: the inner closure has none.
2182+
// Resolve the return type:
2183+
visit::walk_fn_ret_ty(this, &fn_decl.output);
2184+
// Resolve the body
2185+
this.visit_expr(body);
2186+
}
2187+
})
21602188
});
21612189
}
2190+
ExprKind::Async(..) | ExprKind::Closure(..) => {
2191+
self.with_label_rib(ClosureOrAsyncRibKind, |this| visit::walk_expr(this, expr));
2192+
}
21622193
_ => {
21632194
visit::walk_expr(self, expr);
21642195
}

0 commit comments

Comments
 (0)