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