From eccfeca2f6738515e07dae693ff9f9f8b4d4ad0f Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 23 Jun 2024 16:58:15 -0700 Subject: [PATCH] Parenthesize break values containing leading label --- compiler/rustc_ast/src/util/classify.rs | 79 ++++++++++++++++++- .../rustc_ast_pretty/src/pprust/state/expr.rs | 8 +- .../ui/unpretty/expanded-interpolation.stdout | 4 +- 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs index 4b2544ac47ed8..b75aa79a0418f 100644 --- a/compiler/rustc_ast/src/util/classify.rs +++ b/compiler/rustc_ast/src/util/classify.rs @@ -1,7 +1,8 @@ //! Routines the parser and pretty-printer use to classify AST nodes. use crate::ast::ExprKind::*; -use crate::{ast, token::Delimiter}; +use crate::ast::{self, MatchKind}; +use crate::token::Delimiter; /// This classification determines whether various syntactic positions break out /// of parsing the current expression (true) or continue parsing more of the @@ -81,6 +82,82 @@ pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool { } } +/// Returns whether the leftmost token of the given expression is the label of a +/// labeled loop or block, such as in `'inner: loop { break 'inner 1 } + 1`. +/// +/// Such expressions are not allowed as the value of an unlabeled break. +/// +/// ```ignore (illustrative) +/// 'outer: { +/// break 'inner: loop { break 'inner 1 } + 1; // invalid syntax +/// +/// break 'outer 'inner: loop { break 'inner 1 } + 1; // okay +/// +/// break ('inner: loop { break 'inner 1 } + 1); // okay +/// +/// break ('inner: loop { break 'inner 1 }) + 1; // okay +/// } +/// ``` +pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool { + loop { + match &expr.kind { + Block(_, label) | ForLoop { label, .. } | Loop(_, label, _) | While(_, _, label) => { + return label.is_some(); + } + + Assign(e, _, _) + | AssignOp(_, e, _) + | Binary(_, e, _) + | Call(e, _) + | Cast(e, _) + | Field(e, _) + | Index(e, _, _) + | Match(e, _, MatchKind::Postfix) + | Range(Some(e), _, _) + | Try(e) => { + expr = e; + } + MethodCall(method_call) => { + expr = &method_call.receiver; + } + + AddrOf(..) + | Array(..) + | Await(..) + | Become(..) + | Break(..) + | Closure(..) + | ConstBlock(..) + | Continue(..) + | FormatArgs(..) + | Gen(..) + | If(..) + | IncludedBytes(..) + | InlineAsm(..) + | Let(..) + | Lit(..) + | MacCall(..) + | Match(_, _, MatchKind::Prefix) + | OffsetOf(..) + | Paren(..) + | Path(..) + | Range(None, _, _) + | Repeat(..) + | Ret(..) + | Struct(..) + | TryBlock(..) + | Tup(..) + | Type(..) + | Unary(..) + | Underscore + | Yeet(..) + | Yield(..) + | Err(..) + | Dummy => return false, + } + } +} + pub enum TrailingBrace<'a> { /// Trailing brace in a macro call, like the one in `x as *const brace! {}`. /// We will suggest changing the macro call to a different delimiter. diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 1e117c46b6e29..86b6b0e219928 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -5,6 +5,7 @@ use ast::{ForLoopKind, MatchKind}; use itertools::{Itertools, Position}; use rustc_ast::ptr::P; use rustc_ast::token; +use rustc_ast::util::classify; use rustc_ast::util::literal::escape_byte_str_symbol; use rustc_ast::util::parser::{self, AssocOp, Fixity}; use rustc_ast::{self as ast, BlockCheckMode}; @@ -610,9 +611,12 @@ impl<'a> State<'a> { } if let Some(expr) = opt_expr { self.space(); - self.print_expr_maybe_paren( + self.print_expr_cond_paren( expr, - parser::PREC_JUMP, + // Parenthesize if required by precedence, or in the + // case of `break 'inner: loop { break 'inner 1 } + 1` + expr.precedence().order() < parser::PREC_JUMP + || (opt_label.is_none() && classify::leading_labeled_expr(expr)), fixup.subsequent_subexpression(), ); } diff --git a/tests/ui/unpretty/expanded-interpolation.stdout b/tests/ui/unpretty/expanded-interpolation.stdout index ed075c9114ce6..556e57dbd9210 100644 --- a/tests/ui/unpretty/expanded-interpolation.stdout +++ b/tests/ui/unpretty/expanded-interpolation.stdout @@ -25,12 +25,12 @@ fn break_labeled_loop() { 'outer: loop { break 'outer 'inner: loop { break 'inner 1; } + 1; }; let paren_around_break_value = - 'outer: loop { break 'inner: loop { break 'inner 1; } + 1; }; + 'outer: loop { break ('inner: loop { break 'inner 1; } + 1); }; macro_rules! breaking { ($value:expr) => { break $value }; } let paren_around_break_value = - loop { break 'inner: loop { break 'inner 1; } + 1; }; + loop { break ('inner: loop { break 'inner 1; } + 1); }; } fn if_let() {