rustc_errors/
emitter.rs

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