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