1#![cfg(not(syn_disable_nightly_tests))] 2#![cfg(not(miri))] 3#![recursion_limit = "1024"] 4#![feature(rustc_private)] 5#![allow( 6 clippy::blocks_in_conditions, 7 clippy::manual_assert, 8 clippy::manual_let_else, 9 clippy::match_like_matches_macro, 10 clippy::uninlined_format_args 11)] 12 13extern crate rustc_ast; 14extern crate rustc_ast_pretty; 15extern crate rustc_data_structures; 16extern crate rustc_driver; 17extern crate rustc_error_messages; 18extern crate rustc_errors; 19extern crate rustc_expand; 20extern crate rustc_parse as parse; 21extern crate rustc_session; 22extern crate rustc_span; 23 24use crate::common::eq::SpanlessEq; 25use quote::quote; 26use rustc_ast::ast::{ 27 AngleBracketedArg, AngleBracketedArgs, Crate, GenericArg, GenericParamKind, Generics, 28 WhereClause, 29}; 30use rustc_ast::mut_visit::{self, MutVisitor}; 31use rustc_ast_pretty::pprust; 32use rustc_error_messages::{DiagnosticMessage, LazyFallbackBundle}; 33use rustc_errors::{translation, Diagnostic, PResult}; 34use rustc_session::parse::ParseSess; 35use rustc_span::source_map::FilePathMapping; 36use rustc_span::FileName; 37use std::borrow::Cow; 38use std::fs; 39use std::panic; 40use std::path::Path; 41use std::process; 42use std::sync::atomic::{AtomicUsize, Ordering}; 43use std::time::Instant; 44 45#[macro_use] 46mod macros; 47 48#[allow(dead_code)] 49mod common; 50 51mod repo; 52 53#[test] 54fn test_round_trip() { 55 common::rayon_init(); 56 repo::clone_rust(); 57 let abort_after = common::abort_after(); 58 if abort_after == 0 { 59 panic!("Skipping all round_trip tests"); 60 } 61 62 let failed = AtomicUsize::new(0); 63 64 repo::for_each_rust_file(|path| test(path, &failed, abort_after)); 65 66 let failed = failed.load(Ordering::Relaxed); 67 if failed > 0 { 68 panic!("{} failures", failed); 69 } 70} 71 72fn test(path: &Path, failed: &AtomicUsize, abort_after: usize) { 73 let content = fs::read_to_string(path).unwrap(); 74 75 let start = Instant::now(); 76 let (krate, elapsed) = match syn::parse_file(&content) { 77 Ok(krate) => (krate, start.elapsed()), 78 Err(msg) => { 79 errorf!("=== {}: syn failed to parse\n{:?}\n", path.display(), msg); 80 let prev_failed = failed.fetch_add(1, Ordering::Relaxed); 81 if prev_failed + 1 >= abort_after { 82 process::exit(1); 83 } 84 return; 85 } 86 }; 87 let back = quote!(#krate).to_string(); 88 let edition = repo::edition(path).parse().unwrap(); 89 90 rustc_span::create_session_if_not_set_then(edition, |_| { 91 let equal = match panic::catch_unwind(|| { 92 let locale_resources = rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(); 93 let file_path_mapping = FilePathMapping::empty(); 94 let sess = ParseSess::new(locale_resources, file_path_mapping); 95 let before = match librustc_parse(content, &sess) { 96 Ok(before) => before, 97 Err(diagnostic) => { 98 errorf!( 99 "=== {}: ignore - librustc failed to parse original content: {}\n", 100 path.display(), 101 translate_message(&diagnostic), 102 ); 103 diagnostic.cancel(); 104 return Err(true); 105 } 106 }; 107 let after = match librustc_parse(back, &sess) { 108 Ok(after) => after, 109 Err(mut diagnostic) => { 110 errorf!("=== {}: librustc failed to parse", path.display()); 111 diagnostic.emit(); 112 return Err(false); 113 } 114 }; 115 Ok((before, after)) 116 }) { 117 Err(_) => { 118 errorf!("=== {}: ignoring librustc panic\n", path.display()); 119 true 120 } 121 Ok(Err(equal)) => equal, 122 Ok(Ok((mut before, mut after))) => { 123 normalize(&mut before); 124 normalize(&mut after); 125 if SpanlessEq::eq(&before, &after) { 126 errorf!( 127 "=== {}: pass in {}ms\n", 128 path.display(), 129 elapsed.as_secs() * 1000 + u64::from(elapsed.subsec_nanos()) / 1_000_000 130 ); 131 true 132 } else { 133 errorf!( 134 "=== {}: FAIL\n{}\n!=\n{}\n", 135 path.display(), 136 pprust::crate_to_string_for_macros(&before), 137 pprust::crate_to_string_for_macros(&after), 138 ); 139 false 140 } 141 } 142 }; 143 if !equal { 144 let prev_failed = failed.fetch_add(1, Ordering::Relaxed); 145 if prev_failed + 1 >= abort_after { 146 process::exit(1); 147 } 148 } 149 }); 150} 151 152fn librustc_parse(content: String, sess: &ParseSess) -> PResult<Crate> { 153 static COUNTER: AtomicUsize = AtomicUsize::new(0); 154 let counter = COUNTER.fetch_add(1, Ordering::Relaxed); 155 let name = FileName::Custom(format!("test_round_trip{}", counter)); 156 parse::parse_crate_from_source_str(name, content, sess) 157} 158 159fn translate_message(diagnostic: &Diagnostic) -> Cow<'static, str> { 160 thread_local! { 161 static FLUENT_BUNDLE: LazyFallbackBundle = { 162 let locale_resources = rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(); 163 let with_directionality_markers = false; 164 rustc_error_messages::fallback_fluent_bundle(locale_resources, with_directionality_markers) 165 }; 166 } 167 168 let message = &diagnostic.messages[0].0; 169 let args = translation::to_fluent_args(diagnostic.args()); 170 171 let (identifier, attr) = match message { 172 DiagnosticMessage::Str(msg) | DiagnosticMessage::Eager(msg) => return msg.clone(), 173 DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr), 174 }; 175 176 FLUENT_BUNDLE.with(|fluent_bundle| { 177 let message = fluent_bundle 178 .get_message(identifier) 179 .expect("missing diagnostic in fluent bundle"); 180 let value = match attr { 181 Some(attr) => message 182 .get_attribute(attr) 183 .expect("missing attribute in fluent message") 184 .value(), 185 None => message.value().expect("missing value in fluent message"), 186 }; 187 188 let mut err = Vec::new(); 189 let translated = fluent_bundle.format_pattern(value, Some(&args), &mut err); 190 assert!(err.is_empty()); 191 Cow::Owned(translated.into_owned()) 192 }) 193} 194 195fn normalize(krate: &mut Crate) { 196 struct NormalizeVisitor; 197 198 impl MutVisitor for NormalizeVisitor { 199 fn visit_angle_bracketed_parameter_data(&mut self, e: &mut AngleBracketedArgs) { 200 #[derive(Ord, PartialOrd, Eq, PartialEq)] 201 enum Group { 202 Lifetimes, 203 TypesAndConsts, 204 Constraints, 205 } 206 e.args.sort_by_key(|arg| match arg { 207 AngleBracketedArg::Arg(arg) => match arg { 208 GenericArg::Lifetime(_) => Group::Lifetimes, 209 GenericArg::Type(_) | GenericArg::Const(_) => Group::TypesAndConsts, 210 }, 211 AngleBracketedArg::Constraint(_) => Group::Constraints, 212 }); 213 mut_visit::noop_visit_angle_bracketed_parameter_data(e, self); 214 } 215 216 fn visit_generics(&mut self, e: &mut Generics) { 217 #[derive(Ord, PartialOrd, Eq, PartialEq)] 218 enum Group { 219 Lifetimes, 220 TypesAndConsts, 221 } 222 e.params.sort_by_key(|param| match param.kind { 223 GenericParamKind::Lifetime => Group::Lifetimes, 224 GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => { 225 Group::TypesAndConsts 226 } 227 }); 228 mut_visit::noop_visit_generics(e, self); 229 } 230 231 fn visit_where_clause(&mut self, e: &mut WhereClause) { 232 if e.predicates.is_empty() { 233 e.has_where_token = false; 234 } 235 } 236 } 237 238 NormalizeVisitor.visit_crate(krate); 239} 240