aboutsummaryrefslogtreecommitdiff
path: root/syn/tests/test_precedence.rs
diff options
context:
space:
mode:
Diffstat (limited to 'syn/tests/test_precedence.rs')
-rw-r--r--syn/tests/test_precedence.rs394
1 files changed, 394 insertions, 0 deletions
diff --git a/syn/tests/test_precedence.rs b/syn/tests/test_precedence.rs
new file mode 100644
index 0000000..3529c06
--- /dev/null
+++ b/syn/tests/test_precedence.rs
@@ -0,0 +1,394 @@
+#![cfg(not(syn_disable_nightly_tests))]
+#![recursion_limit = "1024"]
+#![feature(rustc_private)]
+
+//! The tests in this module do the following:
+//!
+//! 1. Parse a given expression in both `syn` and `libsyntax`.
+//! 2. Fold over the expression adding brackets around each subexpression (with
+//! some complications - see the `syn_brackets` and `libsyntax_brackets`
+//! methods).
+//! 3. Serialize the `syn` expression back into a string, and re-parse it with
+//! `libsyntax`.
+//! 4. Respan all of the expressions, replacing the spans with the default
+//! spans.
+//! 5. Compare the expressions with one another, if they are not equal fail.
+
+extern crate rustc_data_structures;
+extern crate rustc_span;
+extern crate syntax;
+
+mod features;
+
+use quote::quote;
+use rayon::iter::{IntoParallelIterator, ParallelIterator};
+use regex::Regex;
+use rustc_span::edition::Edition;
+use syntax::ast;
+use syntax::ptr::P;
+use walkdir::{DirEntry, WalkDir};
+
+use std::fs::File;
+use std::io::Read;
+use std::process;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+use common::eq::SpanlessEq;
+use common::parse;
+
+#[macro_use]
+mod macros;
+
+#[allow(dead_code)]
+mod common;
+
+mod repo;
+
+/// Test some pre-set expressions chosen by us.
+#[test]
+fn test_simple_precedence() {
+ const EXPRS: &[&str] = &[
+ "1 + 2 * 3 + 4",
+ "1 + 2 * ( 3 + 4 )",
+ "{ for i in r { } *some_ptr += 1; }",
+ "{ loop { break 5; } }",
+ "{ if true { () }.mthd() }",
+ "{ for i in unsafe { 20 } { } }",
+ ];
+
+ let mut failed = 0;
+
+ for input in EXPRS {
+ let expr = if let Some(expr) = parse::syn_expr(input) {
+ expr
+ } else {
+ failed += 1;
+ continue;
+ };
+
+ let pf = match test_expressions(vec![expr]) {
+ (1, 0) => "passed",
+ (0, 1) => {
+ failed += 1;
+ "failed"
+ }
+ _ => unreachable!(),
+ };
+ errorf!("=== {}: {}\n", input, pf);
+ }
+
+ if failed > 0 {
+ panic!("Failed {} tests", failed);
+ }
+}
+
+/// Test expressions from rustc, like in `test_round_trip`.
+#[test]
+fn test_rustc_precedence() {
+ repo::clone_rust();
+ let abort_after = common::abort_after();
+ if abort_after == 0 {
+ panic!("Skipping all precedence tests");
+ }
+
+ let passed = AtomicUsize::new(0);
+ let failed = AtomicUsize::new(0);
+
+ // 2018 edition is hard
+ let edition_regex = Regex::new(r"\b(async|try)[!(]").unwrap();
+
+ WalkDir::new("tests/rust")
+ .sort_by(|a, b| a.file_name().cmp(b.file_name()))
+ .into_iter()
+ .filter_entry(repo::base_dir_filter)
+ .collect::<Result<Vec<DirEntry>, walkdir::Error>>()
+ .unwrap()
+ .into_par_iter()
+ .for_each(|entry| {
+ let path = entry.path();
+ if path.is_dir() {
+ return;
+ }
+
+ // Our version of `libsyntax` can't parse this tests
+ if path
+ .to_str()
+ .unwrap()
+ .ends_with("optional_comma_in_match_arm.rs")
+ {
+ return;
+ }
+
+ let mut file = File::open(path).unwrap();
+ let mut content = String::new();
+ file.read_to_string(&mut content).unwrap();
+ let content = edition_regex.replace_all(&content, "_$0");
+
+ let (l_passed, l_failed) = match syn::parse_file(&content) {
+ Ok(file) => {
+ let exprs = collect_exprs(file);
+ test_expressions(exprs)
+ }
+ Err(msg) => {
+ errorf!("syn failed to parse\n{:?}\n", msg);
+ (0, 1)
+ }
+ };
+
+ errorf!(
+ "=== {}: {} passed | {} failed\n",
+ path.display(),
+ l_passed,
+ l_failed
+ );
+
+ passed.fetch_add(l_passed, Ordering::SeqCst);
+ let prev_failed = failed.fetch_add(l_failed, Ordering::SeqCst);
+
+ if prev_failed + l_failed >= abort_after {
+ process::exit(1);
+ }
+ });
+
+ let passed = passed.load(Ordering::SeqCst);
+ let failed = failed.load(Ordering::SeqCst);
+
+ errorf!("\n===== Precedence Test Results =====\n");
+ errorf!("{} passed | {} failed\n", passed, failed);
+
+ if failed > 0 {
+ panic!("{} failures", failed);
+ }
+}
+
+fn test_expressions(exprs: Vec<syn::Expr>) -> (usize, usize) {
+ let mut passed = 0;
+ let mut failed = 0;
+
+ syntax::with_globals(Edition::Edition2018, || {
+ for expr in exprs {
+ let raw = quote!(#expr).to_string();
+
+ let libsyntax_ast = if let Some(e) = libsyntax_parse_and_rewrite(&raw) {
+ e
+ } else {
+ failed += 1;
+ errorf!("\nFAIL - libsyntax failed to parse raw\n");
+ continue;
+ };
+
+ let syn_expr = syn_brackets(expr);
+ let syn_ast = if let Some(e) = parse::libsyntax_expr(&quote!(#syn_expr).to_string()) {
+ e
+ } else {
+ failed += 1;
+ errorf!("\nFAIL - libsyntax failed to parse bracketed\n");
+ continue;
+ };
+
+ if SpanlessEq::eq(&syn_ast, &libsyntax_ast) {
+ passed += 1;
+ } else {
+ failed += 1;
+ errorf!("\nFAIL\n{:?}\n!=\n{:?}\n", syn_ast, libsyntax_ast);
+ }
+ }
+ });
+
+ (passed, failed)
+}
+
+fn libsyntax_parse_and_rewrite(input: &str) -> Option<P<ast::Expr>> {
+ parse::libsyntax_expr(input).and_then(libsyntax_brackets)
+}
+
+/// Wrap every expression which is not already wrapped in parens with parens, to
+/// reveal the precidence of the parsed expressions, and produce a stringified
+/// form of the resulting expression.
+///
+/// This method operates on libsyntax objects.
+fn libsyntax_brackets(mut libsyntax_expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
+ use rustc_data_structures::thin_vec::ThinVec;
+ use rustc_span::DUMMY_SP;
+ use std::mem;
+ use syntax::ast::{Block, Expr, ExprKind, Field, Mac, Pat, Stmt, StmtKind, Ty};
+ use syntax::mut_visit::MutVisitor;
+ use syntax::util::map_in_place::MapInPlace;
+
+ struct BracketsVisitor {
+ failed: bool,
+ };
+
+ fn flat_map_field<T: MutVisitor>(mut f: Field, vis: &mut T) -> Vec<Field> {
+ if f.is_shorthand {
+ noop_visit_expr(&mut f.expr, vis);
+ } else {
+ vis.visit_expr(&mut f.expr);
+ }
+ vec![f]
+ }
+
+ fn flat_map_stmt<T: MutVisitor>(stmt: Stmt, vis: &mut T) -> Vec<Stmt> {
+ let kind = match stmt.kind {
+ // Don't wrap toplevel expressions in statements.
+ StmtKind::Expr(mut e) => {
+ noop_visit_expr(&mut e, vis);
+ StmtKind::Expr(e)
+ }
+ StmtKind::Semi(mut e) => {
+ noop_visit_expr(&mut e, vis);
+ StmtKind::Semi(e)
+ }
+ s => s,
+ };
+
+ vec![Stmt { kind, ..stmt }]
+ }
+
+ fn noop_visit_expr<T: MutVisitor>(e: &mut Expr, vis: &mut T) {
+ use syntax::mut_visit::{noop_visit_expr, visit_opt, visit_thin_attrs};
+ match &mut e.kind {
+ ExprKind::Struct(path, fields, expr) => {
+ vis.visit_path(path);
+ fields.flat_map_in_place(|field| flat_map_field(field, vis));
+ visit_opt(expr, |expr| vis.visit_expr(expr));
+ vis.visit_id(&mut e.id);
+ vis.visit_span(&mut e.span);
+ visit_thin_attrs(&mut e.attrs, vis);
+ }
+ _ => noop_visit_expr(e, vis),
+ }
+ }
+
+ impl MutVisitor for BracketsVisitor {
+ fn visit_expr(&mut self, e: &mut P<Expr>) {
+ noop_visit_expr(e, self);
+ match e.kind {
+ ExprKind::If(..) | ExprKind::Block(..) | ExprKind::Let(..) => {}
+ _ => {
+ let inner = mem::replace(
+ e,
+ P(Expr {
+ id: ast::DUMMY_NODE_ID,
+ kind: ExprKind::Err,
+ span: DUMMY_SP,
+ attrs: ThinVec::new(),
+ }),
+ );
+ e.kind = ExprKind::Paren(inner);
+ }
+ }
+ }
+
+ fn visit_block(&mut self, block: &mut P<Block>) {
+ self.visit_id(&mut block.id);
+ block
+ .stmts
+ .flat_map_in_place(|stmt| flat_map_stmt(stmt, self));
+ self.visit_span(&mut block.span);
+ }
+
+ // We don't want to look at expressions that might appear in patterns or
+ // types yet. We'll look into comparing those in the future. For now
+ // focus on expressions appearing in other places.
+ fn visit_pat(&mut self, pat: &mut P<Pat>) {
+ let _ = pat;
+ }
+
+ fn visit_ty(&mut self, ty: &mut P<Ty>) {
+ let _ = ty;
+ }
+
+ fn visit_mac(&mut self, mac: &mut Mac) {
+ // By default when folding over macros, libsyntax panics. This is
+ // because it's usually not what you want, you want to run after
+ // macro expansion. We do want to do that (syn doesn't do macro
+ // expansion), so we implement visit_mac to just return the macro
+ // unchanged.
+ let _ = mac;
+ }
+ }
+
+ let mut folder = BracketsVisitor { failed: false };
+ folder.visit_expr(&mut libsyntax_expr);
+ if folder.failed {
+ None
+ } else {
+ Some(libsyntax_expr)
+ }
+}
+
+/// Wrap every expression which is not already wrapped in parens with parens, to
+/// reveal the precedence of the parsed expressions, and produce a stringified
+/// form of the resulting expression.
+fn syn_brackets(syn_expr: syn::Expr) -> syn::Expr {
+ use syn::fold::*;
+ use syn::*;
+
+ struct ParenthesizeEveryExpr;
+ impl Fold for ParenthesizeEveryExpr {
+ fn fold_expr(&mut self, expr: Expr) -> Expr {
+ match expr {
+ Expr::Group(_) => unreachable!(),
+ Expr::If(..) | Expr::Unsafe(..) | Expr::Block(..) | Expr::Let(..) => {
+ fold_expr(self, expr)
+ }
+ _ => Expr::Paren(ExprParen {
+ attrs: Vec::new(),
+ expr: Box::new(fold_expr(self, expr)),
+ paren_token: token::Paren::default(),
+ }),
+ }
+ }
+
+ fn fold_stmt(&mut self, stmt: Stmt) -> Stmt {
+ match stmt {
+ // Don't wrap toplevel expressions in statements.
+ Stmt::Expr(e) => Stmt::Expr(fold_expr(self, e)),
+ Stmt::Semi(e, semi) => Stmt::Semi(fold_expr(self, e), semi),
+ s => s,
+ }
+ }
+
+ // We don't want to look at expressions that might appear in patterns or
+ // types yet. We'll look into comparing those in the future. For now
+ // focus on expressions appearing in other places.
+ fn fold_pat(&mut self, pat: Pat) -> Pat {
+ pat
+ }
+
+ fn fold_type(&mut self, ty: Type) -> Type {
+ ty
+ }
+ }
+
+ let mut folder = ParenthesizeEveryExpr;
+ folder.fold_expr(syn_expr)
+}
+
+/// Walk through a crate collecting all expressions we can find in it.
+fn collect_exprs(file: syn::File) -> Vec<syn::Expr> {
+ use syn::fold::*;
+ use syn::punctuated::Punctuated;
+ use syn::*;
+
+ struct CollectExprs(Vec<Expr>);
+ impl Fold for CollectExprs {
+ fn fold_expr(&mut self, expr: Expr) -> Expr {
+ match expr {
+ Expr::Verbatim(tokens) if tokens.is_empty() => {}
+ _ => self.0.push(expr),
+ }
+
+ Expr::Tuple(ExprTuple {
+ attrs: vec![],
+ elems: Punctuated::new(),
+ paren_token: token::Paren::default(),
+ })
+ }
+ }
+
+ let mut folder = CollectExprs(vec![]);
+ folder.fold_file(file);
+ folder.0
+}