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