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