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::cmp::{Reverse, max, min};
12use std::error::Report;
13use std::io::prelude::*;
14use std::io::{self, IsTerminal};
15use std::iter;
16use std::path::Path;
17use std::sync::Arc;
18
19use anstream::{AutoStream, ColorChoice};
20use anstyle::{AnsiColor, Effects};
21use derive_setters::Setters;
22use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
23use rustc_data_structures::sync::{DynSend, IntoDynSyncSend};
24use rustc_error_messages::{FluentArgs, SpanLabel};
25use rustc_lexer;
26use rustc_lint_defs::pluralize;
27use rustc_span::hygiene::{ExpnKind, MacroKind};
28use rustc_span::source_map::SourceMap;
29use rustc_span::{FileLines, FileName, SourceFile, Span, char_width, str_width};
30use tracing::{debug, instrument, trace, warn};
31
32use crate::registry::Registry;
33use crate::snippet::{
34    Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString,
35};
36use crate::styled_buffer::StyledBuffer;
37use crate::timings::TimingRecord;
38use crate::translation::{Translator, to_fluent_args};
39use crate::{
40    CodeSuggestion, DiagInner, DiagMessage, ErrCode, Level, MultiSpan, Subdiag,
41    SubstitutionHighlight, SuggestionStyle, TerminalUrl,
42};
43
44/// Default column width, used in tests and when terminal dimensions cannot be determined.
45const DEFAULT_COLUMN_WIDTH: usize = 140;
46
47/// Describes the way the content of the `rendered` field of the json output is generated
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum HumanReadableErrorType {
50    Default { short: bool },
51    AnnotateSnippet { short: bool, unicode: bool },
52}
53
54impl HumanReadableErrorType {
55    pub fn short(&self) -> bool {
56        match self {
57            HumanReadableErrorType::Default { short }
58            | HumanReadableErrorType::AnnotateSnippet { short, .. } => *short,
59        }
60    }
61}
62
63#[derive(Clone, Copy, Debug)]
64struct Margin {
65    /// The available whitespace in the left that can be consumed when centering.
66    pub whitespace_left: usize,
67    /// The column of the beginning of leftmost span.
68    pub span_left: usize,
69    /// The column of the end of rightmost span.
70    pub span_right: usize,
71    /// The beginning of the line to be displayed.
72    pub computed_left: usize,
73    /// The end of the line to be displayed.
74    pub computed_right: usize,
75    /// The current width of the terminal. Uses value of `DEFAULT_COLUMN_WIDTH` constant by default
76    /// and in tests.
77    pub column_width: usize,
78    /// The end column of a span label, including the span. Doesn't account for labels not in the
79    /// same line as the span.
80    pub label_right: usize,
81}
82
83impl Margin {
84    fn new(
85        whitespace_left: usize,
86        span_left: usize,
87        span_right: usize,
88        label_right: usize,
89        column_width: usize,
90        max_line_len: usize,
91    ) -> Self {
92        // The 6 is padding to give a bit of room for `...` when displaying:
93        // ```
94        // error: message
95        //   --> file.rs:16:58
96        //    |
97        // 16 | ... fn foo(self) -> Self::Bar {
98        //    |                     ^^^^^^^^^
99        // ```
100
101        let mut m = Margin {
102            whitespace_left: whitespace_left.saturating_sub(6),
103            span_left: span_left.saturating_sub(6),
104            span_right: span_right + 6,
105            computed_left: 0,
106            computed_right: 0,
107            column_width,
108            label_right: label_right + 6,
109        };
110        m.compute(max_line_len);
111        m
112    }
113
114    fn was_cut_left(&self) -> bool {
115        self.computed_left > 0
116    }
117
118    fn compute(&mut self, max_line_len: usize) {
119        // When there's a lot of whitespace (>20), we want to trim it as it is useless.
120        // FIXME: this doesn't account for '\t', but to do so correctly we need to perform that
121        // calculation later, right before printing in order to be accurate with both unicode
122        // handling and trimming of long lines.
123        self.computed_left = if self.whitespace_left > 20 {
124            self.whitespace_left - 16 // We want some padding.
125        } else {
126            0
127        };
128        // We want to show as much as possible, max_line_len is the rightmost boundary for the
129        // relevant code.
130        self.computed_right = max(max_line_len, self.computed_left);
131
132        if self.computed_right - self.computed_left > self.column_width {
133            // Trimming only whitespace isn't enough, let's get craftier.
134            if self.label_right - self.whitespace_left <= self.column_width {
135                // Attempt to fit the code window only trimming whitespace.
136                self.computed_left = self.whitespace_left;
137                self.computed_right = self.computed_left + self.column_width;
138            } else if self.label_right - self.span_left <= self.column_width {
139                // Attempt to fit the code window considering only the spans and labels.
140                let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
141                self.computed_left = self.span_left.saturating_sub(padding_left);
142                self.computed_right = self.computed_left + self.column_width;
143            } else if self.span_right - self.span_left <= self.column_width {
144                // Attempt to fit the code window considering the spans and labels plus padding.
145                let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
146                self.computed_left = self.span_left.saturating_sub(padding_left);
147                self.computed_right = self.computed_left + self.column_width;
148            } else {
149                // Mostly give up but still don't show the full line.
150                self.computed_left = self.span_left;
151                self.computed_right = self.span_right;
152            }
153        }
154    }
155
156    fn left(&self, line_len: usize) -> usize {
157        min(self.computed_left, line_len)
158    }
159
160    fn right(&self, line_len: usize) -> usize {
161        if line_len.saturating_sub(self.computed_left) <= self.column_width {
162            line_len
163        } else {
164            min(line_len, self.computed_right)
165        }
166    }
167}
168
169pub enum TimingEvent {
170    Start,
171    End,
172}
173
174const ANONYMIZED_LINE_NUM: &str = "LL";
175
176pub type DynEmitter = dyn Emitter + DynSend;
177
178/// Emitter trait for emitting errors and other structured information.
179pub trait Emitter {
180    /// Emit a structured diagnostic.
181    fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
182
183    /// Emit a notification that an artifact has been output.
184    /// Currently only supported for the JSON format.
185    fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
186
187    /// Emit a timestamp with start/end of a timing section.
188    /// Currently only supported for the JSON format.
189    fn emit_timing_section(&mut self, _record: TimingRecord, _event: TimingEvent) {}
190
191    /// Emit a report about future breakage.
192    /// Currently only supported for the JSON format.
193    fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}
194
195    /// Emit list of unused externs.
196    /// Currently only supported for the JSON format.
197    fn emit_unused_externs(
198        &mut self,
199        _lint_level: rustc_lint_defs::Level,
200        _unused_externs: &[&str],
201    ) {
202    }
203
204    /// Checks if should show explanations about "rustc --explain"
205    fn should_show_explain(&self) -> bool {
206        true
207    }
208
209    /// Checks if we can use colors in the current output stream.
210    fn supports_color(&self) -> bool {
211        false
212    }
213
214    fn source_map(&self) -> Option<&SourceMap>;
215
216    fn translator(&self) -> &Translator;
217
218    /// Formats the substitutions of the primary_span
219    ///
220    /// There are a lot of conditions to this method, but in short:
221    ///
222    /// * If the current `DiagInner` has only one visible `CodeSuggestion`,
223    ///   we format the `help` suggestion depending on the content of the
224    ///   substitutions. In that case, we modify the span and clear the
225    ///   suggestions.
226    ///
227    /// * If the current `DiagInner` has multiple suggestions,
228    ///   we leave `primary_span` and the suggestions untouched.
229    fn primary_span_formatted(
230        &self,
231        primary_span: &mut MultiSpan,
232        suggestions: &mut Vec<CodeSuggestion>,
233        fluent_args: &FluentArgs<'_>,
234    ) {
235        if let Some((sugg, rest)) = suggestions.split_first() {
236            let msg = self
237                .translator()
238                .translate_message(&sugg.msg, fluent_args)
239                .map_err(Report::new)
240                .unwrap();
241            if rest.is_empty()
242               // ^ if there is only one suggestion
243               // don't display multi-suggestions as labels
244               && let [substitution] = sugg.substitutions.as_slice()
245               // don't display multipart suggestions as labels
246               && let [part] = substitution.parts.as_slice()
247               // don't display long messages as labels
248               && msg.split_whitespace().count() < 10
249               // don't display multiline suggestions as labels
250               && !part.snippet.contains('\n')
251               && ![
252                    // when this style is set we want the suggestion to be a message, not inline
253                    SuggestionStyle::HideCodeAlways,
254                    // trivial suggestion for tooling's sake, never shown
255                    SuggestionStyle::CompletelyHidden,
256                    // subtle suggestion, never shown inline
257                    SuggestionStyle::ShowAlways,
258               ].contains(&sugg.style)
259            {
260                let snippet = part.snippet.trim();
261                let msg = if snippet.is_empty() || sugg.style.hide_inline() {
262                    // This substitution is only removal OR we explicitly don't want to show the
263                    // code inline (`hide_inline`). Therefore, we don't show the substitution.
264                    format!("help: {msg}")
265                } else {
266                    // Show the default suggestion text with the substitution
267                    let confusion_type = self
268                        .source_map()
269                        .map(|sm| detect_confusion_type(sm, snippet, part.span))
270                        .unwrap_or(ConfusionType::None);
271                    format!("help: {}{}: `{}`", msg, confusion_type.label_text(), snippet,)
272                };
273                primary_span.push_span_label(part.span, msg);
274
275                // We return only the modified primary_span
276                suggestions.clear();
277            } else {
278                // if there are multiple suggestions, print them all in full
279                // to be consistent. We could try to figure out if we can
280                // make one (or the first one) inline, but that would give
281                // undue importance to a semi-random suggestion
282            }
283        } else {
284            // do nothing
285        }
286    }
287
288    fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
289        &self,
290        span: &mut MultiSpan,
291        children: &mut Vec<Subdiag>,
292        level: &Level,
293        backtrace: bool,
294    ) {
295        // Check for spans in macros, before `fix_multispans_in_extern_macros`
296        // has a chance to replace them.
297        let has_macro_spans: Vec<_> = iter::once(&*span)
298            .chain(children.iter().map(|child| &child.span))
299            .flat_map(|span| span.primary_spans())
300            .flat_map(|sp| sp.macro_backtrace())
301            .filter_map(|expn_data| {
302                match expn_data.kind {
303                    ExpnKind::Root => None,
304
305                    // Skip past non-macro entries, just in case there
306                    // are some which do actually involve macros.
307                    ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
308
309                    ExpnKind::Macro(macro_kind, name) => {
310                        Some((macro_kind, name, expn_data.hide_backtrace))
311                    }
312                }
313            })
314            .collect();
315
316        if !backtrace {
317            self.fix_multispans_in_extern_macros(span, children);
318        }
319
320        self.render_multispans_macro_backtrace(span, children, backtrace);
321
322        if !backtrace {
323            // Skip builtin macros, as their expansion isn't relevant to the end user. This includes
324            // actual intrinsics, like `asm!`.
325            if let Some((macro_kind, name, _)) = has_macro_spans.first()
326                && let Some((_, _, false)) = has_macro_spans.last()
327            {
328                // Mark the actual macro this originates from
329                let and_then = if let Some((macro_kind, last_name, _)) = has_macro_spans.last()
330                    && last_name != name
331                {
332                    let descr = macro_kind.descr();
333                    format!(" which comes from the expansion of the {descr} `{last_name}`")
334                } else {
335                    "".to_string()
336                };
337
338                let descr = macro_kind.descr();
339                let msg = format!(
340                    "this {level} originates in the {descr} `{name}`{and_then} \
341                    (in Nightly builds, run with -Z macro-backtrace for more info)",
342                );
343
344                children.push(Subdiag {
345                    level: Level::Note,
346                    messages: vec![(DiagMessage::from(msg), Style::NoStyle)],
347                    span: MultiSpan::new(),
348                });
349            }
350        }
351    }
352
353    fn render_multispans_macro_backtrace(
354        &self,
355        span: &mut MultiSpan,
356        children: &mut Vec<Subdiag>,
357        backtrace: bool,
358    ) {
359        for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
360            self.render_multispan_macro_backtrace(span, backtrace);
361        }
362    }
363
364    fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
365        let mut new_labels = FxIndexSet::default();
366
367        for &sp in span.primary_spans() {
368            if sp.is_dummy() {
369                continue;
370            }
371
372            // FIXME(eddyb) use `retain` on `macro_backtrace` to remove all the
373            // entries we don't want to print, to make sure the indices being
374            // printed are contiguous (or omitted if there's only one entry).
375            let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
376            for (i, trace) in macro_backtrace.iter().rev().enumerate() {
377                if trace.def_site.is_dummy() {
378                    continue;
379                }
380
381                if always_backtrace {
382                    new_labels.insert((
383                        trace.def_site,
384                        format!(
385                            "in this expansion of `{}`{}",
386                            trace.kind.descr(),
387                            if macro_backtrace.len() > 1 {
388                                // if macro_backtrace.len() == 1 it'll be
389                                // pointed at by "in this macro invocation"
390                                format!(" (#{})", i + 1)
391                            } else {
392                                String::new()
393                            },
394                        ),
395                    ));
396                }
397
398                // Don't add a label on the call site if the diagnostic itself
399                // already points to (a part of) that call site, as the label
400                // is meant for showing the relevant invocation when the actual
401                // diagnostic is pointing to some part of macro definition.
402                //
403                // This also handles the case where an external span got replaced
404                // with the call site span by `fix_multispans_in_extern_macros`.
405                //
406                // NB: `-Zmacro-backtrace` overrides this, for uniformity, as the
407                // "in this expansion of" label above is always added in that mode,
408                // and it needs an "in this macro invocation" label to match that.
409                let redundant_span = trace.call_site.contains(sp);
410
411                if !redundant_span || always_backtrace {
412                    let msg: Cow<'static, _> = match trace.kind {
413                        ExpnKind::Macro(MacroKind::Attr, _) => {
414                            "this attribute macro expansion".into()
415                        }
416                        ExpnKind::Macro(MacroKind::Derive, _) => {
417                            "this derive macro expansion".into()
418                        }
419                        ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
420                        ExpnKind::Root => "the crate root".into(),
421                        ExpnKind::AstPass(kind) => kind.descr().into(),
422                        ExpnKind::Desugaring(kind) => {
423                            format!("this {} desugaring", kind.descr()).into()
424                        }
425                    };
426                    new_labels.insert((
427                        trace.call_site,
428                        format!(
429                            "in {}{}",
430                            msg,
431                            if macro_backtrace.len() > 1 && always_backtrace {
432                                // only specify order when the macro
433                                // backtrace is multiple levels deep
434                                format!(" (#{})", i + 1)
435                            } else {
436                                String::new()
437                            },
438                        ),
439                    ));
440                }
441                if !always_backtrace {
442                    break;
443                }
444            }
445        }
446
447        for (label_span, label_text) in new_labels {
448            span.push_span_label(label_span, label_text);
449        }
450    }
451
452    // This does a small "fix" for multispans by looking to see if it can find any that
453    // point directly at external macros. Since these are often difficult to read,
454    // this will change the span to point at the use site.
455    fn fix_multispans_in_extern_macros(&self, span: &mut MultiSpan, children: &mut Vec<Subdiag>) {
456        debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
457        self.fix_multispan_in_extern_macros(span);
458        for child in children.iter_mut() {
459            self.fix_multispan_in_extern_macros(&mut child.span);
460        }
461        debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
462    }
463
464    // This "fixes" MultiSpans that contain `Span`s pointing to locations inside of external macros.
465    // Since these locations are often difficult to read,
466    // we move these spans from the external macros to their corresponding use site.
467    fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) {
468        let Some(source_map) = self.source_map() else { return };
469        // First, find all the spans in external macros and point instead at their use site.
470        let replacements: Vec<(Span, Span)> = span
471            .primary_spans()
472            .iter()
473            .copied()
474            .chain(span.span_labels().iter().map(|sp_label| sp_label.span))
475            .filter_map(|sp| {
476                if !sp.is_dummy() && source_map.is_imported(sp) {
477                    let maybe_callsite = sp.source_callsite();
478                    if sp != maybe_callsite {
479                        return Some((sp, maybe_callsite));
480                    }
481                }
482                None
483            })
484            .collect();
485
486        // After we have them, make sure we replace these 'bad' def sites with their use sites.
487        for (from, to) in replacements {
488            span.replace(from, to);
489        }
490    }
491}
492
493impl Emitter for HumanEmitter {
494    fn source_map(&self) -> Option<&SourceMap> {
495        self.sm.as_deref()
496    }
497
498    fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
499        let fluent_args = to_fluent_args(diag.args.iter());
500
501        if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() {
502            diag.children.insert(0, diag.emitted_at_sub_diag());
503        }
504
505        let mut suggestions = diag.suggestions.unwrap_tag();
506        self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
507
508        self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
509            &mut diag.span,
510            &mut diag.children,
511            &diag.level,
512            self.macro_backtrace,
513        );
514
515        self.emit_messages_default(
516            &diag.level,
517            &diag.messages,
518            &fluent_args,
519            &diag.code,
520            &diag.span,
521            &diag.children,
522            &suggestions,
523        );
524    }
525
526    fn should_show_explain(&self) -> bool {
527        !self.short_message
528    }
529
530    fn translator(&self) -> &Translator {
531        &self.translator
532    }
533}
534
535/// An emitter that adds a note to each diagnostic.
536pub struct EmitterWithNote {
537    pub emitter: Box<dyn Emitter + DynSend>,
538    pub note: String,
539}
540
541impl Emitter for EmitterWithNote {
542    fn source_map(&self) -> Option<&SourceMap> {
543        None
544    }
545
546    fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
547        diag.sub(Level::Note, self.note.clone(), MultiSpan::new());
548        self.emitter.emit_diagnostic(diag, registry);
549    }
550
551    fn translator(&self) -> &Translator {
552        self.emitter.translator()
553    }
554}
555
556pub struct SilentEmitter {
557    pub translator: Translator,
558}
559
560impl Emitter for SilentEmitter {
561    fn source_map(&self) -> Option<&SourceMap> {
562        None
563    }
564
565    fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {}
566
567    fn translator(&self) -> &Translator {
568        &self.translator
569    }
570}
571
572/// Maximum number of suggestions to be shown
573///
574/// Arbitrary, but taken from trait import suggestion limit
575pub const MAX_SUGGESTIONS: usize = 4;
576
577#[derive(Clone, Copy, Debug, PartialEq, Eq)]
578pub enum ColorConfig {
579    Auto,
580    Always,
581    Never,
582}
583
584impl ColorConfig {
585    pub fn to_color_choice(self) -> ColorChoice {
586        match self {
587            ColorConfig::Always => {
588                if io::stderr().is_terminal() {
589                    ColorChoice::Always
590                } else {
591                    ColorChoice::AlwaysAnsi
592                }
593            }
594            ColorConfig::Never => ColorChoice::Never,
595            ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto,
596            ColorConfig::Auto => ColorChoice::Never,
597        }
598    }
599}
600
601#[derive(Debug, Clone, Copy, PartialEq, Eq)]
602pub enum OutputTheme {
603    Ascii,
604    Unicode,
605}
606
607/// Handles the writing of `HumanReadableErrorType::Default` and `HumanReadableErrorType::Short`
608#[derive(Setters)]
609pub struct HumanEmitter {
610    #[setters(skip)]
611    dst: IntoDynSyncSend<Destination>,
612    sm: Option<Arc<SourceMap>>,
613    #[setters(skip)]
614    translator: Translator,
615    short_message: bool,
616    ui_testing: bool,
617    ignored_directories_in_source_blocks: Vec<String>,
618    diagnostic_width: Option<usize>,
619
620    macro_backtrace: bool,
621    track_diagnostics: bool,
622    terminal_url: TerminalUrl,
623    theme: OutputTheme,
624}
625
626#[derive(Debug)]
627pub(crate) struct FileWithAnnotatedLines {
628    pub(crate) file: Arc<SourceFile>,
629    pub(crate) lines: Vec<Line>,
630    multiline_depth: usize,
631}
632
633impl HumanEmitter {
634    pub fn new(dst: Destination, translator: Translator) -> HumanEmitter {
635        HumanEmitter {
636            dst: IntoDynSyncSend(dst),
637            sm: None,
638            translator,
639            short_message: false,
640            ui_testing: false,
641            ignored_directories_in_source_blocks: Vec::new(),
642            diagnostic_width: None,
643            macro_backtrace: false,
644            track_diagnostics: false,
645            terminal_url: TerminalUrl::No,
646            theme: OutputTheme::Ascii,
647        }
648    }
649
650    fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
651        if self.ui_testing {
652            Cow::Borrowed(ANONYMIZED_LINE_NUM)
653        } else {
654            Cow::Owned(line_num.to_string())
655        }
656    }
657
658    fn draw_line(
659        &self,
660        buffer: &mut StyledBuffer,
661        source_string: &str,
662        line_index: usize,
663        line_offset: usize,
664        width_offset: usize,
665        code_offset: usize,
666        margin: Margin,
667    ) -> usize {
668        let line_len = source_string.len();
669        // Create the source line we will highlight.
670        let left = margin.left(line_len);
671        let right = margin.right(line_len);
672        // FIXME: The following code looks fishy. See #132860.
673        // On long lines, we strip the source line, accounting for unicode.
674        let code: String = source_string
675            .chars()
676            .enumerate()
677            .skip_while(|(i, _)| *i < left)
678            .take_while(|(i, _)| *i < right)
679            .map(|(_, c)| c)
680            .collect();
681        let code = normalize_whitespace(&code);
682        let was_cut_right =
683            source_string.chars().enumerate().skip_while(|(i, _)| *i < right).next().is_some();
684        buffer.puts(line_offset, code_offset, &code, Style::Quotation);
685        let placeholder = self.margin();
686        if margin.was_cut_left() {
687            // We have stripped some code/whitespace from the beginning, make it clear.
688            buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber);
689        }
690        if was_cut_right {
691            let padding = str_width(placeholder);
692            // We have stripped some code after the rightmost span end, make it clear we did so.
693            buffer.puts(
694                line_offset,
695                code_offset + str_width(&code) - padding,
696                placeholder,
697                Style::LineNumber,
698            );
699        }
700        self.draw_line_num(buffer, line_index, line_offset, width_offset - 3);
701        self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2);
702        left
703    }
704
705    #[instrument(level = "trace", skip(self), ret)]
706    fn render_source_line(
707        &self,
708        buffer: &mut StyledBuffer,
709        file: Arc<SourceFile>,
710        line: &Line,
711        width_offset: usize,
712        code_offset: usize,
713        margin: Margin,
714        close_window: bool,
715    ) -> Vec<(usize, Style)> {
716        // Draw:
717        //
718        //   LL | ... code ...
719        //      |     ^^-^ span label
720        //      |       |
721        //      |       secondary span label
722        //
723        //   ^^ ^ ^^^ ^^^^ ^^^ we don't care about code too far to the right of a span, we trim it
724        //   |  | |   |
725        //   |  | |   actual code found in your source code and the spans we use to mark it
726        //   |  | when there's too much wasted space to the left, trim it
727        //   |  vertical divider between the column number and the code
728        //   column number
729
730        if line.line_index == 0 {
731            return Vec::new();
732        }
733
734        let Some(source_string) = file.get_line(line.line_index - 1) else {
735            return Vec::new();
736        };
737        trace!(?source_string);
738
739        let line_offset = buffer.num_lines();
740
741        // Left trim.
742        // FIXME: This looks fishy. See #132860.
743        let left = self.draw_line(
744            buffer,
745            &source_string,
746            line.line_index,
747            line_offset,
748            width_offset,
749            code_offset,
750            margin,
751        );
752
753        // Special case when there's only one annotation involved, it is the start of a multiline
754        // span and there's no text at the beginning of the code line. Instead of doing the whole
755        // graph:
756        //
757        // 2 |   fn foo() {
758        //   |  _^
759        // 3 | |
760        // 4 | | }
761        //   | |_^ test
762        //
763        // we simplify the output to:
764        //
765        // 2 | / fn foo() {
766        // 3 | |
767        // 4 | | }
768        //   | |_^ test
769        let mut buffer_ops = vec![];
770        let mut annotations = vec![];
771        let mut short_start = true;
772        for ann in &line.annotations {
773            if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
774                if source_string.chars().take(ann.start_col.file).all(|c| c.is_whitespace()) {
775                    let uline = self.underline(ann.is_primary);
776                    let chr = uline.multiline_whole_line;
777                    annotations.push((depth, uline.style));
778                    buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style));
779                } else {
780                    short_start = false;
781                    break;
782                }
783            } else if let AnnotationType::MultilineLine(_) = ann.annotation_type {
784            } else {
785                short_start = false;
786                break;
787            }
788        }
789        if short_start {
790            for (y, x, c, s) in buffer_ops {
791                buffer.putc(y, x, c, s);
792            }
793            return annotations;
794        }
795
796        // We want to display like this:
797        //
798        //      vec.push(vec.pop().unwrap());
799        //      ---      ^^^               - previous borrow ends here
800        //      |        |
801        //      |        error occurs here
802        //      previous borrow of `vec` occurs here
803        //
804        // But there are some weird edge cases to be aware of:
805        //
806        //      vec.push(vec.pop().unwrap());
807        //      --------                    - previous borrow ends here
808        //      ||
809        //      |this makes no sense
810        //      previous borrow of `vec` occurs here
811        //
812        // For this reason, we group the lines into "highlight lines"
813        // and "annotations lines", where the highlight lines have the `^`.
814
815        // Sort the annotations by (start, end col)
816        // The labels are reversed, sort and then reversed again.
817        // Consider a list of annotations (A1, A2, C1, C2, B1, B2) where
818        // the letter signifies the span. Here we are only sorting by the
819        // span and hence, the order of the elements with the same span will
820        // not change. On reversing the ordering (|a, b| but b.cmp(a)), you get
821        // (C1, C2, B1, B2, A1, A2). All the elements with the same span are
822        // still ordered first to last, but all the elements with different
823        // spans are ordered by their spans in last to first order. Last to
824        // first order is important, because the jiggly lines and | are on
825        // the left, so the rightmost span needs to be rendered first,
826        // otherwise the lines would end up needing to go over a message.
827
828        let mut annotations = line.annotations.clone();
829        annotations.sort_by_key(|a| Reverse(a.start_col));
830
831        // First, figure out where each label will be positioned.
832        //
833        // In the case where you have the following annotations:
834        //
835        //      vec.push(vec.pop().unwrap());
836        //      --------                    - previous borrow ends here [C]
837        //      ||
838        //      |this makes no sense [B]
839        //      previous borrow of `vec` occurs here [A]
840        //
841        // `annotations_position` will hold [(2, A), (1, B), (0, C)].
842        //
843        // We try, when possible, to stick the rightmost annotation at the end
844        // of the highlight line:
845        //
846        //      vec.push(vec.pop().unwrap());
847        //      ---      ---               - previous borrow ends here
848        //
849        // But sometimes that's not possible because one of the other
850        // annotations overlaps it. For example, from the test
851        // `span_overlap_label`, we have the following annotations
852        // (written on distinct lines for clarity):
853        //
854        //      fn foo(x: u32) {
855        //      --------------
856        //             -
857        //
858        // In this case, we can't stick the rightmost-most label on
859        // the highlight line, or we would get:
860        //
861        //      fn foo(x: u32) {
862        //      -------- x_span
863        //      |
864        //      fn_span
865        //
866        // which is totally weird. Instead we want:
867        //
868        //      fn foo(x: u32) {
869        //      --------------
870        //      |      |
871        //      |      x_span
872        //      fn_span
873        //
874        // which is...less weird, at least. In fact, in general, if
875        // the rightmost span overlaps with any other span, we should
876        // use the "hang below" version, so we can at least make it
877        // clear where the span *starts*. There's an exception for this
878        // logic, when the labels do not have a message:
879        //
880        //      fn foo(x: u32) {
881        //      --------------
882        //             |
883        //             x_span
884        //
885        // instead of:
886        //
887        //      fn foo(x: u32) {
888        //      --------------
889        //      |      |
890        //      |      x_span
891        //      <EMPTY LINE>
892        //
893        let mut overlap = vec![false; annotations.len()];
894        let mut annotations_position = vec![];
895        let mut line_len: usize = 0;
896        let mut p = 0;
897        for (i, annotation) in annotations.iter().enumerate() {
898            for (j, next) in annotations.iter().enumerate() {
899                if overlaps(next, annotation, 0) && j > i {
900                    overlap[i] = true;
901                    overlap[j] = true;
902                }
903                if overlaps(next, annotation, 0)  // This label overlaps with another one and both
904                    && annotation.has_label()     // take space (they have text and are not
905                    && j > i                      // multiline lines).
906                    && p == 0
907                // We're currently on the first line, move the label one line down
908                {
909                    // If we're overlapping with an un-labelled annotation with the same span
910                    // we can just merge them in the output
911                    if next.start_col == annotation.start_col
912                        && next.end_col == annotation.end_col
913                        && !next.has_label()
914                    {
915                        continue;
916                    }
917
918                    // This annotation needs a new line in the output.
919                    p += 1;
920                    break;
921                }
922            }
923            annotations_position.push((p, annotation));
924            for (j, next) in annotations.iter().enumerate() {
925                if j > i {
926                    let l = next.label.as_ref().map_or(0, |label| label.len() + 2);
927                    if (overlaps(next, annotation, l) // Do not allow two labels to be in the same
928                                                     // line if they overlap including padding, to
929                                                     // avoid situations like:
930                                                     //
931                                                     //      fn foo(x: u32) {
932                                                     //      -------^------
933                                                     //      |      |
934                                                     //      fn_spanx_span
935                                                     //
936                        && annotation.has_label()    // Both labels must have some text, otherwise
937                        && next.has_label())         // they are not overlapping.
938                                                     // Do not add a new line if this annotation
939                                                     // or the next are vertical line placeholders.
940                        || (annotation.takes_space() // If either this or the next annotation is
941                            && next.has_label())     // multiline start/end, move it to a new line
942                        || (annotation.has_label()   // so as not to overlap the horizontal lines.
943                            && next.takes_space())
944                        || (annotation.takes_space() && next.takes_space())
945                        || (overlaps(next, annotation, l)
946                            && next.end_col <= annotation.end_col
947                            && next.has_label()
948                            && p == 0)
949                    // Avoid #42595.
950                    {
951                        // This annotation needs a new line in the output.
952                        p += 1;
953                        break;
954                    }
955                }
956            }
957            line_len = max(line_len, p);
958        }
959
960        if line_len != 0 {
961            line_len += 1;
962        }
963
964        // If there are no annotations or the only annotations on this line are
965        // MultilineLine, then there's only code being shown, stop processing.
966        if line.annotations.iter().all(|a| a.is_line()) {
967            return vec![];
968        }
969
970        if annotations_position
971            .iter()
972            .all(|(_, ann)| matches!(ann.annotation_type, AnnotationType::MultilineStart(_)))
973            && let Some(max_pos) = annotations_position.iter().map(|(pos, _)| *pos).max()
974        {
975            // Special case the following, so that we minimize overlapping multiline spans.
976            //
977            // 3 │       X0 Y0 Z0
978            //   │ ┏━━━━━┛  │  │     < We are writing these lines
979            //   │ ┃┌───────┘  │     < by reverting the "depth" of
980            //   │ ┃│┌─────────┘     < their multiline spans.
981            // 4 │ ┃││   X1 Y1 Z1
982            // 5 │ ┃││   X2 Y2 Z2
983            //   │ ┃│└────╿──│──┘ `Z` label
984            //   │ ┃└─────│──┤
985            //   │ ┗━━━━━━┥  `Y` is a good letter too
986            //   ╰╴       `X` is a good letter
987            for (pos, _) in &mut annotations_position {
988                *pos = max_pos - *pos;
989            }
990            // We know then that we don't need an additional line for the span label, saving us
991            // one line of vertical space.
992            line_len = line_len.saturating_sub(1);
993        }
994
995        // Write the column separator.
996        //
997        // After this we will have:
998        //
999        // 2 |   fn foo() {
1000        //   |
1001        //   |
1002        //   |
1003        // 3 |
1004        // 4 |   }
1005        //   |
1006        for pos in 0..=line_len {
1007            self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2);
1008        }
1009        if close_window {
1010            self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2);
1011        }
1012
1013        // Write the horizontal lines for multiline annotations
1014        // (only the first and last lines need this).
1015        //
1016        // After this we will have:
1017        //
1018        // 2 |   fn foo() {
1019        //   |  __________
1020        //   |
1021        //   |
1022        // 3 |
1023        // 4 |   }
1024        //   |  _
1025        for &(pos, annotation) in &annotations_position {
1026            let underline = self.underline(annotation.is_primary);
1027            let pos = pos + 1;
1028            match annotation.annotation_type {
1029                AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => {
1030                    let pre: usize = source_string
1031                        .chars()
1032                        .take(annotation.start_col.file)
1033                        .skip(left)
1034                        .map(|c| char_width(c))
1035                        .sum();
1036                    self.draw_range(
1037                        buffer,
1038                        underline.multiline_horizontal,
1039                        line_offset + pos,
1040                        width_offset + depth,
1041                        code_offset + pre,
1042                        underline.style,
1043                    );
1044                }
1045                _ => {}
1046            }
1047        }
1048
1049        // Write the vertical lines for labels that are on a different line as the underline.
1050        //
1051        // After this we will have:
1052        //
1053        // 2 |   fn foo() {
1054        //   |  __________
1055        //   | |    |
1056        //   | |
1057        // 3 | |
1058        // 4 | | }
1059        //   | |_
1060        for &(pos, annotation) in &annotations_position {
1061            let underline = self.underline(annotation.is_primary);
1062            let pos = pos + 1;
1063
1064            let code_offset = code_offset
1065                + source_string
1066                    .chars()
1067                    .take(annotation.start_col.file)
1068                    .skip(left)
1069                    .map(|c| char_width(c))
1070                    .sum::<usize>();
1071            if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
1072                for p in line_offset + 1..=line_offset + pos {
1073                    buffer.putc(
1074                        p,
1075                        code_offset,
1076                        match annotation.annotation_type {
1077                            AnnotationType::MultilineLine(_) => underline.multiline_vertical,
1078                            _ => underline.vertical_text_line,
1079                        },
1080                        underline.style,
1081                    );
1082                }
1083                if let AnnotationType::MultilineStart(_) = annotation.annotation_type {
1084                    buffer.putc(
1085                        line_offset + pos,
1086                        code_offset,
1087                        underline.bottom_right,
1088                        underline.style,
1089                    );
1090                }
1091                if let AnnotationType::MultilineEnd(_) = annotation.annotation_type
1092                    && annotation.has_label()
1093                {
1094                    buffer.putc(
1095                        line_offset + pos,
1096                        code_offset,
1097                        underline.multiline_bottom_right_with_text,
1098                        underline.style,
1099                    );
1100                }
1101            }
1102            match annotation.annotation_type {
1103                AnnotationType::MultilineStart(depth) => {
1104                    buffer.putc(
1105                        line_offset + pos,
1106                        width_offset + depth - 1,
1107                        underline.top_left,
1108                        underline.style,
1109                    );
1110                    for p in line_offset + pos + 1..line_offset + line_len + 2 {
1111                        buffer.putc(
1112                            p,
1113                            width_offset + depth - 1,
1114                            underline.multiline_vertical,
1115                            underline.style,
1116                        );
1117                    }
1118                }
1119                AnnotationType::MultilineEnd(depth) => {
1120                    for p in line_offset..line_offset + pos {
1121                        buffer.putc(
1122                            p,
1123                            width_offset + depth - 1,
1124                            underline.multiline_vertical,
1125                            underline.style,
1126                        );
1127                    }
1128                    buffer.putc(
1129                        line_offset + pos,
1130                        width_offset + depth - 1,
1131                        underline.bottom_left,
1132                        underline.style,
1133                    );
1134                }
1135                _ => (),
1136            }
1137        }
1138
1139        // Write the labels on the annotations that actually have a label.
1140        //
1141        // After this we will have:
1142        //
1143        // 2 |   fn foo() {
1144        //   |  __________
1145        //   |      |
1146        //   |      something about `foo`
1147        // 3 |
1148        // 4 |   }
1149        //   |  _  test
1150        for &(pos, annotation) in &annotations_position {
1151            let style =
1152                if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary };
1153            let (pos, col) = if pos == 0 {
1154                let pre: usize = source_string
1155                    .chars()
1156                    .take(annotation.end_col.file)
1157                    .skip(left)
1158                    .map(|c| char_width(c))
1159                    .sum();
1160                if annotation.end_col.file == 0 {
1161                    (pos + 1, (pre + 2))
1162                } else {
1163                    let pad = if annotation.end_col.file - annotation.start_col.file == 0 {
1164                        2
1165                    } else {
1166                        1
1167                    };
1168                    (pos + 1, (pre + pad))
1169                }
1170            } else {
1171                let pre: usize = source_string
1172                    .chars()
1173                    .take(annotation.start_col.file)
1174                    .skip(left)
1175                    .map(|c| char_width(c))
1176                    .sum();
1177                (pos + 2, pre)
1178            };
1179            if let Some(ref label) = annotation.label {
1180                buffer.puts(line_offset + pos, code_offset + col, label, style);
1181            }
1182        }
1183
1184        // Sort from biggest span to smallest span so that smaller spans are
1185        // represented in the output:
1186        //
1187        // x | fn foo()
1188        //   | ^^^---^^
1189        //   | |  |
1190        //   | |  something about `foo`
1191        //   | something about `fn foo()`
1192        annotations_position.sort_by_key(|(_, ann)| {
1193            // Decreasing order. When annotations share the same length, prefer `Primary`.
1194            (Reverse(ann.len()), ann.is_primary)
1195        });
1196
1197        // Write the underlines.
1198        //
1199        // After this we will have:
1200        //
1201        // 2 |   fn foo() {
1202        //   |  ____-_____^
1203        //   |      |
1204        //   |      something about `foo`
1205        // 3 |
1206        // 4 |   }
1207        //   |  _^  test
1208        for &(pos, annotation) in &annotations_position {
1209            let uline = self.underline(annotation.is_primary);
1210            let width = annotation.end_col.file - annotation.start_col.file;
1211            let previous: String =
1212                source_string.chars().take(annotation.start_col.file).skip(left).collect();
1213            let underlined: String =
1214                source_string.chars().skip(annotation.start_col.file).take(width).collect();
1215            debug!(?previous, ?underlined);
1216            let code_offset = code_offset
1217                + source_string
1218                    .chars()
1219                    .take(annotation.start_col.file)
1220                    .skip(left)
1221                    .map(|c| char_width(c))
1222                    .sum::<usize>();
1223            let ann_width: usize = source_string
1224                .chars()
1225                .skip(annotation.start_col.file)
1226                .take(width)
1227                .map(|c| char_width(c))
1228                .sum();
1229            let ann_width = if ann_width == 0
1230                && matches!(annotation.annotation_type, AnnotationType::Singleline)
1231            {
1232                1
1233            } else {
1234                ann_width
1235            };
1236            for p in 0..ann_width {
1237                // The default span label underline.
1238                buffer.putc(line_offset + 1, code_offset + p, uline.underline, uline.style);
1239            }
1240
1241            if pos == 0
1242                && matches!(
1243                    annotation.annotation_type,
1244                    AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1245                )
1246            {
1247                // The beginning of a multiline span with its leftward moving line on the same line.
1248                buffer.putc(
1249                    line_offset + 1,
1250                    code_offset,
1251                    match annotation.annotation_type {
1252                        AnnotationType::MultilineStart(_) => uline.top_right_flat,
1253                        AnnotationType::MultilineEnd(_) => uline.multiline_end_same_line,
1254                        _ => panic!("unexpected annotation type: {annotation:?}"),
1255                    },
1256                    uline.style,
1257                );
1258            } else if pos != 0
1259                && matches!(
1260                    annotation.annotation_type,
1261                    AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1262                )
1263            {
1264                // The beginning of a multiline span with its leftward moving line on another line,
1265                // so we start going down first.
1266                buffer.putc(
1267                    line_offset + 1,
1268                    code_offset,
1269                    match annotation.annotation_type {
1270                        AnnotationType::MultilineStart(_) => uline.multiline_start_down,
1271                        AnnotationType::MultilineEnd(_) => uline.multiline_end_up,
1272                        _ => panic!("unexpected annotation type: {annotation:?}"),
1273                    },
1274                    uline.style,
1275                );
1276            } else if pos != 0 && annotation.has_label() {
1277                // The beginning of a span label with an actual label, we'll point down.
1278                buffer.putc(line_offset + 1, code_offset, uline.label_start, uline.style);
1279            }
1280        }
1281
1282        // We look for individual *long* spans, and we trim the *middle*, so that we render
1283        // LL | ...= [0, 0, 0, ..., 0, 0];
1284        //    |      ^^^^^^^^^^...^^^^^^^ expected `&[u8]`, found `[{integer}; 1680]`
1285        for (i, (_pos, annotation)) in annotations_position.iter().enumerate() {
1286            // Skip cases where multiple spans overlap each other.
1287            if overlap[i] {
1288                continue;
1289            };
1290            let AnnotationType::Singleline = annotation.annotation_type else { continue };
1291            let width = annotation.end_col.display - annotation.start_col.display;
1292            if width > margin.column_width * 2 && width > 10 {
1293                // If the terminal is *too* small, we keep at least a tiny bit of the span for
1294                // display.
1295                let pad = max(margin.column_width / 3, 5);
1296                // Code line
1297                buffer.replace(
1298                    line_offset,
1299                    annotation.start_col.file + pad,
1300                    annotation.end_col.file - pad,
1301                    self.margin(),
1302                );
1303                // Underline line
1304                buffer.replace(
1305                    line_offset + 1,
1306                    annotation.start_col.file + pad,
1307                    annotation.end_col.file - pad,
1308                    self.margin(),
1309                );
1310            }
1311        }
1312        annotations_position
1313            .iter()
1314            .filter_map(|&(_, annotation)| match annotation.annotation_type {
1315                AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => {
1316                    let style = if annotation.is_primary {
1317                        Style::LabelPrimary
1318                    } else {
1319                        Style::LabelSecondary
1320                    };
1321                    Some((p, style))
1322                }
1323                _ => None,
1324            })
1325            .collect::<Vec<_>>()
1326    }
1327
1328    fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
1329        let Some(ref sm) = self.sm else {
1330            return 0;
1331        };
1332
1333        let will_be_emitted = |span: Span| {
1334            !span.is_dummy() && {
1335                let file = sm.lookup_source_file(span.hi());
1336                should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file)
1337            }
1338        };
1339
1340        let mut max = 0;
1341        for primary_span in msp.primary_spans() {
1342            if will_be_emitted(*primary_span) {
1343                let hi = sm.lookup_char_pos(primary_span.hi());
1344                max = (hi.line).max(max);
1345            }
1346        }
1347        if !self.short_message {
1348            for span_label in msp.span_labels() {
1349                if will_be_emitted(span_label.span) {
1350                    let hi = sm.lookup_char_pos(span_label.span.hi());
1351                    max = (hi.line).max(max);
1352                }
1353            }
1354        }
1355
1356        max
1357    }
1358
1359    fn get_max_line_num(&mut self, span: &MultiSpan, children: &[Subdiag]) -> usize {
1360        let primary = self.get_multispan_max_line_num(span);
1361        children
1362            .iter()
1363            .map(|sub| self.get_multispan_max_line_num(&sub.span))
1364            .max()
1365            .unwrap_or(0)
1366            .max(primary)
1367    }
1368
1369    /// Adds a left margin to every line but the first, given a padding length and the label being
1370    /// displayed, keeping the provided highlighting.
1371    fn msgs_to_buffer(
1372        &self,
1373        buffer: &mut StyledBuffer,
1374        msgs: &[(DiagMessage, Style)],
1375        args: &FluentArgs<'_>,
1376        padding: usize,
1377        label: &str,
1378        override_style: Option<Style>,
1379    ) -> usize {
1380        // The extra 5 ` ` is padding that's always needed to align to the `note: `:
1381        //
1382        //   error: message
1383        //     --> file.rs:13:20
1384        //      |
1385        //   13 |     <CODE>
1386        //      |      ^^^^
1387        //      |
1388        //      = note: multiline
1389        //              message
1390        //   ++^^^----xx
1391        //    |  |   | |
1392        //    |  |   | magic `2`
1393        //    |  |   length of label
1394        //    |  magic `3`
1395        //    `max_line_num_len`
1396        let padding = " ".repeat(padding + label.len() + 5);
1397
1398        /// Returns `override` if it is present and `style` is `NoStyle` or `style` otherwise
1399        fn style_or_override(style: Style, override_: Option<Style>) -> Style {
1400            match (style, override_) {
1401                (Style::NoStyle, Some(override_)) => override_,
1402                _ => style,
1403            }
1404        }
1405
1406        let mut line_number = 0;
1407
1408        // Provided the following diagnostic message:
1409        //
1410        //     let msgs = vec![
1411        //       ("
1412        //       ("highlighted multiline\nstring to\nsee how it ", Style::NoStyle),
1413        //       ("looks", Style::Highlight),
1414        //       ("with\nvery ", Style::NoStyle),
1415        //       ("weird", Style::Highlight),
1416        //       (" formats\n", Style::NoStyle),
1417        //       ("see?", Style::Highlight),
1418        //     ];
1419        //
1420        // the expected output on a note is (* surround the highlighted text)
1421        //
1422        //        = note: highlighted multiline
1423        //                string to
1424        //                see how it *looks* with
1425        //                very *weird* formats
1426        //                see?
1427        for (text, style) in msgs.iter() {
1428            let text = self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1429            let text = &normalize_whitespace(&text);
1430            let lines = text.split('\n').collect::<Vec<_>>();
1431            if lines.len() > 1 {
1432                for (i, line) in lines.iter().enumerate() {
1433                    if i != 0 {
1434                        line_number += 1;
1435                        buffer.append(line_number, &padding, Style::NoStyle);
1436                    }
1437                    buffer.append(line_number, line, style_or_override(*style, override_style));
1438                }
1439            } else {
1440                buffer.append(line_number, text, style_or_override(*style, override_style));
1441            }
1442        }
1443        line_number
1444    }
1445
1446    #[instrument(level = "trace", skip(self, args), ret)]
1447    fn emit_messages_default_inner(
1448        &mut self,
1449        msp: &MultiSpan,
1450        msgs: &[(DiagMessage, Style)],
1451        args: &FluentArgs<'_>,
1452        code: &Option<ErrCode>,
1453        level: &Level,
1454        max_line_num_len: usize,
1455        is_secondary: bool,
1456        is_cont: bool,
1457    ) -> io::Result<CodeWindowStatus> {
1458        let mut buffer = StyledBuffer::new();
1459
1460        if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
1461        {
1462            // This is a secondary message with no span info
1463            for _ in 0..max_line_num_len {
1464                buffer.prepend(0, " ", Style::NoStyle);
1465            }
1466            self.draw_note_separator(&mut buffer, 0, max_line_num_len + 1, is_cont);
1467            if *level != Level::FailureNote {
1468                buffer.append(0, level.to_str(), Style::MainHeaderMsg);
1469                buffer.append(0, ": ", Style::NoStyle);
1470            }
1471            let printed_lines =
1472                self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None);
1473            if is_cont && matches!(self.theme, OutputTheme::Unicode) {
1474                // There's another note after this one, associated to the subwindow above.
1475                // We write additional vertical lines to join them:
1476                //   ╭▸ test.rs:3:3
1477                //   │
1478                // 3 │   code
1479                //   │   ━━━━
1480                //   │
1481                //   ├ note: foo
1482                //   │       bar
1483                //   ╰ note: foo
1484                //           bar
1485                for i in 1..=printed_lines {
1486                    self.draw_col_separator_no_space(&mut buffer, i, max_line_num_len + 1);
1487                }
1488            }
1489        } else {
1490            let mut label_width = 0;
1491            // The failure note level itself does not provide any useful diagnostic information
1492            if *level != Level::FailureNote {
1493                buffer.append(0, level.to_str(), Style::Level(*level));
1494                label_width += level.to_str().len();
1495            }
1496            if let Some(code) = code {
1497                buffer.append(0, "[", Style::Level(*level));
1498                let code = if let TerminalUrl::Yes = self.terminal_url {
1499                    let path = "https://doc.rust-lang.org/error_codes";
1500                    format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
1501                } else {
1502                    code.to_string()
1503                };
1504                buffer.append(0, &code, Style::Level(*level));
1505                buffer.append(0, "]", Style::Level(*level));
1506                label_width += 2 + code.len();
1507            }
1508            let header_style = if is_secondary {
1509                Style::HeaderMsg
1510            } else if self.short_message {
1511                // For short messages avoid bolding the message, as it doesn't look great (#63835).
1512                Style::NoStyle
1513            } else {
1514                Style::MainHeaderMsg
1515            };
1516            if *level != Level::FailureNote {
1517                buffer.append(0, ": ", header_style);
1518                label_width += 2;
1519            }
1520            let mut line = 0;
1521            let mut pad = false;
1522            for (text, style) in msgs.iter() {
1523                let text =
1524                    self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1525                // Account for newlines to align output to its label.
1526                for text in normalize_whitespace(&text).split('\n') {
1527                    buffer.append(
1528                        line,
1529                        &format!(
1530                            "{}{}",
1531                            if pad { " ".repeat(label_width) } else { String::new() },
1532                            text
1533                        ),
1534                        match style {
1535                            Style::Highlight => *style,
1536                            _ => header_style,
1537                        },
1538                    );
1539                    line += 1;
1540                    pad = true;
1541                }
1542                pad = false;
1543                // We add lines above, but if the last line has no explicit newline (which would
1544                // yield an empty line), then we revert one line up to continue with the next
1545                // styled text chunk on the same line as the last one from the prior one. Otherwise
1546                // every `text` would appear on their own line (because even though they didn't end
1547                // in '\n', they advanced `line` by one).
1548                if line > 0 {
1549                    line -= 1;
1550                }
1551            }
1552            if self.short_message {
1553                let labels = msp
1554                    .span_labels()
1555                    .into_iter()
1556                    .filter_map(|label| match label.label {
1557                        Some(msg) if label.is_primary => {
1558                            let text = self.translator.translate_message(&msg, args).ok()?;
1559                            if !text.trim().is_empty() { Some(text.to_string()) } else { None }
1560                        }
1561                        _ => None,
1562                    })
1563                    .collect::<Vec<_>>()
1564                    .join(", ");
1565                if !labels.is_empty() {
1566                    buffer.append(line, ": ", Style::NoStyle);
1567                    buffer.append(line, &labels, Style::NoStyle);
1568                }
1569            }
1570        }
1571        let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
1572        trace!("{annotated_files:#?}");
1573        let mut code_window_status = CodeWindowStatus::Open;
1574
1575        // Make sure our primary file comes first
1576        let primary_span = msp.primary_span().unwrap_or_default();
1577        let (Some(sm), false) = (self.sm.as_ref(), primary_span.is_dummy()) else {
1578            // If we don't have span information, emit and exit
1579            return emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)
1580                .map(|_| code_window_status);
1581        };
1582        let primary_lo = sm.lookup_char_pos(primary_span.lo());
1583        if let Ok(pos) =
1584            annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
1585        {
1586            annotated_files.swap(0, pos);
1587        }
1588
1589        // An end column separator should be emitted when a file with with a
1590        // source, is followed by one without a source
1591        let mut col_sep_before_no_show_source = false;
1592        let annotated_files_len = annotated_files.len();
1593        // Print out the annotate source lines that correspond with the error
1594        for (file_idx, annotated_file) in annotated_files.into_iter().enumerate() {
1595            // we can't annotate anything if the source is unavailable.
1596            if !should_show_source_code(
1597                &self.ignored_directories_in_source_blocks,
1598                sm,
1599                &annotated_file.file,
1600            ) {
1601                if !self.short_message {
1602                    // Add an end column separator when a file without a source
1603                    // comes after one with a source
1604                    //    ╭▸ $DIR/deriving-meta-unknown-trait.rs:1:10
1605                    //    │
1606                    // LL │ #[derive(Eqr)]
1607                    //    │          ━━━
1608                    //    ╰╴ (<- It prints *this* line)
1609                    //    ╭▸ $SRC_DIR/core/src/cmp.rs:356:0
1610                    //    │
1611                    //    ╰╴note: similarly named derive macro `Eq` defined here
1612                    if col_sep_before_no_show_source {
1613                        let buffer_msg_line_offset = buffer.num_lines();
1614                        self.draw_col_separator_end(
1615                            &mut buffer,
1616                            buffer_msg_line_offset,
1617                            max_line_num_len + 1,
1618                        );
1619                    }
1620                    col_sep_before_no_show_source = false;
1621
1622                    // We'll just print an unannotated message.
1623                    for (annotation_id, line) in annotated_file.lines.iter().enumerate() {
1624                        let mut annotations = line.annotations.clone();
1625                        annotations.sort_by_key(|a| Reverse(a.start_col));
1626                        let mut line_idx = buffer.num_lines();
1627
1628                        let labels: Vec<_> = annotations
1629                            .iter()
1630                            .filter_map(|a| Some((a.label.as_ref()?, a.is_primary)))
1631                            .filter(|(l, _)| !l.is_empty())
1632                            .collect();
1633
1634                        if annotation_id == 0 || !labels.is_empty() {
1635                            buffer.append(
1636                                line_idx,
1637                                &format!(
1638                                    "{}:{}:{}",
1639                                    sm.filename_for_diagnostics(&annotated_file.file.name),
1640                                    sm.doctest_offset_line(
1641                                        &annotated_file.file.name,
1642                                        line.line_index
1643                                    ),
1644                                    annotations[0].start_col.file + 1,
1645                                ),
1646                                Style::LineAndColumn,
1647                            );
1648                            if annotation_id == 0 {
1649                                buffer.prepend(line_idx, self.file_start(), Style::LineNumber);
1650                            } else {
1651                                buffer.prepend(
1652                                    line_idx,
1653                                    self.secondary_file_start(),
1654                                    Style::LineNumber,
1655                                );
1656                            }
1657                            for _ in 0..max_line_num_len {
1658                                buffer.prepend(line_idx, " ", Style::NoStyle);
1659                            }
1660                            line_idx += 1;
1661                        }
1662                        if is_cont
1663                            && file_idx == annotated_files_len - 1
1664                            && annotation_id == annotated_file.lines.len() - 1
1665                            && !labels.is_empty()
1666                        {
1667                            code_window_status = CodeWindowStatus::Closed;
1668                        }
1669                        let labels_len = labels.len();
1670                        for (label_idx, (label, is_primary)) in labels.into_iter().enumerate() {
1671                            let style = if is_primary {
1672                                Style::LabelPrimary
1673                            } else {
1674                                Style::LabelSecondary
1675                            };
1676                            self.draw_col_separator_no_space(
1677                                &mut buffer,
1678                                line_idx,
1679                                max_line_num_len + 1,
1680                            );
1681                            line_idx += 1;
1682                            self.draw_note_separator(
1683                                &mut buffer,
1684                                line_idx,
1685                                max_line_num_len + 1,
1686                                label_idx != labels_len - 1,
1687                            );
1688                            buffer.append(line_idx, "note", Style::MainHeaderMsg);
1689                            buffer.append(line_idx, ": ", Style::NoStyle);
1690                            buffer.append(line_idx, label, style);
1691                            line_idx += 1;
1692                        }
1693                    }
1694                }
1695                continue;
1696            } else {
1697                col_sep_before_no_show_source = true;
1698            }
1699            // print out the span location and spacer before we print the annotated source
1700            // to do this, we need to know if this span will be primary
1701            let is_primary = primary_lo.file.stable_id == annotated_file.file.stable_id;
1702            if is_primary {
1703                let loc = primary_lo.clone();
1704                if !self.short_message {
1705                    // remember where we are in the output buffer for easy reference
1706                    let buffer_msg_line_offset = buffer.num_lines();
1707
1708                    buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber);
1709                    buffer.append(
1710                        buffer_msg_line_offset,
1711                        &format!(
1712                            "{}:{}:{}",
1713                            sm.filename_for_diagnostics(&loc.file.name),
1714                            sm.doctest_offset_line(&loc.file.name, loc.line),
1715                            loc.col.0 + 1,
1716                        ),
1717                        Style::LineAndColumn,
1718                    );
1719                    for _ in 0..max_line_num_len {
1720                        buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1721                    }
1722                } else {
1723                    buffer.prepend(
1724                        0,
1725                        &format!(
1726                            "{}:{}:{}: ",
1727                            sm.filename_for_diagnostics(&loc.file.name),
1728                            sm.doctest_offset_line(&loc.file.name, loc.line),
1729                            loc.col.0 + 1,
1730                        ),
1731                        Style::LineAndColumn,
1732                    );
1733                }
1734            } else if !self.short_message {
1735                // remember where we are in the output buffer for easy reference
1736                let buffer_msg_line_offset = buffer.num_lines();
1737
1738                // Add spacing line, as shown:
1739                //   --> $DIR/file:54:15
1740                //    |
1741                // LL |         code
1742                //    |         ^^^^
1743                //    | (<- It prints *this* line)
1744                //   ::: $DIR/other_file.rs:15:5
1745                //    |
1746                // LL |     code
1747                //    |     ----
1748                self.draw_col_separator_no_space(
1749                    &mut buffer,
1750                    buffer_msg_line_offset,
1751                    max_line_num_len + 1,
1752                );
1753
1754                // Then, the secondary file indicator
1755                buffer.prepend(
1756                    buffer_msg_line_offset + 1,
1757                    self.secondary_file_start(),
1758                    Style::LineNumber,
1759                );
1760                let loc = if let Some(first_line) = annotated_file.lines.first() {
1761                    let col = if let Some(first_annotation) = first_line.annotations.first() {
1762                        format!(":{}", first_annotation.start_col.file + 1)
1763                    } else {
1764                        String::new()
1765                    };
1766                    format!(
1767                        "{}:{}{}",
1768                        sm.filename_for_diagnostics(&annotated_file.file.name),
1769                        sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
1770                        col
1771                    )
1772                } else {
1773                    format!("{}", sm.filename_for_diagnostics(&annotated_file.file.name))
1774                };
1775                buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
1776                for _ in 0..max_line_num_len {
1777                    buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
1778                }
1779            }
1780
1781            if !self.short_message {
1782                // Put in the spacer between the location and annotated source
1783                let buffer_msg_line_offset = buffer.num_lines();
1784                self.draw_col_separator_no_space(
1785                    &mut buffer,
1786                    buffer_msg_line_offset,
1787                    max_line_num_len + 1,
1788                );
1789
1790                // Contains the vertical lines' positions for active multiline annotations
1791                let mut multilines = FxIndexMap::default();
1792
1793                // Get the left-side margin to remove it
1794                let mut whitespace_margin = usize::MAX;
1795                for line_idx in 0..annotated_file.lines.len() {
1796                    let file = Arc::clone(&annotated_file.file);
1797                    let line = &annotated_file.lines[line_idx];
1798                    if let Some(source_string) =
1799                        line.line_index.checked_sub(1).and_then(|l| file.get_line(l))
1800                    {
1801                        // Whitespace can only be removed (aka considered leading)
1802                        // if the lexer considers it whitespace.
1803                        // non-rustc_lexer::is_whitespace() chars are reported as an
1804                        // error (ex. no-break-spaces \u{a0}), and thus can't be considered
1805                        // for removal during error reporting.
1806                        // FIXME: doesn't account for '\t' properly.
1807                        let leading_whitespace = source_string
1808                            .chars()
1809                            .take_while(|c| rustc_lexer::is_whitespace(*c))
1810                            .count();
1811                        if source_string.chars().any(|c| !rustc_lexer::is_whitespace(c)) {
1812                            whitespace_margin = min(whitespace_margin, leading_whitespace);
1813                        }
1814                    }
1815                }
1816                if whitespace_margin == usize::MAX {
1817                    whitespace_margin = 0;
1818                }
1819
1820                // Left-most column any visible span points at.
1821                let mut span_left_margin = usize::MAX;
1822                for line in &annotated_file.lines {
1823                    for ann in &line.annotations {
1824                        span_left_margin = min(span_left_margin, ann.start_col.file);
1825                        span_left_margin = min(span_left_margin, ann.end_col.file);
1826                    }
1827                }
1828                if span_left_margin == usize::MAX {
1829                    span_left_margin = 0;
1830                }
1831
1832                // Right-most column any visible span points at.
1833                let mut span_right_margin = 0;
1834                let mut label_right_margin = 0;
1835                let mut max_line_len = 0;
1836                for line in &annotated_file.lines {
1837                    max_line_len = max(
1838                        max_line_len,
1839                        line.line_index
1840                            .checked_sub(1)
1841                            .and_then(|l| annotated_file.file.get_line(l))
1842                            .map_or(0, |s| s.len()),
1843                    );
1844                    for ann in &line.annotations {
1845                        span_right_margin = max(span_right_margin, ann.start_col.file);
1846                        span_right_margin = max(span_right_margin, ann.end_col.file);
1847                        // FIXME: account for labels not in the same line
1848                        let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
1849                        label_right_margin =
1850                            max(label_right_margin, ann.end_col.file + label_right);
1851                    }
1852                }
1853
1854                let width_offset = 3 + max_line_num_len;
1855                let code_offset = if annotated_file.multiline_depth == 0 {
1856                    width_offset
1857                } else {
1858                    width_offset + annotated_file.multiline_depth + 1
1859                };
1860
1861                let column_width = self.column_width(code_offset);
1862
1863                let margin = Margin::new(
1864                    whitespace_margin,
1865                    span_left_margin,
1866                    span_right_margin,
1867                    label_right_margin,
1868                    column_width,
1869                    max_line_len,
1870                );
1871
1872                // Next, output the annotate source for this file
1873                for line_idx in 0..annotated_file.lines.len() {
1874                    let previous_buffer_line = buffer.num_lines();
1875
1876                    let depths = self.render_source_line(
1877                        &mut buffer,
1878                        Arc::clone(&annotated_file.file),
1879                        &annotated_file.lines[line_idx],
1880                        width_offset,
1881                        code_offset,
1882                        margin,
1883                        !is_cont
1884                            && file_idx + 1 == annotated_files_len
1885                            && line_idx + 1 == annotated_file.lines.len(),
1886                    );
1887
1888                    let mut to_add = FxIndexMap::default();
1889
1890                    for (depth, style) in depths {
1891                        // FIXME(#120456) - is `swap_remove` correct?
1892                        if multilines.swap_remove(&depth).is_none() {
1893                            to_add.insert(depth, style);
1894                        }
1895                    }
1896
1897                    // Set the multiline annotation vertical lines to the left of
1898                    // the code in this line.
1899                    for (depth, style) in &multilines {
1900                        for line in previous_buffer_line..buffer.num_lines() {
1901                            self.draw_multiline_line(
1902                                &mut buffer,
1903                                line,
1904                                width_offset,
1905                                *depth,
1906                                *style,
1907                            );
1908                        }
1909                    }
1910                    // check to see if we need to print out or elide lines that come between
1911                    // this annotated line and the next one.
1912                    if line_idx < (annotated_file.lines.len() - 1) {
1913                        let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1914                            - annotated_file.lines[line_idx].line_index;
1915                        if line_idx_delta > 2 {
1916                            let last_buffer_line_num = buffer.num_lines();
1917                            self.draw_line_separator(
1918                                &mut buffer,
1919                                last_buffer_line_num,
1920                                width_offset,
1921                            );
1922
1923                            // Set the multiline annotation vertical lines on `...` bridging line.
1924                            for (depth, style) in &multilines {
1925                                self.draw_multiline_line(
1926                                    &mut buffer,
1927                                    last_buffer_line_num,
1928                                    width_offset,
1929                                    *depth,
1930                                    *style,
1931                                );
1932                            }
1933                            if let Some(line) = annotated_file.lines.get(line_idx) {
1934                                for ann in &line.annotations {
1935                                    if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1936                                    {
1937                                        // In the case where we have elided the entire start of the
1938                                        // multispan because those lines were empty, we still need
1939                                        // to draw the `|`s across the `...`.
1940                                        self.draw_multiline_line(
1941                                            &mut buffer,
1942                                            last_buffer_line_num,
1943                                            width_offset,
1944                                            pos,
1945                                            if ann.is_primary {
1946                                                Style::UnderlinePrimary
1947                                            } else {
1948                                                Style::UnderlineSecondary
1949                                            },
1950                                        );
1951                                    }
1952                                }
1953                            }
1954                        } else if line_idx_delta == 2 {
1955                            let unannotated_line = annotated_file
1956                                .file
1957                                .get_line(annotated_file.lines[line_idx].line_index)
1958                                .unwrap_or_else(|| Cow::from(""));
1959
1960                            let last_buffer_line_num = buffer.num_lines();
1961
1962                            self.draw_line(
1963                                &mut buffer,
1964                                &normalize_whitespace(&unannotated_line),
1965                                annotated_file.lines[line_idx + 1].line_index - 1,
1966                                last_buffer_line_num,
1967                                width_offset,
1968                                code_offset,
1969                                margin,
1970                            );
1971
1972                            for (depth, style) in &multilines {
1973                                self.draw_multiline_line(
1974                                    &mut buffer,
1975                                    last_buffer_line_num,
1976                                    width_offset,
1977                                    *depth,
1978                                    *style,
1979                                );
1980                            }
1981                            if let Some(line) = annotated_file.lines.get(line_idx) {
1982                                for ann in &line.annotations {
1983                                    if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1984                                    {
1985                                        self.draw_multiline_line(
1986                                            &mut buffer,
1987                                            last_buffer_line_num,
1988                                            width_offset,
1989                                            pos,
1990                                            if ann.is_primary {
1991                                                Style::UnderlinePrimary
1992                                            } else {
1993                                                Style::UnderlineSecondary
1994                                            },
1995                                        );
1996                                    }
1997                                }
1998                            }
1999                        }
2000                    }
2001
2002                    multilines.extend(&to_add);
2003                }
2004            }
2005            trace!("buffer: {:#?}", buffer.render());
2006        }
2007
2008        // final step: take our styled buffer, render it, then output it
2009        emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2010
2011        Ok(code_window_status)
2012    }
2013
2014    fn column_width(&self, code_offset: usize) -> usize {
2015        if let Some(width) = self.diagnostic_width {
2016            width.saturating_sub(code_offset)
2017        } else if self.ui_testing || cfg!(miri) {
2018            DEFAULT_COLUMN_WIDTH.saturating_sub(code_offset)
2019        } else {
2020            termize::dimensions()
2021                .map(|(w, _)| w.saturating_sub(code_offset))
2022                .unwrap_or(DEFAULT_COLUMN_WIDTH)
2023        }
2024    }
2025
2026    fn emit_suggestion_default(
2027        &mut self,
2028        span: &MultiSpan,
2029        suggestion: &CodeSuggestion,
2030        args: &FluentArgs<'_>,
2031        level: &Level,
2032        max_line_num_len: usize,
2033    ) -> io::Result<()> {
2034        let Some(ref sm) = self.sm else {
2035            return Ok(());
2036        };
2037
2038        // Render the replacements for each suggestion
2039        let suggestions = suggestion.splice_lines(sm);
2040        debug!(?suggestions);
2041
2042        if suggestions.is_empty() {
2043            // Here we check if there are suggestions that have actual code changes. We sometimes
2044            // suggest the same code that is already there, instead of changing how we produce the
2045            // suggestions and filtering there, we just don't emit the suggestion.
2046            // Suggestions coming from macros can also have malformed spans. This is a heavy handed
2047            // approach to avoid ICEs by ignoring the suggestion outright.
2048            return Ok(());
2049        }
2050
2051        let mut buffer = StyledBuffer::new();
2052
2053        // Render the suggestion message
2054        buffer.append(0, level.to_str(), Style::Level(*level));
2055        buffer.append(0, ": ", Style::HeaderMsg);
2056
2057        let mut msg = vec![(suggestion.msg.to_owned(), Style::NoStyle)];
2058        if let Some(confusion_type) =
2059            suggestions.iter().take(MAX_SUGGESTIONS).find_map(|(_, _, _, confusion_type)| {
2060                if confusion_type.has_confusion() { Some(*confusion_type) } else { None }
2061            })
2062        {
2063            msg.push((confusion_type.label_text().into(), Style::NoStyle));
2064        }
2065        self.msgs_to_buffer(
2066            &mut buffer,
2067            &msg,
2068            args,
2069            max_line_num_len,
2070            "suggestion",
2071            Some(Style::HeaderMsg),
2072        );
2073
2074        let other_suggestions = suggestions.len().saturating_sub(MAX_SUGGESTIONS);
2075
2076        let mut row_num = 2;
2077        for (i, (complete, parts, highlights, _)) in
2078            suggestions.into_iter().enumerate().take(MAX_SUGGESTIONS)
2079        {
2080            debug!(?complete, ?parts, ?highlights);
2081
2082            let has_deletion =
2083                parts.iter().any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
2084            let is_multiline = complete.lines().count() > 1;
2085
2086            if i == 0 {
2087                self.draw_col_separator_start(&mut buffer, row_num - 1, max_line_num_len + 1);
2088            } else {
2089                buffer.puts(
2090                    row_num - 1,
2091                    max_line_num_len + 1,
2092                    self.multi_suggestion_separator(),
2093                    Style::LineNumber,
2094                );
2095            }
2096            if let Some(span) = span.primary_span() {
2097                // Compare the primary span of the diagnostic with the span of the suggestion
2098                // being emitted. If they belong to the same file, we don't *need* to show the
2099                // file name, saving in verbosity, but if it *isn't* we do need it, otherwise we're
2100                // telling users to make a change but not clarifying *where*.
2101                let loc = sm.lookup_char_pos(parts[0].span.lo());
2102                if (span.is_dummy() || loc.file.name != sm.span_to_filename(span))
2103                    && loc.file.name.is_real()
2104                {
2105                    // --> file.rs:line:col
2106                    //  |
2107                    let arrow = self.file_start();
2108                    buffer.puts(row_num - 1, 0, arrow, Style::LineNumber);
2109                    let filename = sm.filename_for_diagnostics(&loc.file.name);
2110                    let offset = sm.doctest_offset_line(&loc.file.name, loc.line);
2111                    let message = format!("{}:{}:{}", filename, offset, loc.col.0 + 1);
2112                    if row_num == 2 {
2113                        let col = usize::max(max_line_num_len + 1, arrow.len());
2114                        buffer.puts(1, col, &message, Style::LineAndColumn);
2115                    } else {
2116                        buffer.append(row_num - 1, &message, Style::LineAndColumn);
2117                    }
2118                    for _ in 0..max_line_num_len {
2119                        buffer.prepend(row_num - 1, " ", Style::NoStyle);
2120                    }
2121                    self.draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
2122                    row_num += 1;
2123                }
2124            }
2125            let show_code_change = if has_deletion && !is_multiline {
2126                DisplaySuggestion::Diff
2127            } else if let [part] = &parts[..]
2128                && part.snippet.ends_with('\n')
2129                && part.snippet.trim() == complete.trim()
2130            {
2131                // We are adding a line(s) of code before code that was already there.
2132                DisplaySuggestion::Add
2133            } else if (parts.len() != 1 || parts[0].snippet.trim() != complete.trim())
2134                && !is_multiline
2135            {
2136                DisplaySuggestion::Underline
2137            } else {
2138                DisplaySuggestion::None
2139            };
2140
2141            if let DisplaySuggestion::Diff = show_code_change {
2142                row_num += 1;
2143            }
2144
2145            let file_lines = sm
2146                .span_to_lines(parts[0].span)
2147                .expect("span_to_lines failed when emitting suggestion");
2148
2149            assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
2150
2151            let line_start = sm.lookup_char_pos(parts[0].original_span.lo()).line;
2152            let mut lines = complete.lines();
2153            let lines_len = lines.clone().count();
2154            if lines.clone().next().is_none() {
2155                // Account for a suggestion to completely remove a line(s) with whitespace (#94192).
2156                let line_end = sm.lookup_char_pos(parts[0].original_span.hi()).line;
2157                for line in line_start..=line_end {
2158                    self.draw_line_num(
2159                        &mut buffer,
2160                        line,
2161                        row_num - 1 + line - line_start,
2162                        max_line_num_len,
2163                    );
2164                    buffer.puts(
2165                        row_num - 1 + line - line_start,
2166                        max_line_num_len + 1,
2167                        "- ",
2168                        Style::Removal,
2169                    );
2170                    buffer.puts(
2171                        row_num - 1 + line - line_start,
2172                        max_line_num_len + 3,
2173                        &normalize_whitespace(&file_lines.file.get_line(line - 1).unwrap()),
2174                        Style::Removal,
2175                    );
2176                }
2177                row_num += line_end - line_start;
2178            }
2179            let mut unhighlighted_lines = Vec::new();
2180            let mut last_pos = 0;
2181            let mut is_item_attribute = false;
2182            for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
2183                last_pos = line_pos;
2184                debug!(%line_pos, %line, ?highlight_parts);
2185
2186                // Remember lines that are not highlighted to hide them if needed
2187                if highlight_parts.is_empty() {
2188                    unhighlighted_lines.push((line_pos, line));
2189                    continue;
2190                }
2191                if highlight_parts.len() == 1
2192                    && line.trim().starts_with("#[")
2193                    && line.trim().ends_with(']')
2194                    && lines_len == 1
2195                {
2196                    is_item_attribute = true;
2197                }
2198
2199                match unhighlighted_lines.len() {
2200                    0 => (),
2201                    // Since we show first line, "..." line and last line,
2202                    // There is no reason to hide if there are 3 or less lines
2203                    // (because then we just replace a line with ... which is
2204                    // not helpful)
2205                    n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
2206                        self.draw_code_line(
2207                            &mut buffer,
2208                            &mut row_num,
2209                            &[],
2210                            p + line_start,
2211                            l,
2212                            show_code_change,
2213                            max_line_num_len,
2214                            &file_lines,
2215                            is_multiline,
2216                        )
2217                    }),
2218                    // Print first unhighlighted line, "..." and last unhighlighted line, like so:
2219                    //
2220                    // LL | this line was highlighted
2221                    // LL | this line is just for context
2222                    // ...
2223                    // LL | this line is just for context
2224                    // LL | this line was highlighted
2225                    _ => {
2226                        let last_line = unhighlighted_lines.pop();
2227                        let first_line = unhighlighted_lines.drain(..).next();
2228
2229                        if let Some((p, l)) = first_line {
2230                            self.draw_code_line(
2231                                &mut buffer,
2232                                &mut row_num,
2233                                &[],
2234                                p + line_start,
2235                                l,
2236                                show_code_change,
2237                                max_line_num_len,
2238                                &file_lines,
2239                                is_multiline,
2240                            )
2241                        }
2242
2243                        let placeholder = self.margin();
2244                        let padding = str_width(placeholder);
2245                        buffer.puts(
2246                            row_num,
2247                            max_line_num_len.saturating_sub(padding),
2248                            placeholder,
2249                            Style::LineNumber,
2250                        );
2251                        row_num += 1;
2252
2253                        if let Some((p, l)) = last_line {
2254                            self.draw_code_line(
2255                                &mut buffer,
2256                                &mut row_num,
2257                                &[],
2258                                p + line_start,
2259                                l,
2260                                show_code_change,
2261                                max_line_num_len,
2262                                &file_lines,
2263                                is_multiline,
2264                            )
2265                        }
2266                    }
2267                }
2268
2269                self.draw_code_line(
2270                    &mut buffer,
2271                    &mut row_num,
2272                    &highlight_parts,
2273                    line_pos + line_start,
2274                    line,
2275                    show_code_change,
2276                    max_line_num_len,
2277                    &file_lines,
2278                    is_multiline,
2279                )
2280            }
2281            if let DisplaySuggestion::Add = show_code_change
2282                && is_item_attribute
2283            {
2284                // The suggestion adds an entire line of code, ending on a newline, so we'll also
2285                // print the *following* line, to provide context of what we're advising people to
2286                // do. Otherwise you would only see contextless code that can be confused for
2287                // already existing code, despite the colors and UI elements.
2288                // We special case `#[derive(_)]\n` and other attribute suggestions, because those
2289                // are the ones where context is most useful.
2290                let file_lines = sm
2291                    .span_to_lines(parts[0].span.shrink_to_hi())
2292                    .expect("span_to_lines failed when emitting suggestion");
2293                let line_num = sm.lookup_char_pos(parts[0].span.lo()).line;
2294                if let Some(line) = file_lines.file.get_line(line_num - 1) {
2295                    let line = normalize_whitespace(&line);
2296                    self.draw_code_line(
2297                        &mut buffer,
2298                        &mut row_num,
2299                        &[],
2300                        line_num + last_pos + 1,
2301                        &line,
2302                        DisplaySuggestion::None,
2303                        max_line_num_len,
2304                        &file_lines,
2305                        is_multiline,
2306                    )
2307                }
2308            }
2309
2310            // This offset and the ones below need to be signed to account for replacement code
2311            // that is shorter than the original code.
2312            let mut offsets: Vec<(usize, isize)> = Vec::new();
2313            // Only show an underline in the suggestions if the suggestion is not the
2314            // entirety of the code being shown and the displayed code is not multiline.
2315            if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
2316                show_code_change
2317            {
2318                for part in parts {
2319                    let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
2320                    let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
2321
2322                    // If this addition is _only_ whitespace, then don't trim it,
2323                    // or else we're just not rendering anything.
2324                    let is_whitespace_addition = part.snippet.trim().is_empty();
2325
2326                    // Do not underline the leading...
2327                    let start = if is_whitespace_addition {
2328                        0
2329                    } else {
2330                        part.snippet.len().saturating_sub(part.snippet.trim_start().len())
2331                    };
2332                    // ...or trailing spaces. Account for substitutions containing unicode
2333                    // characters.
2334                    let sub_len: usize = str_width(if is_whitespace_addition {
2335                        &part.snippet
2336                    } else {
2337                        part.snippet.trim()
2338                    });
2339
2340                    let offset: isize = offsets
2341                        .iter()
2342                        .filter_map(
2343                            |(start, v)| if span_start_pos < *start { None } else { Some(v) },
2344                        )
2345                        .sum();
2346                    let underline_start = (span_start_pos + start) as isize + offset;
2347                    let underline_end = (span_start_pos + start + sub_len) as isize + offset;
2348                    assert!(underline_start >= 0 && underline_end >= 0);
2349                    let padding: usize = max_line_num_len + 3;
2350                    for p in underline_start..underline_end {
2351                        if let DisplaySuggestion::Underline = show_code_change
2352                            && is_different(sm, &part.snippet, part.span)
2353                        {
2354                            // If this is a replacement, underline with `~`, if this is an addition
2355                            // underline with `+`.
2356                            buffer.putc(
2357                                row_num,
2358                                (padding as isize + p) as usize,
2359                                if part.is_addition(sm) { '+' } else { self.diff() },
2360                                Style::Addition,
2361                            );
2362                        }
2363                    }
2364                    if let DisplaySuggestion::Diff = show_code_change {
2365                        // Colorize removal with red in diff format.
2366
2367                        // Below, there's some tricky buffer indexing going on. `row_num` at this
2368                        // point corresponds to:
2369                        //
2370                        //    |
2371                        // LL | CODE
2372                        //    | ++++  <- `row_num`
2373                        //
2374                        // in the buffer. When we have a diff format output, we end up with
2375                        //
2376                        //    |
2377                        // LL - OLDER   <- row_num - 2
2378                        // LL + NEWER
2379                        //    |         <- row_num
2380                        //
2381                        // The `row_num - 2` is to select the buffer line that has the "old version
2382                        // of the diff" at that point. When the removal is a single line, `i` is
2383                        // `0`, `newlines` is `1` so `(newlines - i - 1)` ends up being `0`, so row
2384                        // points at `LL - OLDER`. When the removal corresponds to multiple lines,
2385                        // we end up with `newlines > 1` and `i` being `0..newlines - 1`.
2386                        //
2387                        //    |
2388                        // LL - OLDER   <- row_num - 2 - (newlines - last_i - 1)
2389                        // LL - CODE
2390                        // LL - BEING
2391                        // LL - REMOVED <- row_num - 2 - (newlines - first_i - 1)
2392                        // LL + NEWER
2393                        //    |         <- row_num
2394                        let snippet = sm.span_to_snippet(part.span).unwrap_or_default();
2395                        let newlines = snippet.lines().count();
2396                        if newlines > 0 && row_num > newlines {
2397                            // Account for removals where the part being removed spans multiple
2398                            // lines.
2399                            // FIXME: We check the number of rows because in some cases, like in
2400                            // `tests/ui/lint/invalid-nan-comparison-suggestion.rs`, the rendered
2401                            // suggestion will only show the first line of code being replaced. The
2402                            // proper way of doing this would be to change the suggestion rendering
2403                            // logic to show the whole prior snippet, but the current output is not
2404                            // too bad to begin with, so we side-step that issue here.
2405                            for (i, line) in snippet.lines().enumerate() {
2406                                let line = normalize_whitespace(line);
2407                                let row = (row_num - 2 - (newlines - i - 1)).max(2);
2408                                // On the first line, we highlight between the start of the part
2409                                // span, and the end of that line.
2410                                // On the last line, we highlight between the start of the line, and
2411                                // the column of the part span end.
2412                                // On all others, we highlight the whole line.
2413                                let start = if i == 0 {
2414                                    (padding as isize + span_start_pos as isize) as usize
2415                                } else {
2416                                    padding
2417                                };
2418                                let end = if i == 0 {
2419                                    (padding as isize
2420                                        + span_start_pos as isize
2421                                        + line.len() as isize)
2422                                        as usize
2423                                } else if i == newlines - 1 {
2424                                    (padding as isize + span_end_pos as isize) as usize
2425                                } else {
2426                                    (padding as isize + line.len() as isize) as usize
2427                                };
2428                                buffer.set_style_range(row, start, end, Style::Removal, true);
2429                            }
2430                        } else {
2431                            // The removed code fits all in one line.
2432                            buffer.set_style_range(
2433                                row_num - 2,
2434                                (padding as isize + span_start_pos as isize) as usize,
2435                                (padding as isize + span_end_pos as isize) as usize,
2436                                Style::Removal,
2437                                true,
2438                            );
2439                        }
2440                    }
2441
2442                    // length of the code after substitution
2443                    let full_sub_len = str_width(&part.snippet) as isize;
2444
2445                    // length of the code to be substituted
2446                    let snippet_len = span_end_pos as isize - span_start_pos as isize;
2447                    // For multiple substitutions, use the position *after* the previous
2448                    // substitutions have happened, only when further substitutions are
2449                    // located strictly after.
2450                    offsets.push((span_end_pos, full_sub_len - snippet_len));
2451                }
2452                row_num += 1;
2453            }
2454
2455            // if we elided some lines, add an ellipsis
2456            if lines.next().is_some() {
2457                let placeholder = self.margin();
2458                let padding = str_width(placeholder);
2459                buffer.puts(
2460                    row_num,
2461                    max_line_num_len.saturating_sub(padding),
2462                    placeholder,
2463                    Style::LineNumber,
2464                );
2465            } else {
2466                let row = match show_code_change {
2467                    DisplaySuggestion::Diff
2468                    | DisplaySuggestion::Add
2469                    | DisplaySuggestion::Underline => row_num - 1,
2470                    DisplaySuggestion::None => row_num,
2471                };
2472                if other_suggestions > 0 {
2473                    self.draw_col_separator_no_space(&mut buffer, row, max_line_num_len + 1);
2474                } else {
2475                    self.draw_col_separator_end(&mut buffer, row, max_line_num_len + 1);
2476                }
2477                row_num = row + 1;
2478            }
2479        }
2480        if other_suggestions > 0 {
2481            self.draw_note_separator(&mut buffer, row_num, max_line_num_len + 1, false);
2482            let msg = format!(
2483                "and {} other candidate{}",
2484                other_suggestions,
2485                pluralize!(other_suggestions)
2486            );
2487            buffer.append(row_num, &msg, Style::NoStyle);
2488        }
2489
2490        emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2491        Ok(())
2492    }
2493
2494    #[instrument(level = "trace", skip(self, args, code, children, suggestions))]
2495    fn emit_messages_default(
2496        &mut self,
2497        level: &Level,
2498        messages: &[(DiagMessage, Style)],
2499        args: &FluentArgs<'_>,
2500        code: &Option<ErrCode>,
2501        span: &MultiSpan,
2502        children: &[Subdiag],
2503        suggestions: &[CodeSuggestion],
2504    ) {
2505        let max_line_num_len = if self.ui_testing {
2506            ANONYMIZED_LINE_NUM.len()
2507        } else {
2508            let n = self.get_max_line_num(span, children);
2509            num_decimal_digits(n)
2510        };
2511
2512        match self.emit_messages_default_inner(
2513            span,
2514            messages,
2515            args,
2516            code,
2517            level,
2518            max_line_num_len,
2519            false,
2520            !children.is_empty()
2521                || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden),
2522        ) {
2523            Ok(code_window_status) => {
2524                if !children.is_empty()
2525                    || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
2526                {
2527                    let mut buffer = StyledBuffer::new();
2528                    if !self.short_message {
2529                        if let Some(child) = children.iter().next()
2530                            && child.span.primary_spans().is_empty()
2531                        {
2532                            // We'll continue the vertical bar to point into the next note.
2533                            self.draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
2534                        } else if matches!(code_window_status, CodeWindowStatus::Open) {
2535                            // We'll close the vertical bar to visually end the code window.
2536                            self.draw_col_separator_end(&mut buffer, 0, max_line_num_len + 1);
2537                        }
2538                    }
2539                    if let Err(e) = emit_to_destination(
2540                        &buffer.render(),
2541                        level,
2542                        &mut self.dst,
2543                        self.short_message,
2544                    ) {
2545                        panic!("failed to emit error: {e}")
2546                    }
2547                }
2548                if !self.short_message {
2549                    for (i, child) in children.iter().enumerate() {
2550                        assert!(child.level.can_be_subdiag());
2551                        let span = &child.span;
2552                        // FIXME: audit that this behaves correctly with suggestions.
2553                        let should_close = match children.get(i + 1) {
2554                            Some(c) => !c.span.primary_spans().is_empty(),
2555                            None => i + 1 == children.len(),
2556                        };
2557                        if let Err(err) = self.emit_messages_default_inner(
2558                            span,
2559                            &child.messages,
2560                            args,
2561                            &None,
2562                            &child.level,
2563                            max_line_num_len,
2564                            true,
2565                            !should_close,
2566                        ) {
2567                            panic!("failed to emit error: {err}");
2568                        }
2569                    }
2570                    for (i, sugg) in suggestions.iter().enumerate() {
2571                        match sugg.style {
2572                            SuggestionStyle::CompletelyHidden => {
2573                                // do not display this suggestion, it is meant only for tools
2574                            }
2575                            SuggestionStyle::HideCodeAlways => {
2576                                if let Err(e) = self.emit_messages_default_inner(
2577                                    &MultiSpan::new(),
2578                                    &[(sugg.msg.to_owned(), Style::HeaderMsg)],
2579                                    args,
2580                                    &None,
2581                                    &Level::Help,
2582                                    max_line_num_len,
2583                                    true,
2584                                    // FIXME: this needs to account for the suggestion type,
2585                                    //        some don't take any space.
2586                                    i + 1 != suggestions.len(),
2587                                ) {
2588                                    panic!("failed to emit error: {e}");
2589                                }
2590                            }
2591                            SuggestionStyle::HideCodeInline
2592                            | SuggestionStyle::ShowCode
2593                            | SuggestionStyle::ShowAlways => {
2594                                if let Err(e) = self.emit_suggestion_default(
2595                                    span,
2596                                    sugg,
2597                                    args,
2598                                    &Level::Help,
2599                                    max_line_num_len,
2600                                ) {
2601                                    panic!("failed to emit error: {e}");
2602                                }
2603                            }
2604                        }
2605                    }
2606                }
2607            }
2608            Err(e) => panic!("failed to emit error: {e}"),
2609        }
2610
2611        match writeln!(self.dst) {
2612            Err(e) => panic!("failed to emit error: {e}"),
2613            _ => {
2614                if let Err(e) = self.dst.flush() {
2615                    panic!("failed to emit error: {e}")
2616                }
2617            }
2618        }
2619    }
2620
2621    fn draw_code_line(
2622        &self,
2623        buffer: &mut StyledBuffer,
2624        row_num: &mut usize,
2625        highlight_parts: &[SubstitutionHighlight],
2626        line_num: usize,
2627        line_to_add: &str,
2628        show_code_change: DisplaySuggestion,
2629        max_line_num_len: usize,
2630        file_lines: &FileLines,
2631        is_multiline: bool,
2632    ) {
2633        if let DisplaySuggestion::Diff = show_code_change {
2634            // We need to print more than one line if the span we need to remove is multiline.
2635            // For more info: https://github.com/rust-lang/rust/issues/92741
2636            let lines_to_remove = file_lines.lines.iter().take(file_lines.lines.len() - 1);
2637            for (index, line_to_remove) in lines_to_remove.enumerate() {
2638                self.draw_line_num(buffer, line_num + index, *row_num - 1, max_line_num_len);
2639                buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2640                let line = normalize_whitespace(
2641                    &file_lines.file.get_line(line_to_remove.line_index).unwrap(),
2642                );
2643                buffer.puts(*row_num - 1, max_line_num_len + 3, &line, Style::NoStyle);
2644                *row_num += 1;
2645            }
2646            // If the last line is exactly equal to the line we need to add, we can skip both of
2647            // them. This allows us to avoid output like the following:
2648            // 2 - &
2649            // 2 + if true { true } else { false }
2650            // 3 - if true { true } else { false }
2651            // If those lines aren't equal, we print their diff
2652            let last_line_index = file_lines.lines[file_lines.lines.len() - 1].line_index;
2653            let last_line = &file_lines.file.get_line(last_line_index).unwrap();
2654            if last_line != line_to_add {
2655                self.draw_line_num(
2656                    buffer,
2657                    line_num + file_lines.lines.len() - 1,
2658                    *row_num - 1,
2659                    max_line_num_len,
2660                );
2661                buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2662                buffer.puts(
2663                    *row_num - 1,
2664                    max_line_num_len + 3,
2665                    &normalize_whitespace(last_line),
2666                    Style::NoStyle,
2667                );
2668                if !line_to_add.trim().is_empty() {
2669                    // Check if after the removal, the line is left with only whitespace. If so, we
2670                    // will not show an "addition" line, as removing the whole line is what the user
2671                    // would really want.
2672                    // For example, for the following:
2673                    //   |
2674                    // 2 -     .await
2675                    // 2 +     (note the left over whitespace)
2676                    //   |
2677                    // We really want
2678                    //   |
2679                    // 2 -     .await
2680                    //   |
2681                    // *row_num -= 1;
2682                    self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2683                    buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2684                    buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2685                } else {
2686                    *row_num -= 1;
2687                }
2688            } else {
2689                *row_num -= 2;
2690            }
2691        } else if is_multiline {
2692            self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2693            match &highlight_parts {
2694                [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
2695                    buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2696                }
2697                [] | [SubstitutionHighlight { start: 0, end: 0 }] => {
2698                    self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
2699                }
2700                _ => {
2701                    let diff = self.diff();
2702                    buffer.puts(
2703                        *row_num,
2704                        max_line_num_len + 1,
2705                        &format!("{diff} "),
2706                        Style::Addition,
2707                    );
2708                }
2709            }
2710            //   LL | line_to_add
2711            //   ++^^^
2712            //    |  |
2713            //    |  magic `3`
2714            //    `max_line_num_len`
2715            buffer.puts(
2716                *row_num,
2717                max_line_num_len + 3,
2718                &normalize_whitespace(line_to_add),
2719                Style::NoStyle,
2720            );
2721        } else if let DisplaySuggestion::Add = show_code_change {
2722            self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2723            buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2724            buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2725        } else {
2726            self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2727            self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2728            buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2729        }
2730
2731        // Colorize addition/replacements with green.
2732        for &SubstitutionHighlight { start, end } in highlight_parts {
2733            // This is a no-op for empty ranges
2734            if start != end {
2735                // Account for tabs when highlighting (#87972).
2736                let tabs: usize = line_to_add
2737                    .chars()
2738                    .take(start)
2739                    .map(|ch| match ch {
2740                        '\t' => 3,
2741                        _ => 0,
2742                    })
2743                    .sum();
2744                buffer.set_style_range(
2745                    *row_num,
2746                    max_line_num_len + 3 + start + tabs,
2747                    max_line_num_len + 3 + end + tabs,
2748                    Style::Addition,
2749                    true,
2750                );
2751            }
2752        }
2753        *row_num += 1;
2754    }
2755
2756    fn underline(&self, is_primary: bool) -> UnderlineParts {
2757        //               X0 Y0
2758        // label_start > ┯━━━━ < underline
2759        //               │ < vertical_text_line
2760        //               text
2761
2762        //    multiline_start_down ⤷ X0 Y0
2763        //            top_left > ┌───╿──┘ < top_right_flat
2764        //           top_left > ┏│━━━┙ < top_right
2765        // multiline_vertical > ┃│
2766        //                      ┃│   X1 Y1
2767        //                      ┃│   X2 Y2
2768        //                      ┃└────╿──┘ < multiline_end_same_line
2769        //        bottom_left > ┗━━━━━┥ < bottom_right_with_text
2770        //   multiline_horizontal ^   `X` is a good letter
2771
2772        // multiline_whole_line > ┏ X0 Y0
2773        //                        ┃   X1 Y1
2774        //                        ┗━━━━┛ < multiline_end_same_line
2775
2776        // multiline_whole_line > ┏ X0 Y0
2777        //                        ┃ X1 Y1
2778        //                        ┃  ╿ < multiline_end_up
2779        //                        ┗━━┛ < bottom_right
2780
2781        match (self.theme, is_primary) {
2782            (OutputTheme::Ascii, true) => UnderlineParts {
2783                style: Style::UnderlinePrimary,
2784                underline: '^',
2785                label_start: '^',
2786                vertical_text_line: '|',
2787                multiline_vertical: '|',
2788                multiline_horizontal: '_',
2789                multiline_whole_line: '/',
2790                multiline_start_down: '^',
2791                bottom_right: '|',
2792                top_left: ' ',
2793                top_right_flat: '^',
2794                bottom_left: '|',
2795                multiline_end_up: '^',
2796                multiline_end_same_line: '^',
2797                multiline_bottom_right_with_text: '|',
2798            },
2799            (OutputTheme::Ascii, false) => UnderlineParts {
2800                style: Style::UnderlineSecondary,
2801                underline: '-',
2802                label_start: '-',
2803                vertical_text_line: '|',
2804                multiline_vertical: '|',
2805                multiline_horizontal: '_',
2806                multiline_whole_line: '/',
2807                multiline_start_down: '-',
2808                bottom_right: '|',
2809                top_left: ' ',
2810                top_right_flat: '-',
2811                bottom_left: '|',
2812                multiline_end_up: '-',
2813                multiline_end_same_line: '-',
2814                multiline_bottom_right_with_text: '|',
2815            },
2816            (OutputTheme::Unicode, true) => UnderlineParts {
2817                style: Style::UnderlinePrimary,
2818                underline: '━',
2819                label_start: '┯',
2820                vertical_text_line: '│',
2821                multiline_vertical: '┃',
2822                multiline_horizontal: '━',
2823                multiline_whole_line: '┏',
2824                multiline_start_down: '╿',
2825                bottom_right: '┙',
2826                top_left: '┏',
2827                top_right_flat: '┛',
2828                bottom_left: '┗',
2829                multiline_end_up: '╿',
2830                multiline_end_same_line: '┛',
2831                multiline_bottom_right_with_text: '┥',
2832            },
2833            (OutputTheme::Unicode, false) => UnderlineParts {
2834                style: Style::UnderlineSecondary,
2835                underline: '─',
2836                label_start: '┬',
2837                vertical_text_line: '│',
2838                multiline_vertical: '│',
2839                multiline_horizontal: '─',
2840                multiline_whole_line: '┌',
2841                multiline_start_down: '│',
2842                bottom_right: '┘',
2843                top_left: '┌',
2844                top_right_flat: '┘',
2845                bottom_left: '└',
2846                multiline_end_up: '│',
2847                multiline_end_same_line: '┘',
2848                multiline_bottom_right_with_text: '┤',
2849            },
2850        }
2851    }
2852
2853    fn col_separator(&self) -> char {
2854        match self.theme {
2855            OutputTheme::Ascii => '|',
2856            OutputTheme::Unicode => '│',
2857        }
2858    }
2859
2860    fn note_separator(&self, is_cont: bool) -> &'static str {
2861        match self.theme {
2862            OutputTheme::Ascii => "= ",
2863            OutputTheme::Unicode if is_cont => "├ ",
2864            OutputTheme::Unicode => "╰ ",
2865        }
2866    }
2867
2868    fn multi_suggestion_separator(&self) -> &'static str {
2869        match self.theme {
2870            OutputTheme::Ascii => "|",
2871            OutputTheme::Unicode => "├╴",
2872        }
2873    }
2874
2875    fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2876        let chr = self.col_separator();
2877        buffer.puts(line, col, &format!("{chr} "), Style::LineNumber);
2878    }
2879
2880    fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2881        let chr = self.col_separator();
2882        self.draw_col_separator_no_space_with_style(buffer, chr, line, col, Style::LineNumber);
2883    }
2884
2885    fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2886        match self.theme {
2887            OutputTheme::Ascii => {
2888                self.draw_col_separator_no_space_with_style(
2889                    buffer,
2890                    '|',
2891                    line,
2892                    col,
2893                    Style::LineNumber,
2894                );
2895            }
2896            OutputTheme::Unicode => {
2897                self.draw_col_separator_no_space_with_style(
2898                    buffer,
2899                    '╭',
2900                    line,
2901                    col,
2902                    Style::LineNumber,
2903                );
2904                self.draw_col_separator_no_space_with_style(
2905                    buffer,
2906                    '╴',
2907                    line,
2908                    col + 1,
2909                    Style::LineNumber,
2910                );
2911            }
2912        }
2913    }
2914
2915    fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2916        match self.theme {
2917            OutputTheme::Ascii => {
2918                self.draw_col_separator_no_space_with_style(
2919                    buffer,
2920                    '|',
2921                    line,
2922                    col,
2923                    Style::LineNumber,
2924                );
2925            }
2926            OutputTheme::Unicode => {
2927                self.draw_col_separator_no_space_with_style(
2928                    buffer,
2929                    '╰',
2930                    line,
2931                    col,
2932                    Style::LineNumber,
2933                );
2934                self.draw_col_separator_no_space_with_style(
2935                    buffer,
2936                    '╴',
2937                    line,
2938                    col + 1,
2939                    Style::LineNumber,
2940                );
2941            }
2942        }
2943    }
2944
2945    fn draw_col_separator_no_space_with_style(
2946        &self,
2947        buffer: &mut StyledBuffer,
2948        chr: char,
2949        line: usize,
2950        col: usize,
2951        style: Style,
2952    ) {
2953        buffer.putc(line, col, chr, style);
2954    }
2955
2956    fn draw_range(
2957        &self,
2958        buffer: &mut StyledBuffer,
2959        symbol: char,
2960        line: usize,
2961        col_from: usize,
2962        col_to: usize,
2963        style: Style,
2964    ) {
2965        for col in col_from..col_to {
2966            buffer.putc(line, col, symbol, style);
2967        }
2968    }
2969
2970    fn draw_note_separator(
2971        &self,
2972        buffer: &mut StyledBuffer,
2973        line: usize,
2974        col: usize,
2975        is_cont: bool,
2976    ) {
2977        let chr = self.note_separator(is_cont);
2978        buffer.puts(line, col, chr, Style::LineNumber);
2979    }
2980
2981    fn draw_multiline_line(
2982        &self,
2983        buffer: &mut StyledBuffer,
2984        line: usize,
2985        offset: usize,
2986        depth: usize,
2987        style: Style,
2988    ) {
2989        let chr = match (style, self.theme) {
2990            (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Ascii) => '|',
2991            (_, OutputTheme::Ascii) => '|',
2992            (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Unicode) => '┃',
2993            (_, OutputTheme::Unicode) => '│',
2994        };
2995        buffer.putc(line, offset + depth - 1, chr, style);
2996    }
2997
2998    fn file_start(&self) -> &'static str {
2999        match self.theme {
3000            OutputTheme::Ascii => "--> ",
3001            OutputTheme::Unicode => " ╭▸ ",
3002        }
3003    }
3004
3005    fn secondary_file_start(&self) -> &'static str {
3006        match self.theme {
3007            OutputTheme::Ascii => "::: ",
3008            OutputTheme::Unicode => " ⸬  ",
3009        }
3010    }
3011
3012    fn diff(&self) -> char {
3013        match self.theme {
3014            OutputTheme::Ascii => '~',
3015            OutputTheme::Unicode => '±',
3016        }
3017    }
3018
3019    fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
3020        let (column, dots) = match self.theme {
3021            OutputTheme::Ascii => (0, "..."),
3022            OutputTheme::Unicode => (col - 2, "‡"),
3023        };
3024        buffer.puts(line, column, dots, Style::LineNumber);
3025    }
3026
3027    fn margin(&self) -> &'static str {
3028        match self.theme {
3029            OutputTheme::Ascii => "...",
3030            OutputTheme::Unicode => "…",
3031        }
3032    }
3033
3034    fn draw_line_num(
3035        &self,
3036        buffer: &mut StyledBuffer,
3037        line_num: usize,
3038        line_offset: usize,
3039        max_line_num_len: usize,
3040    ) {
3041        let line_num = self.maybe_anonymized(line_num);
3042        buffer.puts(
3043            line_offset,
3044            max_line_num_len.saturating_sub(str_width(&line_num)),
3045            &line_num,
3046            Style::LineNumber,
3047        );
3048    }
3049}
3050
3051#[derive(Debug, Clone, Copy)]
3052struct UnderlineParts {
3053    style: Style,
3054    underline: char,
3055    label_start: char,
3056    vertical_text_line: char,
3057    multiline_vertical: char,
3058    multiline_horizontal: char,
3059    multiline_whole_line: char,
3060    multiline_start_down: char,
3061    bottom_right: char,
3062    top_left: char,
3063    top_right_flat: char,
3064    bottom_left: char,
3065    multiline_end_up: char,
3066    multiline_end_same_line: char,
3067    multiline_bottom_right_with_text: char,
3068}
3069
3070#[derive(Clone, Copy, Debug)]
3071enum DisplaySuggestion {
3072    Underline,
3073    Diff,
3074    None,
3075    Add,
3076}
3077
3078#[derive(Clone, Copy, Debug)]
3079enum CodeWindowStatus {
3080    Closed,
3081    Open,
3082}
3083
3084impl FileWithAnnotatedLines {
3085    /// Preprocess all the annotations so that they are grouped by file and by line number
3086    /// This helps us quickly iterate over the whole message (including secondary file spans)
3087    pub(crate) fn collect_annotations(
3088        emitter: &dyn Emitter,
3089        args: &FluentArgs<'_>,
3090        msp: &MultiSpan,
3091    ) -> Vec<FileWithAnnotatedLines> {
3092        fn add_annotation_to_file(
3093            file_vec: &mut Vec<FileWithAnnotatedLines>,
3094            file: Arc<SourceFile>,
3095            line_index: usize,
3096            ann: Annotation,
3097        ) {
3098            for slot in file_vec.iter_mut() {
3099                // Look through each of our files for the one we're adding to
3100                if slot.file.stable_id == file.stable_id {
3101                    // See if we already have a line for it
3102                    for line_slot in &mut slot.lines {
3103                        if line_slot.line_index == line_index {
3104                            line_slot.annotations.push(ann);
3105                            return;
3106                        }
3107                    }
3108                    // We don't have a line yet, create one
3109                    slot.lines.push(Line { line_index, annotations: vec![ann] });
3110                    slot.lines.sort();
3111                    return;
3112                }
3113            }
3114            // This is the first time we're seeing the file
3115            file_vec.push(FileWithAnnotatedLines {
3116                file,
3117                lines: vec![Line { line_index, annotations: vec![ann] }],
3118                multiline_depth: 0,
3119            });
3120        }
3121        let mut output = vec![];
3122        let mut multiline_annotations = vec![];
3123
3124        if let Some(sm) = emitter.source_map() {
3125            for SpanLabel { span, is_primary, label } in msp.span_labels() {
3126                // If we don't have a useful span, pick the primary span if that exists.
3127                // Worst case we'll just print an error at the top of the main file.
3128                let span = match (span.is_dummy(), msp.primary_span()) {
3129                    (_, None) | (false, _) => span,
3130                    (true, Some(span)) => span,
3131                };
3132
3133                let lo = sm.lookup_char_pos(span.lo());
3134                let mut hi = sm.lookup_char_pos(span.hi());
3135
3136                // Watch out for "empty spans". If we get a span like 6..6, we
3137                // want to just display a `^` at 6, so convert that to
3138                // 6..7. This is degenerate input, but it's best to degrade
3139                // gracefully -- and the parser likes to supply a span like
3140                // that for EOF, in particular.
3141
3142                if lo.col_display == hi.col_display && lo.line == hi.line {
3143                    hi.col_display += 1;
3144                }
3145
3146                let label = label.as_ref().map(|m| {
3147                    normalize_whitespace(
3148                        &emitter
3149                            .translator()
3150                            .translate_message(m, args)
3151                            .map_err(Report::new)
3152                            .unwrap(),
3153                    )
3154                });
3155
3156                if lo.line != hi.line {
3157                    let ml = MultilineAnnotation {
3158                        depth: 1,
3159                        line_start: lo.line,
3160                        line_end: hi.line,
3161                        start_col: AnnotationColumn::from_loc(&lo),
3162                        end_col: AnnotationColumn::from_loc(&hi),
3163                        is_primary,
3164                        label,
3165                        overlaps_exactly: false,
3166                    };
3167                    multiline_annotations.push((lo.file, ml));
3168                } else {
3169                    let ann = Annotation {
3170                        start_col: AnnotationColumn::from_loc(&lo),
3171                        end_col: AnnotationColumn::from_loc(&hi),
3172                        is_primary,
3173                        label,
3174                        annotation_type: AnnotationType::Singleline,
3175                    };
3176                    add_annotation_to_file(&mut output, lo.file, lo.line, ann);
3177                };
3178            }
3179        }
3180
3181        // Find overlapping multiline annotations, put them at different depths
3182        multiline_annotations.sort_by_key(|(_, ml)| (ml.line_start, usize::MAX - ml.line_end));
3183        for (_, ann) in multiline_annotations.clone() {
3184            for (_, a) in multiline_annotations.iter_mut() {
3185                // Move all other multiline annotations overlapping with this one
3186                // one level to the right.
3187                if !(ann.same_span(a))
3188                    && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
3189                {
3190                    a.increase_depth();
3191                } else if ann.same_span(a) && &ann != a {
3192                    a.overlaps_exactly = true;
3193                } else {
3194                    break;
3195                }
3196            }
3197        }
3198
3199        let mut max_depth = 0; // max overlapping multiline spans
3200        for (_, ann) in &multiline_annotations {
3201            max_depth = max(max_depth, ann.depth);
3202        }
3203        // Change order of multispan depth to minimize the number of overlaps in the ASCII art.
3204        for (_, a) in multiline_annotations.iter_mut() {
3205            a.depth = max_depth - a.depth + 1;
3206        }
3207        for (file, ann) in multiline_annotations {
3208            let mut end_ann = ann.as_end();
3209            if !ann.overlaps_exactly {
3210                // avoid output like
3211                //
3212                //  |        foo(
3213                //  |   _____^
3214                //  |  |_____|
3215                //  | ||         bar,
3216                //  | ||     );
3217                //  | ||      ^
3218                //  | ||______|
3219                //  |  |______foo
3220                //  |         baz
3221                //
3222                // and instead get
3223                //
3224                //  |       foo(
3225                //  |  _____^
3226                //  | |         bar,
3227                //  | |     );
3228                //  | |      ^
3229                //  | |      |
3230                //  | |______foo
3231                //  |        baz
3232                add_annotation_to_file(
3233                    &mut output,
3234                    Arc::clone(&file),
3235                    ann.line_start,
3236                    ann.as_start(),
3237                );
3238                // 4 is the minimum vertical length of a multiline span when presented: two lines
3239                // of code and two lines of underline. This is not true for the special case where
3240                // the beginning doesn't have an underline, but the current logic seems to be
3241                // working correctly.
3242                let middle = min(ann.line_start + 4, ann.line_end);
3243                // We'll show up to 4 lines past the beginning of the multispan start.
3244                // We will *not* include the tail of lines that are only whitespace, a comment or
3245                // a bare delimiter.
3246                let filter = |s: &str| {
3247                    let s = s.trim();
3248                    // Consider comments as empty, but don't consider docstrings to be empty.
3249                    !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
3250                        // Consider lines with nothing but whitespace, a single delimiter as empty.
3251                        && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
3252                };
3253                let until = (ann.line_start..middle)
3254                    .rev()
3255                    .filter_map(|line| file.get_line(line - 1).map(|s| (line + 1, s)))
3256                    .find(|(_, s)| filter(s))
3257                    .map(|(line, _)| line)
3258                    .unwrap_or(ann.line_start);
3259                for line in ann.line_start + 1..until {
3260                    // Every `|` that joins the beginning of the span (`___^`) to the end (`|__^`).
3261                    add_annotation_to_file(&mut output, Arc::clone(&file), line, ann.as_line());
3262                }
3263                let line_end = ann.line_end - 1;
3264                let end_is_empty = file.get_line(line_end - 1).is_some_and(|s| !filter(&s));
3265                if middle < line_end && !end_is_empty {
3266                    add_annotation_to_file(&mut output, Arc::clone(&file), line_end, ann.as_line());
3267                }
3268            } else {
3269                end_ann.annotation_type = AnnotationType::Singleline;
3270            }
3271            add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
3272        }
3273        for file_vec in output.iter_mut() {
3274            file_vec.multiline_depth = max_depth;
3275        }
3276        output
3277    }
3278}
3279
3280// instead of taking the String length or dividing by 10 while > 0, we multiply a limit by 10 until
3281// we're higher. If the loop isn't exited by the `return`, the last multiplication will wrap, which
3282// is OK, because while we cannot fit a higher power of 10 in a usize, the loop will end anyway.
3283// This is also why we need the max number of decimal digits within a `usize`.
3284fn num_decimal_digits(num: usize) -> usize {
3285    #[cfg(target_pointer_width = "64")]
3286    const MAX_DIGITS: usize = 20;
3287
3288    #[cfg(target_pointer_width = "32")]
3289    const MAX_DIGITS: usize = 10;
3290
3291    #[cfg(target_pointer_width = "16")]
3292    const MAX_DIGITS: usize = 5;
3293
3294    let mut lim = 10;
3295    for num_digits in 1..MAX_DIGITS {
3296        if num < lim {
3297            return num_digits;
3298        }
3299        lim = lim.wrapping_mul(10);
3300    }
3301    MAX_DIGITS
3302}
3303
3304// We replace some characters so the CLI output is always consistent and underlines aligned.
3305// Keep the following list in sync with `rustc_span::char_width`.
3306const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
3307    // In terminals without Unicode support the following will be garbled, but in *all* terminals
3308    // the underlying codepoint will be as well. We could gate this replacement behind a "unicode
3309    // support" gate.
3310    ('\0', "␀"),
3311    ('\u{0001}', "␁"),
3312    ('\u{0002}', "␂"),
3313    ('\u{0003}', "␃"),
3314    ('\u{0004}', "␄"),
3315    ('\u{0005}', "␅"),
3316    ('\u{0006}', "␆"),
3317    ('\u{0007}', "␇"),
3318    ('\u{0008}', "␈"),
3319    ('\t', "    "), // We do our own tab replacement
3320    ('\u{000b}', "␋"),
3321    ('\u{000c}', "␌"),
3322    ('\u{000d}', "␍"),
3323    ('\u{000e}', "␎"),
3324    ('\u{000f}', "␏"),
3325    ('\u{0010}', "␐"),
3326    ('\u{0011}', "␑"),
3327    ('\u{0012}', "␒"),
3328    ('\u{0013}', "␓"),
3329    ('\u{0014}', "␔"),
3330    ('\u{0015}', "␕"),
3331    ('\u{0016}', "␖"),
3332    ('\u{0017}', "␗"),
3333    ('\u{0018}', "␘"),
3334    ('\u{0019}', "␙"),
3335    ('\u{001a}', "␚"),
3336    ('\u{001b}', "␛"),
3337    ('\u{001c}', "␜"),
3338    ('\u{001d}', "␝"),
3339    ('\u{001e}', "␞"),
3340    ('\u{001f}', "␟"),
3341    ('\u{007f}', "␡"),
3342    ('\u{200d}', ""), // Replace ZWJ for consistent terminal output of grapheme clusters.
3343    ('\u{202a}', "�"), // The following unicode text flow control characters are inconsistently
3344    ('\u{202b}', "�"), // supported across CLIs and can cause confusion due to the bytes on disk
3345    ('\u{202c}', "�"), // not corresponding to the visible source code, so we replace them always.
3346    ('\u{202d}', "�"),
3347    ('\u{202e}', "�"),
3348    ('\u{2066}', "�"),
3349    ('\u{2067}', "�"),
3350    ('\u{2068}', "�"),
3351    ('\u{2069}', "�"),
3352];
3353
3354pub(crate) fn normalize_whitespace(s: &str) -> String {
3355    const {
3356        let mut i = 1;
3357        while i < OUTPUT_REPLACEMENTS.len() {
3358            assert!(
3359                OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
3360                "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
3361                and must contain no duplicate entries"
3362            );
3363            i += 1;
3364        }
3365    }
3366    // Scan the input string for a character in the ordered table above.
3367    // If it's present, replace it with its alternative string (it can be more than 1 char!).
3368    // Otherwise, retain the input char.
3369    s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
3370        match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
3371            Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
3372            _ => s.push(c),
3373        }
3374        s
3375    })
3376}
3377
3378fn num_overlap(
3379    a_start: usize,
3380    a_end: usize,
3381    b_start: usize,
3382    b_end: usize,
3383    inclusive: bool,
3384) -> bool {
3385    let extra = if inclusive { 1 } else { 0 };
3386    (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
3387}
3388
3389fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
3390    num_overlap(
3391        a1.start_col.display,
3392        a1.end_col.display + padding,
3393        a2.start_col.display,
3394        a2.end_col.display,
3395        false,
3396    )
3397}
3398
3399pub(crate) fn emit_to_destination(
3400    rendered_buffer: &[Vec<StyledString>],
3401    lvl: &Level,
3402    dst: &mut Destination,
3403    short_message: bool,
3404) -> io::Result<()> {
3405    use crate::lock;
3406
3407    // In order to prevent error message interleaving, where multiple error lines get intermixed
3408    // when multiple compiler processes error simultaneously, we emit errors with additional
3409    // steps.
3410    //
3411    // On Unix systems, we write into a buffered terminal rather than directly to a terminal. When
3412    // the .flush() is called we take the buffer created from the buffered writes and write it at
3413    // one shot. Because the Unix systems use ANSI for the colors, which is a text-based styling
3414    // scheme, this buffered approach works and maintains the styling.
3415    //
3416    // On Windows, styling happens through calls to a terminal API. This prevents us from using the
3417    // same buffering approach. Instead, we use a global Windows mutex, which we acquire long
3418    // enough to output the full error message, then we release.
3419    let _buffer_lock = lock::acquire_global_lock("rustc_errors");
3420    for (pos, line) in rendered_buffer.iter().enumerate() {
3421        for part in line {
3422            let style = part.style.anstyle(*lvl);
3423            write!(dst, "{style}{}{style:#}", part.text)?;
3424        }
3425        if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
3426            writeln!(dst)?;
3427        }
3428    }
3429    dst.flush()?;
3430    Ok(())
3431}
3432
3433pub type Destination = AutoStream<Box<dyn Write + Send>>;
3434
3435struct Buffy {
3436    buffer_writer: std::io::Stderr,
3437    buffer: Vec<u8>,
3438}
3439
3440impl Write for Buffy {
3441    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3442        self.buffer.write(buf)
3443    }
3444
3445    fn flush(&mut self) -> io::Result<()> {
3446        self.buffer_writer.write_all(&self.buffer)?;
3447        self.buffer.clear();
3448        Ok(())
3449    }
3450}
3451
3452impl Drop for Buffy {
3453    fn drop(&mut self) {
3454        if !self.buffer.is_empty() {
3455            self.flush().unwrap();
3456            panic!("buffers need to be flushed in order to print their contents");
3457        }
3458    }
3459}
3460
3461pub fn stderr_destination(color: ColorConfig) -> Destination {
3462    let buffer_writer = std::io::stderr();
3463    // We need to resolve `ColorChoice::Auto` before `Box`ing since
3464    // `ColorChoice::Auto` on `dyn Write` will always resolve to `Never`
3465    let choice = get_stderr_color_choice(color, &buffer_writer);
3466    // On Windows we'll be performing global synchronization on the entire
3467    // system for emitting rustc errors, so there's no need to buffer
3468    // anything.
3469    //
3470    // On non-Windows we rely on the atomicity of `write` to ensure errors
3471    // don't get all jumbled up.
3472    if cfg!(windows) {
3473        AutoStream::new(Box::new(buffer_writer), choice)
3474    } else {
3475        let buffer = Vec::new();
3476        AutoStream::new(Box::new(Buffy { buffer_writer, buffer }), choice)
3477    }
3478}
3479
3480pub fn get_stderr_color_choice(color: ColorConfig, stderr: &std::io::Stderr) -> ColorChoice {
3481    let choice = color.to_color_choice();
3482    if matches!(choice, ColorChoice::Auto) { AutoStream::choice(stderr) } else { choice }
3483}
3484
3485/// On Windows, BRIGHT_BLUE is hard to read on black. Use cyan instead.
3486///
3487/// See #36178.
3488const BRIGHT_BLUE: anstyle::Style = if cfg!(windows) {
3489    AnsiColor::BrightCyan.on_default()
3490} else {
3491    AnsiColor::BrightBlue.on_default()
3492};
3493
3494impl Style {
3495    pub(crate) fn anstyle(&self, lvl: Level) -> anstyle::Style {
3496        match self {
3497            Style::Addition => AnsiColor::BrightGreen.on_default(),
3498            Style::Removal => AnsiColor::BrightRed.on_default(),
3499            Style::LineAndColumn => anstyle::Style::new(),
3500            Style::LineNumber => BRIGHT_BLUE.effects(Effects::BOLD),
3501            Style::Quotation => anstyle::Style::new(),
3502            Style::MainHeaderMsg => if cfg!(windows) {
3503                AnsiColor::BrightWhite.on_default()
3504            } else {
3505                anstyle::Style::new()
3506            }
3507            .effects(Effects::BOLD),
3508            Style::UnderlinePrimary | Style::LabelPrimary => lvl.color().effects(Effects::BOLD),
3509            Style::UnderlineSecondary | Style::LabelSecondary => BRIGHT_BLUE.effects(Effects::BOLD),
3510            Style::HeaderMsg | Style::NoStyle => anstyle::Style::new(),
3511            Style::Level(lvl) => lvl.color().effects(Effects::BOLD),
3512            Style::Highlight => AnsiColor::Magenta.on_default().effects(Effects::BOLD),
3513        }
3514    }
3515}
3516
3517/// Whether the original and suggested code are the same.
3518pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3519    let found = match sm.span_to_snippet(sp) {
3520        Ok(snippet) => snippet,
3521        Err(e) => {
3522            warn!(error = ?e, "Invalid span {:?}", sp);
3523            return true;
3524        }
3525    };
3526    found != suggested
3527}
3528
3529/// Whether the original and suggested code are visually similar enough to warrant extra wording.
3530pub fn detect_confusion_type(sm: &SourceMap, suggested: &str, sp: Span) -> ConfusionType {
3531    let found = match sm.span_to_snippet(sp) {
3532        Ok(snippet) => snippet,
3533        Err(e) => {
3534            warn!(error = ?e, "Invalid span {:?}", sp);
3535            return ConfusionType::None;
3536        }
3537    };
3538
3539    let mut has_case_confusion = false;
3540    let mut has_digit_letter_confusion = false;
3541
3542    if found.len() == suggested.len() {
3543        let mut has_case_diff = false;
3544        let mut has_digit_letter_confusable = false;
3545        let mut has_other_diff = false;
3546
3547        let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
3548
3549        let digit_letter_confusables = [('0', 'O'), ('1', 'l'), ('5', 'S'), ('8', 'B'), ('9', 'g')];
3550
3551        for (f, s) in iter::zip(found.chars(), suggested.chars()) {
3552            if f != s {
3553                if f.eq_ignore_ascii_case(&s) {
3554                    // Check for case differences (any character that differs only in case)
3555                    if ascii_confusables.contains(&f) || ascii_confusables.contains(&s) {
3556                        has_case_diff = true;
3557                    } else {
3558                        has_other_diff = true;
3559                    }
3560                } else if digit_letter_confusables.contains(&(f, s))
3561                    || digit_letter_confusables.contains(&(s, f))
3562                {
3563                    // Check for digit-letter confusables (like 0 vs O, 1 vs l, etc.)
3564                    has_digit_letter_confusable = true;
3565                } else {
3566                    has_other_diff = true;
3567                }
3568            }
3569        }
3570
3571        // If we have case differences and no other differences
3572        if has_case_diff && !has_other_diff && found != suggested {
3573            has_case_confusion = true;
3574        }
3575        if has_digit_letter_confusable && !has_other_diff && found != suggested {
3576            has_digit_letter_confusion = true;
3577        }
3578    }
3579
3580    match (has_case_confusion, has_digit_letter_confusion) {
3581        (true, true) => ConfusionType::Both,
3582        (true, false) => ConfusionType::Case,
3583        (false, true) => ConfusionType::DigitLetter,
3584        (false, false) => ConfusionType::None,
3585    }
3586}
3587
3588/// Represents the type of confusion detected between original and suggested code.
3589#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3590pub enum ConfusionType {
3591    /// No confusion detected
3592    None,
3593    /// Only case differences (e.g., "hello" vs "Hello")
3594    Case,
3595    /// Only digit-letter confusion (e.g., "0" vs "O", "1" vs "l")
3596    DigitLetter,
3597    /// Both case and digit-letter confusion
3598    Both,
3599}
3600
3601impl ConfusionType {
3602    /// Returns the appropriate label text for this confusion type.
3603    pub fn label_text(&self) -> &'static str {
3604        match self {
3605            ConfusionType::None => "",
3606            ConfusionType::Case => " (notice the capitalization)",
3607            ConfusionType::DigitLetter => " (notice the digit/letter confusion)",
3608            ConfusionType::Both => " (notice the capitalization and digit/letter confusion)",
3609        }
3610    }
3611
3612    /// Combines two confusion types. If either is `Both`, the result is `Both`.
3613    /// If one is `Case` and the other is `DigitLetter`, the result is `Both`.
3614    /// Otherwise, returns the non-`None` type, or `None` if both are `None`.
3615    pub fn combine(self, other: ConfusionType) -> ConfusionType {
3616        match (self, other) {
3617            (ConfusionType::None, other) => other,
3618            (this, ConfusionType::None) => this,
3619            (ConfusionType::Both, _) | (_, ConfusionType::Both) => ConfusionType::Both,
3620            (ConfusionType::Case, ConfusionType::DigitLetter)
3621            | (ConfusionType::DigitLetter, ConfusionType::Case) => ConfusionType::Both,
3622            (ConfusionType::Case, ConfusionType::Case) => ConfusionType::Case,
3623            (ConfusionType::DigitLetter, ConfusionType::DigitLetter) => ConfusionType::DigitLetter,
3624        }
3625    }
3626
3627    /// Returns true if this confusion type represents any kind of confusion.
3628    pub fn has_confusion(&self) -> bool {
3629        *self != ConfusionType::None
3630    }
3631}
3632
3633pub(crate) fn should_show_source_code(
3634    ignored_directories: &[String],
3635    sm: &SourceMap,
3636    file: &SourceFile,
3637) -> bool {
3638    if !sm.ensure_source_file_source_present(file) {
3639        return false;
3640    }
3641
3642    let FileName::Real(name) = &file.name else { return true };
3643    name.local_path()
3644        .map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
3645        .unwrap_or(true)
3646}