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::manual_assert,
8 clippy::match_wildcard_for_single_variants,
9 clippy::too_many_lines
10)]
11
12//! The tests in this module do the following:
13//!
14//! 1. Parse a given expression in both `syn` and `librustc`.
15//! 2. Fold over the expression adding brackets around each subexpression (with
16//! some complications - see the `syn_brackets` and `librustc_brackets`
17//! methods).
18//! 3. Serialize the `syn` expression back into a string, and re-parse it with
19//! `librustc`.
20//! 4. Respan all of the expressions, replacing the spans with the default
21//! spans.
22//! 5. Compare the expressions with one another, if they are not equal fail.
23
24extern crate rustc_ast;
25extern crate rustc_data_structures;
26extern crate rustc_span;
27extern crate thin_vec;
28
29use crate::common::eq::SpanlessEq;
30use crate::common::parse;
31use quote::quote;
32use rayon::iter::{IntoParallelIterator, ParallelIterator};
33use regex::Regex;
34use rustc_ast::ast;
35use rustc_ast::ptr::P;
36use rustc_span::edition::Edition;
37use std::fs;
38use std::process;
39use std::sync::atomic::{AtomicUsize, Ordering};
40use walkdir::{DirEntry, WalkDir};
41
42#[macro_use]
43mod macros;
44
45#[allow(dead_code)]
46mod common;
47
48mod repo;
49
50/// Test some pre-set expressions chosen by us.
51#[test]
52fn test_simple_precedence() {
53 const EXPRS: &[&str] = &[
54 "1 + 2 * 3 + 4",
55 "1 + 2 * ( 3 + 4 )",
56 "{ for i in r { } *some_ptr += 1; }",
57 "{ loop { break 5; } }",
58 "{ if true { () }.mthd() }",
59 "{ for i in unsafe { 20 } { } }",
60 ];
61
62 let mut failed = 0;
63
64 for input in EXPRS {
65 let expr = if let Some(expr) = parse::syn_expr(input) {
66 expr
67 } else {
68 failed += 1;
69 continue;
70 };
71
72 let pf = match test_expressions(Edition::Edition2018, vec![expr]) {
73 (1, 0) => "passed",
74 (0, 1) => {
75 failed += 1;
76 "failed"
77 }
78 _ => unreachable!(),
79 };
80 errorf!("=== {}: {}\n", input, pf);
81 }
82
83 if failed > 0 {
84 panic!("Failed {} tests", failed);
85 }
86}
87
88/// Test expressions from rustc, like in `test_round_trip`.
89#[test]
90fn test_rustc_precedence() {
91 common::rayon_init();
92 repo::clone_rust();
93 let abort_after = common::abort_after();
94 if abort_after == 0 {
95 panic!("Skipping all precedence tests");
96 }
97
98 let passed = AtomicUsize::new(0);
99 let failed = AtomicUsize::new(0);
100
101 // 2018 edition is hard
102 let edition_regex = Regex::new(r"\b(async|try)[!(]").unwrap();
103
104 WalkDir::new("tests/rust")
105 .sort_by(|a, b| a.file_name().cmp(b.file_name()))
106 .into_iter()
107 .filter_entry(repo::base_dir_filter)
108 .collect::<Result<Vec<DirEntry>, walkdir::Error>>()
109 .unwrap()
110 .into_par_iter()
111 .for_each(|entry| {
112 let path = entry.path();
113 if path.is_dir() {
114 return;
115 }
116
117 let content = fs::read_to_string(path).unwrap();
118 let content = edition_regex.replace_all(&content, "_$0");
119
120 let (l_passed, l_failed) = match syn::parse_file(&content) {
121 Ok(file) => {
122 let edition = repo::edition(path).parse().unwrap();
123 let exprs = collect_exprs(file);
124 test_expressions(edition, exprs)
125 }
126 Err(msg) => {
127 errorf!("syn failed to parse\n{:?}\n", msg);
128 (0, 1)
129 }
130 };
131
132 errorf!(
133 "=== {}: {} passed | {} failed\n",
134 path.display(),
135 l_passed,
136 l_failed
137 );
138
139 passed.fetch_add(l_passed, Ordering::Relaxed);
140 let prev_failed = failed.fetch_add(l_failed, Ordering::Relaxed);
141
142 if prev_failed + l_failed >= abort_after {
143 process::exit(1);
144 }
145 });
146
147 let passed = passed.load(Ordering::Relaxed);
148 let failed = failed.load(Ordering::Relaxed);
149
150 errorf!("\n===== Precedence Test Results =====\n");
151 errorf!("{} passed | {} failed\n", passed, failed);
152
153 if failed > 0 {
154 panic!("{} failures", failed);
155 }
156}
157
158fn test_expressions(edition: Edition, exprs: Vec<syn::Expr>) -> (usize, usize) {
159 let mut passed = 0;
160 let mut failed = 0;
161
162 rustc_span::create_session_if_not_set_then(edition, |_| {
163 for expr in exprs {
164 let raw = quote!(#expr).to_string();
165
166 let librustc_ast = if let Some(e) = librustc_parse_and_rewrite(&raw) {
167 e
168 } else {
169 failed += 1;
170 errorf!("\nFAIL - librustc failed to parse raw\n");
171 continue;
172 };
173
174 let syn_expr = syn_brackets(expr);
175 let syn_ast = if let Some(e) = parse::librustc_expr(&quote!(#syn_expr).to_string()) {
176 e
177 } else {
178 failed += 1;
179 errorf!("\nFAIL - librustc failed to parse bracketed\n");
180 continue;
181 };
182
183 if SpanlessEq::eq(&syn_ast, &librustc_ast) {
184 passed += 1;
185 } else {
186 failed += 1;
187 errorf!("\nFAIL\n{:?}\n!=\n{:?}\n", syn_ast, librustc_ast);
188 }
189 }
190 });
191
192 (passed, failed)
193}
194
195fn librustc_parse_and_rewrite(input: &str) -> Option<P<ast::Expr>> {
196 parse::librustc_expr(input).and_then(librustc_brackets)
197}
198
199/// Wrap every expression which is not already wrapped in parens with parens, to
200/// reveal the precedence of the parsed expressions, and produce a stringified
201/// form of the resulting expression.
202///
203/// This method operates on librustc objects.
204fn librustc_brackets(mut librustc_expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
205 use rustc_ast::ast::{
206 Attribute, Block, BorrowKind, Expr, ExprField, ExprKind, GenericArg, Local, LocalKind, Pat,
207 Stmt, StmtKind, StructExpr, StructRest, Ty,
208 };
209 use rustc_ast::mut_visit::{noop_visit_generic_arg, noop_visit_local, MutVisitor};
210 use rustc_data_structures::map_in_place::MapInPlace;
211 use rustc_span::DUMMY_SP;
212 use std::mem;
213 use std::ops::DerefMut;
214 use thin_vec::ThinVec;
215
216 struct BracketsVisitor {
217 failed: bool,
218 }
219
220 fn flat_map_field<T: MutVisitor>(mut f: ExprField, vis: &mut T) -> Vec<ExprField> {
221 if f.is_shorthand {
222 noop_visit_expr(&mut f.expr, vis);
223 } else {
224 vis.visit_expr(&mut f.expr);
225 }
226 vec![f]
227 }
228
229 fn flat_map_stmt<T: MutVisitor>(stmt: Stmt, vis: &mut T) -> Vec<Stmt> {
230 let kind = match stmt.kind {
231 // Don't wrap toplevel expressions in statements.
232 StmtKind::Expr(mut e) => {
233 noop_visit_expr(&mut e, vis);
234 StmtKind::Expr(e)
235 }
236 StmtKind::Semi(mut e) => {
237 noop_visit_expr(&mut e, vis);
238 StmtKind::Semi(e)
239 }
240 s => s,
241 };
242
243 vec![Stmt { kind, ..stmt }]
244 }
245
246 fn noop_visit_expr<T: MutVisitor>(e: &mut Expr, vis: &mut T) {
247 use rustc_ast::mut_visit::{noop_visit_expr, visit_attrs};
248 match &mut e.kind {
249 ExprKind::AddrOf(BorrowKind::Raw, ..) => {}
250 ExprKind::Struct(expr) => {
251 let StructExpr {
252 qself,
253 path,
254 fields,
255 rest,
256 } = expr.deref_mut();
257 vis.visit_qself(qself);
258 vis.visit_path(path);
259 fields.flat_map_in_place(|field| flat_map_field(field, vis));
260 if let StructRest::Base(rest) = rest {
261 vis.visit_expr(rest);
262 }
263 vis.visit_id(&mut e.id);
264 vis.visit_span(&mut e.span);
265 visit_attrs(&mut e.attrs, vis);
266 }
267 _ => noop_visit_expr(e, vis),
268 }
269 }
270
271 impl MutVisitor for BracketsVisitor {
272 fn visit_expr(&mut self, e: &mut P<Expr>) {
273 match e.kind {
274 ExprKind::ConstBlock(..) => {}
275 _ => noop_visit_expr(e, self),
276 }
277 match e.kind {
278 ExprKind::If(..) | ExprKind::Block(..) | ExprKind::Let(..) => {}
279 _ => {
280 let inner = mem::replace(
281 e,
282 P(Expr {
283 id: ast::DUMMY_NODE_ID,
284 kind: ExprKind::Err,
285 span: DUMMY_SP,
286 attrs: ThinVec::new(),
287 tokens: None,
288 }),
289 );
290 e.kind = ExprKind::Paren(inner);
291 }
292 }
293 }
294
295 fn visit_generic_arg(&mut self, arg: &mut GenericArg) {
296 match arg {
297 // Don't wrap unbraced const generic arg as that's invalid syntax.
298 GenericArg::Const(anon_const) => {
299 if let ExprKind::Block(..) = &mut anon_const.value.kind {
300 noop_visit_expr(&mut anon_const.value, self);
301 }
302 }
303 _ => noop_visit_generic_arg(arg, self),
304 }
305 }
306
307 fn visit_block(&mut self, block: &mut P<Block>) {
308 self.visit_id(&mut block.id);
309 block
310 .stmts
311 .flat_map_in_place(|stmt| flat_map_stmt(stmt, self));
312 self.visit_span(&mut block.span);
313 }
314
315 fn visit_local(&mut self, local: &mut P<Local>) {
316 match local.kind {
317 LocalKind::InitElse(..) => {}
318 _ => noop_visit_local(local, self),
319 }
320 }
321
322 // We don't want to look at expressions that might appear in patterns or
323 // types yet. We'll look into comparing those in the future. For now
324 // focus on expressions appearing in other places.
325 fn visit_pat(&mut self, pat: &mut P<Pat>) {
326 _ = pat;
327 }
328
329 fn visit_ty(&mut self, ty: &mut P<Ty>) {
330 _ = ty;
331 }
332
333 fn visit_attribute(&mut self, attr: &mut Attribute) {
334 _ = attr;
335 }
336 }
337
338 let mut folder = BracketsVisitor { failed: false };
339 folder.visit_expr(&mut librustc_expr);
340 if folder.failed {
341 None
342 } else {
343 Some(librustc_expr)
344 }
345}
346
347/// Wrap every expression which is not already wrapped in parens with parens, to
348/// reveal the precedence of the parsed expressions, and produce a stringified
349/// form of the resulting expression.
350fn syn_brackets(syn_expr: syn::Expr) -> syn::Expr {
351 use syn::fold::{fold_expr, fold_generic_argument, fold_generic_method_argument, Fold};
352 use syn::{token, Expr, ExprParen, GenericArgument, GenericMethodArgument, Pat, Stmt, Type};
353
354 struct ParenthesizeEveryExpr;
355 impl Fold for ParenthesizeEveryExpr {
356 fn fold_expr(&mut self, expr: Expr) -> Expr {
357 match expr {
358 Expr::Group(_) => unreachable!(),
359 Expr::If(..) | Expr::Unsafe(..) | Expr::Block(..) | Expr::Let(..) => {
360 fold_expr(self, expr)
361 }
362 _ => Expr::Paren(ExprParen {
363 attrs: Vec::new(),
364 expr: Box::new(fold_expr(self, expr)),
365 paren_token: token::Paren::default(),
366 }),
367 }
368 }
369
370 fn fold_generic_argument(&mut self, arg: GenericArgument) -> GenericArgument {
371 match arg {
372 GenericArgument::Const(arg) => GenericArgument::Const(match arg {
373 Expr::Block(_) => fold_expr(self, arg),
374 // Don't wrap unbraced const generic arg as that's invalid syntax.
375 _ => arg,
376 }),
377 _ => fold_generic_argument(self, arg),
378 }
379 }
380
381 fn fold_generic_method_argument(
382 &mut self,
383 arg: GenericMethodArgument,
384 ) -> GenericMethodArgument {
385 match arg {
386 GenericMethodArgument::Const(arg) => GenericMethodArgument::Const(match arg {
387 Expr::Block(_) => fold_expr(self, arg),
388 // Don't wrap unbraced const generic arg as that's invalid syntax.
389 _ => arg,
390 }),
391 _ => fold_generic_method_argument(self, arg),
392 }
393 }
394
395 fn fold_stmt(&mut self, stmt: Stmt) -> Stmt {
396 match stmt {
397 // Don't wrap toplevel expressions in statements.
398 Stmt::Expr(e) => Stmt::Expr(fold_expr(self, e)),
399 Stmt::Semi(e, semi) => {
400 if let Expr::Verbatim(_) = e {
401 Stmt::Semi(e, semi)
402 } else {
403 Stmt::Semi(fold_expr(self, e), semi)
404 }
405 }
406 s => s,
407 }
408 }
409
410 // We don't want to look at expressions that might appear in patterns or
411 // types yet. We'll look into comparing those in the future. For now
412 // focus on expressions appearing in other places.
413 fn fold_pat(&mut self, pat: Pat) -> Pat {
414 pat
415 }
416
417 fn fold_type(&mut self, ty: Type) -> Type {
418 ty
419 }
420 }
421
422 let mut folder = ParenthesizeEveryExpr;
423 folder.fold_expr(syn_expr)
424}
425
426/// Walk through a crate collecting all expressions we can find in it.
427fn collect_exprs(file: syn::File) -> Vec<syn::Expr> {
428 use syn::fold::Fold;
429 use syn::punctuated::Punctuated;
430 use syn::{token, ConstParam, Expr, ExprTuple, Path};
431
432 struct CollectExprs(Vec<Expr>);
433 impl Fold for CollectExprs {
434 fn fold_expr(&mut self, expr: Expr) -> Expr {
435 match expr {
436 Expr::Verbatim(_) => {}
437 _ => self.0.push(expr),
438 }
439
440 Expr::Tuple(ExprTuple {
441 attrs: vec![],
442 elems: Punctuated::new(),
443 paren_token: token::Paren::default(),
444 })
445 }
446
447 fn fold_path(&mut self, path: Path) -> Path {
448 // Skip traversing into const generic path arguments
449 path
450 }
451
452 fn fold_const_param(&mut self, const_param: ConstParam) -> ConstParam {
453 const_param
454 }
455 }
456
457 let mut folder = CollectExprs(vec![]);
458 folder.fold_file(file);
459 folder.0
460}
461