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