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