1//! This test does the following for every file in the rust-lang/rust repo: 2//! 3//! 1. Parse the file using syn into a syn::File. 4//! 2. Extract every syn::Expr from the file. 5//! 3. Print each expr to a string of source code. 6//! 4. Parse the source code using librustc_parse into a rustc_ast::Expr. 7//! 5. For both the syn::Expr and rustc_ast::Expr, crawl the syntax tree to 8//! insert parentheses surrounding every subexpression. 9//! 6. Serialize the fully parenthesized syn::Expr to a string of source code. 10//! 7. Parse the fully parenthesized source code using librustc_parse. 11//! 8. Compare the rustc_ast::Expr resulting from parenthesizing using rustc 12//! data structures vs syn data structures, ignoring spans. If they agree, 13//! rustc's parser and syn's parser have identical handling of expression 14//! precedence. 15 16#![cfg(not(syn_disable_nightly_tests))] 17#![cfg(not(miri))] 18#![recursion_limit = "1024"] 19#![feature(rustc_private)] 20#![allow( 21 clippy::blocks_in_conditions, 22 clippy::doc_markdown, 23 clippy::explicit_deref_methods, 24 clippy::let_underscore_untyped, 25 clippy::manual_assert, 26 clippy::manual_let_else, 27 clippy::match_like_matches_macro, 28 clippy::match_wildcard_for_single_variants, 29 clippy::too_many_lines, 30 clippy::uninlined_format_args 31)] 32 33extern crate rustc_ast; 34extern crate rustc_ast_pretty; 35extern crate rustc_data_structures; 36extern crate rustc_driver; 37extern crate rustc_span; 38extern crate smallvec; 39extern crate thin_vec; 40 41use crate::common::eq::SpanlessEq; 42use crate::common::parse; 43use quote::ToTokens; 44use rustc_ast::ast; 45use rustc_ast::ptr::P; 46use rustc_ast_pretty::pprust; 47use rustc_span::edition::Edition; 48use std::fs; 49use std::path::Path; 50use std::process; 51use std::sync::atomic::{AtomicUsize, Ordering}; 52 53#[macro_use] 54mod macros; 55 56#[allow(dead_code)] 57mod common; 58 59mod repo; 60 61#[test] 62fn test_rustc_precedence() { 63 common::rayon_init(); 64 repo::clone_rust(); 65 let abort_after = common::abort_after(); 66 if abort_after == 0 { 67 panic!("Skipping all precedence tests"); 68 } 69 70 let passed = AtomicUsize::new(0); 71 let failed = AtomicUsize::new(0); 72 73 repo::for_each_rust_file(|path| { 74 let content = fs::read_to_string(path).unwrap(); 75 76 let (l_passed, l_failed) = match syn::parse_file(&content) { 77 Ok(file) => { 78 let edition = repo::edition(path).parse().unwrap(); 79 let exprs = collect_exprs(file); 80 let (l_passed, l_failed) = test_expressions(path, edition, exprs); 81 errorf!( 82 "=== {}: {} passed | {} failed\n", 83 path.display(), 84 l_passed, 85 l_failed, 86 ); 87 (l_passed, l_failed) 88 } 89 Err(msg) => { 90 errorf!("\nFAIL {} - syn failed to parse: {}\n", path.display(), msg); 91 (0, 1) 92 } 93 }; 94 95 passed.fetch_add(l_passed, Ordering::Relaxed); 96 let prev_failed = failed.fetch_add(l_failed, Ordering::Relaxed); 97 98 if prev_failed + l_failed >= abort_after { 99 process::exit(1); 100 } 101 }); 102 103 let passed = passed.load(Ordering::Relaxed); 104 let failed = failed.load(Ordering::Relaxed); 105 106 errorf!("\n===== Precedence Test Results =====\n"); 107 errorf!("{} passed | {} failed\n", passed, failed); 108 109 if failed > 0 { 110 panic!("{} failures", failed); 111 } 112} 113 114fn test_expressions(path: &Path, edition: Edition, exprs: Vec<syn::Expr>) -> (usize, usize) { 115 let mut passed = 0; 116 let mut failed = 0; 117 118 rustc_span::create_session_if_not_set_then(edition, |_| { 119 for expr in exprs { 120 let source_code = expr.to_token_stream().to_string(); 121 let librustc_ast = if let Some(e) = librustc_parse_and_rewrite(&source_code) { 122 e 123 } else { 124 failed += 1; 125 errorf!( 126 "\nFAIL {} - librustc failed to parse original\n", 127 path.display(), 128 ); 129 continue; 130 }; 131 132 let syn_parenthesized_code = 133 syn_parenthesize(expr.clone()).to_token_stream().to_string(); 134 let syn_ast = if let Some(e) = parse::librustc_expr(&syn_parenthesized_code) { 135 e 136 } else { 137 failed += 1; 138 errorf!( 139 "\nFAIL {} - librustc failed to parse parenthesized\n", 140 path.display(), 141 ); 142 continue; 143 }; 144 145 if !SpanlessEq::eq(&syn_ast, &librustc_ast) { 146 failed += 1; 147 let syn_pretty = pprust::expr_to_string(&syn_ast); 148 let librustc_pretty = pprust::expr_to_string(&librustc_ast); 149 errorf!( 150 "\nFAIL {}\n{}\nsyn != rustc\n{}\n", 151 path.display(), 152 syn_pretty, 153 librustc_pretty, 154 ); 155 continue; 156 } 157 158 let expr_invisible = make_parens_invisible(expr); 159 let Ok(reparsed_expr_invisible) = syn::parse2(expr_invisible.to_token_stream()) else { 160 failed += 1; 161 errorf!( 162 "\nFAIL {} - syn failed to parse invisible delimiters\n{}\n", 163 path.display(), 164 source_code, 165 ); 166 continue; 167 }; 168 if expr_invisible != reparsed_expr_invisible { 169 failed += 1; 170 errorf!( 171 "\nFAIL {} - mismatch after parsing invisible delimiters\n{}\n", 172 path.display(), 173 source_code, 174 ); 175 continue; 176 } 177 178 passed += 1; 179 } 180 }); 181 182 (passed, failed) 183} 184 185fn librustc_parse_and_rewrite(input: &str) -> Option<P<ast::Expr>> { 186 parse::librustc_expr(input).map(librustc_parenthesize) 187} 188 189fn librustc_parenthesize(mut librustc_expr: P<ast::Expr>) -> P<ast::Expr> { 190 use rustc_ast::ast::{ 191 AssocItem, AssocItemKind, Attribute, BinOpKind, Block, BorrowKind, BoundConstness, Expr, 192 ExprField, ExprKind, GenericArg, GenericBound, ItemKind, Local, LocalKind, Pat, Stmt, 193 StmtKind, StructExpr, StructRest, TraitBoundModifiers, Ty, 194 }; 195 use rustc_ast::mut_visit::{ 196 noop_flat_map_assoc_item, noop_visit_generic_arg, noop_visit_item_kind, noop_visit_local, 197 noop_visit_param_bound, MutVisitor, 198 }; 199 use rustc_data_structures::flat_map_in_place::FlatMapInPlace; 200 use rustc_span::DUMMY_SP; 201 use smallvec::SmallVec; 202 use std::mem; 203 use std::ops::DerefMut; 204 use thin_vec::ThinVec; 205 206 struct FullyParenthesize; 207 208 fn contains_let_chain(expr: &Expr) -> bool { 209 match &expr.kind { 210 ExprKind::Let(..) => true, 211 ExprKind::Binary(binop, left, right) => { 212 binop.node == BinOpKind::And 213 && (contains_let_chain(left) || contains_let_chain(right)) 214 } 215 _ => false, 216 } 217 } 218 219 fn flat_map_field<T: MutVisitor>(mut f: ExprField, vis: &mut T) -> Vec<ExprField> { 220 if f.is_shorthand { 221 noop_visit_expr(&mut f.expr, vis); 222 } else { 223 vis.visit_expr(&mut f.expr); 224 } 225 vec![f] 226 } 227 228 fn flat_map_stmt<T: MutVisitor>(stmt: Stmt, vis: &mut T) -> Vec<Stmt> { 229 let kind = match stmt.kind { 230 // Don't wrap toplevel expressions in statements. 231 StmtKind::Expr(mut e) => { 232 noop_visit_expr(&mut e, vis); 233 StmtKind::Expr(e) 234 } 235 StmtKind::Semi(mut e) => { 236 noop_visit_expr(&mut e, vis); 237 StmtKind::Semi(e) 238 } 239 s => s, 240 }; 241 242 vec![Stmt { kind, ..stmt }] 243 } 244 245 fn noop_visit_expr<T: MutVisitor>(e: &mut Expr, vis: &mut T) { 246 use rustc_ast::mut_visit::{noop_visit_expr, visit_attrs}; 247 match &mut e.kind { 248 ExprKind::AddrOf(BorrowKind::Raw, ..) => {} 249 ExprKind::Struct(expr) => { 250 let StructExpr { 251 qself, 252 path, 253 fields, 254 rest, 255 } = expr.deref_mut(); 256 vis.visit_qself(qself); 257 vis.visit_path(path); 258 fields.flat_map_in_place(|field| flat_map_field(field, vis)); 259 if let StructRest::Base(rest) = rest { 260 vis.visit_expr(rest); 261 } 262 vis.visit_id(&mut e.id); 263 vis.visit_span(&mut e.span); 264 visit_attrs(&mut e.attrs, vis); 265 } 266 _ => noop_visit_expr(e, vis), 267 } 268 } 269 270 impl MutVisitor for FullyParenthesize { 271 fn visit_expr(&mut self, e: &mut P<Expr>) { 272 noop_visit_expr(e, self); 273 match e.kind { 274 ExprKind::Block(..) | ExprKind::If(..) | ExprKind::Let(..) => {} 275 ExprKind::Binary(..) if contains_let_chain(e) => {} 276 _ => { 277 let inner = mem::replace( 278 e, 279 P(Expr { 280 id: ast::DUMMY_NODE_ID, 281 kind: ExprKind::Err, 282 span: DUMMY_SP, 283 attrs: ThinVec::new(), 284 tokens: None, 285 }), 286 ); 287 e.kind = ExprKind::Paren(inner); 288 } 289 } 290 } 291 292 fn visit_generic_arg(&mut self, arg: &mut GenericArg) { 293 match arg { 294 // Don't wrap unbraced const generic arg as that's invalid syntax. 295 GenericArg::Const(anon_const) => { 296 if let ExprKind::Block(..) = &mut anon_const.value.kind { 297 noop_visit_expr(&mut anon_const.value, self); 298 } 299 } 300 _ => noop_visit_generic_arg(arg, self), 301 } 302 } 303 304 fn visit_param_bound(&mut self, bound: &mut GenericBound) { 305 match bound { 306 GenericBound::Trait( 307 _, 308 TraitBoundModifiers { 309 constness: BoundConstness::Maybe(_), 310 .. 311 }, 312 ) => {} 313 _ => noop_visit_param_bound(bound, self), 314 } 315 } 316 317 fn visit_block(&mut self, block: &mut P<Block>) { 318 self.visit_id(&mut block.id); 319 block 320 .stmts 321 .flat_map_in_place(|stmt| flat_map_stmt(stmt, self)); 322 self.visit_span(&mut block.span); 323 } 324 325 fn visit_local(&mut self, local: &mut P<Local>) { 326 match local.kind { 327 LocalKind::InitElse(..) => {} 328 _ => noop_visit_local(local, self), 329 } 330 } 331 332 fn visit_item_kind(&mut self, item: &mut ItemKind) { 333 match item { 334 ItemKind::Const(const_item) 335 if !const_item.generics.params.is_empty() 336 || !const_item.generics.where_clause.predicates.is_empty() => {} 337 _ => noop_visit_item_kind(item, self), 338 } 339 } 340 341 fn flat_map_trait_item(&mut self, item: P<AssocItem>) -> SmallVec<[P<AssocItem>; 1]> { 342 match &item.kind { 343 AssocItemKind::Const(const_item) 344 if !const_item.generics.params.is_empty() 345 || !const_item.generics.where_clause.predicates.is_empty() => 346 { 347 SmallVec::from([item]) 348 } 349 _ => noop_flat_map_assoc_item(item, self), 350 } 351 } 352 353 fn flat_map_impl_item(&mut self, item: P<AssocItem>) -> SmallVec<[P<AssocItem>; 1]> { 354 match &item.kind { 355 AssocItemKind::Const(const_item) 356 if !const_item.generics.params.is_empty() 357 || !const_item.generics.where_clause.predicates.is_empty() => 358 { 359 SmallVec::from([item]) 360 } 361 _ => noop_flat_map_assoc_item(item, self), 362 } 363 } 364 365 // We don't want to look at expressions that might appear in patterns or 366 // types yet. We'll look into comparing those in the future. For now 367 // focus on expressions appearing in other places. 368 fn visit_pat(&mut self, pat: &mut P<Pat>) { 369 let _ = pat; 370 } 371 372 fn visit_ty(&mut self, ty: &mut P<Ty>) { 373 let _ = ty; 374 } 375 376 fn visit_attribute(&mut self, attr: &mut Attribute) { 377 let _ = attr; 378 } 379 } 380 381 let mut folder = FullyParenthesize; 382 folder.visit_expr(&mut librustc_expr); 383 librustc_expr 384} 385 386fn syn_parenthesize(syn_expr: syn::Expr) -> syn::Expr { 387 use syn::fold::{fold_expr, fold_generic_argument, Fold}; 388 use syn::{token, BinOp, Expr, ExprParen, GenericArgument, MetaNameValue, Pat, Stmt, Type}; 389 390 struct FullyParenthesize; 391 392 fn parenthesize(expr: Expr) -> Expr { 393 Expr::Paren(ExprParen { 394 attrs: Vec::new(), 395 expr: Box::new(expr), 396 paren_token: token::Paren::default(), 397 }) 398 } 399 400 fn needs_paren(expr: &Expr) -> bool { 401 match expr { 402 Expr::Group(_) => unreachable!(), 403 Expr::If(_) | Expr::Unsafe(_) | Expr::Block(_) | Expr::Let(_) => false, 404 Expr::Binary(_) => !contains_let_chain(expr), 405 _ => true, 406 } 407 } 408 409 fn contains_let_chain(expr: &Expr) -> bool { 410 match expr { 411 Expr::Let(_) => true, 412 Expr::Binary(expr) => { 413 matches!(expr.op, BinOp::And(_)) 414 && (contains_let_chain(&expr.left) || contains_let_chain(&expr.right)) 415 } 416 _ => false, 417 } 418 } 419 420 impl Fold for FullyParenthesize { 421 fn fold_expr(&mut self, expr: Expr) -> Expr { 422 let needs_paren = needs_paren(&expr); 423 let folded = fold_expr(self, expr); 424 if needs_paren { 425 parenthesize(folded) 426 } else { 427 folded 428 } 429 } 430 431 fn fold_generic_argument(&mut self, arg: GenericArgument) -> GenericArgument { 432 match arg { 433 GenericArgument::Const(arg) => GenericArgument::Const(match arg { 434 Expr::Block(_) => fold_expr(self, arg), 435 // Don't wrap unbraced const generic arg as that's invalid syntax. 436 _ => arg, 437 }), 438 _ => fold_generic_argument(self, arg), 439 } 440 } 441 442 fn fold_stmt(&mut self, stmt: Stmt) -> Stmt { 443 match stmt { 444 // Don't wrap toplevel expressions in statements. 445 Stmt::Expr(Expr::Verbatim(_), Some(_)) => stmt, 446 Stmt::Expr(e, semi) => Stmt::Expr(fold_expr(self, e), semi), 447 s => s, 448 } 449 } 450 451 fn fold_meta_name_value(&mut self, meta: MetaNameValue) -> MetaNameValue { 452 // Don't turn #[p = "..."] into #[p = ("...")]. 453 meta 454 } 455 456 // We don't want to look at expressions that might appear in patterns or 457 // types yet. We'll look into comparing those in the future. For now 458 // focus on expressions appearing in other places. 459 fn fold_pat(&mut self, pat: Pat) -> Pat { 460 pat 461 } 462 463 fn fold_type(&mut self, ty: Type) -> Type { 464 ty 465 } 466 } 467 468 let mut folder = FullyParenthesize; 469 folder.fold_expr(syn_expr) 470} 471 472fn make_parens_invisible(expr: syn::Expr) -> syn::Expr { 473 use syn::fold::{fold_expr, fold_stmt, Fold}; 474 use syn::{token, Expr, ExprGroup, ExprParen, Stmt}; 475 476 struct MakeParensInvisible; 477 478 impl Fold for MakeParensInvisible { 479 fn fold_expr(&mut self, mut expr: Expr) -> Expr { 480 if let Expr::Paren(paren) = expr { 481 expr = Expr::Group(ExprGroup { 482 attrs: paren.attrs, 483 group_token: token::Group(paren.paren_token.span.join()), 484 expr: paren.expr, 485 }); 486 } 487 fold_expr(self, expr) 488 } 489 490 fn fold_stmt(&mut self, stmt: Stmt) -> Stmt { 491 if let Stmt::Expr(expr @ (Expr::Binary(_) | Expr::Cast(_)), None) = stmt { 492 Stmt::Expr( 493 Expr::Paren(ExprParen { 494 attrs: Vec::new(), 495 paren_token: token::Paren::default(), 496 expr: Box::new(fold_expr(self, expr)), 497 }), 498 None, 499 ) 500 } else { 501 fold_stmt(self, stmt) 502 } 503 } 504 } 505 506 let mut folder = MakeParensInvisible; 507 folder.fold_expr(expr) 508} 509 510/// Walk through a crate collecting all expressions we can find in it. 511fn collect_exprs(file: syn::File) -> Vec<syn::Expr> { 512 use syn::fold::Fold; 513 use syn::punctuated::Punctuated; 514 use syn::{token, ConstParam, Expr, ExprTuple, Pat, Path}; 515 516 struct CollectExprs(Vec<Expr>); 517 impl Fold for CollectExprs { 518 fn fold_expr(&mut self, expr: Expr) -> Expr { 519 match expr { 520 Expr::Verbatim(_) => {} 521 _ => self.0.push(expr), 522 } 523 524 Expr::Tuple(ExprTuple { 525 attrs: vec![], 526 elems: Punctuated::new(), 527 paren_token: token::Paren::default(), 528 }) 529 } 530 531 fn fold_pat(&mut self, pat: Pat) -> Pat { 532 pat 533 } 534 535 fn fold_path(&mut self, path: Path) -> Path { 536 // Skip traversing into const generic path arguments 537 path 538 } 539 540 fn fold_const_param(&mut self, const_param: ConstParam) -> ConstParam { 541 const_param 542 } 543 } 544 545 let mut folder = CollectExprs(vec![]); 546 folder.fold_file(file); 547 folder.0 548} 549