]>
Commit | Line | Data |
---|---|---|
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 | //! | |
8 | //! The output types are defined in `librustc::session::config::ErrorOutputType`. | |
9 | ||
9fa01778 | 10 | use Destination::*; |
1a4d82fc | 11 | |
60c5eb7d XL |
12 | use syntax_pos::source_map::SourceMap; |
13 | use syntax_pos::{MultiSpan, SourceFile, Span}; | |
223e47cc | 14 | |
60c5eb7d XL |
15 | use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, Style, StyledString}; |
16 | use crate::styled_buffer::StyledBuffer; | |
17 | use crate::Level::Error; | |
9fa01778 | 18 | use crate::{ |
60c5eb7d | 19 | pluralize, CodeSuggestion, Diagnostic, DiagnosticId, Level, SubDiagnostic, SuggestionStyle, |
9fa01778 | 20 | }; |
9cc50fc6 | 21 | |
60c5eb7d | 22 | use log::*; |
b7449926 | 23 | use rustc_data_structures::fx::FxHashMap; |
0531ce1d | 24 | use rustc_data_structures::sync::Lrc; |
041b39d2 | 25 | use std::borrow::Cow; |
60c5eb7d | 26 | use std::cmp::{max, min, Reverse}; |
c34b1796 | 27 | use std::io; |
60c5eb7d | 28 | use std::io::prelude::*; |
48663c56 | 29 | use std::path::Path; |
60c5eb7d XL |
30 | use termcolor::{Ansi, BufferWriter, ColorChoice, ColorSpec, StandardStream}; |
31 | use termcolor::{Buffer, Color, WriteColor}; | |
1a4d82fc | 32 | |
48663c56 XL |
33 | /// Describes the way the content of the `rendered` field of the json output is generated |
34 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
35 | pub enum HumanReadableErrorType { | |
36 | Default(ColorConfig), | |
dc9dc135 | 37 | AnnotateSnippet(ColorConfig), |
48663c56 XL |
38 | Short(ColorConfig), |
39 | } | |
40 | ||
41 | impl HumanReadableErrorType { | |
42 | /// Returns a (`short`, `color`) tuple | |
43 | pub fn unzip(self) -> (bool, ColorConfig) { | |
44 | match self { | |
45 | HumanReadableErrorType::Default(cc) => (false, cc), | |
46 | HumanReadableErrorType::Short(cc) => (true, cc), | |
dc9dc135 | 47 | HumanReadableErrorType::AnnotateSnippet(cc) => (false, cc), |
48663c56 XL |
48 | } |
49 | } | |
50 | pub fn new_emitter( | |
51 | self, | |
52 | dst: Box<dyn Write + Send>, | |
60c5eb7d | 53 | source_map: Option<Lrc<SourceMap>>, |
48663c56 | 54 | teach: bool, |
e1599b0c XL |
55 | terminal_width: Option<usize>, |
56 | external_macro_backtrace: bool, | |
48663c56 XL |
57 | ) -> EmitterWriter { |
58 | let (short, color_config) = self.unzip(); | |
e1599b0c | 59 | let color = color_config.suggests_using_colors(); |
60c5eb7d XL |
60 | EmitterWriter::new( |
61 | dst, | |
62 | source_map, | |
63 | short, | |
64 | teach, | |
65 | color, | |
66 | terminal_width, | |
67 | external_macro_backtrace, | |
68 | ) | |
e1599b0c XL |
69 | } |
70 | } | |
71 | ||
72 | #[derive(Clone, Copy, Debug)] | |
73 | struct Margin { | |
74 | /// The available whitespace in the left that can be consumed when centering. | |
75 | pub whitespace_left: usize, | |
76 | /// The column of the beginning of left-most span. | |
77 | pub span_left: usize, | |
78 | /// The column of the end of right-most span. | |
79 | pub span_right: usize, | |
80 | /// The beginning of the line to be displayed. | |
81 | pub computed_left: usize, | |
82 | /// The end of the line to be displayed. | |
83 | pub computed_right: usize, | |
84 | /// The current width of the terminal. 140 by default and in tests. | |
85 | pub column_width: usize, | |
86 | /// The end column of a span label, including the span. Doesn't account for labels not in the | |
87 | /// same line as the span. | |
88 | pub label_right: usize, | |
89 | } | |
90 | ||
91 | impl Margin { | |
92 | fn new( | |
93 | whitespace_left: usize, | |
94 | span_left: usize, | |
95 | span_right: usize, | |
96 | label_right: usize, | |
97 | column_width: usize, | |
98 | max_line_len: usize, | |
99 | ) -> Self { | |
100 | // The 6 is padding to give a bit of room for `...` when displaying: | |
101 | // ``` | |
102 | // error: message | |
103 | // --> file.rs:16:58 | |
104 | // | | |
105 | // 16 | ... fn foo(self) -> Self::Bar { | |
106 | // | ^^^^^^^^^ | |
107 | // ``` | |
108 | ||
109 | let mut m = Margin { | |
e74abb32 XL |
110 | whitespace_left: whitespace_left.saturating_sub(6), |
111 | span_left: span_left.saturating_sub(6), | |
e1599b0c XL |
112 | span_right: span_right + 6, |
113 | computed_left: 0, | |
114 | computed_right: 0, | |
115 | column_width, | |
116 | label_right: label_right + 6, | |
117 | }; | |
118 | m.compute(max_line_len); | |
119 | m | |
120 | } | |
121 | ||
122 | fn was_cut_left(&self) -> bool { | |
123 | self.computed_left > 0 | |
124 | } | |
125 | ||
126 | fn was_cut_right(&self, line_len: usize) -> bool { | |
60c5eb7d XL |
127 | let right = |
128 | if self.computed_right == self.span_right || self.computed_right == self.label_right { | |
129 | // Account for the "..." padding given above. Otherwise we end | |
130 | // up with code lines that do fit but end in "..." as if they | |
131 | // were trimmed. | |
132 | self.computed_right - 6 | |
133 | } else { | |
134 | self.computed_right | |
135 | }; | |
e74abb32 | 136 | right < line_len && self.computed_left + self.column_width < line_len |
e1599b0c XL |
137 | } |
138 | ||
139 | fn compute(&mut self, max_line_len: usize) { | |
140 | // When there's a lot of whitespace (>20), we want to trim it as it is useless. | |
141 | self.computed_left = if self.whitespace_left > 20 { | |
142 | self.whitespace_left - 16 // We want some padding. | |
143 | } else { | |
144 | 0 | |
145 | }; | |
146 | // We want to show as much as possible, max_line_len is the right-most boundary for the | |
147 | // relevant code. | |
148 | self.computed_right = max(max_line_len, self.computed_left); | |
149 | ||
150 | if self.computed_right - self.computed_left > self.column_width { | |
151 | // Trimming only whitespace isn't enough, let's get craftier. | |
152 | if self.label_right - self.whitespace_left <= self.column_width { | |
153 | // Attempt to fit the code window only trimming whitespace. | |
154 | self.computed_left = self.whitespace_left; | |
155 | self.computed_right = self.computed_left + self.column_width; | |
156 | } else if self.label_right - self.span_left <= self.column_width { | |
157 | // Attempt to fit the code window considering only the spans and labels. | |
158 | let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; | |
159 | self.computed_left = self.span_left.saturating_sub(padding_left); | |
160 | self.computed_right = self.computed_left + self.column_width; | |
161 | } else if self.span_right - self.span_left <= self.column_width { | |
162 | // Attempt to fit the code window considering the spans and labels plus padding. | |
163 | let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; | |
164 | self.computed_left = self.span_left.saturating_sub(padding_left); | |
165 | self.computed_right = self.computed_left + self.column_width; | |
60c5eb7d XL |
166 | } else { |
167 | // Mostly give up but still don't show the full line. | |
e1599b0c XL |
168 | self.computed_left = self.span_left; |
169 | self.computed_right = self.span_right; | |
170 | } | |
171 | } | |
172 | } | |
173 | ||
174 | fn left(&self, line_len: usize) -> usize { | |
175 | min(self.computed_left, line_len) | |
176 | } | |
177 | ||
178 | fn right(&self, line_len: usize) -> usize { | |
e74abb32 | 179 | if line_len.saturating_sub(self.computed_left) <= self.column_width { |
e1599b0c XL |
180 | line_len |
181 | } else { | |
e74abb32 | 182 | min(line_len, self.computed_right) |
e1599b0c | 183 | } |
48663c56 XL |
184 | } |
185 | } | |
186 | ||
0531ce1d XL |
187 | const ANONYMIZED_LINE_NUM: &str = "LL"; |
188 | ||
5bcae85e | 189 | /// Emitter trait for emitting errors. |
9cc50fc6 | 190 | pub trait Emitter { |
9cc50fc6 | 191 | /// Emit a structured diagnostic. |
e74abb32 | 192 | fn emit_diagnostic(&mut self, diag: &Diagnostic); |
48663c56 XL |
193 | |
194 | /// Emit a notification that an artifact has been output. | |
195 | /// This is currently only supported for the JSON format, | |
196 | /// other formats can, and will, simply ignore it. | |
dc9dc135 | 197 | fn emit_artifact_notification(&mut self, _path: &Path, _artifact_type: &str) {} |
0531ce1d | 198 | |
9fa01778 | 199 | /// Checks if should show explanations about "rustc --explain" |
0531ce1d XL |
200 | fn should_show_explain(&self) -> bool { |
201 | true | |
202 | } | |
a7813a04 | 203 | |
60c5eb7d | 204 | fn source_map(&self) -> Option<&Lrc<SourceMap>>; |
e74abb32 | 205 | |
e1599b0c XL |
206 | /// Formats the substitutions of the primary_span |
207 | /// | |
208 | /// The are a lot of conditions to this method, but in short: | |
209 | /// | |
210 | /// * If the current `Diagnostic` has only one visible `CodeSuggestion`, | |
211 | /// we format the `help` suggestion depending on the content of the | |
212 | /// substitutions. In that case, we return the modified span only. | |
213 | /// | |
214 | /// * If the current `Diagnostic` has multiple suggestions, | |
215 | /// we return the original `primary_span` and the original suggestions. | |
216 | fn primary_span_formatted<'a>( | |
217 | &mut self, | |
e74abb32 | 218 | diag: &'a Diagnostic, |
e1599b0c | 219 | ) -> (MultiSpan, &'a [CodeSuggestion]) { |
e74abb32 XL |
220 | let mut primary_span = diag.span.clone(); |
221 | if let Some((sugg, rest)) = diag.suggestions.split_first() { | |
7cac9316 | 222 | if rest.is_empty() && |
e1599b0c | 223 | // ^ if there is only one suggestion |
7cac9316 | 224 | // don't display multi-suggestions as labels |
abe05a73 XL |
225 | sugg.substitutions.len() == 1 && |
226 | // don't display multipart suggestions as labels | |
227 | sugg.substitutions[0].parts.len() == 1 && | |
7cac9316 XL |
228 | // don't display long messages as labels |
229 | sugg.msg.split_whitespace().count() < 10 && | |
230 | // don't display multiline suggestions as labels | |
9fa01778 | 231 | !sugg.substitutions[0].parts[0].snippet.contains('\n') && |
e74abb32 XL |
232 | ![ |
233 | // when this style is set we want the suggestion to be a message, not inline | |
234 | SuggestionStyle::HideCodeAlways, | |
235 | // trivial suggestion for tooling's sake, never shown | |
236 | SuggestionStyle::CompletelyHidden, | |
237 | // subtle suggestion, never shown inline | |
238 | SuggestionStyle::ShowAlways, | |
239 | ].contains(&sugg.style) | |
9fa01778 | 240 | { |
ff7c6d11 | 241 | let substitution = &sugg.substitutions[0].parts[0].snippet.trim(); |
9fa01778 | 242 | let msg = if substitution.len() == 0 || sugg.style.hide_inline() { |
e1599b0c XL |
243 | // This substitution is only removal OR we explicitly don't want to show the |
244 | // code inline (`hide_inline`). Therefore, we don't show the substitution. | |
041b39d2 XL |
245 | format!("help: {}", sugg.msg) |
246 | } else { | |
e1599b0c | 247 | // Show the default suggestion text with the substitution |
e74abb32 XL |
248 | format!( |
249 | "help: {}{}: `{}`", | |
250 | sugg.msg, | |
60c5eb7d XL |
251 | if self |
252 | .source_map() | |
253 | .map(|sm| is_case_difference( | |
254 | &**sm, | |
255 | substitution, | |
256 | sugg.substitutions[0].parts[0].span, | |
257 | )) | |
258 | .unwrap_or(false) | |
259 | { | |
e74abb32 XL |
260 | " (notice the capitalization)" |
261 | } else { | |
262 | "" | |
263 | }, | |
264 | substitution, | |
265 | ) | |
041b39d2 | 266 | }; |
abe05a73 | 267 | primary_span.push_span_label(sugg.substitutions[0].parts[0].span, msg); |
e1599b0c XL |
268 | |
269 | // We return only the modified primary_span | |
270 | (primary_span, &[]) | |
7cac9316 XL |
271 | } else { |
272 | // if there are multiple suggestions, print them all in full | |
273 | // to be consistent. We could try to figure out if we can | |
274 | // make one (or the first one) inline, but that would give | |
275 | // undue importance to a semi-random suggestion | |
e74abb32 | 276 | (primary_span, &diag.suggestions) |
7cac9316 | 277 | } |
e1599b0c | 278 | } else { |
e74abb32 | 279 | (primary_span, &diag.suggestions) |
e1599b0c XL |
280 | } |
281 | } | |
282 | ||
283 | // This does a small "fix" for multispans by looking to see if it can find any that | |
284 | // point directly at <*macros>. Since these are often difficult to read, this | |
285 | // will change the span to point at the use site. | |
60c5eb7d XL |
286 | fn fix_multispans_in_std_macros( |
287 | &self, | |
288 | source_map: &Option<Lrc<SourceMap>>, | |
289 | span: &mut MultiSpan, | |
290 | children: &mut Vec<SubDiagnostic>, | |
291 | level: &Level, | |
292 | backtrace: bool, | |
293 | ) { | |
e1599b0c XL |
294 | let mut spans_updated = self.fix_multispan_in_std_macros(source_map, span, backtrace); |
295 | for child in children.iter_mut() { | |
60c5eb7d XL |
296 | spans_updated |= |
297 | self.fix_multispan_in_std_macros(source_map, &mut child.span, backtrace); | |
7cac9316 | 298 | } |
e1599b0c XL |
299 | let msg = if level == &Error { |
300 | "this error originates in a macro outside of the current crate \ | |
301 | (in Nightly builds, run with -Z external-macro-backtrace \ | |
60c5eb7d XL |
302 | for more info)" |
303 | .to_string() | |
e1599b0c XL |
304 | } else { |
305 | "this warning originates in a macro outside of the current crate \ | |
306 | (in Nightly builds, run with -Z external-macro-backtrace \ | |
60c5eb7d XL |
307 | for more info)" |
308 | .to_string() | |
e1599b0c | 309 | }; |
7cac9316 | 310 | |
e1599b0c XL |
311 | if spans_updated { |
312 | children.push(SubDiagnostic { | |
313 | level: Level::Note, | |
60c5eb7d | 314 | message: vec![(msg, Style::NoStyle)], |
e1599b0c XL |
315 | span: MultiSpan::new(), |
316 | render_span: None, | |
317 | }); | |
318 | } | |
319 | } | |
320 | ||
321 | // This "fixes" MultiSpans that contain Spans that are pointing to locations inside of | |
322 | // <*macros>. Since these locations are often difficult to read, we move these Spans from | |
323 | // <*macros> to their corresponding use site. | |
60c5eb7d XL |
324 | fn fix_multispan_in_std_macros( |
325 | &self, | |
326 | source_map: &Option<Lrc<SourceMap>>, | |
327 | span: &mut MultiSpan, | |
328 | always_backtrace: bool, | |
329 | ) -> bool { | |
e74abb32 XL |
330 | let sm = match source_map { |
331 | Some(ref sm) => sm, | |
332 | None => return false, | |
333 | }; | |
e1599b0c | 334 | |
e74abb32 XL |
335 | let mut before_after: Vec<(Span, Span)> = vec![]; |
336 | let mut new_labels: Vec<(Span, String)> = vec![]; | |
e1599b0c | 337 | |
e74abb32 XL |
338 | // First, find all the spans in <*macros> and point instead at their use site |
339 | for sp in span.primary_spans() { | |
340 | if sp.is_dummy() { | |
341 | continue; | |
342 | } | |
343 | let call_sp = sm.call_span_if_macro(*sp); | |
344 | if call_sp != *sp && !always_backtrace { | |
345 | before_after.push((*sp, call_sp)); | |
346 | } | |
347 | let backtrace_len = sp.macro_backtrace().len(); | |
348 | for (i, trace) in sp.macro_backtrace().iter().rev().enumerate() { | |
349 | // Only show macro locations that are local | |
350 | // and display them like a span_note | |
351 | if trace.def_site_span.is_dummy() { | |
e1599b0c XL |
352 | continue; |
353 | } | |
e74abb32 | 354 | if always_backtrace { |
60c5eb7d XL |
355 | new_labels.push(( |
356 | trace.def_site_span, | |
357 | format!( | |
358 | "in this expansion of `{}`{}", | |
359 | trace.macro_decl_name, | |
360 | if backtrace_len > 2 { | |
361 | // if backtrace_len == 1 it'll be pointed | |
362 | // at by "in this macro invocation" | |
363 | format!(" (#{})", i + 1) | |
364 | } else { | |
365 | String::new() | |
366 | } | |
367 | ), | |
368 | )); | |
e1599b0c | 369 | } |
e74abb32 | 370 | // Check to make sure we're not in any <*macros> |
60c5eb7d XL |
371 | if !sm.span_to_filename(trace.def_site_span).is_macros() |
372 | && !trace.macro_decl_name.starts_with("desugaring of ") | |
373 | && !trace.macro_decl_name.starts_with("#[") | |
374 | || always_backtrace | |
375 | { | |
376 | new_labels.push(( | |
377 | trace.call_site, | |
378 | format!( | |
379 | "in this macro invocation{}", | |
380 | if backtrace_len > 2 && always_backtrace { | |
381 | // only specify order when the macro | |
382 | // backtrace is multiple levels deep | |
383 | format!(" (#{})", i + 1) | |
384 | } else { | |
385 | String::new() | |
386 | } | |
387 | ), | |
388 | )); | |
e74abb32 XL |
389 | if !always_backtrace { |
390 | break; | |
e1599b0c XL |
391 | } |
392 | } | |
393 | } | |
e74abb32 XL |
394 | } |
395 | for (label_span, label_text) in new_labels { | |
396 | span.push_span_label(label_span, label_text); | |
397 | } | |
398 | for sp_label in span.span_labels() { | |
399 | if sp_label.span.is_dummy() { | |
400 | continue; | |
e1599b0c | 401 | } |
60c5eb7d | 402 | if sm.span_to_filename(sp_label.span.clone()).is_macros() && !always_backtrace { |
e74abb32 XL |
403 | let v = sp_label.span.macro_backtrace(); |
404 | if let Some(use_site) = v.last() { | |
405 | before_after.push((sp_label.span.clone(), use_site.call_site.clone())); | |
e1599b0c XL |
406 | } |
407 | } | |
e74abb32 XL |
408 | } |
409 | // After we have them, make sure we replace these 'bad' def sites with their use sites | |
410 | let spans_updated = !before_after.is_empty(); | |
411 | for (before, after) in before_after { | |
412 | span.replace(before, after); | |
e1599b0c XL |
413 | } |
414 | ||
415 | spans_updated | |
416 | } | |
417 | } | |
418 | ||
419 | impl Emitter for EmitterWriter { | |
60c5eb7d | 420 | fn source_map(&self) -> Option<&Lrc<SourceMap>> { |
e74abb32 XL |
421 | self.sm.as_ref() |
422 | } | |
423 | ||
424 | fn emit_diagnostic(&mut self, diag: &Diagnostic) { | |
425 | let mut children = diag.children.clone(); | |
426 | let (mut primary_span, suggestions) = self.primary_span_formatted(&diag); | |
e1599b0c | 427 | |
60c5eb7d XL |
428 | self.fix_multispans_in_std_macros( |
429 | &self.sm, | |
430 | &mut primary_span, | |
431 | &mut children, | |
432 | &diag.level, | |
433 | self.external_macro_backtrace, | |
434 | ); | |
435 | ||
436 | self.emit_messages_default( | |
437 | &diag.level, | |
438 | &diag.styled_message(), | |
439 | &diag.code, | |
440 | &primary_span, | |
441 | &children, | |
442 | &suggestions, | |
443 | ); | |
1a4d82fc | 444 | } |
0531ce1d XL |
445 | |
446 | fn should_show_explain(&self) -> bool { | |
447 | !self.short_message | |
448 | } | |
223e47cc LB |
449 | } |
450 | ||
60c5eb7d XL |
451 | /// An emitter that does nothing when emitting a diagnostic. |
452 | pub struct SilentEmitter; | |
453 | ||
454 | impl Emitter for SilentEmitter { | |
455 | fn source_map(&self) -> Option<&Lrc<SourceMap>> { | |
456 | None | |
457 | } | |
458 | fn emit_diagnostic(&mut self, _: &Diagnostic) {} | |
459 | } | |
460 | ||
9cc50fc6 | 461 | /// maximum number of lines we will print for each error; arbitrary. |
7453a54e | 462 | pub const MAX_HIGHLIGHT_LINES: usize = 6; |
7cac9316 XL |
463 | /// maximum number of suggestions to be shown |
464 | /// | |
465 | /// Arbitrary, but taken from trait import suggestion limit | |
466 | pub const MAX_SUGGESTIONS: usize = 4; | |
7453a54e | 467 | |
9cc50fc6 | 468 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
1a4d82fc JJ |
469 | pub enum ColorConfig { |
470 | Auto, | |
471 | Always, | |
9cc50fc6 | 472 | Never, |
223e47cc LB |
473 | } |
474 | ||
9cc50fc6 | 475 | impl ColorConfig { |
48663c56 XL |
476 | fn to_color_choice(self) -> ColorChoice { |
477 | match self { | |
a1dfa0c6 XL |
478 | ColorConfig::Always => { |
479 | if atty::is(atty::Stream::Stderr) { | |
480 | ColorChoice::Always | |
481 | } else { | |
482 | ColorChoice::AlwaysAnsi | |
483 | } | |
484 | } | |
0531ce1d | 485 | ColorConfig::Never => ColorChoice::Never, |
60c5eb7d | 486 | ColorConfig::Auto if atty::is(atty::Stream::Stderr) => ColorChoice::Auto, |
0531ce1d | 487 | ColorConfig::Auto => ColorChoice::Never, |
9cc50fc6 | 488 | } |
d9579d0f | 489 | } |
48663c56 XL |
490 | fn suggests_using_colors(self) -> bool { |
491 | match self { | |
60c5eb7d | 492 | ColorConfig::Always | ColorConfig::Auto => true, |
48663c56 XL |
493 | ColorConfig::Never => false, |
494 | } | |
495 | } | |
d9579d0f AL |
496 | } |
497 | ||
dc9dc135 | 498 | /// Handles the writing of `HumanReadableErrorType::Default` and `HumanReadableErrorType::Short` |
9cc50fc6 SL |
499 | pub struct EmitterWriter { |
500 | dst: Destination, | |
60c5eb7d | 501 | sm: Option<Lrc<SourceMap>>, |
abe05a73 | 502 | short_message: bool, |
2c00a5a8 | 503 | teach: bool, |
0531ce1d | 504 | ui_testing: bool, |
e1599b0c XL |
505 | terminal_width: Option<usize>, |
506 | ||
507 | external_macro_backtrace: bool, | |
a7813a04 | 508 | } |
223e47cc | 509 | |
dc9dc135 XL |
510 | #[derive(Debug)] |
511 | pub struct FileWithAnnotatedLines { | |
512 | pub file: Lrc<SourceFile>, | |
513 | pub lines: Vec<Line>, | |
476ff2be | 514 | multiline_depth: usize, |
223e47cc LB |
515 | } |
516 | ||
1a4d82fc | 517 | impl EmitterWriter { |
e1599b0c XL |
518 | pub fn stderr( |
519 | color_config: ColorConfig, | |
60c5eb7d | 520 | source_map: Option<Lrc<SourceMap>>, |
e1599b0c XL |
521 | short_message: bool, |
522 | teach: bool, | |
523 | terminal_width: Option<usize>, | |
524 | external_macro_backtrace: bool, | |
525 | ) -> EmitterWriter { | |
0531ce1d XL |
526 | let dst = Destination::from_stderr(color_config); |
527 | EmitterWriter { | |
528 | dst, | |
a1dfa0c6 | 529 | sm: source_map, |
0531ce1d XL |
530 | short_message, |
531 | teach, | |
532 | ui_testing: false, | |
e1599b0c XL |
533 | terminal_width, |
534 | external_macro_backtrace, | |
1a4d82fc JJ |
535 | } |
536 | } | |
537 | ||
48663c56 XL |
538 | pub fn new( |
539 | dst: Box<dyn Write + Send>, | |
60c5eb7d | 540 | source_map: Option<Lrc<SourceMap>>, |
48663c56 XL |
541 | short_message: bool, |
542 | teach: bool, | |
543 | colored: bool, | |
e1599b0c XL |
544 | terminal_width: Option<usize>, |
545 | external_macro_backtrace: bool, | |
48663c56 | 546 | ) -> EmitterWriter { |
c30ab7b3 | 547 | EmitterWriter { |
48663c56 | 548 | dst: Raw(dst, colored), |
a1dfa0c6 | 549 | sm: source_map, |
2c00a5a8 XL |
550 | short_message, |
551 | teach, | |
0531ce1d | 552 | ui_testing: false, |
e1599b0c XL |
553 | terminal_width, |
554 | external_macro_backtrace, | |
0531ce1d XL |
555 | } |
556 | } | |
557 | ||
558 | pub fn ui_testing(mut self, ui_testing: bool) -> Self { | |
559 | self.ui_testing = ui_testing; | |
560 | self | |
561 | } | |
562 | ||
563 | fn maybe_anonymized(&self, line_num: usize) -> String { | |
60c5eb7d | 564 | if self.ui_testing { ANONYMIZED_LINE_NUM.to_string() } else { line_num.to_string() } |
a7813a04 XL |
565 | } |
566 | ||
e1599b0c XL |
567 | fn draw_line( |
568 | &self, | |
569 | buffer: &mut StyledBuffer, | |
570 | source_string: &str, | |
571 | line_index: usize, | |
572 | line_offset: usize, | |
573 | width_offset: usize, | |
574 | code_offset: usize, | |
575 | margin: Margin, | |
576 | ) { | |
577 | let line_len = source_string.len(); | |
578 | // Create the source line we will highlight. | |
579 | let left = margin.left(line_len); | |
580 | let right = margin.right(line_len); | |
581 | // On long lines, we strip the source line, accounting for unicode. | |
582 | let mut taken = 0; | |
60c5eb7d XL |
583 | let code: String = source_string |
584 | .chars() | |
585 | .skip(left) | |
586 | .take_while(|ch| { | |
587 | // Make sure that the trimming on the right will fall within the | |
588 | // terminal width. FIXME: `unicode_width` sometimes disagrees | |
589 | // with terminals on how wide a `char` is. For now, just accept | |
590 | // that sometimes the code line will be longer than desired. | |
591 | let next = unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); | |
592 | if taken + next > right - left { | |
593 | return false; | |
594 | } | |
595 | taken += next; | |
596 | true | |
597 | }) | |
598 | .collect(); | |
e1599b0c XL |
599 | buffer.puts(line_offset, code_offset, &code, Style::Quotation); |
600 | if margin.was_cut_left() { | |
601 | // We have stripped some code/whitespace from the beginning, make it clear. | |
602 | buffer.puts(line_offset, code_offset, "...", Style::LineNumber); | |
603 | } | |
604 | if margin.was_cut_right(line_len) { | |
605 | // We have stripped some code after the right-most span end, make it clear we did so. | |
606 | buffer.puts(line_offset, code_offset + taken - 3, "...", Style::LineNumber); | |
607 | } | |
608 | buffer.puts(line_offset, 0, &self.maybe_anonymized(line_index), Style::LineNumber); | |
609 | ||
610 | draw_col_separator(buffer, line_offset, width_offset - 2); | |
611 | } | |
612 | ||
613 | fn render_source_line( | |
614 | &self, | |
615 | buffer: &mut StyledBuffer, | |
616 | file: Lrc<SourceFile>, | |
617 | line: &Line, | |
618 | width_offset: usize, | |
619 | code_offset: usize, | |
620 | margin: Margin, | |
621 | ) -> Vec<(usize, Style)> { | |
622 | // Draw: | |
623 | // | |
624 | // LL | ... code ... | |
625 | // | ^^-^ span label | |
626 | // | | | |
627 | // | secondary span label | |
628 | // | |
629 | // ^^ ^ ^^^ ^^^^ ^^^ we don't care about code too far to the right of a span, we trim it | |
630 | // | | | | | |
631 | // | | | actual code found in your source code and the spans we use to mark it | |
632 | // | | when there's too much wasted space to the left, trim it | |
633 | // | vertical divider between the column number and the code | |
634 | // column number | |
635 | ||
2c00a5a8 XL |
636 | if line.line_index == 0 { |
637 | return Vec::new(); | |
638 | } | |
639 | ||
7cac9316 XL |
640 | let source_string = match file.get_line(line.line_index - 1) { |
641 | Some(s) => s, | |
642 | None => return Vec::new(), | |
643 | }; | |
5bcae85e SL |
644 | |
645 | let line_offset = buffer.num_lines(); | |
c1a9b12d | 646 | |
e1599b0c XL |
647 | let left = margin.left(source_string.len()); // Left trim |
648 | // Account for unicode characters of width !=0 that were removed. | |
60c5eb7d XL |
649 | let left = source_string |
650 | .chars() | |
651 | .take(left) | |
e74abb32 XL |
652 | .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1)) |
653 | .sum(); | |
5bcae85e | 654 | |
e1599b0c XL |
655 | self.draw_line( |
656 | buffer, | |
657 | &source_string, | |
658 | line.line_index, | |
659 | line_offset, | |
660 | width_offset, | |
661 | code_offset, | |
662 | margin, | |
663 | ); | |
5bcae85e | 664 | |
cc61c64b XL |
665 | // Special case when there's only one annotation involved, it is the start of a multiline |
666 | // span and there's no text at the beginning of the code line. Instead of doing the whole | |
667 | // graph: | |
668 | // | |
669 | // 2 | fn foo() { | |
670 | // | _^ | |
671 | // 3 | | | |
672 | // 4 | | } | |
673 | // | |_^ test | |
674 | // | |
675 | // we simplify the output to: | |
676 | // | |
677 | // 2 | / fn foo() { | |
678 | // 3 | | | |
679 | // 4 | | } | |
680 | // | |_^ test | |
e74abb32 XL |
681 | if let [ann] = &line.annotations[..] { |
682 | if let AnnotationType::MultilineStart(depth) = ann.annotation_type { | |
683 | if source_string.chars().take(ann.start_col).all(|c| c.is_whitespace()) { | |
684 | let style = if ann.is_primary { | |
685 | Style::UnderlinePrimary | |
686 | } else { | |
687 | Style::UnderlineSecondary | |
688 | }; | |
689 | buffer.putc(line_offset, width_offset + depth - 1, '/', style); | |
690 | return vec![(depth, style)]; | |
cc61c64b XL |
691 | } |
692 | } | |
693 | } | |
694 | ||
5bcae85e SL |
695 | // We want to display like this: |
696 | // | |
697 | // vec.push(vec.pop().unwrap()); | |
476ff2be | 698 | // --- ^^^ - previous borrow ends here |
5bcae85e SL |
699 | // | | |
700 | // | error occurs here | |
701 | // previous borrow of `vec` occurs here | |
702 | // | |
703 | // But there are some weird edge cases to be aware of: | |
704 | // | |
705 | // vec.push(vec.pop().unwrap()); | |
706 | // -------- - previous borrow ends here | |
707 | // || | |
708 | // |this makes no sense | |
709 | // previous borrow of `vec` occurs here | |
710 | // | |
711 | // For this reason, we group the lines into "highlight lines" | |
cc61c64b | 712 | // and "annotations lines", where the highlight lines have the `^`. |
5bcae85e SL |
713 | |
714 | // Sort the annotations by (start, end col) | |
3b2f2976 XL |
715 | // The labels are reversed, sort and then reversed again. |
716 | // Consider a list of annotations (A1, A2, C1, C2, B1, B2) where | |
717 | // the letter signifies the span. Here we are only sorting by the | |
718 | // span and hence, the order of the elements with the same span will | |
719 | // not change. On reversing the ordering (|a, b| but b.cmp(a)), you get | |
720 | // (C1, C2, B1, B2, A1, A2). All the elements with the same span are | |
721 | // still ordered first to last, but all the elements with different | |
722 | // spans are ordered by their spans in last to first order. Last to | |
723 | // first order is important, because the jiggly lines and | are on | |
724 | // the left, so the rightmost span needs to be rendered first, | |
725 | // otherwise the lines would end up needing to go over a message. | |
726 | ||
5bcae85e | 727 | let mut annotations = line.annotations.clone(); |
8faf50e0 | 728 | annotations.sort_by_key(|a| Reverse(a.start_col)); |
5bcae85e | 729 | |
476ff2be SL |
730 | // First, figure out where each label will be positioned. |
731 | // | |
732 | // In the case where you have the following annotations: | |
733 | // | |
734 | // vec.push(vec.pop().unwrap()); | |
735 | // -------- - previous borrow ends here [C] | |
736 | // || | |
737 | // |this makes no sense [B] | |
738 | // previous borrow of `vec` occurs here [A] | |
739 | // | |
740 | // `annotations_position` will hold [(2, A), (1, B), (0, C)]. | |
741 | // | |
742 | // We try, when possible, to stick the rightmost annotation at the end | |
743 | // of the highlight line: | |
5bcae85e SL |
744 | // |
745 | // vec.push(vec.pop().unwrap()); | |
746 | // --- --- - previous borrow ends here | |
747 | // | |
748 | // But sometimes that's not possible because one of the other | |
749 | // annotations overlaps it. For example, from the test | |
750 | // `span_overlap_label`, we have the following annotations | |
751 | // (written on distinct lines for clarity): | |
752 | // | |
753 | // fn foo(x: u32) { | |
754 | // -------------- | |
755 | // - | |
756 | // | |
757 | // In this case, we can't stick the rightmost-most label on | |
758 | // the highlight line, or we would get: | |
759 | // | |
760 | // fn foo(x: u32) { | |
761 | // -------- x_span | |
762 | // | | |
763 | // fn_span | |
764 | // | |
765 | // which is totally weird. Instead we want: | |
766 | // | |
767 | // fn foo(x: u32) { | |
768 | // -------------- | |
769 | // | | | |
770 | // | x_span | |
771 | // fn_span | |
772 | // | |
773 | // which is...less weird, at least. In fact, in general, if | |
774 | // the rightmost span overlaps with any other span, we should | |
775 | // use the "hang below" version, so we can at least make it | |
32a655c1 SL |
776 | // clear where the span *starts*. There's an exception for this |
777 | // logic, when the labels do not have a message: | |
778 | // | |
779 | // fn foo(x: u32) { | |
780 | // -------------- | |
781 | // | | |
782 | // x_span | |
783 | // | |
784 | // instead of: | |
785 | // | |
786 | // fn foo(x: u32) { | |
787 | // -------------- | |
788 | // | | | |
789 | // | x_span | |
790 | // <EMPTY LINE> | |
791 | // | |
476ff2be SL |
792 | let mut annotations_position = vec![]; |
793 | let mut line_len = 0; | |
794 | let mut p = 0; | |
8bb4bdeb XL |
795 | for (i, annotation) in annotations.iter().enumerate() { |
796 | for (j, next) in annotations.iter().enumerate() { | |
797 | if overlaps(next, annotation, 0) // This label overlaps with another one and both | |
cc61c64b XL |
798 | && annotation.has_label() // take space (they have text and are not |
799 | && j > i // multiline lines). | |
60c5eb7d XL |
800 | && p == 0 |
801 | // We're currently on the first line, move the label one line down | |
32a655c1 | 802 | { |
48663c56 XL |
803 | // If we're overlapping with an un-labelled annotation with the same span |
804 | // we can just merge them in the output | |
805 | if next.start_col == annotation.start_col | |
60c5eb7d XL |
806 | && next.end_col == annotation.end_col |
807 | && !next.has_label() | |
48663c56 XL |
808 | { |
809 | continue; | |
810 | } | |
811 | ||
32a655c1 | 812 | // This annotation needs a new line in the output. |
476ff2be | 813 | p += 1; |
8bb4bdeb | 814 | break; |
a7813a04 | 815 | } |
c1a9b12d | 816 | } |
476ff2be | 817 | annotations_position.push((p, annotation)); |
8bb4bdeb | 818 | for (j, next) in annotations.iter().enumerate() { |
60c5eb7d | 819 | if j > i { |
e74abb32 | 820 | let l = next.label.as_ref().map_or(0, |label| label.len() + 2); |
cc61c64b | 821 | if (overlaps(next, annotation, l) // Do not allow two labels to be in the same |
8bb4bdeb XL |
822 | // line if they overlap including padding, to |
823 | // avoid situations like: | |
824 | // | |
825 | // fn foo(x: u32) { | |
826 | // -------^------ | |
827 | // | | | |
828 | // fn_spanx_span | |
829 | // | |
8bb4bdeb | 830 | && annotation.has_label() // Both labels must have some text, otherwise |
cc61c64b XL |
831 | && next.has_label()) // they are not overlapping. |
832 | // Do not add a new line if this annotation | |
833 | // or the next are vertical line placeholders. | |
834 | || (annotation.takes_space() // If either this or the next annotation is | |
835 | && next.has_label()) // multiline start/end, move it to a new line | |
836 | || (annotation.has_label() // so as not to overlap the orizontal lines. | |
837 | && next.takes_space()) | |
041b39d2 XL |
838 | || (annotation.takes_space() && next.takes_space()) |
839 | || (overlaps(next, annotation, l) | |
840 | && next.end_col <= annotation.end_col | |
841 | && next.has_label() | |
60c5eb7d XL |
842 | && p == 0) |
843 | // Avoid #42595. | |
8bb4bdeb | 844 | { |
cc61c64b | 845 | // This annotation needs a new line in the output. |
8bb4bdeb XL |
846 | p += 1; |
847 | break; | |
848 | } | |
476ff2be SL |
849 | } |
850 | } | |
e74abb32 | 851 | line_len = max(line_len, p); |
476ff2be | 852 | } |
cc61c64b | 853 | |
476ff2be SL |
854 | if line_len != 0 { |
855 | line_len += 1; | |
5bcae85e SL |
856 | } |
857 | ||
476ff2be SL |
858 | // If there are no annotations or the only annotations on this line are |
859 | // MultilineLine, then there's only code being shown, stop processing. | |
8faf50e0 | 860 | if line.annotations.iter().all(|a| a.is_line()) { |
cc61c64b | 861 | return vec![]; |
5bcae85e SL |
862 | } |
863 | ||
60c5eb7d | 864 | // Write the column separator. |
32a655c1 SL |
865 | // |
866 | // After this we will have: | |
867 | // | |
868 | // 2 | fn foo() { | |
869 | // | | |
870 | // | | |
871 | // | | |
872 | // 3 | | |
873 | // 4 | } | |
874 | // | | |
0731742a | 875 | for pos in 0..=line_len { |
476ff2be | 876 | draw_col_separator(buffer, line_offset + pos + 1, width_offset - 2); |
60c5eb7d | 877 | buffer.putc(line_offset + pos + 1, width_offset - 2, '|', Style::LineNumber); |
476ff2be SL |
878 | } |
879 | ||
880 | // Write the horizontal lines for multiline annotations | |
881 | // (only the first and last lines need this). | |
882 | // | |
883 | // After this we will have: | |
884 | // | |
885 | // 2 | fn foo() { | |
886 | // | __________ | |
887 | // | | |
888 | // | | |
889 | // 3 | | |
890 | // 4 | } | |
891 | // | _ | |
892 | for &(pos, annotation) in &annotations_position { | |
893 | let style = if annotation.is_primary { | |
894 | Style::UnderlinePrimary | |
895 | } else { | |
896 | Style::UnderlineSecondary | |
897 | }; | |
898 | let pos = pos + 1; | |
899 | match annotation.annotation_type { | |
60c5eb7d | 900 | AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => { |
e1599b0c XL |
901 | draw_range( |
902 | buffer, | |
903 | '_', | |
904 | line_offset + pos, | |
905 | width_offset + depth, | |
906 | code_offset + annotation.start_col - left, | |
907 | style, | |
908 | ); | |
476ff2be | 909 | } |
2c00a5a8 | 910 | _ if self.teach => { |
e1599b0c XL |
911 | buffer.set_style_range( |
912 | line_offset, | |
913 | code_offset + annotation.start_col - left, | |
914 | code_offset + annotation.end_col - left, | |
915 | style, | |
916 | annotation.is_primary, | |
917 | ); | |
2c00a5a8 XL |
918 | } |
919 | _ => {} | |
476ff2be SL |
920 | } |
921 | } | |
922 | ||
cc61c64b | 923 | // Write the vertical lines for labels that are on a different line as the underline. |
476ff2be SL |
924 | // |
925 | // After this we will have: | |
926 | // | |
927 | // 2 | fn foo() { | |
928 | // | __________ | |
929 | // | | | | |
930 | // | | | |
60c5eb7d | 931 | // 3 | | |
476ff2be SL |
932 | // 4 | | } |
933 | // | |_ | |
934 | for &(pos, annotation) in &annotations_position { | |
935 | let style = if annotation.is_primary { | |
936 | Style::UnderlinePrimary | |
937 | } else { | |
938 | Style::UnderlineSecondary | |
939 | }; | |
940 | let pos = pos + 1; | |
32a655c1 | 941 | |
cc61c64b | 942 | if pos > 1 && (annotation.has_label() || annotation.takes_space()) { |
0731742a | 943 | for p in line_offset + 1..=line_offset + pos { |
60c5eb7d XL |
944 | buffer.putc( |
945 | p, | |
946 | (code_offset + annotation.start_col).saturating_sub(left), | |
947 | '|', | |
948 | style, | |
949 | ); | |
476ff2be SL |
950 | } |
951 | } | |
952 | match annotation.annotation_type { | |
953 | AnnotationType::MultilineStart(depth) => { | |
954 | for p in line_offset + pos + 1..line_offset + line_len + 2 { | |
60c5eb7d | 955 | buffer.putc(p, width_offset + depth - 1, '|', style); |
476ff2be SL |
956 | } |
957 | } | |
958 | AnnotationType::MultilineEnd(depth) => { | |
0731742a | 959 | for p in line_offset..=line_offset + pos { |
60c5eb7d | 960 | buffer.putc(p, width_offset + depth - 1, '|', style); |
476ff2be SL |
961 | } |
962 | } | |
476ff2be SL |
963 | _ => (), |
964 | } | |
965 | } | |
966 | ||
967 | // Write the labels on the annotations that actually have a label. | |
968 | // | |
969 | // After this we will have: | |
970 | // | |
971 | // 2 | fn foo() { | |
cc61c64b XL |
972 | // | __________ |
973 | // | | | |
974 | // | something about `foo` | |
975 | // 3 | | |
976 | // 4 | } | |
977 | // | _ test | |
476ff2be | 978 | for &(pos, annotation) in &annotations_position { |
60c5eb7d XL |
979 | let style = |
980 | if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary }; | |
476ff2be | 981 | let (pos, col) = if pos == 0 { |
e74abb32 | 982 | (pos + 1, (annotation.end_col + 1).saturating_sub(left)) |
476ff2be | 983 | } else { |
e74abb32 | 984 | (pos + 2, annotation.start_col.saturating_sub(left)) |
476ff2be SL |
985 | }; |
986 | if let Some(ref label) = annotation.label { | |
e1599b0c | 987 | buffer.puts(line_offset + pos, code_offset + col, &label, style); |
476ff2be SL |
988 | } |
989 | } | |
990 | ||
991 | // Sort from biggest span to smallest span so that smaller spans are | |
992 | // represented in the output: | |
993 | // | |
994 | // x | fn foo() | |
995 | // | ^^^---^^ | |
996 | // | | | | |
997 | // | | something about `foo` | |
998 | // | something about `fn foo()` | |
e74abb32 XL |
999 | annotations_position.sort_by_key(|(_, ann)| { |
1000 | // Decreasing order. When annotations share the same length, prefer `Primary`. | |
1001 | (Reverse(ann.len()), ann.is_primary) | |
476ff2be | 1002 | }); |
5bcae85e | 1003 | |
476ff2be SL |
1004 | // Write the underlines. |
1005 | // | |
1006 | // After this we will have: | |
1007 | // | |
1008 | // 2 | fn foo() { | |
cc61c64b XL |
1009 | // | ____-_____^ |
1010 | // | | | |
1011 | // | something about `foo` | |
1012 | // 3 | | |
1013 | // 4 | } | |
1014 | // | _^ test | |
476ff2be SL |
1015 | for &(_, annotation) in &annotations_position { |
1016 | let (underline, style) = if annotation.is_primary { | |
1017 | ('^', Style::UnderlinePrimary) | |
5bcae85e | 1018 | } else { |
476ff2be SL |
1019 | ('-', Style::UnderlineSecondary) |
1020 | }; | |
1021 | for p in annotation.start_col..annotation.end_col { | |
e1599b0c XL |
1022 | buffer.putc( |
1023 | line_offset + 1, | |
e74abb32 | 1024 | (code_offset + p).saturating_sub(left), |
e1599b0c XL |
1025 | underline, |
1026 | style, | |
1027 | ); | |
c1a9b12d SL |
1028 | } |
1029 | } | |
60c5eb7d XL |
1030 | annotations_position |
1031 | .iter() | |
1032 | .filter_map(|&(_, annotation)| match annotation.annotation_type { | |
cc61c64b XL |
1033 | AnnotationType::MultilineStart(p) | AnnotationType::MultilineEnd(p) => { |
1034 | let style = if annotation.is_primary { | |
1035 | Style::LabelPrimary | |
1036 | } else { | |
1037 | Style::LabelSecondary | |
1038 | }; | |
1039 | Some((p, style)) | |
abe05a73 | 1040 | } |
60c5eb7d XL |
1041 | _ => None, |
1042 | }) | |
1043 | .collect::<Vec<_>>() | |
5bcae85e SL |
1044 | } |
1045 | ||
1046 | fn get_multispan_max_line_num(&mut self, msp: &MultiSpan) -> usize { | |
e74abb32 XL |
1047 | let sm = match self.sm { |
1048 | Some(ref sm) => sm, | |
1049 | None => return 0, | |
1050 | }; | |
1051 | ||
5bcae85e | 1052 | let mut max = 0; |
e74abb32 XL |
1053 | for primary_span in msp.primary_spans() { |
1054 | if !primary_span.is_dummy() { | |
1055 | let hi = sm.lookup_char_pos(primary_span.hi()); | |
1056 | max = (hi.line).max(max); | |
5bcae85e | 1057 | } |
e74abb32 XL |
1058 | } |
1059 | if !self.short_message { | |
1060 | for span_label in msp.span_labels() { | |
1061 | if !span_label.span.is_dummy() { | |
1062 | let hi = sm.lookup_char_pos(span_label.span.hi()); | |
1063 | max = (hi.line).max(max); | |
a7813a04 | 1064 | } |
7453a54e | 1065 | } |
c1a9b12d | 1066 | } |
e74abb32 | 1067 | |
5bcae85e | 1068 | max |
c1a9b12d SL |
1069 | } |
1070 | ||
8faf50e0 | 1071 | fn get_max_line_num(&mut self, span: &MultiSpan, children: &[SubDiagnostic]) -> usize { |
9e0c209e | 1072 | let primary = self.get_multispan_max_line_num(span); |
60c5eb7d XL |
1073 | children |
1074 | .iter() | |
e74abb32 XL |
1075 | .map(|sub| self.get_multispan_max_line_num(&sub.span)) |
1076 | .max() | |
1077 | .unwrap_or(0) | |
1078 | .max(primary) | |
c1a9b12d SL |
1079 | } |
1080 | ||
9fa01778 | 1081 | /// Adds a left margin to every line but the first, given a padding length and the label being |
32a655c1 | 1082 | /// displayed, keeping the provided highlighting. |
60c5eb7d XL |
1083 | fn msg_to_buffer( |
1084 | &self, | |
1085 | buffer: &mut StyledBuffer, | |
1086 | msg: &[(String, Style)], | |
1087 | padding: usize, | |
1088 | label: &str, | |
1089 | override_style: Option<Style>, | |
1090 | ) { | |
32a655c1 SL |
1091 | // The extra 5 ` ` is padding that's always needed to align to the `note: `: |
1092 | // | |
1093 | // error: message | |
1094 | // --> file.rs:13:20 | |
1095 | // | | |
1096 | // 13 | <CODE> | |
1097 | // | ^^^^ | |
1098 | // | | |
1099 | // = note: multiline | |
1100 | // message | |
1101 | // ++^^^----xx | |
1102 | // | | | | | |
1103 | // | | | magic `2` | |
1104 | // | | length of label | |
1105 | // | magic `3` | |
1106 | // `max_line_num_len` | |
8faf50e0 | 1107 | let padding = " ".repeat(padding + label.len() + 5); |
32a655c1 | 1108 | |
e74abb32 XL |
1109 | /// Returns `override` if it is present and `style` is `NoStyle` or `style` otherwise |
1110 | fn style_or_override(style: Style, override_: Option<Style>) -> Style { | |
1111 | match (style, override_) { | |
1112 | (Style::NoStyle, Some(override_)) => override_, | |
1113 | _ => style, | |
32a655c1 | 1114 | } |
32a655c1 SL |
1115 | } |
1116 | ||
1117 | let mut line_number = 0; | |
1118 | ||
1119 | // Provided the following diagnostic message: | |
1120 | // | |
1121 | // let msg = vec![ | |
1122 | // (" | |
1123 | // ("highlighted multiline\nstring to\nsee how it ", Style::NoStyle), | |
1124 | // ("looks", Style::Highlight), | |
1125 | // ("with\nvery ", Style::NoStyle), | |
1126 | // ("weird", Style::Highlight), | |
1127 | // (" formats\n", Style::NoStyle), | |
1128 | // ("see?", Style::Highlight), | |
1129 | // ]; | |
1130 | // | |
ff7c6d11 | 1131 | // the expected output on a note is (* surround the highlighted text) |
32a655c1 SL |
1132 | // |
1133 | // = note: highlighted multiline | |
1134 | // string to | |
1135 | // see how it *looks* with | |
1136 | // very *weird* formats | |
1137 | // see? | |
1138 | for &(ref text, ref style) in msg.iter() { | |
1139 | let lines = text.split('\n').collect::<Vec<_>>(); | |
1140 | if lines.len() > 1 { | |
1141 | for (i, line) in lines.iter().enumerate() { | |
1142 | if i != 0 { | |
1143 | line_number += 1; | |
1144 | buffer.append(line_number, &padding, Style::NoStyle); | |
1145 | } | |
1146 | buffer.append(line_number, line, style_or_override(*style, override_style)); | |
1147 | } | |
1148 | } else { | |
1149 | buffer.append(line_number, text, style_or_override(*style, override_style)); | |
1150 | } | |
1151 | } | |
1152 | } | |
1153 | ||
9fa01778 XL |
1154 | fn emit_message_default( |
1155 | &mut self, | |
1156 | msp: &MultiSpan, | |
1157 | msg: &[(String, Style)], | |
1158 | code: &Option<DiagnosticId>, | |
1159 | level: &Level, | |
1160 | max_line_num_len: usize, | |
1161 | is_secondary: bool, | |
1162 | ) -> io::Result<()> { | |
5bcae85e | 1163 | let mut buffer = StyledBuffer::new(); |
60c5eb7d | 1164 | let header_style = if is_secondary { Style::HeaderMsg } else { Style::MainHeaderMsg }; |
5bcae85e | 1165 | |
60c5eb7d XL |
1166 | if !msp.has_primary_spans() && !msp.has_span_labels() && is_secondary && !self.short_message |
1167 | { | |
5bcae85e SL |
1168 | // This is a secondary message with no span info |
1169 | for _ in 0..max_line_num_len { | |
1170 | buffer.prepend(0, " ", Style::NoStyle); | |
1171 | } | |
1172 | draw_note_separator(&mut buffer, 0, max_line_num_len + 1); | |
e1599b0c XL |
1173 | if *level != Level::FailureNote { |
1174 | let level_str = level.to_string(); | |
1175 | if !level_str.is_empty() { | |
1176 | buffer.append(0, &level_str, Style::MainHeaderMsg); | |
1177 | buffer.append(0, ": ", Style::NoStyle); | |
1178 | } | |
0531ce1d | 1179 | } |
32a655c1 | 1180 | self.msg_to_buffer(&mut buffer, msg, max_line_num_len, "note", None); |
c30ab7b3 | 1181 | } else { |
0531ce1d | 1182 | let level_str = level.to_string(); |
e1599b0c XL |
1183 | // The failure note level itself does not provide any useful diagnostic information |
1184 | if *level != Level::FailureNote && !level_str.is_empty() { | |
0531ce1d XL |
1185 | buffer.append(0, &level_str, Style::Level(level.clone())); |
1186 | } | |
abe05a73 XL |
1187 | // only render error codes, not lint codes |
1188 | if let Some(DiagnosticId::Error(ref code)) = *code { | |
1189 | buffer.append(0, "[", Style::Level(level.clone())); | |
1190 | buffer.append(0, &code, Style::Level(level.clone())); | |
1191 | buffer.append(0, "]", Style::Level(level.clone())); | |
5bcae85e | 1192 | } |
e1599b0c | 1193 | if *level != Level::FailureNote && !level_str.is_empty() { |
8faf50e0 | 1194 | buffer.append(0, ": ", header_style); |
0531ce1d | 1195 | } |
32a655c1 | 1196 | for &(ref text, _) in msg.iter() { |
8faf50e0 | 1197 | buffer.append(0, text, header_style); |
32a655c1 | 1198 | } |
5bcae85e SL |
1199 | } |
1200 | ||
dc9dc135 | 1201 | let mut annotated_files = FileWithAnnotatedLines::collect_annotations(msp, &self.sm); |
a7813a04 | 1202 | |
5bcae85e | 1203 | // Make sure our primary file comes first |
a1dfa0c6 | 1204 | let (primary_lo, sm) = if let (Some(sm), Some(ref primary_span)) = |
60c5eb7d XL |
1205 | (self.sm.as_ref(), msp.primary_span().as_ref()) |
1206 | { | |
8faf50e0 | 1207 | if !primary_span.is_dummy() { |
a1dfa0c6 | 1208 | (sm.lookup_char_pos(primary_span.lo()), sm) |
5bcae85e | 1209 | } else { |
abe05a73 | 1210 | emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; |
5bcae85e | 1211 | return Ok(()); |
c30ab7b3 SL |
1212 | } |
1213 | } else { | |
1214 | // If we don't have span information, emit and exit | |
abe05a73 | 1215 | emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; |
c30ab7b3 SL |
1216 | return Ok(()); |
1217 | }; | |
5bcae85e | 1218 | if let Ok(pos) = |
60c5eb7d XL |
1219 | annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name)) |
1220 | { | |
5bcae85e SL |
1221 | annotated_files.swap(0, pos); |
1222 | } | |
1223 | ||
1224 | // Print out the annotate source lines that correspond with the error | |
1225 | for annotated_file in annotated_files { | |
7cac9316 | 1226 | // we can't annotate anything if the source is unavailable. |
a1dfa0c6 | 1227 | if !sm.ensure_source_file_source_present(annotated_file.file.clone()) { |
7cac9316 XL |
1228 | continue; |
1229 | } | |
1230 | ||
5bcae85e SL |
1231 | // print out the span location and spacer before we print the annotated source |
1232 | // to do this, we need to know if this span will be primary | |
1233 | let is_primary = primary_lo.file.name == annotated_file.file.name; | |
1234 | if is_primary { | |
5bcae85e | 1235 | let loc = primary_lo.clone(); |
abe05a73 XL |
1236 | if !self.short_message { |
1237 | // remember where we are in the output buffer for easy reference | |
1238 | let buffer_msg_line_offset = buffer.num_lines(); | |
1239 | ||
1240 | buffer.prepend(buffer_msg_line_offset, "--> ", Style::LineNumber); | |
e1599b0c XL |
1241 | buffer.append( |
1242 | buffer_msg_line_offset, | |
1243 | &format!( | |
1244 | "{}:{}:{}", | |
1245 | loc.file.name, | |
1246 | sm.doctest_offset_line(&loc.file.name, loc.line), | |
1247 | loc.col.0 + 1, | |
1248 | ), | |
1249 | Style::LineAndColumn, | |
1250 | ); | |
abe05a73 XL |
1251 | for _ in 0..max_line_num_len { |
1252 | buffer.prepend(buffer_msg_line_offset, " ", Style::NoStyle); | |
1253 | } | |
1254 | } else { | |
e1599b0c XL |
1255 | buffer.prepend( |
1256 | 0, | |
1257 | &format!( | |
1258 | "{}:{}:{}: ", | |
1259 | loc.file.name, | |
1260 | sm.doctest_offset_line(&loc.file.name, loc.line), | |
1261 | loc.col.0 + 1, | |
1262 | ), | |
1263 | Style::LineAndColumn, | |
1264 | ); | |
5bcae85e | 1265 | } |
abe05a73 | 1266 | } else if !self.short_message { |
5bcae85e SL |
1267 | // remember where we are in the output buffer for easy reference |
1268 | let buffer_msg_line_offset = buffer.num_lines(); | |
1269 | ||
1270 | // Add spacing line | |
1271 | draw_col_separator(&mut buffer, buffer_msg_line_offset, max_line_num_len + 1); | |
1272 | ||
1273 | // Then, the secondary file indicator | |
1274 | buffer.prepend(buffer_msg_line_offset + 1, "::: ", Style::LineNumber); | |
2c00a5a8 XL |
1275 | let loc = if let Some(first_line) = annotated_file.lines.first() { |
1276 | let col = if let Some(first_annotation) = first_line.annotations.first() { | |
1277 | format!(":{}", first_annotation.start_col + 1) | |
1278 | } else { | |
b7449926 | 1279 | String::new() |
2c00a5a8 | 1280 | }; |
60c5eb7d XL |
1281 | format!( |
1282 | "{}:{}{}", | |
1283 | annotated_file.file.name, | |
1284 | sm.doctest_offset_line(&annotated_file.file.name, first_line.line_index), | |
1285 | col | |
1286 | ) | |
2c00a5a8 XL |
1287 | } else { |
1288 | annotated_file.file.name.to_string() | |
1289 | }; | |
60c5eb7d | 1290 | buffer.append(buffer_msg_line_offset + 1, &loc, Style::LineAndColumn); |
5bcae85e SL |
1291 | for _ in 0..max_line_num_len { |
1292 | buffer.prepend(buffer_msg_line_offset + 1, " ", Style::NoStyle); | |
c1a9b12d | 1293 | } |
a7813a04 | 1294 | } |
c1a9b12d | 1295 | |
abe05a73 XL |
1296 | if !self.short_message { |
1297 | // Put in the spacer between the location and annotated source | |
1298 | let buffer_msg_line_offset = buffer.num_lines(); | |
60c5eb7d XL |
1299 | draw_col_separator_no_space( |
1300 | &mut buffer, | |
1301 | buffer_msg_line_offset, | |
1302 | max_line_num_len + 1, | |
1303 | ); | |
5bcae85e | 1304 | |
abe05a73 | 1305 | // Contains the vertical lines' positions for active multiline annotations |
b7449926 | 1306 | let mut multilines = FxHashMap::default(); |
cc61c64b | 1307 | |
e1599b0c XL |
1308 | // Get the left-side margin to remove it |
1309 | let mut whitespace_margin = std::usize::MAX; | |
1310 | for line_idx in 0..annotated_file.lines.len() { | |
1311 | let file = annotated_file.file.clone(); | |
1312 | let line = &annotated_file.lines[line_idx]; | |
1313 | if let Some(source_string) = file.get_line(line.line_index - 1) { | |
60c5eb7d XL |
1314 | let leading_whitespace = |
1315 | source_string.chars().take_while(|c| c.is_whitespace()).count(); | |
e1599b0c | 1316 | if source_string.chars().any(|c| !c.is_whitespace()) { |
60c5eb7d | 1317 | whitespace_margin = min(whitespace_margin, leading_whitespace); |
e1599b0c XL |
1318 | } |
1319 | } | |
1320 | } | |
1321 | if whitespace_margin == std::usize::MAX { | |
1322 | whitespace_margin = 0; | |
1323 | } | |
1324 | ||
1325 | // Left-most column any visible span points at. | |
1326 | let mut span_left_margin = std::usize::MAX; | |
1327 | for line in &annotated_file.lines { | |
1328 | for ann in &line.annotations { | |
1329 | span_left_margin = min(span_left_margin, ann.start_col); | |
1330 | span_left_margin = min(span_left_margin, ann.end_col); | |
1331 | } | |
1332 | } | |
1333 | if span_left_margin == std::usize::MAX { | |
1334 | span_left_margin = 0; | |
1335 | } | |
1336 | ||
1337 | // Right-most column any visible span points at. | |
1338 | let mut span_right_margin = 0; | |
1339 | let mut label_right_margin = 0; | |
1340 | let mut max_line_len = 0; | |
1341 | for line in &annotated_file.lines { | |
60c5eb7d XL |
1342 | max_line_len = max( |
1343 | max_line_len, | |
1344 | annotated_file.file.get_line(line.line_index - 1).map_or(0, |s| s.len()), | |
1345 | ); | |
e1599b0c XL |
1346 | for ann in &line.annotations { |
1347 | span_right_margin = max(span_right_margin, ann.start_col); | |
1348 | span_right_margin = max(span_right_margin, ann.end_col); | |
1349 | // FIXME: account for labels not in the same line | |
e74abb32 | 1350 | let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1); |
e1599b0c XL |
1351 | label_right_margin = max(label_right_margin, ann.end_col + label_right); |
1352 | } | |
1353 | } | |
1354 | ||
1355 | let width_offset = 3 + max_line_num_len; | |
1356 | let code_offset = if annotated_file.multiline_depth == 0 { | |
1357 | width_offset | |
1358 | } else { | |
1359 | width_offset + annotated_file.multiline_depth + 1 | |
1360 | }; | |
1361 | ||
1362 | let column_width = if let Some(width) = self.terminal_width { | |
1363 | width.saturating_sub(code_offset) | |
1364 | } else if self.ui_testing { | |
1365 | 140 | |
1366 | } else { | |
1367 | term_size::dimensions() | |
1368 | .map(|(w, _)| w.saturating_sub(code_offset)) | |
1369 | .unwrap_or(std::usize::MAX) | |
1370 | }; | |
1371 | ||
1372 | let margin = Margin::new( | |
1373 | whitespace_margin, | |
1374 | span_left_margin, | |
1375 | span_right_margin, | |
1376 | label_right_margin, | |
1377 | column_width, | |
1378 | max_line_len, | |
1379 | ); | |
1380 | ||
abe05a73 XL |
1381 | // Next, output the annotate source for this file |
1382 | for line_idx in 0..annotated_file.lines.len() { | |
1383 | let previous_buffer_line = buffer.num_lines(); | |
cc61c64b | 1384 | |
e1599b0c XL |
1385 | let depths = self.render_source_line( |
1386 | &mut buffer, | |
1387 | annotated_file.file.clone(), | |
1388 | &annotated_file.lines[line_idx], | |
1389 | width_offset, | |
1390 | code_offset, | |
1391 | margin, | |
1392 | ); | |
cc61c64b | 1393 | |
b7449926 | 1394 | let mut to_add = FxHashMap::default(); |
5bcae85e | 1395 | |
abe05a73 XL |
1396 | for (depth, style) in depths { |
1397 | if multilines.get(&depth).is_some() { | |
1398 | multilines.remove(&depth); | |
1399 | } else { | |
1400 | to_add.insert(depth, style); | |
1401 | } | |
cc61c64b | 1402 | } |
cc61c64b | 1403 | |
abe05a73 XL |
1404 | // Set the multiline annotation vertical lines to the left of |
1405 | // the code in this line. | |
1406 | for (depth, style) in &multilines { | |
1407 | for line in previous_buffer_line..buffer.num_lines() { | |
60c5eb7d | 1408 | draw_multiline_line(&mut buffer, line, width_offset, *depth, *style); |
cc61c64b | 1409 | } |
abe05a73 XL |
1410 | } |
1411 | // check to see if we need to print out or elide lines that come between | |
1412 | // this annotated line and the next one. | |
1413 | if line_idx < (annotated_file.lines.len() - 1) { | |
60c5eb7d XL |
1414 | let line_idx_delta = annotated_file.lines[line_idx + 1].line_index |
1415 | - annotated_file.lines[line_idx].line_index; | |
abe05a73 XL |
1416 | if line_idx_delta > 2 { |
1417 | let last_buffer_line_num = buffer.num_lines(); | |
1418 | buffer.puts(last_buffer_line_num, 0, "...", Style::LineNumber); | |
1419 | ||
1420 | // Set the multiline annotation vertical lines on `...` bridging line. | |
1421 | for (depth, style) in &multilines { | |
60c5eb7d XL |
1422 | draw_multiline_line( |
1423 | &mut buffer, | |
1424 | last_buffer_line_num, | |
1425 | width_offset, | |
1426 | *depth, | |
1427 | *style, | |
1428 | ); | |
abe05a73 XL |
1429 | } |
1430 | } else if line_idx_delta == 2 { | |
60c5eb7d XL |
1431 | let unannotated_line = annotated_file |
1432 | .file | |
abe05a73 XL |
1433 | .get_line(annotated_file.lines[line_idx].line_index) |
1434 | .unwrap_or_else(|| Cow::from("")); | |
1435 | ||
1436 | let last_buffer_line_num = buffer.num_lines(); | |
1437 | ||
e1599b0c XL |
1438 | self.draw_line( |
1439 | &mut buffer, | |
1440 | &unannotated_line, | |
1441 | annotated_file.lines[line_idx + 1].line_index - 1, | |
1442 | last_buffer_line_num, | |
1443 | width_offset, | |
1444 | code_offset, | |
1445 | margin, | |
1446 | ); | |
abe05a73 XL |
1447 | |
1448 | for (depth, style) in &multilines { | |
e1599b0c XL |
1449 | draw_multiline_line( |
1450 | &mut buffer, | |
1451 | last_buffer_line_num, | |
1452 | width_offset, | |
1453 | *depth, | |
1454 | *style, | |
1455 | ); | |
abe05a73 | 1456 | } |
cc61c64b | 1457 | } |
c1a9b12d | 1458 | } |
cc61c64b | 1459 | |
abe05a73 XL |
1460 | multilines.extend(&to_add); |
1461 | } | |
7453a54e SL |
1462 | } |
1463 | } | |
5bcae85e | 1464 | |
5bcae85e | 1465 | // final step: take our styled buffer, render it, then output it |
abe05a73 | 1466 | emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; |
5bcae85e SL |
1467 | |
1468 | Ok(()) | |
1469 | } | |
ff7c6d11 | 1470 | |
9fa01778 XL |
1471 | fn emit_suggestion_default( |
1472 | &mut self, | |
1473 | suggestion: &CodeSuggestion, | |
1474 | level: &Level, | |
1475 | max_line_num_len: usize, | |
1476 | ) -> io::Result<()> { | |
e74abb32 XL |
1477 | let sm = match self.sm { |
1478 | Some(ref sm) => sm, | |
60c5eb7d | 1479 | None => return Ok(()), |
e74abb32 | 1480 | }; |
5bcae85e | 1481 | |
60c5eb7d XL |
1482 | // Render the replacements for each suggestion |
1483 | let suggestions = suggestion.splice_lines(&**sm); | |
1484 | ||
1485 | if suggestions.is_empty() { | |
1486 | // Suggestions coming from macros can have malformed spans. This is a heavy handed | |
1487 | // approach to avoid ICEs by ignoring the suggestion outright. | |
1488 | return Ok(()); | |
1489 | } | |
1490 | ||
e74abb32 XL |
1491 | let mut buffer = StyledBuffer::new(); |
1492 | ||
1493 | // Render the suggestion message | |
1494 | let level_str = level.to_string(); | |
1495 | if !level_str.is_empty() { | |
1496 | buffer.append(0, &level_str, Style::Level(level.clone())); | |
1497 | buffer.append(0, ": ", Style::HeaderMsg); | |
1498 | } | |
1499 | self.msg_to_buffer( | |
1500 | &mut buffer, | |
1501 | &[(suggestion.msg.to_owned(), Style::NoStyle)], | |
1502 | max_line_num_len, | |
1503 | "suggestion", | |
1504 | Some(Style::HeaderMsg), | |
1505 | ); | |
1506 | ||
e74abb32 XL |
1507 | let mut row_num = 2; |
1508 | let mut notice_capitalization = false; | |
1509 | for (complete, parts, only_capitalization) in suggestions.iter().take(MAX_SUGGESTIONS) { | |
1510 | notice_capitalization |= only_capitalization; | |
1511 | // Only show underline if the suggestion spans a single line and doesn't cover the | |
1512 | // entirety of the code output. If you have multiple replacements in the same line | |
1513 | // of code, show the underline. | |
60c5eb7d | 1514 | let show_underline = !(parts.len() == 1 && parts[0].snippet.trim() == complete.trim()) |
e74abb32 XL |
1515 | && complete.lines().count() == 1; |
1516 | ||
60c5eb7d XL |
1517 | let lines = sm |
1518 | .span_to_lines(parts[0].span) | |
1519 | .expect("span_to_lines failed when emitting suggestion"); | |
e74abb32 XL |
1520 | |
1521 | assert!(!lines.lines.is_empty()); | |
1522 | ||
1523 | let line_start = sm.lookup_char_pos(parts[0].span.lo()).line; | |
1524 | draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1); | |
1525 | let mut line_pos = 0; | |
1526 | let mut lines = complete.lines(); | |
1527 | for line in lines.by_ref().take(MAX_HIGHLIGHT_LINES) { | |
1528 | // Print the span column to avoid confusion | |
60c5eb7d XL |
1529 | buffer.puts( |
1530 | row_num, | |
1531 | 0, | |
1532 | &self.maybe_anonymized(line_start + line_pos), | |
1533 | Style::LineNumber, | |
1534 | ); | |
e74abb32 XL |
1535 | // print the suggestion |
1536 | draw_col_separator(&mut buffer, row_num, max_line_num_len + 1); | |
1537 | buffer.append(row_num, line, Style::NoStyle); | |
1538 | line_pos += 1; | |
1539 | row_num += 1; | |
0531ce1d | 1540 | } |
94b46f34 | 1541 | |
e74abb32 XL |
1542 | // This offset and the ones below need to be signed to account for replacement code |
1543 | // that is shorter than the original code. | |
1544 | let mut offset: isize = 0; | |
1545 | // Only show an underline in the suggestions if the suggestion is not the | |
1546 | // entirety of the code being shown and the displayed code is not multiline. | |
1547 | if show_underline { | |
1548 | draw_col_separator(&mut buffer, row_num, max_line_num_len + 1); | |
1549 | for part in parts { | |
1550 | let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display; | |
1551 | let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display; | |
1552 | ||
1553 | // Do not underline the leading... | |
60c5eb7d | 1554 | let start = part.snippet.len().saturating_sub(part.snippet.trim_start().len()); |
e74abb32 XL |
1555 | // ...or trailing spaces. Account for substitutions containing unicode |
1556 | // characters. | |
60c5eb7d XL |
1557 | let sub_len: usize = part |
1558 | .snippet | |
1559 | .trim() | |
1560 | .chars() | |
e74abb32 XL |
1561 | .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1)) |
1562 | .sum(); | |
1563 | ||
1564 | let underline_start = (span_start_pos + start) as isize + offset; | |
1565 | let underline_end = (span_start_pos + start + sub_len) as isize + offset; | |
1566 | for p in underline_start..underline_end { | |
60c5eb7d XL |
1567 | buffer.putc( |
1568 | row_num, | |
1569 | max_line_num_len + 3 + p as usize, | |
1570 | '^', | |
1571 | Style::UnderlinePrimary, | |
1572 | ); | |
e74abb32 XL |
1573 | } |
1574 | // underline removals too | |
1575 | if underline_start == underline_end { | |
60c5eb7d XL |
1576 | for p in underline_start - 1..underline_start + 1 { |
1577 | buffer.putc( | |
1578 | row_num, | |
1579 | max_line_num_len + 3 + p as usize, | |
1580 | '-', | |
1581 | Style::UnderlineSecondary, | |
1582 | ); | |
94b46f34 | 1583 | } |
041b39d2 | 1584 | } |
5bcae85e | 1585 | |
e74abb32 | 1586 | // length of the code after substitution |
60c5eb7d XL |
1587 | let full_sub_len = part |
1588 | .snippet | |
1589 | .chars() | |
e74abb32 XL |
1590 | .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1)) |
1591 | .sum::<usize>() as isize; | |
1592 | ||
1593 | // length of the code to be substituted | |
1594 | let snippet_len = span_end_pos as isize - span_start_pos as isize; | |
1595 | // For multiple substitutions, use the position *after* the previous | |
1596 | // substitutions have happened. | |
1597 | offset += full_sub_len - snippet_len; | |
7cac9316 | 1598 | } |
e74abb32 | 1599 | row_num += 1; |
7cac9316 | 1600 | } |
e74abb32 XL |
1601 | |
1602 | // if we elided some lines, add an ellipsis | |
1603 | if lines.next().is_some() { | |
1604 | buffer.puts(row_num, max_line_num_len - 1, "...", Style::LineNumber); | |
1605 | } else if !show_underline { | |
1606 | draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1); | |
1607 | row_num += 1; | |
7453a54e | 1608 | } |
c1a9b12d | 1609 | } |
e74abb32 XL |
1610 | if suggestions.len() > MAX_SUGGESTIONS { |
1611 | let others = suggestions.len() - MAX_SUGGESTIONS; | |
60c5eb7d | 1612 | let msg = format!("and {} other candidate{}", others, pluralize!(others)); |
e74abb32 XL |
1613 | buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle); |
1614 | } else if notice_capitalization { | |
1615 | let msg = "notice the capitalization difference"; | |
1616 | buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle); | |
1617 | } | |
1618 | emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; | |
7453a54e | 1619 | Ok(()) |
c1a9b12d | 1620 | } |
ff7c6d11 | 1621 | |
e74abb32 XL |
1622 | fn emit_messages_default( |
1623 | &mut self, | |
1624 | level: &Level, | |
1625 | message: &[(String, Style)], | |
1626 | code: &Option<DiagnosticId>, | |
1627 | span: &MultiSpan, | |
1628 | children: &[SubDiagnostic], | |
1629 | suggestions: &[CodeSuggestion], | |
1630 | ) { | |
0531ce1d XL |
1631 | let max_line_num_len = if self.ui_testing { |
1632 | ANONYMIZED_LINE_NUM.len() | |
1633 | } else { | |
1634 | self.get_max_line_num(span, children).to_string().len() | |
1635 | }; | |
5bcae85e | 1636 | |
e74abb32 | 1637 | match self.emit_message_default(span, message, code, level, max_line_num_len, false) { |
5bcae85e | 1638 | Ok(()) => { |
60c5eb7d XL |
1639 | if !children.is_empty() |
1640 | || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden) | |
1641 | { | |
5bcae85e | 1642 | let mut buffer = StyledBuffer::new(); |
abe05a73 XL |
1643 | if !self.short_message { |
1644 | draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1); | |
1645 | } | |
60c5eb7d XL |
1646 | match emit_to_destination( |
1647 | &buffer.render(), | |
1648 | level, | |
1649 | &mut self.dst, | |
1650 | self.short_message, | |
1651 | ) { | |
5bcae85e | 1652 | Ok(()) => (), |
60c5eb7d | 1653 | Err(e) => panic!("failed to emit error: {}", e), |
5bcae85e SL |
1654 | } |
1655 | } | |
abe05a73 XL |
1656 | if !self.short_message { |
1657 | for child in children { | |
1658 | let span = child.render_span.as_ref().unwrap_or(&child.span); | |
9fa01778 XL |
1659 | match self.emit_message_default( |
1660 | &span, | |
1661 | &child.styled_message(), | |
1662 | &None, | |
1663 | &child.level, | |
1664 | max_line_num_len, | |
1665 | true, | |
1666 | ) { | |
abe05a73 | 1667 | Err(e) => panic!("failed to emit error: {}", e), |
60c5eb7d | 1668 | _ => (), |
abe05a73 XL |
1669 | } |
1670 | } | |
1671 | for sugg in suggestions { | |
9fa01778 XL |
1672 | if sugg.style == SuggestionStyle::CompletelyHidden { |
1673 | // do not display this suggestion, it is meant only for tools | |
1674 | } else if sugg.style == SuggestionStyle::HideCodeAlways { | |
1675 | match self.emit_message_default( | |
1676 | &MultiSpan::new(), | |
1677 | &[(sugg.msg.to_owned(), Style::HeaderMsg)], | |
1678 | &None, | |
1679 | &Level::Help, | |
1680 | max_line_num_len, | |
1681 | true, | |
1682 | ) { | |
1683 | Err(e) => panic!("failed to emit error: {}", e), | |
60c5eb7d | 1684 | _ => (), |
9fa01778 XL |
1685 | } |
1686 | } else { | |
60c5eb7d XL |
1687 | match self.emit_suggestion_default(sugg, &Level::Help, max_line_num_len) |
1688 | { | |
9fa01778 | 1689 | Err(e) => panic!("failed to emit error: {}", e), |
60c5eb7d | 1690 | _ => (), |
9fa01778 | 1691 | } |
5bcae85e SL |
1692 | } |
1693 | } | |
1694 | } | |
1695 | } | |
c30ab7b3 | 1696 | Err(e) => panic!("failed to emit error: {}", e), |
5bcae85e | 1697 | } |
0531ce1d XL |
1698 | |
1699 | let mut dst = self.dst.writable(); | |
dc9dc135 | 1700 | match writeln!(dst) { |
5bcae85e | 1701 | Err(e) => panic!("failed to emit error: {}", e), |
60c5eb7d XL |
1702 | _ => match dst.flush() { |
1703 | Err(e) => panic!("failed to emit error: {}", e), | |
1704 | _ => (), | |
1705 | }, | |
c1a9b12d | 1706 | } |
c1a9b12d | 1707 | } |
1a4d82fc | 1708 | } |
970d7e83 | 1709 | |
dc9dc135 XL |
1710 | impl FileWithAnnotatedLines { |
1711 | /// Preprocess all the annotations so that they are grouped by file and by line number | |
1712 | /// This helps us quickly iterate over the whole message (including secondary file spans) | |
1713 | pub fn collect_annotations( | |
1714 | msp: &MultiSpan, | |
60c5eb7d | 1715 | source_map: &Option<Lrc<SourceMap>>, |
dc9dc135 | 1716 | ) -> Vec<FileWithAnnotatedLines> { |
60c5eb7d XL |
1717 | fn add_annotation_to_file( |
1718 | file_vec: &mut Vec<FileWithAnnotatedLines>, | |
1719 | file: Lrc<SourceFile>, | |
1720 | line_index: usize, | |
1721 | ann: Annotation, | |
1722 | ) { | |
dc9dc135 XL |
1723 | for slot in file_vec.iter_mut() { |
1724 | // Look through each of our files for the one we're adding to | |
1725 | if slot.file.name == file.name { | |
1726 | // See if we already have a line for it | |
1727 | for line_slot in &mut slot.lines { | |
1728 | if line_slot.line_index == line_index { | |
1729 | line_slot.annotations.push(ann); | |
1730 | return; | |
1731 | } | |
1732 | } | |
1733 | // We don't have a line yet, create one | |
60c5eb7d | 1734 | slot.lines.push(Line { line_index, annotations: vec![ann] }); |
dc9dc135 XL |
1735 | slot.lines.sort(); |
1736 | return; | |
1737 | } | |
1738 | } | |
1739 | // This is the first time we're seeing the file | |
1740 | file_vec.push(FileWithAnnotatedLines { | |
1741 | file, | |
60c5eb7d | 1742 | lines: vec![Line { line_index, annotations: vec![ann] }], |
dc9dc135 XL |
1743 | multiline_depth: 0, |
1744 | }); | |
1745 | } | |
1746 | ||
1747 | let mut output = vec![]; | |
1748 | let mut multiline_annotations = vec![]; | |
1749 | ||
1750 | if let Some(ref sm) = source_map { | |
1751 | for span_label in msp.span_labels() { | |
1752 | if span_label.span.is_dummy() { | |
1753 | continue; | |
1754 | } | |
1755 | ||
1756 | let lo = sm.lookup_char_pos(span_label.span.lo()); | |
1757 | let mut hi = sm.lookup_char_pos(span_label.span.hi()); | |
1758 | ||
1759 | // Watch out for "empty spans". If we get a span like 6..6, we | |
1760 | // want to just display a `^` at 6, so convert that to | |
1761 | // 6..7. This is degenerate input, but it's best to degrade | |
1762 | // gracefully -- and the parser likes to supply a span like | |
1763 | // that for EOF, in particular. | |
1764 | ||
1765 | if lo.col_display == hi.col_display && lo.line == hi.line { | |
1766 | hi.col_display += 1; | |
1767 | } | |
1768 | ||
e74abb32 | 1769 | if lo.line != hi.line { |
dc9dc135 XL |
1770 | let ml = MultilineAnnotation { |
1771 | depth: 1, | |
1772 | line_start: lo.line, | |
1773 | line_end: hi.line, | |
1774 | start_col: lo.col_display, | |
1775 | end_col: hi.col_display, | |
1776 | is_primary: span_label.is_primary, | |
e74abb32 | 1777 | label: span_label.label, |
dc9dc135 XL |
1778 | overlaps_exactly: false, |
1779 | }; | |
e74abb32 | 1780 | multiline_annotations.push((lo.file, ml)); |
dc9dc135 | 1781 | } else { |
e74abb32 XL |
1782 | let ann = Annotation { |
1783 | start_col: lo.col_display, | |
1784 | end_col: hi.col_display, | |
1785 | is_primary: span_label.is_primary, | |
1786 | label: span_label.label, | |
1787 | annotation_type: AnnotationType::Singleline, | |
1788 | }; | |
dc9dc135 | 1789 | add_annotation_to_file(&mut output, lo.file, lo.line, ann); |
e74abb32 | 1790 | }; |
dc9dc135 XL |
1791 | } |
1792 | } | |
1793 | ||
1794 | // Find overlapping multiline annotations, put them at different depths | |
1795 | multiline_annotations.sort_by_key(|&(_, ref ml)| (ml.line_start, ml.line_end)); | |
e74abb32 XL |
1796 | for (_, ann) in multiline_annotations.clone() { |
1797 | for (_, a) in multiline_annotations.iter_mut() { | |
dc9dc135 XL |
1798 | // Move all other multiline annotations overlapping with this one |
1799 | // one level to the right. | |
60c5eb7d XL |
1800 | if !(ann.same_span(a)) |
1801 | && num_overlap(ann.line_start, ann.line_end, a.line_start, a.line_end, true) | |
dc9dc135 XL |
1802 | { |
1803 | a.increase_depth(); | |
1804 | } else if ann.same_span(a) && &ann != a { | |
1805 | a.overlaps_exactly = true; | |
1806 | } else { | |
1807 | break; | |
1808 | } | |
1809 | } | |
1810 | } | |
1811 | ||
60c5eb7d | 1812 | let mut max_depth = 0; // max overlapping multiline spans |
dc9dc135 | 1813 | for (file, ann) in multiline_annotations { |
e74abb32 | 1814 | max_depth = max(max_depth, ann.depth); |
dc9dc135 XL |
1815 | let mut end_ann = ann.as_end(); |
1816 | if !ann.overlaps_exactly { | |
1817 | // avoid output like | |
1818 | // | |
1819 | // | foo( | |
1820 | // | _____^ | |
1821 | // | |_____| | |
1822 | // | || bar, | |
1823 | // | || ); | |
1824 | // | || ^ | |
1825 | // | ||______| | |
1826 | // | |______foo | |
1827 | // | baz | |
1828 | // | |
1829 | // and instead get | |
1830 | // | |
1831 | // | foo( | |
1832 | // | _____^ | |
1833 | // | | bar, | |
1834 | // | | ); | |
1835 | // | | ^ | |
1836 | // | | | | |
1837 | // | |______foo | |
1838 | // | baz | |
1839 | add_annotation_to_file(&mut output, file.clone(), ann.line_start, ann.as_start()); | |
1840 | // 4 is the minimum vertical length of a multiline span when presented: two lines | |
1841 | // of code and two lines of underline. This is not true for the special case where | |
1842 | // the beginning doesn't have an underline, but the current logic seems to be | |
1843 | // working correctly. | |
1844 | let middle = min(ann.line_start + 4, ann.line_end); | |
1845 | for line in ann.line_start + 1..middle { | |
1846 | // Every `|` that joins the beginning of the span (`___^`) to the end (`|__^`). | |
1847 | add_annotation_to_file(&mut output, file.clone(), line, ann.as_line()); | |
1848 | } | |
e74abb32 XL |
1849 | let line_end = ann.line_end - 1; |
1850 | if middle < line_end { | |
1851 | add_annotation_to_file(&mut output, file.clone(), line_end, ann.as_line()); | |
dc9dc135 XL |
1852 | } |
1853 | } else { | |
1854 | end_ann.annotation_type = AnnotationType::Singleline; | |
1855 | } | |
1856 | add_annotation_to_file(&mut output, file, ann.line_end, end_ann); | |
1857 | } | |
1858 | for file_vec in output.iter_mut() { | |
1859 | file_vec.multiline_depth = max_depth; | |
1860 | } | |
1861 | output | |
1862 | } | |
1863 | } | |
1864 | ||
5bcae85e SL |
1865 | fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) { |
1866 | buffer.puts(line, col, "| ", Style::LineNumber); | |
7453a54e SL |
1867 | } |
1868 | ||
5bcae85e | 1869 | fn draw_col_separator_no_space(buffer: &mut StyledBuffer, line: usize, col: usize) { |
476ff2be SL |
1870 | draw_col_separator_no_space_with_style(buffer, line, col, Style::LineNumber); |
1871 | } | |
1872 | ||
60c5eb7d XL |
1873 | fn draw_col_separator_no_space_with_style( |
1874 | buffer: &mut StyledBuffer, | |
1875 | line: usize, | |
1876 | col: usize, | |
1877 | style: Style, | |
1878 | ) { | |
476ff2be SL |
1879 | buffer.putc(line, col, '|', style); |
1880 | } | |
1881 | ||
60c5eb7d XL |
1882 | fn draw_range( |
1883 | buffer: &mut StyledBuffer, | |
1884 | symbol: char, | |
1885 | line: usize, | |
1886 | col_from: usize, | |
1887 | col_to: usize, | |
1888 | style: Style, | |
1889 | ) { | |
476ff2be SL |
1890 | for col in col_from..col_to { |
1891 | buffer.putc(line, col, symbol, style); | |
1892 | } | |
5bcae85e SL |
1893 | } |
1894 | ||
1895 | fn draw_note_separator(buffer: &mut StyledBuffer, line: usize, col: usize) { | |
1896 | buffer.puts(line, col, "= ", Style::LineNumber); | |
1897 | } | |
1898 | ||
60c5eb7d XL |
1899 | fn draw_multiline_line( |
1900 | buffer: &mut StyledBuffer, | |
1901 | line: usize, | |
1902 | offset: usize, | |
1903 | depth: usize, | |
1904 | style: Style, | |
1905 | ) { | |
cc61c64b XL |
1906 | buffer.putc(line, offset + depth - 1, '|', style); |
1907 | } | |
1908 | ||
60c5eb7d XL |
1909 | fn num_overlap( |
1910 | a_start: usize, | |
1911 | a_end: usize, | |
1912 | b_start: usize, | |
1913 | b_end: usize, | |
1914 | inclusive: bool, | |
1915 | ) -> bool { | |
1916 | let extra = if inclusive { 1 } else { 0 }; | |
1917 | (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start) | |
476ff2be | 1918 | } |
8bb4bdeb XL |
1919 | fn overlaps(a1: &Annotation, a2: &Annotation, padding: usize) -> bool { |
1920 | num_overlap(a1.start_col, a1.end_col + padding, a2.start_col, a2.end_col, false) | |
5bcae85e SL |
1921 | } |
1922 | ||
60c5eb7d XL |
1923 | fn emit_to_destination( |
1924 | rendered_buffer: &[Vec<StyledString>], | |
1925 | lvl: &Level, | |
1926 | dst: &mut Destination, | |
1927 | short_message: bool, | |
1928 | ) -> io::Result<()> { | |
9fa01778 | 1929 | use crate::lock; |
9e0c209e | 1930 | |
0531ce1d XL |
1931 | let mut dst = dst.writable(); |
1932 | ||
9e0c209e SL |
1933 | // In order to prevent error message interleaving, where multiple error lines get intermixed |
1934 | // when multiple compiler processes error simultaneously, we emit errors with additional | |
1935 | // steps. | |
1936 | // | |
1937 | // On Unix systems, we write into a buffered terminal rather than directly to a terminal. When | |
1938 | // the .flush() is called we take the buffer created from the buffered writes and write it at | |
1939 | // one shot. Because the Unix systems use ANSI for the colors, which is a text-based styling | |
1940 | // scheme, this buffered approach works and maintains the styling. | |
1941 | // | |
1942 | // On Windows, styling happens through calls to a terminal API. This prevents us from using the | |
1943 | // same buffering approach. Instead, we use a global Windows mutex, which we acquire long | |
1944 | // enough to output the full error message, then we release. | |
1945 | let _buffer_lock = lock::acquire_global_lock("rustc_errors"); | |
0531ce1d | 1946 | for (pos, line) in rendered_buffer.iter().enumerate() { |
5bcae85e SL |
1947 | for part in line { |
1948 | dst.apply_style(lvl.clone(), part.style)?; | |
1949 | write!(dst, "{}", part.text)?; | |
0531ce1d | 1950 | dst.reset()?; |
a7813a04 | 1951 | } |
0531ce1d | 1952 | if !short_message && (!lvl.is_failure_note() || pos != rendered_buffer.len() - 1) { |
dc9dc135 | 1953 | writeln!(dst)?; |
abe05a73 | 1954 | } |
9cc50fc6 | 1955 | } |
9e0c209e | 1956 | dst.flush()?; |
9cc50fc6 SL |
1957 | Ok(()) |
1958 | } | |
1959 | ||
5bcae85e | 1960 | pub enum Destination { |
0531ce1d XL |
1961 | Terminal(StandardStream), |
1962 | Buffered(BufferWriter), | |
48663c56 XL |
1963 | // The bool denotes whether we should be emitting ansi color codes or not |
1964 | Raw(Box<(dyn Write + Send)>, bool), | |
9cc50fc6 SL |
1965 | } |
1966 | ||
0531ce1d XL |
1967 | pub enum WritableDst<'a> { |
1968 | Terminal(&'a mut StandardStream), | |
1969 | Buffered(&'a mut BufferWriter, Buffer), | |
48663c56 XL |
1970 | Raw(&'a mut (dyn Write + Send)), |
1971 | ColoredRaw(Ansi<&'a mut (dyn Write + Send)>), | |
9e0c209e SL |
1972 | } |
1973 | ||
9cc50fc6 | 1974 | impl Destination { |
0531ce1d XL |
1975 | fn from_stderr(color: ColorConfig) -> Destination { |
1976 | let choice = color.to_color_choice(); | |
1977 | // On Windows we'll be performing global synchronization on the entire | |
1978 | // system for emitting rustc errors, so there's no need to buffer | |
1979 | // anything. | |
1980 | // | |
1981 | // On non-Windows we rely on the atomicity of `write` to ensure errors | |
1982 | // don't get all jumbled up. | |
1983 | if cfg!(windows) { | |
1984 | Terminal(StandardStream::stderr(choice)) | |
1985 | } else { | |
1986 | Buffered(BufferWriter::stderr(choice)) | |
9e0c209e SL |
1987 | } |
1988 | } | |
1989 | ||
416331ca | 1990 | fn writable(&mut self) -> WritableDst<'_> { |
0531ce1d XL |
1991 | match *self { |
1992 | Destination::Terminal(ref mut t) => WritableDst::Terminal(t), | |
1993 | Destination::Buffered(ref mut t) => { | |
1994 | let buf = t.buffer(); | |
1995 | WritableDst::Buffered(t, buf) | |
1996 | } | |
48663c56 XL |
1997 | Destination::Raw(ref mut t, false) => WritableDst::Raw(t), |
1998 | Destination::Raw(ref mut t, true) => WritableDst::ColoredRaw(Ansi::new(t)), | |
9cc50fc6 SL |
1999 | } |
2000 | } | |
0531ce1d | 2001 | } |
9cc50fc6 | 2002 | |
0531ce1d | 2003 | impl<'a> WritableDst<'a> { |
c30ab7b3 | 2004 | fn apply_style(&mut self, lvl: Level, style: Style) -> io::Result<()> { |
0531ce1d | 2005 | let mut spec = ColorSpec::new(); |
a7813a04 | 2006 | match style { |
041b39d2 | 2007 | Style::LineAndColumn => {} |
a7813a04 | 2008 | Style::LineNumber => { |
0531ce1d XL |
2009 | spec.set_bold(true); |
2010 | spec.set_intense(true); | |
9e0c209e | 2011 | if cfg!(windows) { |
0531ce1d | 2012 | spec.set_fg(Some(Color::Cyan)); |
9e0c209e | 2013 | } else { |
0531ce1d | 2014 | spec.set_fg(Some(Color::Blue)); |
9e0c209e | 2015 | } |
a7813a04 | 2016 | } |
5bcae85e | 2017 | Style::Quotation => {} |
dc9dc135 | 2018 | Style::MainHeaderMsg => { |
0531ce1d | 2019 | spec.set_bold(true); |
9e0c209e | 2020 | if cfg!(windows) { |
60c5eb7d | 2021 | spec.set_intense(true).set_fg(Some(Color::White)); |
9e0c209e | 2022 | } |
a7813a04 XL |
2023 | } |
2024 | Style::UnderlinePrimary | Style::LabelPrimary => { | |
0531ce1d XL |
2025 | spec = lvl.color(); |
2026 | spec.set_bold(true); | |
a7813a04 | 2027 | } |
60c5eb7d XL |
2028 | Style::UnderlineSecondary | Style::LabelSecondary => { |
2029 | spec.set_bold(true).set_intense(true); | |
9e0c209e | 2030 | if cfg!(windows) { |
0531ce1d | 2031 | spec.set_fg(Some(Color::Cyan)); |
9e0c209e | 2032 | } else { |
0531ce1d | 2033 | spec.set_fg(Some(Color::Blue)); |
9e0c209e | 2034 | } |
a7813a04 | 2035 | } |
60c5eb7d | 2036 | Style::HeaderMsg | Style::NoStyle => {} |
0531ce1d XL |
2037 | Style::Level(lvl) => { |
2038 | spec = lvl.color(); | |
2039 | spec.set_bold(true); | |
2040 | } | |
2041 | Style::Highlight => { | |
2042 | spec.set_bold(true); | |
a7813a04 XL |
2043 | } |
2044 | } | |
0531ce1d | 2045 | self.set_color(&spec) |
a7813a04 XL |
2046 | } |
2047 | ||
0531ce1d | 2048 | fn set_color(&mut self, color: &ColorSpec) -> io::Result<()> { |
a7813a04 | 2049 | match *self { |
0531ce1d XL |
2050 | WritableDst::Terminal(ref mut t) => t.set_color(color), |
2051 | WritableDst::Buffered(_, ref mut t) => t.set_color(color), | |
48663c56 | 2052 | WritableDst::ColoredRaw(ref mut t) => t.set_color(color), |
60c5eb7d | 2053 | WritableDst::Raw(_) => Ok(()), |
a7813a04 | 2054 | } |
a7813a04 XL |
2055 | } |
2056 | ||
0531ce1d | 2057 | fn reset(&mut self) -> io::Result<()> { |
a7813a04 | 2058 | match *self { |
0531ce1d XL |
2059 | WritableDst::Terminal(ref mut t) => t.reset(), |
2060 | WritableDst::Buffered(_, ref mut t) => t.reset(), | |
48663c56 | 2061 | WritableDst::ColoredRaw(ref mut t) => t.reset(), |
0531ce1d | 2062 | WritableDst::Raw(_) => Ok(()), |
a7813a04 | 2063 | } |
a7813a04 | 2064 | } |
9cc50fc6 SL |
2065 | } |
2066 | ||
0531ce1d | 2067 | impl<'a> Write for WritableDst<'a> { |
c34b1796 AL |
2068 | fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { |
2069 | match *self { | |
0531ce1d XL |
2070 | WritableDst::Terminal(ref mut t) => t.write(bytes), |
2071 | WritableDst::Buffered(_, ref mut buf) => buf.write(bytes), | |
2072 | WritableDst::Raw(ref mut w) => w.write(bytes), | |
48663c56 | 2073 | WritableDst::ColoredRaw(ref mut t) => t.write(bytes), |
c34b1796 AL |
2074 | } |
2075 | } | |
0531ce1d | 2076 | |
c34b1796 | 2077 | fn flush(&mut self) -> io::Result<()> { |
1a4d82fc | 2078 | match *self { |
0531ce1d XL |
2079 | WritableDst::Terminal(ref mut t) => t.flush(), |
2080 | WritableDst::Buffered(_, ref mut buf) => buf.flush(), | |
2081 | WritableDst::Raw(ref mut w) => w.flush(), | |
48663c56 | 2082 | WritableDst::ColoredRaw(ref mut w) => w.flush(), |
0531ce1d XL |
2083 | } |
2084 | } | |
2085 | } | |
2086 | ||
2087 | impl<'a> Drop for WritableDst<'a> { | |
2088 | fn drop(&mut self) { | |
2089 | match *self { | |
2090 | WritableDst::Buffered(ref mut dst, ref mut buf) => { | |
2091 | drop(dst.print(buf)); | |
2092 | } | |
2093 | _ => {} | |
1a4d82fc JJ |
2094 | } |
2095 | } | |
9e0c209e | 2096 | } |
e74abb32 XL |
2097 | |
2098 | /// Whether the original and suggested code are visually similar enough to warrant extra wording. | |
60c5eb7d | 2099 | pub fn is_case_difference(sm: &SourceMap, suggested: &str, sp: Span) -> bool { |
e74abb32 | 2100 | // FIXME: this should probably be extended to also account for `FO0` → `FOO` and unicode. |
60c5eb7d XL |
2101 | let found = match sm.span_to_snippet(sp) { |
2102 | Ok(snippet) => snippet, | |
2103 | Err(e) => { | |
2104 | warn!("Invalid span {:?}. Err={:?}", sp, e); | |
2105 | return false; | |
2106 | } | |
2107 | }; | |
e74abb32 XL |
2108 | let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z']; |
2109 | // All the chars that differ in capitalization are confusable (above): | |
60c5eb7d XL |
2110 | let confusable = found |
2111 | .chars() | |
2112 | .zip(suggested.chars()) | |
2113 | .filter(|(f, s)| f != s) | |
2114 | .all(|(f, s)| (ascii_confusables.contains(&f) || ascii_confusables.contains(&s))); | |
e74abb32 XL |
2115 | confusable && found.to_lowercase() == suggested.to_lowercase() |
2116 | // FIXME: We sometimes suggest the same thing we already have, which is a | |
2117 | // bug, but be defensive against that here. | |
2118 | && found != suggested | |
2119 | } |