about summary refs log tree commit diff
path: root/compiler/rustc_ast/src/util
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-07-02 17:47:45 +0200
committerGitHub <noreply@github.com>2024-07-02 17:47:45 +0200
commitf8f67b296915e70a67fbf4595b1f54d9d773be57 (patch)
tree0188cd7a6c3431559cc52d81c8a3bf37e743f2a8 /compiler/rustc_ast/src/util
parent7d97c59438e933e86f557ed999da3b8dfc6855a7 (diff)
parent06982239a6210cfc7b0b370634c9a9160c9b9dc4 (diff)
downloadrust-f8f67b296915e70a67fbf4595b1f54d9d773be57.tar.gz
rust-f8f67b296915e70a67fbf4595b1f54d9d773be57.zip
Rollup merge of #126883 - dtolnay:breakvalue, r=fmease
Parenthesize break values containing leading label

The AST pretty printer previously produced invalid syntax in the case of `break` expressions with a value that begins with a loop or block label.

```rust
macro_rules! expr {
    ($e:expr) => {
        $e
    };
}

fn main() {
    loop {
        break expr!('a: loop { break 'a 1; } + 1);
    };
}
```

`rustc -Zunpretty=expanded main.rs `:

```console
#![feature(prelude_import)]
#![no_std]
#[prelude_import]
use ::std::prelude::rust_2015::*;
#[macro_use]
extern crate std;
macro_rules! expr { ($e:expr) => { $e }; }

fn main() { loop { break 'a: loop { break 'a 1; } + 1; }; }
```

The expanded code is not valid Rust syntax. Printing invalid syntax is bad because it blocks `cargo expand` from being able to format the output as Rust syntax using rustfmt.

```console
error: parentheses are required around this expression to avoid confusion with a labeled break expression
 --> <anon>:9:26
  |
9 | fn main() { loop { break 'a: loop { break 'a 1; } + 1; }; }
  |                          ^^^^^^^^^^^^^^^^^^^^^^^^
  |
help: wrap the expression in parentheses
  |
9 | fn main() { loop { break ('a: loop { break 'a 1; }) + 1; }; }
  |                          +                        +
```

This PR updates the AST pretty-printer to insert parentheses around the value of a `break` expression as required to avoid this edge case.
Diffstat (limited to 'compiler/rustc_ast/src/util')
-rw-r--r--compiler/rustc_ast/src/util/classify.rs79
1 files changed, 78 insertions, 1 deletions
diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs
index 4b2544ac47e..541b95ea971 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, _)
+            | Await(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(..)
+            | 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.