Skip to main content

rustc_errors/
emitter.rs

1//! The current rustc diagnostics emitter.
2//!
3//! An `Emitter` takes care of generating the output from a `Diag` struct.
4//!
5//! There are various `Emitter` implementations that generate different output formats such as
6//! JSON and human readable output.
7//!
8//! The output types are defined in `rustc_session::config::ErrorOutputType`.
9
10use std::borrow::Cow;
11use std::error::Report;
12use std::io::prelude::*;
13use std::io::{self, IsTerminal};
14use std::iter;
15use std::path::Path;
16
17use anstream::{AutoStream, ColorChoice};
18use anstyle::{AnsiColor, Effects};
19use rustc_data_structures::fx::FxIndexSet;
20use rustc_data_structures::sync::DynSend;
21use rustc_error_messages::FluentArgs;
22use rustc_span::hygiene::{ExpnKind, MacroKind};
23use rustc_span::source_map::SourceMap;
24use rustc_span::{FileName, SourceFile, Span};
25use tracing::{debug, warn};
26
27use crate::registry::Registry;
28use crate::timings::TimingRecord;
29use crate::translation::Translator;
30use crate::{
31    CodeSuggestion, DiagInner, DiagMessage, Level, MultiSpan, Style, Subdiag, SuggestionStyle,
32};
33
34/// Describes the way the content of the `rendered` field of the json output is generated
35#[derive(#[automatically_derived]
impl ::core::clone::Clone for HumanReadableErrorType {
    #[inline]
    fn clone(&self) -> HumanReadableErrorType {
        let _: ::core::clone::AssertParamIsClone<bool>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for HumanReadableErrorType { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for HumanReadableErrorType {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "HumanReadableErrorType", "short", &self.short, "unicode",
            &&self.unicode)
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for HumanReadableErrorType {
    #[inline]
    fn eq(&self, other: &HumanReadableErrorType) -> bool {
        self.short == other.short && self.unicode == other.unicode
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for HumanReadableErrorType {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_receiver_is_total_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<bool>;
    }
}Eq)]
36pub struct HumanReadableErrorType {
37    pub short: bool,
38    pub unicode: bool,
39}
40
41impl HumanReadableErrorType {
42    pub fn short(&self) -> bool {
43        self.short
44    }
45}
46
47pub enum TimingEvent {
48    Start,
49    End,
50}
51
52pub type DynEmitter = dyn Emitter + DynSend;
53
54/// Emitter trait for emitting errors and other structured information.
55pub trait Emitter {
56    /// Emit a structured diagnostic.
57    fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
58
59    /// Emit a notification that an artifact has been output.
60    /// Currently only supported for the JSON format.
61    fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
62
63    /// Emit a timestamp with start/end of a timing section.
64    /// Currently only supported for the JSON format.
65    fn emit_timing_section(&mut self, _record: TimingRecord, _event: TimingEvent) {}
66
67    /// Emit a report about future breakage.
68    /// Currently only supported for the JSON format.
69    fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}
70
71    /// Emit list of unused externs.
72    /// Currently only supported for the JSON format.
73    fn emit_unused_externs(
74        &mut self,
75        _lint_level: rustc_lint_defs::Level,
76        _unused_externs: &[&str],
77    ) {
78    }
79
80    /// Checks if should show explanations about "rustc --explain"
81    fn should_show_explain(&self) -> bool {
82        true
83    }
84
85    /// Checks if we can use colors in the current output stream.
86    fn supports_color(&self) -> bool {
87        false
88    }
89
90    fn source_map(&self) -> Option<&SourceMap>;
91
92    fn translator(&self) -> &Translator;
93
94    /// Formats the substitutions of the primary_span
95    ///
96    /// There are a lot of conditions to this method, but in short:
97    ///
98    /// * If the current `DiagInner` has only one visible `CodeSuggestion`,
99    ///   we format the `help` suggestion depending on the content of the
100    ///   substitutions. In that case, we modify the span and clear the
101    ///   suggestions.
102    ///
103    /// * If the current `DiagInner` has multiple suggestions,
104    ///   we leave `primary_span` and the suggestions untouched.
105    fn primary_span_formatted(
106        &self,
107        primary_span: &mut MultiSpan,
108        suggestions: &mut Vec<CodeSuggestion>,
109        fluent_args: &FluentArgs<'_>,
110    ) {
111        if let Some((sugg, rest)) = suggestions.split_first() {
112            let msg = self
113                .translator()
114                .translate_message(&sugg.msg, fluent_args)
115                .map_err(Report::new)
116                .unwrap();
117            if rest.is_empty()
118               // ^ if there is only one suggestion
119               // don't display multi-suggestions as labels
120               && let [substitution] = sugg.substitutions.as_slice()
121               // don't display multipart suggestions as labels
122               && let [part] = substitution.parts.as_slice()
123               // don't display long messages as labels
124               && msg.split_whitespace().count() < 10
125               // don't display multiline suggestions as labels
126               && !part.snippet.contains('\n')
127               && ![
128                    // when this style is set we want the suggestion to be a message, not inline
129                    SuggestionStyle::HideCodeAlways,
130                    // trivial suggestion for tooling's sake, never shown
131                    SuggestionStyle::CompletelyHidden,
132                    // subtle suggestion, never shown inline
133                    SuggestionStyle::ShowAlways,
134               ].contains(&sugg.style)
135            {
136                let snippet = part.snippet.trim();
137                let msg = if snippet.is_empty() || sugg.style.hide_inline() {
138                    // This substitution is only removal OR we explicitly don't want to show the
139                    // code inline (`hide_inline`). Therefore, we don't show the substitution.
140                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("help: {0}", msg))
    })format!("help: {msg}")
141                } else {
142                    // Show the default suggestion text with the substitution
143                    let confusion_type = self
144                        .source_map()
145                        .map(|sm| detect_confusion_type(sm, snippet, part.span))
146                        .unwrap_or(ConfusionType::None);
147                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("help: {0}{1}: `{2}`", msg,
                confusion_type.label_text(), snippet))
    })format!("help: {}{}: `{}`", msg, confusion_type.label_text(), snippet,)
