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