Skip to content

Commit 49ca3d9

Browse files
committed
Auto merge of #87026 - FabianWolff:issue-86948, r=estebank
Allow labeled loops as value expressions for `break` Fixes #86948. This is currently allowed: ```rust return 'label: loop { break 'label 42; }; break ('label: loop { break 'label 42; }); break 1 + 'label: loop { break 'label 42; }; break 'outer 'inner: loop { break 'inner 42; }; ``` But not this: ```rust break 'label: loop { break 'label 42; }; ``` I have fixed this, so that the above now parses as an unlabeled break with a labeled loop as its value expression.
2 parents 71ff9b4 + 7c81132 commit 49ca3d9

File tree

6 files changed

+157
-21
lines changed

6 files changed

+157
-21
lines changed

compiler/rustc_lint/src/context.rs

+8
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,14 @@ pub trait LintContext: Sized {
750750
db.note(&format!("to ignore the value produced by the macro, add a semicolon after the invocation of `{name}`"));
751751
}
752752
}
753+
BuiltinLintDiagnostics::BreakWithLabelAndLoop(span) => {
754+
db.multipart_suggestion(
755+
"wrap this expression in parentheses",
756+
vec![(span.shrink_to_lo(), "(".to_string()),
757+
(span.shrink_to_hi(), ")".to_string())],
758+
Applicability::MachineApplicable
759+
);
760+
}
753761
}
754762
// Rewrap `db`, and pass control to the user.
755763
decorate(LintDiagnosticBuilder::new(db));

compiler/rustc_lint_defs/src/builtin.rs

+30
Original file line numberDiff line numberDiff line change
@@ -2978,6 +2978,7 @@ declare_lint_pass! {
29782978
RUST_2021_PRELUDE_COLLISIONS,
29792979
RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX,
29802980
UNSUPPORTED_CALLING_CONVENTIONS,
2981+
BREAK_WITH_LABEL_AND_LOOP,
29812982
]
29822983
}
29832984

@@ -3351,3 +3352,32 @@ declare_lint! {
33513352
reference: "issue #00000 <https://github.com/rust-lang/rust/issues/00000>",
33523353
};
33533354
}
3355+
3356+
declare_lint! {
3357+
/// The `break_with_label_and_loop` lint detects labeled `break` expressions with
3358+
/// an unlabeled loop as their value expression.
3359+
///
3360+
/// ### Example
3361+
///
3362+
/// ```rust
3363+
/// 'label: loop {
3364+
/// break 'label loop { break 42; };
3365+
/// };
3366+
/// ```
3367+
///
3368+
/// {{produces}}
3369+
///
3370+
/// ### Explanation
3371+
///
3372+
/// In Rust, loops can have a label, and `break` expressions can refer to that label to
3373+
/// break out of specific loops (and not necessarily the innermost one). `break` expressions
3374+
/// can also carry a value expression, which can be another loop. A labeled `break` with an
3375+
/// unlabeled loop as its value expression is easy to confuse with an unlabeled break with
3376+
/// a labeled loop and is thus discouraged (but allowed for compatibility); use parentheses
3377+
/// around the loop expression to silence this warning. Unlabeled `break` expressions with
3378+
/// labeled loops yield a hard error, which can also be silenced by wrapping the expression
3379+
/// in parentheses.
3380+
pub BREAK_WITH_LABEL_AND_LOOP,
3381+
Warn,
3382+
"`break` expression with label and unlabeled loop as value expression"
3383+
}

compiler/rustc_lint_defs/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ pub enum BuiltinLintDiagnostics {
304304
OrPatternsBackCompat(Span, String),
305305
ReservedPrefix(Span),
306306
TrailingMacro(bool, Ident),
307+
BreakWithLabelAndLoop(Span),
307308
}
308309

309310
/// Lints that are buffered up early on in the `Session` before the

compiler/rustc_parse/src/parser/expr.rs

