Skip to content

Commit bae625f

Browse files
committed
clippy_dev: Allocate onto an arena when parsing.
1 parent 7579e71 commit bae625f

File tree

5 files changed

+200
-122
lines changed

5 files changed

+200
-122
lines changed

clippy_dev/src/deprecate_lint.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ pub fn deprecate<'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'cx str,
2323
return;
2424
};
2525

26-
let prefixed_name = String::from_iter(["clippy::", name]);
27-
match deprecated_lints.binary_search_by(|x| x.name.cmp(&prefixed_name)) {
26+
let prefixed_name = cx.str_buf.with(|buf| {
27+
buf.extend(["clippy::", name]);
28+
cx.arena.alloc_str(buf)
29+
});
30+
match deprecated_lints.binary_search_by(|x| x.name.cmp(prefixed_name)) {
2831
Ok(_) => {
2932
println!("`{name}` is already deprecated");
3033
return;
@@ -33,8 +36,8 @@ pub fn deprecate<'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'cx str,
3336
idx,
3437
DeprecatedLint {
3538
name: prefixed_name,
36-
reason: reason.into(),
37-
version: clippy_version.rust_display().to_string(),
39+
reason,
40+
version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()),
3841
},
3942
),
4043
}
@@ -58,8 +61,8 @@ pub fn deprecate<'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'cx str,
5861
}
5962
}
6063

61-
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint>) -> io::Result<bool> {
62-
fn remove_lint(name: &str, lints: &mut Vec<Lint>) {
64+
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint<'_>>) -> io::Result<bool> {
65+
fn remove_lint(name: &str, lints: &mut Vec<Lint<'_>>) {
6366
lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos));
6467
}
6568

clippy_dev/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
new_range_api,
66
os_str_slice,
77
os_string_truncate,
8+
pattern,
89
rustc_private,
910
slice_split_once
1011
)]
@@ -17,6 +18,7 @@
1718
)]
1819
#![allow(clippy::missing_panics_doc)]
1920

21+
extern crate rustc_arena;
2022
#[expect(unused_extern_crates, reason = "required to link to rustc crates")]
2123
extern crate rustc_driver;
2224
extern crate rustc_lexer;

clippy_dev/src/parse.rs

Lines changed: 160 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,109 @@ pub mod cursor;
22

33
use self::cursor::{Capture, Cursor};
44
use crate::utils::{ErrAction, File, Scoped, expect_action, walk_dir_no_dot_or_target};
5+
use core::fmt::{Display, Write as _};
56
use core::range::Range;
7+
use rustc_arena::DroplessArena;
68
use std::fs;
79
use std::path::{self, Path, PathBuf};
10+
use std::str::pattern::Pattern;
811

9-
pub struct ParseCxImpl;
10-
pub type ParseCx<'cx> = &'cx mut ParseCxImpl;
12+
pub struct ParseCxImpl<'cx> {
13+
pub arena: &'cx DroplessArena,
14+
pub str_buf: StrBuf,
15+
}
16+
pub type ParseCx<'cx> = &'cx mut ParseCxImpl<'cx>;
1117

1218
/// Calls the given function inside a newly created parsing context.
13-
pub fn new_parse_cx<'env, T>(f: impl for<'cx> FnOnce(&'cx mut Scoped<'cx, 'env, ParseCxImpl>) -> T) -> T {
14-
f(&mut Scoped::new(ParseCxImpl))
19+
pub fn new_parse_cx<'env, T>(f: impl for<'cx> FnOnce(&'cx mut Scoped<'cx, 'env, ParseCxImpl<'cx>>) -> T) -> T {
20+
let arena = DroplessArena::default();
21+
f(&mut Scoped::new(ParseCxImpl {
22+
arena: &arena,
23+
str_buf: StrBuf::with_capacity(128),
24+
}))
1525
}
1626

