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::{FxHashMap, FxIndexMap, FxIndexSet};
21use rustc_data_structures::sync::{DynSend, IntoDynSyncSend};
22use rustc_error_messages::{FluentArgs, SpanLabel};
23use rustc_lexer;
24use rustc_lint_defs::pluralize;
25use rustc_span::hygiene::{ExpnKind, MacroKind};
26use rustc_span::source_map::SourceMap;
27use rustc_span::{FileLines, FileName, SourceFile, Span, char_width, str_width};
28use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
29use tracing::{debug, instrument, trace, warn};
30
31use crate::registry::Registry;
32use crate::snippet::{
33 Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString,
34};
35use crate::styled_buffer::StyledBuffer;
36use crate::timings::TimingRecord;
37use crate::translation::{Translator, to_fluent_args};
38use crate::{
39 CodeSuggestion, DiagInner, DiagMessage, ErrCode, Level, MultiSpan, Subdiag,
40 SubstitutionHighlight, SuggestionStyle, TerminalUrl,
41};
42
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 format!(
266 "help: {}{}: `{}`",
267 msg,
268 if self
269 .source_map()
270 .is_some_and(|sm| is_case_difference(sm, snippet, part.span,))
271 {
272 " (notice the capitalization)"
273 } else {
274 ""
275 },
276 snippet,
277 )
278 };
279 primary_span.push_span_label(part.span, msg);
280
281 suggestions.clear();
283 } else {
284 }
289 } else {
290 }
292 }
293
294 fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
295 &self,
296 span: &mut MultiSpan,
297 children: &mut Vec<Subdiag>,
298 level: &Level,
299 backtrace: bool,
300 ) {
301 let has_macro_spans: Vec<_> = iter::once(&*span)
304 .chain(children.iter().map(|child| &child.span))
305 .flat_map(|span| span.primary_spans())
306 .flat_map(|sp| sp.macro_backtrace())
307 .filter_map(|expn_data| {
308 match expn_data.kind {
309 ExpnKind::Root => None,
310
311 ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
314
315 ExpnKind::Macro(macro_kind, name) => {
316 Some((macro_kind, name, expn_data.hide_backtrace))
317 }
318 }
319 })
320 .collect();
321
322 if !backtrace {
323 self.fix_multispans_in_extern_macros(span, children);
324 }
325
326 self.render_multispans_macro_backtrace(span, children, backtrace);
327
328 if !backtrace {
329 if let Some((macro_kind, name, _)) = has_macro_spans.first()
332 && let Some((_, _, false)) = has_macro_spans.last()
333 {
334 let and_then = if let Some((macro_kind, last_name, _)) = has_macro_spans.last()
336 && last_name != name
337 {
338 let descr = macro_kind.descr();
339 format!(" which comes from the expansion of the {descr} `{last_name}`")
340 } else {
341 "".to_string()
342 };
343
344 let descr = macro_kind.descr();
345 let msg = format!(
346 "this {level} originates in the {descr} `{name}`{and_then} \
347 (in Nightly builds, run with -Z macro-backtrace for more info)",
348 );
349
350 children.push(Subdiag {
351 level: Level::Note,
352 messages: vec![(DiagMessage::from(msg), Style::NoStyle)],
353 span: MultiSpan::new(),
354 });
355 }
356 }
357 }
358
359 fn render_multispans_macro_backtrace(
360 &self,
361 span: &mut MultiSpan,
362 children: &mut Vec<Subdiag>,
363 backtrace: bool,
364 ) {
365 for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
366 self.render_multispan_macro_backtrace(span, backtrace);
367 }
368 }
369
370 fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
371 let mut new_labels = FxIndexSet::default();
372
373 for &sp in span.primary_spans() {
374 if sp.is_dummy() {
375 continue;
376 }
377
378 let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
382 for (i, trace) in macro_backtrace.iter().rev().enumerate() {
383 if trace.def_site.is_dummy() {
384 continue;
385 }
386
387 if always_backtrace {
388 new_labels.insert((
389 trace.def_site,
390 format!(
391 "in this expansion of `{}`{}",
392 trace.kind.descr(),
393 if macro_backtrace.len() > 1 {
394 format!(" (#{})", i + 1)
397 } else {
398 String::new()
399 },
400 ),
401 ));
402 }
403
404 let redundant_span = trace.call_site.contains(sp);
416
417 if !redundant_span || always_backtrace {
418 let msg: Cow<'static, _> = match trace.kind {
419 ExpnKind::Macro(MacroKind::Attr, _) => {
420 "this procedural macro expansion".into()
421 }
422 ExpnKind::Macro(MacroKind::Derive, _) => {
423 "this derive macro expansion".into()
424 }
425 ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
426 ExpnKind::Root => "the crate root".into(),
427 ExpnKind::AstPass(kind) => kind.descr().into(),
428 ExpnKind::Desugaring(kind) => {
429 format!("this {} desugaring", kind.descr()).into()
430 }
431 };
432 new_labels.insert((
433 trace.call_site,
434 format!(
435 "in {}{}",
436 msg,
437 if macro_backtrace.len() > 1 && always_backtrace {
438 format!(" (#{})", i + 1)
441 } else {
442 String::new()
443 },
444 ),
445 ));
446 }
447 if !always_backtrace {
448 break;
449 }
450 }
451 }
452
453 for (label_span, label_text) in new_labels {
454 span.push_span_label(label_span, label_text);
455 }
456 }
457
458 fn fix_multispans_in_extern_macros(&self, span: &mut MultiSpan, children: &mut Vec<Subdiag>) {
462 debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
463 self.fix_multispan_in_extern_macros(span);
464 for child in children.iter_mut() {
465 self.fix_multispan_in_extern_macros(&mut child.span);
466 }
467 debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
468 }
469
470 fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) {
474 let Some(source_map) = self.source_map() else { return };
475 let replacements: Vec<(Span, Span)> = span
477 .primary_spans()
478 .iter()
479 .copied()
480 .chain(span.span_labels().iter().map(|sp_label| sp_label.span))
481 .filter_map(|sp| {
482 if !sp.is_dummy() && source_map.is_imported(sp) {
483 let maybe_callsite = sp.source_callsite();
484 if sp != maybe_callsite {
485 return Some((sp, maybe_callsite));
486 }
487 }
488 None
489 })
490 .collect();
491
492 for (from, to) in replacements {
494 span.replace(from, to);
495 }
496 }
497}
498
499impl Emitter for HumanEmitter {
500 fn source_map(&self) -> Option<&SourceMap> {
501 self.sm.as_deref()
502 }
503
504 fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
505 let fluent_args = to_fluent_args(diag.args.iter());
506
507 if self.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() {
508 diag.children.insert(0, diag.emitted_at_sub_diag());
509 }
510
511 let mut suggestions = diag.suggestions.unwrap_tag();
512 self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
513
514 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
515 &mut diag.span,
516 &mut diag.children,
517 &diag.level,
518 self.macro_backtrace,
519 );
520
521 self.emit_messages_default(
522 &diag.level,
523 &diag.messages,
524 &fluent_args,
525 &diag.code,
526 &diag.span,
527 &diag.children,
528 &suggestions,
529 );
530 }
531
532 fn should_show_explain(&self) -> bool {
533 !self.short_message
534 }
535
536 fn supports_color(&self) -> bool {
537 self.dst.supports_color()
538 }
539
540 fn translator(&self) -> &Translator {
541 &self.translator
542 }
543}
544
545pub struct FatalOnlyEmitter {
549 pub fatal_emitter: Box<dyn Emitter + DynSend>,
550 pub fatal_note: Option<String>,
551}
552
553impl Emitter for FatalOnlyEmitter {
554 fn source_map(&self) -> Option<&SourceMap> {
555 None
556 }
557
558 fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
559 if diag.level == Level::Fatal {
560 if let Some(fatal_note) = &self.fatal_note {
561 diag.sub(Level::Note, fatal_note.clone(), MultiSpan::new());
562 }
563 self.fatal_emitter.emit_diagnostic(diag, registry);
564 }
565 }
566
567 fn translator(&self) -> &Translator {
568 self.fatal_emitter.translator()
569 }
570}
571
572pub struct SilentEmitter {
573 pub translator: Translator,
574}
575
576impl Emitter for SilentEmitter {
577 fn source_map(&self) -> Option<&SourceMap> {
578 None
579 }
580
581 fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {}
582
583 fn translator(&self) -> &Translator {
584 &self.translator
585 }
586}
587
588pub const MAX_SUGGESTIONS: usize = 4;
592
593#[derive(Clone, Copy, Debug, PartialEq, Eq)]
594pub enum ColorConfig {
595 Auto,
596 Always,
597 Never,
598}
599
600impl ColorConfig {
601 pub fn to_color_choice(self) -> ColorChoice {
602 match self {
603 ColorConfig::Always => {
604 if io::stderr().is_terminal() {
605 ColorChoice::Always
606 } else {
607 ColorChoice::AlwaysAnsi
608 }
609 }
610 ColorConfig::Never => ColorChoice::Never,
611 ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto,
612 ColorConfig::Auto => ColorChoice::Never,
613 }
614 }
615}
616
617#[derive(Debug, Clone, Copy, PartialEq, Eq)]
618pub enum OutputTheme {
619 Ascii,
620 Unicode,
621}
622
623#[derive(Setters)]
625pub struct HumanEmitter {
626 #[setters(skip)]
627 dst: IntoDynSyncSend<Destination>,
628 sm: Option<Arc<SourceMap>>,
629 #[setters(skip)]
630 translator: Translator,
631 short_message: bool,
632 ui_testing: bool,
633 ignored_directories_in_source_blocks: Vec<String>,
634 diagnostic_width: Option<usize>,
635
636 macro_backtrace: bool,
637 track_diagnostics: bool,
638 terminal_url: TerminalUrl,
639 theme: OutputTheme,
640}
641
642#[derive(Debug)]
643pub(crate) struct FileWithAnnotatedLines {
644 pub(crate) file: Arc<SourceFile>,
645 pub(crate) lines: Vec<Line>,
646 multiline_depth: usize,
647}
648
649impl HumanEmitter {
650 pub fn new(dst: Destination, translator: Translator) -> HumanEmitter {
651 HumanEmitter {
652 dst: IntoDynSyncSend(dst),
653 sm: None,
654 translator,
655 short_message: false,
656 ui_testing: false,
657 ignored_directories_in_source_blocks: Vec::new(),
658 diagnostic_width: None,
659 macro_backtrace: false,
660 track_diagnostics: false,
661 terminal_url: TerminalUrl::No,
662 theme: OutputTheme::Ascii,
663 }
664 }
665
666 fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
667 if self.ui_testing {
668 Cow::Borrowed(ANONYMIZED_LINE_NUM)
669 } else {
670 Cow::Owned(line_num.to_string())
671 }
672 }
673
674 fn draw_line(
675 &self,
676 buffer: &mut StyledBuffer,
677 source_string: &str,
678 line_index: usize,
679 line_offset: usize,
680 width_offset: usize,
681 code_offset: usize,
682 margin: Margin,
683 ) -> usize {
684 let line_len = source_string.len();
685 let left = margin.left(line_len);
687 let right = margin.right(line_len);
688 let code: String = source_string
691 .chars()
692 .enumerate()
693 .skip_while(|(i, _)| *i < left)
694 .take_while(|(i, _)| *i < right)
695 .map(|(_, c)| c)
696 .collect();
697 let code = normalize_whitespace(&code);
698 let was_cut_right =
699 source_string.chars().enumerate().skip_while(|(i, _)| *i < right).next().is_some();
700 buffer.puts(line_offset, code_offset, &code, Style::Quotation);
701 let placeholder = self.margin();
702 if margin.was_cut_left() {
703 buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber);
705 }
706 if was_cut_right {
707 let padding = str_width(placeholder);
708 buffer.puts(
710 line_offset,
711 code_offset + str_width(&code) - padding,
712 placeholder,
713 Style::LineNumber,
714 );
715 }
716 self.draw_line_num(buffer, line_index, line_offset, width_offset - 3);
717 self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2);
718 left
719 }
720
721 #[instrument(level = "trace", skip(self), ret)]
722 fn render_source_line(
723 &self,
724 buffer: &mut StyledBuffer,
725 file: Arc<SourceFile>,
726 line: &Line,
727 width_offset: usize,
728 code_offset: usize,
729 margin: Margin,
730 close_window: bool,
731 ) -> Vec<(usize, Style)> {
732 if line.line_index == 0 {
747 return Vec::new();
748 }
749
750 let Some(source_string) = file.get_line(line.line_index - 1) else {
751 return Vec::new();
752 };
753 trace!(?source_string);
754
755 let line_offset = buffer.num_lines();
756
757 let left = self.draw_line(
760 buffer,
761 &source_string,
762 line.line_index,
763 line_offset,
764 width_offset,
765 code_offset,
766 margin,
767 );
768
769 let mut buffer_ops = vec![];
786 let mut annotations = vec![];
787 let mut short_start = true;
788 for ann in &line.annotations {
789 if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
790 if source_string.chars().take(ann.start_col.file).all(|c| c.is_whitespace()) {
791 let uline = self.underline(ann.is_primary);
792 let chr = uline.multiline_whole_line;
793 annotations.push((depth, uline.style));
794 buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style));
795 } else {
796 short_start = false;
797 break;
798 }
799 } else if let AnnotationType::MultilineLine(_) = ann.annotation_type {
800 } else {
801 short_start = false;
802 break;
803 }
804 }
805 if short_start {
806 for (y, x, c, s) in buffer_ops {
807 buffer.putc(y, x, c, s);
808 }
809 return annotations;
810 }
811
812 let mut annotations = line.annotations.clone();
845 annotations.sort_by_key(|a| Reverse(a.start_col));
846
847 let mut overlap = vec![false; annotations.len()];
910 let mut annotations_position = vec![];
911 let mut line_len: usize = 0;
912 let mut p = 0;
913 for (i, annotation) in annotations.iter().enumerate() {
914 for (j, next) in annotations.iter().enumerate() {
915 if overlaps(next, annotation, 0) && j > i {
916 overlap[i] = true;
917 overlap[j] = true;
918 }
919 if overlaps(next, annotation, 0) && annotation.has_label() && j > i && p == 0
923 {
925 if next.start_col == annotation.start_col
928 && next.end_col == annotation.end_col
929 && !next.has_label()
930 {
931 continue;
932 }
933
934 p += 1;
936 break;
937 }
938 }
939 annotations_position.push((p, annotation));
940 for (j, next) in annotations.iter().enumerate() {
941 if j > i {
942 let l = next.label.as_ref().map_or(0, |label| label.len() + 2);
943 if (overlaps(next, annotation, l) && annotation.has_label() && next.has_label()) || (annotation.takes_space() && next.has_label()) || (annotation.has_label() && next.takes_space())
960 || (annotation.takes_space() && next.takes_space())
961 || (overlaps(next, annotation, l)
962 && next.end_col <= annotation.end_col
963 && next.has_label()
964 && p == 0)
965 {
967 p += 1;
969 break;
970 }
971 }
972 }
973 line_len = max(line_len, p);
974 }
975
976 if line_len != 0 {
977 line_len += 1;
978 }
979
980 if line.annotations.iter().all(|a| a.is_line()) {
983 return vec![];
984 }
985
986 if annotations_position
987 .iter()
988 .all(|(_, ann)| matches!(ann.annotation_type, AnnotationType::MultilineStart(_)))
989 && let Some(max_pos) = annotations_position.iter().map(|(pos, _)| *pos).max()
990 {
991 for (pos, _) in &mut annotations_position {
1004 *pos = max_pos - *pos;
1005 }
1006 line_len = line_len.saturating_sub(1);
1009 }
1010
1011 for pos in 0..=line_len {
1023 self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2);
1024 }
1025 if close_window {
1026 self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2);
1027 }
1028
1029 for &(pos, annotation) in &annotations_position {
1042 let underline = self.underline(annotation.is_primary);
1043 let pos = pos + 1;
1044 match annotation.annotation_type {
1045 AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => {
1046 let pre: usize = source_string
1047 .chars()
1048 .take(annotation.start_col.file)
1049 .skip(left)
1050 .map(|c| char_width(c))
1051 .sum();
1052 self.draw_range(
1053 buffer,
1054 underline.multiline_horizontal,
1055 line_offset + pos,
1056 width_offset + depth,
1057 code_offset + pre,
1058 underline.style,
1059 );
1060 }
1061 _ => {}
1062 }
1063 }
1064
1065 for &(pos, annotation) in &annotations_position {
1077 let underline = self.underline(annotation.is_primary);
1078 let pos = pos + 1;
1079
1080 let code_offset = code_offset
1081 + source_string
1082 .chars()
1083 .take(annotation.start_col.file)
1084 .skip(left)
1085 .map(|c| char_width(c))
1086 .sum::<usize>();
1087 if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
1088 for p in line_offset + 1..=line_offset + pos {
1089 buffer.putc(
1090 p,
1091 code_offset,
1092 match annotation.annotation_type {
1093 AnnotationType::MultilineLine(_) => underline.multiline_vertical,
1094 _ => underline.vertical_text_line,
1095 },
1096 underline.style,
1097 );
1098 }
1099 if let AnnotationType::MultilineStart(_) = annotation.annotation_type {
1100 buffer.putc(
1101 line_offset + pos,
1102 code_offset,
1103 underline.bottom_right,
1104 underline.style,
1105 );
1106 }
1107 if let AnnotationType::MultilineEnd(_) = annotation.annotation_type
1108 && annotation.has_label()
1109 {
1110 buffer.putc(
1111 line_offset + pos,
1112 code_offset,
1113 underline.multiline_bottom_right_with_text,
1114 underline.style,
1115 );
1116 }
1117 }
1118 match annotation.annotation_type {
1119 AnnotationType::MultilineStart(depth) => {
1120 buffer.putc(
1121 line_offset + pos,
1122 width_offset + depth - 1,
1123 underline.top_left,
1124 underline.style,
1125 );
1126 for p in line_offset + pos + 1..line_offset + line_len + 2 {
1127 buffer.putc(
1128 p,
1129 width_offset + depth - 1,
1130 underline.multiline_vertical,
1131 underline.style,
1132 );
1133 }
1134 }
1135 AnnotationType::MultilineEnd(depth) => {
1136 for p in line_offset..line_offset + pos {
1137 buffer.putc(
1138 p,
1139 width_offset + depth - 1,
1140 underline.multiline_vertical,
1141 underline.style,
1142 );
1143 }
1144 buffer.putc(
1145 line_offset + pos,
1146 width_offset + depth - 1,
1147 underline.bottom_left,
1148 underline.style,
1149 );
1150 }
1151 _ => (),
1152 }
1153 }
1154
1155 for &(pos, annotation) in &annotations_position {
1167 let style =
1168 if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary };
1169 let (pos, col) = if pos == 0 {
1170 let pre: usize = source_string
1171 .chars()
1172 .take(annotation.end_col.file)
1173 .skip(left)
1174 .map(|c| char_width(c))
1175 .sum();
1176 if annotation.end_col.file == 0 {
1177 (pos + 1, (pre + 2))
1178 } else {
1179 let pad = if annotation.end_col.file - annotation.start_col.file == 0 {
1180 2
1181 } else {
1182 1
1183 };
1184 (pos + 1, (pre + pad))
1185 }
1186 } else {
1187 let pre: usize = source_string
1188 .chars()
1189 .take(annotation.start_col.file)
1190 .skip(left)
1191 .map(|c| char_width(c))
1192 .sum();
1193 (pos + 2, pre)
1194 };
1195 if let Some(ref label) = annotation.label {
1196 buffer.puts(line_offset + pos, code_offset + col, label, style);
1197 }
1198 }
1199
1200 annotations_position.sort_by_key(|(_, ann)| {
1209 (Reverse(ann.len()), ann.is_primary)
1211 });
1212
1213 for &(pos, annotation) in &annotations_position {
1225 let uline = self.underline(annotation.is_primary);
1226 let width = annotation.end_col.file - annotation.start_col.file;
1227 let previous: String =
1228 source_string.chars().take(annotation.start_col.file).skip(left).collect();
1229 let underlined: String =
1230 source_string.chars().skip(annotation.start_col.file).take(width).collect();
1231 debug!(?previous, ?underlined);
1232 let code_offset = code_offset
1233 + source_string
1234 .chars()
1235 .take(annotation.start_col.file)
1236 .skip(left)
1237 .map(|c| char_width(c))
1238 .sum::<usize>();
1239 let ann_width: usize = source_string
1240 .chars()
1241 .skip(annotation.start_col.file)
1242 .take(width)
1243 .map(|c| char_width(c))
1244 .sum();
1245 let ann_width = if ann_width == 0
1246 && matches!(annotation.annotation_type, AnnotationType::Singleline)
1247 {
1248 1
1249 } else {
1250 ann_width
1251 };
1252 for p in 0..ann_width {
1253 buffer.putc(line_offset + 1, code_offset + p, uline.underline, uline.style);
1255 }
1256
1257 if pos == 0
1258 && matches!(
1259 annotation.annotation_type,
1260 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1261 )
1262 {
1263 buffer.putc(
1265 line_offset + 1,
1266 code_offset,
1267 match annotation.annotation_type {
1268 AnnotationType::MultilineStart(_) => uline.top_right_flat,
1269 AnnotationType::MultilineEnd(_) => uline.multiline_end_same_line,
1270 _ => panic!("unexpected annotation type: {annotation:?}"),
1271 },
1272 uline.style,
1273 );
1274 } else if pos != 0
1275 && matches!(
1276 annotation.annotation_type,
1277 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1278 )
1279 {
1280 buffer.putc(
1283 line_offset + 1,
1284 code_offset,
1285 match annotation.annotation_type {
1286 AnnotationType::MultilineStart(_) => uline.multiline_start_down,
1287 AnnotationType::MultilineEnd(_) => uline.multiline_end_up,
1288 _ => panic!("unexpected annotation type: {annotation:?}"),
1289 },
1290 uline.style,
1291 );
1292 } else if pos != 0 && annotation.has_label() {
1293 buffer.putc(line_offset + 1, code_offset, uline.label_start, uline.style);
1295 }
1296 }
1297
1298 for (i, (_pos, annotation)) in annotations_position.iter().enumerate() {
1302 if overlap[i] {
1304 continue;
1305 };
1306 let AnnotationType::Singleline = annotation.annotation_type else { continue };
1307 let width = annotation.end_col.display - annotation.start_col.display;
1308 if width > margin.column_width * 2 && width > 10 {
1309 let pad = max(margin.column_width / 3, 5);
1312 buffer.replace(
1314 line_offset,
1315 annotation.start_col.file + pad,
1316 annotation.end_col.file - pad,
1317 self.margin(),
1318 );
1319 buffer.replace(
1321 line_offset + 1,
1322 annotation.start_col.file + pad,
1323 annotation.end_col.file - pad,
1324 self.margin(),
1325 );
1326 }
1327 }
1328 annotations_position
1329 .iter()
1330 .filter_map(|&(_, annotation)| match annotation.annotation_type {
1331 AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => {
1332 let style = if annotation.is_primary {
1333 Style::LabelPrimary
1334 } else {
1335 Style::LabelSecondary
1336 };
1337 Some((p, style))
1338 }
1339 _ => None,
1340 })
1341 .collect::<Vec<_>>()
1342 }
1343
1344 fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
1345 let Some(ref sm) = self.sm else {
1346 return 0;
1347 };
1348
1349 let will_be_emitted = |span: Span| {
1350 !span.is_dummy() && {
1351 let file = sm.lookup_source_file(span.hi());
1352 should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file)
1353 }
1354 };
1355
1356 let mut max = 0;
1357 for primary_span in msp.primary_spans() {
1358 if will_be_emitted(*primary_span) {
1359 let hi = sm.lookup_char_pos(primary_span.hi());
1360 max = (hi.line).max(max);
1361 }
1362 }
1363 if !self.short_message {
1364 for span_label in msp.span_labels() {
1365 if will_be_emitted(span_label.span) {
1366 let hi = sm.lookup_char_pos(span_label.span.hi());
1367 max = (hi.line).max(max);
1368 }
1369 }
1370 }
1371
1372 max
1373 }
1374
1375 fn get_max_line_num(&mut self, span: &MultiSpan, children: &[Subdiag]) -> usize {
1376 let primary = self.get_multispan_max_line_num(span);
1377 children
1378 .iter()
1379 .map(|sub| self.get_multispan_max_line_num(&sub.span))
1380 .max()
1381 .unwrap_or(0)
1382 .max(primary)
1383 }
1384
1385 fn msgs_to_buffer(
1388 &self,
1389 buffer: &mut StyledBuffer,
1390 msgs: &[(DiagMessage, Style)],
1391 args: &FluentArgs<'_>,
1392 padding: usize,
1393 label: &str,
1394 override_style: Option<Style>,
1395 ) -> usize {
1396 let padding = " ".repeat(padding + label.len() + 5);
1413
1414 fn style_or_override(style: Style, override_: Option<Style>) -> Style {
1416 match (style, override_) {
1417 (Style::NoStyle, Some(override_)) => override_,
1418 _ => style,
1419 }
1420 }
1421
1422 let mut line_number = 0;
1423
1424 for (text, style) in msgs.iter() {
1444 let text = self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1445 let text = &normalize_whitespace(&text);
1446 let lines = text.split('\n').collect::<Vec<_>>();
1447 if lines.len() > 1 {
1448 for (i, line) in lines.iter().enumerate() {
1449 if i != 0 {
1450 line_number += 1;
1451 buffer.append(line_number, &padding, Style::NoStyle);
1452 }
1453 buffer.append(line_number, line, style_or_override(*style, override_style));
1454 }
1455 } else {
1456 buffer.append(line_number, text, style_or_override(*style, override_style));
1457 }
1458 }
1459 line_number
1460 }
1461
1462 #[instrument(level = "trace", skip(self, args), ret)]
1463 fn emit_messages_default_inner(
1464 &mut self,
1465 msp: &MultiSpan,
1466 msgs: &[(DiagMessage, Style)],
1467 args: &FluentArgs<'_>,
1468 code: &Option<ErrCode>,
1469 level: &Level,
1470 max_line_num_len: usize,
1471 is_secondary: bool,
1472 is_cont: bool,
1473 ) -> io::Result<()> {
1474 let mut buffer = StyledBuffer::new();
1475
1476 if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
1477 {
1478 for _ in 0..max_line_num_len {
1480 buffer.prepend(0, " ", Style::NoStyle);
1481 }
1482 self.draw_note_separator(&mut buffer, 0, max_line_num_len + 1, is_cont);
1483 if *level != Level::FailureNote {
1484 buffer.append(0, level.to_str(), Style::MainHeaderMsg);
1485 buffer.append(0, ": ", Style::NoStyle);
1486 }
1487 let printed_lines =
1488 self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None);
1489 if is_cont && matches!(self.theme, OutputTheme::Unicode) {
1490 for i in 1..=printed_lines {
1502 self.draw_col_separator_no_space(&mut buffer, i, max_line_num_len + 1);
1503 }
1504 }
1505 } else {
1506 let mut label_width = 0;
1507 if *level != Level::FailureNote {
1509 buffer.append(0, level.to_str(), Style::Level(*level));
1510 label_width += level.to_str().len();
1511 }
1512 if let Some(code) = code {
1513 buffer.append(0, "[", Style::Level(*level));
1514 let code = if let TerminalUrl::Yes = self.terminal_url {
1515 let path = "https://doc.rust-lang.org/error_codes";
1516 format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
1517 } else {
1518 code.to_string()
1519 };
1520 buffer.append(0, &code, Style::Level(*level));
1521 buffer.append(0, "]", Style::Level(*level));
1522 label_width += 2 + code.len();
1523 }
1524 let header_style = if is_secondary {
1525 Style::HeaderMsg
1526 } else if self.short_message {
1527 Style::NoStyle
1529 } else {
1530 Style::MainHeaderMsg
1531 };
1532 if *level != Level::FailureNote {
1533 buffer.append(0, ": ", header_style);
1534 label_width += 2;
1535 }
1536 let mut line = 0;
1537 for (text, style) in msgs.iter() {
1538 let text =
1539 self.translator.translate_message(text, args).map_err(Report::new).unwrap();
1540 for text in normalize_whitespace(&text).lines() {
1542 buffer.append(
1543 line,
1544 &format!(
1545 "{}{}",
1546 if line == 0 { String::new() } else { " ".repeat(label_width) },
1547 text
1548 ),
1549 match style {
1550 Style::Highlight => *style,
1551 _ => header_style,
1552 },
1553 );
1554 line += 1;
1555 }
1556 if line > 0 {
1562 line -= 1;
1563 }
1564 }
1565 if self.short_message {
1566 let labels = msp
1567 .span_labels()
1568 .into_iter()
1569 .filter_map(|label| match label.label {
1570 Some(msg) if label.is_primary => {
1571 let text = self.translator.translate_message(&msg, args).ok()?;
1572 if !text.trim().is_empty() { Some(text.to_string()) } else { None }
1573 }
1574 _ => None,
1575 })
1576 .collect::<Vec<_>>()
1577 .join(", ");
1578 if !labels.is_empty() {
1579 buffer.append(line, ": ", Style::NoStyle);
1580 buffer.append(line, &labels, Style::NoStyle);
1581 }
1582 }
1583 }
1584 let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
1585 trace!("{annotated_files:#?}");
1586
1587 let primary_span = msp.primary_span().unwrap_or_default();
1589 let (Some(sm), false) = (self.sm.as_ref(), primary_span.is_dummy()) else {
1590 return emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message);
1592 };
1593 let primary_lo = sm.lookup_char_pos(primary_span.lo());
1594 if let Ok(pos) =
1595 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
1596 {
1597 annotated_files.swap(0, pos);
1598 }
1599
1600 let annotated_files_len = annotated_files.len();
1601 for (file_idx, annotated_file) in annotated_files.into_iter().enumerate() {
1603 if !should_show_source_code(
1605 &self.ignored_directories_in_source_blocks,
1606 sm,
1607 &annotated_file.file,
1608 ) {
1609 if !self.short_message {
1610 for (annotation_id, line) in annotated_file.lines.iter().enumerate() {
1612 let mut annotations = line.annotations.clone();
1613 annotations.sort_by_key(|a| Reverse(a.start_col));
1614 let mut line_idx = buffer.num_lines();
1615
1616 let labels: Vec<_> = annotations
1617 .iter()
1618 .filter_map(|a| Some((a.label.as_ref()?, a.is_primary)))
1619 .filter(|(l, _)| !l.is_empty())
1620 .collect();
1621
1622 if annotation_id == 0 || !labels.is_empty() {
1623 buffer.append(
1624 line_idx,
1625 &format!(
1626 "{}:{}:{}",
1627 sm.filename_for_diagnostics(&annotated_file.file.name),
1628 sm.doctest_offset_line(
1629 &annotated_file.file.name,
1630 line.line_index
1631 ),
1632 annotations[0].start_col.file + 1,
1633 ),
1634 Style::LineAndColumn,
1635 );
1636 if annotation_id == 0 {
1637 buffer.prepend(line_idx, self.file_start(), Style::LineNumber);
1638 } else {
1639 buffer.prepend(
1640 line_idx,
1641 self.secondary_file_start(),
1642 Style::LineNumber,
1643 );
1644 }
1645 for _ in 0..max_line_num_len {
1646 buffer.prepend(line_idx, " ", Style::NoStyle);
1647 }
1648 line_idx += 1;
1649 }
1650 for (label, is_primary) in labels.into_iter() {
1651 let style = if is_primary {
1652 Style::LabelPrimary
1653 } else {
1654 Style::LabelSecondary
1655 };
1656 let pipe = self.col_separator();
1657 buffer.prepend(line_idx, &format!(" {pipe}"), Style::LineNumber);
1658 for _ in 0..max_line_num_len {
1659 buffer.prepend(line_idx, " ", Style::NoStyle);
1660 }
1661 line_idx += 1;
1662 let chr = self.note_separator();
1663 buffer.append(line_idx, &format!(" {chr} note: "), style);
1664 for _ in 0..max_line_num_len {
1665 buffer.prepend(line_idx, " ", Style::NoStyle);
1666 }
1667 buffer.append(line_idx, label, style);
1668 line_idx += 1;
1669 }
1670 }
1671 }
1672 continue;
1673 }
1674
1675 let is_primary = primary_lo.file.name == annotated_file.file.name;
1678 if is_primary {
1679 let loc = primary_lo.clone();
1680 if !self.short_message {
1681 let buffer_msg_line_offset = buffer.num_lines();
1683
1684 buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber);
1685 buffer.append(
1686 buffer_msg_line_offset,
1687 &format!(
1688 "{}:{}:{}",
1689 sm.filename_for_diagnostics(&loc.file.name),
1690 sm.doctest_offset_line(&loc.file.name, loc.line),
1691 loc.col.0 + 1,
1692 ),
1693 Style::LineAndColumn,
1694 );
1695 for _ in 0..max_line_num_len {
1696 buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1697 }
1698 } else {
1699 buffer.prepend(
1700 0,
1701 &format!(
1702 "{}:{}:{}: ",
1703 sm.filename_for_diagnostics(&loc.file.name),
1704 sm.doctest_offset_line(&loc.file.name, loc.line),
1705 loc.col.0 + 1,
1706 ),
1707 Style::LineAndColumn,
1708 );
1709 }
1710 } else if !self.short_message {
1711 let buffer_msg_line_offset = buffer.num_lines();
1713
1714 self.draw_col_separator_no_space(
1725 &mut buffer,
1726 buffer_msg_line_offset,
1727 max_line_num_len + 1,
1728 );
1729
1730 buffer.prepend(
1732 buffer_msg_line_offset + 1,
1733 self.secondary_file_start(),
1734 Style::LineNumber,
1735 );
1736 let loc = if let Some(first_line) = annotated_file.lines.first() {
1737 let col = if let Some(first_annotation) = first_line.annotations.first() {
1738 format!(":{}", first_annotation.start_col.file + 1)
1739 } else {
1740 String::new()
1741 };
1742 format!(
1743 "{}:{}{}",
1744 sm.filename_for_diagnostics(&annotated_file.file.name),
1745 sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
1746 col
1747 )
1748 } else {
1749 format!("{}", sm.filename_for_diagnostics(&annotated_file.file.name))
1750 };
1751 buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
1752 for _ in 0..max_line_num_len {
1753 buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
1754 }
1755 }
1756
1757 if !self.short_message {
1758 let buffer_msg_line_offset = buffer.num_lines();
1760 self.draw_col_separator_no_space(
1761 &mut buffer,
1762 buffer_msg_line_offset,
1763 max_line_num_len + 1,
1764 );
1765
1766 let mut multilines = FxIndexMap::default();
1768
1769 let mut whitespace_margin = usize::MAX;
1771 for line_idx in 0..annotated_file.lines.len() {
1772 let file = Arc::clone(&annotated_file.file);
1773 let line = &annotated_file.lines[line_idx];
1774 if let Some(source_string) =
1775 line.line_index.checked_sub(1).and_then(|l| file.get_line(l))
1776 {
1777 let leading_whitespace = source_string
1784 .chars()
1785 .take_while(|c| rustc_lexer::is_whitespace(*c))
1786 .count();
1787 if source_string.chars().any(|c| !rustc_lexer::is_whitespace(c)) {
1788 whitespace_margin = min(whitespace_margin, leading_whitespace);
1789 }
1790 }
1791 }
1792 if whitespace_margin == usize::MAX {
1793 whitespace_margin = 0;
1794 }
1795
1796 let mut span_left_margin = usize::MAX;
1798 for line in &annotated_file.lines {
1799 for ann in &line.annotations {
1800 span_left_margin = min(span_left_margin, ann.start_col.file);
1801 span_left_margin = min(span_left_margin, ann.end_col.file);
1802 }
1803 }
1804 if span_left_margin == usize::MAX {
1805 span_left_margin = 0;
1806 }
1807
1808 let mut span_right_margin = 0;
1810 let mut label_right_margin = 0;
1811 let mut max_line_len = 0;
1812 for line in &annotated_file.lines {
1813 max_line_len = max(
1814 max_line_len,
1815 line.line_index
1816 .checked_sub(1)
1817 .and_then(|l| annotated_file.file.get_line(l))
1818 .map_or(0, |s| s.len()),
1819 );
1820 for ann in &line.annotations {
1821 span_right_margin = max(span_right_margin, ann.start_col.file);
1822 span_right_margin = max(span_right_margin, ann.end_col.file);
1823 let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
1825 label_right_margin =
1826 max(label_right_margin, ann.end_col.file + label_right);
1827 }
1828 }
1829
1830 let width_offset = 3 + max_line_num_len;
1831 let code_offset = if annotated_file.multiline_depth == 0 {
1832 width_offset
1833 } else {
1834 width_offset + annotated_file.multiline_depth + 1
1835 };
1836
1837 let column_width = self.column_width(code_offset);
1838
1839 let margin = Margin::new(
1840 whitespace_margin,
1841 span_left_margin,
1842 span_right_margin,
1843 label_right_margin,
1844 column_width,
1845 max_line_len,
1846 );
1847
1848 for line_idx in 0..annotated_file.lines.len() {
1850 let previous_buffer_line = buffer.num_lines();
1851
1852 let depths = self.render_source_line(
1853 &mut buffer,
1854 Arc::clone(&annotated_file.file),
1855 &annotated_file.lines[line_idx],
1856 width_offset,
1857 code_offset,
1858 margin,
1859 !is_cont
1860 && file_idx + 1 == annotated_files_len
1861 && line_idx + 1 == annotated_file.lines.len(),
1862 );
1863
1864 let mut to_add = FxHashMap::default();
1865
1866 for (depth, style) in depths {
1867 if multilines.swap_remove(&depth).is_none() {
1869 to_add.insert(depth, style);
1870 }
1871 }
1872
1873 for (depth, style) in &multilines {
1876 for line in previous_buffer_line..buffer.num_lines() {
1877 self.draw_multiline_line(
1878 &mut buffer,
1879 line,
1880 width_offset,
1881 *depth,
1882 *style,
1883 );
1884 }
1885 }
1886 if line_idx < (annotated_file.lines.len() - 1) {
1889 let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1890 - annotated_file.lines[line_idx].line_index;
1891 if line_idx_delta > 2 {
1892 let last_buffer_line_num = buffer.num_lines();
1893 self.draw_line_separator(
1894 &mut buffer,
1895 last_buffer_line_num,
1896 width_offset,
1897 );
1898
1899 for (depth, style) in &multilines {
1901 self.draw_multiline_line(
1902 &mut buffer,
1903 last_buffer_line_num,
1904 width_offset,
1905 *depth,
1906 *style,
1907 );
1908 }
1909 if let Some(line) = annotated_file.lines.get(line_idx) {
1910 for ann in &line.annotations {
1911 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1912 {
1913 self.draw_multiline_line(
1917 &mut buffer,
1918 last_buffer_line_num,
1919 width_offset,
1920 pos,
1921 if ann.is_primary {
1922 Style::UnderlinePrimary
1923 } else {
1924 Style::UnderlineSecondary
1925 },
1926 );
1927 }
1928 }
1929 }
1930 } else if line_idx_delta == 2 {
1931 let unannotated_line = annotated_file
1932 .file
1933 .get_line(annotated_file.lines[line_idx].line_index)
1934 .unwrap_or_else(|| Cow::from(""));
1935
1936 let last_buffer_line_num = buffer.num_lines();
1937
1938 self.draw_line(
1939 &mut buffer,
1940 &normalize_whitespace(&unannotated_line),
1941 annotated_file.lines[line_idx + 1].line_index - 1,
1942 last_buffer_line_num,
1943 width_offset,
1944 code_offset,
1945 margin,
1946 );
1947
1948 for (depth, style) in &multilines {
1949 self.draw_multiline_line(
1950 &mut buffer,
1951 last_buffer_line_num,
1952 width_offset,
1953 *depth,
1954 *style,
1955 );
1956 }
1957 if let Some(line) = annotated_file.lines.get(line_idx) {
1958 for ann in &line.annotations {
1959 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1960 {
1961 self.draw_multiline_line(
1962 &mut buffer,
1963 last_buffer_line_num,
1964 width_offset,
1965 pos,
1966 if ann.is_primary {
1967 Style::UnderlinePrimary
1968 } else {
1969 Style::UnderlineSecondary
1970 },
1971 );
1972 }
1973 }
1974 }
1975 }
1976 }
1977
1978 multilines.extend(&to_add);
1979 }
1980 }
1981 trace!("buffer: {:#?}", buffer.render());
1982 }
1983
1984 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
1986
1987 Ok(())
1988 }
1989
1990 fn column_width(&self, code_offset: usize) -> usize {
1991 if let Some(width) = self.diagnostic_width {
1992 width.saturating_sub(code_offset)
1993 } else if self.ui_testing || cfg!(miri) {
1994 DEFAULT_COLUMN_WIDTH
1995 } else {
1996 termize::dimensions()
1997 .map(|(w, _)| w.saturating_sub(code_offset))
1998 .unwrap_or(DEFAULT_COLUMN_WIDTH)
1999 }
2000 }
2001
2002 fn emit_suggestion_default(
2003 &mut self,
2004 span: &MultiSpan,
2005 suggestion: &CodeSuggestion,
2006 args: &FluentArgs<'_>,
2007 level: &Level,
2008 max_line_num_len: usize,
2009 ) -> io::Result<()> {
2010 let Some(ref sm) = self.sm else {
2011 return Ok(());
2012 };
2013
2014 let suggestions = suggestion.splice_lines(sm);
2016 debug!(?suggestions);
2017
2018 if suggestions.is_empty() {
2019 return Ok(());
2025 }
2026
2027 let mut buffer = StyledBuffer::new();
2028
2029 buffer.append(0, level.to_str(), Style::Level(*level));
2031 buffer.append(0, ": ", Style::HeaderMsg);
2032
2033 let mut msg = vec![(suggestion.msg.to_owned(), Style::NoStyle)];
2034 if suggestions
2035 .iter()
2036 .take(MAX_SUGGESTIONS)
2037 .any(|(_, _, _, only_capitalization)| *only_capitalization)
2038 {
2039 msg.push((" (notice the capitalization difference)".into(), Style::NoStyle));
2040 }
2041 self.msgs_to_buffer(
2042 &mut buffer,
2043 &msg,
2044 args,
2045 max_line_num_len,
2046 "suggestion",
2047 Some(Style::HeaderMsg),
2048 );
2049
2050 let other_suggestions = suggestions.len().saturating_sub(MAX_SUGGESTIONS);
2051
2052 let mut row_num = 2;
2053 for (i, (complete, parts, highlights, _)) in
2054 suggestions.into_iter().enumerate().take(MAX_SUGGESTIONS)
2055 {
2056 debug!(?complete, ?parts, ?highlights);
2057
2058 let has_deletion =
2059 parts.iter().any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
2060 let is_multiline = complete.lines().count() > 1;
2061
2062 if i == 0 {
2063 self.draw_col_separator_start(&mut buffer, row_num - 1, max_line_num_len + 1);
2064 } else {
2065 buffer.puts(
2066 row_num - 1,
2067 max_line_num_len + 1,
2068 self.multi_suggestion_separator(),
2069 Style::LineNumber,
2070 );
2071 }
2072 if let Some(span) = span.primary_span() {
2073 let loc = sm.lookup_char_pos(parts[0].span.lo());
2078 if (span.is_dummy() || loc.file.name != sm.span_to_filename(span))
2079 && loc.file.name.is_real()
2080 {
2081 let arrow = self.file_start();
2084 buffer.puts(row_num - 1, 0, arrow, Style::LineNumber);
2085 let filename = sm.filename_for_diagnostics(&loc.file.name);
2086 let offset = sm.doctest_offset_line(&loc.file.name, loc.line);
2087 let message = format!("{}:{}:{}", filename, offset, loc.col.0 + 1);
2088 if row_num == 2 {
2089 let col = usize::max(max_line_num_len + 1, arrow.len());
2090 buffer.puts(1, col, &message, Style::LineAndColumn);
2091 } else {
2092 buffer.append(row_num - 1, &message, Style::LineAndColumn);
2093 }
2094 for _ in 0..max_line_num_len {
2095 buffer.prepend(row_num - 1, " ", Style::NoStyle);
2096 }
2097 self.draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
2098 row_num += 1;
2099 }
2100 }
2101 let show_code_change = if has_deletion && !is_multiline {
2102 DisplaySuggestion::Diff
2103 } else if let [part] = &parts[..]
2104 && part.snippet.ends_with('\n')
2105 && part.snippet.trim() == complete.trim()
2106 {
2107 DisplaySuggestion::Add
2109 } else if (parts.len() != 1 || parts[0].snippet.trim() != complete.trim())
2110 && !is_multiline
2111 {
2112 DisplaySuggestion::Underline
2113 } else {
2114 DisplaySuggestion::None
2115 };
2116
2117 if let DisplaySuggestion::Diff = show_code_change {
2118 row_num += 1;
2119 }
2120
2121 let file_lines = sm
2122 .span_to_lines(parts[0].span)
2123 .expect("span_to_lines failed when emitting suggestion");
2124
2125 assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
2126
2127 let line_start = sm.lookup_char_pos(parts[0].span.lo()).line;
2128 let mut lines = complete.lines();
2129 if lines.clone().next().is_none() {
2130 let line_end = sm.lookup_char_pos(parts[0].span.hi()).line;
2132 for line in line_start..=line_end {
2133 self.draw_line_num(
2134 &mut buffer,
2135 line,
2136 row_num - 1 + line - line_start,
2137 max_line_num_len,
2138 );
2139 buffer.puts(
2140 row_num - 1 + line - line_start,
2141 max_line_num_len + 1,
2142 "- ",
2143 Style::Removal,
2144 );
2145 buffer.puts(
2146 row_num - 1 + line - line_start,
2147 max_line_num_len + 3,
2148 &normalize_whitespace(&file_lines.file.get_line(line - 1).unwrap()),
2149 Style::Removal,
2150 );
2151 }
2152 row_num += line_end - line_start;
2153 }
2154 let mut unhighlighted_lines = Vec::new();
2155 let mut last_pos = 0;
2156 let mut is_item_attribute = false;
2157 for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
2158 last_pos = line_pos;
2159 debug!(%line_pos, %line, ?highlight_parts);
2160
2161 if highlight_parts.is_empty() {
2163 unhighlighted_lines.push((line_pos, line));
2164 continue;
2165 }
2166 if highlight_parts.len() == 1
2167 && line.trim().starts_with("#[")
2168 && line.trim().ends_with(']')
2169 {
2170 is_item_attribute = true;
2171 }
2172
2173 match unhighlighted_lines.len() {
2174 0 => (),
2175 n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
2180 self.draw_code_line(
2181 &mut buffer,
2182 &mut row_num,
2183 &[],
2184 p + line_start,
2185 l,
2186 show_code_change,
2187 max_line_num_len,
2188 &file_lines,
2189 is_multiline,
2190 )
2191 }),
2192 _ => {
2200 let last_line = unhighlighted_lines.pop();
2201 let first_line = unhighlighted_lines.drain(..).next();
2202
2203 if let Some((p, l)) = first_line {
2204 self.draw_code_line(
2205 &mut buffer,
2206 &mut row_num,
2207 &[],
2208 p + line_start,
2209 l,
2210 show_code_change,
2211 max_line_num_len,
2212 &file_lines,
2213 is_multiline,
2214 )
2215 }
2216
2217 let placeholder = self.margin();
2218 let padding = str_width(placeholder);
2219 buffer.puts(
2220 row_num,
2221 max_line_num_len.saturating_sub(padding),
2222 placeholder,
2223 Style::LineNumber,
2224 );
2225 row_num += 1;
2226
2227 if let Some((p, l)) = last_line {
2228 self.draw_code_line(
2229 &mut buffer,
2230 &mut row_num,
2231 &[],
2232 p + line_start,
2233 l,
2234 show_code_change,
2235 max_line_num_len,
2236 &file_lines,
2237 is_multiline,
2238 )
2239 }
2240 }
2241 }
2242
2243 self.draw_code_line(
2244 &mut buffer,
2245 &mut row_num,
2246 &highlight_parts,
2247 line_pos + line_start,
2248 line,
2249 show_code_change,
2250 max_line_num_len,
2251 &file_lines,
2252 is_multiline,
2253 )
2254 }
2255 if let DisplaySuggestion::Add = show_code_change
2256 && is_item_attribute
2257 {
2258 let file_lines = sm
2265 .span_to_lines(parts[0].span.shrink_to_hi())
2266 .expect("span_to_lines failed when emitting suggestion");
2267 let line_num = sm.lookup_char_pos(parts[0].span.lo()).line;
2268 if let Some(line) = file_lines.file.get_line(line_num - 1) {
2269 let line = normalize_whitespace(&line);
2270 self.draw_code_line(
2271 &mut buffer,
2272 &mut row_num,
2273 &[],
2274 line_num + last_pos + 1,
2275 &line,
2276 DisplaySuggestion::None,
2277 max_line_num_len,
2278 &file_lines,
2279 is_multiline,
2280 )
2281 }
2282 }
2283
2284 let mut offsets: Vec<(usize, isize)> = Vec::new();
2287 if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
2290 show_code_change
2291 {
2292 for part in parts {
2293 let snippet = if let Ok(snippet) = sm.span_to_snippet(part.span) {
2294 snippet
2295 } else {
2296 String::new()
2297 };
2298 let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
2299 let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
2300
2301 let is_whitespace_addition = part.snippet.trim().is_empty();
2304
2305 let start = if is_whitespace_addition {
2307 0
2308 } else {
2309 part.snippet.len().saturating_sub(part.snippet.trim_start().len())
2310 };
2311 let sub_len: usize = str_width(if is_whitespace_addition {
2314 &part.snippet
2315 } else {
2316 part.snippet.trim()
2317 });
2318
2319 let offset: isize = offsets
2320 .iter()
2321 .filter_map(
2322 |(start, v)| if span_start_pos < *start { None } else { Some(v) },
2323 )
2324 .sum();
2325 let underline_start = (span_start_pos + start) as isize + offset;
2326 let underline_end = (span_start_pos + start + sub_len) as isize + offset;
2327 assert!(underline_start >= 0 && underline_end >= 0);
2328 let padding: usize = max_line_num_len + 3;
2329 for p in underline_start..underline_end {
2330 if let DisplaySuggestion::Underline = show_code_change
2331 && is_different(sm, &part.snippet, part.span)
2332 {
2333 buffer.putc(
2336 row_num,
2337 (padding as isize + p) as usize,
2338 if part.is_addition(sm) { '+' } else { self.diff() },
2339 Style::Addition,
2340 );
2341 }
2342 }
2343 if let DisplaySuggestion::Diff = show_code_change {
2344 let newlines = snippet.lines().count();
2375 if newlines > 0 && row_num > newlines {
2376 for (i, line) in snippet.lines().enumerate() {
2385 let line = normalize_whitespace(line);
2386 let row = row_num - 2 - (newlines - i - 1);
2387 let start = if i == 0 {
2393 (padding as isize + span_start_pos as isize) as usize
2394 } else {
2395 padding
2396 };
2397 let end = if i == 0 {
2398 (padding as isize
2399 + span_start_pos as isize
2400 + line.len() as isize)
2401 as usize
2402 } else if i == newlines - 1 {
2403 (padding as isize + span_end_pos as isize) as usize
2404 } else {
2405 (padding as isize + line.len() as isize) as usize
2406 };
2407 buffer.set_style_range(row, start, end, Style::Removal, true);
2408 }
2409 } else {
2410 buffer.set_style_range(
2412 row_num - 2,
2413 (padding as isize + span_start_pos as isize) as usize,
2414 (padding as isize + span_end_pos as isize) as usize,
2415 Style::Removal,
2416 true,
2417 );
2418 }
2419 }
2420
2421 let full_sub_len = str_width(&part.snippet) as isize;
2423
2424 let snippet_len = span_end_pos as isize - span_start_pos as isize;
2426 offsets.push((span_end_pos, full_sub_len - snippet_len));
2430 }
2431 row_num += 1;
2432 }
2433
2434 if lines.next().is_some() {
2436 let placeholder = self.margin();
2437 let padding = str_width(placeholder);
2438 buffer.puts(
2439 row_num,
2440 max_line_num_len.saturating_sub(padding),
2441 placeholder,
2442 Style::LineNumber,
2443 );
2444 } else {
2445 let row = match show_code_change {
2446 DisplaySuggestion::Diff
2447 | DisplaySuggestion::Add
2448 | DisplaySuggestion::Underline => row_num - 1,
2449 DisplaySuggestion::None => row_num,
2450 };
2451 if other_suggestions > 0 {
2452 self.draw_col_separator_no_space(&mut buffer, row, max_line_num_len + 1);
2453 } else {
2454 self.draw_col_separator_end(&mut buffer, row, max_line_num_len + 1);
2455 }
2456 row_num = row + 1;
2457 }
2458 }
2459 if other_suggestions > 0 {
2460 self.draw_note_separator(&mut buffer, row_num, max_line_num_len + 1, false);
2461 let msg = format!(
2462 "and {} other candidate{}",
2463 other_suggestions,
2464 pluralize!(other_suggestions)
2465 );
2466 buffer.append(row_num, &msg, Style::NoStyle);
2467 }
2468
2469 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2470 Ok(())
2471 }
2472
2473 #[instrument(level = "trace", skip(self, args, code, children, suggestions))]
2474 fn emit_messages_default(
2475 &mut self,
2476 level: &Level,
2477 messages: &[(DiagMessage, Style)],
2478 args: &FluentArgs<'_>,
2479 code: &Option<ErrCode>,
2480 span: &MultiSpan,
2481 children: &[Subdiag],
2482 suggestions: &[CodeSuggestion],
2483 ) {
2484 let max_line_num_len = if self.ui_testing {
2485 ANONYMIZED_LINE_NUM.len()
2486 } else {
2487 let n = self.get_max_line_num(span, children);
2488 num_decimal_digits(n)
2489 };
2490
2491 match self.emit_messages_default_inner(
2492 span,
2493 messages,
2494 args,
2495 code,
2496 level,
2497 max_line_num_len,
2498 false,
2499 !children.is_empty()
2500 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden),
2501 ) {
2502 Ok(()) => {
2503 if !children.is_empty()
2504 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
2505 {
2506 let mut buffer = StyledBuffer::new();
2507 if !self.short_message {
2508 if let Some(child) = children.iter().next()
2509 && child.span.primary_spans().is_empty()
2510 {
2511 self.draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
2513 } else {
2514 self.draw_col_separator_end(&mut buffer, 0, max_line_num_len + 1);
2516 }
2517 }
2518 if let Err(e) = emit_to_destination(
2519 &buffer.render(),
2520 level,
2521 &mut self.dst,
2522 self.short_message,
2523 ) {
2524 panic!("failed to emit error: {e}")
2525 }
2526 }
2527 if !self.short_message {
2528 for (i, child) in children.iter().enumerate() {
2529 assert!(child.level.can_be_subdiag());
2530 let span = &child.span;
2531 let should_close = match children.get(i + 1) {
2533 Some(c) => !c.span.primary_spans().is_empty(),
2534 None => i + 1 == children.len(),
2535 };
2536 if let Err(err) = self.emit_messages_default_inner(
2537 span,
2538 &child.messages,
2539 args,
2540 &None,
2541 &child.level,
2542 max_line_num_len,
2543 true,
2544 !should_close,
2545 ) {
2546 panic!("failed to emit error: {err}");
2547 }
2548 }
2549 for (i, sugg) in suggestions.iter().enumerate() {
2550 match sugg.style {
2551 SuggestionStyle::CompletelyHidden => {
2552 }
2554 SuggestionStyle::HideCodeAlways => {
2555 if let Err(e) = self.emit_messages_default_inner(
2556 &MultiSpan::new(),
2557 &[(sugg.msg.to_owned(), Style::HeaderMsg)],
2558 args,
2559 &None,
2560 &Level::Help,
2561 max_line_num_len,
2562 true,
2563 i + 1 != suggestions.len(),
2566 ) {
2567 panic!("failed to emit error: {e}");
2568 }
2569 }
2570 SuggestionStyle::HideCodeInline
2571 | SuggestionStyle::ShowCode
2572 | SuggestionStyle::ShowAlways => {
2573 if let Err(e) = self.emit_suggestion_default(
2574 span,
2575 sugg,
2576 args,
2577 &Level::Help,
2578 max_line_num_len,
2579 ) {
2580 panic!("failed to emit error: {e}");
2581 }
2582 }
2583 }
2584 }
2585 }
2586 }
2587 Err(e) => panic!("failed to emit error: {e}"),
2588 }
2589
2590 match writeln!(self.dst) {
2591 Err(e) => panic!("failed to emit error: {e}"),
2592 _ => {
2593 if let Err(e) = self.dst.flush() {
2594 panic!("failed to emit error: {e}")
2595 }
2596 }
2597 }
2598 }
2599
2600 fn draw_code_line(
2601 &self,
2602 buffer: &mut StyledBuffer,
2603 row_num: &mut usize,
2604 highlight_parts: &[SubstitutionHighlight],
2605 line_num: usize,
2606 line_to_add: &str,
2607 show_code_change: DisplaySuggestion,
2608 max_line_num_len: usize,
2609 file_lines: &FileLines,
2610 is_multiline: bool,
2611 ) {
2612 if let DisplaySuggestion::Diff = show_code_change {
2613 let lines_to_remove = file_lines.lines.iter().take(file_lines.lines.len() - 1);
2616 for (index, line_to_remove) in lines_to_remove.enumerate() {
2617 self.draw_line_num(buffer, line_num + index, *row_num - 1, max_line_num_len);
2618 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2619 let line = normalize_whitespace(
2620 &file_lines.file.get_line(line_to_remove.line_index).unwrap(),
2621 );
2622 buffer.puts(*row_num - 1, max_line_num_len + 3, &line, Style::NoStyle);
2623 *row_num += 1;
2624 }
2625 let last_line_index = file_lines.lines[file_lines.lines.len() - 1].line_index;
2632 let last_line = &file_lines.file.get_line(last_line_index).unwrap();
2633 if last_line != line_to_add {
2634 self.draw_line_num(
2635 buffer,
2636 line_num + file_lines.lines.len() - 1,
2637 *row_num - 1,
2638 max_line_num_len,
2639 );
2640 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2641 buffer.puts(
2642 *row_num - 1,
2643 max_line_num_len + 3,
2644 &normalize_whitespace(last_line),
2645 Style::NoStyle,
2646 );
2647 if !line_to_add.trim().is_empty() {
2648 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2662 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2663 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2664 } else {
2665 *row_num -= 1;
2666 }
2667 } else {
2668 *row_num -= 2;
2669 }
2670 } else if is_multiline {
2671 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2672 match &highlight_parts {
2673 [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
2674 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2675 }
2676 [] => {
2677 self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
2679 }
2680 _ => {
2681 let diff = self.diff();
2682 buffer.puts(
2683 *row_num,
2684 max_line_num_len + 1,
2685 &format!("{diff} "),
2686 Style::Addition,
2687 );
2688 }
2689 }
2690 buffer.puts(
2696 *row_num,
2697 max_line_num_len + 3,
2698 &normalize_whitespace(line_to_add),
2699 Style::NoStyle,
2700 );
2701 } else if let DisplaySuggestion::Add = show_code_change {
2702 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2703 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2704 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2705 } else {
2706 self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
2707 self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2708 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2709 }
2710
2711 for &SubstitutionHighlight { start, end } in highlight_parts {
2713 if start != end {
2715 let tabs: usize = line_to_add
2717 .chars()
2718 .take(start)
2719 .map(|ch| match ch {
2720 '\t' => 3,
2721 _ => 0,
2722 })
2723 .sum();
2724 buffer.set_style_range(
2725 *row_num,
2726 max_line_num_len + 3 + start + tabs,
2727 max_line_num_len + 3 + end + tabs,
2728 Style::Addition,
2729 true,
2730 );
2731 }
2732 }
2733 *row_num += 1;
2734 }
2735
2736 fn underline(&self, is_primary: bool) -> UnderlineParts {
2737 match (self.theme, is_primary) {
2762 (OutputTheme::Ascii, true) => UnderlineParts {
2763 style: Style::UnderlinePrimary,
2764 underline: '^',
2765 label_start: '^',
2766 vertical_text_line: '|',
2767 multiline_vertical: '|',
2768 multiline_horizontal: '_',
2769 multiline_whole_line: '/',
2770 multiline_start_down: '^',
2771 bottom_right: '|',
2772 top_left: ' ',
2773 top_right_flat: '^',
2774 bottom_left: '|',
2775 multiline_end_up: '^',
2776 multiline_end_same_line: '^',
2777 multiline_bottom_right_with_text: '|',
2778 },
2779 (OutputTheme::Ascii, false) => UnderlineParts {
2780 style: Style::UnderlineSecondary,
2781 underline: '-',
2782 label_start: '-',
2783 vertical_text_line: '|',
2784 multiline_vertical: '|',
2785 multiline_horizontal: '_',
2786 multiline_whole_line: '/',
2787 multiline_start_down: '-',
2788 bottom_right: '|',
2789 top_left: ' ',
2790 top_right_flat: '-',
2791 bottom_left: '|',
2792 multiline_end_up: '-',
2793 multiline_end_same_line: '-',
2794 multiline_bottom_right_with_text: '|',
2795 },
2796 (OutputTheme::Unicode, true) => UnderlineParts {
2797 style: Style::UnderlinePrimary,
2798 underline: '━',
2799 label_start: '┯',
2800 vertical_text_line: '│',
2801 multiline_vertical: '┃',
2802 multiline_horizontal: '━',
2803 multiline_whole_line: '┏',
2804 multiline_start_down: '╿',
2805 bottom_right: '┙',
2806 top_left: '┏',
2807 top_right_flat: '┛',
2808 bottom_left: '┗',
2809 multiline_end_up: '╿',
2810 multiline_end_same_line: '┛',
2811 multiline_bottom_right_with_text: '┥',
2812 },
2813 (OutputTheme::Unicode, false) => UnderlineParts {
2814 style: Style::UnderlineSecondary,
2815 underline: '─',
2816 label_start: '┬',
2817 vertical_text_line: '│',
2818 multiline_vertical: '│',
2819 multiline_horizontal: '─',
2820 multiline_whole_line: '┌',
2821 multiline_start_down: '│',
2822 bottom_right: '┘',
2823 top_left: '┌',
2824 top_right_flat: '┘',
2825 bottom_left: '└',
2826 multiline_end_up: '│',
2827 multiline_end_same_line: '┘',
2828 multiline_bottom_right_with_text: '┤',
2829 },
2830 }
2831 }
2832
2833 fn col_separator(&self) -> char {
2834 match self.theme {
2835 OutputTheme::Ascii => '|',
2836 OutputTheme::Unicode => '│',
2837 }
2838 }
2839
2840 fn note_separator(&self) -> char {
2841 match self.theme {
2842 OutputTheme::Ascii => '=',
2843 OutputTheme::Unicode => '╰',
2844 }
2845 }
2846
2847 fn multi_suggestion_separator(&self) -> &'static str {
2848 match self.theme {
2849 OutputTheme::Ascii => "|",
2850 OutputTheme::Unicode => "├╴",
2851 }
2852 }
2853
2854 fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2855 let chr = self.col_separator();
2856 buffer.puts(line, col, &format!("{chr} "), Style::LineNumber);
2857 }
2858
2859 fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2860 let chr = self.col_separator();
2861 self.draw_col_separator_no_space_with_style(buffer, chr, line, col, Style::LineNumber);
2862 }
2863
2864 fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2865 match self.theme {
2866 OutputTheme::Ascii => {
2867 self.draw_col_separator_no_space_with_style(
2868 buffer,
2869 '|',
2870 line,
2871 col,
2872 Style::LineNumber,
2873 );
2874 }
2875 OutputTheme::Unicode => {
2876 self.draw_col_separator_no_space_with_style(
2877 buffer,
2878 '╭',
2879 line,
2880 col,
2881 Style::LineNumber,
2882 );
2883 self.draw_col_separator_no_space_with_style(
2884 buffer,
2885 '╴',
2886 line,
2887 col + 1,
2888 Style::LineNumber,
2889 );
2890 }
2891 }
2892 }
2893
2894 fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2895 match self.theme {
2896 OutputTheme::Ascii => {
2897 self.draw_col_separator_no_space_with_style(
2898 buffer,
2899 '|',
2900 line,
2901 col,
2902 Style::LineNumber,
2903 );
2904 }
2905 OutputTheme::Unicode => {
2906 self.draw_col_separator_no_space_with_style(
2907 buffer,
2908 '╰',
2909 line,
2910 col,
2911 Style::LineNumber,
2912 );
2913 self.draw_col_separator_no_space_with_style(
2914 buffer,
2915 '╴',
2916 line,
2917 col + 1,
2918 Style::LineNumber,
2919 );
2920 }
2921 }
2922 }
2923
2924 fn draw_col_separator_no_space_with_style(
2925 &self,
2926 buffer: &mut StyledBuffer,
2927 chr: char,
2928 line: usize,
2929 col: usize,
2930 style: Style,
2931 ) {
2932 buffer.putc(line, col, chr, style);
2933 }
2934
2935 fn draw_range(
2936 &self,
2937 buffer: &mut StyledBuffer,
2938 symbol: char,
2939 line: usize,
2940 col_from: usize,
2941 col_to: usize,
2942 style: Style,
2943 ) {
2944 for col in col_from..col_to {
2945 buffer.putc(line, col, symbol, style);
2946 }
2947 }
2948
2949 fn draw_note_separator(
2950 &self,
2951 buffer: &mut StyledBuffer,
2952 line: usize,
2953 col: usize,
2954 is_cont: bool,
2955 ) {
2956 let chr = match self.theme {
2957 OutputTheme::Ascii => "= ",
2958 OutputTheme::Unicode if is_cont => "├ ",
2959 OutputTheme::Unicode => "╰ ",
2960 };
2961 buffer.puts(line, col, chr, Style::LineNumber);
2962 }
2963
2964 fn draw_multiline_line(
2965 &self,
2966 buffer: &mut StyledBuffer,
2967 line: usize,
2968 offset: usize,
2969 depth: usize,
2970 style: Style,
2971 ) {
2972 let chr = match (style, self.theme) {
2973 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Ascii) => '|',
2974 (_, OutputTheme::Ascii) => '|',
2975 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Unicode) => '┃',
2976 (_, OutputTheme::Unicode) => '│',
2977 };
2978 buffer.putc(line, offset + depth - 1, chr, style);
2979 }
2980
2981 fn file_start(&self) -> &'static str {
2982 match self.theme {
2983 OutputTheme::Ascii => "--> ",
2984 OutputTheme::Unicode => " ╭▸ ",
2985 }
2986 }
2987
2988 fn secondary_file_start(&self) -> &'static str {
2989 match self.theme {
2990 OutputTheme::Ascii => "::: ",
2991 OutputTheme::Unicode => " ⸬ ",
2992 }
2993 }
2994
2995 fn diff(&self) -> char {
2996 match self.theme {
2997 OutputTheme::Ascii => '~',
2998 OutputTheme::Unicode => '±',
2999 }
3000 }
3001
3002 fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
3003 let (column, dots) = match self.theme {
3004 OutputTheme::Ascii => (0, "..."),
3005 OutputTheme::Unicode => (col - 2, "‡"),
3006 };
3007 buffer.puts(line, column, dots, Style::LineNumber);
3008 }
3009
3010 fn margin(&self) -> &'static str {
3011 match self.theme {
3012 OutputTheme::Ascii => "...",
3013 OutputTheme::Unicode => "…",
3014 }
3015 }
3016
3017 fn draw_line_num(
3018 &self,
3019 buffer: &mut StyledBuffer,
3020 line_num: usize,
3021 line_offset: usize,
3022 max_line_num_len: usize,
3023 ) {
3024 let line_num = self.maybe_anonymized(line_num);
3025 buffer.puts(
3026 line_offset,
3027 max_line_num_len.saturating_sub(str_width(&line_num)),
3028 &line_num,
3029 Style::LineNumber,
3030 );
3031 }
3032}
3033
3034#[derive(Debug, Clone, Copy)]
3035struct UnderlineParts {
3036 style: Style,
3037 underline: char,
3038 label_start: char,
3039 vertical_text_line: char,
3040 multiline_vertical: char,
3041 multiline_horizontal: char,
3042 multiline_whole_line: char,
3043 multiline_start_down: char,
3044 bottom_right: char,
3045 top_left: char,
3046 top_right_flat: char,
3047 bottom_left: char,
3048 multiline_end_up: char,
3049 multiline_end_same_line: char,
3050 multiline_bottom_right_with_text: char,
3051}
3052
3053#[derive(Clone, Copy, Debug)]
3054enum DisplaySuggestion {
3055 Underline,
3056 Diff,
3057 None,
3058 Add,
3059}
3060
3061impl FileWithAnnotatedLines {
3062 pub(crate) fn collect_annotations(
3065 emitter: &dyn Emitter,
3066 args: &FluentArgs<'_>,
3067 msp: &MultiSpan,
3068 ) -> Vec<FileWithAnnotatedLines> {
3069 fn add_annotation_to_file(
3070 file_vec: &mut Vec<FileWithAnnotatedLines>,
3071 file: Arc<SourceFile>,
3072 line_index: usize,
3073 ann: Annotation,
3074 ) {
3075 for slot in file_vec.iter_mut() {
3076 if slot.file.name == file.name {
3078 for line_slot in &mut slot.lines {
3080 if line_slot.line_index == line_index {
3081 line_slot.annotations.push(ann);
3082 return;
3083 }
3084 }
3085 slot.lines.push(Line { line_index, annotations: vec![ann] });
3087 slot.lines.sort();
3088 return;
3089 }
3090 }
3091 file_vec.push(FileWithAnnotatedLines {
3093 file,
3094 lines: vec![Line { line_index, annotations: vec![ann] }],
3095 multiline_depth: 0,
3096 });
3097 }
3098
3099 let mut output = vec![];
3100 let mut multiline_annotations = vec![];
3101
3102 if let Some(sm) = emitter.source_map() {
3103 for SpanLabel { span, is_primary, label } in msp.span_labels() {
3104 let span = match (span.is_dummy(), msp.primary_span()) {
3107 (_, None) | (false, _) => span,
3108 (true, Some(span)) => span,
3109 };
3110
3111 let lo = sm.lookup_char_pos(span.lo());
3112 let mut hi = sm.lookup_char_pos(span.hi());
3113
3114 if lo.col_display == hi.col_display && lo.line == hi.line {
3121 hi.col_display += 1;
3122 }
3123
3124 let label = label.as_ref().map(|m| {
3125 normalize_whitespace(
3126 &emitter
3127 .translator()
3128 .translate_message(m, args)
3129 .map_err(Report::new)
3130 .unwrap(),
3131 )
3132 });
3133
3134 if lo.line != hi.line {
3135 let ml = MultilineAnnotation {
3136 depth: 1,
3137 line_start: lo.line,
3138 line_end: hi.line,
3139 start_col: AnnotationColumn::from_loc(&lo),
3140 end_col: AnnotationColumn::from_loc(&hi),
3141 is_primary,
3142 label,
3143 overlaps_exactly: false,
3144 };
3145 multiline_annotations.push((lo.file, ml));
3146 } else {
3147 let ann = Annotation {
3148 start_col: AnnotationColumn::from_loc(&lo),
3149 end_col: AnnotationColumn::from_loc(&hi),
3150 is_primary,
3151 label,
3152 annotation_type: AnnotationType::Singleline,
3153 };
3154 add_annotation_to_file(&mut output, lo.file, lo.line, ann);
3155 };
3156 }
3157 }
3158
3159 multiline_annotations.sort_by_key(|(_, ml)| (ml.line_start, usize::MAX - ml.line_end));
3161 for (_, ann) in multiline_annotations.clone() {
3162 for (_, a) in multiline_annotations.iter_mut() {
3163 if !(ann.same_span(a))
3166 && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
3167 {
3168 a.increase_depth();
3169 } else if ann.same_span(a) && &ann != a {
3170 a.overlaps_exactly = true;
3171 } else {
3172 break;
3173 }
3174 }
3175 }
3176
3177 let mut max_depth = 0; for (_, ann) in &multiline_annotations {
3179 max_depth = max(max_depth, ann.depth);
3180 }
3181 for (_, a) in multiline_annotations.iter_mut() {
3183 a.depth = max_depth - a.depth + 1;
3184 }
3185 for (file, ann) in multiline_annotations {
3186 let mut end_ann = ann.as_end();
3187 if !ann.overlaps_exactly {
3188 add_annotation_to_file(
3211 &mut output,
3212 Arc::clone(&file),
3213 ann.line_start,
3214 ann.as_start(),
3215 );
3216 let middle = min(ann.line_start + 4, ann.line_end);
3221 let filter = |s: &str| {
3225 let s = s.trim();
3226 !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
3228 && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
3230 };
3231 let until = (ann.line_start..middle)
3232 .rev()
3233 .filter_map(|line| file.get_line(line - 1).map(|s| (line + 1, s)))
3234 .find(|(_, s)| filter(s))
3235 .map(|(line, _)| line)
3236 .unwrap_or(ann.line_start);
3237 for line in ann.line_start + 1..until {
3238 add_annotation_to_file(&mut output, Arc::clone(&file), line, ann.as_line());
3240 }
3241 let line_end = ann.line_end - 1;
3242 let end_is_empty = file.get_line(line_end - 1).is_some_and(|s| !filter(&s));
3243 if middle < line_end && !end_is_empty {
3244 add_annotation_to_file(&mut output, Arc::clone(&file), line_end, ann.as_line());
3245 }
3246 } else {
3247 end_ann.annotation_type = AnnotationType::Singleline;
3248 }
3249 add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
3250 }
3251 for file_vec in output.iter_mut() {
3252 file_vec.multiline_depth = max_depth;
3253 }
3254 output
3255 }
3256}
3257
3258fn num_decimal_digits(num: usize) -> usize {
3263 #[cfg(target_pointer_width = "64")]
3264 const MAX_DIGITS: usize = 20;
3265
3266 #[cfg(target_pointer_width = "32")]
3267 const MAX_DIGITS: usize = 10;
3268
3269 #[cfg(target_pointer_width = "16")]
3270 const MAX_DIGITS: usize = 5;
3271
3272 let mut lim = 10;
3273 for num_digits in 1..MAX_DIGITS {
3274 if num < lim {
3275 return num_digits;
3276 }
3277 lim = lim.wrapping_mul(10);
3278 }
3279 MAX_DIGITS
3280}
3281
3282const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
3285 ('\0', "␀"),
3289 ('\u{0001}', "␁"),
3290 ('\u{0002}', "␂"),
3291 ('\u{0003}', "␃"),
3292 ('\u{0004}', "␄"),
3293 ('\u{0005}', "␅"),
3294 ('\u{0006}', "␆"),
3295 ('\u{0007}', "␇"),
3296 ('\u{0008}', "␈"),
3297 ('\t', " "), ('\u{000b}', "␋"),
3299 ('\u{000c}', "␌"),
3300 ('\u{000d}', "␍"),
3301 ('\u{000e}', "␎"),
3302 ('\u{000f}', "␏"),
3303 ('\u{0010}', "␐"),
3304 ('\u{0011}', "␑"),
3305 ('\u{0012}', "␒"),
3306 ('\u{0013}', "␓"),
3307 ('\u{0014}', "␔"),
3308 ('\u{0015}', "␕"),
3309 ('\u{0016}', "␖"),
3310 ('\u{0017}', "␗"),
3311 ('\u{0018}', "␘"),
3312 ('\u{0019}', "␙"),
3313 ('\u{001a}', "␚"),
3314 ('\u{001b}', "␛"),
3315 ('\u{001c}', "␜"),
3316 ('\u{001d}', "␝"),
3317 ('\u{001e}', "␞"),
3318 ('\u{001f}', "␟"),
3319 ('\u{007f}', "␡"),
3320 ('\u{200d}', ""), ('\u{202a}', "�"), ('\u{202b}', "�"), ('\u{202c}', "�"), ('\u{202d}', "�"),
3325 ('\u{202e}', "�"),
3326 ('\u{2066}', "�"),
3327 ('\u{2067}', "�"),
3328 ('\u{2068}', "�"),
3329 ('\u{2069}', "�"),
3330];
3331
3332fn normalize_whitespace(s: &str) -> String {
3333 const {
3334 let mut i = 1;
3335 while i < OUTPUT_REPLACEMENTS.len() {
3336 assert!(
3337 OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
3338 "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
3339 and must contain no duplicate entries"
3340 );
3341 i += 1;
3342 }
3343 }
3344 s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
3348 match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
3349 Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
3350 _ => s.push(c),
3351 }
3352 s
3353 })
3354}
3355
3356fn num_overlap(
3357 a_start: usize,
3358 a_end: usize,
3359 b_start: usize,
3360 b_end: usize,
3361 inclusive: bool,
3362) -> bool {
3363 let extra = if inclusive { 1 } else { 0 };
3364 (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
3365}
3366
3367fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
3368 num_overlap(
3369 a1.start_col.display,
3370 a1.end_col.display + padding,
3371 a2.start_col.display,
3372 a2.end_col.display,
3373 false,
3374 )
3375}
3376
3377fn emit_to_destination(
3378 rendered_buffer: &[Vec<StyledString>],
3379 lvl: &Level,
3380 dst: &mut Destination,
3381 short_message: bool,
3382) -> io::Result<()> {
3383 use crate::lock;
3384
3385 let _buffer_lock = lock::acquire_global_lock("rustc_errors");
3398 for (pos, line) in rendered_buffer.iter().enumerate() {
3399 for part in line {
3400 let style = part.style.color_spec(*lvl);
3401 dst.set_color(&style)?;
3402 write!(dst, "{}", part.text)?;
3403 dst.reset()?;
3404 }
3405 if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
3406 writeln!(dst)?;
3407 }
3408 }
3409 dst.flush()?;
3410 Ok(())
3411}
3412
3413pub type Destination = Box<dyn WriteColor + Send>;
3414
3415struct Buffy {
3416 buffer_writer: BufferWriter,
3417 buffer: Buffer,
3418}
3419
3420impl Write for Buffy {
3421 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3422 self.buffer.write(buf)
3423 }
3424
3425 fn flush(&mut self) -> io::Result<()> {
3426 self.buffer_writer.print(&self.buffer)?;
3427 self.buffer.clear();
3428 Ok(())
3429 }
3430}
3431
3432impl Drop for Buffy {
3433 fn drop(&mut self) {
3434 if !self.buffer.is_empty() {
3435 self.flush().unwrap();
3436 panic!("buffers need to be flushed in order to print their contents");
3437 }
3438 }
3439}
3440
3441impl WriteColor for Buffy {
3442 fn supports_color(&self) -> bool {
3443 self.buffer.supports_color()
3444 }
3445
3446 fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
3447 self.buffer.set_color(spec)
3448 }
3449
3450 fn reset(&mut self) -> io::Result<()> {
3451 self.buffer.reset()
3452 }
3453}
3454
3455pub fn stderr_destination(color: ColorConfig) -> Destination {
3456 let choice = color.to_color_choice();
3457 if cfg!(windows) {
3464 Box::new(StandardStream::stderr(choice))
3465 } else {
3466 let buffer_writer = BufferWriter::stderr(choice);
3467 let buffer = buffer_writer.buffer();
3468 Box::new(Buffy { buffer_writer, buffer })
3469 }
3470}
3471
3472const BRIGHT_BLUE: Color = if cfg!(windows) { Color::Cyan } else { Color::Blue };
3476
3477impl Style {
3478 fn color_spec(&self, lvl: Level) -> ColorSpec {
3479 let mut spec = ColorSpec::new();
3480 match self {
3481 Style::Addition => {
3482 spec.set_fg(Some(Color::Green)).set_intense(true);
3483 }
3484 Style::Removal => {
3485 spec.set_fg(Some(Color::Red)).set_intense(true);
3486 }
3487 Style::LineAndColumn => {}
3488 Style::LineNumber => {
3489 spec.set_bold(true);
3490 spec.set_intense(true);
3491 spec.set_fg(Some(BRIGHT_BLUE));
3492 }
3493 Style::Quotation => {}
3494 Style::MainHeaderMsg => {
3495 spec.set_bold(true);
3496 if cfg!(windows) {
3497 spec.set_intense(true).set_fg(Some(Color::White));
3498 }
3499 }
3500 Style::UnderlinePrimary | Style::LabelPrimary => {
3501 spec = lvl.color();
3502 spec.set_bold(true);
3503 }
3504 Style::UnderlineSecondary | Style::LabelSecondary => {
3505 spec.set_bold(true).set_intense(true);
3506 spec.set_fg(Some(BRIGHT_BLUE));
3507 }
3508 Style::HeaderMsg | Style::NoStyle => {}
3509 Style::Level(lvl) => {
3510 spec = lvl.color();
3511 spec.set_bold(true);
3512 }
3513 Style::Highlight => {
3514 spec.set_bold(true).set_fg(Some(Color::Magenta));
3515 }
3516 }
3517 spec
3518 }
3519}
3520
3521pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3523 let found = match sm.span_to_snippet(sp) {
3524 Ok(snippet) => snippet,
3525 Err(e) => {
3526 warn!(error = ?e, "Invalid span {:?}", sp);
3527 return true;
3528 }
3529 };
3530 found != suggested
3531}
3532
3533pub fn is_case_difference(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3535 let found = match sm.span_to_snippet(sp) {
3537 Ok(snippet) => snippet,
3538 Err(e) => {
3539 warn!(error = ?e, "Invalid span {:?}", sp);
3540 return false;
3541 }
3542 };
3543 let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
3544 let confusable = iter::zip(found.chars(), suggested.chars())
3546 .filter(|(f, s)| f != s)
3547 .all(|(f, s)| ascii_confusables.contains(&f) || ascii_confusables.contains(&s));
3548 confusable && found.to_lowercase() == suggested.to_lowercase()
3549 && found != suggested
3552}
3553
3554pub(crate) fn should_show_source_code(
3555 ignored_directories: &[String],
3556 sm: &SourceMap,
3557 file: &SourceFile,
3558) -> bool {
3559 if !sm.ensure_source_file_source_present(file) {
3560 return false;
3561 }
3562
3563 let FileName::Real(name) = &file.name else { return true };
3564 name.local_path()
3565 .map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
3566 .unwrap_or(true)
3567}