Rollup merge of #154057 - aytey:fix-block-index-paren, r=fmease

Parenthesize block-like expressions in index base of pretty printer

The AST pretty printer produces invalid Rust when a block expression is the base of an index operation inside a macro expansion. This is a gap in the parenthesization fix from rust-lang/rust#119105 — the `FixupContext` approach handles statement position but not the case where a block-index is nested inside another expression.

The following is a correct program:

```rust
macro_rules! block_arr {
    () => {{ [0u8; 4] }};
}

macro_rules! as_slice {
    () => {{ &block_arr!()[..] }};
}

fn main() { let _: &[u8] = as_slice!(); }
```

But `rustc -Zunpretty=expanded` produces output that is not valid Rust, because the closing brace of `{ [0u8; 4] }` creates a statement boundary, causing the parser to treat `[..]` as a separate expression:

```rust
fn main() { let _: &[u8] = { &{ [0u8; 4] }[..] }; }
```

```
error: expected one of `.`, `;`, `?`, `}`, or an operator, found `[`
```

Fixed output after this change:

```rust
fn main() { let _: &[u8] = { &({ [0u8; 4] })[..] }; }
```

Since `{ ... }[...]` never parses as indexing a block regardless of context, the fix unconditionally parenthesizes "complete" expressions (block, match, if, loop, etc.) when they appear as the base of an index operation.
This commit is contained in:
Jonathan Brouwer
2026-04-08 23:04:35 +02:00
committed by GitHub
3 changed files with 46 additions and 2 deletions
@@ -235,7 +235,15 @@ fn print_expr_call(&mut self, func: &ast::Expr, args: &[Box<ast::Expr>], fixup:
// In order to call a named field, needs parens: `(self.fun)()`
// But not for an unnamed field: `self.0()`
ast::ExprKind::Field(_, name) => !name.is_numeric(),
_ => func_fixup.precedence(func) < ExprPrecedence::Unambiguous,
// Block-like expressions (block, match, if, loop, ...) never
// parse as the callee of a call, regardless of context: the
// closing brace ends the expression and `(args)` becomes a
// separate tuple. Parenthesize them so the call survives a
// pretty-print round trip.
_ => {
func_fixup.precedence(func) < ExprPrecedence::Unambiguous
|| classify::expr_is_complete(func)
}
};
self.print_expr_cond_paren(func, needs_paren, func_fixup);
@@ -677,7 +685,8 @@ pub(super) fn print_expr_outer_attr_style(
let expr_fixup = fixup.leftmost_subexpression_with_operator(true);
self.print_expr_cond_paren(
expr,
expr_fixup.precedence(expr) < ExprPrecedence::Unambiguous,
expr_fixup.precedence(expr) < ExprPrecedence::Unambiguous
|| classify::expr_is_complete(expr),
expr_fixup,
);
self.word("[");
+18
View File
@@ -0,0 +1,18 @@
//@ pretty-mode:expanded
//@ pp-exact:block-index-paren.pp
macro_rules! block_arr {
() => {{ [0u8; 4] }};
}
macro_rules! as_slice {
() => {{ &block_arr!()[..] }};
}
macro_rules! group {
($e:expr) => { $e };
}
fn scope() { &group!({ drop })(0); }
fn main() { let _: &[u8] = as_slice!(); }
+17
View File
@@ -0,0 +1,17 @@
#![feature(prelude_import)]
#![no_std]
extern crate std;
#[prelude_import]
use ::std::prelude::rust_2015::*;
//@ pretty-mode:expanded
//@ pp-exact:block-index-paren.pp
macro_rules! block_arr { () => {{ [0u8; 4] }}; }
macro_rules! as_slice { () => {{ &block_arr!()[..] }}; }
macro_rules! group { ($e:expr) => { $e }; }
fn scope() { &({ drop })(0); }
fn main() { let _: &[u8] = { &({ [0u8; 4] })[..] }; }