From e15897f6c4bd948526b8f43d3ea8874075eccab9 Mon Sep 17 00:00:00 2001 From: "Andrew V. Teylu" Date: Thu, 19 Mar 2026 10:27:40 +0000 Subject: [PATCH] Parenthesize or-patterns in prefix pattern positions in pretty printer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AST pretty printer was dropping parentheses around or-patterns when they appeared inside `@` bindings, `&` references, or `box` patterns. For example: - `v @ (1 | 2 | 3)` was printed as `v @ 1 | 2 | 3` - `&(1 | 2 | 3)` was printed as `&1 | 2 | 3` - `box (1 | 2 | 3)` was printed as `box 1 | 2 | 3` Since `|` has the lowest precedence among pattern operators, all of these are parsed incorrectly without parentheses — e.g. `v @ 1 | 2 | 3` becomes `(v @ 1) | 2 | 3`, binding `v` only to the first alternative. This caused E0408 ("variable not bound in all patterns") when the expanded output was fed back to the compiler, affecting crates like html5ever and wgpu-core that use macros expanding to or-patterns after `@`. The fix adds a `print_pat_paren_if_or` helper that wraps `PatKind::Or` subpatterns in parentheses, and uses it in the `@`, `&`, and `box` printing arms. This is similar in spirit to the existing `FixupContext` parenthesization approach used for expression printing. Signed-off-by: Andrew V. Teylu --- compiler/rustc_ast_pretty/src/pprust/state.rs | 23 ++++++++++-- tests/pretty/or-pattern-paren.pp | 27 ++++++++++++++ tests/pretty/or-pattern-paren.rs | 36 +++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 tests/pretty/or-pattern-paren.pp create mode 100644 tests/pretty/or-pattern-paren.rs diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 4ba5dc541342..74f0f970f76d 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -1749,6 +1749,23 @@ fn print_qpath(&mut self, path: &ast::Path, qself: &ast::QSelf, colons_before_pa } } + /// Print a pattern, parenthesizing it if it is an or-pattern (`A | B`). + /// + /// Or-patterns have the lowest precedence among patterns, so they need + /// parentheses when nested inside `@` bindings, `&` references, or `box` + /// patterns — otherwise `x @ A | B` parses as `(x @ A) | B`, `&A | B` + /// parses as `(&A) | B`, etc. + fn print_pat_paren_if_or(&mut self, pat: &ast::Pat) { + let needs_paren = matches!(pat.kind, PatKind::Or(..)); + if needs_paren { + self.popen(); + } + self.print_pat(pat); + if needs_paren { + self.pclose(); + } + } + fn print_pat(&mut self, pat: &ast::Pat) { self.maybe_print_comment(pat.span.lo()); self.ann.pre(self, AnnNode::Pat(pat)); @@ -1776,7 +1793,7 @@ fn print_pat(&mut self, pat: &ast::Pat) { if let Some(p) = sub { self.space(); self.word_space("@"); - self.print_pat(p); + self.print_pat_paren_if_or(p); } } PatKind::TupleStruct(qself, path, elts) => { @@ -1848,7 +1865,7 @@ fn print_pat(&mut self, pat: &ast::Pat) { } PatKind::Box(inner) => { self.word("box "); - self.print_pat(inner); + self.print_pat_paren_if_or(inner); } PatKind::Deref(inner) => { self.word("deref!"); @@ -1872,7 +1889,7 @@ fn print_pat(&mut self, pat: &ast::Pat) { self.print_pat(inner); self.pclose(); } else { - self.print_pat(inner); + self.print_pat_paren_if_or(inner); } } PatKind::Expr(e) => self.print_expr(e, FixupContext::default()), diff --git a/tests/pretty/or-pattern-paren.pp b/tests/pretty/or-pattern-paren.pp new file mode 100644 index 000000000000..6ea94eb7b91f --- /dev/null +++ b/tests/pretty/or-pattern-paren.pp @@ -0,0 +1,27 @@ +#![feature(prelude_import)] +#![no_std] +#![feature(box_patterns)] +extern crate std; +#[prelude_import] +use ::std::prelude::rust_2015::*; + +//@ pretty-compare-only +//@ pretty-mode:expanded +//@ pp-exact:or-pattern-paren.pp + +macro_rules! or_pat { ($($name:pat),+) => { $($name)|+ } } + +fn check_at(x: Option) { + match x { + Some(v @ (1 | 2 | 3)) => + + + { + ::std::io::_print(format_args!("{0}\n", v)); + } + _ => {} + } +} +fn check_ref(x: &i32) { match x { &(1 | 2 | 3) => {} _ => {} } } +fn check_box(x: Box) { match x { box (1 | 2 | 3) => {} _ => {} } } +fn main() { check_at(Some(2)); check_ref(&1); check_box(Box::new(1)); } diff --git a/tests/pretty/or-pattern-paren.rs b/tests/pretty/or-pattern-paren.rs new file mode 100644 index 000000000000..ea6a8f3de9e4 --- /dev/null +++ b/tests/pretty/or-pattern-paren.rs @@ -0,0 +1,36 @@ +#![feature(box_patterns)] + +//@ pretty-compare-only +//@ pretty-mode:expanded +//@ pp-exact:or-pattern-paren.pp + +macro_rules! or_pat { + ($($name:pat),+) => { $($name)|+ } +} + +fn check_at(x: Option) { + match x { + Some(v @ or_pat!(1, 2, 3)) => println!("{v}"), + _ => {} + } +} + +fn check_ref(x: &i32) { + match x { + &or_pat!(1, 2, 3) => {} + _ => {} + } +} + +fn check_box(x: Box) { + match x { + box or_pat!(1, 2, 3) => {} + _ => {} + } +} + +fn main() { + check_at(Some(2)); + check_ref(&1); + check_box(Box::new(1)); +}