148                };
149                primary_span.push_span_label(part.span, msg);
150
151                // We return only the modified primary_span
152                suggestions.clear();
153            } else {
154                // if there are multiple suggestions, print them all in full
155                // to be consistent. We could try to figure out if we can
156                // make one (or the first one) inline, but that would give
157                // undue importance to a semi-random suggestion
158            }
159        } else {
160            // do nothing
161        }
162    }
163
164    fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
165        &self,
166        span: &mut MultiSpan,
167        children: &mut Vec<Subdiag>,
168        level: &Level,
169        backtrace: bool,
170    ) {
171        // Check for spans in macros, before `fix_multispans_in_extern_macros`
172        // has a chance to replace them.
173        let has_macro_spans: Vec<_> = iter::once(&*span)
174            .chain(children.iter().map(|child| &child.span))
175            .flat_map(|span| span.primary_spans())
176            .flat_map(|sp| sp.macro_backtrace())
177            .filter_map(|expn_data| {
178                match expn_data.kind {
179                    ExpnKind::Root => None,
180
181                    // Skip past non-macro entries, just in case there
182                    // are some which do actually involve macros.
183                    ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
184
185                    ExpnKind::Macro(macro_kind, name) => {
186                        Some((macro_kind, name, expn_data.hide_backtrace))
187                    }
188                }
189            })
190            .collect();
191
192        if !backtrace {
193            self.fix_multispans_in_extern_macros(span, children);
194        }
195
196        self.render_multispans_macro_backtrace(span, children, backtrace);
197
198        if !backtrace {
199            // Skip builtin macros, as their expansion isn't relevant to the end user. This includes
200            // actual intrinsics, like `asm!`.
201            if let Some((macro_kind, name, _)) = has_macro_spans.first()
202                && let Some((_, _, false)) = has_macro_spans.last()
203            {
204                // Mark the actual macro this originates from
205                let and_then = if let Some((macro_kind, last_name, _)) = has_macro_spans.last()
206                    && last_name != name
207                {
208                    let descr = macro_kind.descr();
209                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(" which comes from the expansion of the {0} `{1}`",
                descr, last_name))
    })format!(" which comes from the expansion of the {descr} `{last_name}`")
210                } else {
211                    "".to_string()
212                };
213
214                let descr = macro_kind.descr();
215                let msg = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("this {0} originates in the {1} `{2}`{3} (in Nightly builds, run with -Z macro-backtrace for more info)",
                level, descr, name, and_then))
    })format!(
216                    "this {level} originates in the {descr} `{name}`{and_then} \
217                    (in Nightly builds, run with -Z macro-backtrace for more info)",
218                );
219
220                children.push(Subdiag {
221                    level: Level::Note,
222                    messages: <[_]>::into_vec(::alloc::boxed::box_new([(DiagMessage::from(msg),
                    Style::NoStyle)]))vec![(DiagMessage::from(msg), Style::NoStyle)],
223                    span: MultiSpan::new(),
224                });
225            }
226        }
227    }
228
229    fn render_multispans_macro_backtrace(
230        &self,
231        span: &mut MultiSpan,
232        children: &mut Vec<Subdiag>,
233        backtrace: bool,
234    ) {
235        for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
236            self.render_multispan_macro_backtrace(span, backtrace);
237        }
238    }
239
240    fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
241        let mut new_labels = FxIndexSet::default();
242
243        for &sp in span.primary_spans() {
244            if sp.is_dummy() {
245                continue;
246            }
247
248            // FIXME(eddyb) use `retain` on `macro_backtrace` to remove all the
249            // entries we don't want to print, to make sure the indices being
250            // printed are contiguous (or omitted if there's only one entry).
251            let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
252            for (i, trace) in macro_backtrace.iter().rev().enumerate() {
253                if trace.def_site.is_dummy() {
254                    continue;
255                }
256
257                if always_backtrace {
258                    new_labels.insert((
259                        trace.def_site,
260                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("in this expansion of `{0}`{1}",
                trace.kind.descr(),
                if macro_backtrace.len() > 1 {
                    ::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!(" (#{0})", i + 1))
                        })
                } else { String::new() }))
    })format!(
261                            "in this expansion of `{}`{}",
262                            trace.kind.descr(),
263                            if macro_backtrace.len() > 1 {
264                                // if macro_backtrace.len() == 1 it'll be
265                                // pointed at by "in this macro invocation"
266                                format!(" (#{})", i + 1)
267                            } else {
268                                String::new()
269                            },
270                        ),
271                    ));
272                }
273
274                // Don't add a label on the call site if the diagnostic itself
275                // already points to (a part of) that call site, as the label
276                // is meant for showing the relevant invocation when the actual
277                // diagnostic is pointing to some part of macro definition.
278                //
279                // This also handles the case where an external span got replaced
280                // with the call site span by `fix_multispans_in_extern_macros`.
281                //
282                // NB: `-Zmacro-backtrace` overrides this, for uniformity, as the
283                // "in this expansion of" label above is always added in that mode,
284                // and it needs an "in this macro invocation" label to match that.
285                let redundant_span = trace.call_site.contains(sp);
286
287                if !redundant_span || always_backtrace {
288                    let msg: Cow<'static, _> = match trace.kind {
289                        ExpnKind::Macro(MacroKind::Attr, _) => {
290                            "this attribute macro expansion".into()
291                        }
292                        ExpnKind::Macro(MacroKind::Derive, _) => {
293                            "this derive macro expansion".into()
294                        }
295                        ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
296                        ExpnKind::Root => "the crate root".into(),
297                        ExpnKind::AstPass(kind) => kind.descr().into(),
298                        ExpnKind::Desugaring(kind) => {
299                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("this {0} desugaring",
                kind.descr()))
    })format!("this {} desugaring", kind.descr()).into()
