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