Skip to main content

rustfmt_nightly/parse/
session.rs

1use std::path::Path;
2use std::sync::Arc;
3use std::sync::atomic::{AtomicBool, Ordering};
4
5use rustc_data_structures::sync::IntoDynSyncSend;
6use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitter;
7use rustc_errors::emitter::{DynEmitter, Emitter, SilentEmitter, stderr_destination};
8use rustc_errors::registry::Registry;
9use rustc_errors::translation::Translator;
10use rustc_errors::{ColorConfig, Diag, DiagCtxt, DiagInner, Level as DiagnosticLevel};
11use rustc_session::parse::ParseSess as RawParseSess;
12use rustc_span::{
13    BytePos, Span,
14    source_map::{FilePathMapping, SourceMap},
15    symbol,
16};
17
18use crate::config::file_lines::LineRange;
19use crate::config::options::Color;
20use crate::ignore_path::IgnorePathSet;
21use crate::parse::parser::{ModError, ModulePathSuccess};
22use crate::source_map::LineRangeUtils;
23use crate::utils::starts_with_newline;
24use crate::visitor::SnippetProvider;
25use crate::{Config, ErrorKind, FileName};
26
27/// ParseSess holds structs necessary for constructing a parser.
28pub(crate) struct ParseSess {
29    raw_psess: RawParseSess,
30    ignore_path_set: Arc<IgnorePathSet>,
31    can_reset_errors: Arc<AtomicBool>,
32}
33
34/// Emit errors against every files expect ones specified in the `ignore_path_set`.
35struct SilentOnIgnoredFilesEmitter {
36    ignore_path_set: IntoDynSyncSend<Arc<IgnorePathSet>>,
37    source_map: Arc<SourceMap>,
38    emitter: Box<DynEmitter>,
39    has_non_ignorable_parser_errors: bool,
40    can_reset: Arc<AtomicBool>,
41}
42
43impl SilentOnIgnoredFilesEmitter {
44    fn handle_non_ignoreable_error(&mut self, diag: DiagInner, registry: &Registry) {
45        self.has_non_ignorable_parser_errors = true;
46        self.can_reset.store(false, Ordering::Release);
47        self.emitter.emit_diagnostic(diag, registry);
48    }
49}
50
51impl Emitter for SilentOnIgnoredFilesEmitter {
52    fn source_map(&self) -> Option<&SourceMap> {
53        None
54    }
55
56    fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry) {
57        if diag.level() == DiagnosticLevel::Fatal {
58            return self.handle_non_ignoreable_error(diag, registry);
59        }
60        if let Some(primary_span) = &diag.span.primary_span() {
61            let file_name = self.source_map.span_to_filename(*primary_span);
62            if let rustc_span::FileName::Real(real) = file_name {
63                if let Some(path) = real.local_path() {
64                    if self
65                        .ignore_path_set
66                        .is_match(&FileName::Real(path.to_path_buf()))
67                    {
68                        if !self.has_non_ignorable_parser_errors {
69                            self.can_reset.store(true, Ordering::Release);
70                        }
71                        return;
72                    }
73                }
74            }
75        }
76        self.handle_non_ignoreable_error(diag, registry);
77    }
78
79    fn translator(&self) -> &Translator {
80        self.emitter.translator()
81    }
82}
83
84impl From<Color> for ColorConfig {
85    fn from(color: Color) -> Self {
86        match color {
87            Color::Auto => ColorConfig::Auto,
88            Color::Always => ColorConfig::Always,
89            Color::Never => ColorConfig::Never,
90        }
91    }
92}
93
94fn default_dcx(
95    source_map: Arc<SourceMap>,
96    ignore_path_set: Arc<IgnorePathSet>,
97    can_reset: Arc<AtomicBool>,
98    show_parse_errors: bool,
99    color: Color,
100) -> DiagCtxt {
101    let supports_color = term::stderr().map_or(false, |term| term.supports_color());
102    let emit_color = if supports_color {
103        ColorConfig::from(color)
104    } else {
105        ColorConfig::Never
106    };
107
108    let translator = rustc_driver::default_translator();
109
110    let emitter: Box<DynEmitter> = if show_parse_errors {
111        Box::new(
112            AnnotateSnippetEmitter::new(stderr_destination(emit_color), translator)
113                .sm(Some(source_map.clone())),
114        )
115    } else {
116        Box::new(SilentEmitter { translator })
117    };
118    DiagCtxt::new(Box::new(SilentOnIgnoredFilesEmitter {
119        has_non_ignorable_parser_errors: false,
120        source_map,
121        emitter,
122        ignore_path_set: IntoDynSyncSend(ignore_path_set),
123        can_reset,
124    }))
125}
126
127impl ParseSess {
128    pub(crate) fn new(config: &Config) -> Result<ParseSess, ErrorKind> {
129        let ignore_path_set = match IgnorePathSet::from_ignore_list(&config.ignore()) {
130            Ok(ignore_path_set) => Arc::new(ignore_path_set),
131            Err(e) => return Err(ErrorKind::InvalidGlobPattern(e)),
132        };
133        let source_map = Arc::new(SourceMap::new(FilePathMapping::empty()));
134        let can_reset_errors = Arc::new(AtomicBool::new(false));
135
136        let dcx = default_dcx(
137            Arc::clone(&source_map),
138            Arc::clone(&ignore_path_set),
139            Arc::clone(&can_reset_errors),
140            config.show_parse_errors(),
141            config.color(),
142        );
143        let raw_psess = RawParseSess::with_dcx(dcx, source_map);
144
145        Ok(ParseSess {
146            raw_psess,
147            ignore_path_set,
148            can_reset_errors,
149        })
150    }
151
152    /// Determine the submodule path for the given module identifier.
153    ///
154    /// * `id` - The name of the module
155    /// * `relative` - If Some(symbol), the symbol name is a directory relative to the dir_path.
156    ///   If relative is Some, resolve the submodule at {dir_path}/{symbol}/{id}.rs
157    ///   or {dir_path}/{symbol}/{id}/mod.rs. if None, resolve the module at {dir_path}/{id}.rs.
158    /// *  `dir_path` - Module resolution will occur relative to this directory.
159    pub(crate) fn default_submod_path(
160        &self,
161        id: symbol::Ident,
162        relative: Option<symbol::Ident>,
163        dir_path: &Path,
164    ) -> Result<ModulePathSuccess, ModError<'_>> {
165        rustc_expand::module::default_submod_path(&self.raw_psess, id, relative, dir_path).or_else(
166            |e| {
167                // If resolving a module relative to {dir_path}/{symbol} fails because a file
168                // could not be found, then try to resolve the module relative to {dir_path}.
169                // If we still can't find the module after searching for it in {dir_path},
170                // surface the original error.
171                if matches!(e, ModError::FileNotFound(..)) && relative.is_some() {
172                    rustc_expand::module::default_submod_path(&self.raw_psess, id, None, dir_path)
173                        .map_err(|_| e)
174                } else {
175                    Err(e)
176                }
177            },
178        )
179    }
180
181    pub(crate) fn is_file_parsed(&self, path: &Path) -> bool {
182        self.raw_psess
183            .source_map()
184            .get_source_file(&rustc_span::FileName::Real(
185                self.raw_psess
186                    .source_map()
187                    .path_mapping()
188                    .to_real_filename(self.raw_psess.source_map().working_dir(), path),
189            ))
190            .is_some()
191    }
192
193    pub(crate) fn ignore_file(&self, path: &FileName) -> bool {
194        self.ignore_path_set.as_ref().is_match(path)
195    }
196
197    pub(crate) fn set_silent_emitter(&mut self) {
198        self.raw_psess.dcx().make_silent();
199    }
200
201    pub(crate) fn span_to_filename(&self, span: Span) -> FileName {
202        self.raw_psess.source_map().span_to_filename(span).into()
203    }
204
205    pub(crate) fn span_to_file_contents(&self, span: Span) -> Arc<rustc_span::SourceFile> {
206        self.raw_psess
207            .source_map()
208            .lookup_source_file(span.data().lo)
209    }
210
211    pub(crate) fn span_to_first_line_string(&self, span: Span) -> String {
212        let file_lines = self.raw_psess.source_map().span_to_lines(span).ok();
213
214        match file_lines {
215            Some(fl) => fl
216                .file
217                .get_line(fl.lines[0].line_index)
218                .map_or_else(String::new, |s| s.to_string()),
219            None => String::new(),
220        }
221    }
222
223    pub(crate) fn line_of_byte_pos(&self, pos: BytePos) -> usize {
224        self.raw_psess.source_map().lookup_char_pos(pos).line
225    }
226
227    // TODO(calebcartwright): Preemptive, currently unused addition
228    // that will be used to support formatting scenarios that take original
229    // positions into account
230    /// Determines whether two byte positions are in the same source line.
231    #[allow(dead_code)]
232    pub(crate) fn byte_pos_same_line(&self, a: BytePos, b: BytePos) -> bool {
233        self.line_of_byte_pos(a) == self.line_of_byte_pos(b)
234    }
235
236    pub(crate) fn span_to_debug_info(&self, span: Span) -> String {
237        self.raw_psess.source_map().span_to_diagnostic_string(span)
238    }
239
240    pub(crate) fn inner(&self) -> &RawParseSess {
241        &self.raw_psess
242    }
243
244    pub(crate) fn snippet_provider(&self, span: Span) -> SnippetProvider {
245        let source_file = self.raw_psess.source_map().lookup_char_pos(span.lo()).file;
246        SnippetProvider::new(
247            source_file.start_pos,
248            source_file.end_position(),
249            Arc::clone(source_file.src.as_ref().unwrap()),
250        )
251    }
252
253    pub(crate) fn get_original_snippet(&self, filename: &FileName) -> Option<Arc<String>> {
254        let rustc_filename = match filename {
255            FileName::Real(path) => rustc_span::FileName::Real(
256                self.raw_psess
257                    .source_map()
258                    .path_mapping()
259                    .to_real_filename(self.raw_psess.source_map().working_dir(), path),
260            ),
261            FileName::Stdin => rustc_span::FileName::Custom("stdin".to_owned()),
262        };
263
264        self.raw_psess
265            .source_map()
266            .get_source_file(&rustc_filename)
267            .and_then(|source_file| source_file.src.clone())
268    }
269}
270
271// Methods that should be restricted within the parse module.
272impl ParseSess {
273    pub(super) fn emit_diagnostics(&self, diagnostics: Vec<Diag<'_>>) {
274        for diagnostic in diagnostics {
275            diagnostic.emit();
276        }
277    }
278
279    pub(super) fn can_reset_errors(&self) -> bool {
280        self.can_reset_errors.load(Ordering::Acquire)
281    }
282
283    pub(super) fn has_errors(&self) -> bool {
284        self.raw_psess.dcx().has_errors().is_some()
285    }
286
287    pub(super) fn reset_errors(&self) {
288        self.raw_psess.dcx().reset_err_count();
289    }
290}
291
292impl LineRangeUtils for ParseSess {
293    fn lookup_line_range(&self, span: Span) -> LineRange {
294        let snippet = self
295            .raw_psess
296            .source_map()
297            .span_to_snippet(span)
298            .unwrap_or_default();
299        let lo = self.raw_psess.source_map().lookup_line(span.lo()).unwrap();
300        let hi = self.raw_psess.source_map().lookup_line(span.hi()).unwrap();
301
302        debug_assert_eq!(
303            lo.sf.name, hi.sf.name,
304            "span crossed file boundary: lo: {lo:?}, hi: {hi:?}"
305        );
306
307        // in case the span starts with a newline, the line range is off by 1 without the
308        // adjustment below
309        let offset = 1 + if starts_with_newline(&snippet) { 1 } else { 0 };
310        // Line numbers start at 1
311        LineRange {
312            file: lo.sf.clone(),
313            lo: lo.line + offset,
314            hi: hi.line + offset,
315        }
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322
323    use rustfmt_config_proc_macro::nightly_only_test;
324
325    mod emitter {
326        use super::*;
327        use crate::config::IgnoreList;
328        use crate::utils::mk_sp;
329        use rustc_errors::MultiSpan;
330        use rustc_span::FileName as SourceMapFileName;
331        use std::path::PathBuf;
332        use std::sync::atomic::AtomicU32;
333
334        struct TestEmitter {
335            num_emitted_errors: Arc<AtomicU32>,
336        }
337
338        impl Emitter for TestEmitter {
339            fn source_map(&self) -> Option<&SourceMap> {
340                None
341            }
342
343            fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {
344                self.num_emitted_errors.fetch_add(1, Ordering::Release);
345            }
346
347            fn translator(&self) -> &Translator {
348                panic!("test emitter attempted to translate a diagnostic");
349            }
350        }
351
352        fn build_diagnostic(level: DiagnosticLevel, span: Option<MultiSpan>) -> DiagInner {
353            let mut diag = DiagInner::new(level, "");
354            diag.messages.clear();
355            if let Some(span) = span {
356                diag.span = span;
357            }
358            diag
359        }
360
361        fn build_emitter(
362            num_emitted_errors: Arc<AtomicU32>,
363            can_reset: Arc<AtomicBool>,
364            source_map: Option<Arc<SourceMap>>,
365            ignore_list: Option<IgnoreList>,
366        ) -> SilentOnIgnoredFilesEmitter {
367            let emitter_writer = TestEmitter { num_emitted_errors };
368            let source_map =
369                source_map.unwrap_or_else(|| Arc::new(SourceMap::new(FilePathMapping::empty())));
370            let ignore_path_set = Arc::new(
371                IgnorePathSet::from_ignore_list(&ignore_list.unwrap_or_default()).unwrap(),
372            );
373            SilentOnIgnoredFilesEmitter {
374                has_non_ignorable_parser_errors: false,
375                source_map,
376                emitter: Box::new(emitter_writer),
377                ignore_path_set: IntoDynSyncSend(ignore_path_set),
378                can_reset,
379            }
380        }
381
382        fn get_ignore_list(config: &str) -> IgnoreList {
383            Config::from_toml(config, Path::new("./rustfmt.toml"))
384                .unwrap()
385                .ignore()
386        }
387
388        fn filename(sm: &SourceMap, path: &str) -> SourceMapFileName {
389            SourceMapFileName::Real(
390                sm.path_mapping()
391                    .to_real_filename(sm.working_dir(), PathBuf::from(path)),
392            )
393        }
394
395        #[test]
396        fn handles_fatal_parse_error_in_ignored_file() {
397            let num_emitted_errors = Arc::new(AtomicU32::new(0));
398            let can_reset_errors = Arc::new(AtomicBool::new(false));
399            let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#);
400            let source_map = Arc::new(SourceMap::new(FilePathMapping::empty()));
401            let source =
402                String::from(r#"extern "system" fn jni_symbol!( funcName ) ( ... ) -> {} "#);
403            source_map.new_source_file(filename(&source_map, "foo.rs"), source);
404            let registry = Registry::new(&[]);
405            let mut emitter = build_emitter(
406                Arc::clone(&num_emitted_errors),
407                Arc::clone(&can_reset_errors),
408                Some(Arc::clone(&source_map)),
409                Some(ignore_list),
410            );
411            let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
412            let fatal_diagnostic = build_diagnostic(DiagnosticLevel::Fatal, Some(span));
413            emitter.emit_diagnostic(fatal_diagnostic, &registry);
414            assert_eq!(num_emitted_errors.load(Ordering::Acquire), 1);
415            assert_eq!(can_reset_errors.load(Ordering::Acquire), false);
416        }
417
418        #[nightly_only_test]
419        #[test]
420        fn handles_recoverable_parse_error_in_ignored_file() {
421            let num_emitted_errors = Arc::new(AtomicU32::new(0));
422            let can_reset_errors = Arc::new(AtomicBool::new(false));
423            let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#);
424            let source_map = Arc::new(SourceMap::new(FilePathMapping::empty()));
425            let source = String::from(r#"pub fn bar() { 1x; }"#);
426            source_map.new_source_file(filename(&source_map, "foo.rs"), source);
427            let registry = Registry::new(&[]);
428            let mut emitter = build_emitter(
429                Arc::clone(&num_emitted_errors),
430                Arc::clone(&can_reset_errors),
431                Some(Arc::clone(&source_map)),
432                Some(ignore_list),
433            );
434            let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
435            let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(span));
436            emitter.emit_diagnostic(non_fatal_diagnostic, &registry);
437            assert_eq!(num_emitted_errors.load(Ordering::Acquire), 0);
438            assert_eq!(can_reset_errors.load(Ordering::Acquire), true);
439        }
440
441        #[nightly_only_test]
442        #[test]
443        fn handles_recoverable_parse_error_in_non_ignored_file() {
444            let num_emitted_errors = Arc::new(AtomicU32::new(0));
445            let can_reset_errors = Arc::new(AtomicBool::new(false));
446            let source_map = Arc::new(SourceMap::new(FilePathMapping::empty()));
447            let source = String::from(r#"pub fn bar() { 1x; }"#);
448            source_map.new_source_file(filename(&source_map, "foo.rs"), source);
449            let registry = Registry::new(&[]);
450            let mut emitter = build_emitter(
451                Arc::clone(&num_emitted_errors),
452                Arc::clone(&can_reset_errors),
453                Some(Arc::clone(&source_map)),
454                None,
455            );
456            let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
457            let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(span));
458            emitter.emit_diagnostic(non_fatal_diagnostic, &registry);
459            assert_eq!(num_emitted_errors.load(Ordering::Acquire), 1);
460            assert_eq!(can_reset_errors.load(Ordering::Acquire), false);
461        }
462
463        #[nightly_only_test]
464        #[test]
465        fn handles_mix_of_recoverable_parse_error() {
466            let num_emitted_errors = Arc::new(AtomicU32::new(0));
467            let can_reset_errors = Arc::new(AtomicBool::new(false));
468            let source_map = Arc::new(SourceMap::new(FilePathMapping::empty()));
469            let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#);
470            let bar_source = String::from(r#"pub fn bar() { 1x; }"#);
471            let foo_source = String::from(r#"pub fn foo() { 1x; }"#);
472            let fatal_source =
473                String::from(r#"extern "system" fn jni_symbol!( funcName ) ( ... ) -> {} "#);
474            source_map.new_source_file(filename(&source_map, "bar.rs"), bar_source);
475            source_map.new_source_file(filename(&source_map, "foo.rs"), foo_source);
476            source_map.new_source_file(filename(&source_map, "fatal.rs"), fatal_source);
477            let registry = Registry::new(&[]);
478            let mut emitter = build_emitter(
479                Arc::clone(&num_emitted_errors),
480                Arc::clone(&can_reset_errors),
481                Some(Arc::clone(&source_map)),
482                Some(ignore_list),
483            );
484            let bar_span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
485            let foo_span = MultiSpan::from_span(mk_sp(BytePos(21), BytePos(22)));
486            let bar_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(bar_span));
487            let foo_diagnostic = build_diagnostic(DiagnosticLevel::Warning, Some(foo_span));
488            let fatal_diagnostic = build_diagnostic(DiagnosticLevel::Fatal, None);
489            emitter.emit_diagnostic(bar_diagnostic, &registry);
490            emitter.emit_diagnostic(foo_diagnostic, &registry);
491            emitter.emit_diagnostic(fatal_diagnostic, &registry);
492            assert_eq!(num_emitted_errors.load(Ordering::Acquire), 2);
493            assert_eq!(can_reset_errors.load(Ordering::Acquire), false);
494        }
495    }
496}