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