]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_errors/src/emitter.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / compiler / rustc_errors / src / emitter.rs
CommitLineData
dc9dc135
XL
1//! The current rustc diagnostics emitter.
2//!
3//! An `Emitter` takes care of generating the output from a `DiagnosticBuilder` struct.
4//!
5//! There are various `Emitter` implementations that generate different output formats such as
6//! JSON and human readable output.
7//!
ba9703b0 8//! The output types are defined in `rustc_session::config::ErrorOutputType`.
dc9dc135 9
9fa01778 10use Destination::*;
1a4d82fc 11
dfeec247 12use rustc_span::source_map::SourceMap;
923072b8 13use rustc_span::{FileLines, SourceFile, Span};
223e47cc 14
60c5eb7d
XL
15use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Style, StyledString};
16use crate::styled_buffer::StyledBuffer;
94222f64 17use crate::{
04454e1e
FG
18 CodeSuggestion, Diagnostic, DiagnosticArg, DiagnosticId, DiagnosticMessage, FluentBundle,
19 Handler, LazyFallbackBundle, Level, MultiSpan, SubDiagnostic, SubstitutionHighlight,
94222f64
XL
20 SuggestionStyle,
21};
29967ef6
XL
22
23use rustc_lint_defs::pluralize;
9cc50fc6 24
b7449926 25use rustc_data_structures::fx::FxHashMap;
0531ce1d 26use rustc_data_structures::sync::Lrc;
04454e1e 27use rustc_error_messages::FluentArgs;
dfeec247 28use rustc_span::hygiene::{ExpnKind, MacroKind};
041b39d2 29use std::borrow::Cow;
60c5eb7d 30use std::cmp::{max, min, Reverse};
c34b1796 31use std::io;
60c5eb7d 32use std::io::prelude::*;
74b04a01 33use std::iter;
48663c56 34use std::path::Path;
60c5eb7d
XL
35use termcolor::{Ansi, BufferWriter, ColorChoice, ColorSpec, StandardStream};
36use termcolor::{Buffer, Color, WriteColor};
3dfed10e 37use tracing::*;
1a4d82fc 38
f035d41b
XL
39/// Default column width, used in tests and when terminal dimensions cannot be determined.
40const DEFAULT_COLUMN_WIDTH: usize = 140;
41
48663c56
XL
42/// Describes the way the content of the `rendered` field of the json output is generated
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum HumanReadableErrorType {
45 Default(ColorConfig),
dc9dc135 46 AnnotateSnippet(ColorConfig),
48663c56
XL
47 Short(ColorConfig),
48}
49
50impl HumanReadableErrorType {
51 /// Returns a (`short`, `color`) tuple
52 pub fn unzip(self) -> (bool, ColorConfig) {
53 match self {
54 HumanReadableErrorType::Default(cc) => (false, cc),
55 HumanReadableErrorType::Short(cc) => (true, cc),
dc9dc135 56 HumanReadableErrorType::AnnotateSnippet(cc) => (false, cc),
48663c56
XL
57 }
58 }
59 pub fn new_emitter(
60 self,
61 dst: Box<dyn Write + Send>,
60c5eb7d 62 source_map: Option<Lrc<SourceMap>>,
04454e1e
FG
63 bundle: Option<Lrc<FluentBundle>>,
64 fallback_bundle: LazyFallbackBundle,
48663c56 65 teach: bool,
e1599b0c 66 terminal_width: Option<usize>,
74b04a01 67 macro_backtrace: bool,
48663c56
XL
68 ) -> EmitterWriter {
69 let (short, color_config) = self.unzip();
e1599b0c 70 let color = color_config.suggests_using_colors();
04454e1e
FG
71 EmitterWriter::new(
72 dst,
73 source_map,
74 bundle,
75 fallback_bundle,
76 short,
77 teach,
78 color,
79 terminal_width,
80 macro_backtrace,
81 )
e1599b0c
XL
82 }
83}
84
85#[derive(Clone, Copy, Debug)]
86struct Margin {
87 /// The available whitespace in the left that can be consumed when centering.
88 pub whitespace_left: usize,
89 /// The column of the beginning of left-most span.
90 pub span_left: usize,
91 /// The column of the end of right-most span.
92 pub span_right: usize,
93 /// The beginning of the line to be displayed.
94 pub computed_left: usize,
95 /// The end of the line to be displayed.
96 pub computed_right: usize,
f035d41b
XL
97 /// The current width of the terminal. Uses value of `DEFAULT_COLUMN_WIDTH` constant by default
98 /// and in tests.
e1599b0c
XL
99 pub column_width: usize,
100 /// The end column of a span label, including the span. Doesn't account for labels not in the
101 /// same line as the span.
102 pub label_right: usize,
103}
104
105impl Margin {
106 fn new(
107 whitespace_left: usize,
108 span_left: usize,
109 span_right: usize,
110 label_right: usize,
111 column_width: usize,
112 max_line_len: usize,
113 ) -> Self {
114 // The 6 is padding to give a bit of room for `...` when displaying:
115 // ```
116 // error: message
117 // --> file.rs:16:58
118 // |
119 // 16 | ... fn foo(self) -> Self::Bar {
120 // | ^^^^^^^^^
121 // ```
122
123 let mut m = Margin {
e74abb32
XL
124 whitespace_left: whitespace_left.saturating_sub(6),
125 span_left: span_left.saturating_sub(6),
e1599b0c
XL
126 span_right: span_right + 6,
127 computed_left: 0,
128 computed_right: 0,
129 column_width,
130 label_right: label_right + 6,
131 };
132 m.compute(max_line_len);
133 m
134 }
135
136 fn was_cut_left(&self) -> bool {
137 self.computed_left > 0
138 }
139
140 fn was_cut_right(&self, line_len: usize) -> bool {
60c5eb7d
XL
141 let right =
142 if self.computed_right == self.span_right || self.computed_right == self.label_right {
dfeec247
XL
143 // Account for the "..." padding given above. Otherwise we end up with code lines that
144 // do fit but end in "..." as if they were trimmed.
60c5eb7d
XL
145 self.computed_right - 6
146 } else {
147 self.computed_right
148 };
e74abb32 149 right < line_len && self.computed_left + self.column_width < line_len
e1599b0c
XL
150 }
151
152 fn compute(&mut self, max_line_len: usize) {
153 // When there's a lot of whitespace (>20), we want to trim it as it is useless.
154 self.computed_left = if self.whitespace_left > 20 {
155 self.whitespace_left - 16 // We want some padding.
156 } else {
157 0
158 };
159 // We want to show as much as possible, max_line_len is the right-most boundary for the
160 // relevant code.
161 self.computed_right = max(max_line_len, self.computed_left);
162
163 if self.computed_right - self.computed_left > self.column_width {
164 // Trimming only whitespace isn't enough, let's get craftier.
165 if self.label_right - self.whitespace_left <= self.column_width {
166 // Attempt to fit the code window only trimming whitespace.
167 self.computed_left = self.whitespace_left;
168 self.computed_right = self.computed_left + self.column_width;
169 } else if self.label_right - self.span_left <= self.column_width {
170 // Attempt to fit the code window considering only the spans and labels.
171 let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2;
172 self.computed_left = self.span_left.saturating_sub(padding_left);
173 self.computed_right = self.computed_left + self.column_width;
174 } else if self.span_right - self.span_left <= self.column_width {
175 // Attempt to fit the code window considering the spans and labels plus padding.
176 let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2;
177 self.computed_left = self.span_left.saturating_sub(padding_left);
178 self.computed_right = self.computed_left + self.column_width;
60c5eb7d
XL
179 } else {
180 // Mostly give up but still don't show the full line.
e1599b0c
XL
181 self.computed_left = self.span_left;
182 self.computed_right = self.span_right;
183 }
184 }
185 }
186
187 fn left(&self, line_len: usize) -> usize {
188 min(self.computed_left, line_len)
189 }
190
191 fn right(&self, line_len: usize) -> usize {
e74abb32 192 if line_len.saturating_sub(self.computed_left) <= self.column_width {
e1599b0c
XL
193 line_len
194 } else {
e74abb32 195 min(line_len, self.computed_right)
e1599b0c 196 }
48663c56
XL
197 }
198}
199
0531ce1d
XL
200const ANONYMIZED_LINE_NUM: &str = "LL";
201
5bcae85e 202/// Emitter trait for emitting errors.
9cc50fc6 203pub trait Emitter {
9cc50fc6 204 /// Emit a structured diagnostic.
e74abb32 205 fn emit_diagnostic(&mut self, diag: &Diagnostic);
48663c56
XL
206
207 /// Emit a notification that an artifact has been output.
208 /// This is currently only supported for the JSON format,
209 /// other formats can, and will, simply ignore it.
dc9dc135 210 fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {}
0531ce1d 211
136023e0 212 fn emit_future_breakage_report(&mut self, _diags: Vec<Diagnostic>) {}
29967ef6 213
cdc7bbd5 214 /// Emit list of unused externs
04454e1e
FG
215 fn emit_unused_externs(
216 &mut self,
217 _lint_level: rustc_lint_defs::Level,
218 _unused_externs: &[&str],
219 ) {
220 }
cdc7bbd5 221
9fa01778 222 /// Checks if should show explanations about "rustc --explain"
0531ce1d
XL
223 fn should_show_explain(&self) -> bool {
224 true
225 }
a7813a04 226
fc512014
XL
227 /// Checks if we can use colors in the current output stream.
228 fn supports_color(&self) -> bool {
229 false
230 }
231
60c5eb7d 232 fn source_map(&self) -> Option<&Lrc<SourceMap>>;
e74abb32 233
04454e1e
FG
234 /// Return `FluentBundle` with localized diagnostics for the locale requested by the user. If no
235 /// language was requested by the user then this will be `None` and `fallback_fluent_bundle`
236 /// should be used.
237 fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>>;
238
239 /// Return `FluentBundle` with localized diagnostics for the default locale of the compiler.
240 /// Used when the user has not requested a specific language or when a localized diagnostic is
241 /// unavailable for the requested locale.
242 fn fallback_fluent_bundle(&self) -> &FluentBundle;
243
244 /// Convert diagnostic arguments (a rustc internal type that exists to implement
245 /// `Encodable`/`Decodable`) into `FluentArgs` which is necessary to perform translation.
246 ///
247 /// Typically performed once for each diagnostic at the start of `emit_diagnostic` and then
248 /// passed around as a reference thereafter.
249 fn to_fluent_args<'arg>(&self, args: &[DiagnosticArg<'arg>]) -> FluentArgs<'arg> {
250 FromIterator::from_iter(args.to_vec().drain(..))
251 }
252
253 /// Convert `DiagnosticMessage`s to a string, performing translation if necessary.
254 fn translate_messages(
255 &self,
256 messages: &[(DiagnosticMessage, Style)],
257 args: &FluentArgs<'_>,
258 ) -> Cow<'_, str> {
259 Cow::Owned(
260 messages.iter().map(|(m, _)| self.translate_message(m, args)).collect::<String>(),
261 )
262 }
263
264 /// Convert a `DiagnosticMessage` to a string, performing translation if necessary.
265 fn translate_message<'a>(
266 &'a self,
267 message: &'a DiagnosticMessage,
268 args: &'a FluentArgs<'_>,
269 ) -> Cow<'_, str> {
270 trace!(?message, ?args);
271 let (identifier, attr) = match message {
272 DiagnosticMessage::Str(msg) => return Cow::Borrowed(&msg),
273 DiagnosticMessage::FluentIdentifier(identifier, attr) => (identifier, attr),
274 };
275
276 let bundle = match self.fluent_bundle() {
277 Some(bundle) if bundle.has_message(&identifier) => bundle,
278 _ => self.fallback_fluent_bundle(),
279 };
280
281 let message = bundle.get_message(&identifier).expect("missing diagnostic in fluent bundle");
282 let value = match attr {
283 Some(attr) => {
284 message.get_attribute(attr).expect("missing attribute in fluent message").value()
285 }
286 None => message.value().expect("missing value in fluent message"),
287 };
288
289 let mut err = vec![];
290 let translated = bundle.format_pattern(value, Some(&args), &mut err);
291 trace!(?translated, ?err);
292 debug_assert!(
293 err.is_empty(),
294 "identifier: {:?}, args: {:?}, errors: {:?}",
295 identifier,
296 args,
297 err
298 );
299 translated
300 }
301
e1599b0c
XL
302 /// Formats the substitutions of the primary_span
303 ///
a2a8927a 304 /// There are a lot of conditions to this method, but in short:
e1599b0c
XL
305 ///
306 /// * If the current `Diagnostic` has only one visible `CodeSuggestion`,
307 /// we format the `help` suggestion depending on the content of the
308 /// substitutions. In that case, we return the modified span only.
309 ///
310 /// * If the current `Diagnostic` has multiple suggestions,
311 /// we return the original `primary_span` and the original suggestions.
312 fn primary_span_formatted<'a>(
313 &mut self,
e74abb32 314 diag: &'a Diagnostic,
04454e1e 315 fluent_args: &FluentArgs<'_>,
e1599b0c 316 ) -> (MultiSpan, &'a [CodeSuggestion]) {
e74abb32 317 let mut primary_span = diag.span.clone();
5099ac24
FG
318 let suggestions = diag.suggestions.as_ref().map_or(&[][..], |suggestions| &suggestions[..]);
319 if let Some((sugg, rest)) = suggestions.split_first() {
04454e1e 320 let msg = self.translate_message(&sugg.msg, fluent_args);
7cac9316 321 if rest.is_empty() &&
e1599b0c 322 // ^ if there is only one suggestion
7cac9316 323 // don't display multi-suggestions as labels
abe05a73
XL
324 sugg.substitutions.len() == 1 &&
325 // don't display multipart suggestions as labels
326 sugg.substitutions[0].parts.len() == 1 &&
7cac9316 327 // don't display long messages as labels
04454e1e 328 msg.split_whitespace().count() < 10 &&
7cac9316 329 // don't display multiline suggestions as labels
9fa01778 330 !sugg.substitutions[0].parts[0].snippet.contains('\n') &&
e74abb32
XL
331 ![
332 // when this style is set we want the suggestion to be a message, not inline
333 SuggestionStyle::HideCodeAlways,
334 // trivial suggestion for tooling's sake, never shown
335 SuggestionStyle::CompletelyHidden,
336 // subtle suggestion, never shown inline
337 SuggestionStyle::ShowAlways,
338 ].contains(&sugg.style)
9fa01778 339 {
ff7c6d11 340 let substitution = &sugg.substitutions[0].parts[0].snippet.trim();
74b04a01 341 let msg = if substitution.is_empty() || sugg.style.hide_inline() {
e1599b0c
XL
342 // This substitution is only removal OR we explicitly don't want to show the
343 // code inline (`hide_inline`). Therefore, we don't show the substitution.
04454e1e 344 format!("help: {}", &msg)
041b39d2 345 } else {
e1599b0c 346 // Show the default suggestion text with the substitution
e74abb32
XL
347 format!(
348 "help: {}{}: `{}`",
04454e1e 349 &msg,
60c5eb7d
XL
350 if self
351 .source_map()
352 .map(|sm| is_case_difference(
353 &**sm,
354 substitution,
355 sugg.substitutions[0].parts[0].span,
356 ))
357 .unwrap_or(false)
358 {
e74abb32
XL
359 " (notice the capitalization)"
360 } else {
361 ""
362 },
363 substitution,
364 )
041b39d2 365 };
abe05a73 366 primary_span.push_span_label(sugg.substitutions[0].parts[0].span, msg);
e1599b0c
XL
367
368 // We return only the modified primary_span
369 (primary_span, &[])
7cac9316
XL
370 } else {
371 // if there are multiple suggestions, print them all in full
372 // to be consistent. We could try to figure out if we can
373 // make one (or the first one) inline, but that would give
374 // undue importance to a semi-random suggestion
5099ac24 375 (primary_span, suggestions)
7cac9316 376 }
e1599b0c 377 } else {
5099ac24 378 (primary_span, suggestions)
e1599b0c
XL
379 }
380 }
381
74b04a01 382 fn fix_multispans_in_extern_macros_and_render_macro_backtrace(
60c5eb7d
XL
383 &self,
384 source_map: &Option<Lrc<SourceMap>>,
385 span: &mut MultiSpan,
386 children: &mut Vec<SubDiagnostic>,
387 level: &Level,
388 backtrace: bool,
389 ) {
74b04a01
XL
390 // Check for spans in macros, before `fix_multispans_in_extern_macros`
391 // has a chance to replace them.
392 let has_macro_spans = iter::once(&*span)
393 .chain(children.iter().map(|child| &child.span))
394 .flat_map(|span| span.primary_spans())
f9f354fc
XL
395 .flat_map(|sp| sp.macro_backtrace())
396 .find_map(|expn_data| {
397 match expn_data.kind {
398 ExpnKind::Root => None,
74b04a01 399
f9f354fc
XL
400 // Skip past non-macro entries, just in case there
401 // are some which do actually involve macros.
29967ef6 402 ExpnKind::Inlined | ExpnKind::Desugaring(..) | ExpnKind::AstPass(..) => None,
74b04a01 403
136023e0 404 ExpnKind::Macro(macro_kind, name) => Some((macro_kind, name)),
f9f354fc
XL
405 }
406 });
74b04a01
XL
407
408 if !backtrace {
409 self.fix_multispans_in_extern_macros(source_map, span, children);
7cac9316
XL
410 }
411
74b04a01
XL
412 self.render_multispans_macro_backtrace(span, children, backtrace);
413
414 if !backtrace {
17df50a5
XL
415 if let Some((macro_kind, name)) = has_macro_spans {
416 let descr = macro_kind.descr();
417
74b04a01 418 let msg = format!(
17df50a5 419 "this {level} originates in the {descr} `{name}` \
74b04a01 420 (in Nightly builds, run with -Z macro-backtrace for more info)",
74b04a01
XL
421 );
422
423 children.push(SubDiagnostic {
424 level: Level::Note,
04454e1e 425 message: vec![(DiagnosticMessage::Str(msg), Style::NoStyle)],
74b04a01
XL
426 span: MultiSpan::new(),
427 render_span: None,
428 });
429 }
e1599b0c
XL
430 }
431 }
432
74b04a01 433 fn render_multispans_macro_backtrace(
60c5eb7d 434 &self,
60c5eb7d 435 span: &mut MultiSpan,
74b04a01
XL
436 children: &mut Vec<SubDiagnostic>,
437 backtrace: bool,
438 ) {
439 for span in iter::once(span).chain(children.iter_mut().map(|child| &mut child.span)) {
440 self.render_multispan_macro_backtrace(span, backtrace);
441 }
442 }
e1599b0c 443
74b04a01 444 fn render_multispan_macro_backtrace(&self, span: &mut MultiSpan, always_backtrace: bool) {
e74abb32 445 let mut new_labels: Vec<(Span, String)> = vec![];
e1599b0c 446
74b04a01 447 for &sp in span.primary_spans() {
e74abb32
XL
448 if sp.is_dummy() {
449 continue;
450 }
74b04a01
XL
451
452 // FIXME(eddyb) use `retain` on `macro_backtrace` to remove all the
453 // entries we don't want to print, to make sure the indices being
454 // printed are contiguous (or omitted if there's only one entry).
dfeec247 455 let macro_backtrace: Vec<_> = sp.macro_backtrace().collect();
dfeec247 456 for (i, trace) in macro_backtrace.iter().rev().enumerate() {
dfeec247 457 if trace.def_site.is_dummy() {
e1599b0c
XL
458 continue;
459 }
74b04a01 460
136023e0 461 if always_backtrace && !matches!(trace.kind, ExpnKind::Inlined) {
60c5eb7d 462 new_labels.push((
dfeec247 463 trace.def_site,
60c5eb7d 464 format!(
136023e0 465 "in this expansion of `{}`{}",
dfeec247 466 trace.kind.descr(),
3dfed10e 467 if macro_backtrace.len() > 1 {
74b04a01
XL
468 // if macro_backtrace.len() == 1 it'll be
469 // pointed at by "in this macro invocation"
60c5eb7d
XL
470 format!(" (#{})", i + 1)
471 } else {
472 String::new()
74b04a01 473 },
60c5eb7d
XL
474 ),
475 ));
e1599b0c 476 }
74b04a01
XL
477
478 // Don't add a label on the call site if the diagnostic itself
479 // already points to (a part of) that call site, as the label
480 // is meant for showing the relevant invocation when the actual
481 // diagnostic is pointing to some part of macro definition.
482 //
483 // This also handles the case where an external span got replaced
484 // with the call site span by `fix_multispans_in_extern_macros`.
485 //
486 // NB: `-Zmacro-backtrace` overrides this, for uniformity, as the
487 // "in this expansion of" label above is always added in that mode,
488 // and it needs an "in this macro invocation" label to match that.
489 let redundant_span = trace.call_site.contains(sp);
490
136023e0
XL
491 if !redundant_span || always_backtrace {
492 let msg: Cow<'static, _> = match trace.kind {
493 ExpnKind::Macro(MacroKind::Attr, _) => {
494 "this procedural macro expansion".into()
495 }
496 ExpnKind::Macro(MacroKind::Derive, _) => {
497 "this derive macro expansion".into()
498 }
499 ExpnKind::Macro(MacroKind::Bang, _) => "this macro invocation".into(),
5e7ed085
FG
500 ExpnKind::Inlined => "this inlined function call".into(),
501 ExpnKind::Root => "the crate root".into(),
136023e0
XL
502 ExpnKind::AstPass(kind) => kind.descr().into(),
503 ExpnKind::Desugaring(kind) => {
504 format!("this {} desugaring", kind.descr()).into()
505 }
506 };
60c5eb7d
XL
507 new_labels.push((
508 trace.call_site,
509 format!(
136023e0
XL
510 "in {}{}",
511 msg,
3dfed10e 512 if macro_backtrace.len() > 1 && always_backtrace {
60c5eb7d
XL
513 // only specify order when the macro
514 // backtrace is multiple levels deep
515 format!(" (#{})", i + 1)
516 } else {
517 String::new()
74b04a01 518 },
60c5eb7d
XL
519 ),
520 ));
74b04a01
XL
521 }
522 if !always_backtrace {
523 break;
e1599b0c
XL
524 }
525 }
e74abb32 526 }
74b04a01 527
e74abb32
XL
528 for (label_span, label_text) in new_labels {
529 span.push_span_label(label_span, label_text);
530 }
74b04a01
XL
531 }
532
533 // This does a small "fix" for multispans by looking to see if it can find any that
ba9703b0
XL
534 // point directly at external macros. Since these are often difficult to read,
535 // this will change the span to point at the use site.
74b04a01
XL
536 fn fix_multispans_in_extern_macros(
537 &self,
538 source_map: &Option<Lrc<SourceMap>>,
539 span: &mut MultiSpan,
540 children: &mut Vec<SubDiagnostic>,
541 ) {
3c0e092e 542 let Some(source_map) = source_map else { return };
ba9703b0 543 debug!("fix_multispans_in_extern_macros: before: span={:?} children={:?}", span, children);
cdc7bbd5
XL
544 self.fix_multispan_in_extern_macros(source_map, span);
545 for child in children.iter_mut() {
546 self.fix_multispan_in_extern_macros(source_map, &mut child.span);
e74abb32 547 }
ba9703b0 548 debug!("fix_multispans_in_extern_macros: after: span={:?} children={:?}", span, children);
74b04a01
XL
549 }
550
ba9703b0
XL
551 // This "fixes" MultiSpans that contain `Span`s pointing to locations inside of external macros.
552 // Since these locations are often difficult to read,
553 // we move these spans from the external macros to their corresponding use site.
cdc7bbd5 554 fn fix_multispan_in_extern_macros(&self, source_map: &Lrc<SourceMap>, span: &mut MultiSpan) {
ba9703b0 555 // First, find all the spans in external macros and point instead at their use site.
74b04a01
XL
556 let replacements: Vec<(Span, Span)> = span
557 .primary_spans()
558 .iter()
559 .copied()
560 .chain(span.span_labels().iter().map(|sp_label| sp_label.span))
561 .filter_map(|sp| {
cdc7bbd5 562 if !sp.is_dummy() && source_map.is_imported(sp) {
74b04a01
XL
563 let maybe_callsite = sp.source_callsite();
564 if sp != maybe_callsite {
565 return Some((sp, maybe_callsite));
566 }
567 }
568 None
569 })
570 .collect();
571
ba9703b0 572 // After we have them, make sure we replace these 'bad' def sites with their use sites.
74b04a01
XL
573 for (from, to) in replacements {
574 span.replace(from, to);
e1599b0c 575 }
e1599b0c
XL
576 }
577}
578
579impl Emitter for EmitterWriter {
60c5eb7d 580 fn source_map(&self) -> Option<&Lrc<SourceMap>> {
e74abb32
XL
581 self.sm.as_ref()
582 }
583
04454e1e
FG
584 fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
585 self.fluent_bundle.as_ref()
586 }
587
588 fn fallback_fluent_bundle(&self) -> &FluentBundle {
589 &**self.fallback_bundle
590 }
591
e74abb32 592 fn emit_diagnostic(&mut self, diag: &Diagnostic) {
04454e1e
FG
593 let fluent_args = self.to_fluent_args(diag.args());
594
e74abb32 595 let mut children = diag.children.clone();
04454e1e 596 let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args);
ba9703b0 597 debug!("emit_diagnostic: suggestions={:?}", suggestions);
e1599b0c 598
74b04a01 599 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
60c5eb7d
XL
600 &self.sm,
601 &mut primary_span,
602 &mut children,
603 &diag.level,
74b04a01 604 self.macro_backtrace,
60c5eb7d
XL
605 );
606
607 self.emit_messages_default(
608 &diag.level,
04454e1e
FG
609 &diag.message,
610 &fluent_args,
60c5eb7d
XL
611 &diag.code,
612 &primary_span,
613 &children,
614 &suggestions,
615 );
1a4d82fc 616 }
0531ce1d
XL
617
618 fn should_show_explain(&self) -> bool {
619 !self.short_message
620 }
fc512014
XL
621
622 fn supports_color(&self) -> bool {
623 self.dst.supports_color()
624 }
223e47cc
LB
625}
626
3c0e092e
XL
627/// An emitter that does nothing when emitting a non-fatal diagnostic.
628/// Fatal diagnostics are forwarded to `fatal_handler` to avoid silent
629/// failures of rustc, as witnessed e.g. in issue #89358.
630pub struct SilentEmitter {
631 pub fatal_handler: Handler,
632 pub fatal_note: Option<String>,
633}
60c5eb7d
XL
634
635impl Emitter for SilentEmitter {
636 fn source_map(&self) -> Option<&Lrc<SourceMap>> {
637 None
638 }
04454e1e
FG
639
640 fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
641 None
642 }
643
644 fn fallback_fluent_bundle(&self) -> &FluentBundle {
645 panic!("silent emitter attempted to translate message")
646 }
647
3c0e092e
XL
648 fn emit_diagnostic(&mut self, d: &Diagnostic) {
649 if d.level == Level::Fatal {
650 let mut d = d.clone();
651 if let Some(ref note) = self.fatal_note {
652 d.note(note);
653 }
5e7ed085 654 self.fatal_handler.emit_diagnostic(&mut d);
3c0e092e
XL
655 }
656 }
60c5eb7d
XL
657}
658
dfeec247
XL
659/// Maximum number of lines we will print for a multiline suggestion; arbitrary.
660///
661/// This should be replaced with a more involved mechanism to output multiline suggestions that
29967ef6 662/// more closely mimics the regular diagnostic output, where irrelevant code lines are elided.
dfeec247
XL
663pub const MAX_SUGGESTION_HIGHLIGHT_LINES: usize = 6;
664/// Maximum number of suggestions to be shown
7cac9316
XL
665///
666/// Arbitrary, but taken from trait import suggestion limit
667pub const MAX_SUGGESTIONS: usize = 4;
7453a54e 668
9cc50fc6 669#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1a4d82fc
JJ
670pub enum ColorConfig {
671 Auto,
672 Always,
9cc50fc6 673 Never,
223e47cc
LB
674}
675
9cc50fc6 676impl ColorConfig {
48663c56
XL
677 fn to_color_choice(self) -> ColorChoice {
678 match self {
a1dfa0c6
XL
679 ColorConfig::Always => {
680 if atty::is(atty::Stream::Stderr) {
681 ColorChoice::Always
682 } else {
683 ColorChoice::AlwaysAnsi
684 }
685 }
0531ce1d 686 ColorConfig::Never => ColorChoice::Never,
60c5eb7d 687 ColorConfig::Auto if atty::is(atty::Stream::Stderr) => ColorChoice::Auto,
0531ce1d 688 ColorConfig::Auto => ColorChoice::Never,
9cc50fc6 689 }
d9579d0f 690 }
48663c56
XL
691 fn suggests_using_colors(self) -> bool {
692 match self {
60c5eb7d 693 ColorConfig::Always | ColorConfig::Auto => true,
48663c56
XL
694 ColorConfig::Never => false,
695 }
696 }
d9579d0f
AL
697}
698
dc9dc135 699/// Handles the writing of `HumanReadableErrorType::Default` and `HumanReadableErrorType::Short`
9cc50fc6
SL
700pub struct EmitterWriter {
701 dst: Destination,
60c5eb7d 702 sm: Option<Lrc<SourceMap>>,
04454e1e
FG
703 fluent_bundle: Option<Lrc<FluentBundle>>,
704 fallback_bundle: LazyFallbackBundle,
abe05a73 705 short_message: bool,
2c00a5a8 706 teach: bool,
0531ce1d 707 ui_testing: bool,
e1599b0c
XL
708 terminal_width: Option<usize>,
709
74b04a01 710 macro_backtrace: bool,
a7813a04 711}
223e47cc 712
dc9dc135
XL
713#[derive(Debug)]
714pub struct FileWithAnnotatedLines {
715 pub file: Lrc<SourceFile>,
716 pub lines: Vec<Line>,
476ff2be 717 multiline_depth: usize,
223e47cc
LB
718}
719
1a4d82fc 720impl EmitterWriter {
e1599b0c
XL
721 pub fn stderr(
722 color_config: ColorConfig,
60c5eb7d 723 source_map: Option<Lrc<SourceMap>>,
04454e1e
FG
724 fluent_bundle: Option<Lrc<FluentBundle>>,
725 fallback_bundle: LazyFallbackBundle,
e1599b0c
XL
726 short_message: bool,
727 teach: bool,
728 terminal_width: Option<usize>,
74b04a01 729 macro_backtrace: bool,
e1599b0c 730 ) -> EmitterWriter {
0531ce1d
XL
731 let dst = Destination::from_stderr(color_config);
732 EmitterWriter {
733 dst,
a1dfa0c6 734 sm: source_map,
04454e1e
FG
735 fluent_bundle,
736 fallback_bundle,
0531ce1d
XL
737 short_message,
738 teach,
739 ui_testing: false,
e1599b0c 740 terminal_width,
74b04a01 741 macro_backtrace,
1a4d82fc
JJ
742 }
743 }
744
48663c56
XL
745 pub fn new(
746 dst: Box<dyn Write + Send>,
60c5eb7d 747 source_map: Option<Lrc<SourceMap>>,
04454e1e
FG
748 fluent_bundle: Option<Lrc<FluentBundle>>,
749 fallback_bundle: LazyFallbackBundle,
48663c56
XL
750 short_message: bool,
751 teach: bool,
752 colored: bool,
e1599b0c 753 terminal_width: Option<usize>,
74b04a01 754 macro_backtrace: bool,
48663c56 755 ) -> EmitterWriter {
c30ab7b3 756 EmitterWriter {
48663c56 757 dst: Raw(dst, colored),
a1dfa0c6 758 sm: source_map,
04454e1e
FG
759 fluent_bundle,
760 fallback_bundle,
2c00a5a8
XL
761 short_message,
762 teach,
0531ce1d 763 ui_testing: false,
e1599b0c 764 terminal_width,
74b04a01 765 macro_backtrace,
0531ce1d
XL
766 }
767 }
768
769 pub fn ui_testing(mut self, ui_testing: bool) -> Self {
770 self.ui_testing = ui_testing;
771 self
772 }
773
923072b8
FG
774 fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> {
775 if self.ui_testing {
776 Cow::Borrowed(ANONYMIZED_LINE_NUM)
777 } else {
778 Cow::Owned(line_num.to_string())
779 }
a7813a04
XL
780 }
781
e1599b0c
XL
782 fn draw_line(
783 &self,
784 buffer: &mut StyledBuffer,
785 source_string: &str,
786 line_index: usize,
787 line_offset: usize,
788 width_offset: usize,
789 code_offset: usize,
790 margin: Margin,
791 ) {
5869c6ff
XL
792 // Tabs are assumed to have been replaced by spaces in calling code.
793 debug_assert!(!source_string.contains('\t'));
e1599b0c
XL
794 let line_len = source_string.len();
795 // Create the source line we will highlight.
796 let left = margin.left(line_len);
797 let right = margin.right(line_len);
798 // On long lines, we strip the source line, accounting for unicode.
799 let mut taken = 0;
60c5eb7d
XL
800 let code: String = source_string
801 .chars()
802 .skip(left)
803 .take_while(|ch| {
dfeec247
XL
804 // Make sure that the trimming on the right will fall within the terminal width.
805 // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is.
806 // For now, just accept that sometimes the code line will be longer than desired.
60c5eb7d
XL
807 let next = unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1);
808 if taken + next > right - left {
809 return false;
810 }
811 taken += next;
812 true
813 })
814 .collect();
e1599b0c
XL
815 buffer.puts(line_offset, code_offset, &code, Style::Quotation);
816 if margin.was_cut_left() {
817 // We have stripped some code/whitespace from the beginning, make it clear.
818 buffer.puts(line_offset, code_offset, "...", Style::LineNumber);
819 }
820 if margin.was_cut_right(line_len) {
821 // We have stripped some code after the right-most span end, make it clear we did so.
822 buffer.puts(line_offset, code_offset + taken - 3, "...", Style::LineNumber);
823 }
824 buffer.puts(line_offset, 0, &self.maybe_anonymized(line_index), Style::LineNumber);
825
923072b8 826 draw_col_separator_no_space(buffer, line_offset, width_offset - 2);
e1599b0c
XL
827 }
828
829 fn render_source_line(
830 &self,
831 buffer: &mut StyledBuffer,
832 file: Lrc<SourceFile>,
833 line: &Line,
834 width_offset: usize,
835 code_offset: usize,
836 margin: Margin,
837 ) -> Vec<(usize, Style)> {
838 // Draw:
839 //
840 // LL | ... code ...
841 // | ^^-^ span label
842 // | |
843 // | secondary span label
844 //
845 // ^^ ^ ^^^ ^^^^ ^^^ we don't care about code too far to the right of a span, we trim it
846 // | | | |
847 // | | | actual code found in your source code and the spans we use to mark it
848 // | | when there's too much wasted space to the left, trim it
849 // | vertical divider between the column number and the code
850 // column number
851
2c00a5a8
XL
852 if line.line_index == 0 {
853 return Vec::new();
854 }
855
7cac9316 856 let source_string = match file.get_line(line.line_index - 1) {
3c0e092e 857 Some(s) => normalize_whitespace(&*s),
7cac9316
XL
858 None => return Vec::new(),
859 };
5bcae85e
SL
860
861 let line_offset = buffer.num_lines();
c1a9b12d 862
a2a8927a
XL
863 // Left trim
864 let left = margin.left(source_string.len());
865
e1599b0c 866 // Account for unicode characters of width !=0 that were removed.
60c5eb7d
XL
867 let left = source_string
868 .chars()
869 .take(left)
e74abb32
XL
870 .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
871 .sum();
5bcae85e 872
e1599b0c
XL
873 self.draw_line(
874 buffer,
875 &source_string,
876 line.line_index,
877 line_offset,
878 width_offset,
879 code_offset,
880 margin,
881 );
5bcae85e 882
cc61c64b
XL
883 // Special case when there's only one annotation involved, it is the start of a multiline
884 // span and there's no text at the beginning of the code line. Instead of doing the whole
885 // graph:
886 //
887 // 2 | fn foo() {
888 // | _^
889 // 3 | |
890 // 4 | | }
891 // | |_^ test
892 //
893 // we simplify the output to:
894 //
895 // 2 | / fn foo() {
896 // 3 | |
897 // 4 | | }
898 // | |_^ test
e74abb32
XL
899 if let [ann] = &line.annotations[..] {
900 if let AnnotationType::MultilineStart(depth) = ann.annotation_type {
901 if source_string.chars().take(ann.start_col).all(|c| c.is_whitespace()) {
902 let style = if ann.is_primary {
903 Style::UnderlinePrimary
904 } else {
905 Style::UnderlineSecondary
906 };
907 buffer.putc(line_offset, width_offset + depth - 1, '/', style);
908 return vec![(depth, style)];
cc61c64b
XL
909 }
910 }
911 }
912
5bcae85e
SL
913 // We want to display like this:
914 //
915 // vec.push(vec.pop().unwrap());
476ff2be 916 // --- ^^^ - previous borrow ends here
5bcae85e
SL
917 // | |
918 // | error occurs here
919 // previous borrow of `vec` occurs here
920 //
921 // But there are some weird edge cases to be aware of:
922 //
923 // vec.push(vec.pop().unwrap());
924 // -------- - previous borrow ends here
925 // ||
926 // |this makes no sense
927 // previous borrow of `vec` occurs here
928 //
929 // For this reason, we group the lines into "highlight lines"
cc61c64b 930 // and "annotations lines", where the highlight lines have the `^`.
5bcae85e
SL
931
932 // Sort the annotations by (start, end col)
3b2f2976
XL
933 // The labels are reversed, sort and then reversed again.
934 // Consider a list of annotations (A1, A2, C1, C2, B1, B2) where
935 // the letter signifies the span. Here we are only sorting by the
936 // span and hence, the order of the elements with the same span will
937 // not change. On reversing the ordering (|a, b| but b.cmp(a)), you get
938 // (C1, C2, B1, B2, A1, A2). All the elements with the same span are
939 // still ordered first to last, but all the elements with different
940 // spans are ordered by their spans in last to first order. Last to
941 // first order is important, because the jiggly lines and | are on
942 // the left, so the rightmost span needs to be rendered first,
943 // otherwise the lines would end up needing to go over a message.
944
5bcae85e 945 let mut annotations = line.annotations.clone();
8faf50e0 946 annotations.sort_by_key(|a| Reverse(a.start_col));
5bcae85e 947
476ff2be
SL
948 // First, figure out where each label will be positioned.
949 //
950 // In the case where you have the following annotations:
951 //
952 // vec.push(vec.pop().unwrap());
953 // -------- - previous borrow ends here [C]
954 // ||
955 // |this makes no sense [B]
956 // previous borrow of `vec` occurs here [A]
957 //
958 // `annotations_position` will hold [(2, A), (1, B), (0, C)].
959 //
960 // We try, when possible, to stick the rightmost annotation at the end
961 // of the highlight line:
5bcae85e
SL
962 //
963 // vec.push(vec.pop().unwrap());
964 // --- --- - previous borrow ends here
965 //
966 // But sometimes that's not possible because one of the other
967 // annotations overlaps it. For example, from the test
968 // `span_overlap_label`, we have the following annotations
969 // (written on distinct lines for clarity):
970 //
971 // fn foo(x: u32) {
972 // --------------
973 // -
974 //
975 // In this case, we can't stick the rightmost-most label on
976 // the highlight line, or we would get:
977 //
978 // fn foo(x: u32) {
979 // -------- x_span
980 // |
981 // fn_span
982 //
983 // which is totally weird. Instead we want:
984 //
985 // fn foo(x: u32) {
986 // --------------
987 // | |
988 // | x_span
989 // fn_span
990 //
991 // which is...less weird, at least. In fact, in general, if
992 // the rightmost span overlaps with any other span, we should
993 // use the "hang below" version, so we can at least make it
32a655c1
SL
994 // clear where the span *starts*. There's an exception for this
995 // logic, when the labels do not have a message:
996 //
997 // fn foo(x: u32) {
998 // --------------
999 // |
1000 // x_span
1001 //
1002 // instead of:
1003 //
1004 // fn foo(x: u32) {
1005 // --------------
1006 // | |
1007 // | x_span
1008 // <EMPTY LINE>
1009 //
476ff2be
SL
1010 let mut annotations_position = vec![];
1011 let mut line_len = 0;
1012 let mut p = 0;
8bb4bdeb
XL
1013 for (i, annotation) in annotations.iter().enumerate() {
1014 for (j, next) in annotations.iter().enumerate() {
1015 if overlaps(next, annotation, 0) // This label overlaps with another one and both
cc61c64b
XL
1016 && annotation.has_label() // take space (they have text and are not
1017 && j > i // multiline lines).
60c5eb7d
XL
1018 && p == 0
1019 // We're currently on the first line, move the label one line down
32a655c1 1020 {
48663c56
XL
1021 // If we're overlapping with an un-labelled annotation with the same span
1022 // we can just merge them in the output
1023 if next.start_col == annotation.start_col
60c5eb7d
XL
1024 && next.end_col == annotation.end_col
1025 && !next.has_label()
48663c56
XL
1026 {
1027 continue;
1028 }
1029
32a655c1 1030 // This annotation needs a new line in the output.
476ff2be 1031 p += 1;
8bb4bdeb 1032 break;
a7813a04 1033 }
c1a9b12d 1034 }
476ff2be 1035 annotations_position.push((p, annotation));
8bb4bdeb 1036 for (j, next) in annotations.iter().enumerate() {
60c5eb7d 1037 if j > i {
e74abb32 1038 let l = next.label.as_ref().map_or(0, |label| label.len() + 2);
cc61c64b 1039 if (overlaps(next, annotation, l) // Do not allow two labels to be in the same
8bb4bdeb
XL
1040 // line if they overlap including padding, to
1041 // avoid situations like:
1042 //
1043 // fn foo(x: u32) {
1044 // -------^------
1045 // | |
1046 // fn_spanx_span
1047 //
8bb4bdeb 1048 && annotation.has_label() // Both labels must have some text, otherwise
cc61c64b
XL
1049 && next.has_label()) // they are not overlapping.
1050 // Do not add a new line if this annotation
1051 // or the next are vertical line placeholders.
1052 || (annotation.takes_space() // If either this or the next annotation is
1053 && next.has_label()) // multiline start/end, move it to a new line
29967ef6 1054 || (annotation.has_label() // so as not to overlap the horizontal lines.
cc61c64b 1055 && next.takes_space())
041b39d2
XL
1056 || (annotation.takes_space() && next.takes_space())
1057 || (overlaps(next, annotation, l)
1058 && next.end_col <= annotation.end_col
1059 && next.has_label()
60c5eb7d
XL
1060 && p == 0)
1061 // Avoid #42595.
8bb4bdeb 1062 {
cc61c64b 1063 // This annotation needs a new line in the output.
8bb4bdeb
XL
1064 p += 1;
1065 break;
1066 }
476ff2be
SL
1067 }
1068 }
e74abb32 1069 line_len = max(line_len, p);
476ff2be 1070 }
cc61c64b 1071
476ff2be
SL
1072 if line_len != 0 {
1073 line_len += 1;
5bcae85e
SL
1074 }
1075
476ff2be
SL
1076 // If there are no annotations or the only annotations on this line are
1077 // MultilineLine, then there's only code being shown, stop processing.
8faf50e0 1078 if line.annotations.iter().all(|a| a.is_line()) {
cc61c64b 1079 return vec![];
5bcae85e
SL
1080 }
1081
60c5eb7d 1082 // Write the column separator.
32a655c1
SL
1083 //
1084 // After this we will have:
1085 //
1086 // 2 | fn foo() {
1087 // |
1088 // |
1089 // |
1090 // 3 |
1091 // 4 | }
1092 // |
0731742a 1093 for pos in 0..=line_len {
476ff2be 1094 draw_col_separator(buffer, line_offset + pos + 1, width_offset - 2);
476ff2be
SL
1095 }
1096
1097 // Write the horizontal lines for multiline annotations
1098 // (only the first and last lines need this).
1099 //
1100 // After this we will have:
1101 //
1102 // 2 | fn foo() {
1103 // | __________
1104 // |
1105 // |
1106 // 3 |
1107 // 4 | }
1108 // | _
1109 for &(pos, annotation) in &annotations_position {
1110 let style = if annotation.is_primary {
1111 Style::UnderlinePrimary
1112 } else {
1113 Style::UnderlineSecondary
1114 };
1115 let pos = pos + 1;
1116 match annotation.annotation_type {
60c5eb7d 1117 AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => {
e1599b0c
XL
1118 draw_range(
1119 buffer,
1120 '_',
1121 line_offset + pos,
1122 width_offset + depth,
6c58768f 1123 (code_offset + annotation.start_col).saturating_sub(left),
e1599b0c
XL
1124 style,
1125 );
476ff2be 1126 }
2c00a5a8 1127 _ if self.teach => {
e1599b0c
XL
1128 buffer.set_style_range(
1129 line_offset,
6c58768f
XL
1130 (code_offset + annotation.start_col).saturating_sub(left),
1131 (code_offset + annotation.end_col).saturating_sub(left),
e1599b0c
XL
1132 style,
1133 annotation.is_primary,
1134 );
2c00a5a8
XL
1135 }
1136 _ => {}
476ff2be
SL
1137 }
1138 }
1139
cc61c64b 1140 // Write the vertical lines for labels that are on a different line as the underline.
476ff2be
SL
1141 //
1142 // After this we will have:
1143 //
1144 // 2 | fn foo() {
1145 // | __________
1146 // | | |
1147 // | |
60c5eb7d 1148 // 3 | |
476ff2be
SL
1149 // 4 | | }
1150 // | |_
1151 for &(pos, annotation) in &annotations_position {
1152 let style = if annotation.is_primary {
1153 Style::UnderlinePrimary
1154 } else {
1155 Style::UnderlineSecondary
1156 };
1157 let pos = pos + 1;
32a655c1 1158
cc61c64b 1159 if pos > 1 && (annotation.has_label() || annotation.takes_space()) {
0731742a 1160 for p in line_offset + 1..=line_offset + pos {
60c5eb7d
XL
1161 buffer.putc(
1162 p,
1163 (code_offset + annotation.start_col).saturating_sub(left),
1164 '|',
1165 style,
1166 );
476ff2be
SL
1167 }
1168 }
1169 match annotation.annotation_type {
1170 AnnotationType::MultilineStart(depth) => {
1171 for p in line_offset + pos + 1..line_offset + line_len + 2 {
60c5eb7d 1172 buffer.putc(p, width_offset + depth - 1, '|', style);
476ff2be
SL
1173 }
1174 }
1175 AnnotationType::MultilineEnd(depth) => {
0731742a 1176 for p in line_offset..=line_offset + pos {
60c5eb7d 1177 buffer.putc(p, width_offset + depth - 1, '|', style);
476ff2be
SL
1178 }
1179 }
476ff2be
SL
1180 _ => (),
1181 }
1182 }
1183
1184 // Write the labels on the annotations that actually have a label.
1185 //
1186 // After this we will have:
1187 //
1188 // 2 | fn foo() {
cc61c64b
XL
1189 // | __________
1190 // | |
1191 // | something about `foo`
1192 // 3 |
1193 // 4 | }
1194 // | _ test
476ff2be 1195 for &(pos, annotation) in &annotations_position {
60c5eb7d
XL
1196 let style =
1197 if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary };
476ff2be 1198 let (pos, col) = if pos == 0 {
e74abb32 1199 (pos + 1, (annotation.end_col + 1).saturating_sub(left))
476ff2be 1200 } else {
e74abb32 1201 (pos + 2, annotation.start_col.saturating_sub(left))
476ff2be
SL
1202 };
1203 if let Some(ref label) = annotation.label {
e1599b0c 1204 buffer.puts(line_offset + pos, code_offset + col, &label, style);
476ff2be
SL
1205 }
1206 }
1207
1208 // Sort from biggest span to smallest span so that smaller spans are
1209 // represented in the output:
1210 //
1211 // x | fn foo()
1212 // | ^^^---^^
1213 // | | |
1214 // | | something about `foo`
1215 // | something about `fn foo()`
e74abb32
XL
1216 annotations_position.sort_by_key(|(_, ann)| {
1217 // Decreasing order. When annotations share the same length, prefer `Primary`.
1218 (Reverse(ann.len()), ann.is_primary)
476ff2be 1219 });
5bcae85e 1220
476ff2be
SL
1221 // Write the underlines.
1222 //
1223 // After this we will have:
1224 //
1225 // 2 | fn foo() {
cc61c64b
XL
1226 // | ____-_____^
1227 // | |
1228 // | something about `foo`
1229 // 3 |
1230 // 4 | }
1231 // | _^ test
476ff2be
SL
1232 for &(_, annotation) in &annotations_position {
1233 let (underline, style) = if annotation.is_primary {
1234 ('^', Style::UnderlinePrimary)
5bcae85e 1235 } else {
476ff2be
SL
1236 ('-', Style::UnderlineSecondary)
1237 };
1238 for p in annotation.start_col..annotation.end_col {
e1599b0c
XL
1239 buffer.putc(
1240 line_offset + 1,
e74abb32 1241 (code_offset + p).saturating_sub(left),
e1599b0c
XL
1242 underline,
1243 style,
1244 );
c1a9b12d
SL
1245 }
1246 }
60c5eb7d
XL
1247 annotations_position
1248 .iter()
1249 .filter_map(|&(_, annotation)| match annotation.annotation_type {
cc61c64b
XL
1250 AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => {
1251 let style = if annotation.is_primary {
1252 Style::LabelPrimary
1253 } else {
1254 Style::LabelSecondary
1255 };
1256 Some((p, style))
abe05a73 1257 }
60c5eb7d
XL
1258 _ => None,
1259 })
1260 .collect::<Vec<_>>()
5bcae85e
SL
1261 }
1262
1263 fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize {
5e7ed085
FG
1264 let Some(ref sm) = self.sm else {
1265 return 0;
e74abb32
XL
1266 };
1267
923072b8
FG
1268 let will_be_emitted = |span: Span| {
1269 !span.is_dummy() && {
1270 let file = sm.lookup_source_file(span.hi());
1271 sm.ensure_source_file_source_present(file)
1272 }
1273 };
1274
5bcae85e 1275 let mut max = 0;
e74abb32 1276 for primary_span in msp.primary_spans() {
923072b8 1277 if will_be_emitted(*primary_span) {
e74abb32
XL
1278 let hi = sm.lookup_char_pos(primary_span.hi());
1279 max = (hi.line).max(max);
5bcae85e 1280 }
e74abb32
XL
1281 }
1282 if !self.short_message {
1283 for span_label in msp.span_labels() {
923072b8 1284 if will_be_emitted(span_label.span) {
e74abb32
XL
1285 let hi = sm.lookup_char_pos(span_label.span.hi());
1286 max = (hi.line).max(max);
a7813a04 1287 }
7453a54e 1288 }
c1a9b12d 1289 }
e74abb32 1290
5bcae85e 1291 max
c1a9b12d
SL
1292 }
1293
8faf50e0 1294 fn get_max_line_num(&mut self, span: &MultiSpan, children: &[SubDiagnostic]) -> usize {
9e0c209e 1295 let primary = self.get_multispan_max_line_num(span);
60c5eb7d
XL
1296 children
1297 .iter()
e74abb32
XL
1298 .map(|sub| self.get_multispan_max_line_num(&sub.span))
1299 .max()
1300 .unwrap_or(0)
1301 .max(primary)
c1a9b12d
SL
1302 }
1303
9fa01778 1304 /// Adds a left margin to every line but the first, given a padding length and the label being
32a655c1 1305 /// displayed, keeping the provided highlighting.
60c5eb7d
XL
1306 fn msg_to_buffer(
1307 &self,
1308 buffer: &mut StyledBuffer,
04454e1e
FG
1309 msg: &[(DiagnosticMessage, Style)],
1310 args: &FluentArgs<'_>,
60c5eb7d
XL
1311 padding: usize,
1312 label: &str,
1313 override_style: Option<Style>,
1314 ) {
32a655c1
SL
1315 // The extra 5 ` ` is padding that's always needed to align to the `note: `:
1316 //
1317 // error: message
1318 // --> file.rs:13:20
1319 // |
1320 // 13 | <CODE>
1321 // | ^^^^
1322 // |
1323 // = note: multiline
1324 // message
1325 // ++^^^----xx
1326 // | | | |
1327 // | | | magic `2`
1328 // | | length of label
1329 // | magic `3`
1330 // `max_line_num_len`
8faf50e0 1331 let padding = " ".repeat(padding + label.len() + 5);
32a655c1 1332
e74abb32
XL
1333 /// Returns `override` if it is present and `style` is `NoStyle` or `style` otherwise
1334 fn style_or_override(style: Style, override_: Option<Style>) -> Style {
1335 match (style, override_) {
1336 (Style::NoStyle, Some(override_)) => override_,
1337 _ => style,
32a655c1 1338 }
32a655c1
SL
1339 }
1340
1341 let mut line_number = 0;
1342
1343 // Provided the following diagnostic message:
1344 //
1345 // let msg = vec![
1346 // ("
1347 // ("highlighted multiline\nstring to\nsee how it ", Style::NoStyle),
1348 // ("looks", Style::Highlight),
1349 // ("with\nvery ", Style::NoStyle),
1350 // ("weird", Style::Highlight),
1351 // (" formats\n", Style::NoStyle),
1352 // ("see?", Style::Highlight),
1353 // ];
1354 //
ff7c6d11 1355 // the expected output on a note is (* surround the highlighted text)
32a655c1
SL
1356 //
1357 // = note: highlighted multiline
1358 // string to
1359 // see how it *looks* with
1360 // very *weird* formats
1361 // see?
1362 for &(ref text, ref style) in msg.iter() {
04454e1e 1363 let text = self.translate_message(text, args);
32a655c1
SL
1364 let lines = text.split('\n').collect::<Vec<_>>();
1365 if lines.len() > 1 {
1366 for (i, line) in lines.iter().enumerate() {
1367 if i != 0 {
1368 line_number += 1;
1369 buffer.append(line_number, &padding, Style::NoStyle);
1370 }
1371 buffer.append(line_number, line, style_or_override(*style, override_style));
1372 }
1373 } else {
04454e1e 1374 buffer.append(line_number, &text, style_or_override(*style, override_style));
32a655c1
SL
1375 }
1376 }
1377 }
1378
9fa01778
XL
1379 fn emit_message_default(
1380 &mut self,
1381 msp: &MultiSpan,
04454e1e
FG
1382 msg: &[(DiagnosticMessage, Style)],
1383 args: &FluentArgs<'_>,
9fa01778
XL
1384 code: &Option<DiagnosticId>,
1385 level: &Level,
1386 max_line_num_len: usize,
1387 is_secondary: bool,
1388 ) -> io::Result<()> {
5bcae85e
SL
1389 let mut buffer = StyledBuffer::new();
1390
60c5eb7d
XL
1391 if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message
1392 {
5bcae85e
SL
1393 // This is a secondary message with no span info
1394 for _ in 0..max_line_num_len {
1395 buffer.prepend(0, " ", Style::NoStyle);
1396 }
1397 draw_note_separator(&mut buffer, 0, max_line_num_len + 1);
e1599b0c 1398 if *level != Level::FailureNote {
1b1a35ee
XL
1399 buffer.append(0, level.to_str(), Style::MainHeaderMsg);
1400 buffer.append(0, ": ", Style::NoStyle);
0531ce1d 1401 }
04454e1e 1402 self.msg_to_buffer(&mut buffer, msg, args, max_line_num_len, "note", None);
c30ab7b3 1403 } else {
3c0e092e 1404 let mut label_width = 0;
e1599b0c 1405 // The failure note level itself does not provide any useful diagnostic information
1b1a35ee
XL
1406 if *level != Level::FailureNote {
1407 buffer.append(0, level.to_str(), Style::Level(*level));
3c0e092e 1408 label_width += level.to_str().len();
0531ce1d 1409 }
abe05a73
XL
1410 // only render error codes, not lint codes
1411 if let Some(DiagnosticId::Error(ref code)) = *code {
dfeec247
XL
1412 buffer.append(0, "[", Style::Level(*level));
1413 buffer.append(0, &code, Style::Level(*level));
1414 buffer.append(0, "]", Style::Level(*level));
3c0e092e 1415 label_width += 2 + code.len();
5bcae85e 1416 }
cdc7bbd5 1417 let header_style = if is_secondary { Style::HeaderMsg } else { Style::MainHeaderMsg };
1b1a35ee 1418 if *level != Level::FailureNote {
8faf50e0 1419 buffer.append(0, ": ", header_style);
3c0e092e 1420 label_width += 2;
0531ce1d 1421 }
32a655c1 1422 for &(ref text, _) in msg.iter() {
04454e1e 1423 let text = self.translate_message(text, args);
3c0e092e 1424 // Account for newlines to align output to its label.
04454e1e 1425 for (line, text) in normalize_whitespace(&text).lines().enumerate() {
3c0e092e
XL
1426 buffer.append(
1427 0 + line,
1428 &format!(
1429 "{}{}",
1430 if line == 0 { String::new() } else { " ".repeat(label_width) },
1431 text
1432 ),
1433 header_style,
1434 );
1435 }
32a655c1 1436 }
5bcae85e
SL
1437 }
1438
04454e1e 1439 let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
a7813a04 1440
5bcae85e 1441 // Make sure our primary file comes first
a1dfa0c6 1442 let (primary_lo, sm) = if let (Some(sm), Some(ref primary_span)) =
60c5eb7d
XL
1443 (self.sm.as_ref(), msp.primary_span().as_ref())
1444 {
8faf50e0 1445 if !primary_span.is_dummy() {
a1dfa0c6 1446 (sm.lookup_char_pos(primary_span.lo()), sm)
5bcae85e 1447 } else {
abe05a73 1448 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
5bcae85e 1449 return Ok(());
c30ab7b3
SL
1450 }
1451 } else {
1452 // If we don't have span information, emit and exit
abe05a73 1453 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
c30ab7b3
SL
1454 return Ok(());
1455 };
5bcae85e 1456 if let Ok(pos) =
60c5eb7d
XL
1457 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
1458 {
5bcae85e
SL
1459 annotated_files.swap(0, pos);
1460 }
1461
1462 // Print out the annotate source lines that correspond with the error
1463 for annotated_file in annotated_files {
7cac9316 1464 // we can't annotate anything if the source is unavailable.
a1dfa0c6 1465 if !sm.ensure_source_file_source_present(annotated_file.file.clone()) {
7cac9316
XL
1466 continue;
1467 }
1468
5bcae85e
SL
1469 // print out the span location and spacer before we print the annotated source
1470 // to do this, we need to know if this span will be primary
1471 let is_primary = primary_lo.file.name == annotated_file.file.name;
1472 if is_primary {
5bcae85e 1473 let loc = primary_lo.clone();
abe05a73
XL
1474 if !self.short_message {
1475 // remember where we are in the output buffer for easy reference
1476 let buffer_msg_line_offset = buffer.num_lines();
1477
1478 buffer.prepend(buffer_msg_line_offset, "--> ", Style::LineNumber);
e1599b0c
XL
1479 buffer.append(
1480 buffer_msg_line_offset,
1481 &format!(
1482 "{}:{}:{}",
94222f64 1483 sm.filename_for_diagnostics(&loc.file.name),
e1599b0c
XL
1484 sm.doctest_offset_line(&loc.file.name, loc.line),
1485 loc.col.0 + 1,
1486 ),
1487 Style::LineAndColumn,
1488 );
abe05a73
XL
1489 for _ in 0..max_line_num_len {
1490 buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle);
1491 }
1492 } else {
e1599b0c
XL
1493 buffer.prepend(
1494 0,
1495 &format!(
1496 "{}:{}:{}: ",
94222f64 1497 sm.filename_for_diagnostics(&loc.file.name),
e1599b0c
XL
1498 sm.doctest_offset_line(&loc.file.name, loc.line),
1499 loc.col.0 + 1,
1500 ),
1501 Style::LineAndColumn,
1502 );
5bcae85e 1503 }
abe05a73 1504 } else if !self.short_message {
5bcae85e
SL
1505 // remember where we are in the output buffer for easy reference
1506 let buffer_msg_line_offset = buffer.num_lines();
1507
1508 // Add spacing line
94222f64
XL
1509 draw_col_separator_no_space(
1510 &mut buffer,
1511 buffer_msg_line_offset,
1512 max_line_num_len + 1,
1513 );
5bcae85e
SL
1514
1515 // Then, the secondary file indicator
1516 buffer.prepend(buffer_msg_line_offset + 1, "::: ", Style::LineNumber);
2c00a5a8
XL
1517 let loc = if let Some(first_line) = annotated_file.lines.first() {
1518 let col = if let Some(first_annotation) = first_line.annotations.first() {
1519 format!(":{}", first_annotation.start_col + 1)
1520 } else {
b7449926 1521 String::new()
2c00a5a8 1522 };
60c5eb7d
XL
1523 format!(
1524 "{}:{}{}",
94222f64 1525 sm.filename_for_diagnostics(&annotated_file.file.name),
60c5eb7d
XL
1526 sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index),
1527 col
1528 )
2c00a5a8 1529 } else {
94222f64 1530 format!("{}", sm.filename_for_diagnostics(&annotated_file.file.name))
2c00a5a8 1531 };
60c5eb7d 1532 buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn);
5bcae85e
SL
1533 for _ in 0..max_line_num_len {
1534 buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle);
c1a9b12d 1535 }
a7813a04 1536 }
c1a9b12d 1537
abe05a73
XL
1538 if !self.short_message {
1539 // Put in the spacer between the location and annotated source
1540 let buffer_msg_line_offset = buffer.num_lines();
60c5eb7d
XL
1541 draw_col_separator_no_space(
1542 &mut buffer,
1543 buffer_msg_line_offset,
1544 max_line_num_len + 1,
1545 );
5bcae85e 1546
abe05a73 1547 // Contains the vertical lines' positions for active multiline annotations
b7449926 1548 let mut multilines = FxHashMap::default();
cc61c64b 1549
e1599b0c 1550 // Get the left-side margin to remove it
ba9703b0 1551 let mut whitespace_margin = usize::MAX;
e1599b0c
XL
1552 for line_idx in 0..annotated_file.lines.len() {
1553 let file = annotated_file.file.clone();
1554 let line = &annotated_file.lines[line_idx];
1555 if let Some(source_string) = file.get_line(line.line_index - 1) {
5869c6ff
XL
1556 let leading_whitespace = source_string
1557 .chars()
1558 .take_while(|c| c.is_whitespace())
1559 .map(|c| {
1560 match c {
1561 // Tabs are displayed as 4 spaces
1562 '\t' => 4,
1563 _ => 1,
1564 }
1565 })
1566 .sum();
e1599b0c 1567 if source_string.chars().any(|c| !c.is_whitespace()) {
60c5eb7d 1568 whitespace_margin = min(whitespace_margin, leading_whitespace);
e1599b0c
XL
1569 }
1570 }
1571 }
ba9703b0 1572 if whitespace_margin == usize::MAX {
e1599b0c
XL
1573 whitespace_margin = 0;
1574 }
1575
1576 // Left-most column any visible span points at.
ba9703b0 1577 let mut span_left_margin = usize::MAX;
e1599b0c
XL
1578 for line in &annotated_file.lines {
1579 for ann in &line.annotations {
1580 span_left_margin = min(span_left_margin, ann.start_col);
1581 span_left_margin = min(span_left_margin, ann.end_col);
1582 }
1583 }
ba9703b0 1584 if span_left_margin == usize::MAX {
e1599b0c
XL
1585 span_left_margin = 0;
1586 }
1587
1588 // Right-most column any visible span points at.
1589 let mut span_right_margin = 0;
1590 let mut label_right_margin = 0;
1591 let mut max_line_len = 0;
1592 for line in &annotated_file.lines {
60c5eb7d
XL
1593 max_line_len = max(
1594 max_line_len,
1595 annotated_file.file.get_line(line.line_index - 1).map_or(0, |s| s.len()),
1596 );
e1599b0c
XL
1597 for ann in &line.annotations {
1598 span_right_margin = max(span_right_margin, ann.start_col);
1599 span_right_margin = max(span_right_margin, ann.end_col);
1600 // FIXME: account for labels not in the same line
e74abb32 1601 let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1);
e1599b0c
XL
1602 label_right_margin = max(label_right_margin, ann.end_col + label_right);
1603 }
1604 }
1605
1606 let width_offset = 3 + max_line_num_len;
1607 let code_offset = if annotated_file.multiline_depth == 0 {
1608 width_offset
1609 } else {
1610 width_offset + annotated_file.multiline_depth + 1
1611 };
1612
1613 let column_width = if let Some(width) = self.terminal_width {
1614 width.saturating_sub(code_offset)
1615 } else if self.ui_testing {
f035d41b 1616 DEFAULT_COLUMN_WIDTH
e1599b0c 1617 } else {
74b04a01 1618 termize::dimensions()
e1599b0c 1619 .map(|(w, _)| w.saturating_sub(code_offset))
f035d41b 1620 .unwrap_or(DEFAULT_COLUMN_WIDTH)
e1599b0c
XL
1621 };
1622
1623 let margin = Margin::new(
1624 whitespace_margin,
1625 span_left_margin,
1626 span_right_margin,
1627 label_right_margin,
1628 column_width,
1629 max_line_len,
1630 );
1631
abe05a73
XL
1632 // Next, output the annotate source for this file
1633 for line_idx in 0..annotated_file.lines.len() {
1634 let previous_buffer_line = buffer.num_lines();
cc61c64b 1635
e1599b0c
XL
1636 let depths = self.render_source_line(
1637 &mut buffer,
1638 annotated_file.file.clone(),
1639 &annotated_file.lines[line_idx],
1640 width_offset,
1641 code_offset,
1642 margin,
1643 );
cc61c64b 1644
b7449926 1645 let mut to_add = FxHashMap::default();
5bcae85e 1646
abe05a73 1647 for (depth, style) in depths {
cdc7bbd5 1648 if multilines.remove(&depth).is_none() {
abe05a73
XL
1649 to_add.insert(depth, style);
1650 }
cc61c64b 1651 }
cc61c64b 1652
abe05a73
XL
1653 // Set the multiline annotation vertical lines to the left of
1654 // the code in this line.
1655 for (depth, style) in &multilines {
1656 for line in previous_buffer_line..buffer.num_lines() {
60c5eb7d 1657 draw_multiline_line(&mut buffer, line, width_offset, *depth, *style);
cc61c64b 1658 }
abe05a73
XL
1659 }
1660 // check to see if we need to print out or elide lines that come between
1661 // this annotated line and the next one.
1662 if line_idx < (annotated_file.lines.len() - 1) {
60c5eb7d
XL
1663 let line_idx_delta = annotated_file.lines[line_idx + 1].line_index
1664 - annotated_file.lines[line_idx].line_index;
abe05a73
XL
1665 if line_idx_delta > 2 {
1666 let last_buffer_line_num = buffer.num_lines();
1667 buffer.puts(last_buffer_line_num, 0, "...", Style::LineNumber);
1668
1669 // Set the multiline annotation vertical lines on `...` bridging line.
1670 for (depth, style) in &multilines {
60c5eb7d
XL
1671 draw_multiline_line(
1672 &mut buffer,
1673 last_buffer_line_num,
1674 width_offset,
1675 *depth,
1676 *style,
1677 );
abe05a73
XL
1678 }
1679 } else if line_idx_delta == 2 {
60c5eb7d
XL
1680 let unannotated_line = annotated_file
1681 .file
abe05a73
XL
1682 .get_line(annotated_file.lines[line_idx].line_index)
1683 .unwrap_or_else(|| Cow::from(""));
1684
1685 let last_buffer_line_num = buffer.num_lines();
1686
e1599b0c
XL
1687 self.draw_line(
1688 &mut buffer,
3c0e092e 1689 &normalize_whitespace(&unannotated_line),
e1599b0c
XL
1690 annotated_file.lines[line_idx + 1].line_index - 1,
1691 last_buffer_line_num,
1692 width_offset,
1693 code_offset,
1694 margin,
1695 );
abe05a73
XL
1696
1697 for (depth, style) in &multilines {
e1599b0c
XL
1698 draw_multiline_line(
1699 &mut buffer,
1700 last_buffer_line_num,
1701 width_offset,
1702 *depth,
1703 *style,
1704 );
abe05a73 1705 }
cc61c64b 1706 }
c1a9b12d 1707 }
cc61c64b 1708
abe05a73
XL
1709 multilines.extend(&to_add);
1710 }
7453a54e
SL
1711 }
1712 }
5bcae85e 1713
5bcae85e 1714 // final step: take our styled buffer, render it, then output it
abe05a73 1715 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
5bcae85e
SL
1716
1717 Ok(())
1718 }
ff7c6d11 1719
9fa01778
XL
1720 fn emit_suggestion_default(
1721 &mut self,
923072b8 1722 span: &MultiSpan,
9fa01778 1723 suggestion: &CodeSuggestion,
04454e1e 1724 args: &FluentArgs<'_>,
9fa01778
XL
1725 level: &Level,
1726 max_line_num_len: usize,
1727 ) -> io::Result<()> {
5e7ed085
FG
1728 let Some(ref sm) = self.sm else {
1729 return Ok(());
e74abb32 1730 };
5bcae85e 1731
60c5eb7d
XL
1732 // Render the replacements for each suggestion
1733 let suggestions = suggestion.splice_lines(&**sm);
ba9703b0 1734 debug!("emit_suggestion_default: suggestions={:?}", suggestions);
60c5eb7d
XL
1735
1736 if suggestions.is_empty() {
1737 // Suggestions coming from macros can have malformed spans. This is a heavy handed
1738 // approach to avoid ICEs by ignoring the suggestion outright.
1739 return Ok(());
1740 }
1741
e74abb32
XL
1742 let mut buffer = StyledBuffer::new();
1743
1744 // Render the suggestion message
1b1a35ee
XL
1745 buffer.append(0, level.to_str(), Style::Level(*level));
1746 buffer.append(0, ": ", Style::HeaderMsg);
1747
e74abb32
XL
1748 self.msg_to_buffer(
1749 &mut buffer,
1750 &[(suggestion.msg.to_owned(), Style::NoStyle)],
04454e1e 1751 args,
e74abb32
XL
1752 max_line_num_len,
1753 "suggestion",
1754 Some(Style::HeaderMsg),
1755 );
1756
e74abb32 1757 let mut row_num = 2;
94222f64 1758 draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1);
e74abb32 1759 let mut notice_capitalization = false;
94222f64
XL
1760 for (complete, parts, highlights, only_capitalization) in
1761 suggestions.iter().take(MAX_SUGGESTIONS)
1762 {
e74abb32 1763 notice_capitalization |= only_capitalization;
e74abb32 1764
94222f64
XL
1765 let has_deletion = parts.iter().any(|p| p.is_deletion());
1766 let is_multiline = complete.lines().count() > 1;
1767
923072b8
FG
1768 if let Some(span) = span.primary_span() {
1769 // Compare the primary span of the diagnostic with the span of the suggestion
1770 // being emitted. If they belong to the same file, we don't *need* to show the
1771 // file name, saving in verbosity, but if it *isn't* we do need it, otherwise we're
1772 // telling users to make a change but not clarifying *where*.
1773 let loc = sm.lookup_char_pos(parts[0].span.lo());
1774 if loc.file.name != sm.span_to_filename(span) && loc.file.name.is_real() {
1775 buffer.puts(row_num - 1, 0, "--> ", Style::LineNumber);
1776 buffer.append(
1777 row_num - 1,
1778 &format!(
1779 "{}:{}:{}",
1780 sm.filename_for_diagnostics(&loc.file.name),
1781 sm.doctest_offset_line(&loc.file.name, loc.line),
1782 loc.col.0 + 1,
1783 ),
1784 Style::LineAndColumn,
1785 );
1786 for _ in 0..max_line_num_len {
1787 buffer.prepend(row_num - 1, " ", Style::NoStyle);
1788 }
1789 row_num += 1;
1790 }
a2a8927a 1791 }
a2a8927a
XL
1792 let show_code_change = if has_deletion && !is_multiline {
1793 DisplaySuggestion::Diff
1794 } else if (parts.len() != 1 || parts[0].snippet.trim() != complete.trim())
1795 && !is_multiline
1796 {
1797 DisplaySuggestion::Underline
1798 } else {
1799 DisplaySuggestion::None
1800 };
94222f64 1801
a2a8927a 1802 if let DisplaySuggestion::Diff = show_code_change {
94222f64
XL
1803 row_num += 1;
1804 }
1805
1806 let file_lines = sm
60c5eb7d
XL
1807 .span_to_lines(parts[0].span)
1808 .expect("span_to_lines failed when emitting suggestion");
e74abb32 1809
94222f64 1810 assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy());
e74abb32
XL
1811
1812 let line_start = sm.lookup_char_pos(parts[0].span.lo()).line;
923072b8 1813 draw_col_separator_no_space(&mut buffer, row_num - 1, max_line_num_len + 1);
e74abb32 1814 let mut lines = complete.lines();
5e7ed085
FG
1815 if lines.clone().next().is_none() {
1816 // Account for a suggestion to completely remove a line(s) with whitespace (#94192).
1817 let line_end = sm.lookup_char_pos(parts[0].span.hi()).line;
1818 for line in line_start..=line_end {
1819 buffer.puts(
1820 row_num - 1 + line - line_start,
1821 0,
1822 &self.maybe_anonymized(line),
1823 Style::LineNumber,
1824 );
1825 buffer.puts(
1826 row_num - 1 + line - line_start,
1827 max_line_num_len + 1,
1828 "- ",
1829 Style::Removal,
1830 );
1831 buffer.puts(
1832 row_num - 1 + line - line_start,
1833 max_line_num_len + 3,
1834 &normalize_whitespace(&*file_lines.file.get_line(line - 1).unwrap()),
1835 Style::Removal,
1836 );
1837 }
1838 row_num += line_end - line_start;
1839 }
923072b8
FG
1840 let mut unhighlighted_lines = Vec::new();
1841 for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() {
1842 debug!(%line_pos, %line, ?highlight_parts);
1843
1844 // Remember lines that are not highlighted to hide them if needed
1845 if highlight_parts.is_empty() {
1846 unhighlighted_lines.push((line_pos, line));
1847 continue;
94222f64
XL
1848 }
1849
923072b8
FG
1850 match unhighlighted_lines.len() {
1851 0 => (),
1852 // Since we show first line, "..." line and last line,
1853 // There is no reason to hide if there are 3 or less lines
1854 // (because then we just replace a line with ... which is
1855 // not helpful)
1856 n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| {
1857 self.draw_code_line(
1858 &mut buffer,
1859 &mut row_num,
1860 &Vec::new(),
1861 p,
1862 l,
1863 line_start,
1864 show_code_change,
1865 max_line_num_len,
1866 &file_lines,
1867 is_multiline,
1868 )
1869 }),
1870 // Print first unhighlighted line, "..." and last unhighlighted line, like so:
1871 //
1872 // LL | this line was highlighted
1873 // LL | this line is just for context
1874 // ...
1875 // LL | this line is just for context
1876 // LL | this line was highlighted
1877 _ => {
1878 let last_line = unhighlighted_lines.pop();
1879 let first_line = unhighlighted_lines.drain(..).next();
1880
1881 first_line.map(|(p, l)| {
1882 self.draw_code_line(
1883 &mut buffer,
1884 &mut row_num,
1885 &Vec::new(),
1886 p,
1887 l,
1888 line_start,
1889 show_code_change,
1890 max_line_num_len,
1891 &file_lines,
1892 is_multiline,
1893 )
1894 });
94222f64 1895
923072b8
FG
1896 buffer.puts(row_num, max_line_num_len - 1, "...", Style::LineNumber);
1897 row_num += 1;
1898
1899 last_line.map(|(p, l)| {
1900 self.draw_code_line(
1901 &mut buffer,
1902 &mut row_num,
1903 &Vec::new(),
1904 p,
1905 l,
1906 line_start,
1907 show_code_change,
1908 max_line_num_len,
1909 &file_lines,
1910 is_multiline,
1911 )
1912 });
1913 }
94222f64 1914 }
923072b8
FG
1915
1916 self.draw_code_line(
1917 &mut buffer,
1918 &mut row_num,
1919 highlight_parts,
1920 line_pos,
1921 line,
1922 line_start,
1923 show_code_change,
1924 max_line_num_len,
1925 &file_lines,
1926 is_multiline,
1927 )
0531ce1d 1928 }
94b46f34 1929
e74abb32
XL
1930 // This offset and the ones below need to be signed to account for replacement code
1931 // that is shorter than the original code.
dfeec247 1932 let mut offsets: Vec<(usize, isize)> = Vec::new();
e74abb32
XL
1933 // Only show an underline in the suggestions if the suggestion is not the
1934 // entirety of the code being shown and the displayed code is not multiline.
a2a8927a 1935 if let DisplaySuggestion::Diff | DisplaySuggestion::Underline = show_code_change {
923072b8 1936 draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
e74abb32
XL
1937 for part in parts {
1938 let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display;
1939 let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display;
1940
1941 // Do not underline the leading...
60c5eb7d 1942 let start = part.snippet.len().saturating_sub(part.snippet.trim_start().len());
e74abb32
XL
1943 // ...or trailing spaces. Account for substitutions containing unicode
1944 // characters.
60c5eb7d
XL
1945 let sub_len: usize = part
1946 .snippet
1947 .trim()
1948 .chars()
e74abb32
XL
1949 .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
1950 .sum();
1951
dfeec247
XL
1952 let offset: isize = offsets
1953 .iter()
1954 .filter_map(
1955 |(start, v)| if span_start_pos <= *start { None } else { Some(v) },
1956 )
1957 .sum();
e74abb32
XL
1958 let underline_start = (span_start_pos + start) as isize + offset;
1959 let underline_end = (span_start_pos + start + sub_len) as isize + offset;
dfeec247 1960 assert!(underline_start >= 0 && underline_end >= 0);
94222f64 1961 let padding: usize = max_line_num_len + 3;
e74abb32 1962 for p in underline_start..underline_end {
a2a8927a 1963 if let DisplaySuggestion::Underline = show_code_change {
94222f64
XL
1964 // If this is a replacement, underline with `^`, if this is an addition
1965 // underline with `+`.
60c5eb7d
XL
1966 buffer.putc(
1967 row_num,
94222f64
XL
1968 (padding as isize + p) as usize,
1969 if part.is_addition(&sm) { '+' } else { '~' },
1970 Style::Addition,
60c5eb7d 1971 );
94b46f34 1972 }
041b39d2 1973 }
a2a8927a 1974 if let DisplaySuggestion::Diff = show_code_change {
94222f64
XL
1975 // Colorize removal with red in diff format.
1976 buffer.set_style_range(
1977 row_num - 2,
1978 (padding as isize + span_start_pos as isize) as usize,
1979 (padding as isize + span_end_pos as isize) as usize,
1980 Style::Removal,
1981 true,
1982 );
1983 }
5bcae85e 1984
e74abb32 1985 // length of the code after substitution
60c5eb7d
XL
1986 let full_sub_len = part
1987 .snippet
1988 .chars()
e74abb32
XL
1989 .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
1990 .sum::<usize>() as isize;
1991
1992 // length of the code to be substituted
1993 let snippet_len = span_end_pos as isize - span_start_pos as isize;
1994 // For multiple substitutions, use the position *after* the previous
dfeec247
XL
1995 // substitutions have happened, only when further substitutions are
1996 // located strictly after.
1997 offsets.push((span_end_pos, full_sub_len - snippet_len));
7cac9316 1998 }
e74abb32 1999 row_num += 1;
7cac9316 2000 }
e74abb32
XL
2001
2002 // if we elided some lines, add an ellipsis
2003 if lines.next().is_some() {
2004 buffer.puts(row_num, max_line_num_len - 1, "...", Style::LineNumber);
a2a8927a 2005 } else if let DisplaySuggestion::None = show_code_change {
e74abb32
XL
2006 draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1);
2007 row_num += 1;
7453a54e 2008 }
c1a9b12d 2009 }
e74abb32
XL
2010 if suggestions.len() > MAX_SUGGESTIONS {
2011 let others = suggestions.len() - MAX_SUGGESTIONS;
60c5eb7d 2012 let msg = format!("and {} other candidate{}", others, pluralize!(others));
e74abb32
XL
2013 buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle);
2014 } else if notice_capitalization {
2015 let msg = "notice the capitalization difference";
2016 buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle);
2017 }
2018 emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?;
7453a54e 2019 Ok(())
c1a9b12d 2020 }
ff7c6d11 2021
e74abb32
XL
2022 fn emit_messages_default(
2023 &mut self,
2024 level: &Level,
04454e1e
FG
2025 message: &[(DiagnosticMessage, Style)],
2026 args: &FluentArgs<'_>,
e74abb32
XL
2027 code: &Option<DiagnosticId>,
2028 span: &MultiSpan,
2029 children: &[SubDiagnostic],
2030 suggestions: &[CodeSuggestion],
2031 ) {
0531ce1d
XL
2032 let max_line_num_len = if self.ui_testing {
2033 ANONYMIZED_LINE_NUM.len()
2034 } else {
6a06907d
XL
2035 let n = self.get_max_line_num(span, children);
2036 num_decimal_digits(n)
0531ce1d 2037 };
5bcae85e 2038
04454e1e 2039 match self.emit_message_default(span, message, args, code, level, max_line_num_len, false) {
5bcae85e 2040 Ok(()) => {
60c5eb7d
XL
2041 if !children.is_empty()
2042 || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden)
2043 {
5bcae85e 2044 let mut buffer = StyledBuffer::new();
abe05a73
XL
2045 if !self.short_message {
2046 draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
2047 }
cdc7bbd5 2048 if let Err(e) = emit_to_destination(
60c5eb7d
XL
2049 &buffer.render(),
2050 level,
2051 &mut self.dst,
2052 self.short_message,
2053 ) {
cdc7bbd5 2054 panic!("failed to emit error: {}", e)
5bcae85e
SL
2055 }
2056 }
abe05a73
XL
2057 if !self.short_message {
2058 for child in children {
2059 let span = child.render_span.as_ref().unwrap_or(&child.span);
ba9703b0 2060 if let Err(err) = self.emit_message_default(
9fa01778 2061 &span,
04454e1e
FG
2062 &child.message,
2063 args,
9fa01778
XL
2064 &None,
2065 &child.level,
2066 max_line_num_len,
2067 true,
2068 ) {
ba9703b0 2069 panic!("failed to emit error: {}", err);
abe05a73
XL
2070 }
2071 }
2072 for sugg in suggestions {
9fa01778
XL
2073 if sugg.style == SuggestionStyle::CompletelyHidden {
2074 // do not display this suggestion, it is meant only for tools
2075 } else if sugg.style == SuggestionStyle::HideCodeAlways {
ba9703b0 2076 if let Err(e) = self.emit_message_default(
9fa01778
XL
2077 &MultiSpan::new(),
2078 &[(sugg.msg.to_owned(), Style::HeaderMsg)],
04454e1e 2079 args,
9fa01778
XL
2080 &None,
2081 &Level::Help,
2082 max_line_num_len,
2083 true,
2084 ) {
ba9703b0 2085 panic!("failed to emit error: {}", e);
9fa01778 2086 }
923072b8
FG
2087 } else if let Err(e) = self.emit_suggestion_default(
2088 span,
2089 sugg,
2090 args,
2091 &Level::Help,
2092 max_line_num_len,
2093 ) {
ba9703b0
XL
2094 panic!("failed to emit error: {}", e);
2095 };
5bcae85e
SL
2096 }
2097 }
2098 }
c30ab7b3 2099 Err(e) => panic!("failed to emit error: {}", e),
5bcae85e 2100 }
0531ce1d
XL
2101
2102 let mut dst = self.dst.writable();
dc9dc135 2103 match writeln!(dst) {
5bcae85e 2104 Err(e) => panic!("failed to emit error: {}", e),
ba9703b0
XL
2105 _ => {
2106 if let Err(e) = dst.flush() {
2107 panic!("failed to emit error: {}", e)
2108 }
2109 }
c1a9b12d 2110 }
c1a9b12d 2111 }
923072b8
FG
2112
2113 fn draw_code_line(
2114 &self,
2115 buffer: &mut StyledBuffer,
2116 row_num: &mut usize,
2117 highlight_parts: &Vec<SubstitutionHighlight>,
2118 line_pos: usize,
2119 line: &str,
2120 line_start: usize,
2121 show_code_change: DisplaySuggestion,
2122 max_line_num_len: usize,
2123 file_lines: &FileLines,
2124 is_multiline: bool,
2125 ) {
2126 // Print the span column to avoid confusion
2127 buffer.puts(*row_num, 0, &self.maybe_anonymized(line_start + line_pos), Style::LineNumber);
2128 if let DisplaySuggestion::Diff = show_code_change {
2129 // Add the line number for both addition and removal to drive the point home.
2130 //
2131 // N - fn foo<A: T>(bar: A) {
2132 // N + fn foo(bar: impl T) {
2133 buffer.puts(
2134 *row_num - 1,
2135 0,
2136 &self.maybe_anonymized(line_start + line_pos),
2137 Style::LineNumber,
2138 );
2139 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
2140 buffer.puts(
2141 *row_num - 1,
2142 max_line_num_len + 3,
2143 &normalize_whitespace(
2144 &*file_lines.file.get_line(file_lines.lines[line_pos].line_index).unwrap(),
2145 ),
2146 Style::NoStyle,
2147 );
2148 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2149 } else if is_multiline {
2150 match &highlight_parts[..] {
2151 [SubstitutionHighlight { start: 0, end }] if *end == line.len() => {
2152 buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
2153 }
2154 [] => {
2155 draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2156 }
2157 _ => {
2158 buffer.puts(*row_num, max_line_num_len + 1, "~ ", Style::Addition);
2159 }
2160 }
2161 } else {
2162 draw_col_separator(buffer, *row_num, max_line_num_len + 1);
2163 }
2164
2165 // print the suggestion
2166 buffer.append(*row_num, &normalize_whitespace(line), Style::NoStyle);
2167
2168 // Colorize addition/replacements with green.
2169 for &SubstitutionHighlight { start, end } in highlight_parts {
2170 // Account for tabs when highlighting (#87972).
2171 let tabs: usize = line
2172 .chars()
2173 .take(start)
2174 .map(|ch| match ch {
2175 '\t' => 3,
2176 _ => 0,
2177 })
2178 .sum();
2179 buffer.set_style_range(
2180 *row_num,
2181 max_line_num_len + 3 + start + tabs,
2182 max_line_num_len + 3 + end + tabs,
2183 Style::Addition,
2184 true,
2185 );
2186 }
2187 *row_num += 1;
2188 }
2189}
2190
2191#[derive(Clone, Copy)]
2192enum DisplaySuggestion {
2193 Underline,
2194 Diff,
2195 None,
1a4d82fc 2196}
970d7e83 2197
dc9dc135
XL
2198impl FileWithAnnotatedLines {
2199 /// Preprocess all the annotations so that they are grouped by file and by line number
2200 /// This helps us quickly iterate over the whole message (including secondary file spans)
2201 pub fn collect_annotations(
04454e1e
FG
2202 emitter: &dyn Emitter,
2203 args: &FluentArgs<'_>,
dc9dc135 2204 msp: &MultiSpan,
dc9dc135 2205 ) -> Vec<FileWithAnnotatedLines> {
60c5eb7d
XL
2206 fn add_annotation_to_file(
2207 file_vec: &mut Vec<FileWithAnnotatedLines>,
2208 file: Lrc<SourceFile>,
2209 line_index: usize,
2210 ann: Annotation,
2211 ) {
dc9dc135
XL
2212 for slot in file_vec.iter_mut() {
2213 // Look through each of our files for the one we're adding to
2214 if slot.file.name == file.name {
2215 // See if we already have a line for it
2216 for line_slot in &mut slot.lines {
2217 if line_slot.line_index == line_index {
2218 line_slot.annotations.push(ann);
2219 return;
2220 }
2221 }
2222 // We don't have a line yet, create one
60c5eb7d 2223 slot.lines.push(Line { line_index, annotations: vec![ann] });
dc9dc135
XL
2224 slot.lines.sort();
2225 return;
2226 }
2227 }
2228 // This is the first time we're seeing the file
2229 file_vec.push(FileWithAnnotatedLines {
2230 file,
60c5eb7d 2231 lines: vec![Line { line_index, annotations: vec![ann] }],
dc9dc135
XL
2232 multiline_depth: 0,
2233 });
2234 }
2235
2236 let mut output = vec![];
2237 let mut multiline_annotations = vec![];
2238
04454e1e 2239 if let Some(ref sm) = emitter.source_map() {
dc9dc135
XL
2240 for span_label in msp.span_labels() {
2241 if span_label.span.is_dummy() {
2242 continue;
2243 }
2244
2245 let lo = sm.lookup_char_pos(span_label.span.lo());
2246 let mut hi = sm.lookup_char_pos(span_label.span.hi());
2247
2248 // Watch out for "empty spans". If we get a span like 6..6, we
2249 // want to just display a `^` at 6, so convert that to
2250 // 6..7. This is degenerate input, but it's best to degrade
2251 // gracefully -- and the parser likes to supply a span like
2252 // that for EOF, in particular.
2253
2254 if lo.col_display == hi.col_display && lo.line == hi.line {
2255 hi.col_display += 1;
2256 }
2257
e74abb32 2258 if lo.line != hi.line {
dc9dc135
XL
2259 let ml = MultilineAnnotation {
2260 depth: 1,
2261 line_start: lo.line,
2262 line_end: hi.line,
2263 start_col: lo.col_display,
2264 end_col: hi.col_display,
2265 is_primary: span_label.is_primary,
04454e1e
FG
2266 label: span_label
2267 .label
2268 .as_ref()
2269 .map(|m| emitter.translate_message(m, args).to_string()),
dc9dc135
XL
2270 overlaps_exactly: false,
2271 };
e74abb32 2272 multiline_annotations.push((lo.file, ml));
dc9dc135 2273 } else {
e74abb32
XL
2274 let ann = Annotation {
2275 start_col: lo.col_display,
2276 end_col: hi.col_display,
2277 is_primary: span_label.is_primary,
04454e1e
FG
2278 label: span_label
2279 .label
2280 .as_ref()
2281 .map(|m| emitter.translate_message(m, args).to_string()),
e74abb32
XL
2282 annotation_type: AnnotationType::Singleline,
2283 };
dc9dc135 2284 add_annotation_to_file(&mut output, lo.file, lo.line, ann);
e74abb32 2285 };
dc9dc135
XL
2286 }
2287 }
2288
2289 // Find overlapping multiline annotations, put them at different depths
2290 multiline_annotations.sort_by_key(|&(_, ref ml)| (ml.line_start, ml.line_end));
e74abb32
XL
2291 for (_, ann) in multiline_annotations.clone() {
2292 for (_, a) in multiline_annotations.iter_mut() {
dc9dc135
XL
2293 // Move all other multiline annotations overlapping with this one
2294 // one level to the right.
60c5eb7d
XL
2295 if !(ann.same_span(a))
2296 && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true)
dc9dc135
XL
2297 {
2298 a.increase_depth();
2299 } else if ann.same_span(a) && &ann != a {
2300 a.overlaps_exactly = true;
2301 } else {
2302 break;
2303 }
2304 }
2305 }
2306
60c5eb7d 2307 let mut max_depth = 0; // max overlapping multiline spans
dc9dc135 2308 for (file, ann) in multiline_annotations {
e74abb32 2309 max_depth = max(max_depth, ann.depth);
dc9dc135
XL
2310 let mut end_ann = ann.as_end();
2311 if !ann.overlaps_exactly {
2312 // avoid output like
2313 //
2314 // | foo(
2315 // | _____^
2316 // | |_____|
2317 // | || bar,
2318 // | || );
2319 // | || ^
2320 // | ||______|
2321 // | |______foo
2322 // | baz
2323 //
2324 // and instead get
2325 //
2326 // | foo(
2327 // | _____^
2328 // | | bar,
2329 // | | );
2330 // | | ^
2331 // | | |
2332 // | |______foo
2333 // | baz
2334 add_annotation_to_file(&mut output, file.clone(), ann.line_start, ann.as_start());
2335 // 4 is the minimum vertical length of a multiline span when presented: two lines
2336 // of code and two lines of underline. This is not true for the special case where
2337 // the beginning doesn't have an underline, but the current logic seems to be
2338 // working correctly.
2339 let middle = min(ann.line_start + 4, ann.line_end);
2340 for line in ann.line_start + 1..middle {
2341 // Every `|` that joins the beginning of the span (`___^`) to the end (`|__^`).
2342 add_annotation_to_file(&mut output, file.clone(), line, ann.as_line());
2343 }
e74abb32
XL
2344 let line_end = ann.line_end - 1;
2345 if middle < line_end {
2346 add_annotation_to_file(&mut output, file.clone(), line_end, ann.as_line());
dc9dc135
XL
2347 }
2348 } else {
2349 end_ann.annotation_type = AnnotationType::Singleline;
2350 }
2351 add_annotation_to_file(&mut output, file, ann.line_end, end_ann);
2352 }
2353 for file_vec in output.iter_mut() {
2354 file_vec.multiline_depth = max_depth;
2355 }
2356 output
2357 }
2358}
2359
6a06907d
XL
2360// instead of taking the String length or dividing by 10 while > 0, we multiply a limit by 10 until
2361// we're higher. If the loop isn't exited by the `return`, the last multiplication will wrap, which
2362// is OK, because while we cannot fit a higher power of 10 in a usize, the loop will end anyway.
2363// This is also why we need the max number of decimal digits within a `usize`.
2364fn num_decimal_digits(num: usize) -> usize {
2365 #[cfg(target_pointer_width = "64")]
2366 const MAX_DIGITS: usize = 20;
2367
2368 #[cfg(target_pointer_width = "32")]
2369 const MAX_DIGITS: usize = 10;
2370
2371 #[cfg(target_pointer_width = "16")]
2372 const MAX_DIGITS: usize = 5;
2373
2374 let mut lim = 10;
2375 for num_digits in 1..MAX_DIGITS {
2376 if num < lim {
2377 return num_digits;
2378 }
2379 lim = lim.wrapping_mul(10);
2380 }
2381 MAX_DIGITS
2382}
2383
c295e0f8
XL
2384// We replace some characters so the CLI output is always consistent and underlines aligned.
2385const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
2386 ('\t', " "), // We do our own tab replacement
3c0e092e 2387 ('\u{200D}', ""), // Replace ZWJ with nothing for consistent terminal output of grapheme clusters.
c295e0f8 2388 ('\u{202A}', ""), // The following unicode text flow control characters are inconsistently
a2a8927a 2389 ('\u{202B}', ""), // supported across CLIs and can cause confusion due to the bytes on disk
c295e0f8
XL
2390 ('\u{202D}', ""), // not corresponding to the visible source code, so we replace them always.
2391 ('\u{202E}', ""),
2392 ('\u{2066}', ""),
2393 ('\u{2067}', ""),
2394 ('\u{2068}', ""),
2395 ('\u{202C}', ""),
2396 ('\u{2069}', ""),
2397];
2398
3c0e092e 2399fn normalize_whitespace(str: &str) -> String {
c295e0f8
XL
2400 let mut s = str.to_string();
2401 for (c, replacement) in OUTPUT_REPLACEMENTS {
2402 s = s.replace(*c, replacement);
2403 }
2404 s
5869c6ff
XL
2405}
2406
5bcae85e
SL
2407fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
2408 buffer.puts(line, col, "| ", Style::LineNumber);
7453a54e
SL
2409}
2410
5bcae85e 2411fn draw_col_separator_no_space(buffer: &mut StyledBuffer, line: usize, col: usize) {
476ff2be
SL
2412 draw_col_separator_no_space_with_style(buffer, line, col, Style::LineNumber);
2413}
2414
60c5eb7d
XL
2415fn draw_col_separator_no_space_with_style(
2416 buffer: &mut StyledBuffer,
2417 line: usize,
2418 col: usize,
2419 style: Style,
2420) {
476ff2be
SL
2421 buffer.putc(line, col, '|', style);
2422}
2423
60c5eb7d
XL
2424fn draw_range(
2425 buffer: &mut StyledBuffer,
2426 symbol: char,
2427 line: usize,
2428 col_from: usize,
2429 col_to: usize,
2430 style: Style,
2431) {
476ff2be
SL
2432 for col in col_from..col_to {
2433 buffer.putc(line, col, symbol, style);
2434 }
5bcae85e
SL
2435}
2436
2437fn draw_note_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {
2438 buffer.puts(line, col, "= ", Style::LineNumber);
2439}
2440
60c5eb7d
XL
2441fn draw_multiline_line(
2442 buffer: &mut StyledBuffer,
2443 line: usize,
2444 offset: usize,
2445 depth: usize,
2446 style: Style,
2447) {
cc61c64b
XL
2448 buffer.putc(line, offset + depth - 1, '|', style);
2449}
2450
60c5eb7d
XL
2451fn num_overlap(
2452 a_start: usize,
2453 a_end: usize,
2454 b_start: usize,
2455 b_end: usize,
2456 inclusive: bool,
2457) -> bool {
2458 let extra = if inclusive { 1 } else { 0 };
2459 (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start)
476ff2be 2460}
8bb4bdeb
XL
2461fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool {
2462 num_overlap(a1.start_col, a1.end_col + padding, a2.start_col, a2.end_col, false)
5bcae85e
SL
2463}
2464
60c5eb7d
XL
2465fn emit_to_destination(
2466 rendered_buffer: &[Vec<StyledString>],
2467 lvl: &Level,
2468 dst: &mut Destination,
2469 short_message: bool,
2470) -> io::Result<()> {
9fa01778 2471 use crate::lock;
9e0c209e 2472
0531ce1d
XL
2473 let mut dst = dst.writable();
2474
9e0c209e
SL
2475 // In order to prevent error message interleaving, where multiple error lines get intermixed
2476 // when multiple compiler processes error simultaneously, we emit errors with additional
2477 // steps.
2478 //
2479 // On Unix systems, we write into a buffered terminal rather than directly to a terminal. When
2480 // the .flush() is called we take the buffer created from the buffered writes and write it at
2481 // one shot. Because the Unix systems use ANSI for the colors, which is a text-based styling
2482 // scheme, this buffered approach works and maintains the styling.
2483 //
2484 // On Windows, styling happens through calls to a terminal API. This prevents us from using the
2485 // same buffering approach. Instead, we use a global Windows mutex, which we acquire long
2486 // enough to output the full error message, then we release.
2487 let _buffer_lock = lock::acquire_global_lock("rustc_errors");
0531ce1d 2488 for (pos, line) in rendered_buffer.iter().enumerate() {
5bcae85e 2489 for part in line {
ba9703b0 2490 dst.apply_style(*lvl, part.style)?;
5bcae85e 2491 write!(dst, "{}", part.text)?;
0531ce1d 2492 dst.reset()?;
a7813a04 2493 }
0531ce1d 2494 if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) {
dc9dc135 2495 writeln!(dst)?;
abe05a73 2496 }
9cc50fc6 2497 }
9e0c209e 2498 dst.flush()?;
9cc50fc6
SL
2499 Ok(())
2500}
2501
5bcae85e 2502pub enum Destination {
0531ce1d
XL
2503 Terminal(StandardStream),
2504 Buffered(BufferWriter),
48663c56
XL
2505 // The bool denotes whether we should be emitting ansi color codes or not
2506 Raw(Box<(dyn Write + Send)>, bool),
9cc50fc6
SL
2507}
2508
0531ce1d
XL
2509pub enum WritableDst<'a> {
2510 Terminal(&'a mut StandardStream),
2511 Buffered(&'a mut BufferWriter, Buffer),
48663c56
XL
2512 Raw(&'a mut (dyn Write + Send)),
2513 ColoredRaw(Ansi<&'a mut (dyn Write + Send)>),
9e0c209e
SL
2514}
2515
9cc50fc6 2516impl Destination {
0531ce1d
XL
2517 fn from_stderr(color: ColorConfig) -> Destination {
2518 let choice = color.to_color_choice();
2519 // On Windows we'll be performing global synchronization on the entire
2520 // system for emitting rustc errors, so there's no need to buffer
2521 // anything.
2522 //
2523 // On non-Windows we rely on the atomicity of `write` to ensure errors
2524 // don't get all jumbled up.
2525 if cfg!(windows) {
2526 Terminal(StandardStream::stderr(choice))
2527 } else {
2528 Buffered(BufferWriter::stderr(choice))
9e0c209e
SL
2529 }
2530 }
2531
416331ca 2532 fn writable(&mut self) -> WritableDst<'_> {
0531ce1d
XL
2533 match *self {
2534 Destination::Terminal(ref mut t) => WritableDst::Terminal(t),
2535 Destination::Buffered(ref mut t) => {
2536 let buf = t.buffer();
2537 WritableDst::Buffered(t, buf)
2538 }
48663c56
XL
2539 Destination::Raw(ref mut t, false) => WritableDst::Raw(t),
2540 Destination::Raw(ref mut t, true) => WritableDst::ColoredRaw(Ansi::new(t)),
9cc50fc6
SL
2541 }
2542 }
fc512014
XL
2543
2544 fn supports_color(&self) -> bool {
2545 match *self {
2546 Self::Terminal(ref stream) => stream.supports_color(),
2547 Self::Buffered(ref buffer) => buffer.buffer().supports_color(),
2548 Self::Raw(_, supports_color) => supports_color,
2549 }
2550 }
0531ce1d 2551}
9cc50fc6 2552
0531ce1d 2553impl<'a> WritableDst<'a> {
c30ab7b3 2554 fn apply_style(&mut self, lvl: Level, style: Style) -> io::Result<()> {
0531ce1d 2555 let mut spec = ColorSpec::new();
a7813a04 2556 match style {
94222f64
XL
2557 Style::Addition => {
2558 spec.set_fg(Some(Color::Green)).set_intense(true);
2559 }
2560 Style::Removal => {
2561 spec.set_fg(Some(Color::Red)).set_intense(true);
2562 }
041b39d2 2563 Style::LineAndColumn => {}
a7813a04 2564 Style::LineNumber => {
0531ce1d
XL
2565 spec.set_bold(true);
2566 spec.set_intense(true);
9e0c209e 2567 if cfg!(windows) {
0531ce1d 2568 spec.set_fg(Some(Color::Cyan));
9e0c209e 2569 } else {
0531ce1d 2570 spec.set_fg(Some(Color::Blue));
9e0c209e 2571 }
a7813a04 2572 }
5bcae85e 2573 Style::Quotation => {}
dc9dc135 2574 Style::MainHeaderMsg => {
0531ce1d 2575 spec.set_bold(true);
9e0c209e 2576 if cfg!(windows) {
60c5eb7d 2577 spec.set_intense(true).set_fg(Some(Color::White));
9e0c209e 2578 }
a7813a04
XL
2579 }
2580 Style::UnderlinePrimary | Style::LabelPrimary => {
0531ce1d
XL
2581 spec = lvl.color();
2582 spec.set_bold(true);
a7813a04 2583 }
60c5eb7d
XL
2584 Style::UnderlineSecondary | Style::LabelSecondary => {
2585 spec.set_bold(true).set_intense(true);
9e0c209e 2586 if cfg!(windows) {
0531ce1d 2587 spec.set_fg(Some(Color::Cyan));
9e0c209e 2588 } else {
0531ce1d 2589 spec.set_fg(Some(Color::Blue));
9e0c209e 2590 }
a7813a04 2591 }
60c5eb7d 2592 Style::HeaderMsg | Style::NoStyle => {}
0531ce1d
XL
2593 Style::Level(lvl) => {
2594 spec = lvl.color();
2595 spec.set_bold(true);
2596 }
2597 Style::Highlight => {
2598 spec.set_bold(true);
a7813a04
XL
2599 }
2600 }
0531ce1d 2601 self.set_color(&spec)
a7813a04
XL
2602 }
2603
0531ce1d 2604 fn set_color(&mut self, color: &ColorSpec) -> io::Result<()> {
a7813a04 2605 match *self {
0531ce1d
XL
2606 WritableDst::Terminal(ref mut t) => t.set_color(color),
2607 WritableDst::Buffered(_, ref mut t) => t.set_color(color),
48663c56 2608 WritableDst::ColoredRaw(ref mut t) => t.set_color(color),
60c5eb7d 2609 WritableDst::Raw(_) => Ok(()),
a7813a04 2610 }
a7813a04
XL
2611 }
2612
0531ce1d 2613 fn reset(&mut self) -> io::Result<()> {
a7813a04 2614 match *self {
0531ce1d
XL
2615 WritableDst::Terminal(ref mut t) => t.reset(),
2616 WritableDst::Buffered(_, ref mut t) => t.reset(),
48663c56 2617 WritableDst::ColoredRaw(ref mut t) => t.reset(),
0531ce1d 2618 WritableDst::Raw(_) => Ok(()),
a7813a04 2619 }
a7813a04 2620 }
9cc50fc6
SL
2621}
2622
0531ce1d 2623impl<'a> Write for WritableDst<'a> {
c34b1796
AL
2624 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
2625 match *self {
0531ce1d
XL
2626 WritableDst::Terminal(ref mut t) => t.write(bytes),
2627 WritableDst::Buffered(_, ref mut buf) => buf.write(bytes),
2628 WritableDst::Raw(ref mut w) => w.write(bytes),
48663c56 2629 WritableDst::ColoredRaw(ref mut t) => t.write(bytes),
c34b1796
AL
2630 }
2631 }
0531ce1d 2632
c34b1796 2633 fn flush(&mut self) -> io::Result<()> {
1a4d82fc 2634 match *self {
0531ce1d
XL
2635 WritableDst::Terminal(ref mut t) => t.flush(),
2636 WritableDst::Buffered(_, ref mut buf) => buf.flush(),
2637 WritableDst::Raw(ref mut w) => w.flush(),
48663c56 2638 WritableDst::ColoredRaw(ref mut w) => w.flush(),
0531ce1d
XL
2639 }
2640 }
2641}
2642
2643impl<'a> Drop for WritableDst<'a> {
2644 fn drop(&mut self) {
ba9703b0
XL
2645 if let WritableDst::Buffered(ref mut dst, ref mut buf) = self {
2646 drop(dst.print(buf));
1a4d82fc
JJ
2647 }
2648 }
9e0c209e 2649}
e74abb32
XL
2650
2651/// Whether the original and suggested code are visually similar enough to warrant extra wording.
60c5eb7d 2652pub fn is_case_difference(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
e74abb32 2653 // FIXME: this should probably be extended to also account for `FO0` → `FOO` and unicode.
60c5eb7d
XL
2654 let found = match sm.span_to_snippet(sp) {
2655 Ok(snippet) => snippet,
2656 Err(e) => {
c295e0f8 2657 warn!(error = ?e, "Invalid span {:?}", sp);
60c5eb7d
XL
2658 return false;
2659 }
2660 };
e74abb32
XL
2661 let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
2662 // All the chars that differ in capitalization are confusable (above):
cdc7bbd5 2663 let confusable = iter::zip(found.chars(), suggested.chars())
60c5eb7d
XL
2664 .filter(|(f, s)| f != s)
2665 .all(|(f, s)| (ascii_confusables.contains(&f) || ascii_confusables.contains(&s)));
e74abb32
XL
2666 confusable && found.to_lowercase() == suggested.to_lowercase()
2667 // FIXME: We sometimes suggest the same thing we already have, which is a
2668 // bug, but be defensive against that here.
2669 && found != suggested
2670}