rustc_errors/emitter.rs
1//! The current rustc diagnostics emitter.
2//!
3//! An `Emitter` takes care of generating the output from a `Diag` struct.
4//!
5//! There are various `Emitter` implementations that generate different output formats such as
6//! JSON and human readable output.
7//!
8//! The output types are defined in `rustc_session::config::ErrorOutputType`.
9
10use 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::translation::{Translate, to_fluent_args};
38use crate::{
39 CodeSuggestion, DiagInner, DiagMessage, ErrCode, FluentBundle, LazyFallbackBundle, Level,
40 MultiSpan, Subdiag, SubstitutionHighlight, SuggestionStyle, TerminalUrl,
41};
42
43/// Default column width, used in tests and when terminal dimensions cannot be determined.
44const DEFAULT_COLUMN_WIDTH: usize = 140;
45
46/// Describes the way the content of the `rendered` field of the json output is generated
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum HumanReadableErrorType {
49 Default,
50 Unicode,
51 AnnotateSnippet,
52 Short,
53}
54
55impl HumanReadableErrorType {
56 pub fn short(&self) -> bool {
57 *self == HumanReadableErrorType::Short
58 }
59}
60
61#[derive(Clone, Copy, Debug)]
62struct Margin {
63 /// The available whitespace in the left that can be consumed when centering.
64 pub whitespace_left: usize,
65 /// The column of the beginning of leftmost span.
66 pub span_left: usize,
67 /// The column of the end of rightmost span.
68 pub span_right: usize,
69 /// The beginning of the line to be displayed.
70 pub computed_left: usize,
71 /// The end of the line to be displayed.
72 pub computed_right: usize,
73 /// The current width of the terminal. Uses value of `DEFAULT_COLUMN_WIDTH` constant by default
74 /// and in tests.
75 pub column_width: usize,
76 /// The end column of a span label, including the span. Doesn't account for labels not in the
77 /// same line as the span.
78 pub label_right: usize,
79}
80
81impl Margin {
82 fn new(
83 whitespace_left: usize,
84 span_left: usize,
85 span_right: usize,
86 label_right: usize,
87 column_width: usize,
88 max_line_len: usize,
89 ) -> Self {
90 // The 6 is padding to give a bit of room for `...` when displaying:
91 // ```
92 // error: message
93 // --> file.rs:16:58
94 // |
95 // 16 | ... fn foo(self) -> Self::Bar {
96 // | ^^^^^^^^^
97 // ```
98
99 let mut m = Margin {
100 whitespace_left: whitespace_left.saturating_sub(6),
101 span_left: span_left.saturating_sub(6),
102 span_right: span_right + 6,
103 computed_left: 0,
104 computed_right: 0,
105 column_width,
106 label_right: label_right + 6,
107 };
108 m.compute(max_line_len);
109 m
110 }
111
112 fn was_cut_left(&self) -> bool {
113 self.computed_left > 0
114 }
115
116 fn was_cut_right(&self, line_len: usize) -> bool {
117 let right =
118 if self.computed_right == self.span_right || self.computed_right == self.label_right {
119 // FIXME: This comment refers to the only callsite of this method.
120 // Rephrase it or refactor it, so it can stand on its own.
121 // Account for the "..." padding given above. Otherwise we end up with code lines
122 // that do fit but end in "..." as if they were trimmed.
123 // FIXME: Don't hard-code this offset. Is this meant to represent
124 // `2 * str_width(self.margin())`?
125 self.computed_right - 6
126 } else {
127 self.computed_right
128 };
129 right < line_len && self.computed_left + self.column_width < line_len
130 }
131
132 fn compute(&mut self, max_line_len: usize) {
133 // When there's a lot of whitespace (>20), we want to trim it as it is useless.
134 self.computed_left = if self.whitespace_left > 20 {
135 self.whitespace_left - 16 // We want some padding.
136 } else {
137 0
138 };
139 // We want to show as much as possible, max_line_len is the rightmost boundary for the
140 // relevant code.
141 self.computed_right = max(max_line_len, self.computed_left);
142
143 if self.computed_right - self.computed_left > self.column_width {
144 // Trimming only whitespace isn't enough, let's get craftier.
145 if self.label_right - self.whitespace_left <= self.column_width {
146 // Attempt to fit the code window only trimming whitespace.
147 self.computed_left = self.whitespace_left;
148 self.computed_right = self.computed_left + self.column_width;
149 } else if self.label_right - self.span_left <= self.column_width {
150 // Attempt to fit the code window considering only the spans and labels.
151 let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
152 self.computed_left = self.span_left.saturating_sub(padding_left);
153 self.computed_right = self.computed_left + self.column_width;
154 } else if self.span_right - self.span_left <= self.column_width {
155 // Attempt to fit the code window considering the spans and labels plus padding.
156 let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
157 self.computed_left = self.span_left.saturating_sub(padding_left);
158 self.computed_right = self.computed_left + self.column_width;
159 } else {
160 // Mostly give up but still don't show the full line.
161 self.computed_left = self.span_left;
162 self.computed_right = self.span_right;
163 }
164 }
165 }
166
167 fn left(&self, line_len: usize) -> usize {
168 min(self.computed_left, line_len)
169 }
170
171 fn right(&self, line_len: usize) -> usize {
172 if line_len.saturating_sub(self.computed_left) <= self.column_width {
173 line_len
174 } else {
175 min(line_len, self.computed_right)
176 }
177 }
178}
179
180const ANONYMIZED_LINE_NUM: &str = "LL";
181
182pub type DynEmitter = dyn Emitter + DynSend;
183
184/// Emitter trait for emitting errors.
185pub trait Emitter: Translate {
186 /// Emit a structured diagnostic.
187 fn emit_diagnostic(&mut self, diag: DiagInner, registry: &Registry);
188
189 /// Emit a notification that an artifact has been output.
190 /// Currently only supported for the JSON format.
191 fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
192
193 /// Emit a report about future breakage.
194 /// Currently only supported for the JSON format.
195 fn emit_future_breakage_report(&mut self, _diags: Vec<DiagInner>, _registry: &Registry) {}
196
197 /// Emit list of unused externs.
198 /// Currently only supported for the JSON format.
199 fn emit_unused_externs(
200 &mut self,
201 _lint_level: rustc_lint_defs::Level,
202 _unused_externs: &[&str],
203 ) {
204 }
205
206 /// Checks if should show explanations about "rustc --explain"
207 fn should_show_explain(&self) -> bool {
208 true
209 }
210
211 /// Checks if we can use colors in the current output stream.
212 fn supports_color(&self) -> bool {
213 false
214 }
215
216 fn source_map(&self) -> Option<&SourceMap>;
217
218 /// Formats the substitutions of the primary_span
219 ///
220 /// There are a lot of conditions to this method, but in short:
221 ///
222 /// * If the current `DiagInner` has only one visible `CodeSuggestion`,
223 /// we format the `help` suggestion depending on the content of the
224 /// substitutions. In that case, we modify the span and clear the
225 /// suggestions.
226 ///
227 /// * If the current `DiagInner` has multiple suggestions,
228 /// we leave `primary_span` and the suggestions untouched.
229 fn primary_span_formatted(
230 &mut self,
231 primary_span: &mut MultiSpan,
232 suggestions: &mut Vec<CodeSuggestion>,
233 fluent_args: &FluentArgs<'_>,
234 ) {
235 if let Some((sugg, rest)) = suggestions.split_first() {
236 let msg = self.translate_message(&sugg.msg, fluent_args).map_err(Report::new).unwrap();
237 if rest.is_empty()
238 // ^ if there is only one suggestion
239 // don't display multi-suggestions as labels
240 && let [substitution] = sugg.substitutions.as_slice()
241 // don't display multipart suggestions as labels
242 && let [part] = substitution.parts.as_slice()
243 // don't display long messages as labels
244 && msg.split_whitespace().count() < 10
245 // don't display multiline suggestions as labels
246 && !part.snippet.contains('\n')
247 && ![
248 // when this style is set we want the suggestion to be a message, not inline
249 SuggestionStyle::HideCodeAlways,
250 // trivial suggestion for tooling's sake, never shown
251 SuggestionStyle::CompletelyHidden,
252 // subtle suggestion, never shown inline
253 SuggestionStyle::ShowAlways,
254 ].contains(&sugg.style)
255 {
256 let snippet = part.snippet.trim();
257 let msg = if snippet.is_empty() || sugg.style.hide_inline() {
258 // This substitution is only removal OR we explicitly don't want to show the
259 // code inline (`hide_inline`). Therefore, we don't show the substitution.
260 format!("help: {msg}")
261 } else {
262 // Show the default suggestion text with the substitution
263 format!(
264 "help: {}{}: `{}`",
265 msg,
266 if self
267 .source_map()
268 .is_some_and(|sm| is_case_difference(sm, snippet, part.span,))
269 {
270 " (notice the capitalization)"
271 } else {
272 ""
273 },
274 snippet,
275 )
276 };
277 primary_span.push_span_label(part.span, msg);
278
279 // We return only the modified primary_span
280 suggestions.clear();
281 } else {
282 // if there are multiple suggestions, print them all in full
283 // to be consistent. We could try to figure out if we can
284 // make one (or the first one) inline, but that would give
285 // undue importance to a semi-random suggestion
286 }
287 } else {
288 // do nothing
289 }
290 }
291
292 fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
293 &self,
294 span: &mut MultiSpan,
295 children: &mut Vec<Subdiag>,
296 level: &Level,
297 backtrace: bool,
298 ) {
299 // Check for spans in macros, before `fix_multispans_in_extern_macros`
300 // has a chance to replace them.
301 let has_macro_spans: Vec<_> = iter::once(&*span)
302 .chain(children.iter().map(|child| &child.span))
303 .flat_map(|span| span.primary_spans())
304 .flat_map(|sp| sp.macro_backtrace())
305 .filter_map(|expn_data| {
306 match expn_data.kind {
307 ExpnKind::Root => None,
308
309 // Skip past non-macro entries, just in case there
310 // are some which do actually involve macros.
311 ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
312
313 ExpnKind::Macro(macro_kind, name) => Some((macro_kind, name)),
314 }
315 })
316 .collect();
317
318 if !backtrace {
319 self.fix_multispans_in_extern_macros(span, children);
320 }
321
322 self.render_multispans_macro_backtrace(span, children, backtrace);
323
324 if !backtrace {
325 if let Some((macro_kind, name)) = has_macro_spans.first() {
326 // Mark the actual macro this originates from
327 let and_then = if let Some((macro_kind, last_name)) = has_macro_spans.last()
328 && last_name != name
329 {
330 let descr = macro_kind.descr();
331 format!(" which comes from the expansion of the {descr} `{last_name}`",)
332 } else {
333 "".to_string()
334 };
335
336 let descr = macro_kind.descr();
337 let msg = format!(
338 "this {level} originates in the {descr} `{name}`{and_then} \
339 (in Nightly builds, run with -Z macro-backtrace for more info)",
340 );
341
342 children.push(Subdiag {
343 level: Level::Note,
344 messages: vec![(DiagMessage::from(msg), Style::NoStyle)],
345 span: MultiSpan::new(),
346 });
347 }
348 }
349 }
350
351 fn render_multispans_macro_backtrace(
352 &self,
353 span: &mut MultiSpan,
354 children: &mut Vec<Subdiag>,
355 backtrace: bool,
356 ) {
357 for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
358 self.render_multispan_macro_backtrace(span, backtrace);
359 }
360 }
361
362 fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
363 let mut new_labels = FxIndexSet::default();
364
365 for &sp in span.primary_spans() {
366 if sp.is_dummy() {
367 continue;
368 }
369
370 // FIXME(eddyb) use `retain` on `macro_backtrace` to remove all the
371 // entries we don't want to print, to make sure the indices being
372 // printed are contiguous (or omitted if there's only one entry).
373 let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
374 for (i, trace) in macro_backtrace.iter().rev().enumerate() {
375 if trace.def_site.is_dummy() {
376 continue;
377 }
378
379 if always_backtrace {
380 new_labels.insert((
381 trace.def_site,
382 format!(
383 "in this expansion of `{}`{}",
384 trace.kind.descr(),
385 if macro_backtrace.len() > 1 {
386 // if macro_backtrace.len() == 1 it'll be
387 // pointed at by "in this macro invocation"
388 format!(" (#{})", i + 1)
389 } else {
390 String::new()
391 },
392 ),
393 ));
394 }
395
396 // Don't add a label on the call site if the diagnostic itself
397 // already points to (a part of) that call site, as the label
398 // is meant for showing the relevant invocation when the actual
399 // diagnostic is pointing to some part of macro definition.
400 //
401 // This also handles the case where an external span got replaced
402 // with the call site span by `fix_multispans_in_extern_macros`.
403 //
404 // NB: `-Zmacro-backtrace` overrides this, for uniformity, as the
405 // "in this expansion of" label above is always added in that mode,
406 // and it needs an "in this macro invocation" label to match that.
407 let redundant_span = trace.call_site.contains(sp);
408
409 if !redundant_span || always_backtrace {
410 let msg: Cow<'static, _> = match trace.kind {
411 ExpnKind::Macro(MacroKind::Attr, _) => {
412 "this procedural macro expansion".into()
413 }
414 ExpnKind::Macro(MacroKind::Derive, _) => {
415 "this derive macro expansion".into()
416 }
417 ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
418 ExpnKind::Root => "the crate root".into(),
419 ExpnKind::AstPass(kind) => kind.descr().into(),
420 ExpnKind::Desugaring(kind) => {
421 format!("this {} desugaring", kind.descr()).into()
422 }
423 };
424 new_labels.insert((
425 trace.call_site,
426 format!(
427 "in {}{}",
428 msg,
429 if macro_backtrace.len() > 1 && always_backtrace {
430 // only specify order when the macro
431 // backtrace is multiple levels deep
432 format!(" (#{})", i + 1)
433 } else {
434 String::new()
435 },
436 ),
437 ));
438 }
439 if !always_backtrace {
440 break;
441 }
442 }
443 }
444
445 for (label_span, label_text) in new_labels {
446 span.push_span_label(label_span, label_text);
447 }
448 }
449
450 // This does a small "fix" for multispans by looking to see if it can find any that
451 // point directly at external macros. Since these are often difficult to read,
452 // this will change the span to point at the use site.
453 fn fix_multispans_in_extern_macros(&self, span: &mut MultiSpan, children: &mut Vec<Subdiag>) {
454 debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
455 self.fix_multispan_in_extern_macros(span);
456 for child in children.iter_mut() {
457 self.fix_multispan_in_extern_macros(&mut child.span);
458 }
459 debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
460 }
461
462 // This "fixes" MultiSpans that contain `Span`s pointing to locations inside of external macros.
463 // Since these locations are often difficult to read,
464 // we move these spans from the external macros to their corresponding use site.
465 fn fix_multispan_in_extern_macros(&self, span: &mut MultiSpan) {
466 let Some(source_map) = self.source_map() else { return };
467 // First, find all the spans in external macros and point instead at their use site.
468 let replacements: Vec<(Span, Span)> = span
469 .primary_spans()
470 .iter()
471 .copied()
472 .chain(span.span_labels().iter().map(|sp_label| sp_label.span))
473 .filter_map(|sp| {
474 if !sp.is_dummy() && source_map.is_imported(sp) {
475 let maybe_callsite = sp.source_callsite();
476 if sp != maybe_callsite {
477 return Some((sp, maybe_callsite));
478 }
479 }
480 None
481 })
482 .collect();
483
484 // After we have them, make sure we replace these 'bad' def sites with their use sites.
485 for (from, to) in replacements {
486 span.replace(from, to);
487 }
488 }
489}
490
491impl Translate for HumanEmitter {
492 fn fluent_bundle(&self) -> Option<&FluentBundle> {
493 self.fluent_bundle.as_deref()
494 }
495
496 fn fallback_fluent_bundle(&self) -> &FluentBundle {
497 &self.fallback_bundle
498 }
499}
500
501impl Emitter for HumanEmitter {
502 fn source_map(&self) -> Option<&SourceMap> {
503 self.sm.as_deref()
504 }
505
506 fn emit_diagnostic(&mut self, mut diag: DiagInner, _registry: &Registry) {
507 let fluent_args = to_fluent_args(diag.args.iter());
508
509 let mut suggestions = diag.suggestions.unwrap_tag();
510 self.primary_span_formatted(&mut diag.span, &mut suggestions, &fluent_args);
511
512 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
513 &mut diag.span,
514 &mut diag.children,
515 &diag.level,
516 self.macro_backtrace,
517 );
518
519 self.emit_messages_default(
520 &diag.level,
521 &diag.messages,
522 &fluent_args,
523 &diag.code,
524 &diag.span,
525 &diag.children,
526 &suggestions,
527 self.track_diagnostics.then_some(&diag.emitted_at),
528 );
529 }
530
531 fn should_show_explain(&self) -> bool {
532 !self.short_message
533 }
534
535 fn supports_color(&self) -> bool {
536 self.dst.supports_color()
537 }
538}
539
540/// An emitter that does nothing when emitting a non-fatal diagnostic.
541/// Fatal diagnostics are forwarded to `fatal_emitter` to avoid silent
542/// failures of rustc, as witnessed e.g. in issue #89358.
543pub struct SilentEmitter {
544 pub fatal_emitter: Box<dyn Emitter + DynSend>,
545 pub fatal_note: Option<String>,
546 pub emit_fatal_diagnostic: bool,
547}
548
549impl Translate for SilentEmitter {
550 fn fluent_bundle(&self) -> Option<&FluentBundle> {
551 None
552 }
553
554 fn fallback_fluent_bundle(&self) -> &FluentBundle {
555 self.fatal_emitter.fallback_fluent_bundle()
556 }
557}
558
559impl Emitter for SilentEmitter {
560 fn source_map(&self) -> Option<&SourceMap> {
561 None
562 }
563
564 fn emit_diagnostic(&mut self, mut diag: DiagInner, registry: &Registry) {
565 if self.emit_fatal_diagnostic && diag.level == Level::Fatal {
566 if let Some(fatal_note) = &self.fatal_note {
567 diag.sub(Level::Note, fatal_note.clone(), MultiSpan::new());
568 }
569 self.fatal_emitter.emit_diagnostic(diag, registry);
570 }
571 }
572}
573
574/// Maximum number of suggestions to be shown
575///
576/// Arbitrary, but taken from trait import suggestion limit
577pub const MAX_SUGGESTIONS: usize = 4;
578
579#[derive(Clone, Copy, Debug, PartialEq, Eq)]
580pub enum ColorConfig {
581 Auto,
582 Always,
583 Never,
584}
585
586impl ColorConfig {
587 pub fn to_color_choice(self) -> ColorChoice {
588 match self {
589 ColorConfig::Always => {
590 if io::stderr().is_terminal() {
591 ColorChoice::Always
592 } else {
593 ColorChoice::AlwaysAnsi
594 }
595 }
596 ColorConfig::Never => ColorChoice::Never,
597 ColorConfig::Auto if io::stderr().is_terminal() => ColorChoice::Auto,
598 ColorConfig::Auto => ColorChoice::Never,
599 }
600 }
601}
602
603#[derive(Debug, Clone, Copy, PartialEq, Eq)]
604pub enum OutputTheme {
605 Ascii,
606 Unicode,
607}
608
609/// Handles the writing of `HumanReadableErrorType::Default` and `HumanReadableErrorType::Short`
610#[derive(Setters)]
611pub struct HumanEmitter {
612 #[setters(skip)]
613 dst: IntoDynSyncSend<Destination>,
614 sm: Option<Arc<SourceMap>>,
615 fluent_bundle: Option<Arc<FluentBundle>>,
616 #[setters(skip)]
617 fallback_bundle: LazyFallbackBundle,
618 short_message: bool,
619 teach: bool,
620 ui_testing: bool,
621 ignored_directories_in_source_blocks: Vec<String>,
622 diagnostic_width: Option<usize>,
623
624 macro_backtrace: bool,
625 track_diagnostics: bool,
626 terminal_url: TerminalUrl,
627 theme: OutputTheme,
628}
629
630#[derive(Debug)]
631pub(crate) struct FileWithAnnotatedLines {
632 pub(crate) file: Arc<SourceFile>,
633 pub(crate) lines: Vec<Line>,
634 multiline_depth: usize,
635}
636
637impl HumanEmitter {
638 pub fn new(dst: Destination, fallback_bundle: LazyFallbackBundle) -> HumanEmitter {
639 HumanEmitter {
640 dst: IntoDynSyncSend(dst),
641 sm: None,
642 fluent_bundle: None,
643 fallback_bundle,
644 short_message: false,
645 teach: false,
646 ui_testing: false,
647 ignored_directories_in_source_blocks: Vec::new(),
648 diagnostic_width: None,
649 macro_backtrace: false,
650 track_diagnostics: false,
651 terminal_url: TerminalUrl::No,
652 theme: OutputTheme::Ascii,
653 }
654 }
655
656 fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
657 if self.ui_testing {
658 Cow::Borrowed(ANONYMIZED_LINE_NUM)
659 } else {
660 Cow::Owned(line_num.to_string())
661 }
662 }
663
664 fn draw_line(
665 &self,
666 buffer: &mut StyledBuffer,
667 source_string: &str,
668 line_index: usize,
669 line_offset: usize,
670 width_offset: usize,
671 code_offset: usize,
672 margin: Margin,
673 ) {
674 // Tabs are assumed to have been replaced by spaces in calling code.
675 debug_assert!(!source_string.contains('\t'));
676 let line_len = source_string.len();
677 // Create the source line we will highlight.
678 let left = margin.left(line_len);
679 let right = margin.right(line_len);
680 // FIXME: The following code looks fishy. See #132860.
681 // On long lines, we strip the source line, accounting for unicode.
682 let mut taken = 0;
683 let code: String = source_string
684 .chars()
685 .skip(left)
686 .take_while(|ch| {
687 // Make sure that the trimming on the right will fall within the terminal width.
688 let next = char_width(*ch);
689 if taken + next > right - left {
690 return false;
691 }
692 taken += next;
693 true
694 })
695 .collect();
696 buffer.puts(line_offset, code_offset, &code, Style::Quotation);
697 let placeholder = self.margin();
698 if margin.was_cut_left() {
699 // We have stripped some code/whitespace from the beginning, make it clear.
700 buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber);
701 }
702 if margin.was_cut_right(line_len) {
703 let padding = str_width(placeholder);
704 // We have stripped some code after the rightmost span end, make it clear we did so.
705 buffer.puts(line_offset, code_offset + taken - padding, placeholder, Style::LineNumber);
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 }
711
712 #[instrument(level = "trace", skip(self), ret)]
713 fn render_source_line(
714 &self,
715 buffer: &mut StyledBuffer,
716 file: Arc<SourceFile>,
717 line: &Line,
718 width_offset: usize,
719 code_offset: usize,
720 margin: Margin,
721 close_window: bool,
722 ) -> Vec<(usize, Style)> {
723 // Draw:
724 //
725 // LL | ... code ...
726 // | ^^-^ span label
727 // | |
728 // | secondary span label
729 //
730 // ^^ ^ ^^^ ^^^^ ^^^ we don't care about code too far to the right of a span, we trim it
731 // | | | |
732 // | | | actual code found in your source code and the spans we use to mark it
733 // | | when there's too much wasted space to the left, trim it
734 // | vertical divider between the column number and the code
735 // column number
736
737 if line.line_index == 0 {
738 return Vec::new();
739 }
740
741 let source_string = match file.get_line(line.line_index - 1) {
742 Some(s) => normalize_whitespace(&s),
743 None => return Vec::new(),
744 };
745 trace!(?source_string);
746
747 let line_offset = buffer.num_lines();
748
749 // Left trim
750 let left = margin.left(source_string.len());
751
752 // FIXME: This looks fishy. See #132860.
753 // Account for unicode characters of width !=0 that were removed.
754 let left = source_string.chars().take(left).map(|ch| char_width(ch)).sum();
755
756 self.draw_line(
757 buffer,
758 &source_string,
759 line.line_index,
760 line_offset,
761 width_offset,
762 code_offset,
763 margin,
764 );
765
766 // Special case when there's only one annotation involved, it is the start of a multiline
767 // span and there's no text at the beginning of the code line. Instead of doing the whole
768 // graph:
769 //
770 // 2 | fn foo() {
771 // | _^
772 // 3 | |
773 // 4 | | }
774 // | |_^ test
775 //
776 // we simplify the output to:
777 //
778 // 2 | / fn foo() {
779 // 3 | |
780 // 4 | | }
781 // | |_^ test
782 let mut buffer_ops = vec![];
783 let mut annotations = vec![];
784 let mut short_start = true;
785 for ann in &line.annotations {
786 if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
787 if source_string.chars().take(ann.start_col.display).all(|c| c.is_whitespace()) {
788 let uline = self.underline(ann.is_primary);
789 let chr = uline.multiline_whole_line;
790 annotations.push((depth, uline.style));
791 buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style));
792 } else {
793 short_start = false;
794 break;
795 }
796 } else if let AnnotationType::MultilineLine(_) = ann.annotation_type {
797 } else {
798 short_start = false;
799 break;
800 }
801 }
802 if short_start {
803 for (y, x, c, s) in buffer_ops {
804 buffer.putc(y, x, c, s);
805 }
806 return annotations;
807 }
808
809 // We want to display like this:
810 //
811 // vec.push(vec.pop().unwrap());
812 // --- ^^^ - previous borrow ends here
813 // | |
814 // | error occurs here
815 // previous borrow of `vec` occurs here
816 //
817 // But there are some weird edge cases to be aware of:
818 //
819 // vec.push(vec.pop().unwrap());
820 // -------- - previous borrow ends here
821 // ||
822 // |this makes no sense
823 // previous borrow of `vec` occurs here
824 //
825 // For this reason, we group the lines into "highlight lines"
826 // and "annotations lines", where the highlight lines have the `^`.
827
828 // Sort the annotations by (start, end col)
829 // The labels are reversed, sort and then reversed again.
830 // Consider a list of annotations (A1, A2, C1, C2, B1, B2) where
831 // the letter signifies the span. Here we are only sorting by the
832 // span and hence, the order of the elements with the same span will
833 // not change. On reversing the ordering (|a, b| but b.cmp(a)), you get
834 // (C1, C2, B1, B2, A1, A2). All the elements with the same span are
835 // still ordered first to last, but all the elements with different
836 // spans are ordered by their spans in last to first order. Last to
837 // first order is important, because the jiggly lines and | are on
838 // the left, so the rightmost span needs to be rendered first,
839 // otherwise the lines would end up needing to go over a message.
840
841 let mut annotations = line.annotations.clone();
842 annotations.sort_by_key(|a| Reverse(a.start_col));
843
844 // First, figure out where each label will be positioned.
845 //
846 // In the case where you have the following annotations:
847 //
848 // vec.push(vec.pop().unwrap());
849 // -------- - previous borrow ends here [C]
850 // ||
851 // |this makes no sense [B]
852 // previous borrow of `vec` occurs here [A]
853 //
854 // `annotations_position` will hold [(2, A), (1, B), (0, C)].
855 //
856 // We try, when possible, to stick the rightmost annotation at the end
857 // of the highlight line:
858 //
859 // vec.push(vec.pop().unwrap());
860 // --- --- - previous borrow ends here
861 //
862 // But sometimes that's not possible because one of the other
863 // annotations overlaps it. For example, from the test
864 // `span_overlap_label`, we have the following annotations
865 // (written on distinct lines for clarity):
866 //
867 // fn foo(x: u32) {
868 // --------------
869 // -
870 //
871 // In this case, we can't stick the rightmost-most label on
872 // the highlight line, or we would get:
873 //
874 // fn foo(x: u32) {
875 // -------- x_span
876 // |
877 // fn_span
878 //
879 // which is totally weird. Instead we want:
880 //
881 // fn foo(x: u32) {
882 // --------------
883 // | |
884 // | x_span
885 // fn_span
886 //
887 // which is...less weird, at least. In fact, in general, if
888 // the rightmost span overlaps with any other span, we should
889 // use the "hang below" version, so we can at least make it
890 // clear where the span *starts*. There's an exception for this
891 // logic, when the labels do not have a message:
892 //
893 // fn foo(x: u32) {
894 // --------------
895 // |
896 // x_span
897 //
898 // instead of:
899 //
900 // fn foo(x: u32) {
901 // --------------
902 // | |
903 // | x_span
904 // <EMPTY LINE>
905 //
906 let mut annotations_position = vec![];
907 let mut line_len: usize = 0;
908 let mut p = 0;
909 for (i, annotation) in annotations.iter().enumerate() {
910 for (j, next) in annotations.iter().enumerate() {
911 if overlaps(next, annotation, 0) // This label overlaps with another one and both
912 && annotation.has_label() // take space (they have text and are not
913 && j > i // multiline lines).
914 && p == 0
915 // We're currently on the first line, move the label one line down
916 {
917 // If we're overlapping with an un-labelled annotation with the same span
918 // we can just merge them in the output
919 if next.start_col == annotation.start_col
920 && next.end_col == annotation.end_col
921 && !next.has_label()
922 {
923 continue;
924 }
925
926 // This annotation needs a new line in the output.
927 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) // Do not allow two labels to be in the same
936 // line if they overlap including padding, to
937 // avoid situations like:
938 //
939 // fn foo(x: u32) {
940 // -------^------
941 // | |
942 // fn_spanx_span
943 //
944 && annotation.has_label() // Both labels must have some text, otherwise
945 && next.has_label()) // they are not overlapping.
946 // Do not add a new line if this annotation
947 // or the next are vertical line placeholders.
948 || (annotation.takes_space() // If either this or the next annotation is
949 && next.has_label()) // multiline start/end, move it to a new line
950 || (annotation.has_label() // so as not to overlap the horizontal lines.
951 && 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 // Avoid #42595.
958 {
959 // This annotation needs a new line in the output.
960 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 there are no annotations or the only annotations on this line are
973 // MultilineLine, then there's only code being shown, stop processing.
974 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 // Special case the following, so that we minimize overlapping multiline spans.
984 //
985 // 3 │ X0 Y0 Z0
986 // │ ┏━━━━━┛ │ │ < We are writing these lines
987 // │ ┃┌───────┘ │ < by reverting the "depth" of
988 // │ ┃│┌─────────┘ < their multiline spans.
989 // 4 │ ┃││ X1 Y1 Z1
990 // 5 │ ┃││ X2 Y2 Z2
991 // │ ┃│└────╿──│──┘ `Z` label
992 // │ ┃└─────│──┤
993 // │ ┗━━━━━━┥ `Y` is a good letter too
994 // ╰╴ `X` is a good letter
995 for (pos, _) in &mut annotations_position {
996 *pos = max_pos - *pos;
997 }
998 // We know then that we don't need an additional line for the span label, saving us
999 // one line of vertical space.
1000 line_len = line_len.saturating_sub(1);
1001 }
1002
1003 // Write the column separator.
1004 //
1005 // After this we will have:
1006 //
1007 // 2 | fn foo() {
1008 // |
1009 // |
1010 // |
1011 // 3 |
1012 // 4 | }
1013 // |
1014 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 // Write the horizontal lines for multiline annotations
1022 // (only the first and last lines need this).
1023 //
1024 // After this we will have:
1025 //
1026 // 2 | fn foo() {
1027 // | __________
1028 // |
1029 // |
1030 // 3 |
1031 // 4 | }
1032 // | _
1033 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 self.draw_range(
1039 buffer,
1040 underline.multiline_horizontal,
1041 line_offset + pos,
1042 width_offset + depth,
1043 (code_offset + annotation.start_col.display).saturating_sub(left),
1044 underline.style,
1045 );
1046 }
1047 _ if self.teach => {
1048 buffer.set_style_range(
1049 line_offset,
1050 (code_offset + annotation.start_col.display).saturating_sub(left),
1051 (code_offset + annotation.end_col.display).saturating_sub(left),
1052 underline.style,
1053 annotation.is_primary,
1054 );
1055 }
1056 _ => {}
1057 }
1058 }
1059
1060 // Write the vertical lines for labels that are on a different line as the underline.
1061 //
1062 // After this we will have:
1063 //
1064 // 2 | fn foo() {
1065 // | __________
1066 // | | |
1067 // | |
1068 // 3 | |
1069 // 4 | | }
1070 // | |_
1071 for &(pos, annotation) in &annotations_position {
1072 let underline = self.underline(annotation.is_primary);
1073 let pos = pos + 1;
1074
1075 if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
1076 for p in line_offset + 1..=line_offset + pos {
1077 buffer.putc(
1078 p,
1079 (code_offset + annotation.start_col.display).saturating_sub(left),
1080 match annotation.annotation_type {
1081 AnnotationType::MultilineLine(_) => underline.multiline_vertical,
1082 _ => underline.vertical_text_line,
1083 },
1084 underline.style,
1085 );
1086 }
1087 if let AnnotationType::MultilineStart(_) = annotation.annotation_type {
1088 buffer.putc(
1089 line_offset + pos,
1090 (code_offset + annotation.start_col.display).saturating_sub(left),
1091 underline.bottom_right,
1092 underline.style,
1093 );
1094 }
1095 if let AnnotationType::MultilineEnd(_) = annotation.annotation_type
1096 && annotation.has_label()
1097 {
1098 buffer.putc(
1099 line_offset + pos,
1100 (code_offset + annotation.start_col.display).saturating_sub(left),
1101 underline.multiline_bottom_right_with_text,
1102 underline.style,
1103 );
1104 }
1105 }
1106 match annotation.annotation_type {
1107 AnnotationType::MultilineStart(depth) => {
1108 buffer.putc(
1109 line_offset + pos,
1110 width_offset + depth - 1,
1111 underline.top_left,
1112 underline.style,
1113 );
1114 for p in line_offset + pos + 1..line_offset + line_len + 2 {
1115 buffer.putc(
1116 p,
1117 width_offset + depth - 1,
1118 underline.multiline_vertical,
1119 underline.style,
1120 );
1121 }
1122 }
1123 AnnotationType::MultilineEnd(depth) => {
1124 for p in line_offset..line_offset + pos {
1125 buffer.putc(
1126 p,
1127 width_offset + depth - 1,
1128 underline.multiline_vertical,
1129 underline.style,
1130 );
1131 }
1132 buffer.putc(
1133 line_offset + pos,
1134 width_offset + depth - 1,
1135 underline.bottom_left,
1136 underline.style,
1137 );
1138 }
1139 _ => (),
1140 }
1141 }
1142
1143 // Write the labels on the annotations that actually have a label.
1144 //
1145 // After this we will have:
1146 //
1147 // 2 | fn foo() {
1148 // | __________
1149 // | |
1150 // | something about `foo`
1151 // 3 |
1152 // 4 | }
1153 // | _ test
1154 for &(pos, annotation) in &annotations_position {
1155 let style =
1156 if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary };
1157 let (pos, col) = if pos == 0 {
1158 if annotation.end_col.display == 0 {
1159 (pos + 1, (annotation.end_col.display + 2).saturating_sub(left))
1160 } else {
1161 (pos + 1, (annotation.end_col.display + 1).saturating_sub(left))
1162 }
1163 } else {
1164 (pos + 2, annotation.start_col.display.saturating_sub(left))
1165 };
1166 if let Some(ref label) = annotation.label {
1167 buffer.puts(line_offset + pos, code_offset + col, label, style);
1168 }
1169 }
1170
1171 // Sort from biggest span to smallest span so that smaller spans are
1172 // represented in the output:
1173 //
1174 // x | fn foo()
1175 // | ^^^---^^
1176 // | | |
1177 // | | something about `foo`
1178 // | something about `fn foo()`
1179 annotations_position.sort_by_key(|(_, ann)| {
1180 // Decreasing order. When annotations share the same length, prefer `Primary`.
1181 (Reverse(ann.len()), ann.is_primary)
1182 });
1183
1184 // Write the underlines.
1185 //
1186 // After this we will have:
1187 //
1188 // 2 | fn foo() {
1189 // | ____-_____^
1190 // | |
1191 // | something about `foo`
1192 // 3 |
1193 // 4 | }
1194 // | _^ test
1195 for &(pos, annotation) in &annotations_position {
1196 let uline = self.underline(annotation.is_primary);
1197 for p in annotation.start_col.display..annotation.end_col.display {
1198 // The default span label underline.
1199 buffer.putc(
1200 line_offset + 1,
1201 (code_offset + p).saturating_sub(left),
1202 uline.underline,
1203 uline.style,
1204 );
1205 }
1206
1207 if pos == 0
1208 && matches!(
1209 annotation.annotation_type,
1210 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1211 )
1212 {
1213 // The beginning of a multiline span with its leftward moving line on the same line.
1214 buffer.putc(
1215 line_offset + 1,
1216 (code_offset + annotation.start_col.display).saturating_sub(left),
1217 match annotation.annotation_type {
1218 AnnotationType::MultilineStart(_) => uline.top_right_flat,
1219 AnnotationType::MultilineEnd(_) => uline.multiline_end_same_line,
1220 _ => panic!("unexpected annotation type: {annotation:?}"),
1221 },
1222 uline.style,
1223 );
1224 } else if pos != 0
1225 && matches!(
1226 annotation.annotation_type,
1227 AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_)
1228 )
1229 {
1230 // The beginning of a multiline span with its leftward moving line on another line,
1231 // so we start going down first.
1232 buffer.putc(
1233 line_offset + 1,
1234 (code_offset + annotation.start_col.display).saturating_sub(left),
1235 match annotation.annotation_type {
1236 AnnotationType::MultilineStart(_) => uline.multiline_start_down,
1237 AnnotationType::MultilineEnd(_) => uline.multiline_end_up,
1238 _ => panic!("unexpected annotation type: {annotation:?}"),
1239 },
1240 uline.style,
1241 );
1242 } else if pos != 0 && annotation.has_label() {
1243 // The beginning of a span label with an actual label, we'll point down.
1244 buffer.putc(
1245 line_offset + 1,
1246 (code_offset + annotation.start_col.display).saturating_sub(left),
1247 uline.label_start,
1248 uline.style,
1249 );
1250 }
1251 }
1252 annotations_position
1253 .iter()
1254 .filter_map(|&(_, annotation)| match annotation.annotation_type {
1255 AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => {
1256 let style = if annotation.is_primary {
1257 Style::LabelPrimary
1258 } else {
1259 Style::LabelSecondary
1260 };
1261 Some((p, style))
1262 }
1263 _ => None,
1264 })
1265 .collect::<Vec<_>>()
1266 }
1267
1268 fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
1269 let Some(ref sm) = self.sm else {
1270 return 0;
1271 };
1272
1273 let will_be_emitted = |span: Span| {
1274 !span.is_dummy() && {
1275 let file = sm.lookup_source_file(span.hi());
1276 should_show_source_code(&self.ignored_directories_in_source_blocks, sm, &file)
1277 }
1278 };
1279
1280 let mut max = 0;
1281 for primary_span in msp.primary_spans() {
1282 if will_be_emitted(*primary_span) {
1283 let hi = sm.lookup_char_pos(primary_span.hi());
1284 max = (hi.line).max(max);
1285 }
1286 }
1287 if !self.short_message {
1288 for span_label in msp.span_labels() {
1289 if will_be_emitted(span_label.span) {
1290 let hi = sm.lookup_char_pos(span_label.span.hi());
1291 max = (hi.line).max(max);
1292 }
1293 }
1294 }
1295
1296 max
1297 }
1298
1299 fn get_max_line_num(&mut self, span: &MultiSpan, children: &[Subdiag]) -> usize {
1300 let primary = self.get_multispan_max_line_num(span);
1301 children
1302 .iter()
1303 .map(|sub| self.get_multispan_max_line_num(&sub.span))
1304 .max()
1305 .unwrap_or(0)
1306 .max(primary)
1307 }
1308
1309 /// Adds a left margin to every line but the first, given a padding length and the label being
1310 /// displayed, keeping the provided highlighting.
1311 fn msgs_to_buffer(
1312 &self,
1313 buffer: &mut StyledBuffer,
1314 msgs: &[(DiagMessage, Style)],
1315 args: &FluentArgs<'_>,
1316 padding: usize,
1317 label: &str,
1318 override_style: Option<Style>,
1319 ) -> usize {
1320 // The extra 5 ` ` is padding that's always needed to align to the `note: `:
1321 //
1322 // error: message
1323 // --> file.rs:13:20
1324 // |
1325 // 13 | <CODE>
1326 // | ^^^^
1327 // |
1328 // = note: multiline
1329 // message
1330 // ++^^^----xx
1331 // | | | |
1332 // | | | magic `2`
1333 // | | length of label
1334 // | magic `3`
1335 // `max_line_num_len`
1336 let padding = " ".repeat(padding + label.len() + 5);
1337
1338 /// Returns `override` if it is present and `style` is `NoStyle` or `style` otherwise
1339 fn style_or_override(style: Style, override_: Option<Style>) -> Style {
1340 match (style, override_) {
1341 (Style::NoStyle, Some(override_)) => override_,
1342 _ => style,
1343 }
1344 }
1345
1346 let mut line_number = 0;
1347
1348 // Provided the following diagnostic message:
1349 //
1350 // let msgs = vec![
1351 // ("
1352 // ("highlighted multiline\nstring to\nsee how it ", Style::NoStyle),
1353 // ("looks", Style::Highlight),
1354 // ("with\nvery ", Style::NoStyle),
1355 // ("weird", Style::Highlight),
1356 // (" formats\n", Style::NoStyle),
1357 // ("see?", Style::Highlight),
1358 // ];
1359 //
1360 // the expected output on a note is (* surround the highlighted text)
1361 //
1362 // = note: highlighted multiline
1363 // string to
1364 // see how it *looks* with
1365 // very *weird* formats
1366 // see?
1367 for (text, style) in msgs.iter() {
1368 let text = self.translate_message(text, args).map_err(Report::new).unwrap();
1369 let text = &normalize_whitespace(&text);
1370 let lines = text.split('\n').collect::<Vec<_>>();
1371 if lines.len() > 1 {
1372 for (i, line) in lines.iter().enumerate() {
1373 if i != 0 {
1374 line_number += 1;
1375 buffer.append(line_number, &padding, Style::NoStyle);
1376 }
1377 buffer.append(line_number, line, style_or_override(*style, override_style));
1378 }
1379 } else {
1380 buffer.append(line_number, text, style_or_override(*style, override_style));
1381 }
1382 }
1383 line_number
1384 }
1385
1386 #[instrument(level = "trace", skip(self, args), ret)]
1387 fn emit_messages_default_inner(
1388 &mut self,
1389 msp: &MultiSpan,
1390 msgs: &[(DiagMessage, Style)],
1391 args: &FluentArgs<'_>,
1392 code: &Option<ErrCode>,
1393 level: &Level,
1394 max_line_num_len: usize,
1395 is_secondary: bool,
1396 emitted_at: Option<&DiagLocation>,
1397 is_cont: bool,
1398 ) -> io::Result<()> {
1399 let mut buffer = StyledBuffer::new();
1400
1401 if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
1402 {
1403 // This is a secondary message with no span info
1404 for _ in 0..max_line_num_len {
1405 buffer.prepend(0, " ", Style::NoStyle);
1406 }
1407 self.draw_note_separator(&mut buffer, 0, max_line_num_len + 1, is_cont);
1408 if *level != Level::FailureNote {
1409 buffer.append(0, level.to_str(), Style::MainHeaderMsg);
1410 buffer.append(0, ": ", Style::NoStyle);
1411 }
1412 let printed_lines =
1413 self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None);
1414 if is_cont && matches!(self.theme, OutputTheme::Unicode) {
1415 // There's another note after this one, associated to the subwindow above.
1416 // We write additional vertical lines to join them:
1417 // ╭▸ test.rs:3:3
1418 // │
1419 // 3 │ code
1420 // │ ━━━━
1421 // │
1422 // ├ note: foo
1423 // │ bar
1424 // ╰ note: foo
1425 // bar
1426 for i in 1..=printed_lines {
1427 self.draw_col_separator_no_space(&mut buffer, i, max_line_num_len + 1);
1428 }
1429 }
1430 } else {
1431 let mut label_width = 0;
1432 // The failure note level itself does not provide any useful diagnostic information
1433 if *level != Level::FailureNote {
1434 buffer.append(0, level.to_str(), Style::Level(*level));
1435 label_width += level.to_str().len();
1436 }
1437 if let Some(code) = code {
1438 buffer.append(0, "[", Style::Level(*level));
1439 let code = if let TerminalUrl::Yes = self.terminal_url {
1440 let path = "https://doc.rust-lang.org/error_codes";
1441 format!("\x1b]8;;{path}/{code}.html\x07{code}\x1b]8;;\x07")
1442 } else {
1443 code.to_string()
1444 };
1445 buffer.append(0, &code, Style::Level(*level));
1446 buffer.append(0, "]", Style::Level(*level));
1447 label_width += 2 + code.len();
1448 }
1449 let header_style = if is_secondary {
1450 Style::HeaderMsg
1451 } else if self.short_message {
1452 // For short messages avoid bolding the message, as it doesn't look great (#63835).
1453 Style::NoStyle
1454 } else {
1455 Style::MainHeaderMsg
1456 };
1457 if *level != Level::FailureNote {
1458 buffer.append(0, ": ", header_style);
1459 label_width += 2;
1460 }
1461 let mut line = 0;
1462 for (text, style) in msgs.iter() {
1463 let text = self.translate_message(text, args).map_err(Report::new).unwrap();
1464 // Account for newlines to align output to its label.
1465 for text in normalize_whitespace(&text).lines() {
1466 buffer.append(
1467 line,
1468 &format!(
1469 "{}{}",
1470 if line == 0 { String::new() } else { " ".repeat(label_width) },
1471 text
1472 ),
1473 match style {
1474 Style::Highlight => *style,
1475 _ => header_style,
1476 },
1477 );
1478 line += 1;
1479 }
1480 // We add lines above, but if the last line has no explicit newline (which would
1481 // yield an empty line), then we revert one line up to continue with the next
1482 // styled text chunk on the same line as the last one from the prior one. Otherwise
1483 // every `text` would appear on their own line (because even though they didn't end
1484 // in '\n', they advanced `line` by one).
1485 if line > 0 {
1486 line -= 1;
1487 }
1488 }
1489 if self.short_message {
1490 let labels = msp
1491 .span_labels()
1492 .into_iter()
1493 .filter_map(|label| match label.label {
1494 Some(msg) if label.is_primary => {
1495 let text = self.translate_message(&msg, args).ok()?;
1496 if !text.trim().is_empty() { Some(text.to_string()) } else { None }
1497 }
1498 _ => None,
1499 })
1500 .collect::<Vec<_>>()
1501 .join(", ");
1502 if !labels.is_empty() {
1503 buffer.append(line, ": ", Style::NoStyle);
1504 buffer.append(line, &labels, Style::NoStyle);
1505 }
1506 }
1507 }
1508 let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
1509 trace!("{annotated_files:#?}");
1510
1511 // Make sure our primary file comes first
1512 let primary_span = msp.primary_span().unwrap_or_default();
1513 let (Some(sm), false) = (self.sm.as_ref(), primary_span.is_dummy()) else {
1514 // If we don't have span information, emit and exit
1515 return emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message);
1516 };
1517 let primary_lo = sm.lookup_char_pos(primary_span.lo());
1518 if let Ok(pos) =
1519 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
1520 {
1521 annotated_files.swap(0, pos);
1522 }
1523
1524 // Print out the annotate source lines that correspond with the error
1525 for annotated_file in annotated_files {
1526 // we can't annotate anything if the source is unavailable.
1527 if !should_show_source_code(
1528 &self.ignored_directories_in_source_blocks,
1529 sm,
1530 &annotated_file.file,
1531 ) {
1532 if !self.short_message {
1533 // We'll just print an unannotated message.
1534 for (annotation_id, line) in annotated_file.lines.iter().enumerate() {
1535 let mut annotations = line.annotations.clone();
1536 annotations.sort_by_key(|a| Reverse(a.start_col));
1537 let mut line_idx = buffer.num_lines();
1538
1539 let labels: Vec<_> = annotations
1540 .iter()
1541 .filter_map(|a| Some((a.label.as_ref()?, a.is_primary)))
1542 .filter(|(l, _)| !l.is_empty())
1543 .collect();
1544
1545 if annotation_id == 0 || !labels.is_empty() {
1546 buffer.append(
1547 line_idx,
1548 &format!(
1549 "{}:{}:{}",
1550 sm.filename_for_diagnostics(&annotated_file.file.name),
1551 sm.doctest_offset_line(
1552 &annotated_file.file.name,
1553 line.line_index
1554 ),
1555 annotations[0].start_col.file + 1,
1556 ),
1557 Style::LineAndColumn,
1558 );
1559 if annotation_id == 0 {
1560 buffer.prepend(line_idx, self.file_start(), Style::LineNumber);
1561 } else {
1562 buffer.prepend(
1563 line_idx,
1564 self.secondary_file_start(),
1565 Style::LineNumber,
1566 );
1567 }
1568 for _ in 0..max_line_num_len {
1569 buffer.prepend(line_idx, " ", Style::NoStyle);
1570 }
1571 line_idx += 1;
1572 }
1573 for (label, is_primary) in labels.into_iter() {
1574 let style = if is_primary {
1575 Style::LabelPrimary
1576 } else {
1577 Style::LabelSecondary
1578 };
1579 let pipe = self.col_separator();
1580 buffer.prepend(line_idx, &format!(" {pipe}"), Style::LineNumber);
1581 for _ in 0..max_line_num_len {
1582 buffer.prepend(line_idx, " ", Style::NoStyle);
1583 }
1584 line_idx += 1;
1585 let chr = self.note_separator();
1586 buffer.append(line_idx, &format!(" {chr} note: "), style);
1587 for _ in 0..max_line_num_len {
1588 buffer.prepend(line_idx, " ", Style::NoStyle);
1589 }
1590 buffer.append(line_idx, label, style);
1591 line_idx += 1;
1592 }
1593 }
1594 }
1595 continue;
1596 }
1597
1598 // print out the span location and spacer before we print the annotated source
1599 // to do this, we need to know if this span will be primary
1600 let is_primary = primary_lo.file.name == annotated_file.file.name;
1601 if is_primary {
1602 let loc = primary_lo.clone();
1603 if !self.short_message {
1604 // remember where we are in the output buffer for easy reference
1605 let buffer_msg_line_offset = buffer.num_lines();
1606
1607 buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber);
1608 buffer.append(
1609 buffer_msg_line_offset,
1610 &format!(
1611 "{}:{}:{}",
1612 sm.filename_for_diagnostics(&loc.file.name),
1613 sm.doctest_offset_line(&loc.file.name, loc.line),
1614 loc.col.0 + 1,
1615 ),
1616 Style::LineAndColumn,
1617 );
1618 for _ in 0..max_line_num_len {
1619 buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1620 }
1621 } else {
1622 buffer.prepend(
1623 0,
1624 &format!(
1625 "{}:{}:{}: ",
1626 sm.filename_for_diagnostics(&loc.file.name),
1627 sm.doctest_offset_line(&loc.file.name, loc.line),
1628 loc.col.0 + 1,
1629 ),
1630 Style::LineAndColumn,
1631 );
1632 }
1633 } else if !self.short_message {
1634 // remember where we are in the output buffer for easy reference
1635 let buffer_msg_line_offset = buffer.num_lines();
1636
1637 // Add spacing line, as shown:
1638 // --> $DIR/file:54:15
1639 // |
1640 // LL | code
1641 // | ^^^^
1642 // | (<- It prints *this* line)
1643 // ::: $DIR/other_file.rs:15:5
1644 // |
1645 // LL | code
1646 // | ----
1647 self.draw_col_separator_no_space(
1648 &mut buffer,
1649 buffer_msg_line_offset,
1650 max_line_num_len + 1,
1651 );
1652
1653 // Then, the secondary file indicator
1654 buffer.prepend(
1655 buffer_msg_line_offset + 1,
1656 self.secondary_file_start(),
1657 Style::LineNumber,
1658 );
1659 let loc = if let Some(first_line) = annotated_file.lines.first() {
1660 let col = if let Some(first_annotation) = first_line.annotations.first() {
1661 format!(":{}", first_annotation.start_col.file + 1)
1662 } else {
1663 String::new()
1664 };
1665 format!(
1666 "{}:{}{}",
1667 sm.filename_for_diagnostics(&annotated_file.file.name),
1668 sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
1669 col
1670 )
1671 } else {
1672 format!("{}", sm.filename_for_diagnostics(&annotated_file.file.name))
1673 };
1674 buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
1675 for _ in 0..max_line_num_len {
1676 buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
1677 }
1678 }
1679
1680 if !self.short_message {
1681 // Put in the spacer between the location and annotated source
1682 let buffer_msg_line_offset = buffer.num_lines();
1683 self.draw_col_separator_no_space(
1684 &mut buffer,
1685 buffer_msg_line_offset,
1686 max_line_num_len + 1,
1687 );
1688
1689 // Contains the vertical lines' positions for active multiline annotations
1690 let mut multilines = FxIndexMap::default();
1691
1692 // Get the left-side margin to remove it
1693 let mut whitespace_margin = usize::MAX;
1694 for line_idx in 0..annotated_file.lines.len() {
1695 let file = Arc::clone(&annotated_file.file);
1696 let line = &annotated_file.lines[line_idx];
1697 if let Some(source_string) =
1698 line.line_index.checked_sub(1).and_then(|l| file.get_line(l))
1699 {
1700 // Whitespace can only be removed (aka considered leading)
1701 // if the lexer considers it whitespace.
1702 // non-rustc_lexer::is_whitespace() chars are reported as an
1703 // error (ex. no-break-spaces \u{a0}), and thus can't be considered
1704 // for removal during error reporting.
1705 let leading_whitespace = source_string
1706 .chars()
1707 .take_while(|c| rustc_lexer::is_whitespace(*c))
1708 .map(|c| {
1709 match c {
1710 // Tabs are displayed as 4 spaces
1711 '\t' => 4,
1712 _ => 1,
1713 }
1714 })
1715 .sum();
1716 if source_string.chars().any(|c| !rustc_lexer::is_whitespace(c)) {
1717 whitespace_margin = min(whitespace_margin, leading_whitespace);
1718 }
1719 }
1720 }
1721 if whitespace_margin == usize::MAX {
1722 whitespace_margin = 0;
1723 }
1724
1725 // Left-most column any visible span points at.
1726 let mut span_left_margin = usize::MAX;
1727 for line in &annotated_file.lines {
1728 for ann in &line.annotations {
1729 span_left_margin = min(span_left_margin, ann.start_col.display);
1730 span_left_margin = min(span_left_margin, ann.end_col.display);
1731 }
1732 }
1733 if span_left_margin == usize::MAX {
1734 span_left_margin = 0;
1735 }
1736
1737 // Right-most column any visible span points at.
1738 let mut span_right_margin = 0;
1739 let mut label_right_margin = 0;
1740 let mut max_line_len = 0;
1741 for line in &annotated_file.lines {
1742 max_line_len = max(
1743 max_line_len,
1744 line.line_index
1745 .checked_sub(1)
1746 .and_then(|l| annotated_file.file.get_line(l))
1747 .map_or(0, |s| s.len()),
1748 );
1749 for ann in &line.annotations {
1750 span_right_margin = max(span_right_margin, ann.start_col.display);
1751 span_right_margin = max(span_right_margin, ann.end_col.display);
1752 // FIXME: account for labels not in the same line
1753 let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
1754 label_right_margin =
1755 max(label_right_margin, ann.end_col.display + label_right);
1756 }
1757 }
1758
1759 let width_offset = 3 + max_line_num_len;
1760 let code_offset = if annotated_file.multiline_depth == 0 {
1761 width_offset
1762 } else {
1763 width_offset + annotated_file.multiline_depth + 1
1764 };
1765
1766 let column_width = if let Some(width) = self.diagnostic_width {
1767 width.saturating_sub(code_offset)
1768 } else if self.ui_testing || cfg!(miri) {
1769 DEFAULT_COLUMN_WIDTH
1770 } else {
1771 termize::dimensions()
1772 .map(|(w, _)| w.saturating_sub(code_offset))
1773 .unwrap_or(DEFAULT_COLUMN_WIDTH)
1774 };
1775
1776 let margin = Margin::new(
1777 whitespace_margin,
1778 span_left_margin,
1779 span_right_margin,
1780 label_right_margin,
1781 column_width,
1782 max_line_len,
1783 );
1784
1785 // Next, output the annotate source for this file
1786 for line_idx in 0..annotated_file.lines.len() {
1787 let previous_buffer_line = buffer.num_lines();
1788
1789 let depths = self.render_source_line(
1790 &mut buffer,
1791 Arc::clone(&annotated_file.file),
1792 &annotated_file.lines[line_idx],
1793 width_offset,
1794 code_offset,
1795 margin,
1796 !is_cont && line_idx + 1 == annotated_file.lines.len(),
1797 );
1798
1799 let mut to_add = FxHashMap::default();
1800
1801 for (depth, style) in depths {
1802 // FIXME(#120456) - is `swap_remove` correct?
1803 if multilines.swap_remove(&depth).is_none() {
1804 to_add.insert(depth, style);
1805 }
1806 }
1807
1808 // Set the multiline annotation vertical lines to the left of
1809 // the code in this line.
1810 for (depth, style) in &multilines {
1811 for line in previous_buffer_line..buffer.num_lines() {
1812 self.draw_multiline_line(
1813 &mut buffer,
1814 line,
1815 width_offset,
1816 *depth,
1817 *style,
1818 );
1819 }
1820 }
1821 // check to see if we need to print out or elide lines that come between
1822 // this annotated line and the next one.
1823 if line_idx < (annotated_file.lines.len() - 1) {
1824 let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1825 - annotated_file.lines[line_idx].line_index;
1826 if line_idx_delta > 2 {
1827 let last_buffer_line_num = buffer.num_lines();
1828 self.draw_line_separator(
1829 &mut buffer,
1830 last_buffer_line_num,
1831 width_offset,
1832 );
1833
1834 // Set the multiline annotation vertical lines on `...` bridging line.
1835 for (depth, style) in &multilines {
1836 self.draw_multiline_line(
1837 &mut buffer,
1838 last_buffer_line_num,
1839 width_offset,
1840 *depth,
1841 *style,
1842 );
1843 }
1844 if let Some(line) = annotated_file.lines.get(line_idx) {
1845 for ann in &line.annotations {
1846 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1847 {
1848 // In the case where we have elided the entire start of the
1849 // multispan because those lines were empty, we still need
1850 // to draw the `|`s across the `...`.
1851 self.draw_multiline_line(
1852 &mut buffer,
1853 last_buffer_line_num,
1854 width_offset,
1855 pos,
1856 if ann.is_primary {
1857 Style::UnderlinePrimary
1858 } else {
1859 Style::UnderlineSecondary
1860 },
1861 );
1862 }
1863 }
1864 }
1865 } else if line_idx_delta == 2 {
1866 let unannotated_line = annotated_file
1867 .file
1868 .get_line(annotated_file.lines[line_idx].line_index)
1869 .unwrap_or_else(|| Cow::from(""));
1870
1871 let last_buffer_line_num = buffer.num_lines();
1872
1873 self.draw_line(
1874 &mut buffer,
1875 &normalize_whitespace(&unannotated_line),
1876 annotated_file.lines[line_idx + 1].line_index - 1,
1877 last_buffer_line_num,
1878 width_offset,
1879 code_offset,
1880 margin,
1881 );
1882
1883 for (depth, style) in &multilines {
1884 self.draw_multiline_line(
1885 &mut buffer,
1886 last_buffer_line_num,
1887 width_offset,
1888 *depth,
1889 *style,
1890 );
1891 }
1892 if let Some(line) = annotated_file.lines.get(line_idx) {
1893 for ann in &line.annotations {
1894 if let AnnotationType::MultilineStart(pos) = ann.annotation_type
1895 {
1896 self.draw_multiline_line(
1897 &mut buffer,
1898 last_buffer_line_num,
1899 width_offset,
1900 pos,
1901 if ann.is_primary {
1902 Style::UnderlinePrimary
1903 } else {
1904 Style::UnderlineSecondary
1905 },
1906 );
1907 }
1908 }
1909 }
1910 }
1911 }
1912
1913 multilines.extend(&to_add);
1914 }
1915 }
1916 trace!("buffer: {:#?}", buffer.render());
1917 }
1918
1919 if let Some(tracked) = emitted_at {
1920 let track = format!("-Ztrack-diagnostics: created at {tracked}");
1921 let len = buffer.num_lines();
1922 buffer.append(len, &track, Style::NoStyle);
1923 }
1924
1925 // final step: take our styled buffer, render it, then output it
1926 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
1927
1928 Ok(())
1929 }
1930
1931 fn emit_suggestion_default(
1932 &mut self,
1933 span: &MultiSpan,
1934 suggestion: &CodeSuggestion,
1935 args: &FluentArgs<'_>,
1936 level: &Level,
1937 max_line_num_len: usize,
1938 ) -> io::Result<()> {
1939 let Some(ref sm) = self.sm else {
1940 return Ok(());
1941 };
1942
1943 // Render the replacements for each suggestion
1944 let suggestions = suggestion.splice_lines(sm);
1945 debug!(?suggestions);
1946
1947 if suggestions.is_empty() {
1948 // Here we check if there are suggestions that have actual code changes. We sometimes
1949 // suggest the same code that is already there, instead of changing how we produce the
1950 // suggestions and filtering there, we just don't emit the suggestion.
1951 // Suggestions coming from macros can also have malformed spans. This is a heavy handed
1952 // approach to avoid ICEs by ignoring the suggestion outright.
1953 return Ok(());
1954 }
1955
1956 let mut buffer = StyledBuffer::new();
1957
1958 // Render the suggestion message
1959 buffer.append(0, level.to_str(), Style::Level(*level));
1960 buffer.append(0, ": ", Style::HeaderMsg);
1961
1962 let mut msg = vec![(suggestion.msg.to_owned(), Style::NoStyle)];
1963 if suggestions
1964 .iter()
1965 .take(MAX_SUGGESTIONS)
1966 .any(|(_, _, _, only_capitalization)| *only_capitalization)
1967 {
1968 msg.push((" (notice the capitalization difference)".into(), Style::NoStyle));
1969 }
1970 self.msgs_to_buffer(
1971 &mut buffer,
1972 &msg,
1973 args,
1974 max_line_num_len,
1975 "suggestion",
1976 Some(Style::HeaderMsg),
1977 );
1978
1979 let other_suggestions = suggestions.len().saturating_sub(MAX_SUGGESTIONS);
1980
1981 let mut row_num = 2;
1982 for (i, (complete, parts, highlights, _)) in
1983 suggestions.into_iter().enumerate().take(MAX_SUGGESTIONS)
1984 {
1985 debug!(?complete, ?parts, ?highlights);
1986
1987 let has_deletion =
1988 parts.iter().any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm));
1989 let is_multiline = complete.lines().count() > 1;
1990
1991 if i == 0 {
1992 self.draw_col_separator_start(&mut buffer, row_num - 1, max_line_num_len + 1);
1993 } else {
1994 buffer.puts(
1995 row_num - 1,
1996 max_line_num_len + 1,
1997 self.multi_suggestion_separator(),
1998 Style::LineNumber,
1999 );
2000 }
2001 if let Some(span) = span.primary_span() {
2002 // Compare the primary span of the diagnostic with the span of the suggestion
2003 // being emitted. If they belong to the same file, we don't *need* to show the
2004 // file name, saving in verbosity, but if it *isn't* we do need it, otherwise we're
2005 // telling users to make a change but not clarifying *where*.
2006 let loc = sm.lookup_char_pos(parts[0].span.lo());
2007 if loc.file.name != sm.span_to_filename(span) && loc.file.name.is_real() {
2008 // --> file.rs:line:col
2009 // |
2010 let arrow = self.file_start();
2011 buffer.puts(row_num - 1, 0, arrow, Style::LineNumber);
2012 let filename = sm.filename_for_diagnostics(&loc.file.name);
2013 let offset = sm.doctest_offset_line(&loc.file.name, loc.line);
2014 let message = format!("{}:{}:{}", filename, offset, loc.col.0 + 1);
2015 if row_num == 2 {
2016 let col = usize::max(max_line_num_len + 1, arrow.len());
2017 buffer.puts(1, col, &message, Style::LineAndColumn);
2018 } else {
2019 buffer.append(row_num - 1, &message, Style::LineAndColumn);
2020 }
2021 for _ in 0..max_line_num_len {
2022 buffer.prepend(row_num - 1, " ", Style::NoStyle);
2023 }
2024 self.draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
2025 row_num += 1;
2026 }
2027 }
2028 let show_code_change = if has_deletion && !is_multiline {
2029 DisplaySuggestion::Diff
2030 } else if let [part] = &parts[..]
2031 && part.snippet.ends_with('\n')
2032 && part.snippet.trim() == complete.trim()
2033 {
2034 // We are adding a line(s) of code before code that was already there.
2035 DisplaySuggestion::Add
2036 } else if (parts.len() != 1 || parts[0].snippet.trim() != complete.trim())
2037 && !is_multiline
2038 {
2039 DisplaySuggestion::Underline
2040 } else {
2041 DisplaySuggestion::None
2042 };
2043
2044 if let DisplaySuggestion::Diff = show_code_change {
2045 row_num += 1;
2046 }
2047
2048 let file_lines = sm
2049 .span_to_lines(parts[0].span)
2050 .expect("span_to_lines failed when emitting suggestion");
2051
2052 assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
2053
2054 let line_start = sm.lookup_char_pos(parts[0].span.lo()).line;
2055 let mut lines = complete.lines();
2056 if lines.clone().next().is_none() {
2057 // Account for a suggestion to completely remove a line(s) with whitespace (#94192).
2058 let line_end = sm.lookup_char_pos(parts[0].span.hi()).line;
2059 for line in line_start..=line_end {
2060 buffer.puts(
2061 row_num - 1 + line - line_start,
2062 0,
2063 &self.maybe_anonymized(line),
2064 Style::LineNumber,
2065 );
2066 buffer.puts(
2067 row_num - 1 + line - line_start,
2068 max_line_num_len + 1,
2069 "- ",
2070 Style::Removal,
2071 );
2072 buffer.puts(
2073 row_num - 1 + line - line_start,
2074 max_line_num_len + 3,
2075 &normalize_whitespace(&file_lines.file.get_line(line - 1).unwrap()),
2076 Style::Removal,
2077 );
2078 }
2079 row_num += line_end - line_start;
2080 }
2081 let mut unhighlighted_lines = Vec::new();
2082 let mut last_pos = 0;
2083 let mut is_item_attribute = false;
2084 for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
2085 last_pos = line_pos;
2086 debug!(%line_pos, %line, ?highlight_parts);
2087
2088 // Remember lines that are not highlighted to hide them if needed
2089 if highlight_parts.is_empty() {
2090 unhighlighted_lines.push((line_pos, line));
2091 continue;
2092 }
2093 if highlight_parts.len() == 1
2094 && line.trim().starts_with("#[")
2095 && line.trim().ends_with(']')
2096 {
2097 is_item_attribute = true;
2098 }
2099
2100 match unhighlighted_lines.len() {
2101 0 => (),
2102 // Since we show first line, "..." line and last line,
2103 // There is no reason to hide if there are 3 or less lines
2104 // (because then we just replace a line with ... which is
2105 // not helpful)
2106 n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
2107 self.draw_code_line(
2108 &mut buffer,
2109 &mut row_num,
2110 &[],
2111 p + line_start,
2112 l,
2113 show_code_change,
2114 max_line_num_len,
2115 &file_lines,
2116 is_multiline,
2117 )
2118 }),
2119 // Print first unhighlighted line, "..." and last unhighlighted line, like so:
2120 //
2121 // LL | this line was highlighted
2122 // LL | this line is just for context
2123 // ...
2124 // LL | this line is just for context
2125 // LL | this line was highlighted
2126 _ => {
2127 let last_line = unhighlighted_lines.pop();
2128 let first_line = unhighlighted_lines.drain(..).next();
2129
2130 if let Some((p, l)) = first_line {
2131 self.draw_code_line(
2132 &mut buffer,
2133 &mut row_num,
2134 &[],
2135 p + line_start,
2136 l,
2137 show_code_change,
2138 max_line_num_len,
2139 &file_lines,
2140 is_multiline,
2141 )
2142 }
2143
2144 let placeholder = self.margin();
2145 let padding = str_width(placeholder);
2146 buffer.puts(
2147 row_num,
2148 max_line_num_len.saturating_sub(padding),
2149 placeholder,
2150 Style::LineNumber,
2151 );
2152 row_num += 1;
2153
2154 if let Some((p, l)) = last_line {
2155 self.draw_code_line(
2156 &mut buffer,
2157 &mut row_num,
2158 &[],
2159 p + line_start,
2160 l,
2161 show_code_change,
2162 max_line_num_len,
2163 &file_lines,
2164 is_multiline,
2165 )
2166 }
2167 }
2168 }
2169
2170 self.draw_code_line(
2171 &mut buffer,
2172 &mut row_num,
2173 &highlight_parts,
2174 line_pos + line_start,
2175 line,
2176 show_code_change,
2177 max_line_num_len,
2178 &file_lines,
2179 is_multiline,
2180 )
2181 }
2182 if let DisplaySuggestion::Add = show_code_change
2183 && is_item_attribute
2184 {
2185 // The suggestion adds an entire line of code, ending on a newline, so we'll also
2186 // print the *following* line, to provide context of what we're advising people to
2187 // do. Otherwise you would only see contextless code that can be confused for
2188 // already existing code, despite the colors and UI elements.
2189 // We special case `#[derive(_)]\n` and other attribute suggestions, because those
2190 // are the ones where context is most useful.
2191 let file_lines = sm
2192 .span_to_lines(parts[0].span.shrink_to_hi())
2193 .expect("span_to_lines failed when emitting suggestion");
2194 let line_num = sm.lookup_char_pos(parts[0].span.lo()).line;
2195 if let Some(line) = file_lines.file.get_line(line_num - 1) {
2196 let line = normalize_whitespace(&line);
2197 self.draw_code_line(
2198 &mut buffer,
2199 &mut row_num,
2200 &[],
2201 line_num + last_pos + 1,
2202 &line,
2203 DisplaySuggestion::None,
2204 max_line_num_len,
2205 &file_lines,
2206 is_multiline,
2207 )
2208 }
2209 }
2210
2211 // This offset and the ones below need to be signed to account for replacement code
2212 // that is shorter than the original code.
2213 let mut offsets: Vec<(usize, isize)> = Vec::new();
2214 // Only show an underline in the suggestions if the suggestion is not the
2215 // entirety of the code being shown and the displayed code is not multiline.
2216 if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
2217 show_code_change
2218 {
2219 for mut part in parts {
2220 // If this is a replacement of, e.g. `"a"` into `"ab"`, adjust the
2221 // suggestion and snippet to look as if we just suggested to add
2222 // `"b"`, which is typically much easier for the user to understand.
2223 part.trim_trivial_replacements(sm);
2224
2225 let snippet = if let Ok(snippet) = sm.span_to_snippet(part.span) {
2226 snippet
2227 } else {
2228 String::new()
2229 };
2230 let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
2231 let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
2232
2233 // If this addition is _only_ whitespace, then don't trim it,
2234 // or else we're just not rendering anything.
2235 let is_whitespace_addition = part.snippet.trim().is_empty();
2236
2237 // Do not underline the leading...
2238 let start = if is_whitespace_addition {
2239 0
2240 } else {
2241 part.snippet.len().saturating_sub(part.snippet.trim_start().len())
2242 };
2243 // ...or trailing spaces. Account for substitutions containing unicode
2244 // characters.
2245 let sub_len: usize = str_width(if is_whitespace_addition {
2246 &part.snippet
2247 } else {
2248 part.snippet.trim()
2249 });
2250
2251 let offset: isize = offsets
2252 .iter()
2253 .filter_map(
2254 |(start, v)| if span_start_pos < *start { None } else { Some(v) },
2255 )
2256 .sum();
2257 let underline_start = (span_start_pos + start) as isize + offset;
2258 let underline_end = (span_start_pos + start + sub_len) as isize + offset;
2259 assert!(underline_start >= 0 && underline_end >= 0);
2260 let padding: usize = max_line_num_len + 3;
2261 for p in underline_start..underline_end {
2262 if let DisplaySuggestion::Underline = show_code_change
2263 && is_different(sm, &part.snippet, part.span)
2264 {
2265 // If this is a replacement, underline with `~`, if this is an addition
2266 // underline with `+`.
2267 buffer.putc(
2268 row_num,
2269 (padding as isize + p) as usize,
2270 if part.is_addition(sm) { '+' } else { self.diff() },
2271 Style::Addition,
2272 );
2273 }
2274 }
2275 if let DisplaySuggestion::Diff = show_code_change {
2276 // Colorize removal with red in diff format.
2277
2278 // Below, there's some tricky buffer indexing going on. `row_num` at this
2279 // point corresponds to:
2280 //
2281 // |
2282 // LL | CODE
2283 // | ++++ <- `row_num`
2284 //
2285 // in the buffer. When we have a diff format output, we end up with
2286 //
2287 // |
2288 // LL - OLDER <- row_num - 2
2289 // LL + NEWER
2290 // | <- row_num
2291 //
2292 // The `row_num - 2` is to select the buffer line that has the "old version
2293 // of the diff" at that point. When the removal is a single line, `i` is
2294 // `0`, `newlines` is `1` so `(newlines - i - 1)` ends up being `0`, so row
2295 // points at `LL - OLDER`. When the removal corresponds to multiple lines,
2296 // we end up with `newlines > 1` and `i` being `0..newlines - 1`.
2297 //
2298 // |
2299 // LL - OLDER <- row_num - 2 - (newlines - last_i - 1)
2300 // LL - CODE
2301 // LL - BEING
2302 // LL - REMOVED <- row_num - 2 - (newlines - first_i - 1)
2303 // LL + NEWER
2304 // | <- row_num
2305
2306 let newlines = snippet.lines().count();
2307 if newlines > 0 && row_num > newlines {
2308 // Account for removals where the part being removed spans multiple
2309 // lines.
2310 // FIXME: We check the number of rows because in some cases, like in
2311 // `tests/ui/lint/invalid-nan-comparison-suggestion.rs`, the rendered
2312 // suggestion will only show the first line of code being replaced. The
2313 // proper way of doing this would be to change the suggestion rendering
2314 // logic to show the whole prior snippet, but the current output is not
2315 // too bad to begin with, so we side-step that issue here.
2316 for (i, line) in snippet.lines().enumerate() {
2317 let line = normalize_whitespace(line);
2318 let row = row_num - 2 - (newlines - i - 1);
2319 // On the first line, we highlight between the start of the part
2320 // span, and the end of that line.
2321 // On the last line, we highlight between the start of the line, and
2322 // the column of the part span end.
2323 // On all others, we highlight the whole line.
2324 let start = if i == 0 {
2325 (padding as isize + span_start_pos as isize) as usize
2326 } else {
2327 padding
2328 };
2329 let end = if i == 0 {
2330 (padding as isize
2331 + span_start_pos as isize
2332 + line.len() as isize)
2333 as usize
2334 } else if i == newlines - 1 {
2335 (padding as isize + span_end_pos as isize) as usize
2336 } else {
2337 (padding as isize + line.len() as isize) as usize
2338 };
2339 buffer.set_style_range(row, start, end, Style::Removal, true);
2340 }
2341 } else {
2342 // The removed code fits all in one line.
2343 buffer.set_style_range(
2344 row_num - 2,
2345 (padding as isize + span_start_pos as isize) as usize,
2346 (padding as isize + span_end_pos as isize) as usize,
2347 Style::Removal,
2348 true,
2349 );
2350 }
2351 }
2352
2353 // length of the code after substitution
2354 let full_sub_len = str_width(&part.snippet) as isize;
2355
2356 // length of the code to be substituted
2357 let snippet_len = span_end_pos as isize - span_start_pos as isize;
2358 // For multiple substitutions, use the position *after* the previous
2359 // substitutions have happened, only when further substitutions are
2360 // located strictly after.
2361 offsets.push((span_end_pos, full_sub_len - snippet_len));
2362 }
2363 row_num += 1;
2364 }
2365
2366 // if we elided some lines, add an ellipsis
2367 if lines.next().is_some() {
2368 let placeholder = self.margin();
2369 let padding = str_width(placeholder);
2370 buffer.puts(
2371 row_num,
2372 max_line_num_len.saturating_sub(padding),
2373 placeholder,
2374 Style::LineNumber,
2375 );
2376 } else {
2377 let row = match show_code_change {
2378 DisplaySuggestion::Diff
2379 | DisplaySuggestion::Add
2380 | DisplaySuggestion::Underline => row_num - 1,
2381 DisplaySuggestion::None => row_num,
2382 };
2383 self.draw_col_separator_end(&mut buffer, row, max_line_num_len + 1);
2384 row_num = row + 1;
2385 }
2386 }
2387 if other_suggestions > 0 {
2388 let msg = format!(
2389 "and {} other candidate{}",
2390 other_suggestions,
2391 pluralize!(other_suggestions)
2392 );
2393 buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle);
2394 }
2395
2396 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
2397 Ok(())
2398 }
2399
2400 #[instrument(level = "trace", skip(self, args, code, children, suggestions))]
2401 fn emit_messages_default(
2402 &mut self,
2403 level: &Level,
2404 messages: &[(DiagMessage, Style)],
2405 args: &FluentArgs<'_>,
2406 code: &Option<ErrCode>,
2407 span: &MultiSpan,
2408 children: &[Subdiag],
2409 suggestions: &[CodeSuggestion],
2410 emitted_at: Option<&DiagLocation>,
2411 ) {
2412 let max_line_num_len = if self.ui_testing {
2413 ANONYMIZED_LINE_NUM.len()
2414 } else {
2415 let n = self.get_max_line_num(span, children);
2416 num_decimal_digits(n)
2417 };
2418
2419 match self.emit_messages_default_inner(
2420 span,
2421 messages,
2422 args,
2423 code,
2424 level,
2425 max_line_num_len,
2426 false,
2427 emitted_at,
2428 !children.is_empty()
2429 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden),
2430 ) {
2431 Ok(()) => {
2432 if !children.is_empty()
2433 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
2434 {
2435 let mut buffer = StyledBuffer::new();
2436 if !self.short_message {
2437 if let Some(child) = children.iter().next()
2438 && child.span.primary_spans().is_empty()
2439 {
2440 // We'll continue the vertical bar to point into the next note.
2441 self.draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
2442 } else {
2443 // We'll close the vertical bar to visually end the code window.
2444 self.draw_col_separator_end(&mut buffer, 0, max_line_num_len + 1);
2445 }
2446 }
2447 if let Err(e) = emit_to_destination(
2448 &buffer.render(),
2449 level,
2450 &mut self.dst,
2451 self.short_message,
2452 ) {
2453 panic!("failed to emit error: {e}")
2454 }
2455 }
2456 if !self.short_message {
2457 for (i, child) in children.iter().enumerate() {
2458 assert!(child.level.can_be_subdiag());
2459 let span = &child.span;
2460 // FIXME: audit that this behaves correctly with suggestions.
2461 let should_close = match children.get(i + 1) {
2462 Some(c) => !c.span.primary_spans().is_empty(),
2463 None => i + 1 == children.len(),
2464 };
2465 if let Err(err) = self.emit_messages_default_inner(
2466 span,
2467 &child.messages,
2468 args,
2469 &None,
2470 &child.level,
2471 max_line_num_len,
2472 true,
2473 None,
2474 !should_close,
2475 ) {
2476 panic!("failed to emit error: {err}");
2477 }
2478 }
2479 for (i, sugg) in suggestions.iter().enumerate() {
2480 match sugg.style {
2481 SuggestionStyle::CompletelyHidden => {
2482 // do not display this suggestion, it is meant only for tools
2483 }
2484 SuggestionStyle::HideCodeAlways => {
2485 if let Err(e) = self.emit_messages_default_inner(
2486 &MultiSpan::new(),
2487 &[(sugg.msg.to_owned(), Style::HeaderMsg)],
2488 args,
2489 &None,
2490 &Level::Help,
2491 max_line_num_len,
2492 true,
2493 None,
2494 // FIXME: this needs to account for the suggestion type,
2495 // some don't take any space.
2496 i + 1 != suggestions.len(),
2497 ) {
2498 panic!("failed to emit error: {e}");
2499 }
2500 }
2501 SuggestionStyle::HideCodeInline
2502 | SuggestionStyle::ShowCode
2503 | SuggestionStyle::ShowAlways => {
2504 if let Err(e) = self.emit_suggestion_default(
2505 span,
2506 sugg,
2507 args,
2508 &Level::Help,
2509 max_line_num_len,
2510 ) {
2511 panic!("failed to emit error: {e}");
2512 }
2513 }
2514 }
2515 }
2516 }
2517 }
2518 Err(e) => panic!("failed to emit error: {e}"),
2519 }
2520
2521 match writeln!(self.dst) {
2522 Err(e) => panic!("failed to emit error: {e}"),
2523 _ => {
2524 if let Err(e) = self.dst.flush() {
2525 panic!("failed to emit error: {e}")
2526 }
2527 }
2528 }
2529 }
2530
2531 fn draw_code_line(
2532 &self,
2533 buffer: &mut StyledBuffer,
2534 row_num: &mut usize,
2535 highlight_parts: &[SubstitutionHighlight],
2536 line_num: usize,
2537 line_to_add: &str,
2538 show_code_change: DisplaySuggestion,
2539 max_line_num_len: usize,
2540 file_lines: &FileLines,
2541 is_multiline: bool,
2542 ) {
2543 if let DisplaySuggestion::Diff = show_code_change {
2544 // We need to print more than one line if the span we need to remove is multiline.
2545 // For more info: https://github.com/rust-lang/rust/issues/92741
2546 let lines_to_remove = file_lines.lines.iter().take(file_lines.lines.len() - 1);
2547 for (index, line_to_remove) in lines_to_remove.enumerate() {
2548 buffer.puts(
2549 *row_num - 1,
2550 0,
2551 &self.maybe_anonymized(line_num + index),
2552 Style::LineNumber,
2553 );
2554 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2555 let line = normalize_whitespace(
2556 &file_lines.file.get_line(line_to_remove.line_index).unwrap(),
2557 );
2558 buffer.puts(*row_num - 1, max_line_num_len + 3, &line, Style::NoStyle);
2559 *row_num += 1;
2560 }
2561 // If the last line is exactly equal to the line we need to add, we can skip both of
2562 // them. This allows us to avoid output like the following:
2563 // 2 - &
2564 // 2 + if true { true } else { false }
2565 // 3 - if true { true } else { false }
2566 // If those lines aren't equal, we print their diff
2567 let last_line_index = file_lines.lines[file_lines.lines.len() - 1].line_index;
2568 let last_line = &file_lines.file.get_line(last_line_index).unwrap();
2569 if last_line != line_to_add {
2570 buffer.puts(
2571 *row_num - 1,
2572 0,
2573 &self.maybe_anonymized(line_num + file_lines.lines.len() - 1),
2574 Style::LineNumber,
2575 );
2576 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2577 buffer.puts(
2578 *row_num - 1,
2579 max_line_num_len + 3,
2580 &normalize_whitespace(last_line),
2581 Style::NoStyle,
2582 );
2583 if !line_to_add.trim().is_empty() {
2584 // Check if after the removal, the line is left with only whitespace. If so, we
2585 // will not show an "addition" line, as removing the whole line is what the user
2586 // would really want.
2587 // For example, for the following:
2588 // |
2589 // 2 - .await
2590 // 2 + (note the left over whitespace)
2591 // |
2592 // We really want
2593 // |
2594 // 2 - .await
2595 // |
2596 // *row_num -= 1;
2597 buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2598 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2599 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2600 } else {
2601 *row_num -= 1;
2602 }
2603 } else {
2604 *row_num -= 2;
2605 }
2606 } else if is_multiline {
2607 buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2608 match &highlight_parts {
2609 [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
2610 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2611 }
2612 [] => {
2613 // FIXME: needed? Doesn't get exercised in any test.
2614 self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1);
2615 }
2616 _ => {
2617 let diff = self.diff();
2618 buffer.puts(
2619 *row_num,
2620 max_line_num_len + 1,
2621 &format!("{diff} "),
2622 Style::Addition,
2623 );
2624 }
2625 }
2626 // LL | line_to_add
2627 // ++^^^
2628 // | |
2629 // | magic `3`
2630 // `max_line_num_len`
2631 buffer.puts(
2632 *row_num,
2633 max_line_num_len + 3,
2634 &normalize_whitespace(line_to_add),
2635 Style::NoStyle,
2636 );
2637 } else if let DisplaySuggestion::Add = show_code_change {
2638 buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2639 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2640 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2641 } else {
2642 buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
2643 self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2644 buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
2645 }
2646
2647 // Colorize addition/replacements with green.
2648 for &SubstitutionHighlight { start, end } in highlight_parts {
2649 // This is a no-op for empty ranges
2650 if start != end {
2651 // Account for tabs when highlighting (#87972).
2652 let tabs: usize = line_to_add
2653 .chars()
2654 .take(start)
2655 .map(|ch| match ch {
2656 '\t' => 3,
2657 _ => 0,
2658 })
2659 .sum();
2660 buffer.set_style_range(
2661 *row_num,
2662 max_line_num_len + 3 + start + tabs,
2663 max_line_num_len + 3 + end + tabs,
2664 Style::Addition,
2665 true,
2666 );
2667 }
2668 }
2669 *row_num += 1;
2670 }
2671
2672 fn underline(&self, is_primary: bool) -> UnderlineParts {
2673 // X0 Y0
2674 // label_start > ┯━━━━ < underline
2675 // │ < vertical_text_line
2676 // text
2677
2678 // multiline_start_down ⤷ X0 Y0
2679 // top_left > ┌───╿──┘ < top_right_flat
2680 // top_left > ┏│━━━┙ < top_right
2681 // multiline_vertical > ┃│
2682 // ┃│ X1 Y1
2683 // ┃│ X2 Y2
2684 // ┃└────╿──┘ < multiline_end_same_line
2685 // bottom_left > ┗━━━━━┥ < bottom_right_with_text
2686 // multiline_horizontal ^ `X` is a good letter
2687
2688 // multiline_whole_line > ┏ X0 Y0
2689 // ┃ X1 Y1
2690 // ┗━━━━┛ < multiline_end_same_line
2691
2692 // multiline_whole_line > ┏ X0 Y0
2693 // ┃ X1 Y1
2694 // ┃ ╿ < multiline_end_up
2695 // ┗━━┛ < bottom_right
2696
2697 match (self.theme, is_primary) {
2698 (OutputTheme::Ascii, true) => UnderlineParts {
2699 style: Style::UnderlinePrimary,
2700 underline: '^',
2701 label_start: '^',
2702 vertical_text_line: '|',
2703 multiline_vertical: '|',
2704 multiline_horizontal: '_',
2705 multiline_whole_line: '/',
2706 multiline_start_down: '^',
2707 bottom_right: '|',
2708 top_left: ' ',
2709 top_right_flat: '^',
2710 bottom_left: '|',
2711 multiline_end_up: '^',
2712 multiline_end_same_line: '^',
2713 multiline_bottom_right_with_text: '|',
2714 },
2715 (OutputTheme::Ascii, false) => UnderlineParts {
2716 style: Style::UnderlineSecondary,
2717 underline: '-',
2718 label_start: '-',
2719 vertical_text_line: '|',
2720 multiline_vertical: '|',
2721 multiline_horizontal: '_',
2722 multiline_whole_line: '/',
2723 multiline_start_down: '-',
2724 bottom_right: '|',
2725 top_left: ' ',
2726 top_right_flat: '-',
2727 bottom_left: '|',
2728 multiline_end_up: '-',
2729 multiline_end_same_line: '-',
2730 multiline_bottom_right_with_text: '|',
2731 },
2732 (OutputTheme::Unicode, true) => UnderlineParts {
2733 style: Style::UnderlinePrimary,
2734 underline: '━',
2735 label_start: '┯',
2736 vertical_text_line: '│',
2737 multiline_vertical: '┃',
2738 multiline_horizontal: '━',
2739 multiline_whole_line: '┏',
2740 multiline_start_down: '╿',
2741 bottom_right: '┙',
2742 top_left: '┏',
2743 top_right_flat: '┛',
2744 bottom_left: '┗',
2745 multiline_end_up: '╿',
2746 multiline_end_same_line: '┛',
2747 multiline_bottom_right_with_text: '┥',
2748 },
2749 (OutputTheme::Unicode, false) => UnderlineParts {
2750 style: Style::UnderlineSecondary,
2751 underline: '─',
2752 label_start: '┬',
2753 vertical_text_line: '│',
2754 multiline_vertical: '│',
2755 multiline_horizontal: '─',
2756 multiline_whole_line: '┌',
2757 multiline_start_down: '│',
2758 bottom_right: '┘',
2759 top_left: '┌',
2760 top_right_flat: '┘',
2761 bottom_left: '└',
2762 multiline_end_up: '│',
2763 multiline_end_same_line: '┘',
2764 multiline_bottom_right_with_text: '┤',
2765 },
2766 }
2767 }
2768
2769 fn col_separator(&self) -> char {
2770 match self.theme {
2771 OutputTheme::Ascii => '|',
2772 OutputTheme::Unicode => '│',
2773 }
2774 }
2775
2776 fn note_separator(&self) -> char {
2777 match self.theme {
2778 OutputTheme::Ascii => '=',
2779 OutputTheme::Unicode => '╰',
2780 }
2781 }
2782
2783 fn multi_suggestion_separator(&self) -> &'static str {
2784 match self.theme {
2785 OutputTheme::Ascii => "|",
2786 OutputTheme::Unicode => "├╴",
2787 }
2788 }
2789
2790 fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2791 let chr = self.col_separator();
2792 buffer.puts(line, col, &format!("{chr} "), Style::LineNumber);
2793 }
2794
2795 fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2796 let chr = self.col_separator();
2797 self.draw_col_separator_no_space_with_style(buffer, chr, line, col, Style::LineNumber);
2798 }
2799
2800 fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2801 match self.theme {
2802 OutputTheme::Ascii => {
2803 self.draw_col_separator_no_space_with_style(
2804 buffer,
2805 '|',
2806 line,
2807 col,
2808 Style::LineNumber,
2809 );
2810 }
2811 OutputTheme::Unicode => {
2812 self.draw_col_separator_no_space_with_style(
2813 buffer,
2814 '╭',
2815 line,
2816 col,
2817 Style::LineNumber,
2818 );
2819 self.draw_col_separator_no_space_with_style(
2820 buffer,
2821 '╴',
2822 line,
2823 col + 1,
2824 Style::LineNumber,
2825 );
2826 }
2827 }
2828 }
2829
2830 fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2831 match self.theme {
2832 OutputTheme::Ascii => {
2833 self.draw_col_separator_no_space_with_style(
2834 buffer,
2835 '|',
2836 line,
2837 col,
2838 Style::LineNumber,
2839 );
2840 }
2841 OutputTheme::Unicode => {
2842 self.draw_col_separator_no_space_with_style(
2843 buffer,
2844 '╰',
2845 line,
2846 col,
2847 Style::LineNumber,
2848 );
2849 self.draw_col_separator_no_space_with_style(
2850 buffer,
2851 '╴',
2852 line,
2853 col + 1,
2854 Style::LineNumber,
2855 );
2856 }
2857 }
2858 }
2859
2860 fn draw_col_separator_no_space_with_style(
2861 &self,
2862 buffer: &mut StyledBuffer,
2863 chr: char,
2864 line: usize,
2865 col: usize,
2866 style: Style,
2867 ) {
2868 buffer.putc(line, col, chr, style);
2869 }
2870
2871 fn draw_range(
2872 &self,
2873 buffer: &mut StyledBuffer,
2874 symbol: char,
2875 line: usize,
2876 col_from: usize,
2877 col_to: usize,
2878 style: Style,
2879 ) {
2880 for col in col_from..col_to {
2881 buffer.putc(line, col, symbol, style);
2882 }
2883 }
2884
2885 fn draw_note_separator(
2886 &self,
2887 buffer: &mut StyledBuffer,
2888 line: usize,
2889 col: usize,
2890 is_cont: bool,
2891 ) {
2892 let chr = match self.theme {
2893 OutputTheme::Ascii => "= ",
2894 OutputTheme::Unicode if is_cont => "├ ",
2895 OutputTheme::Unicode => "╰ ",
2896 };
2897 buffer.puts(line, col, chr, Style::LineNumber);
2898 }
2899
2900 fn draw_multiline_line(
2901 &self,
2902 buffer: &mut StyledBuffer,
2903 line: usize,
2904 offset: usize,
2905 depth: usize,
2906 style: Style,
2907 ) {
2908 let chr = match (style, self.theme) {
2909 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Ascii) => '|',
2910 (_, OutputTheme::Ascii) => '|',
2911 (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Unicode) => '┃',
2912 (_, OutputTheme::Unicode) => '│',
2913 };
2914 buffer.putc(line, offset + depth - 1, chr, style);
2915 }
2916
2917 fn file_start(&self) -> &'static str {
2918 match self.theme {
2919 OutputTheme::Ascii => "--> ",
2920 OutputTheme::Unicode => " ╭▸ ",
2921 }
2922 }
2923
2924 fn secondary_file_start(&self) -> &'static str {
2925 match self.theme {
2926 OutputTheme::Ascii => "::: ",
2927 OutputTheme::Unicode => " ⸬ ",
2928 }
2929 }
2930
2931 fn diff(&self) -> char {
2932 match self.theme {
2933 OutputTheme::Ascii => '~',
2934 OutputTheme::Unicode => '±',
2935 }
2936 }
2937
2938 fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) {
2939 let (column, dots) = match self.theme {
2940 OutputTheme::Ascii => (0, "..."),
2941 OutputTheme::Unicode => (col - 2, "‡"),
2942 };
2943 buffer.puts(line, column, dots, Style::LineNumber);
2944 }
2945
2946 fn margin(&self) -> &'static str {
2947 match self.theme {
2948 OutputTheme::Ascii => "...",
2949 OutputTheme::Unicode => "…",
2950 }
2951 }
2952}
2953
2954#[derive(Debug, Clone, Copy)]
2955struct UnderlineParts {
2956 style: Style,
2957 underline: char,
2958 label_start: char,
2959 vertical_text_line: char,
2960 multiline_vertical: char,
2961 multiline_horizontal: char,
2962 multiline_whole_line: char,
2963 multiline_start_down: char,
2964 bottom_right: char,
2965 top_left: char,
2966 top_right_flat: char,
2967 bottom_left: char,
2968 multiline_end_up: char,
2969 multiline_end_same_line: char,
2970 multiline_bottom_right_with_text: char,
2971}
2972
2973#[derive(Clone, Copy, Debug)]
2974enum DisplaySuggestion {
2975 Underline,
2976 Diff,
2977 None,
2978 Add,
2979}
2980
2981impl FileWithAnnotatedLines {
2982 /// Preprocess all the annotations so that they are grouped by file and by line number
2983 /// This helps us quickly iterate over the whole message (including secondary file spans)
2984 pub(crate) fn collect_annotations(
2985 emitter: &dyn Emitter,
2986 args: &FluentArgs<'_>,
2987 msp: &MultiSpan,
2988 ) -> Vec<FileWithAnnotatedLines> {
2989 fn add_annotation_to_file(
2990 file_vec: &mut Vec<FileWithAnnotatedLines>,
2991 file: Arc<SourceFile>,
2992 line_index: usize,
2993 ann: Annotation,
2994 ) {
2995 for slot in file_vec.iter_mut() {
2996 // Look through each of our files for the one we're adding to
2997 if slot.file.name == file.name {
2998 // See if we already have a line for it
2999 for line_slot in &mut slot.lines {
3000 if line_slot.line_index == line_index {
3001 line_slot.annotations.push(ann);
3002 return;
3003 }
3004 }
3005 // We don't have a line yet, create one
3006 slot.lines.push(Line { line_index, annotations: vec![ann] });
3007 slot.lines.sort();
3008 return;
3009 }
3010 }
3011 // This is the first time we're seeing the file
3012 file_vec.push(FileWithAnnotatedLines {
3013 file,
3014 lines: vec![Line { line_index, annotations: vec![ann] }],
3015 multiline_depth: 0,
3016 });
3017 }
3018
3019 let mut output = vec![];
3020 let mut multiline_annotations = vec![];
3021
3022 if let Some(sm) = emitter.source_map() {
3023 for SpanLabel { span, is_primary, label } in msp.span_labels() {
3024 // If we don't have a useful span, pick the primary span if that exists.
3025 // Worst case we'll just print an error at the top of the main file.
3026 let span = match (span.is_dummy(), msp.primary_span()) {
3027 (_, None) | (false, _) => span,
3028 (true, Some(span)) => span,
3029 };
3030
3031 let lo = sm.lookup_char_pos(span.lo());
3032 let mut hi = sm.lookup_char_pos(span.hi());
3033
3034 // Watch out for "empty spans". If we get a span like 6..6, we
3035 // want to just display a `^` at 6, so convert that to
3036 // 6..7. This is degenerate input, but it's best to degrade
3037 // gracefully -- and the parser likes to supply a span like
3038 // that for EOF, in particular.
3039
3040 if lo.col_display == hi.col_display && lo.line == hi.line {
3041 hi.col_display += 1;
3042 }
3043
3044 let label = label.as_ref().map(|m| {
3045 normalize_whitespace(
3046 &emitter.translate_message(m, args).map_err(Report::new).unwrap(),
3047 )
3048 });
3049
3050 if lo.line != hi.line {
3051 let ml = MultilineAnnotation {
3052 depth: 1,
3053 line_start: lo.line,
3054 line_end: hi.line,
3055 start_col: AnnotationColumn::from_loc(&lo),
3056 end_col: AnnotationColumn::from_loc(&hi),
3057 is_primary,
3058 label,
3059 overlaps_exactly: false,
3060 };
3061 multiline_annotations.push((lo.file, ml));
3062 } else {
3063 let ann = Annotation {
3064 start_col: AnnotationColumn::from_loc(&lo),
3065 end_col: AnnotationColumn::from_loc(&hi),
3066 is_primary,
3067 label,
3068 annotation_type: AnnotationType::Singleline,
3069 };
3070 add_annotation_to_file(&mut output, lo.file, lo.line, ann);
3071 };
3072 }
3073 }
3074
3075 // Find overlapping multiline annotations, put them at different depths
3076 multiline_annotations.sort_by_key(|(_, ml)| (ml.line_start, usize::MAX - ml.line_end));
3077 for (_, ann) in multiline_annotations.clone() {
3078 for (_, a) in multiline_annotations.iter_mut() {
3079 // Move all other multiline annotations overlapping with this one
3080 // one level to the right.
3081 if !(ann.same_span(a))
3082 && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
3083 {
3084 a.increase_depth();
3085 } else if ann.same_span(a) && &ann != a {
3086 a.overlaps_exactly = true;
3087 } else {
3088 break;
3089 }
3090 }
3091 }
3092
3093 let mut max_depth = 0; // max overlapping multiline spans
3094 for (_, ann) in &multiline_annotations {
3095 max_depth = max(max_depth, ann.depth);
3096 }
3097 // Change order of multispan depth to minimize the number of overlaps in the ASCII art.
3098 for (_, a) in multiline_annotations.iter_mut() {
3099 a.depth = max_depth - a.depth + 1;
3100 }
3101 for (file, ann) in multiline_annotations {
3102 let mut end_ann = ann.as_end();
3103 if !ann.overlaps_exactly {
3104 // avoid output like
3105 //
3106 // | foo(
3107 // | _____^
3108 // | |_____|
3109 // | || bar,
3110 // | || );
3111 // | || ^
3112 // | ||______|
3113 // | |______foo
3114 // | baz
3115 //
3116 // and instead get
3117 //
3118 // | foo(
3119 // | _____^
3120 // | | bar,
3121 // | | );
3122 // | | ^
3123 // | | |
3124 // | |______foo
3125 // | baz
3126 add_annotation_to_file(
3127 &mut output,
3128 Arc::clone(&file),
3129 ann.line_start,
3130 ann.as_start(),
3131 );
3132 // 4 is the minimum vertical length of a multiline span when presented: two lines
3133 // of code and two lines of underline. This is not true for the special case where
3134 // the beginning doesn't have an underline, but the current logic seems to be
3135 // working correctly.
3136 let middle = min(ann.line_start + 4, ann.line_end);
3137 // We'll show up to 4 lines past the beginning of the multispan start.
3138 // We will *not* include the tail of lines that are only whitespace, a comment or
3139 // a bare delimiter.
3140 let filter = |s: &str| {
3141 let s = s.trim();
3142 // Consider comments as empty, but don't consider docstrings to be empty.
3143 !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
3144 // Consider lines with nothing but whitespace, a single delimiter as empty.
3145 && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
3146 };
3147 let until = (ann.line_start..middle)
3148 .rev()
3149 .filter_map(|line| file.get_line(line - 1).map(|s| (line + 1, s)))
3150 .find(|(_, s)| filter(s))
3151 .map(|(line, _)| line)
3152 .unwrap_or(ann.line_start);
3153 for line in ann.line_start + 1..until {
3154 // Every `|` that joins the beginning of the span (`___^`) to the end (`|__^`).
3155 add_annotation_to_file(&mut output, Arc::clone(&file), line, ann.as_line());
3156 }
3157 let line_end = ann.line_end - 1;
3158 let end_is_empty = file.get_line(line_end - 1).is_some_and(|s| !filter(&s));
3159 if middle < line_end && !end_is_empty {
3160 add_annotation_to_file(&mut output, Arc::clone(&file), line_end, ann.as_line());
3161 }
3162 } else {
3163 end_ann.annotation_type = AnnotationType::Singleline;
3164 }
3165 add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
3166 }
3167 for file_vec in output.iter_mut() {
3168 file_vec.multiline_depth = max_depth;
3169 }
3170 output
3171 }
3172}
3173
3174// instead of taking the String length or dividing by 10 while > 0, we multiply a limit by 10 until
3175// we're higher. If the loop isn't exited by the `return`, the last multiplication will wrap, which
3176// is OK, because while we cannot fit a higher power of 10 in a usize, the loop will end anyway.
3177// This is also why we need the max number of decimal digits within a `usize`.
3178fn num_decimal_digits(num: usize) -> usize {
3179 #[cfg(target_pointer_width = "64")]
3180 const MAX_DIGITS: usize = 20;
3181
3182 #[cfg(target_pointer_width = "32")]
3183 const MAX_DIGITS: usize = 10;
3184
3185 #[cfg(target_pointer_width = "16")]
3186 const MAX_DIGITS: usize = 5;
3187
3188 let mut lim = 10;
3189 for num_digits in 1..MAX_DIGITS {
3190 if num < lim {
3191 return num_digits;
3192 }
3193 lim = lim.wrapping_mul(10);
3194 }
3195 MAX_DIGITS
3196}
3197
3198// We replace some characters so the CLI output is always consistent and underlines aligned.
3199// Keep the following list in sync with `rustc_span::char_width`.
3200const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
3201 // In terminals without Unicode support the following will be garbled, but in *all* terminals
3202 // the underlying codepoint will be as well. We could gate this replacement behind a "unicode
3203 // support" gate.
3204 ('\0', "␀"),
3205 ('\u{0001}', "␁"),
3206 ('\u{0002}', "␂"),
3207 ('\u{0003}', "␃"),
3208 ('\u{0004}', "␄"),
3209 ('\u{0005}', "␅"),
3210 ('\u{0006}', "␆"),
3211 ('\u{0007}', "␇"),
3212 ('\u{0008}', "␈"),
3213 ('\t', " "), // We do our own tab replacement
3214 ('\u{000b}', "␋"),
3215 ('\u{000c}', "␌"),
3216 ('\u{000d}', "␍"),
3217 ('\u{000e}', "␎"),
3218 ('\u{000f}', "␏"),
3219 ('\u{0010}', "␐"),
3220 ('\u{0011}', "␑"),
3221 ('\u{0012}', "␒"),
3222 ('\u{0013}', "␓"),
3223 ('\u{0014}', "␔"),
3224 ('\u{0015}', "␕"),
3225 ('\u{0016}', "␖"),
3226 ('\u{0017}', "␗"),
3227 ('\u{0018}', "␘"),
3228 ('\u{0019}', "␙"),
3229 ('\u{001a}', "␚"),
3230 ('\u{001b}', "␛"),
3231 ('\u{001c}', "␜"),
3232 ('\u{001d}', "␝"),
3233 ('\u{001e}', "␞"),
3234 ('\u{001f}', "␟"),
3235 ('\u{007f}', "␡"),
3236 ('\u{200d}', ""), // Replace ZWJ for consistent terminal output of grapheme clusters.
3237 ('\u{202a}', "�"), // The following unicode text flow control characters are inconsistently
3238 ('\u{202b}', "�"), // supported across CLIs and can cause confusion due to the bytes on disk
3239 ('\u{202c}', "�"), // not corresponding to the visible source code, so we replace them always.
3240 ('\u{202d}', "�"),
3241 ('\u{202e}', "�"),
3242 ('\u{2066}', "�"),
3243 ('\u{2067}', "�"),
3244 ('\u{2068}', "�"),
3245 ('\u{2069}', "�"),
3246];
3247
3248fn normalize_whitespace(s: &str) -> String {
3249 const {
3250 let mut i = 1;
3251 while i < OUTPUT_REPLACEMENTS.len() {
3252 assert!(
3253 OUTPUT_REPLACEMENTS[i - 1].0 < OUTPUT_REPLACEMENTS[i].0,
3254 "The OUTPUT_REPLACEMENTS array must be sorted (for binary search to work) \
3255 and must contain no duplicate entries"
3256 );
3257 i += 1;
3258 }
3259 }
3260 // Scan the input string for a character in the ordered table above.
3261 // If it's present, replace it with its alternative string (it can be more than 1 char!).
3262 // Otherwise, retain the input char.
3263 s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
3264 match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
3265 Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1),
3266 _ => s.push(c),
3267 }
3268 s
3269 })
3270}
3271
3272fn num_overlap(
3273 a_start: usize,
3274 a_end: usize,
3275 b_start: usize,
3276 b_end: usize,
3277 inclusive: bool,
3278) -> bool {
3279 let extra = if inclusive { 1 } else { 0 };
3280 (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
3281}
3282
3283fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
3284 num_overlap(
3285 a1.start_col.display,
3286 a1.end_col.display + padding,
3287 a2.start_col.display,
3288 a2.end_col.display,
3289 false,
3290 )
3291}
3292
3293fn emit_to_destination(
3294 rendered_buffer: &[Vec<StyledString>],
3295 lvl: &Level,
3296 dst: &mut Destination,
3297 short_message: bool,
3298) -> io::Result<()> {
3299 use crate::lock;
3300
3301 // In order to prevent error message interleaving, where multiple error lines get intermixed
3302 // when multiple compiler processes error simultaneously, we emit errors with additional
3303 // steps.
3304 //
3305 // On Unix systems, we write into a buffered terminal rather than directly to a terminal. When
3306 // the .flush() is called we take the buffer created from the buffered writes and write it at
3307 // one shot. Because the Unix systems use ANSI for the colors, which is a text-based styling
3308 // scheme, this buffered approach works and maintains the styling.
3309 //
3310 // On Windows, styling happens through calls to a terminal API. This prevents us from using the
3311 // same buffering approach. Instead, we use a global Windows mutex, which we acquire long
3312 // enough to output the full error message, then we release.
3313 let _buffer_lock = lock::acquire_global_lock("rustc_errors");
3314 for (pos, line) in rendered_buffer.iter().enumerate() {
3315 for part in line {
3316 let style = part.style.color_spec(*lvl);
3317 dst.set_color(&style)?;
3318 write!(dst, "{}", part.text)?;
3319 dst.reset()?;
3320 }
3321 if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
3322 writeln!(dst)?;
3323 }
3324 }
3325 dst.flush()?;
3326 Ok(())
3327}
3328
3329pub type Destination = Box<dyn WriteColor + Send>;
3330
3331struct Buffy {
3332 buffer_writer: BufferWriter,
3333 buffer: Buffer,
3334}
3335
3336impl Write for Buffy {
3337 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
3338 self.buffer.write(buf)
3339 }
3340
3341 fn flush(&mut self) -> io::Result<()> {
3342 self.buffer_writer.print(&self.buffer)?;
3343 self.buffer.clear();
3344 Ok(())
3345 }
3346}
3347
3348impl Drop for Buffy {
3349 fn drop(&mut self) {
3350 if !self.buffer.is_empty() {
3351 self.flush().unwrap();
3352 panic!("buffers need to be flushed in order to print their contents");
3353 }
3354 }
3355}
3356
3357impl WriteColor for Buffy {
3358 fn supports_color(&self) -> bool {
3359 self.buffer.supports_color()
3360 }
3361
3362 fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
3363 self.buffer.set_color(spec)
3364 }
3365
3366 fn reset(&mut self) -> io::Result<()> {
3367 self.buffer.reset()
3368 }
3369}
3370
3371pub fn stderr_destination(color: ColorConfig) -> Destination {
3372 let choice = color.to_color_choice();
3373 // On Windows we'll be performing global synchronization on the entire
3374 // system for emitting rustc errors, so there's no need to buffer
3375 // anything.
3376 //
3377 // On non-Windows we rely on the atomicity of `write` to ensure errors
3378 // don't get all jumbled up.
3379 if cfg!(windows) {
3380 Box::new(StandardStream::stderr(choice))
3381 } else {
3382 let buffer_writer = BufferWriter::stderr(choice);
3383 let buffer = buffer_writer.buffer();
3384 Box::new(Buffy { buffer_writer, buffer })
3385 }
3386}
3387
3388/// On Windows, BRIGHT_BLUE is hard to read on black. Use cyan instead.
3389///
3390/// See #36178.
3391const BRIGHT_BLUE: Color = if cfg!(windows) { Color::Cyan } else { Color::Blue };
3392
3393impl Style {
3394 fn color_spec(&self, lvl: Level) -> ColorSpec {
3395 let mut spec = ColorSpec::new();
3396 match self {
3397 Style::Addition => {
3398 spec.set_fg(Some(Color::Green)).set_intense(true);
3399 }
3400 Style::Removal => {
3401 spec.set_fg(Some(Color::Red)).set_intense(true);
3402 }
3403 Style::LineAndColumn => {}
3404 Style::LineNumber => {
3405 spec.set_bold(true);
3406 spec.set_intense(true);
3407 spec.set_fg(Some(BRIGHT_BLUE));
3408 }
3409 Style::Quotation => {}
3410 Style::MainHeaderMsg => {
3411 spec.set_bold(true);
3412 if cfg!(windows) {
3413 spec.set_intense(true).set_fg(Some(Color::White));
3414 }
3415 }
3416 Style::UnderlinePrimary | Style::LabelPrimary => {
3417 spec = lvl.color();
3418 spec.set_bold(true);
3419 }
3420 Style::UnderlineSecondary | Style::LabelSecondary => {
3421 spec.set_bold(true).set_intense(true);
3422 spec.set_fg(Some(BRIGHT_BLUE));
3423 }
3424 Style::HeaderMsg | Style::NoStyle => {}
3425 Style::Level(lvl) => {
3426 spec = lvl.color();
3427 spec.set_bold(true);
3428 }
3429 Style::Highlight => {
3430 spec.set_bold(true).set_fg(Some(Color::Magenta));
3431 }
3432 }
3433 spec
3434 }
3435}
3436
3437/// Whether the original and suggested code are the same.
3438pub fn is_different(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3439 let found = match sm.span_to_snippet(sp) {
3440 Ok(snippet) => snippet,
3441 Err(e) => {
3442 warn!(error = ?e, "Invalid span {:?}", sp);
3443 return true;
3444 }
3445 };
3446 found != suggested
3447}
3448
3449/// Whether the original and suggested code are visually similar enough to warrant extra wording.
3450pub fn is_case_difference(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
3451 // FIXME: this should probably be extended to also account for `FO0` → `FOO` and unicode.
3452 let found = match sm.span_to_snippet(sp) {
3453 Ok(snippet) => snippet,
3454 Err(e) => {
3455 warn!(error = ?e, "Invalid span {:?}", sp);
3456 return false;
3457 }
3458 };
3459 let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
3460 // All the chars that differ in capitalization are confusable (above):
3461 let confusable = iter::zip(found.chars(), suggested.chars())
3462 .filter(|(f, s)| f != s)
3463 .all(|(f, s)| (ascii_confusables.contains(&f) || ascii_confusables.contains(&s)));
3464 confusable && found.to_lowercase() == suggested.to_lowercase()
3465 // FIXME: We sometimes suggest the same thing we already have, which is a
3466 // bug, but be defensive against that here.
3467 && found != suggested
3468}
3469
3470pub(crate) fn should_show_source_code(
3471 ignored_directories: &[String],
3472 sm: &SourceMap,
3473 file: &SourceFile,
3474) -> bool {
3475 if !sm.ensure_source_file_source_present(file) {
3476 return false;
3477 }
3478
3479 let FileName::Real(name) = &file.name else { return true };
3480 name.local_path()
3481 .map(|path| ignored_directories.iter().all(|dir| !path.starts_with(dir)))
3482 .unwrap_or(true)
3483}