rustc_errors/
emitter.rs

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