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
13 extern crate rustc_ast;
14 extern crate rustc_ast_pretty;
15 extern crate rustc_data_structures;
16 extern crate rustc_driver;
17 extern crate rustc_error_messages;
18 extern crate rustc_errors;
19 extern crate rustc_expand;
20 extern crate rustc_parse as parse;
21 extern crate rustc_session;
22 extern crate rustc_span;
23
24 use crate::common::eq::SpanlessEq;
25 use quote::quote;
26 use rustc_ast::ast::{
27 AngleBracketedArg, AngleBracketedArgs, Crate, GenericArg, GenericParamKind, Generics,
28 WhereClause,
29 };
30 use rustc_ast::mut_visit::{self, MutVisitor};
31 use rustc_ast_pretty::pprust;
32 use rustc_error_messages::{DiagnosticMessage, LazyFallbackBundle};
33 use rustc_errors::{translation, Diagnostic, PResult};
34 use rustc_session::parse::ParseSess;
35 use rustc_span::source_map::FilePathMapping;
36 use rustc_span::FileName;
37 use std::borrow::Cow;
38 use std::fs;
39 use std::panic;
40 use std::path::Path;
41 use std::process;
42 use std::sync::atomic::{AtomicUsize, Ordering};
43 use std::time::Instant;
44
45 #[macro_use]
46 mod macros;
47
48 #[allow(dead_code)]
49 mod common;
50
51 mod repo;
52
53 #[test]
test_round_tripnull54 fn 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
testnull72 fn 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
librustc_parsenull152 fn 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
translate_messagenull159 fn 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
normalizenull195 fn 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