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 anstream::{AutoStream, ColorChoice};
20use anstyle::{AnsiColor, Effects};
21use derive_setters::Setters;
22use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
23use rustc_data_structures::sync::{DynSend, IntoDynSyncSend};
24use rustc_error_messages::{FluentArgs, SpanLabel};
25use rustc_lexer;
26use rustc_lint_defs::pluralize;
27use rustc_span::hygiene::{ExpnKind, MacroKind};
28use rustc_span::source_map::SourceMap;
29use rustc_span::{FileLines, FileName, SourceFile, Span, char_width, str_width};
30use tracing::{debug, instrument, trace, warn};
31
32use crate::registry::Registry;
33use crate::snippet::{
34 Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString,
35};
36use crate::styled_buffer::StyledBuffer;
37use crate::timings::TimingRecord;
38use crate::translation::{Translator, to_fluent_args};
39use crate::{
40 CodeSuggestion, DiagInner, DiagMessage, ErrCode, Level, MultiSpan, Subdiag,
41 SubstitutionHighlight, SuggestionStyle, TerminalUrl,
42};
43
44const DEFAULT_COLUMN_WIDTH: usize = 140;
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum HumanReadableErrorType {
50 Default,
51 Unicode,
52 AnnotateSnippet,
53 Short,
54}
55
56impl HumanReadableErrorType {
57 pub fn short(&self) -> bool {
58 *self == HumanReadableErrorType::Short
59 }
60}
61
62#[derive(Clone, Copy, Debug)]
63struct Margin {
64 pub whitespace_left: usize,
66 pub span_left: usize,
68 pub span_right: usize,
70 pub computed_left: usize,
72 pub computed_right: usize,
74 pub column_width: usize,
77 pub label_right: usize,
80}
81
82impl Margin {
83 fn new(
84 whitespace_left: usize,
85 span_left: usize,
86 span_right: usize,
87 label_right: usize,
88 column_width: usize,
89 max_line_len: usize,
90 ) -> Self {
91 let mut m = Margin {
101 whitespace_left: whitespace_left.saturating_sub(6),
102 span_left: span_left.saturating_sub(6),
103 span_right: span_right + 6,
104 computed_left: 0,
105 computed_right: 0,
106 column_width,
107 label_right: label_right + 6,
108 };
109 m.compute(max_line_len);
110 m
111 }
112
113 fn was_cut_left(&self) -> bool {
114 self.computed_left > 0
115 }
116
117 fn compute(&mut self, max_line_len: usize) {
118 self.computed_left = if self.whitespace_left > 20 {
123 self.whitespace_left - 16 } else {
125 0
126 };
127 self.computed_right = max(max_line_len, self.computed_left);
130
131 if self.computed_right - self.computed_left > self.column_width {
132 if self.label_right - self.whitespace_left <= self.column_width {
134 self.computed_left = self.whitespace_left;
136 self.computed_right = self.computed_left + self.column_width;
137 } else if self.label_right - self.span_left <= self.column_width {
138 let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
140 self.computed_left = self.span_left.saturating_sub(padding_left);
141 self.computed_right = self.computed_left + self.column_width;
142 } else if self.span_right - self.span_left <= self.column_width {
143 let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
145 self.computed_left = self.span_left.saturating_sub(padding_left);
146 self.computed_right = self.computed_left + self.column_width;
147 } else {
148 self.computed_left = self.span_left;
150 self.computed_right = self.span_right;
151 }
152 }
153 }
154
155 fn left(&self, line_len: usize) -> usize {
156 min(self.computed_left, line_len)
157 }
158
159 fn right(&self, line_len: usize) -> usize {
160 if line_len.saturating_sub(self.computed_left) <= self.column_width {
161 line_len
162 } else {
163 min(line_len, self.computed_right)
164 }
165 }
166}
167
168pub enum TimingEvent {
169 Start,
170 End,
171}
172
173const ANONYMIZED_LINE_NUM: &str = "LL";
174
175pub type DynEmitter = dyn Emitter + DynSend;
176
177pub trait Emitter {
179 fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
181
182 fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
185
186 fn emit_timing_section(&mut self, _record: TimingRecord, _event: TimingEvent) {}
189
190 fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}
193
194 fn emit_unused_externs(
197 &mut self,
198 _lint_level: rustc_lint_defs::Level,
199 _unused_externs: &[&str],
200 ) {
201 }
202
203 fn should_show_explain(&self) -> bool {
205 true
206 }
207
208 fn supports_color(&self) -> bool {
210 false
211 }
212
213 fn source_map(&self) -> Option<&SourceMap>;
214
215 fn translator(&self) -> &Translator;
216
217 fn primary_span_formatted(
229 &self,
230 primary_span: &mut MultiSpan,
231 suggestions: &mut Vec<CodeSuggestion>,
232 fluent_args: &FluentArgs<'_>,
233 ) {
234 if let Some((sugg, rest)) = suggestions.split_first() {
235 let msg = self
236 .translator()
237 .translate_message(&sugg.msg, fluent_args)
238 .map_err(Report::new)
239 .unwrap();
240 if rest.is_empty()
241 && let [substitution] = sugg.substitutions.as_slice()
244 && let [part] = substitution.parts.as_slice()
246 && msg.split_whitespace().count() < 10
248 && !part.snippet.contains('\n')
250 && ![
251 SuggestionStyle::HideCodeAlways,
253 SuggestionStyle::CompletelyHidden,
255 SuggestionStyle::ShowAlways,
257 ].contains(&sugg.style)
258 {
259 let snippet = part.snippet.trim();
260 let msg = if snippet.is_empty() || sugg.style.hide_inline() {
261 format!("help: {msg}")
264 } else {
265 let confusion_type = self
267 .source_map()
268 .map(|sm| detect_confusion_type(sm, snippet, part.span))
269 .unwrap_or(ConfusionType::None);
270 format!("help: {}{}: `{}`", msg, confusion_type.label_text(), snippet,)
271 };
272 primary_span.push_span_label(part.span, msg);
273
274 suggestions.clear();
276 } else {
277 }
282 } else {
283 }
285 }
286
287 fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
288 &self,
289 span: &mut MultiSpan,
290 children: &mut Vec<Subdiag>,
291 level: &Level,
292 backtrace: bool,
293 ) {
294 let has_macro_spans: Vec<_> = iter::once(&*span)
297 .chain(children.iter().map(|child| &child.span))
298 .flat_map(|span| span.primary_spans())
299 .flat_map(|sp| sp.macro_backtrace())
300 .filter_map(|expn_data| {
301 match expn_data.kind {
302 ExpnKind::Root => None,
303
304 ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
307
308 ExpnKind::Macro(macro_kind, name) => {
309 Some((macro_kind, name, expn_data.hide_backtrace))
310 }
311 }
312 })
313 .collect();
314
315 if !backtrace {
316 self.fix_multispans_in_extern_macros(span, children);
317 }
318
319 self.render_multispans_macro_backtrace(span, children, backtrace);
320
321 if !backtrace {
322 if let Some((macro_kind, name, _)) = has_macro_spans.first()
325 && let Some((_, _, false)) = has_macro_spans.last()
326 {
327 let and_then = if let Some((macro_kind, last_name, _)) = has_macro_spans.last()
329 && last_name != name
330 {
331 let descr = macro_kind.descr();
332 format!(" which comes from the expansion of the {descr} `{last_name}`")
333 } else {
334 "".to_string()
335 };
336
337 let descr = macro_kind.descr();
338 let msg = format!(
339 "this {level} originates in the {descr} `{name}`{and_then} \
340 (in Nightly builds, run with -Z macro-backtrace for more info)",
341 );
342
343 children.push(Subdiag {
344 level: Level::Note,
345 messages: vec![(DiagMessage::from(msg), Style::NoStyle)],
346 span: MultiSpan::new(),
347 });
348 }
349 }
350 }
351
352 fn render_multispans_macro_backtrace(
353 &self,
354 span: &mut MultiSpan,
355 children: &mut Vec<Subdiag>,
356 backtrace: bool,
357 ) {
358 for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
359 self.render_multispan_macro_backtrace(span, backtrace);
360 }
361 }
362
363 fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
364 let mut new_labels = FxIndexSet::default();
365
366 for &sp in span.primary_spans() {
367 if sp.is_dummy() {
368 continue;
369 }
370
371 let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
375 for (i, trace) in macro_backtrace.iter().rev().enumerate() {
376 if trace.def_site.is_dummy() {
377 continue;
378 }
379
380 if always_backtrace {
381 new_labels.insert((
382 trace.def_site,
383 format!(
384 "in this expansion of `{}`{}",
385 trace.kind.descr(),
386 if macro_backtrace.len() > 1 {
387 format!(" (#{})", i + 1)
390 } else {
391 String::new()
392 },
393 ),
394 ));
395 }
396
397 let redundant_span = trace.call_site.contains(sp);
409
410 if !redundant_span || always_backtrace {
411 let msg: Cow<'static, _> = match trace.kind {
412 ExpnKind::Macro(MacroKind::Attr, _) => {
413 "this attribute macro expansion".into()
414 }
415 ExpnKind::Macro(MacroKind::Derive, _) => {
416 "this derive macro expansion".into()
417 }
418 ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
419 ExpnKind::Root => "the crate root".into(),
420 ExpnKind::AstPass(kind) => kind.descr().into(),
421 ExpnKind::Desugaring(kind) => {
422 format!("this {} desugaring", kind.descr()).into()
423 }
424 };
425 new_labels.insert((
426 trace.call_site,
427 format!(
428 "in {}{}",
429 msg,
430 if macro_backtrace.len() > 1 && always_backtrace {
431 format!(" (#{})", i + 1)
434 } else {
435 String::new()
436 },
437 ),
438 ));
439 }
440 if !always_backtrace {
441 break;
442 }
443 }
444 }
445
446 for (label_span, label_text) in new_labels {
447 span.push_span_label(label_span, label_text);
448 }
449 }
450
451 fn fix_multispans_in_extern_macros(&self, span: &mut MultiSpan, children: &mut Vec<Subdiag>) {
455 debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
456 self.fix_multispan_in_extern_macros(span);
457 for child in children.iter_mut() {
458 self.fix_multispan_in_extern_macros(&mut child.span);
459 }
460 debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
461 }
462
463 fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) {
467 let Some(source_map) = self.source_map() else { return };
468 let replacements: Vec<(Span, Span)> = span
470 .primary_spans()
471 .iter()
472 .copied()
473 .chain(span.span_labels().iter().map(|sp_label| sp_label.span))
474 .filter_map(|sp| {
475 if !sp.is_dummy() && source_map.is_imported(sp) {
476 let maybe_callsite = sp.source_callsite();
477 if sp != maybe_callsite {
478 return Some((sp, maybe_callsite));
479 }
480 }
481 None
482 })
483 .collect();
484
485 for (from, to) in replacements {
487 span.replace(from, to);
488 }
489 }
490}
491
492impl Emitter for HumanEmitter {
493 fn source_map(&self) -> Option<&SourceMap> {
494 self.sm.as_deref()
495 }
496
497 fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
498 let fluent_args = to_fluent_args(diag.args.iter());
499
500 if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() {
501 diag.children.insert(0, diag.emitted_at_sub_diag());
502 }
503
504 let mut suggestions = diag.suggestions.unwrap_tag();
505 self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
506
507 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
508 &mut diag.span,
509 &mut diag.children,
510 &diag.level,
511 self.macro_backtrace,
512 );
513
514 self.emit_messages_default(
515 &diag.level,
516 &diag.messages,
517 &fluent_args,
518 &diag.code,
519 &diag.span,
520 &diag.children,
521 &suggestions,
522 );
523 }
524
525 fn should_show_explain(&self) -> bool {
526 !self.short_message
527 }
528
529 fn translator(&self) -> &Translator {
530 &self.translator
531 }
532}
533
534pub struct FatalOnlyEmitter {
538 pub fatal_emitter: Box<dyn Emitter + DynSend>,
539 pub fatal_note: Option<String>,
540}
541
542impl Emitter for FatalOnlyEmitter {
543 fn source_map(&self) -> Option<&SourceMap> {
544 None
545 }
546
547 fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
548 if diag.level == Level::Fatal {
549 if let Some(fatal_note) = &self.fatal_note {
550 diag.sub(Level::Note, fatal_note.clone(), MultiSpan::new());
551 }
552 self.fatal_emitter.emit_diagnostic(diag, registry);
553 }
554 }
555
556 fn translator(&self) -> &Translator {
557 self.fatal_emitter.translator()
558 }
559}
560
561pub struct SilentEmitter {
562 pub translator: Translator,
563}
564
565impl Emitter for SilentEmitter {
566 fn source_map(&self) -> Option<&SourceMap> {
567 None
568 }
569
570 fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {}
571
572 fn translator(&self) -> &Translator {
573 &self.translator
574 }
575}
576
577pub const MAX_SUGGESTIONS: usize = 4;
581
582#[derive(Clone, Copy, Debug, PartialEq, Eq)]
583pub enum ColorConfig {
584 Auto,
585 Always,
586 Never,
587}
588
589impl ColorConfig {
590 pub fn to_color_choice(self) -> ColorChoice {
591 match self {
592 ColorConfig::Always => {
593 if io::stderr().is_terminal() {
594 ColorChoice::Always
595 } else {
596 ColorChoice::AlwaysAnsi
597 }
598 }
599 ColorConfig::Never => ColorChoice::Never,
600 ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto,
601 ColorConfig::Auto => ColorChoice::Never,
602 }
603 }
604}
605
606#[derive(Debug, Clone, Copy, PartialEq, Eq)]
607pub enum OutputTheme {
608 Ascii,
609 Unicode,
610}
611
612#[derive(Setters)]
614pub struct HumanEmitter {
615 #[setters(skip)]
616 dst: IntoDynSyncSend<Destination>,
617 sm: Option<Arc<SourceMap>>,
618 #[setters(skip)]
619 translator: Translator,
620 short_message: bool,
621 ui_testing: bool,
622 ignored_directories_in_source_blocks: Vec<String>,
623 diagnostic_width: Option<usize>,
624
625 macro_backtrace: bool,
626 track_diagnostics: bool,
627 terminal_url: TerminalUrl,
628 theme: OutputTheme,
629}
630
631#[derive(Debug)]
632pub(crate) struct FileWithAnnotatedLines {
633 pub(crate) file: Arc<SourceFile>,
634 pub(crate) lines: Vec<Line>,
635 multiline_depth: usize,
636}
637
638impl HumanEmitter {
639 pub fn new(dst: Destination, translator: Translator) -> HumanEmitter {
640 HumanEmitter {
641 dst: IntoDynSyncSend(dst),
642 sm: None,
643 translator,
644 short_message: false,
645 ui_testing: false,
646 ignored_directories_in_source_blocks: Vec::new(),
647 diagnostic_width: None,
648 macro_backtrace: false,
649 track_diagnostics: false,
650 terminal_url: TerminalUrl::No,
651 theme: OutputTheme::Ascii,
652 }
653 }
654
655 fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
656 if self.ui_testing {
657 Cow::Borrowed(ANONYMIZED_LINE_NUM)
658 } else {
659 Cow::Owned(line_num.to_string())
660 }
661 }
662
663 fn draw_line(
664 &self,
665 buffer: &mut StyledBuffer,
666 source_string: &str,
667 line_index: usize,
668 line_offset: usize,
669 width_offset: usize,
670 code_offset: usize,
671 margin: Margin,
672 ) -> usize {
673 let line_len = source_string.len();
674 let left = margin.left(line_len);
676 let right = margin.right(line_len);
677 let code: String = source_string
680 .chars()
681 .enumerate()
682 .skip_while(|(i, _)| *i < left)
683 .take_while(|(i, _)| *i < right)
684 .map(|(_, c)| c)
685 .collect();
686 let code = normalize_whitespace(&code);
687 let was_cut_right =
688 source_string.chars().enumerate().skip_while(|(i, _)| *i < right).next().is_some();
689 buffer.puts(line_offset, code_offset, &code, Style::Quotation);
690 let placeholder = self.margin();
691 if margin.was_cut_left() {
692 buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber);
694 }
695 if was_cut_right {
696 let padding = str_width(placeholder);
697 buffer.puts(
699 line_offset,
700 code_offset + str_width(&code) - padding,
701 placeholder,
702 Style::LineNumber,
703 );
704 }
705 self.draw_line_num(buffer, line_index, line_offset, width_offset - 3);
706 self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2);
707 left
708 }
709
710 #[instrument(level = "trace", skip(self), ret)]
711 fn render_source_line(
712 &self,
713 buffer: &mut StyledBuffer,
714 file: Arc<SourceFile>,
715 line: &Line,
716 width_offset: usize,
717 code_offset: usize,
718 margin: Margin,
719 close_window: bool,
720 ) -> Vec<(usize, Style)> {
721 if line.line_index == 0 {
736 return Vec::new();
737 }
738
739 let Some(source_string) = file.get_line(line.line_index - 1) else {
740 return Vec::new();
741 };
742 trace!(?source_string);
743
744 let line_offset = buffer.num_lines();
745
746 let left = self.draw_line(
749 buffer,
750 &source_string,
751 line.line_index,
752 line_offset,
753 width_offset,
754 code_offset,
755 margin,
756 );
757
758 let mut buffer_ops = vec![];
775 let mut annotations = vec![];
776 let mut short_start = true;
777 for ann in &line.annotations {
778 if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
779 if source_string.chars().take(ann.start_col.file).all(|c| c.is_whitespace()) {
780 let uline = self.underline(ann.is_primary);
781 let chr = uline.multiline_whole_line;
782 annotations.push((depth, uline.style));
783 buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style));
784 } else {
785 short_start = false;
786 break;
787 }
788 } else if let AnnotationType::MultilineLine(_) = ann.annotation_type {
789 } else {
790 short_start = false;
791 break;
792 }
793 }
794 if short_start {
795 for (y, x, c, s) in buffer_ops {
796 buffer.putc(y, x, c, s);
797 }
798 return annotations;
799 }
800
801 let mut annotations = line.annotations.clone();
834 annotations.sort_by_key(|a| Reverse(a.start_col));
835
836 let mut overlap = vec![false; annotations.len()];
899 let mut annotations_position = vec![];
900 let mut line_len: usize = 0;
901 let mut p = 0;
902 for (i, annotation) in annotations.iter().enumerate() {
903 for (j, next) in annotations.iter().enumerate() {
904 if overlaps(next, annotation, 0) && j > i {
905 overlap[i] = true;
906 overlap[j] = true;
907 }
908 if overlaps(next, annotation, 0) && annotation.has_label() && j > i && p == 0
912 {
914 if next.start_col == annotation.start_col
917 && next.end_col == annotation.end_col
918 && !next.has_label()
919 {
920 continue;
921 }
922
923 p += 1;
925 break;
926 }
927 }
928 annotations_position.push((p, annotation));
929 for (j, next) in annotations.iter().enumerate() {
930 if j > i {
931 let l = next.label.as_ref().map_or(0, |label| label.len() + 2);
932 if (overlaps(next, annotation, l) && annotation.has_label() && next.has_label()) || (annotation.takes_space() && next.has_label()) || (annotation.has_label() && next.takes_space())
949 || (annotation.takes_space() && next.takes_space())
950 || (overlaps(next, annotation, l)
951 && next.end_col <= annotation.end_col
952 && next.has_label()
953 && p == 0)
954 {
956 p += 1;
958 break;
959 }
960 }
961 }
962 line_len = max(line_len, p);
963 }
964
965 if line_len != 0 {
966 line_len += 1;
967 }
968
969 if line.annotations.iter().all(|a| a.is_line()) {
972 return vec![];
973 }
974
975 if annotations_position
976 .iter()
977 .all(|(_, ann)| matches!(ann.annotation_type, AnnotationType::MultilineStart(_)))
978 && let Some(max_pos) = annotations_position.iter().map(|(pos, _)| *pos).max()
979 {
980 for (pos, _) in &mut annotations_position {
993 *pos = max_pos - *pos;
994 }
995 line_len = line_len.saturating_sub(1);
998 }
999
1000 for pos in 0..=line_len {
1012 self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2);
1013 }
1014 if close_window {
1015 self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2);
1016 }
1017
1018 for &(pos, annotation) in &annotations_position {
1031 let underline = self.underline(annotation.is_primary);
1032 let pos = pos + 1;
1033 match annotation.annotation_type {
1034 AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => {
1035 let pre: usize = source_string
1036 .chars()
1037 .take(annotation.start_col.file)
1038 .skip(left)
1039 .map(|c| char_width(c))
1040 .sum();
1041 self.draw_range(
1042 buffer,
1043 underline.multiline_horizontal,
1044 line_offset + pos,
1045 width_offset + depth,
1046 code_offset + pre,
1047 underline.style,
1048 );
1049 }
1050 _ => {}
1051 }
1052 }
1053
1054 for &(pos, annotation) in &annotations_position {
1066 let underline = self.underline(annotation.is_primary);
1067 let pos = pos + 1;
1068
1069 let code_offset = code_offset
1070 + source_string
1071 .chars()
1072 .take(annotation.start_col.file)
1073 .skip(left)
1074 .map(|c| char_width(c))
1075 .sum::<usize>();
1076 if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
1077 for p in line_offset + 1..=line_offset + pos {
1078 buffer.putc(
1079 p,
1080 code_offset,
1081 match annotation.annotation_type {
1082 AnnotationType::MultilineLine(_) => underline.multiline_vertical,
1083 _ => underline.vertical_text_line,
1084 },
1085 underline.style,
1086 );
1087 }
1088 if let AnnotationType::MultilineStart(_) = annotation.annotation_type {
1089 buffer.putc(
1090 line_offset + pos,
1091 code_offset,
1092 underline.bottom_right,
1093 underline.style,
1094 );
1095 }
1096 if let AnnotationType::MultilineEnd(_) = annotation.annotation_type
1097 && annotation.has_label()
1098 {
1099 buffer.putc(
1100 line_offset + pos,
1101 code_offset,
1102 underline.multiline_bottom_right_with_text,
1103 underline.style,
1104 );
1105 }
1106 }
1107 match annotation.annotation_type {
1108 AnnotationType::MultilineStart(depth) => {
1109 buffer.putc(
1110 line_offset + pos,
1111 width_offset + depth - 1,
1112 underline.top_left,
1113 underline.style,
1114 );
1115 for p in line_offset + pos + 1..line_offset + line_len + 2 {
1116 buffer.putc(
1117 p,
1118 width_offset + depth - 1,
1119 underline.multiline_vertical,
1120 underline.style,
1121 );
1122 }
1123 }
1124 AnnotationType::MultilineEnd(depth) => {
1125 for p in line_offset..line_offset + pos {
1126 buffer.putc(
1127 p,
1128 width_offset + depth - 1,
1129 underline.multiline_vertical,
1130 underline.style,
1131 );
1132 }
1133 buffer.putc(
1134 line_offset + pos,
1135 width_offset + depth - 1,
1136 underline.bottom_left,
1137 underline.style,
1138 );
1139 }
1140 _ => (),
1141 }
1142 }
1143
1144 for &(pos, annotation) in &annotations_position {
1156 let style =
1157 if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary };
1158 let (pos, col) = if pos == 0 {
1159 let pre: usize = source_string
1160 .chars()
1161 .take(annotation.end_col.file)
1162 .skip(left)
1163 .map(|c| char_width(c))
1164 .sum();
1165 if annotation.end_col.file == 0 {
1166 (pos + 1, (pre + 2))
1167 } else {
1168 let pad = if annotation.end_col.file - annotation.start_col.file == 0 {
1169 2
1170 } else {
1171 1
1172 };
1173 (pos + 1, (pre + pad))
1174 }
1175 } else {
1176 let pre: usize = source_string
1177 .chars()
1178 .take(annotation.start_col.file)
1179 .skip(left)
1180 .map(|c| char_width(c))
1181 .sum();
1182 (pos + 2, pre)
1183 };
1184 if let Some(ref label) = annotation.label {
1185 buffer.puts(line_offset + pos, code_offset + col, label, style);
1186 }
1187 }
1188
1189 annotations_position.sort_by_key(|(_, ann)| {
1198 (Reverse(ann.len()), ann.is_primary)
1200 });
1201
1202 for &(pos, annotation) in &annotations_position {
1214 let uline = self.underline(annotation.is_primary);
1215 let width = annotation.end_col.file - annotation.start_col.file;
1216 let previous: String =
1217 source_string.chars().take(annotation.start_col.file).skip(left).collect();
1218 let underlined: String =
1219 source_string.chars().skip(annotation.start_col.file).take(width).collect();
1220 debug!(?previous, ?underlined);
1221 let code_offset = code_offset
1222 + source_string
1223 .chars()
1224 .take(annotation.start_col.file)
1225 .skip(left)
1226 .map(|c| char_width(c))
1227 .sum::<usize>();
1228 let ann_width: usize = source_string
1229 .chars()
1230 .skip(annotation.start_col.file)
1231 .take(width)
1232 .map(|c| char_width(c))
1233 .sum();
1234 let ann_width = if ann_width == 0
1235 && matches!(annotation.annotation_type, AnnotationType::Singleline)
1236 {
1237 1
1238 } else {
1239 ann_width
1240 };
1241 for p in 0..ann_width {
1242 buffer.putc(line_offset + 1, code_offset + p, uline.underline, uline.style);
1244 }
1245
1246 if pos == 0
1247 && matches!(
1248 annotation.annotation_type,
1249 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1250 )
1251 {
1252 buffer.putc(
1254 line_offset + 1,
1255 code_offset,
1256 match annotation.annotation_type {
1257 AnnotationType::MultilineStart(_) => uline.top_right_flat,
1258 AnnotationType::MultilineEnd(_) => uline.multiline_end_same_line,
1259 _ => panic!("unexpected annotation type: {annotation:?}"),
1260 },
1261 uline.style,
1262 );
1263 } else if pos != 0
1264 && matches!(
1265 annotation.annotation_type,
1266 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1267 )
1268 {
1269 buffer.putc(
1272 line_offset + 1,
1273 code_offset,
1274 match annotation.annotation_type {
1275 AnnotationType::MultilineStart(_) => uline.multiline_start_down,
1276 AnnotationType::MultilineEnd(_) => uline.multiline_end_up,
1277 _ => panic!("unexpected annotation type: {annotation:?}"),
1278 },
1279 uline.style,
1280 );
1281 } else if pos != 0 && annotation.has_label() {
1282 buffer.putc(line_offset + 1, code_offset, uline.label_start, uline.style);
1284 }
1285 }
1286
1287 for (i, (_pos, annotation)) in annotations_position.iter().enumerate() {
1291 if overlap[i] {
1293 continue;
1294 };
1295 let AnnotationType::Singleline = annotation.annotation_type else { continue };
1296 let width = annotation.end_col.display - annotation.start_col.display;
1297 if width > margin.column_width * 2 && width > 10 {
1298 let pad = max(margin.column_width / 3, 5);
1301 buffer.replace(
1303 line_offset,
1304 annotation.start_col.file + pad,
1305 annotation.end_col.file - pad,
1306 self.margin(),
1307 );
1308 buffer.replace(
1310 line_offset + 1,
1311 annotation.start_col.file + pad,
1312 annotation.end_col.file - pad,
1313 self.margin(),
1314 );
1315 }
1316 }
1317 annotations_position
1318 .iter()
1319 .filter_map(|&(_, annotation)| match annotation.annotation_type {
1320 AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => {
1321 let style = if annotation.is_primary {
1322 Style::LabelPrimary
1323 } else {
1324 Style::LabelSecondary
1325 };
1326 Some((p, style))
1327 }
1328 _ => None,
1329 })
1330 .collect::<Vec<_>>()
1331 }
1332
1333 fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
1334 let Some(ref sm) = self.sm else {
1335 return 0;
1336 };
1337
1338 let will_be_emitted = |span: Span| {
1339 !span.is_dummy() && {
1340 let file = sm.lookup_source_file(span.hi());
1341 should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file)
1342 }
1343 };
1344
1345 let mut max = 0;
1346 for primary_span in msp.primary_spans() {
1347 if will_be_emitted(*primary_span) {
1348 let hi = sm.lookup_char_pos(primary_span.hi());
1349 max = (hi.line).max(max);
1350 }
1351 }
1352 if !self.short_message {
1353 for span_label in msp.span_labels() {
1354 if will_be_emitted(span_label.span) {
1355 let hi = sm.lookup_char_pos(span_label.span.hi());
1356 max = (hi.line).max(max);
1357 }
1358 }
1359 }
1360
1361 max
1362 }
1363
1364 fn get_max_line_num(&mut self, span: &MultiSpan, children: &[Subdiag]) -> usize {
1365 let primary = self.get_multispan_max_line_num(span);
1366 children
1367 .iter()
1368 .map(|sub| self.get_multispan_max_line_num(&sub.span))
1369 .max()
1370 .unwrap_or(0)
1371 .max(primary)
1372 }
1373
1374 fn msgs_to_buffer(
1377 &self,
1378 buffer: &mut StyledBuffer,
1379 msgs: &[(DiagMessage, Style)],
1380 args: &FluentArgs<'_>,
1381 padding: usize,
1382 label: &str,
1383 override_style: Option<Style>,
1384 ) -> usize {
1385 let padding = " ".repeat(padding + label.len() + 5);
1402
1403 fn style_or_override(style: Style, override_: Option<Style>) -> Style {
1405 match (style, override_) {
1406 (Style::NoStyle, Some(override_)) => override_,
1407 _ => style,
1408 }
1409 }
1410
1411 let mut line_number = 0;
1412
1413 for (text, style) in msgs.iter() {
1433 let text = self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1434 let text = &normalize_whitespace(&text);
1435 let lines = text.split('\n').collect::<Vec<_>>();
1436 if lines.len() > 1 {
1437 for (i, line) in lines.iter().enumerate() {
1438 if i != 0 {
1439 line_number += 1;
1440 buffer.append(line_number, &padding, Style::NoStyle);
1441 }
1442 buffer.append(line_number, line, style_or_override(*style, override_style));
1443 }
1444 } else {
1445 buffer.append(line_number, text, style_or_override(*style, override_style));
1446 }
1447 }
1448 line_number
1449 }
1450
1451 #[instrument(level = "trace", skip(self, args), ret)]
1452 fn emit_messages_default_inner(
1453 &mut self,
1454 msp: &MultiSpan,
1455 msgs: &[(DiagMessage, Style)],
1456 args: &FluentArgs<'_>,
1457 code: &Option<ErrCode>,
1458 level: &Level,
1459 max_line_num_len: usize,
1460 is_secondary: bool,
1461 is_cont: bool,
1462 ) -> io::Result<CodeWindowStatus> {
1463 let mut buffer = StyledBuffer::new();
1464
1465 if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
1466 {
1467 for _ in 0..max_line_num_len {
1469 buffer.prepend(0, " ", Style::NoStyle);
1470 }
1471 self.draw_note_separator(&mut buffer, 0, max_line_num_len + 1, is_cont);
1472 if *level != Level::FailureNote {
1473 buffer.append(0, level.to_str(), Style::MainHeaderMsg);
1474 buffer.append(0, ": ", Style::NoStyle);
1475 }
1476 let printed_lines =
1477 self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None);
1478 if is_cont && matches!(self.theme, OutputTheme::Unicode) {
1479 for i in 1..=printed_lines {
1491 self.draw_col_separator_no_space(&mut buffer, i, max_line_num_len + 1);
1492 }
1493 }
1494 } else {
1495 let mut label_width = 0;
1496 if *level != Level::FailureNote {
1498 buffer.append(0, level.to_str(), Style::Level(*level));
1499 label_width += level.to_str().len();
1500 }
1501 if let Some(code) = code {
1502 buffer.append(0, "[", Style::Level(*level));
1503 let code = if let TerminalUrl::Yes = self.terminal_url {
1504 let path = "https://doc.rust-lang.org/error_codes";
1505 format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
1506 } else {
1507 code.to_string()
1508 };
1509 buffer.append(0, &code, Style::Level(*level));
1510 buffer.append(0, "]", Style::Level(*level));
1511 label_width += 2 + code.len();
1512 }
1513 let header_style = if is_secondary {
1514 Style::HeaderMsg
1515 } else if self.short_message {
1516 Style::NoStyle
1518 } else {
1519 Style::MainHeaderMsg
1520 };
1521 if *level != Level::FailureNote {
1522 buffer.append(0, ": ", header_style);
1523 label_width += 2;
1524 }
1525 let mut line = 0;
1526 for (text, style) in msgs.iter() {
1527 let text =
1528 self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1529 for text in normalize_whitespace(&text).lines() {
1531 buffer.append(
1532 line,
1533 &format!(
1534 "{}{}",
1535 if line == 0 { String::new() } else { " ".repeat(label_width) },
1536 text
1537 ),
1538 match style {
1539 Style::Highlight => *style,
1540 _ => header_style,
1541 },
1542 );
1543 line += 1;
1544 }
1545 if line > 0 {
1551 line -= 1;
1552 }
1553 }
1554 if self.short_message {
1555 let labels = msp
1556 .span_labels()
1557 .into_iter()
1558 .filter_map(|label| match label.label {
1559 Some(msg) if label.is_primary => {
1560 let text = self.translator.translate_message(&msg, args).ok()?;
1561 if !text.trim().is_empty() { Some(text.to_string()) } else { None }
1562 }
1563 _ => None,
1564 })
1565 .collect::<Vec<_>>()
1566 .join(", ");
1567 if !labels.is_empty() {
1568 buffer.append(line, ": ", Style::NoStyle);
1569 buffer.append(line, &labels, Style::NoStyle);
1570 }
1571 }
1572 }
1573 let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
1574 trace!("{annotated_files:#?}");
1575 let mut code_window_status = CodeWindowStatus::Open;
1576
1577 let primary_span = msp.primary_span().unwrap_or_default();
1579 let (Some(sm), false) = (self.sm.as_ref(), primary_span.is_dummy()) else {
1580 return emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)
1582 .map(|_| code_window_status);
1583 };
1584 let primary_lo = sm.lookup_char_pos(primary_span.lo());
1585 if let Ok(pos) =
1586 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
1587 {
1588 annotated_files.swap(0, pos);
1589 }
1590
1591 let mut col_sep_before_no_show_source = false;
1594 let annotated_files_len = annotated_files.len();
1595 for (file_idx, annotated_file) in annotated_files.into_iter().enumerate() {
1597 if !should_show_source_code(
1599 &self.ignored_directories_in_source_blocks,
1600 sm,
1601 &annotated_file.file,
1602 ) {
1603 if !self.short_message {
1604 if col_sep_before_no_show_source {
1615 let buffer_msg_line_offset = buffer.num_lines();
1616 self.draw_col_separator_end(
1617 &mut buffer,
1618 buffer_msg_line_offset,
1619 max_line_num_len + 1,
1620 );
1621 }
1622 col_sep_before_no_show_source = false;
1623
1624 for (annotation_id, line) in annotated_file.lines.iter().enumerate() {
1626 let mut annotations = line.annotations.clone();
1627 annotations.sort_by_key(|a| Reverse(a.start_col));
1628 let mut line_idx = buffer.num_lines();
1629
1630 let labels: Vec<_> = annotations
1631 .iter()
1632 .filter_map(|a| Some((a.label.as_ref()?, a.is_primary)))
1633 .filter(|(l, _)| !l.is_empty())
1634 .collect();
1635
1636 if annotation_id == 0 || !labels.is_empty() {
1637 buffer.append(
1638 line_idx,
1639 &format!(
1640 "{}:{}:{}",
1641 sm.filename_for_diagnostics(&annotated_file.file.name),
1642 sm.doctest_offset_line(
1643 &annotated_file.file.name,
1644 line.line_index
1645 ),
1646 annotations[0].start_col.file + 1,
1647 ),
1648 Style::LineAndColumn,
1649 );
1650 if annotation_id == 0 {
1651 buffer.prepend(line_idx, self.file_start(), Style::LineNumber);
1652 } else {
1653 buffer.prepend(
1654 line_idx,
1655 self.secondary_file_start(),
1656 Style::LineNumber,
1657 );
1658 }
1659 for _ in 0..max_line_num_len {
1660 buffer.prepend(line_idx, " ", Style::NoStyle);
1661 }
1662 line_idx += 1;
1663 }
1664 if is_cont
1665 && file_idx == annotated_files_len - 1
1666 && annotation_id == annotated_file.lines.len() - 1
1667 && !labels.is_empty()
1668 {
1669 code_window_status = CodeWindowStatus::Closed;
1670 }
1671 let labels_len = labels.len();
1672 for (label_idx, (label, is_primary)) in labels.into_iter().enumerate() {
1673 let style = if is_primary {
1674 Style::LabelPrimary
1675 } else {
1676 Style::LabelSecondary
1677 };
1678 self.draw_col_separator_no_space(
1679 &mut buffer,
1680 line_idx,
1681 max_line_num_len + 1,
1682 );
1683 line_idx += 1;
1684 self.draw_note_separator(
1685 &mut buffer,
1686 line_idx,
1687 max_line_num_len + 1,
1688 label_idx != labels_len - 1,
1689 );
1690 buffer.append(line_idx, "note", Style::MainHeaderMsg);
1691 buffer.append(line_idx, ": ", Style::NoStyle);
1692 buffer.append(line_idx, label, style);
1693 line_idx += 1;
1694 }
1695 }
1696 }
1697 continue;
1698 } else {
1699 col_sep_before_no_show_source = true;
1700 }
1701 let is_primary = primary_lo.file.name == annotated_file.file.name;
1704 if is_primary {
1705 let loc = primary_lo.clone();
1706 if !self.short_message {
1707 let buffer_msg_line_offset = buffer.num_lines();
1709
1710 buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber);
1711 buffer.append(
1712 buffer_msg_line_offset,
1713 &format!(
1714 "{}:{}:{}",
1715 sm.filename_for_diagnostics(&loc.file.name),
1716 sm.doctest_offset_line(&loc.file.name, loc.line),
1717 loc.col.0 + 1,
1718 ),
1719 Style::LineAndColumn,
1720 );
1721 for _ in 0..max_line_num_len {
1722 buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1723 }
1724 } else {
1725 buffer.prepend(
1726 0,
1727 &format!(
1728 "{}:{}:{}: ",
1729 sm.filename_for_diagnostics(&loc.file.name),
1730 sm.doctest_offset_line(&loc.file.name, loc.line),
1731 loc.col.0 + 1,
1732 ),
1733 Style::LineAndColumn,
1734 );
1735 }
1736 } else if !self.short_message {
1737 let buffer_msg_line_offset = buffer.num_lines();
1739
1740 self.draw_col_separator_no_space(
1751 &mut buffer,
1752 buffer_msg_line_offset,
1753 max_line_num_len + 1,
1754 );
1755
1756 buffer.prepend(
1758 buffer_msg_line_offset + 1,
1759 self.secondary_file_start(),
1760 Style::LineNumber,
1761 );
1762 let loc = if let Some(first_line) = annotated_file.lines.first() {
1763 let col = if let Some(first_annotation) = first_line.annotations.first() {
1764 format!(":{}", first_annotation.start_col.file + 1)
1765 } else {
1766 String::new()
1767 };
1768 format!(
1769 "{}:{}{}",
1770 sm.filename_for_diagnostics(&annotated_file.file.name),
1771 sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
1772 col
1773 )
1774 } else {
1775 format!("{}", sm.filename_for_diagnostics(&annotated_file.file.name))
1776 };
1777 buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
1778 for _ in 0..max_line_num_len {
1779 buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
1780 }
1781 }
1782
1783 if !self.short_message {
1784 let buffer_msg_line_offset = buffer.num_lines();
1786 self.draw_col_separator_no_space(
1787 &mut buffer,
1788 buffer_msg_line_offset,
1789 max_line_num_len + 1,
1790 );
1791
1792 let mut multilines = FxIndexMap::default();
1794
1795 let mut whitespace_margin = usize::MAX;
1797 for line_idx in 0..annotated_file.lines.len() {
1798 let file = Arc::clone(&annotated_file.file);
1799 let line = &annotated_file.lines[line_idx];
1800 if let Some(source_string) =
1801 line.line_index.checked_sub(1).and_then(|l| file.get_line(l))
1802 {
1803 let leading_whitespace = source_string
1810 .chars()
1811 .take_while(|c| rustc_lexer::is_whitespace(*c))
1812 .count();
1813 if source_string.chars().any(|c| !rustc_lexer::is_whitespace(c)) {
1814 whitespace_margin = min(whitespace_margin, leading_whitespace);
1815 }
1816 }
1817 }
1818 if whitespace_margin == usize::MAX {
1819 whitespace_margin = 0;
1820 }
1821
1822 let mut span_left_margin = usize::MAX;
1824 for line in &annotated_file.lines {
1825 for ann in &line.annotations {
1826 span_left_margin = min(span_left_margin, ann.start_col.file);
1827 span_left_margin = min(span_left_margin, ann.end_col.file);
1828 }
1829 }
1830 if span_left_margin == usize::MAX {
1831 span_left_margin = 0;
1832 }
1833
1834 let mut span_right_margin = 0;
1836 let mut label_right_margin = 0;
1837 let mut max_line_len = 0;
1838 for line in &annotated_file.lines {
1839 max_line_len = max(
1840 max_line_len,
1841 line.line_index
1842 .checked_sub(1)
1843 .and_then(|l| annotated_file.file.get_line(l))
1844 .map_or(0, |s| s.len()),
1845 );
1846 for ann in &line.annotations {
1847 span_right_margin = max(span_right_margin, ann.start_col.file);
1848 span_right_margin = max(span_right_margin, ann.end_col.file);
1849 let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
1851 label_right_margin =
1852 max(label_right_margin, ann.end_col.file + label_right);
1853 }
1854 }
1855
1856 let width_offset = 3 + max_line_num_len;
1857 let code_offset = if annotated_file.multiline_depth == 0 {
1858 width_offset
1859 } else {
1860 width_offset + annotated_file.multiline_depth + 1
1861 };
1862
1863 let column_width = self.column_width(code_offset);
1864
1865 let margin = Margin::new(
1866 whitespace_margin,
1867 span_left_margin,
1868 span_right_margin,
1869 label_right_margin,
1870 column_width,
1871 max_line_len,
1872 );
1873
1874 for line_idx in 0..annotated_file.lines.len() {
1876 let previous_buffer_line = buffer.num_lines();
1877
1878 let depths = self.render_source_line(
1879 &mut buffer,
1880 Arc::clone(&annotated_file.file),
1881 &annotated_file.lines[line_idx],
1882 width_offset,
1883 code_offset,
1884 margin,
1885 !is_cont
1886 && file_idx + 1 == annotated_files_len
1887 && line_idx + 1 == annotated_file.lines.len(),
1888 );
1889
1890 let mut to_add = FxIndexMap::default();
1891
1892 for (depth, style) in depths {
1893 if multilines.swap_remove(&depth).is_none() {
1895 to_add.insert(depth, style);
1896 }
1897 }
1898
1899 for (depth, style) in &multilines {
1902 for line in previous_buffer_line..buffer.num_lines() {
1903 self.draw_multiline_line(
1904 &mut buffer,
1905 line,
1906 width_offset,
1907 *depth,
1908 *style,
1909 );
1910 }
1911 }
1912 if line_idx < (annotated_file.lines.len() - 1) {
1915 let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1916 - annotated_file.lines[line_idx].line_index;
1917 if line_idx_delta > 2 {
1918 let last_buffer_line_num = buffer.num_lines();
1919 self.draw_line_separator(
1920 &mut buffer,
1921 last_buffer_line_num,
1922 width_offset,
1923 );
1924
1925 for (depth, style) in &multilines {
1927 self.draw_multiline_line(
1928 &mut buffer,
1929 last_buffer_line_num,
1930 width_offset,
1931 *depth,
1932 *style,
1933 );
1934 }
1935 if let Some(line) = annotated_file.lines.get(line_idx) {
1936 for ann in &line.annotations {
1937 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1938 {
1939 self.draw_multiline_line(
1943 &mut buffer,
1944 last_buffer_line_num,
1945 width_offset,
1946 pos,
1947 if ann.is_primary {
1948 Style::UnderlinePrimary
1949 } else {
1950 Style::UnderlineSecondary
1951 },
1952 );
1953 }
1954 }
1955 }
1956 } else if line_idx_delta == 2 {
1957 let unannotated_line = annotated_file
1958 .file
1959 .get_line(annotated_file.lines[line_idx].line_index)
1960 .unwrap_or_else(|| Cow::from(""));
1961
1962 let last_buffer_line_num = buffer.num_lines();
1963
1964 self.draw_line(
1965 &mut buffer,
1966 &normalize_whitespace(&unannotated_line),
1967 annotated_file.lines[line_idx + 1].line_index - 1,
1968 last_buffer_line_num,
1969 width_offset,
1970 code_offset,
1971 margin,
1972 );
1973
1974 for (depth, style) in &multilines {
1975 self.draw_multiline_line(
1976 &mut buffer,
1977 last_buffer_line_num,
1978 width_offset,
1979 *depth,
1980 *style,
1981 );
1982 }
1983 if let Some(line) = annotated_file.lines.get(line_idx) {
1984 for ann in &line.annotations {
1985 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1986 {
1987 self.draw_multiline_line(
1988 &mut buffer,
1989 last_buffer_line_num,
1990 width_offset,
1991 pos,
1992 if ann.is_primary {
1993 Style::UnderlinePrimary
1994 } else {
1995 Style::UnderlineSecondary
1996 },
1997 );
1998 }
1999 }
2000 }
2001 }
2002 }
2003
2004 multilines.extend(&to_add);
2005 }
2006 }
2007 trace!("buffer: {:#?}", buffer.render());
2008 }
2009
2010 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2012
2013 Ok(code_window_status)
2014 }
2015
2016 fn column_width(&self, code_offset: usize) -> usize {
2017 if let Some(width) = self.diagnostic_width {
2018 width.saturating_sub(code_offset)
2019 } else if self.ui_testing || cfg!(miri) {
2020 DEFAULT_COLUMN_WIDTH.saturating_sub(code_offset)
2021 } else {
2022 termize::dimensions()
2023 .map(|(w, _)| w.saturating_sub(code_offset))
2024 .unwrap_or(DEFAULT_COLUMN_WIDTH)
2025 }
2026 }
2027
2028 fn emit_suggestion_default(
2029 &mut self,
2030 span: &MultiSpan,
2031 suggestion: &CodeSuggestion,
2032 args: &FluentArgs<'_>,
2033 level: &Level,
2034 max_line_num_len: usize,
2035 ) -> io::Result<()> {
2036 let Some(ref sm) = self.sm else {
2037 return Ok(());
2038 };
2039
2040 let suggestions = suggestion.splice_lines(sm);
2042 debug!(?suggestions);
2043
2044 if suggestions.is_empty() {
2045 return Ok(());
2051 }
2052
2053 let mut buffer = StyledBuffer::new();
2054
2055 buffer.append(0, level.to_str(), Style::Level(*level));
2057 buffer.append(0, ": ", Style::HeaderMsg);
2058
2059 let mut msg = vec![(suggestion.msg.to_owned(), Style::NoStyle)];
2060 if let Some(confusion_type) =
2061 suggestions.iter().take(MAX_SUGGESTIONS).find_map(|(_, _, _, confusion_type)| {
2062 if confusion_type.has_confusion() { Some(*confusion_type) } else { None }
2063 })
2064 {
2065 msg.push((confusion_type.label_text().into(), Style::NoStyle));
2066 }
2067 self.msgs_to_buffer(
2068 &mut buffer,
2069 &msg,
2070 args,
2071 max_line_num_len,
2072 "suggestion",
2073 Some(Style::HeaderMsg),
2074 );
2075
2076 let other_suggestions = suggestions.len().saturating_sub(MAX_SUGGESTIONS);
2077
2078 let mut row_num = 2;
2079 for (i, (complete, parts, highlights, _)) in
2080 suggestions.into_iter().enumerate().take(MAX_SUGGESTIONS)
2081 {
2082 debug!(?complete, ?parts, ?highlights);
2083
2084 let has_deletion =
2085 parts.iter().any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
2086 let is_multiline = complete.lines().count() > 1;
2087
2088 if i == 0 {
2089 self.draw_col_separator_start(&mut buffer, row_num - 1, max_line_num_len + 1);
2090 } else {
2091 buffer.puts(
2092 row_num - 1,
2093 max_line_num_len + 1,
2094 self.multi_suggestion_separator(),
2095 Style::LineNumber,
2096 );
2097 }
2098 if let Some(span) = span.primary_span() {
2099 let loc = sm.lookup_char_pos(parts[0].span.lo());
2104 if (span.is_dummy() || loc.file.name != sm.span_to_filename(span))
2105 && loc.file.name.is_real()
2106 {
2107 let arrow = self.file_start();
2110 buffer.puts(row_num - 1, 0, arrow, Style::LineNumber);
2111 let filename = sm.filename_for_diagnostics(&loc.file.name);
2112 let offset = sm.doctest_offset_line(&loc.file.name, loc.line);
2113 let message = format!("{}:{}:{}", filename, offset, loc.col.0 + 1);
2114 if row_num == 2 {
2115 let col = usize::max(max_line_num_len + 1, arrow.len());
2116 buffer.puts(1, col, &message, Style::LineAndColumn);
2117 } else {
2118 buffer.append(row_num - 1, &message, Style::LineAndColumn);
2119 }
2120 for _ in 0..max_line_num_len {
2121 buffer.prepend(row_num - 1, " ", Style::NoStyle);
2122 }
2123 self.draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
2124 row_num += 1;
2125 }
2126 }
2127 let show_code_change = if has_deletion && !is_multiline {
2128 DisplaySuggestion::Diff
2129 } else if let [part] = &parts[..]
2130 && part.snippet.ends_with('\n')
2131 && part.snippet.trim() == complete.trim()
2132 {
2133 DisplaySuggestion::Add
2135 } else if (parts.len() != 1 || parts[0].snippet.trim() != complete.trim())
2136 && !is_multiline
2137 {
2138 DisplaySuggestion::Underline
2139 } else {
2140 DisplaySuggestion::None
2141 };
2142
2143 if let DisplaySuggestion::Diff = show_code_change {
2144 row_num += 1;
2145 }
2146
2147 let file_lines = sm
2148 .span_to_lines(parts[0].span)
2149 .expect("span_to_lines failed when emitting suggestion");
2150
2151 assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
2152
2153 let line_start = sm.lookup_char_pos(parts[0].original_span.lo()).line;
2154 let mut lines = complete.lines();
2155 if lines.clone().next().is_none() {
2156 let line_end = sm.lookup_char_pos(parts[0].original_span.hi()).line;
2158 for line in line_start..=line_end {
2159 self.draw_line_num(
2160 &mut buffer,
2161 line,
2162 row_num - 1 + line - line_start,
2163 max_line_num_len,
2164 );
2165 buffer.puts(
2166 row_num - 1 + line - line_start,
2167 max_line_num_len + 1,
2168 "- ",
2169 Style::Removal,
2170 );
2171 buffer.puts(
2172 row_num - 1 + line - line_start,
2173 max_line_num_len + 3,
2174 &normalize_whitespace(&file_lines.file.get_line(line - 1).unwrap()),
2175 Style::Removal,
2176 );
2177 }
2178 row_num += line_end - line_start;
2179 }
2180 let mut unhighlighted_lines = Vec::new();
2181 let mut last_pos = 0;
2182 let mut is_item_attribute = false;
2183 for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
2184 last_pos = line_pos;
2185 debug!(%line_pos, %line, ?highlight_parts);
2186
2187 if highlight_parts.is_empty() {
2189 unhighlighted_lines.push((line_pos, line));
2190 continue;
2191 }
2192 if highlight_parts.len() == 1
2193 && line.trim().starts_with("#[")
2194 && line.trim().ends_with(']')
2195 {
2196 is_item_attribute = true;
2197 }
2198
2199 match unhighlighted_lines.len() {
2200 0 => (),
2201 n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
2206 self.draw_code_line(
2207 &mut buffer,
2208 &mut row_num,
2209 &[],
2210 p + line_start,
2211 l,
2212 show_code_change,
2213 max_line_num_len,
2214 &file_lines,
2215 is_multiline,
2216 )
2217 }),
2218 _ => {
2226 let last_line = unhighlighted_lines.pop();
2227 let first_line = unhighlighted_lines.drain(..).next();
2228
2229 if let Some((p, l)) = first_line {
2230 self.draw_code_line(
2231 &mut buffer,
2232 &mut row_num,
2233 &[],
2234 p + line_start,
2235 l,
2236 show_code_change,
2237 max_line_num_len,
2238 &file_lines,
2239 is_multiline,
2240 )
2241 }
2242
2243 let placeholder = self.margin();
2244 let padding = str_width(placeholder);
2245 buffer.puts(
2246 row_num,
2247 max_line_num_len.saturating_sub(padding),
2248 placeholder,
2249 Style::LineNumber,
2250 );
2251 row_num += 1;
2252
2253 if let Some((p, l)) = last_line {
2254 self.draw_code_line(
2255 &mut buffer,
2256 &mut row_num,
2257 &[],
2258 p + line_start,
2259 l,
2260 show_code_change,
2261 max_line_num_len,
2262 &file_lines,
2263 is_multiline,
2264 )
2265 }
2266 }
2267 }
2268
2269 self.draw_code_line(
2270 &mut buffer,
2271 &mut row_num,
2272 &highlight_parts,
2273 line_pos + line_start,
2274 line,
2275 show_code_change,
2276 max_line_num_len,
2277 &file_lines,
2278 is_multiline,
2279 )
2280 }
2281 if let DisplaySuggestion::Add = show_code_change
2282 && is_item_attribute
2283 {
2284 let file_lines = sm
2291 .span_to_lines(parts[0].span.shrink_to_hi())
2292 .expect("span_to_lines failed when emitting suggestion");
2293 let line_num = sm.lookup_char_pos(parts[0].span.lo()).line;
2294 if let Some(line) = file_lines.file.get_line(line_num - 1) {
2295 let line = normalize_whitespace(&line);
2296 self.draw_code_line(
2297 &mut buffer,
2298 &mut row_num,
2299 &[],
2300 line_num + last_pos + 1,
2301 &line,
2302 DisplaySuggestion::None,
2303 max_line_num_len,
2304 &file_lines,
2305 is_multiline,
2306 )
2307 }
2308 }
2309
2310 let mut offsets: Vec<(usize, isize)> = Vec::new();
2313 if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
2316 show_code_change
2317 {
2318 for part in parts {
2319 let snippet = if let Ok(snippet) = sm.span_to_snippet(part.span) {
2320 snippet
2321 } else {
2322 String::new()
2323 };
2324 let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
2325 let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
2326
2327 let is_whitespace_addition = part.snippet.trim().is_empty();
2330
2331 let start = if is_whitespace_addition {
2333 0
2334 } else {
2335 part.snippet.len().saturating_sub(part.snippet.trim_start().len())
2336 };
2337 let sub_len: usize = str_width(if is_whitespace_addition {
2340 &part.snippet
2341 } else {
2342 part.snippet.trim()
2343 });
2344
2345 let offset: isize = offsets
2346 .iter()
2347 .filter_map(
2348 |(start, v)| if span_start_pos < *start { None } else { Some(v) },
2349 )
2350 .sum();
2351 let underline_start = (span_start_pos + start) as isize + offset;
2352 let underline_end = (span_start_pos + start + sub_len) as isize + offset;
2353 let padding: usize = max_line_num_len + 3;
2354 for p in underline_start..underline_end {
2355 if let DisplaySuggestion::Underline = show_code_change
2356 && is_different(sm, &part.snippet, part.span)
2357 {
2358 buffer.putc(
2361 row_num,
2362 (padding as isize + p) as usize,
2363 if part.is_addition(sm) { '+' } else { self.diff() },
2364 Style::Addition,
2365 );
2366 }
2367 }
2368 if let DisplaySuggestion::Diff = show_code_change {
2369 let newlines = snippet.lines().count();
2400 if newlines > 0 && row_num > newlines {
2401 for (i, line) in snippet.lines().enumerate() {
2410 let line = normalize_whitespace(line);
2411 let row = (row_num - 2 - (newlines - i - 1)).max(2);
2412 let start = if i == 0 {
2418 (padding as isize + span_start_pos as isize) as usize
2419 } else {
2420 padding
2421 };
2422 let end = if i == 0 {
2423 (padding as isize
2424 + span_start_pos as isize
2425 + line.len() as isize)
2426 as usize
2427 } else if i == newlines - 1 {
2428 (padding as isize + span_end_pos as isize) as usize
2429 } else {
2430 (padding as isize + line.len() as isize) as usize
2431 };
2432 buffer.set_style_range(row, start, end, Style::Removal, true);
2433 }
2434 } else {
2435 buffer.set_style_range(
2437 row_num - 2,
2438 (padding as isize + span_start_pos as isize) as usize,
2439 (padding as isize + span_end_pos as isize) as usize,
2440 Style::Removal,
2441 true,
2442 );
2443 }
2444 }
2445
2446 let full_sub_len = str_width(&part.snippet) as isize;
2448
2449 let snippet_len = span_end_pos as isize - span_start_pos as isize;
2451 offsets.push((span_end_pos, full_sub_len - snippet_len));
2455 }
2456 row_num += 1;
2457 }
2458
2459 if lines.next().is_some() {
2461 let placeholder = self.margin();
2462 let padding = str_width(placeholder);
2463 buffer.puts(
2464 row_num,
2465 max_line_num_len.saturating_sub(padding),
2466 placeholder,
2467 Style::LineNumber,
2468 );
2469 } else {
2470 let row = match show_code_change {
2471 DisplaySuggestion::Diff
2472 | DisplaySuggestion::Add
2473 | DisplaySuggestion::Underline => row_num - 1,
2474 DisplaySuggestion::None => row_num,
2475 };
2476 if other_suggestions > 0 {
2477 self.draw_col_separator_no_space(&mut buffer, row, max_line_num_len + 1);
2478 } else {
2479 self.draw_col_separator_end(&mut buffer, row, max_line_num_len + 1);
2480 }
2481 row_num = row + 1;
2482 }
2483 }
2484 if other_suggestions > 0 {
2485 self.draw_note_separator(&mut buffer, row_num, max_line_num_len + 1, false);
2486 let msg = format!(
2487 "and {} other candidate{}",
2488 other_suggestions,
2489 pluralize!(other_suggestions)
2490 );
2491 buffer.append(row_num, &msg, Style::NoStyle);
2492 }
2493
2494 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2495 Ok(())
2496 }
2497
2498 #[instrument(level = "trace", skip(self, args, code, children, suggestions))]
2499 fn emit_messages_default(
2500 &mut self,
2501 level: &Level,
2502 messages: &[(DiagMessage, Style)],
2503 args: &FluentArgs<'_>,
2504 code: &Option<ErrCode>,
2505 span: &MultiSpan,
2506 children: &[Subdiag],
2507 suggestions: &[CodeSuggestion],
2508 ) {
2509 let max_line_num_len = if self.ui_testing {
2510 ANONYMIZED_LINE_NUM.len()
2511 } else {
2512 let n = self.get_max_line_num(span, children);
2513 num_decimal_digits(n)
2514 };
2515
2516 match self.emit_messages_default_inner(
2517 span,
2518 messages,
2519 args,
2520 code,
2521 level,
2522 max_line_num_len,
2523 false,
2524 !children.is_empty()
2525 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden),
2526 ) {
2527 Ok(code_window_status) => {
2528 if !children.is_empty()
2529 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
2530 {
2531 let mut buffer = StyledBuffer::new();
2532 if !self.short_message {
2533 if let Some(child) = children.iter().next()
2534 && child.span.primary_spans().is_empty()
2535 {
2536 self.draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
2538 } else if matches!(code_window_status, CodeWindowStatus::Open) {
2539 self.draw_col_separator_end(&mut buffer, 0, max_line_num_len + 1);
2541 }
2542 }
2543 if let Err(e) = emit_to_destination(
2544 &buffer.render(),
2545 level,
2546 &mut self.dst,
2547 self.short_message,
2548 ) {
2549 panic!("failed to emit error: {e}")
2550 }
2551 }
2552 if !self.short_message {
2553 for (i, child) in children.iter().enumerate() {
2554 assert!(child.level.can_be_subdiag());
2555 let span = &child.span;
2556 let should_close = match children.get(i + 1) {
2558 Some(c) => !c.span.primary_spans().is_empty(),
2559 None => i + 1 == children.len(),
2560 };
2561 if let Err(err) = self.emit_messages_default_inner(
2562 span,
2563 &child.messages,
2564 args,
2565 &None,
2566 &child.level,
2567 max_line_num_len,
2568 true,
2569 !should_close,
2570 ) {
2571 panic!("failed to emit error: {err}");
2572 }
2573 }
2574 for (i, sugg) in suggestions.iter().enumerate() {
2575 match sugg.style {
2576 SuggestionStyle::CompletelyHidden => {
2577 }
2579 SuggestionStyle::HideCodeAlways => {
2580 if let Err(e) = self.emit_messages_default_inner(
2581 &MultiSpan::new(),
2582 &[(sugg.msg.to_owned(), Style::HeaderMsg)],
2583 args,
2584 &None,
2585 &Level::Help,
2586 max_line_num_len,
2587 true,
2588 i + 1 != suggestions.len(),
2591 ) {
2592 panic!("failed to emit error: {e}");
2593 }
2594 }
2595 SuggestionStyle::HideCodeInline
2596 | SuggestionStyle::ShowCode
2597 | SuggestionStyle::ShowAlways => {
2598 if let Err(e) = self.emit_suggestion_default(
2599 span,
2600 sugg,
2601 args,
2602 &Level::Help,
2603 max_line_num_len,
2604 ) {
2605 panic!("failed to emit error: {e}");
2606 }
2607 }
2608 }
2609 }
2610 }
2611 }
2612 Err(e) => panic!("failed to emit error: {e}"),
2613 }
2614
2615 match writeln!(self.dst) {
2616 Err(e) => panic!("failed to emit error: {e}"),
2617 _ => {
2618 if let Err(e) = self.dst.flush() {
2619 panic!("failed to emit error: {e}")
2620 }
2621 }
2622 }
2623 }
2624
2625 fn draw_code_line(
2626 &self,
2627 buffer: &mut StyledBuffer,
2628 row_num: &mut usize,
2629 highlight_parts: &[SubstitutionHighlight],
2630 line_num: usize,
2631 line_to_add: &str,
2632 show_code_change: DisplaySuggestion,
2633 max_line_num_len: usize,
2634 file_lines: &FileLines,
2635 is_multiline: bool,
2636 ) {
2637 if let DisplaySuggestion::Diff = show_code_change {
2638 let lines_to_remove = file_lines.lines.iter().take(file_lines.lines.len() - 1);
2641 for (index, line_to_remove) in lines_to_remove.enumerate() {
2642 self.draw_line_num(buffer, line_num + index, *row_num - 1, max_line_num_len);
2643 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2644 let line = normalize_whitespace(
2645 &file_lines.file.get_line(line_to_remove.line_index).unwrap(),
2646 );
2647 buffer.puts(*row_num - 1, max_line_num_len + 3, &line, Style::NoStyle);
2648 *row_num += 1;
2649 }
2650 let last_line_index = file_lines.lines[file_lines.lines.len() - 1].line_index;
2657 let last_line = &file_lines.file.get_line(last_line_index).unwrap();
2658 if last_line != line_to_add {
2659 self.draw_line_num(
2660 buffer,
2661 line_num + file_lines.lines.len() - 1,
2662 *row_num - 1,
2663 max_line_num_len,
2664 );
2665 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2666 buffer.puts(
2667 *row_num - 1,
2668 max_line_num_len + 3,
2669 &normalize_whitespace(last_line),
2670 Style::NoStyle,
2671 );
2672 if !line_to_add.trim().is_empty() {
2673 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2687 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2688 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2689 } else {
2690 *row_num -= 1;
2691 }
2692 } else {
2693 *row_num -= 2;
2694 }
2695 } else if is_multiline {
2696 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2697 match &highlight_parts {
2698 [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
2699 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2700 }
2701 [] => {
2702 self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
2704 }
2705 _ => {
2706 let diff = self.diff();
2707 buffer.puts(
2708 *row_num,
2709 max_line_num_len + 1,
2710 &format!("{diff} "),
2711 Style::Addition,
2712 );
2713 }
2714 }
2715 buffer.puts(
2721 *row_num,
2722 max_line_num_len + 3,
2723 &normalize_whitespace(line_to_add),
2724 Style::NoStyle,
2725 );
2726 } else if let DisplaySuggestion::Add = show_code_change {
2727 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2728 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2729 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2730 } else {
2731 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2732 self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2733 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2734 }
2735
2736 for &SubstitutionHighlight { start, end } in highlight_parts {
2738 if start != end {
2740 let tabs: usize = line_to_add
2742 .chars()
2743 .take(start)
2744 .map(|ch| match ch {
2745 '\t' => 3,
2746 _ => 0,
2747 })
2748 .sum();
2749 buffer.set_style_range(
2750 *row_num,
2751 max_line_num_len + 3 + start + tabs,
2752 max_line_num_len + 3 + end + tabs,
2753 Style::Addition,
2754 true,
2755 );
2756 }
2757 }
2758 *row_num += 1;
2759 }
2760
2761 fn underline(&self, is_primary: bool) -> UnderlineParts {
2762 match (self.theme, is_primary) {
2787 (OutputTheme::Ascii, true) => UnderlineParts {
2788 style: Style::UnderlinePrimary,
2789 underline: '^',
2790 label_start: '^',
2791 vertical_text_line: '|',
2792 multiline_vertical: '|',
2793 multiline_horizontal: '_',
2794 multiline_whole_line: '/',
2795 multiline_start_down: '^',
2796 bottom_right: '|',
2797 top_left: ' ',
2798 top_right_flat: '^',
2799 bottom_left: '|',
2800 multiline_end_up: '^',
2801 multiline_end_same_line: '^',
2802 multiline_bottom_right_with_text: '|',
2803 },
2804 (OutputTheme::Ascii, false) => UnderlineParts {
2805 style: Style::UnderlineSecondary,
2806 underline: '-',
2807 label_start: '-',
2808 vertical_text_line: '|',
2809 multiline_vertical: '|',
2810 multiline_horizontal: '_',
2811 multiline_whole_line: '/',
2812 multiline_start_down: '-',
2813 bottom_right: '|',
2814 top_left: ' ',
2815 top_right_flat: '-',
2816 bottom_left: '|',
2817 multiline_end_up: '-',
2818 multiline_end_same_line: '-',
2819 multiline_bottom_right_with_text: '|',
2820 },
2821 (OutputTheme::Unicode, true) => UnderlineParts {
2822 style: Style::UnderlinePrimary,
2823 underline: '━',
2824 label_start: '┯',
2825 vertical_text_line: '│',
2826 multiline_vertical: '┃',
2827 multiline_horizontal: '━',
2828 multiline_whole_line: '┏',
2829 multiline_start_down: '╿',
2830 bottom_right: '┙',
2831 top_left: '┏',
2832 top_right_flat: '┛',
2833 bottom_left: '┗',
2834 multiline_end_up: '╿',
2835 multiline_end_same_line: '┛',
2836 multiline_bottom_right_with_text: '┥',
2837 },
2838 (OutputTheme::Unicode, false) => UnderlineParts {
2839 style: Style::UnderlineSecondary,
2840 underline: '─',
2841 label_start: '┬',
2842 vertical_text_line: '│',
2843 multiline_vertical: '│',
2844 multiline_horizontal: '─',
2845 multiline_whole_line: '┌',
2846 multiline_start_down: '│',
2847 bottom_right: '┘',
2848 top_left: '┌',
2849 top_right_flat: '┘',
2850 bottom_left: '└',
2851 multiline_end_up: '│',
2852 multiline_end_same_line: '┘',
2853 multiline_bottom_right_with_text: '┤',
2854 },
2855 }
2856 }
2857
2858 fn col_separator(&self) -> char {
2859 match self.theme {
2860 OutputTheme::Ascii => '|',
2861 OutputTheme::Unicode => '│',
2862 }
2863 }
2864
2865 fn note_separator(&self, is_cont: bool) -> &'static str {
2866 match self.theme {
2867 OutputTheme::Ascii => "= ",
2868 OutputTheme::Unicode if is_cont => "├ ",
2869 OutputTheme::Unicode => "╰ ",
2870 }
2871 }
2872
2873 fn multi_suggestion_separator(&self) -> &'static str {
2874 match self.theme {
2875 OutputTheme::Ascii => "|",
2876 OutputTheme::Unicode => "├╴",
2877 }
2878 }
2879
2880 fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2881 let chr = self.col_separator();
2882 buffer.puts(line, col, &format!("{chr} "), Style::LineNumber);
2883 }
2884
2885 fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2886 let chr = self.col_separator();
2887 self.draw_col_separator_no_space_with_style(buffer, chr, line, col, Style::LineNumber);
2888 }
2889
2890 fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2891 match self.theme {
2892 OutputTheme::Ascii => {
2893 self.draw_col_separator_no_space_with_style(
2894 buffer,
2895 '|',
2896 line,
2897 col,
2898 Style::LineNumber,
2899 );
2900 }
2901 OutputTheme::Unicode => {
2902 self.draw_col_separator_no_space_with_style(
2903 buffer,
2904 '╭',
2905 line,
2906 col,
2907 Style::LineNumber,
2908 );
2909 self.draw_col_separator_no_space_with_style(
2910 buffer,
2911 '╴',
2912 line,
2913 col + 1,
2914 Style::LineNumber,
2915 );
2916 }
2917 }
2918 }
2919
2920 fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2921 match self.theme {
2922 OutputTheme::Ascii => {
2923 self.draw_col_separator_no_space_with_style(
2924 buffer,
2925 '|',
2926 line,
2927 col,
2928 Style::LineNumber,
2929 );
2930 }
2931 OutputTheme::Unicode => {
2932 self.draw_col_separator_no_space_with_style(
2933 buffer,
2934 '╰',
2935 line,
2936 col,
2937 Style::LineNumber,
2938 );
2939 self.draw_col_separator_no_space_with_style(
2940 buffer,
2941 '╴',
2942 line,
2943 col + 1,
2944 Style::LineNumber,
2945 );
2946 }
2947 }
2948 }
2949
2950 fn draw_col_separator_no_space_with_style(
2951 &self,
2952 buffer: &mut StyledBuffer,
2953 chr: char,
2954 line: usize,
2955 col: usize,
2956 style: Style,
2957 ) {
2958 buffer.putc(line, col, chr, style);
2959 }
2960
2961 fn draw_range(
2962 &self,
2963 buffer: &mut StyledBuffer,
2964 symbol: char,
2965 line: usize,
2966 col_from: usize,
2967 col_to: usize,
2968 style: Style,
2969 ) {
2970 for col in col_from..col_to {
2971 buffer.putc(line, col, symbol, style);
2972 }
2973 }
2974
2975 fn draw_note_separator(
2976 &self,
2977 buffer: &mut StyledBuffer,
2978 line: usize,
2979 col: usize,
2980 is_cont: bool,
2981 ) {
2982 let chr = self.note_separator(is_cont);
2983 buffer.puts(line, col, chr, Style::LineNumber);
2984 }
2985
2986 fn draw_multiline_line(
2987 &self,
2988 buffer: &mut StyledBuffer,
2989 line: usize,
2990 offset: usize,
2991 depth: usize,
2992 style: Style,
2993 ) {
2994 let chr = match (style, self.theme) {
2995 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Ascii) => '|',
2996 (_, OutputTheme::Ascii) => '|',
2997 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Unicode) => '┃',
2998 (_, OutputTheme::Unicode) => '│',
2999 };
3000 buffer.putc(line, offset + depth - 1, chr, style);
3001 }
3002
3003 fn file_start(&self) -> &'static str {
3004 match self.theme {
3005 OutputTheme::Ascii => "--> ",
3006 OutputTheme::Unicode => " ╭▸ ",
3007 }
3008 }
3009
3010 fn secondary_file_start(&self) -> &'static str {
3011 match self.theme {
3012 OutputTheme::Ascii => "::: ",
3013 OutputTheme::Unicode => " ⸬ ",
3014 }
3015 }
3016
3017 fn diff(&self) -> char {
3018 match self.theme {
3019 OutputTheme::Ascii => '~',
3020 OutputTheme::Unicode => '±',
3021 }
3022 }
3023
3024 fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
3025 let (column, dots) = match self.theme {
3026 OutputTheme::Ascii => (0, "..."),
3027 OutputTheme::Unicode => (col - 2, "‡"),
3028 };
3029 buffer.puts(line, column, dots, Style::LineNumber);
3030 }
3031
3032 fn margin(&self) -> &'static str {
3033 match self.theme {
3034 OutputTheme::Ascii => "...",
3035 OutputTheme::Unicode => "…",
3036 }
3037 }
3038
3039 fn draw_line_num(
3040 &self,
3041 buffer: &mut StyledBuffer,
3042 line_num: usize,
3043 line_offset: usize,
3044 max_line_num_len: usize,
3045 ) {
3046 let line_num = self.maybe_anonymized(line_num);
3047 buffer.puts(
3048 line_offset,
3049 max_line_num_len.saturating_sub(str_width(&line_num)),
3050 &line_num,
3051 Style::LineNumber,
3052 );
3053 }
3054}
3055
3056#[derive(Debug, Clone, Copy)]
3057struct UnderlineParts {
3058 style: Style,
3059 underline: char,
3060 label_start: char,
3061 vertical_text_line: char,
3062 multiline_vertical: char,
3063 multiline_horizontal: char,
3064 multiline_whole_line: char,
3065 multiline_start_down: char,
3066 bottom_right: char,
3067 top_left: char,
3068 top_right_flat: char,
3069 bottom_left: char,
3070 multiline_end_up: char,
3071 multiline_end_same_line: char,
3072 multiline_bottom_right_with_text: char,
3073}
3074
3075#[derive(Clone, Copy, Debug)]
3076enum DisplaySuggestion {
3077 Underline,
3078 Diff,
3079 None,
3080 Add,
3081}
3082
3083#[derive(Clone, Copy, Debug)]
3084enum CodeWindowStatus {
3085 Closed,
3086 Open,
3087}
3088
3089impl FileWithAnnotatedLines {
3090 pub(crate) fn collect_annotations(
3093 emitter: &dyn Emitter,
3094 args: &FluentArgs<'_>,
3095 msp: &MultiSpan,
3096 ) -> Vec<FileWithAnnotatedLines> {
3097 fn add_annotation_to_file(
3098 file_vec: &mut Vec<FileWithAnnotatedLines>,
3099 file: Arc<SourceFile>,
3100 line_index: usize,
3101 ann: Annotation,
3102 ) {
3103 for slot in file_vec.iter_mut() {
3104 if slot.file.name == file.name {
3106 for line_slot in &mut slot.lines {
3108 if line_slot.line_index == line_index {
3109 line_slot.annotations.push(ann);
3110 return;
3111 }
3112 }
3113 slot.lines.push(Line { line_index, annotations: vec![ann] });
3115 slot.lines.sort();
3116 return;
3117 }
3118 }
3119 file_vec.push(FileWithAnnotatedLines {
3121 file,
3122 lines: vec![Line { line_index, annotations: vec![ann] }],
3123 multiline_depth: 0,
3124 });
3125 }
3126 let mut output = vec![];
3127 let mut multiline_annotations = vec![];
3128
3129 if let Some(sm) = emitter.source_map() {
3130 for SpanLabel { span, is_primary, label } in msp.span_labels() {
3131 let span = match (span.is_dummy(), msp.primary_span()) {
3134 (_, None) | (false, _) => span,
3135 (true, Some(span)) => span,
3136 };
3137
3138 let lo = sm.lookup_char_pos(span.lo());
3139 let mut hi = sm.lookup_char_pos(span.hi());
3140
3141 if lo.col_display == hi.col_display && lo.line == hi.line {
3148 hi.col_display += 1;
3149 }
3150
3151 let label = label.as_ref().map(|m| {
3152 normalize_whitespace(
3153 &emitter
3154 .translator()
3155 .translate_message(m, args)
3156 .map_err(Report::new)
3157 .unwrap(),
3158 )
3159 });
3160
3161 if lo.line != hi.line {
3162 let ml = MultilineAnnotation {
3163 depth: 1,
3164 line_start: lo.line,
3165 line_end: hi.line,
3166 start_col: AnnotationColumn::from_loc(&lo),
3167 end_col: AnnotationColumn::from_loc(&hi),
3168 is_primary,
3169 label,
3170 overlaps_exactly: false,
3171 };
3172 multiline_annotations.push((lo.file, ml));
3173 } else {
3174 let ann = Annotation {
3175 start_col: AnnotationColumn::from_loc(&lo),
3176 end_col: AnnotationColumn::from_loc(&hi),
3177 is_primary,
3178 label,
3179 annotation_type: AnnotationType::Singleline,
3180 };
3181 add_annotation_to_file(&mut output, lo.file, lo.line, ann);
3182 };
3183 }
3184 }
3185
3186 multiline_annotations.sort_by_key(|(_, ml)| (ml.line_start, usize::MAX - ml.line_end));
3188 for (_, ann) in multiline_annotations.clone() {
3189 for (_, a) in multiline_annotations.iter_mut() {
3190 if !(ann.same_span(a))
3193 && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
3194 {
3195 a.increase_depth();
3196 } else if ann.same_span(a) && &ann != a {
3197 a.overlaps_exactly = true;
3198 } else {
3199 break;
3200 }
3201 }
3202 }
3203
3204 let mut max_depth = 0; for (_, ann) in &multiline_annotations {
3206 max_depth = max(max_depth, ann.depth);
3207 }
3208 for (_, a) in multiline_annotations.iter_mut() {
3210 a.depth = max_depth - a.depth + 1;
3211 }
3212 for (file, ann) in multiline_annotations {
3213 let mut end_ann = ann.as_end();
3214 if !ann.overlaps_exactly {
3215 add_annotation_to_file(
3238 &mut output,
3239 Arc::clone(&file),
3240 ann.line_start,
3241 ann.as_start(),
3242 );
3243 let middle = min(ann.line_start + 4, ann.line_end);
3248 let filter = |s: &str| {
3252 let s = s.trim();
3253 !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
3255 && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
3257 };
3258 let until = (ann.line_start..middle)
3259 .rev()
3260 .filter_map(|line| file.get_line(line - 1).map(|s| (line + 1, s)))
3261 .find(|(_, s)| filter(s))
3262 .map(|(line, _)| line)
3263 .unwrap_or(ann.line_start);
3264 for line in ann.line_start + 1..until {
3265 add_annotation_to_file(&mut output, Arc::clone(&file), line, ann.as_line());
3267 }
3268 let line_end = ann.line_end - 1;
3269 let end_is_empty = file.get_line(line_end - 1).is_some_and(|s| !filter(&s));
3270 if middle < line_end && !end_is_empty {
3271 add_annotation_to_file(&mut output, Arc::clone(&file), line_end, ann.as_line());
3272 }
3273 } else {
3274 end_ann.annotation_type = AnnotationType::Singleline;
3275 }
3276 add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
3277 }
3278 for file_vec in output.iter_mut() {
3279 file_vec.multiline_depth = max_depth;
3280 }
3281 output
3282 }
3283}
3284
3285fn num_decimal_digits(num: usize) -> usize {
3290 #[cfg(target_pointer_width = "64")]
3291 const MAX_DIGITS: usize = 20;
3292
3293 #[cfg(target_pointer_width = "32")]
3294 const MAX_DIGITS: usize = 10;
3295
3296 #[cfg(target_pointer_width = "16")]
3297 const MAX_DIGITS: usize = 5;
3298
3299 let mut lim = 10;
3300 for num_digits in 1..MAX_DIGITS {
3301 if num < lim {
3302 return num_digits;
3303 }
3304 lim = lim.wrapping_mul(10);
3305 }
3306 MAX_DIGITS
3307}
3308
3309const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
3312 ('\0', "␀"),
3316 ('\u{0001}', "␁"),
3317 ('\u{0002}', "␂"),
3318 ('\u{0003}', "␃"),
3319 ('\u{0004}', "␄"),
3320 ('\u{0005}', "␅"),
3321 ('\u{0006}', "␆"),
3322 ('\u{0007}', "␇"),
3323 ('\u{0008}', "␈"),
3324 ('\t', " "), ('\u{000b}', "␋"),
3326 ('\u{000c}', "␌"),
3327 ('\u{000d}', "␍"),
3328 ('\u{000e}', "␎"),
3329 ('\u{000f}', "␏"),
3330 ('\u{0010}', "␐"),
3331 ('\u{0011}', "␑"),
3332 ('\u{0012}', "␒"),
3333 ('\u{0013}', "␓"),
3334 ('\u{0014}', "␔"),
3335 ('\u{0015}', "␕"),
3336 ('\u{0016}', "␖"),
3337 ('\u{0017}', "␗"),
3338 ('\u{0018}', "␘"),
3339 ('\u{0019}', "␙"),
3340 ('\u{001a}', "␚"),
3341 ('\u{001b}', "␛"),
3342 ('\u{001c}', "␜"),
3343 ('\u{001d}', "␝"),
3344 ('\u{001e}', "␞"),
3345 ('\u{001f}', "␟"),
3346 ('\u{007f}', "␡"),
3347 ('\u{200d}', ""), ('\u{202a}', "�"), ('\u{202b}', "�"), ('\u{202c}', "�"), ('\u{202d}', "�"),
3352 ('\u{202e}', "�"),
3353 ('\u{2066}', "�"),
3354 ('\u{2067}', "�"),
3355 ('\u{2068}', "�"),
3356 ('\u{2069}', "�"),
3357];
3358
3359pub(crate) fn normalize_whitespace(s: &str) -> String {
3360 const {
3361 let mut i = 1;
3362 while i < OUTPUT_REPLACEMENTS.len() {
3363 assert!(
3364 OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
3365 "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
3366 and must contain no duplicate entries"
3367 );
3368 i += 1;
3369 }
3370 }
3371 s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
3375 match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
3376 Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
3377 _ => s.push(c),
3378 }
3379 s
3380 })
3381}
3382
3383fn num_overlap(
3384 a_start: usize,
3385 a_end: usize,
3386 b_start: usize,
3387 b_end: usize,
3388 inclusive: bool,
3389) -> bool {
3390 let extra = if inclusive { 1 } else { 0 };
3391 (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
3392}
3393
3394fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
3395 num_overlap(
3396 a1.start_col.display,
3397 a1.end_col.display + padding,
3398 a2.start_col.display,
3399 a2.end_col.display,
3400 false,
3401 )
3402}
3403
3404pub(crate) fn emit_to_destination(
3405 rendered_buffer: &[Vec<StyledString>],
3406 lvl: &Level,
3407 dst: &mut Destination,
3408 short_message: bool,
3409) -> io::Result<()> {
3410 use crate::lock;
3411
3412 let _buffer_lock = lock::acquire_global_lock("rustc_errors");
3425 for (pos, line) in rendered_buffer.iter().enumerate() {
3426 for part in line {
3427 let style = part.style.anstyle(*lvl);
3428 write!(dst, "{style}{}{style:#}", part.text)?;
3429 }
3430 if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
3431 writeln!(dst)?;
3432 }
3433 }
3434 dst.flush()?;
3435 Ok(())
3436}
3437
3438pub type Destination = AutoStream<Box<dyn Write + Send>>;
3439
3440struct Buffy {
3441 buffer_writer: std::io::Stderr,
3442 buffer: Vec<u8>,
3443}
3444
3445impl Write for Buffy {
3446 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3447 self.buffer.write(buf)
3448 }
3449
3450 fn flush(&mut self) -> io::Result<()> {
3451 self.buffer_writer.write_all(&self.buffer)?;
3452 self.buffer.clear();
3453 Ok(())
3454 }
3455}
3456
3457impl Drop for Buffy {
3458 fn drop(&mut self) {
3459 if !self.buffer.is_empty() {
3460 self.flush().unwrap();
3461 panic!("buffers need to be flushed in order to print their contents");
3462 }
3463 }
3464}
3465
3466pub fn stderr_destination(color: ColorConfig) -> Destination {
3467 let buffer_writer = std::io::stderr();
3468 let choice = color.to_color_choice();
3469 let choice = if matches!(choice, ColorChoice::Auto) {
3472 AutoStream::choice(&buffer_writer)
3473 } else {
3474 choice
3475 };
3476 if cfg!(windows) {
3483 AutoStream::new(Box::new(buffer_writer), choice)
3484 } else {
3485 let buffer = Vec::new();
3486 AutoStream::new(Box::new(Buffy { buffer_writer, buffer }), choice)
3487 }
3488}
3489
3490const BRIGHT_BLUE: anstyle::Style = if cfg!(windows) {
3494 AnsiColor::BrightCyan.on_default()
3495} else {
3496 AnsiColor::BrightBlue.on_default()
3497};
3498
3499impl Style {
3500 pub(crate) fn anstyle(&self, lvl: Level) -> anstyle::Style {
3501 match self {
3502 Style::Addition => AnsiColor::BrightGreen.on_default(),
3503 Style::Removal => AnsiColor::BrightRed.on_default(),
3504 Style::LineAndColumn => anstyle::Style::new(),
3505 Style::LineNumber => BRIGHT_BLUE.effects(Effects::BOLD),
3506 Style::Quotation => anstyle::Style::new(),
3507 Style::MainHeaderMsg => if cfg!(windows) {
3508 AnsiColor::BrightWhite.on_default()
3509 } else {
3510 anstyle::Style::new()
3511 }
3512 .effects(Effects::BOLD),
3513 Style::UnderlinePrimary | Style::LabelPrimary => lvl.color().effects(Effects::BOLD),
3514 Style::UnderlineSecondary | Style::LabelSecondary => BRIGHT_BLUE.effects(Effects::BOLD),
3515 Style::HeaderMsg | Style::NoStyle => anstyle::Style::new(),
3516 Style::Level(lvl) => lvl.color().effects(Effects::BOLD),
3517 Style::Highlight => AnsiColor::Magenta.on_default().effects(Effects::BOLD),
3518 }
3519 }
3520}
3521
3522pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3524 let found = match sm.span_to_snippet(sp) {
3525 Ok(snippet) => snippet,
3526 Err(e) => {
3527 warn!(error = ?e, "Invalid span {:?}", sp);
3528 return true;
3529 }
3530 };
3531 found != suggested
3532}
3533
3534pub fn detect_confusion_type(sm: &SourceMap, suggested: &str, sp: Span) -> ConfusionType {
3536 let found = match sm.span_to_snippet(sp) {
3537 Ok(snippet) => snippet,
3538 Err(e) => {
3539 warn!(error = ?e, "Invalid span {:?}", sp);
3540 return ConfusionType::None;
3541 }
3542 };
3543
3544 let mut has_case_confusion = false;
3545 let mut has_digit_letter_confusion = false;
3546
3547 if found.len() == suggested.len() {
3548 let mut has_case_diff = false;
3549 let mut has_digit_letter_confusable = false;
3550 let mut has_other_diff = false;
3551
3552 let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
3553
3554 let digit_letter_confusables = [('0', 'O'), ('1', 'l'), ('5', 'S'), ('8', 'B'), ('9', 'g')];
3555
3556 for (f, s) in iter::zip(found.chars(), suggested.chars()) {
3557 if f != s {
3558 if f.eq_ignore_ascii_case(&s) {
3559 if ascii_confusables.contains(&f) || ascii_confusables.contains(&s) {
3561 has_case_diff = true;
3562 } else {
3563 has_other_diff = true;
3564 }
3565 } else if digit_letter_confusables.contains(&(f, s))
3566 || digit_letter_confusables.contains(&(s, f))
3567 {
3568 has_digit_letter_confusable = true;
3570 } else {
3571 has_other_diff = true;
3572 }
3573 }
3574 }
3575
3576 if has_case_diff && !has_other_diff && found != suggested {
3578 has_case_confusion = true;
3579 }
3580 if has_digit_letter_confusable && !has_other_diff && found != suggested {
3581 has_digit_letter_confusion = true;
3582 }
3583 }
3584
3585 match (has_case_confusion, has_digit_letter_confusion) {
3586 (true, true) => ConfusionType::Both,
3587 (true, false) => ConfusionType::Case,
3588 (false, true) => ConfusionType::DigitLetter,
3589 (false, false) => ConfusionType::None,
3590 }
3591}
3592
3593#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3595pub enum ConfusionType {
3596 None,
3598 Case,
3600 DigitLetter,
3602 Both,
3604}
3605
3606impl ConfusionType {
3607 pub fn label_text(&self) -> &'static str {
3609 match self {
3610 ConfusionType::None => "",
3611 ConfusionType::Case => " (notice the capitalization)",
3612 ConfusionType::DigitLetter => " (notice the digit/letter confusion)",
3613 ConfusionType::Both => " (notice the capitalization and digit/letter confusion)",
3614 }
3615 }
3616
3617 pub fn combine(self, other: ConfusionType) -> ConfusionType {
3621 match (self, other) {
3622 (ConfusionType::None, other) => other,
3623 (this, ConfusionType::None) => this,
3624 (ConfusionType::Both, _) | (_, ConfusionType::Both) => ConfusionType::Both,
3625 (ConfusionType::Case, ConfusionType::DigitLetter)
3626 | (ConfusionType::DigitLetter, ConfusionType::Case) => ConfusionType::Both,
3627 (ConfusionType::Case, ConfusionType::Case) => ConfusionType::Case,
3628 (ConfusionType::DigitLetter, ConfusionType::DigitLetter) => ConfusionType::DigitLetter,
3629 }
3630 }
3631
3632 pub fn has_confusion(&self) -> bool {
3634 *self != ConfusionType::None
3635 }
3636}
3637
3638pub(crate) fn should_show_source_code(
3639 ignored_directories: &[String],
3640 sm: &SourceMap,
3641 file: &SourceFile,
3642) -> bool {
3643 if !sm.ensure_source_file_source_present(file) {
3644 return false;
3645 }
3646
3647 let FileName::Real(name) = &file.name else { return true };
3648 name.local_path()
3649 .map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
3650 .unwrap_or(true)
3651}