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::diagnostic::DiagLocation;
32use crate::registry::Registry;
33use crate::snippet::{
34 Annotation, AnnotationColumn, AnnotationType, Line, MultilineAnnotation, Style, StyledString,
35};
36use crate::styled_buffer::StyledBuffer;
37use crate::timings::TimingRecord;
38use crate::translation::{Translate, to_fluent_args};
39use crate::{
40 CodeSuggestion, DiagInner, DiagMessage, ErrCode, FluentBundle, LazyFallbackBundle, Level,
41 MultiSpan, Subdiag, SubstitutionHighlight, SuggestionStyle, TerminalUrl,
42};
43
44const DEFAULT_COLUMN_WIDTH: usize = 140;
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum HumanReadableErrorType {
50 Default,
51 Unicode,
52 AnnotateSnippet,
53 Short,
54}
55
56impl HumanReadableErrorType {
57 pub fn short(&self) -> bool {
58 *self == HumanReadableErrorType::Short
59 }
60}
61
62#[derive(Clone, Copy, Debug)]
63struct Margin {
64 pub whitespace_left: usize,
66 pub span_left: usize,
68 pub span_right: usize,
70 pub computed_left: usize,
72 pub computed_right: usize,
74 pub column_width: usize,
77 pub label_right: usize,
80}
81
82impl Margin {
83 fn new(
84 whitespace_left: usize,
85 span_left: usize,
86 span_right: usize,
87 label_right: usize,
88 column_width: usize,
89 max_line_len: usize,
90 ) -> Self {
91 let mut m = Margin {
101 whitespace_left: whitespace_left.saturating_sub(6),
102 span_left: span_left.saturating_sub(6),
103 span_right: span_right + 6,
104 computed_left: 0,
105 computed_right: 0,
106 column_width,
107 label_right: label_right + 6,
108 };
109 m.compute(max_line_len);
110 m
111 }
112
113 fn was_cut_left(&self) -> bool {
114 self.computed_left > 0
115 }
116
117 fn compute(&mut self, max_line_len: usize) {
118 self.computed_left = if self.whitespace_left > 20 {
123 self.whitespace_left - 16 } else {
125 0
126 };
127 self.computed_right = max(max_line_len, self.computed_left);
130
131 if self.computed_right - self.computed_left > self.column_width {
132 if self.label_right - self.whitespace_left <= self.column_width {
134 self.computed_left = self.whitespace_left;
136 self.computed_right = self.computed_left + self.column_width;
137 } else if self.label_right - self.span_left <= self.column_width {
138 let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
140 self.computed_left = self.span_left.saturating_sub(padding_left);
141 self.computed_right = self.computed_left + self.column_width;
142 } else if self.span_right - self.span_left <= self.column_width {
143 let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
145 self.computed_left = self.span_left.saturating_sub(padding_left);
146 self.computed_right = self.computed_left + self.column_width;
147 } else {
148 self.computed_left = self.span_left;
150 self.computed_right = self.span_right;
151 }
152 }
153 }
154
155 fn left(&self, line_len: usize) -> usize {
156 min(self.computed_left, line_len)
157 }
158
159 fn right(&self, line_len: usize) -> usize {
160 if line_len.saturating_sub(self.computed_left) <= self.column_width {
161 line_len
162 } else {
163 min(line_len, self.computed_right)
164 }
165 }
166}
167
168pub enum TimingEvent {
169 Start,
170 End,
171}
172
173const ANONYMIZED_LINE_NUM: &str = "LL";
174
175pub type DynEmitter = dyn Emitter + DynSend;
176
177pub trait Emitter: Translate {
179 fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
181
182 fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
185
186 fn emit_timing_section(&mut self, _record: TimingRecord, _event: TimingEvent) {}
189
190 fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}
193
194 fn emit_unused_externs(
197 &mut self,
198 _lint_level: rustc_lint_defs::Level,
199 _unused_externs: &[&str],
200 ) {
201 }
202
203 fn should_show_explain(&self) -> bool {
205 true
206 }
207
208 fn supports_color(&self) -> bool {
210 false
211 }
212
213 fn source_map(&self) -> Option<&SourceMap>;
214
215 fn primary_span_formatted(
227 &mut self,
228 primary_span: &mut MultiSpan,
229 suggestions: &mut Vec<CodeSuggestion>,
230 fluent_args: &FluentArgs<'_>,
231 ) {
232 if let Some((sugg, rest)) = suggestions.split_first() {
233 let msg = self.translate_message(&sugg.msg, fluent_args).map_err(Report::new).unwrap();
234 if rest.is_empty()
235 && let [substitution] = sugg.substitutions.as_slice()
238 && let [part] = substitution.parts.as_slice()
240 && msg.split_whitespace().count() < 10
242 && !part.snippet.contains('\n')
244 && ![
245 SuggestionStyle::HideCodeAlways,
247 SuggestionStyle::CompletelyHidden,
249 SuggestionStyle::ShowAlways,
251 ].contains(&sugg.style)
252 {
253 let snippet = part.snippet.trim();
254 let msg = if snippet.is_empty() || sugg.style.hide_inline() {
255 format!("help: {msg}")
258 } else {
259 format!(
261 "help: {}{}: `{}`",
262 msg,
263 if self
264 .source_map()
265 .is_some_and(|sm| is_case_difference(sm, snippet, part.span,))
266 {
267 " (notice the capitalization)"
268 } else {
269 ""
270 },
271 snippet,
272 )
273 };
274 primary_span.push_span_label(part.span, msg);
275
276 suggestions.clear();
278 } else {
279 }
284 } else {
285 }
287 }
288
289 fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
290 &self,
291 span: &mut MultiSpan,
292 children: &mut Vec<Subdiag>,
293 level: &Level,
294 backtrace: bool,
295 ) {
296 let has_macro_spans: Vec<_> = iter::once(&*span)
299 .chain(children.iter().map(|child| &child.span))
300 .flat_map(|span| span.primary_spans())
301 .flat_map(|sp| sp.macro_backtrace())
302 .filter_map(|expn_data| {
303 match expn_data.kind {
304 ExpnKind::Root => None,
305
306 ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
309
310 ExpnKind::Macro(macro_kind, name) => {
311 Some((macro_kind, name, expn_data.hide_backtrace))
312 }
313 }
314 })
315 .collect();
316
317 if !backtrace {
318 self.fix_multispans_in_extern_macros(span, children);
319 }
320
321 self.render_multispans_macro_backtrace(span, children, backtrace);
322
323 if !backtrace {
324 if let Some((macro_kind, name, _)) = has_macro_spans.first()
327 && let Some((_, _, false)) = has_macro_spans.last()
328 {
329 let and_then = if let Some((macro_kind, last_name, _)) = has_macro_spans.last()
331 && last_name != name
332 {
333 let descr = macro_kind.descr();
334 format!(" which comes from the expansion of the {descr} `{last_name}`")
335 } else {
336 "".to_string()
337 };
338
339 let descr = macro_kind.descr();
340 let msg = format!(
341 "this {level} originates in the {descr} `{name}`{and_then} \
342 (in Nightly builds, run with -Z macro-backtrace for more info)",
343 );
344
345 children.push(Subdiag {
346 level: Level::Note,
347 messages: vec![(DiagMessage::from(msg), Style::NoStyle)],
348 span: MultiSpan::new(),
349 });
350 }
351 }
352 }
353
354 fn render_multispans_macro_backtrace(
355 &self,
356 span: &mut MultiSpan,
357 children: &mut Vec<Subdiag>,
358 backtrace: bool,
359 ) {
360 for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
361 self.render_multispan_macro_backtrace(span, backtrace);
362 }
363 }
364
365 fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
366 let mut new_labels = FxIndexSet::default();
367
368 for &sp in span.primary_spans() {
369 if sp.is_dummy() {
370 continue;
371 }
372
373 let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
377 for (i, trace) in macro_backtrace.iter().rev().enumerate() {
378 if trace.def_site.is_dummy() {
379 continue;
380 }
381
382 if always_backtrace {
383 new_labels.insert((
384 trace.def_site,
385 format!(
386 "in this expansion of `{}`{}",
387 trace.kind.descr(),
388 if macro_backtrace.len() > 1 {
389 format!(" (#{})", i + 1)
392 } else {
393 String::new()
394 },
395 ),
396 ));
397 }
398
399 let redundant_span = trace.call_site.contains(sp);
411
412 if !redundant_span || always_backtrace {
413 let msg: Cow<'static, _> = match trace.kind {
414 ExpnKind::Macro(MacroKind::Attr, _) => {
415 "this procedural macro expansion".into()
416 }
417 ExpnKind::Macro(MacroKind::Derive, _) => {
418 "this derive macro expansion".into()
419 }
420 ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
421 ExpnKind::Root => "the crate root".into(),
422 ExpnKind::AstPass(kind) => kind.descr().into(),
423 ExpnKind::Desugaring(kind) => {
424 format!("this {} desugaring", kind.descr()).into()
425 }
426 };
427 new_labels.insert((
428 trace.call_site,
429 format!(
430 "in {}{}",
431 msg,
432 if macro_backtrace.len() > 1 && always_backtrace {
433 format!(" (#{})", i + 1)
436 } else {
437 String::new()
438 },
439 ),
440 ));
441 }
442 if !always_backtrace {
443 break;
444 }
445 }
446 }
447
448 for (label_span, label_text) in new_labels {
449 span.push_span_label(label_span, label_text);
450 }
451 }
452
453 fn fix_multispans_in_extern_macros(&self, span: &mut MultiSpan, children: &mut Vec<Subdiag>) {
457 debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
458 self.fix_multispan_in_extern_macros(span);
459 for child in children.iter_mut() {
460 self.fix_multispan_in_extern_macros(&mut child.span);
461 }
462 debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
463 }
464
465 fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) {
469 let Some(source_map) = self.source_map() else { return };
470 let replacements: Vec<(Span, Span)> = span
472 .primary_spans()
473 .iter()
474 .copied()
475 .chain(span.span_labels().iter().map(|sp_label| sp_label.span))
476 .filter_map(|sp| {
477 if !sp.is_dummy() && source_map.is_imported(sp) {
478 let maybe_callsite = sp.source_callsite();
479 if sp != maybe_callsite {
480 return Some((sp, maybe_callsite));
481 }
482 }
483 None
484 })
485 .collect();
486
487 for (from, to) in replacements {
489 span.replace(from, to);
490 }
491 }
492}
493
494impl Translate for HumanEmitter {
495 fn fluent_bundle(&self) -> Option<&FluentBundle> {
496 self.fluent_bundle.as_deref()
497 }
498
499 fn fallback_fluent_bundle(&self) -> &FluentBundle {
500 &self.fallback_bundle
501 }
502}
503
504impl Emitter for HumanEmitter {
505 fn source_map(&self) -> Option<&SourceMap> {
506 self.sm.as_deref()
507 }
508
509 fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
510 let fluent_args = to_fluent_args(diag.args.iter());
511
512 let mut suggestions = diag.suggestions.unwrap_tag();
513 self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
514
515 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
516 &mut diag.span,
517 &mut diag.children,
518 &diag.level,
519 self.macro_backtrace,
520 );
521
522 self.emit_messages_default(
523 &diag.level,
524 &diag.messages,
525 &fluent_args,
526 &diag.code,
527 &diag.span,
528 &diag.children,
529 &suggestions,
530 self.track_diagnostics.then_some(&diag.emitted_at),
531 );
532 }
533
534 fn should_show_explain(&self) -> bool {
535 !self.short_message
536 }
537
538 fn supports_color(&self) -> bool {
539 self.dst.supports_color()
540 }
541}
542
543pub struct SilentEmitter {
547 pub fatal_emitter: Box<dyn Emitter + DynSend>,
548 pub fatal_note: Option<String>,
549 pub emit_fatal_diagnostic: bool,
550}
551
552impl Translate for SilentEmitter {
553 fn fluent_bundle(&self) -> Option<&FluentBundle> {
554 None
555 }
556
557 fn fallback_fluent_bundle(&self) -> &FluentBundle {
558 self.fatal_emitter.fallback_fluent_bundle()
559 }
560}
561
562impl Emitter for SilentEmitter {
563 fn source_map(&self) -> Option<&SourceMap> {
564 None
565 }
566
567 fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
568 if self.emit_fatal_diagnostic && diag.level == Level::Fatal {
569 if let Some(fatal_note) = &self.fatal_note {
570 diag.sub(Level::Note, fatal_note.clone(), MultiSpan::new());
571 }
572 self.fatal_emitter.emit_diagnostic(diag, registry);
573 }
574 }
575}
576
577pub const MAX_SUGGESTIONS: usize = 4;
581
582#[derive(Clone, Copy, Debug, PartialEq, Eq)]
583pub enum ColorConfig {
584 Auto,
585 Always,
586 Never,
587}
588
589impl ColorConfig {
590 pub fn to_color_choice(self) -> ColorChoice {
591 match self {
592 ColorConfig::Always => {
593 if io::stderr().is_terminal() {
594 ColorChoice::Always
595 } else {
596 ColorChoice::AlwaysAnsi
597 }
598 }
599 ColorConfig::Never => ColorChoice::Never,
600 ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto,
601 ColorConfig::Auto => ColorChoice::Never,
602 }
603 }
604}
605
606#[derive(Debug, Clone, Copy, PartialEq, Eq)]
607pub enum OutputTheme {
608 Ascii,
609 Unicode,
610}
611
612#[derive(Setters)]
614pub struct HumanEmitter {
615 #[setters(skip)]
616 dst: IntoDynSyncSend<Destination>,
617 sm: Option<Arc<SourceMap>>,
618 fluent_bundle: Option<Arc<FluentBundle>>,
619 #[setters(skip)]
620 fallback_bundle: LazyFallbackBundle,
621 short_message: bool,
622 ui_testing: bool,
623 ignored_directories_in_source_blocks: Vec<String>,
624 diagnostic_width: Option<usize>,
625
626 macro_backtrace: bool,
627 track_diagnostics: bool,
628 terminal_url: TerminalUrl,
629 theme: OutputTheme,
630}
631
632#[derive(Debug)]
633pub(crate) struct FileWithAnnotatedLines {
634 pub(crate) file: Arc<SourceFile>,
635 pub(crate) lines: Vec<Line>,
636 multiline_depth: usize,
637}
638
639impl HumanEmitter {
640 pub fn new(dst: Destination, fallback_bundle: LazyFallbackBundle) -> HumanEmitter {
641 HumanEmitter {
642 dst: IntoDynSyncSend(dst),
643 sm: None,
644 fluent_bundle: None,
645 fallback_bundle,
646 short_message: false,
647 ui_testing: false,
648 ignored_directories_in_source_blocks: Vec::new(),
649 diagnostic_width: None,
650 macro_backtrace: false,
651 track_diagnostics: false,
652 terminal_url: TerminalUrl::No,
653 theme: OutputTheme::Ascii,
654 }
655 }
656
657 fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
658 if self.ui_testing {
659 Cow::Borrowed(ANONYMIZED_LINE_NUM)
660 } else {
661 Cow::Owned(line_num.to_string())
662 }
663 }
664
665 fn draw_line(
666 &self,
667 buffer: &mut StyledBuffer,
668 source_string: &str,
669 line_index: usize,
670 line_offset: usize,
671 width_offset: usize,
672 code_offset: usize,
673 margin: Margin,
674 ) -> usize {
675 let line_len = source_string.len();
676 let left = margin.left(line_len);
678 let right = margin.right(line_len);
679 let code: String = source_string
682 .chars()
683 .enumerate()
684 .skip_while(|(i, _)| *i < left)
685 .take_while(|(i, _)| *i < right)
686 .map(|(_, c)| c)
687 .collect();
688 let code = normalize_whitespace(&code);
689 let was_cut_right =
690 source_string.chars().enumerate().skip_while(|(i, _)| *i < right).next().is_some();
691 buffer.puts(line_offset, code_offset, &code, Style::Quotation);
692 let placeholder = self.margin();
693 if margin.was_cut_left() {
694 buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber);
696 }
697 if was_cut_right {
698 let padding = str_width(placeholder);
699 buffer.puts(
701 line_offset,
702 code_offset + str_width(&code) - padding,
703 placeholder,
704 Style::LineNumber,
705 );
706 }
707 buffer.puts(line_offset, 0, &self.maybe_anonymized(line_index), Style::LineNumber);
708
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.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 emitted_at: Option<&DiagLocation>,
1465 is_cont: bool,
1466 ) -> io::Result<()> {
1467 let mut buffer = StyledBuffer::new();
1468
1469 if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
1470 {
1471 for _ in 0..max_line_num_len {
1473 buffer.prepend(0, " ", Style::NoStyle);
1474 }
1475 self.draw_note_separator(&mut buffer, 0, max_line_num_len + 1, is_cont);
1476 if *level != Level::FailureNote {
1477 buffer.append(0, level.to_str(), Style::MainHeaderMsg);
1478 buffer.append(0, ": ", Style::NoStyle);
1479 }
1480 let printed_lines =
1481 self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None);
1482 if is_cont && matches!(self.theme, OutputTheme::Unicode) {
1483 for i in 1..=printed_lines {
1495 self.draw_col_separator_no_space(&mut buffer, i, max_line_num_len + 1);
1496 }
1497 }
1498 } else {
1499 let mut label_width = 0;
1500 if *level != Level::FailureNote {
1502 buffer.append(0, level.to_str(), Style::Level(*level));
1503 label_width += level.to_str().len();
1504 }
1505 if let Some(code) = code {
1506 buffer.append(0, "[", Style::Level(*level));
1507 let code = if let TerminalUrl::Yes = self.terminal_url {
1508 let path = "https://doc.rust-lang.org/error_codes";
1509 format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
1510 } else {
1511 code.to_string()
1512 };
1513 buffer.append(0, &code, Style::Level(*level));
1514 buffer.append(0, "]", Style::Level(*level));
1515 label_width += 2 + code.len();
1516 }
1517 let header_style = if is_secondary {
1518 Style::HeaderMsg
1519 } else if self.short_message {
1520 Style::NoStyle
1522 } else {
1523 Style::MainHeaderMsg
1524 };
1525 if *level != Level::FailureNote {
1526 buffer.append(0, ": ", header_style);
1527 label_width += 2;
1528 }
1529 let mut line = 0;
1530 for (text, style) in msgs.iter() {
1531 let text = self.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.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 for annotated_file in annotated_files {
1594 if !should_show_source_code(
1596 &self.ignored_directories_in_source_blocks,
1597 sm,
1598 &annotated_file.file,
1599 ) {
1600 if !self.short_message {
1601 for (annotation_id, line) in annotated_file.lines.iter().enumerate() {
1603 let mut annotations = line.annotations.clone();
1604 annotations.sort_by_key(|a| Reverse(a.start_col));
1605 let mut line_idx = buffer.num_lines();
1606
1607 let labels: Vec<_> = annotations
1608 .iter()
1609 .filter_map(|a| Some((a.label.as_ref()?, a.is_primary)))
1610 .filter(|(l, _)| !l.is_empty())
1611 .collect();
1612
1613 if annotation_id == 0 || !labels.is_empty() {
1614 buffer.append(
1615 line_idx,
1616 &format!(
1617 "{}:{}:{}",
1618 sm.filename_for_diagnostics(&annotated_file.file.name),
1619 sm.doctest_offset_line(
1620 &annotated_file.file.name,
1621 line.line_index
1622 ),
1623 annotations[0].start_col.file + 1,
1624 ),
1625 Style::LineAndColumn,
1626 );
1627 if annotation_id == 0 {
1628 buffer.prepend(line_idx, self.file_start(), Style::LineNumber);
1629 } else {
1630 buffer.prepend(
1631 line_idx,
1632 self.secondary_file_start(),
1633 Style::LineNumber,
1634 );
1635 }
1636 for _ in 0..max_line_num_len {
1637 buffer.prepend(line_idx, " ", Style::NoStyle);
1638 }
1639 line_idx += 1;
1640 }
1641 for (label, is_primary) in labels.into_iter() {
1642 let style = if is_primary {
1643 Style::LabelPrimary
1644 } else {
1645 Style::LabelSecondary
1646 };
1647 let pipe = self.col_separator();
1648 buffer.prepend(line_idx, &format!(" {pipe}"), Style::LineNumber);
1649 for _ in 0..max_line_num_len {
1650 buffer.prepend(line_idx, " ", Style::NoStyle);
1651 }
1652 line_idx += 1;
1653 let chr = self.note_separator();
1654 buffer.append(line_idx, &format!(" {chr} note: "), style);
1655 for _ in 0..max_line_num_len {
1656 buffer.prepend(line_idx, " ", Style::NoStyle);
1657 }
1658 buffer.append(line_idx, label, style);
1659 line_idx += 1;
1660 }
1661 }
1662 }
1663 continue;
1664 }
1665
1666 let is_primary = primary_lo.file.name == annotated_file.file.name;
1669 if is_primary {
1670 let loc = primary_lo.clone();
1671 if !self.short_message {
1672 let buffer_msg_line_offset = buffer.num_lines();
1674
1675 buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber);
1676 buffer.append(
1677 buffer_msg_line_offset,
1678 &format!(
1679 "{}:{}:{}",
1680 sm.filename_for_diagnostics(&loc.file.name),
1681 sm.doctest_offset_line(&loc.file.name, loc.line),
1682 loc.col.0 + 1,
1683 ),
1684 Style::LineAndColumn,
1685 );
1686 for _ in 0..max_line_num_len {
1687 buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1688 }
1689 } else {
1690 buffer.prepend(
1691 0,
1692 &format!(
1693 "{}:{}:{}: ",
1694 sm.filename_for_diagnostics(&loc.file.name),
1695 sm.doctest_offset_line(&loc.file.name, loc.line),
1696 loc.col.0 + 1,
1697 ),
1698 Style::LineAndColumn,
1699 );
1700 }
1701 } else if !self.short_message {
1702 let buffer_msg_line_offset = buffer.num_lines();
1704
1705 self.draw_col_separator_no_space(
1716 &mut buffer,
1717 buffer_msg_line_offset,
1718 max_line_num_len + 1,
1719 );
1720
1721 buffer.prepend(
1723 buffer_msg_line_offset + 1,
1724 self.secondary_file_start(),
1725 Style::LineNumber,
1726 );
1727 let loc = if let Some(first_line) = annotated_file.lines.first() {
1728 let col = if let Some(first_annotation) = first_line.annotations.first() {
1729 format!(":{}", first_annotation.start_col.file + 1)
1730 } else {
1731 String::new()
1732 };
1733 format!(
1734 "{}:{}{}",
1735 sm.filename_for_diagnostics(&annotated_file.file.name),
1736 sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
1737 col
1738 )
1739 } else {
1740 format!("{}", sm.filename_for_diagnostics(&annotated_file.file.name))
1741 };
1742 buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
1743 for _ in 0..max_line_num_len {
1744 buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
1745 }
1746 }
1747
1748 if !self.short_message {
1749 let buffer_msg_line_offset = buffer.num_lines();
1751 self.draw_col_separator_no_space(
1752 &mut buffer,
1753 buffer_msg_line_offset,
1754 max_line_num_len + 1,
1755 );
1756
1757 let mut multilines = FxIndexMap::default();
1759
1760 let mut whitespace_margin = usize::MAX;
1762 for line_idx in 0..annotated_file.lines.len() {
1763 let file = Arc::clone(&annotated_file.file);
1764 let line = &annotated_file.lines[line_idx];
1765 if let Some(source_string) =
1766 line.line_index.checked_sub(1).and_then(|l| file.get_line(l))
1767 {
1768 let leading_whitespace = source_string
1775 .chars()
1776 .take_while(|c| rustc_lexer::is_whitespace(*c))
1777 .count();
1778 if source_string.chars().any(|c| !rustc_lexer::is_whitespace(c)) {
1779 whitespace_margin = min(whitespace_margin, leading_whitespace);
1780 }
1781 }
1782 }
1783 if whitespace_margin == usize::MAX {
1784 whitespace_margin = 0;
1785 }
1786
1787 let mut span_left_margin = usize::MAX;
1789 for line in &annotated_file.lines {
1790 for ann in &line.annotations {
1791 span_left_margin = min(span_left_margin, ann.start_col.file);
1792 span_left_margin = min(span_left_margin, ann.end_col.file);
1793 }
1794 }
1795 if span_left_margin == usize::MAX {
1796 span_left_margin = 0;
1797 }
1798
1799 let mut span_right_margin = 0;
1801 let mut label_right_margin = 0;
1802 let mut max_line_len = 0;
1803 for line in &annotated_file.lines {
1804 max_line_len = max(
1805 max_line_len,
1806 line.line_index
1807 .checked_sub(1)
1808 .and_then(|l| annotated_file.file.get_line(l))
1809 .map_or(0, |s| s.len()),
1810 );
1811 for ann in &line.annotations {
1812 span_right_margin = max(span_right_margin, ann.start_col.file);
1813 span_right_margin = max(span_right_margin, ann.end_col.file);
1814 let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
1816 label_right_margin =
1817 max(label_right_margin, ann.end_col.file + label_right);
1818 }
1819 }
1820
1821 let width_offset = 3 + max_line_num_len;
1822 let code_offset = if annotated_file.multiline_depth == 0 {
1823 width_offset
1824 } else {
1825 width_offset + annotated_file.multiline_depth + 1
1826 };
1827
1828 let column_width = self.column_width(code_offset);
1829
1830 let margin = Margin::new(
1831 whitespace_margin,
1832 span_left_margin,
1833 span_right_margin,
1834 label_right_margin,
1835 column_width,
1836 max_line_len,
1837 );
1838
1839 for line_idx in 0..annotated_file.lines.len() {
1841 let previous_buffer_line = buffer.num_lines();
1842
1843 let depths = self.render_source_line(
1844 &mut buffer,
1845 Arc::clone(&annotated_file.file),
1846 &annotated_file.lines[line_idx],
1847 width_offset,
1848 code_offset,
1849 margin,
1850 !is_cont && line_idx + 1 == annotated_file.lines.len(),
1851 );
1852
1853 let mut to_add = FxHashMap::default();
1854
1855 for (depth, style) in depths {
1856 if multilines.swap_remove(&depth).is_none() {
1858 to_add.insert(depth, style);
1859 }
1860 }
1861
1862 for (depth, style) in &multilines {
1865 for line in previous_buffer_line..buffer.num_lines() {
1866 self.draw_multiline_line(
1867 &mut buffer,
1868 line,
1869 width_offset,
1870 *depth,
1871 *style,
1872 );
1873 }
1874 }
1875 if line_idx < (annotated_file.lines.len() - 1) {
1878 let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1879 - annotated_file.lines[line_idx].line_index;
1880 if line_idx_delta > 2 {
1881 let last_buffer_line_num = buffer.num_lines();
1882 self.draw_line_separator(
1883 &mut buffer,
1884 last_buffer_line_num,
1885 width_offset,
1886 );
1887
1888 for (depth, style) in &multilines {
1890 self.draw_multiline_line(
1891 &mut buffer,
1892 last_buffer_line_num,
1893 width_offset,
1894 *depth,
1895 *style,
1896 );
1897 }
1898 if let Some(line) = annotated_file.lines.get(line_idx) {
1899 for ann in &line.annotations {
1900 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1901 {
1902 self.draw_multiline_line(
1906 &mut buffer,
1907 last_buffer_line_num,
1908 width_offset,
1909 pos,
1910 if ann.is_primary {
1911 Style::UnderlinePrimary
1912 } else {
1913 Style::UnderlineSecondary
1914 },
1915 );
1916 }
1917 }
1918 }
1919 } else if line_idx_delta == 2 {
1920 let unannotated_line = annotated_file
1921 .file
1922 .get_line(annotated_file.lines[line_idx].line_index)
1923 .unwrap_or_else(|| Cow::from(""));
1924
1925 let last_buffer_line_num = buffer.num_lines();
1926
1927 self.draw_line(
1928 &mut buffer,
1929 &normalize_whitespace(&unannotated_line),
1930 annotated_file.lines[line_idx + 1].line_index - 1,
1931 last_buffer_line_num,
1932 width_offset,
1933 code_offset,
1934 margin,
1935 );
1936
1937 for (depth, style) in &multilines {
1938 self.draw_multiline_line(
1939 &mut buffer,
1940 last_buffer_line_num,
1941 width_offset,
1942 *depth,
1943 *style,
1944 );
1945 }
1946 if let Some(line) = annotated_file.lines.get(line_idx) {
1947 for ann in &line.annotations {
1948 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1949 {
1950 self.draw_multiline_line(
1951 &mut buffer,
1952 last_buffer_line_num,
1953 width_offset,
1954 pos,
1955 if ann.is_primary {
1956 Style::UnderlinePrimary
1957 } else {
1958 Style::UnderlineSecondary
1959 },
1960 );
1961 }
1962 }
1963 }
1964 }
1965 }
1966
1967 multilines.extend(&to_add);
1968 }
1969 }
1970 trace!("buffer: {:#?}", buffer.render());
1971 }
1972
1973 if let Some(tracked) = emitted_at {
1974 let track = format!("-Ztrack-diagnostics: created at {tracked}");
1975 let len = buffer.num_lines();
1976 buffer.append(len, &track, Style::NoStyle);
1977 }
1978
1979 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
1981
1982 Ok(())
1983 }
1984
1985 fn column_width(&self, code_offset: usize) -> usize {
1986 if let Some(width) = self.diagnostic_width {
1987 width.saturating_sub(code_offset)
1988 } else if self.ui_testing || cfg!(miri) {
1989 DEFAULT_COLUMN_WIDTH
1990 } else {
1991 termize::dimensions()
1992 .map(|(w, _)| w.saturating_sub(code_offset))
1993 .unwrap_or(DEFAULT_COLUMN_WIDTH)
1994 }
1995 }
1996
1997 fn emit_suggestion_default(
1998 &mut self,
1999 span: &MultiSpan,
2000 suggestion: &CodeSuggestion,
2001 args: &FluentArgs<'_>,
2002 level: &Level,
2003 max_line_num_len: usize,
2004 ) -> io::Result<()> {
2005 let Some(ref sm) = self.sm else {
2006 return Ok(());
2007 };
2008
2009 let suggestions = suggestion.splice_lines(sm);
2011 debug!(?suggestions);
2012
2013 if suggestions.is_empty() {
2014 return Ok(());
2020 }
2021
2022 let mut buffer = StyledBuffer::new();
2023
2024 buffer.append(0, level.to_str(), Style::Level(*level));
2026 buffer.append(0, ": ", Style::HeaderMsg);
2027
2028 let mut msg = vec![(suggestion.msg.to_owned(), Style::NoStyle)];
2029 if suggestions
2030 .iter()
2031 .take(MAX_SUGGESTIONS)
2032 .any(|(_, _, _, only_capitalization)| *only_capitalization)
2033 {
2034 msg.push((" (notice the capitalization difference)".into(), Style::NoStyle));
2035 }
2036 self.msgs_to_buffer(
2037 &mut buffer,
2038 &msg,
2039 args,
2040 max_line_num_len,
2041 "suggestion",
2042 Some(Style::HeaderMsg),
2043 );
2044
2045 let other_suggestions = suggestions.len().saturating_sub(MAX_SUGGESTIONS);
2046
2047 let mut row_num = 2;
2048 for (i, (complete, parts, highlights, _)) in
2049 suggestions.into_iter().enumerate().take(MAX_SUGGESTIONS)
2050 {
2051 debug!(?complete, ?parts, ?highlights);
2052
2053 let has_deletion =
2054 parts.iter().any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
2055 let is_multiline = complete.lines().count() > 1;
2056
2057 if i == 0 {
2058 self.draw_col_separator_start(&mut buffer, row_num - 1, max_line_num_len + 1);
2059 } else {
2060 buffer.puts(
2061 row_num - 1,
2062 max_line_num_len + 1,
2063 self.multi_suggestion_separator(),
2064 Style::LineNumber,
2065 );
2066 }
2067 if let Some(span) = span.primary_span() {
2068 let loc = sm.lookup_char_pos(parts[0].span.lo());
2073 if loc.file.name != sm.span_to_filename(span) && loc.file.name.is_real() {
2074 let arrow = self.file_start();
2077 buffer.puts(row_num - 1, 0, arrow, Style::LineNumber);
2078 let filename = sm.filename_for_diagnostics(&loc.file.name);
2079 let offset = sm.doctest_offset_line(&loc.file.name, loc.line);
2080 let message = format!("{}:{}:{}", filename, offset, loc.col.0 + 1);
2081 if row_num == 2 {
2082 let col = usize::max(max_line_num_len + 1, arrow.len());
2083 buffer.puts(1, col, &message, Style::LineAndColumn);
2084 } else {
2085 buffer.append(row_num - 1, &message, Style::LineAndColumn);
2086 }
2087 for _ in 0..max_line_num_len {
2088 buffer.prepend(row_num - 1, " ", Style::NoStyle);
2089 }
2090 self.draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
2091 row_num += 1;
2092 }
2093 }
2094 let show_code_change = if has_deletion && !is_multiline {
2095 DisplaySuggestion::Diff
2096 } else if let [part] = &parts[..]
2097 && part.snippet.ends_with('\n')
2098 && part.snippet.trim() == complete.trim()
2099 {
2100 DisplaySuggestion::Add
2102 } else if (parts.len() != 1 || parts[0].snippet.trim() != complete.trim())
2103 && !is_multiline
2104 {
2105 DisplaySuggestion::Underline
2106 } else {
2107 DisplaySuggestion::None
2108 };
2109
2110 if let DisplaySuggestion::Diff = show_code_change {
2111 row_num += 1;
2112 }
2113
2114 let file_lines = sm
2115 .span_to_lines(parts[0].span)
2116 .expect("span_to_lines failed when emitting suggestion");
2117
2118 assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
2119
2120 let line_start = sm.lookup_char_pos(parts[0].span.lo()).line;
2121 let mut lines = complete.lines();
2122 if lines.clone().next().is_none() {
2123 let line_end = sm.lookup_char_pos(parts[0].span.hi()).line;
2125 for line in line_start..=line_end {
2126 buffer.puts(
2127 row_num - 1 + line - line_start,
2128 0,
2129 &self.maybe_anonymized(line),
2130 Style::LineNumber,
2131 );
2132 buffer.puts(
2133 row_num - 1 + line - line_start,
2134 max_line_num_len + 1,
2135 "- ",
2136 Style::Removal,
2137 );
2138 buffer.puts(
2139 row_num - 1 + line - line_start,
2140 max_line_num_len + 3,
2141 &normalize_whitespace(&file_lines.file.get_line(line - 1).unwrap()),
2142 Style::Removal,
2143 );
2144 }
2145 row_num += line_end - line_start;
2146 }
2147 let mut unhighlighted_lines = Vec::new();
2148 let mut last_pos = 0;
2149 let mut is_item_attribute = false;
2150 for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
2151 last_pos = line_pos;
2152 debug!(%line_pos, %line, ?highlight_parts);
2153
2154 if highlight_parts.is_empty() {
2156 unhighlighted_lines.push((line_pos, line));
2157 continue;
2158 }
2159 if highlight_parts.len() == 1
2160 && line.trim().starts_with("#[")
2161 && line.trim().ends_with(']')
2162 {
2163 is_item_attribute = true;
2164 }
2165
2166 match unhighlighted_lines.len() {
2167 0 => (),
2168 n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
2173 self.draw_code_line(
2174 &mut buffer,
2175 &mut row_num,
2176 &[],
2177 p + line_start,
2178 l,
2179 show_code_change,
2180 max_line_num_len,
2181 &file_lines,
2182 is_multiline,
2183 )
2184 }),
2185 _ => {
2193 let last_line = unhighlighted_lines.pop();
2194 let first_line = unhighlighted_lines.drain(..).next();
2195
2196 if let Some((p, l)) = first_line {
2197 self.draw_code_line(
2198 &mut buffer,
2199 &mut row_num,
2200 &[],
2201 p + line_start,
2202 l,
2203 show_code_change,
2204 max_line_num_len,
2205 &file_lines,
2206 is_multiline,
2207 )
2208 }
2209
2210 let placeholder = self.margin();
2211 let padding = str_width(placeholder);
2212 buffer.puts(
2213 row_num,
2214 max_line_num_len.saturating_sub(padding),
2215 placeholder,
2216 Style::LineNumber,
2217 );
2218 row_num += 1;
2219
2220 if let Some((p, l)) = last_line {
2221 self.draw_code_line(
2222 &mut buffer,
2223 &mut row_num,
2224 &[],
2225 p + line_start,
2226 l,
2227 show_code_change,
2228 max_line_num_len,
2229 &file_lines,
2230 is_multiline,
2231 )
2232 }
2233 }
2234 }
2235
2236 self.draw_code_line(
2237 &mut buffer,
2238 &mut row_num,
2239 &highlight_parts,
2240 line_pos + line_start,
2241 line,
2242 show_code_change,
2243 max_line_num_len,
2244 &file_lines,
2245 is_multiline,
2246 )
2247 }
2248 if let DisplaySuggestion::Add = show_code_change
2249 && is_item_attribute
2250 {
2251 let file_lines = sm
2258 .span_to_lines(parts[0].span.shrink_to_hi())
2259 .expect("span_to_lines failed when emitting suggestion");
2260 let line_num = sm.lookup_char_pos(parts[0].span.lo()).line;
2261 if let Some(line) = file_lines.file.get_line(line_num - 1) {
2262 let line = normalize_whitespace(&line);
2263 self.draw_code_line(
2264 &mut buffer,
2265 &mut row_num,
2266 &[],
2267 line_num + last_pos + 1,
2268 &line,
2269 DisplaySuggestion::None,
2270 max_line_num_len,
2271 &file_lines,
2272 is_multiline,
2273 )
2274 }
2275 }
2276
2277 let mut offsets: Vec<(usize, isize)> = Vec::new();
2280 if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
2283 show_code_change
2284 {
2285 for part in parts {
2286 let snippet = if let Ok(snippet) = sm.span_to_snippet(part.span) {
2287 snippet
2288 } else {
2289 String::new()
2290 };
2291 let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
2292 let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
2293
2294 let is_whitespace_addition = part.snippet.trim().is_empty();
2297
2298 let start = if is_whitespace_addition {
2300 0
2301 } else {
2302 part.snippet.len().saturating_sub(part.snippet.trim_start().len())
2303 };
2304 let sub_len: usize = str_width(if is_whitespace_addition {
2307 &part.snippet
2308 } else {
2309 part.snippet.trim()
2310 });
2311
2312 let offset: isize = offsets
2313 .iter()
2314 .filter_map(
2315 |(start, v)| if span_start_pos < *start { None } else { Some(v) },
2316 )
2317 .sum();
2318 let underline_start = (span_start_pos + start) as isize + offset;
2319 let underline_end = (span_start_pos + start + sub_len) as isize + offset;
2320 assert!(underline_start >= 0 && underline_end >= 0);
2321 let padding: usize = max_line_num_len + 3;
2322 for p in underline_start..underline_end {
2323 if let DisplaySuggestion::Underline = show_code_change
2324 && is_different(sm, &part.snippet, part.span)
2325 {
2326 buffer.putc(
2329 row_num,
2330 (padding as isize + p) as usize,
2331 if part.is_addition(sm) { '+' } else { self.diff() },
2332 Style::Addition,
2333 );
2334 }
2335 }
2336 if let DisplaySuggestion::Diff = show_code_change {
2337 let newlines = snippet.lines().count();
2368 if newlines > 0 && row_num > newlines {
2369 for (i, line) in snippet.lines().enumerate() {
2378 let line = normalize_whitespace(line);
2379 let row = row_num - 2 - (newlines - i - 1);
2380 let start = if i == 0 {
2386 (padding as isize + span_start_pos as isize) as usize
2387 } else {
2388 padding
2389 };
2390 let end = if i == 0 {
2391 (padding as isize
2392 + span_start_pos as isize
2393 + line.len() as isize)
2394 as usize
2395 } else if i == newlines - 1 {
2396 (padding as isize + span_end_pos as isize) as usize
2397 } else {
2398 (padding as isize + line.len() as isize) as usize
2399 };
2400 buffer.set_style_range(row, start, end, Style::Removal, true);
2401 }
2402 } else {
2403 buffer.set_style_range(
2405 row_num - 2,
2406 (padding as isize + span_start_pos as isize) as usize,
2407 (padding as isize + span_end_pos as isize) as usize,
2408 Style::Removal,
2409 true,
2410 );
2411 }
2412 }
2413
2414 let full_sub_len = str_width(&part.snippet) as isize;
2416
2417 let snippet_len = span_end_pos as isize - span_start_pos as isize;
2419 offsets.push((span_end_pos, full_sub_len - snippet_len));
2423 }
2424 row_num += 1;
2425 }
2426
2427 if lines.next().is_some() {
2429 let placeholder = self.margin();
2430 let padding = str_width(placeholder);
2431 buffer.puts(
2432 row_num,
2433 max_line_num_len.saturating_sub(padding),
2434 placeholder,
2435 Style::LineNumber,
2436 );
2437 } else {
2438 let row = match show_code_change {
2439 DisplaySuggestion::Diff
2440 | DisplaySuggestion::Add
2441 | DisplaySuggestion::Underline => row_num - 1,
2442 DisplaySuggestion::None => row_num,
2443 };
2444 self.draw_col_separator_end(&mut buffer, row, max_line_num_len + 1);
2445 row_num = row + 1;
2446 }
2447 }
2448 if other_suggestions > 0 {
2449 let msg = format!(
2450 "and {} other candidate{}",
2451 other_suggestions,
2452 pluralize!(other_suggestions)
2453 );
2454 buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle);
2455 }
2456
2457 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2458 Ok(())
2459 }
2460
2461 #[instrument(level = "trace", skip(self, args, code, children, suggestions))]
2462 fn emit_messages_default(
2463 &mut self,
2464 level: &Level,
2465 messages: &[(DiagMessage, Style)],
2466 args: &FluentArgs<'_>,
2467 code: &Option<ErrCode>,
2468 span: &MultiSpan,
2469 children: &[Subdiag],
2470 suggestions: &[CodeSuggestion],
2471 emitted_at: Option<&DiagLocation>,
2472 ) {
2473 let max_line_num_len = if self.ui_testing {
2474 ANONYMIZED_LINE_NUM.len()
2475 } else {
2476 let n = self.get_max_line_num(span, children);
2477 num_decimal_digits(n)
2478 };
2479
2480 match self.emit_messages_default_inner(
2481 span,
2482 messages,
2483 args,
2484 code,
2485 level,
2486 max_line_num_len,
2487 false,
2488 emitted_at,
2489 !children.is_empty()
2490 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden),
2491 ) {
2492 Ok(()) => {
2493 if !children.is_empty()
2494 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
2495 {
2496 let mut buffer = StyledBuffer::new();
2497 if !self.short_message {
2498 if let Some(child) = children.iter().next()
2499 && child.span.primary_spans().is_empty()
2500 {
2501 self.draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
2503 } else {
2504 self.draw_col_separator_end(&mut buffer, 0, max_line_num_len + 1);
2506 }
2507 }
2508 if let Err(e) = emit_to_destination(
2509 &buffer.render(),
2510 level,
2511 &mut self.dst,
2512 self.short_message,
2513 ) {
2514 panic!("failed to emit error: {e}")
2515 }
2516 }
2517 if !self.short_message {
2518 for (i, child) in children.iter().enumerate() {
2519 assert!(child.level.can_be_subdiag());
2520 let span = &child.span;
2521 let should_close = match children.get(i + 1) {
2523 Some(c) => !c.span.primary_spans().is_empty(),
2524 None => i + 1 == children.len(),
2525 };
2526 if let Err(err) = self.emit_messages_default_inner(
2527 span,
2528 &child.messages,
2529 args,
2530 &None,
2531 &child.level,
2532 max_line_num_len,
2533 true,
2534 None,
2535 !should_close,
2536 ) {
2537 panic!("failed to emit error: {err}");
2538 }
2539 }
2540 for (i, sugg) in suggestions.iter().enumerate() {
2541 match sugg.style {
2542 SuggestionStyle::CompletelyHidden => {
2543 }
2545 SuggestionStyle::HideCodeAlways => {
2546 if let Err(e) = self.emit_messages_default_inner(
2547 &MultiSpan::new(),
2548 &[(sugg.msg.to_owned(), Style::HeaderMsg)],
2549 args,
2550 &None,
2551 &Level::Help,
2552 max_line_num_len,
2553 true,
2554 None,
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 buffer.puts(
2610 *row_num - 1,
2611 0,
2612 &self.maybe_anonymized(line_num + index),
2613 Style::LineNumber,
2614 );
2615 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2616 let line = normalize_whitespace(
2617 &file_lines.file.get_line(line_to_remove.line_index).unwrap(),
2618 );
2619 buffer.puts(*row_num - 1, max_line_num_len + 3, &line, Style::NoStyle);
2620 *row_num += 1;
2621 }
2622 let last_line_index = file_lines.lines[file_lines.lines.len() - 1].line_index;
2629 let last_line = &file_lines.file.get_line(last_line_index).unwrap();
2630 if last_line != line_to_add {
2631 buffer.puts(
2632 *row_num - 1,
2633 0,
2634 &self.maybe_anonymized(line_num + file_lines.lines.len() - 1),
2635 Style::LineNumber,
2636 );
2637 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2638 buffer.puts(
2639 *row_num - 1,
2640 max_line_num_len + 3,
2641 &normalize_whitespace(last_line),
2642 Style::NoStyle,
2643 );
2644 if !line_to_add.trim().is_empty() {
2645 buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2659 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2660 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2661 } else {
2662 *row_num -= 1;
2663 }
2664 } else {
2665 *row_num -= 2;
2666 }
2667 } else if is_multiline {
2668 buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2669 match &highlight_parts {
2670 [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
2671 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2672 }
2673 [] => {
2674 self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
2676 }
2677 _ => {
2678 let diff = self.diff();
2679 buffer.puts(
2680 *row_num,
2681 max_line_num_len + 1,
2682 &format!("{diff} "),
2683 Style::Addition,
2684 );
2685 }
2686 }
2687 buffer.puts(
2693 *row_num,
2694 max_line_num_len + 3,
2695 &normalize_whitespace(line_to_add),
2696 Style::NoStyle,
2697 );
2698 } else if let DisplaySuggestion::Add = show_code_change {
2699 buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2700 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2701 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2702 } else {
2703 buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2704 self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2705 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2706 }
2707
2708 for &SubstitutionHighlight { start, end } in highlight_parts {
2710 if start != end {
2712 let tabs: usize = line_to_add
2714 .chars()
2715 .take(start)
2716 .map(|ch| match ch {
2717 '\t' => 3,
2718 _ => 0,
2719 })
2720 .sum();
2721 buffer.set_style_range(
2722 *row_num,
2723 max_line_num_len + 3 + start + tabs,
2724 max_line_num_len + 3 + end + tabs,
2725 Style::Addition,
2726 true,
2727 );
2728 }
2729 }
2730 *row_num += 1;
2731 }
2732
2733 fn underline(&self, is_primary: bool) -> UnderlineParts {
2734 match (self.theme, is_primary) {
2759 (OutputTheme::Ascii, true) => UnderlineParts {
2760 style: Style::UnderlinePrimary,
2761 underline: '^',
2762 label_start: '^',
2763 vertical_text_line: '|',
2764 multiline_vertical: '|',
2765 multiline_horizontal: '_',
2766 multiline_whole_line: '/',
2767 multiline_start_down: '^',
2768 bottom_right: '|',
2769 top_left: ' ',
2770 top_right_flat: '^',
2771 bottom_left: '|',
2772 multiline_end_up: '^',
2773 multiline_end_same_line: '^',
2774 multiline_bottom_right_with_text: '|',
2775 },
2776 (OutputTheme::Ascii, false) => UnderlineParts {
2777 style: Style::UnderlineSecondary,
2778 underline: '-',
2779 label_start: '-',
2780 vertical_text_line: '|',
2781 multiline_vertical: '|',
2782 multiline_horizontal: '_',
2783 multiline_whole_line: '/',
2784 multiline_start_down: '-',
2785 bottom_right: '|',
2786 top_left: ' ',
2787 top_right_flat: '-',
2788 bottom_left: '|',
2789 multiline_end_up: '-',
2790 multiline_end_same_line: '-',
2791 multiline_bottom_right_with_text: '|',
2792 },
2793 (OutputTheme::Unicode, true) => UnderlineParts {
2794 style: Style::UnderlinePrimary,
2795 underline: '━',
2796 label_start: '┯',
2797 vertical_text_line: '│',
2798 multiline_vertical: '┃',
2799 multiline_horizontal: '━',
2800 multiline_whole_line: '┏',
2801 multiline_start_down: '╿',
2802 bottom_right: '┙',
2803 top_left: '┏',
2804 top_right_flat: '┛',
2805 bottom_left: '┗',
2806 multiline_end_up: '╿',
2807 multiline_end_same_line: '┛',
2808 multiline_bottom_right_with_text: '┥',
2809 },
2810 (OutputTheme::Unicode, false) => UnderlineParts {
2811 style: Style::UnderlineSecondary,
2812 underline: '─',
2813 label_start: '┬',
2814 vertical_text_line: '│',
2815 multiline_vertical: '│',
2816 multiline_horizontal: '─',
2817 multiline_whole_line: '┌',
2818 multiline_start_down: '│',
2819 bottom_right: '┘',
2820 top_left: '┌',
2821 top_right_flat: '┘',
2822 bottom_left: '└',
2823 multiline_end_up: '│',
2824 multiline_end_same_line: '┘',
2825 multiline_bottom_right_with_text: '┤',
2826 },
2827 }
2828 }
2829
2830 fn col_separator(&self) -> char {
2831 match self.theme {
2832 OutputTheme::Ascii => '|',
2833 OutputTheme::Unicode => '│',
2834 }
2835 }
2836
2837 fn note_separator(&self) -> char {
2838 match self.theme {
2839 OutputTheme::Ascii => '=',
2840 OutputTheme::Unicode => '╰',
2841 }
2842 }
2843
2844 fn multi_suggestion_separator(&self) -> &'static str {
2845 match self.theme {
2846 OutputTheme::Ascii => "|",
2847 OutputTheme::Unicode => "├╴",
2848 }
2849 }
2850
2851 fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2852 let chr = self.col_separator();
2853 buffer.puts(line, col, &format!("{chr} "), Style::LineNumber);
2854 }
2855
2856 fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2857 let chr = self.col_separator();
2858 self.draw_col_separator_no_space_with_style(buffer, chr, line, col, Style::LineNumber);
2859 }
2860
2861 fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2862 match self.theme {
2863 OutputTheme::Ascii => {
2864 self.draw_col_separator_no_space_with_style(
2865 buffer,
2866 '|',
2867 line,
2868 col,
2869 Style::LineNumber,
2870 );
2871 }
2872 OutputTheme::Unicode => {
2873 self.draw_col_separator_no_space_with_style(
2874 buffer,
2875 '╭',
2876 line,
2877 col,
2878 Style::LineNumber,
2879 );
2880 self.draw_col_separator_no_space_with_style(
2881 buffer,
2882 '╴',
2883 line,
2884 col + 1,
2885 Style::LineNumber,
2886 );
2887 }
2888 }
2889 }
2890
2891 fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2892 match self.theme {
2893 OutputTheme::Ascii => {
2894 self.draw_col_separator_no_space_with_style(
2895 buffer,
2896 '|',
2897 line,
2898 col,
2899 Style::LineNumber,
2900 );
2901 }
2902 OutputTheme::Unicode => {
2903 self.draw_col_separator_no_space_with_style(
2904 buffer,
2905 '╰',
2906 line,
2907 col,
2908 Style::LineNumber,
2909 );
2910 self.draw_col_separator_no_space_with_style(
2911 buffer,
2912 '╴',
2913 line,
2914 col + 1,
2915 Style::LineNumber,
2916 );
2917 }
2918 }
2919 }
2920
2921 fn draw_col_separator_no_space_with_style(
2922 &self,
2923 buffer: &mut StyledBuffer,
2924 chr: char,
2925 line: usize,
2926 col: usize,
2927 style: Style,
2928 ) {
2929 buffer.putc(line, col, chr, style);
2930 }
2931
2932 fn draw_range(
2933 &self,
2934 buffer: &mut StyledBuffer,
2935 symbol: char,
2936 line: usize,
2937 col_from: usize,
2938 col_to: usize,
2939 style: Style,
2940 ) {
2941 for col in col_from..col_to {
2942 buffer.putc(line, col, symbol, style);
2943 }
2944 }
2945
2946 fn draw_note_separator(
2947 &self,
2948 buffer: &mut StyledBuffer,
2949 line: usize,
2950 col: usize,
2951 is_cont: bool,
2952 ) {
2953 let chr = match self.theme {
2954 OutputTheme::Ascii => "= ",
2955 OutputTheme::Unicode if is_cont => "├ ",
2956 OutputTheme::Unicode => "╰ ",
2957 };
2958 buffer.puts(line, col, chr, Style::LineNumber);
2959 }
2960
2961 fn draw_multiline_line(
2962 &self,
2963 buffer: &mut StyledBuffer,
2964 line: usize,
2965 offset: usize,
2966 depth: usize,
2967 style: Style,
2968 ) {
2969 let chr = match (style, self.theme) {
2970 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Ascii) => '|',
2971 (_, OutputTheme::Ascii) => '|',
2972 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Unicode) => '┃',
2973 (_, OutputTheme::Unicode) => '│',
2974 };
2975 buffer.putc(line, offset + depth - 1, chr, style);
2976 }
2977
2978 fn file_start(&self) -> &'static str {
2979 match self.theme {
2980 OutputTheme::Ascii => "--> ",
2981 OutputTheme::Unicode => " ╭▸ ",
2982 }
2983 }
2984
2985 fn secondary_file_start(&self) -> &'static str {
2986 match self.theme {
2987 OutputTheme::Ascii => "::: ",
2988 OutputTheme::Unicode => " ⸬ ",
2989 }
2990 }
2991
2992 fn diff(&self) -> char {
2993 match self.theme {
2994 OutputTheme::Ascii => '~',
2995 OutputTheme::Unicode => '±',
2996 }
2997 }
2998
2999 fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
3000 let (column, dots) = match self.theme {
3001 OutputTheme::Ascii => (0, "..."),
3002 OutputTheme::Unicode => (col - 2, "‡"),
3003 };
3004 buffer.puts(line, column, dots, Style::LineNumber);
3005 }
3006
3007 fn margin(&self) -> &'static str {
3008 match self.theme {
3009 OutputTheme::Ascii => "...",
3010 OutputTheme::Unicode => "…",
3011 }
3012 }
3013}
3014
3015#[derive(Debug, Clone, Copy)]
3016struct UnderlineParts {
3017 style: Style,
3018 underline: char,
3019 label_start: char,
3020 vertical_text_line: char,
3021 multiline_vertical: char,
3022 multiline_horizontal: char,
3023 multiline_whole_line: char,
3024 multiline_start_down: char,
3025 bottom_right: char,
3026 top_left: char,
3027 top_right_flat: char,
3028 bottom_left: char,
3029 multiline_end_up: char,
3030 multiline_end_same_line: char,
3031 multiline_bottom_right_with_text: char,
3032}
3033
3034#[derive(Clone, Copy, Debug)]
3035enum DisplaySuggestion {
3036 Underline,
3037 Diff,
3038 None,
3039 Add,
3040}
3041
3042impl FileWithAnnotatedLines {
3043 pub(crate) fn collect_annotations(
3046 emitter: &dyn Emitter,
3047 args: &FluentArgs<'_>,
3048 msp: &MultiSpan,
3049 ) -> Vec<FileWithAnnotatedLines> {
3050 fn add_annotation_to_file(
3051 file_vec: &mut Vec<FileWithAnnotatedLines>,
3052 file: Arc<SourceFile>,
3053 line_index: usize,
3054 ann: Annotation,
3055 ) {
3056 for slot in file_vec.iter_mut() {
3057 if slot.file.name == file.name {
3059 for line_slot in &mut slot.lines {
3061 if line_slot.line_index == line_index {
3062 line_slot.annotations.push(ann);
3063 return;
3064 }
3065 }
3066 slot.lines.push(Line { line_index, annotations: vec![ann] });
3068 slot.lines.sort();
3069 return;
3070 }
3071 }
3072 file_vec.push(FileWithAnnotatedLines {
3074 file,
3075 lines: vec![Line { line_index, annotations: vec![ann] }],
3076 multiline_depth: 0,
3077 });
3078 }
3079
3080 let mut output = vec![];
3081 let mut multiline_annotations = vec![];
3082
3083 if let Some(sm) = emitter.source_map() {
3084 for SpanLabel { span, is_primary, label } in msp.span_labels() {
3085 let span = match (span.is_dummy(), msp.primary_span()) {
3088 (_, None) | (false, _) => span,
3089 (true, Some(span)) => span,
3090 };
3091
3092 let lo = sm.lookup_char_pos(span.lo());
3093 let mut hi = sm.lookup_char_pos(span.hi());
3094
3095 if lo.col_display == hi.col_display && lo.line == hi.line {
3102 hi.col_display += 1;
3103 }
3104
3105 let label = label.as_ref().map(|m| {
3106 normalize_whitespace(
3107 &emitter.translate_message(m, args).map_err(Report::new).unwrap(),
3108 )
3109 });
3110
3111 if lo.line != hi.line {
3112 let ml = MultilineAnnotation {
3113 depth: 1,
3114 line_start: lo.line,
3115 line_end: hi.line,
3116 start_col: AnnotationColumn::from_loc(&lo),
3117 end_col: AnnotationColumn::from_loc(&hi),
3118 is_primary,
3119 label,
3120 overlaps_exactly: false,
3121 };
3122 multiline_annotations.push((lo.file, ml));
3123 } else {
3124 let ann = Annotation {
3125 start_col: AnnotationColumn::from_loc(&lo),
3126 end_col: AnnotationColumn::from_loc(&hi),
3127 is_primary,
3128 label,
3129 annotation_type: AnnotationType::Singleline,
3130 };
3131 add_annotation_to_file(&mut output, lo.file, lo.line, ann);
3132 };
3133 }
3134 }
3135
3136 multiline_annotations.sort_by_key(|(_, ml)| (ml.line_start, usize::MAX - ml.line_end));
3138 for (_, ann) in multiline_annotations.clone() {
3139 for (_, a) in multiline_annotations.iter_mut() {
3140 if !(ann.same_span(a))
3143 && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
3144 {
3145 a.increase_depth();
3146 } else if ann.same_span(a) && &ann != a {
3147 a.overlaps_exactly = true;
3148 } else {
3149 break;
3150 }
3151 }
3152 }
3153
3154 let mut max_depth = 0; for (_, ann) in &multiline_annotations {
3156 max_depth = max(max_depth, ann.depth);
3157 }
3158 for (_, a) in multiline_annotations.iter_mut() {
3160 a.depth = max_depth - a.depth + 1;
3161 }
3162 for (file, ann) in multiline_annotations {
3163 let mut end_ann = ann.as_end();
3164 if !ann.overlaps_exactly {
3165 add_annotation_to_file(
3188 &mut output,
3189 Arc::clone(&file),
3190 ann.line_start,
3191 ann.as_start(),
3192 );
3193 let middle = min(ann.line_start + 4, ann.line_end);
3198 let filter = |s: &str| {
3202 let s = s.trim();
3203 !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
3205 && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
3207 };
3208 let until = (ann.line_start..middle)
3209 .rev()
3210 .filter_map(|line| file.get_line(line - 1).map(|s| (line + 1, s)))
3211 .find(|(_, s)| filter(s))
3212 .map(|(line, _)| line)
3213 .unwrap_or(ann.line_start);
3214 for line in ann.line_start + 1..until {
3215 add_annotation_to_file(&mut output, Arc::clone(&file), line, ann.as_line());
3217 }
3218 let line_end = ann.line_end - 1;
3219 let end_is_empty = file.get_line(line_end - 1).is_some_and(|s| !filter(&s));
3220 if middle < line_end && !end_is_empty {
3221 add_annotation_to_file(&mut output, Arc::clone(&file), line_end, ann.as_line());
3222 }
3223 } else {
3224 end_ann.annotation_type = AnnotationType::Singleline;
3225 }
3226 add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
3227 }
3228 for file_vec in output.iter_mut() {
3229 file_vec.multiline_depth = max_depth;
3230 }
3231 output
3232 }
3233}
3234
3235fn num_decimal_digits(num: usize) -> usize {
3240 #[cfg(target_pointer_width = "64")]
3241 const MAX_DIGITS: usize = 20;
3242
3243 #[cfg(target_pointer_width = "32")]
3244 const MAX_DIGITS: usize = 10;
3245
3246 #[cfg(target_pointer_width = "16")]
3247 const MAX_DIGITS: usize = 5;
3248
3249 let mut lim = 10;
3250 for num_digits in 1..MAX_DIGITS {
3251 if num < lim {
3252 return num_digits;
3253 }
3254 lim = lim.wrapping_mul(10);
3255 }
3256 MAX_DIGITS
3257}
3258
3259const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
3262 ('\0', "␀"),
3266 ('\u{0001}', "␁"),
3267 ('\u{0002}', "␂"),
3268 ('\u{0003}', "␃"),
3269 ('\u{0004}', "␄"),
3270 ('\u{0005}', "␅"),
3271 ('\u{0006}', "␆"),
3272 ('\u{0007}', "␇"),
3273 ('\u{0008}', "␈"),
3274 ('\t', " "), ('\u{000b}', "␋"),
3276 ('\u{000c}', "␌"),
3277 ('\u{000d}', "␍"),
3278 ('\u{000e}', "␎"),
3279 ('\u{000f}', "␏"),
3280 ('\u{0010}', "␐"),
3281 ('\u{0011}', "␑"),
3282 ('\u{0012}', "␒"),
3283 ('\u{0013}', "␓"),
3284 ('\u{0014}', "␔"),
3285 ('\u{0015}', "␕"),
3286 ('\u{0016}', "␖"),
3287 ('\u{0017}', "␗"),
3288 ('\u{0018}', "␘"),
3289 ('\u{0019}', "␙"),
3290 ('\u{001a}', "␚"),
3291 ('\u{001b}', "␛"),
3292 ('\u{001c}', "␜"),
3293 ('\u{001d}', "␝"),
3294 ('\u{001e}', "␞"),
3295 ('\u{001f}', "␟"),
3296 ('\u{007f}', "␡"),
3297 ('\u{200d}', ""), ('\u{202a}', "�"), ('\u{202b}', "�"), ('\u{202c}', "�"), ('\u{202d}', "�"),
3302 ('\u{202e}', "�"),
3303 ('\u{2066}', "�"),
3304 ('\u{2067}', "�"),
3305 ('\u{2068}', "�"),
3306 ('\u{2069}', "�"),
3307];
3308
3309fn normalize_whitespace(s: &str) -> String {
3310 const {
3311 let mut i = 1;
3312 while i < OUTPUT_REPLACEMENTS.len() {
3313 assert!(
3314 OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
3315 "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
3316 and must contain no duplicate entries"
3317 );
3318 i += 1;
3319 }
3320 }
3321 s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
3325 match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
3326 Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
3327 _ => s.push(c),
3328 }
3329 s
3330 })
3331}
3332
3333fn num_overlap(
3334 a_start: usize,
3335 a_end: usize,
3336 b_start: usize,
3337 b_end: usize,
3338 inclusive: bool,
3339) -> bool {
3340 let extra = if inclusive { 1 } else { 0 };
3341 (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
3342}
3343
3344fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
3345 num_overlap(
3346 a1.start_col.display,
3347 a1.end_col.display + padding,
3348 a2.start_col.display,
3349 a2.end_col.display,
3350 false,
3351 )
3352}
3353
3354fn emit_to_destination(
3355 rendered_buffer: &[Vec<StyledString>],
3356 lvl: &Level,
3357 dst: &mut Destination,
3358 short_message: bool,
3359) -> io::Result<()> {
3360 use crate::lock;
3361
3362 let _buffer_lock = lock::acquire_global_lock("rustc_errors");
3375 for (pos, line) in rendered_buffer.iter().enumerate() {
3376 for part in line {
3377 let style = part.style.color_spec(*lvl);
3378 dst.set_color(&style)?;
3379 write!(dst, "{}", part.text)?;
3380 dst.reset()?;
3381 }
3382 if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
3383 writeln!(dst)?;
3384 }
3385 }
3386 dst.flush()?;
3387 Ok(())
3388}
3389
3390pub type Destination = Box<dyn WriteColor + Send>;
3391
3392struct Buffy {
3393 buffer_writer: BufferWriter,
3394 buffer: Buffer,
3395}
3396
3397impl Write for Buffy {
3398 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3399 self.buffer.write(buf)
3400 }
3401
3402 fn flush(&mut self) -> io::Result<()> {
3403 self.buffer_writer.print(&self.buffer)?;
3404 self.buffer.clear();
3405 Ok(())
3406 }
3407}
3408
3409impl Drop for Buffy {
3410 fn drop(&mut self) {
3411 if !self.buffer.is_empty() {
3412 self.flush().unwrap();
3413 panic!("buffers need to be flushed in order to print their contents");
3414 }
3415 }
3416}
3417
3418impl WriteColor for Buffy {
3419 fn supports_color(&self) -> bool {
3420 self.buffer.supports_color()
3421 }
3422
3423 fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
3424 self.buffer.set_color(spec)
3425 }
3426
3427 fn reset(&mut self) -> io::Result<()> {
3428 self.buffer.reset()
3429 }
3430}
3431
3432pub fn stderr_destination(color: ColorConfig) -> Destination {
3433 let choice = color.to_color_choice();
3434 if cfg!(windows) {
3441 Box::new(StandardStream::stderr(choice))
3442 } else {
3443 let buffer_writer = BufferWriter::stderr(choice);
3444 let buffer = buffer_writer.buffer();
3445 Box::new(Buffy { buffer_writer, buffer })
3446 }
3447}
3448
3449const BRIGHT_BLUE: Color = if cfg!(windows) { Color::Cyan } else { Color::Blue };
3453
3454impl Style {
3455 fn color_spec(&self, lvl: Level) -> ColorSpec {
3456 let mut spec = ColorSpec::new();
3457 match self {
3458 Style::Addition => {
3459 spec.set_fg(Some(Color::Green)).set_intense(true);
3460 }
3461 Style::Removal => {
3462 spec.set_fg(Some(Color::Red)).set_intense(true);
3463 }
3464 Style::LineAndColumn => {}
3465 Style::LineNumber => {
3466 spec.set_bold(true);
3467 spec.set_intense(true);
3468 spec.set_fg(Some(BRIGHT_BLUE));
3469 }
3470 Style::Quotation => {}
3471 Style::MainHeaderMsg => {
3472 spec.set_bold(true);
3473 if cfg!(windows) {
3474 spec.set_intense(true).set_fg(Some(Color::White));
3475 }
3476 }
3477 Style::UnderlinePrimary | Style::LabelPrimary => {
3478 spec = lvl.color();
3479 spec.set_bold(true);
3480 }
3481 Style::UnderlineSecondary | Style::LabelSecondary => {
3482 spec.set_bold(true).set_intense(true);
3483 spec.set_fg(Some(BRIGHT_BLUE));
3484 }
3485 Style::HeaderMsg | Style::NoStyle => {}
3486 Style::Level(lvl) => {
3487 spec = lvl.color();
3488 spec.set_bold(true);
3489 }
3490 Style::Highlight => {
3491 spec.set_bold(true).set_fg(Some(Color::Magenta));
3492 }
3493 }
3494 spec
3495 }
3496}
3497
3498pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3500 let found = match sm.span_to_snippet(sp) {
3501 Ok(snippet) => snippet,
3502 Err(e) => {
3503 warn!(error = ?e, "Invalid span {:?}", sp);
3504 return true;
3505 }
3506 };
3507 found != suggested
3508}
3509
3510pub fn is_case_difference(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3512 let found = match sm.span_to_snippet(sp) {
3514 Ok(snippet) => snippet,
3515 Err(e) => {
3516 warn!(error = ?e, "Invalid span {:?}", sp);
3517 return false;
3518 }
3519 };
3520 let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
3521 let confusable = iter::zip(found.chars(), suggested.chars())
3523 .filter(|(f, s)| f != s)
3524 .all(|(f, s)| (ascii_confusables.contains(&f) || ascii_confusables.contains(&s)));
3525 confusable && found.to_lowercase() == suggested.to_lowercase()
3526 && found != suggested
3529}
3530
3531pub(crate) fn should_show_source_code(
3532 ignored_directories: &[String],
3533 sm: &SourceMap,
3534 file: &SourceFile,
3535) -> bool {
3536 if !sm.ensure_source_file_source_present(file) {
3537 return false;
3538 }
3539
3540 let FileName::Real(name) = &file.name else { return true };
3541 name.local_path()
3542 .map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
3543 .unwrap_or(true)
3544}