300                        }
301                    };
302                    new_labels.insert((
303                        trace.call_site,
304                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("in {0}{1}", msg,
                if macro_backtrace.len() > 1 && always_backtrace {
                    ::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!(" (#{0})", i + 1))
                        })
                } else { String::new() }))
    })format!(
305                            "in {}{}",
306                            msg,
307                            if macro_backtrace.len() > 1 && always_backtrace {
308                                // only specify order when the macro
309                                // backtrace is multiple levels deep
310                                format!(" (#{})", i + 1)
311                            } else {
312                                String::new()
313                            },
314                        ),
315                    ));
316                }
317                if !always_backtrace {
318                    break;
319                }
320            }
321        }
322
323        for (label_span, label_text) in new_labels {
324            span.push_span_label(label_span, label_text);
325        }
326    }
327
328    // This does a small "fix" for multispans by looking to see if it can find any that
329    // point directly at external macros. Since these are often difficult to read,
330    // this will change the span to point at the use site.
331    fn fix_multispans_in_extern_macros(&self, span: &mut MultiSpan, children: &mut Vec<Subdiag>) {
332        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_errors/src/emitter.rs:332",
                        "rustc_errors::emitter", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_errors/src/emitter.rs"),
                        ::tracing_core::__macro_support::Option::Some(332u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_errors::emitter"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("fix_multispans_in_extern_macros: before: span={0:?} children={1:?}",
                                                    span, children) as &dyn Value))])
            });
    } else { ; }
};debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
333        self.fix_multispan_in_extern_macros(span);
334        for child in children.iter_mut() {
335            self.fix_multispan_in_extern_macros(&mut child.span);
336        }
337        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_errors/src/emitter.rs:337",
                        "rustc_errors::emitter", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_errors/src/emitter.rs"),
                        ::tracing_core::__macro_support::Option::Some(337u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_errors::emitter"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("fix_multispans_in_extern_macros: after: span={0:?} children={1:?}",
                                                    span, children) as &dyn Value))])
            });
    } else { ; }
};debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
338    }
339
340    // This "fixes" MultiSpans that contain `Span`s pointing to locations inside of external macros.
341    // Since these locations are often difficult to read,
342    // we move these spans from the external macros to their corresponding use site.
343    fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) {
344        let Some(source_map) = self.source_map() else { return };
345        // First, find all the spans in external macros and point instead at their use site.
346        let replacements: Vec<(Span, Span)> = span
347            .primary_spans()
348            .iter()
349            .copied()
350            .chain(span.span_labels().iter().map(|sp_label| sp_label.span))
351            .filter_map(|sp| {
352                if !sp.is_dummy() && source_map.is_imported(sp) {
353                    let mut span = sp;
354                    while let Some(callsite) = span.parent_callsite() {
355                        span = callsite;
356                        if !source_map.is_imported(span) {
357                            return Some((sp, span));
358                        }
359                    }
360                }
361                None
362            })
363            .collect();
364
365        // After we have them, make sure we replace these 'bad' def sites with their use sites.
366        for (from, to) in replacements {
367            span.replace(from, to);
368        }
369    }
370}
371
372/// An emitter that adds a note to each diagnostic.
373pub struct EmitterWithNote {
374    pub emitter: Box<dyn Emitter + DynSend>,
375    pub note: String,
376}
377
378impl Emitter for EmitterWithNote {
379    fn source_map(&self) -> Option<&SourceMap> {
380        None
381    }
382
383    fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
384        diag.sub(Level::Note, self.note.clone(), MultiSpan::new());
385        self.emitter.emit_diagnostic(diag, registry);
386    }
387
388    fn translator(&self) -> &Translator {
389        self.emitter.translator()
390    }
391}
392
393pub struct SilentEmitter {
394    pub translator: Translator,
395}
396
397impl Emitter for SilentEmitter {
398    fn source_map(&self) -> Option<&SourceMap> {
399        None
400    }
401
402    fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {}
403
404    fn translator(&self) -> &Translator {
405        &self.translator
406    }
407}
408
409/// Maximum number of suggestions to be shown
410///
411/// Arbitrary, but taken from trait import suggestion limit
412pub const MAX_SUGGESTIONS: usize = 4;
413
414#[derive(#[automatically_derived]
impl ::core::clone::Clone for ColorConfig {
    #[inline]
    fn clone(&self) -> ColorConfig { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ColorConfig { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for ColorConfig {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ColorConfig::Auto => "Auto",
                ColorConfig::Always => "Always",
                ColorConfig::Never => "Never",
            })
    }
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for ColorConfig {
    #[inline]
    fn eq(&self, other: &ColorConfig) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ColorConfig {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_receiver_is_total_eq(&self) {}
}Eq)]
415pub enum ColorConfig {
416    Auto,
417    Always,
418    Never,
419}
420
421impl ColorConfig {
422    pub fn to_color_choice(self) -> ColorChoice {
423        match self {
424            ColorConfig::Always => {
425                if io::stderr().is_terminal() {
426                    ColorChoice::Always
427                } else {
428                    ColorChoice::AlwaysAnsi
429                }
430            }
431            ColorConfig::Never => ColorChoice::Never,
432            ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto,
433            ColorConfig::Auto => ColorChoice::Never,
434        }
435    }
436}
437
438#[derive(#[automatically_derived]
impl ::core::fmt::Debug for OutputTheme {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                OutputTheme::Ascii => "Ascii",
                OutputTheme::Unicode => "Unicode",
            })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for OutputTheme {
    #[inline]
    fn clone(&self) -> OutputTheme { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for OutputTheme { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for OutputTheme {
    #[inline]
    fn eq(&self, other: &OutputTheme) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for OutputTheme {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_receiver_is_total_eq(&self) {}
}Eq)]
439pub enum OutputTheme {
440    Ascii,
441    Unicode,
442}
443
444// We replace some characters so the CLI output is always consistent and underlines aligned.
445// Keep the following list in sync with `rustc_span::char_width`.
446const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
447    // In terminals without Unicode support the following will be garbled, but in *all* terminals
448    // the underlying codepoint will be as well. We could gate this replacement behind a "unicode
449    // support" gate.
450    ('\0', "␀"),
451    ('\u{0001}', "␁"),
452    ('\u{0002}', "␂"),
453    ('\u{0003}', "␃"),
454    ('\u{0004}', "␄"),
455    ('\u{0005}', "␅"),
456    ('\u{0006}', "␆"),
457    ('\u{0007}', "␇"),
458    ('\u{0008}', "␈"),
459    ('\t', "    "), // We do our own tab replacement
460    ('\u{000b}', "␋"),
461    ('\u{000c}', "␌"),
462    ('\u{000d}', "␍"),
463    ('\u{000e}', "␎"),
464    ('\u{000f}', "␏"),
465    ('\u{0010}', "␐"),
466    ('\u{0011}', "␑"),
467    ('\u{0012}', "␒"),
468    ('\u{0013}', "␓"),
469    ('\u{0014}', "␔"),
470    ('\u{0015}', "␕"),
471    ('\u{0016}', "␖"),
472    ('\u{0017}', "␗"),
473    ('\u{0018}', "␘"),
474    ('\u{0019}', "␙"),
475    ('\u{001a}', "␚"),
476    ('\u{001b}', "␛"),
477    ('\u{001c}', "␜"),
478    ('\u{001d}', "␝"),
479    ('\u{001e}', "␞"),
480    ('\u{001f}', "␟"),
481    ('\u{007f}', "␡"),
482    ('\u{200d}', ""), // Replace ZWJ for consistent terminal output of grapheme clusters.
483    ('\u{202a}', "�"), // The following unicode text flow control characters are inconsistently
484    ('\u{202b}', "�"), // supported across CLIs and can cause confusion due to the bytes on disk
485    ('\u{202c}', "�"), // not corresponding to the visible source code, so we replace them always.
486    ('\u{202d}', "�"),
487    ('\u{202e}', "�"),
488    ('\u{2066}', "�"),
489    ('\u{2067}', "�"),
490    ('\u{2068}', "�"),
491    ('\u{2069}', "�"),
492];
493
494pub(crate) fn normalize_whitespace(s: &str) -> String {
495    const {
496        let mut i = 1;
497        while i < OUTPUT_REPLACEMENTS.len() {
498            if !(OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0) {
    {
        ::core::panicking::panic_fmt(format_args!("The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) and must contain no duplicate entries"));
    }
};assert!(
499                OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
500                "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
501                and must contain no duplicate entries"
502            );
503            i += 1;
504        }
505    }
506    // Scan the input string for a character in the ordered table above.
507    // If it's present, replace it with its alternative string (it can be more than 1 char!).
508    // Otherwise, retain the input char.
509    s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
510        match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
511            Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
512            _ => s.push(c),
513        }
514        s
515    })
516}
517
518pub type Destination = AutoStream<Box<dyn Write + Send>>;
519
520struct Buffy {
521    buffer_writer: std::io::Stderr,
522    buffer: Vec<u8>,
523}
524
525impl Write for Buffy {
526    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
527        self.buffer.write(buf)
528    }
529
530    fn flush(&mut self) -> io::Result<()> {
531        self.buffer_writer.write_all(&self.buffer)?;
532        self.buffer.clear();
533        Ok(())
534    }
535}
536
537impl Drop for Buffy {
538    fn drop(&mut self) {
539        if !self.buffer.is_empty() {
540            self.flush().unwrap();
541            {
    ::core::panicking::panic_fmt(format_args!("buffers need to be flushed in order to print their contents"));
};panic!("buffers need to be flushed in order to print their contents");
542        }
543    }
544}
545
546pub fn stderr_destination(color: ColorConfig) -> Destination {
547    let buffer_writer = std::io::stderr();
548    // We need to resolve `ColorChoice::Auto` before `Box`ing since
549    // `ColorChoice::Auto` on `dyn Write` will always resolve to `Never`
550    let choice = get_stderr_color_choice(color, &buffer_writer);
551    // On Windows we'll be performing global synchronization on the entire
552    // system for emitting rustc errors, so there's no need to buffer
553    // anything.
554    //
555    // On non-Windows we rely on the atomicity of `write` to ensure errors
556    // don't get all jumbled up.
557    if falsecfg!(windows) {
558        AutoStream::new(Box::new(buffer_writer), choice)
559    } else {
560        let buffer = Vec::new();
561        AutoStream::new(Box::new(Buffy { buffer_writer, buffer }), choice)
562    }
563}
564
565pub fn get_stderr_color_choice(color: ColorConfig, stderr: &std::io::Stderr) -> ColorChoice {
566    let choice = color.to_color_choice();
567    if #[allow(non_exhaustive_omitted_patterns)] match choice {
    ColorChoice::Auto => true,
    _ => false,
}matches!(choice, ColorChoice::Auto) { AutoStream::choice(stderr) } else { choice }
568}
569
570/// On Windows, BRIGHT_BLUE is hard to read on black. Use cyan instead.
571///
572/// See #36178.
573const BRIGHT_BLUE: anstyle::Style = if falsecfg!(windows) {
574    AnsiColor::BrightCyan.on_default()
575} else {
576    AnsiColor::BrightBlue.on_default()
577};
578
579impl Style {
580    pub(crate) fn anstyle(&self, lvl: Level) -> anstyle::Style {
581        match self {
582            Style::Addition => AnsiColor::BrightGreen.on_default(),
583            Style::Removal => AnsiColor::BrightRed.on_default(),
584            Style::LineAndColumn => anstyle::Style::new(),
585            Style::LineNumber => BRIGHT_BLUE.effects(Effects::BOLD),
586            Style::Quotation => anstyle::Style::new(),
587            Style::MainHeaderMsg => if falsecfg!(windows) {
588                AnsiColor::BrightWhite.on_default()
589            } else {
590                anstyle::Style::new()
591            }
592            .effects(Effects::BOLD),
593            Style::UnderlinePrimary | Style::LabelPrimary => lvl.color().effects(Effects::BOLD),
594            Style::UnderlineSecondary | Style::LabelSecondary => BRIGHT_BLUE.effects(Effects::BOLD),
595            Style::HeaderMsg | Style::NoStyle => anstyle::Style::new(),
596            Style::Level(lvl) => lvl.color().effects(Effects::BOLD),
597            Style::Highlight => AnsiColor::Magenta.on_default().effects(Effects::BOLD),
598        }
599    }
600}
601
602/// Whether the original and suggested code are the same.
603pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
604    let found = match sm.span_to_snippet(sp) {
605        Ok(snippet) => snippet,
606        Err(e) => {
607            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_errors/src/emitter.rs:607",
                        "rustc_errors::emitter", ::tracing::Level::WARN,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_errors/src/emitter.rs"),
                        ::tracing_core::__macro_support::Option::Some(607u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_errors::emitter"),
                        ::tracing_core::field::FieldSet::new(&["message", "error"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::WARN <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::WARN <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("Invalid span {0:?}",
                                                    sp) as &dyn Value)),
                                (&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&debug(&e) as
                                            &dyn Value))])
            });
    } else { ; }
};warn!(error = ?e, "Invalid span {:?}", sp);
608            return true;
609        }
610    };
611    found != suggested
612}
613
614/// Whether the original and suggested code are visually similar enough to warrant extra wording.
615pub fn detect_confusion_type(sm: &SourceMap, suggested: &str, sp: Span) -> ConfusionType {
616    let found = match sm.span_to_snippet(sp) {
617        Ok(snippet) => snippet,
618        Err(e) => {
619            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_errors/src/emitter.rs:619",
                        "rustc_errors::emitter", ::tracing::Level::WARN,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_errors/src/emitter.rs"),
                        ::tracing_core::__macro_support::Option::Some(619u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_errors::emitter"),
                        ::tracing_core::field::FieldSet::new(&["message", "error"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::WARN <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::WARN <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("Invalid span {0:?}",
                                                    sp) as &dyn Value)),
                                (&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&debug(&e) as
                                            &dyn Value))])
            });
    } else { ; }
};warn!(error = ?e, "Invalid span {:?}", sp);
620            return ConfusionType::None;
621        }
622    };
623
624    let mut has_case_confusion = false;
625    let mut has_digit_letter_confusion = false;
626
627    if found.len() == suggested.len() {
628        let mut has_case_diff = false;
629        let mut has_digit_letter_confusable = false;
630        let mut has_other_diff = false;
631
632        // Letters whose lowercase version is very similar to the uppercase
633        // version.
634        let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
635
636        let digit_letter_confusables = [('0', 'O'), ('1', 'l'), ('5', 'S'), ('8', 'B'), ('9', 'g')];
637
638        for (f, s) in iter::zip(found.chars(), suggested.chars()) {
639            if f != s {
640                if f.eq_ignore_ascii_case(&s) {
641                    // Check for case differences (any character that differs only in case)
642                    if ascii_confusables.contains(&f) || ascii_confusables.contains(&s) {
643                        has_case_diff = true;
644                    } else {
645                        has_other_diff = true;
646                    }
647                } else if digit_letter_confusables.contains(&(f, s))
648                    || digit_letter_confusables.contains(&(s, f))
649                {
650                    // Check for digit-letter confusables (like 0 vs O, 1 vs l, etc.)
651                    has_digit_letter_confusable = true;
652                } else {
653                    has_other_diff = true;
654                }
655            }
656        }
657
658        // If we have case differences and no other differences
659        if has_case_diff && !has_other_diff && found != suggested {
660            has_case_confusion = true;
661        }
662        if has_digit_letter_confusable && !has_other_diff && found != suggested {
663            has_digit_letter_confusion = true;
664        }
665    }
666
667    match (has_case_confusion, has_digit_letter_confusion) {
668        (true, true) => ConfusionType::Both,
669        (true, false) => ConfusionType::Case,
670        (false, true) => ConfusionType::DigitLetter,
671        (false, false) => ConfusionType::None,
672    }
673}
674
675/// Represents the type of confusion detected between original and suggested code.
676#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ConfusionType {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ConfusionType::None => "None",
                ConfusionType::Case => "Case",
                ConfusionType::DigitLetter => "DigitLetter",
                ConfusionType::Both => "Both",
            })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ConfusionType {
    #[inline]
    fn clone(&self) -> ConfusionType { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ConfusionType { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for ConfusionType {
    #[inline]
    fn eq(&self, other: &ConfusionType) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ConfusionType {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_receiver_is_total_eq(&self) {}
}Eq)]
677pub enum ConfusionType {
678    /// No confusion detected
679    None,
680    /// Only case differences (e.g., "hello" vs "Hello")
681    Case,
682    /// Only digit-letter confusion (e.g., "0" vs "O", "1" vs "l")
683    DigitLetter,
684    /// Both case and digit-letter confusion
685    Both,
686}
687
688impl ConfusionType {
689    /// Returns the appropriate label text for this confusion type.
690    pub fn label_text(&self) -> &'static str {
691        match self {
692            ConfusionType::None => "",
693            ConfusionType::Case => " (notice the capitalization)",
694            ConfusionType::DigitLetter => " (notice the digit/letter confusion)",
695            ConfusionType::Both => " (notice the capitalization and digit/letter confusion)",
696        }
697    }
698
699    /// Combines two confusion types. If either is `Both`, the result is `Both`.
700    /// If one is `Case` and the other is `DigitLetter`, the result is `Both`.
701    /// Otherwise, returns the non-`None` type, or `None` if both are `None`.
702    pub fn combine(self, other: ConfusionType) -> ConfusionType {
703        match (self, other) {
704            (ConfusionType::None, other) => other,
705            (this, ConfusionType::None) => this,
706            (ConfusionType::Both, _) | (_, ConfusionType::Both) => ConfusionType::Both,
707            (ConfusionType::Case, ConfusionType::DigitLetter)
708            | (ConfusionType::DigitLetter, ConfusionType::Case) => ConfusionType::Both,
709            (ConfusionType::Case, ConfusionType::Case) => ConfusionType::Case,
710            (ConfusionType::DigitLetter, ConfusionType::DigitLetter) => ConfusionType::DigitLetter,
711        }
712    }
713
714    /// Returns true if this confusion type represents any kind of confusion.
715    pub fn has_confusion(&self) -> bool {
716        *self != ConfusionType::None
717    }
718}
719
720pub(crate) fn should_show_source_code(
721    ignored_directories: &[String],
722    sm: &SourceMap,
723    file: &SourceFile,
724) -> bool {
725    if !sm.ensure_source_file_source_present(file) {
726        return false;
727    }
728
729    let FileName::Real(name) = &file.name else { return true };
730    name.local_path()
731        .map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
732        .unwrap_or(true)
733}