diff options
| author | Esteban Küber <esteban@kuber.com.ar> | 2024-06-17 15:14:07 +0000 |
|---|---|---|
| committer | León Orell Valerian Liehr <me@fmease.dev> | 2024-11-10 23:57:18 +0100 |
| commit | 1d78004575bea3b958b199d50a8491ae3fd65679 (patch) | |
| tree | 61e6fcb1b058f58c6e8bb1c4b39661deb7d65150 /compiler | |
| parent | f61306d47bc98af8bb9d15f1adf6086785590a8c (diff) | |
| download | rust-1d78004575bea3b958b199d50a8491ae3fd65679.tar.gz rust-1d78004575bea3b958b199d50a8491ae3fd65679.zip | |
Add Unicode block-drawing compiler output support
Add nightly-only theming support to rustc output using Unicode box drawing characters instead of ASCII-art to draw the terminal UI: After: ``` error: foo ╭▸ test.rs:3:3 │ 3 │ X0 Y0 Z0 │ ┌───╿──│──┘ │ ┌│───│──┘ │ ┏││━━━┙ │ ┃││ 4 │ ┃││ X1 Y1 Z1 5 │ ┃││ X2 Y2 Z2 │ ┃│└────╿──│──┘ `Z` label │ ┃└─────│──┤ │ ┗━━━━━━┥ `Y` is a good letter too │ `X` is a good letter ╰╴ note: bar ╭▸ test.rs:4:3 │ 4 │ ┏ X1 Y1 Z1 5 │ ┃ X2 Y2 Z2 6 │ ┃ X3 Y3 Z3 │ ┗━━━━━━━━━━┛ ├ note: bar ╰ note: baz note: qux ╭▸ test.rs:4:3 │ 4 │ X1 Y1 Z1 ╰╴ ━━━━━━━━ ``` Before: ``` error: foo --> test.rs:3:3 | 3 | X0 Y0 Z0 | ___^__-__- | |___|__| | ||___| | ||| 4 | ||| X1 Y1 Z1 5 | ||| X2 Y2 Z2 | |||____^__-__- `Z` label | ||_____|__| | |______| `Y` is a good letter too | `X` is a good letter | note: bar --> test.rs:4:3 | 4 | / X1 Y1 Z1 5 | | X2 Y2 Z2 6 | | X3 Y3 Z3 | |__________^ = note: bar = note: baz note: qux --> test.rs:4:3 | 4 | X1 Y1 Z1 | ^^^^^^^^ ```
Diffstat (limited to 'compiler')
| -rw-r--r-- | compiler/rustc_errors/src/emitter.rs | 703 | ||||
| -rw-r--r-- | compiler/rustc_errors/src/json.rs | 7 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/tests.rs | 1231 | ||||
| -rw-r--r-- | compiler/rustc_session/src/config.rs | 9 | ||||
| -rw-r--r-- | compiler/rustc_session/src/session.rs | 14 |
5 files changed, 1815 insertions, 149 deletions
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs index 0ccc71ae06c..120f5ba7d48 100644 --- a/compiler/rustc_errors/src/emitter.rs +++ b/compiler/rustc_errors/src/emitter.rs @@ -44,6 +44,7 @@ const DEFAULT_COLUMN_WIDTH: usize = 140; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum HumanReadableErrorType { Default, + Unicode, AnnotateSnippet, Short, } @@ -595,6 +596,12 @@ impl ColorConfig { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OutputTheme { + Ascii, + Unicode, +} + /// Handles the writing of `HumanReadableErrorType::Default` and `HumanReadableErrorType::Short` #[derive(Setters)] pub struct HumanEmitter { @@ -613,6 +620,7 @@ pub struct HumanEmitter { macro_backtrace: bool, track_diagnostics: bool, terminal_url: TerminalUrl, + theme: OutputTheme, } #[derive(Debug)] @@ -637,6 +645,7 @@ impl HumanEmitter { macro_backtrace: false, track_diagnostics: false, terminal_url: TerminalUrl::No, + theme: OutputTheme::Ascii, } } @@ -680,17 +689,19 @@ impl HumanEmitter { }) .collect(); buffer.puts(line_offset, code_offset, &code, Style::Quotation); + let placeholder = self.margin(); if margin.was_cut_left() { // We have stripped some code/whitespace from the beginning, make it clear. - buffer.puts(line_offset, code_offset, "...", Style::LineNumber); + buffer.puts(line_offset, code_offset, placeholder, Style::LineNumber); } if margin.was_cut_right(line_len) { + let padding: usize = placeholder.chars().map(|ch| char_width(ch)).sum(); // We have stripped some code after the rightmost span end, make it clear we did so. - buffer.puts(line_offset, code_offset + taken - 3, "...", Style::LineNumber); + buffer.puts(line_offset, code_offset + taken - padding, placeholder, Style::LineNumber); } buffer.puts(line_offset, 0, &self.maybe_anonymized(line_index), Style::LineNumber); - draw_col_separator_no_space(buffer, line_offset, width_offset - 2); + self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2); } #[instrument(level = "trace", skip(self), ret)] @@ -702,6 +713,7 @@ impl HumanEmitter { width_offset: usize, code_offset: usize, margin: Margin, + close_window: bool, ) -> Vec<(usize, Style)> { // Draw: // @@ -767,13 +779,10 @@ impl HumanEmitter { for ann in &line.annotations { if let AnnotationType::MultilineStart(depth) = ann.annotation_type { if source_string.chars().take(ann.start_col.display).all(|c| c.is_whitespace()) { - let style = if ann.is_primary { - Style::UnderlinePrimary - } else { - Style::UnderlineSecondary - }; - annotations.push((depth, style)); - buffer_ops.push((line_offset, width_offset + depth - 1, '/', style)); + let uline = self.underline(ann.is_primary); + let chr = uline.multiline_whole_line; + annotations.push((depth, uline.style)); + buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style)); } else { short_start = false; break; @@ -970,7 +979,7 @@ impl HumanEmitter { // 3 │ X0 Y0 Z0 // │ ┏━━━━━┛ │ │ < We are writing these lines // │ ┃┌───────┘ │ < by reverting the "depth" of - // │ ┃│┌─────────┘ < their multilne spans. + // │ ┃│┌─────────┘ < their multiline spans. // 4 │ ┃││ X1 Y1 Z1 // 5 │ ┃││ X2 Y2 Z2 // │ ┃│└────╿──│──┘ `Z` label @@ -997,7 +1006,10 @@ impl HumanEmitter { // 4 | } // | for pos in 0..=line_len { - draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2); + self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2); + } + if close_window { + self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2); } // Write the horizontal lines for multiline annotations @@ -1013,21 +1025,17 @@ impl HumanEmitter { // 4 | } // | _ for &(pos, annotation) in &annotations_position { - let style = if annotation.is_primary { - Style::UnderlinePrimary - } else { - Style::UnderlineSecondary - }; + let underline = self.underline(annotation.is_primary); let pos = pos + 1; match annotation.annotation_type { AnnotationType::MultilineStart(depth) | AnnotationType::MultilineEnd(depth) => { - draw_range( + self.draw_range( buffer, - '_', + underline.multiline_horizontal, line_offset + pos, width_offset + depth, (code_offset + annotation.start_col.display).saturating_sub(left), - style, + underline.style, ); } _ if self.teach => { @@ -1035,7 +1043,7 @@ impl HumanEmitter { line_offset, (code_offset + annotation.start_col.display).saturating_sub(left), (code_offset + annotation.end_col.display).saturating_sub(left), - style, + underline.style, annotation.is_primary, ); } @@ -1055,33 +1063,78 @@ impl HumanEmitter { // 4 | | } // | |_ for &(pos, annotation) in &annotations_position { - let style = if annotation.is_primary { - Style::UnderlinePrimary - } else { - Style::UnderlineSecondary - }; + let underline = self.underline(annotation.is_primary); let pos = pos + 1; if pos > 1 && (annotation.has_label() || annotation.takes_space()) { for p in line_offset + 1..=line_offset + pos { + if let AnnotationType::MultilineLine(_) = annotation.annotation_type { + buffer.putc( + p, + (code_offset + annotation.start_col.display).saturating_sub(left), + underline.multiline_vertical, + underline.style, + ); + } else { + buffer.putc( + p, + (code_offset + annotation.start_col.display).saturating_sub(left), + underline.vertical_text_line, + underline.style, + ); + } + } + if let AnnotationType::MultilineStart(_) = annotation.annotation_type { buffer.putc( - p, + line_offset + pos, (code_offset + annotation.start_col.display).saturating_sub(left), - '|', - style, + underline.bottom_right, + underline.style, + ); + } + if let AnnotationType::MultilineEnd(_) = annotation.annotation_type + && annotation.has_label() + { + buffer.putc( + line_offset + pos, + (code_offset + annotation.start_col.display).saturating_sub(left), + underline.multiline_bottom_right_with_text, + underline.style, ); } } match annotation.annotation_type { AnnotationType::MultilineStart(depth) => { + buffer.putc( + line_offset + pos, + width_offset + depth - 1, + underline.top_left, + underline.style, + ); for p in line_offset + pos + 1..line_offset + line_len + 2 { - buffer.putc(p, width_offset + depth - 1, '|', style); + buffer.putc( + p, + width_offset + depth - 1, + underline.multiline_vertical, + underline.style, + ); } } AnnotationType::MultilineEnd(depth) => { - for p in line_offset..=line_offset + pos { - buffer.putc(p, width_offset + depth - 1, '|', style); + for p in line_offset..line_offset + pos { + buffer.putc( + p, + width_offset + depth - 1, + underline.multiline_vertical, + underline.style, + ); } + buffer.putc( + line_offset + pos, + width_offset + depth - 1, + underline.bottom_left, + underline.style, + ); } _ => (), } @@ -1102,7 +1155,11 @@ impl HumanEmitter { let style = if annotation.is_primary { Style::LabelPrimary } else { Style::LabelSecondary }; let (pos, col) = if pos == 0 { - (pos + 1, (annotation.end_col.display + 1).saturating_sub(left)) + if annotation.end_col.display == 0 { + (pos + 1, (annotation.end_col.display + 2).saturating_sub(left)) + } else { + (pos + 1, (annotation.end_col.display + 1).saturating_sub(left)) + } } else { (pos + 2, annotation.start_col.display.saturating_sub(left)) }; @@ -1135,18 +1192,60 @@ impl HumanEmitter { // 3 | // 4 | } // | _^ test - for &(_, annotation) in &annotations_position { - let (underline, style) = if annotation.is_primary { - ('^', Style::UnderlinePrimary) - } else { - ('-', Style::UnderlineSecondary) - }; + for &(pos, annotation) in &annotations_position { + let uline = self.underline(annotation.is_primary); for p in annotation.start_col.display..annotation.end_col.display { + // The default span label underline. buffer.putc( line_offset + 1, (code_offset + p).saturating_sub(left), - underline, - style, + uline.underline, + uline.style, + ); + } + + if pos == 0 + && matches!( + annotation.annotation_type, + AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_) + ) + { + // The beginning of a multiline span with its leftward moving line on the same line. + buffer.putc( + line_offset + 1, + (code_offset + annotation.start_col.display).saturating_sub(left), + match annotation.annotation_type { + AnnotationType::MultilineStart(_) => uline.top_right_flat, + AnnotationType::MultilineEnd(_) => uline.multiline_end_same_line, + _ => panic!("unexpected annotation type: {annotation:?}"), + }, + uline.style, + ); + } else if pos != 0 + && matches!( + annotation.annotation_type, + AnnotationType::MultilineStart(_) | AnnotationType::MultilineEnd(_) + ) + { + // The beginning of a multiline span with its leftward moving line on another line, + // so we start going down first. + buffer.putc( + line_offset + 1, + (code_offset + annotation.start_col.display).saturating_sub(left), + match annotation.annotation_type { + AnnotationType::MultilineStart(_) => uline.multiline_start_down, + AnnotationType::MultilineEnd(_) => uline.multiline_end_up, + _ => panic!("unexpected annotation type: {annotation:?}"), + }, + uline.style, + ); + } else if pos != 0 && annotation.has_label() { + // The beginning of a span label with an actual label, we'll point down. + buffer.putc( + line_offset + 1, + (code_offset + annotation.start_col.display).saturating_sub(left), + uline.label_start, + uline.style, ); } } @@ -1217,7 +1316,7 @@ impl HumanEmitter { padding: usize, label: &str, override_style: Option<Style>, - ) { + ) -> usize { // The extra 5 ` ` is padding that's always needed to align to the `note: `: // // error: message @@ -1281,6 +1380,7 @@ impl HumanEmitter { buffer.append(line_number, text, style_or_override(*style, override_style)); } } + line_number } #[instrument(level = "trace", skip(self, args), ret)] @@ -1294,6 +1394,7 @@ impl HumanEmitter { max_line_num_len: usize, is_secondary: bool, emitted_at: Option<&DiagLocation>, + is_cont: bool, ) -> io::Result<()> { let mut buffer = StyledBuffer::new(); @@ -1303,12 +1404,29 @@ impl HumanEmitter { for _ in 0..max_line_num_len { buffer.prepend(0, " ", Style::NoStyle); } - draw_note_separator(&mut buffer, 0, max_line_num_len + 1); + self.draw_note_separator(&mut buffer, 0, max_line_num_len + 1, is_cont); if *level != Level::FailureNote { buffer.append(0, level.to_str(), Style::MainHeaderMsg); buffer.append(0, ": ", Style::NoStyle); } - self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None); + let printed_lines = + self.msgs_to_buffer(&mut buffer, msgs, args, max_line_num_len, "note", None); + if is_cont && matches!(self.theme, OutputTheme::Unicode) { + // There's another note after this one, associated to the subwindow above. + // We write additional vertical lines to join them: + // ╭▸ test.rs:3:3 + // │ + // 3 │ code + // │ ━━━━ + // │ + // ├ note: foo + // │ bar + // ╰ note: foo + // bar + for i in 1..=printed_lines { + self.draw_col_separator_no_space(&mut buffer, i, max_line_num_len + 1); + } + } } else { let mut label_width = 0; // The failure note level itself does not provide any useful diagnostic information @@ -1439,9 +1557,13 @@ impl HumanEmitter { Style::LineAndColumn, ); if annotation_id == 0 { - buffer.prepend(line_idx, "--> ", Style::LineNumber); + buffer.prepend(line_idx, self.file_start(), Style::LineNumber); } else { - buffer.prepend(line_idx, "::: ", Style::LineNumber); + buffer.prepend( + line_idx, + self.secondary_file_start(), + Style::LineNumber, + ); } for _ in 0..max_line_num_len { buffer.prepend(line_idx, " ", Style::NoStyle); @@ -1454,12 +1576,14 @@ impl HumanEmitter { } else { Style::LabelSecondary }; - buffer.prepend(line_idx, " |", Style::LineNumber); + let pipe = self.col_separator(); + buffer.prepend(line_idx, &format!(" {pipe}"), Style::LineNumber); for _ in 0..max_line_num_len { buffer.prepend(line_idx, " ", Style::NoStyle); } line_idx += 1; - buffer.append(line_idx, " = note: ", style); + let chr = self.note_separator(); + buffer.append(line_idx, &format!(" {chr} note: "), style); for _ in 0..max_line_num_len { buffer.prepend(line_idx, " ", Style::NoStyle); } @@ -1480,7 +1604,7 @@ impl HumanEmitter { // remember where we are in the output buffer for easy reference let buffer_msg_line_offset = buffer.num_lines(); - buffer.prepend(buffer_msg_line_offset, "--> ", Style::LineNumber); + buffer.prepend(buffer_msg_line_offset, self.file_start(), Style::LineNumber); buffer.append( buffer_msg_line_offset, &format!( @@ -1510,15 +1634,28 @@ impl HumanEmitter { // remember where we are in the output buffer for easy reference let buffer_msg_line_offset = buffer.num_lines(); - // Add spacing line - draw_col_separator_no_space( + // Add spacing line, as shown: + // --> $DIR/file:54:15 + // | + // LL | code + // | ^^^^ + // | (<- It prints *this* line) + // ::: $DIR/other_file.rs:15:5 + // | + // LL | code + // | ---- + self.draw_col_separator_no_space( &mut buffer, buffer_msg_line_offset, max_line_num_len + 1, ); // Then, the secondary file indicator - buffer.prepend(buffer_msg_line_offset + 1, "::: ", Style::LineNumber); + buffer.prepend( + buffer_msg_line_offset + 1, + self.secondary_file_start(), + Style::LineNumber, + ); let loc = if let Some(first_line) = annotated_file.lines.first() { let col = if let Some(first_annotation) = first_line.annotations.first() { format!(":{}", first_annotation.start_col.file + 1) @@ -1543,7 +1680,7 @@ impl HumanEmitter { if !self.short_message { // Put in the spacer between the location and annotated source let buffer_msg_line_offset = buffer.num_lines(); - draw_col_separator_no_space( + self.draw_col_separator_no_space( &mut buffer, buffer_msg_line_offset, max_line_num_len + 1, @@ -1651,6 +1788,7 @@ impl HumanEmitter { width_offset, code_offset, margin, + !is_cont && line_idx + 1 == annotated_file.lines.len(), ); let mut to_add = FxHashMap::default(); @@ -1666,7 +1804,13 @@ impl HumanEmitter { // the code in this line. for (depth, style) in &multilines { for line in previous_buffer_line..buffer.num_lines() { - draw_multiline_line(&mut buffer, line, width_offset, *depth, *style); + self.draw_multiline_line( + &mut buffer, + line, + width_offset, + *depth, + *style, + ); } } // check to see if we need to print out or elide lines that come between @@ -1676,11 +1820,15 @@ impl HumanEmitter { - annotated_file.lines[line_idx].line_index; if line_idx_delta > 2 { let last_buffer_line_num = buffer.num_lines(); - buffer.puts(last_buffer_line_num, 0, "...", Style::LineNumber); + self.draw_line_separator( + &mut buffer, + last_buffer_line_num, + width_offset, + ); // Set the multiline annotation vertical lines on `...` bridging line. for (depth, style) in &multilines { - draw_multiline_line( + self.draw_multiline_line( &mut buffer, last_buffer_line_num, width_offset, @@ -1695,7 +1843,7 @@ impl HumanEmitter { // In the case where we have elided the entire start of the // multispan because those lines were empty, we still need // to draw the `|`s across the `...`. - draw_multiline_line( + self.draw_multiline_line( &mut buffer, last_buffer_line_num, width_offset, @@ -1728,7 +1876,7 @@ impl HumanEmitter { ); for (depth, style) in &multilines { - draw_multiline_line( + self.draw_multiline_line( &mut buffer, last_buffer_line_num, width_offset, @@ -1740,7 +1888,7 @@ impl HumanEmitter { for ann in &line.annotations { if let AnnotationType::MultilineStart(pos) = ann.annotation_type { - draw_multiline_line( + self.draw_multiline_line( &mut buffer, last_buffer_line_num, width_offset, @@ -1824,13 +1972,24 @@ impl HumanEmitter { ); let mut row_num = 2; - draw_col_separator_no_space(&mut buffer, 1, max_line_num_len + 1); - for (complete, parts, highlights, _) in suggestions.iter().take(MAX_SUGGESTIONS) { + for (i, (complete, parts, highlights, _)) in + suggestions.iter().enumerate().take(MAX_SUGGESTIONS) + { debug!(?complete, ?parts, ?highlights); let has_deletion = parts.iter().any(|p| p.is_deletion(sm)); let is_multiline = complete.lines().count() > 1; + if i == 0 { + self.draw_col_separator_start(&mut buffer, row_num - 1, max_line_num_len + 1); + } else { + buffer.puts( + row_num - 1, + max_line_num_len + 1, + self.multi_suggestion_separator(), + Style::LineNumber, + ); + } if let Some(span) = span.primary_span() { // Compare the primary span of the diagnostic with the span of the suggestion // being emitted. If they belong to the same file, we don't *need* to show the @@ -1838,7 +1997,9 @@ impl HumanEmitter { // telling users to make a change but not clarifying *where*. let loc = sm.lookup_char_pos(parts[0].span.lo()); if loc.file.name != sm.span_to_filename(span) && loc.file.name.is_real() { - let arrow = "--> "; + // --> file.rs:line:col + // | + let arrow = self.file_start(); buffer.puts(row_num - 1, 0, arrow, Style::LineNumber); let filename = sm.filename_for_diagnostics(&loc.file.name); let offset = sm.doctest_offset_line(&loc.file.name, loc.line); @@ -1852,6 +2013,7 @@ impl HumanEmitter { for _ in 0..max_line_num_len { buffer.prepend(row_num - 1, " ", Style::NoStyle); } + self.draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1); row_num += 1; } } @@ -1882,7 +2044,6 @@ impl HumanEmitter { assert!(!file_lines.lines.is_empty() || parts[0].span.is_dummy()); let line_start = sm.lookup_char_pos(parts[0].span.lo()).line; - draw_col_separator_no_space(&mut buffer, row_num - 1, max_line_num_len + 1); let mut lines = complete.lines(); if lines.clone().next().is_none() { // Account for a suggestion to completely remove a line(s) with whitespace (#94192). @@ -1972,7 +2133,14 @@ impl HumanEmitter { ) } - buffer.puts(row_num, 0, "...", Style::LineNumber); + let placeholder = self.margin(); + let padding: usize = placeholder.chars().map(|ch| char_width(ch)).sum(); + buffer.puts( + row_num, + max_line_num_len.saturating_sub(padding), + placeholder, + Style::LineNumber, + ); row_num += 1; if let Some((p, l)) = last_line { @@ -2040,7 +2208,6 @@ impl HumanEmitter { if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add = show_code_change { - draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1); for part in parts { let span_start_pos = sm.lookup_char_pos(part.span.lo()).col_display; let span_end_pos = sm.lookup_char_pos(part.span.hi()).col_display; @@ -2082,7 +2249,7 @@ impl HumanEmitter { buffer.putc( row_num, (padding as isize + p) as usize, - if part.is_addition(sm) { '+' } else { '~' }, + if part.is_addition(sm) { '+' } else { self.diff() }, Style::Addition, ); } @@ -2114,10 +2281,23 @@ impl HumanEmitter { // if we elided some lines, add an ellipsis if lines.next().is_some() { - buffer.puts(row_num, max_line_num_len - 1, "...", Style::LineNumber); - } else if let DisplaySuggestion::None = show_code_change { - draw_col_separator_no_space(&mut buffer, row_num, max_line_num_len + 1); - row_num += 1; + let placeholder = self.margin(); + let padding: usize = placeholder.chars().map(|ch| char_width(ch)).sum(); + buffer.puts( + row_num, + max_line_num_len.saturating_sub(padding), + placeholder, + Style::LineNumber, + ); + } else { + let row = match show_code_change { + DisplaySuggestion::Diff + | DisplaySuggestion::Add + | DisplaySuggestion::Underline => row_num - 1, + DisplaySuggestion::None => row_num, + }; + self.draw_col_separator_end(&mut buffer, row, max_line_num_len + 1); + row_num = row + 1; } } if suggestions.len() > MAX_SUGGESTIONS { @@ -2125,6 +2305,7 @@ impl HumanEmitter { let msg = format!("and {} other candidate{}", others, pluralize!(others)); buffer.puts(row_num, max_line_num_len + 3, &msg, Style::NoStyle); } + emit_to_destination(&buffer.render(), level, &mut self.dst, self.short_message)?; Ok(()) } @@ -2157,6 +2338,8 @@ impl HumanEmitter { max_line_num_len, false, emitted_at, + !children.is_empty() + || suggestions.iter().any(|s| s.style != SuggestionStyle::CompletelyHidden), ) { Ok(()) => { if !children.is_empty() @@ -2164,7 +2347,15 @@ impl HumanEmitter { { let mut buffer = StyledBuffer::new(); if !self.short_message { - draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1); + if let Some(child) = children.iter().next() + && child.span.primary_spans().is_empty() + { + // We'll continue the vertical bar to point into the next note. + self.draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1); + } else { + // We'll close the vertical bar to visually end the code window. + self.draw_col_separator_end(&mut buffer, 0, max_line_num_len + 1); + } } if let Err(e) = emit_to_destination( &buffer.render(), @@ -2176,9 +2367,14 @@ impl HumanEmitter { } } if !self.short_message { - for child in children { + for (i, child) in children.iter().enumerate() { assert!(child.level.can_be_subdiag()); let span = &child.span; + // FIXME: audit that this behaves correctly with suggestions. + let should_close = match children.get(i + 1) { + Some(c) => !c.span.primary_spans().is_empty(), + None => i + 1 == children.len(), + }; if let Err(err) = self.emit_messages_default_inner( span, &child.messages, @@ -2188,11 +2384,12 @@ impl HumanEmitter { max_line_num_len, true, None, + !should_close, ) { panic!("failed to emit error: {err}"); } } - for sugg in suggestions { + for (i, sugg) in suggestions.iter().enumerate() { match sugg.style { SuggestionStyle::CompletelyHidden => { // do not display this suggestion, it is meant only for tools @@ -2207,6 +2404,9 @@ impl HumanEmitter { max_line_num_len, true, None, + // FIXME: this needs to account for the suggestion type, + // some don't take any space. + i + 1 != suggestions.len(), ) { panic!("failed to emit error: {e}"); } @@ -2323,10 +2523,17 @@ impl HumanEmitter { buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition); } [] => { - draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1); + // FIXME: needed? Doesn't get excercised in any test. + self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1); } _ => { - buffer.puts(*row_num, max_line_num_len + 1, "~ ", Style::Addition); + let diff = self.diff(); + buffer.puts( + *row_num, + max_line_num_len + 1, + &format!("{diff} "), + Style::Addition, + ); } } // LL | line_to_add @@ -2346,7 +2553,7 @@ impl HumanEmitter { buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle); } else { buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber); - draw_col_separator(buffer, *row_num, max_line_num_len + 1); + self.draw_col_separator(buffer, *row_num, max_line_num_len + 1); buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle); } @@ -2374,6 +2581,306 @@ impl HumanEmitter { } *row_num += 1; } + + fn underline(&self, is_primary: bool) -> UnderlineParts { + // X0 Y0 + // label_start > ┯━━━━ < underline + // │ < vertical_text_line + // text + + // multiline_start_down ⤷ X0 Y0 + // top_left > ┌───╿──┘ < top_right_flat + // top_left > ┏│━━━┙ < top_right + // multiline_vertical > ┃│ + // ┃│ X1 Y1 + // ┃│ X2 Y2 + // ┃└────╿──┘ < multiline_end_same_line + // bottom_left > ┗━━━━━┥ < bottom_right_with_text + // multiline_horizontal ^ `X` is a good letter + + // multiline_whole_line > ┏ X0 Y0 + // ┃ X1 Y1 + // ┗━━━━┛ < multiline_end_same_line + + // multiline_whole_line > ┏ X0 Y0 + // ┃ X1 Y1 + // ┃ ╿ < multiline_end_up + // ┗━━┛ < bottom_right + + match (self.theme, is_primary) { + (OutputTheme::Ascii, true) => UnderlineParts { + style: Style::UnderlinePrimary, + underline: '^', + label_start: '^', + vertical_text_line: '|', + multiline_vertical: '|', + multiline_horizontal: '_', + multiline_whole_line: '/', + multiline_start_down: '^', + bottom_right: '|', + top_left: ' ', + top_right_flat: '^', + bottom_left: '|', + multiline_end_up: '^', + multiline_end_same_line: '^', + multiline_bottom_right_with_text: '|', + }, + (OutputTheme::Ascii, false) => UnderlineParts { + style: Style::UnderlineSecondary, + underline: '-', + label_start: '-', + vertical_text_line: '|', + multiline_vertical: '|', + multiline_horizontal: '_', + multiline_whole_line: '/', + multiline_start_down: '-', + bottom_right: '|', + top_left: ' ', + top_right_flat: '-', + bottom_left: '|', + multiline_end_up: '-', + multiline_end_same_line: '-', + multiline_bottom_right_with_text: '|', + }, + (OutputTheme::Unicode, true) => UnderlineParts { + style: Style::UnderlinePrimary, + underline: '━', + label_start: '┯', + vertical_text_line: '│', + multiline_vertical: '┃', + multiline_horizontal: '━', + multiline_whole_line: '┏', + multiline_start_down: '╿', + bottom_right: '┙', + top_left: '┏', + top_right_flat: '┛', + bottom_left: '┗', + multiline_end_up: '╿', + multiline_end_same_line: '┛', + multiline_bottom_right_with_text: '┥', + }, + (OutputTheme::Unicode, false) => UnderlineParts { + style: Style::UnderlineSecondary, + underline: '─', + label_start: '┬', + vertical_text_line: '│', + multiline_vertical: '│', + multiline_horizontal: '─', + multiline_whole_line: '┌', + multiline_start_down: '│', + bottom_right: '┘', + top_left: '┌', + top_right_flat: '┘', + bottom_left: '└', + multiline_end_up: '│', + multiline_end_same_line: '┘', + multiline_bottom_right_with_text: '┤', + }, + } + } + + fn col_separator(&self) -> char { + match self.theme { + OutputTheme::Ascii => '|', + OutputTheme::Unicode => '│', + } + } + + fn note_separator(&self) -> char { + match self.theme { + OutputTheme::Ascii => '=', + OutputTheme::Unicode => '╰', + } + } + + fn multi_suggestion_separator(&self) -> &'static str { + match self.theme { + OutputTheme::Ascii => "|", + OutputTheme::Unicode => "├╴", + } + } + + fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) { + let chr = self.col_separator(); + buffer.puts(line, col, &format!("{chr} "), Style::LineNumber); + } + + fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) { + let chr = self.col_separator(); + self.draw_col_separator_no_space_with_style(buffer, chr, line, col, Style::LineNumber); + } + + fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) { + match self.theme { + OutputTheme::Ascii => { + self.draw_col_separator_no_space_with_style( + buffer, + '|', + line, + col, + Style::LineNumber, + ); + } + OutputTheme::Unicode => { + self.draw_col_separator_no_space_with_style( + buffer, + '╭', + line, + col, + Style::LineNumber, + ); + self.draw_col_separator_no_space_with_style( + buffer, + '╴', + line, + col + 1, + Style::LineNumber, + ); + } + } + } + + fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) { + match self.theme { + OutputTheme::Ascii => { + self.draw_col_separator_no_space_with_style( + buffer, + '|', + line, + col, + Style::LineNumber, + ); + } + OutputTheme::Unicode => { + self.draw_col_separator_no_space_with_style( + buffer, + '╰', + line, + col, + Style::LineNumber, + ); + self.draw_col_separator_no_space_with_style( + buffer, + '╴', + line, + col + 1, + Style::LineNumber, + ); + } + } + } + + fn draw_col_separator_no_space_with_style( + &self, + buffer: &mut StyledBuffer, + chr: char, + line: usize, + col: usize, + style: Style, + ) { + buffer.putc(line, col, chr, style); + } + + fn draw_range( + &self, + buffer: &mut StyledBuffer, + symbol: char, + line: usize, + col_from: usize, + col_to: usize, + style: Style, + ) { + for col in col_from..col_to { + buffer.putc(line, col, symbol, style); + } + } + + fn draw_note_separator( + &self, + buffer: &mut StyledBuffer, + line: usize, + col: usize, + is_cont: bool, + ) { + let chr = match self.theme { + OutputTheme::Ascii => "= ", + OutputTheme::Unicode if is_cont => "├ ", + OutputTheme::Unicode => "╰ ", + }; + buffer.puts(line, col, chr, Style::LineNumber); + } + + fn draw_multiline_line( + &self, + buffer: &mut StyledBuffer, + line: usize, + offset: usize, + depth: usize, + style: Style, + ) { + let chr = match (style, self.theme) { + (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Ascii) => '|', + (_, OutputTheme::Ascii) => '|', + (Style::UnderlinePrimary | Style::LabelPrimary, OutputTheme::Unicode) => '┃', + (_, OutputTheme::Unicode) => '│', + }; + buffer.putc(line, offset + depth - 1, chr, style); + } + + fn file_start(&self) -> &'static str { + match self.theme { + OutputTheme::Ascii => "--> ", + OutputTheme::Unicode => " ╭▸ ", + } + } + + fn secondary_file_start(&self) -> &'static str { + match self.theme { + OutputTheme::Ascii => "::: ", + OutputTheme::Unicode => " ⸬ ", + } + } + + fn diff(&self) -> char { + match self.theme { + OutputTheme::Ascii => '~', + OutputTheme::Unicode => '±', + } + } + + fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) { + let (column, dots) = match self.theme { + OutputTheme::Ascii => (0, "..."), + OutputTheme::Unicode => (col - 2, "‡"), + }; + buffer.puts(line, column, dots, Style::LineNumber); + } + + fn margin(&self) -> &'static str { + match self.theme { + OutputTheme::Ascii => "...", + OutputTheme::Unicode => "…", + } + } +} + +#[derive(Debug, Clone, Copy)] +struct UnderlineParts { + style: Style, + underline: char, + label_start: char, + vertical_text_line: char, + multiline_vertical: char, + multiline_horizontal: char, + multiline_whole_line: char, + multiline_start_down: char, + bottom_right: char, + top_left: char, + top_right_flat: char, + bottom_left: char, + multiline_end_up: char, + multiline_end_same_line: char, + multiline_bottom_right_with_text: char, } #[derive(Clone, Copy, Debug)] @@ -2661,50 +3168,6 @@ fn normalize_whitespace(s: &str) -> String { }) } -fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) { - buffer.puts(line, col, "| ", Style::LineNumber); -} - -fn draw_col_separator_no_space(buffer: &mut StyledBuffer, line: usize, col: usize) { - draw_col_separator_no_space_with_style(buffer, line, col, Style::LineNumber); -} - -fn draw_col_separator_no_space_with_style( - buffer: &mut StyledBuffer, - line: usize, - col: usize, - style: Style, -) { - buffer.putc(line, col, '|', style); -} - -fn draw_range( - buffer: &mut StyledBuffer, - symbol: char, - line: usize, - col_from: usize, - col_to: usize, - style: Style, -) { - for col in col_from..col_to { - buffer.putc(line, col, symbol, style); - } -} - -fn draw_note_separator(buffer: &mut StyledBuffer, line: usize, col: usize) { - buffer.puts(line, col, "= ", Style::LineNumber); -} - -fn draw_multiline_line( - buffer: &mut StyledBuffer, - line: usize, - offset: usize, - depth: usize, - style: Style, -) { - buffer.putc(line, offset + depth - 1, '|', style); -} - fn num_overlap( a_start: usize, a_end: usize, diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs index 1534e256520..785e6f1c6ee 100644 --- a/compiler/rustc_errors/src/json.rs +++ b/compiler/rustc_errors/src/json.rs @@ -27,7 +27,7 @@ use termcolor::{ColorSpec, WriteColor}; use crate::diagnostic::IsLint; use crate::emitter::{ - ColorConfig, Destination, Emitter, HumanEmitter, HumanReadableErrorType, + ColorConfig, Destination, Emitter, HumanEmitter, HumanReadableErrorType, OutputTheme, should_show_source_code, }; use crate::registry::Registry; @@ -377,6 +377,11 @@ impl Diagnostic { .terminal_url(je.terminal_url) .ui_testing(je.ui_testing) .ignored_directories_in_source_blocks(je.ignored_directories_in_source_blocks.clone()) + .theme(if let HumanReadableErrorType::Unicode = je.json_rendered { + OutputTheme::Unicode + } else { + OutputTheme::Ascii + }) .emit_diagnostic(diag); let buf = Arc::try_unwrap(buf.0).unwrap().into_inner().unwrap(); let buf = String::from_utf8(buf).unwrap(); diff --git a/compiler/rustc_parse/src/parser/tests.rs b/compiler/rustc_parse/src/parser/tests.rs index 92684505ab0..decaecd2682 100644 --- a/compiler/rustc_parse/src/parser/tests.rs +++ b/compiler/rustc_parse/src/parser/tests.rs @@ -12,7 +12,7 @@ use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, Toke use rustc_ast::{self as ast, PatKind, visit}; use rustc_ast_pretty::pprust::item_to_string; use rustc_data_structures::sync::Lrc; -use rustc_errors::emitter::HumanEmitter; +use rustc_errors::emitter::{HumanEmitter, OutputTheme}; use rustc_errors::{DiagCtxt, MultiSpan, PResult}; use rustc_session::parse::ParseSess; use rustc_span::source_map::{FilePathMapping, SourceMap}; @@ -36,16 +36,17 @@ fn string_to_parser(psess: &ParseSess, source_str: String) -> Parser<'_> { )) } -fn create_test_handler() -> (DiagCtxt, Lrc<SourceMap>, Arc<Mutex<Vec<u8>>>) { +fn create_test_handler(theme: OutputTheme) -> (DiagCtxt, Lrc<SourceMap>, Arc<Mutex<Vec<u8>>>) { let output = Arc::new(Mutex::new(Vec::new())); let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); let fallback_bundle = rustc_errors::fallback_fluent_bundle( vec![crate::DEFAULT_LOCALE_RESOURCE, crate::DEFAULT_LOCALE_RESOURCE], false, ); - let emitter = HumanEmitter::new(Box::new(Shared { data: output.clone() }), fallback_bundle) + let mut emitter = HumanEmitter::new(Box::new(Shared { data: output.clone() }), fallback_bundle) .sm(Some(source_map.clone())) .diagnostic_width(Some(140)); + emitter = emitter.theme(theme); let dcx = DiagCtxt::new(Box::new(emitter)); (dcx, source_map, output) } @@ -69,7 +70,7 @@ fn with_expected_parse_error<T, F>(source_str: &str, expected_output: &str, f: F where F: for<'a> FnOnce(&mut Parser<'a>) -> PResult<'a, T>, { - let (handler, source_map, output) = create_test_handler(); + let (handler, source_map, output) = create_test_handler(OutputTheme::Ascii); let psess = ParseSess::with_dcx(handler, source_map); let mut p = string_to_parser(&psess, source_str.to_string()); let result = f(&mut p); @@ -189,34 +190,55 @@ impl<T: Write> Write for Shared<T> { } #[allow(rustc::untranslatable_diagnostic)] // no translation needed for tests -fn test_harness(file_text: &str, span_labels: Vec<SpanLabel>, expected_output: &str) { +fn test_harness( + file_text: &str, + span_labels: Vec<SpanLabel>, + notes: Vec<(Option<(Position, Position)>, &'static str)>, + expected_output_ascii: &str, + expected_output_unicode: &str, +) { create_default_session_globals_then(|| { - let (dcx, source_map, output) = create_test_handler(); - source_map.new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned()); - - let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end); - let mut msp = MultiSpan::from_span(primary_span); - for span_label in span_labels { - let span = make_span(&file_text, &span_label.start, &span_label.end); - msp.push_span_label(span, span_label.label); - println!("span: {:?} label: {:?}", span, span_label.label); - println!("text: {:?}", source_map.span_to_snippet(span)); - } + for (theme, expected_output) in [ + (OutputTheme::Ascii, expected_output_ascii), + (OutputTheme::Unicode, expected_output_unicode), + ] { + let (dcx, source_map, output) = create_test_handler(theme); + source_map + .new_source_file(Path::new("test.rs").to_owned().into(), file_text.to_owned()); + + let primary_span = make_span(&file_text, &span_labels[0].start, &span_labels[0].end); + let mut msp = MultiSpan::from_span(primary_span); + for span_label in &span_labels { + let span = make_span(&file_text, &span_label.start, &span_label.end); + msp.push_span_label(span, span_label.label); + println!("span: {:?} label: {:?}", span, span_label.label); + println!("text: {:?}", source_map.span_to_snippet(span)); + } - dcx.handle().span_err(msp, "foo"); + let mut err = dcx.handle().struct_span_err(msp, "foo"); + for (position, note) in ¬es { + if let Some((start, end)) = position { + let span = make_span(&file_text, &start, &end); + err.span_note(span, *note); + } else { + err.note(*note); + } + } + err.emit(); - assert!( - expected_output.chars().next() == Some('\n'), - "expected output should begin with newline" - ); - let expected_output = &expected_output[1..]; + assert!( + expected_output.chars().next() == Some('\n'), + "expected output should begin with newline" + ); + let expected_output = &expected_output[1..]; - let bytes = output.lock().unwrap(); - let actual_output = str::from_utf8(&bytes).unwrap(); - println!("expected output:\n------\n{}------", expected_output); - println!("actual output:\n------\n{}------", actual_output); + let bytes = output.lock().unwrap(); + let actual_output = str::from_utf8(&bytes).unwrap(); + println!("expected output:\n------\n{}------", expected_output); + println!("actual output:\n------\n{}------", actual_output); - assert!(expected_output == actual_output) + assert!(expected_output == actual_output) + } }) } @@ -253,6 +275,7 @@ fn foo() { end: Position { string: "}", count: 1 }, label: "test", }], + vec![], r#" error: foo --> test.rs:2:10 @@ -263,6 +286,16 @@ error: foo | |_^ test "#, + r#" +error: foo + ╭▸ test.rs:2:10 + │ +2 │ fn foo() { + │ ┏━━━━━━━━━━┛ +3 │ ┃ } + ╰╴┗━┛ test + +"#, ); } @@ -280,6 +313,7 @@ fn foo() { end: Position { string: "}", count: 1 }, label: "test", }], + vec![], r#" error: foo --> test.rs:2:10 @@ -291,6 +325,17 @@ error: foo | |___^ test "#, + r#" +error: foo + ╭▸ test.rs:2:10 + │ +2 │ fn foo() { + │ ┏━━━━━━━━━━┛ + ‡ ┃ +5 │ ┃ } + ╰╴┗━━━┛ test + +"#, ); } #[test] @@ -315,6 +360,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -329,6 +375,20 @@ error: foo | `X` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ X0 Y0 + │ ┏━━━━┛ │ + │ ┃┌──────┘ +4 │ ┃│ X1 Y1 +5 │ ┃│ X2 Y2 + │ ┃└────╿──┘ `Y` is a good letter too + │ ┗━━━━━┥ + ╰╴ `X` is a good letter + +"#, ); } @@ -353,6 +413,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -366,6 +427,72 @@ error: foo | `Y` is a good letter too "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ X0 Y0 + │ ┏━━━━┛ │ + │ ┃┌──────┘ +4 │ ┃│ Y1 X1 + │ ┗│━━━━│━━┛ `X` is a good letter + │ └────┤ + ╰╴ `Y` is a good letter too + +"#, + ); +} + +#[test] +fn multiline_and_normal_overlap() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![ + SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "X2", count: 1 }, + label: "`X` is a good letter", + }, + SpanLabel { + start: Position { string: "X0", count: 1 }, + end: Position { string: "Y0", count: 1 }, + label: "`Y` is a good letter too", + }, + ], + vec![], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ___---^- + | | | + | | `Y` is a good letter too +4 | | X1 Y1 Z1 +5 | | X2 Y2 Z2 + | |____^ `X` is a good letter + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━┬──┛─ + │ ┃ │ + │ ┃ `Y` is a good letter too +4 │ ┃ X1 Y1 Z1 +5 │ ┃ X2 Y2 Z2 + ╰╴┗━━━━┛ `X` is a good letter + +"#, ); } @@ -392,6 +519,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:6 @@ -406,6 +534,789 @@ error: foo | |____- `Y` is a good letter too "#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━━━┛ +4 │ ┃ X1 Y1 Z1 + │ ┃┌─────────┘ +5 │ ┃│ X2 Y2 Z2 + │ ┗│━━━━┛ `X` is a good letter +6 │ │ X3 Y3 Z3 + ╰╴ └────┘ `Y` is a good letter too + +"#, + ); +} + +#[test] +fn different_note_1() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![(None, "bar")], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ╰ note: bar + +"#, + ); +} + +#[test] +fn different_note_2() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![(None, "bar"), (None, "qux")], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar + = note: qux + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ├ note: bar + ╰ note: qux + +"#, + ); +} + +#[test] +fn different_note_3() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![(None, "bar"), (None, "baz"), (None, "qux")], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar + = note: baz + = note: qux + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ├ note: bar + ├ note: baz + ╰ note: qux + +"#, + ); +} + +#[test] +fn different_note_spanned_1() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + )], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_2() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + ), + ( + Some((Position { string: "X2", count: 1 }, Position { string: "Y2", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ +note: qux + --> test.rs:5:3 + | +5 | X2 Y2 Z2 + | ^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ +note: qux + ╭▸ test.rs:5:3 + │ +5 │ X2 Y2 Z2 + ╰╴ ━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_3() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + ), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "baz", + ), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ +note: baz + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ +note: qux + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ +note: baz + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ +note: qux + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_4() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + ), + (None, "qux"), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + = note: qux + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + │ ━━━━━━━━ + ╰ note: qux + +"#, + ); +} + +#[test] +fn different_note_spanned_5() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + (None, "bar"), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar +note: qux + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ╰ note: bar +note: qux + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_6() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + (None, "bar"), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "baz", + ), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar +note: baz + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ +note: qux + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ╰ note: bar +note: baz + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ +note: qux + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_7() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z3", count: 1 })), + "bar", + ), + (None, "baz"), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | / X1 Y1 Z1 +5 | | X2 Y2 Z2 +6 | | X3 Y3 Z3 + | |__________^ + = note: baz +note: qux + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ ┏ X1 Y1 Z1 +5 │ ┃ X2 Y2 Z2 +6 │ ┃ X3 Y3 Z3 + │ ┗━━━━━━━━━━┛ + ╰ note: baz +note: qux + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_8() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + ), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "baz", + ), + (None, "qux"), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ +note: baz + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + = note: qux + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ +note: baz + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + │ ━━━━━━━━ + ╰ note: qux + +"#, + ); +} + +#[test] +fn different_note_spanned_9() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + (None, "bar"), + (None, "baz"), + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "qux", + ), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | + = note: bar + = note: baz +note: qux + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + │ + ├ note: bar + ╰ note: baz +note: qux + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + ╰╴ ━━━━━━━━ + +"#, + ); +} + +#[test] +fn different_note_spanned_10() { + test_harness( + r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#, + vec![SpanLabel { + start: Position { string: "Y0", count: 1 }, + end: Position { string: "Z0", count: 1 }, + label: "`X` is a good letter", + }], + vec![ + ( + Some((Position { string: "X1", count: 1 }, Position { string: "Z1", count: 1 })), + "bar", + ), + (None, "baz"), + (None, "qux"), + ], + r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ^^^^^ `X` is a good letter + | +note: bar + --> test.rs:4:3 + | +4 | X1 Y1 Z1 + | ^^^^^^^^ + = note: baz + = note: qux + +"#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ━━━━━ `X` is a good letter + ╰╴ +note: bar + ╭▸ test.rs:4:3 + │ +4 │ X1 Y1 Z1 + │ ━━━━━━━━ + ├ note: baz + ╰ note: qux + +"#, ); } @@ -436,6 +1347,7 @@ fn foo() { label: "`Z` label", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -452,6 +1364,22 @@ error: foo | `X` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━┛ │ │ + │ ┃┌───────┘ │ + │ ┃│┌─────────┘ +4 │ ┃││ X1 Y1 Z1 +5 │ ┃││ X2 Y2 Z2 + │ ┃│└────╿──│──┘ `Z` label + │ ┃└─────│──┤ + │ ┗━━━━━━┥ `Y` is a good letter too + ╰╴ `X` is a good letter + +"#, ); } @@ -482,6 +1410,7 @@ fn foo() { label: "`Z` label", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -496,6 +1425,20 @@ error: foo | `Z` label "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ ┏ X0 Y0 Z0 +4 │ ┃ X1 Y1 Z1 +5 │ ┃ X2 Y2 Z2 + │ ┃ ╿ + │ ┃ │ + │ ┃ `X` is a good letter + │ ┗━━━━`Y` is a good letter too + ╰╴ `Z` label + +"#, ); } @@ -527,6 +1470,7 @@ fn foo() { label: "`Z`", }, ], + vec![], r#" error: foo --> test.rs:3:6 @@ -545,6 +1489,24 @@ error: foo | |_______- `Z` "#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━━━┛ +4 │ ┃ X1 Y1 Z1 + │ ┃┌────╿─┘ + │ ┗│━━━━┥ + │ │ `X` is a good letter +5 │ │ X2 Y2 Z2 + │ └───│──────┘ `Y` is a good letter too + │ ┌───┘ + │ │ +6 │ │ X3 Y3 Z3 + ╰╴ └───────┘ `Z` + +"#, ); } @@ -571,6 +1533,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -584,6 +1547,19 @@ error: foo | |__________- `Y` is a good letter too "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ ┏ X0 Y0 Z0 +4 │ ┃ X1 Y1 Z1 + │ ┗━━━━┛ `X` is a good letter +5 │ X2 Y2 Z2 + │ ┌──────┘ +6 │ │ X3 Y3 Z3 + ╰╴└──────────┘ `Y` is a good letter too + +"#, ); } @@ -610,6 +1586,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:6 @@ -625,6 +1602,21 @@ error: foo | |__________- `Y` is a good letter too "#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━━━┛ +4 │ ┃ X1 Y1 Z1 + │ ┃┌────╿────┘ + │ ┗│━━━━┥ + │ │ `X` is a good letter +5 │ │ X2 Y2 Z2 +6 │ │ X3 Y3 Z3 + ╰╴ └──────────┘ `Y` is a good letter too + +"#, ); } @@ -653,6 +1645,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:7 @@ -661,6 +1654,57 @@ error: foo | ----^^^^-^^-- `a` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:7 + │ +3 │ a { b { c } d } + ╰╴ ────━━━━─━━── `a` is a good letter + +"#, + ); +} + +#[test] +fn multiline_notes() { + test_harness( + r#" +fn foo() { + a { b { c } d } +} +"#, + vec![SpanLabel { + start: Position { string: "a", count: 1 }, + end: Position { string: "d", count: 1 }, + label: "`a` is a good letter", + }], + vec![(None, "foo\nbar"), (None, "foo\nbar")], + r#" +error: foo + --> test.rs:3:3 + | +3 | a { b { c } d } + | ^^^^^^^^^^^^^ `a` is a good letter + | + = note: foo + bar + = note: foo + bar + +"#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + │ ━━━━━━━━━━━━━ `a` is a good letter + │ + ├ note: foo + │ bar + ╰ note: foo + bar + +"#, ); } @@ -684,6 +1728,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -692,6 +1737,14 @@ error: foo | ^^^^-------^^ `a` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + ╰╴ ━━━━───────━━ `a` is a good letter + +"#, ); } @@ -720,6 +1773,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:7 @@ -730,6 +1784,16 @@ error: foo | `b` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:7 + │ +3 │ a { b { c } d } + │ ────┯━━━─━━── + │ │ + ╰╴ `b` is a good letter + +"#, ); } @@ -753,6 +1817,7 @@ fn foo() { label: "`b` is a good letter", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -763,6 +1828,16 @@ error: foo | `b` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + │ ━━━━┬──────━━ + │ │ + ╰╴ `b` is a good letter + +"#, ); } @@ -786,6 +1861,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -796,6 +1872,16 @@ error: foo | `a` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a bc d + │ ┯━━━──── + │ │ + ╰╴ `a` is a good letter + +"#, ); } @@ -819,6 +1905,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -827,6 +1914,14 @@ error: foo | ^^^^-------^^ "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + ╰╴ ━━━━───────━━ + +"#, ); } @@ -855,6 +1950,7 @@ fn foo() { label: "", }, ], + vec![], r#" error: foo --> test.rs:3:7 @@ -863,6 +1959,14 @@ error: foo | ----^^^^-^^-- "#, + r#" +error: foo + ╭▸ test.rs:3:7 + │ +3 │ a { b { c } d } + ╰╴ ────━━━━─━━── + +"#, ); } @@ -886,6 +1990,7 @@ fn foo() { label: "`b` is a good letter", }, ], + vec![], r#" error: foo --> test.rs:3:3 @@ -897,6 +2002,17 @@ error: foo | `a` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + │ ┯━━━┬──────━━ + │ │ │ + │ │ `b` is a good letter + ╰╴ `a` is a good letter + +"#, ); } @@ -913,6 +2029,7 @@ fn foo() { end: Position { string: "d", count: 1 }, label: "`a` is a good letter", }], + vec![], r#" error: foo --> test.rs:3:3 @@ -921,6 +2038,14 @@ error: foo | ^^^^^^^^^^^^^ `a` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + ╰╴ ━━━━━━━━━━━━━ `a` is a good letter + +"#, ); } @@ -937,6 +2062,7 @@ fn foo() { end: Position { string: "d", count: 1 }, label: "", }], + vec![], r#" error: foo --> test.rs:3:3 @@ -945,6 +2071,14 @@ error: foo | ^^^^^^^^^^^^^ "#, + r#" +error: foo + ╭▸ test.rs:3:3 + │ +3 │ a { b { c } d } + ╰╴ ━━━━━━━━━━━━━ + +"#, ); } @@ -981,6 +2115,7 @@ fn foo() { label: "`Y` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:6 @@ -1000,6 +2135,25 @@ error: foo | |__________- `Y` is a good letter too "#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━━━┛ +4 │ ┃ X1 Y1 Z1 + │ ┃┌────╿────┘ + │ ┗│━━━━┥ + │ │ `X` is a good letter +5 │ │ 1 +6 │ │ 2 +7 │ │ 3 + ‡ │ +15 │ │ X2 Y2 Z2 +16 │ │ X3 Y3 Z3 + ╰╴ └──────────┘ `Y` is a good letter too + +"#, ); } @@ -1036,6 +2190,7 @@ fn foo() { label: "`Z` is a good letter too", }, ], + vec![], r#" error: foo --> test.rs:3:6 @@ -1058,6 +2213,28 @@ error: foo | |________^ `Y` is a good letter "#, + r#" +error: foo + ╭▸ test.rs:3:6 + │ +3 │ X0 Y0 Z0 + │ ┏━━━━━━━┛ +4 │ ┃ 1 +5 │ ┃ 2 +6 │ ┃ 3 +7 │ ┃ X1 Y1 Z1 + │ ┃┌─────────┘ +8 │ ┃│ 4 +9 │ ┃│ 5 +10 │ ┃│ 6 +11 │ ┃│ X2 Y2 Z2 + │ ┃└──────────┘ `Z` is a good letter too + ‡ ┃ +15 │ ┃ 10 +16 │ ┃ X3 Y3 Z3 + ╰╴┗━━━━━━━━┛ `Y` is a good letter + +"#, ); } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index d733e32f209..352152a1bd4 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1714,6 +1714,9 @@ pub fn parse_json(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Json for sub_option in option.split(',') { match sub_option { "diagnostic-short" => json_rendered = HumanReadableErrorType::Short, + "diagnostic-unicode" => { + json_rendered = HumanReadableErrorType::Unicode; + } "diagnostic-rendered-ansi" => json_color = ColorConfig::Always, "artifacts" => json_artifact_notifications = true, "unused-externs" => json_unused_externs = JsonUnusedExterns::Loud, @@ -1760,6 +1763,9 @@ pub fn parse_error_format( ErrorOutputType::Json { pretty: true, json_rendered, color_config: json_color } } Some("short") => ErrorOutputType::HumanReadable(HumanReadableErrorType::Short, color), + Some("human-unicode") => { + ErrorOutputType::HumanReadable(HumanReadableErrorType::Unicode, color) + } Some(arg) => { early_dcx.abort_if_error_and_set_error_format(ErrorOutputType::HumanReadable( HumanReadableErrorType::Default, @@ -1831,6 +1837,9 @@ fn check_error_format_stability( { early_dcx.early_fatal("`--error-format=human-annotate-rs` is unstable"); } + if let ErrorOutputType::HumanReadable(HumanReadableErrorType::Unicode, _) = error_format { + early_dcx.early_fatal("`--error-format=human-unicode` is unstable"); + } } } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 1963cf4eb7c..cc16073db76 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -16,7 +16,9 @@ use rustc_data_structures::sync::{ }; use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitter; use rustc_errors::codes::*; -use rustc_errors::emitter::{DynEmitter, HumanEmitter, HumanReadableErrorType, stderr_destination}; +use rustc_errors::emitter::{ + DynEmitter, HumanEmitter, HumanReadableErrorType, OutputTheme, stderr_destination, +}; use rustc_errors::json::JsonEmitter; use rustc_errors::registry::Registry; use rustc_errors::{ @@ -965,6 +967,11 @@ fn default_emitter( .macro_backtrace(macro_backtrace) .track_diagnostics(track_diagnostics) .terminal_url(terminal_url) + .theme(if let HumanReadableErrorType::Unicode = kind { + OutputTheme::Unicode + } else { + OutputTheme::Ascii + }) .ignored_directories_in_source_blocks( sopts.unstable_opts.ignore_directory_in_diagnostics_source_blocks.clone(), ); @@ -1468,6 +1475,11 @@ fn mk_emitter(output: ErrorOutputType) -> Box<DynEmitter> { let short = kind.short(); Box::new( HumanEmitter::new(stderr_destination(color_config), fallback_bundle) + .theme(if let HumanReadableErrorType::Unicode = kind { + OutputTheme::Unicode + } else { + OutputTheme::Ascii + }) .short_message(short), ) } |
