Skip to content

Commit 4e15201

Browse files
authored
perf: find tab groups in doc comments without allocating (#17410)
`get_chunks_of_tabs` collected the char indices of every doc comment into a vector and inspected them pairwise, allocating for every doc comment in the crate. A tab is a single byte in UTF-8 that cannot appear inside the encoding of any other character, so the groups can be found on the raw bytes with a fast single-byte search instead. | crate | change | |---|---| | syn 2.0.71 | -0.20% | | serde 1.0.204 | -0.08% | | wasmi 0.35.0 | -0.11% | | regex 1.10.5 | -1.61% | | ryu 1.0.18 | -0.12% | changelog: none
2 parents ef124f6 + f37b6db commit 4e15201

2 files changed

Lines changed: 34 additions & 58 deletions

File tree

clippy_lints/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ clippy_config = { path = "../clippy_config" }
1515
clippy_utils = { path = "../clippy_utils" }
1616
declare_clippy_lint = { path = "../declare_clippy_lint" }
1717
itertools = "0.15"
18+
memchr = "2"
1819
quine-mc_cluskey = "0.2"
1920
regex-syntax = "0.8"
2021
serde = { version = "1.0", features = ["derive"] }

clippy_lints/src/tabs_in_doc_comments.rs

Lines changed: 33 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::iter;
2+
13
use clippy_utils::diagnostics::span_lint_and_sugg;
24
use rustc_ast::ast;
35
use rustc_errors::Applicability;
@@ -94,57 +96,30 @@ impl EarlyLintPass for TabsInDocComments {
9496
///
9597
/// scans the string for groups of tabs and returns the start(inclusive) and end positions
9698
/// (exclusive) of all groups
97-
/// e.g. "sd\tasd\t\taa" will be converted to [(2, 3), (6, 8)] as
99+
/// e.g. "sd\tasd\t\taa" will yield [(2, 3), (6, 8)] as
98100
/// 012 3456 7 89
99101
/// ^-^ ^---^
100-
fn get_chunks_of_tabs(the_str: &str) -> Vec<(u32, u32)> {
102+
fn get_chunks_of_tabs(the_str: &str) -> impl Iterator<Item = (u32, u32)> {
101103
let line_length_way_to_long = "doc comment longer than 2^32 chars";
102-
let mut spans: Vec<(u32, u32)> = vec![];
103-
let mut current_start: u32 = 0;
104-
105-
// tracker to decide if the last group of tabs is not closed by a non-tab character
106-
let mut is_active = false;
107-
108-
// Note that we specifically need the char _byte_ indices here, not the positional indexes
109-
// within the char array to deal with multi-byte characters properly. `char_indices` does
110-
// exactly that. It provides an iterator over tuples of the form `(byte position, char)`.
111-
let char_indices: Vec<_> = the_str.char_indices().collect();
112-
113-
if let [(_, '\t')] = char_indices.as_slice() {
114-
return vec![(0, 1)];
115-
}
116-
117-
for entry in char_indices.windows(2) {
118-
match entry {
119-
[(_, '\t'), (_, '\t')] => {
120-
// either string starts with double tab, then we have to set it active,
121-
// otherwise is_active is true anyway
122-
is_active = true;
123-
},
124-
[(_, _), (index_b, '\t')] => {
125-
// as ['\t', '\t'] is excluded, this has to be a start of a tab group,
126-
// set indices accordingly
127-
is_active = true;
128-
current_start = u32::try_from(*index_b).unwrap();
129-
},
130-
[(_, '\t'), (index_b, _)] => {
131-
// this now has to be an end of the group, hence we have to push a new tuple
132-
is_active = false;
133-
spans.push((current_start, u32::try_from(*index_b).unwrap()));
134-
},
135-
_ => {},
104+
let mut haystack = the_str.as_bytes();
105+
let mut offset = 0;
106+
107+
iter::from_fn(move || {
108+
if let Some(i) = memchr::memchr(b'\t', haystack) {
109+
let len = 1 + haystack[i + 1..].iter().take_while(|&&x| x == b'\t').count();
110+
let start = offset + i;
111+
let end = start + len;
112+
haystack = &haystack[i + len..];
113+
offset = end;
114+
Some((
115+
u32::try_from(start).expect(line_length_way_to_long),
116+
u32::try_from(end).expect(line_length_way_to_long),
117+
))
118+
} else {
119+
haystack = &[];
120+
None
136121
}
137-
}
138-
139-
// only possible when tabs are at the end, insert last group
140-
if is_active {
141-
spans.push((
142-
current_start,
143-
u32::try_from(char_indices.last().unwrap().0 + 1).expect(line_length_way_to_long),
144-
));
145-
}
146-
147-
spans
122+
})
148123
}
149124

150125
#[cfg(test)]
@@ -153,77 +128,77 @@ mod tests_for_get_chunks_of_tabs {
153128

154129
#[test]
155130
fn test_unicode_han_string() {
156-
let res = get_chunks_of_tabs(" \u{4f4d}\t");
131+
let res: Vec<_> = get_chunks_of_tabs(" \u{4f4d}\t").collect();
157132

158133
assert_eq!(res, vec![(4, 5)]);
159134
}
160135

161136
#[test]
162137
fn test_empty_string() {
163-
let res = get_chunks_of_tabs("");
138+
let res: Vec<_> = get_chunks_of_tabs("").collect();
164139

165140
assert_eq!(res, vec![]);
166141
}
167142

168143
#[test]
169144
fn test_simple() {
170-
let res = get_chunks_of_tabs("sd\t\t\taa");
145+
let res: Vec<_> = get_chunks_of_tabs("sd\t\t\taa").collect();
171146

172147
assert_eq!(res, vec![(2, 5)]);
173148
}
174149

175150
#[test]
176151
fn test_only_t() {
177-
let res = get_chunks_of_tabs("\t\t");
152+
let res: Vec<_> = get_chunks_of_tabs("\t\t").collect();
178153

179154
assert_eq!(res, vec![(0, 2)]);
180155
}
181156

182157
#[test]
183158
fn test_only_one_t() {
184-
let res = get_chunks_of_tabs("\t");
159+
let res: Vec<_> = get_chunks_of_tabs("\t").collect();
185160

186161
assert_eq!(res, vec![(0, 1)]);
187162
}
188163

189164
#[test]
190165
fn test_double() {
191-
let res = get_chunks_of_tabs("sd\tasd\t\taa");
166+
let res: Vec<_> = get_chunks_of_tabs("sd\tasd\t\taa").collect();
192167

193168
assert_eq!(res, vec![(2, 3), (6, 8)]);
194169
}
195170

196171
#[test]
197172
fn test_start() {
198-
let res = get_chunks_of_tabs("\t\taa");
173+
let res: Vec<_> = get_chunks_of_tabs("\t\taa").collect();
199174

200175
assert_eq!(res, vec![(0, 2)]);
201176
}
202177

203178
#[test]
204179
fn test_end() {
205-
let res = get_chunks_of_tabs("aa\t\t");
180+
let res: Vec<_> = get_chunks_of_tabs("aa\t\t").collect();
206181

207182
assert_eq!(res, vec![(2, 4)]);
208183
}
209184

210185
#[test]
211186
fn test_start_single() {
212-
let res = get_chunks_of_tabs("\taa");
187+
let res: Vec<_> = get_chunks_of_tabs("\taa").collect();
213188

214189
assert_eq!(res, vec![(0, 1)]);
215190
}
216191

217192
#[test]
218193
fn test_end_single() {
219-
let res = get_chunks_of_tabs("aa\t");
194+
let res: Vec<_> = get_chunks_of_tabs("aa\t").collect();
220195

221196
assert_eq!(res, vec![(2, 3)]);
222197
}
223198

224199
#[test]
225200
fn test_no_tabs() {
226-
let res = get_chunks_of_tabs("dsfs");
201+
let res: Vec<_> = get_chunks_of_tabs("dsfs").collect();
227202

228203
assert_eq!(res, vec![]);
229204
}

0 commit comments

Comments
 (0)