+51-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use rustc_ast::{AnonConst, BinOp, BinOpKind, FnDecl, FnRetTy, MacCall, Param, Ty
1515
use rustc_ast::{Arm, Async, BlockCheckMode, Expr, ExprKind, Label, Movability, RangeLimits};
1616
use rustc_ast_pretty::pprust;
1717
use rustc_errors::{Applicability, DiagnosticBuilder, PResult};
18+
use rustc_session::lint::builtin::BREAK_WITH_LABEL_AND_LOOP;
19+
use rustc_session::lint::BuiltinLintDiagnostics;
1820
use rustc_span::edition::LATEST_STABLE_EDITION;
1921
use rustc_span::source_map::{self, Span, Spanned};
2022
use rustc_span::symbol::{kw, sym, Ident, Symbol};
@@ -1375,14 +1377,59 @@ impl<'a> Parser<'a> {
13751377
self.maybe_recover_from_bad_qpath(expr, true)
13761378
}
13771379

1378-
/// Parse `"('label ":")? break expr?`.
1380+
/// Parse `"break" (('label (:? expr)?) | expr?)` with `"break"` token already eaten.
1381+
/// If the label is followed immediately by a `:` token, the label and `:` are
1382+
/// parsed as part of the expression (i.e. a labeled loop). The language team has
1383+
/// decided in #87026 to require parentheses as a visual aid to avoid confusion if
1384+
/// the break expression of an unlabeled break is a labeled loop (as in
1385+
/// `break 'lbl: loop {}`); a labeled break with an unlabeled loop as its value
1386+
/// expression only gets a warning for compatibility reasons; and a labeled break
1387+
/// with a labeled loop does not even get a warning because there is no ambiguity.
13791388
fn parse_break_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
13801389
let lo = self.prev_token.span;
1381-
let label = self.eat_label();
1382-
let kind = if self.token != token::OpenDelim(token::Brace)
1390+
let mut label = self.eat_label();
1391+
let kind = if label.is_some() && self.token == token::Colon {
1392+
// The value expression can be a labeled loop, see issue #86948, e.g.:
1393+
// `loop { break 'label: loop { break 'label 42; }; }`
1394+
let lexpr = self.parse_labeled_expr(label.take().unwrap(), AttrVec::new(), true)?;
1395+
self.struct_span_err(
1396+
lexpr.span,
1397+
"parentheses are required around this expression to avoid confusion with a labeled break expression",
1398+
)
1399+
.multipart_suggestion(
1400+
"wrap the expression in parentheses",
1401+
vec![
1402+
(lexpr.span.shrink_to_lo(), "(".to_string()),
1403+
(lexpr.span.shrink_to_hi(), ")".to_string()),
1404+
],
1405+
Applicability::MachineApplicable,
1406+
)
1407+
.emit();
1408+
Some(lexpr)
1409+
} else if self.token != token::OpenDelim(token::Brace)
13831410
|| !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
13841411
{
1385-
self.parse_expr_opt()?
1412+
let expr = self.parse_expr_opt()?;
1413+
if let Some(ref expr) = expr {
1414+
if label.is_some()
1415+
&& matches!(
1416+
expr.kind,
1417+
ExprKind::While(_, _, None)
1418+
| ExprKind::ForLoop(_, _, _, None)
1419+
| ExprKind::Loop(_, None)
1420+
| ExprKind::Block(_, None)
1421+
)
1422+
{
1423+
self.sess.buffer_lint_with_diagnostic(
1424+
BREAK_WITH_LABEL_AND_LOOP,
1425+
lo.to(expr.span),
1426+
ast::CRATE_NODE_ID,
1427+
"this labeled break expression is easy to confuse with an unlabeled break with a labeled value expression",
1428+
BuiltinLintDiagnostics::BreakWithLabelAndLoop(expr.span),
1429+
);
1430+
}
1431+
}
1432+
expr
13861433
} else {
13871434
None
13881435
};
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,39 @@
1+
#![allow(unused, dead_code)]
2+
13
fn foo() -> u32 {
24
return 'label: loop { break 'label 42; };
35
}
46

