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 
33 extern crate rustc_ast;
34 extern crate rustc_ast_pretty;
35 extern crate rustc_data_structures;
36 extern crate rustc_driver;
37 extern crate rustc_span;
38 extern crate smallvec;
39 extern crate thin_vec;
40 
41 use crate::common::eq::SpanlessEq;
42 use crate::common::parse;
43 use quote::ToTokens;
44 use rustc_ast::ast;
45 use rustc_ast::ptr::P;
46 use rustc_ast_pretty::pprust;
47 use rustc_span::edition::Edition;
48 use std::fs;
49 use std::path::Path;
50 use std::process;
51 use std::sync::atomic::{AtomicUsize, Ordering};
52 
53 #[macro_use]
54 mod macros;
55 
56 #[allow(dead_code)]
57 mod common;
58 
59 mod repo;
60 
61 #[test]
test_rustc_precedencenull62 fn 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 
test_expressionsnull114 fn 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 
librustc_parse_and_rewritenull185 fn librustc_parse_and_rewrite(input: &str) -> Option<P<ast::Expr>> {
186     parse::librustc_expr(input).map(librustc_parenthesize)
187 }
188 
librustc_parenthesizenull189 fn 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 
syn_parenthesizenull386 fn 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 
make_parens_invisiblenull472 fn 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.
collect_exprsnull511 fn 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