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