17-
pub struct Lint {
18-
pub name: String,
19-
pub group: String,
20-
pub module: String,
27+
/// A string used as a temporary buffer used to avoid allocating for short lived strings.
28+
pub struct StrBuf(String);
29+
impl StrBuf {
30+
/// Creates a new buffer with the specified initial capacity.
31+
pub fn with_capacity(cap: usize) -> Self {
32+
Self(String::with_capacity(cap))
33+
}
34+
35+
/// Allocates the result of formatting the given value onto the arena.
36+
pub fn alloc_display<'cx>(&mut self, arena: &'cx DroplessArena, value: impl Display) -> &'cx str {
37+
self.0.clear();
38+
write!(self.0, "{value}").expect("`Display` impl returned an error");
39+
arena.alloc_str(&self.0)
40+
}
41+
42+
/// Allocates the string onto the arena with all ascii characters converted to
43+
/// lowercase.
44+
pub fn alloc_ascii_lower<'cx>(&mut self, arena: &'cx DroplessArena, s: &str) -> &'cx str {
45+
self.0.clear();
46+
self.0.push_str(s);
47+
self.0.make_ascii_lowercase();
48+
arena.alloc_str(&self.0)
49+
}
50+
51+
/// Allocates the result of replacing all instances the pattern with the given string
52+
/// onto the arena.
53+
pub fn alloc_replaced<'cx>(
54+
&mut self,
55+
arena: &'cx DroplessArena,
56+
s: &str,
57+
pat: impl Pattern,
58+
replacement: &str,
59+
) -> &'cx str {
60+
let mut parts = s.split(pat);
61+
let Some(first) = parts.next() else {
62+
return "";
63+
};
64+
self.0.clear();
65+
self.0.push_str(first);
66+
for part in parts {
67+
self.0.push_str(replacement);
68+
self.0.push_str(part);
69+
}
70+
if self.0.is_empty() {
71+
""
72+
} else {
73+
arena.alloc_str(&self.0)
74+
}
75+
}
76+
77+
/// Performs an operation with the freshly cleared buffer.
78+
pub fn with<T>(&mut self, f: impl FnOnce(&mut String) -> T) -> T {
79+
self.0.clear();
80+
f(&mut self.0)
81+
}
82+
}
83+
84+
pub struct Lint<'cx> {
85+
pub name: &'cx str,
86+
pub group: &'cx str,
87+
pub module: &'cx str,
2188
pub path: PathBuf,
2289
pub declaration_range: Range<usize>,
2390
}
2491

25-
pub struct DeprecatedLint {
26-
pub name: String,
27-
pub reason: String,
28-
pub version: String,
92+
pub struct DeprecatedLint<'cx> {
93+
pub name: &'cx str,
94+
pub reason: &'cx str,
95+
pub version: &'cx str,
2996
}
3097

31-
pub struct RenamedLint {
32-
pub old_name: String,
33-
pub new_name: String,
34-
pub version: String,
98+
pub struct RenamedLint<'cx> {
99+
pub old_name: &'cx str,
100+
pub new_name: &'cx str,
101+
pub version: &'cx str,
35102
}
36103

