rustc_errors/
emitter.rs

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