|
| 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