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