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