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