rustc_errors/
emitter.rs

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

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy