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