• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #![cfg(not(syn_disable_nightly_tests))]
2 #![cfg(not(miri))]
3 #![recursion_limit = "1024"]
4 #![feature(rustc_private)]
5 #![allow(
6     clippy::explicit_deref_methods,
7     clippy::let_underscore_untyped,
8     clippy::manual_assert,
9     clippy::manual_let_else,
10     clippy::match_like_matches_macro,
11     clippy::match_wildcard_for_single_variants,
12     clippy::too_many_lines,
13     clippy::uninlined_format_args
14 )]
15 
16 //! The tests in this module do the following:
17 //!
18 //! 1. Parse a given expression in both `syn` and `librustc`.
19 //! 2. Fold over the expression adding brackets around each subexpression (with
20 //!    some complications - see the `syn_brackets` and `librustc_brackets`
21 //!    methods).
22 //! 3. Serialize the `syn` expression back into a string, and re-parse it with
23 //!    `librustc`.
24 //! 4. Respan all of the expressions, replacing the spans with the default
25 //!    spans.
26 //! 5. Compare the expressions with one another, if they are not equal fail.
27 
28 extern crate rustc_ast;
29 extern crate rustc_ast_pretty;
30 extern crate rustc_data_structures;
31 extern crate rustc_driver;
32 extern crate rustc_span;
33 extern crate smallvec;
34 extern crate thin_vec;
35 
36 use crate::common::eq::SpanlessEq;
37 use crate::common::parse;
38 use quote::quote;
39 use regex::Regex;
40 use rustc_ast::ast;
41 use rustc_ast::ptr::P;
42 use rustc_ast_pretty::pprust;
43 use rustc_span::edition::Edition;
44 use std::fs;
45 use std::path::Path;
46 use std::process;
47 use std::sync::atomic::{AtomicUsize, Ordering};
48 
49 #[macro_use]
50 mod macros;
51 
52 #[allow(dead_code)]
53 mod common;
54 
55 mod repo;
56 
57 #[test]
test_rustc_precedence()58 fn test_rustc_precedence() {
59     common::rayon_init();
60     repo::clone_rust();
61     let abort_after = common::abort_after();
62     if abort_after == 0 {
63         panic!("Skipping all precedence tests");
64     }
65 
66     let passed = AtomicUsize::new(0);
67     let failed = AtomicUsize::new(0);
68 
69     // 2018 edition is hard
70     let edition_regex = Regex::new(r"\b(async|try)[!(]").unwrap();
71 
72     repo::for_each_rust_file(|path| {
73         let content = fs::read_to_string(path).unwrap();
74         let content = edition_regex.replace_all(&content, "_$0");
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_expressions(path: &Path, edition: Edition, exprs: Vec<syn::Expr>) -> (usize, usize)114 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 raw = quote!(#expr).to_string();
121 
122             let librustc_ast = if let Some(e) = librustc_parse_and_rewrite(&raw) {
123                 e
124             } else {
125                 failed += 1;
126                 errorf!("\nFAIL {} - librustc failed to parse raw\n", path.display());
127                 continue;
128             };
129 
130             let syn_expr = syn_brackets(expr);
131             let syn_ast = if let Some(e) = parse::librustc_expr(&quote!(#syn_expr).to_string()) {
132                 e
133             } else {
134                 failed += 1;
135                 errorf!(
136                     "\nFAIL {} - librustc failed to parse bracketed\n",
137                     path.display(),
138                 );
139                 continue;
140             };
141 
142             if SpanlessEq::eq(&syn_ast, &librustc_ast) {
143                 passed += 1;
144             } else {
145                 failed += 1;
146                 let syn_program = pprust::expr_to_string(&syn_ast);
147                 let librustc_program = pprust::expr_to_string(&librustc_ast);
148                 errorf!(
149                     "\nFAIL {}\n{}\nsyn != rustc\n{}\n",
150                     path.display(),
151                     syn_program,
152                     librustc_program,
153                 );
154             }
155         }
156     });
157 
158     (passed, failed)
159 }
160 
librustc_parse_and_rewrite(input: &str) -> Option<P<ast::Expr>>161 fn librustc_parse_and_rewrite(input: &str) -> Option<P<ast::Expr>> {
162     parse::librustc_expr(input).and_then(librustc_brackets)
163 }
164 
165 /// Wrap every expression which is not already wrapped in parens with parens, to
166 /// reveal the precedence of the parsed expressions, and produce a stringified
167 /// form of the resulting expression.
168 ///
169 /// This method operates on librustc objects.
librustc_brackets(mut librustc_expr: P<ast::Expr>) -> Option<P<ast::Expr>>170 fn librustc_brackets(mut librustc_expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
171     use rustc_ast::ast::{
172         AssocItem, AssocItemKind, Attribute, BinOpKind, Block, BorrowKind, Expr, ExprField,
173         ExprKind, GenericArg, GenericBound, ItemKind, Local, LocalKind, Pat, Stmt, StmtKind,
174         StructExpr, StructRest, TraitBoundModifier, Ty,
175     };
176     use rustc_ast::mut_visit::{
177         noop_flat_map_assoc_item, noop_visit_generic_arg, noop_visit_item_kind, noop_visit_local,
178         noop_visit_param_bound, MutVisitor,
179     };
180     use rustc_data_structures::flat_map_in_place::FlatMapInPlace;
181     use rustc_span::DUMMY_SP;
182     use smallvec::SmallVec;
183     use std::mem;
184     use std::ops::DerefMut;
185     use thin_vec::ThinVec;
186 
187     struct BracketsVisitor {
188         failed: bool,
189     }
190 
191     fn contains_let_chain(expr: &Expr) -> bool {
192         match &expr.kind {
193             ExprKind::Let(..) => true,
194             ExprKind::Binary(binop, left, right) => {
195                 binop.node == BinOpKind::And
196                     && (contains_let_chain(left) || contains_let_chain(right))
197             }
198             _ => false,
199         }
200     }
201 
202     fn flat_map_field<T: MutVisitor>(mut f: ExprField, vis: &mut T) -> Vec<ExprField> {
203         if f.is_shorthand {
204             noop_visit_expr(&mut f.expr, vis);
205         } else {
206             vis.visit_expr(&mut f.expr);
207         }
208         vec![f]
209     }
210 
211     fn flat_map_stmt<T: MutVisitor>(stmt: Stmt, vis: &mut T) -> Vec<Stmt> {
212         let kind = match stmt.kind {
213             // Don't wrap toplevel expressions in statements.
214             StmtKind::Expr(mut e) => {
215                 noop_visit_expr(&mut e, vis);
216                 StmtKind::Expr(e)
217             }
218             StmtKind::Semi(mut e) => {
219                 noop_visit_expr(&mut e, vis);
220                 StmtKind::Semi(e)
221             }
222             s => s,
223         };
224 
225         vec![Stmt { kind, ..stmt }]
226     }
227 
228     fn noop_visit_expr<T: MutVisitor>(e: &mut Expr, vis: &mut T) {
229         use rustc_ast::mut_visit::{noop_visit_expr, visit_attrs};
230         match &mut e.kind {
231             ExprKind::AddrOf(BorrowKind::Raw, ..) => {}
232             ExprKind::Struct(expr) => {
233                 let StructExpr {
234                     qself,
235                     path,
236                     fields,
237                     rest,
238                 } = expr.deref_mut();
239                 vis.visit_qself(qself);
240                 vis.visit_path(path);
241                 fields.flat_map_in_place(|field| flat_map_field(field, vis));
242                 if let StructRest::Base(rest) = rest {
243                     vis.visit_expr(rest);
244                 }
245                 vis.visit_id(&mut e.id);
246                 vis.visit_span(&mut e.span);
247                 visit_attrs(&mut e.attrs, vis);
248             }
249             _ => noop_visit_expr(e, vis),
250         }
251     }
252 
253     impl MutVisitor for BracketsVisitor {
254         fn visit_expr(&mut self, e: &mut P<Expr>) {
255             noop_visit_expr(e, self);
256             match e.kind {
257                 ExprKind::Block(..) | ExprKind::If(..) | ExprKind::Let(..) => {}
258                 ExprKind::Binary(..) if contains_let_chain(e) => {}
259                 _ => {
260                     let inner = mem::replace(
261                         e,
262                         P(Expr {
263                             id: ast::DUMMY_NODE_ID,
264                             kind: ExprKind::Err,
265                             span: DUMMY_SP,
266                             attrs: ThinVec::new(),
267                             tokens: None,
268                         }),
269                     );
270                     e.kind = ExprKind::Paren(inner);
271                 }
272             }
273         }
274 
275         fn visit_generic_arg(&mut self, arg: &mut GenericArg) {
276             match arg {
277                 // Don't wrap unbraced const generic arg as that's invalid syntax.
278                 GenericArg::Const(anon_const) => {
279                     if let ExprKind::Block(..) = &mut anon_const.value.kind {
280                         noop_visit_expr(&mut anon_const.value, self);
281                     }
282                 }
283                 _ => noop_visit_generic_arg(arg, self),
284             }
285         }
286 
287         fn visit_param_bound(&mut self, bound: &mut GenericBound) {
288             match bound {
289                 GenericBound::Trait(
290                     _,
291                     TraitBoundModifier::MaybeConst | TraitBoundModifier::MaybeConstMaybe,
292                 ) => {}
293                 _ => noop_visit_param_bound(bound, self),
294             }
295         }
296 
297         fn visit_block(&mut self, block: &mut P<Block>) {
298             self.visit_id(&mut block.id);
299             block
300                 .stmts
301                 .flat_map_in_place(|stmt| flat_map_stmt(stmt, self));
302             self.visit_span(&mut block.span);
303         }
304 
305         fn visit_local(&mut self, local: &mut P<Local>) {
306             match local.kind {
307                 LocalKind::InitElse(..) => {}
308                 _ => noop_visit_local(local, self),
309             }
310         }
311 
312         fn visit_item_kind(&mut self, item: &mut ItemKind) {
313             match item {
314                 ItemKind::Const(const_item)
315                     if !const_item.generics.params.is_empty()
316                         || !const_item.generics.where_clause.predicates.is_empty() => {}
317                 _ => noop_visit_item_kind(item, self),
318             }
319         }
320 
321         fn flat_map_trait_item(&mut self, item: P<AssocItem>) -> SmallVec<[P<AssocItem>; 1]> {
322             match &item.kind {
323                 AssocItemKind::Const(const_item)
324                     if !const_item.generics.params.is_empty()
325                         || !const_item.generics.where_clause.predicates.is_empty() =>
326                 {
327                     SmallVec::from([item])
328                 }
329                 _ => noop_flat_map_assoc_item(item, self),
330             }
331         }
332 
333         fn flat_map_impl_item(&mut self, item: P<AssocItem>) -> SmallVec<[P<AssocItem>; 1]> {
334             match &item.kind {
335                 AssocItemKind::Const(const_item)
336                     if !const_item.generics.params.is_empty()
337                         || !const_item.generics.where_clause.predicates.is_empty() =>
338                 {
339                     SmallVec::from([item])
340                 }
341                 _ => noop_flat_map_assoc_item(item, self),
342             }
343         }
344 
345         // We don't want to look at expressions that might appear in patterns or
346         // types yet. We'll look into comparing those in the future. For now
347         // focus on expressions appearing in other places.
348         fn visit_pat(&mut self, pat: &mut P<Pat>) {
349             let _ = pat;
350         }
351 
352         fn visit_ty(&mut self, ty: &mut P<Ty>) {
353             let _ = ty;
354         }
355 
356         fn visit_attribute(&mut self, attr: &mut Attribute) {
357             let _ = attr;
358         }
359     }
360 
361     let mut folder = BracketsVisitor { failed: false };
362     folder.visit_expr(&mut librustc_expr);
363     if folder.failed {
364         None
365     } else {
366         Some(librustc_expr)
367     }
368 }
369 
370 /// Wrap every expression which is not already wrapped in parens with parens, to
371 /// reveal the precedence of the parsed expressions, and produce a stringified
372 /// form of the resulting expression.
syn_brackets(syn_expr: syn::Expr) -> syn::Expr373 fn syn_brackets(syn_expr: syn::Expr) -> syn::Expr {
374     use syn::fold::{fold_expr, fold_generic_argument, Fold};
375     use syn::{token, BinOp, Expr, ExprParen, GenericArgument, MetaNameValue, Pat, Stmt, Type};
376 
377     struct ParenthesizeEveryExpr;
378 
379     fn parenthesize(expr: Expr) -> Expr {
380         Expr::Paren(ExprParen {
381             attrs: Vec::new(),
382             expr: Box::new(expr),
383             paren_token: token::Paren::default(),
384         })
385     }
386 
387     fn needs_paren(expr: &Expr) -> bool {
388         match expr {
389             Expr::Group(_) => unreachable!(),
390             Expr::If(_) | Expr::Unsafe(_) | Expr::Block(_) | Expr::Let(_) => false,
391             Expr::Binary(_) => !contains_let_chain(expr),
392             _ => true,
393         }
394     }
395 
396     fn contains_let_chain(expr: &Expr) -> bool {
397         match expr {
398             Expr::Let(_) => true,
399             Expr::Binary(expr) => {
400                 matches!(expr.op, BinOp::And(_))
401                     && (contains_let_chain(&expr.left) || contains_let_chain(&expr.right))
402             }
403             _ => false,
404         }
405     }
406 
407     impl Fold for ParenthesizeEveryExpr {
408         fn fold_expr(&mut self, expr: Expr) -> Expr {
409             let needs_paren = needs_paren(&expr);
410             let folded = fold_expr(self, expr);
411             if needs_paren {
412                 parenthesize(folded)
413             } else {
414                 folded
415             }
416         }
417 
418         fn fold_generic_argument(&mut self, arg: GenericArgument) -> GenericArgument {
419             match arg {
420                 GenericArgument::Const(arg) => GenericArgument::Const(match arg {
421                     Expr::Block(_) => fold_expr(self, arg),
422                     // Don't wrap unbraced const generic arg as that's invalid syntax.
423                     _ => arg,
424                 }),
425                 _ => fold_generic_argument(self, arg),
426             }
427         }
428 
429         fn fold_stmt(&mut self, stmt: Stmt) -> Stmt {
430             match stmt {
431                 // Don't wrap toplevel expressions in statements.
432                 Stmt::Expr(Expr::Verbatim(_), Some(_)) => stmt,
433                 Stmt::Expr(e, semi) => Stmt::Expr(fold_expr(self, e), semi),
434                 s => s,
435             }
436         }
437 
438         fn fold_meta_name_value(&mut self, meta: MetaNameValue) -> MetaNameValue {
439             // Don't turn #[p = "..."] into #[p = ("...")].
440             meta
441         }
442 
443         // We don't want to look at expressions that might appear in patterns or
444         // types yet. We'll look into comparing those in the future. For now
445         // focus on expressions appearing in other places.
446         fn fold_pat(&mut self, pat: Pat) -> Pat {
447             pat
448         }
449 
450         fn fold_type(&mut self, ty: Type) -> Type {
451             ty
452         }
453     }
454 
455     let mut folder = ParenthesizeEveryExpr;
456     folder.fold_expr(syn_expr)
457 }
458 
459 /// Walk through a crate collecting all expressions we can find in it.
collect_exprs(file: syn::File) -> Vec<syn::Expr>460 fn collect_exprs(file: syn::File) -> Vec<syn::Expr> {
461     use syn::fold::Fold;
462     use syn::punctuated::Punctuated;
463     use syn::{token, ConstParam, Expr, ExprTuple, Pat, Path};
464 
465     struct CollectExprs(Vec<Expr>);
466     impl Fold for CollectExprs {
467         fn fold_expr(&mut self, expr: Expr) -> Expr {
468             match expr {
469                 Expr::Verbatim(_) => {}
470                 _ => self.0.push(expr),
471             }
472 
473             Expr::Tuple(ExprTuple {
474                 attrs: vec![],
475                 elems: Punctuated::new(),
476                 paren_token: token::Paren::default(),
477             })
478         }
479 
480         fn fold_pat(&mut self, pat: Pat) -> Pat {
481             pat
482         }
483 
484         fn fold_path(&mut self, path: Path) -> Path {
485             // Skip traversing into const generic path arguments
486             path
487         }
488 
489         fn fold_const_param(&mut self, const_param: ConstParam) -> ConstParam {
490             const_param
491         }
492     }
493 
494     let mut folder = CollectExprs(vec![]);
495     folder.fold_file(file);
496     folder.0
497 }
498