Skip to content

Commit 267dcb2

Browse files
committed
Add pretty-printer parenthesis insertion test
1 parent a522d78 commit 267dcb2

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed

Diff for: tests/ui-fulldeps/pprust-parenthesis-insertion.rs

+242
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
//@ run-pass
2+
//@ ignore-cross-compile
3+
4+
// This test covers the AST pretty-printer's automatic insertion of parentheses
5+
// into unparenthesized syntax trees according to precedence and various grammar
6+
// restrictions and edge cases.
7+
//
8+
// For example if the following syntax tree represents the expression a*(b+c),
9+
// in which the parenthesis is necessary for precedence:
10+
//
11+
// Binary('*', Path("a"), Paren(Binary('+', Path("b"), Path("c"))))
12+
//
13+
// then the pretty-printer needs to be able to print the following
14+
// unparenthesized syntax tree with an automatically inserted parenthesization.
15+
//
16+
// Binary('*', Path("a"), Binary('+', Path("b"), Path("c")))
17+
//
18+
// Handling this correctly is relevant in real-world code when pretty-printing
19+
// macro-generated syntax trees, in which expressions can get interpolated into
20+
// one another without any parenthesization being visible in the syntax tree.
21+
//
22+
// macro_rules! repro {
23+
// ($rhs:expr) => {
24+
// a * $rhs
25+
// };
26+
// }
27+
//
28+
// let _ = repro!(b + c);
29+
30+
#![feature(rustc_private)]
31+
32+
extern crate rustc_ast;
33+
extern crate rustc_ast_pretty;
34+
extern crate rustc_driver;
35+
extern crate rustc_errors;
36+
extern crate rustc_parse;
37+
extern crate rustc_session;
38+
extern crate rustc_span;
39+
extern crate smallvec;
40+
41+
use std::mem;
42+
use std::process::ExitCode;
43+
44+
use rustc_ast::ast::{DUMMY_NODE_ID, Expr, ExprKind, Stmt};
45+
use rustc_ast::mut_visit::{self, DummyAstNode as _, MutVisitor};
46+
use rustc_ast::node_id::NodeId;
47+
use rustc_ast::ptr::P;
48+
use rustc_ast_pretty::pprust;
49+
use rustc_errors::Diag;
50+
use rustc_parse::parser::Recovery;
51+
use rustc_session::parse::ParseSess;
52+
use rustc_span::{DUMMY_SP, FileName, Span};
53+
use smallvec::SmallVec;
54+
55+
// Every parenthesis in the following expressions is re-inserted by the
56+
// pretty-printer.
57+
//
58+
// FIXME: Some of them shouldn't be.
59+
static EXPRS: &[&str] = &[
60+
// Straightforward binary operator precedence.
61+
"2 * 2 + 2",
62+
"2 + 2 * 2",
63+
"(2 + 2) * 2",
64+
"2 * (2 + 2)",
65+
"2 + 2 + 2",
66+
// Return has lower precedence than a binary operator.
67+
"(return 2) + 2",
68+
"2 + (return 2)", // FIXME: no parenthesis needed.
69+
"(return) + 2", // FIXME: no parenthesis needed.
70+
// These mean different things.
71+
"return - 2",
72+
"(return) - 2",
73+
// These mean different things.
74+
"if let _ = true && false {}",
75+
"if let _ = (true && false) {}",
76+
// Conditions end at the first curly brace, so struct expressions need to be
77+
// parenthesized. Except in a match guard, where conditions end at arrow.
78+
"if let _ = (Struct {}) {}",
79+
"match 2 { _ if let _ = Struct {} => {} }",
80+
// Match arms terminate eagerly, so parenthesization is needed around some
81+
// expressions.
82+
"match 2 { _ => 1 - 1 }",
83+
"match 2 { _ => ({ 1 }) - 1 }",
84+
// Grammar restriction: break value starting with a labeled loop is not
85+
// allowed, except if the break is also labeled.
86+
"break 'outer 'inner: loop {} + 2",
87+
"break ('inner: loop {} + 2)",
88+
// Grammar restriction: the value in let-else is not allowed to end in a
89+
// curly brace.
90+
"{ let _ = 1 + 1 else {}; }",
91+
"{ let _ = (loop {}) else {}; }",
92+
"{ let _ = mac!() else {}; }",
93+
"{ let _ = (mac! {}) else {}; }",
94+
// Parentheses are necessary to prevent an eager statement boundary.
95+
"{ 2 - 1 }",
96+
"{ (match 2 {}) - 1 }",
97+
"{ (match 2 {})() - 1 }",
98+
"{ (match 2 {})[0] - 1 }",
99+
"{ (loop {}) - 1 }",
100+
// Angle bracket is eagerly parsed as a path's generic argument list.
101+
"(2 as T) < U",
102+
"(2 as T<U>) < V", // FIXME: no parentheses needed.
103+
/*
104+
// FIXME: pretty-printer produces invalid syntax. `2 + 2 as T < U`
105+
"(2 + 2 as T) < U",
106+
*/
107+
/*
108+
// FIXME: pretty-printer produces invalid syntax. `if (let _ = () && Struct {}.x) {}`
109+
"if let _ = () && (Struct {}).x {}",
110+
*/
111+
/*
112+
// FIXME: pretty-printer produces invalid syntax. `(1 < 2 == false) as usize`
113+
"((1 < 2) == false) as usize",
114+
*/
115+
/*
116+
// FIXME: pretty-printer produces invalid syntax. `for _ in 1..{ 2 } {}`
117+
"for _ in (1..{ 2 }) {}",
118+
*/
119+
/*
120+
// FIXME: pretty-printer loses the attribute. `{ let Struct { field } = s; }`
121+
"{ let Struct { #[attr] field } = s; }",
122+
*/
123+
/*
124+
// FIXME: pretty-printer turns this into a range. `0..to_string()`
125+
"(0.).to_string()",
126+
"0. .. 1.",
127+
*/
128+
/*
129+
// FIXME: pretty-printer loses the dyn*. `i as Trait`
130+
"i as dyn* Trait",
131+
*/
132+
];
133+
134+
// Flatten the content of parenthesis nodes into their parent node. For example
135+
// this syntax tree representing the expression a*(b+c):
136+
//
137+
// Binary('*', Path("a"), Paren(Binary('+', Path("b"), Path("c"))))
138+
//
139+
// would unparenthesize to:
140+
//
141+
// Binary('*', Path("a"), Binary('+', Path("b"), Path("c")))
142+
struct Unparenthesize;
143+
144+
impl MutVisitor for Unparenthesize {
145+
fn visit_expr(&mut self, e: &mut P<Expr>) {
146+
while let ExprKind::Paren(paren) = &mut e.kind {
147+
**e = mem::replace(&mut *paren, Expr::dummy());
148+
}
149+
mut_visit::walk_expr(self, e);
150+
}
151+
}
152+
153+
// Erase Span information that could distinguish between identical expressions
154+
// parsed from different source strings.
155+
struct Normalize;
156+
157+
impl MutVisitor for Normalize {
158+
const VISIT_TOKENS: bool = true;
159+
160+
fn visit_id(&mut self, id: &mut NodeId) {
161+
*id = DUMMY_NODE_ID;
162+
}
163+
164+
fn visit_span(&mut self, span: &mut Span) {
165+
*span = DUMMY_SP;
166+
}
167+
168+
fn visit_expr(&mut self, expr: &mut P<Expr>) {
169+
if let ExprKind::Binary(binop, _left, _right) = &mut expr.kind {
170+
self.visit_span(&mut binop.span);
171+
}
172+
mut_visit::walk_expr(self, expr);
173+
}
174+
175+
fn flat_map_stmt(&mut self, mut stmt: Stmt) -> SmallVec<[Stmt; 1]> {
176+
self.visit_span(&mut stmt.span);
177+
mut_visit::walk_flat_map_stmt(self, stmt)
178+
}
179+
}
180+
181+
fn parse_expr(psess: &ParseSess, source_code: &str) -> Option<P<Expr>> {
182+
let parser = rustc_parse::unwrap_or_emit_fatal(rustc_parse::new_parser_from_source_str(
183+
psess,
184+
FileName::anon_source_code(source_code),
185+
source_code.to_owned(),
186+
));
187+
188+
let mut expr = parser.recovery(Recovery::Forbidden).parse_expr().map_err(Diag::cancel).ok()?;
189+
Normalize.visit_expr(&mut expr);
190+
Some(expr)
191+
}
192+
193+
fn main() -> ExitCode {
194+
let mut status = ExitCode::SUCCESS;
195+
let mut fail = |description: &str, before: &str, after: &str| {
196+
status = ExitCode::FAILURE;
197+
eprint!(
198+
"{description}\n BEFORE: {before}\n AFTER: {after}\n\n",
199+
before = before.replace('\n', "\n "),
200+
after = after.replace('\n', "\n "),
201+
);
202+
};
203+
204+
rustc_span::create_default_session_globals_then(|| {
205+
let psess = &ParseSess::new(vec![rustc_parse::DEFAULT_LOCALE_RESOURCE]);
206+
207+
for &source_code in EXPRS {
208+
let expr = parse_expr(psess, source_code).unwrap();
209+
210+
// Check for FALSE POSITIVE: pretty-printer inserting parentheses where not needed.
211+
// Pseudocode:
212+
// assert(expr == parse(print(expr)))
213+
let printed = &pprust::expr_to_string(&expr);
214+
let Some(expr2) = parse_expr(psess, printed) else {
215+
fail("Pretty-printer produced invalid syntax", source_code, printed);
216+
continue;
217+
};
218+
if format!("{expr:#?}") != format!("{expr2:#?}") {
219+
fail("Pretty-printer inserted unnecessary parenthesis", source_code, printed);
220+
continue;
221+
}
222+
223+
// Check for FALSE NEGATIVE: pretty-printer failing to place necessary parentheses.
224+
// Pseudocode:
225+
// assert(unparenthesize(expr) == unparenthesize(parse(print(unparenthesize(expr)))))
226+
let mut expr = expr;
227+
Unparenthesize.visit_expr(&mut expr);
228+
let printed = &pprust::expr_to_string(&expr);
229+
let Some(mut expr2) = parse_expr(psess, printed) else {
230+
fail("Pretty-printer with no parens produced invalid syntax", source_code, printed);
231+
continue;
232+
};
233+
Unparenthesize.visit_expr(&mut expr2);
234+
if format!("{expr:#?}") != format!("{expr2:#?}") {
235+
fail("Pretty-printer lost necessary parentheses", source_code, printed);
236+
continue;
237+
}
238+
}
239+
});
240+
241+
status
242+
}

0 commit comments

Comments
 (0)