57
fn bar() -> u32 {
68
loop { break 'label: loop { break 'label 42; }; }
7-
//~^ ERROR expected identifier, found keyword `loop`
8-
//~| ERROR expected type, found keyword `loop`
9+
//~^ ERROR: parentheses are required around this expression to avoid confusion
10+
//~| HELP: wrap the expression in parentheses
11+
}
12+
13+
fn baz() -> u32 {
14+
'label: loop {
15+
break 'label
16+
//~^ WARNING: this labeled break expression is easy to confuse with an unlabeled break
17+
loop { break 42; };
18+
//~^ HELP: wrap this expression in parentheses
19+
};
20+
21+
'label2: loop {
22+
break 'label2 'inner: loop { break 42; };
23+
// no warnings or errors here
24+
}
925
}
1026

1127
pub fn main() {
12-
foo();
28+
// Regression test for issue #86948, as resolved in #87026:
29+
let a = 'first_loop: loop {
30+
break 'first_loop 1;
31+
};
32+
let b = loop {
33+
break 'inner_loop: loop {
34+
//~^ ERROR: parentheses are required around this expression to avoid confusion
35+
//~| HELP: wrap the expression in parentheses
36+
break 'inner_loop 1;
37+
};
38+
};
1339
}
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,47 @@
1-
error: expected identifier, found keyword `loop`
2-
--> $DIR/lifetime_starts_expressions.rs:6:26
1+
error: parentheses are required around this expression to avoid confusion with a labeled break expression
2+
--> $DIR/lifetime_starts_expressions.rs:8:18
33
|
44
LL | loop { break 'label: loop { break 'label 42; }; }
5-
| ^^^^ expected identifier, found keyword
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66
|
7-
help: you can escape reserved keywords to use them as identifiers
7+
help: wrap the expression in parentheses
88
|
9-
LL | loop { break 'label: r#loop { break 'label 42; }; }
10-
| ^^^^^^
9+
LL | loop { break ('label: loop { break 'label 42; }); }
10+
| ^ ^
1111

12-
error: expected type, found keyword `loop`
13-
--> $DIR/lifetime_starts_expressions.rs:6:26
12+
error: parentheses are required around this expression to avoid confusion with a labeled break expression
13+
--> $DIR/lifetime_starts_expressions.rs:33:15
1414
|
15-
LL | loop { break 'label: loop { break 'label 42; }; }
16-
| - ^^^^ expected type
17-
| |
18-
| help: maybe write a path separator here: `::`
15+
LL | break 'inner_loop: loop {
16+
| _______________^
17+
LL | |
18+
LL | |
19+
LL | | break 'inner_loop 1;
20+
LL | | };
21+
| |_________^
22+
|
23+
help: wrap the expression in parentheses
24+
|
25+
LL | break ('inner_loop: loop {
26+
LL |
27+
LL |
28+
LL | break 'inner_loop 1;
29+
LL | });
30+
|
31+
32+
warning: this labeled break expression is easy to confuse with an unlabeled break with a labeled value expression
33+
--> $DIR/lifetime_starts_expressions.rs:15:9
34+
|
35+
LL | / break 'label
36+
LL | |
37+
LL | | loop { break 42; };
38+
| |______________________________^
39+
|
40+
= note: `#[warn(break_with_label_and_loop)]` on by default
41+
help: wrap this expression in parentheses
1942
|
20-
= note: `#![feature(type_ascription)]` lets you annotate an expression with a type: `<expr>: <type>`
43+
LL | (loop { break 42; });
44+
| ^ ^
2145

22-
error: aborting due to 2 previous errors
46+
error: aborting due to 2 previous errors; 1 warning emitted
2347

0 commit comments

Comments
 (0)