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