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 | |
24 | extern crate rustc_ast; |
25 | extern crate rustc_data_structures; |
26 | extern crate rustc_span; |
27 | extern crate thin_vec; |
28 | |
29 | use crate::common::eq::SpanlessEq; |
30 | use crate::common::parse; |
31 | use quote::quote; |
32 | use rayon::iter::{IntoParallelIterator, ParallelIterator}; |
33 | use regex::Regex; |
34 | use rustc_ast::ast; |
35 | use rustc_ast::ptr::P; |
36 | use rustc_span::edition::Edition; |
37 | use std::fs; |
38 | use std::process; |
39 | use std::sync::atomic::{AtomicUsize, Ordering}; |
40 | use walkdir::{DirEntry, WalkDir}; |
41 | |
42 | #[macro_use ] |
43 | mod macros; |
44 | |
45 | #[allow (dead_code)] |
46 | mod common; |
47 | |
48 | mod repo; |
49 | |
50 | /// Test some pre-set expressions chosen by us. |
51 | #[test] |
52 | fn 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] |
90 | fn 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 | |
158 | fn 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("e!(#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 | |
195 | fn 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. |
204 | fn 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. |
350 | fn 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. |
427 | fn 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 | |