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