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::{DiagMessage, LazyFallbackBundle};
33 use rustc_errors::{translation, Diag, PResult};
34 use rustc_session::parse::ParseSess;
35 use rustc_span::FileName;
36 use std::borrow::Cow;
37 use std::fs;
38 use std::panic;
39 use std::path::Path;
40 use std::process;
41 use std::sync::atomic::{AtomicUsize, Ordering};
42 use std::time::Instant;
43 
44 #[macro_use]
45 mod macros;
46 
47 #[allow(dead_code)]
48 mod common;
49 
50 mod repo;
51 
52 #[test]
test_round_trip()53 fn 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 
test(path: &Path, failed: &AtomicUsize, abort_after: usize)71 fn 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 sess = ParseSess::new(locale_resources);
93             let before = match librustc_parse(content, &sess) {
94                 Ok(before) => before,
95                 Err(diagnostic) => {
96                     errorf!(
97                         "=== {}: ignore - librustc failed to parse original content: {}\n",
98                         path.display(),
99                         translate_message(&diagnostic),
100                     );
101                     diagnostic.cancel();
102                     return Err(true);
103                 }
104             };
105             let after = match librustc_parse(back, &sess) {
106                 Ok(after) => after,
107                 Err(diagnostic) => {
108                     errorf!("=== {}: librustc failed to parse", path.display());
109                     diagnostic.emit();
110                     return Err(false);
111                 }
112             };
113             Ok((before, after))
114         }) {
115             Err(_) => {
116                 errorf!("=== {}: ignoring librustc panic\n", path.display());
117                 true
118             }
119             Ok(Err(equal)) => equal,
120             Ok(Ok((mut before, mut after))) => {
121                 normalize(&mut before);
122                 normalize(&mut after);
123                 if SpanlessEq::eq(&before, &after) {
124                     errorf!(
125                         "=== {}: pass in {}ms\n",
126                         path.display(),
127                         elapsed.as_secs() * 1000 + u64::from(elapsed.subsec_nanos()) / 1_000_000
128                     );
129                     true
130                 } else {
131                     errorf!(
132                         "=== {}: FAIL\n{}\n!=\n{}\n",
133                         path.display(),
134                         pprust::crate_to_string_for_macros(&before),
135                         pprust::crate_to_string_for_macros(&after),
136                     );
137                     false
138                 }
139             }
140         };
141         if !equal {
142             let prev_failed = failed.fetch_add(1, Ordering::Relaxed);
143             if prev_failed + 1 >= abort_after {
144                 process::exit(1);
145             }
146         }
147     });
148 }
149 
librustc_parse(content: String, sess: &ParseSess) -> PResult<Crate>150 fn librustc_parse(content: String, sess: &ParseSess) -> PResult<Crate> {
151     static COUNTER: AtomicUsize = AtomicUsize::new(0);
152     let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
153     let name = FileName::Custom(format!("test_round_trip{}", counter));
154     parse::parse_crate_from_source_str(name, content, sess)
155 }
156 
translate_message(diagnostic: &Diag) -> Cow<'static, str>157 fn translate_message(diagnostic: &Diag) -> Cow<'static, str> {
158     thread_local! {
159         static FLUENT_BUNDLE: LazyFallbackBundle = {
160             let locale_resources = rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec();
161             let with_directionality_markers = false;
162             rustc_error_messages::fallback_fluent_bundle(locale_resources, with_directionality_markers)
163         };
164     }
165 
166     let message = &diagnostic.messages[0].0;
167     let args = translation::to_fluent_args(diagnostic.args.iter());
168 
169     let (identifier, attr) = match message {
170         DiagMessage::Str(msg) | DiagMessage::Translated(msg) => return msg.clone(),
171         DiagMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
172     };
173 
174     FLUENT_BUNDLE.with(|fluent_bundle| {
175         let message = fluent_bundle
176             .get_message(identifier)
177             .expect("missing diagnostic in fluent bundle");
178         let value = match attr {
179             Some(attr) => message
180                 .get_attribute(attr)
181                 .expect("missing attribute in fluent message")
182                 .value(),
183             None => message.value().expect("missing value in fluent message"),
184         };
185 
186         let mut err = Vec::new();
187         let translated = fluent_bundle.format_pattern(value, Some(&args), &mut err);
188         assert!(err.is_empty());
189         Cow::Owned(translated.into_owned())
190     })
191 }
192 
normalize(krate: &mut Crate)193 fn normalize(krate: &mut Crate) {
194     struct NormalizeVisitor;
195 
196     impl MutVisitor for NormalizeVisitor {
197         fn visit_angle_bracketed_parameter_data(&mut self, e: &mut AngleBracketedArgs) {
198             #[derive(Ord, PartialOrd, Eq, PartialEq)]
199             enum Group {
200                 Lifetimes,
201                 TypesAndConsts,
202                 Constraints,
203             }
204             e.args.sort_by_key(|arg| match arg {
205                 AngleBracketedArg::Arg(arg) => match arg {
206                     GenericArg::Lifetime(_) => Group::Lifetimes,
207                     GenericArg::Type(_) | GenericArg::Const(_) => Group::TypesAndConsts,
208                 },
209                 AngleBracketedArg::Constraint(_) => Group::Constraints,
210             });
211             mut_visit::noop_visit_angle_bracketed_parameter_data(e, self);
212         }
213 
214         fn visit_generics(&mut self, e: &mut Generics) {
215             #[derive(Ord, PartialOrd, Eq, PartialEq)]
216             enum Group {
217                 Lifetimes,
218                 TypesAndConsts,
219             }
220             e.params.sort_by_key(|param| match param.kind {
221                 GenericParamKind::Lifetime => Group::Lifetimes,
222                 GenericParamKind::Type { .. } | GenericParamKind::Const { .. } => {
223                     Group::TypesAndConsts
224                 }
225             });
226             mut_visit::noop_visit_generics(e, self);
227         }
228 
229         fn visit_where_clause(&mut self, e: &mut WhereClause) {
230             if e.predicates.is_empty() {
231                 e.has_where_token = false;
232             }
233         }
234     }
235 
236     NormalizeVisitor.visit_crate(krate);
237 }
238