rustc_errors/
emitter.rs

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