37-
impl ParseCxImpl {
104+
impl<'cx> ParseCxImpl<'cx> {
38105
/// Finds all lint declarations (`declare_clippy_lint!`)
39106
#[must_use]
40-
pub fn find_lint_decls(&mut self) -> Vec<Lint> {
107+
pub fn find_lint_decls(&mut self) -> Vec<Lint<'cx>> {
41108
let mut lints = Vec::with_capacity(1000);
42109
let mut contents = String::new();
43110
for e in expect_action(fs::read_dir("."), ErrAction::Read, ".") {
@@ -63,29 +130,59 @@ impl ParseCxImpl {
63130
&& let Some(path) = path.get(crate_path.len() + 1..)
64131
{
65132
let module = if path == "lib" {
66-
String::new()
133+
""
67134
} else {
68135
let path = path
69136
.strip_suffix("mod")
70137
.and_then(|x| x.strip_suffix(path::MAIN_SEPARATOR))
71138
.unwrap_or(path);
72-
path.replace(['/', '\\'], "::")
139+
self.str_buf
140+
.alloc_replaced(self.arena, path, path::MAIN_SEPARATOR, "::")
73141
};
74-
parse_clippy_lint_decls(
142+
self.parse_clippy_lint_decls(
75143
e.path(),
76144
File::open_read_to_cleared_string(e.path(), &mut contents),
77-
&module,
145+
module,
78146
&mut lints,
79147
);
80148
}
81149
}
82150
}
83-
lints.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
151+
lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name));
84152
lints
85153
}
86154

155+
/// Parse a source file looking for `declare_clippy_lint` macro invocations.
156+
fn parse_clippy_lint_decls(&mut self, path: &Path, contents: &str, module: &'cx str, lints: &mut Vec<Lint<'cx>>) {
157+
#[allow(clippy::enum_glob_use)]
158+
use cursor::Pat::*;
159+
#[rustfmt::skip]
160+
static DECL_TOKENS: &[cursor::Pat<'_>] = &[
161+
// !{ /// docs
162+
Bang, OpenBrace, AnyComment,
163+
// #[clippy::version = "version"]
164+
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket,
165+
// pub NAME, GROUP,
166+
Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma,
167+
];
168+
169+
let mut cursor = Cursor::new(contents);
170+
let mut captures = [Capture::EMPTY; 2];
171+
while let Some(start) = cursor.find_ident("declare_clippy_lint") {
172+
if cursor.match_all(DECL_TOKENS, &mut captures) && cursor.find_pat(CloseBrace) {
173+
lints.push(Lint {
174+
name: self.str_buf.alloc_ascii_lower(self.arena, cursor.get_text(captures[0])),
175+
group: self.arena.alloc_str(cursor.get_text(captures[1])),
176+
module,
177+
path: path.into(),
178+
declaration_range: start as usize..cursor.pos() as usize,
179+
});
180+
}
181+
}
182+
}
183+
87184
#[must_use]
88-
pub fn read_deprecated_lints(&mut self) -> (Vec<DeprecatedLint>, Vec<RenamedLint>) {
185+
pub fn read_deprecated_lints(&mut self) -> (Vec<DeprecatedLint<'cx>>, Vec<RenamedLint<'cx>>) {
89186
#[allow(clippy::enum_glob_use)]
90187
use cursor::Pat::*;
91188
#[rustfmt::skip]
@@ -124,9 +221,9 @@ impl ParseCxImpl {
124221
if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(DEPRECATED_TOKENS, &mut []) {
125222
while cursor.match_all(DECL_TOKENS, &mut captures) {
126223
deprecated.push(DeprecatedLint {
127-
name: parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])),
128-
reason: parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])),
129-
version: parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])),
224+
name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])),
225+
reason: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])),
226+
version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])),
130227
});
131228
}
132229
} else {
@@ -136,81 +233,53 @@ impl ParseCxImpl {
136233
if cursor.find_ident("declare_with_version").is_some() && cursor.match_all(RENAMED_TOKENS, &mut []) {
137234
while cursor.match_all(DECL_TOKENS, &mut captures) {
138235
renamed.push(RenamedLint {
139-
old_name: parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])),
140-
new_name: parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])),
141-
version: parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])),
236+
old_name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[1])),
237+
new_name: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[2])),
238+
version: self.parse_str_single_line(path.as_ref(), cursor.get_text(captures[0])),
142239
});
143240
}
144241
} else {
145242
panic!("error reading renamed lints");
146243
}
147244

148-
deprecated.sort_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
149-
renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(&rhs.old_name));
245+
deprecated.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name));
246+
renamed.sort_by(|lhs, rhs| lhs.old_name.cmp(rhs.old_name));
150247
(deprecated, renamed)
151248
}
152-
}
153249

