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("e!(#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