Change output normalization logic to be linear against size of output

Scan strings to be normalized for printing in a linear scan and collect
the resulting `String` only once.

Use a binary search when looking for chars to be replaced, instead of a
`HashMap::get`.
This commit is contained in:
Esteban Küber 2024-07-25 18:38:28 +00:00
parent 4db3d12e6f
commit 6d9ee6ee26
3 changed files with 55 additions and 47 deletions

View File

@ -3867,6 +3867,7 @@ version = "0.0.0"
dependencies = [
"annotate-snippets 0.10.2",
"derive_setters",
"either",
"rustc_ast",
"rustc_ast_pretty",
"rustc_data_structures",

View File

@ -7,6 +7,7 @@ edition = "2021"
# tidy-alphabetical-start
annotate-snippets = "0.10"
derive_setters = "0.1.6"
either = "1.5.0"
rustc_ast = { path = "../rustc_ast" }
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
rustc_data_structures = { path = "../rustc_data_structures" }

View File

@ -16,6 +16,7 @@
use std::path::Path;
use derive_setters::Setters;
use either::Either;
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
use rustc_data_structures::sync::{DynSend, IntoDynSyncSend, Lrc};
use rustc_error_messages::{FluentArgs, SpanLabel};
@ -2559,60 +2560,65 @@ fn num_decimal_digits(num: usize) -> usize {
// We replace some characters so the CLI output is always consistent and underlines aligned.
// Keep the following list in sync with `rustc_span::char_width`.
// ATTENTION: keep lexicografically sorted so that the binary search will work
const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[
('\t', " "), // We do our own tab replacement
('\u{200D}', ""), // Replace ZWJ with nothing for consistent terminal output of grapheme clusters.
('\u{202A}', "<EFBFBD>"), // The following unicode text flow control characters are inconsistently
('\u{202B}', "<EFBFBD>"), // supported across CLIs and can cause confusion due to the bytes on disk
('\u{202D}', "<EFBFBD>"), // not corresponding to the visible source code, so we replace them always.
('\u{202E}', "<EFBFBD>"),
('\u{2066}', "<EFBFBD>"),
('\u{2067}', "<EFBFBD>"),
('\u{2068}', "<EFBFBD>"),
('\u{202C}', "<EFBFBD>"),
('\u{2069}', "<EFBFBD>"),
// In terminals without Unicode support the following will be garbled, but in *all* terminals
// the underlying codepoint will be as well. We could gate this replacement behind a "unicode
// support" gate.
('\u{0000}', ""),
('\u{0001}', ""),
('\u{0002}', ""),
('\u{0003}', ""),
('\u{0004}', ""),
('\u{0005}', ""),
('\u{0006}', ""),
('\u{0007}', ""),
('\u{0008}', ""),
('\u{000B}', ""),
('\u{000C}', ""),
('\u{000D}', ""),
('\u{000E}', ""),
('\u{000F}', ""),
('\u{0010}', ""),
('\u{0011}', ""),
('\u{0012}', ""),
('\u{0013}', ""),
('\u{0014}', ""),
('\u{0015}', ""),
('\u{0016}', ""),
('\u{0017}', ""),
('\u{0018}', ""),
('\u{0019}', ""),
('\u{001A}', ""),
('\u{001B}', ""),
('\u{001C}', ""),
('\u{001D}', ""),
('\u{001E}', ""),
('\u{001F}', ""),
('\u{007F}', ""),
('\0', ""),
('\u{1}', ""),
('\u{2}', ""),
('\u{3}', ""),
('\u{4}', ""),
('\u{5}', ""),
('\u{6}', ""),
('\u{7}', ""),
('\u{8}', ""),
('\t', " "), // We do our own tab replacement
('\u{b}', ""),
('\u{c}', ""),
('\r', ""),
('\u{e}', ""),
('\u{f}', ""),
('\u{10}', ""),
('\u{11}', ""),
('\u{12}', ""),
('\u{13}', ""),
('\u{14}', ""),
('\u{15}', ""),
('\u{16}', ""),
('\u{17}', ""),
('\u{18}', ""),
('\u{19}', ""),
('\u{1a}', ""),
('\u{1b}', ""),
('\u{1c}', ""),
('\u{1d}', ""),
('\u{1e}', ""),
('\u{1f}', ""),
('\u{7f}', ""),
('\u{200d}', ""), // Replace ZWJ for consistent terminal output of grapheme clusters.
('\u{202a}', "<EFBFBD>"), // The following unicode text flow control characters are inconsistently
('\u{202b}', "<EFBFBD>"), // supported across CLIs and can cause confusion due to the bytes on disk
('\u{202c}', "<EFBFBD>"), // not corresponding to the visible source code, so we replace them always.
('\u{202d}', "<EFBFBD>"),
('\u{202e}', "<EFBFBD>"),
('\u{2066}', "<EFBFBD>"),
('\u{2067}', "<EFBFBD>"),
('\u{2068}', "<EFBFBD>"),
('\u{2069}', "<EFBFBD>"),
];
fn normalize_whitespace(str: &str) -> String {
let mut s = str.to_string();
for (c, replacement) in OUTPUT_REPLACEMENTS {
s = s.replace(*c, replacement);
}
s
// Scan the input string for a character in the ordered table above. If it's present, replace
// it with it's alternative string (it can be more than 1 char!). Otherwise, retain the input
// char. At the end, allocate all chars into a string in one operation.
str.chars()
.flat_map(|c| match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) {
Ok(i) => Either::Left(OUTPUT_REPLACEMENTS[i].1.chars()),
_ => Either::Right([c].into_iter()),
})
.collect()
}
fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {