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