154-
/// Parse a source file looking for `declare_clippy_lint` macro invocations.
155-
fn parse_clippy_lint_decls(path: &Path, contents: &str, module: &str, lints: &mut Vec<Lint>) {
156-
#[allow(clippy::enum_glob_use)]
157-
use cursor::Pat::*;
158-
#[rustfmt::skip]
159-
static DECL_TOKENS: &[cursor::Pat<'_>] = &[
160-
// !{ /// docs
161-
Bang, OpenBrace, AnyComment,
162-
// #[clippy::version = "version"]
163-
Pound, OpenBracket, Ident("clippy"), DoubleColon, Ident("version"), Eq, LitStr, CloseBracket,
164-
// pub NAME, GROUP,
165-
Ident("pub"), CaptureIdent, Comma, AnyComment, CaptureIdent, Comma,
166-
];
167-
168-
let mut cursor = Cursor::new(contents);
169-
let mut captures = [Capture::EMPTY; 2];
170-
while let Some(start) = cursor.find_ident("declare_clippy_lint") {
171-
if cursor.match_all(DECL_TOKENS, &mut captures) && cursor.find_pat(CloseBrace) {
172-
lints.push(Lint {
173-
name: cursor.get_text(captures[0]).to_lowercase(),
174-
group: cursor.get_text(captures[1]).into(),
175-
module: module.into(),
176-
path: path.into(),
177-
declaration_range: start as usize..cursor.pos() as usize,
178-
});
250+
/// Removes the line splices and surrounding quotes from a string literal
251+
fn parse_str_lit(&mut self, s: &str) -> &'cx str {
252+
let (s, is_raw) = if let Some(s) = s.strip_prefix("r") {
253+
(s.trim_matches('#'), true)
254+
} else {
255+
(s, false)
256+
};
257+
let s = s
258+
.strip_prefix('"')
259+
.and_then(|s| s.strip_suffix('"'))
260+
.unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
261+
262+
if is_raw {
263+
if s.is_empty() { "" } else { self.arena.alloc_str(s) }
264+
} else {
265+
self.str_buf.with(|buf| {
266+
rustc_literal_escaper::unescape_str(s, &mut |_, ch| {
267+
if let Ok(ch) = ch {
268+
buf.push(ch);
269+
}
270+
});
271+
if buf.is_empty() { "" } else { self.arena.alloc_str(buf) }
272+
})
179273
}
180274
}
181-
}
182275

183-
/// Removes the line splices and surrounding quotes from a string literal
184-
fn parse_str_lit(s: &str) -> String {
185-
let (s, is_raw) = if let Some(s) = s.strip_prefix("r") {
186-
(s.trim_matches('#'), true)
187-
} else {
188-
(s, false)
189-
};
190-
let s = s
191-
.strip_prefix('"')
192-
.and_then(|s| s.strip_suffix('"'))
193-
.unwrap_or_else(|| panic!("expected quoted string, found `{s}`"));
194-
195-
if is_raw {
196-
s.into()
197-
} else {
198-
let mut res = String::with_capacity(s.len());
199-
rustc_literal_escaper::unescape_str(s, &mut |_, ch| {
200-
if let Ok(ch) = ch {
201-
res.push(ch);
202-
}
203-
});
204-
res
276+
fn parse_str_single_line(&mut self, path: &Path, s: &str) -> &'cx str {
277+
let value = self.parse_str_lit(s);
278+
assert!(
279+
!value.contains('\n'),
280+
"error parsing `{}`: `{s}` should be a single line string",
281+
path.display(),
282+
);
283+
value
205284
}
206285
}
207-
208-
fn parse_str_single_line(path: &Path, s: &str) -> String {
209-
let value = parse_str_lit(s);
210-
assert!(
211-
!value.contains('\n'),
212-
"error parsing `{}`: `{s}` should be a single line string",
213-
path.display(),
214-
);
215-
value
216-
}

0 commit comments

Comments
 (0)