1use 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::{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
43const DEFAULT_COLUMN_WIDTH: usize = 140;
45
46#[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 pub whitespace_left: usize,
65 pub span_left: usize,
67 pub span_right: usize,
69 pub computed_left: usize,
71 pub computed_right: usize,
73 pub column_width: usize,
76 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 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 self.computed_left = if self.whitespace_left > 20 {
122 self.whitespace_left - 16 } else {
124 0
125 };
126 self.computed_right = max(max_line_len, self.computed_left);
129
130 if self.computed_right - self.computed_left > self.column_width {
131 if self.label_right - self.whitespace_left <= self.column_width {
133 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 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 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 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
176pub trait Emitter {
178 fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
180
181 fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
184
185 fn emit_timing_section(&mut self, _record: TimingRecord, _event: TimingEvent) {}
188
189 fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}
192
193 fn emit_unused_externs(
196 &mut self,
197 _lint_level: rustc_lint_defs::Level,
198 _unused_externs: &[&str],
199 ) {
200 }
201
202 fn should_show_explain(&self) -> bool {
204 true
205 }
206
207 fn supports_color(&self) -> bool {
209 false
210 }
211
212 fn source_map(&self) -> Option<&SourceMap>;
213
214 fn translator(&self) -> &Translator;
215
216 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 && let [substitution] = sugg.substitutions.as_slice()
243 && let [part] = substitution.parts.as_slice()
245 && msg.split_whitespace().count() < 10
247 && !part.snippet.contains('\n')
249 && ![
250 SuggestionStyle::HideCodeAlways,
252 SuggestionStyle::CompletelyHidden,
254 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 format!("help: {msg}")
263 } else {
264 let confusion_type = self
266 .source_map()
267 .map(|sm| detect_confusion_type(sm, snippet, part.span))
268 .unwrap_or(ConfusionType::None);
269 format!("help: {}{}: `{}`", msg, confusion_type.label_text(), snippet,)
270 };
271 primary_span.push_span_label(part.span, msg);
272
273 suggestions.clear();
275 } else {
276 }
281 } else {
282 }
284 }
285
286 fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
287 &self,
288 span: &mut MultiSpan,
289 children: &mut Vec<Subdiag>,
290 level: &Level,
291 backtrace: bool,
292 ) {
293 let has_macro_spans: Vec<_> = iter::once(&*span)
296 .chain(children.iter().map(|child| &child.span))
297 .flat_map(|span| span.primary_spans())
298 .flat_map(|sp| sp.macro_backtrace())
299 .filter_map(|expn_data| {
300 match expn_data.kind {
301 ExpnKind::Root => None,
302
303 ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
306
307 ExpnKind::Macro(macro_kind, name) => {
308 Some((macro_kind, name, expn_data.hide_backtrace))
309 }
310 }
311 })
312 .collect();
313
314 if !backtrace {
315 self.fix_multispans_in_extern_macros(span, children);
316 }
317
318 self.render_multispans_macro_backtrace(span, children, backtrace);
319
320 if !backtrace {
321 if let Some((macro_kind, name, _)) = has_macro_spans.first()
324 && let Some((_, _, false)) = has_macro_spans.last()
325 {
326 let and_then = if let Some((macro_kind, last_name, _)) = has_macro_spans.last()
328 && last_name != name
329 {
330 let descr = macro_kind.descr();
331 format!(" which comes from the expansion of the {descr} `{last_name}`")
332 } else {
333 "".to_string()
334 };
335
336 let descr = macro_kind.descr();
337 let msg = format!(
338 "this {level} originates in the {descr} `{name}`{and_then} \
339 (in Nightly builds, run with -Z macro-backtrace for more info)",
340 );
341
342 children.push(Subdiag {
343 level: Level::Note,
344 messages: vec![(DiagMessage::from(msg), Style::NoStyle)],
345 span: MultiSpan::new(),
346 });
347 }
348 }
349 }
350
351 fn render_multispans_macro_backtrace(
352 &self,
353 span: &mut MultiSpan,
354 children: &mut Vec<Subdiag>,
355 backtrace: bool,
356 ) {
357 for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
358 self.render_multispan_macro_backtrace(span, backtrace);
359 }
360 }
361
362 fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
363 let mut new_labels = FxIndexSet::default();
364
365 for &sp in span.primary_spans() {
366 if sp.is_dummy() {
367 continue;
368 }
369
370 let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
374 for (i, trace) in macro_backtrace.iter().rev().enumerate() {
375 if trace.def_site.is_dummy() {
376 continue;
377 }
378
379 if always_backtrace {
380 new_labels.insert((
381 trace.def_site,
382 format!(
383 "in this expansion of `{}`{}",
384 trace.kind.descr(),
385 if macro_backtrace.len() > 1 {
386 format!(" (#{})", i + 1)
389 } else {
390 String::new()
391 },
392 ),
393 ));
394 }
395
396 let redundant_span = trace.call_site.contains(sp);
408
409 if !redundant_span || always_backtrace {
410 let msg: Cow<'static, _> = match trace.kind {
411 ExpnKind::Macro(MacroKind::Attr, _) => {
412 "this attribute macro expansion".into()
413 }
414 ExpnKind::Macro(MacroKind::Derive, _) => {
415 "this derive macro expansion".into()
416 }
417 ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
418 ExpnKind::Root => "the crate root".into(),
419 ExpnKind::AstPass(kind) => kind.descr().into(),
420 ExpnKind::Desugaring(kind) => {
421 format!("this {} desugaring", kind.descr()).into()
422 }
423 };
424 new_labels.insert((
425 trace.call_site,
426 format!(
427 "in {}{}",
428 msg,
429 if macro_backtrace.len() > 1 && always_backtrace {
430 format!(" (#{})", i + 1)
433 } else {
434 String::new()
435 },
436 ),
437 ));
438 }
439 if !always_backtrace {
440 break;
441 }
442 }
443 }
444
445 for (label_span, label_text) in new_labels {
446 span.push_span_label(label_span, label_text);
447 }
448 }
449
450 fn fix_multispans_in_extern_macros(&self, span: &mut MultiSpan, children: &mut Vec<Subdiag>) {
454 debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
455 self.fix_multispan_in_extern_macros(span);
456 for child in children.iter_mut() {
457 self.fix_multispan_in_extern_macros(&mut child.span);
458 }
459 debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
460 }
461
462 fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) {
466 let Some(source_map) = self.source_map() else { return };
467 let replacements: Vec<(Span, Span)> = span
469 .primary_spans()
470 .iter()
471 .copied()
472 .chain(span.span_labels().iter().map(|sp_label| sp_label.span))
473 .filter_map(|sp| {
474 if !sp.is_dummy() && source_map.is_imported(sp) {
475 let maybe_callsite = sp.source_callsite();
476 if sp != maybe_callsite {
477 return Some((sp, maybe_callsite));
478 }
479 }
480 None
481 })
482 .collect();
483
484 for (from, to) in replacements {
486 span.replace(from, to);
487 }
488 }
489}
490
491impl Emitter for HumanEmitter {
492 fn source_map(&self) -> Option<&SourceMap> {
493 self.sm.as_deref()
494 }
495
496 fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
497 let fluent_args = to_fluent_args(diag.args.iter());
498
499 if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() {
500 diag.children.insert(0, diag.emitted_at_sub_diag());
501 }
502
503 let mut suggestions = diag.suggestions.unwrap_tag();
504 self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
505
506 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
507 &mut diag.span,
508 &mut diag.children,
509 &diag.level,
510 self.macro_backtrace,
511 );
512
513 self.emit_messages_default(
514 &diag.level,
515 &diag.messages,
516 &fluent_args,
517 &diag.code,
518 &diag.span,
519 &diag.children,
520 &suggestions,
521 );
522 }
523
524 fn should_show_explain(&self) -> bool {
525 !self.short_message
526 }
527
528 fn supports_color(&self) -> bool {
529 self.dst.supports_color()
530 }
531
532 fn translator(&self) -> &Translator {
533 &self.translator
534 }
535}
536
537pub struct FatalOnlyEmitter {
541 pub fatal_emitter: Box<dyn Emitter + DynSend>,
542 pub fatal_note: Option<String>,
543}
544
545impl Emitter for FatalOnlyEmitter {
546 fn source_map(&self) -> Option<&SourceMap> {
547 None
548 }
549
550 fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
551 if diag.level == Level::Fatal {
552 if let Some(fatal_note) = &self.fatal_note {
553 diag.sub(Level::Note, fatal_note.clone(), MultiSpan::new());
554 }
555 self.fatal_emitter.emit_diagnostic(diag, registry);
556 }
557 }
558
559 fn translator(&self) -> &Translator {
560 self.fatal_emitter.translator()
561 }
562}
563
564pub struct SilentEmitter {
565 pub translator: Translator,
566}
567
568impl Emitter for SilentEmitter {
569 fn source_map(&self) -> Option<&SourceMap> {
570 None
571 }
572
573 fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {}
574
575 fn translator(&self) -> &Translator {
576 &self.translator
577 }
578}
579
580pub const MAX_SUGGESTIONS: usize = 4;
584
585#[derive(Clone, Copy, Debug, PartialEq, Eq)]
586pub enum ColorConfig {
587 Auto,
588 Always,
589 Never,
590}
591
592impl ColorConfig {
593 pub fn to_color_choice(self) -> ColorChoice {
594 match self {
595 ColorConfig::Always => {
596 if io::stderr().is_terminal() {
597 ColorChoice::Always
598 } else {
599 ColorChoice::AlwaysAnsi
600 }
601 }
602 ColorConfig::Never => ColorChoice::Never,
603 ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto,
604 ColorConfig::Auto => ColorChoice::Never,
605 }
606 }
607}
608
609#[derive(Debug, Clone, Copy, PartialEq, Eq)]
610pub enum OutputTheme {
611 Ascii,
612 Unicode,
613}
614
615#[derive(Setters)]
617pub struct HumanEmitter {
618 #[setters(skip)]
619 dst: IntoDynSyncSend<Destination>,
620 sm: Option<Arc<SourceMap>>,
621 #[setters(skip)]
622 translator: Translator,
623 short_message: bool,
624 ui_testing: bool,
625 ignored_directories_in_source_blocks: Vec<String>,
626 diagnostic_width: Option<usize>,
627
628 macro_backtrace: bool,
629 track_diagnostics: bool,
630 terminal_url: TerminalUrl,
631 theme: OutputTheme,
632}
633
634#[derive(Debug)]
635pub(crate) struct FileWithAnnotatedLines {
636 pub(crate) file: Arc<SourceFile>,
637 pub(crate) lines: Vec<Line>,
638 multiline_depth: usize,
639}
640
641impl HumanEmitter {
642 pub fn new(dst: Destination, translator: Translator) -> HumanEmitter {
643 HumanEmitter {
644 dst: IntoDynSyncSend(dst),
645 sm: None,
646 translator,
647 short_message: false,
648 ui_testing: false,
649 ignored_directories_in_source_blocks: Vec::new(),
650 diagnostic_width: None,
651 macro_backtrace: false,
652 track_diagnostics: false,
653 terminal_url: TerminalUrl::No,
654 theme: OutputTheme::Ascii,
655 }
656 }
657
658 fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
659 if self.ui_testing {
660 Cow::Borrowed(ANONYMIZED_LINE_NUM)
661 } else {
662 Cow::Owned(line_num.to_string())
663 }
664 }
665
666 fn draw_line(
667 &self,
668 buffer: &mut StyledBuffer,
669 source_string: &str,
670 line_index: usize,
671 line_offset: usize,
672 width_offset: usize,
673 code_offset: usize,
674 margin: Margin,
675 ) -> usize {
676 let line_len = source_string.len();
677 let left = margin.left(line_len);
679 let right = margin.right(line_len);
680 let code: String = source_string
683 .chars()
684 .enumerate()
685 .skip_while(|(i, _)| *i < left)
686 .take_while(|(i, _)| *i < right)
687 .map(|(_, c)| c)
688 .collect();
689 let code = normalize_whitespace(&code);
690 let was_cut_right =
691 source_string.chars().enumerate().skip_while(|(i, _)| *i < right).next().is_some();
692 buffer.puts(line_offset, code_offset, &code, Style::Quotation);
693 let placeholder = self.margin();
694 if margin.was_cut_left() {
695 buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber);
697 }
698 if was_cut_right {
699 let padding = str_width(placeholder);
700 buffer.puts(
702 line_offset,
703 code_offset + str_width(&code) - padding,
704 placeholder,
705 Style::LineNumber,
706 );
707 }
708 self.draw_line_num(buffer, line_index, line_offset, width_offset - 3);
709 self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2);
710 left
711 }
712
713 #[instrument(level = "trace", skip(self), ret)]
714 fn render_source_line(
715 &self,
716 buffer: &mut StyledBuffer,
717 file: Arc<SourceFile>,
718 line: &Line,
719 width_offset: usize,
720 code_offset: usize,
721 margin: Margin,
722 close_window: bool,
723 ) -> Vec<(usize, Style)> {
724 if line.line_index == 0 {
739 return Vec::new();
740 }
741
742 let Some(source_string) = file.get_line(line.line_index - 1) else {
743 return Vec::new();
744 };
745 trace!(?source_string);
746
747 let line_offset = buffer.num_lines();
748
749 let left = self.draw_line(
752 buffer,
753 &source_string,
754 line.line_index,
755 line_offset,
756 width_offset,
757 code_offset,
758 margin,
759 );
760
761 let mut buffer_ops = vec![];
778 let mut annotations = vec![];
779 let mut short_start = true;
780 for ann in &line.annotations {
781 if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
782 if source_string.chars().take(ann.start_col.file).all(|c| c.is_whitespace()) {
783 let uline = self.underline(ann.is_primary);
784 let chr = uline.multiline_whole_line;
785 annotations.push((depth, uline.style));
786 buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style));
787 } else {
788 short_start = false;
789 break;
790 }
791 } else if let AnnotationType::MultilineLine(_) = ann.annotation_type {
792 } else {
793 short_start = false;
794 break;
795 }
796 }
797 if short_start {
798 for (y, x, c, s) in buffer_ops {
799 buffer.putc(y, x, c, s);
800 }
801 return annotations;
802 }
803
804 let mut annotations = line.annotations.clone();
837 annotations.sort_by_key(|a| Reverse(a.start_col));
838
839 let mut overlap = vec![false; annotations.len()];
902 let mut annotations_position = vec![];
903 let mut line_len: usize = 0;
904 let mut p = 0;
905 for (i, annotation) in annotations.iter().enumerate() {
906 for (j, next) in annotations.iter().enumerate() {
907 if overlaps(next, annotation, 0) && j > i {
908 overlap[i] = true;
909 overlap[j] = true;
910 }
911 if overlaps(next, annotation, 0) && annotation.has_label() && j > i && p == 0
915 {
917 if next.start_col == annotation.start_col
920 && next.end_col == annotation.end_col
921 && !next.has_label()
922 {
923 continue;
924 }
925
926 p += 1;
928 break;
929 }
930 }
931 annotations_position.push((p, annotation));
932 for (j, next) in annotations.iter().enumerate() {
933 if j > i {
934 let l = next.label.as_ref().map_or(0, |label| label.len() + 2);
935 if (overlaps(next, annotation, l) && annotation.has_label() && next.has_label()) || (annotation.takes_space() && next.has_label()) || (annotation.has_label() && next.takes_space())
952 || (annotation.takes_space() && next.takes_space())
953 || (overlaps(next, annotation, l)
954 && next.end_col <= annotation.end_col
955 && next.has_label()
956 && p == 0)
957 {
959 p += 1;
961 break;
962 }
963 }
964 }
965 line_len = max(line_len, p);
966 }
967
968 if line_len != 0 {
969 line_len += 1;
970 }
971
972 if line.annotations.iter().all(|a| a.is_line()) {
975 return vec![];
976 }
977
978 if annotations_position
979 .iter()
980 .all(|(_, ann)| matches!(ann.annotation_type, AnnotationType::MultilineStart(_)))
981 && let Some(max_pos) = annotations_position.iter().map(|(pos, _)| *pos).max()
982 {
983 for (pos, _) in &mut annotations_position {
996 *pos = max_pos - *pos;
997 }
998 line_len = line_len.saturating_sub(1);
1001 }
1002
1003 for pos in 0..=line_len {
1015 self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2);
1016 }
1017 if close_window {
1018 self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2);
1019 }
1020
1021 for &(pos, annotation) in &annotations_position {
1034 let underline = self.underline(annotation.is_primary);
1035 let pos = pos + 1;
1036 match annotation.annotation_type {
1037 AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => {
1038 let pre: usize = source_string
1039 .chars()
1040 .take(annotation.start_col.file)
1041 .skip(left)
1042 .map(|c| char_width(c))
1043 .sum();
1044 self.draw_range(
1045 buffer,
1046 underline.multiline_horizontal,
1047 line_offset + pos,
1048 width_offset + depth,
1049 code_offset + pre,
1050 underline.style,
1051 );
1052 }
1053 _ => {}
1054 }
1055 }
1056
1057 for &(pos, annotation) in &annotations_position {
1069 let underline = self.underline(annotation.is_primary);
1070 let pos = pos + 1;
1071
1072 let code_offset = code_offset
1073 + source_string
1074 .chars()
1075 .take(annotation.start_col.file)
1076 .skip(left)
1077 .map(|c| char_width(c))
1078 .sum::<usize>();
1079 if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
1080 for p in line_offset + 1..=line_offset + pos {
1081 buffer.putc(
1082 p,
1083 code_offset,
1084 match annotation.annotation_type {
1085 AnnotationType::MultilineLine(_) => underline.multiline_vertical,
1086 _ => underline.vertical_text_line,
1087 },
1088 underline.style,
1089 );
1090 }
1091 if let AnnotationType::MultilineStart(_) = annotation.annotation_type {
1092 buffer.putc(
1093 line_offset + pos,
1094 code_offset,
1095 underline.bottom_right,
1096 underline.style,
1097 );
1098 }
1099 if let AnnotationType::MultilineEnd(_) = annotation.annotation_type
1100 && annotation.has_label()
1101 {
1102 buffer.putc(
1103 line_offset + pos,
1104 code_offset,
1105 underline.multiline_bottom_right_with_text,
1106 underline.style,
1107 );
1108 }
1109 }
1110 match annotation.annotation_type {
1111 AnnotationType::MultilineStart(depth) => {
1112 buffer.putc(
1113 line_offset + pos,
1114 width_offset + depth - 1,
1115 underline.top_left,
1116 underline.style,
1117 );
1118 for p in line_offset + pos + 1..line_offset + line_len + 2 {
1119 buffer.putc(
1120 p,
1121 width_offset + depth - 1,
1122 underline.multiline_vertical,
1123 underline.style,
1124 );
1125 }
1126 }
1127 AnnotationType::MultilineEnd(depth) => {
1128 for p in line_offset..line_offset + pos {
1129 buffer.putc(
1130 p,
1131 width_offset + depth - 1,
1132 underline.multiline_vertical,
1133 underline.style,
1134 );
1135 }
1136 buffer.putc(
1137 line_offset + pos,
1138 width_offset + depth - 1,
1139 underline.bottom_left,
1140 underline.style,
1141 );
1142 }
1143 _ => (),
1144 }
1145 }
1146
1147 for &(pos, annotation) in &annotations_position {
1159 let style =
1160 if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary };
1161 let (pos, col) = if pos == 0 {
1162 let pre: usize = source_string
1163 .chars()
1164 .take(annotation.end_col.file)
1165 .skip(left)
1166 .map(|c| char_width(c))
1167 .sum();
1168 if annotation.end_col.file == 0 {
1169 (pos + 1, (pre + 2))
1170 } else {
1171 let pad = if annotation.end_col.file - annotation.start_col.file == 0 {
1172 2
1173 } else {
1174 1
1175 };
1176 (pos + 1, (pre + pad))
1177 }
1178 } else {
1179 let pre: usize = source_string
1180 .chars()
1181 .take(annotation.start_col.file)
1182 .skip(left)
1183 .map(|c| char_width(c))
1184 .sum();
1185 (pos + 2, pre)
1186 };
1187 if let Some(ref label) = annotation.label {
1188 buffer.puts(line_offset + pos, code_offset + col, label, style);
1189 }
1190 }
1191
1192 annotations_position.sort_by_key(|(_, ann)| {
1201 (Reverse(ann.len()), ann.is_primary)
1203 });
1204
1205 for &(pos, annotation) in &annotations_position {
1217 let uline = self.underline(annotation.is_primary);
1218 let width = annotation.end_col.file - annotation.start_col.file;
1219 let previous: String =
1220 source_string.chars().take(annotation.start_col.file).skip(left).collect();
1221 let underlined: String =
1222 source_string.chars().skip(annotation.start_col.file).take(width).collect();
1223 debug!(?previous, ?underlined);
1224 let code_offset = code_offset
1225 + source_string
1226 .chars()
1227 .take(annotation.start_col.file)
1228 .skip(left)
1229 .map(|c| char_width(c))
1230 .sum::<usize>();
1231 let ann_width: usize = source_string
1232 .chars()
1233 .skip(annotation.start_col.file)
1234 .take(width)
1235 .map(|c| char_width(c))
1236 .sum();
1237 let ann_width = if ann_width == 0
1238 && matches!(annotation.annotation_type, AnnotationType::Singleline)
1239 {
1240 1
1241 } else {
1242 ann_width
1243 };
1244 for p in 0..ann_width {
1245 buffer.putc(line_offset + 1, code_offset + p, uline.underline, uline.style);
1247 }
1248
1249 if pos == 0
1250 && matches!(
1251 annotation.annotation_type,
1252 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1253 )
1254 {
1255 buffer.putc(
1257 line_offset + 1,
1258 code_offset,
1259 match annotation.annotation_type {
1260 AnnotationType::MultilineStart(_) => uline.top_right_flat,
1261 AnnotationType::MultilineEnd(_) => uline.multiline_end_same_line,
1262 _ => panic!("unexpected annotation type: {annotation:?}"),
1263 },
1264 uline.style,
1265 );
1266 } else if pos != 0
1267 && matches!(
1268 annotation.annotation_type,
1269 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1270 )
1271 {
1272 buffer.putc(
1275 line_offset + 1,
1276 code_offset,
1277 match annotation.annotation_type {
1278 AnnotationType::MultilineStart(_) => uline.multiline_start_down,
1279 AnnotationType::MultilineEnd(_) => uline.multiline_end_up,
1280 _ => panic!("unexpected annotation type: {annotation:?}"),
1281 },
1282 uline.style,
1283 );
1284 } else if pos != 0 && annotation.has_label() {
1285 buffer.putc(line_offset + 1, code_offset, uline.label_start, uline.style);
1287 }
1288 }
1289
1290 for (i, (_pos, annotation)) in annotations_position.iter().enumerate() {
1294 if overlap[i] {
1296 continue;
1297 };
1298 let AnnotationType::Singleline = annotation.annotation_type else { continue };
1299 let width = annotation.end_col.display - annotation.start_col.display;
1300 if width > margin.column_width * 2 && width > 10 {
1301 let pad = max(margin.column_width / 3, 5);
1304 buffer.replace(
1306 line_offset,
1307 annotation.start_col.file + pad,
1308 annotation.end_col.file - pad,
1309 self.margin(),
1310 );
1311 buffer.replace(
1313 line_offset + 1,
1314 annotation.start_col.file + pad,
1315 annotation.end_col.file - pad,
1316 self.margin(),
1317 );
1318 }
1319 }
1320 annotations_position
1321 .iter()
1322 .filter_map(|&(_, annotation)| match annotation.annotation_type {
1323 AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => {
1324 let style = if annotation.is_primary {
1325 Style::LabelPrimary
1326 } else {
1327 Style::LabelSecondary
1328 };
1329 Some((p, style))
1330 }
1331 _ => None,
1332 })
1333 .collect::<Vec<_>>()
1334 }
1335
1336 fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
1337 let Some(ref sm) = self.sm else {
1338 return 0;
1339 };
1340
1341 let will_be_emitted = |span: Span| {
1342 !span.is_dummy() && {
1343 let file = sm.lookup_source_file(span.hi());
1344 should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file)
1345 }
1346 };
1347
1348 let mut max = 0;
1349 for primary_span in msp.primary_spans() {
1350 if will_be_emitted(*primary_span) {
1351 let hi = sm.lookup_char_pos(primary_span.hi());
1352 max = (hi.line).max(max);
1353 }
1354 }
1355 if !self.short_message {
1356 for span_label in msp.span_labels() {
1357 if will_be_emitted(span_label.span) {
1358 let hi = sm.lookup_char_pos(span_label.span.hi());
1359 max = (hi.line).max(max);
1360 }
1361 }
1362 }
1363
1364 max
1365 }
1366
1367 fn get_max_line_num(&mut self, span: &MultiSpan, children: &[Subdiag]) -> usize {
1368 let primary = self.get_multispan_max_line_num(span);
1369 children
1370 .iter()
1371 .map(|sub| self.get_multispan_max_line_num(&sub.span))
1372 .max()
1373 .unwrap_or(0)
1374 .max(primary)
1375 }
1376
1377 fn msgs_to_buffer(
1380 &self,
1381 buffer: &mut StyledBuffer,
1382 msgs: &[(DiagMessage, Style)],
1383 args: &FluentArgs<'_>,
1384 padding: usize,
1385 label: &str,
1386 override_style: Option<Style>,
1387 ) -> usize {
1388 let padding = " ".repeat(padding + label.len() + 5);
1405
1406 fn style_or_override(style: Style, override_: Option<Style>) -> Style {
1408 match (style, override_) {
1409 (Style::NoStyle, Some(override_)) => override_,
1410 _ => style,
1411 }
1412 }
1413
1414 let mut line_number = 0;
1415
1416 for (text, style) in msgs.iter() {
1436 let text = self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1437 let text = &normalize_whitespace(&text);
1438 let lines = text.split('\n').collect::<Vec<_>>();
1439 if lines.len() > 1 {
1440 for (i, line) in lines.iter().enumerate() {
1441 if i != 0 {
1442 line_number += 1;
1443 buffer.append(line_number, &padding, Style::NoStyle);
1444 }
1445 buffer.append(line_number, line, style_or_override(*style, override_style));
1446 }
1447 } else {
1448 buffer.append(line_number, text, style_or_override(*style, override_style));
1449 }
1450 }
1451 line_number
1452 }
1453
1454 #[instrument(level = "trace", skip(self, args), ret)]
1455 fn emit_messages_default_inner(
1456 &mut self,
1457 msp: &MultiSpan,
1458 msgs: &[(DiagMessage, Style)],
1459 args: &FluentArgs<'_>,
1460 code: &Option<ErrCode>,
1461 level: &Level,
1462 max_line_num_len: usize,
1463 is_secondary: bool,
1464 is_cont: bool,
1465 ) -> io::Result<CodeWindowStatus> {
1466 let mut buffer = StyledBuffer::new();
1467
1468 if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
1469 {
1470 for _ in 0..max_line_num_len {
1472 buffer.prepend(0, " ", Style::NoStyle);
1473 }
1474 self.draw_note_separator(&mut buffer, 0, max_line_num_len + 1, is_cont);
1475 if *level != Level::FailureNote {
1476 buffer.append(0, level.to_str(), Style::MainHeaderMsg);
1477 buffer.append(0, ": ", Style::NoStyle);
1478 }
1479 let printed_lines =
1480 self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None);
1481 if is_cont && matches!(self.theme, OutputTheme::Unicode) {
1482 for i in 1..=printed_lines {
1494 self.draw_col_separator_no_space(&mut buffer, i, max_line_num_len + 1);
1495 }
1496 }
1497 } else {
1498 let mut label_width = 0;
1499 if *level != Level::FailureNote {
1501 buffer.append(0, level.to_str(), Style::Level(*level));
1502 label_width += level.to_str().len();
1503 }
1504 if let Some(code) = code {
1505 buffer.append(0, "[", Style::Level(*level));
1506 let code = if let TerminalUrl::Yes = self.terminal_url {
1507 let path = "https://doc.rust-lang.org/error_codes";
1508 format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
1509 } else {
1510 code.to_string()
1511 };
1512 buffer.append(0, &code, Style::Level(*level));
1513 buffer.append(0, "]", Style::Level(*level));
1514 label_width += 2 + code.len();
1515 }
1516 let header_style = if is_secondary {
1517 Style::HeaderMsg
1518 } else if self.short_message {
1519 Style::NoStyle
1521 } else {
1522 Style::MainHeaderMsg
1523 };
1524 if *level != Level::FailureNote {
1525 buffer.append(0, ": ", header_style);
1526 label_width += 2;
1527 }
1528 let mut line = 0;
1529 for (text, style) in msgs.iter() {
1530 let text =
1531 self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1532 for text in normalize_whitespace(&text).lines() {
1534 buffer.append(
1535 line,
1536 &format!(
1537 "{}{}",
1538 if line == 0 { String::new() } else { " ".repeat(label_width) },
1539 text
1540 ),
1541 match style {
1542 Style::Highlight => *style,
1543 _ => header_style,
1544 },
1545 );
1546 line += 1;
1547 }
1548 if line > 0 {
1554 line -= 1;
1555 }
1556 }
1557 if self.short_message {
1558 let labels = msp
1559 .span_labels()
1560 .into_iter()
1561 .filter_map(|label| match label.label {
1562 Some(msg) if label.is_primary => {
1563 let text = self.translator.translate_message(&msg, args).ok()?;
1564 if !text.trim().is_empty() { Some(text.to_string()) } else { None }
1565 }
1566 _ => None,
1567 })
1568 .collect::<Vec<_>>()
1569 .join(", ");
1570 if !labels.is_empty() {
1571 buffer.append(line, ": ", Style::NoStyle);
1572 buffer.append(line, &labels, Style::NoStyle);
1573 }
1574 }
1575 }
1576 let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
1577 trace!("{annotated_files:#?}");
1578 let mut code_window_status = CodeWindowStatus::Open;
1579
1580 let primary_span = msp.primary_span().unwrap_or_default();
1582 let (Some(sm), false) = (self.sm.as_ref(), primary_span.is_dummy()) else {
1583 return emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)
1585 .map(|_| code_window_status);
1586 };
1587 let primary_lo = sm.lookup_char_pos(primary_span.lo());
1588 if let Ok(pos) =
1589 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
1590 {
1591 annotated_files.swap(0, pos);
1592 }
1593
1594 let mut col_sep_before_no_show_source = false;
1597 let annotated_files_len = annotated_files.len();
1598 for (file_idx, annotated_file) in annotated_files.into_iter().enumerate() {
1600 if !should_show_source_code(
1602 &self.ignored_directories_in_source_blocks,
1603 sm,
1604 &annotated_file.file,
1605 ) {
1606 if !self.short_message {
1607 if col_sep_before_no_show_source {
1618 let buffer_msg_line_offset = buffer.num_lines();
1619 self.draw_col_separator_end(
1620 &mut buffer,
1621 buffer_msg_line_offset,
1622 max_line_num_len + 1,
1623 );
1624 }
1625 col_sep_before_no_show_source = false;
1626
1627 for (annotation_id, line) in annotated_file.lines.iter().enumerate() {
1629 let mut annotations = line.annotations.clone();
1630 annotations.sort_by_key(|a| Reverse(a.start_col));
1631 let mut line_idx = buffer.num_lines();
1632
1633 let labels: Vec<_> = annotations
1634 .iter()
1635 .filter_map(|a| Some((a.label.as_ref()?, a.is_primary)))
1636 .filter(|(l, _)| !l.is_empty())
1637 .collect();
1638
1639 if annotation_id == 0 || !labels.is_empty() {
1640 buffer.append(
1641 line_idx,
1642 &format!(
1643 "{}:{}:{}",
1644 sm.filename_for_diagnostics(&annotated_file.file.name),
1645 sm.doctest_offset_line(
1646 &annotated_file.file.name,
1647 line.line_index
1648 ),
1649 annotations[0].start_col.file + 1,
1650 ),
1651 Style::LineAndColumn,
1652 );
1653 if annotation_id == 0 {
1654 buffer.prepend(line_idx, self.file_start(), Style::LineNumber);
1655 } else {
1656 buffer.prepend(
1657 line_idx,
1658 self.secondary_file_start(),
1659 Style::LineNumber,
1660 );
1661 }
1662 for _ in 0..max_line_num_len {
1663 buffer.prepend(line_idx, " ", Style::NoStyle);
1664 }
1665 line_idx += 1;
1666 }
1667 if is_cont
1668 && file_idx == annotated_files_len - 1
1669 && annotation_id == annotated_file.lines.len() - 1
1670 && !labels.is_empty()
1671 {
1672 code_window_status = CodeWindowStatus::Closed;
1673 }
1674 let labels_len = labels.len();
1675 for (label_idx, (label, is_primary)) in labels.into_iter().enumerate() {
1676 let style = if is_primary {
1677 Style::LabelPrimary
1678 } else {
1679 Style::LabelSecondary
1680 };
1681 self.draw_col_separator_no_space(
1682 &mut buffer,
1683 line_idx,
1684 max_line_num_len + 1,
1685 );
1686 line_idx += 1;
1687 self.draw_note_separator(
1688 &mut buffer,
1689 line_idx,
1690 max_line_num_len + 1,
1691 label_idx != labels_len - 1,
1692 );
1693 buffer.append(line_idx, "note", Style::MainHeaderMsg);
1694 buffer.append(line_idx, ": ", Style::NoStyle);
1695 buffer.append(line_idx, label, style);
1696 line_idx += 1;
1697 }
1698 }
1699 }
1700 continue;
1701 } else {
1702 col_sep_before_no_show_source = true;
1703 }
1704
1705 let is_primary = primary_lo.file.name == annotated_file.file.name;
1708 if is_primary {
1709 let loc = primary_lo.clone();
1710 if !self.short_message {
1711 let buffer_msg_line_offset = buffer.num_lines();
1713
1714 buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber);
1715 buffer.append(
1716 buffer_msg_line_offset,
1717 &format!(
1718 "{}:{}:{}",
1719 sm.filename_for_diagnostics(&loc.file.name),
1720 sm.doctest_offset_line(&loc.file.name, loc.line),
1721 loc.col.0 + 1,
1722 ),
1723 Style::LineAndColumn,
1724 );
1725 for _ in 0..max_line_num_len {
1726 buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1727 }
1728 } else {
1729 buffer.prepend(
1730 0,
1731 &format!(
1732 "{}:{}:{}: ",
1733 sm.filename_for_diagnostics(&loc.file.name),
1734 sm.doctest_offset_line(&loc.file.name, loc.line),
1735 loc.col.0 + 1,
1736 ),
1737 Style::LineAndColumn,
1738 );
1739 }
1740 } else if !self.short_message {
1741 let buffer_msg_line_offset = buffer.num_lines();
1743
1744 self.draw_col_separator_no_space(
1755 &mut buffer,
1756 buffer_msg_line_offset,
1757 max_line_num_len + 1,
1758 );
1759
1760 buffer.prepend(
1762 buffer_msg_line_offset + 1,
1763 self.secondary_file_start(),
1764 Style::LineNumber,
1765 );
1766 let loc = if let Some(first_line) = annotated_file.lines.first() {
1767 let col = if let Some(first_annotation) = first_line.annotations.first() {
1768 format!(":{}", first_annotation.start_col.file + 1)
1769 } else {
1770 String::new()
1771 };
1772 format!(
1773 "{}:{}{}",
1774 sm.filename_for_diagnostics(&annotated_file.file.name),
1775 sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
1776 col
1777 )
1778 } else {
1779 format!("{}", sm.filename_for_diagnostics(&annotated_file.file.name))
1780 };
1781 buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
1782 for _ in 0..max_line_num_len {
1783 buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
1784 }
1785 }
1786
1787 if !self.short_message {
1788 let buffer_msg_line_offset = buffer.num_lines();
1790 self.draw_col_separator_no_space(
1791 &mut buffer,
1792 buffer_msg_line_offset,
1793 max_line_num_len + 1,
1794 );
1795
1796 let mut multilines = FxIndexMap::default();
1798
1799 let mut whitespace_margin = usize::MAX;
1801 for line_idx in 0..annotated_file.lines.len() {
1802 let file = Arc::clone(&annotated_file.file);
1803 let line = &annotated_file.lines[line_idx];
1804 if let Some(source_string) =
1805 line.line_index.checked_sub(1).and_then(|l| file.get_line(l))
1806 {
1807 let leading_whitespace = source_string
1814 .chars()
1815 .take_while(|c| rustc_lexer::is_whitespace(*c))
1816 .count();
1817 if source_string.chars().any(|c| !rustc_lexer::is_whitespace(c)) {
1818 whitespace_margin = min(whitespace_margin, leading_whitespace);
1819 }
1820 }
1821 }
1822 if whitespace_margin == usize::MAX {
1823 whitespace_margin = 0;
1824 }
1825
1826 let mut span_left_margin = usize::MAX;
1828 for line in &annotated_file.lines {
1829 for ann in &line.annotations {
1830 span_left_margin = min(span_left_margin, ann.start_col.file);
1831 span_left_margin = min(span_left_margin, ann.end_col.file);
1832 }
1833 }
1834 if span_left_margin == usize::MAX {
1835 span_left_margin = 0;
1836 }
1837
1838 let mut span_right_margin = 0;
1840 let mut label_right_margin = 0;
1841 let mut max_line_len = 0;
1842 for line in &annotated_file.lines {
1843 max_line_len = max(
1844 max_line_len,
1845 line.line_index
1846 .checked_sub(1)
1847 .and_then(|l| annotated_file.file.get_line(l))
1848 .map_or(0, |s| s.len()),
1849 );
1850 for ann in &line.annotations {
1851 span_right_margin = max(span_right_margin, ann.start_col.file);
1852 span_right_margin = max(span_right_margin, ann.end_col.file);
1853 let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
1855 label_right_margin =
1856 max(label_right_margin, ann.end_col.file + label_right);
1857 }
1858 }
1859
1860 let width_offset = 3 + max_line_num_len;
1861 let code_offset = if annotated_file.multiline_depth == 0 {
1862 width_offset
1863 } else {
1864 width_offset + annotated_file.multiline_depth + 1
1865 };
1866
1867 let column_width = self.column_width(code_offset);
1868
1869 let margin = Margin::new(
1870 whitespace_margin,
1871 span_left_margin,
1872 span_right_margin,
1873 label_right_margin,
1874 column_width,
1875 max_line_len,
1876 );
1877
1878 for line_idx in 0..annotated_file.lines.len() {
1880 let previous_buffer_line = buffer.num_lines();
1881
1882 let depths = self.render_source_line(
1883 &mut buffer,
1884 Arc::clone(&annotated_file.file),
1885 &annotated_file.lines[line_idx],
1886 width_offset,
1887 code_offset,
1888 margin,
1889 !is_cont
1890 && file_idx + 1 == annotated_files_len
1891 && line_idx + 1 == annotated_file.lines.len(),
1892 );
1893
1894 let mut to_add = FxIndexMap::default();
1895
1896 for (depth, style) in depths {
1897 if multilines.swap_remove(&depth).is_none() {
1899 to_add.insert(depth, style);
1900 }
1901 }
1902
1903 for (depth, style) in &multilines {
1906 for line in previous_buffer_line..buffer.num_lines() {
1907 self.draw_multiline_line(
1908 &mut buffer,
1909 line,
1910 width_offset,
1911 *depth,
1912 *style,
1913 );
1914 }
1915 }
1916 if line_idx < (annotated_file.lines.len() - 1) {
1919 let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1920 - annotated_file.lines[line_idx].line_index;
1921 if line_idx_delta > 2 {
1922 let last_buffer_line_num = buffer.num_lines();
1923 self.draw_line_separator(
1924 &mut buffer,
1925 last_buffer_line_num,
1926 width_offset,
1927 );
1928
1929 for (depth, style) in &multilines {
1931 self.draw_multiline_line(
1932 &mut buffer,
1933 last_buffer_line_num,
1934 width_offset,
1935 *depth,
1936 *style,
1937 );
1938 }
1939 if let Some(line) = annotated_file.lines.get(line_idx) {
1940 for ann in &line.annotations {
1941 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1942 {
1943 self.draw_multiline_line(
1947 &mut buffer,
1948 last_buffer_line_num,
1949 width_offset,
1950 pos,
1951 if ann.is_primary {
1952 Style::UnderlinePrimary
1953 } else {
1954 Style::UnderlineSecondary
1955 },
1956 );
1957 }
1958 }
1959 }
1960 } else if line_idx_delta == 2 {
1961 let unannotated_line = annotated_file
1962 .file
1963 .get_line(annotated_file.lines[line_idx].line_index)
1964 .unwrap_or_else(|| Cow::from(""));
1965
1966 let last_buffer_line_num = buffer.num_lines();
1967
1968 self.draw_line(
1969 &mut buffer,
1970 &normalize_whitespace(&unannotated_line),
1971 annotated_file.lines[line_idx + 1].line_index - 1,
1972 last_buffer_line_num,
1973 width_offset,
1974 code_offset,
1975 margin,
1976 );
1977
1978 for (depth, style) in &multilines {
1979 self.draw_multiline_line(
1980 &mut buffer,
1981 last_buffer_line_num,
1982 width_offset,
1983 *depth,
1984 *style,
1985 );
1986 }
1987 if let Some(line) = annotated_file.lines.get(line_idx) {
1988 for ann in &line.annotations {
1989 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1990 {
1991 self.draw_multiline_line(
1992 &mut buffer,
1993 last_buffer_line_num,
1994 width_offset,
1995 pos,
1996 if ann.is_primary {
1997 Style::UnderlinePrimary
1998 } else {
1999 Style::UnderlineSecondary
2000 },
2001 );
2002 }
2003 }
2004 }
2005 }
2006 }
2007
2008 multilines.extend(&to_add);
2009 }
2010 }
2011 trace!("buffer: {:#?}", buffer.render());
2012 }
2013
2014 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2016
2017 Ok(code_window_status)
2018 }
2019
2020 fn column_width(&self, code_offset: usize) -> usize {
2021 if let Some(width) = self.diagnostic_width {
2022 width.saturating_sub(code_offset)
2023 } else if self.ui_testing || cfg!(miri) {
2024 DEFAULT_COLUMN_WIDTH
2025 } else {
2026 termize::dimensions()
2027 .map(|(w, _)| w.saturating_sub(code_offset))
2028 .unwrap_or(DEFAULT_COLUMN_WIDTH)
2029 }
2030 }
2031
2032 fn emit_suggestion_default(
2033 &mut self,
2034 span: &MultiSpan,
2035 suggestion: &CodeSuggestion,
2036 args: &FluentArgs<'_>,
2037 level: &Level,
2038 max_line_num_len: usize,
2039 ) -> io::Result<()> {
2040 let Some(ref sm) = self.sm else {
2041 return Ok(());
2042 };
2043
2044 let suggestions = suggestion.splice_lines(sm);
2046 debug!(?suggestions);
2047
2048 if suggestions.is_empty() {
2049 return Ok(());
2055 }
2056
2057 let mut buffer = StyledBuffer::new();
2058
2059 buffer.append(0, level.to_str(), Style::Level(*level));
2061 buffer.append(0, ": ", Style::HeaderMsg);
2062
2063 let mut msg = vec![(suggestion.msg.to_owned(), Style::NoStyle)];
2064 if let Some(confusion_type) =
2065 suggestions.iter().take(MAX_SUGGESTIONS).find_map(|(_, _, _, confusion_type)| {
2066 if confusion_type.has_confusion() { Some(*confusion_type) } else { None }
2067 })
2068 {
2069 msg.push((confusion_type.label_text().into(), Style::NoStyle));
2070 }
2071 self.msgs_to_buffer(
2072 &mut buffer,
2073 &msg,
2074 args,
2075 max_line_num_len,
2076 "suggestion",
2077 Some(Style::HeaderMsg),
2078 );
2079
2080 let other_suggestions = suggestions.len().saturating_sub(MAX_SUGGESTIONS);
2081
2082 let mut row_num = 2;
2083 for (i, (complete, parts, highlights, _)) in
2084 suggestions.into_iter().enumerate().take(MAX_SUGGESTIONS)
2085 {
2086 debug!(?complete, ?parts, ?highlights);
2087
2088 let has_deletion =
2089 parts.iter().any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
2090 let is_multiline = complete.lines().count() > 1;
2091
2092 if i == 0 {
2093 self.draw_col_separator_start(&mut buffer, row_num - 1, max_line_num_len + 1);
2094 } else {
2095 buffer.puts(
2096 row_num - 1,
2097 max_line_num_len + 1,
2098 self.multi_suggestion_separator(),
2099 Style::LineNumber,
2100 );
2101 }
2102 if let Some(span) = span.primary_span() {
2103 let loc = sm.lookup_char_pos(parts[0].span.lo());
2108 if (span.is_dummy() || loc.file.name != sm.span_to_filename(span))
2109 && loc.file.name.is_real()
2110 {
2111 let arrow = self.file_start();
2114 buffer.puts(row_num - 1, 0, arrow, Style::LineNumber);
2115 let filename = sm.filename_for_diagnostics(&loc.file.name);
2116 let offset = sm.doctest_offset_line(&loc.file.name, loc.line);
2117 let message = format!("{}:{}:{}", filename, offset, loc.col.0 + 1);
2118 if row_num == 2 {
2119 let col = usize::max(max_line_num_len + 1, arrow.len());
2120 buffer.puts(1, col, &message, Style::LineAndColumn);
2121 } else {
2122 buffer.append(row_num - 1, &message, Style::LineAndColumn);
2123 }
2124 for _ in 0..max_line_num_len {
2125 buffer.prepend(row_num - 1, " ", Style::NoStyle);
2126 }
2127 self.draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
2128 row_num += 1;
2129 }
2130 }
2131 let show_code_change = if has_deletion && !is_multiline {
2132 DisplaySuggestion::Diff
2133 } else if let [part] = &parts[..]
2134 && part.snippet.ends_with('\n')
2135 && part.snippet.trim() == complete.trim()
2136 {
2137 DisplaySuggestion::Add
2139 } else if (parts.len() != 1 || parts[0].snippet.trim() != complete.trim())
2140 && !is_multiline
2141 {
2142 DisplaySuggestion::Underline
2143 } else {
2144 DisplaySuggestion::None
2145 };
2146
2147 if let DisplaySuggestion::Diff = show_code_change {
2148 row_num += 1;
2149 }
2150
2151 let file_lines = sm
2152 .span_to_lines(parts[0].span)
2153 .expect("span_to_lines failed when emitting suggestion");
2154
2155 assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
2156
2157 let line_start = sm.lookup_char_pos(parts[0].span.lo()).line;
2158 let mut lines = complete.lines();
2159 if lines.clone().next().is_none() {
2160 let line_end = sm.lookup_char_pos(parts[0].span.hi()).line;
2162 for line in line_start..=line_end {
2163 self.draw_line_num(
2164 &mut buffer,
2165 line,
2166 row_num - 1 + line - line_start,
2167 max_line_num_len,
2168 );
2169 buffer.puts(
2170 row_num - 1 + line - line_start,
2171 max_line_num_len + 1,
2172 "- ",
2173 Style::Removal,
2174 );
2175 buffer.puts(
2176 row_num - 1 + line - line_start,
2177 max_line_num_len + 3,
2178 &normalize_whitespace(&file_lines.file.get_line(line - 1).unwrap()),
2179 Style::Removal,
2180 );
2181 }
2182 row_num += line_end - line_start;
2183 }
2184 let mut unhighlighted_lines = Vec::new();
2185 let mut last_pos = 0;
2186 let mut is_item_attribute = false;
2187 for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
2188 last_pos = line_pos;
2189 debug!(%line_pos, %line, ?highlight_parts);
2190
2191 if highlight_parts.is_empty() {
2193 unhighlighted_lines.push((line_pos, line));
2194 continue;
2195 }
2196 if highlight_parts.len() == 1
2197 && line.trim().starts_with("#[")
2198 && line.trim().ends_with(']')
2199 {
2200 is_item_attribute = true;
2201 }
2202
2203 match unhighlighted_lines.len() {
2204 0 => (),
2205 n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
2210 self.draw_code_line(
2211 &mut buffer,
2212 &mut row_num,
2213 &[],
2214 p + line_start,
2215 l,
2216 show_code_change,
2217 max_line_num_len,
2218 &file_lines,
2219 is_multiline,
2220 )
2221 }),
2222 _ => {
2230 let last_line = unhighlighted_lines.pop();
2231 let first_line = unhighlighted_lines.drain(..).next();
2232
2233 if let Some((p, l)) = first_line {
2234 self.draw_code_line(
2235 &mut buffer,
2236 &mut row_num,
2237 &[],
2238 p + line_start,
2239 l,
2240 show_code_change,
2241 max_line_num_len,
2242 &file_lines,
2243 is_multiline,
2244 )
2245 }
2246
2247 let placeholder = self.margin();
2248 let padding = str_width(placeholder);
2249 buffer.puts(
2250 row_num,
2251 max_line_num_len.saturating_sub(padding),
2252 placeholder,
2253 Style::LineNumber,
2254 );
2255 row_num += 1;
2256
2257 if let Some((p, l)) = last_line {
2258 self.draw_code_line(
2259 &mut buffer,
2260 &mut row_num,
2261 &[],
2262 p + line_start,
2263 l,
2264 show_code_change,
2265 max_line_num_len,
2266 &file_lines,
2267 is_multiline,
2268 )
2269 }
2270 }
2271 }
2272
2273 self.draw_code_line(
2274 &mut buffer,
2275 &mut row_num,
2276 &highlight_parts,
2277 line_pos + line_start,
2278 line,
2279 show_code_change,
2280 max_line_num_len,
2281 &file_lines,
2282 is_multiline,
2283 )
2284 }
2285 if let DisplaySuggestion::Add = show_code_change
2286 && is_item_attribute
2287 {
2288 let file_lines = sm
2295 .span_to_lines(parts[0].span.shrink_to_hi())
2296 .expect("span_to_lines failed when emitting suggestion");
2297 let line_num = sm.lookup_char_pos(parts[0].span.lo()).line;
2298 if let Some(line) = file_lines.file.get_line(line_num - 1) {
2299 let line = normalize_whitespace(&line);
2300 self.draw_code_line(
2301 &mut buffer,
2302 &mut row_num,
2303 &[],
2304 line_num + last_pos + 1,
2305 &line,
2306 DisplaySuggestion::None,
2307 max_line_num_len,
2308 &file_lines,
2309 is_multiline,
2310 )
2311 }
2312 }
2313
2314 let mut offsets: Vec<(usize, isize)> = Vec::new();
2317 if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
2320 show_code_change
2321 {
2322 for part in parts {
2323 let snippet = if let Ok(snippet) = sm.span_to_snippet(part.span) {
2324 snippet
2325 } else {
2326 String::new()
2327 };
2328 let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
2329 let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
2330
2331 let is_whitespace_addition = part.snippet.trim().is_empty();
2334
2335 let start = if is_whitespace_addition {
2337 0
2338 } else {
2339 part.snippet.len().saturating_sub(part.snippet.trim_start().len())
2340 };
2341 let sub_len: usize = str_width(if is_whitespace_addition {
2344 &part.snippet
2345 } else {
2346 part.snippet.trim()
2347 });
2348
2349 let offset: isize = offsets
2350 .iter()
2351 .filter_map(
2352 |(start, v)| if span_start_pos < *start { None } else { Some(v) },
2353 )
2354 .sum();
2355 let underline_start = (span_start_pos + start) as isize + offset;
2356 let underline_end = (span_start_pos + start + sub_len) as isize + offset;
2357 assert!(underline_start >= 0 && underline_end >= 0);
2358 let padding: usize = max_line_num_len + 3;
2359 for p in underline_start..underline_end {
2360 if let DisplaySuggestion::Underline = show_code_change
2361 && is_different(sm, &part.snippet, part.span)
2362 {
2363 buffer.putc(
2366 row_num,
2367 (padding as isize + p) as usize,
2368 if part.is_addition(sm) { '+' } else { self.diff() },
2369 Style::Addition,
2370 );
2371 }
2372 }
2373 if let DisplaySuggestion::Diff = show_code_change {
2374 let newlines = snippet.lines().count();
2405 if newlines > 0 && row_num > newlines {
2406 for (i, line) in snippet.lines().enumerate() {
2415 let line = normalize_whitespace(line);
2416 let row = row_num - 2 - (newlines - i - 1);
2417 let start = if i == 0 {
2423 (padding as isize + span_start_pos as isize) as usize
2424 } else {
2425 padding
2426 };
2427 let end = if i == 0 {
2428 (padding as isize
2429 + span_start_pos as isize
2430 + line.len() as isize)
2431 as usize
2432 } else if i == newlines - 1 {
2433 (padding as isize + span_end_pos as isize) as usize
2434 } else {
2435 (padding as isize + line.len() as isize) as usize
2436 };
2437 buffer.set_style_range(row, start, end, Style::Removal, true);
2438 }
2439 } else {
2440 buffer.set_style_range(
2442 row_num - 2,
2443 (padding as isize + span_start_pos as isize) as usize,
2444 (padding as isize + span_end_pos as isize) as usize,
2445 Style::Removal,
2446 true,
2447 );
2448 }
2449 }
2450
2451 let full_sub_len = str_width(&part.snippet) as isize;
2453
2454 let snippet_len = span_end_pos as isize - span_start_pos as isize;
2456 offsets.push((span_end_pos, full_sub_len - snippet_len));
2460 }
2461 row_num += 1;
2462 }
2463
2464 if lines.next().is_some() {
2466 let placeholder = self.margin();
2467 let padding = str_width(placeholder);
2468 buffer.puts(
2469 row_num,
2470 max_line_num_len.saturating_sub(padding),
2471 placeholder,
2472 Style::LineNumber,
2473 );
2474 } else {
2475 let row = match show_code_change {
2476 DisplaySuggestion::Diff
2477 | DisplaySuggestion::Add
2478 | DisplaySuggestion::Underline => row_num - 1,
2479 DisplaySuggestion::None => row_num,
2480 };
2481 if other_suggestions > 0 {
2482 self.draw_col_separator_no_space(&mut buffer, row, max_line_num_len + 1);
2483 } else {
2484 self.draw_col_separator_end(&mut buffer, row, max_line_num_len + 1);
2485 }
2486 row_num = row + 1;
2487 }
2488 }
2489 if other_suggestions > 0 {
2490 self.draw_note_separator(&mut buffer, row_num, max_line_num_len + 1, false);
2491 let msg = format!(
2492 "and {} other candidate{}",
2493 other_suggestions,
2494 pluralize!(other_suggestions)
2495 );
2496 buffer.append(row_num, &msg, Style::NoStyle);
2497 }
2498
2499 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2500 Ok(())
2501 }
2502
2503 #[instrument(level = "trace", skip(self, args, code, children, suggestions))]
2504 fn emit_messages_default(
2505 &mut self,
2506 level: &Level,
2507 messages: &[(DiagMessage, Style)],
2508 args: &FluentArgs<'_>,
2509 code: &Option<ErrCode>,
2510 span: &MultiSpan,
2511 children: &[Subdiag],
2512 suggestions: &[CodeSuggestion],
2513 ) {
2514 let max_line_num_len = if self.ui_testing {
2515 ANONYMIZED_LINE_NUM.len()
2516 } else {
2517 let n = self.get_max_line_num(span, children);
2518 num_decimal_digits(n)
2519 };
2520
2521 match self.emit_messages_default_inner(
2522 span,
2523 messages,
2524 args,
2525 code,
2526 level,
2527 max_line_num_len,
2528 false,
2529 !children.is_empty()
2530 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden),
2531 ) {
2532 Ok(code_window_status) => {
2533 if !children.is_empty()
2534 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
2535 {
2536 let mut buffer = StyledBuffer::new();
2537 if !self.short_message {
2538 if let Some(child) = children.iter().next()
2539 && child.span.primary_spans().is_empty()
2540 {
2541 self.draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
2543 } else if matches!(code_window_status, CodeWindowStatus::Open) {
2544 self.draw_col_separator_end(&mut buffer, 0, max_line_num_len + 1);
2546 }
2547 }
2548 if let Err(e) = emit_to_destination(
2549 &buffer.render(),
2550 level,
2551 &mut self.dst,
2552 self.short_message,
2553 ) {
2554 panic!("failed to emit error: {e}")
2555 }
2556 }
2557 if !self.short_message {
2558 for (i, child) in children.iter().enumerate() {
2559 assert!(child.level.can_be_subdiag());
2560 let span = &child.span;
2561 let should_close = match children.get(i + 1) {
2563 Some(c) => !c.span.primary_spans().is_empty(),
2564 None => i + 1 == children.len(),
2565 };
2566 if let Err(err) = self.emit_messages_default_inner(
2567 span,
2568 &child.messages,
2569 args,
2570 &None,
2571 &child.level,
2572 max_line_num_len,
2573 true,
2574 !should_close,
2575 ) {
2576 panic!("failed to emit error: {err}");
2577 }
2578 }
2579 for (i, sugg) in suggestions.iter().enumerate() {
2580 match sugg.style {
2581 SuggestionStyle::CompletelyHidden => {
2582 }
2584 SuggestionStyle::HideCodeAlways => {
2585 if let Err(e) = self.emit_messages_default_inner(
2586 &MultiSpan::new(),
2587 &[(sugg.msg.to_owned(), Style::HeaderMsg)],
2588 args,
2589 &None,
2590 &Level::Help,
2591 max_line_num_len,
2592 true,
2593 i + 1 != suggestions.len(),
2596 ) {
2597 panic!("failed to emit error: {e}");
2598 }
2599 }
2600 SuggestionStyle::HideCodeInline
2601 | SuggestionStyle::ShowCode
2602 | SuggestionStyle::ShowAlways => {
2603 if let Err(e) = self.emit_suggestion_default(
2604 span,
2605 sugg,
2606 args,
2607 &Level::Help,
2608 max_line_num_len,
2609 ) {
2610 panic!("failed to emit error: {e}");
2611 }
2612 }
2613 }
2614 }
2615 }
2616 }
2617 Err(e) => panic!("failed to emit error: {e}"),
2618 }
2619
2620 match writeln!(self.dst) {
2621 Err(e) => panic!("failed to emit error: {e}"),
2622 _ => {
2623 if let Err(e) = self.dst.flush() {
2624 panic!("failed to emit error: {e}")
2625 }
2626 }
2627 }
2628 }
2629
2630 fn draw_code_line(
2631 &self,
2632 buffer: &mut StyledBuffer,
2633 row_num: &mut usize,
2634 highlight_parts: &[SubstitutionHighlight],
2635 line_num: usize,
2636 line_to_add: &str,
2637 show_code_change: DisplaySuggestion,
2638 max_line_num_len: usize,
2639 file_lines: &FileLines,
2640 is_multiline: bool,
2641 ) {
2642 if let DisplaySuggestion::Diff = show_code_change {
2643 let lines_to_remove = file_lines.lines.iter().take(file_lines.lines.len() - 1);
2646 for (index, line_to_remove) in lines_to_remove.enumerate() {
2647 self.draw_line_num(buffer, line_num + index, *row_num - 1, max_line_num_len);
2648 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2649 let line = normalize_whitespace(
2650 &file_lines.file.get_line(line_to_remove.line_index).unwrap(),
2651 );
2652 buffer.puts(*row_num - 1, max_line_num_len + 3, &line, Style::NoStyle);
2653 *row_num += 1;
2654 }
2655 let last_line_index = file_lines.lines[file_lines.lines.len() - 1].line_index;
2662 let last_line = &file_lines.file.get_line(last_line_index).unwrap();
2663 if last_line != line_to_add {
2664 self.draw_line_num(
2665 buffer,
2666 line_num + file_lines.lines.len() - 1,
2667 *row_num - 1,
2668 max_line_num_len,
2669 );
2670 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2671 buffer.puts(
2672 *row_num - 1,
2673 max_line_num_len + 3,
2674 &normalize_whitespace(last_line),
2675 Style::NoStyle,
2676 );
2677 if !line_to_add.trim().is_empty() {
2678 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2692 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2693 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2694 } else {
2695 *row_num -= 1;
2696 }
2697 } else {
2698 *row_num -= 2;
2699 }
2700 } else if is_multiline {
2701 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2702 match &highlight_parts {
2703 [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
2704 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2705 }
2706 [] => {
2707 self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
2709 }
2710 _ => {
2711 let diff = self.diff();
2712 buffer.puts(
2713 *row_num,
2714 max_line_num_len + 1,
2715 &format!("{diff} "),
2716 Style::Addition,
2717 );
2718 }
2719 }
2720 buffer.puts(
2726 *row_num,
2727 max_line_num_len + 3,
2728 &normalize_whitespace(line_to_add),
2729 Style::NoStyle,
2730 );
2731 } else if let DisplaySuggestion::Add = show_code_change {
2732 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2733 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2734 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2735 } else {
2736 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2737 self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2738 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2739 }
2740
2741 for &SubstitutionHighlight { start, end } in highlight_parts {
2743 if start != end {
2745 let tabs: usize = line_to_add
2747 .chars()
2748 .take(start)
2749 .map(|ch| match ch {
2750 '\t' => 3,
2751 _ => 0,
2752 })
2753 .sum();
2754 buffer.set_style_range(
2755 *row_num,
2756 max_line_num_len + 3 + start + tabs,
2757 max_line_num_len + 3 + end + tabs,
2758 Style::Addition,
2759 true,
2760 );
2761 }
2762 }
2763 *row_num += 1;
2764 }
2765
2766 fn underline(&self, is_primary: bool) -> UnderlineParts {
2767 match (self.theme, is_primary) {
2792 (OutputTheme::Ascii, true) => UnderlineParts {
2793 style: Style::UnderlinePrimary,
2794 underline: '^',
2795 label_start: '^',
2796 vertical_text_line: '|',
2797 multiline_vertical: '|',
2798 multiline_horizontal: '_',
2799 multiline_whole_line: '/',
2800 multiline_start_down: '^',
2801 bottom_right: '|',
2802 top_left: ' ',
2803 top_right_flat: '^',
2804 bottom_left: '|',
2805 multiline_end_up: '^',
2806 multiline_end_same_line: '^',
2807 multiline_bottom_right_with_text: '|',
2808 },
2809 (OutputTheme::Ascii, false) => UnderlineParts {
2810 style: Style::UnderlineSecondary,
2811 underline: '-',
2812 label_start: '-',
2813 vertical_text_line: '|',
2814 multiline_vertical: '|',
2815 multiline_horizontal: '_',
2816 multiline_whole_line: '/',
2817 multiline_start_down: '-',
2818 bottom_right: '|',
2819 top_left: ' ',
2820 top_right_flat: '-',
2821 bottom_left: '|',
2822 multiline_end_up: '-',
2823 multiline_end_same_line: '-',
2824 multiline_bottom_right_with_text: '|',
2825 },
2826 (OutputTheme::Unicode, true) => UnderlineParts {
2827 style: Style::UnderlinePrimary,
2828 underline: '━',
2829 label_start: '┯',
2830 vertical_text_line: '│',
2831 multiline_vertical: '┃',
2832 multiline_horizontal: '━',
2833 multiline_whole_line: '┏',
2834 multiline_start_down: '╿',
2835 bottom_right: '┙',
2836 top_left: '┏',
2837 top_right_flat: '┛',
2838 bottom_left: '┗',
2839 multiline_end_up: '╿',
2840 multiline_end_same_line: '┛',
2841 multiline_bottom_right_with_text: '┥',
2842 },
2843 (OutputTheme::Unicode, false) => UnderlineParts {
2844 style: Style::UnderlineSecondary,
2845 underline: '─',
2846 label_start: '┬',
2847 vertical_text_line: '│',
2848 multiline_vertical: '│',
2849 multiline_horizontal: '─',
2850 multiline_whole_line: '┌',
2851 multiline_start_down: '│',
2852 bottom_right: '┘',
2853 top_left: '┌',
2854 top_right_flat: '┘',
2855 bottom_left: '└',
2856 multiline_end_up: '│',
2857 multiline_end_same_line: '┘',
2858 multiline_bottom_right_with_text: '┤',
2859 },
2860 }
2861 }
2862
2863 fn col_separator(&self) -> char {
2864 match self.theme {
2865 OutputTheme::Ascii => '|',
2866 OutputTheme::Unicode => '│',
2867 }
2868 }
2869
2870 fn note_separator(&self, is_cont: bool) -> &'static str {
2871 match self.theme {
2872 OutputTheme::Ascii => "= ",
2873 OutputTheme::Unicode if is_cont => "├ ",
2874 OutputTheme::Unicode => "╰ ",
2875 }
2876 }
2877
2878 fn multi_suggestion_separator(&self) -> &'static str {
2879 match self.theme {
2880 OutputTheme::Ascii => "|",
2881 OutputTheme::Unicode => "├╴",
2882 }
2883 }
2884
2885 fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2886 let chr = self.col_separator();
2887 buffer.puts(line, col, &format!("{chr} "), Style::LineNumber);
2888 }
2889
2890 fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2891 let chr = self.col_separator();
2892 self.draw_col_separator_no_space_with_style(buffer, chr, line, col, Style::LineNumber);
2893 }
2894
2895 fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2896 match self.theme {
2897 OutputTheme::Ascii => {
2898 self.draw_col_separator_no_space_with_style(
2899 buffer,
2900 '|',
2901 line,
2902 col,
2903 Style::LineNumber,
2904 );
2905 }
2906 OutputTheme::Unicode => {
2907 self.draw_col_separator_no_space_with_style(
2908 buffer,
2909 '╭',
2910 line,
2911 col,
2912 Style::LineNumber,
2913 );
2914 self.draw_col_separator_no_space_with_style(
2915 buffer,
2916 '╴',
2917 line,
2918 col + 1,
2919 Style::LineNumber,
2920 );
2921 }
2922 }
2923 }
2924
2925 fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2926 match self.theme {
2927 OutputTheme::Ascii => {
2928 self.draw_col_separator_no_space_with_style(
2929 buffer,
2930 '|',
2931 line,
2932 col,
2933 Style::LineNumber,
2934 );
2935 }
2936 OutputTheme::Unicode => {
2937 self.draw_col_separator_no_space_with_style(
2938 buffer,
2939 '╰',
2940 line,
2941 col,
2942 Style::LineNumber,
2943 );
2944 self.draw_col_separator_no_space_with_style(
2945 buffer,
2946 '╴',
2947 line,
2948 col + 1,
2949 Style::LineNumber,
2950 );
2951 }
2952 }
2953 }
2954
2955 fn draw_col_separator_no_space_with_style(
2956 &self,
2957 buffer: &mut StyledBuffer,
2958 chr: char,
2959 line: usize,
2960 col: usize,
2961 style: Style,
2962 ) {
2963 buffer.putc(line, col, chr, style);
2964 }
2965
2966 fn draw_range(
2967 &self,
2968 buffer: &mut StyledBuffer,
2969 symbol: char,
2970 line: usize,
2971 col_from: usize,
2972 col_to: usize,
2973 style: Style,
2974 ) {
2975 for col in col_from..col_to {
2976 buffer.putc(line, col, symbol, style);
2977 }
2978 }
2979
2980 fn draw_note_separator(
2981 &self,
2982 buffer: &mut StyledBuffer,
2983 line: usize,
2984 col: usize,
2985 is_cont: bool,
2986 ) {
2987 let chr = self.note_separator(is_cont);
2988 buffer.puts(line, col, chr, Style::LineNumber);
2989 }
2990
2991 fn draw_multiline_line(
2992 &self,
2993 buffer: &mut StyledBuffer,
2994 line: usize,
2995 offset: usize,
2996 depth: usize,
2997 style: Style,
2998 ) {
2999 let chr = match (style, self.theme) {
3000 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Ascii) => '|',
3001 (_, OutputTheme::Ascii) => '|',
3002 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Unicode) => '┃',
3003 (_, OutputTheme::Unicode) => '│',
3004 };
3005 buffer.putc(line, offset + depth - 1, chr, style);
3006 }
3007
3008 fn file_start(&self) -> &'static str {
3009 match self.theme {
3010 OutputTheme::Ascii => "--> ",
3011 OutputTheme::Unicode => " ╭▸ ",
3012 }
3013 }
3014
3015 fn secondary_file_start(&self) -> &'static str {
3016 match self.theme {
3017 OutputTheme::Ascii => "::: ",
3018 OutputTheme::Unicode => " ⸬ ",
3019 }
3020 }
3021
3022 fn diff(&self) -> char {
3023 match self.theme {
3024 OutputTheme::Ascii => '~',
3025 OutputTheme::Unicode => '±',
3026 }
3027 }
3028
3029 fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
3030 let (column, dots) = match self.theme {
3031 OutputTheme::Ascii => (0, "..."),
3032 OutputTheme::Unicode => (col - 2, "‡"),
3033 };
3034 buffer.puts(line, column, dots, Style::LineNumber);
3035 }
3036
3037 fn margin(&self) -> &'static str {
3038 match self.theme {
3039 OutputTheme::Ascii => "...",
3040 OutputTheme::Unicode => "…",
3041 }
3042 }
3043
3044 fn draw_line_num(
3045 &self,
3046 buffer: &mut StyledBuffer,
3047 line_num: usize,
3048 line_offset: usize,
3049 max_line_num_len: usize,
3050 ) {
3051 let line_num = self.maybe_anonymized(line_num);
3052 buffer.puts(
3053 line_offset,
3054 max_line_num_len.saturating_sub(str_width(&line_num)),
3055 &line_num,
3056 Style::LineNumber,
3057 );
3058 }
3059}
3060
3061#[derive(Debug, Clone, Copy)]
3062struct UnderlineParts {
3063 style: Style,
3064 underline: char,
3065 label_start: char,
3066 vertical_text_line: char,
3067 multiline_vertical: char,
3068 multiline_horizontal: char,
3069 multiline_whole_line: char,
3070 multiline_start_down: char,
3071 bottom_right: char,
3072 top_left: char,
3073 top_right_flat: char,
3074 bottom_left: char,
3075 multiline_end_up: char,
3076 multiline_end_same_line: char,
3077 multiline_bottom_right_with_text: char,
3078}
3079
3080#[derive(Clone, Copy, Debug)]
3081enum DisplaySuggestion {
3082 Underline,
3083 Diff,
3084 None,
3085 Add,
3086}
3087
3088#[derive(Clone, Copy, Debug)]
3089enum CodeWindowStatus {
3090 Closed,
3091 Open,
3092}
3093
3094impl FileWithAnnotatedLines {
3095 pub(crate) fn collect_annotations(
3098 emitter: &dyn Emitter,
3099 args: &FluentArgs<'_>,
3100 msp: &MultiSpan,
3101 ) -> Vec<FileWithAnnotatedLines> {
3102 fn add_annotation_to_file(
3103 file_vec: &mut Vec<FileWithAnnotatedLines>,
3104 file: Arc<SourceFile>,
3105 line_index: usize,
3106 ann: Annotation,
3107 ) {
3108 for slot in file_vec.iter_mut() {
3109 if slot.file.name == file.name {
3111 for line_slot in &mut slot.lines {
3113 if line_slot.line_index == line_index {
3114 line_slot.annotations.push(ann);
3115 return;
3116 }
3117 }
3118 slot.lines.push(Line { line_index, annotations: vec![ann] });
3120 slot.lines.sort();
3121 return;
3122 }
3123 }
3124 file_vec.push(FileWithAnnotatedLines {
3126 file,
3127 lines: vec![Line { line_index, annotations: vec![ann] }],
3128 multiline_depth: 0,
3129 });
3130 }
3131
3132 let mut output = vec![];
3133 let mut multiline_annotations = vec![];
3134
3135 if let Some(sm) = emitter.source_map() {
3136 for SpanLabel { span, is_primary, label } in msp.span_labels() {
3137 let span = match (span.is_dummy(), msp.primary_span()) {
3140 (_, None) | (false, _) => span,
3141 (true, Some(span)) => span,
3142 };
3143
3144 let lo = sm.lookup_char_pos(span.lo());
3145 let mut hi = sm.lookup_char_pos(span.hi());
3146
3147 if lo.col_display == hi.col_display && lo.line == hi.line {
3154 hi.col_display += 1;
3155 }
3156
3157 let label = label.as_ref().map(|m| {
3158 normalize_whitespace(
3159 &emitter
3160 .translator()
3161 .translate_message(m, args)
3162 .map_err(Report::new)
3163 .unwrap(),
3164 )
3165 });
3166
3167 if lo.line != hi.line {
3168 let ml = MultilineAnnotation {
3169 depth: 1,
3170 line_start: lo.line,
3171 line_end: hi.line,
3172 start_col: AnnotationColumn::from_loc(&lo),
3173 end_col: AnnotationColumn::from_loc(&hi),
3174 is_primary,
3175 label,
3176 overlaps_exactly: false,
3177 };
3178 multiline_annotations.push((lo.file, ml));
3179 } else {
3180 let ann = Annotation {
3181 start_col: AnnotationColumn::from_loc(&lo),
3182 end_col: AnnotationColumn::from_loc(&hi),
3183 is_primary,
3184 label,
3185 annotation_type: AnnotationType::Singleline,
3186 };
3187 add_annotation_to_file(&mut output, lo.file, lo.line, ann);
3188 };
3189 }
3190 }
3191
3192 multiline_annotations.sort_by_key(|(_, ml)| (ml.line_start, usize::MAX - ml.line_end));
3194 for (_, ann) in multiline_annotations.clone() {
3195 for (_, a) in multiline_annotations.iter_mut() {
3196 if !(ann.same_span(a))
3199 && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
3200 {
3201 a.increase_depth();
3202 } else if ann.same_span(a) && &ann != a {
3203 a.overlaps_exactly = true;
3204 } else {
3205 break;
3206 }
3207 }
3208 }
3209
3210 let mut max_depth = 0; for (_, ann) in &multiline_annotations {
3212 max_depth = max(max_depth, ann.depth);
3213 }
3214 for (_, a) in multiline_annotations.iter_mut() {
3216 a.depth = max_depth - a.depth + 1;
3217 }
3218 for (file, ann) in multiline_annotations {
3219 let mut end_ann = ann.as_end();
3220 if !ann.overlaps_exactly {
3221 add_annotation_to_file(
3244 &mut output,
3245 Arc::clone(&file),
3246 ann.line_start,
3247 ann.as_start(),
3248 );
3249 let middle = min(ann.line_start + 4, ann.line_end);
3254 let filter = |s: &str| {
3258 let s = s.trim();
3259 !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
3261 && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
3263 };
3264 let until = (ann.line_start..middle)
3265 .rev()
3266 .filter_map(|line| file.get_line(line - 1).map(|s| (line + 1, s)))
3267 .find(|(_, s)| filter(s))
3268 .map(|(line, _)| line)
3269 .unwrap_or(ann.line_start);
3270 for line in ann.line_start + 1..until {
3271 add_annotation_to_file(&mut output, Arc::clone(&file), line, ann.as_line());
3273 }
3274 let line_end = ann.line_end - 1;
3275 let end_is_empty = file.get_line(line_end - 1).is_some_and(|s| !filter(&s));
3276 if middle < line_end && !end_is_empty {
3277 add_annotation_to_file(&mut output, Arc::clone(&file), line_end, ann.as_line());
3278 }
3279 } else {
3280 end_ann.annotation_type = AnnotationType::Singleline;
3281 }
3282 add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
3283 }
3284 for file_vec in output.iter_mut() {
3285 file_vec.multiline_depth = max_depth;
3286 }
3287 output
3288 }
3289}
3290
3291fn num_decimal_digits(num: usize) -> usize {
3296 #[cfg(target_pointer_width = "64")]
3297 const MAX_DIGITS: usize = 20;
3298
3299 #[cfg(target_pointer_width = "32")]
3300 const MAX_DIGITS: usize = 10;
3301
3302 #[cfg(target_pointer_width = "16")]
3303 const MAX_DIGITS: usize = 5;
3304
3305 let mut lim = 10;
3306 for num_digits in 1..MAX_DIGITS {
3307 if num < lim {
3308 return num_digits;
3309 }
3310 lim = lim.wrapping_mul(10);
3311 }
3312 MAX_DIGITS
3313}
3314
3315const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
3318 ('\0', "␀"),
3322 ('\u{0001}', "␁"),
3323 ('\u{0002}', "␂"),
3324 ('\u{0003}', "␃"),
3325 ('\u{0004}', "␄"),
3326 ('\u{0005}', "␅"),
3327 ('\u{0006}', "␆"),
3328 ('\u{0007}', "␇"),
3329 ('\u{0008}', "␈"),
3330 ('\t', " "), ('\u{000b}', "␋"),
3332 ('\u{000c}', "␌"),
3333 ('\u{000d}', "␍"),
3334 ('\u{000e}', "␎"),
3335 ('\u{000f}', "␏"),
3336 ('\u{0010}', "␐"),
3337 ('\u{0011}', "␑"),
3338 ('\u{0012}', "␒"),
3339 ('\u{0013}', "␓"),
3340 ('\u{0014}', "␔"),
3341 ('\u{0015}', "␕"),
3342 ('\u{0016}', "␖"),
3343 ('\u{0017}', "␗"),
3344 ('\u{0018}', "␘"),
3345 ('\u{0019}', "␙"),
3346 ('\u{001a}', "␚"),
3347 ('\u{001b}', "␛"),
3348 ('\u{001c}', "␜"),
3349 ('\u{001d}', "␝"),
3350 ('\u{001e}', "␞"),
3351 ('\u{001f}', "␟"),
3352 ('\u{007f}', "␡"),
3353 ('\u{200d}', ""), ('\u{202a}', "�"), ('\u{202b}', "�"), ('\u{202c}', "�"), ('\u{202d}', "�"),
3358 ('\u{202e}', "�"),
3359 ('\u{2066}', "�"),
3360 ('\u{2067}', "�"),
3361 ('\u{2068}', "�"),
3362 ('\u{2069}', "�"),
3363];
3364
3365fn normalize_whitespace(s: &str) -> String {
3366 const {
3367 let mut i = 1;
3368 while i < OUTPUT_REPLACEMENTS.len() {
3369 assert!(
3370 OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
3371 "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
3372 and must contain no duplicate entries"
3373 );
3374 i += 1;
3375 }
3376 }
3377 s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
3381 match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
3382 Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
3383 _ => s.push(c),
3384 }
3385 s
3386 })
3387}
3388
3389fn num_overlap(
3390 a_start: usize,
3391 a_end: usize,
3392 b_start: usize,
3393 b_end: usize,
3394 inclusive: bool,
3395) -> bool {
3396 let extra = if inclusive { 1 } else { 0 };
3397 (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
3398}
3399
3400fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
3401 num_overlap(
3402 a1.start_col.display,
3403 a1.end_col.display + padding,
3404 a2.start_col.display,
3405 a2.end_col.display,
3406 false,
3407 )
3408}
3409
3410fn emit_to_destination(
3411 rendered_buffer: &[Vec<StyledString>],
3412 lvl: &Level,
3413 dst: &mut Destination,
3414 short_message: bool,
3415) -> io::Result<()> {
3416 use crate::lock;
3417
3418 let _buffer_lock = lock::acquire_global_lock("rustc_errors");
3431 for (pos, line) in rendered_buffer.iter().enumerate() {
3432 for part in line {
3433 let style = part.style.color_spec(*lvl);
3434 dst.set_color(&style)?;
3435 write!(dst, "{}", part.text)?;
3436 dst.reset()?;
3437 }
3438 if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
3439 writeln!(dst)?;
3440 }
3441 }
3442 dst.flush()?;
3443 Ok(())
3444}
3445
3446pub type Destination = Box<dyn WriteColor + Send>;
3447
3448struct Buffy {
3449 buffer_writer: BufferWriter,
3450 buffer: Buffer,
3451}
3452
3453impl Write for Buffy {
3454 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3455 self.buffer.write(buf)
3456 }
3457
3458 fn flush(&mut self) -> io::Result<()> {
3459 self.buffer_writer.print(&self.buffer)?;
3460 self.buffer.clear();
3461 Ok(())
3462 }
3463}
3464
3465impl Drop for Buffy {
3466 fn drop(&mut self) {
3467 if !self.buffer.is_empty() {
3468 self.flush().unwrap();
3469 panic!("buffers need to be flushed in order to print their contents");
3470 }
3471 }
3472}
3473
3474impl WriteColor for Buffy {
3475 fn supports_color(&self) -> bool {
3476 self.buffer.supports_color()
3477 }
3478
3479 fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
3480 self.buffer.set_color(spec)
3481 }
3482
3483 fn reset(&mut self) -> io::Result<()> {
3484 self.buffer.reset()
3485 }
3486}
3487
3488pub fn stderr_destination(color: ColorConfig) -> Destination {
3489 let choice = color.to_color_choice();
3490 if cfg!(windows) {
3497 Box::new(StandardStream::stderr(choice))
3498 } else {
3499 let buffer_writer = BufferWriter::stderr(choice);
3500 let buffer = buffer_writer.buffer();
3501 Box::new(Buffy { buffer_writer, buffer })
3502 }
3503}
3504
3505const BRIGHT_BLUE: Color = if cfg!(windows) { Color::Cyan } else { Color::Blue };
3509
3510impl Style {
3511 fn color_spec(&self, lvl: Level) -> ColorSpec {
3512 let mut spec = ColorSpec::new();
3513 match self {
3514 Style::Addition => {
3515 spec.set_fg(Some(Color::Green)).set_intense(true);
3516 }
3517 Style::Removal => {
3518 spec.set_fg(Some(Color::Red)).set_intense(true);
3519 }
3520 Style::LineAndColumn => {}
3521 Style::LineNumber => {
3522 spec.set_bold(true);
3523 spec.set_intense(true);
3524 spec.set_fg(Some(BRIGHT_BLUE));
3525 }
3526 Style::Quotation => {}
3527 Style::MainHeaderMsg => {
3528 spec.set_bold(true);
3529 if cfg!(windows) {
3530 spec.set_intense(true).set_fg(Some(Color::White));
3531 }
3532 }
3533 Style::UnderlinePrimary | Style::LabelPrimary => {
3534 spec = lvl.color();
3535 spec.set_bold(true);
3536 }
3537 Style::UnderlineSecondary | Style::LabelSecondary => {
3538 spec.set_bold(true).set_intense(true);
3539 spec.set_fg(Some(BRIGHT_BLUE));
3540 }
3541 Style::HeaderMsg | Style::NoStyle => {}
3542 Style::Level(lvl) => {
3543 spec = lvl.color();
3544 spec.set_bold(true);
3545 }
3546 Style::Highlight => {
3547 spec.set_bold(true).set_fg(Some(Color::Magenta));
3548 }
3549 }
3550 spec
3551 }
3552}
3553
3554pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3556 let found = match sm.span_to_snippet(sp) {
3557 Ok(snippet) => snippet,
3558 Err(e) => {
3559 warn!(error = ?e, "Invalid span {:?}", sp);
3560 return true;
3561 }
3562 };
3563 found != suggested
3564}
3565
3566pub fn detect_confusion_type(sm: &SourceMap, suggested: &str, sp: Span) -> ConfusionType {
3568 let found = match sm.span_to_snippet(sp) {
3569 Ok(snippet) => snippet,
3570 Err(e) => {
3571 warn!(error = ?e, "Invalid span {:?}", sp);
3572 return ConfusionType::None;
3573 }
3574 };
3575
3576 let mut has_case_confusion = false;
3577 let mut has_digit_letter_confusion = false;
3578
3579 if found.len() == suggested.len() {
3580 let mut has_case_diff = false;
3581 let mut has_digit_letter_confusable = false;
3582 let mut has_other_diff = false;
3583
3584 let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
3585
3586 let digit_letter_confusables = [('0', 'O'), ('1', 'l'), ('5', 'S'), ('8', 'B'), ('9', 'g')];
3587
3588 for (f, s) in iter::zip(found.chars(), suggested.chars()) {
3589 if f != s {
3590 if f.eq_ignore_ascii_case(&s) {
3591 if ascii_confusables.contains(&f) || ascii_confusables.contains(&s) {
3593 has_case_diff = true;
3594 } else {
3595 has_other_diff = true;
3596 }
3597 } else if digit_letter_confusables.contains(&(f, s))
3598 || digit_letter_confusables.contains(&(s, f))
3599 {
3600 has_digit_letter_confusable = true;
3602 } else {
3603 has_other_diff = true;
3604 }
3605 }
3606 }
3607
3608 if has_case_diff && !has_other_diff && found != suggested {
3610 has_case_confusion = true;
3611 }
3612 if has_digit_letter_confusable && !has_other_diff && found != suggested {
3613 has_digit_letter_confusion = true;
3614 }
3615 }
3616
3617 match (has_case_confusion, has_digit_letter_confusion) {
3618 (true, true) => ConfusionType::Both,
3619 (true, false) => ConfusionType::Case,
3620 (false, true) => ConfusionType::DigitLetter,
3621 (false, false) => ConfusionType::None,
3622 }
3623}
3624
3625#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3627pub enum ConfusionType {
3628 None,
3630 Case,
3632 DigitLetter,
3634 Both,
3636}
3637
3638impl ConfusionType {
3639 pub fn label_text(&self) -> &'static str {
3641 match self {
3642 ConfusionType::None => "",
3643 ConfusionType::Case => " (notice the capitalization)",
3644 ConfusionType::DigitLetter => " (notice the digit/letter confusion)",
3645 ConfusionType::Both => " (notice the capitalization and digit/letter confusion)",
3646 }
3647 }
3648
3649 pub fn combine(self, other: ConfusionType) -> ConfusionType {
3653 match (self, other) {
3654 (ConfusionType::None, other) => other,
3655 (this, ConfusionType::None) => this,
3656 (ConfusionType::Both, _) | (_, ConfusionType::Both) => ConfusionType::Both,
3657 (ConfusionType::Case, ConfusionType::DigitLetter)
3658 | (ConfusionType::DigitLetter, ConfusionType::Case) => ConfusionType::Both,
3659 (ConfusionType::Case, ConfusionType::Case) => ConfusionType::Case,
3660 (ConfusionType::DigitLetter, ConfusionType::DigitLetter) => ConfusionType::DigitLetter,
3661 }
3662 }
3663
3664 pub fn has_confusion(&self) -> bool {
3666 *self != ConfusionType::None
3667 }
3668}
3669
3670pub(crate) fn should_show_source_code(
3671 ignored_directories: &[String],
3672 sm: &SourceMap,
3673 file: &SourceFile,
3674) -> bool {
3675 if !sm.ensure_source_file_source_present(file) {
3676 return false;
3677 }
3678
3679 let FileName::Real(name) = &file.name else { return true };
3680 name.local_path()
3681 .map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
3682 .unwrap_or(true)
3683}