diff --git a/src/expr.rs b/src/expr.rs index 2c0b346f26..22400be329 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -2463,18 +2463,36 @@ pub(crate) mod parsing { #[cfg(feature = "full")] fn expr_break(input: ParseStream, allow_struct: AllowStruct) -> Result { + let break_token: Token![break] = input.parse()?; + + let ahead = input.fork(); + let label: Option = ahead.parse()?; + if label.is_some() && ahead.peek(Token![:]) { + // Not allowed: `break 'label: loop {...}` + // Parentheses are required. `break ('label: loop {...})` + let _ = ambiguous_expr(input, allow_struct)?; + let start_span = label.unwrap().apostrophe; + let end_span = input.cursor().prev_span(); + return Err(crate::error::new2( + start_span, + end_span, + "parentheses required", + )); + } + + input.advance_to(&ahead); + let expr = if can_begin_expr(input) && (allow_struct.0 || !input.peek(token::Brace)) { + let expr = ambiguous_expr(input, allow_struct)?; + Some(Box::new(expr)) + } else { + None + }; + Ok(ExprBreak { attrs: Vec::new(), - break_token: input.parse()?, - label: input.parse()?, - expr: { - if can_begin_expr(input) && (allow_struct.0 || !input.peek(token::Brace)) { - let expr = ambiguous_expr(input, allow_struct)?; - Some(Box::new(expr)) - } else { - None - } - }, + break_token, + label, + expr, }) } diff --git a/tests/test_expr.rs b/tests/test_expr.rs index 5d529bf144..2574ea5487 100644 --- a/tests/test_expr.rs +++ b/tests/test_expr.rs @@ -1,11 +1,11 @@ -#![allow(clippy::uninlined_format_args)] +#![allow(clippy::single_element_loop, clippy::uninlined_format_args)] #[macro_use] mod macros; use proc_macro2::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; use quote::quote; -use syn::{Expr, ExprRange}; +use syn::{Expr, ExprRange, Stmt}; #[test] fn test_expr_parse() { @@ -310,3 +310,32 @@ fn test_ranges() { syn::parse_str::("lo...").unwrap_err(); syn::parse_str::("lo...hi").unwrap_err(); } + +#[test] +fn test_ambiguous_label() { + for stmt in [ + quote! { + return 'label: loop { break 'label 42; }; + }, + quote! { + break ('label: loop { break 'label 42; }); + }, + quote! { + break 1 + 'label: loop { break 'label 42; }; + }, + quote! { + break 'outer 'inner: loop { break 'inner 42; }; + }, + ] { + syn::parse2::(stmt).unwrap(); + } + + for stmt in [ + // Parentheses required. See https://github.com/rust-lang/rust/pull/87026. + quote! { + break 'label: loop { break 'label 42; }; + }, + ] { + syn::parse2::(stmt).unwrap_err(); + } +}