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