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